titan-agent 5.4.0 → 5.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent.js +1 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/agentLoop.js +77 -12
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/agentWakeup.js +8 -3
- package/dist/agent/agentWakeup.js.map +1 -1
- package/dist/agent/commandPost.js +6 -1
- package/dist/agent/commandPost.js.map +1 -1
- package/dist/agent/heartbeatScheduler.js +36 -4
- package/dist/agent/heartbeatScheduler.js.map +1 -1
- package/dist/agent/toolRunner.js +30 -0
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/config/config.js +30 -8
- package/dist/config/config.js.map +1 -1
- package/dist/config/schema.js +10 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/eval/record.js +1 -1
- package/dist/eval/record.js.map +1 -1
- package/dist/gateway/server.js +26 -0
- package/dist/gateway/server.js.map +1 -1
- package/dist/mesh/transport.js +60 -8
- package/dist/mesh/transport.js.map +1 -1
- package/dist/providers/anthropic.js +3 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/google.js +94 -20
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/modelCapabilities.js +59 -0
- package/dist/providers/modelCapabilities.js.map +1 -0
- package/dist/providers/ollama.js +3 -2
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.js +4 -3
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openai_compat.js +3 -2
- package/dist/providers/openai_compat.js.map +1 -1
- package/dist/providers/router.js +63 -21
- package/dist/providers/router.js.map +1 -1
- package/dist/skills/registry.js +176 -163
- package/dist/skills/registry.js.map +1 -1
- package/dist/telemetry/activityLog.js +1 -1
- package/dist/telemetry/activityLog.js.map +1 -1
- package/dist/utils/constants.js +2 -2
- package/dist/utils/constants.js.map +1 -1
- package/docs/AGENT-HIERARCHY.md +154 -0
- package/docs/superpowers/plans/2026-04-29-titan-production-fix.md +241 -0
- package/package.json +2 -2
- package/scripts/start-workers.sh +39 -0
- package/scripts/task-feeder.ts +38 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/ollama.ts"],"sourcesContent":["/**\n * TITAN — Ollama Provider (Local LLMs)\n */\nimport {\n LLMProvider,\n type ChatOptions,\n type ChatResponse,\n type ChatStreamChunk,\n type ToolCall,\n} from './base.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { fetchWithRetry } from '../utils/helpers.js';\nimport { v4 as uuid } from 'uuid';\nimport * as fs from 'fs';\n\nconst COMPONENT = 'Ollama';\n\n/**\n * Per-model context window map for Ollama cloud models.\n * Auto-configures num_ctx to each model's actual maximum to prevent truncation.\n * Sources: Ollama Cloud model cards, March 2026.\n */\nconst CLOUD_MODEL_CTX: Record<string, number> = {\n // GLM-5.1 — 198K context (newest agentic flagship, SOTA SWE-Bench Pro)\n 'glm-5.1:cloud': 198656,\n // GLM-5 — 128K context\n 'glm-5:cloud': 131072,\n // Kimi K2.5 — 256K context (native multimodal agentic, agent swarm)\n 'kimi-k2.5:cloud': 262144,\n // Kimi K2.6 — 256K context (next-gen agentic, enhanced reasoning)\n 'kimi-k2.6:cloud': 262144,\n // Qwen3 Coder Next — 262K context (massive)\n 'qwen3-coder-next:cloud': 262144,\n // Qwen3.5 397B Cloud — 256K context (all variants support 256K)\n 'qwen3.5:397b-cloud': 262144,\n // DeepSeek V3.1 — 128K context\n 'deepseek-v3.1:671b-cloud': 131072,\n // DeepSeek V3.2 — 160K context (DSA long-context optimized)\n 'deepseek-v3.2:671b-cloud': 163840,\n 'deepseek-v3.2:cloud': 163840,\n // Devstral 2 — 128K context\n 'devstral-2:cloud': 131072,\n // Devstral Small 2 (local) — 32K\n 'devstral-small-2': 32768,\n 'devstral-small-2:latest': 32768,\n // Nemotron 3 Nano — 1M native, 32K practical for local\n 'nemotron-3-nano': 32768,\n 'nemotron-3-nano:latest': 32768,\n 'nemotron-3-nano:4b': 32768,\n 'nemotron-3-nano:30b': 32768,\n // Nemotron 3 Super — 256K context (MoE 120B/12B active)\n 'nemotron-3-super:cloud': 262144,\n // Gemini 3 Flash — 1M context\n 'gemini-3-flash-preview:latest': 1048576,\n // GPT OSS — 128K\n 'gpt-oss:120b-cloud': 131072,\n // MiniMax M2.7 — 200K context (Agent Teams, dynamic tool search)\n 'minimax-m2.7:cloud': 204800,\n // Gemma 4 — 256K context (native function calling)\n 'gemma4:cloud': 262144,\n // Qwen3.5 35B local — 32K\n 'qwen3.5:35b': 32768,\n};\n\n/**\n * Model capability profiles — controls how TITAN adapts to each model's strengths.\n * Instead of blanket rules for all models, each model gets tuned behavior.\n *\n * selfSelectsTools: Model picks tools well on its own — don't force tool_choice='required'\n * thinkingWithTools: Model benefits from thinking (<think> tags) during tool calling\n * needsSystemMerge: Model ignores standalone system messages — merge into first user msg\n * toolTemperature: Optimal temperature for tool-calling tasks (null = use caller's value or 0.5 default)\n * toolTopP: Optimal top_p for tool calling (null = omit)\n * toolTopK: Optimal top_k for tool calling (null = omit)\n */\ninterface ModelCapabilities {\n selfSelectsTools: boolean;\n thinkingWithTools: boolean;\n needsSystemMerge: boolean;\n toolTemperature: number | null;\n toolTopP: number | null;\n toolTopK: number | null;\n}\n\nconst DEFAULT_CAPABILITIES: ModelCapabilities = {\n selfSelectsTools: false,\n thinkingWithTools: false,\n needsSystemMerge: true, // Conservative default: merge for unknown models\n toolTemperature: 0.5,\n toolTopP: null,\n toolTopK: null,\n};\n\n/** Heuristic: infer capabilities from model name patterns when no hardcoded\n * entry exists. Most modern models (2024+) support native tool calling and\n * handle system prompts correctly. This prevents unknown models from being\n * crippled by overly conservative defaults. */\nfunction inferCapabilitiesFromName(modelName: string): Partial<ModelCapabilities> | undefined {\n const lower = modelName.toLowerCase();\n\n // Cloud-hosted models are almost always modern and capable\n if (lower.includes(':cloud') || lower.includes('-cloud')) {\n return { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 };\n }\n\n // Large local models (30B+) are typically capable\n const sizeMatch = lower.match(/(\\d+)b/);\n if (sizeMatch) {\n const size = parseInt(sizeMatch[1], 10);\n if (size >= 30) {\n return { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 0.5 };\n }\n }\n\n // Known-capable families by name pattern (even if not in hardcoded map)\n const capableFamilies = ['qwen', 'glm', 'deepseek', 'kimi', 'gemma', 'nemotron', 'devstral', 'gemini', 'mistral-large', 'llama3.3', 'llama4', 'phi4', 'command-r-plus'];\n for (const family of capableFamilies) {\n if (lower.includes(family)) {\n return { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 0.5 };\n }\n }\n\n // Truly unknown small local models — stay conservative\n return undefined;\n}\n\nconst MODEL_CAPABILITIES: Record<string, Partial<ModelCapabilities>> = {\n // ── Qwen family — excellent tool calling, uses thinking ──\n 'qwen3.5': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.7 },\n 'qwen3': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.7 },\n 'qwen3-coder-next': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.5 },\n\n // ── DeepSeek family — strong reasoning, good tool use ──\n 'deepseek-v3': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n 'deepseek-v3.1': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n 'deepseek-v3.2': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n\n // ── MiniMax M2.7 — XML tool format, needs special handling ──\n // Hunt Finding #05 (2026-04-14): flipped selfSelectsTools from true→false.\n // Confirmed by reproducing: a \"use shell to run uptime\" prompt returned\n // fabricated uptime text with no tool call. The model hallucinates instead\n // of calling tools when given the choice. Setting false forces the agent\n // loop's forceToolUse to fire `tool_choice: required`, which prevents this\n // class of hallucination at the API level.\n 'minimax-m2.7': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.8, toolTopP: 0.95, toolTopK: 40 },\n 'minimax-m2': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.8, toolTopP: 0.95 },\n\n // ── Gemma family — good tool use, no thinking ──\n 'gemma4': { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 1.0, toolTopP: 0.95, toolTopK: 64 },\n 'gemma-3': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.4 },\n\n // ── GLM family — GLM-5.1 is agentic flagship, SOTA SWE-Bench Pro, 198K ctx ──\n 'glm-5.1': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n 'glm-5': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.4 },\n 'glm-4.7': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.4 },\n\n // ── Nemotron — Super is 256K MoE optimized for collaborative agents ──\n 'nemotron-3-super': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: true, toolTemperature: 0.4 },\n 'nemotron-3-nano': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n\n // ── Kimi K2.5 — 256K, native agentic, agent swarm decomposition ──\n 'kimi-k2.5': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n 'kimi-k2.6': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n\n // ── Devstral — code-focused ──\n 'devstral-2': { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 0.4 },\n 'devstral-small-2': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n\n // ── Gemini — handles system messages well ──\n 'gemini-3-flash': { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 0.5 },\n\n // ── Llama/Mistral — weaker tool calling ──\n 'llama3.1': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n 'llama3.2': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n 'mistral': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n};\n\n/** Resolve capabilities for a model.\n *\n * Lookup order:\n * 1. Empirical probe result from capabilities registry (~/.titan/model-capabilities.json)\n * — This reflects ACTUAL behavior tested against the live model\n * 2. Hardcoded MODEL_CAPABILITIES map (this file) — matched by longest prefix\n * 3. DEFAULT_CAPABILITIES — conservative fallback for unknown models\n */\nfunction getModelCapabilities(modelName: string): ModelCapabilities {\n // Step 1: Check empirical probe registry (preferred)\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { getProbeResult, isProbeStale } = require('../agent/capabilitiesRegistry.js') as typeof import('../agent/capabilitiesRegistry.js');\n const probe = getProbeResult(modelName) || getProbeResult(`ollama/${modelName}`);\n if (probe && !isProbeStale(probe)) {\n // Convert probe result to capability flags\n return {\n ...DEFAULT_CAPABILITIES,\n selfSelectsTools: probe.nativeToolCalls,\n thinkingWithTools: probe.hasThinkingMode && !probe.needsExplicitThinkFalse,\n needsSystemMerge: !probe.respectsSystemPrompt,\n toolTemperature: probe.nativeToolCalls ? 0.5 : 0.3,\n toolTopP: null,\n toolTopK: null,\n };\n }\n } catch {\n // Registry not available (e.g., during tests) — fall through\n }\n\n/** Track which unknown models we've already triggered background probes for */\nconst probeInFlight = new Set<string>();\n\n/** Trigger a background capability probe for an unknown model.\n * Fire-and-forget: the next request will pick up the result from the registry. */\nfunction triggerBackgroundProbe(modelName: string): void {\n if (probeInFlight.has(modelName)) return;\n probeInFlight.add(modelName);\n // Dynamic import to avoid circular deps at module load time\n import('../agent/modelProbe.js')\n .then(({ probeModel }) => probeModel(`ollama/${modelName}`))\n .then((result) => import('../agent/capabilitiesRegistry.js')\n .then(({ recordProbeResult }) => {\n recordProbeResult(result);\n logger.info(COMPONENT, `Background probe complete for ${modelName}: nativeTools=${result.nativeToolCalls}, respectsSystem=${result.respectsSystemPrompt}`);\n }))\n .catch((err) => logger.warn(COMPONENT, `Background probe failed for ${modelName}: ${(err as Error).message}`))\n .finally(() => probeInFlight.delete(modelName));\n}\n\n // Step 2: Hardcoded map (prefix-matched, longest wins)\n const bare = modelName.includes('/') ? modelName.split('/').slice(1).join('/') : modelName;\n const baseName = bare.replace(/:(cloud|latest|\\d+b(-cloud)?)$/i, '');\n\n let bestMatch: Partial<ModelCapabilities> | undefined;\n let bestLen = 0;\n for (const [pattern, caps] of Object.entries(MODEL_CAPABILITIES)) {\n if (baseName === pattern || baseName.startsWith(pattern)) {\n if (pattern.length > bestLen) {\n bestMatch = caps;\n bestLen = pattern.length;\n }\n }\n }\n\n if (!bestMatch) {\n // Try heuristic inference from model name before falling back to defaults\n const inferred = inferCapabilitiesFromName(modelName);\n if (inferred) {\n logger.info(COMPONENT, `Model \"${modelName}\" not in hardcoded map — using inferred capabilities: ${JSON.stringify(inferred)}`);\n bestMatch = inferred;\n } else {\n logger.info(COMPONENT, `Model \"${modelName}\" not in capabilities database or registry — using conservative defaults. Triggering background probe...`);\n triggerBackgroundProbe(modelName);\n }\n }\n return { ...DEFAULT_CAPABILITIES, ...(bestMatch || {}) };\n}\n\n/** Get the optimal num_ctx for a given model name */\nfunction getModelCtx(modelName: string): number {\n const bare = modelName.includes('/') ? modelName.split('/').slice(1).join('/') : modelName;\n if (CLOUD_MODEL_CTX[bare]) return CLOUD_MODEL_CTX[bare];\n\n // Heuristic: modern cloud models typically have 128K+ context\n if (bare.endsWith(':cloud') || bare.endsWith('-cloud')) return 131072;\n\n // Heuristic: large local models (30B+) often support 32K-64K\n const sizeMatch = bare.match(/(\\d+)b/i);\n if (sizeMatch) {\n const size = parseInt(sizeMatch[1], 10);\n if (size >= 70) return 65536;\n if (size >= 30) return 32768;\n if (size >= 14) return 16384;\n }\n\n // Conservative fallback for tiny unknown local models\n return 8192;\n}\n\n/** Max system prompt length for cloud models with tool calling.\n * Cloud models have 128K+ context — keep this high enough to always include\n * the full descriptions of any tools actively being used in the current task.\n */\nconst CLOUD_MAX_SYSTEM_PROMPT = 8000;\n\n/** Compress a system prompt for cloud models with tool calling.\n * Preserves (in priority order):\n * 1. Tool Execution rules (ReAct loop, MUST/NEVER — highest priority)\n * 2. Active tool descriptions (tools currently in use — must not be stripped)\n * 3. Identity\n * 4. Brief capabilities + behavior reminder\n *\n * @param content The full system prompt to compress\n * @param activeTools Descriptions of tools actively in use — always preserved\n */\nfunction compressSystemPrompt(content: string, activeTools?: Array<{ name: string; description: string }>): string {\n if (content.length <= CLOUD_MAX_SYSTEM_PROMPT) return content;\n\n const sections: string[] = [];\n\n // 1. Tool Execution rules — always first, always preserved\n const toolExecMatch = content.match(/## Tool Execution — HIGHEST PRIORITY[\\s\\S]*?(?=\\n## CRITICAL)/);\n if (toolExecMatch) {\n sections.push(toolExecMatch[0].trim());\n } else {\n sections.push(`## Tool Execution — HIGHEST PRIORITY\nYou are an AI agent. Your PRIMARY function is to execute tasks using tools.\n\nReAct Loop: THINK → ACT (call tool) → OBSERVE (read result) → REPEAT until done.\n\nMUST: call web_search+web_fetch for factual questions, call write_file/edit_file to save files (NEVER output file content as text), call shell for commands, call tool_search if unsure which tool to use.\nNEVER: describe what you could do, output file content inline, generate current facts from memory, tell user to visit a URL.\n\nRight: asked to write a file → call write_file immediately.\nWrong: asked to write a file → output the content as text in your reply.`);\n }\n\n // 2. Identity (shortened)\n const identityMatch = content.match(/## CRITICAL: Your Identity[\\s\\S]*?(?=\\n## )/);\n if (identityMatch) sections.push(identityMatch[0].trim());\n\n // 3. Brief capabilities + behavior\n sections.push('## Tools Available\\nShell, file read/write/edit, web search/fetch, browser, memory, weather, code execution, gmail, gdrive, gcal_personal, gtasks, gcontacts. Use tool_search to discover any tool not listed here.');\n sections.push('## Behavior\\n- Lead with action — call tools immediately, explain briefly after\\n- Never re-plan mid-task after CONFIRM — execute directly\\n- Confirm before destructive operations');\n\n // 4. Active tool descriptions — only inject if budget allows (max 2000 chars for tools).\n // This prevents the model from forgetting available actions mid-task (e.g. after CONFIRM).\n if (activeTools && activeTools.length > 0) {\n const TOOL_BUDGET = 2000;\n const toolLines: string[] = [];\n let toolChars = 0;\n for (const t of activeTools) {\n // Use first 150 chars of description to keep it compact\n const desc = t.description.length > 150 ? t.description.slice(0, 147) + '...' : t.description;\n const line = `- **${t.name}**: ${desc}`;\n if (toolChars + line.length > TOOL_BUDGET) break;\n toolLines.push(line);\n toolChars += line.length;\n }\n if (toolLines.length > 0) {\n sections.push(`## Active Tools\\n${toolLines.join('\\n')}`);\n }\n }\n\n const compressed = sections.join('\\n\\n');\n // Safety: never return something larger than the original\n if (compressed.length >= content.length) {\n logger.info(COMPONENT, `Compressed prompt would be larger (${compressed.length} vs ${content.length}), using truncated original`);\n return content.slice(0, CLOUD_MAX_SYSTEM_PROMPT);\n }\n logger.info(COMPONENT, `Compressed system prompt for cloud model: ${content.length} → ${compressed.length} chars`);\n return compressed;\n}\n\n/**\n * Trim messages for cloud models while preserving tool call/response pairs.\n * Naive slicing can split a tool call from its response, breaking the tool calling contract.\n * This walks backwards keeping assistant+tool pairs together.\n */\nfunction trimPreservingToolPairs(msgs: Array<Record<string, unknown>>, maxTotal: number): Array<Record<string, unknown>> {\n const systemMsgs = msgs.filter(m => m.role === 'system');\n const nonSystem = msgs.filter(m => m.role !== 'system');\n const maxNonSystem = maxTotal - systemMsgs.length;\n\n if (nonSystem.length <= maxNonSystem) return msgs;\n\n // Walk backwards, keeping tool/assistant pairs together\n const kept: Array<Record<string, unknown>> = [];\n let i = nonSystem.length - 1;\n while (i >= 0 && kept.length < maxNonSystem) {\n const msg = nonSystem[i];\n if (msg.role === 'tool') {\n // Keep this tool result and find its assistant parent\n kept.unshift(msg);\n for (let j = i - 1; j >= 0; j--) {\n if (nonSystem[j].role === 'assistant' && (nonSystem[j].tool_calls || nonSystem[j].toolCalls)) {\n kept.unshift(nonSystem[j]);\n i = j - 1;\n break;\n }\n if (nonSystem[j].role === 'tool') {\n // Sibling tool result from same batch\n kept.unshift(nonSystem[j]);\n } else {\n i = j;\n break;\n }\n }\n } else {\n kept.unshift(msg);\n i--;\n }\n }\n\n return [...systemMsgs, ...kept];\n}\n\n/** Simplify tool parameter schemas for cloud models.\n * Strips Zod artifacts ($schema, additionalProperties, etc.) that can\n * confuse cloud model tool-calling.\n */\nfunction simplifySchema(schema: Record<string, unknown> | undefined): Record<string, unknown> {\n if (!schema) return { type: 'object', properties: {} };\n const clean: Record<string, unknown> = { type: schema.type || 'object' };\n if (schema.properties) {\n const props: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(schema.properties as Record<string, Record<string, unknown>>)) {\n // Flatten each property to just type + description\n const prop: Record<string, unknown> = { type: val.type || 'string' };\n if (val.description) prop.description = val.description;\n if (val.enum) prop.enum = val.enum;\n if (val.default !== undefined) prop.default = val.default;\n props[key] = prop;\n }\n clean.properties = props;\n }\n if (schema.required) clean.required = schema.required;\n return clean;\n}\n\nexport class OllamaProvider extends LLMProvider {\n readonly name = 'ollama';\n readonly displayName = 'Ollama (Local)';\n\n private get baseUrl(): string {\n const config = loadConfig();\n return config.providers.ollama.baseUrl || process.env.OLLAMA_BASE_URL || 'http://localhost:11434';\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const model = (options.model || 'llama3.1').replace('ollama/', '');\n logger.debug(COMPONENT, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n // Cloud models (Ollama Pro): detect via -cloud suffix or :cloud tag\n const isCloudModel = model.includes('-cloud') || model.includes(':cloud');\n const hasTools = options.tools && options.tools.length > 0;\n const hasToolRoleMessages = options.messages.some(m => m.role === 'tool');\n\n const body: Record<string, unknown> = {\n model,\n messages: options.messages.map((m) => {\n const msg: Record<string, unknown> = { role: m.role };\n // Compress system prompts for cloud models with tools to improve tool-calling compliance.\n // Pass descriptions of complex tools (>200 chars) so compression always preserves them —\n // prevents the model from forgetting available actions mid-task (e.g. after CONFIRM).\n if (m.role === 'system' && isCloudModel && hasTools) {\n const activeToolDescs = (options.tools ?? [])\n .filter(t => (t.function.description?.length ?? 0) > 200)\n .map(t => ({ name: t.function.name, description: t.function.description ?? '' }));\n msg.content = compressSystemPrompt(m.content, activeToolDescs.length > 0 ? activeToolDescs : undefined);\n } else {\n msg.content = m.content;\n }\n if (m.toolCalls && m.toolCalls.length > 0) {\n msg.tool_calls = m.toolCalls.map(tc => {\n let parsedArgs: Record<string, unknown> = {};\n try {\n parsedArgs = JSON.parse(tc.function.arguments || '{}');\n } catch {\n logger.warn(COMPONENT, `Malformed tool arguments for ${tc.function.name}, using empty args`);\n }\n // v4.13: Gemini's Ollama-compat adapter rejects\n // function_call.name === ''. Some models emit empty\n // names for tool_calls when the call is malformed;\n // stamp a placeholder so the whole turn isn't\n // rejected with HTTP 400 \"Name cannot be empty\".\n const fnName = (tc.function.name || '').trim() || 'unknown_tool';\n const out: Record<string, unknown> = {\n id: tc.id,\n type: tc.type || 'function',\n function: {\n name: fnName,\n arguments: parsedArgs,\n },\n };\n // v4.13: relay Gemini thought_signature through the\n // round-trip. Ollama's Gemini proxy needs it on every\n // subsequent functionCall part or rejects with\n // \"Function call is missing a thought_signature\".\n if (tc.thoughtSignature) {\n (out.function as Record<string, unknown>).thought_signature = tc.thoughtSignature;\n out.thought_signature = tc.thoughtSignature;\n }\n return out;\n });\n }\n if (m.toolCallId) msg.tool_call_id = m.toolCallId;\n // Cloud models (Gemini API) require function_response.name to be non-empty.\n // Guarantee a non-empty name on every tool-role message.\n if (m.role === 'tool') {\n const toolName = (m.name || '').trim() || 'tool';\n msg.name = toolName;\n } else if (m.name) {\n msg.name = m.name;\n }\n return msg;\n }),\n stream: false,\n keep_alive: '30m',\n options: {\n // Auto-configure context window per model's known maximum.\n // getModelCtx() returns the correct num_ctx for each cloud/local model.\n // v4.10.0-local (cost cap): capped cloud num_predict to 8K\n // (was 32K). OpenRouter's paid models reject requests whose\n // max_tokens exceeds the remaining credit, even though most\n // responses don't come close to that. 8K is plenty for any\n // single turn and keeps us from getting HTTP 402s when\n // credit runs low.\n num_predict: options.maxTokens || (isCloudModel ? 8192 : 16384),\n num_ctx: getModelCtx(model),\n temperature: options.temperature ?? 0.7,\n },\n };\n\n // Model capabilities — adapts behavior per model family\n const caps = getModelCapabilities(model);\n\n // Thinking mode: explicitly control per model capabilities.\n // Models that don't benefit from thinking (thinkingWithTools=false) get it disabled\n // to prevent content being routed to the thinking field instead of content field.\n // This is critical for models like minimax-m2.7:cloud which put ALL output in\n // the thinking field when think is unset, leaving content empty.\n if (options.thinking === false) {\n body.think = false;\n } else if (!caps.thinkingWithTools) {\n // Model doesn't support thinking — disable it to prevent 400 errors\n // from Ollama (e.g. \"titan-qwen3.5:4b does not support thinking\").\n body.think = false;\n } else if (options.thinking === true) {\n body.think = true;\n }\n // Otherwise: omit body.think — let the model decide\n\n // Per-turn override: when the conversation contains tool-role messages,\n // force think=false regardless of caller intent. The GLM-family\n // tool-call parser on the server (vLLM #39611, confirmed by Z.ai docs\n // for GLM-5.1) silently drops tool results when enable_thinking=true,\n // breaking the multi-turn tool loop. Z.ai's own guidance: disable\n // thinking on tool-call turns. This keeps reasoning available for\n // planning turns while preventing the drop on execution turns.\n if (hasToolRoleMessages && body.think !== false) {\n const priorIntent = body.think === undefined ? 'unset' : String(body.think);\n body.think = false;\n logger.info(COMPONENT, `[ToolTurnThinkOverride] Forcing think=false for ${model} (tool-role message present, caller intent=${priorIntent})`);\n }\n\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools.map((t) => ({\n type: 'function',\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: simplifySchema(t.function.parameters),\n },\n }));\n // Per-model optimal sampling for tool calling\n (body.options as Record<string, unknown>).temperature = options.temperature ?? caps.toolTemperature ?? 0.5;\n if (caps.toolTopP) (body.options as Record<string, unknown>).top_p = caps.toolTopP;\n if (caps.toolTopK) (body.options as Record<string, unknown>).top_k = caps.toolTopK;\n\n // Force a tool call on the first round when the task requires it\n // Models that self-select tools well don't need forcing — it hurts them\n if (options.forceToolUse && !caps.selfSelectsTools) {\n body.tool_choice = 'required';\n logger.info(COMPONENT, `[ToolChoiceRequired] Setting tool_choice=required for ${model} (forceToolUse=true, selfSelectsTools=false)`);\n } else if (options.forceToolUse && caps.selfSelectsTools) {\n logger.info(COMPONENT, `[ToolChoiceSkipped] forceToolUse=true but selfSelectsTools=true for ${model} — NOT setting tool_choice`);\n }\n }\n\n // Ollama-native structured outputs — constrain generation to a JSON schema.\n // https://docs.ollama.com/capabilities/structured-outputs.md\n if (options.format !== undefined) {\n body.format = options.format;\n }\n\n // Cloud models: trim conversation history preserving tool call/response pairs.\n // With 131K context window, cloud models can handle much longer histories.\n // E1: Use >= 80 with margin (trim to 75) to prevent off-by-one at exact boundary.\n if (isCloudModel && hasTools) {\n const msgs = body.messages as Array<Record<string, unknown>>;\n if (msgs.length >= 80) {\n const trimmed = trimPreservingToolPairs(msgs, 80);\n logger.info(COMPONENT, `Cloud model context trim: ${msgs.length} → ${trimmed.length} messages`);\n body.messages = trimmed;\n }\n }\n\n // Some models ignore standalone system messages during tool calling.\n // Only merge when the model's capability profile says it needs it.\n if (hasTools && caps.needsSystemMerge) {\n const msgs = body.messages as Array<Record<string, unknown>>;\n const sysIdx = msgs.findIndex(m => m.role === 'system');\n const firstUserIdx = msgs.findIndex(m => m.role === 'user');\n if (sysIdx >= 0 && firstUserIdx >= 0 && msgs[sysIdx].content) {\n const sysContent = msgs[sysIdx].content as string;\n msgs[firstUserIdx].content = `[System Instructions]\\n${sysContent}\\n\\n[User Message]\\n${msgs[firstUserIdx].content}`;\n msgs.splice(sysIdx, 1); // Remove the standalone system message\n logger.info(COMPONENT, `Merged system prompt into first user message for cloud model compatibility`);\n }\n }\n\n const sentMessages = body.messages as Array<{role: string; content: string}>;\n const toolNames = body.tools ? (body.tools as Array<{function: {name: string}}>).map(t => t.function.name) : [];\n logger.info(COMPONENT, `Chat request: model=${model}, cloud=${isCloudModel}, tools=[${toolNames.join(',')}], think=${body.think}, messages=${sentMessages.length}`);\n \n if (process.env.DUMP_OLLAMA_BODY === '1' || model.includes('gemini')) {\n logger.error(COMPONENT, `[DUMP_BODY] Dumping failing request body for ${model} to /tmp/ollama-body-dump.json`);\n try {\n fs.writeFileSync('/tmp/ollama-body-dump.json', JSON.stringify(body, null, 2));\n } catch (e) {\n logger.error(COMPONENT, `Failed to dump body: ${e}`);\n }\n }\n\n // Cloud models routed through Ollama need longer timeouts (they proxy to remote APIs)\n const timeoutMs = isCloudModel ? 300_000 : 120_000; // 5min cloud, 2min local\n let response = await fetchWithRetry(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }, { timeoutMs });\n\n if (!response.ok) {\n const errorText = await response.text();\n // Fallback: if model doesn't support native tool calling or tokenization\n // fails with tools, retry without tools. Covers Gemini proxy errors like\n // \"does not support tools\" and \"tokenization\" failures on malformed schemas.\n if (response.status === 400 && body.tools && (\n errorText.includes('does not support tools') ||\n errorText.includes('tokenization') ||\n errorText.includes('tokenize') ||\n errorText.includes('Invalid JSON')\n )) {\n logger.warn(COMPONENT, `Model ${model} does not support native tool calling — running in chat-only mode`);\n delete body.tools;\n response = await fetchWithRetry(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }, { timeoutMs });\n if (!response.ok) {\n const retryText = await response.text();\n // Hunt Finding #37 (2026-04-14): use createProviderError to\n // attach status + parsed Retry-After so the router actually\n // respects the provider's backoff hint.\n const { createProviderError } = await import('./errorTaxonomy.js');\n throw createProviderError('Ollama', response, retryText, { provider: 'ollama', model });\n }\n } else {\n const { createProviderError } = await import('./errorTaxonomy.js');\n throw createProviderError('Ollama', response, errorText, { provider: 'ollama', model });\n }\n }\n\n // v4.13 ancestor-extraction (Hermes rate_limit_tracker): capture any\n // x-ratelimit-* headers the Ollama proxy exposes. Graceful no-op when\n // the headers aren't present. Provider name is 'ollama' so the router's\n // proactive-backoff logic can consult per-provider state.\n try {\n const { recordHeaders } = await import('./rateLimitTracker.js');\n recordHeaders('ollama', response.headers);\n } catch { /* never fail the chat on tracker issues */ }\n\n const data = await response.json() as Record<string, unknown>;\n const message = data.message as Record<string, unknown>;\n logger.info(COMPONENT, `Response from ${model}: tool_calls=${JSON.stringify(message.tool_calls)}, content_length=${((message.content as string) || '').length}`);\n const toolCalls: ToolCall[] = [];\n\n // v5.0.2: Only accept tool_calls from the model if tools were actually\n // sent in the request. Prevents hallucinated tool calls when the safety\n // system has stripped all tools (activeTools = []) or for models that\n // emit tool_calls even without tool definitions.\n if (message.tool_calls && options.tools && options.tools.length > 0) {\n for (const tc of message.tool_calls as Array<Record<string, unknown>>) {\n const fn = tc.function as Record<string, unknown>;\n // v4.13: capture Gemini thought_signature if present — needed\n // on the round-trip back or Gemini rejects the next request.\n const thoughtSig = (tc.thought_signature as string | undefined) ??\n (tc.thoughtSignature as string | undefined) ??\n (fn.thought_signature as string | undefined) ??\n (fn.thoughtSignature as string | undefined);\n toolCalls.push({\n id: uuid(),\n type: 'function',\n function: {\n name: fn.name as string,\n arguments: JSON.stringify(fn.arguments),\n },\n ...(thoughtSig ? { thoughtSignature: thoughtSig } : {}),\n });\n }\n }\n\n // A2: Hallucinated tool name detection at provider level (LangGraph pattern)\n if (options.tools && toolCalls.length > 0) {\n const validNames = new Set(options.tools.map(t => t.function.name));\n const invalid = toolCalls.filter(tc => !validNames.has(tc.function.name));\n if (invalid.length > 0) {\n logger.warn(COMPONENT, `[HallucinationGuard] Model hallucinated ${invalid.length} tool name(s): ${invalid.map(tc => tc.function.name).join(', ')}. Will be caught by toolRunner with corrective feedback.`);\n }\n }\n\n // If content is empty but thinking field has content, use it as a fallback.\n // This handles models that route output to thinking field when think is\n // unset or misconfigured. The router's stripThinkingFromResponse() will\n // clean up any reasoning that leaks through, so we can be permissive here.\n let content = (message.content as string) || '';\n if (!content && message.thinking) {\n const thinking = (message.thinking as string) || '';\n if (thinking.length > 0) {\n logger.info(COMPONENT, `[ThinkingFallback] Content empty, using thinking field (${thinking.length} chars)`);\n content = thinking;\n }\n }\n // Strip leaked thinking tags from Qwen/DeepSeek models\n content = content.replace(/^[\\s\\S]*?<\\/think>\\s*/m, '').trim();\n\n return {\n id: uuid(),\n content,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: {\n promptTokens: (data.prompt_eval_count as number) || 0,\n completionTokens: (data.eval_count as number) || 0,\n totalTokens: ((data.prompt_eval_count as number) || 0) + ((data.eval_count as number) || 0),\n },\n finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',\n model: `ollama/${model}`,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const model = (options.model || 'llama3.1').replace('ollama/', '');\n\n // Cloud models (Ollama Pro): detect via -cloud suffix or :cloud tag\n const isCloudModel = model.includes('-cloud') || model.includes(':cloud');\n const hasTools = options.tools && options.tools.length > 0;\n const hasToolRoleMessages = options.messages.some(m => m.role === 'tool');\n\n const body: Record<string, unknown> = {\n model,\n messages: options.messages.map((m) => {\n const msg: Record<string, unknown> = { role: m.role };\n // Compress system prompts for cloud models with tools — preserve active tool descriptions\n if (m.role === 'system' && isCloudModel && hasTools) {\n const activeToolDescs = (options.tools ?? [])\n .filter(t => (t.function.description?.length ?? 0) > 200)\n .map(t => ({ name: t.function.name, description: t.function.description ?? '' }));\n msg.content = compressSystemPrompt(m.content, activeToolDescs.length > 0 ? activeToolDescs : undefined);\n } else {\n msg.content = m.content;\n }\n if (m.toolCalls && m.toolCalls.length > 0) {\n msg.tool_calls = m.toolCalls.map(tc => ({\n id: tc.id,\n type: tc.type || 'function',\n function: { name: tc.function.name, arguments: JSON.parse(tc.function.arguments || '{}') }\n }));\n }\n if (m.toolCallId) msg.tool_call_id = m.toolCallId;\n // Cloud models (Gemini API) require function_response.name to be non-empty\n if (m.role === 'tool') {\n msg.name = m.name || 'tool';\n } else if (m.name) {\n msg.name = m.name;\n }\n return msg;\n }),\n stream: true,\n keep_alive: '30m',\n options: {\n // v4.10.0-local (cost cap): 8K cloud cap matches non-stream path\n num_predict: options.maxTokens || (isCloudModel ? 8192 : 16384),\n num_ctx: getModelCtx(model),\n temperature: options.temperature ?? 0.7,\n },\n };\n\n // Model capabilities — adapts behavior per model family\n const caps = getModelCapabilities(model);\n\n // Thinking mode: respect explicit setting, otherwise use model capabilities.\n // Disable for models that don't support thinking — prevents 400 errors.\n if (options.thinking === false) {\n body.think = false;\n } else if (!caps.thinkingWithTools) {\n body.think = false;\n } else if (options.thinking === true) {\n body.think = true;\n }\n\n // Per-turn override for tool-role turns (see chat() for rationale: vLLM #39611 / Z.ai docs).\n if (hasToolRoleMessages && body.think !== false) {\n const priorIntent = body.think === undefined ? 'unset' : String(body.think);\n body.think = false;\n logger.info(COMPONENT, `[ToolTurnThinkOverride] (stream) Forcing think=false for ${model} (tool-role message present, caller intent=${priorIntent})`);\n }\n\n if (hasTools) {\n body.tools = options.tools!.map((t) => ({\n type: 'function',\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: simplifySchema(t.function.parameters),\n },\n }));\n // Per-model optimal sampling for tool calling\n (body.options as Record<string, unknown>).temperature = options.temperature ?? caps.toolTemperature ?? 0.5;\n if (caps.toolTopP) (body.options as Record<string, unknown>).top_p = caps.toolTopP;\n if (caps.toolTopK) (body.options as Record<string, unknown>).top_k = caps.toolTopK;\n\n // Force tool_choice when requested — skip for models that self-select well\n if (options.forceToolUse && !caps.selfSelectsTools) {\n body.tool_choice = 'required';\n }\n }\n\n // Ollama-native structured outputs (stream variant).\n if (options.format !== undefined) {\n body.format = options.format;\n }\n\n // Optimize: trim history preserving tool pairs (cloud models only — local models have smaller contexts)\n if (isCloudModel && hasTools) {\n const msgs = body.messages as Array<Record<string, unknown>>;\n if (msgs.length > 80) {\n const trimmed = trimPreservingToolPairs(msgs, 80);\n logger.info(COMPONENT, `[Stream] Cloud model context trim: ${msgs.length} → ${trimmed.length} messages`);\n body.messages = trimmed;\n }\n }\n // Merge system into first user message only for models that need it\n if (hasTools && caps.needsSystemMerge) {\n const msgs2 = body.messages as Array<Record<string, unknown>>;\n const sysIdx = msgs2.findIndex(m => m.role === 'system');\n const firstUserIdx = msgs2.findIndex(m => m.role === 'user');\n if (sysIdx >= 0 && firstUserIdx >= 0 && msgs2[sysIdx].content) {\n const sysContent = msgs2[sysIdx].content as string;\n msgs2[firstUserIdx].content = `[System Instructions]\\n${sysContent}\\n\\n[User Message]\\n${msgs2[firstUserIdx].content}`;\n msgs2.splice(sysIdx, 1);\n }\n }\n\n try {\n // Cloud models need longer timeouts for streaming too\n const streamTimeoutMs = isCloudModel ? 300_000 : 120_000;\n let response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(streamTimeoutMs),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n // Fallback: if model doesn't support native tool calling, retry without tools\n if (response.status === 400 && errorText.includes('does not support tools') && body.tools) {\n logger.warn(COMPONENT, `Model ${model} does not support native tool calling — running in chat-only mode`);\n delete body.tools;\n response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(streamTimeoutMs),\n });\n if (!response.ok || !response.body) {\n const retryText = await response.text();\n yield { type: 'error', error: `Ollama error (${response.status}): ${retryText}` };\n return;\n }\n } else {\n yield { type: 'error', error: `Ollama error (${response.status}): ${errorText}` };\n return;\n }\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let insideThink = false;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const chunk = JSON.parse(line);\n // Handle thinking field for models that put content there\n // Some models (e.g. qwen3.5, nemotron-super:cloud) use the thinking field\n // even when think=false is set — treat thinking as content in that case\n if (!chunk.message?.content && chunk.message?.thinking) {\n if (body.think === false) {\n chunk.message.content = chunk.message.thinking;\n }\n }\n if (chunk.message?.content) {\n let text = chunk.message.content;\n // Strip leaked <think>...</think> blocks from Qwen/DeepSeek\n if (text.includes('<think>')) insideThink = true;\n if (insideThink) {\n if (text.includes('</think>')) {\n text = text.split('</think>').pop()?.trim() || '';\n insideThink = false;\n } else {\n continue; // suppress thinking content\n }\n }\n if (text) yield { type: 'text', content: text };\n }\n // v5.0.2: Only yield tool_calls if tools were sent in the request\n if (chunk.message?.tool_calls && options.tools && options.tools.length > 0) {\n for (const tc of chunk.message.tool_calls) {\n const fn = tc.function as Record<string, unknown>;\n yield {\n type: 'tool_call',\n toolCall: { id: uuid(), type: 'function', function: { name: fn.name as string, arguments: JSON.stringify(fn.arguments) } },\n };\n }\n }\n if (chunk.done) break;\n } catch { /* skip malformed NDJSON lines */ }\n }\n }\n yield { type: 'done' };\n } catch (error) {\n yield { type: 'error', error: (error as Error).message };\n }\n }\n\n async listModels(): Promise<string[]> {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n if (!response.ok) {\n // Hunt Finding #29 (2026-04-14): consume the body even on\n // error paths so the underlying socket can return to the\n // keep-alive pool. Without this, every non-200 response\n // leaks its socket until the GC gets around to it.\n await response.body?.cancel().catch(() => {});\n return [];\n }\n const data = await response.json() as { models?: Array<{ name: string }> };\n return (data.models || []).map((m) => m.name);\n } catch {\n return [];\n }\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n const ok = response.ok;\n // Hunt Finding #29 (2026-04-14): ALWAYS consume or cancel the\n // body. Previously we returned response.ok directly, leaving the\n // body stream dangling and the socket held open.\n await response.body?.cancel().catch(() => {});\n return ok;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":["modelName"]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/ollama.ts"],"sourcesContent":["/**\n * TITAN — Ollama Provider (Local LLMs)\n */\nimport {\n LLMProvider,\n type ChatOptions,\n type ChatResponse,\n type ChatStreamChunk,\n type ToolCall,\n} from './base.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { fetchWithRetry } from '../utils/helpers.js';\nimport { v4 as uuid } from 'uuid';\nimport * as fs from 'fs';\nimport { clampMaxTokens } from './modelCapabilities.js';\n\nconst COMPONENT = 'Ollama';\n\n/**\n * Per-model context window map for Ollama cloud models.\n * Auto-configures num_ctx to each model's actual maximum to prevent truncation.\n * Sources: Ollama Cloud model cards, March 2026.\n */\nconst CLOUD_MODEL_CTX: Record<string, number> = {\n // GLM-5.1 — 198K context (newest agentic flagship, SOTA SWE-Bench Pro)\n 'glm-5.1:cloud': 198656,\n // GLM-5 — 128K context\n 'glm-5:cloud': 131072,\n // Kimi K2.5 — 256K context (native multimodal agentic, agent swarm)\n 'kimi-k2.5:cloud': 262144,\n // Kimi K2.6 — 256K context (next-gen agentic, enhanced reasoning)\n 'kimi-k2.6:cloud': 262144,\n // Qwen3 Coder Next — 262K context (massive)\n 'qwen3-coder-next:cloud': 262144,\n // Qwen3.5 397B Cloud — 256K context (all variants support 256K)\n 'qwen3.5:397b-cloud': 262144,\n // DeepSeek V3.1 — 128K context\n 'deepseek-v3.1:671b-cloud': 131072,\n // DeepSeek V3.2 — 160K context (DSA long-context optimized)\n 'deepseek-v3.2:671b-cloud': 163840,\n 'deepseek-v3.2:cloud': 163840,\n // Devstral 2 — 128K context\n 'devstral-2:cloud': 131072,\n // Devstral Small 2 (local) — 32K\n 'devstral-small-2': 32768,\n 'devstral-small-2:latest': 32768,\n // Nemotron 3 Nano — 1M native, 32K practical for local\n 'nemotron-3-nano': 32768,\n 'nemotron-3-nano:latest': 32768,\n 'nemotron-3-nano:4b': 32768,\n 'nemotron-3-nano:30b': 32768,\n // Nemotron 3 Super — 256K context (MoE 120B/12B active)\n 'nemotron-3-super:cloud': 262144,\n // Gemini 3 Flash — 1M context\n 'gemini-3-flash-preview:latest': 1048576,\n // GPT OSS — 128K\n 'gpt-oss:120b-cloud': 131072,\n // MiniMax M2.7 — 200K context (Agent Teams, dynamic tool search)\n 'minimax-m2.7:cloud': 204800,\n // Gemma 4 — 256K context (native function calling)\n 'gemma4:cloud': 262144,\n // Qwen3.5 35B local — 32K\n 'qwen3.5:35b': 32768,\n};\n\n/**\n * Model capability profiles — controls how TITAN adapts to each model's strengths.\n * Instead of blanket rules for all models, each model gets tuned behavior.\n *\n * selfSelectsTools: Model picks tools well on its own — don't force tool_choice='required'\n * thinkingWithTools: Model benefits from thinking (<think> tags) during tool calling\n * needsSystemMerge: Model ignores standalone system messages — merge into first user msg\n * toolTemperature: Optimal temperature for tool-calling tasks (null = use caller's value or 0.5 default)\n * toolTopP: Optimal top_p for tool calling (null = omit)\n * toolTopK: Optimal top_k for tool calling (null = omit)\n */\ninterface ModelCapabilities {\n selfSelectsTools: boolean;\n thinkingWithTools: boolean;\n needsSystemMerge: boolean;\n toolTemperature: number | null;\n toolTopP: number | null;\n toolTopK: number | null;\n}\n\nconst DEFAULT_CAPABILITIES: ModelCapabilities = {\n selfSelectsTools: false,\n thinkingWithTools: false,\n needsSystemMerge: true, // Conservative default: merge for unknown models\n toolTemperature: 0.5,\n toolTopP: null,\n toolTopK: null,\n};\n\n/** Heuristic: infer capabilities from model name patterns when no hardcoded\n * entry exists. Most modern models (2024+) support native tool calling and\n * handle system prompts correctly. This prevents unknown models from being\n * crippled by overly conservative defaults. */\nfunction inferCapabilitiesFromName(modelName: string): Partial<ModelCapabilities> | undefined {\n const lower = modelName.toLowerCase();\n\n // Cloud-hosted models are almost always modern and capable\n if (lower.includes(':cloud') || lower.includes('-cloud')) {\n return { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 };\n }\n\n // Large local models (30B+) are typically capable\n const sizeMatch = lower.match(/(\\d+)b/);\n if (sizeMatch) {\n const size = parseInt(sizeMatch[1], 10);\n if (size >= 30) {\n return { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 0.5 };\n }\n }\n\n // Known-capable families by name pattern (even if not in hardcoded map)\n const capableFamilies = ['qwen', 'glm', 'deepseek', 'kimi', 'gemma', 'nemotron', 'devstral', 'gemini', 'mistral-large', 'llama3.3', 'llama4', 'phi4', 'command-r-plus'];\n for (const family of capableFamilies) {\n if (lower.includes(family)) {\n return { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 0.5 };\n }\n }\n\n // Truly unknown small local models — stay conservative\n return undefined;\n}\n\nconst MODEL_CAPABILITIES: Record<string, Partial<ModelCapabilities>> = {\n // ── Qwen family — excellent tool calling, uses thinking ──\n 'qwen3.5': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.7 },\n 'qwen3': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.7 },\n 'qwen3-coder-next': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.5 },\n\n // ── DeepSeek family — strong reasoning, good tool use ──\n 'deepseek-v3': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n 'deepseek-v3.1': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n 'deepseek-v3.2': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n\n // ── MiniMax M2.7 — XML tool format, needs special handling ──\n // Hunt Finding #05 (2026-04-14): flipped selfSelectsTools from true→false.\n // Confirmed by reproducing: a \"use shell to run uptime\" prompt returned\n // fabricated uptime text with no tool call. The model hallucinates instead\n // of calling tools when given the choice. Setting false forces the agent\n // loop's forceToolUse to fire `tool_choice: required`, which prevents this\n // class of hallucination at the API level.\n 'minimax-m2.7': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.8, toolTopP: 0.95, toolTopK: 40 },\n 'minimax-m2': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.8, toolTopP: 0.95 },\n\n // ── Gemma family — good tool use, no thinking ──\n 'gemma4': { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 1.0, toolTopP: 0.95, toolTopK: 64 },\n 'gemma-3': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.4 },\n\n // ── GLM family — GLM-5.1 is agentic flagship, SOTA SWE-Bench Pro, 198K ctx ──\n 'glm-5.1': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n 'glm-5': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.4 },\n 'glm-4.7': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.4 },\n\n // ── Nemotron — Super is 256K MoE optimized for collaborative agents ──\n 'nemotron-3-super': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: true, toolTemperature: 0.4 },\n 'nemotron-3-nano': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n\n // ── Kimi K2.5 — 256K, native agentic, agent swarm decomposition ──\n 'kimi-k2.5': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n 'kimi-k2.6': { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },\n\n // ── Devstral — code-focused ──\n 'devstral-2': { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 0.4 },\n 'devstral-small-2': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n\n // ── Gemini — handles system messages well ──\n 'gemini-3-flash': { selfSelectsTools: true, thinkingWithTools: false, needsSystemMerge: false, toolTemperature: 0.5 },\n\n // ── Llama/Mistral — weaker tool calling ──\n 'llama3.1': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n 'llama3.2': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n 'mistral': { selfSelectsTools: false, thinkingWithTools: false, needsSystemMerge: true, toolTemperature: 0.3 },\n};\n\n/** Resolve capabilities for a model.\n *\n * Lookup order:\n * 1. Empirical probe result from capabilities registry (~/.titan/model-capabilities.json)\n * — This reflects ACTUAL behavior tested against the live model\n * 2. Hardcoded MODEL_CAPABILITIES map (this file) — matched by longest prefix\n * 3. DEFAULT_CAPABILITIES — conservative fallback for unknown models\n */\nfunction getModelCapabilities(modelName: string): ModelCapabilities {\n // Step 1: Check empirical probe registry (preferred)\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { getProbeResult, isProbeStale } = require('../agent/capabilitiesRegistry.js') as typeof import('../agent/capabilitiesRegistry.js');\n const probe = getProbeResult(modelName) || getProbeResult(`ollama/${modelName}`);\n if (probe && !isProbeStale(probe)) {\n // Convert probe result to capability flags\n return {\n ...DEFAULT_CAPABILITIES,\n selfSelectsTools: probe.nativeToolCalls,\n thinkingWithTools: probe.hasThinkingMode && !probe.needsExplicitThinkFalse,\n needsSystemMerge: !probe.respectsSystemPrompt,\n toolTemperature: probe.nativeToolCalls ? 0.5 : 0.3,\n toolTopP: null,\n toolTopK: null,\n };\n }\n } catch {\n // Registry not available (e.g., during tests) — fall through\n }\n\n/** Track which unknown models we've already triggered background probes for */\nconst probeInFlight = new Set<string>();\n\n/** Trigger a background capability probe for an unknown model.\n * Fire-and-forget: the next request will pick up the result from the registry. */\nfunction triggerBackgroundProbe(modelName: string): void {\n if (probeInFlight.has(modelName)) return;\n probeInFlight.add(modelName);\n // Dynamic import to avoid circular deps at module load time\n import('../agent/modelProbe.js')\n .then(({ probeModel }) => probeModel(`ollama/${modelName}`))\n .then((result) => import('../agent/capabilitiesRegistry.js')\n .then(({ recordProbeResult }) => {\n recordProbeResult(result);\n logger.info(COMPONENT, `Background probe complete for ${modelName}: nativeTools=${result.nativeToolCalls}, respectsSystem=${result.respectsSystemPrompt}`);\n }))\n .catch((err) => logger.warn(COMPONENT, `Background probe failed for ${modelName}: ${(err as Error).message}`))\n .finally(() => probeInFlight.delete(modelName));\n}\n\n // Step 2: Hardcoded map (prefix-matched, longest wins)\n const bare = modelName.includes('/') ? modelName.split('/').slice(1).join('/') : modelName;\n const baseName = bare.replace(/:(cloud|latest|\\d+b(-cloud)?)$/i, '');\n\n let bestMatch: Partial<ModelCapabilities> | undefined;\n let bestLen = 0;\n for (const [pattern, caps] of Object.entries(MODEL_CAPABILITIES)) {\n if (baseName === pattern || baseName.startsWith(pattern)) {\n if (pattern.length > bestLen) {\n bestMatch = caps;\n bestLen = pattern.length;\n }\n }\n }\n\n if (!bestMatch) {\n // Try heuristic inference from model name before falling back to defaults\n const inferred = inferCapabilitiesFromName(modelName);\n if (inferred) {\n logger.info(COMPONENT, `Model \"${modelName}\" not in hardcoded map — using inferred capabilities: ${JSON.stringify(inferred)}`);\n bestMatch = inferred;\n } else {\n logger.info(COMPONENT, `Model \"${modelName}\" not in capabilities database or registry — using conservative defaults. Triggering background probe...`);\n triggerBackgroundProbe(modelName);\n }\n }\n return { ...DEFAULT_CAPABILITIES, ...(bestMatch || {}) };\n}\n\n/** Get the optimal num_ctx for a given model name */\nfunction getModelCtx(modelName: string): number {\n const bare = modelName.includes('/') ? modelName.split('/').slice(1).join('/') : modelName;\n if (CLOUD_MODEL_CTX[bare]) return CLOUD_MODEL_CTX[bare];\n\n // Heuristic: modern cloud models typically have 128K+ context\n if (bare.endsWith(':cloud') || bare.endsWith('-cloud')) return 131072;\n\n // Heuristic: large local models (30B+) often support 32K-64K\n const sizeMatch = bare.match(/(\\d+)b/i);\n if (sizeMatch) {\n const size = parseInt(sizeMatch[1], 10);\n if (size >= 70) return 65536;\n if (size >= 30) return 32768;\n if (size >= 14) return 16384;\n }\n\n // Conservative fallback for tiny unknown local models\n return 8192;\n}\n\n/** Max system prompt length for cloud models with tool calling.\n * Cloud models have 128K+ context — keep this high enough to always include\n * the full descriptions of any tools actively being used in the current task.\n */\nconst CLOUD_MAX_SYSTEM_PROMPT = 8000;\n\n/** Compress a system prompt for cloud models with tool calling.\n * Preserves (in priority order):\n * 1. Tool Execution rules (ReAct loop, MUST/NEVER — highest priority)\n * 2. Active tool descriptions (tools currently in use — must not be stripped)\n * 3. Identity\n * 4. Brief capabilities + behavior reminder\n *\n * @param content The full system prompt to compress\n * @param activeTools Descriptions of tools actively in use — always preserved\n */\nfunction compressSystemPrompt(content: string, activeTools?: Array<{ name: string; description: string }>): string {\n if (content.length <= CLOUD_MAX_SYSTEM_PROMPT) return content;\n\n const sections: string[] = [];\n\n // 1. Tool Execution rules — always first, always preserved\n const toolExecMatch = content.match(/## Tool Execution — HIGHEST PRIORITY[\\s\\S]*?(?=\\n## CRITICAL)/);\n if (toolExecMatch) {\n sections.push(toolExecMatch[0].trim());\n } else {\n sections.push(`## Tool Execution — HIGHEST PRIORITY\nYou are an AI agent. Your PRIMARY function is to execute tasks using tools.\n\nReAct Loop: THINK → ACT (call tool) → OBSERVE (read result) → REPEAT until done.\n\nMUST: call web_search+web_fetch for factual questions, call write_file/edit_file to save files (NEVER output file content as text), call shell for commands, call tool_search if unsure which tool to use.\nNEVER: describe what you could do, output file content inline, generate current facts from memory, tell user to visit a URL.\n\nRight: asked to write a file → call write_file immediately.\nWrong: asked to write a file → output the content as text in your reply.`);\n }\n\n // 2. Identity (shortened)\n const identityMatch = content.match(/## CRITICAL: Your Identity[\\s\\S]*?(?=\\n## )/);\n if (identityMatch) sections.push(identityMatch[0].trim());\n\n // 3. Brief capabilities + behavior\n sections.push('## Tools Available\\nShell, file read/write/edit, web search/fetch, browser, memory, weather, code execution, gmail, gdrive, gcal_personal, gtasks, gcontacts. Use tool_search to discover any tool not listed here.');\n sections.push('## Behavior\\n- Lead with action — call tools immediately, explain briefly after\\n- Never re-plan mid-task after CONFIRM — execute directly\\n- Confirm before destructive operations');\n\n // 4. Active tool descriptions — only inject if budget allows (max 2000 chars for tools).\n // This prevents the model from forgetting available actions mid-task (e.g. after CONFIRM).\n if (activeTools && activeTools.length > 0) {\n const TOOL_BUDGET = 2000;\n const toolLines: string[] = [];\n let toolChars = 0;\n for (const t of activeTools) {\n // Use first 150 chars of description to keep it compact\n const desc = t.description.length > 150 ? t.description.slice(0, 147) + '...' : t.description;\n const line = `- **${t.name}**: ${desc}`;\n if (toolChars + line.length > TOOL_BUDGET) break;\n toolLines.push(line);\n toolChars += line.length;\n }\n if (toolLines.length > 0) {\n sections.push(`## Active Tools\\n${toolLines.join('\\n')}`);\n }\n }\n\n const compressed = sections.join('\\n\\n');\n // Safety: never return something larger than the original\n if (compressed.length >= content.length) {\n logger.info(COMPONENT, `Compressed prompt would be larger (${compressed.length} vs ${content.length}), using truncated original`);\n return content.slice(0, CLOUD_MAX_SYSTEM_PROMPT);\n }\n logger.info(COMPONENT, `Compressed system prompt for cloud model: ${content.length} → ${compressed.length} chars`);\n return compressed;\n}\n\n/**\n * Trim messages for cloud models while preserving tool call/response pairs.\n * Naive slicing can split a tool call from its response, breaking the tool calling contract.\n * This walks backwards keeping assistant+tool pairs together.\n */\nfunction trimPreservingToolPairs(msgs: Array<Record<string, unknown>>, maxTotal: number): Array<Record<string, unknown>> {\n const systemMsgs = msgs.filter(m => m.role === 'system');\n const nonSystem = msgs.filter(m => m.role !== 'system');\n const maxNonSystem = maxTotal - systemMsgs.length;\n\n if (nonSystem.length <= maxNonSystem) return msgs;\n\n // Walk backwards, keeping tool/assistant pairs together\n const kept: Array<Record<string, unknown>> = [];\n let i = nonSystem.length - 1;\n while (i >= 0 && kept.length < maxNonSystem) {\n const msg = nonSystem[i];\n if (msg.role === 'tool') {\n // Keep this tool result and find its assistant parent\n kept.unshift(msg);\n for (let j = i - 1; j >= 0; j--) {\n if (nonSystem[j].role === 'assistant' && (nonSystem[j].tool_calls || nonSystem[j].toolCalls)) {\n kept.unshift(nonSystem[j]);\n i = j - 1;\n break;\n }\n if (nonSystem[j].role === 'tool') {\n // Sibling tool result from same batch\n kept.unshift(nonSystem[j]);\n } else {\n i = j;\n break;\n }\n }\n } else {\n kept.unshift(msg);\n i--;\n }\n }\n\n return [...systemMsgs, ...kept];\n}\n\n/** Simplify tool parameter schemas for cloud models.\n * Strips Zod artifacts ($schema, additionalProperties, etc.) that can\n * confuse cloud model tool-calling.\n */\nfunction simplifySchema(schema: Record<string, unknown> | undefined): Record<string, unknown> {\n if (!schema) return { type: 'object', properties: {} };\n const clean: Record<string, unknown> = { type: schema.type || 'object' };\n if (schema.properties) {\n const props: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(schema.properties as Record<string, Record<string, unknown>>)) {\n // Flatten each property to just type + description\n const prop: Record<string, unknown> = { type: val.type || 'string' };\n if (val.description) prop.description = val.description;\n if (val.enum) prop.enum = val.enum;\n if (val.default !== undefined) prop.default = val.default;\n props[key] = prop;\n }\n clean.properties = props;\n }\n if (schema.required) clean.required = schema.required;\n return clean;\n}\n\nexport class OllamaProvider extends LLMProvider {\n readonly name = 'ollama';\n readonly displayName = 'Ollama (Local)';\n\n private get baseUrl(): string {\n const config = loadConfig();\n return config.providers.ollama.baseUrl || process.env.OLLAMA_BASE_URL || 'http://localhost:11434';\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const model = (options.model || 'llama3.1').replace('ollama/', '');\n logger.debug(COMPONENT, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n // Cloud models (Ollama Pro): detect via -cloud suffix or :cloud tag\n const isCloudModel = model.includes('-cloud') || model.includes(':cloud');\n const hasTools = options.tools && options.tools.length > 0;\n const hasToolRoleMessages = options.messages.some(m => m.role === 'tool');\n\n const body: Record<string, unknown> = {\n model,\n messages: options.messages.map((m) => {\n const msg: Record<string, unknown> = { role: m.role };\n // Compress system prompts for cloud models with tools to improve tool-calling compliance.\n // Pass descriptions of complex tools (>200 chars) so compression always preserves them —\n // prevents the model from forgetting available actions mid-task (e.g. after CONFIRM).\n if (m.role === 'system' && isCloudModel && hasTools) {\n const activeToolDescs = (options.tools ?? [])\n .filter(t => (t.function.description?.length ?? 0) > 200)\n .map(t => ({ name: t.function.name, description: t.function.description ?? '' }));\n msg.content = compressSystemPrompt(m.content, activeToolDescs.length > 0 ? activeToolDescs : undefined);\n } else {\n msg.content = m.content;\n }\n if (m.toolCalls && m.toolCalls.length > 0) {\n msg.tool_calls = m.toolCalls.map(tc => {\n let parsedArgs: Record<string, unknown> = {};\n try {\n parsedArgs = JSON.parse(tc.function.arguments || '{}');\n } catch {\n logger.warn(COMPONENT, `Malformed tool arguments for ${tc.function.name}, using empty args`);\n }\n // v4.13: Gemini's Ollama-compat adapter rejects\n // function_call.name === ''. Some models emit empty\n // names for tool_calls when the call is malformed;\n // stamp a placeholder so the whole turn isn't\n // rejected with HTTP 400 \"Name cannot be empty\".\n const fnName = (tc.function.name || '').trim() || 'unknown_tool';\n const out: Record<string, unknown> = {\n id: tc.id,\n type: tc.type || 'function',\n function: {\n name: fnName,\n arguments: parsedArgs,\n },\n };\n // v4.13: relay Gemini thought_signature through the\n // round-trip. Ollama's Gemini proxy needs it on every\n // subsequent functionCall part or rejects with\n // \"Function call is missing a thought_signature\".\n if (tc.thoughtSignature) {\n (out.function as Record<string, unknown>).thought_signature = tc.thoughtSignature;\n out.thought_signature = tc.thoughtSignature;\n }\n return out;\n });\n }\n if (m.toolCallId) msg.tool_call_id = m.toolCallId;\n // Cloud models (Gemini API) require function_response.name to be non-empty.\n // Guarantee a non-empty name on every tool-role message.\n if (m.role === 'tool') {\n const toolName = (m.name || '').trim() || 'tool';\n msg.name = toolName;\n } else if (m.name) {\n msg.name = m.name;\n }\n return msg;\n }),\n stream: false,\n keep_alive: '30m',\n options: {\n // Auto-configure context window per model's known maximum.\n // getModelCtx() returns the correct num_ctx for each cloud/local model.\n // v4.10.0-local (cost cap): capped cloud num_predict to 8K\n // (was 32K). OpenRouter's paid models reject requests whose\n // max_tokens exceeds the remaining credit, even though most\n // responses don't come close to that. 8K is plenty for any\n // single turn and keeps us from getting HTTP 402s when\n // credit runs low.\n num_predict: clampMaxTokens(options.model || 'ollama/llama3.1', options.maxTokens),\n num_ctx: getModelCtx(model),\n temperature: options.temperature ?? 0.7,\n },\n };\n\n // Model capabilities — adapts behavior per model family\n const caps = getModelCapabilities(model);\n\n // Thinking mode: explicitly control per model capabilities.\n // Models that don't benefit from thinking (thinkingWithTools=false) get it disabled\n // to prevent content being routed to the thinking field instead of content field.\n // This is critical for models like minimax-m2.7:cloud which put ALL output in\n // the thinking field when think is unset, leaving content empty.\n if (options.thinking === false) {\n body.think = false;\n } else if (!caps.thinkingWithTools) {\n // Model doesn't support thinking — disable it to prevent 400 errors\n // from Ollama (e.g. \"titan-qwen3.5:4b does not support thinking\").\n body.think = false;\n } else if (options.thinking === true) {\n body.think = true;\n }\n // Otherwise: omit body.think — let the model decide\n\n // Per-turn override: when the conversation contains tool-role messages,\n // force think=false regardless of caller intent. The GLM-family\n // tool-call parser on the server (vLLM #39611, confirmed by Z.ai docs\n // for GLM-5.1) silently drops tool results when enable_thinking=true,\n // breaking the multi-turn tool loop. Z.ai's own guidance: disable\n // thinking on tool-call turns. This keeps reasoning available for\n // planning turns while preventing the drop on execution turns.\n if (hasToolRoleMessages && body.think !== false) {\n const priorIntent = body.think === undefined ? 'unset' : String(body.think);\n body.think = false;\n logger.info(COMPONENT, `[ToolTurnThinkOverride] Forcing think=false for ${model} (tool-role message present, caller intent=${priorIntent})`);\n }\n\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools.map((t) => ({\n type: 'function',\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: simplifySchema(t.function.parameters),\n },\n }));\n // Per-model optimal sampling for tool calling\n (body.options as Record<string, unknown>).temperature = options.temperature ?? caps.toolTemperature ?? 0.5;\n if (caps.toolTopP) (body.options as Record<string, unknown>).top_p = caps.toolTopP;\n if (caps.toolTopK) (body.options as Record<string, unknown>).top_k = caps.toolTopK;\n\n // Force a tool call on the first round when the task requires it\n // Models that self-select tools well don't need forcing — it hurts them\n if (options.forceToolUse && !caps.selfSelectsTools) {\n body.tool_choice = 'required';\n logger.info(COMPONENT, `[ToolChoiceRequired] Setting tool_choice=required for ${model} (forceToolUse=true, selfSelectsTools=false)`);\n } else if (options.forceToolUse && caps.selfSelectsTools) {\n logger.info(COMPONENT, `[ToolChoiceSkipped] forceToolUse=true but selfSelectsTools=true for ${model} — NOT setting tool_choice`);\n }\n }\n\n // Ollama-native structured outputs — constrain generation to a JSON schema.\n // https://docs.ollama.com/capabilities/structured-outputs.md\n if (options.format !== undefined) {\n body.format = options.format;\n }\n\n // Cloud models: trim conversation history preserving tool call/response pairs.\n // With 131K context window, cloud models can handle much longer histories.\n // E1: Use >= 80 with margin (trim to 75) to prevent off-by-one at exact boundary.\n if (isCloudModel && hasTools) {\n const msgs = body.messages as Array<Record<string, unknown>>;\n if (msgs.length >= 80) {\n const trimmed = trimPreservingToolPairs(msgs, 80);\n logger.info(COMPONENT, `Cloud model context trim: ${msgs.length} → ${trimmed.length} messages`);\n body.messages = trimmed;\n }\n }\n\n // Some models ignore standalone system messages during tool calling.\n // Only merge when the model's capability profile says it needs it.\n if (hasTools && caps.needsSystemMerge) {\n const msgs = body.messages as Array<Record<string, unknown>>;\n const sysIdx = msgs.findIndex(m => m.role === 'system');\n const firstUserIdx = msgs.findIndex(m => m.role === 'user');\n if (sysIdx >= 0 && firstUserIdx >= 0 && msgs[sysIdx].content) {\n const sysContent = msgs[sysIdx].content as string;\n msgs[firstUserIdx].content = `[System Instructions]\\n${sysContent}\\n\\n[User Message]\\n${msgs[firstUserIdx].content}`;\n msgs.splice(sysIdx, 1); // Remove the standalone system message\n logger.info(COMPONENT, `Merged system prompt into first user message for cloud model compatibility`);\n }\n }\n\n const sentMessages = body.messages as Array<{role: string; content: string}>;\n const toolNames = body.tools ? (body.tools as Array<{function: {name: string}}>).map(t => t.function.name) : [];\n logger.info(COMPONENT, `Chat request: model=${model}, cloud=${isCloudModel}, tools=[${toolNames.join(',')}], think=${body.think}, messages=${sentMessages.length}`);\n \n if (process.env.DUMP_OLLAMA_BODY === '1' || model.includes('gemini')) {\n logger.error(COMPONENT, `[DUMP_BODY] Dumping failing request body for ${model} to /tmp/ollama-body-dump.json`);\n try {\n fs.writeFileSync('/tmp/ollama-body-dump.json', JSON.stringify(body, null, 2));\n } catch (e) {\n logger.error(COMPONENT, `Failed to dump body: ${e}`);\n }\n }\n\n // Cloud models routed through Ollama need longer timeouts (they proxy to remote APIs)\n const timeoutMs = isCloudModel ? 300_000 : 120_000; // 5min cloud, 2min local\n let response = await fetchWithRetry(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }, { timeoutMs });\n\n if (!response.ok) {\n const errorText = await response.text();\n // Fallback: if model doesn't support native tool calling or tokenization\n // fails with tools, retry without tools. Covers Gemini proxy errors like\n // \"does not support tools\" and \"tokenization\" failures on malformed schemas.\n if (response.status === 400 && body.tools && (\n errorText.includes('does not support tools') ||\n errorText.includes('tokenization') ||\n errorText.includes('tokenize') ||\n errorText.includes('Invalid JSON')\n )) {\n logger.warn(COMPONENT, `Model ${model} does not support native tool calling — running in chat-only mode`);\n delete body.tools;\n response = await fetchWithRetry(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n }, { timeoutMs });\n if (!response.ok) {\n const retryText = await response.text();\n // Hunt Finding #37 (2026-04-14): use createProviderError to\n // attach status + parsed Retry-After so the router actually\n // respects the provider's backoff hint.\n const { createProviderError } = await import('./errorTaxonomy.js');\n throw createProviderError('Ollama', response, retryText, { provider: 'ollama', model });\n }\n } else {\n const { createProviderError } = await import('./errorTaxonomy.js');\n throw createProviderError('Ollama', response, errorText, { provider: 'ollama', model });\n }\n }\n\n // v4.13 ancestor-extraction (Hermes rate_limit_tracker): capture any\n // x-ratelimit-* headers the Ollama proxy exposes. Graceful no-op when\n // the headers aren't present. Provider name is 'ollama' so the router's\n // proactive-backoff logic can consult per-provider state.\n try {\n const { recordHeaders } = await import('./rateLimitTracker.js');\n recordHeaders('ollama', response.headers);\n } catch { /* never fail the chat on tracker issues */ }\n\n const data = await response.json() as Record<string, unknown>;\n const message = data.message as Record<string, unknown>;\n logger.info(COMPONENT, `Response from ${model}: tool_calls=${JSON.stringify(message.tool_calls)}, content_length=${((message.content as string) || '').length}`);\n const toolCalls: ToolCall[] = [];\n\n // v5.0.2: Only accept tool_calls from the model if tools were actually\n // sent in the request. Prevents hallucinated tool calls when the safety\n // system has stripped all tools (activeTools = []) or for models that\n // emit tool_calls even without tool definitions.\n if (message.tool_calls && options.tools && options.tools.length > 0) {\n for (const tc of message.tool_calls as Array<Record<string, unknown>>) {\n const fn = tc.function as Record<string, unknown>;\n // v4.13: capture Gemini thought_signature if present — needed\n // on the round-trip back or Gemini rejects the next request.\n const thoughtSig = (tc.thought_signature as string | undefined) ??\n (tc.thoughtSignature as string | undefined) ??\n (fn.thought_signature as string | undefined) ??\n (fn.thoughtSignature as string | undefined);\n toolCalls.push({\n id: uuid(),\n type: 'function',\n function: {\n name: fn.name as string,\n arguments: JSON.stringify(fn.arguments),\n },\n ...(thoughtSig ? { thoughtSignature: thoughtSig } : {}),\n });\n }\n }\n\n // A2: Hallucinated tool name detection at provider level (LangGraph pattern)\n if (options.tools && toolCalls.length > 0) {\n const validNames = new Set(options.tools.map(t => t.function.name));\n const invalid = toolCalls.filter(tc => !validNames.has(tc.function.name));\n if (invalid.length > 0) {\n logger.warn(COMPONENT, `[HallucinationGuard] Model hallucinated ${invalid.length} tool name(s): ${invalid.map(tc => tc.function.name).join(', ')}. Will be caught by toolRunner with corrective feedback.`);\n }\n }\n\n // If content is empty but thinking field has content, use it as a fallback.\n // This handles models that route output to thinking field when think is\n // unset or misconfigured. The router's stripThinkingFromResponse() will\n // clean up any reasoning that leaks through, so we can be permissive here.\n let content = (message.content as string) || '';\n if (!content && message.thinking) {\n const thinking = (message.thinking as string) || '';\n if (thinking.length > 0) {\n logger.info(COMPONENT, `[ThinkingFallback] Content empty, using thinking field (${thinking.length} chars)`);\n content = thinking;\n }\n }\n // Strip leaked thinking tags from Qwen/DeepSeek models\n content = content.replace(/^[\\s\\S]*?<\\/think>\\s*/m, '').trim();\n\n return {\n id: uuid(),\n content,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: {\n promptTokens: (data.prompt_eval_count as number) || 0,\n completionTokens: (data.eval_count as number) || 0,\n totalTokens: ((data.prompt_eval_count as number) || 0) + ((data.eval_count as number) || 0),\n },\n finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',\n model: `ollama/${model}`,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const model = (options.model || 'llama3.1').replace('ollama/', '');\n\n // Cloud models (Ollama Pro): detect via -cloud suffix or :cloud tag\n const isCloudModel = model.includes('-cloud') || model.includes(':cloud');\n const hasTools = options.tools && options.tools.length > 0;\n const hasToolRoleMessages = options.messages.some(m => m.role === 'tool');\n\n const body: Record<string, unknown> = {\n model,\n messages: options.messages.map((m) => {\n const msg: Record<string, unknown> = { role: m.role };\n // Compress system prompts for cloud models with tools — preserve active tool descriptions\n if (m.role === 'system' && isCloudModel && hasTools) {\n const activeToolDescs = (options.tools ?? [])\n .filter(t => (t.function.description?.length ?? 0) > 200)\n .map(t => ({ name: t.function.name, description: t.function.description ?? '' }));\n msg.content = compressSystemPrompt(m.content, activeToolDescs.length > 0 ? activeToolDescs : undefined);\n } else {\n msg.content = m.content;\n }\n if (m.toolCalls && m.toolCalls.length > 0) {\n msg.tool_calls = m.toolCalls.map(tc => ({\n id: tc.id,\n type: tc.type || 'function',\n function: { name: tc.function.name, arguments: JSON.parse(tc.function.arguments || '{}') }\n }));\n }\n if (m.toolCallId) msg.tool_call_id = m.toolCallId;\n // Cloud models (Gemini API) require function_response.name to be non-empty\n if (m.role === 'tool') {\n msg.name = m.name || 'tool';\n } else if (m.name) {\n msg.name = m.name;\n }\n return msg;\n }),\n stream: true,\n keep_alive: '30m',\n options: {\n // v4.10.0-local (cost cap): 8K cloud cap matches non-stream path\n num_predict: clampMaxTokens(options.model || 'ollama/llama3.1', options.maxTokens),\n num_ctx: getModelCtx(model),\n temperature: options.temperature ?? 0.7,\n },\n };\n\n // Model capabilities — adapts behavior per model family\n const caps = getModelCapabilities(model);\n\n // Thinking mode: respect explicit setting, otherwise use model capabilities.\n // Disable for models that don't support thinking — prevents 400 errors.\n if (options.thinking === false) {\n body.think = false;\n } else if (!caps.thinkingWithTools) {\n body.think = false;\n } else if (options.thinking === true) {\n body.think = true;\n }\n\n // Per-turn override for tool-role turns (see chat() for rationale: vLLM #39611 / Z.ai docs).\n if (hasToolRoleMessages && body.think !== false) {\n const priorIntent = body.think === undefined ? 'unset' : String(body.think);\n body.think = false;\n logger.info(COMPONENT, `[ToolTurnThinkOverride] (stream) Forcing think=false for ${model} (tool-role message present, caller intent=${priorIntent})`);\n }\n\n if (hasTools) {\n body.tools = options.tools!.map((t) => ({\n type: 'function',\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: simplifySchema(t.function.parameters),\n },\n }));\n // Per-model optimal sampling for tool calling\n (body.options as Record<string, unknown>).temperature = options.temperature ?? caps.toolTemperature ?? 0.5;\n if (caps.toolTopP) (body.options as Record<string, unknown>).top_p = caps.toolTopP;\n if (caps.toolTopK) (body.options as Record<string, unknown>).top_k = caps.toolTopK;\n\n // Force tool_choice when requested — skip for models that self-select well\n if (options.forceToolUse && !caps.selfSelectsTools) {\n body.tool_choice = 'required';\n }\n }\n\n // Ollama-native structured outputs (stream variant).\n if (options.format !== undefined) {\n body.format = options.format;\n }\n\n // Optimize: trim history preserving tool pairs (cloud models only — local models have smaller contexts)\n if (isCloudModel && hasTools) {\n const msgs = body.messages as Array<Record<string, unknown>>;\n if (msgs.length > 80) {\n const trimmed = trimPreservingToolPairs(msgs, 80);\n logger.info(COMPONENT, `[Stream] Cloud model context trim: ${msgs.length} → ${trimmed.length} messages`);\n body.messages = trimmed;\n }\n }\n // Merge system into first user message only for models that need it\n if (hasTools && caps.needsSystemMerge) {\n const msgs2 = body.messages as Array<Record<string, unknown>>;\n const sysIdx = msgs2.findIndex(m => m.role === 'system');\n const firstUserIdx = msgs2.findIndex(m => m.role === 'user');\n if (sysIdx >= 0 && firstUserIdx >= 0 && msgs2[sysIdx].content) {\n const sysContent = msgs2[sysIdx].content as string;\n msgs2[firstUserIdx].content = `[System Instructions]\\n${sysContent}\\n\\n[User Message]\\n${msgs2[firstUserIdx].content}`;\n msgs2.splice(sysIdx, 1);\n }\n }\n\n try {\n // Cloud models need longer timeouts for streaming too\n const streamTimeoutMs = isCloudModel ? 300_000 : 120_000;\n let response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(streamTimeoutMs),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n // Fallback: if model doesn't support native tool calling, retry without tools\n if (response.status === 400 && errorText.includes('does not support tools') && body.tools) {\n logger.warn(COMPONENT, `Model ${model} does not support native tool calling — running in chat-only mode`);\n delete body.tools;\n response = await fetch(`${this.baseUrl}/api/chat`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(streamTimeoutMs),\n });\n if (!response.ok || !response.body) {\n const retryText = await response.text();\n yield { type: 'error', error: `Ollama error (${response.status}): ${retryText}` };\n return;\n }\n } else {\n yield { type: 'error', error: `Ollama error (${response.status}): ${errorText}` };\n return;\n }\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let insideThink = false;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const chunk = JSON.parse(line);\n // Handle thinking field for models that put content there\n // Some models (e.g. qwen3.5, nemotron-super:cloud) use the thinking field\n // even when think=false is set — treat thinking as content in that case\n if (!chunk.message?.content && chunk.message?.thinking) {\n if (body.think === false) {\n chunk.message.content = chunk.message.thinking;\n }\n }\n if (chunk.message?.content) {\n let text = chunk.message.content;\n // Strip leaked <think>...</think> blocks from Qwen/DeepSeek\n if (text.includes('<think>')) insideThink = true;\n if (insideThink) {\n if (text.includes('</think>')) {\n text = text.split('</think>').pop()?.trim() || '';\n insideThink = false;\n } else {\n continue; // suppress thinking content\n }\n }\n if (text) yield { type: 'text', content: text };\n }\n // v5.0.2: Only yield tool_calls if tools were sent in the request\n if (chunk.message?.tool_calls && options.tools && options.tools.length > 0) {\n for (const tc of chunk.message.tool_calls) {\n const fn = tc.function as Record<string, unknown>;\n yield {\n type: 'tool_call',\n toolCall: { id: uuid(), type: 'function', function: { name: fn.name as string, arguments: JSON.stringify(fn.arguments) } },\n };\n }\n }\n if (chunk.done) break;\n } catch { /* skip malformed NDJSON lines */ }\n }\n }\n yield { type: 'done' };\n } catch (error) {\n yield { type: 'error', error: (error as Error).message };\n }\n }\n\n async listModels(): Promise<string[]> {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n if (!response.ok) {\n // Hunt Finding #29 (2026-04-14): consume the body even on\n // error paths so the underlying socket can return to the\n // keep-alive pool. Without this, every non-200 response\n // leaks its socket until the GC gets around to it.\n await response.body?.cancel().catch(() => {});\n return [];\n }\n const data = await response.json() as { models?: Array<{ name: string }> };\n return (data.models || []).map((m) => m.name);\n } catch {\n return [];\n }\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}/api/tags`);\n const ok = response.ok;\n // Hunt Finding #29 (2026-04-14): ALWAYS consume or cancel the\n // body. Previously we returned response.ok directly, leaving the\n // body stream dangling and the socket held open.\n await response.body?.cancel().catch(() => {});\n return ok;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":["modelName"]}
|
package/dist/providers/openai.js
CHANGED
|
@@ -7,6 +7,7 @@ import logger from "../utils/logger.js";
|
|
|
7
7
|
import { fetchWithRetry } from "../utils/helpers.js";
|
|
8
8
|
import { resolveApiKey } from "./authResolver.js";
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
|
+
import { clampMaxTokens } from "./modelCapabilities.js";
|
|
10
11
|
const COMPONENT = "OpenAI";
|
|
11
12
|
class OpenAIProvider extends LLMProvider {
|
|
12
13
|
name = "openai";
|
|
@@ -51,9 +52,9 @@ class OpenAIProvider extends LLMProvider {
|
|
|
51
52
|
})
|
|
52
53
|
};
|
|
53
54
|
if (isReasoningModel) {
|
|
54
|
-
body.max_completion_tokens = options.maxTokens
|
|
55
|
+
body.max_completion_tokens = clampMaxTokens(model, options.maxTokens);
|
|
55
56
|
} else {
|
|
56
|
-
body.max_tokens = options.maxTokens
|
|
57
|
+
body.max_tokens = clampMaxTokens(model, options.maxTokens);
|
|
57
58
|
}
|
|
58
59
|
if (options.tools && options.tools.length > 0) {
|
|
59
60
|
body.tools = options.tools;
|
|
@@ -147,7 +148,7 @@ class OpenAIProvider extends LLMProvider {
|
|
|
147
148
|
if (isReasoningModel) {
|
|
148
149
|
body.max_completion_tokens = options.maxTokens || 8192;
|
|
149
150
|
} else {
|
|
150
|
-
body.max_tokens = options.maxTokens
|
|
151
|
+
body.max_tokens = clampMaxTokens(model, options.maxTokens);
|
|
151
152
|
}
|
|
152
153
|
if (options.tools && options.tools.length > 0) body.tools = options.tools;
|
|
153
154
|
if (options.temperature !== void 0 && !isReasoningModel) body.temperature = options.temperature;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/openai.ts"],"sourcesContent":["/**\n * TITAN — OpenAI Provider (GPT-4, o-series)\n */\nimport {\n LLMProvider,\n type ChatOptions,\n type ChatResponse,\n type ChatStreamChunk,\n type ToolCall,\n} from './base.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { fetchWithRetry } from '../utils/helpers.js';\nimport { resolveApiKey } from './authResolver.js';\nimport { v4 as uuid } from 'uuid';\n\nconst COMPONENT = 'OpenAI';\n\nexport class OpenAIProvider extends LLMProvider {\n readonly name = 'openai';\n readonly displayName = 'OpenAI (GPT)';\n\n private get apiKey(): string {\n const config = loadConfig();\n const p = config.providers.openai;\n return resolveApiKey('openai', p.authProfiles || [], p.apiKey || '', 'OPENAI_API_KEY', p.rotationStrategy, p.credentialCooldownMs);\n }\n\n private get baseUrl(): string {\n const config = loadConfig();\n return config.providers.openai.baseUrl || 'https://api.openai.com';\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const model = options.model || 'gpt-4o';\n const apiKey = this.apiKey;\n if (!apiKey) throw new Error('OpenAI API key not configured');\n\n logger.debug(COMPONENT, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n const cleanModel = model.replace('openai/', '');\n const isReasoningModel = /^(o1|o3|o4)/.test(cleanModel);\n\n const body: Record<string, unknown> = {\n model: cleanModel,\n messages: options.messages.map((m) => {\n if (m.role === 'tool') {\n return { role: 'tool', content: m.content, tool_call_id: m.toolCallId };\n }\n if (m.role === 'assistant' && m.toolCalls) {\n return {\n role: 'assistant',\n content: m.content || null,\n tool_calls: m.toolCalls.map((tc) => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.function.name, arguments: tc.function.arguments },\n })),\n };\n }\n // o-series reasoning models use 'developer' role instead of 'system'\n if (m.role === 'system' && isReasoningModel) {\n return { role: 'developer', content: m.content };\n }\n return { role: m.role, content: m.content };\n }),\n };\n\n // o-series models require max_completion_tokens, not max_tokens\n if (isReasoningModel) {\n body.max_completion_tokens = options.maxTokens || 8192;\n } else {\n body.max_tokens = options.maxTokens || 8192;\n }\n\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools;\n // Force at least one tool call on first round when task requires it.\n // Use \"auto\" for o-series (they manage tool use internally via reasoning).\n if (options.forceToolUse && !isReasoningModel) {\n body.tool_choice = 'required';\n }\n }\n\n // o-series models reject the temperature parameter\n if (options.temperature !== undefined && !isReasoningModel) {\n body.temperature = options.temperature;\n }\n\n // Reasoning effort for o-series models\n if (options.thinking && isReasoningModel) {\n const effortMap: Record<string, string> = { low: 'low', medium: 'medium', high: 'high' };\n body.reasoning_effort = effortMap[options.thinkingLevel || 'medium'] || 'medium';\n }\n\n const response = await fetchWithRetry(`${this.baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n // Hunt Finding #37: attach status + Retry-After so the router can respect backoff\n const { createProviderError } = await import('./errorTaxonomy.js');\n throw createProviderError('OpenAI API', response, errorText, { provider: 'openai', model });\n }\n\n const data = await response.json() as Record<string, unknown>;\n const choices = data.choices as Array<Record<string, unknown>> | undefined;\n\n if (!choices || choices.length === 0) {\n return {\n id: (data.id as string) || uuid(),\n content: '',\n usage: undefined,\n finishReason: 'stop',\n model,\n };\n }\n\n const choice = choices[0];\n const message = choice.message as Record<string, unknown>;\n\n const toolCalls: ToolCall[] = [];\n if (message.tool_calls) {\n for (const tc of message.tool_calls as Array<Record<string, unknown>>) {\n const fn = tc.function as Record<string, string>;\n toolCalls.push({\n id: tc.id as string,\n type: 'function',\n function: { name: fn.name, arguments: fn.arguments },\n });\n }\n }\n\n const usage = data.usage as { prompt_tokens: number; completion_tokens: number; total_tokens: number } | undefined;\n\n return {\n id: (data.id as string) || uuid(),\n content: (message.content as string) || '',\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: usage\n ? {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n }\n : undefined,\n finishReason: toolCalls.length > 0 ? 'tool_calls' : (choice.finish_reason as 'stop' | 'length') || 'stop',\n model,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const model = options.model || 'gpt-4o';\n const apiKey = this.apiKey;\n if (!apiKey) { yield { type: 'error', error: 'OpenAI API key not configured' }; return; }\n\n const cleanModel = model.replace('openai/', '');\n const isReasoningModel = /^(o1|o3|o4)/.test(cleanModel);\n\n const body: Record<string, unknown> = {\n model: cleanModel,\n stream: true,\n messages: options.messages.map((m) => {\n if (m.role === 'tool') return { role: 'tool', content: m.content, tool_call_id: m.toolCallId };\n if (m.role === 'assistant' && m.toolCalls) {\n return {\n role: 'assistant', content: m.content || null,\n tool_calls: m.toolCalls.map((tc) => ({ id: tc.id, type: 'function', function: { name: tc.function.name, arguments: tc.function.arguments } })),\n };\n }\n if (m.role === 'system' && isReasoningModel) return { role: 'developer', content: m.content };\n return { role: m.role, content: m.content };\n }),\n };\n\n if (isReasoningModel) { body.max_completion_tokens = options.maxTokens || 8192; }\n else { body.max_tokens = options.maxTokens || 8192; }\n if (options.tools && options.tools.length > 0) body.tools = options.tools;\n if (options.temperature !== undefined && !isReasoningModel) body.temperature = options.temperature;\n\n // Reasoning effort for o-series models\n if (options.thinking && isReasoningModel) {\n const effortMap: Record<string, string> = { low: 'low', medium: 'medium', high: 'high' };\n body.reasoning_effort = effortMap[options.thinkingLevel || 'medium'] || 'medium';\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },\n body: JSON.stringify(body),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n yield { type: 'error', error: `OpenAI API error (${response.status}): ${errorText}` };\n return;\n }\n\n const toolCalls = new Map<number, { id: string; name: string; args: string }>();\n yield* this.parseOpenAISSE(response.body, toolCalls);\n } catch (error) {\n yield { type: 'error', error: (error as Error).message };\n }\n }\n\n /** Parse OpenAI-format SSE stream and yield ChatStreamChunks */\n private async *parseOpenAISSE(\n body: ReadableStream<Uint8Array>,\n toolCalls: Map<number, { id: string; name: string; args: string }>,\n ): AsyncGenerator<ChatStreamChunk> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const json = line.slice(6).trim();\n if (json === '[DONE]') { break; }\n if (!json) continue;\n\n try {\n const chunk = JSON.parse(json);\n const delta = chunk.choices?.[0]?.delta;\n if (!delta) continue;\n\n if (delta.content) {\n yield { type: 'text', content: delta.content };\n }\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n if (!toolCalls.has(idx)) {\n toolCalls.set(idx, { id: tc.id || '', name: '', args: '' });\n }\n const entry = toolCalls.get(idx)!;\n if (tc.id) entry.id = tc.id;\n if (tc.function?.name) entry.name = tc.function.name;\n if (tc.function?.arguments) entry.args += tc.function.arguments;\n }\n }\n } catch { /* skip malformed lines */ }\n }\n }\n\n // Emit accumulated tool calls\n for (const [, tc] of toolCalls) {\n if (tc.id && tc.name) {\n yield { type: 'tool_call', toolCall: { id: tc.id, type: 'function', function: { name: tc.name, arguments: tc.args || '{}' } } };\n }\n }\n yield { type: 'done' };\n }\n\n async listModels(): Promise<string[]> {\n return ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1', 'o1-mini', 'o3-mini'];\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n if (!this.apiKey) return false;\n const response = await fetch(`${this.baseUrl}/v1/models`, {\n headers: { Authorization: `Bearer ${this.apiKey}` },\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/openai.ts"],"sourcesContent":["/**\n * TITAN — OpenAI Provider (GPT-4, o-series)\n */\nimport {\n LLMProvider,\n type ChatOptions,\n type ChatResponse,\n type ChatStreamChunk,\n type ToolCall,\n} from './base.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\nimport { fetchWithRetry } from '../utils/helpers.js';\nimport { resolveApiKey } from './authResolver.js';\nimport { v4 as uuid } from 'uuid';\nimport { clampMaxTokens } from './modelCapabilities.js';\n\nconst COMPONENT = 'OpenAI';\n\nexport class OpenAIProvider extends LLMProvider {\n readonly name = 'openai';\n readonly displayName = 'OpenAI (GPT)';\n\n private get apiKey(): string {\n const config = loadConfig();\n const p = config.providers.openai;\n return resolveApiKey('openai', p.authProfiles || [], p.apiKey || '', 'OPENAI_API_KEY', p.rotationStrategy, p.credentialCooldownMs);\n }\n\n private get baseUrl(): string {\n const config = loadConfig();\n return config.providers.openai.baseUrl || 'https://api.openai.com';\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const model = options.model || 'gpt-4o';\n const apiKey = this.apiKey;\n if (!apiKey) throw new Error('OpenAI API key not configured');\n\n logger.debug(COMPONENT, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n const cleanModel = model.replace('openai/', '');\n const isReasoningModel = /^(o1|o3|o4)/.test(cleanModel);\n\n const body: Record<string, unknown> = {\n model: cleanModel,\n messages: options.messages.map((m) => {\n if (m.role === 'tool') {\n return { role: 'tool', content: m.content, tool_call_id: m.toolCallId };\n }\n if (m.role === 'assistant' && m.toolCalls) {\n return {\n role: 'assistant',\n content: m.content || null,\n tool_calls: m.toolCalls.map((tc) => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.function.name, arguments: tc.function.arguments },\n })),\n };\n }\n // o-series reasoning models use 'developer' role instead of 'system'\n if (m.role === 'system' && isReasoningModel) {\n return { role: 'developer', content: m.content };\n }\n return { role: m.role, content: m.content };\n }),\n };\n\n // o-series models require max_completion_tokens, not max_tokens\n if (isReasoningModel) {\n body.max_completion_tokens = clampMaxTokens(model, options.maxTokens);\n } else {\n body.max_tokens = clampMaxTokens(model, options.maxTokens);\n }\n\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools;\n // Force at least one tool call on first round when task requires it.\n // Use \"auto\" for o-series (they manage tool use internally via reasoning).\n if (options.forceToolUse && !isReasoningModel) {\n body.tool_choice = 'required';\n }\n }\n\n // o-series models reject the temperature parameter\n if (options.temperature !== undefined && !isReasoningModel) {\n body.temperature = options.temperature;\n }\n\n // Reasoning effort for o-series models\n if (options.thinking && isReasoningModel) {\n const effortMap: Record<string, string> = { low: 'low', medium: 'medium', high: 'high' };\n body.reasoning_effort = effortMap[options.thinkingLevel || 'medium'] || 'medium';\n }\n\n const response = await fetchWithRetry(`${this.baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n // Hunt Finding #37: attach status + Retry-After so the router can respect backoff\n const { createProviderError } = await import('./errorTaxonomy.js');\n throw createProviderError('OpenAI API', response, errorText, { provider: 'openai', model });\n }\n\n const data = await response.json() as Record<string, unknown>;\n const choices = data.choices as Array<Record<string, unknown>> | undefined;\n\n if (!choices || choices.length === 0) {\n return {\n id: (data.id as string) || uuid(),\n content: '',\n usage: undefined,\n finishReason: 'stop',\n model,\n };\n }\n\n const choice = choices[0];\n const message = choice.message as Record<string, unknown>;\n\n const toolCalls: ToolCall[] = [];\n if (message.tool_calls) {\n for (const tc of message.tool_calls as Array<Record<string, unknown>>) {\n const fn = tc.function as Record<string, string>;\n toolCalls.push({\n id: tc.id as string,\n type: 'function',\n function: { name: fn.name, arguments: fn.arguments },\n });\n }\n }\n\n const usage = data.usage as { prompt_tokens: number; completion_tokens: number; total_tokens: number } | undefined;\n\n return {\n id: (data.id as string) || uuid(),\n content: (message.content as string) || '',\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: usage\n ? {\n promptTokens: usage.prompt_tokens,\n completionTokens: usage.completion_tokens,\n totalTokens: usage.total_tokens,\n }\n : undefined,\n finishReason: toolCalls.length > 0 ? 'tool_calls' : (choice.finish_reason as 'stop' | 'length') || 'stop',\n model,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const model = options.model || 'gpt-4o';\n const apiKey = this.apiKey;\n if (!apiKey) { yield { type: 'error', error: 'OpenAI API key not configured' }; return; }\n\n const cleanModel = model.replace('openai/', '');\n const isReasoningModel = /^(o1|o3|o4)/.test(cleanModel);\n\n const body: Record<string, unknown> = {\n model: cleanModel,\n stream: true,\n messages: options.messages.map((m) => {\n if (m.role === 'tool') return { role: 'tool', content: m.content, tool_call_id: m.toolCallId };\n if (m.role === 'assistant' && m.toolCalls) {\n return {\n role: 'assistant', content: m.content || null,\n tool_calls: m.toolCalls.map((tc) => ({ id: tc.id, type: 'function', function: { name: tc.function.name, arguments: tc.function.arguments } })),\n };\n }\n if (m.role === 'system' && isReasoningModel) return { role: 'developer', content: m.content };\n return { role: m.role, content: m.content };\n }),\n };\n\n if (isReasoningModel) { body.max_completion_tokens = options.maxTokens || 8192; }\n else { body.max_tokens = clampMaxTokens(model, options.maxTokens); }\n if (options.tools && options.tools.length > 0) body.tools = options.tools;\n if (options.temperature !== undefined && !isReasoningModel) body.temperature = options.temperature;\n\n // Reasoning effort for o-series models\n if (options.thinking && isReasoningModel) {\n const effortMap: Record<string, string> = { low: 'low', medium: 'medium', high: 'high' };\n body.reasoning_effort = effortMap[options.thinkingLevel || 'medium'] || 'medium';\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },\n body: JSON.stringify(body),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n yield { type: 'error', error: `OpenAI API error (${response.status}): ${errorText}` };\n return;\n }\n\n const toolCalls = new Map<number, { id: string; name: string; args: string }>();\n yield* this.parseOpenAISSE(response.body, toolCalls);\n } catch (error) {\n yield { type: 'error', error: (error as Error).message };\n }\n }\n\n /** Parse OpenAI-format SSE stream and yield ChatStreamChunks */\n private async *parseOpenAISSE(\n body: ReadableStream<Uint8Array>,\n toolCalls: Map<number, { id: string; name: string; args: string }>,\n ): AsyncGenerator<ChatStreamChunk> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue;\n const json = line.slice(6).trim();\n if (json === '[DONE]') { break; }\n if (!json) continue;\n\n try {\n const chunk = JSON.parse(json);\n const delta = chunk.choices?.[0]?.delta;\n if (!delta) continue;\n\n if (delta.content) {\n yield { type: 'text', content: delta.content };\n }\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n if (!toolCalls.has(idx)) {\n toolCalls.set(idx, { id: tc.id || '', name: '', args: '' });\n }\n const entry = toolCalls.get(idx)!;\n if (tc.id) entry.id = tc.id;\n if (tc.function?.name) entry.name = tc.function.name;\n if (tc.function?.arguments) entry.args += tc.function.arguments;\n }\n }\n } catch { /* skip malformed lines */ }\n }\n }\n\n // Emit accumulated tool calls\n for (const [, tc] of toolCalls) {\n if (tc.id && tc.name) {\n yield { type: 'tool_call', toolCall: { id: tc.id, type: 'function', function: { name: tc.name, arguments: tc.args || '{}' } } };\n }\n }\n yield { type: 'done' };\n }\n\n async listModels(): Promise<string[]> {\n return ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1', 'o1-mini', 'o3-mini'];\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n if (!this.apiKey) return false;\n const response = await fetch(`${this.baseUrl}/v1/models`, {\n headers: { Authorization: `Bearer ${this.apiKey}` },\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -7,6 +7,7 @@ import logger from "../utils/logger.js";
|
|
|
7
7
|
import { fetchWithRetry } from "../utils/helpers.js";
|
|
8
8
|
import { resolveApiKey } from "./authResolver.js";
|
|
9
9
|
import { v4 as uuid } from "uuid";
|
|
10
|
+
import { clampMaxTokens } from "./modelCapabilities.js";
|
|
10
11
|
class OpenAICompatProvider extends LLMProvider {
|
|
11
12
|
name;
|
|
12
13
|
displayName;
|
|
@@ -60,7 +61,7 @@ class OpenAICompatProvider extends LLMProvider {
|
|
|
60
61
|
}
|
|
61
62
|
return { role: m.role, content: m.content || " " };
|
|
62
63
|
}),
|
|
63
|
-
max_tokens: options.maxTokens
|
|
64
|
+
max_tokens: clampMaxTokens(model, options.maxTokens)
|
|
64
65
|
};
|
|
65
66
|
if (options.tools && options.tools.length > 0) {
|
|
66
67
|
body.tools = options.tools;
|
|
@@ -144,7 +145,7 @@ class OpenAICompatProvider extends LLMProvider {
|
|
|
144
145
|
}
|
|
145
146
|
return { role: m.role, content: m.content || " " };
|
|
146
147
|
}),
|
|
147
|
-
max_tokens: options.maxTokens
|
|
148
|
+
max_tokens: clampMaxTokens(model, options.maxTokens)
|
|
148
149
|
};
|
|
149
150
|
if (options.tools && options.tools.length > 0) body.tools = options.tools;
|
|
150
151
|
if (options.temperature !== void 0) body.temperature = options.temperature;
|