@moraya/core 0.2.0 → 0.4.0

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.
Files changed (54) hide show
  1. package/README.md +215 -41
  2. package/dist/ai/drivers/claude.d.ts +6 -0
  3. package/dist/ai/drivers/claude.js +229 -0
  4. package/dist/ai/drivers/claude.js.map +1 -0
  5. package/dist/ai/drivers/gemini.d.ts +6 -0
  6. package/dist/ai/drivers/gemini.js +212 -0
  7. package/dist/ai/drivers/gemini.js.map +1 -0
  8. package/dist/ai/drivers/index.d.ts +14 -0
  9. package/dist/ai/drivers/index.js +617 -0
  10. package/dist/ai/drivers/index.js.map +1 -0
  11. package/dist/ai/drivers/ollama.d.ts +8 -0
  12. package/dist/ai/drivers/ollama.js +158 -0
  13. package/dist/ai/drivers/ollama.js.map +1 -0
  14. package/dist/ai/drivers/openai.d.ts +7 -0
  15. package/dist/ai/drivers/openai.js +225 -0
  16. package/dist/ai/drivers/openai.js.map +1 -0
  17. package/dist/ai/drivers/tool-bridge.d.ts +37 -0
  18. package/dist/ai/drivers/tool-bridge.js +138 -0
  19. package/dist/ai/drivers/tool-bridge.js.map +1 -0
  20. package/dist/ai/drivers/types.d.ts +2 -0
  21. package/dist/ai/drivers/types.js +1 -0
  22. package/dist/ai/drivers/types.js.map +1 -0
  23. package/dist/ai/drivers/util.d.ts +13 -0
  24. package/dist/ai/drivers/util.js +40 -0
  25. package/dist/ai/drivers/util.js.map +1 -0
  26. package/dist/ai/image.d.ts +37 -0
  27. package/dist/ai/image.js +36 -0
  28. package/dist/ai/image.js.map +1 -0
  29. package/dist/ai/index.d.ts +37 -0
  30. package/dist/ai/index.js +826 -0
  31. package/dist/ai/index.js.map +1 -0
  32. package/dist/ai/types.d.ts +92 -0
  33. package/dist/ai/types.js +1 -0
  34. package/dist/ai/types.js.map +1 -0
  35. package/dist/ai/voice.d.ts +42 -0
  36. package/dist/ai/voice.js +34 -0
  37. package/dist/ai/voice.js.map +1 -0
  38. package/dist/chat-markdown/index.d.ts +82 -0
  39. package/dist/chat-markdown/index.js +165 -0
  40. package/dist/chat-markdown/index.js.map +1 -0
  41. package/dist/i18n/locales/ar.json +806 -732
  42. package/dist/i18n/locales/de.json +912 -838
  43. package/dist/i18n/locales/en.json +34 -5
  44. package/dist/i18n/locales/es.json +952 -876
  45. package/dist/i18n/locales/fr.json +1784 -1708
  46. package/dist/i18n/locales/hi.json +1808 -1734
  47. package/dist/i18n/locales/ja.json +839 -765
  48. package/dist/i18n/locales/ko.json +1783 -1709
  49. package/dist/i18n/locales/pt.json +894 -820
  50. package/dist/i18n/locales/ru.json +812 -738
  51. package/dist/i18n/locales/zh-CN.json +34 -5
  52. package/dist/i18n/locales/zh-Hant.json +1039 -965
  53. package/dist/types-CwM77g7u.d.ts +88 -0
  54. package/package.json +26 -2
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/ai/drivers/tool-bridge.ts","../../../src/ai/catalog.ts","../../../src/ai/drivers/util.ts","../../../src/ai/drivers/claude.ts","../../../src/ai/drivers/openai.ts","../../../src/ai/drivers/gemini.ts","../../../src/ai/drivers/ollama.ts","../../../src/ai/drivers/index.ts"],"sourcesContent":["/**\n * Tool formatting + response parsing, shared by all consumers.\n * Ported verbatim from the desktop app's tool-bridge (MCP-specific glue like\n * `mcpToolsToToolDefs` stays in the desktop repo — it has no place in core).\n */\nimport type { AIProvider, ToolDefinition, ToolCallRequest } from '../types'\n\nconst GEMINI_UNSUPPORTED_KEYS = new Set([\n 'additionalProperties', '$schema', '$id', '$ref', '$defs', 'definitions',\n 'patternProperties', 'unevaluatedProperties', 'dependentRequired',\n 'dependentSchemas', 'const',\n])\n\nfunction sanitizeGeminiSchema(schema: unknown): unknown {\n if (Array.isArray(schema)) return schema.map(sanitizeGeminiSchema)\n if (schema && typeof schema === 'object') {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(schema as Record<string, unknown>)) {\n if (GEMINI_UNSUPPORTED_KEYS.has(key)) continue\n out[key] = sanitizeGeminiSchema(value)\n }\n return out\n }\n return schema\n}\n\n/** Format tools into provider-specific request-body fields (merge into body). */\nexport function formatToolsForProvider(\n provider: AIProvider,\n tools: ToolDefinition[],\n): Record<string, unknown> {\n if (tools.length === 0) return {}\n switch (provider) {\n case 'claude':\n return {\n tools: tools.map(t => ({ name: t.name, description: t.description, input_schema: t.input_schema })),\n }\n case 'gemini':\n return {\n tools: [{\n functionDeclarations: tools.map(t => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeGeminiSchema(t.input_schema),\n })),\n }],\n }\n default:\n // OpenAI-compatible (openai/deepseek/grok/mistral/glm/minimax/doubao/custom/ollama)\n return {\n tools: tools.map(t => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.input_schema },\n })),\n }\n }\n}\n\nexport function parseClaudeToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const content = data.content as Array<Record<string, unknown>> | undefined\n const stopReason = (data.stop_reason as string) || 'end_turn'\n const toolCalls: ToolCallRequest[] = []\n let textContent = ''\n if (content) {\n for (const block of content) {\n if (block.type === 'tool_use') {\n toolCalls.push({ id: block.id as string, name: block.name as string, arguments: (block.input as Record<string, unknown>) || {} })\n } else if (block.type === 'text') {\n textContent += block.text as string\n }\n }\n }\n return { toolCalls, textContent, stopReason }\n}\n\nexport function parseOpenAIToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const choices = data.choices as Array<Record<string, unknown>> | undefined\n if (!choices || choices.length === 0) return { toolCalls: [], textContent: '', stopReason: 'stop' }\n const choice = choices[0]!\n const message = choice.message as Record<string, unknown> | undefined\n const finishReason = (choice.finish_reason as string) || 'stop'\n const textContent = (message?.content as string) || ''\n const toolCalls: ToolCallRequest[] = []\n const rawToolCalls = message?.tool_calls as Array<Record<string, unknown>> | undefined\n if (rawToolCalls) {\n for (const tc of rawToolCalls) {\n const fn = tc.function as Record<string, unknown>\n let args: Record<string, unknown> = {}\n try { args = JSON.parse(fn.arguments as string) } catch { /* truncated */ }\n toolCalls.push({ id: tc.id as string, name: fn.name as string, arguments: args })\n }\n }\n return { toolCalls, textContent, stopReason: finishReason === 'tool_calls' ? 'tool_use' : finishReason }\n}\n\nexport function parseGeminiToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const candidates = data.candidates as Array<Record<string, unknown>> | undefined\n if (!candidates || candidates.length === 0) return { toolCalls: [], textContent: '', stopReason: 'stop' }\n const content = candidates[0]!.content as Record<string, unknown> | undefined\n const parts = content?.parts as Array<Record<string, unknown>> | undefined\n const toolCalls: ToolCallRequest[] = []\n let textContent = ''\n if (parts) {\n for (const part of parts) {\n if (part.functionCall) {\n const fc = part.functionCall as Record<string, unknown>\n const thoughtSignature =\n (part.thoughtSignature as string | undefined) ?? (fc.thoughtSignature as string | undefined)\n toolCalls.push({\n id: `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n name: fc.name as string,\n arguments: (fc.args as Record<string, unknown>) || {},\n ...(thoughtSignature ? { providerMeta: { thoughtSignature } } : {}),\n })\n } else if (part.text) {\n textContent += part.text as string\n }\n }\n }\n return { toolCalls, textContent, stopReason: toolCalls.length > 0 ? 'tool_use' : 'stop' }\n}\n\nexport function buildClaudeToolResultMessages(\n toolResults: Array<{ callId: string; content: string; isError?: boolean }>,\n): Record<string, unknown> {\n return {\n role: 'user',\n content: toolResults.map(r => ({\n type: 'tool_result', tool_use_id: r.callId, content: r.content, is_error: r.isError || false,\n })),\n }\n}\n\nexport function buildOpenAIToolResultMessages(\n toolResults: Array<{ callId: string; name: string; content: string }>,\n): Array<Record<string, unknown>> {\n return toolResults.map(r => ({ role: 'tool', tool_call_id: r.callId, content: r.content }))\n}\n","/**\n * Provider catalog — default models, base URLs, labels, id aliases.\n * Baselined on the desktop app. Pure data; no host imports.\n */\nimport type { AIProvider } from './types'\n\nexport const DEFAULT_MODELS: Record<AIProvider, string[]> = {\n claude: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],\n openai: ['gpt-5.2', 'gpt-5.2-pro', 'gpt-5', 'gpt-5-mini', 'o4-mini', 'gpt-4o', 'gpt-4o-mini', 'o3', 'o3-mini'],\n gemini: ['gemini-3.1-pro-preview', 'gemini-3-flash-preview', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.0-pro-exp'],\n deepseek: ['deepseek-chat', 'deepseek-reasoner'],\n ollama: ['llama3.3', 'llama3.2', 'qwen2.5', 'qwen2.5-coder', 'phi4', 'gemma3', 'deepseek-r1', 'mistral', 'codellama'],\n grok: ['grok-4', 'grok-4-1-fast-reasoning', 'grok-4-1-fast-non-reasoning', 'grok-code-fast-1', 'grok-3'],\n mistral: ['mistral-large-latest', 'mistral-small-latest', 'magistral-medium-latest', 'magistral-small-latest', 'codestral-latest', 'devstral-latest'],\n glm: ['glm-5', 'glm-4-plus', 'glm-4-air', 'glm-4-flash', 'glm-z1-flash', 'glm-z1-air'],\n minimax: ['MiniMax-M2.5', 'MiniMax-M2.5-highspeed', 'MiniMax-Text-01'],\n doubao: [],\n custom: [],\n 'local-mlx': ['Qwen2.5-1.5B-Instruct-4bit'],\n 'local-llama': ['qwen2.5-1.5b-instruct-q4'],\n}\n\nexport const PROVIDER_BASE_URLS: Record<AIProvider, string> = {\n claude: 'https://api.anthropic.com',\n openai: 'https://api.openai.com',\n gemini: 'https://generativelanguage.googleapis.com',\n deepseek: 'https://api.deepseek.com',\n ollama: 'http://localhost:11434',\n grok: 'https://api.x.ai',\n mistral: 'https://api.mistral.ai',\n glm: 'https://open.bigmodel.cn/api/paas/v4',\n minimax: 'https://api.minimax.io/v1',\n doubao: 'https://ark.cn-beijing.volces.com/api/v3',\n custom: '',\n 'local-mlx': '',\n 'local-llama': '',\n}\n\nexport const PROVIDER_LABELS: Record<AIProvider, string> = {\n claude: 'Claude',\n openai: 'OpenAI',\n gemini: 'Gemini',\n deepseek: 'DeepSeek',\n ollama: 'Ollama',\n grok: 'Grok',\n mistral: 'Mistral',\n glm: 'GLM',\n minimax: 'MiniMax',\n doubao: 'Doubao',\n custom: 'Custom (OpenAI-compatible)',\n 'local-mlx': 'On-device (iOS)',\n 'local-llama': 'On-device (Android)',\n}\n\n/** Legacy/foreign provider ids → canonical `AIProvider`. */\nexport const PROVIDER_ALIASES: Record<string, AIProvider> = {\n anthropic: 'claude',\n}\n\n/** Normalize an incoming provider id (applies aliases). */\nexport function normalizeProvider(id: string): AIProvider {\n return (PROVIDER_ALIASES[id] ?? id) as AIProvider\n}\n","import { PROVIDER_BASE_URLS } from '../catalog'\nimport type { AIProviderConfig } from '../types'\n\nexport function resolveBaseUrl(config: AIProviderConfig, fallback: string): string {\n return config.baseUrl || PROVIDER_BASE_URLS[config.provider] || fallback\n}\n\n/** Build an OpenAI-compatible endpoint, avoiding a double version prefix. */\nexport function openaiEndpoint(baseUrl: string, path: string): string {\n const clean = baseUrl.replace(/\\/+$/, '')\n if (/\\/v\\d+$/.test(clean)) return `${clean}${path}`\n return `${clean}/v1${path}`\n}\n\nexport const NOOP_FOLD = {\n pushEnvelope() { return undefined },\n finish() { return { stopReason: 'end_turn' } },\n}\n","import type { AIProviderConfig, AIRequest, AIResponse, ChatMessage, ToolCallRequest } from '../types'\nimport type { TransportRequest } from '../transport'\nimport type { AIDriver, StreamFold } from './types'\nimport { formatToolsForProvider, parseClaudeToolCalls } from './tool-bridge'\nimport { resolveBaseUrl } from './util'\n\nfunction buildClaudeMessages(messages: ChatMessage[]): Array<Record<string, unknown>> {\n const result: Array<Record<string, unknown>> = []\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {\n const content: Array<Record<string, unknown>> = []\n if (msg.content) content.push({ type: 'text', text: msg.content })\n for (const tc of msg.toolCalls) content.push({ type: 'tool_use', id: tc.id, name: tc.name, input: tc.arguments })\n result.push({ role: 'assistant', content })\n } else if (msg.role === 'tool') {\n const lastMsg = result[result.length - 1]\n const toolResultBlock = { type: 'tool_result', tool_use_id: msg.toolCallId, content: msg.content, is_error: msg.isError || false }\n if (lastMsg && lastMsg.role === 'user' && Array.isArray(lastMsg.content) &&\n (lastMsg.content as Array<Record<string, unknown>>).every(b => b.type === 'tool_result')) {\n (lastMsg.content as Array<Record<string, unknown>>).push(toolResultBlock)\n } else {\n result.push({ role: 'user', content: [toolResultBlock] })\n }\n } else if (msg.role === 'user' && msg.images && msg.images.length > 0) {\n const content: Array<Record<string, unknown>> = []\n for (const img of msg.images) content.push({ type: 'image', source: { type: 'base64', media_type: img.mimeType, data: img.base64 } })\n if (msg.content) content.push({ type: 'text', text: msg.content })\n result.push({ role: 'user', content })\n } else {\n result.push({ role: msg.role, content: msg.content })\n }\n }\n return result\n}\n\nexport const claudeDriver: AIDriver = {\n supportsStreaming: true,\n\n buildChatRequest(config, request, stream): TransportRequest {\n const baseUrl = resolveBaseUrl(config, 'https://api.anthropic.com')\n const systemMessages = request.messages.filter(m => m.role === 'system')\n const chatMessages = request.messages.filter(m => m.role !== 'system')\n\n const body: Record<string, unknown> = {\n model: request.model || config.model,\n max_tokens: request.maxTokens ?? config.maxTokens ?? 41920,\n messages: buildClaudeMessages(chatMessages),\n }\n if (stream) body.stream = true\n if (systemMessages.length > 0) body.system = systemMessages.map(m => m.content).join('\\n')\n const temperature = request.temperature ?? config.temperature\n if (temperature !== undefined) body.temperature = temperature\n const topP = request.topP ?? config.topP\n if (topP !== undefined) body.top_p = topP\n // Anthropic caps stop_sequences (~5) — trim quietly.\n if (request.stop && request.stop.length > 0) body.stop_sequences = request.stop.slice(0, 5)\n if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider('claude', request.tools))\n\n return {\n provider: 'claude',\n configId: config.id,\n method: 'POST',\n url: `${baseUrl}/v1/messages`,\n headers: {\n 'anthropic-version': '2023-06-01',\n // Required for direct browser fetch; harmless when proxied via Rust.\n 'anthropic-dangerous-direct-browser-access': 'true',\n },\n body: JSON.stringify(body),\n auth: { scheme: 'header', headerName: 'x-api-key' },\n }\n },\n\n parseResponse(json, _config): AIResponse {\n const parsed = parseClaudeToolCalls(json)\n const usage = json.usage as Record<string, number> | undefined\n return {\n content: parsed.textContent,\n model: (json.model as string) || _config.model,\n usage: { inputTokens: usage?.input_tokens || 0, outputTokens: usage?.output_tokens || 0 },\n ...(parsed.toolCalls.length > 0 ? { toolCalls: parsed.toolCalls } : {}),\n stopReason: parsed.stopReason,\n }\n },\n\n createStreamFold(): StreamFold {\n const partials = new Map<number, { id: string; name: string; json: string }>()\n const toolCalls: ToolCallRequest[] = []\n let stopReason = 'end_turn'\n let inputTokens = 0\n let outputTokens = 0\n return {\n pushEnvelope(raw) {\n let v: Record<string, unknown>\n try { v = JSON.parse(raw) } catch { return undefined }\n switch (v.type as string) {\n case 'message_start': {\n const u = (v.message as Record<string, unknown> | undefined)?.usage as Record<string, number> | undefined\n if (u?.input_tokens) inputTokens = u.input_tokens\n break\n }\n case 'content_block_delta': {\n const delta = v.delta as Record<string, unknown> | undefined\n if (delta?.type === 'text_delta') return (delta.text as string) || undefined\n if (delta?.type === 'input_json_delta') {\n const p = partials.get(v.index as number)\n if (p) p.json += (delta.partial_json as string) || ''\n }\n break\n }\n case 'content_block_start': {\n const block = v.content_block as Record<string, unknown> | undefined\n if (block?.type === 'tool_use') partials.set(v.index as number, { id: block.id as string, name: block.name as string, json: '' })\n break\n }\n case 'content_block_stop': {\n const p = partials.get(v.index as number)\n if (p) {\n try { toolCalls.push({ id: p.id, name: p.name, arguments: JSON.parse(p.json || '{}') }) } catch { /* truncated */ }\n partials.delete(v.index as number)\n }\n break\n }\n case 'message_delta': {\n const d = v.delta as Record<string, unknown> | undefined\n if (d?.stop_reason) stopReason = d.stop_reason === 'tool_use' ? 'tool_use' : (d.stop_reason as string)\n const u = v.usage as Record<string, number> | undefined\n if (u?.output_tokens) outputTokens = u.output_tokens\n break\n }\n }\n return undefined\n },\n finish() {\n const usage = (inputTokens || outputTokens) ? { inputTokens, outputTokens } : undefined\n return { ...(toolCalls.length > 0 ? { toolCalls } : {}), stopReason, ...(usage ? { usage } : {}) }\n },\n }\n },\n}\n","import type { AIProviderConfig, AIRequest, AIResponse, ChatMessage, ToolCallRequest } from '../types'\nimport type { TransportRequest } from '../transport'\nimport type { AIDriver, StreamFold } from './types'\nimport { formatToolsForProvider, parseOpenAIToolCalls } from './tool-bridge'\nimport { resolveBaseUrl, openaiEndpoint } from './util'\n\nfunction buildOpenAIMessages(messages: ChatMessage[]): Array<Record<string, unknown>> {\n return messages.map(msg => {\n if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {\n return {\n role: 'assistant',\n content: msg.content || null,\n tool_calls: msg.toolCalls.map(tc => ({\n id: tc.id, type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },\n })),\n }\n } else if (msg.role === 'tool') {\n return { role: 'tool', tool_call_id: msg.toolCallId, content: msg.content }\n } else if (msg.role === 'user' && msg.images && msg.images.length > 0) {\n const content: Array<Record<string, unknown>> = []\n for (const img of msg.images) content.push({ type: 'image_url', image_url: { url: `data:${img.mimeType};base64,${img.base64}` } })\n if (msg.content) content.push({ type: 'text', text: msg.content })\n return { role: 'user', content }\n }\n return { role: msg.role, content: msg.content }\n })\n}\n\n/** OpenAI-compatible driver: openai/deepseek/grok/mistral/glm/minimax/doubao/custom. */\nexport const openaiDriver: AIDriver = {\n supportsStreaming: true,\n\n buildChatRequest(config, request, stream): TransportRequest {\n const baseUrl = resolveBaseUrl(config, 'https://api.openai.com')\n const body: Record<string, unknown> = {\n model: request.model || config.model,\n max_tokens: request.maxTokens ?? config.maxTokens ?? 41920,\n temperature: request.temperature ?? config.temperature ?? 0.7,\n messages: buildOpenAIMessages(request.messages),\n }\n if (stream) {\n body.stream = true\n // Ask for token usage in the final SSE chunk. Limited to OpenAI/DeepSeek —\n // some other OpenAI-compatible endpoints reject unknown body fields.\n if (config.provider === 'openai' || config.provider === 'deepseek') {\n body.stream_options = { include_usage: true }\n }\n }\n const topP = request.topP ?? config.topP\n if (topP !== undefined) body.top_p = topP\n // OpenAI rejects >4 stop sequences (400) — trim quietly.\n if (request.stop && request.stop.length > 0) body.stop = request.stop.slice(0, 4)\n if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider(config.provider, request.tools))\n\n // Proxy-provider key: deepseek keeps its own (for Rust auth), everything\n // else OpenAI-compatible maps to 'openai'.\n const proxyProvider = config.provider === 'deepseek' ? 'deepseek' : 'openai'\n return {\n provider: proxyProvider,\n configId: config.id,\n method: 'POST',\n url: openaiEndpoint(baseUrl, '/chat/completions'),\n headers: {},\n body: JSON.stringify(body),\n auth: { scheme: 'bearer' },\n }\n },\n\n parseResponse(json, _config): AIResponse {\n const parsed = parseOpenAIToolCalls(json)\n const usage = json.usage as Record<string, number> | undefined\n return {\n content: parsed.textContent,\n model: (json.model as string) || _config.model,\n usage: { inputTokens: usage?.prompt_tokens || 0, outputTokens: usage?.completion_tokens || 0 },\n ...(parsed.toolCalls.length > 0 ? { toolCalls: parsed.toolCalls } : {}),\n stopReason: parsed.stopReason,\n }\n },\n\n createStreamFold(): StreamFold {\n const toolMap = new Map<number, { id: string; name: string; args: string }>()\n let stopReason = 'end_turn'\n let usage: { inputTokens: number; outputTokens: number } | undefined\n return {\n pushEnvelope(raw) {\n let v: Record<string, unknown>\n try { v = JSON.parse(raw) } catch { return undefined }\n const u = v.usage as Record<string, number> | undefined\n if (u) usage = { inputTokens: u.prompt_tokens || 0, outputTokens: u.completion_tokens || 0 }\n const choices = v.choices as Array<Record<string, unknown>> | undefined\n if (!choices || choices.length === 0) return undefined\n const choice = choices[0]!\n const fr = choice.finish_reason as string | null\n if (fr) stopReason = fr === 'tool_calls' ? 'tool_use' : fr === 'length' ? 'max_tokens' : fr\n const delta = choice.delta as Record<string, unknown> | undefined\n const rawTC = delta?.tool_calls as Array<Record<string, unknown>> | undefined\n if (rawTC) {\n for (const tc of rawTC) {\n const idx = (tc.index as number) ?? 0\n const fn = tc.function as Record<string, unknown> | undefined\n let entry = toolMap.get(idx)\n if (!entry) { entry = { id: (tc.id as string) || '', name: '', args: '' }; toolMap.set(idx, entry) }\n if (tc.id) entry.id = tc.id as string\n if (fn?.name) entry.name = fn.name as string\n if (fn?.arguments) entry.args += fn.arguments as string\n }\n }\n return (delta?.content as string) || undefined\n },\n finish() {\n const toolCalls: ToolCallRequest[] = []\n for (const [, entry] of [...toolMap.entries()].sort((a, b) => a[0] - b[0])) {\n let args: Record<string, unknown> = {}\n try { args = JSON.parse(entry.args || '{}') } catch { continue }\n toolCalls.push({ id: entry.id, name: entry.name, arguments: args })\n }\n return { ...(toolCalls.length > 0 ? { toolCalls } : {}), stopReason, ...(usage ? { usage } : {}) }\n },\n }\n },\n}\n","import type { AIRequest, AIResponse, ChatMessage, ToolCallRequest } from '../types'\nimport type { TransportRequest } from '../transport'\nimport type { AIDriver, StreamFold } from './types'\nimport { formatToolsForProvider, parseGeminiToolCalls } from './tool-bridge'\nimport { resolveBaseUrl } from './util'\n\nfunction buildGeminiContents(messages: ChatMessage[]): Array<Record<string, unknown>> {\n return messages.map(msg => {\n if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {\n const parts: Array<Record<string, unknown>> = []\n if (msg.content) parts.push({ text: msg.content })\n for (const tc of msg.toolCalls) {\n const sig = tc.providerMeta?.thoughtSignature as string | undefined\n const part: Record<string, unknown> = { functionCall: { name: tc.name, args: tc.arguments } }\n if (sig) part.thoughtSignature = sig\n parts.push(part)\n }\n return { role: 'model', parts }\n } else if (msg.role === 'tool') {\n return { role: 'user', parts: [{ functionResponse: { name: msg.toolName, response: { content: msg.content } } }] }\n }\n if (msg.role === 'user' && msg.images && msg.images.length > 0) {\n const parts: Array<Record<string, unknown>> = []\n for (const img of msg.images) parts.push({ inlineData: { mimeType: img.mimeType, data: img.base64 } })\n if (msg.content) parts.push({ text: msg.content })\n return { role: 'user', parts }\n }\n return { role: msg.role === 'assistant' ? 'model' : 'user', parts: [{ text: msg.content }] }\n })\n}\n\nexport const geminiDriver: AIDriver = {\n supportsStreaming: true,\n\n buildChatRequest(config, request, stream): TransportRequest {\n const baseUrl = resolveBaseUrl(config, 'https://generativelanguage.googleapis.com')\n const systemMessages = request.messages.filter(m => m.role === 'system')\n const chatMessages = request.messages.filter(m => m.role !== 'system')\n\n const generationConfig: Record<string, unknown> = {\n maxOutputTokens: request.maxTokens ?? config.maxTokens ?? 41920,\n temperature: request.temperature ?? config.temperature ?? 0.7,\n }\n const topP = request.topP ?? config.topP\n if (topP !== undefined) generationConfig.topP = topP\n if (request.stop && request.stop.length > 0) generationConfig.stopSequences = request.stop\n\n const body: Record<string, unknown> = { contents: buildGeminiContents(chatMessages), generationConfig }\n if (systemMessages.length > 0) body.systemInstruction = { parts: [{ text: systemMessages.map(m => m.content).join('\\n') }] }\n if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider('gemini', request.tools))\n\n const model = request.model || config.model\n const verb = stream ? 'streamGenerateContent?alt=sse' : 'generateContent'\n return {\n provider: 'gemini',\n configId: config.id,\n method: 'POST',\n url: `${baseUrl}/v1beta/models/${model}:${verb}`,\n headers: {},\n body: JSON.stringify(body),\n auth: { scheme: 'query', queryParam: 'key' },\n }\n },\n\n parseResponse(json, config): AIResponse {\n const parsed = parseGeminiToolCalls(json)\n const usage = json.usageMetadata as Record<string, number> | undefined\n return {\n content: parsed.textContent,\n model: config.model,\n usage: { inputTokens: usage?.promptTokenCount || 0, outputTokens: usage?.candidatesTokenCount || 0 },\n ...(parsed.toolCalls.length > 0 ? { toolCalls: parsed.toolCalls } : {}),\n stopReason: parsed.stopReason === 'tool_use' ? 'tool_use' : 'end_turn',\n }\n },\n\n createStreamFold(): StreamFold {\n const toolCalls: ToolCallRequest[] = []\n let stopReason = 'end_turn'\n let usage: { inputTokens: number; outputTokens: number } | undefined\n return {\n pushEnvelope(raw) {\n let v: Record<string, unknown>\n try { v = JSON.parse(raw) } catch { return undefined }\n const um = v.usageMetadata as Record<string, number> | undefined\n if (um) usage = { inputTokens: um.promptTokenCount || 0, outputTokens: um.candidatesTokenCount || 0 }\n const candidates = v.candidates as Array<Record<string, unknown>> | undefined\n const cand = candidates?.[0]\n if (!cand) return undefined\n const parts = (cand.content as Record<string, unknown> | undefined)?.parts as Array<Record<string, unknown>> | undefined\n let text = ''\n if (parts) {\n for (const part of parts) {\n if (part.functionCall) {\n const fc = part.functionCall as Record<string, unknown>\n const sig = (part.thoughtSignature as string | undefined) ?? (fc.thoughtSignature as string | undefined)\n toolCalls.push({\n id: `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n name: fc.name as string,\n arguments: (fc.args as Record<string, unknown>) || {},\n ...(sig ? { providerMeta: { thoughtSignature: sig } } : {}),\n })\n stopReason = 'tool_use'\n } else if (part.text) {\n text += part.text as string\n }\n }\n }\n return text || undefined\n },\n finish() { return { ...(toolCalls.length > 0 ? { toolCalls } : {}), stopReason, ...(usage ? { usage } : {}) } },\n }\n },\n}\n","import type { AIResponse, ChatMessage, ToolCallRequest } from '../types'\nimport type { TransportRequest } from '../transport'\nimport type { AIDriver } from './types'\nimport { formatToolsForProvider } from './tool-bridge'\nimport { resolveBaseUrl, NOOP_FOLD } from './util'\n\nfunction buildOllamaMessages(messages: ChatMessage[]): Array<Record<string, unknown>> {\n return messages.map(msg => {\n if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {\n return {\n role: 'assistant',\n content: msg.content || '',\n tool_calls: msg.toolCalls.map(tc => ({\n id: tc.id, type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },\n })),\n }\n } else if (msg.role === 'tool') {\n return { role: 'tool', tool_call_id: msg.toolCallId, content: msg.content }\n }\n const result: Record<string, unknown> = { role: msg.role, content: msg.content }\n if (msg.role === 'user' && msg.images && msg.images.length > 0) result.images = msg.images.map(img => img.base64)\n return result\n })\n}\n\n/** Ollama uses its native /api/chat. PC always calls it non-streaming, so the\n * orchestrator one-shots it (supportsStreaming=false). */\nexport const ollamaDriver: AIDriver = {\n supportsStreaming: false,\n\n buildChatRequest(config, request, _stream): TransportRequest {\n const baseUrl = resolveBaseUrl(config, 'http://localhost:11434')\n const body: Record<string, unknown> = {\n model: request.model || config.model,\n messages: buildOllamaMessages(request.messages),\n stream: false,\n options: {\n temperature: request.temperature ?? config.temperature ?? 0.7,\n num_predict: request.maxTokens ?? config.maxTokens ?? 41920,\n },\n }\n if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider('ollama', request.tools))\n return {\n provider: 'ollama',\n configId: config.id,\n method: 'POST',\n url: `${baseUrl}/api/chat`,\n headers: {},\n body: JSON.stringify(body),\n auth: { scheme: 'none' },\n }\n },\n\n parseResponse(json, _config): AIResponse {\n const message = json.message as Record<string, unknown> | undefined\n const toolCalls: ToolCallRequest[] = []\n const rawToolCalls = message?.tool_calls as Array<Record<string, unknown>> | undefined\n if (rawToolCalls) {\n for (const tc of rawToolCalls) {\n const fn = tc.function as Record<string, unknown>\n toolCalls.push({\n id: `ollama-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n name: fn.name as string,\n arguments: (fn.arguments as Record<string, unknown>) || {},\n })\n }\n }\n return {\n content: (message?.content as string) || '',\n model: (json.model as string) || _config.model,\n usage: { inputTokens: (json.prompt_eval_count as number) || 0, outputTokens: (json.eval_count as number) || 0 },\n ...(toolCalls.length > 0 ? { toolCalls } : {}),\n stopReason: toolCalls.length > 0 ? 'tool_use' : 'end_turn',\n }\n },\n\n createStreamFold() { return NOOP_FOLD },\n}\n","import type { AIProvider } from '../types'\nimport type { AIDriver } from './types'\nimport { claudeDriver } from './claude'\nimport { openaiDriver } from './openai'\nimport { geminiDriver } from './gemini'\nimport { ollamaDriver } from './ollama'\n\n/** Resolve the driver for an HTTP provider. On-device providers (local-mlx /\n * local-llama) are NOT HTTP — consumers handle those locally and never call\n * the orchestrator for them, so this throws to catch misuse. */\nexport function getDriver(provider: AIProvider): AIDriver {\n switch (provider) {\n case 'claude':\n return claudeDriver\n case 'gemini':\n return geminiDriver\n case 'ollama':\n return ollamaDriver\n case 'openai':\n case 'deepseek':\n case 'grok':\n case 'mistral':\n case 'glm':\n case 'minimax':\n case 'doubao':\n case 'custom':\n return openaiDriver\n default:\n throw new Error(`No HTTP driver for provider: ${provider}`)\n }\n}\n\nexport { claudeDriver, openaiDriver, geminiDriver, ollamaDriver }\nexport type { AIDriver, StreamFold } from './types'\n"],"mappings":"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","names":[]}
@@ -0,0 +1,8 @@
1
+ import { A as AIDriver } from '../../types-CwM77g7u.js';
2
+ import '../types.js';
3
+
4
+ /** Ollama uses its native /api/chat. PC always calls it non-streaming, so the
5
+ * orchestrator one-shots it (supportsStreaming=false). */
6
+ declare const ollamaDriver: AIDriver;
7
+
8
+ export { ollamaDriver };
@@ -0,0 +1,158 @@
1
+ // src/ai/drivers/tool-bridge.ts
2
+ var GEMINI_UNSUPPORTED_KEYS = /* @__PURE__ */ new Set([
3
+ "additionalProperties",
4
+ "$schema",
5
+ "$id",
6
+ "$ref",
7
+ "$defs",
8
+ "definitions",
9
+ "patternProperties",
10
+ "unevaluatedProperties",
11
+ "dependentRequired",
12
+ "dependentSchemas",
13
+ "const"
14
+ ]);
15
+ function sanitizeGeminiSchema(schema) {
16
+ if (Array.isArray(schema)) return schema.map(sanitizeGeminiSchema);
17
+ if (schema && typeof schema === "object") {
18
+ const out = {};
19
+ for (const [key, value] of Object.entries(schema)) {
20
+ if (GEMINI_UNSUPPORTED_KEYS.has(key)) continue;
21
+ out[key] = sanitizeGeminiSchema(value);
22
+ }
23
+ return out;
24
+ }
25
+ return schema;
26
+ }
27
+ function formatToolsForProvider(provider, tools) {
28
+ if (tools.length === 0) return {};
29
+ switch (provider) {
30
+ case "claude":
31
+ return {
32
+ tools: tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema }))
33
+ };
34
+ case "gemini":
35
+ return {
36
+ tools: [{
37
+ functionDeclarations: tools.map((t) => ({
38
+ name: t.name,
39
+ description: t.description,
40
+ parameters: sanitizeGeminiSchema(t.input_schema)
41
+ }))
42
+ }]
43
+ };
44
+ default:
45
+ return {
46
+ tools: tools.map((t) => ({
47
+ type: "function",
48
+ function: { name: t.name, description: t.description, parameters: t.input_schema }
49
+ }))
50
+ };
51
+ }
52
+ }
53
+
54
+ // src/ai/catalog.ts
55
+ var PROVIDER_BASE_URLS = {
56
+ claude: "https://api.anthropic.com",
57
+ openai: "https://api.openai.com",
58
+ gemini: "https://generativelanguage.googleapis.com",
59
+ deepseek: "https://api.deepseek.com",
60
+ ollama: "http://localhost:11434",
61
+ grok: "https://api.x.ai",
62
+ mistral: "https://api.mistral.ai",
63
+ glm: "https://open.bigmodel.cn/api/paas/v4",
64
+ minimax: "https://api.minimax.io/v1",
65
+ doubao: "https://ark.cn-beijing.volces.com/api/v3",
66
+ custom: "",
67
+ "local-mlx": "",
68
+ "local-llama": ""
69
+ };
70
+
71
+ // src/ai/drivers/util.ts
72
+ function resolveBaseUrl(config, fallback) {
73
+ return config.baseUrl || PROVIDER_BASE_URLS[config.provider] || fallback;
74
+ }
75
+ var NOOP_FOLD = {
76
+ pushEnvelope() {
77
+ return void 0;
78
+ },
79
+ finish() {
80
+ return { stopReason: "end_turn" };
81
+ }
82
+ };
83
+
84
+ // src/ai/drivers/ollama.ts
85
+ function buildOllamaMessages(messages) {
86
+ return messages.map((msg) => {
87
+ if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
88
+ return {
89
+ role: "assistant",
90
+ content: msg.content || "",
91
+ tool_calls: msg.toolCalls.map((tc) => ({
92
+ id: tc.id,
93
+ type: "function",
94
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
95
+ }))
96
+ };
97
+ } else if (msg.role === "tool") {
98
+ return { role: "tool", tool_call_id: msg.toolCallId, content: msg.content };
99
+ }
100
+ const result = { role: msg.role, content: msg.content };
101
+ if (msg.role === "user" && msg.images && msg.images.length > 0) result.images = msg.images.map((img) => img.base64);
102
+ return result;
103
+ });
104
+ }
105
+ var ollamaDriver = {
106
+ supportsStreaming: false,
107
+ buildChatRequest(config, request, _stream) {
108
+ const baseUrl = resolveBaseUrl(config, "http://localhost:11434");
109
+ const body = {
110
+ model: request.model || config.model,
111
+ messages: buildOllamaMessages(request.messages),
112
+ stream: false,
113
+ options: {
114
+ temperature: request.temperature ?? config.temperature ?? 0.7,
115
+ num_predict: request.maxTokens ?? config.maxTokens ?? 41920
116
+ }
117
+ };
118
+ if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider("ollama", request.tools));
119
+ return {
120
+ provider: "ollama",
121
+ configId: config.id,
122
+ method: "POST",
123
+ url: `${baseUrl}/api/chat`,
124
+ headers: {},
125
+ body: JSON.stringify(body),
126
+ auth: { scheme: "none" }
127
+ };
128
+ },
129
+ parseResponse(json, _config) {
130
+ const message = json.message;
131
+ const toolCalls = [];
132
+ const rawToolCalls = message?.tool_calls;
133
+ if (rawToolCalls) {
134
+ for (const tc of rawToolCalls) {
135
+ const fn = tc.function;
136
+ toolCalls.push({
137
+ id: `ollama-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
138
+ name: fn.name,
139
+ arguments: fn.arguments || {}
140
+ });
141
+ }
142
+ }
143
+ return {
144
+ content: message?.content || "",
145
+ model: json.model || _config.model,
146
+ usage: { inputTokens: json.prompt_eval_count || 0, outputTokens: json.eval_count || 0 },
147
+ ...toolCalls.length > 0 ? { toolCalls } : {},
148
+ stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn"
149
+ };
150
+ },
151
+ createStreamFold() {
152
+ return NOOP_FOLD;
153
+ }
154
+ };
155
+ export {
156
+ ollamaDriver
157
+ };
158
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/ai/drivers/tool-bridge.ts","../../../src/ai/catalog.ts","../../../src/ai/drivers/util.ts","../../../src/ai/drivers/ollama.ts"],"sourcesContent":["/**\n * Tool formatting + response parsing, shared by all consumers.\n * Ported verbatim from the desktop app's tool-bridge (MCP-specific glue like\n * `mcpToolsToToolDefs` stays in the desktop repo — it has no place in core).\n */\nimport type { AIProvider, ToolDefinition, ToolCallRequest } from '../types'\n\nconst GEMINI_UNSUPPORTED_KEYS = new Set([\n 'additionalProperties', '$schema', '$id', '$ref', '$defs', 'definitions',\n 'patternProperties', 'unevaluatedProperties', 'dependentRequired',\n 'dependentSchemas', 'const',\n])\n\nfunction sanitizeGeminiSchema(schema: unknown): unknown {\n if (Array.isArray(schema)) return schema.map(sanitizeGeminiSchema)\n if (schema && typeof schema === 'object') {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(schema as Record<string, unknown>)) {\n if (GEMINI_UNSUPPORTED_KEYS.has(key)) continue\n out[key] = sanitizeGeminiSchema(value)\n }\n return out\n }\n return schema\n}\n\n/** Format tools into provider-specific request-body fields (merge into body). */\nexport function formatToolsForProvider(\n provider: AIProvider,\n tools: ToolDefinition[],\n): Record<string, unknown> {\n if (tools.length === 0) return {}\n switch (provider) {\n case 'claude':\n return {\n tools: tools.map(t => ({ name: t.name, description: t.description, input_schema: t.input_schema })),\n }\n case 'gemini':\n return {\n tools: [{\n functionDeclarations: tools.map(t => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeGeminiSchema(t.input_schema),\n })),\n }],\n }\n default:\n // OpenAI-compatible (openai/deepseek/grok/mistral/glm/minimax/doubao/custom/ollama)\n return {\n tools: tools.map(t => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.input_schema },\n })),\n }\n }\n}\n\nexport function parseClaudeToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const content = data.content as Array<Record<string, unknown>> | undefined\n const stopReason = (data.stop_reason as string) || 'end_turn'\n const toolCalls: ToolCallRequest[] = []\n let textContent = ''\n if (content) {\n for (const block of content) {\n if (block.type === 'tool_use') {\n toolCalls.push({ id: block.id as string, name: block.name as string, arguments: (block.input as Record<string, unknown>) || {} })\n } else if (block.type === 'text') {\n textContent += block.text as string\n }\n }\n }\n return { toolCalls, textContent, stopReason }\n}\n\nexport function parseOpenAIToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const choices = data.choices as Array<Record<string, unknown>> | undefined\n if (!choices || choices.length === 0) return { toolCalls: [], textContent: '', stopReason: 'stop' }\n const choice = choices[0]!\n const message = choice.message as Record<string, unknown> | undefined\n const finishReason = (choice.finish_reason as string) || 'stop'\n const textContent = (message?.content as string) || ''\n const toolCalls: ToolCallRequest[] = []\n const rawToolCalls = message?.tool_calls as Array<Record<string, unknown>> | undefined\n if (rawToolCalls) {\n for (const tc of rawToolCalls) {\n const fn = tc.function as Record<string, unknown>\n let args: Record<string, unknown> = {}\n try { args = JSON.parse(fn.arguments as string) } catch { /* truncated */ }\n toolCalls.push({ id: tc.id as string, name: fn.name as string, arguments: args })\n }\n }\n return { toolCalls, textContent, stopReason: finishReason === 'tool_calls' ? 'tool_use' : finishReason }\n}\n\nexport function parseGeminiToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const candidates = data.candidates as Array<Record<string, unknown>> | undefined\n if (!candidates || candidates.length === 0) return { toolCalls: [], textContent: '', stopReason: 'stop' }\n const content = candidates[0]!.content as Record<string, unknown> | undefined\n const parts = content?.parts as Array<Record<string, unknown>> | undefined\n const toolCalls: ToolCallRequest[] = []\n let textContent = ''\n if (parts) {\n for (const part of parts) {\n if (part.functionCall) {\n const fc = part.functionCall as Record<string, unknown>\n const thoughtSignature =\n (part.thoughtSignature as string | undefined) ?? (fc.thoughtSignature as string | undefined)\n toolCalls.push({\n id: `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n name: fc.name as string,\n arguments: (fc.args as Record<string, unknown>) || {},\n ...(thoughtSignature ? { providerMeta: { thoughtSignature } } : {}),\n })\n } else if (part.text) {\n textContent += part.text as string\n }\n }\n }\n return { toolCalls, textContent, stopReason: toolCalls.length > 0 ? 'tool_use' : 'stop' }\n}\n\nexport function buildClaudeToolResultMessages(\n toolResults: Array<{ callId: string; content: string; isError?: boolean }>,\n): Record<string, unknown> {\n return {\n role: 'user',\n content: toolResults.map(r => ({\n type: 'tool_result', tool_use_id: r.callId, content: r.content, is_error: r.isError || false,\n })),\n }\n}\n\nexport function buildOpenAIToolResultMessages(\n toolResults: Array<{ callId: string; name: string; content: string }>,\n): Array<Record<string, unknown>> {\n return toolResults.map(r => ({ role: 'tool', tool_call_id: r.callId, content: r.content }))\n}\n","/**\n * Provider catalog — default models, base URLs, labels, id aliases.\n * Baselined on the desktop app. Pure data; no host imports.\n */\nimport type { AIProvider } from './types'\n\nexport const DEFAULT_MODELS: Record<AIProvider, string[]> = {\n claude: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],\n openai: ['gpt-5.2', 'gpt-5.2-pro', 'gpt-5', 'gpt-5-mini', 'o4-mini', 'gpt-4o', 'gpt-4o-mini', 'o3', 'o3-mini'],\n gemini: ['gemini-3.1-pro-preview', 'gemini-3-flash-preview', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.0-pro-exp'],\n deepseek: ['deepseek-chat', 'deepseek-reasoner'],\n ollama: ['llama3.3', 'llama3.2', 'qwen2.5', 'qwen2.5-coder', 'phi4', 'gemma3', 'deepseek-r1', 'mistral', 'codellama'],\n grok: ['grok-4', 'grok-4-1-fast-reasoning', 'grok-4-1-fast-non-reasoning', 'grok-code-fast-1', 'grok-3'],\n mistral: ['mistral-large-latest', 'mistral-small-latest', 'magistral-medium-latest', 'magistral-small-latest', 'codestral-latest', 'devstral-latest'],\n glm: ['glm-5', 'glm-4-plus', 'glm-4-air', 'glm-4-flash', 'glm-z1-flash', 'glm-z1-air'],\n minimax: ['MiniMax-M2.5', 'MiniMax-M2.5-highspeed', 'MiniMax-Text-01'],\n doubao: [],\n custom: [],\n 'local-mlx': ['Qwen2.5-1.5B-Instruct-4bit'],\n 'local-llama': ['qwen2.5-1.5b-instruct-q4'],\n}\n\nexport const PROVIDER_BASE_URLS: Record<AIProvider, string> = {\n claude: 'https://api.anthropic.com',\n openai: 'https://api.openai.com',\n gemini: 'https://generativelanguage.googleapis.com',\n deepseek: 'https://api.deepseek.com',\n ollama: 'http://localhost:11434',\n grok: 'https://api.x.ai',\n mistral: 'https://api.mistral.ai',\n glm: 'https://open.bigmodel.cn/api/paas/v4',\n minimax: 'https://api.minimax.io/v1',\n doubao: 'https://ark.cn-beijing.volces.com/api/v3',\n custom: '',\n 'local-mlx': '',\n 'local-llama': '',\n}\n\nexport const PROVIDER_LABELS: Record<AIProvider, string> = {\n claude: 'Claude',\n openai: 'OpenAI',\n gemini: 'Gemini',\n deepseek: 'DeepSeek',\n ollama: 'Ollama',\n grok: 'Grok',\n mistral: 'Mistral',\n glm: 'GLM',\n minimax: 'MiniMax',\n doubao: 'Doubao',\n custom: 'Custom (OpenAI-compatible)',\n 'local-mlx': 'On-device (iOS)',\n 'local-llama': 'On-device (Android)',\n}\n\n/** Legacy/foreign provider ids → canonical `AIProvider`. */\nexport const PROVIDER_ALIASES: Record<string, AIProvider> = {\n anthropic: 'claude',\n}\n\n/** Normalize an incoming provider id (applies aliases). */\nexport function normalizeProvider(id: string): AIProvider {\n return (PROVIDER_ALIASES[id] ?? id) as AIProvider\n}\n","import { PROVIDER_BASE_URLS } from '../catalog'\nimport type { AIProviderConfig } from '../types'\n\nexport function resolveBaseUrl(config: AIProviderConfig, fallback: string): string {\n return config.baseUrl || PROVIDER_BASE_URLS[config.provider] || fallback\n}\n\n/** Build an OpenAI-compatible endpoint, avoiding a double version prefix. */\nexport function openaiEndpoint(baseUrl: string, path: string): string {\n const clean = baseUrl.replace(/\\/+$/, '')\n if (/\\/v\\d+$/.test(clean)) return `${clean}${path}`\n return `${clean}/v1${path}`\n}\n\nexport const NOOP_FOLD = {\n pushEnvelope() { return undefined },\n finish() { return { stopReason: 'end_turn' } },\n}\n","import type { AIResponse, ChatMessage, ToolCallRequest } from '../types'\nimport type { TransportRequest } from '../transport'\nimport type { AIDriver } from './types'\nimport { formatToolsForProvider } from './tool-bridge'\nimport { resolveBaseUrl, NOOP_FOLD } from './util'\n\nfunction buildOllamaMessages(messages: ChatMessage[]): Array<Record<string, unknown>> {\n return messages.map(msg => {\n if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {\n return {\n role: 'assistant',\n content: msg.content || '',\n tool_calls: msg.toolCalls.map(tc => ({\n id: tc.id, type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },\n })),\n }\n } else if (msg.role === 'tool') {\n return { role: 'tool', tool_call_id: msg.toolCallId, content: msg.content }\n }\n const result: Record<string, unknown> = { role: msg.role, content: msg.content }\n if (msg.role === 'user' && msg.images && msg.images.length > 0) result.images = msg.images.map(img => img.base64)\n return result\n })\n}\n\n/** Ollama uses its native /api/chat. PC always calls it non-streaming, so the\n * orchestrator one-shots it (supportsStreaming=false). */\nexport const ollamaDriver: AIDriver = {\n supportsStreaming: false,\n\n buildChatRequest(config, request, _stream): TransportRequest {\n const baseUrl = resolveBaseUrl(config, 'http://localhost:11434')\n const body: Record<string, unknown> = {\n model: request.model || config.model,\n messages: buildOllamaMessages(request.messages),\n stream: false,\n options: {\n temperature: request.temperature ?? config.temperature ?? 0.7,\n num_predict: request.maxTokens ?? config.maxTokens ?? 41920,\n },\n }\n if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider('ollama', request.tools))\n return {\n provider: 'ollama',\n configId: config.id,\n method: 'POST',\n url: `${baseUrl}/api/chat`,\n headers: {},\n body: JSON.stringify(body),\n auth: { scheme: 'none' },\n }\n },\n\n parseResponse(json, _config): AIResponse {\n const message = json.message as Record<string, unknown> | undefined\n const toolCalls: ToolCallRequest[] = []\n const rawToolCalls = message?.tool_calls as Array<Record<string, unknown>> | undefined\n if (rawToolCalls) {\n for (const tc of rawToolCalls) {\n const fn = tc.function as Record<string, unknown>\n toolCalls.push({\n id: `ollama-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n name: fn.name as string,\n arguments: (fn.arguments as Record<string, unknown>) || {},\n })\n }\n }\n return {\n content: (message?.content as string) || '',\n model: (json.model as string) || _config.model,\n usage: { inputTokens: (json.prompt_eval_count as number) || 0, outputTokens: (json.eval_count as number) || 0 },\n ...(toolCalls.length > 0 ? { toolCalls } : {}),\n stopReason: toolCalls.length > 0 ? 'tool_use' : 'end_turn',\n }\n },\n\n createStreamFold() { return NOOP_FOLD },\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,7 @@
1
+ import { A as AIDriver } from '../../types-CwM77g7u.js';
2
+ import '../types.js';
3
+
4
+ /** OpenAI-compatible driver: openai/deepseek/grok/mistral/glm/minimax/doubao/custom. */
5
+ declare const openaiDriver: AIDriver;
6
+
7
+ export { openaiDriver };
@@ -0,0 +1,225 @@
1
+ // src/ai/drivers/tool-bridge.ts
2
+ var GEMINI_UNSUPPORTED_KEYS = /* @__PURE__ */ new Set([
3
+ "additionalProperties",
4
+ "$schema",
5
+ "$id",
6
+ "$ref",
7
+ "$defs",
8
+ "definitions",
9
+ "patternProperties",
10
+ "unevaluatedProperties",
11
+ "dependentRequired",
12
+ "dependentSchemas",
13
+ "const"
14
+ ]);
15
+ function sanitizeGeminiSchema(schema) {
16
+ if (Array.isArray(schema)) return schema.map(sanitizeGeminiSchema);
17
+ if (schema && typeof schema === "object") {
18
+ const out = {};
19
+ for (const [key, value] of Object.entries(schema)) {
20
+ if (GEMINI_UNSUPPORTED_KEYS.has(key)) continue;
21
+ out[key] = sanitizeGeminiSchema(value);
22
+ }
23
+ return out;
24
+ }
25
+ return schema;
26
+ }
27
+ function formatToolsForProvider(provider, tools) {
28
+ if (tools.length === 0) return {};
29
+ switch (provider) {
30
+ case "claude":
31
+ return {
32
+ tools: tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema }))
33
+ };
34
+ case "gemini":
35
+ return {
36
+ tools: [{
37
+ functionDeclarations: tools.map((t) => ({
38
+ name: t.name,
39
+ description: t.description,
40
+ parameters: sanitizeGeminiSchema(t.input_schema)
41
+ }))
42
+ }]
43
+ };
44
+ default:
45
+ return {
46
+ tools: tools.map((t) => ({
47
+ type: "function",
48
+ function: { name: t.name, description: t.description, parameters: t.input_schema }
49
+ }))
50
+ };
51
+ }
52
+ }
53
+ function parseOpenAIToolCalls(data) {
54
+ const choices = data.choices;
55
+ if (!choices || choices.length === 0) return { toolCalls: [], textContent: "", stopReason: "stop" };
56
+ const choice = choices[0];
57
+ const message = choice.message;
58
+ const finishReason = choice.finish_reason || "stop";
59
+ const textContent = message?.content || "";
60
+ const toolCalls = [];
61
+ const rawToolCalls = message?.tool_calls;
62
+ if (rawToolCalls) {
63
+ for (const tc of rawToolCalls) {
64
+ const fn = tc.function;
65
+ let args = {};
66
+ try {
67
+ args = JSON.parse(fn.arguments);
68
+ } catch {
69
+ }
70
+ toolCalls.push({ id: tc.id, name: fn.name, arguments: args });
71
+ }
72
+ }
73
+ return { toolCalls, textContent, stopReason: finishReason === "tool_calls" ? "tool_use" : finishReason };
74
+ }
75
+
76
+ // src/ai/catalog.ts
77
+ var PROVIDER_BASE_URLS = {
78
+ claude: "https://api.anthropic.com",
79
+ openai: "https://api.openai.com",
80
+ gemini: "https://generativelanguage.googleapis.com",
81
+ deepseek: "https://api.deepseek.com",
82
+ ollama: "http://localhost:11434",
83
+ grok: "https://api.x.ai",
84
+ mistral: "https://api.mistral.ai",
85
+ glm: "https://open.bigmodel.cn/api/paas/v4",
86
+ minimax: "https://api.minimax.io/v1",
87
+ doubao: "https://ark.cn-beijing.volces.com/api/v3",
88
+ custom: "",
89
+ "local-mlx": "",
90
+ "local-llama": ""
91
+ };
92
+
93
+ // src/ai/drivers/util.ts
94
+ function resolveBaseUrl(config, fallback) {
95
+ return config.baseUrl || PROVIDER_BASE_URLS[config.provider] || fallback;
96
+ }
97
+ function openaiEndpoint(baseUrl, path) {
98
+ const clean = baseUrl.replace(/\/+$/, "");
99
+ if (/\/v\d+$/.test(clean)) return `${clean}${path}`;
100
+ return `${clean}/v1${path}`;
101
+ }
102
+
103
+ // src/ai/drivers/openai.ts
104
+ function buildOpenAIMessages(messages) {
105
+ return messages.map((msg) => {
106
+ if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
107
+ return {
108
+ role: "assistant",
109
+ content: msg.content || null,
110
+ tool_calls: msg.toolCalls.map((tc) => ({
111
+ id: tc.id,
112
+ type: "function",
113
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
114
+ }))
115
+ };
116
+ } else if (msg.role === "tool") {
117
+ return { role: "tool", tool_call_id: msg.toolCallId, content: msg.content };
118
+ } else if (msg.role === "user" && msg.images && msg.images.length > 0) {
119
+ const content = [];
120
+ for (const img of msg.images) content.push({ type: "image_url", image_url: { url: `data:${img.mimeType};base64,${img.base64}` } });
121
+ if (msg.content) content.push({ type: "text", text: msg.content });
122
+ return { role: "user", content };
123
+ }
124
+ return { role: msg.role, content: msg.content };
125
+ });
126
+ }
127
+ var openaiDriver = {
128
+ supportsStreaming: true,
129
+ buildChatRequest(config, request, stream) {
130
+ const baseUrl = resolveBaseUrl(config, "https://api.openai.com");
131
+ const body = {
132
+ model: request.model || config.model,
133
+ max_tokens: request.maxTokens ?? config.maxTokens ?? 41920,
134
+ temperature: request.temperature ?? config.temperature ?? 0.7,
135
+ messages: buildOpenAIMessages(request.messages)
136
+ };
137
+ if (stream) {
138
+ body.stream = true;
139
+ if (config.provider === "openai" || config.provider === "deepseek") {
140
+ body.stream_options = { include_usage: true };
141
+ }
142
+ }
143
+ const topP = request.topP ?? config.topP;
144
+ if (topP !== void 0) body.top_p = topP;
145
+ if (request.stop && request.stop.length > 0) body.stop = request.stop.slice(0, 4);
146
+ if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider(config.provider, request.tools));
147
+ const proxyProvider = config.provider === "deepseek" ? "deepseek" : "openai";
148
+ return {
149
+ provider: proxyProvider,
150
+ configId: config.id,
151
+ method: "POST",
152
+ url: openaiEndpoint(baseUrl, "/chat/completions"),
153
+ headers: {},
154
+ body: JSON.stringify(body),
155
+ auth: { scheme: "bearer" }
156
+ };
157
+ },
158
+ parseResponse(json, _config) {
159
+ const parsed = parseOpenAIToolCalls(json);
160
+ const usage = json.usage;
161
+ return {
162
+ content: parsed.textContent,
163
+ model: json.model || _config.model,
164
+ usage: { inputTokens: usage?.prompt_tokens || 0, outputTokens: usage?.completion_tokens || 0 },
165
+ ...parsed.toolCalls.length > 0 ? { toolCalls: parsed.toolCalls } : {},
166
+ stopReason: parsed.stopReason
167
+ };
168
+ },
169
+ createStreamFold() {
170
+ const toolMap = /* @__PURE__ */ new Map();
171
+ let stopReason = "end_turn";
172
+ let usage;
173
+ return {
174
+ pushEnvelope(raw) {
175
+ let v;
176
+ try {
177
+ v = JSON.parse(raw);
178
+ } catch {
179
+ return void 0;
180
+ }
181
+ const u = v.usage;
182
+ if (u) usage = { inputTokens: u.prompt_tokens || 0, outputTokens: u.completion_tokens || 0 };
183
+ const choices = v.choices;
184
+ if (!choices || choices.length === 0) return void 0;
185
+ const choice = choices[0];
186
+ const fr = choice.finish_reason;
187
+ if (fr) stopReason = fr === "tool_calls" ? "tool_use" : fr === "length" ? "max_tokens" : fr;
188
+ const delta = choice.delta;
189
+ const rawTC = delta?.tool_calls;
190
+ if (rawTC) {
191
+ for (const tc of rawTC) {
192
+ const idx = tc.index ?? 0;
193
+ const fn = tc.function;
194
+ let entry = toolMap.get(idx);
195
+ if (!entry) {
196
+ entry = { id: tc.id || "", name: "", args: "" };
197
+ toolMap.set(idx, entry);
198
+ }
199
+ if (tc.id) entry.id = tc.id;
200
+ if (fn?.name) entry.name = fn.name;
201
+ if (fn?.arguments) entry.args += fn.arguments;
202
+ }
203
+ }
204
+ return delta?.content || void 0;
205
+ },
206
+ finish() {
207
+ const toolCalls = [];
208
+ for (const [, entry] of [...toolMap.entries()].sort((a, b) => a[0] - b[0])) {
209
+ let args = {};
210
+ try {
211
+ args = JSON.parse(entry.args || "{}");
212
+ } catch {
213
+ continue;
214
+ }
215
+ toolCalls.push({ id: entry.id, name: entry.name, arguments: args });
216
+ }
217
+ return { ...toolCalls.length > 0 ? { toolCalls } : {}, stopReason, ...usage ? { usage } : {} };
218
+ }
219
+ };
220
+ }
221
+ };
222
+ export {
223
+ openaiDriver
224
+ };
225
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/ai/drivers/tool-bridge.ts","../../../src/ai/catalog.ts","../../../src/ai/drivers/util.ts","../../../src/ai/drivers/openai.ts"],"sourcesContent":["/**\n * Tool formatting + response parsing, shared by all consumers.\n * Ported verbatim from the desktop app's tool-bridge (MCP-specific glue like\n * `mcpToolsToToolDefs` stays in the desktop repo — it has no place in core).\n */\nimport type { AIProvider, ToolDefinition, ToolCallRequest } from '../types'\n\nconst GEMINI_UNSUPPORTED_KEYS = new Set([\n 'additionalProperties', '$schema', '$id', '$ref', '$defs', 'definitions',\n 'patternProperties', 'unevaluatedProperties', 'dependentRequired',\n 'dependentSchemas', 'const',\n])\n\nfunction sanitizeGeminiSchema(schema: unknown): unknown {\n if (Array.isArray(schema)) return schema.map(sanitizeGeminiSchema)\n if (schema && typeof schema === 'object') {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(schema as Record<string, unknown>)) {\n if (GEMINI_UNSUPPORTED_KEYS.has(key)) continue\n out[key] = sanitizeGeminiSchema(value)\n }\n return out\n }\n return schema\n}\n\n/** Format tools into provider-specific request-body fields (merge into body). */\nexport function formatToolsForProvider(\n provider: AIProvider,\n tools: ToolDefinition[],\n): Record<string, unknown> {\n if (tools.length === 0) return {}\n switch (provider) {\n case 'claude':\n return {\n tools: tools.map(t => ({ name: t.name, description: t.description, input_schema: t.input_schema })),\n }\n case 'gemini':\n return {\n tools: [{\n functionDeclarations: tools.map(t => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeGeminiSchema(t.input_schema),\n })),\n }],\n }\n default:\n // OpenAI-compatible (openai/deepseek/grok/mistral/glm/minimax/doubao/custom/ollama)\n return {\n tools: tools.map(t => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.input_schema },\n })),\n }\n }\n}\n\nexport function parseClaudeToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const content = data.content as Array<Record<string, unknown>> | undefined\n const stopReason = (data.stop_reason as string) || 'end_turn'\n const toolCalls: ToolCallRequest[] = []\n let textContent = ''\n if (content) {\n for (const block of content) {\n if (block.type === 'tool_use') {\n toolCalls.push({ id: block.id as string, name: block.name as string, arguments: (block.input as Record<string, unknown>) || {} })\n } else if (block.type === 'text') {\n textContent += block.text as string\n }\n }\n }\n return { toolCalls, textContent, stopReason }\n}\n\nexport function parseOpenAIToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const choices = data.choices as Array<Record<string, unknown>> | undefined\n if (!choices || choices.length === 0) return { toolCalls: [], textContent: '', stopReason: 'stop' }\n const choice = choices[0]!\n const message = choice.message as Record<string, unknown> | undefined\n const finishReason = (choice.finish_reason as string) || 'stop'\n const textContent = (message?.content as string) || ''\n const toolCalls: ToolCallRequest[] = []\n const rawToolCalls = message?.tool_calls as Array<Record<string, unknown>> | undefined\n if (rawToolCalls) {\n for (const tc of rawToolCalls) {\n const fn = tc.function as Record<string, unknown>\n let args: Record<string, unknown> = {}\n try { args = JSON.parse(fn.arguments as string) } catch { /* truncated */ }\n toolCalls.push({ id: tc.id as string, name: fn.name as string, arguments: args })\n }\n }\n return { toolCalls, textContent, stopReason: finishReason === 'tool_calls' ? 'tool_use' : finishReason }\n}\n\nexport function parseGeminiToolCalls(data: Record<string, unknown>): {\n toolCalls: ToolCallRequest[]; textContent: string; stopReason: string\n} {\n const candidates = data.candidates as Array<Record<string, unknown>> | undefined\n if (!candidates || candidates.length === 0) return { toolCalls: [], textContent: '', stopReason: 'stop' }\n const content = candidates[0]!.content as Record<string, unknown> | undefined\n const parts = content?.parts as Array<Record<string, unknown>> | undefined\n const toolCalls: ToolCallRequest[] = []\n let textContent = ''\n if (parts) {\n for (const part of parts) {\n if (part.functionCall) {\n const fc = part.functionCall as Record<string, unknown>\n const thoughtSignature =\n (part.thoughtSignature as string | undefined) ?? (fc.thoughtSignature as string | undefined)\n toolCalls.push({\n id: `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n name: fc.name as string,\n arguments: (fc.args as Record<string, unknown>) || {},\n ...(thoughtSignature ? { providerMeta: { thoughtSignature } } : {}),\n })\n } else if (part.text) {\n textContent += part.text as string\n }\n }\n }\n return { toolCalls, textContent, stopReason: toolCalls.length > 0 ? 'tool_use' : 'stop' }\n}\n\nexport function buildClaudeToolResultMessages(\n toolResults: Array<{ callId: string; content: string; isError?: boolean }>,\n): Record<string, unknown> {\n return {\n role: 'user',\n content: toolResults.map(r => ({\n type: 'tool_result', tool_use_id: r.callId, content: r.content, is_error: r.isError || false,\n })),\n }\n}\n\nexport function buildOpenAIToolResultMessages(\n toolResults: Array<{ callId: string; name: string; content: string }>,\n): Array<Record<string, unknown>> {\n return toolResults.map(r => ({ role: 'tool', tool_call_id: r.callId, content: r.content }))\n}\n","/**\n * Provider catalog — default models, base URLs, labels, id aliases.\n * Baselined on the desktop app. Pure data; no host imports.\n */\nimport type { AIProvider } from './types'\n\nexport const DEFAULT_MODELS: Record<AIProvider, string[]> = {\n claude: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],\n openai: ['gpt-5.2', 'gpt-5.2-pro', 'gpt-5', 'gpt-5-mini', 'o4-mini', 'gpt-4o', 'gpt-4o-mini', 'o3', 'o3-mini'],\n gemini: ['gemini-3.1-pro-preview', 'gemini-3-flash-preview', 'gemini-2.5-flash', 'gemini-2.5-flash-lite', 'gemini-2.0-pro-exp'],\n deepseek: ['deepseek-chat', 'deepseek-reasoner'],\n ollama: ['llama3.3', 'llama3.2', 'qwen2.5', 'qwen2.5-coder', 'phi4', 'gemma3', 'deepseek-r1', 'mistral', 'codellama'],\n grok: ['grok-4', 'grok-4-1-fast-reasoning', 'grok-4-1-fast-non-reasoning', 'grok-code-fast-1', 'grok-3'],\n mistral: ['mistral-large-latest', 'mistral-small-latest', 'magistral-medium-latest', 'magistral-small-latest', 'codestral-latest', 'devstral-latest'],\n glm: ['glm-5', 'glm-4-plus', 'glm-4-air', 'glm-4-flash', 'glm-z1-flash', 'glm-z1-air'],\n minimax: ['MiniMax-M2.5', 'MiniMax-M2.5-highspeed', 'MiniMax-Text-01'],\n doubao: [],\n custom: [],\n 'local-mlx': ['Qwen2.5-1.5B-Instruct-4bit'],\n 'local-llama': ['qwen2.5-1.5b-instruct-q4'],\n}\n\nexport const PROVIDER_BASE_URLS: Record<AIProvider, string> = {\n claude: 'https://api.anthropic.com',\n openai: 'https://api.openai.com',\n gemini: 'https://generativelanguage.googleapis.com',\n deepseek: 'https://api.deepseek.com',\n ollama: 'http://localhost:11434',\n grok: 'https://api.x.ai',\n mistral: 'https://api.mistral.ai',\n glm: 'https://open.bigmodel.cn/api/paas/v4',\n minimax: 'https://api.minimax.io/v1',\n doubao: 'https://ark.cn-beijing.volces.com/api/v3',\n custom: '',\n 'local-mlx': '',\n 'local-llama': '',\n}\n\nexport const PROVIDER_LABELS: Record<AIProvider, string> = {\n claude: 'Claude',\n openai: 'OpenAI',\n gemini: 'Gemini',\n deepseek: 'DeepSeek',\n ollama: 'Ollama',\n grok: 'Grok',\n mistral: 'Mistral',\n glm: 'GLM',\n minimax: 'MiniMax',\n doubao: 'Doubao',\n custom: 'Custom (OpenAI-compatible)',\n 'local-mlx': 'On-device (iOS)',\n 'local-llama': 'On-device (Android)',\n}\n\n/** Legacy/foreign provider ids → canonical `AIProvider`. */\nexport const PROVIDER_ALIASES: Record<string, AIProvider> = {\n anthropic: 'claude',\n}\n\n/** Normalize an incoming provider id (applies aliases). */\nexport function normalizeProvider(id: string): AIProvider {\n return (PROVIDER_ALIASES[id] ?? id) as AIProvider\n}\n","import { PROVIDER_BASE_URLS } from '../catalog'\nimport type { AIProviderConfig } from '../types'\n\nexport function resolveBaseUrl(config: AIProviderConfig, fallback: string): string {\n return config.baseUrl || PROVIDER_BASE_URLS[config.provider] || fallback\n}\n\n/** Build an OpenAI-compatible endpoint, avoiding a double version prefix. */\nexport function openaiEndpoint(baseUrl: string, path: string): string {\n const clean = baseUrl.replace(/\\/+$/, '')\n if (/\\/v\\d+$/.test(clean)) return `${clean}${path}`\n return `${clean}/v1${path}`\n}\n\nexport const NOOP_FOLD = {\n pushEnvelope() { return undefined },\n finish() { return { stopReason: 'end_turn' } },\n}\n","import type { AIProviderConfig, AIRequest, AIResponse, ChatMessage, ToolCallRequest } from '../types'\nimport type { TransportRequest } from '../transport'\nimport type { AIDriver, StreamFold } from './types'\nimport { formatToolsForProvider, parseOpenAIToolCalls } from './tool-bridge'\nimport { resolveBaseUrl, openaiEndpoint } from './util'\n\nfunction buildOpenAIMessages(messages: ChatMessage[]): Array<Record<string, unknown>> {\n return messages.map(msg => {\n if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {\n return {\n role: 'assistant',\n content: msg.content || null,\n tool_calls: msg.toolCalls.map(tc => ({\n id: tc.id, type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },\n })),\n }\n } else if (msg.role === 'tool') {\n return { role: 'tool', tool_call_id: msg.toolCallId, content: msg.content }\n } else if (msg.role === 'user' && msg.images && msg.images.length > 0) {\n const content: Array<Record<string, unknown>> = []\n for (const img of msg.images) content.push({ type: 'image_url', image_url: { url: `data:${img.mimeType};base64,${img.base64}` } })\n if (msg.content) content.push({ type: 'text', text: msg.content })\n return { role: 'user', content }\n }\n return { role: msg.role, content: msg.content }\n })\n}\n\n/** OpenAI-compatible driver: openai/deepseek/grok/mistral/glm/minimax/doubao/custom. */\nexport const openaiDriver: AIDriver = {\n supportsStreaming: true,\n\n buildChatRequest(config, request, stream): TransportRequest {\n const baseUrl = resolveBaseUrl(config, 'https://api.openai.com')\n const body: Record<string, unknown> = {\n model: request.model || config.model,\n max_tokens: request.maxTokens ?? config.maxTokens ?? 41920,\n temperature: request.temperature ?? config.temperature ?? 0.7,\n messages: buildOpenAIMessages(request.messages),\n }\n if (stream) {\n body.stream = true\n // Ask for token usage in the final SSE chunk. Limited to OpenAI/DeepSeek —\n // some other OpenAI-compatible endpoints reject unknown body fields.\n if (config.provider === 'openai' || config.provider === 'deepseek') {\n body.stream_options = { include_usage: true }\n }\n }\n const topP = request.topP ?? config.topP\n if (topP !== undefined) body.top_p = topP\n // OpenAI rejects >4 stop sequences (400) — trim quietly.\n if (request.stop && request.stop.length > 0) body.stop = request.stop.slice(0, 4)\n if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider(config.provider, request.tools))\n\n // Proxy-provider key: deepseek keeps its own (for Rust auth), everything\n // else OpenAI-compatible maps to 'openai'.\n const proxyProvider = config.provider === 'deepseek' ? 'deepseek' : 'openai'\n return {\n provider: proxyProvider,\n configId: config.id,\n method: 'POST',\n url: openaiEndpoint(baseUrl, '/chat/completions'),\n headers: {},\n body: JSON.stringify(body),\n auth: { scheme: 'bearer' },\n }\n },\n\n parseResponse(json, _config): AIResponse {\n const parsed = parseOpenAIToolCalls(json)\n const usage = json.usage as Record<string, number> | undefined\n return {\n content: parsed.textContent,\n model: (json.model as string) || _config.model,\n usage: { inputTokens: usage?.prompt_tokens || 0, outputTokens: usage?.completion_tokens || 0 },\n ...(parsed.toolCalls.length > 0 ? { toolCalls: parsed.toolCalls } : {}),\n stopReason: parsed.stopReason,\n }\n },\n\n createStreamFold(): StreamFold {\n const toolMap = new Map<number, { id: string; name: string; args: string }>()\n let stopReason = 'end_turn'\n let usage: { inputTokens: number; outputTokens: number } | undefined\n return {\n pushEnvelope(raw) {\n let v: Record<string, unknown>\n try { v = JSON.parse(raw) } catch { return undefined }\n const u = v.usage as Record<string, number> | undefined\n if (u) usage = { inputTokens: u.prompt_tokens || 0, outputTokens: u.completion_tokens || 0 }\n const choices = v.choices as Array<Record<string, unknown>> | undefined\n if (!choices || choices.length === 0) return undefined\n const choice = choices[0]!\n const fr = choice.finish_reason as string | null\n if (fr) stopReason = fr === 'tool_calls' ? 'tool_use' : fr === 'length' ? 'max_tokens' : fr\n const delta = choice.delta as Record<string, unknown> | undefined\n const rawTC = delta?.tool_calls as Array<Record<string, unknown>> | undefined\n if (rawTC) {\n for (const tc of rawTC) {\n const idx = (tc.index as number) ?? 0\n const fn = tc.function as Record<string, unknown> | undefined\n let entry = toolMap.get(idx)\n if (!entry) { entry = { id: (tc.id as string) || '', name: '', args: '' }; toolMap.set(idx, entry) }\n if (tc.id) entry.id = tc.id as string\n if (fn?.name) entry.name = fn.name as string\n if (fn?.arguments) entry.args += fn.arguments as string\n }\n }\n return (delta?.content as string) || undefined\n },\n finish() {\n const toolCalls: ToolCallRequest[] = []\n for (const [, entry] of [...toolMap.entries()].sort((a, b) => a[0] - b[0])) {\n let args: Record<string, unknown> = {}\n try { args = JSON.parse(entry.args || '{}') } catch { continue }\n toolCalls.push({ id: entry.id, name: entry.name, arguments: args })\n }\n return { ...(toolCalls.length > 0 ? { toolCalls } : {}), stopReason, ...(usage ? { usage } : {}) }\n },\n }\n },\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,37 @@
1
+ import { AIProvider, ToolDefinition, ToolCallRequest } from '../types.js';
2
+
3
+ /**
4
+ * Tool formatting + response parsing, shared by all consumers.
5
+ * Ported verbatim from the desktop app's tool-bridge (MCP-specific glue like
6
+ * `mcpToolsToToolDefs` stays in the desktop repo — it has no place in core).
7
+ */
8
+
9
+ /** Format tools into provider-specific request-body fields (merge into body). */
10
+ declare function formatToolsForProvider(provider: AIProvider, tools: ToolDefinition[]): Record<string, unknown>;
11
+ declare function parseClaudeToolCalls(data: Record<string, unknown>): {
12
+ toolCalls: ToolCallRequest[];
13
+ textContent: string;
14
+ stopReason: string;
15
+ };
16
+ declare function parseOpenAIToolCalls(data: Record<string, unknown>): {
17
+ toolCalls: ToolCallRequest[];
18
+ textContent: string;
19
+ stopReason: string;
20
+ };
21
+ declare function parseGeminiToolCalls(data: Record<string, unknown>): {
22
+ toolCalls: ToolCallRequest[];
23
+ textContent: string;
24
+ stopReason: string;
25
+ };
26
+ declare function buildClaudeToolResultMessages(toolResults: Array<{
27
+ callId: string;
28
+ content: string;
29
+ isError?: boolean;
30
+ }>): Record<string, unknown>;
31
+ declare function buildOpenAIToolResultMessages(toolResults: Array<{
32
+ callId: string;
33
+ name: string;
34
+ content: string;
35
+ }>): Array<Record<string, unknown>>;
36
+
37
+ export { buildClaudeToolResultMessages, buildOpenAIToolResultMessages, formatToolsForProvider, parseClaudeToolCalls, parseGeminiToolCalls, parseOpenAIToolCalls };