@open-mercato/ai-assistant 0.4.11-develop.2226.2963a4b52d → 0.4.11-develop.2231.cfcd603204

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 (31) hide show
  1. package/README.md +69 -9
  2. package/dist/modules/ai_assistant/api/route/route.js +32 -44
  3. package/dist/modules/ai_assistant/api/route/route.js.map +2 -2
  4. package/dist/modules/ai_assistant/lib/ai-sdk.js +1 -0
  5. package/dist/modules/ai_assistant/lib/ai-sdk.js.map +2 -2
  6. package/dist/modules/ai_assistant/lib/chat-config.js +52 -40
  7. package/dist/modules/ai_assistant/lib/chat-config.js.map +2 -2
  8. package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +65 -0
  9. package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +7 -0
  10. package/dist/modules/ai_assistant/lib/llm-adapters/google.js +59 -0
  11. package/dist/modules/ai_assistant/lib/llm-adapters/google.js.map +7 -0
  12. package/dist/modules/ai_assistant/lib/llm-adapters/openai.js +65 -0
  13. package/dist/modules/ai_assistant/lib/llm-adapters/openai.js.map +7 -0
  14. package/dist/modules/ai_assistant/lib/llm-bootstrap.js +47 -0
  15. package/dist/modules/ai_assistant/lib/llm-bootstrap.js.map +7 -0
  16. package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +203 -0
  17. package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +7 -0
  18. package/jest.config.cjs +1 -0
  19. package/package.json +4 -4
  20. package/src/modules/ai_assistant/api/route/route.ts +49 -46
  21. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +72 -0
  22. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-google.test.ts +71 -0
  23. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +160 -0
  24. package/src/modules/ai_assistant/lib/__tests__/llm-bootstrap.test.ts +81 -0
  25. package/src/modules/ai_assistant/lib/ai-sdk.ts +10 -0
  26. package/src/modules/ai_assistant/lib/chat-config.ts +75 -42
  27. package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +91 -0
  28. package/src/modules/ai_assistant/lib/llm-adapters/google.ts +80 -0
  29. package/src/modules/ai_assistant/lib/llm-adapters/openai.ts +145 -0
  30. package/src/modules/ai_assistant/lib/llm-bootstrap.ts +80 -0
  31. package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +262 -0
package/README.md CHANGED
@@ -115,22 +115,82 @@ curl http://localhost:4096/mcp
115
115
  | `ANTHROPIC_API_KEY` | If using Anthropic | - | Anthropic API key |
116
116
  | `OPENAI_API_KEY` | If using OpenAI | - | OpenAI API key |
117
117
  | `GOOGLE_GENERATIVE_AI_API_KEY` | If using Google | - | Google Generative AI API key |
118
- | `OPENCODE_PROVIDER` | Yes | - | LLM provider: `anthropic`, `openai`, or `google` |
118
+ | `DEEPINFRA_API_KEY` | If using DeepInfra | - | DeepInfra API key (OpenAI-compatible preset) |
119
+ | `GROQ_API_KEY` | If using Groq | - | Groq API key (OpenAI-compatible preset) |
120
+ | `TOGETHER_API_KEY` | If using Together | - | Together AI API key |
121
+ | `FIREWORKS_API_KEY` | If using Fireworks | - | Fireworks AI API key |
122
+ | `AZURE_OPENAI_API_KEY` | If using Azure | - | Azure OpenAI API key |
123
+ | `AZURE_OPENAI_BASE_URL` | If using Azure | - | Azure deployment URL |
124
+ | `LITELLM_API_KEY` | If using LiteLLM | - | LiteLLM proxy API key |
125
+ | `LITELLM_BASE_URL` | If using LiteLLM | `http://localhost:4000/v1` | LiteLLM proxy URL |
126
+ | `OLLAMA_API_KEY` | If using Ollama | `ollama` | Ollama API key (usually `ollama`) |
127
+ | `OLLAMA_BASE_URL` | If using Ollama | `http://localhost:11434/v1` | Ollama local URL |
128
+ | `OPENCODE_PROVIDER` | Yes | - | LLM provider id (any registered provider, see below) |
119
129
  | `OPENCODE_MODEL` | No | See table below | Override the model for selected provider |
120
130
  | `MCP_SERVER_API_KEY` | For production | - | Open Mercato API key (`omk_...`) for MCP server auth |
121
131
  | `MCP_DEV_PORT` | No | `3001` | Port for development MCP server |
122
132
  | `MCP_DEBUG` | No | `false` | Enable debug logging |
123
133
  | `OPENCODE_URL` | No | `http://localhost:4096` | OpenCode server URL |
124
134
 
125
- ### Models by Provider
126
-
127
- If `OPENCODE_MODEL` is not set, these models are used:
135
+ ### Built-in Providers
136
+
137
+ The registry ships 10 built-in providers via the ports & adapters
138
+ architecture (see [`.ai/specs/2026-04-14-llm-provider-ports-and-adapters.md`](../../.ai/specs/2026-04-14-llm-provider-ports-and-adapters.md)).
139
+
140
+ Native protocol adapters:
141
+
142
+ | Provider id | SDK | Default model | Context |
143
+ |-------------|-----|---------------|---------|
144
+ | `anthropic` | `@ai-sdk/anthropic` | `claude-haiku-4-5-20251001` | 200K |
145
+ | `google` | `@ai-sdk/google` | `gemini-3-flash` | 1M |
146
+
147
+ OpenAI-compatible presets (all share one protocol adapter with different
148
+ `baseURL` and env keys):
149
+
150
+ | Provider id | Base URL | Default model | Context |
151
+ |-------------|----------|---------------|---------|
152
+ | `openai` | `api.openai.com` | `gpt-5-mini` | 128K |
153
+ | `deepinfra` | `api.deepinfra.com/v1/openai` | `zai-org/GLM-5.1` | 202K |
154
+ | `groq` | `api.groq.com/openai/v1` | `llama-3.3-70b-versatile` | 131K |
155
+ | `together` | `api.together.xyz/v1` | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | 131K |
156
+ | `fireworks` | `api.fireworks.ai/inference/v1` | `llama-v3p3-70b-instruct` | 131K |
157
+ | `azure` | `$AZURE_OPENAI_BASE_URL` | `gpt-5-mini` | 128K |
158
+ | `litellm` | `$LITELLM_BASE_URL` | `gpt-4o-mini` | 128K |
159
+ | `ollama` | `$OLLAMA_BASE_URL` | `llama3.3` | 131K |
160
+
161
+ DeepInfra preset ships a curated model catalog including `GLM-5.1`,
162
+ `GLM-4.7-Flash`, `Qwen3-235B-A22B-Instruct-2507`,
163
+ `Llama-4-Scout-17B-16E-Instruct`, `DeepSeek-V3.2-Exp`, and
164
+ `Qwen3-Coder-30B-A3B-Instruct`.
165
+
166
+ ### Extending with Custom Providers
167
+
168
+ Downstream applications can register their own providers at bootstrap
169
+ time without forking this package. Example adding a custom LiteLLM
170
+ proxy that routes to multiple upstream backends:
171
+
172
+ ```ts
173
+ // src/bootstrap.ts (downstream app)
174
+ import { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'
175
+ import { createOpenAICompatibleProvider } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/llm-adapters/openai'
176
+
177
+ llmProviderRegistry.register(
178
+ createOpenAICompatibleProvider({
179
+ id: 'internal-litellm',
180
+ name: 'Internal LiteLLM',
181
+ baseURL: process.env.INTERNAL_LITELLM_URL!,
182
+ envKeys: ['INTERNAL_LITELLM_KEY'],
183
+ defaultModel: 'internal/gpt-5',
184
+ defaultModels: [
185
+ { id: 'internal/gpt-5', name: 'GPT-5 (internal)', contextWindow: 128000 },
186
+ ],
187
+ }),
188
+ )
189
+ ```
128
190
 
