titan-agent 5.6.1 → 5.6.3

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.
@@ -230,13 +230,47 @@ class AnthropicProvider extends LLMProvider {
230
230
  }
231
231
  }
232
232
  async listModels() {
233
- return [
233
+ const FALLBACK = [
234
+ // Claude 4.x family (2025-2026)
235
+ "claude-opus-4-7",
236
+ "claude-opus-4-6",
237
+ "claude-opus-4-5",
234
238
  "claude-opus-4-0",
239
+ "claude-sonnet-4-6",
240
+ "claude-sonnet-4-5",
235
241
  "claude-sonnet-4-20250514",
242
+ "claude-haiku-4-6",
243
+ "claude-haiku-4-5-20251001",
236
244
  "claude-haiku-4-20250414",
245
+ // Claude 3.7 / 3.5 / 3.0 (still production-supported)
246
+ "claude-3-7-sonnet-20250219",
237
247
  "claude-3-5-sonnet-20241022",
238
- "claude-3-5-haiku-20241022"
248
+ "claude-3-5-sonnet-20240620",
249
+ "claude-3-5-haiku-20241022",
250
+ "claude-3-opus-20240229",
251
+ "claude-3-sonnet-20240229",
252
+ "claude-3-haiku-20240307"
239
253
  ];
254
+ if (!this.apiKey) return FALLBACK;
255
+ try {
256
+ const response = await fetch(`${this.baseUrl}/v1/models?limit=1000`, {
257
+ headers: {
258
+ "x-api-key": this.apiKey,
259
+ "anthropic-version": "2023-06-01"
260
+ },
261
+ signal: AbortSignal.timeout(5e3)
262
+ });
263
+ if (!response.ok) {
264
+ logger.debug(COMPONENT, `listModels: ${response.status} from /v1/models, using fallback`);
265
+ return FALLBACK;
266
+ }
267
+ const data = await response.json();
268
+ const ids = (data.data || []).map((m) => m.id).filter(Boolean);
269
+ return ids.length > 0 ? ids : FALLBACK;
270
+ } catch (err) {
271
+ logger.debug(COMPONENT, `listModels failed: ${err.message}, using fallback`);
272
+ return FALLBACK;
273
+ }
240
274
  }
241
275
  async healthCheck() {
242
276
  try {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/anthropic.ts"],"sourcesContent":["/**\n * TITAN — Anthropic/Claude Provider\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 = 'Anthropic';\n\nexport class AnthropicProvider extends LLMProvider {\n readonly name = 'anthropic';\n readonly displayName = 'Anthropic (Claude)';\n\n private get apiKey(): string {\n const config = loadConfig();\n const p = config.providers.anthropic;\n return resolveApiKey('anthropic', p.authProfiles || [], p.apiKey || '', 'ANTHROPIC_API_KEY', p.rotationStrategy, p.credentialCooldownMs);\n }\n\n private get baseUrl(): string {\n const config = loadConfig();\n return config.providers.anthropic.baseUrl || 'https://api.anthropic.com';\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const model = options.model || 'claude-sonnet-4-20250514';\n const apiKey = this.apiKey;\n if (!apiKey) throw new Error('Anthropic API key not configured');\n\n logger.debug(COMPONENT, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n const systemMessage = options.messages.find((m) => m.role === 'system');\n const nonSystemMessages = options.messages.filter((m) => m.role !== 'system');\n\n const body: Record<string, unknown> = {\n model: model.replace('anthropic/', ''),\n max_tokens: clampMaxTokens(model, options.maxTokens),\n messages: nonSystemMessages.map((m) => ({\n role: m.role === 'tool' ? 'user' : m.role,\n content: m.role === 'tool'\n ? [{ type: 'tool_result', tool_use_id: m.toolCallId, content: m.content }]\n : m.content,\n })),\n };\n\n if (systemMessage) {\n // TITAN pattern: prompt cache splitting\n // Place cache_control breakpoint on system prompt to cache it across turns.\n // This reduces input costs by ~75% for subsequent messages in the same session.\n body.system = [\n {\n type: 'text',\n text: systemMessage.content,\n cache_control: { type: 'ephemeral' },\n },\n ];\n }\n\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools.map((t) => ({\n name: t.function.name,\n description: t.function.description,\n input_schema: t.function.parameters,\n }));\n // Force at least one tool call on first round when task requires it.\n // Cannot combine tool_choice:any with extended thinking — skip if thinking enabled.\n if (options.forceToolUse && !options.thinking) {\n body.tool_choice = { type: 'any' };\n }\n }\n\n if (options.temperature !== undefined) {\n body.temperature = options.temperature;\n }\n\n // Extended thinking support\n if (options.thinking) {\n const budgetMap: Record<string, number> = { low: 1024, medium: 4096, high: 16384 };\n const budgetTokens = budgetMap[options.thinkingLevel || 'medium'] || 4096;\n body.thinking = { type: 'enabled', budget_tokens: budgetTokens };\n }\n\n const response = await fetchWithRetry(`${this.baseUrl}/v1/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\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('Anthropic API', response, errorText, { provider: 'anthropic', model });\n }\n\n const data = await response.json() as Record<string, unknown>;\n const content = data.content as Array<Record<string, unknown>> | undefined;\n\n let textContent = '';\n const toolCalls: ToolCall[] = [];\n\n if (!content || !Array.isArray(content)) {\n return {\n id: (data.id as string) || uuid(),\n content: '',\n usage: undefined,\n finishReason: 'stop',\n model,\n };\n }\n\n for (const block of content) {\n if (block.type === 'text') {\n textContent += block.text as string;\n } else if (block.type === 'tool_use') {\n toolCalls.push({\n id: block.id as string,\n type: 'function',\n function: {\n name: block.name as string,\n arguments: JSON.stringify(block.input),\n },\n });\n }\n }\n\n const usage = data.usage as { input_tokens: number; output_tokens: number } | undefined;\n\n return {\n id: (data.id as string) || uuid(),\n content: textContent,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: usage\n ? {\n promptTokens: usage.input_tokens,\n completionTokens: usage.output_tokens,\n totalTokens: usage.input_tokens + usage.output_tokens,\n }\n : undefined,\n finishReason: (() => {\n const sr = data.stop_reason as string | undefined;\n if (sr === 'max_tokens') return 'length';\n if (sr === 'tool_use') return 'tool_calls';\n return toolCalls.length > 0 ? 'tool_calls' : 'stop';\n })(),\n model,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const model = options.model || 'claude-sonnet-4-20250514';\n const apiKey = this.apiKey;\n if (!apiKey) { yield { type: 'error', error: 'Anthropic API key not configured' }; return; }\n\n const systemMessage = options.messages.find((m) => m.role === 'system');\n const nonSystemMessages = options.messages.filter((m) => m.role !== 'system');\n\n const body: Record<string, unknown> = {\n model: model.replace('anthropic/', ''),\n max_tokens: clampMaxTokens(model, options.maxTokens),\n stream: true,\n messages: nonSystemMessages.map((m) => ({\n role: m.role === 'tool' ? 'user' : m.role,\n content: m.role === 'tool'\n ? [{ type: 'tool_result', tool_use_id: m.toolCallId, content: m.content }]\n : m.content,\n })),\n };\n\n if (systemMessage) {\n body.system = [{ type: 'text', text: systemMessage.content, cache_control: { type: 'ephemeral' } }];\n }\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools.map((t) => ({\n name: t.function.name,\n description: t.function.description,\n input_schema: t.function.parameters,\n }));\n }\n if (options.temperature !== undefined) body.temperature = options.temperature;\n\n // Extended thinking support\n if (options.thinking) {\n const budgetMap: Record<string, number> = { low: 1024, medium: 4096, high: 16384 };\n const budgetTokens = budgetMap[options.thinkingLevel || 'medium'] || 4096;\n body.thinking = { type: 'enabled', budget_tokens: budgetTokens };\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/v1/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n yield { type: 'error', error: `Anthropic API error (${response.status}): ${errorText}` };\n return;\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let currentToolId = '';\n let currentToolName = '';\n let toolArgsBuffer = '';\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]' || !json) continue;\n\n try {\n const event = JSON.parse(json);\n if (event.type === 'content_block_delta') {\n const delta = event.delta;\n if (delta.type === 'text_delta' && delta.text) {\n yield { type: 'text', content: delta.text };\n } else if (delta.type === 'input_json_delta' && delta.partial_json) {\n toolArgsBuffer += delta.partial_json;\n }\n } else if (event.type === 'content_block_start') {\n const block = event.content_block;\n if (block?.type === 'tool_use') {\n currentToolId = block.id;\n currentToolName = block.name;\n toolArgsBuffer = '';\n }\n } else if (event.type === 'content_block_stop') {\n if (currentToolId) {\n yield {\n type: 'tool_call',\n toolCall: {\n id: currentToolId,\n type: 'function',\n function: { name: currentToolName, arguments: toolArgsBuffer || '{}' },\n },\n };\n currentToolId = '';\n toolArgsBuffer = '';\n }\n }\n } catch { /* skip malformed SSE 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 return [\n 'claude-opus-4-0',\n 'claude-sonnet-4-20250514',\n 'claude-haiku-4-20250414',\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-haiku-20241022',\n ];\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n if (!this.apiKey) return false;\n const response = await fetch(`${this.baseUrl}/v1/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: 'claude-haiku-4-20250414',\n max_tokens: 1,\n messages: [{ role: 'user', content: 'ping' }],\n }),\n });\n return response.ok || response.status === 400; // 400 = valid auth but bad request\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/providers/anthropic.ts"],"sourcesContent":["/**\n * TITAN — Anthropic/Claude Provider\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 = 'Anthropic';\n\nexport class AnthropicProvider extends LLMProvider {\n readonly name = 'anthropic';\n readonly displayName = 'Anthropic (Claude)';\n\n private get apiKey(): string {\n const config = loadConfig();\n const p = config.providers.anthropic;\n return resolveApiKey('anthropic', p.authProfiles || [], p.apiKey || '', 'ANTHROPIC_API_KEY', p.rotationStrategy, p.credentialCooldownMs);\n }\n\n private get baseUrl(): string {\n const config = loadConfig();\n return config.providers.anthropic.baseUrl || 'https://api.anthropic.com';\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const model = options.model || 'claude-sonnet-4-20250514';\n const apiKey = this.apiKey;\n if (!apiKey) throw new Error('Anthropic API key not configured');\n\n logger.debug(COMPONENT, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n const systemMessage = options.messages.find((m) => m.role === 'system');\n const nonSystemMessages = options.messages.filter((m) => m.role !== 'system');\n\n const body: Record<string, unknown> = {\n model: model.replace('anthropic/', ''),\n max_tokens: clampMaxTokens(model, options.maxTokens),\n messages: nonSystemMessages.map((m) => ({\n role: m.role === 'tool' ? 'user' : m.role,\n content: m.role === 'tool'\n ? [{ type: 'tool_result', tool_use_id: m.toolCallId, content: m.content }]\n : m.content,\n })),\n };\n\n if (systemMessage) {\n // TITAN pattern: prompt cache splitting\n // Place cache_control breakpoint on system prompt to cache it across turns.\n // This reduces input costs by ~75% for subsequent messages in the same session.\n body.system = [\n {\n type: 'text',\n text: systemMessage.content,\n cache_control: { type: 'ephemeral' },\n },\n ];\n }\n\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools.map((t) => ({\n name: t.function.name,\n description: t.function.description,\n input_schema: t.function.parameters,\n }));\n // Force at least one tool call on first round when task requires it.\n // Cannot combine tool_choice:any with extended thinking — skip if thinking enabled.\n if (options.forceToolUse && !options.thinking) {\n body.tool_choice = { type: 'any' };\n }\n }\n\n if (options.temperature !== undefined) {\n body.temperature = options.temperature;\n }\n\n // Extended thinking support\n if (options.thinking) {\n const budgetMap: Record<string, number> = { low: 1024, medium: 4096, high: 16384 };\n const budgetTokens = budgetMap[options.thinkingLevel || 'medium'] || 4096;\n body.thinking = { type: 'enabled', budget_tokens: budgetTokens };\n }\n\n const response = await fetchWithRetry(`${this.baseUrl}/v1/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\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('Anthropic API', response, errorText, { provider: 'anthropic', model });\n }\n\n const data = await response.json() as Record<string, unknown>;\n const content = data.content as Array<Record<string, unknown>> | undefined;\n\n let textContent = '';\n const toolCalls: ToolCall[] = [];\n\n if (!content || !Array.isArray(content)) {\n return {\n id: (data.id as string) || uuid(),\n content: '',\n usage: undefined,\n finishReason: 'stop',\n model,\n };\n }\n\n for (const block of content) {\n if (block.type === 'text') {\n textContent += block.text as string;\n } else if (block.type === 'tool_use') {\n toolCalls.push({\n id: block.id as string,\n type: 'function',\n function: {\n name: block.name as string,\n arguments: JSON.stringify(block.input),\n },\n });\n }\n }\n\n const usage = data.usage as { input_tokens: number; output_tokens: number } | undefined;\n\n return {\n id: (data.id as string) || uuid(),\n content: textContent,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: usage\n ? {\n promptTokens: usage.input_tokens,\n completionTokens: usage.output_tokens,\n totalTokens: usage.input_tokens + usage.output_tokens,\n }\n : undefined,\n finishReason: (() => {\n const sr = data.stop_reason as string | undefined;\n if (sr === 'max_tokens') return 'length';\n if (sr === 'tool_use') return 'tool_calls';\n return toolCalls.length > 0 ? 'tool_calls' : 'stop';\n })(),\n model,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const model = options.model || 'claude-sonnet-4-20250514';\n const apiKey = this.apiKey;\n if (!apiKey) { yield { type: 'error', error: 'Anthropic API key not configured' }; return; }\n\n const systemMessage = options.messages.find((m) => m.role === 'system');\n const nonSystemMessages = options.messages.filter((m) => m.role !== 'system');\n\n const body: Record<string, unknown> = {\n model: model.replace('anthropic/', ''),\n max_tokens: clampMaxTokens(model, options.maxTokens),\n stream: true,\n messages: nonSystemMessages.map((m) => ({\n role: m.role === 'tool' ? 'user' : m.role,\n content: m.role === 'tool'\n ? [{ type: 'tool_result', tool_use_id: m.toolCallId, content: m.content }]\n : m.content,\n })),\n };\n\n if (systemMessage) {\n body.system = [{ type: 'text', text: systemMessage.content, cache_control: { type: 'ephemeral' } }];\n }\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools.map((t) => ({\n name: t.function.name,\n description: t.function.description,\n input_schema: t.function.parameters,\n }));\n }\n if (options.temperature !== undefined) body.temperature = options.temperature;\n\n // Extended thinking support\n if (options.thinking) {\n const budgetMap: Record<string, number> = { low: 1024, medium: 4096, high: 16384 };\n const budgetTokens = budgetMap[options.thinkingLevel || 'medium'] || 4096;\n body.thinking = { type: 'enabled', budget_tokens: budgetTokens };\n }\n\n try {\n const response = await fetch(`${this.baseUrl}/v1/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n yield { type: 'error', error: `Anthropic API error (${response.status}): ${errorText}` };\n return;\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n let currentToolId = '';\n let currentToolName = '';\n let toolArgsBuffer = '';\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]' || !json) continue;\n\n try {\n const event = JSON.parse(json);\n if (event.type === 'content_block_delta') {\n const delta = event.delta;\n if (delta.type === 'text_delta' && delta.text) {\n yield { type: 'text', content: delta.text };\n } else if (delta.type === 'input_json_delta' && delta.partial_json) {\n toolArgsBuffer += delta.partial_json;\n }\n } else if (event.type === 'content_block_start') {\n const block = event.content_block;\n if (block?.type === 'tool_use') {\n currentToolId = block.id;\n currentToolName = block.name;\n toolArgsBuffer = '';\n }\n } else if (event.type === 'content_block_stop') {\n if (currentToolId) {\n yield {\n type: 'tool_call',\n toolCall: {\n id: currentToolId,\n type: 'function',\n function: { name: currentToolName, arguments: toolArgsBuffer || '{}' },\n },\n };\n currentToolId = '';\n toolArgsBuffer = '';\n }\n }\n } catch { /* skip malformed SSE 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 // Hardcoded comprehensive fallback used when no key is configured\n // or the live discovery call fails. Covers every Claude API model\n // ID currently in production as of 2026-05. Keeps the picker\n // useful even on a fresh-clone TITAN with no key — the user can\n // browse and choose, then configure the key. Live discovery (when\n // a key IS configured) will replace this with whatever the upstream\n // /v1/models returns, which may include models the key doesn't\n // have access to.\n const FALLBACK = [\n // Claude 4.x family (2025-2026)\n 'claude-opus-4-7',\n 'claude-opus-4-6',\n 'claude-opus-4-5',\n 'claude-opus-4-0',\n 'claude-sonnet-4-6',\n 'claude-sonnet-4-5',\n 'claude-sonnet-4-20250514',\n 'claude-haiku-4-6',\n 'claude-haiku-4-5-20251001',\n 'claude-haiku-4-20250414',\n // Claude 3.7 / 3.5 / 3.0 (still production-supported)\n 'claude-3-7-sonnet-20250219',\n 'claude-3-5-sonnet-20241022',\n 'claude-3-5-sonnet-20240620',\n 'claude-3-5-haiku-20241022',\n 'claude-3-opus-20240229',\n 'claude-3-sonnet-20240229',\n 'claude-3-haiku-20240307',\n ];\n if (!this.apiKey) return FALLBACK;\n\n // Live discovery — Anthropic's /v1/models is paginated (page size 1000)\n // and returns objects shaped like `{ id, type: 'model', display_name, created_at }`.\n try {\n const response = await fetch(`${this.baseUrl}/v1/models?limit=1000`, {\n headers: {\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n },\n signal: AbortSignal.timeout(5000),\n });\n if (!response.ok) {\n logger.debug(COMPONENT, `listModels: ${response.status} from /v1/models, using fallback`);\n return FALLBACK;\n }\n const data = await response.json() as { data?: Array<{ id: string }> };\n const ids = (data.data || []).map((m) => m.id).filter(Boolean);\n return ids.length > 0 ? ids : FALLBACK;\n } catch (err) {\n logger.debug(COMPONENT, `listModels failed: ${(err as Error).message}, using fallback`);\n return FALLBACK;\n }\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n if (!this.apiKey) return false;\n const response = await fetch(`${this.baseUrl}/v1/messages`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: 'claude-haiku-4-20250414',\n max_tokens: 1,\n messages: [{ role: 'user', content: 'ping' }],\n }),\n });\n return response.ok || response.status === 400; // 400 = valid auth but bad request\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -251,7 +251,46 @@ class GoogleProvider extends LLMProvider {
251
251
  }
252
252
  }
253
253
  async listModels() {
254
- return ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash", "gemini-1.5-pro"];
254
+ const FALLBACK = [
255
+ // Gemini 3.x (2026)
256
+ "gemini-3.1-pro",
257
+ "gemini-3.1-flash",
258
+ "gemini-3.1-flash-lite",
259
+ "gemini-3.0-pro",
260
+ "gemini-3.0-flash",
261
+ // Gemini 2.5 / 2.0
262
+ "gemini-2.5-pro",
263
+ "gemini-2.5-flash",
264
+ "gemini-2.5-flash-lite",
265
+ "gemini-2.0-flash",
266
+ "gemini-2.0-flash-lite",
267
+ "gemini-2.0-flash-thinking-exp",
268
+ "gemini-2.0-pro-exp",
269
+ // Gemini 1.5 (still production-supported)
270
+ "gemini-1.5-pro",
271
+ "gemini-1.5-pro-002",
272
+ "gemini-1.5-flash",
273
+ "gemini-1.5-flash-002",
274
+ "gemini-1.5-flash-8b"
275
+ ];
276
+ if (!this.apiKey) return FALLBACK;
277
+ try {
278
+ const response = await fetch("https://generativelanguage.googleapis.com/v1beta/models?pageSize=200", {
279
+ headers: { "x-goog-api-key": this.apiKey },
280
+ signal: AbortSignal.timeout(5e3)
281
+ });
282
+ if (!response.ok) {
283
+ logger.debug(COMPONENT, `listModels: ${response.status} from /v1beta/models, using fallback`);
284
+ return FALLBACK;
285
+ }
286
+ const data = await response.json();
287
+ const ids = (data.models || []).filter((m) => (m.supportedGenerationMethods || []).includes("generateContent")).map((m) => m.name.replace(/^models\//, "")).filter((id) => /gemini/i.test(id) && !/embedding|tts|image/i.test(id));
288
+ ids.sort().reverse();
289
+ return ids.length > 0 ? ids : FALLBACK;
290
+ } catch (err) {
291
+ logger.debug(COMPONENT, `listModels failed: ${err.message}, using fallback`);
292
+ return FALLBACK;
293
+ }
255
294
  }
256
295
  async healthCheck() {
257
296
  try {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/google.ts"],"sourcesContent":["/**\n * TITAN — Google Gemini Provider\n */\nimport {\n LLMProvider,\n type ChatOptions,\n type ChatMessage,\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';\nimport { mkdirSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nconst COMPONENT = 'Google';\n\n/**\n * When true, every Gemini request body that fails serialization-validation OR\n * gets a non-2xx response is dumped to ~/.titan/debug/gemini-requests/ for\n * post-mortem. Toggled via `GOOGLE_DUMP_REQUEST_BODY=1` env var or the\n * provider's `dumpRequestBody` config flag — keeps it off by default since\n * each dump is a JSON file with full prompt content.\n */\nfunction shouldDumpRequestBody(): boolean {\n if (process.env.GOOGLE_DUMP_REQUEST_BODY === '1' || process.env.GOOGLE_DUMP_REQUEST_BODY === 'true') {\n return true;\n }\n try {\n const cfg = loadConfig();\n const p = (cfg.providers as Record<string, unknown> | undefined)?.google as\n | { dumpRequestBody?: boolean }\n | undefined;\n return Boolean(p?.dumpRequestBody);\n } catch {\n return false;\n }\n}\n\nconst GEMINI_DEBUG_DIR = join(homedir(), '.titan', 'debug', 'gemini-requests');\n\nfunction dumpRequestBody(reason: string, body: unknown, extra?: Record<string, unknown>): void {\n if (!shouldDumpRequestBody()) return;\n try {\n mkdirSync(GEMINI_DEBUG_DIR, { recursive: true });\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const path = join(GEMINI_DEBUG_DIR, `${stamp}-${reason}.json`);\n writeFileSync(path, JSON.stringify({ reason, body, ...(extra ?? {}) }, null, 2));\n logger.info(COMPONENT, `Dumped Gemini request body → ${path}`);\n } catch (err) {\n logger.warn(COMPONENT, `Failed to dump Gemini request body: ${(err as Error).message}`);\n }\n}\n\n/**\n * Build the Gemini `contents[]` array from TITAN ChatMessages, with strict\n * pre-serialization validation of `tool` messages.\n *\n * Why this matters:\n * Gemini's `functionResponse` requires a non-empty `name` field paired with\n * a valid `tool_call_id` from a prior assistant turn. If the agent loop\n * ever emits a tool result whose corresponding tool call cannot be located\n * in conversation history, Gemini rejects the whole request with a 400 and\n * the error message is opaque (\"function_response without function_call\").\n *\n * Rather than push the malformed message and let Gemini blow up, we:\n * 1. Build a map of every tool_call.id → name from prior assistant messages.\n * 2. For each `tool` message, ensure (a) the name is non-empty (use the\n * toolCallId map as a backstop) and (b) the toolCallId references a\n * known prior call.\n * 3. Drop or relabel messages that fail validation, with a warning that\n * names the offending message so it shows up in logs.\n * 4. If `dumpRequestBody` is enabled, write the full pre-validation body\n * to disk for inspection before any silent corrections.\n */\nfunction buildContents(messages: ChatMessage[]): { contents: Array<Record<string, unknown>>; corrections: number } {\n // Pass 1: build a lookup of valid tool_call_id → function name from\n // every prior assistant turn that emitted toolCalls.\n const toolCallNameById = new Map<string, string>();\n for (const m of messages) {\n if (m.role === 'assistant' && Array.isArray(m.toolCalls)) {\n for (const tc of m.toolCalls) {\n if (tc.id && tc.function?.name) {\n toolCallNameById.set(tc.id, tc.function.name);\n }\n }\n }\n }\n\n let corrections = 0;\n const contents: Array<Record<string, unknown>> = [];\n\n for (const m of messages.filter((x) => x.role !== 'system')) {\n if (m.role === 'tool') {\n // Validation: name must be non-empty AND toolCallId must reference\n // a known prior call. Either failure → log + best-effort repair.\n const callId = m.toolCallId || '';\n const recordedName = callId ? toolCallNameById.get(callId) : undefined;\n const claimedName = (m.name || '').trim();\n\n if (!recordedName) {\n logger.warn(\n COMPONENT,\n `Malformed tool message: tool_call_id=\"${callId}\" has no matching prior tool_call. ` +\n `name=\"${claimedName}\". Dropping to prevent Gemini 400.`,\n );\n corrections++;\n continue;\n }\n\n const finalName = claimedName || recordedName;\n if (!claimedName) {\n logger.warn(\n COMPONENT,\n `Tool message missing name for tool_call_id=\"${callId}\"; ` +\n `inferred \"${finalName}\" from assistant history.`,\n );\n corrections++;\n } else if (claimedName !== recordedName) {\n logger.warn(\n COMPONENT,\n `Tool message name mismatch for tool_call_id=\"${callId}\": ` +\n `claimed \"${claimedName}\" but tool_call recorded \"${recordedName}\". Using recorded.`,\n );\n corrections++;\n }\n\n contents.push({\n role: 'function' as const,\n parts: [{ functionResponse: { name: recordedName, response: { result: m.content } } }],\n });\n continue;\n }\n\n contents.push({\n role: (m.role === 'assistant' ? 'model' : 'user') as string,\n parts: [{ text: m.content }],\n });\n }\n\n return { contents, corrections };\n}\n\nexport class GoogleProvider extends LLMProvider {\n readonly name = 'google';\n readonly displayName = 'Google (Gemini)';\n\n private get apiKey(): string {\n const config = loadConfig();\n const p = config.providers.google;\n return resolveApiKey('google', p.authProfiles || [], p.apiKey || '', 'GOOGLE_API_KEY', p.rotationStrategy, p.credentialCooldownMs);\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const model = (options.model || 'gemini-2.0-flash').replace('google/', '');\n const apiKey = this.apiKey;\n if (!apiKey) throw new Error('Google API key not configured');\n\n logger.debug(COMPONENT, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n const systemInstruction = options.messages.find((m) => m.role === 'system')?.content;\n const { contents, corrections } = buildContents(options.messages);\n if (corrections > 0) {\n logger.warn(COMPONENT, `Applied ${corrections} tool-message correction(s) before sending to Gemini.`);\n }\n\n const body: Record<string, unknown> = {\n contents,\n generationConfig: {\n maxOutputTokens: clampMaxTokens(options.model || 'google/gemini-2.0-flash', options.maxTokens),\n temperature: options.temperature ?? 0.7,\n },\n };\n\n if (systemInstruction) {\n body.systemInstruction = { parts: [{ text: systemInstruction }] };\n }\n\n if (options.tools && options.tools.length > 0) {\n body.tools = [{\n functionDeclarations: options.tools.map((t) => ({\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n })),\n }];\n }\n\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;\n const response = await fetchWithRetry(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-goog-api-key': apiKey,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n // Dump body when the API rejected it so post-mortem has full context\n dumpRequestBody(`http-${response.status}`, body, { errorText, model });\n // Hunt Finding #37: attach status + Retry-After so the router can respect backoff\n const { createProviderError } = await import('./errorTaxonomy.js');\n throw createProviderError('Google API', response, errorText, { provider: 'google', model });\n }\n\n const data = await response.json() as Record<string, unknown>;\n const candidates = data.candidates as Array<Record<string, unknown>>;\n\n let textContent = '';\n const toolCalls: ToolCall[] = [];\n\n if (candidates && candidates.length > 0) {\n const parts = (candidates[0].content as Record<string, unknown>)?.parts as Array<Record<string, unknown>> || [];\n for (const part of parts) {\n if (part.text) {\n textContent += part.text as string;\n }\n if (part.functionCall) {\n const fc = part.functionCall as Record<string, unknown>;\n toolCalls.push({\n id: uuid(),\n type: 'function',\n function: {\n name: fc.name as string,\n arguments: JSON.stringify(fc.args),\n },\n });\n }\n }\n }\n\n const usageMeta = data.usageMetadata as { promptTokenCount?: number; candidatesTokenCount?: number; totalTokenCount?: number } | undefined;\n\n return {\n id: uuid(),\n content: textContent,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: usageMeta\n ? {\n promptTokens: usageMeta.promptTokenCount || 0,\n completionTokens: usageMeta.candidatesTokenCount || 0,\n totalTokens: usageMeta.totalTokenCount || 0,\n }\n : undefined,\n finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',\n model: `google/${model}`,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const model = (options.model || 'gemini-2.0-flash').replace('google/', '');\n const apiKey = this.apiKey;\n if (!apiKey) { yield { type: 'error', error: 'Google API key not configured' }; return; }\n\n const systemInstruction = options.messages.find((m) => m.role === 'system')?.content;\n const { contents, corrections } = buildContents(options.messages);\n if (corrections > 0) {\n logger.warn(COMPONENT, `Applied ${corrections} tool-message correction(s) before streaming to Gemini.`);\n }\n\n const body: Record<string, unknown> = {\n contents,\n generationConfig: { maxOutputTokens: clampMaxTokens(options.model || 'google/gemini-2.0-flash', options.maxTokens), temperature: options.temperature ?? 0.7 },\n };\n if (systemInstruction) body.systemInstruction = { parts: [{ text: systemInstruction }] };\n if (options.tools && options.tools.length > 0) {\n body.tools = [{ functionDeclarations: options.tools.map((t) => ({ name: t.function.name, description: t.function.description, parameters: t.function.parameters })) }];\n }\n\n try {\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse`;\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'x-goog-api-key': apiKey },\n body: JSON.stringify(body),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n dumpRequestBody(`stream-http-${response.status}`, body, { errorText, model });\n yield { type: 'error', error: `Google API error (${response.status}): ${errorText}` };\n return;\n }\n\n const reader = response.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) continue;\n\n try {\n const chunk = JSON.parse(json);\n const candidates = chunk.candidates as Array<Record<string, unknown>> | undefined;\n if (candidates && candidates.length > 0) {\n const parts = (candidates[0].content as Record<string, unknown>)?.parts as Array<Record<string, unknown>> || [];\n for (const part of parts) {\n if (part.text) yield { type: 'text', content: part.text as string };\n if (part.functionCall) {\n const fc = part.functionCall as Record<string, unknown>;\n yield {\n type: 'tool_call',\n toolCall: { id: uuid(), type: 'function', function: { name: fc.name as string, arguments: JSON.stringify(fc.args) } },\n };\n }\n }\n }\n } catch { /* skip malformed SSE 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 return ['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.0-flash', 'gemini-1.5-pro'];\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n if (!this.apiKey) return false;\n const url = `https://generativelanguage.googleapis.com/v1beta/models`;\n const response = await fetch(url, {\n headers: { 'x-goog-api-key': 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/google.ts"],"sourcesContent":["/**\n * TITAN — Google Gemini Provider\n */\nimport {\n LLMProvider,\n type ChatOptions,\n type ChatMessage,\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';\nimport { mkdirSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\nconst COMPONENT = 'Google';\n\n/**\n * When true, every Gemini request body that fails serialization-validation OR\n * gets a non-2xx response is dumped to ~/.titan/debug/gemini-requests/ for\n * post-mortem. Toggled via `GOOGLE_DUMP_REQUEST_BODY=1` env var or the\n * provider's `dumpRequestBody` config flag — keeps it off by default since\n * each dump is a JSON file with full prompt content.\n */\nfunction shouldDumpRequestBody(): boolean {\n if (process.env.GOOGLE_DUMP_REQUEST_BODY === '1' || process.env.GOOGLE_DUMP_REQUEST_BODY === 'true') {\n return true;\n }\n try {\n const cfg = loadConfig();\n const p = (cfg.providers as Record<string, unknown> | undefined)?.google as\n | { dumpRequestBody?: boolean }\n | undefined;\n return Boolean(p?.dumpRequestBody);\n } catch {\n return false;\n }\n}\n\nconst GEMINI_DEBUG_DIR = join(homedir(), '.titan', 'debug', 'gemini-requests');\n\nfunction dumpRequestBody(reason: string, body: unknown, extra?: Record<string, unknown>): void {\n if (!shouldDumpRequestBody()) return;\n try {\n mkdirSync(GEMINI_DEBUG_DIR, { recursive: true });\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n const path = join(GEMINI_DEBUG_DIR, `${stamp}-${reason}.json`);\n writeFileSync(path, JSON.stringify({ reason, body, ...(extra ?? {}) }, null, 2));\n logger.info(COMPONENT, `Dumped Gemini request body → ${path}`);\n } catch (err) {\n logger.warn(COMPONENT, `Failed to dump Gemini request body: ${(err as Error).message}`);\n }\n}\n\n/**\n * Build the Gemini `contents[]` array from TITAN ChatMessages, with strict\n * pre-serialization validation of `tool` messages.\n *\n * Why this matters:\n * Gemini's `functionResponse` requires a non-empty `name` field paired with\n * a valid `tool_call_id` from a prior assistant turn. If the agent loop\n * ever emits a tool result whose corresponding tool call cannot be located\n * in conversation history, Gemini rejects the whole request with a 400 and\n * the error message is opaque (\"function_response without function_call\").\n *\n * Rather than push the malformed message and let Gemini blow up, we:\n * 1. Build a map of every tool_call.id → name from prior assistant messages.\n * 2. For each `tool` message, ensure (a) the name is non-empty (use the\n * toolCallId map as a backstop) and (b) the toolCallId references a\n * known prior call.\n * 3. Drop or relabel messages that fail validation, with a warning that\n * names the offending message so it shows up in logs.\n * 4. If `dumpRequestBody` is enabled, write the full pre-validation body\n * to disk for inspection before any silent corrections.\n */\nfunction buildContents(messages: ChatMessage[]): { contents: Array<Record<string, unknown>>; corrections: number } {\n // Pass 1: build a lookup of valid tool_call_id → function name from\n // every prior assistant turn that emitted toolCalls.\n const toolCallNameById = new Map<string, string>();\n for (const m of messages) {\n if (m.role === 'assistant' && Array.isArray(m.toolCalls)) {\n for (const tc of m.toolCalls) {\n if (tc.id && tc.function?.name) {\n toolCallNameById.set(tc.id, tc.function.name);\n }\n }\n }\n }\n\n let corrections = 0;\n const contents: Array<Record<string, unknown>> = [];\n\n for (const m of messages.filter((x) => x.role !== 'system')) {\n if (m.role === 'tool') {\n // Validation: name must be non-empty AND toolCallId must reference\n // a known prior call. Either failure → log + best-effort repair.\n const callId = m.toolCallId || '';\n const recordedName = callId ? toolCallNameById.get(callId) : undefined;\n const claimedName = (m.name || '').trim();\n\n if (!recordedName) {\n logger.warn(\n COMPONENT,\n `Malformed tool message: tool_call_id=\"${callId}\" has no matching prior tool_call. ` +\n `name=\"${claimedName}\". Dropping to prevent Gemini 400.`,\n );\n corrections++;\n continue;\n }\n\n const finalName = claimedName || recordedName;\n if (!claimedName) {\n logger.warn(\n COMPONENT,\n `Tool message missing name for tool_call_id=\"${callId}\"; ` +\n `inferred \"${finalName}\" from assistant history.`,\n );\n corrections++;\n } else if (claimedName !== recordedName) {\n logger.warn(\n COMPONENT,\n `Tool message name mismatch for tool_call_id=\"${callId}\": ` +\n `claimed \"${claimedName}\" but tool_call recorded \"${recordedName}\". Using recorded.`,\n );\n corrections++;\n }\n\n contents.push({\n role: 'function' as const,\n parts: [{ functionResponse: { name: recordedName, response: { result: m.content } } }],\n });\n continue;\n }\n\n contents.push({\n role: (m.role === 'assistant' ? 'model' : 'user') as string,\n parts: [{ text: m.content }],\n });\n }\n\n return { contents, corrections };\n}\n\nexport class GoogleProvider extends LLMProvider {\n readonly name = 'google';\n readonly displayName = 'Google (Gemini)';\n\n private get apiKey(): string {\n const config = loadConfig();\n const p = config.providers.google;\n return resolveApiKey('google', p.authProfiles || [], p.apiKey || '', 'GOOGLE_API_KEY', p.rotationStrategy, p.credentialCooldownMs);\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const model = (options.model || 'gemini-2.0-flash').replace('google/', '');\n const apiKey = this.apiKey;\n if (!apiKey) throw new Error('Google API key not configured');\n\n logger.debug(COMPONENT, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n const systemInstruction = options.messages.find((m) => m.role === 'system')?.content;\n const { contents, corrections } = buildContents(options.messages);\n if (corrections > 0) {\n logger.warn(COMPONENT, `Applied ${corrections} tool-message correction(s) before sending to Gemini.`);\n }\n\n const body: Record<string, unknown> = {\n contents,\n generationConfig: {\n maxOutputTokens: clampMaxTokens(options.model || 'google/gemini-2.0-flash', options.maxTokens),\n temperature: options.temperature ?? 0.7,\n },\n };\n\n if (systemInstruction) {\n body.systemInstruction = { parts: [{ text: systemInstruction }] };\n }\n\n if (options.tools && options.tools.length > 0) {\n body.tools = [{\n functionDeclarations: options.tools.map((t) => ({\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n })),\n }];\n }\n\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`;\n const response = await fetchWithRetry(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-goog-api-key': apiKey,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n // Dump body when the API rejected it so post-mortem has full context\n dumpRequestBody(`http-${response.status}`, body, { errorText, model });\n // Hunt Finding #37: attach status + Retry-After so the router can respect backoff\n const { createProviderError } = await import('./errorTaxonomy.js');\n throw createProviderError('Google API', response, errorText, { provider: 'google', model });\n }\n\n const data = await response.json() as Record<string, unknown>;\n const candidates = data.candidates as Array<Record<string, unknown>>;\n\n let textContent = '';\n const toolCalls: ToolCall[] = [];\n\n if (candidates && candidates.length > 0) {\n const parts = (candidates[0].content as Record<string, unknown>)?.parts as Array<Record<string, unknown>> || [];\n for (const part of parts) {\n if (part.text) {\n textContent += part.text as string;\n }\n if (part.functionCall) {\n const fc = part.functionCall as Record<string, unknown>;\n toolCalls.push({\n id: uuid(),\n type: 'function',\n function: {\n name: fc.name as string,\n arguments: JSON.stringify(fc.args),\n },\n });\n }\n }\n }\n\n const usageMeta = data.usageMetadata as { promptTokenCount?: number; candidatesTokenCount?: number; totalTokenCount?: number } | undefined;\n\n return {\n id: uuid(),\n content: textContent,\n toolCalls: toolCalls.length > 0 ? toolCalls : undefined,\n usage: usageMeta\n ? {\n promptTokens: usageMeta.promptTokenCount || 0,\n completionTokens: usageMeta.candidatesTokenCount || 0,\n totalTokens: usageMeta.totalTokenCount || 0,\n }\n : undefined,\n finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',\n model: `google/${model}`,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const model = (options.model || 'gemini-2.0-flash').replace('google/', '');\n const apiKey = this.apiKey;\n if (!apiKey) { yield { type: 'error', error: 'Google API key not configured' }; return; }\n\n const systemInstruction = options.messages.find((m) => m.role === 'system')?.content;\n const { contents, corrections } = buildContents(options.messages);\n if (corrections > 0) {\n logger.warn(COMPONENT, `Applied ${corrections} tool-message correction(s) before streaming to Gemini.`);\n }\n\n const body: Record<string, unknown> = {\n contents,\n generationConfig: { maxOutputTokens: clampMaxTokens(options.model || 'google/gemini-2.0-flash', options.maxTokens), temperature: options.temperature ?? 0.7 },\n };\n if (systemInstruction) body.systemInstruction = { parts: [{ text: systemInstruction }] };\n if (options.tools && options.tools.length > 0) {\n body.tools = [{ functionDeclarations: options.tools.map((t) => ({ name: t.function.name, description: t.function.description, parameters: t.function.parameters })) }];\n }\n\n try {\n const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse`;\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'x-goog-api-key': apiKey },\n body: JSON.stringify(body),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n dumpRequestBody(`stream-http-${response.status}`, body, { errorText, model });\n yield { type: 'error', error: `Google API error (${response.status}): ${errorText}` };\n return;\n }\n\n const reader = response.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) continue;\n\n try {\n const chunk = JSON.parse(json);\n const candidates = chunk.candidates as Array<Record<string, unknown>> | undefined;\n if (candidates && candidates.length > 0) {\n const parts = (candidates[0].content as Record<string, unknown>)?.parts as Array<Record<string, unknown>> || [];\n for (const part of parts) {\n if (part.text) yield { type: 'text', content: part.text as string };\n if (part.functionCall) {\n const fc = part.functionCall as Record<string, unknown>;\n yield {\n type: 'tool_call',\n toolCall: { id: uuid(), type: 'function', function: { name: fc.name as string, arguments: JSON.stringify(fc.args) } },\n };\n }\n }\n }\n } catch { /* skip malformed SSE 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 // Hardcoded comprehensive fallback used when no key is configured\n // or live discovery fails. Covers Gemini families currently in\n // production as of 2026-05.\n const FALLBACK = [\n // Gemini 3.x (2026)\n 'gemini-3.1-pro',\n 'gemini-3.1-flash',\n 'gemini-3.1-flash-lite',\n 'gemini-3.0-pro',\n 'gemini-3.0-flash',\n // Gemini 2.5 / 2.0\n 'gemini-2.5-pro',\n 'gemini-2.5-flash',\n 'gemini-2.5-flash-lite',\n 'gemini-2.0-flash',\n 'gemini-2.0-flash-lite',\n 'gemini-2.0-flash-thinking-exp',\n 'gemini-2.0-pro-exp',\n // Gemini 1.5 (still production-supported)\n 'gemini-1.5-pro',\n 'gemini-1.5-pro-002',\n 'gemini-1.5-flash',\n 'gemini-1.5-flash-002',\n 'gemini-1.5-flash-8b',\n ];\n if (!this.apiKey) return FALLBACK;\n\n // Live discovery via /v1beta/models. Response shape:\n // { models: [{ name: \"models/gemini-2.5-pro\", supportedGenerationMethods: [\"generateContent\", ...] }] }\n // Filter to models that support generateContent (chat) — skip embedding-only,\n // image-gen, and aqa models so the picker stays useful.\n try {\n const response = await fetch('https://generativelanguage.googleapis.com/v1beta/models?pageSize=200', {\n headers: { 'x-goog-api-key': this.apiKey },\n signal: AbortSignal.timeout(5000),\n });\n if (!response.ok) {\n logger.debug(COMPONENT, `listModels: ${response.status} from /v1beta/models, using fallback`);\n return FALLBACK;\n }\n const data = await response.json() as {\n models?: Array<{ name: string; supportedGenerationMethods?: string[] }>;\n };\n const ids = (data.models || [])\n .filter((m) => (m.supportedGenerationMethods || []).includes('generateContent'))\n .map((m) => m.name.replace(/^models\\//, ''))\n .filter((id) => /gemini/i.test(id) && !/embedding|tts|image/i.test(id));\n // Sort: newest gemini families first (2.5 > 2.0 > 1.5).\n ids.sort().reverse();\n return ids.length > 0 ? ids : FALLBACK;\n } catch (err) {\n logger.debug(COMPONENT, `listModels failed: ${(err as Error).message}, using fallback`);\n return FALLBACK;\n }\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n if (!this.apiKey) return false;\n const url = `https://generativelanguage.googleapis.com/v1beta/models`;\n const response = await fetch(url, {\n headers: { 'x-goog-api-key': this.apiKey },\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -222,7 +222,56 @@ class OpenAIProvider extends LLMProvider {
222
222
  yield { type: "done" };
223
223
  }
224
224
  async listModels() {
225
- return ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "o1", "o1-mini", "o3-mini"];
225
+ const FALLBACK = [
226
+ // GPT-5 family (2026)
227
+ "gpt-5",
228
+ "gpt-5-mini",
229
+ "gpt-5-nano",
230
+ // GPT-4.5 / 4o family
231
+ "gpt-4.5-preview",
232
+ "gpt-4o",
233
+ "gpt-4o-2024-11-20",
234
+ "gpt-4o-2024-08-06",
235
+ "gpt-4o-mini",
236
+ "gpt-4o-mini-2024-07-18",
237
+ "gpt-4-turbo",
238
+ "gpt-4-turbo-2024-04-09",
239
+ "gpt-4",
240
+ "chatgpt-4o-latest",
241
+ // o-series reasoning models
242
+ "o4-mini",
243
+ "o3",
244
+ "o3-mini",
245
+ "o3-pro",
246
+ "o1",
247
+ "o1-pro",
248
+ "o1-mini",
249
+ "o1-preview",
250
+ // Legacy still in production
251
+ "gpt-3.5-turbo"
252
+ ];
253
+ if (!this.apiKey) return FALLBACK;
254
+ const isChatModel = (id) => {
255
+ if (/^(text-embedding|whisper|tts|dall-e|moderation|davinci-002|babbage-002|computer-use|omni-moderation)/.test(id)) return false;
256
+ return /^(gpt|o1|o3|o4|chatgpt|ft:gpt)/.test(id);
257
+ };
258
+ try {
259
+ const response = await fetch(`${this.baseUrl}/v1/models`, {
260
+ headers: { Authorization: `Bearer ${this.apiKey}` },
261
+ signal: AbortSignal.timeout(5e3)
262
+ });
263
+ if (!response.ok) {
264
+ logger.debug(COMPONENT, `listModels: ${response.status} from /v1/models, using fallback`);
265
+ return FALLBACK;
266
+ }
267
+ const data = await response.json();
268
+ const ids = (data.data || []).map((m) => m.id).filter(isChatModel);
269
+ ids.sort().reverse();
270
+ return ids.length > 0 ? ids : FALLBACK;
271
+ } catch (err) {
272
+ logger.debug(COMPONENT, `listModels failed: ${err.message}, using fallback`);
273
+ return FALLBACK;
274
+ }
226
275
  }
227
276
  async healthCheck() {
228
277
  try {
@@ -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';\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":[]}
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 // Hardcoded comprehensive fallback used when no key is configured\n // or live discovery fails. Covers OpenAI's chat-capable production\n // models as of 2026-05. Live discovery (when a key IS set) replaces\n // this with the user's actual catalogue.\n const FALLBACK = [\n // GPT-5 family (2026)\n 'gpt-5',\n 'gpt-5-mini',\n 'gpt-5-nano',\n // GPT-4.5 / 4o family\n 'gpt-4.5-preview',\n 'gpt-4o',\n 'gpt-4o-2024-11-20',\n 'gpt-4o-2024-08-06',\n 'gpt-4o-mini',\n 'gpt-4o-mini-2024-07-18',\n 'gpt-4-turbo',\n 'gpt-4-turbo-2024-04-09',\n 'gpt-4',\n 'chatgpt-4o-latest',\n // o-series reasoning models\n 'o4-mini',\n 'o3',\n 'o3-mini',\n 'o3-pro',\n 'o1',\n 'o1-pro',\n 'o1-mini',\n 'o1-preview',\n // Legacy still in production\n 'gpt-3.5-turbo',\n ];\n if (!this.apiKey) return FALLBACK;\n\n // Live discovery via /v1/models. The response includes embeddings,\n // image-gen, audio, moderation, etc. — filter to chat-capable\n // generation models so the picker doesn't drown in noise.\n const isChatModel = (id: string): boolean => {\n if (/^(text-embedding|whisper|tts|dall-e|moderation|davinci-002|babbage-002|computer-use|omni-moderation)/.test(id)) return false;\n return /^(gpt|o1|o3|o4|chatgpt|ft:gpt)/.test(id);\n };\n try {\n const response = await fetch(`${this.baseUrl}/v1/models`, {\n headers: { Authorization: `Bearer ${this.apiKey}` },\n signal: AbortSignal.timeout(5000),\n });\n if (!response.ok) {\n logger.debug(COMPONENT, `listModels: ${response.status} from /v1/models, using fallback`);\n return FALLBACK;\n }\n const data = await response.json() as { data?: Array<{ id: string }> };\n const ids = (data.data || []).map((m) => m.id).filter(isChatModel);\n // Sort newest first by name (alphabetical desc tends to bubble new generations up).\n ids.sort().reverse();\n return ids.length > 0 ? ids : FALLBACK;\n } catch (err) {\n logger.debug(COMPONENT, `listModels failed: ${(err as Error).message}, using fallback`);\n return FALLBACK;\n }\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":[]}
@@ -210,20 +210,27 @@ class OpenAICompatProvider extends LLMProvider {
210
210
  }
211
211
  }
212
212
  async listModels() {
213
- if (!this.config.supportsModelList || !this.apiKey) {
213
+ if (!this.config.supportsModelList) {
214
+ return this.config.knownModels;
215
+ }
216
+ const isPublic = this.config.publicModelList === true;
217
+ if (!isPublic && !this.apiKey) {
214
218
  return this.config.knownModels;
215
219
  }
216
220
  try {
221
+ const headers = {
222
+ ...this.config.extraHeaders || {}
223
+ };
224
+ if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
217
225
  const response = await fetch(`${this.baseUrl}/models`, {
218
- headers: {
219
- "Authorization": `Bearer ${this.apiKey}`,
220
- ...this.config.extraHeaders || {}
221
- },
222
- signal: AbortSignal.timeout(5e3)
226
+ headers,
227
+ signal: AbortSignal.timeout(8e3)
223
228
  });
224
229
  if (!response.ok) return this.config.knownModels;
225
230
  const data = await response.json();
226
- return (data.data || []).map((m) => m.id);
231
+ const ids = (data.data || []).map((m) => m.id).filter(Boolean);
232
+ ids.sort();
233
+ return ids.length > 0 ? ids : this.config.knownModels;
227
234
  } catch {
228
235
  return this.config.knownModels;
229
236
  }
@@ -559,6 +566,11 @@ const PROVIDER_PRESETS = [
559
566
  "nvidia/llama-3.1-nemotron-70b-instruct"
560
567
  ],
561
568
  supportsModelList: true,
569
+ // OpenRouter's /api/v1/models is publicly accessible — TITAN can
570
+ // pull the full ~365-model catalogue even before the user has
571
+ // configured an OPENROUTER_API_KEY. Lets the picker show every
572
+ // available model so the user can choose first, then add the key.
573
+ publicModelList: true,
562
574
  extraHeaders: {
563
575
  "HTTP-Referer": "https://titan.local",
564
576
  "X-Title": "TITAN"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/openai_compat.ts"],"sourcesContent":["/**\n * TITAN — Generic OpenAI-Compatible Provider\n * A single provider class that works with any OpenAI-compatible API endpoint.\n * Used by: Groq, Mistral, OpenRouter, Fireworks, xAI, Together, DeepSeek,\n * Cerebras, Cohere, Perplexity, and any custom provider.\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 type { ProviderConfig } from '../config/schema.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\n/** Configuration for an OpenAI-compatible provider */\nexport interface OpenAICompatConfig {\n /** Internal provider name (e.g. 'groq') */\n name: string;\n /** Display name shown to users (e.g. 'Groq (Fast Inference)') */\n displayName: string;\n /** Default API base URL */\n defaultBaseUrl: string;\n /** Environment variable name for the API key */\n envKey: string;\n /** Config key name in titan.json providers section */\n configKey: string;\n /** Default model ID */\n defaultModel: string;\n /** Static model list (returned when health check fails) */\n knownModels: string[];\n /** Extra headers to send with every request */\n extraHeaders?: Record<string, string>;\n /** Whether to fetch models from /v1/models endpoint */\n supportsModelList?: boolean;\n /** Keep org/ prefix in model name (e.g. NIM API expects 'nvidia/model-name') */\n keepModelPrefix?: boolean;\n}\n\nexport class OpenAICompatProvider extends LLMProvider {\n readonly name: string;\n readonly displayName: string;\n private readonly config: OpenAICompatConfig;\n\n constructor(config: OpenAICompatConfig) {\n super();\n this.name = config.name;\n this.displayName = config.displayName;\n this.config = config;\n }\n\n private get apiKey(): string {\n const cfg = loadConfig();\n const providerCfg = (cfg.providers as Record<string, unknown>)[this.config.configKey] as ProviderConfig | undefined;\n return resolveApiKey(this.config.name, providerCfg?.authProfiles || [], providerCfg?.apiKey || '', this.config.envKey, providerCfg?.rotationStrategy, providerCfg?.credentialCooldownMs);\n }\n\n private get baseUrl(): string {\n const cfg = loadConfig();\n const providerCfg = (cfg.providers as Record<string, unknown>)[this.config.configKey] as ProviderConfig | undefined;\n return providerCfg?.baseUrl || this.config.defaultBaseUrl;\n }\n\n /** Sanitize messages for strict APIs (e.g., NIM) that reject empty content strings */\n private sanitizeMessages(messages: ChatOptions['messages']): ChatOptions['messages'] {\n return messages.map(m => ({\n ...m,\n content: m.content || (m.role === 'assistant' && m.toolCalls ? '' : ' '),\n }));\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const rawModel = options.model || this.config.defaultModel;\n // NIM API requires org/model format — keep prefix as-is or add it\n const model = this.config.keepModelPrefix\n ? (rawModel.includes('/') ? rawModel : `${this.name}/${rawModel}`)\n : rawModel.replace(`${this.name}/`, '');\n\n // Kimi API uses dash model IDs (kimi-k2-6) but TITAN uses dots (kimi-k2.6)\n const apiModel = this.config.configKey === 'kimi'\n ? model.replace(/kimi-k2\\.6/g, 'kimi-k2-6').replace(/kimi-k2\\.5/g, 'kimi-k2-5')\n : model;\n const apiKey = this.apiKey;\n if (!apiKey) throw new Error(`${this.displayName} API key not configured (set ${this.config.envKey} or providers.${this.config.configKey}.apiKey)`);\n\n logger.debug(this.name, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n const sanitized = this.sanitizeMessages(options.messages);\n const body: Record<string, unknown> = {\n model: apiModel,\n messages: sanitized.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 return { role: m.role, content: m.content || ' ' };\n }),\n max_tokens: clampMaxTokens(model, options.maxTokens),\n };\n\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools;\n }\n\n if (options.temperature !== undefined) {\n body.temperature = options.temperature;\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n ...(this.config.extraHeaders || {}),\n };\n\n const response = await fetchWithRetry(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\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(`${this.displayName} API`, response, errorText, { provider: this.name, 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: model.includes('/') ? model : `${this.name}/${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) || uuid(),\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: model.includes('/') ? model : `${this.name}/${model}`,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const rawModel = options.model || this.config.defaultModel;\n const model = this.config.keepModelPrefix\n ? (rawModel.includes('/') ? rawModel : `${this.name}/${rawModel}`)\n : rawModel.replace(`${this.name}/`, '');\n const apiKey = this.apiKey;\n if (!apiKey) { yield { type: 'error', error: `${this.displayName} API key not configured` }; return; }\n\n const sanitized = this.sanitizeMessages(options.messages);\n const body: Record<string, unknown> = {\n model,\n stream: true,\n messages: sanitized.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 return { role: m.role, content: m.content || ' ' };\n }),\n max_tokens: clampMaxTokens(model, options.maxTokens),\n };\n if (options.tools && options.tools.length > 0) body.tools = options.tools;\n if (options.temperature !== undefined) body.temperature = options.temperature;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n ...(this.config.extraHeaders || {}),\n };\n\n try {\n const response = await fetch(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n yield { type: 'error', error: `${this.displayName} API error (${response.status}): ${errorText}` };\n return;\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n const toolCalls = new Map<number, { id: string; name: string; args: string }>();\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]' || !json) continue;\n try {\n const chunk = JSON.parse(json);\n const delta = chunk.choices?.[0]?.delta;\n if (!delta) continue;\n if (delta.content) yield { type: 'text', content: delta.content };\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n if (!toolCalls.has(idx)) toolCalls.set(idx, { id: tc.id || '', name: '', args: '' });\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 SSE lines */ }\n }\n }\n\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 } catch (error) {\n yield { type: 'error', error: (error as Error).message };\n }\n }\n\n async listModels(): Promise<string[]> {\n if (!this.config.supportsModelList || !this.apiKey) {\n return this.config.knownModels;\n }\n try {\n const response = await fetch(`${this.baseUrl}/models`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n ...(this.config.extraHeaders || {}),\n },\n signal: AbortSignal.timeout(5000),\n });\n if (!response.ok) return this.config.knownModels;\n const data = await response.json() as { data?: Array<{ id: string }> };\n return (data.data || []).map((m) => m.id);\n } catch {\n return this.config.knownModels;\n }\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n if (!this.apiKey) return false;\n const response = await fetch(`${this.baseUrl}/models`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n ...(this.config.extraHeaders || {}),\n },\n signal: AbortSignal.timeout(5000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n\n// ── Provider Presets ──────────────────────────────────────────────\n\nexport const PROVIDER_PRESETS: OpenAICompatConfig[] = [\n {\n name: 'groq',\n displayName: 'Groq (Fast Inference)',\n defaultBaseUrl: 'https://api.groq.com/openai/v1',\n envKey: 'GROQ_API_KEY',\n configKey: 'groq',\n defaultModel: 'llama-3.3-70b-versatile',\n knownModels: ['llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'mixtral-8x7b-32768', 'gemma2-9b-it', 'deepseek-r1-distill-llama-70b'],\n supportsModelList: true,\n },\n {\n name: 'mistral',\n displayName: 'Mistral AI',\n defaultBaseUrl: 'https://api.mistral.ai/v1',\n envKey: 'MISTRAL_API_KEY',\n configKey: 'mistral',\n defaultModel: 'mistral-small-latest',\n knownModels: ['mistral-large-latest', 'mistral-medium-latest', 'mistral-small-latest', 'codestral-latest', 'mistral-nemo'],\n supportsModelList: true,\n },\n {\n name: 'fireworks',\n displayName: 'Fireworks AI',\n defaultBaseUrl: 'https://api.fireworks.ai/inference/v1',\n envKey: 'FIREWORKS_API_KEY',\n configKey: 'fireworks',\n defaultModel: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n knownModels: ['accounts/fireworks/models/llama-v3p3-70b-instruct', 'accounts/fireworks/models/mixtral-8x7b-instruct', 'accounts/fireworks/models/qwen3-8b'],\n supportsModelList: true,\n },\n {\n name: 'xai',\n displayName: 'xAI (Grok)',\n defaultBaseUrl: 'https://api.x.ai/v1',\n envKey: 'XAI_API_KEY',\n configKey: 'xai',\n defaultModel: 'grok-3-fast',\n knownModels: ['grok-3', 'grok-3-fast', 'grok-3-mini', 'grok-3-mini-fast'],\n supportsModelList: true,\n },\n {\n name: 'together',\n displayName: 'Together AI',\n defaultBaseUrl: 'https://api.together.xyz/v1',\n envKey: 'TOGETHER_API_KEY',\n configKey: 'together',\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n knownModels: ['meta-llama/Llama-3.3-70B-Instruct-Turbo', 'deepseek-ai/DeepSeek-R1', 'Qwen/Qwen2.5-72B-Instruct-Turbo', 'mistralai/Mixtral-8x7B-Instruct-v0.1'],\n supportsModelList: true,\n },\n {\n name: 'deepseek',\n displayName: 'DeepSeek',\n defaultBaseUrl: 'https://api.deepseek.com/v1',\n envKey: 'DEEPSEEK_API_KEY',\n configKey: 'deepseek',\n defaultModel: 'deepseek-chat',\n knownModels: ['deepseek-chat', 'deepseek-reasoner'],\n supportsModelList: false,\n },\n {\n name: 'cerebras',\n displayName: 'Cerebras (Ultra-Fast)',\n defaultBaseUrl: 'https://api.cerebras.ai/v1',\n envKey: 'CEREBRAS_API_KEY',\n configKey: 'cerebras',\n defaultModel: 'llama-3.3-70b',\n knownModels: ['llama-3.3-70b', 'llama-3.1-8b', 'qwen-3-32b'],\n supportsModelList: true,\n },\n {\n name: 'cohere',\n displayName: 'Cohere',\n defaultBaseUrl: 'https://api.cohere.com/compatibility/v1',\n envKey: 'COHERE_API_KEY',\n configKey: 'cohere',\n defaultModel: 'command-r-plus',\n knownModels: ['command-r-plus', 'command-r', 'command-r7b'],\n supportsModelList: false,\n },\n {\n name: 'perplexity',\n displayName: 'Perplexity (Search-Augmented)',\n defaultBaseUrl: 'https://api.perplexity.ai',\n envKey: 'PERPLEXITY_API_KEY',\n configKey: 'perplexity',\n defaultModel: 'sonar',\n knownModels: ['sonar', 'sonar-pro', 'sonar-reasoning'],\n supportsModelList: false,\n },\n {\n name: 'venice',\n displayName: 'Venice AI (Privacy-First)',\n defaultBaseUrl: 'https://api.venice.ai/api/v1',\n envKey: 'VENICE_API_KEY',\n configKey: 'venice',\n defaultModel: 'llama-3.3-70b',\n knownModels: ['llama-3.3-70b', 'deepseek-r1-671b', 'qwen-2.5-vl-72b'],\n supportsModelList: true,\n },\n {\n name: 'bedrock',\n displayName: 'AWS Bedrock (via Proxy)',\n defaultBaseUrl: 'http://localhost:4000/v1',\n envKey: 'AWS_BEDROCK_API_KEY',\n configKey: 'bedrock',\n defaultModel: 'anthropic.claude-3-5-sonnet-20241022-v2:0',\n knownModels: ['anthropic.claude-3-5-sonnet-20241022-v2:0', 'amazon.titan-text-premier-v1:0', 'meta.llama3-70b-instruct-v1:0'],\n supportsModelList: false,\n },\n {\n name: 'litellm',\n displayName: 'LiteLLM (Universal Proxy)',\n defaultBaseUrl: 'http://localhost:4000/v1',\n envKey: 'LITELLM_API_KEY',\n configKey: 'litellm',\n defaultModel: 'gpt-4o',\n knownModels: ['gpt-4o', 'claude-sonnet-4-20250514', 'gemini-2.5-flash'],\n supportsModelList: true,\n },\n // NOTE: Azure OpenAI uses custom endpoints (https://{resource}.openai.azure.com/openai/deployments/{model})\n // and requires api-version query param + api-key header instead of Bearer token.\n // Users must configure baseUrl to their Azure deployment endpoint.\n {\n name: 'azure',\n displayName: 'Azure OpenAI (Enterprise)',\n defaultBaseUrl: '',\n envKey: 'AZURE_OPENAI_API_KEY',\n configKey: 'azure',\n defaultModel: 'gpt-4o',\n knownModels: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview'],\n supportsModelList: false,\n },\n {\n name: 'deepinfra',\n displayName: 'DeepInfra (Fast Inference)',\n defaultBaseUrl: 'https://api.deepinfra.com/v1/openai',\n envKey: 'DEEPINFRA_API_KEY',\n configKey: 'deepinfra',\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct',\n knownModels: ['meta-llama/Llama-3.3-70B-Instruct', 'mistralai/Mixtral-8x22B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct', 'deepseek-ai/DeepSeek-R1'],\n supportsModelList: true,\n },\n {\n name: 'sambanova',\n displayName: 'SambaNova (Fast Inference)',\n defaultBaseUrl: 'https://api.sambanova.ai/v1',\n envKey: 'SAMBANOVA_API_KEY',\n configKey: 'sambanova',\n defaultModel: 'Meta-Llama-3.3-70B-Instruct',\n knownModels: ['Meta-Llama-3.3-70B-Instruct', 'DeepSeek-R1-Distill-Llama-70B', 'Qwen2.5-72B-Instruct'],\n supportsModelList: true,\n },\n {\n name: 'kimi',\n displayName: 'Kimi (Moonshot)',\n defaultBaseUrl: 'https://api.moonshot.ai/v1',\n envKey: 'MOONSHOT_API_KEY',\n configKey: 'kimi',\n defaultModel: 'kimi-k2.5',\n knownModels: ['kimi-k2.6', 'kimi-k2.5', 'kimi-k2', 'moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'],\n supportsModelList: true,\n },\n {\n name: 'huggingface',\n displayName: 'Hugging Face Inference',\n defaultBaseUrl: 'https://api-inference.huggingface.co/v1',\n envKey: 'HF_API_KEY',\n configKey: 'huggingface',\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct',\n knownModels: ['meta-llama/Llama-3.3-70B-Instruct', 'mistralai/Mixtral-8x7B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct', 'microsoft/Phi-3-medium-4k-instruct'],\n supportsModelList: true,\n },\n {\n name: 'ai21',\n displayName: 'AI21 Labs (Jamba)',\n defaultBaseUrl: 'https://api.ai21.com/studio/v1',\n envKey: 'AI21_API_KEY',\n configKey: 'ai21',\n defaultModel: 'jamba-1.5-large',\n knownModels: ['jamba-1.5-large', 'jamba-1.5-mini', 'jamba-instruct'],\n supportsModelList: false,\n },\n {\n name: 'cohere-v2',\n displayName: 'Cohere v2 (OpenAI-compat)',\n defaultBaseUrl: 'https://api.cohere.com/v2',\n envKey: 'COHERE_API_KEY',\n configKey: 'cohere-v2',\n defaultModel: 'command-a-03-2025',\n knownModels: ['command-a-03-2025', 'command-r-plus-08-2024', 'command-r-08-2024', 'command-r7b-12-2024'],\n supportsModelList: false,\n },\n {\n name: 'reka',\n displayName: 'Reka AI',\n defaultBaseUrl: 'https://api.reka.ai/v1',\n envKey: 'REKA_API_KEY',\n configKey: 'reka',\n defaultModel: 'reka-core',\n knownModels: ['reka-core', 'reka-flash', 'reka-edge'],\n supportsModelList: false,\n },\n {\n name: 'zhipu',\n displayName: 'Zhipu GLM',\n defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4',\n envKey: 'ZHIPU_API_KEY',\n configKey: 'zhipu',\n defaultModel: 'glm-4-plus',\n knownModels: ['glm-4-plus', 'glm-4', 'glm-4-flash', 'glm-4-long'],\n supportsModelList: false,\n },\n {\n name: 'yi',\n displayName: '01.AI (Yi)',\n defaultBaseUrl: 'https://api.01.ai/v1',\n envKey: 'YI_API_KEY',\n configKey: 'yi',\n defaultModel: 'yi-large',\n knownModels: ['yi-large', 'yi-medium', 'yi-spark', 'yi-large-turbo'],\n supportsModelList: true,\n },\n {\n name: 'inflection',\n displayName: 'Inflection (Pi)',\n defaultBaseUrl: 'https://api.inflection.ai/v1',\n envKey: 'INFLECTION_API_KEY',\n configKey: 'inflection',\n defaultModel: 'inflection-3-pi',\n knownModels: ['inflection-3-pi', 'inflection-3-productivity'],\n supportsModelList: false,\n },\n {\n name: 'novita',\n displayName: 'Novita AI',\n defaultBaseUrl: 'https://api.novita.ai/v3/openai',\n envKey: 'NOVITA_API_KEY',\n configKey: 'novita',\n defaultModel: 'meta-llama/llama-3.3-70b-instruct',\n knownModels: ['meta-llama/llama-3.3-70b-instruct', 'deepseek/deepseek-r1', 'qwen/qwen-2.5-72b-instruct', 'mistralai/mistral-large-2411'],\n supportsModelList: true,\n },\n {\n name: 'replicate',\n displayName: 'Replicate',\n defaultBaseUrl: 'https://api.replicate.com/v1',\n envKey: 'REPLICATE_API_KEY',\n configKey: 'replicate',\n defaultModel: 'meta/meta-llama-3-70b-instruct',\n knownModels: ['meta/meta-llama-3-70b-instruct', 'mistralai/mixtral-8x7b-instruct-v0.1', 'meta/meta-llama-3.1-405b-instruct'],\n supportsModelList: false,\n },\n {\n name: 'lepton',\n displayName: 'Lepton AI',\n defaultBaseUrl: 'https://llama3-3-70b.lepton.run/api/v1',\n envKey: 'LEPTON_API_KEY',\n configKey: 'lepton',\n defaultModel: 'llama-3.3-70b',\n knownModels: ['llama-3.3-70b', 'mixtral-8x7b', 'qwen2.5-72b'],\n supportsModelList: false,\n },\n {\n name: 'anyscale',\n displayName: 'Anyscale Endpoints',\n defaultBaseUrl: 'https://api.endpoints.anyscale.com/v1',\n envKey: 'ANYSCALE_API_KEY',\n configKey: 'anyscale',\n defaultModel: 'meta-llama/Meta-Llama-3-70B-Instruct',\n knownModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'mistralai/Mixtral-8x7B-Instruct-v0.1', 'codellama/CodeLlama-70b-Instruct-hf'],\n supportsModelList: true,\n },\n {\n name: 'octo',\n displayName: 'OctoAI',\n defaultBaseUrl: 'https://text.octoai.run/v1',\n envKey: 'OCTOAI_API_KEY',\n configKey: 'octo',\n defaultModel: 'meta-llama-3.1-70b-instruct',\n knownModels: ['meta-llama-3.1-70b-instruct', 'mixtral-8x7b-instruct', 'qwen2.5-72b-instruct'],\n supportsModelList: true,\n },\n {\n name: 'nous',\n displayName: 'Nous Research (Hermes)',\n defaultBaseUrl: 'https://inference-api.nousresearch.com/v1',\n envKey: 'NOUS_API_KEY',\n configKey: 'nous',\n defaultModel: 'hermes-3-llama-3.1-70b',\n knownModels: ['hermes-3-llama-3.1-70b', 'hermes-3-llama-3.1-8b', 'hermes-2-pro-mistral-7b'],\n supportsModelList: false,\n },\n {\n name: 'openrouter',\n displayName: 'OpenRouter (Universal Gateway)',\n defaultBaseUrl: 'https://openrouter.ai/api/v1',\n envKey: 'OPENROUTER_API_KEY',\n configKey: 'openrouter',\n defaultModel: 'anthropic/claude-3.5-sonnet',\n knownModels: [\n 'anthropic/claude-3.5-sonnet',\n 'anthropic/claude-3.5-haiku',\n 'anthropic/claude-3-opus',\n 'openai/gpt-4o',\n 'openai/gpt-4o-mini',\n 'meta-llama/llama-3.3-70b-instruct',\n 'google/gemini-2.5-flash-preview',\n 'deepseek/deepseek-chat',\n 'deepseek/deepseek-r1',\n 'x-ai/grok-3-beta',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n ],\n supportsModelList: true,\n extraHeaders: {\n 'HTTP-Referer': 'https://titan.local',\n 'X-Title': 'TITAN',\n },\n },\n {\n name: 'nvidia',\n displayName: 'NVIDIA NIM',\n defaultBaseUrl: 'https://integrate.api.nvidia.com/v1',\n envKey: 'NVIDIA_API_KEY',\n configKey: 'nvidia',\n defaultModel: 'nvidia/llama-3.3-nemotron-super-49b-v1',\n knownModels: [\n 'nvidia/llama-3.3-nemotron-super-49b-v1',\n 'nvidia/llama-3.3-nemotron-super-49b-v1.5',\n 'nvidia/llama-3.1-nemotron-ultra-253b-v1',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'nvidia/nemotron-3-nano-30b-a3b',\n 'nvidia/nemotron-3-super-120b-a12b',\n ],\n supportsModelList: true,\n keepModelPrefix: true,\n },\n {\n name: 'minimax',\n displayName: 'MiniMax',\n defaultBaseUrl: 'https://api.minimax.chat/v1',\n envKey: 'MINIMAX_API_KEY',\n configKey: 'minimax',\n defaultModel: 'minimax-m2.7',\n knownModels: [\n 'minimax-m2.7',\n 'minimax-m2.7-highspeed',\n 'minimax-m2.5',\n 'minimax-01',\n 'minimax-text-01',\n ],\n supportsModelList: false,\n },\n];\n"],"mappings":";AAMA;AAAA,EACI;AAAA,OAKG;AACP,SAAS,kBAAkB;AAE3B,OAAO,YAAY;AACnB,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,MAAM,YAAY;AAC3B,SAAS,sBAAsB;AA0BxB,MAAM,6BAA6B,YAAY;AAAA,EACzC;AAAA,EACA;AAAA,EACQ;AAAA,EAEjB,YAAY,QAA4B;AACpC,UAAM;AACN,SAAK,OAAO,OAAO;AACnB,SAAK,cAAc,OAAO;AAC1B,SAAK,SAAS;AAAA,EAClB;AAAA,EAEA,IAAY,SAAiB;AACzB,UAAM,MAAM,WAAW;AACvB,UAAM,cAAe,IAAI,UAAsC,KAAK,OAAO,SAAS;AACpF,WAAO,cAAc,KAAK,OAAO,MAAM,aAAa,gBAAgB,CAAC,GAAG,aAAa,UAAU,IAAI,KAAK,OAAO,QAAQ,aAAa,kBAAkB,aAAa,oBAAoB;AAAA,EAC3L;AAAA,EAEA,IAAY,UAAkB;AAC1B,UAAM,MAAM,WAAW;AACvB,UAAM,cAAe,IAAI,UAAsC,KAAK,OAAO,SAAS;AACpF,WAAO,aAAa,WAAW,KAAK,OAAO;AAAA,EAC/C;AAAA;AAAA,EAGQ,iBAAiB,UAA4D;AACjF,WAAO,SAAS,IAAI,QAAM;AAAA,MACtB,GAAG;AAAA,MACH,SAAS,EAAE,YAAY,EAAE,SAAS,eAAe,EAAE,YAAY,KAAK;AAAA,IACxE,EAAE;AAAA,EACN;AAAA,EAEA,MAAM,KAAK,SAA6C;AACpD,UAAM,WAAW,QAAQ,SAAS,KAAK,OAAO;AAE9C,UAAM,QAAQ,KAAK,OAAO,kBACnB,SAAS,SAAS,GAAG,IAAI,WAAW,GAAG,KAAK,IAAI,IAAI,QAAQ,KAC7D,SAAS,QAAQ,GAAG,KAAK,IAAI,KAAK,EAAE;AAG1C,UAAM,WAAW,KAAK,OAAO,cAAc,SACrC,MAAM,QAAQ,eAAe,WAAW,EAAE,QAAQ,eAAe,WAAW,IAC5E;AACN,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,GAAG,KAAK,WAAW,gCAAgC,KAAK,OAAO,MAAM,iBAAiB,KAAK,OAAO,SAAS,UAAU;AAElJ,WAAO,MAAM,KAAK,MAAM,uBAAuB,KAAK,cAAc,QAAQ,SAAS,MAAM,EAAE;AAE3F,UAAM,YAAY,KAAK,iBAAiB,QAAQ,QAAQ;AACxD,UAAM,OAAgC;AAAA,MAClC,OAAO;AAAA,MACP,UAAU,UAAU,IAAI,CAAC,MAAM;AAC3B,YAAI,EAAE,SAAS,QAAQ;AACnB,iBAAO,EAAE,MAAM,QAAQ,SAAS,EAAE,WAAW,KAAK,cAAc,EAAE,WAAW;AAAA,QACjF;AACA,YAAI,EAAE,SAAS,eAAe,EAAE,WAAW;AACvC,iBAAO;AAAA,YACH,MAAM;AAAA,YACN,SAAS,EAAE,WAAW;AAAA,YACtB,YAAY,EAAE,UAAU,IAAI,CAAC,QAAQ;AAAA,cACjC,IAAI,GAAG;AAAA,cACP,MAAM;AAAA,cACN,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,WAAW,GAAG,SAAS,UAAU;AAAA,YACzE,EAAE;AAAA,UACN;AAAA,QACJ;AACA,eAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,WAAW,IAAI;AAAA,MACrD,CAAC;AAAA,MACD,YAAY,eAAe,OAAO,QAAQ,SAAS;AAAA,IACvD;AAEA,QAAI,QAAQ,SAAS,QAAQ,MAAM,SAAS,GAAG;AAC3C,WAAK,QAAQ,QAAQ;AAAA,IACzB;AAEA,QAAI,QAAQ,gBAAgB,QAAW;AACnC,WAAK,cAAc,QAAQ;AAAA,IAC/B;AAEA,UAAM,UAAkC;AAAA,MACpC,gBAAgB;AAAA,MAChB,iBAAiB,UAAU,MAAM;AAAA,MACjC,GAAI,KAAK,OAAO,gBAAgB,CAAC;AAAA,IACrC;AAEA,UAAM,WAAW,MAAM,eAAe,GAAG,KAAK,OAAO,qBAAqB;AAAA,MACtE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC7B,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AACd,YAAM,YAAY,MAAM,SAAS,KAAK;AAEtC,YAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,oBAAoB;AACjE,YAAM,oBAAoB,GAAG,KAAK,WAAW,QAAQ,UAAU,WAAW,EAAE,UAAU,KAAK,MAAM,MAAM,CAAC;AAAA,IAC5G;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,UAAU,KAAK;AAErB,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AAClC,aAAO;AAAA,QACH,IAAK,KAAK,MAAiB,KAAK;AAAA,QAChC,SAAS;AAAA,QACT,OAAO;AAAA,QACP,cAAc;AAAA,QACd,OAAO,MAAM,SAAS,GAAG,IAAI,QAAQ,GAAG,KAAK,IAAI,IAAI,KAAK;AAAA,MAC9D;AAAA,IACJ;AAEA,UAAM,SAAS,QAAQ,CAAC;AACxB,UAAM,UAAU,OAAO;AAEvB,UAAM,YAAwB,CAAC;AAC/B,QAAI,QAAQ,YAAY;AACpB,iBAAW,MAAM,QAAQ,YAA8C;AACnE,cAAM,KAAK,GAAG;AACd,kBAAU,KAAK;AAAA,UACX,IAAK,GAAG,MAAiB,KAAK;AAAA,UAC9B,MAAM;AAAA,UACN,UAAU,EAAE,MAAM,GAAG,MAAM,WAAW,GAAG,UAAU;AAAA,QACvD,CAAC;AAAA,MACL;AAAA,IACJ;AAEA,UAAM,QAAQ,KAAK;AAEnB,WAAO;AAAA,MACH,IAAK,KAAK,MAAiB,KAAK;AAAA,MAChC,SAAU,QAAQ,WAAsB;AAAA,MACxC,WAAW,UAAU,SAAS,IAAI,YAAY;AAAA,MAC9C,OAAO,QACD;AAAA,QACE,cAAc,MAAM;AAAA,QACpB,kBAAkB,MAAM;AAAA,QACxB,aAAa,MAAM;AAAA,MACvB,IACE;AAAA,MACN,cAAc,UAAU,SAAS,IAAI,eAAgB,OAAO,iBAAuC;AAAA,MACnG,OAAO,MAAM,SAAS,GAAG,IAAI,QAAQ,GAAG,KAAK,IAAI,IAAI,KAAK;AAAA,IAC9D;AAAA,EACJ;AAAA,EAEA,OAAO,WAAW,SAAuD;AACrE,UAAM,WAAW,QAAQ,SAAS,KAAK,OAAO;AAC9C,UAAM,QAAQ,KAAK,OAAO,kBACnB,SAAS,SAAS,GAAG,IAAI,WAAW,GAAG,KAAK,IAAI,IAAI,QAAQ,KAC7D,SAAS,QAAQ,GAAG,KAAK,IAAI,KAAK,EAAE;AAC1C,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,QAAQ;AAAE,YAAM,EAAE,MAAM,SAAS,OAAO,GAAG,KAAK,WAAW,0BAA0B;AAAG;AAAA,IAAQ;AAErG,UAAM,YAAY,KAAK,iBAAiB,QAAQ,QAAQ;AACxD,UAAM,OAAgC;AAAA,MAClC;AAAA,MACA,QAAQ;AAAA,MACR,UAAU,UAAU,IAAI,CAAC,MAAM;AAC3B,YAAI,EAAE,SAAS,OAAQ,QAAO,EAAE,MAAM,QAAQ,SAAS,EAAE,WAAW,KAAK,cAAc,EAAE,WAAW;AACpG,YAAI,EAAE,SAAS,eAAe,EAAE,WAAW;AACvC,iBAAO;AAAA,YACH,MAAM;AAAA,YAAa,SAAS,EAAE,WAAW;AAAA,YACzC,YAAY,EAAE,UAAU,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,MAAM,YAAY,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,WAAW,GAAG,SAAS,UAAU,EAAE,EAAE;AAAA,UACjJ;AAAA,QACJ;AACA,eAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,WAAW,IAAI;AAAA,MACrD,CAAC;AAAA,MACD,YAAY,eAAe,OAAO,QAAQ,SAAS;AAAA,IACvD;AACA,QAAI,QAAQ,SAAS,QAAQ,MAAM,SAAS,EAAG,MAAK,QAAQ,QAAQ;AACpE,QAAI,QAAQ,gBAAgB,OAAW,MAAK,cAAc,QAAQ;AAElE,UAAM,UAAkC;AAAA,MACpC,gBAAgB;AAAA,MAChB,iBAAiB,UAAU,MAAM;AAAA,MACjC,GAAI,KAAK,OAAO,gBAAgB,CAAC;AAAA,IACrC;AAEA,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,qBAAqB;AAAA,QAC7D,QAAQ;AAAA,QACR;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC7B,CAAC;AAED,UAAI,CAAC,SAAS,MAAM,CAAC,SAAS,MAAM;AAChC,cAAM,YAAY,MAAM,SAAS,KAAK;AACtC,cAAM,EAAE,MAAM,SAAS,OAAO,GAAG,KAAK,WAAW,eAAe,SAAS,MAAM,MAAM,SAAS,GAAG;AACjG;AAAA,MACJ;AAEA,YAAM,SAAS,SAAS,KAAK,UAAU;AACvC,YAAM,UAAU,IAAI,YAAY;AAChC,UAAI,SAAS;AACb,YAAM,YAAY,oBAAI,IAAwD;AAE9E,aAAO,MAAM;AACT,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AACV,kBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAEhD,cAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,iBAAS,MAAM,IAAI,KAAK;AAExB,mBAAW,QAAQ,OAAO;AACtB,cAAI,CAAC,KAAK,WAAW,QAAQ,EAAG;AAChC,gBAAM,OAAO,KAAK,MAAM,CAAC,EAAE,KAAK;AAChC,cAAI,SAAS,YAAY,CAAC,KAAM;AAChC,cAAI;AACA,kBAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,kBAAM,QAAQ,MAAM,UAAU,CAAC,GAAG;AAClC,gBAAI,CAAC,MAAO;AACZ,gBAAI,MAAM,QAAS,OAAM,EAAE,MAAM,QAAQ,SAAS,MAAM,QAAQ;AAChE,gBAAI,MAAM,YAAY;AAClB,yBAAW,MAAM,MAAM,YAAY;AAC/B,sBAAM,MAAM,GAAG,SAAS;AACxB,oBAAI,CAAC,UAAU,IAAI,GAAG,EAAG,WAAU,IAAI,KAAK,EAAE,IAAI,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG,CAAC;AACnF,sBAAM,QAAQ,UAAU,IAAI,GAAG;AAC/B,oBAAI,GAAG,GAAI,OAAM,KAAK,GAAG;AACzB,oBAAI,GAAG,UAAU,KAAM,OAAM,OAAO,GAAG,SAAS;AAChD,oBAAI,GAAG,UAAU,UAAW,OAAM,QAAQ,GAAG,SAAS;AAAA,cAC1D;AAAA,YACJ;AAAA,UACJ,QAAQ;AAAA,UAAiC;AAAA,QAC7C;AAAA,MACJ;AAEA,iBAAW,CAAC,EAAE,EAAE,KAAK,WAAW;AAC5B,YAAI,GAAG,MAAM,GAAG,MAAM;AAClB,gBAAM,EAAE,MAAM,aAAa,UAAU,EAAE,IAAI,GAAG,IAAI,MAAM,YAAY,UAAU,EAAE,MAAM,GAAG,MAAM,WAAW,GAAG,QAAQ,KAAK,EAAE,EAAE;AAAA,QAClI;AAAA,MACJ;AACA,YAAM,EAAE,MAAM,OAAO;AAAA,IACzB,SAAS,OAAO;AACZ,YAAM,EAAE,MAAM,SAAS,OAAQ,MAAgB,QAAQ;AAAA,IAC3D;AAAA,EACJ;AAAA,EAEA,MAAM,aAAgC;AAClC,QAAI,CAAC,KAAK,OAAO,qBAAqB,CAAC,KAAK,QAAQ;AAChD,aAAO,KAAK,OAAO;AAAA,IACvB;AACA,QAAI;AACA,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,WAAW;AAAA,QACnD,SAAS;AAAA,UACL,iBAAiB,UAAU,KAAK,MAAM;AAAA,UACtC,GAAI,KAAK,OAAO,gBAAgB,CAAC;AAAA,QACrC;AAAA,QACA,QAAQ,YAAY,QAAQ,GAAI;AAAA,MACpC,CAAC;AACD,UAAI,CAAC,SAAS,GAAI,QAAO,KAAK,OAAO;AACrC,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,cAAQ,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,IAC5C,QAAQ;AACJ,aAAO,KAAK,OAAO;AAAA,IACvB;AAAA,EACJ;AAAA,EAEA,MAAM,cAAgC;AAClC,QAAI;AACA,UAAI,CAAC,KAAK,OAAQ,QAAO;AACzB,YAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,WAAW;AAAA,QACnD,SAAS;AAAA,UACL,iBAAiB,UAAU,KAAK,MAAM;AAAA,UACtC,GAAI,KAAK,OAAO,gBAAgB,CAAC;AAAA,QACrC;AAAA,QACA,QAAQ,YAAY,QAAQ,GAAI;AAAA,MACpC,CAAC;AACD,aAAO,SAAS;AAAA,IACpB,QAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAIO,MAAM,mBAAyC;AAAA,EAClD;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,2BAA2B,wBAAwB,sBAAsB,gBAAgB,+BAA+B;AAAA,IACtI,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,wBAAwB,yBAAyB,wBAAwB,oBAAoB,cAAc;AAAA,IACzH,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,qDAAqD,mDAAmD,oCAAoC;AAAA,IAC1J,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,UAAU,eAAe,eAAe,kBAAkB;AAAA,IACxE,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,2CAA2C,2BAA2B,mCAAmC,sCAAsC;AAAA,IAC7J,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,iBAAiB,mBAAmB;AAAA,IAClD,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,iBAAiB,gBAAgB,YAAY;AAAA,IAC3D,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,kBAAkB,aAAa,aAAa;AAAA,IAC1D,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,SAAS,aAAa,iBAAiB;AAAA,IACrD,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,iBAAiB,oBAAoB,iBAAiB;AAAA,IACpE,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,6CAA6C,kCAAkC,+BAA+B;AAAA,IAC5H,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,UAAU,4BAA4B,kBAAkB;AAAA,IACtE,mBAAmB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,UAAU,eAAe,eAAe,YAAY;AAAA,IAClE,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,qCAAqC,yCAAyC,6BAA6B,yBAAyB;AAAA,IAClJ,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,+BAA+B,iCAAiC,sBAAsB;AAAA,IACpG,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,aAAa,aAAa,WAAW,kBAAkB,mBAAmB,kBAAkB;AAAA,IAC1G,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,qCAAqC,wCAAwC,6BAA6B,oCAAoC;AAAA,IAC5J,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,mBAAmB,kBAAkB,gBAAgB;AAAA,IACnE,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,qBAAqB,0BAA0B,qBAAqB,qBAAqB;AAAA,IACvG,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,aAAa,cAAc,WAAW;AAAA,IACpD,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,cAAc,SAAS,eAAe,YAAY;AAAA,IAChE,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,YAAY,aAAa,YAAY,gBAAgB;AAAA,IACnE,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,mBAAmB,2BAA2B;AAAA,IAC5D,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,qCAAqC,wBAAwB,8BAA8B,8BAA8B;AAAA,IACvI,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,kCAAkC,wCAAwC,mCAAmC;AAAA,IAC3H,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,iBAAiB,gBAAgB,aAAa;AAAA,IAC5D,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,wCAAwC,wCAAwC,qCAAqC;AAAA,IACnI,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,+BAA+B,yBAAyB,sBAAsB;AAAA,IAC5F,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa,CAAC,0BAA0B,yBAAyB,yBAAyB;AAAA,IAC1F,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAAA,IACA,mBAAmB;AAAA,IACnB,cAAc;AAAA,MACV,gBAAgB;AAAA,MAChB,WAAW;AAAA,IACf;AAAA,EACJ;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAAA,IACA,mBAAmB;AAAA,IACnB,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AAAA,IACA,mBAAmB;AAAA,EACvB;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../src/providers/openai_compat.ts"],"sourcesContent":["/**\n * TITAN — Generic OpenAI-Compatible Provider\n * A single provider class that works with any OpenAI-compatible API endpoint.\n * Used by: Groq, Mistral, OpenRouter, Fireworks, xAI, Together, DeepSeek,\n * Cerebras, Cohere, Perplexity, and any custom provider.\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 type { ProviderConfig } from '../config/schema.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\n/** Configuration for an OpenAI-compatible provider */\nexport interface OpenAICompatConfig {\n /** Internal provider name (e.g. 'groq') */\n name: string;\n /** Display name shown to users (e.g. 'Groq (Fast Inference)') */\n displayName: string;\n /** Default API base URL */\n defaultBaseUrl: string;\n /** Environment variable name for the API key */\n envKey: string;\n /** Config key name in titan.json providers section */\n configKey: string;\n /** Default model ID */\n defaultModel: string;\n /** Static model list (returned when health check fails) */\n knownModels: string[];\n /** Extra headers to send with every request */\n extraHeaders?: Record<string, string>;\n /** Whether to fetch models from /v1/models endpoint */\n supportsModelList?: boolean;\n /**\n * The /models catalogue is publicly accessible (no API key required).\n * When true, listModels() will fetch the catalogue even when the user\n * has not configured an API key — useful for picker UIs that want to\n * show the full catalogue before the user has chosen / paid for a key.\n * OpenRouter is the canonical example.\n */\n publicModelList?: boolean;\n /** Keep org/ prefix in model name (e.g. NIM API expects 'nvidia/model-name') */\n keepModelPrefix?: boolean;\n}\n\nexport class OpenAICompatProvider extends LLMProvider {\n readonly name: string;\n readonly displayName: string;\n private readonly config: OpenAICompatConfig;\n\n constructor(config: OpenAICompatConfig) {\n super();\n this.name = config.name;\n this.displayName = config.displayName;\n this.config = config;\n }\n\n private get apiKey(): string {\n const cfg = loadConfig();\n const providerCfg = (cfg.providers as Record<string, unknown>)[this.config.configKey] as ProviderConfig | undefined;\n return resolveApiKey(this.config.name, providerCfg?.authProfiles || [], providerCfg?.apiKey || '', this.config.envKey, providerCfg?.rotationStrategy, providerCfg?.credentialCooldownMs);\n }\n\n private get baseUrl(): string {\n const cfg = loadConfig();\n const providerCfg = (cfg.providers as Record<string, unknown>)[this.config.configKey] as ProviderConfig | undefined;\n return providerCfg?.baseUrl || this.config.defaultBaseUrl;\n }\n\n /** Sanitize messages for strict APIs (e.g., NIM) that reject empty content strings */\n private sanitizeMessages(messages: ChatOptions['messages']): ChatOptions['messages'] {\n return messages.map(m => ({\n ...m,\n content: m.content || (m.role === 'assistant' && m.toolCalls ? '' : ' '),\n }));\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const rawModel = options.model || this.config.defaultModel;\n // NIM API requires org/model format — keep prefix as-is or add it\n const model = this.config.keepModelPrefix\n ? (rawModel.includes('/') ? rawModel : `${this.name}/${rawModel}`)\n : rawModel.replace(`${this.name}/`, '');\n\n // Kimi API uses dash model IDs (kimi-k2-6) but TITAN uses dots (kimi-k2.6)\n const apiModel = this.config.configKey === 'kimi'\n ? model.replace(/kimi-k2\\.6/g, 'kimi-k2-6').replace(/kimi-k2\\.5/g, 'kimi-k2-5')\n : model;\n const apiKey = this.apiKey;\n if (!apiKey) throw new Error(`${this.displayName} API key not configured (set ${this.config.envKey} or providers.${this.config.configKey}.apiKey)`);\n\n logger.debug(this.name, `Chat request: model=${model}, messages=${options.messages.length}`);\n\n const sanitized = this.sanitizeMessages(options.messages);\n const body: Record<string, unknown> = {\n model: apiModel,\n messages: sanitized.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 return { role: m.role, content: m.content || ' ' };\n }),\n max_tokens: clampMaxTokens(model, options.maxTokens),\n };\n\n if (options.tools && options.tools.length > 0) {\n body.tools = options.tools;\n }\n\n if (options.temperature !== undefined) {\n body.temperature = options.temperature;\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n ...(this.config.extraHeaders || {}),\n };\n\n const response = await fetchWithRetry(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\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(`${this.displayName} API`, response, errorText, { provider: this.name, 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: model.includes('/') ? model : `${this.name}/${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) || uuid(),\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: model.includes('/') ? model : `${this.name}/${model}`,\n };\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const rawModel = options.model || this.config.defaultModel;\n const model = this.config.keepModelPrefix\n ? (rawModel.includes('/') ? rawModel : `${this.name}/${rawModel}`)\n : rawModel.replace(`${this.name}/`, '');\n const apiKey = this.apiKey;\n if (!apiKey) { yield { type: 'error', error: `${this.displayName} API key not configured` }; return; }\n\n const sanitized = this.sanitizeMessages(options.messages);\n const body: Record<string, unknown> = {\n model,\n stream: true,\n messages: sanitized.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 return { role: m.role, content: m.content || ' ' };\n }),\n max_tokens: clampMaxTokens(model, options.maxTokens),\n };\n if (options.tools && options.tools.length > 0) body.tools = options.tools;\n if (options.temperature !== undefined) body.temperature = options.temperature;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n ...(this.config.extraHeaders || {}),\n };\n\n try {\n const response = await fetch(`${this.baseUrl}/chat/completions`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok || !response.body) {\n const errorText = await response.text();\n yield { type: 'error', error: `${this.displayName} API error (${response.status}): ${errorText}` };\n return;\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n const toolCalls = new Map<number, { id: string; name: string; args: string }>();\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]' || !json) continue;\n try {\n const chunk = JSON.parse(json);\n const delta = chunk.choices?.[0]?.delta;\n if (!delta) continue;\n if (delta.content) yield { type: 'text', content: delta.content };\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n if (!toolCalls.has(idx)) toolCalls.set(idx, { id: tc.id || '', name: '', args: '' });\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 SSE lines */ }\n }\n }\n\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 } catch (error) {\n yield { type: 'error', error: (error as Error).message };\n }\n }\n\n async listModels(): Promise<string[]> {\n if (!this.config.supportsModelList) {\n return this.config.knownModels;\n }\n\n // Some upstream catalogues are public (no auth needed) — those\n // providers set `publicModelList: true` so the picker can show\n // the full catalogue even before the user configures a key.\n // OpenRouter is the canonical example (~365 models, no key).\n const isPublic = this.config.publicModelList === true;\n if (!isPublic && !this.apiKey) {\n return this.config.knownModels;\n }\n\n try {\n const headers: Record<string, string> = {\n ...(this.config.extraHeaders || {}),\n };\n if (this.apiKey) headers['Authorization'] = `Bearer ${this.apiKey}`;\n const response = await fetch(`${this.baseUrl}/models`, {\n headers,\n signal: AbortSignal.timeout(8000),\n });\n if (!response.ok) return this.config.knownModels;\n const data = await response.json() as { data?: Array<{ id: string }> };\n const ids = (data.data || []).map((m) => m.id).filter(Boolean);\n // Sort alphabetically for stable UX (the catalogue is\n // already typically grouped by family).\n ids.sort();\n return ids.length > 0 ? ids : this.config.knownModels;\n } catch {\n return this.config.knownModels;\n }\n }\n\n async healthCheck(): Promise<boolean> {\n try {\n if (!this.apiKey) return false;\n const response = await fetch(`${this.baseUrl}/models`, {\n headers: {\n 'Authorization': `Bearer ${this.apiKey}`,\n ...(this.config.extraHeaders || {}),\n },\n signal: AbortSignal.timeout(5000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n\n// ── Provider Presets ──────────────────────────────────────────────\n\nexport const PROVIDER_PRESETS: OpenAICompatConfig[] = [\n {\n name: 'groq',\n displayName: 'Groq (Fast Inference)',\n defaultBaseUrl: 'https://api.groq.com/openai/v1',\n envKey: 'GROQ_API_KEY',\n configKey: 'groq',\n defaultModel: 'llama-3.3-70b-versatile',\n knownModels: ['llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'mixtral-8x7b-32768', 'gemma2-9b-it', 'deepseek-r1-distill-llama-70b'],\n supportsModelList: true,\n },\n {\n name: 'mistral',\n displayName: 'Mistral AI',\n defaultBaseUrl: 'https://api.mistral.ai/v1',\n envKey: 'MISTRAL_API_KEY',\n configKey: 'mistral',\n defaultModel: 'mistral-small-latest',\n knownModels: ['mistral-large-latest', 'mistral-medium-latest', 'mistral-small-latest', 'codestral-latest', 'mistral-nemo'],\n supportsModelList: true,\n },\n {\n name: 'fireworks',\n displayName: 'Fireworks AI',\n defaultBaseUrl: 'https://api.fireworks.ai/inference/v1',\n envKey: 'FIREWORKS_API_KEY',\n configKey: 'fireworks',\n defaultModel: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n knownModels: ['accounts/fireworks/models/llama-v3p3-70b-instruct', 'accounts/fireworks/models/mixtral-8x7b-instruct', 'accounts/fireworks/models/qwen3-8b'],\n supportsModelList: true,\n },\n {\n name: 'xai',\n displayName: 'xAI (Grok)',\n defaultBaseUrl: 'https://api.x.ai/v1',\n envKey: 'XAI_API_KEY',\n configKey: 'xai',\n defaultModel: 'grok-3-fast',\n knownModels: ['grok-3', 'grok-3-fast', 'grok-3-mini', 'grok-3-mini-fast'],\n supportsModelList: true,\n },\n {\n name: 'together',\n displayName: 'Together AI',\n defaultBaseUrl: 'https://api.together.xyz/v1',\n envKey: 'TOGETHER_API_KEY',\n configKey: 'together',\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n knownModels: ['meta-llama/Llama-3.3-70B-Instruct-Turbo', 'deepseek-ai/DeepSeek-R1', 'Qwen/Qwen2.5-72B-Instruct-Turbo', 'mistralai/Mixtral-8x7B-Instruct-v0.1'],\n supportsModelList: true,\n },\n {\n name: 'deepseek',\n displayName: 'DeepSeek',\n defaultBaseUrl: 'https://api.deepseek.com/v1',\n envKey: 'DEEPSEEK_API_KEY',\n configKey: 'deepseek',\n defaultModel: 'deepseek-chat',\n knownModels: ['deepseek-chat', 'deepseek-reasoner'],\n supportsModelList: false,\n },\n {\n name: 'cerebras',\n displayName: 'Cerebras (Ultra-Fast)',\n defaultBaseUrl: 'https://api.cerebras.ai/v1',\n envKey: 'CEREBRAS_API_KEY',\n configKey: 'cerebras',\n defaultModel: 'llama-3.3-70b',\n knownModels: ['llama-3.3-70b', 'llama-3.1-8b', 'qwen-3-32b'],\n supportsModelList: true,\n },\n {\n name: 'cohere',\n displayName: 'Cohere',\n defaultBaseUrl: 'https://api.cohere.com/compatibility/v1',\n envKey: 'COHERE_API_KEY',\n configKey: 'cohere',\n defaultModel: 'command-r-plus',\n knownModels: ['command-r-plus', 'command-r', 'command-r7b'],\n supportsModelList: false,\n },\n {\n name: 'perplexity',\n displayName: 'Perplexity (Search-Augmented)',\n defaultBaseUrl: 'https://api.perplexity.ai',\n envKey: 'PERPLEXITY_API_KEY',\n configKey: 'perplexity',\n defaultModel: 'sonar',\n knownModels: ['sonar', 'sonar-pro', 'sonar-reasoning'],\n supportsModelList: false,\n },\n {\n name: 'venice',\n displayName: 'Venice AI (Privacy-First)',\n defaultBaseUrl: 'https://api.venice.ai/api/v1',\n envKey: 'VENICE_API_KEY',\n configKey: 'venice',\n defaultModel: 'llama-3.3-70b',\n knownModels: ['llama-3.3-70b', 'deepseek-r1-671b', 'qwen-2.5-vl-72b'],\n supportsModelList: true,\n },\n {\n name: 'bedrock',\n displayName: 'AWS Bedrock (via Proxy)',\n defaultBaseUrl: 'http://localhost:4000/v1',\n envKey: 'AWS_BEDROCK_API_KEY',\n configKey: 'bedrock',\n defaultModel: 'anthropic.claude-3-5-sonnet-20241022-v2:0',\n knownModels: ['anthropic.claude-3-5-sonnet-20241022-v2:0', 'amazon.titan-text-premier-v1:0', 'meta.llama3-70b-instruct-v1:0'],\n supportsModelList: false,\n },\n {\n name: 'litellm',\n displayName: 'LiteLLM (Universal Proxy)',\n defaultBaseUrl: 'http://localhost:4000/v1',\n envKey: 'LITELLM_API_KEY',\n configKey: 'litellm',\n defaultModel: 'gpt-4o',\n knownModels: ['gpt-4o', 'claude-sonnet-4-20250514', 'gemini-2.5-flash'],\n supportsModelList: true,\n },\n // NOTE: Azure OpenAI uses custom endpoints (https://{resource}.openai.azure.com/openai/deployments/{model})\n // and requires api-version query param + api-key header instead of Bearer token.\n // Users must configure baseUrl to their Azure deployment endpoint.\n {\n name: 'azure',\n displayName: 'Azure OpenAI (Enterprise)',\n defaultBaseUrl: '',\n envKey: 'AZURE_OPENAI_API_KEY',\n configKey: 'azure',\n defaultModel: 'gpt-4o',\n knownModels: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview'],\n supportsModelList: false,\n },\n {\n name: 'deepinfra',\n displayName: 'DeepInfra (Fast Inference)',\n defaultBaseUrl: 'https://api.deepinfra.com/v1/openai',\n envKey: 'DEEPINFRA_API_KEY',\n configKey: 'deepinfra',\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct',\n knownModels: ['meta-llama/Llama-3.3-70B-Instruct', 'mistralai/Mixtral-8x22B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct', 'deepseek-ai/DeepSeek-R1'],\n supportsModelList: true,\n },\n {\n name: 'sambanova',\n displayName: 'SambaNova (Fast Inference)',\n defaultBaseUrl: 'https://api.sambanova.ai/v1',\n envKey: 'SAMBANOVA_API_KEY',\n configKey: 'sambanova',\n defaultModel: 'Meta-Llama-3.3-70B-Instruct',\n knownModels: ['Meta-Llama-3.3-70B-Instruct', 'DeepSeek-R1-Distill-Llama-70B', 'Qwen2.5-72B-Instruct'],\n supportsModelList: true,\n },\n {\n name: 'kimi',\n displayName: 'Kimi (Moonshot)',\n defaultBaseUrl: 'https://api.moonshot.ai/v1',\n envKey: 'MOONSHOT_API_KEY',\n configKey: 'kimi',\n defaultModel: 'kimi-k2.5',\n knownModels: ['kimi-k2.6', 'kimi-k2.5', 'kimi-k2', 'moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'],\n supportsModelList: true,\n },\n {\n name: 'huggingface',\n displayName: 'Hugging Face Inference',\n defaultBaseUrl: 'https://api-inference.huggingface.co/v1',\n envKey: 'HF_API_KEY',\n configKey: 'huggingface',\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct',\n knownModels: ['meta-llama/Llama-3.3-70B-Instruct', 'mistralai/Mixtral-8x7B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct', 'microsoft/Phi-3-medium-4k-instruct'],\n supportsModelList: true,\n },\n {\n name: 'ai21',\n displayName: 'AI21 Labs (Jamba)',\n defaultBaseUrl: 'https://api.ai21.com/studio/v1',\n envKey: 'AI21_API_KEY',\n configKey: 'ai21',\n defaultModel: 'jamba-1.5-large',\n knownModels: ['jamba-1.5-large', 'jamba-1.5-mini', 'jamba-instruct'],\n supportsModelList: false,\n },\n {\n name: 'cohere-v2',\n displayName: 'Cohere v2 (OpenAI-compat)',\n defaultBaseUrl: 'https://api.cohere.com/v2',\n envKey: 'COHERE_API_KEY',\n configKey: 'cohere-v2',\n defaultModel: 'command-a-03-2025',\n knownModels: ['command-a-03-2025', 'command-r-plus-08-2024', 'command-r-08-2024', 'command-r7b-12-2024'],\n supportsModelList: false,\n },\n {\n name: 'reka',\n displayName: 'Reka AI',\n defaultBaseUrl: 'https://api.reka.ai/v1',\n envKey: 'REKA_API_KEY',\n configKey: 'reka',\n defaultModel: 'reka-core',\n knownModels: ['reka-core', 'reka-flash', 'reka-edge'],\n supportsModelList: false,\n },\n {\n name: 'zhipu',\n displayName: 'Zhipu GLM',\n defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4',\n envKey: 'ZHIPU_API_KEY',\n configKey: 'zhipu',\n defaultModel: 'glm-4-plus',\n knownModels: ['glm-4-plus', 'glm-4', 'glm-4-flash', 'glm-4-long'],\n supportsModelList: false,\n },\n {\n name: 'yi',\n displayName: '01.AI (Yi)',\n defaultBaseUrl: 'https://api.01.ai/v1',\n envKey: 'YI_API_KEY',\n configKey: 'yi',\n defaultModel: 'yi-large',\n knownModels: ['yi-large', 'yi-medium', 'yi-spark', 'yi-large-turbo'],\n supportsModelList: true,\n },\n {\n name: 'inflection',\n displayName: 'Inflection (Pi)',\n defaultBaseUrl: 'https://api.inflection.ai/v1',\n envKey: 'INFLECTION_API_KEY',\n configKey: 'inflection',\n defaultModel: 'inflection-3-pi',\n knownModels: ['inflection-3-pi', 'inflection-3-productivity'],\n supportsModelList: false,\n },\n {\n name: 'novita',\n displayName: 'Novita AI',\n defaultBaseUrl: 'https://api.novita.ai/v3/openai',\n envKey: 'NOVITA_API_KEY',\n configKey: 'novita',\n defaultModel: 'meta-llama/llama-3.3-70b-instruct',\n knownModels: ['meta-llama/llama-3.3-70b-instruct', 'deepseek/deepseek-r1', 'qwen/qwen-2.5-72b-instruct', 'mistralai/mistral-large-2411'],\n supportsModelList: true,\n },\n {\n name: 'replicate',\n displayName: 'Replicate',\n defaultBaseUrl: 'https://api.replicate.com/v1',\n envKey: 'REPLICATE_API_KEY',\n configKey: 'replicate',\n defaultModel: 'meta/meta-llama-3-70b-instruct',\n knownModels: ['meta/meta-llama-3-70b-instruct', 'mistralai/mixtral-8x7b-instruct-v0.1', 'meta/meta-llama-3.1-405b-instruct'],\n supportsModelList: false,\n },\n {\n name: 'lepton',\n displayName: 'Lepton AI',\n defaultBaseUrl: 'https://llama3-3-70b.lepton.run/api/v1',\n envKey: 'LEPTON_API_KEY',\n configKey: 'lepton',\n defaultModel: 'llama-3.3-70b',\n knownModels: ['llama-3.3-70b', 'mixtral-8x7b', 'qwen2.5-72b'],\n supportsModelList: false,\n },\n {\n name: 'anyscale',\n displayName: 'Anyscale Endpoints',\n defaultBaseUrl: 'https://api.endpoints.anyscale.com/v1',\n envKey: 'ANYSCALE_API_KEY',\n configKey: 'anyscale',\n defaultModel: 'meta-llama/Meta-Llama-3-70B-Instruct',\n knownModels: ['meta-llama/Meta-Llama-3-70B-Instruct', 'mistralai/Mixtral-8x7B-Instruct-v0.1', 'codellama/CodeLlama-70b-Instruct-hf'],\n supportsModelList: true,\n },\n {\n name: 'octo',\n displayName: 'OctoAI',\n defaultBaseUrl: 'https://text.octoai.run/v1',\n envKey: 'OCTOAI_API_KEY',\n configKey: 'octo',\n defaultModel: 'meta-llama-3.1-70b-instruct',\n knownModels: ['meta-llama-3.1-70b-instruct', 'mixtral-8x7b-instruct', 'qwen2.5-72b-instruct'],\n supportsModelList: true,\n },\n {\n name: 'nous',\n displayName: 'Nous Research (Hermes)',\n defaultBaseUrl: 'https://inference-api.nousresearch.com/v1',\n envKey: 'NOUS_API_KEY',\n configKey: 'nous',\n defaultModel: 'hermes-3-llama-3.1-70b',\n knownModels: ['hermes-3-llama-3.1-70b', 'hermes-3-llama-3.1-8b', 'hermes-2-pro-mistral-7b'],\n supportsModelList: false,\n },\n {\n name: 'openrouter',\n displayName: 'OpenRouter (Universal Gateway)',\n defaultBaseUrl: 'https://openrouter.ai/api/v1',\n envKey: 'OPENROUTER_API_KEY',\n configKey: 'openrouter',\n defaultModel: 'anthropic/claude-3.5-sonnet',\n knownModels: [\n 'anthropic/claude-3.5-sonnet',\n 'anthropic/claude-3.5-haiku',\n 'anthropic/claude-3-opus',\n 'openai/gpt-4o',\n 'openai/gpt-4o-mini',\n 'meta-llama/llama-3.3-70b-instruct',\n 'google/gemini-2.5-flash-preview',\n 'deepseek/deepseek-chat',\n 'deepseek/deepseek-r1',\n 'x-ai/grok-3-beta',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n ],\n supportsModelList: true,\n // OpenRouter's /api/v1/models is publicly accessible — TITAN can\n // pull the full ~365-model catalogue even before the user has\n // configured an OPENROUTER_API_KEY. Lets the picker show every\n // available model so the user can choose first, then add the key.\n publicModelList: true,\n extraHeaders: {\n 'HTTP-Referer': 'https://titan.local',\n 'X-Title': 'TITAN',\n },\n },\n {\n name: 'nvidia',\n displayName: 'NVIDIA NIM',\n defaultBaseUrl: 'https://integrate.api.nvidia.com/v1',\n envKey: 'NVIDIA_API_KEY',\n configKey: 'nvidia',\n defaultModel: 'nvidia/llama-3.3-nemotron-super-49b-v1',\n knownModels: [\n 'nvidia/llama-3.3-nemotron-super-49b-v1',\n 'nvidia/llama-3.3-nemotron-super-49b-v1.5',\n 'nvidia/llama-3.1-nemotron-ultra-253b-v1',\n 'nvidia/llama-3.1-nemotron-70b-instruct',\n 'nvidia/nemotron-3-nano-30b-a3b',\n 'nvidia/nemotron-3-super-120b-a12b',\n ],\n supportsModelList: true,\n keepModelPrefix: true,\n },\n {\n name: 'minimax',\n displayName: 'MiniMax',\n defaultBaseUrl: 'https://api.minimax.chat/v1',\n envKey: 'MINIMAX_API_KEY',\n configKey: 'minimax',\n defaultModel: 'minimax-m2.7',\n knownModels: [\n 'minimax-m2.7',\n 'minimax-m2.7-highspeed',\n 'minimax-m2.5',\n 'minimax-01',\n 'minimax-text-01',\n ],\n supportsModelList: false,\n },\n];\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
- const TITAN_VERSION = "5.6.1";
4
+ const TITAN_VERSION = "5.6.3";
5
5
  const TITAN_CODENAME = "Spacewalk";
6
6
  const TITAN_NAME = "TITAN";
7
7
  const TITAN_FULL_NAME = "The Intelligent Task Automation Network";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.6.1';\nexport const TITAN_CODENAME = 'Spacewalk';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAGzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.6.3';\nexport const TITAN_CODENAME = 'Spacewalk';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAGzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "titan-agent",
3
- "version": "5.6.1",
3
+ "version": "5.6.3",
4
4
  "description": "TITAN — Autonomous AI agent framework with self-improvement, multi-agent orchestration, 36 LLM providers, 16 channel adapters, GPU VRAM management, mesh networking, LiveKit voice, TITAN-Soma homeostatic drives, and a React Mission Control dashboard. Open-source, TypeScript, MIT licensed.",
5
5
  "author": "Tony Elliott (https://github.com/Djtony707)",
6
6
  "repository": {
package/ui/dist/sw.js CHANGED
@@ -20,7 +20,7 @@
20
20
  * but a default falls back to the source-controlled value here.
21
21
  */
22
22
 
23
- const CACHE_NAME = 'titan-' + ('1778459534110');
23
+ const CACHE_NAME = 'titan-' + ('1778460886559');
24
24
  const ASSETS_PREFIX = '/assets/';
25
25
 
26
26
  self.addEventListener('install', (event) => {