Spaces:
Runtime error
Runtime error
| /** | |
| * OmniRoute Copilot β Chat Engine | |
| * | |
| * Processes user messages, classifies intent, executes tools, | |
| * queries CodeGraph, invokes CLI commands, or responds with | |
| * knowledge from the system prompt. | |
| */ | |
| import { getCopilotSystemPrompt } from "./systemPrompt"; | |
| import { COPILOT_TOOLS, getCopilotTool, getCopilotToolDescriptions } from "./tools"; | |
| // ββ Types ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export interface CopilotMessage { | |
| role: "user" | "assistant" | "system"; | |
| content: string; | |
| } | |
| export interface CopilotRequest { | |
| messages: CopilotMessage[]; | |
| } | |
| export interface CopilotResponse { | |
| message: string; | |
| toolCalls?: Array<{ name: string; args: Record<string, unknown>; result: string }>; | |
| } | |
| // ββ Tool Lookup for Dynamic Dispatch ββββββββββββββββββββββββββββββββββββββββ | |
| const TOOL_NAMES = COPILOT_TOOLS.map((t) => t.name); | |
| // ββ Knowledge-based responses βββββββββββββββββββββββββββββββββββββββββββββββ | |
| function getKnowledgeResponse(query: string): string | null { | |
| const q = query.toLowerCase(); | |
| // Architecture questions | |
| if ( | |
| /architecture|arquitectura|pipeline/.test(q) || | |
| (q.includes("request") && (q.includes("flow") || q.includes("path"))) | |
| ) { | |
| return `## OmniRoute Architecture | |
| The request pipeline flows through: | |
| 1. **API Route** β CORS β Zod validation β Auth (optional) | |
| 2. **Guardrails** β Prompt injection guard, PII masking | |
| 3. **Pre-request Middleware Hooks** (NEW) β mutate routing decisions | |
| 4. **Task-aware routing / Combo resolution** β picks the target | |
| 5. **Cache check** β semantic/signature cache | |
| 6. **Rate limit check** | |
| 7. **Request translation** β OpenAI format β provider format | |
| 8. **Executor** β build URL + headers, fetch with retry | |
| 9. **Response translation** β provider format β client format | |
| 10. **SSE stream or JSON response** | |
| The data layer uses **SQLite** via 45+ domain modules in \`src/lib/db/\`. | |
| The streaming engine lives in \`open-sse/\` (handlers, executors, translator).`; | |
| } | |
| // Combo questions | |
| if (/combo|routing|strategy|estrategia/.test(q)) { | |
| return `## Combo Routing | |
| Combos chain multiple targets (provider+model) with a strategy: | |
| **14 strategies available:** | |
| - \`priority\`: Try targets in order, fall through on failure | |
| - \`weighted\`: Distribute load by weight | |
| - \`round-robin\`: Cycle through targets | |
| - \`auto\`: Intelligent selection (rules, cost, latency, eco, fast, LKGP) | |
| - \`fill-first\`: Fill capacity of first target | |
| - \`cost-optimized\`: Minimize cost | |
| - \`context-optimized\`: Maximize context window | |
| - \`p2c\`: Power of Two Choices | |
| - \`random\` / \`strict-random\`: Random selection | |
| - \`least-used\`: Load balance by usage | |
| - \`reset-aware\`: Account for API reset windows | |
| - \`context-relay\`: Relay context between models | |
| - \`lkgp\`: Last Known Good Provider | |
| Use \`createCombo\` tool or \`runOmniRouteCli\` to create them.`; | |
| } | |
| // Provider questions | |
| if (/provider|proveedor/.test(q)) { | |
| return `## Providers (212+) | |
| OmniRoute supports 212+ providers across categories: | |
| - **Free**: Qoder AI, Qwen Code, Gemini CLI, Kiro AI | |
| - **OAuth** (14): Claude Code, Antigravity, Codex, GitHub Copilot, Cursor, Kimi Coding, Windsurf, etc. | |
| - **API Key** (120+): OpenAI, Anthropic, Gemini, DeepSeek, Groq, xAI, Mistral, etc. | |
| - **Self-Hosted** (8+): LM Studio, vLLM, Ollama, Triton, etc. | |
| - **Custom**: \`openai-compatible-*\` and \`anthropic-compatible-*\` | |
| Use \`listProviders\` to see your configured ones.`; | |
| } | |
| // Debugging/troubleshooting | |
| if (/debug|error|fail|fallo|problema|issue|crash|log/.test(q)) { | |
| return `## Troubleshooting | |
| **Common issues:** | |
| 1. **Provider returns errors**: Check credentials with the health API (\`/api/monitoring/health\`) | |
| 2. **Combo targeting wrong provider**: Check strategy and targets with \`listCombos\` | |
| 3. **Rate limiting**: Check circuit breaker state via health API | |
| 4. **Auth errors**: Verify API key scopes with \`listApiKeys\` | |
| 5. **DeepSeek 400 errors**: Likely \`reasoning_content\` stripping issue β fixed in schemaCoercion.ts | |
| **Use CodeGraph** to investigate specific code paths with \`searchCodeGraph\`.`; | |
| } | |
| // CodeGraph questions | |
| if (/codigo|cΓ³digo|codebase|cΓ³mo funciona|how does|where is|dΓ³nde estΓ‘/.test(q)) { | |
| return `## Codebase Investigation | |
| I can use CodeGraph to explore the OmniRoute codebase. Just ask me: | |
| - "Busca la funciΓ³n handleChatCore" | |
| - "QuiΓ©n llama a sanitizeMessage?" | |
| - "QuΓ© funciones hay en combo.ts?" | |
| - "Dame contexto del archivo chatCore.ts" | |
| - "Lista los archivos TypeScript indexados" | |
| Use these search terms naturally and I'll query the CodeGraph index.`; | |
| } | |
| return null; | |
| } | |
| // ββ Intent Classification ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const INTENT_PATTERNS: Array<{ | |
| pattern: RegExp; | |
| tool: string; | |
| extractArgs: (match: RegExpMatchArray) => Record<string, unknown>; | |
| }> = [ | |
| // ββ Provider tools ββ | |
| { | |
| pattern: /list.*(?:providers?|connections?|accounts)/i, | |
| tool: "listProviders", | |
| extractArgs: () => ({}), | |
| }, | |
| { | |
| pattern: /list.*(oauth|api.?key|free|local).*provider/i, | |
| tool: "listProviders", | |
| extractArgs: (m) => ({ type: (m[1] || "").toLowerCase().replace(/[^a-z]/g, "") }), | |
| }, | |
| // ββ Combo tools ββ | |
| { pattern: /list.*(?:combo|route)/i, tool: "listCombos", extractArgs: () => ({}) }, | |
| { pattern: /show.*(?:combo|route)/i, tool: "listCombos", extractArgs: () => ({}) }, | |
| { pattern: /qu[eΓ©].*combo/i, tool: "listCombos", extractArgs: () => ({}) }, | |
| // Create combo | |
| { | |
| pattern: /crea(?:te|r?)\s*(?:un\s*)?combo/i, | |
| tool: "createCombo", | |
| extractArgs: () => ({}), | |
| }, | |
| // ββ API Key tools ββ | |
| { pattern: /list.*(?:api.?key|key)/i, tool: "listApiKeys", extractArgs: () => ({}) }, | |
| { pattern: /show.*(?:api.?key|key)/i, tool: "listApiKeys", extractArgs: () => ({}) }, | |
| { | |
| pattern: /crea(?:te|r?)\s*(?:un\s*)?(?:api.?)?key/i, | |
| tool: "createApiKey", | |
| extractArgs: () => ({}), | |
| }, | |
| { pattern: /revoke|revocar|borrar.*key/i, tool: "revokeApiKey", extractArgs: () => ({}) }, | |
| // ββ Key Group tools ββ | |
| { pattern: /list.*(?:group|grupo)/i, tool: "listKeyGroups", extractArgs: () => ({}) }, | |
| { pattern: /show.*(?:group|grupo)/i, tool: "listKeyGroups", extractArgs: () => ({}) }, | |
| // ββ CodeGraph tools ββ | |
| // Search symbols | |
| { | |
| pattern: | |
| /(?:busca|search|find|dΓ³nde estΓ‘|where is)\s*(?:el\s*)?(?:sΓmbolo|symbol|function|funciΓ³n|class|clase)?\s*[`"']?([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)[`"']?/i, | |
| tool: "searchCodeGraph", | |
| extractArgs: (m) => ({ query: m[1] }), | |
| }, | |
| // Callers | |
| { | |
| pattern: | |
| /(?:qui[Γ©e]n|who|what)\s*(?:llama|call|usa|use|referenc).*[`"']?([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)[`"']?/i, | |
| tool: "findCallers", | |
| extractArgs: (m) => ({ symbol: m[1] }), | |
| }, | |
| // Callees | |
| { | |
| pattern: | |
| /(?:quΓ©|what|que)\s*(?:llama|call|usa)\s*[`"']?([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)[`"']?/i, | |
| tool: "findCallees", | |
| extractArgs: (m) => ({ symbol: m[1] }), | |
| }, | |
| // File context | |
| { | |
| pattern: | |
| /(?:contexto|context|sΓmbolos|symbols|funciones|functions)\s*(?:de|in|del|en)\s*[`"']?([a-zA-Z0-9_/.-]+(?:\.\w+)?)[`"']?/i, | |
| tool: "getFileContext", | |
| extractArgs: (m) => ({ filePath: m[1] }), | |
| }, | |
| // Files | |
| { | |
| pattern: /list.*(?:archivos|files|index)/i, | |
| tool: "listCodeGraphFiles", | |
| extractArgs: () => ({}), | |
| }, | |
| { pattern: /codegraph.*(?:stats|stat|status)/i, tool: "codeGraphStats", extractArgs: () => ({}) }, | |
| // ββ CLI executor ββ | |
| { | |
| pattern: /^(?:cli|terminal|ejecuta|run|exec)\s+(.+)/i, | |
| tool: "runOmniRouteCli", | |
| extractArgs: (m) => ({ command: m[1].trim() }), | |
| }, | |
| // ββ Health / status ββ | |
| { | |
| pattern: /^(?:health|status|salud|estado)$/i, | |
| tool: "runOmniRouteCli", | |
| extractArgs: () => ({ command: "health" }), | |
| }, | |
| // ββ Help ββ | |
| { | |
| pattern: /^(?:help|ayuda|que puedes hacer|quΓ© puedes hacer|\?)$/i, | |
| tool: "help", | |
| extractArgs: () => ({}), | |
| }, | |
| ]; | |
| function classifyIntent(text: string): { tool: string; args: Record<string, unknown> } | null { | |
| for (const intent of INTENT_PATTERNS) { | |
| const match = text.match(intent.pattern); | |
| if (match) { | |
| return { tool: intent.tool, args: intent.extractArgs(match) }; | |
| } | |
| } | |
| return null; | |
| } | |
| // ββ Help Response ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function getHelpResponse(): string { | |
| return `## OmniRoute Copilot β Comandos disponibles | |
| ### ConfiguraciΓ³n | |
| - "Lista los providers" β \`listProviders\` | |
| - "Lista mis combos" β \`listCombos\` | |
| - "Crea un combo..." β \`createCombo\` (te pedirΓ© detalles) | |
| - "Lista las API keys" β \`listApiKeys\` | |
| - "Crea una API key para desarrollo" β \`createApiKey\` | |
| - "Revoca la key abc123" β \`revokeApiKey\` | |
| - "Lista los grupos" β \`listKeyGroups\` | |
| ### CodeGraph (investigar el cΓ³digo) | |
| - "Busca la funciΓ³n handleChatCore" β \`searchCodeGraph\` | |
| - "QuiΓ©n llama a sanitizeMessage?" β \`findCallers\` | |
| - "QuΓ© funciones hay en combo.ts?" β \`getFileContext\` | |
| - "Lista los archivos indexados" β \`listCodeGraphFiles\` | |
| ### CLI | |
| - "CLI health" β ejecuta \`omniroute health\` | |
| - "CLI list-combos" β ejecuta \`omniroute list-combos\` | |
| - "CLI set-budget 10" β ejecuta \`omniroute set-budget 10\` | |
| ### Conocimiento | |
| - "CΓ³mo funciona OmniRoute?" β explica la arquitectura | |
| - "QuΓ© son los combos?" β explica routing | |
| - "CΓ³mo debuggeo un error?" β troubleshooting | |
| ### Tools disponibles:\n\n${getCopilotToolDescriptions()}`; | |
| } | |
| // ββ Chat Engine ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| export async function processCopilotChat(request: CopilotRequest): Promise<CopilotResponse> { | |
| const lastMessage = request.messages[request.messages.length - 1]; | |
| if (!lastMessage || lastMessage.role !== "user") { | |
| return { message: "No user message found." }; | |
| } | |
| const userText = lastMessage.content.trim(); | |
| if (!userText) { | |
| return { message: "Please provide a message." }; | |
| } | |
| // Classify intent | |
| const intent = classifyIntent(userText); | |
| if (!intent) { | |
| // No tool match β check knowledge base | |
| const knowledge = getKnowledgeResponse(userText); | |
| if (knowledge) { | |
| return { message: knowledge }; | |
| } | |
| // Fallback: respond with help | |
| return { | |
| message: `I understand you want help with OmniRoute.\n\n${getHelpResponse()}`, | |
| }; | |
| } | |
| // Handle help separately | |
| if (intent.tool === "help") { | |
| return { message: getHelpResponse() }; | |
| } | |
| // Handle tools that need more info from the user | |
| if (intent.tool === "createCombo" && !userText.includes("{") && !userText.includes("target")) { | |
| return { | |
| message: `Para crear un combo, necesito algunos detalles: | |
| 1. **Nombre** del combo (ej: "mi-combo-fallback") | |
| 2. **Estrategia** (priority, weighted, round-robin, cost-optimized, auto) | |
| 3. **Targets** β los proveedores/modelos en orden | |
| Puedes decirme algo como: | |
| > Crea un combo llamado "fallback-claude" con estrategia priority y targets: [{"provider":"claude-code","model":"claude-sonnet-4"},{"provider":"openai","model":"gpt-4o"}]`, | |
| }; | |
| } | |
| // Handle createApiKey β extract name from sentence | |
| if (intent.tool === "createApiKey") { | |
| const nameMatch = userText.match( | |
| /(?:llamad[oa]|named?|par[ae]?)\s*["'']?([a-zA-Z0-9_-]+)["'']?/i | |
| ); | |
| const name = nameMatch ? nameMatch[1] : "copilot-key"; | |
| const scopeMatch = userText.match(/(?:con\s*)?scope?s?\s*:?\s*["'']?([a-zA-Z,]+)["'']?/i); | |
| const scopes = scopeMatch ? scopeMatch[1] : undefined; | |
| const tool = getCopilotTool("createApiKey"); | |
| if (!tool) return { message: "Error: createApiKey tool not found." }; | |
| const result = await tool.handler({ | |
| name, | |
| machineId: "copilot", | |
| scopes, | |
| }); | |
| return { | |
| message: result, | |
| toolCalls: [{ name: "createApiKey", args: { name, scopes }, result }], | |
| }; | |
| } | |
| // Handle CLI executor β pass the full command | |
| if (intent.tool === "runOmniRouteCli") { | |
| const tool = getCopilotTool("runOmniRouteCli"); | |
| if (!tool) return { message: "Error: CLI executor not found." }; | |
| const result = await tool.handler(intent.args); | |
| return { | |
| message: result, | |
| toolCalls: [{ name: "runOmniRouteCli", args: intent.args, result }], | |
| }; | |
| } | |
| // For all other tools, dispatch directly | |
| const tool = getCopilotTool(intent.tool); | |
| if (!tool) | |
| return { | |
| message: `I don't have a tool for that yet. Try asking in a different way.\n\n${getHelpResponse()}`, | |
| }; | |
| const result = await tool.handler(intent.args); | |
| return { | |
| message: result, | |
| toolCalls: [{ name: intent.tool, args: intent.args, result }], | |
| }; | |
| } | |