129
- | Provider | Model | Context Window |
130
- |----------|---------------|----------------|
131
- | `anthropic` | `claude-haiku-4-5-20251001` | 200K tokens |
132
- | `openai` | `gpt-5-mini` | 128K tokens |
133
- | `google` | `gemini-3-flash-preview` | 1M tokens |
191
+ The custom provider is immediately available under its id in
192
+ `OPENCODE_PROVIDER`, the backend settings dropdown, and the routing
193
+ layer no changes needed in core.
134
194
 
135
195
  ### .mcp.json Configuration
136
196
 
@@ -1,18 +1,10 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { generateObject } from "../../lib/ai-sdk.js";
3
- import {
4
- createOpenAI,
5
- createAnthropic,
6
- createGoogleGenerativeAI
7
- } from "../../lib/ai-sdk.js";
8
3
  import { z } from "zod";
9
4
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
10
5
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
11
- import {
12
- resolveFirstConfiguredOpenCodeProvider,
13
- resolveOpenCodeModel,
14
- resolveOpenCodeProviderApiKey
15
- } from "@open-mercato/shared/lib/ai/opencode-provider";
6
+ import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
7
+ import { resolveOpenCodeModel } from "@open-mercato/shared/lib/ai/opencode-provider";
16
8
  import {
17
9
  resolveChatConfig,
18
10
  isProviderConfigured
@@ -34,38 +26,30 @@ const RouteResultSchema = z.object({
34
26
  reasoning: z.string()
35
27
  });
36
28
  function createRoutingModel(providerId, configuredModel) {
37
- const { modelId, modelWithProvider } = resolveOpenCodeModel(providerId, {
38
- overrideModel: configuredModel
39
- });
40
- const apiKey = resolveOpenCodeProviderApiKey(providerId);
41
- if (!apiKey) {
42
- throw new Error(`${providerId.toUpperCase()} API key not configured`);
29
+ const provider = llmProviderRegistry.get(providerId);
30
+ if (!provider) {
31
+ throw new Error(`Unknown provider: ${providerId}`);
43
32
  }
44
- switch (providerId) {
45
- case "openai": {
46
- const openai = createOpenAI({ apiKey });
47
- return {
48
- model: openai(modelId),
49
- modelWithProvider
50
- };
51
- }
52
- case "anthropic": {
53
- const anthropic = createAnthropic({ apiKey });
54
- return {
55
- model: anthropic(modelId),
56
- modelWithProvider
57
- };
58
- }
59
- case "google": {
60
- const google = createGoogleGenerativeAI({ apiKey });
61
- return {
62
- model: google(modelId),
63
- modelWithProvider
64
- };
65
- }
66
- default:
67
- throw new Error(`Unknown provider: ${providerId}`);
33
+ let modelId;
34
+ let modelWithProvider;
35
+ try {
36
+ const resolved = resolveOpenCodeModel(providerId, {
37
+ overrideModel: configuredModel
38
+ });
39
+ modelId = resolved.modelId;
40
+ modelWithProvider = resolved.modelWithProvider;
41
+ } catch {
42
+ const requested = (configuredModel ?? "").trim();
43
+ modelId = requested.length > 0 ? requested : provider.defaultModel;
44
+ modelWithProvider = `${providerId}/${modelId}`;
45
+ }
46
+ const apiKey = provider.resolveApiKey();
47
+ if (!apiKey) {
48
+ const envKey = provider.getConfiguredEnvKey();
49
+ throw new Error(`${envKey} not configured for provider "${providerId}"`);
68
50
  }
51
+ const model = provider.createModel({ modelId, apiKey });
52
+ return { model, modelWithProvider };
69
53
  }
70
54
  async function POST(req) {
71
55
  const auth = await getAuthFromRequest(req);
@@ -86,14 +70,18 @@ async function POST(req) {
86
70
  const container = await createRequestContainer();
87
71
  let config = await resolveChatConfig(container);
88
72
  if (!config) {
89
- const configuredProvider = resolveFirstConfiguredOpenCodeProvider();
90
- if (!configuredProvider) {
73
+ const picked = llmProviderRegistry.resolveFirstConfigured({
74
+ order: ["anthropic", "openai", "google"]
75
+ });
76
+ if (!picked) {
91
77
  return NextResponse.json(
92
- { error: "No AI provider configured. Please set an API key for OpenAI, Anthropic, or Google." },
78
+ {
79
+ error: "No AI provider configured. Please set an API key for one of the registered providers (Anthropic, OpenAI, Google, DeepInfra, Groq, \u2026)."
80
+ },
93
81
  { status: 503 }
94
82
  );
95
83
  }
96
- config = { providerId: configuredProvider, model: "", updatedAt: "" };
84
+ config = { providerId: picked.id, model: "", updatedAt: "" };
97
85
  }
98
86
  console.log("[AI Route] Using provider:", config.providerId);
99
87
  if (!isProviderConfigured(config.providerId)) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/ai_assistant/api/route/route.ts"],
4
- "sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { generateObject } from '../../lib/ai-sdk'\nimport {\n createOpenAI,\n createAnthropic,\n createGoogleGenerativeAI,\n} from '../../lib/ai-sdk'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n resolveFirstConfiguredOpenCodeProvider,\n resolveOpenCodeModel,\n resolveOpenCodeProviderApiKey,\n} from '@open-mercato/shared/lib/ai/opencode-provider'\nimport {\n resolveChatConfig,\n isProviderConfigured,\n type ChatProviderId,\n} from '../../lib/chat-config'\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'AI query routing',\n methods: {\n POST: { summary: 'Route user query to appropriate AI handler' },\n },\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nconst RouteResultSchema = z.object({\n intent: z.enum(['tool', 'general_chat']),\n toolName: z.string().optional(),\n confidence: z.number().min(0).max(1),\n reasoning: z.string(),\n})\n\nfunction createRoutingModel(providerId: ChatProviderId, configuredModel?: string) {\n const { modelId, modelWithProvider } = resolveOpenCodeModel(providerId, {\n overrideModel: configuredModel,\n })\n const apiKey = resolveOpenCodeProviderApiKey(providerId)\n if (!apiKey) {\n throw new Error(`${providerId.toUpperCase()} API key not configured`)\n }\n\n switch (providerId) {\n case 'openai': {\n const openai = createOpenAI({ apiKey })\n return {\n model: openai(modelId) as unknown as Parameters<typeof generateObject>[0]['model'],\n modelWithProvider,\n }\n }\n case 'anthropic': {\n const anthropic = createAnthropic({ apiKey })\n return {\n model: anthropic(modelId) as unknown as Parameters<typeof generateObject>[0]['model'],\n modelWithProvider,\n }\n }\n case 'google': {\n const google = createGoogleGenerativeAI({ apiKey })\n return {\n model: google(modelId) as unknown as Parameters<typeof generateObject>[0]['model'],\n modelWithProvider,\n }\n }\n default:\n throw new Error(`Unknown provider: ${providerId}`)\n }\n}\n\nexport async function POST(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const body = await req.json()\n const { query, availableTools } = body as {\n query: string\n availableTools: Array<{ name: string; description: string }>\n }\n\n console.log('[AI Route] Routing query:', query)\n console.log('[AI Route] Available tools count:', availableTools?.length)\n\n if (!query || typeof query !== 'string') {\n return NextResponse.json({ error: 'query is required' }, { status: 400 })\n }\n\n if (!availableTools || !Array.isArray(availableTools)) {\n return NextResponse.json({ error: 'availableTools array is required' }, { status: 400 })\n }\n\n // Get user's configured provider\n const container = await createRequestContainer()\n let config = await resolveChatConfig(container)\n\n // Fallback to first configured provider\n if (!config) {\n const configuredProvider = resolveFirstConfiguredOpenCodeProvider()\n if (!configuredProvider) {\n return NextResponse.json(\n { error: 'No AI provider configured. Please set an API key for OpenAI, Anthropic, or Google.' },\n { status: 503 }\n )\n }\n config = { providerId: configuredProvider, model: '', updatedAt: '' }\n }\n\n console.log('[AI Route] Using provider:', config.providerId)\n\n // Verify the configured provider is still available\n if (!isProviderConfigured(config.providerId)) {\n return NextResponse.json(\n { error: `Configured provider ${config.providerId} is no longer available. Please update settings.` },\n { status: 503 }\n )\n }\n\n // Use fast model for the configured provider\n const { model, modelWithProvider } = createRoutingModel(config.providerId, config.model)\n\n const toolList = availableTools\n .map((t) => `- ${t.name}: ${t.description}`)\n .join('\\n')\n\n console.log('[AI Route] Calling generateObject with', modelWithProvider)\n\n const result = await generateObject({\n model,\n schema: RouteResultSchema,\n prompt: `You are a routing assistant. Given a user query, determine if they want to use a specific tool or have a general conversation.\n\nAvailable tools:\n${toolList}\n\nUser query: \"${query}\"\n\nRespond with:\n- intent: \"tool\" if user wants to perform an action with a specific tool, \"general_chat\" otherwise\n- toolName: the exact tool name if intent is \"tool\"\n- confidence: 0-1 how confident you are\n- reasoning: brief explanation`,\n })\n\n console.log('[AI Route] Result:', result.object)\n return NextResponse.json(result.object)\n } catch (error) {\n console.error('[AI Route] Error routing query:', error)\n return NextResponse.json(\n { error: 'Routing request failed' },\n { status: 500 }\n )\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { generateObject } from '../../lib/ai-sdk'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { resolveOpenCodeModel } from '@open-mercato/shared/lib/ai/opencode-provider'\nimport {\n resolveChatConfig,\n isProviderConfigured,\n type ChatProviderId,\n} from '../../lib/chat-config'\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'AI query routing',\n methods: {\n POST: { summary: 'Route user query to appropriate AI handler' },\n },\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nconst RouteResultSchema = z.object({\n intent: z.enum(['tool', 'general_chat']),\n toolName: z.string().optional(),\n confidence: z.number().min(0).max(1),\n reasoning: z.string(),\n})\n\nfunction createRoutingModel(providerId: ChatProviderId, configuredModel?: string) {\n const provider = llmProviderRegistry.get(providerId)\n if (!provider) {\n throw new Error(`Unknown provider: ${providerId}`)\n }\n\n // resolveOpenCodeModel is still used for token parsing and provider-prefix\n // validation (`openai/gpt-5-mini` vs `anthropic/claude-\u2026`). It falls back\n // to the provider's defaultModel via the opencode-provider facade, which\n // is only populated for the three native providers \u2014 if the registry\n // returns a preset-based provider whose id is unknown to opencode-provider,\n // we short-circuit and use the provider's own defaultModel.\n let modelId: string\n let modelWithProvider: string\n try {\n const resolved = resolveOpenCodeModel(providerId as 'anthropic' | 'openai' | 'google', {\n overrideModel: configuredModel,\n })\n modelId = resolved.modelId\n modelWithProvider = resolved.modelWithProvider\n } catch {\n // Preset-based provider or unknown id \u2014 fall back to the provider's own\n // model list. The explicit override (if any) wins.\n const requested = (configuredModel ?? '').trim()\n modelId = requested.length > 0 ? requested : provider.defaultModel\n modelWithProvider = `${providerId}/${modelId}`\n }\n\n const apiKey = provider.resolveApiKey()\n if (!apiKey) {\n const envKey = provider.getConfiguredEnvKey()\n throw new Error(`${envKey} not configured for provider \"${providerId}\"`)\n }\n\n const model = provider.createModel({ modelId, apiKey }) as unknown as Parameters<\n typeof generateObject\n >[0]['model']\n return { model, modelWithProvider }\n}\n\nexport async function POST(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const body = await req.json()\n const { query, availableTools } = body as {\n query: string\n availableTools: Array<{ name: string; description: string }>\n }\n\n console.log('[AI Route] Routing query:', query)\n console.log('[AI Route] Available tools count:', availableTools?.length)\n\n if (!query || typeof query !== 'string') {\n return NextResponse.json({ error: 'query is required' }, { status: 400 })\n }\n\n if (!availableTools || !Array.isArray(availableTools)) {\n return NextResponse.json({ error: 'availableTools array is required' }, { status: 400 })\n }\n\n // Get user's configured provider\n const container = await createRequestContainer()\n let config = await resolveChatConfig(container)\n\n // Fallback to first configured provider from the LLM provider registry.\n // Default walk order prioritizes the native adapters (backward compatible)\n // before OpenAI-compatible presets.\n if (!config) {\n const picked = llmProviderRegistry.resolveFirstConfigured({\n order: ['anthropic', 'openai', 'google'],\n })\n if (!picked) {\n return NextResponse.json(\n {\n error:\n 'No AI provider configured. Please set an API key for one of the registered providers (Anthropic, OpenAI, Google, DeepInfra, Groq, \u2026).',\n },\n { status: 503 },\n )\n }\n config = { providerId: picked.id, model: '', updatedAt: '' }\n }\n\n console.log('[AI Route] Using provider:', config.providerId)\n\n // Verify the configured provider is still available\n if (!isProviderConfigured(config.providerId)) {\n return NextResponse.json(\n { error: `Configured provider ${config.providerId} is no longer available. Please update settings.` },\n { status: 503 }\n )\n }\n\n // Use fast model for the configured provider\n const { model, modelWithProvider } = createRoutingModel(config.providerId, config.model)\n\n const toolList = availableTools\n .map((t) => `- ${t.name}: ${t.description}`)\n .join('\\n')\n\n console.log('[AI Route] Calling generateObject with', modelWithProvider)\n\n const result = await generateObject({\n model,\n schema: RouteResultSchema,\n prompt: `You are a routing assistant. Given a user query, determine if they want to use a specific tool or have a general conversation.\n\nAvailable tools:\n${toolList}\n\nUser query: \"${query}\"\n\nRespond with:\n- intent: \"tool\" if user wants to perform an action with a specific tool, \"general_chat\" otherwise\n- toolName: the exact tool name if intent is \"tool\"\n- confidence: 0-1 how confident you are\n- reasoning: brief explanation`,\n })\n\n console.log('[AI Route] Result:', result.object)\n return NextResponse.json(result.object)\n } catch (error) {\n console.error('[AI Route] Error routing query:', error)\n return NextResponse.json(\n { error: 'Routing request failed' },\n { status: 500 }\n )\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@ import { streamText, generateObject, stepCountIs } from "ai";
2
2
  import { createOpenAI } from "@ai-sdk/openai";
3
3
  import { createAnthropic } from "@ai-sdk/anthropic";
4
4
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
5
+ import "./llm-bootstrap.js";
5
6
  export {
6
7
  createAnthropic,
7
8
  createGoogleGenerativeAI,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/ai-sdk.ts"],
4
- "sourcesContent": ["// Re-export AI SDK functions from the ai-assistant package\nexport { streamText, generateObject, stepCountIs } from 'ai'\nexport { createOpenAI } from '@ai-sdk/openai'\nexport { createAnthropic } from '@ai-sdk/anthropic'\nexport { createGoogleGenerativeAI } from '@ai-sdk/google'\n"],
5
- "mappings": "AACA,SAAS,YAAY,gBAAgB,mBAAmB;AACxD,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,gCAAgC;",
4
+ "sourcesContent": ["// Re-export AI SDK functions from the ai-assistant package\nexport { streamText, generateObject, stepCountIs } from 'ai'\nexport { createOpenAI } from '@ai-sdk/openai'\nexport { createAnthropic } from '@ai-sdk/anthropic'\nexport { createGoogleGenerativeAI } from '@ai-sdk/google'\n\n// Side-effect import: registers built-in LLM providers (Anthropic, Google,\n// OpenAI + OpenAI-compatible presets for DeepInfra, Groq, Together, etc.)\n// with the shared `llmProviderRegistry` singleton. Consumers that import\n// from `./ai-sdk` transitively trigger provider bootstrap, so any module\n// using `generateObject` / `streamText` already has the registry populated.\n//\n// @see ./llm-bootstrap.ts\n// @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\nimport './llm-bootstrap'\n"],
5
+ "mappings": "AACA,SAAS,YAAY,gBAAgB,mBAAmB;AACxD,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,gCAAgC;AAUzC,OAAO;",
6
6
  "names": []
7
7
  }
@@ -1,50 +1,58 @@
1
- import {
2
- OPEN_CODE_PROVIDER_IDS,
3
- OPEN_CODE_PROVIDERS,
4
- isOpenCodeProviderConfigured
5
- } from "@open-mercato/shared/lib/ai/opencode-provider";
1
+ import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
2
+ import "./llm-bootstrap.js";
6
3
  const CHAT_CONFIG_KEY = "chat_provider";
7
- const CHAT_PROVIDERS = {
8
- openai: {
9
- name: OPEN_CODE_PROVIDERS.openai.name,
10
- envKeyRequired: OPEN_CODE_PROVIDERS.openai.envKeys[0],
11
- defaultModel: OPEN_CODE_PROVIDERS.openai.defaultModel,
12
- models: [
13
- { id: OPEN_CODE_PROVIDERS.openai.defaultModel, name: "GPT-4o Mini", contextWindow: 128e3 }
14
- ]
15
- },
16
- anthropic: {
17
- name: OPEN_CODE_PROVIDERS.anthropic.name,
18
- envKeyRequired: OPEN_CODE_PROVIDERS.anthropic.envKeys[0],
19
- defaultModel: OPEN_CODE_PROVIDERS.anthropic.defaultModel,
20
- models: [
21
- { id: OPEN_CODE_PROVIDERS.anthropic.defaultModel, name: "Claude Haiku 4.5", contextWindow: 2e5 }
22
- ]
23
- },
24
- google: {
25
- name: OPEN_CODE_PROVIDERS.google.name,
26
- envKeyRequired: OPEN_CODE_PROVIDERS.google.envKeys[0],
27
- defaultModel: OPEN_CODE_PROVIDERS.google.defaultModel,
28
- models: [
29
- { id: OPEN_CODE_PROVIDERS.google.defaultModel, name: "Gemini 3 Flash", contextWindow: 1048576 }
30
- ]
4
+ function providerToChatInfo(provider) {
5
+ return {
6
+ name: provider.name,
7
+ envKeyRequired: provider.envKeys[0],
8
+ defaultModel: provider.defaultModel,
9
+ models: provider.defaultModels.map((m) => ({
10
+ id: m.id,
11
+ name: m.name,
12
+ contextWindow: m.contextWindow
13
+ }))
14
+ };
15
+ }
16
+ const CHAT_PROVIDERS = new Proxy(
17
+ {},
18
+ {
19
+ get(_target, prop) {
20
+ if (typeof prop !== "string") return void 0;
21
+ const provider = llmProviderRegistry.get(prop);
22
+ return provider ? providerToChatInfo(provider) : void 0;
23
+ },
24
+ has(_target, prop) {
25
+ if (typeof prop !== "string") return false;
26
+ return llmProviderRegistry.get(prop) !== null;
27
+ },
28
+ ownKeys() {
29
+ return llmProviderRegistry.list().map((p) => p.id);
30
+ },
31
+ getOwnPropertyDescriptor(_target, prop) {
32
+ if (typeof prop !== "string") return void 0;
33
+ const provider = llmProviderRegistry.get(prop);
34
+ if (!provider) return void 0;
35
+ return {
36
+ enumerable: true,
37
+ configurable: true,
38
+ value: providerToChatInfo(provider)
39
+ };
40
+ }
31
41
  }
32
- };
42
+ );
33
43
  const DEFAULT_CHAT_CONFIG = {
34
44
  providerId: "openai",
35
- model: OPEN_CODE_PROVIDERS.openai.defaultModel
45
+ get model() {
46
+ const provider = llmProviderRegistry.get("openai");
47
+ return provider?.defaultModel ?? "gpt-5-mini";
48
+ }
36
49
  };
37
50
  function isProviderConfigured(providerId) {
38
- return isOpenCodeProviderConfigured(providerId);
51
+ const provider = llmProviderRegistry.get(providerId);
52
+ return provider?.isConfigured() ?? false;
39
53
  }
40
54
  function getConfiguredProviders() {
41
- const providers = [];
42
- for (const providerId of OPEN_CODE_PROVIDER_IDS) {
43
- if (isProviderConfigured(providerId)) {
44
- providers.push(providerId);
45
- }
46
- }
47
- return providers;
55
+ return llmProviderRegistry.listConfigured().map((p) => p.id);
48
56
  }
49
57
  async function resolveChatConfig(resolver, options) {
50
58
  const fallback = options?.defaultValue ?? null;
@@ -76,7 +84,11 @@ async function saveChatConfig(resolver, config) {
76
84
  return fullConfig;
77
85
  }
78
86
  function createDefaultConfig() {
79
- return { ...DEFAULT_CHAT_CONFIG, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
87
+ return {
88
+ providerId: DEFAULT_CHAT_CONFIG.providerId,
89
+ model: DEFAULT_CHAT_CONFIG.model,
90
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
91
+ };
80
92
  }
81
93
  function getModelInfo(providerId, modelId) {
82
94
  const provider = CHAT_PROVIDERS[providerId];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/chat-config.ts"],
4
- "sourcesContent": ["import type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport {\n OPEN_CODE_PROVIDER_IDS,\n OPEN_CODE_PROVIDERS,\n isOpenCodeProviderConfigured,\n type OpenCodeProviderId,\n} from '@open-mercato/shared/lib/ai/opencode-provider'\n\n// Types\nexport type ChatProviderId = OpenCodeProviderId\n\nexport type ChatModelInfo = {\n id: string\n name: string\n contextWindow: number\n}\n\nexport type ChatProviderInfo = {\n name: string\n envKeyRequired: string\n defaultModel: string\n models: ChatModelInfo[]\n}\n\nexport type ChatProviderConfig = {\n providerId: ChatProviderId\n model: string\n updatedAt: string\n}\n\n// Constants\nexport const CHAT_CONFIG_KEY = 'chat_provider'\n\nexport const CHAT_PROVIDERS: Record<ChatProviderId, ChatProviderInfo> = {\n openai: {\n name: OPEN_CODE_PROVIDERS.openai.name,\n envKeyRequired: OPEN_CODE_PROVIDERS.openai.envKeys[0],\n defaultModel: OPEN_CODE_PROVIDERS.openai.defaultModel,\n models: [\n { id: OPEN_CODE_PROVIDERS.openai.defaultModel, name: 'GPT-4o Mini', contextWindow: 128000 },\n ],\n },\n anthropic: {\n name: OPEN_CODE_PROVIDERS.anthropic.name,\n envKeyRequired: OPEN_CODE_PROVIDERS.anthropic.envKeys[0],\n defaultModel: OPEN_CODE_PROVIDERS.anthropic.defaultModel,\n models: [\n { id: OPEN_CODE_PROVIDERS.anthropic.defaultModel, name: 'Claude Haiku 4.5', contextWindow: 200000 },\n ],\n },\n google: {\n name: OPEN_CODE_PROVIDERS.google.name,\n envKeyRequired: OPEN_CODE_PROVIDERS.google.envKeys[0],\n defaultModel: OPEN_CODE_PROVIDERS.google.defaultModel,\n models: [\n { id: OPEN_CODE_PROVIDERS.google.defaultModel, name: 'Gemini 3 Flash', contextWindow: 1048576 },\n ],\n },\n}\n\nexport const DEFAULT_CHAT_CONFIG: Omit<ChatProviderConfig, 'updatedAt'> = {\n providerId: 'openai',\n model: OPEN_CODE_PROVIDERS.openai.defaultModel,\n}\n\n// Provider configuration checks\nexport function isProviderConfigured(providerId: ChatProviderId): boolean {\n return isOpenCodeProviderConfigured(providerId)\n}\n\nexport function getConfiguredProviders(): ChatProviderId[] {\n const providers: ChatProviderId[] = []\n for (const providerId of OPEN_CODE_PROVIDER_IDS) {\n if (isProviderConfigured(providerId)) {\n providers.push(providerId)\n }\n }\n return providers\n}\n\n// Config resolution\ntype Resolver = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport async function resolveChatConfig(\n resolver: Resolver,\n options?: { defaultValue?: ChatProviderConfig | null }\n): Promise<ChatProviderConfig | null> {\n const fallback = options?.defaultValue ?? null\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n return fallback\n }\n try {\n const value = await service.getValue<ChatProviderConfig>('ai_assistant', CHAT_CONFIG_KEY, { defaultValue: fallback })\n return value\n } catch {\n return fallback\n }\n}\n\nexport async function saveChatConfig(\n resolver: Resolver,\n config: Omit<ChatProviderConfig, 'updatedAt'>\n): Promise<ChatProviderConfig> {\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n throw new Error('Configuration service unavailable')\n }\n const fullConfig: ChatProviderConfig = {\n ...config,\n updatedAt: new Date().toISOString(),\n }\n await service.setValue('ai_assistant', CHAT_CONFIG_KEY, fullConfig)\n return fullConfig\n}\n\nexport function createDefaultConfig(): ChatProviderConfig {\n return { ...DEFAULT_CHAT_CONFIG, updatedAt: new Date().toISOString() }\n}\n\n// Get model info by ID\nexport function getModelInfo(providerId: ChatProviderId, modelId: string): ChatModelInfo | null {\n const provider = CHAT_PROVIDERS[providerId]\n if (!provider) return null\n return provider.models.find((m) => m.id === modelId) ?? null\n}\n\n// Format context window for display\nexport function formatContextWindow(contextWindow: number): string {\n if (contextWindow >= 1000000) {\n return `${(contextWindow / 1000000).toFixed(1)}M`\n }\n return `${(contextWindow / 1000).toFixed(0)}K`\n}\n"],
5
- "mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAyBA,MAAM,kBAAkB;AAExB,MAAM,iBAA2D;AAAA,EACtE,QAAQ;AAAA,IACN,MAAM,oBAAoB,OAAO;AAAA,IACjC,gBAAgB,oBAAoB,OAAO,QAAQ,CAAC;AAAA,IACpD,cAAc,oBAAoB,OAAO;AAAA,IACzC,QAAQ;AAAA,MACN,EAAE,IAAI,oBAAoB,OAAO,cAAc,MAAM,eAAe,eAAe,MAAO;AAAA,IAC5F;AAAA,EACF;AAAA,EACA,WAAW;AAAA,IACT,MAAM,oBAAoB,UAAU;AAAA,IACpC,gBAAgB,oBAAoB,UAAU,QAAQ,CAAC;AAAA,IACvD,cAAc,oBAAoB,UAAU;AAAA,IAC5C,QAAQ;AAAA,MACN,EAAE,IAAI,oBAAoB,UAAU,cAAc,MAAM,oBAAoB,eAAe,IAAO;AAAA,IACpG;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM,oBAAoB,OAAO;AAAA,IACjC,gBAAgB,oBAAoB,OAAO,QAAQ,CAAC;AAAA,IACpD,cAAc,oBAAoB,OAAO;AAAA,IACzC,QAAQ;AAAA,MACN,EAAE,IAAI,oBAAoB,OAAO,cAAc,MAAM,kBAAkB,eAAe,QAAQ;AAAA,IAChG;AAAA,EACF;AACF;AAEO,MAAM,sBAA6D;AAAA,EACxE,YAAY;AAAA,EACZ,OAAO,oBAAoB,OAAO;AACpC;AAGO,SAAS,qBAAqB,YAAqC;AACxE,SAAO,6BAA6B,UAAU;AAChD;AAEO,SAAS,yBAA2C;AACzD,QAAM,YAA8B,CAAC;AACrC,aAAW,cAAc,wBAAwB;AAC/C,QAAI,qBAAqB,UAAU,GAAG;AACpC,gBAAU,KAAK,UAAU;AAAA,IAC3B;AAAA,EACF;AACA,SAAO;AACT;AAOA,eAAsB,kBACpB,UACA,SACoC;AACpC,QAAM,WAAW,SAAS,gBAAgB;AAC1C,MAAI;AACJ,MAAI;AACF,cAAU,SAAS,QAA6B,qBAAqB;AAAA,EACvE,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,QAAQ,MAAM,QAAQ,SAA6B,gBAAgB,iBAAiB,EAAE,cAAc,SAAS,CAAC;AACpH,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,eACpB,UACA,QAC6B;AAC7B,MAAI;AACJ,MAAI;AACF,cAAU,SAAS,QAA6B,qBAAqB;AAAA,EACvE,QAAQ;AACN,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACA,QAAM,aAAiC;AAAA,IACrC,GAAG;AAAA,IACH,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACA,QAAM,QAAQ,SAAS,gBAAgB,iBAAiB,UAAU;AAClE,SAAO;AACT;AAEO,SAAS,sBAA0C;AACxD,SAAO,EAAE,GAAG,qBAAqB,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE;AACvE;AAGO,SAAS,aAAa,YAA4B,SAAuC;AAC9F,QAAM,WAAW,eAAe,UAAU;AAC1C,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,KAAK;AAC1D;AAGO,SAAS,oBAAoB,eAA+B;AACjE,MAAI,iBAAiB,KAAS;AAC5B,WAAO,IAAI,gBAAgB,KAAS,QAAQ,CAAC,CAAC;AAAA,EAChD;AACA,SAAO,IAAI,gBAAgB,KAAM,QAAQ,CAAC,CAAC;AAC7C;",
4
+ "sourcesContent": ["import type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport type { LlmProvider } from '@open-mercato/shared/lib/ai/llm-provider'\n// Side-effect: ensures the registry is populated with built-in adapters\n// and OpenAI-compatible presets before this module's getters run.\nimport './llm-bootstrap'\n\n// Types\n//\n// `ChatProviderId` was previously a narrow literal union of three ids\n// (`'anthropic' | 'openai' | 'google'`). After the ports & adapters\n// refactor the registry accepts any stable id string, so this type\n// becomes `string`. Backward-compatibility note: downstream callers that\n// used exhaustive switches on the old union must add a `default:` branch.\n// See `.ai/specs/2026-04-14-llm-provider-ports-and-adapters.md`.\nexport type ChatProviderId = string\n\nexport type ChatModelInfo = {\n id: string\n name: string\n contextWindow: number\n}\n\nexport type ChatProviderInfo = {\n name: string\n envKeyRequired: string\n defaultModel: string\n models: ChatModelInfo[]\n}\n\nexport type ChatProviderConfig = {\n providerId: ChatProviderId\n model: string\n updatedAt: string\n}\n\n// Constants\nexport const CHAT_CONFIG_KEY = 'chat_provider'\n\nfunction providerToChatInfo(provider: LlmProvider): ChatProviderInfo {\n return {\n name: provider.name,\n envKeyRequired: provider.envKeys[0],\n defaultModel: provider.defaultModel,\n models: provider.defaultModels.map((m) => ({\n id: m.id,\n name: m.name,\n contextWindow: m.contextWindow,\n })),\n }\n}\n\n/**\n * `CHAT_PROVIDERS` is a dynamic getter that returns all providers\n * registered with `llmProviderRegistry`. The shape\n * (`Record<string, ChatProviderInfo>`) is preserved so existing code that\n * indexed the map with a string literal (`CHAT_PROVIDERS['anthropic']`)\n * keeps working \u2014 it is now a runtime lookup against the registry.\n */\nexport const CHAT_PROVIDERS: Record<string, ChatProviderInfo> = new Proxy(\n {} as Record<string, ChatProviderInfo>,\n {\n get(_target, prop: string): ChatProviderInfo | undefined {\n if (typeof prop !== 'string') return undefined\n const provider = llmProviderRegistry.get(prop)\n return provider ? providerToChatInfo(provider) : undefined\n },\n has(_target, prop: string): boolean {\n if (typeof prop !== 'string') return false\n return llmProviderRegistry.get(prop) !== null\n },\n ownKeys(): string[] {\n return llmProviderRegistry.list().map((p) => p.id)\n },\n getOwnPropertyDescriptor(_target, prop: string): PropertyDescriptor | undefined {\n if (typeof prop !== 'string') return undefined\n const provider = llmProviderRegistry.get(prop)\n if (!provider) return undefined\n return {\n enumerable: true,\n configurable: true,\n value: providerToChatInfo(provider),\n }\n },\n },\n)\n\nexport const DEFAULT_CHAT_CONFIG: Omit<ChatProviderConfig, 'updatedAt'> = {\n providerId: 'openai',\n get model(): string {\n // Lazy resolution so the bootstrap has a chance to register providers\n // before the default is computed.\n const provider = llmProviderRegistry.get('openai')\n return provider?.defaultModel ?? 'gpt-5-mini'\n },\n}\n\n// Provider configuration checks\nexport function isProviderConfigured(providerId: ChatProviderId): boolean {\n const provider = llmProviderRegistry.get(providerId)\n return provider?.isConfigured() ?? false\n}\n\nexport function getConfiguredProviders(): ChatProviderId[] {\n return llmProviderRegistry\n .listConfigured()\n .map((p) => p.id)\n}\n\n// Config resolution\ntype Resolver = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport async function resolveChatConfig(\n resolver: Resolver,\n options?: { defaultValue?: ChatProviderConfig | null }\n): Promise<ChatProviderConfig | null> {\n const fallback = options?.defaultValue ?? null\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n return fallback\n }\n try {\n const value = await service.getValue<ChatProviderConfig>('ai_assistant', CHAT_CONFIG_KEY, { defaultValue: fallback })\n return value\n } catch {\n return fallback\n }\n}\n\nexport async function saveChatConfig(\n resolver: Resolver,\n config: Omit<ChatProviderConfig, 'updatedAt'>\n): Promise<ChatProviderConfig> {\n let service: ModuleConfigService\n try {\n service = resolver.resolve<ModuleConfigService>('moduleConfigService')\n } catch {\n throw new Error('Configuration service unavailable')\n }\n const fullConfig: ChatProviderConfig = {\n ...config,\n updatedAt: new Date().toISOString(),\n }\n await service.setValue('ai_assistant', CHAT_CONFIG_KEY, fullConfig)\n return fullConfig\n}\n\nexport function createDefaultConfig(): ChatProviderConfig {\n return {\n providerId: DEFAULT_CHAT_CONFIG.providerId,\n model: DEFAULT_CHAT_CONFIG.model,\n updatedAt: new Date().toISOString(),\n }\n}\n\n// Get model info by ID\nexport function getModelInfo(providerId: ChatProviderId, modelId: string): ChatModelInfo | null {\n const provider = CHAT_PROVIDERS[providerId]\n if (!provider) return null\n return provider.models.find((m) => m.id === modelId) ?? null\n}\n\n// Format context window for display\nexport function formatContextWindow(contextWindow: number): string {\n if (contextWindow >= 1000000) {\n return `${(contextWindow / 1000000).toFixed(1)}M`\n }\n return `${(contextWindow / 1000).toFixed(0)}K`\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,65 @@
1
+ import { createAnthropic } from "@ai-sdk/anthropic";
2
+ const DEFAULT_MODEL = "claude-haiku-4-5-20251001";
3
+ const DEFAULT_MODELS = [
4
+ {
5
+ id: "claude-haiku-4-5-20251001",
6
+ name: "Claude Haiku 4.5",
7
+ contextWindow: 2e5,
8
+ tags: ["budget"]
9
+ },
10
+ {
11
+ id: "claude-sonnet-4-6-20260107",
12
+ name: "Claude Sonnet 4.6",
13
+ contextWindow: 2e5,
14
+ tags: ["flagship"]
15
+ },
16
+ {
17
+ id: "claude-opus-4-6-20260107",
18
+ name: "Claude Opus 4.6",
19
+ contextWindow: 1e6,
20
+ tags: ["flagship", "reasoning"]
21
+ }
22
+ ];
23
+ function createAnthropicAdapter() {
24
+ const envKeys = ["ANTHROPIC_API_KEY", "OPENCODE_ANTHROPIC_API_KEY"];
25
+ function resolveApiKey(env) {
26
+ const lookup = env ?? process.env;
27
+ for (const key of envKeys) {
28
+ const value = lookup[key];
29
+ if (typeof value === "string") {
30
+ const trimmed = value.trim();
31
+ if (trimmed.length > 0) return trimmed;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+ return {
37
+ id: "anthropic",
38
+ name: "Anthropic",
39
+ envKeys,
40
+ defaultModel: DEFAULT_MODEL,
41
+ defaultModels: DEFAULT_MODELS,
42
+ isConfigured(env) {
43
+ return resolveApiKey(env) !== null;
44
+ },
45
+ resolveApiKey,
46
+ getConfiguredEnvKey(env) {
47
+ const lookup = env ?? process.env;
48
+ for (const key of envKeys) {
49
+ const value = lookup[key];
50
+ if (typeof value === "string" && value.trim().length > 0) {
51
+ return key;
52
+ }
53
+ }
54
+ return envKeys[0];
55
+ },
56
+ createModel(options) {
57
+ const anthropic = createAnthropic({ apiKey: options.apiKey });
58
+ return anthropic(options.modelId);
59
+ }
60
+ };
61
+ }
62
+ export {
63
+ createAnthropicAdapter
64
+ };
65
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/ai_assistant/lib/llm-adapters/anthropic.ts"],
4
+ "sourcesContent": ["/**\n * AnthropicAdapter \u2014 implements the LlmProvider port for Anthropic's\n * Messages API (Claude Haiku, Sonnet, Opus).\n *\n * Wraps `createAnthropic({ apiKey })` from `@ai-sdk/anthropic` and exposes\n * a curated model list for the AI Assistant UI dropdown.\n *\n * @see packages/shared/src/lib/ai/llm-provider.ts\n * @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport type {\n EnvLookup,\n LlmCreateModelOptions,\n LlmModelInfo,\n LlmProvider,\n} from '@open-mercato/shared/lib/ai/llm-provider'\n\nconst DEFAULT_MODEL = 'claude-haiku-4-5-20251001'\n\nconst DEFAULT_MODELS: readonly LlmModelInfo[] = [\n {\n id: 'claude-haiku-4-5-20251001',\n name: 'Claude Haiku 4.5',\n contextWindow: 200000,\n tags: ['budget'],\n },\n {\n id: 'claude-sonnet-4-6-20260107',\n name: 'Claude Sonnet 4.6',\n contextWindow: 200000,\n tags: ['flagship'],\n },\n {\n id: 'claude-opus-4-6-20260107',\n name: 'Claude Opus 4.6',\n contextWindow: 1000000,\n tags: ['flagship', 'reasoning'],\n },\n] as const\n\n/**\n * Factory returning a fresh `AnthropicAdapter` instance. The adapter is\n * stateless \u2014 caller is free to reuse the returned object.\n */\nexport function createAnthropicAdapter(): LlmProvider {\n const envKeys = ['ANTHROPIC_API_KEY', 'OPENCODE_ANTHROPIC_API_KEY'] as const\n\n function resolveApiKey(env?: EnvLookup): string | null {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n }\n\n return {\n id: 'anthropic',\n name: 'Anthropic',\n envKeys,\n defaultModel: DEFAULT_MODEL,\n defaultModels: DEFAULT_MODELS,\n\n isConfigured(env?: EnvLookup): boolean {\n return resolveApiKey(env) !== null\n },\n\n resolveApiKey,\n\n getConfiguredEnvKey(env?: EnvLookup): string {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return key\n }\n }\n return envKeys[0]\n },\n\n createModel(options: LlmCreateModelOptions): unknown {\n const anthropic = createAnthropic({ apiKey: options.apiKey })\n return anthropic(options.modelId)\n },\n }\n}\n"],
5
+ "mappings": "AAWA,SAAS,uBAAuB;AAQhC,MAAM,gBAAgB;AAEtB,MAAM,iBAA0C;AAAA,EAC9C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,QAAQ;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,UAAU;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,YAAY,WAAW;AAAA,EAChC;AACF;AAMO,SAAS,yBAAsC;AACpD,QAAM,UAAU,CAAC,qBAAqB,4BAA4B;AAElE,WAAS,cAAc,KAAgC;AACrD,UAAM,SAAS,OAAO,QAAQ;AAC9B,eAAW,OAAO,SAAS;AACzB,YAAM,QAAQ,OAAO,GAAG;AACxB,UAAI,OAAO,UAAU,UAAU;AAC7B,cAAM,UAAU,MAAM,KAAK;AAC3B,YAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,MACjC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN;AAAA,IACA,cAAc;AAAA,IACd,eAAe;AAAA,IAEf,aAAa,KAA0B;AACrC,aAAO,cAAc,GAAG,MAAM;AAAA,IAChC;AAAA,IAEA;AAAA,IAEA,oBAAoB,KAAyB;AAC3C,YAAM,SAAS,OAAO,QAAQ;AAC9B,iBAAW,OAAO,SAAS;AACzB,cAAM,QAAQ,OAAO,GAAG;AACxB,YAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO,QAAQ,CAAC;AAAA,IAClB;AAAA,IAEA,YAAY,SAAyC;AACnD,YAAM,YAAY,gBAAgB,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAC5D,aAAO,UAAU,QAAQ,OAAO;AAAA,IAClC;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,59 @@
1
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
2
+ const DEFAULT_MODEL = "gemini-3-flash";
3
+ const DEFAULT_MODELS = [
4
+ {
5
+ id: "gemini-3-flash",
6
+ name: "Gemini 3 Flash",
7
+ contextWindow: 1048576,
8
+ tags: ["budget"]
9
+ },
10
+ {
11
+ id: "gemini-3-pro",
12
+ name: "Gemini 3 Pro",
13
+ contextWindow: 1048576,
14
+ tags: ["flagship"]
15
+ }
16
+ ];
17
+ function createGoogleAdapter() {
18
+ const envKeys = ["GOOGLE_GENERATIVE_AI_API_KEY", "OPENCODE_GOOGLE_API_KEY"];
19
+ function resolveApiKey(env) {
20
+ const lookup = env ?? process.env;
21
+ for (const key of envKeys) {
22
+ const value = lookup[key];
23
+ if (typeof value === "string") {
24
+ const trimmed = value.trim();
25
+ if (trimmed.length > 0) return trimmed;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ return {
31
+ id: "google",
32
+ name: "Google",
33
+ envKeys,
34
+ defaultModel: DEFAULT_MODEL,
35
+ defaultModels: DEFAULT_MODELS,
36
+ isConfigured(env) {
37
+ return resolveApiKey(env) !== null;
38
+ },
39
+ resolveApiKey,
40
+ getConfiguredEnvKey(env) {
41
+ const lookup = env ?? process.env;
42
+ for (const key of envKeys) {
43
+ const value = lookup[key];
44
+ if (typeof value === "string" && value.trim().length > 0) {
45
+ return key;
46
+ }
47
+ }
48
+ return envKeys[0];
49
+ },
50
+ createModel(options) {
51
+ const google = createGoogleGenerativeAI({ apiKey: options.apiKey });
52
+ return google(options.modelId);
53
+ }
54
+ };
55
+ }
56
+ export {
57
+ createGoogleAdapter
58
+ };
59
+ //# sourceMappingURL=google.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/ai_assistant/lib/llm-adapters/google.ts"],
4
+ "sourcesContent": ["/**\n * GoogleAdapter \u2014 implements the LlmProvider port for Google's Generative\n * AI API (Gemini Flash, Pro).\n *\n * Wraps `createGoogleGenerativeAI({ apiKey })` from `@ai-sdk/google`.\n *\n * @see packages/shared/src/lib/ai/llm-provider.ts\n * @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport type {\n EnvLookup,\n LlmCreateModelOptions,\n LlmModelInfo,\n LlmProvider,\n} from '@open-mercato/shared/lib/ai/llm-provider'\n\nconst DEFAULT_MODEL = 'gemini-3-flash'\n\nconst DEFAULT_MODELS: readonly LlmModelInfo[] = [\n {\n id: 'gemini-3-flash',\n name: 'Gemini 3 Flash',\n contextWindow: 1048576,\n tags: ['budget'],\n },\n {\n id: 'gemini-3-pro',\n name: 'Gemini 3 Pro',\n contextWindow: 1048576,\n tags: ['flagship'],\n },\n] as const\n\nexport function createGoogleAdapter(): LlmProvider {\n const envKeys = ['GOOGLE_GENERATIVE_AI_API_KEY', 'OPENCODE_GOOGLE_API_KEY'] as const\n\n function resolveApiKey(env?: EnvLookup): string | null {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n }\n\n return {\n id: 'google',\n name: 'Google',\n envKeys,\n defaultModel: DEFAULT_MODEL,\n defaultModels: DEFAULT_MODELS,\n\n isConfigured(env?: EnvLookup): boolean {\n return resolveApiKey(env) !== null\n },\n\n resolveApiKey,\n\n getConfiguredEnvKey(env?: EnvLookup): string {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return key\n }\n }\n return envKeys[0]\n },\n\n createModel(options: LlmCreateModelOptions): unknown {\n const google = createGoogleGenerativeAI({ apiKey: options.apiKey })\n return google(options.modelId)\n },\n }\n}\n"],
5
+ "mappings": "AAUA,SAAS,gCAAgC;AAQzC,MAAM,gBAAgB;AAEtB,MAAM,iBAA0C;AAAA,EAC9C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,QAAQ;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,UAAU;AAAA,EACnB;AACF;AAEO,SAAS,sBAAmC;AACjD,QAAM,UAAU,CAAC,gCAAgC,yBAAyB;AAE1E,WAAS,cAAc,KAAgC;AACrD,UAAM,SAAS,OAAO,QAAQ;AAC9B,eAAW,OAAO,SAAS;AACzB,YAAM,QAAQ,OAAO,GAAG;AACxB,UAAI,OAAO,UAAU,UAAU;AAC7B,cAAM,UAAU,MAAM,KAAK;AAC3B,YAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,MACjC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN;AAAA,IACA,cAAc;AAAA,IACd,eAAe;AAAA,IAEf,aAAa,KAA0B;AACrC,aAAO,cAAc,GAAG,MAAM;AAAA,IAChC;AAAA,IAEA;AAAA,IAEA,oBAAoB,KAAyB;AAC3C,YAAM,SAAS,OAAO,QAAQ;AAC9B,iBAAW,OAAO,SAAS;AACzB,cAAM,QAAQ,OAAO,GAAG;AACxB,YAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO,QAAQ,CAAC;AAAA,IAClB;AAAA,IAEA,YAAY,SAAyC;AACnD,YAAM,SAAS,yBAAyB,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAClE,aAAO,OAAO,QAAQ,OAAO;AAAA,IAC/B;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }