@open-mercato/ai-assistant 0.6.6-develop.6465.1.019f0fb26f → 0.6.6-develop.6471.1.9ab5bdd79a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frontend/hooks/useCommandPalette.js +16 -14
- package/dist/frontend/hooks/useCommandPalette.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js +5 -3
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +4 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js +5 -3
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js +4 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/agents/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +6 -7
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/route.js +5 -3
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/conversations/import/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/conversations/import/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/conversations/route.js +4 -2
- package/dist/modules/ai_assistant/api/ai/conversations/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/run-object/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/run-object/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/chat/route.js +23 -14
- package/dist/modules/ai_assistant/api/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/health/route.js +3 -1
- package/dist/modules/ai_assistant/api/health/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/mcp-key/route.js +3 -1
- package/dist/modules/ai_assistant/api/mcp-key/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/route/route.js +11 -9
- package/dist/modules/ai_assistant/api/route/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/session-key/route.js +3 -1
- package/dist/modules/ai_assistant/api/session-key/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js +4 -2
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/route.js +6 -4
- package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/tools/execute/route.js +3 -1
- package/dist/modules/ai_assistant/api/tools/execute/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/tools/route.js +3 -1
- package/dist/modules/ai_assistant/api/tools/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/daily/route.js +3 -1
- package/dist/modules/ai_assistant/api/usage/daily/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js +3 -1
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/sessions/route.js +3 -1
- package/dist/modules/ai_assistant/api/usage/sessions/route.js.map +2 -2
- package/dist/modules/ai_assistant/cli.js +3 -0
- package/dist/modules/ai_assistant/cli.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-policy.js +7 -5
- package/dist/modules/ai_assistant/lib/agent-policy.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-registry.js +11 -13
- package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +21 -44
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-tools.js +5 -10
- package/dist/modules/ai_assistant/lib/agent-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-overrides.js +5 -9
- package/dist/modules/ai_assistant/lib/ai-overrides.js.map +2 -2
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js +22 -22
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +2 -2
- package/dist/modules/ai_assistant/lib/attachment-parts.js +10 -26
- package/dist/modules/ai_assistant/lib/attachment-parts.js.map +2 -2
- package/dist/modules/ai_assistant/lib/auth.js +3 -1
- package/dist/modules/ai_assistant/lib/auth.js.map +2 -2
- package/dist/modules/ai_assistant/lib/codemode-tools.js +15 -17
- package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/entity-index.js +7 -5
- package/dist/modules/ai_assistant/lib/entity-index.js.map +2 -2
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js +3 -4
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/http-server.js +45 -48
- package/dist/modules/ai_assistant/lib/http-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-bootstrap.js +5 -12
- package/dist/modules/ai_assistant/lib/llm-bootstrap.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-client.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-dev-key-resolution.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-dev-key-resolution.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-server.js +24 -19
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-factory.js +4 -2
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/opencode-client.js +5 -3
- package/dist/modules/ai_assistant/lib/opencode-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/opencode-handlers.js +30 -12
- package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +2 -2
- package/dist/modules/ai_assistant/lib/pending-action-cancel.js +3 -1
- package/dist/modules/ai_assistant/lib/pending-action-cancel.js.map +2 -2
- package/dist/modules/ai_assistant/lib/pending-action-executor.js +3 -1
- package/dist/modules/ai_assistant/lib/pending-action-executor.js.map +2 -2
- package/dist/modules/ai_assistant/lib/prepare-mutation.js +4 -6
- package/dist/modules/ai_assistant/lib/prepare-mutation.js.map +2 -2
- package/dist/modules/ai_assistant/lib/schema-utils.js +3 -1
- package/dist/modules/ai_assistant/lib/schema-utils.js.map +2 -2
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js +4 -8
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-executor.js +3 -1
- package/dist/modules/ai_assistant/lib/tool-executor.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-loader.js +18 -25
- package/dist/modules/ai_assistant/lib/tool-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-registry.js +3 -1
- package/dist/modules/ai_assistant/lib/tool-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-test-runner.js +3 -1
- package/dist/modules/ai_assistant/lib/tool-test-runner.js.map +2 -2
- package/dist/modules/ai_assistant/setup.js +4 -8
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/dist/modules/ai_assistant/subscribers/conversation-shared-notify.js +3 -1
- package/dist/modules/ai_assistant/subscribers/conversation-shared-notify.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js +7 -19
- package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js +5 -11
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js.map +2 -2
- package/package.json +6 -6
- package/src/frontend/hooks/useCommandPalette.ts +17 -16
- package/src/modules/ai_assistant/__tests__/events.test.ts +23 -5
- package/src/modules/ai_assistant/__tests__/integration/ws-c-attachment-bridge.test.ts +21 -3
- package/src/modules/ai_assistant/__tests__/integration/ws-c-policy-and-tools.test.ts +22 -3
- package/src/modules/ai_assistant/__tests__/token-usage-recorder.test.ts +26 -5
- package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/actions/[id]/confirm/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/actions/[id]/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.ts +6 -3
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +5 -2
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.ts +6 -3
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.ts +5 -2
- package/src/modules/ai_assistant/api/ai/agents/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/chat/route.ts +7 -7
- package/src/modules/ai_assistant/api/ai/conversations/[conversationId]/route.ts +6 -3
- package/src/modules/ai_assistant/api/ai/conversations/import/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/conversations/route.ts +5 -2
- package/src/modules/ai_assistant/api/ai/run-object/route.ts +4 -1
- package/src/modules/ai_assistant/api/chat/route.ts +24 -15
- package/src/modules/ai_assistant/api/health/route.ts +4 -1
- package/src/modules/ai_assistant/api/mcp-key/route.ts +4 -1
- package/src/modules/ai_assistant/api/route/route.ts +12 -9
- package/src/modules/ai_assistant/api/session-key/route.ts +4 -1
- package/src/modules/ai_assistant/api/settings/allowlist/route.ts +5 -2
- package/src/modules/ai_assistant/api/settings/route.ts +7 -4
- package/src/modules/ai_assistant/api/tools/execute/route.ts +4 -1
- package/src/modules/ai_assistant/api/tools/route.ts +4 -1
- package/src/modules/ai_assistant/api/usage/daily/route.ts +4 -1
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/route.ts +4 -1
- package/src/modules/ai_assistant/api/usage/sessions/route.ts +4 -1
- package/src/modules/ai_assistant/cli.ts +5 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-policy.mutation-override.test.ts +20 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-registry.test.ts +25 -5
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase0.test.ts +21 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-parity.test.ts +23 -3
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +20 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-tools.test.ts +22 -3
- package/src/modules/ai_assistant/lib/__tests__/ai-overrides.test.ts +23 -3
- package/src/modules/ai_assistant/lib/__tests__/attachment-parts.test.ts +25 -7
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +25 -5
- package/src/modules/ai_assistant/lib/__tests__/pending-action-cancel.test.ts +19 -1
- package/src/modules/ai_assistant/lib/__tests__/prepare-mutation.test.ts +22 -3
- package/src/modules/ai_assistant/lib/agent-policy.ts +8 -5
- package/src/modules/ai_assistant/lib/agent-registry.ts +12 -15
- package/src/modules/ai_assistant/lib/agent-runtime.ts +23 -46
- package/src/modules/ai_assistant/lib/agent-tools.ts +7 -11
- package/src/modules/ai_assistant/lib/ai-overrides.ts +6 -9
- package/src/modules/ai_assistant/lib/api-endpoint-index.ts +23 -22
- package/src/modules/ai_assistant/lib/attachment-parts.ts +14 -29
- package/src/modules/ai_assistant/lib/auth.ts +4 -1
- package/src/modules/ai_assistant/lib/codemode-tools.ts +17 -17
- package/src/modules/ai_assistant/lib/entity-index.ts +8 -5
- package/src/modules/ai_assistant/lib/generated-registry-loader.ts +4 -4
- package/src/modules/ai_assistant/lib/http-server.ts +46 -48
- package/src/modules/ai_assistant/lib/llm-bootstrap.ts +7 -13
- package/src/modules/ai_assistant/lib/mcp-client.ts +4 -1
- package/src/modules/ai_assistant/lib/mcp-dev-key-resolution.ts +4 -1
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +4 -1
- package/src/modules/ai_assistant/lib/mcp-server.ts +27 -19
- package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +4 -1
- package/src/modules/ai_assistant/lib/model-factory.ts +6 -3
- package/src/modules/ai_assistant/lib/opencode-client.ts +6 -3
- package/src/modules/ai_assistant/lib/opencode-handlers.ts +32 -12
- package/src/modules/ai_assistant/lib/pending-action-cancel.ts +4 -1
- package/src/modules/ai_assistant/lib/pending-action-executor.ts +4 -1
- package/src/modules/ai_assistant/lib/prepare-mutation.ts +5 -6
- package/src/modules/ai_assistant/lib/schema-utils.ts +4 -1
- package/src/modules/ai_assistant/lib/token-usage-recorder.ts +5 -8
- package/src/modules/ai_assistant/lib/tool-executor.ts +4 -1
- package/src/modules/ai_assistant/lib/tool-loader.ts +19 -25
- package/src/modules/ai_assistant/lib/tool-registry.ts +4 -1
- package/src/modules/ai_assistant/lib/tool-test-runner.ts +4 -1
- package/src/modules/ai_assistant/setup.ts +5 -8
- package/src/modules/ai_assistant/subscribers/conversation-shared-notify.ts +4 -1
- package/src/modules/ai_assistant/workers/ai-pending-action-cleanup.ts +8 -19
- package/src/modules/ai_assistant/workers/ai-token-usage-prune.ts +6 -11
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { generateObject } from "../../lib/ai-sdk.js";
|
|
3
4
|
import { z } from "zod";
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
isProviderConfigured
|
|
11
12
|
} from "../../lib/chat-config.js";
|
|
12
13
|
import { createModelFactory, AiModelFactoryError } from "../../lib/model-factory.js";
|
|
14
|
+
const logger = createLogger("ai_assistant");
|
|
13
15
|
const openApi = {
|
|
14
16
|
tag: "AI Assistant",
|
|
15
17
|
summary: "AI query routing",
|
|
@@ -60,8 +62,8 @@ async function POST(req) {
|
|
|
60
62
|
try {
|
|
61
63
|
const body = await req.json();
|
|
62
64
|
const { query, availableTools } = body;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
logger.debug("Routing query received", { queryChars: query?.length ?? 0 });
|
|
66
|
+
logger.debug("Available tools count", { count: availableTools?.length });
|
|
65
67
|
if (!query || typeof query !== "string") {
|
|
66
68
|
return NextResponse.json({ error: "query is required" }, { status: 400 });
|
|
67
69
|
}
|
|
@@ -87,9 +89,9 @@ async function POST(req) {
|
|
|
87
89
|
}
|
|
88
90
|
throw error;
|
|
89
91
|
}
|
|
90
|
-
|
|
92
|
+
logger.debug("Using provider", { providerId: factoryResolution.providerId });
|
|
91
93
|
const modelWithProvider2 = `${factoryResolution.providerId}/${factoryResolution.modelId}`;
|
|
92
|
-
|
|
94
|
+
logger.debug("Calling generateObject", { model: modelWithProvider2 });
|
|
93
95
|
const result2 = await generateObject({
|
|
94
96
|
model: factoryResolution.model,
|
|
95
97
|
schema: RouteResultSchema,
|
|
@@ -106,10 +108,10 @@ Respond with:
|
|
|
106
108
|
- confidence: 0-1 how confident you are
|
|
107
109
|
- reasoning: brief explanation`
|
|
108
110
|
});
|
|
109
|
-
|
|
111
|
+
logger.debug("Routing result", { resultKeys: Object.keys(result2.object ?? {}).join(",") });
|
|
110
112
|
return NextResponse.json(result2.object);
|
|
111
113
|
}
|
|
112
|
-
|
|
114
|
+
logger.debug("Using provider", { providerId: config.providerId });
|
|
113
115
|
if (!isProviderConfigured(config.providerId)) {
|
|
114
116
|
return NextResponse.json(
|
|
115
117
|
{ error: `Configured provider ${config.providerId} is no longer available. Please update settings.` },
|
|
@@ -118,7 +120,7 @@ Respond with:
|
|
|
118
120
|
}
|
|
119
121
|
const { model, modelWithProvider } = createRoutingModel(config.providerId, config.model);
|
|
120
122
|
const toolList = availableTools.map((t) => `- ${t.name}: ${t.description}`).join("\n");
|
|
121
|
-
|
|
123
|
+
logger.debug("Calling generateObject", { model: modelWithProvider });
|
|
122
124
|
const result = await generateObject({
|
|
123
125
|
model,
|
|
124
126
|
schema: RouteResultSchema,
|
|
@@ -135,10 +137,10 @@ Respond with:
|
|
|
135
137
|
- confidence: 0-1 how confident you are
|
|
136
138
|
- reasoning: brief explanation`
|
|
137
139
|
});
|
|
138
|
-
|
|
140
|
+
logger.debug("Routing result", { resultKeys: Object.keys(result.object ?? {}).join(",") });
|
|
139
141
|
return NextResponse.json(result.object);
|
|
140
142
|
} catch (error) {
|
|
141
|
-
|
|
143
|
+
logger.error("AI Route \u2014 Error routing query", { err: error });
|
|
142
144
|
return NextResponse.json(
|
|
143
145
|
{ error: "Routing request failed" },
|
|
144
146
|
{ status: 500 }
|
|
@@ -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 { 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'\nimport { createModelFactory, AiModelFactoryError } from '../../lib/model-factory'\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
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAE/C,SAAS,sBAAsB;AAC/B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AACpC,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,oBAAoB,2BAA2B;
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { 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'\nimport { createModelFactory, AiModelFactoryError } from '../../lib/model-factory'\n\nconst logger = createLogger('ai_assistant')\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 logger.debug('Routing query received', { queryChars: query?.length ?? 0 })\n logger.debug('Available tools count', { 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 // When no DB-stored config is present, delegate provider + model resolution\n // to createModelFactory so OM_AI_PROVIDER / OM_AI_MODEL (Phase 0 of spec\n // 2026-04-27-ai-agents-provider-model-baseurl-overrides) and all registered\n // OpenAI-compatible presets are respected without duplicating the\n // resolution chain here. Legacy OPENCODE_PROVIDER / OPENCODE_MODEL envs are\n // still honored as BC fallbacks inside the factory.\n if (!config) {\n let factoryResolution\n try {\n factoryResolution = createModelFactory(container).resolveModel({\n callerOverride: undefined,\n })\n } catch (error) {\n if (error instanceof AiModelFactoryError && error.code === 'no_provider_configured') {\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 throw error\n }\n\n logger.debug('Using provider', { providerId: factoryResolution.providerId })\n\n const modelWithProvider = `${factoryResolution.providerId}/${factoryResolution.modelId}`\n logger.debug('Calling generateObject', { model: modelWithProvider })\n\n const result = await generateObject({\n model: factoryResolution.model as Parameters<typeof generateObject>[0]['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${availableTools.map((t) => `- ${t.name}: ${t.description}`).join('\\n')}\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 logger.debug('Routing result', { resultKeys: Object.keys(result.object ?? {}).join(',') })\n return NextResponse.json(result.object)\n }\n\n logger.debug('Using provider', { providerId: 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 logger.debug('Calling generateObject', { model: 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 logger.debug('Routing result', { resultKeys: Object.keys(result.object ?? {}).join(',') })\n return NextResponse.json(result.object)\n } catch (error) {\n logger.error('AI Route \u2014 Error routing query', { err: 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": ["modelWithProvider", "result"]
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
} from "@open-mercato/core/modules/api_keys/services/apiKeyService";
|
|
9
10
|
import { UserRole } from "@open-mercato/core/modules/auth/data/entities";
|
|
10
11
|
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
12
|
+
const logger = createLogger("ai_assistant");
|
|
11
13
|
const metadata = {
|
|
12
14
|
POST: { requireAuth: true, requireFeatures: ["ai_assistant.view"] }
|
|
13
15
|
};
|
|
@@ -48,7 +50,7 @@ async function POST(req) {
|
|
|
48
50
|
expiresAt: expiresAt.toISOString()
|
|
49
51
|
});
|
|
50
52
|
} catch (error) {
|
|
51
|
-
|
|
53
|
+
logger.error("Session Key \u2014 Error", { err: error });
|
|
52
54
|
return NextResponse.json(
|
|
53
55
|
{ error: "Failed to create session key" },
|
|
54
56
|
{ status: 500 }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/api/session-key/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n generateSessionToken,\n createSessionApiKey,\n} from '@open-mercato/core/modules/api_keys/services/apiKeyService'\nimport { UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nconst SESSION_TTL_MINUTES = 120 // 2 hours\n\n/**\n * Get user's role IDs from the database.\n */\nasync function getUserRoleIds(\n em: EntityManager,\n userId: string,\n tenantId: string | null\n): Promise<string[]> {\n if (!tenantId) return []\n\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as any, role: { tenantId } } as any,\n { populate: ['role'] },\n { tenantId, organizationId: null },\n )\n const linkList = Array.isArray(links) ? links : []\n return linkList\n .map((l) => (l.role as any)?.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n}\n\n/**\n * POST /api/ai_assistant/session-key\n *\n * Creates an ephemeral session API key for programmatic LLM access.\n * The key inherits the user's permissions and expires after 2 hours.\n */\nexport async function POST(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n // Get user's role IDs from database\n const userRoleIds = await getUserRoleIds(em, auth.sub, auth.tenantId)\n\n // Generate session token and create ephemeral key\n const sessionToken = generateSessionToken()\n const expiresAt = new Date(Date.now() + SESSION_TTL_MINUTES * 60 * 1000)\n\n await createSessionApiKey(em, {\n sessionToken,\n userId: auth.sub,\n userRoles: userRoleIds,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n ttlMinutes: SESSION_TTL_MINUTES,\n })\n\n return NextResponse.json({\n sessionToken,\n expiresAt: expiresAt.toISOString(),\n })\n } catch (error) {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n generateSessionToken,\n createSessionApiKey,\n} from '@open-mercato/core/modules/api_keys/services/apiKeyService'\nimport { UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst logger = createLogger('ai_assistant')\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nconst SESSION_TTL_MINUTES = 120 // 2 hours\n\n/**\n * Get user's role IDs from the database.\n */\nasync function getUserRoleIds(\n em: EntityManager,\n userId: string,\n tenantId: string | null\n): Promise<string[]> {\n if (!tenantId) return []\n\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as any, role: { tenantId } } as any,\n { populate: ['role'] },\n { tenantId, organizationId: null },\n )\n const linkList = Array.isArray(links) ? links : []\n return linkList\n .map((l) => (l.role as any)?.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n}\n\n/**\n * POST /api/ai_assistant/session-key\n *\n * Creates an ephemeral session API key for programmatic LLM access.\n * The key inherits the user's permissions and expires after 2 hours.\n */\nexport async function POST(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n // Get user's role IDs from database\n const userRoleIds = await getUserRoleIds(em, auth.sub, auth.tenantId)\n\n // Generate session token and create ephemeral key\n const sessionToken = generateSessionToken()\n const expiresAt = new Date(Date.now() + SESSION_TTL_MINUTES * 60 * 1000)\n\n await createSessionApiKey(em, {\n sessionToken,\n userId: auth.sub,\n userRoles: userRoleIds,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n ttlMinutes: SESSION_TTL_MINUTES,\n })\n\n return NextResponse.json({\n sessionToken,\n expiresAt: expiresAt.toISOString(),\n })\n } catch (error) {\n logger.error('Session Key \u2014 Error', { err: error })\n return NextResponse.json(\n { error: 'Failed to create session key' },\n { status: 500 }\n )\n }\n}\n\n// OpenAPI documentation\nconst responseSchema = z.object({\n sessionToken: z.string().describe('Ephemeral session token for MCP tool calls'),\n expiresAt: z.string().describe('ISO 8601 timestamp when the token expires'),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Create session API key',\n description:\n 'Creates an ephemeral session API key for programmatic LLM access to MCP tools. The key inherits the authenticated user\\'s permissions and expires after 2 hours.',\n methods: {\n POST: {\n summary: 'Generate session key',\n description:\n 'Generates a new session token that can be included in MCP tool calls via the _sessionToken parameter. The token inherits the calling user\\'s roles and organization context.',\n responses: [\n {\n status: 200,\n description: 'Session key created successfully',\n schema: responseSchema,\n },\n ],\n errors: [\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 500, description: 'Failed to create session key', schema: errorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,0BAA0B;AAGnC,MAAM,SAAS,aAAa,cAAc;AAEnC,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,MAAM,sBAAsB;AAK5B,eAAe,eACb,IACA,QACA,UACmB;AACnB,MAAI,CAAC,SAAU,QAAO,CAAC;AAEvB,QAAM,QAAQ,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA,EAAE,MAAM,QAAe,MAAM,EAAE,SAAS,EAAE;AAAA,IAC1C,EAAE,UAAU,CAAC,MAAM,EAAE;AAAA,IACrB,EAAE,UAAU,gBAAgB,KAAK;AAAA,EACnC;AACA,QAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC;AACjD,SAAO,SACJ,IAAI,CAAC,MAAO,EAAE,MAAc,EAAE,EAC9B,OAAO,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC;AACzE;AAQA,eAAsB,KAAK,KAAkB;AAC3C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,KAAK;AACd,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAK,UAAU,QAAuB,IAAI;AAGhD,UAAM,cAAc,MAAM,eAAe,IAAI,KAAK,KAAK,KAAK,QAAQ;AAGpE,UAAM,eAAe,qBAAqB;AAC1C,UAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,sBAAsB,KAAK,GAAI;AAEvE,UAAM,oBAAoB,IAAI;AAAA,MAC5B;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,WAAW;AAAA,MACX,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,MACrB,YAAY;AAAA,IACd,CAAC;AAED,WAAO,aAAa,KAAK;AAAA,MACvB;AAAA,MACA,WAAW,UAAU,YAAY;AAAA,IACnC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,MAAM,4BAAuB,EAAE,KAAK,MAAM,CAAC;AAClD,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,+BAA+B;AAAA,MACxC,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAGA,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,cAAc,EAAE,OAAO,EAAE,SAAS,4CAA4C;AAAA,EAC9E,WAAW,EAAE,OAAO,EAAE,SAAS,2CAA2C;AAC5E,CAAC;AAED,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,OAAO,EAAE,OAAO;AAClB,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,aACE;AAAA,EACF,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aACE;AAAA,MACF,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,gCAAgC,QAAQ,YAAY;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
isProviderAllowed,
|
|
11
12
|
modelAllowlistEnvVarName
|
|
12
13
|
} from "../../../lib/model-allowlist.js";
|
|
14
|
+
const logger = createLogger("ai_assistant");
|
|
13
15
|
const allowlistUpsertSchema = z.object({
|
|
14
16
|
allowedProviders: z.array(z.string().min(1).max(64)).nullable().optional(),
|
|
15
17
|
allowedModelsByProvider: z.record(z.string().min(1).max(64), z.array(z.string().min(1).max(256))).optional()
|
|
@@ -148,7 +150,7 @@ async function PUT(req) {
|
|
|
148
150
|
updatedAt: row.updatedAt
|
|
149
151
|
});
|
|
150
152
|
} catch (error) {
|
|
151
|
-
|
|
153
|
+
logger.error("AI Settings Allowlist \u2014 PUT error", { err: error });
|
|
152
154
|
return NextResponse.json(
|
|
153
155
|
{ error: "Failed to save tenant allowlist." },
|
|
154
156
|
{ status: 500 }
|
|
@@ -179,7 +181,7 @@ async function DELETE(req) {
|
|
|
179
181
|
});
|
|
180
182
|
return NextResponse.json({ cleared });
|
|
181
183
|
} catch (error) {
|
|
182
|
-
|
|
184
|
+
logger.error("AI Settings Allowlist \u2014 DELETE error", { err: error });
|
|
183
185
|
return NextResponse.json(
|
|
184
186
|
{ error: "Failed to clear tenant allowlist." },
|
|
185
187
|
{ status: 500 }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/ai_assistant/api/settings/allowlist/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { AiTenantModelAllowlistRepository } from '../../../data/repositories/AiTenantModelAllowlistRepository'\nimport {\n canonicalProviderId,\n isModelAllowedForProvider,\n isProviderAllowed,\n modelAllowlistEnvVarName,\n} from '../../../lib/model-allowlist'\n\nconst allowlistUpsertSchema = z.object({\n allowedProviders: z.array(z.string().min(1).max(64)).nullable().optional(),\n allowedModelsByProvider: z\n .record(z.string().min(1).max(64), z.array(z.string().min(1).max(256)))\n .optional(),\n})\n\nexport type AllowlistUpsertBody = z.infer<typeof allowlistUpsertSchema>\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'AI assistant tenant allowlist',\n methods: {\n PUT: {\n summary: 'Upsert per-tenant AI provider/model allowlist',\n description:\n 'Persists the per-tenant allowlist of providers and models. The runtime intersects this with the env allowlist (`OM_AI_AVAILABLE_*`) at resolution time. ' +\n 'Tenant values that fall outside the env allowlist are rejected with `provider_not_in_env_allowlist` / `model_not_in_env_allowlist` 400 codes. ' +\n 'Gated by `ai_assistant.settings.manage`.',\n requestBody: {\n contentType: 'application/json',\n description:\n 'Allowlist payload. `allowedProviders: null` clears tenant provider restriction (inherit env). Missing key in `allowedModelsByProvider` inherits env for that provider.',\n schema: allowlistUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Allowlist saved. Returns the saved snapshot.' },\n ],\n errors: [\n { status: 400, description: 'Validation error or values outside env allowlist.' },\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks ai_assistant.settings.manage.' },\n ],\n },\n DELETE: {\n summary: 'Clear per-tenant AI provider/model allowlist',\n description:\n 'Soft-deletes the tenant allowlist row. Tenant overrides revert to env-only enforcement. Idempotent \u2014 returns `{ cleared: false }` when no active row existed.',\n responses: [\n { status: 200, description: 'Returns `{ cleared: boolean }`.' },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks ai_assistant.settings.manage.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['ai_assistant.settings.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['ai_assistant.settings.manage'] },\n}\n\nexport async function PUT(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json(\n { error: 'Request body must be valid JSON.', code: 'validation_error' },\n { status: 400 },\n )\n }\n\n const bodyResult = allowlistUpsertSchema.safeParse(parsedBody)\n if (!bodyResult.success) {\n return NextResponse.json(\n { error: 'Invalid request body.', code: 'validation_error', issues: bodyResult.error.issues },\n { status: 400 },\n )\n }\n\n const env = process.env as Record<string, string | undefined>\n const knownProviderIds = llmProviderRegistry.list().map((provider) => provider.id)\n const canonicalize = (providerId: string) =>\n canonicalProviderId(providerId, knownProviderIds) ?? providerId\n const allowedProviders = bodyResult.data.allowedProviders === undefined\n ? null\n : bodyResult.data.allowedProviders?.map(canonicalize) ?? null\n const allowedModelsByProvider = Object.fromEntries(\n Object.entries(bodyResult.data.allowedModelsByProvider ?? {}).map(([providerId, models]) => [\n canonicalize(providerId),\n models,\n ]),\n )\n\n // Reject tenant entries that escape the env allowlist. The runtime would\n // intersect them away anyway; failing fast at write time keeps stored\n // tenant snapshots honest and visible in admin audits.\n if (Array.isArray(allowedProviders)) {\n for (const providerId of allowedProviders) {\n if (!isProviderAllowed(env, providerId)) {\n return NextResponse.json(\n {\n error: `Provider \"${providerId}\" is not in OM_AI_AVAILABLE_PROVIDERS; tenant allowlist may not widen the env allowlist.`,\n code: 'provider_not_in_env_allowlist',\n },\n { status: 400 },\n )\n }\n }\n }\n for (const providerId of Object.keys(allowedModelsByProvider)) {\n if (!isProviderAllowed(env, providerId)) {\n return NextResponse.json(\n {\n error: `Provider \"${providerId}\" is not in OM_AI_AVAILABLE_PROVIDERS; cannot save tenant model allowlist for it.`,\n code: 'provider_not_in_env_allowlist',\n },\n { status: 400 },\n )\n }\n for (const modelId of allowedModelsByProvider[providerId] ?? []) {\n if (!isModelAllowedForProvider(env, providerId, modelId)) {\n return NextResponse.json(\n {\n error: `Model \"${modelId}\" is not in ${modelAllowlistEnvVarName(providerId)}; tenant allowlist may not widen the env allowlist.`,\n code: 'model_not_in_env_allowlist',\n },\n { status: 400 },\n )\n }\n }\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n const canManage =\n acl.isSuperAdmin || acl.features.includes('ai_assistant.settings.manage')\n if (!canManage) {\n return NextResponse.json({ error: 'Forbidden', code: 'forbidden' }, { status: 403 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiTenantModelAllowlistRepository(em)\n const row = await repo.upsert(\n { allowedProviders, allowedModelsByProvider },\n {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n },\n )\n return NextResponse.json({\n id: row.id,\n tenantId: row.tenantId,\n organizationId: row.organizationId,\n allowedProviders: row.allowedProviders ?? null,\n allowedModelsByProvider: row.allowedModelsByProvider ?? {},\n updatedAt: row.updatedAt,\n })\n } catch (error) {\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { AiTenantModelAllowlistRepository } from '../../../data/repositories/AiTenantModelAllowlistRepository'\nimport {\n canonicalProviderId,\n isModelAllowedForProvider,\n isProviderAllowed,\n modelAllowlistEnvVarName,\n} from '../../../lib/model-allowlist'\n\nconst logger = createLogger('ai_assistant')\n\nconst allowlistUpsertSchema = z.object({\n allowedProviders: z.array(z.string().min(1).max(64)).nullable().optional(),\n allowedModelsByProvider: z\n .record(z.string().min(1).max(64), z.array(z.string().min(1).max(256)))\n .optional(),\n})\n\nexport type AllowlistUpsertBody = z.infer<typeof allowlistUpsertSchema>\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'AI assistant tenant allowlist',\n methods: {\n PUT: {\n summary: 'Upsert per-tenant AI provider/model allowlist',\n description:\n 'Persists the per-tenant allowlist of providers and models. The runtime intersects this with the env allowlist (`OM_AI_AVAILABLE_*`) at resolution time. ' +\n 'Tenant values that fall outside the env allowlist are rejected with `provider_not_in_env_allowlist` / `model_not_in_env_allowlist` 400 codes. ' +\n 'Gated by `ai_assistant.settings.manage`.',\n requestBody: {\n contentType: 'application/json',\n description:\n 'Allowlist payload. `allowedProviders: null` clears tenant provider restriction (inherit env). Missing key in `allowedModelsByProvider` inherits env for that provider.',\n schema: allowlistUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Allowlist saved. Returns the saved snapshot.' },\n ],\n errors: [\n { status: 400, description: 'Validation error or values outside env allowlist.' },\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks ai_assistant.settings.manage.' },\n ],\n },\n DELETE: {\n summary: 'Clear per-tenant AI provider/model allowlist',\n description:\n 'Soft-deletes the tenant allowlist row. Tenant overrides revert to env-only enforcement. Idempotent \u2014 returns `{ cleared: false }` when no active row existed.',\n responses: [\n { status: 200, description: 'Returns `{ cleared: boolean }`.' },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks ai_assistant.settings.manage.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['ai_assistant.settings.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['ai_assistant.settings.manage'] },\n}\n\nexport async function PUT(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json(\n { error: 'Request body must be valid JSON.', code: 'validation_error' },\n { status: 400 },\n )\n }\n\n const bodyResult = allowlistUpsertSchema.safeParse(parsedBody)\n if (!bodyResult.success) {\n return NextResponse.json(\n { error: 'Invalid request body.', code: 'validation_error', issues: bodyResult.error.issues },\n { status: 400 },\n )\n }\n\n const env = process.env as Record<string, string | undefined>\n const knownProviderIds = llmProviderRegistry.list().map((provider) => provider.id)\n const canonicalize = (providerId: string) =>\n canonicalProviderId(providerId, knownProviderIds) ?? providerId\n const allowedProviders = bodyResult.data.allowedProviders === undefined\n ? null\n : bodyResult.data.allowedProviders?.map(canonicalize) ?? null\n const allowedModelsByProvider = Object.fromEntries(\n Object.entries(bodyResult.data.allowedModelsByProvider ?? {}).map(([providerId, models]) => [\n canonicalize(providerId),\n models,\n ]),\n )\n\n // Reject tenant entries that escape the env allowlist. The runtime would\n // intersect them away anyway; failing fast at write time keeps stored\n // tenant snapshots honest and visible in admin audits.\n if (Array.isArray(allowedProviders)) {\n for (const providerId of allowedProviders) {\n if (!isProviderAllowed(env, providerId)) {\n return NextResponse.json(\n {\n error: `Provider \"${providerId}\" is not in OM_AI_AVAILABLE_PROVIDERS; tenant allowlist may not widen the env allowlist.`,\n code: 'provider_not_in_env_allowlist',\n },\n { status: 400 },\n )\n }\n }\n }\n for (const providerId of Object.keys(allowedModelsByProvider)) {\n if (!isProviderAllowed(env, providerId)) {\n return NextResponse.json(\n {\n error: `Provider \"${providerId}\" is not in OM_AI_AVAILABLE_PROVIDERS; cannot save tenant model allowlist for it.`,\n code: 'provider_not_in_env_allowlist',\n },\n { status: 400 },\n )\n }\n for (const modelId of allowedModelsByProvider[providerId] ?? []) {\n if (!isModelAllowedForProvider(env, providerId, modelId)) {\n return NextResponse.json(\n {\n error: `Model \"${modelId}\" is not in ${modelAllowlistEnvVarName(providerId)}; tenant allowlist may not widen the env allowlist.`,\n code: 'model_not_in_env_allowlist',\n },\n { status: 400 },\n )\n }\n }\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n const canManage =\n acl.isSuperAdmin || acl.features.includes('ai_assistant.settings.manage')\n if (!canManage) {\n return NextResponse.json({ error: 'Forbidden', code: 'forbidden' }, { status: 403 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiTenantModelAllowlistRepository(em)\n const row = await repo.upsert(\n { allowedProviders, allowedModelsByProvider },\n {\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n },\n )\n return NextResponse.json({\n id: row.id,\n tenantId: row.tenantId,\n organizationId: row.organizationId,\n allowedProviders: row.allowedProviders ?? null,\n allowedModelsByProvider: row.allowedModelsByProvider ?? {},\n updatedAt: row.updatedAt,\n })\n } catch (error) {\n logger.error('AI Settings Allowlist \u2014 PUT error', { err: error })\n return NextResponse.json(\n { error: 'Failed to save tenant allowlist.' },\n { status: 500 },\n )\n }\n}\n\nexport async function DELETE(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n const canManage =\n acl.isSuperAdmin || acl.features.includes('ai_assistant.settings.manage')\n if (!canManage) {\n return NextResponse.json({ error: 'Forbidden', code: 'forbidden' }, { status: 403 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiTenantModelAllowlistRepository(em)\n const cleared = await repo.clear({\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n })\n return NextResponse.json({ cleared })\n } catch (error) {\n logger.error('AI Settings Allowlist \u2014 DELETE error', { err: error })\n return NextResponse.json(\n { error: 'Failed to clear tenant allowlist.' },\n { status: 500 },\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
readAgentRuntimeOverrideAllowlist,
|
|
30
31
|
readAllowlistConfig
|
|
31
32
|
} from "../../lib/model-allowlist.js";
|
|
33
|
+
const logger = createLogger("ai_assistant");
|
|
32
34
|
function modelCatalogWithAllowlistFallback(models, allowlistModelIds) {
|
|
33
35
|
if (models.length > 0) return models;
|
|
34
36
|
return (allowlistModelIds ?? []).map((id) => ({ id, name: id }));
|
|
@@ -233,7 +235,7 @@ async function GET(req) {
|
|
|
233
235
|
agentResolutions = await Promise.all(agentResolutionPromises);
|
|
234
236
|
}
|
|
235
237
|
} catch (overrideError) {
|
|
236
|
-
|
|
238
|
+
logger.warn("AI Settings \u2014 Failed to compute Phase 4a override fields", { err: overrideError });
|
|
237
239
|
}
|
|
238
240
|
const allowlistConfig = readAllowlistConfig(env, knownProviderIdsForAllowlist);
|
|
239
241
|
const effectiveAllowlist = intersectAllowlists(
|
|
@@ -320,7 +322,7 @@ async function GET(req) {
|
|
|
320
322
|
agents: agentResolutions
|
|
321
323
|
});
|
|
322
324
|
} catch (error) {
|
|
323
|
-
|
|
325
|
+
logger.error("AI Settings \u2014 GET error", { err: error });
|
|
324
326
|
return NextResponse.json({ error: "Failed to fetch settings" }, { status: 500 });
|
|
325
327
|
}
|
|
326
328
|
}
|
|
@@ -525,7 +527,7 @@ async function PUT(req) {
|
|
|
525
527
|
if (error instanceof AiAgentRuntimeOverrideValidationError) {
|
|
526
528
|
return NextResponse.json({ error: error.message, code: "provider_unknown" }, { status: 400 });
|
|
527
529
|
}
|
|
528
|
-
|
|
530
|
+
logger.error("AI Settings \u2014 PUT error", { err: error });
|
|
529
531
|
return NextResponse.json({ error: "Failed to save runtime override." }, { status: 500 });
|
|
530
532
|
}
|
|
531
533
|
}
|
|
@@ -566,7 +568,7 @@ async function DELETE(req) {
|
|
|
566
568
|
});
|
|
567
569
|
return NextResponse.json({ cleared });
|
|
568
570
|
} catch (error) {
|
|
569
|
-
|
|
571
|
+
logger.error("AI Settings \u2014 DELETE error", { err: error });
|
|
570
572
|
return NextResponse.json({ error: "Failed to clear runtime override." }, { status: 500 });
|
|
571
573
|
}
|
|
572
574
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/api/settings/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport {\n OPEN_CODE_PROVIDER_IDS,\n OPEN_CODE_PROVIDERS,\n getOpenCodeProviderConfiguredEnvKey,\n isOpenCodeProviderConfigured,\n} from '@open-mercato/shared/lib/ai/opencode-provider'\nimport { AiAgentRuntimeOverrideRepository, AiAgentRuntimeOverrideValidationError } from '../../data/repositories/AiAgentRuntimeOverrideRepository'\nimport { AiTenantModelAllowlistRepository } from '../../data/repositories/AiTenantModelAllowlistRepository'\nimport { isBaseurlAllowlisted, readBaseurlAllowlist } from '../../lib/baseurl-allowlist'\nimport { loadAgentRegistry, listAgents } from '../../lib/agent-registry'\nimport { createModelFactory, resolveAllowRuntimeOverride } from '../../lib/model-factory'\nimport {\n agentOverrideModelAllowlistEnvVarName,\n agentOverrideProviderAllowlistEnvVarName,\n canonicalProviderId,\n hasAllowlistSnapshotRestrictions,\n intersectEffectiveAllowlistWithSnapshot,\n intersectAllowlists,\n isProviderAllowed,\n isProviderAllowedInEffective,\n isProviderModelAllowed,\n isProviderModelAllowedInEffective,\n modelAllowlistEnvVarName,\n readAgentRuntimeOverrideAllowlist,\n readAllowedModels,\n readAllowedProviders,\n readAllowlistConfig,\n type TenantAllowlistSnapshot,\n} from '../../lib/model-allowlist'\n\nfunction modelCatalogWithAllowlistFallback(\n models: ReadonlyArray<{ id: string; name: string; contextWindow?: number | null; tags?: readonly string[] }>,\n allowlistModelIds: string[] | undefined,\n): ReadonlyArray<{ id: string; name: string; contextWindow?: number | null; tags?: readonly string[] }> {\n if (models.length > 0) return models\n return (allowlistModelIds ?? []).map((id) => ({ id, name: id }))\n}\n\nconst runtimeOverrideUpsertSchema = z.object({\n providerId: z.string().min(1).max(64).nullable().optional(),\n modelId: z.string().min(1).max(256).nullable().optional(),\n baseURL: z.string().url().max(2048).nullable().optional(),\n agentId: z.string().min(1).max(128).nullable().optional(),\n allowedOverrideProviders: z.array(z.string().min(1).max(64)).nullable().optional(),\n allowedOverrideModelsByProvider: z\n .record(z.string().min(1).max(64), z.array(z.string().min(1).max(256)))\n .optional(),\n})\n\nconst runtimeOverrideClearSchema = z.object({\n agentId: z.string().min(1).max(128).nullable().optional(),\n})\n\nexport type RuntimeOverrideUpsertBody = z.infer<typeof runtimeOverrideUpsertSchema>\nexport type RuntimeOverrideClearBody = z.infer<typeof runtimeOverrideClearSchema>\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'AI assistant settings',\n methods: {\n GET: { summary: 'Get AI provider configuration' },\n PUT: {\n summary: 'Upsert per-tenant AI runtime override',\n description:\n 'Creates or updates the per-tenant AI runtime override (provider, model, baseURL). ' +\n 'Optionally scoped to a specific agent via `agentId`. ' +\n 'Gated by `ai_assistant.settings.manage`. ' +\n 'baseURL must match AI_RUNTIME_BASEURL_ALLOWLIST when set.',\n requestBody: {\n contentType: 'application/json',\n description: 'Override payload. All fields nullable/optional; null explicitly clears the axis.',\n schema: runtimeOverrideUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Override saved. Returns the saved row.' },\n ],\n errors: [\n { status: 400, description: 'Validation error: unknown provider, invalid URL, or baseURL not allowlisted.' },\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks ai_assistant.settings.manage.' },\n ],\n },\n DELETE: {\n summary: 'Clear per-tenant AI runtime override',\n description:\n 'Soft-deletes the active per-tenant runtime override. ' +\n 'Pass `agentId` to clear only the agent-specific row; omit to clear the tenant-wide default. ' +\n 'Gated by `ai_assistant.settings.manage`. Idempotent \u2014 returns 200 with `cleared: false` when no active row existed.',\n requestBody: {\n contentType: 'application/json',\n description: 'Optional agentId to scope the delete.',\n schema: runtimeOverrideClearSchema,\n },\n responses: [\n { status: 200, description: 'Returns `{ cleared: boolean }` indicating whether a row was found and removed.' },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks ai_assistant.settings.manage.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n PUT: { requireAuth: true, requireFeatures: ['ai_assistant.settings.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['ai_assistant.settings.manage'] },\n}\n\n/**\n * GET /api/ai_assistant/settings\n *\n * Returns the current OpenCode provider configuration from environment variables\n * plus the Phase 4a additive fields: resolvedDefault, tenantOverride, agents[],\n * and availableProviders[].defaultModels.\n */\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const env = process.env as Record<string, string | undefined>\n const configuredProviderHint = env.OM_AI_PROVIDER?.trim() || env.OPENCODE_PROVIDER?.trim() || null\n const registryProviders = llmProviderRegistry.list()\n const knownProviderIdsForAllowlist: string[] = [\n ...OPEN_CODE_PROVIDER_IDS,\n ...registryProviders\n .map((p) => p.id)\n .filter((id) => !(OPEN_CODE_PROVIDER_IDS as readonly string[]).includes(id)),\n ]\n const registryProviderId = configuredProviderHint\n ? canonicalProviderId(configuredProviderHint, registryProviders.map((provider) => provider.id))\n : null\n const registryProvider = registryProviderId ? llmProviderRegistry.get(registryProviderId) : null\n const fallbackOpenCodeProviderId = (\n (configuredProviderHint\n ? canonicalProviderId(configuredProviderHint, OPEN_CODE_PROVIDER_IDS as readonly string[])\n : null) ?? 'openai'\n ) as keyof typeof OPEN_CODE_PROVIDERS\n const fallbackOpenCodeProvider = OPEN_CODE_PROVIDERS[fallbackOpenCodeProviderId]\n\n const providerId = registryProvider?.id ?? fallbackOpenCodeProviderId\n const providerName = registryProvider?.name ?? fallbackOpenCodeProvider?.name ?? providerId\n const defaultProviderModel = registryProvider?.defaultModel ?? fallbackOpenCodeProvider?.defaultModel ?? ''\n const configuredModelHint = env.OM_AI_MODEL?.trim() || env.OPENCODE_MODEL?.trim() || defaultProviderModel\n const fallbackModelWithProvider = `${providerId}/${configuredModelHint}`\n const apiKeyConfigured = registryProvider\n ? registryProvider.isConfigured(env)\n : fallbackOpenCodeProvider\n ? isOpenCodeProviderConfigured(fallbackOpenCodeProviderId)\n : false\n const displayEnvKey = registryProvider\n ? registryProvider.getConfiguredEnvKey(env)\n : fallbackOpenCodeProvider\n ? getOpenCodeProviderConfiguredEnvKey(fallbackOpenCodeProviderId)\n : null\n\n // Check if MCP_SERVER_API_KEY is configured (required for MCP authentication)\n const mcpKeyConfigured = !!process.env.MCP_SERVER_API_KEY?.trim()\n\n // Phase 4a: resolve tenant override row and per-agent resolution matrix\n let tenantOverride: {\n providerId: string | null\n modelId: string | null\n baseURL: string | null\n agentId: string | null\n updatedAt: string\n } | null = null\n\n let agentResolutions: Array<{\n agentId: string\n moduleId: string\n allowRuntimeOverride: boolean\n allowRuntimeModelOverride: boolean\n codeDefaultProviderId: string | null\n codeDefaultModelId: string | null\n override: {\n providerId: string | null\n modelId: string | null\n baseURL: string | null\n updatedAt: string\n } | null\n runtimeOverrideAllowlist: {\n env: TenantAllowlistSnapshot | null\n tenant: TenantAllowlistSnapshot | null\n effective: ReturnType<typeof intersectAllowlists>\n envVarNames: {\n providers: string\n modelsByProvider: Record<string, string>\n }\n }\n providerId: string\n modelId: string\n baseURL: string | null\n source: string\n }> = []\n\n let resolvedDefault: {\n providerId: string\n modelId: string\n baseURL: string | null\n source: string\n } | null = null\n\n let tenantAllowlistSnapshot: TenantAllowlistSnapshot | null = null\n\n try {\n const container = await createRequestContainer()\n const tenantId = auth.tenantId ?? null\n const organizationId = auth.orgId ?? null\n\n if (tenantId) {\n const em = container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const overrideRow = await repo.getDefault({ tenantId, organizationId, agentId: null })\n if (overrideRow) {\n tenantOverride = {\n providerId: overrideRow.providerId ?? null,\n modelId: overrideRow.modelId ?? null,\n baseURL: overrideRow.baseUrl ?? null,\n agentId: overrideRow.agentId ?? null,\n updatedAt: overrideRow.updatedAt.toISOString(),\n }\n }\n\n const allowlistRepo = new AiTenantModelAllowlistRepository(em)\n tenantAllowlistSnapshot = await allowlistRepo.getSnapshot({\n tenantId,\n organizationId,\n })\n\n const factory = createModelFactory(container)\n const defaultResolution = factory.resolveModel({\n tenantAllowlist: tenantAllowlistSnapshot,\n tenantOverride: tenantOverride\n ? { providerId: tenantOverride.providerId, modelId: tenantOverride.modelId, baseURL: tenantOverride.baseURL }\n : undefined,\n })\n resolvedDefault = {\n providerId: defaultResolution.providerId,\n modelId: defaultResolution.modelId,\n baseURL: defaultResolution.baseURL ?? null,\n source: defaultResolution.source,\n }\n\n await loadAgentRegistry()\n const agents = listAgents()\n const agentResolutionPromises = agents.map(async (agent) => {\n const agentOverrideRow = await repo.getExact({\n tenantId,\n organizationId,\n agentId: agent.id,\n })\n const agentTenantOverride = agentOverrideRow\n ? {\n providerId: agentOverrideRow.providerId ?? null,\n modelId: agentOverrideRow.modelId ?? null,\n baseURL: agentOverrideRow.baseUrl ?? null,\n }\n : (tenantOverride ?? undefined)\n const agentResolution = factory.resolveModel({\n moduleId: agent.moduleId,\n agentDefaultModel: agent.defaultModel,\n agentDefaultProvider: agent.defaultProvider,\n agentDefaultBaseUrl: agent.defaultBaseUrl,\n allowRuntimeOverride: resolveAllowRuntimeOverride(agent),\n tenantOverride: agentTenantOverride,\n tenantAllowlist: tenantAllowlistSnapshot,\n })\n const agentEnvAllowlist = readAgentRuntimeOverrideAllowlist(\n env,\n agent.id,\n knownProviderIdsForAllowlist,\n )\n const agentTenantAllowlist = agentOverrideRow\n ? {\n allowedProviders: agentOverrideRow.allowedOverrideProviders ?? null,\n allowedModelsByProvider: agentOverrideRow.allowedOverrideModelsByProvider ?? {},\n }\n : null\n const baseEffectiveAllowlist = intersectAllowlists(\n env,\n knownProviderIdsForAllowlist,\n tenantAllowlistSnapshot,\n )\n const agentEffectiveAllowlist = intersectEffectiveAllowlistWithSnapshot(\n intersectEffectiveAllowlistWithSnapshot(\n baseEffectiveAllowlist,\n knownProviderIdsForAllowlist,\n agentEnvAllowlist,\n ),\n knownProviderIdsForAllowlist,\n agentTenantAllowlist,\n )\n const agentModelEnvVars = Object.fromEntries(\n knownProviderIdsForAllowlist.map((providerId) => [\n providerId,\n agentOverrideModelAllowlistEnvVarName(agent.id, providerId),\n ]),\n )\n return {\n agentId: agent.id,\n moduleId: agent.moduleId,\n allowRuntimeOverride: resolveAllowRuntimeOverride(agent),\n allowRuntimeModelOverride: resolveAllowRuntimeOverride(agent),\n codeDefaultProviderId: agent.defaultProvider ?? null,\n codeDefaultModelId: agent.defaultModel ?? null,\n override: agentOverrideRow\n ? {\n providerId: agentOverrideRow.providerId ?? null,\n modelId: agentOverrideRow.modelId ?? null,\n baseURL: agentOverrideRow.baseUrl ?? null,\n updatedAt: agentOverrideRow.updatedAt.toISOString(),\n }\n : null,\n runtimeOverrideAllowlist: {\n env: agentEnvAllowlist,\n tenant: hasAllowlistSnapshotRestrictions(agentTenantAllowlist)\n ? agentTenantAllowlist\n : null,\n effective: agentEffectiveAllowlist,\n envVarNames: {\n providers: agentOverrideProviderAllowlistEnvVarName(agent.id),\n modelsByProvider: agentModelEnvVars,\n },\n },\n providerId: agentResolution.providerId,\n modelId: agentResolution.modelId,\n baseURL: agentResolution.baseURL ?? null,\n source: agentResolution.source,\n }\n })\n agentResolutions = await Promise.all(agentResolutionPromises)\n }\n } catch (overrideError) {\n // Phase 4a fields are best-effort \u2014 log and continue returning the base response\n console.warn('[AI Settings] Failed to compute Phase 4a override fields:', overrideError)\n }\n\n // Build availableProviders with Phase 4a defaultModels, then clip to the\n // EFFECTIVE allowlist \u2014 env intersected with the per-tenant snapshot.\n // The env allowlist is the OUTER constraint; the tenant allowlist (Phase\n // 1780-6) narrows it further. The settings UI must never offer a value\n // the runtime would refuse to honor.\n const allowlistConfig = readAllowlistConfig(env, knownProviderIdsForAllowlist)\n const effectiveAllowlist = intersectAllowlists(\n env,\n knownProviderIdsForAllowlist,\n tenantAllowlistSnapshot,\n )\n\n const allRawProviders = [\n ...OPEN_CODE_PROVIDER_IDS.map((id) => {\n const info = OPEN_CODE_PROVIDERS[id]\n const registryProvider = llmProviderRegistry.get(id)\n return {\n id,\n name: info.name,\n defaultModel: info.defaultModel,\n envKey: getOpenCodeProviderConfiguredEnvKey(id),\n configured: isOpenCodeProviderConfigured(id),\n defaultModels: registryProvider?.defaultModels ?? [],\n }\n }),\n // Also surface any llmProviderRegistry providers not in OPEN_CODE_PROVIDER_IDS\n ...llmProviderRegistry.list()\n .filter((p) => !(OPEN_CODE_PROVIDER_IDS as readonly string[]).includes(p.id))\n .map((p) => ({\n id: p.id,\n name: p.name,\n defaultModel: p.defaultModels[0]?.id ?? '',\n envKey: null,\n configured: p.isConfigured(),\n defaultModels: p.defaultModels,\n })),\n ]\n\n const availableProviders = allRawProviders\n .filter((p) => isProviderAllowedInEffective(effectiveAllowlist, p.id))\n .map((p) => {\n const effectiveModelsList = effectiveAllowlist.modelsByProvider[p.id]\n const catalogModels = modelCatalogWithAllowlistFallback(\n p.defaultModels,\n effectiveModelsList,\n )\n const clippedDefaults = effectiveModelsList !== undefined\n ? catalogModels.filter((m) => effectiveModelsList.includes(m.id))\n : catalogModels\n return {\n ...p,\n defaultModel: effectiveModelsList && !effectiveModelsList.includes(p.defaultModel)\n ? (effectiveModelsList[0] ?? p.defaultModel)\n : p.defaultModel || clippedDefaults[0]?.id || '',\n defaultModels: clippedDefaults,\n }\n })\n\n const allowlistProviders = allRawProviders\n .filter((p) => isProviderAllowed(env, p.id))\n .map((p) => {\n const envModelsList = allowlistConfig.modelsByProvider[p.id]\n const catalogModels = modelCatalogWithAllowlistFallback(\n p.defaultModels,\n envModelsList,\n )\n const envClippedDefaults = envModelsList !== undefined\n ? catalogModels.filter((m) => envModelsList.includes(m.id))\n : catalogModels\n return {\n ...p,\n defaultModel: envModelsList && !envModelsList.includes(p.defaultModel)\n ? (envModelsList[0] ?? p.defaultModel)\n : p.defaultModel || envClippedDefaults[0]?.id || '',\n defaultModels: envClippedDefaults,\n }\n })\n\n return NextResponse.json({\n provider: {\n id: providerId,\n name: providerName,\n model: resolvedDefault\n ? `${resolvedDefault.providerId}/${resolvedDefault.modelId}`\n : fallbackModelWithProvider,\n defaultModel: defaultProviderModel,\n envKey: displayEnvKey,\n configured: apiKeyConfigured,\n },\n availableProviders,\n // Editable universe for the tenant allowlist page. This is clipped only\n // by env so tenant-hidden models remain visible and can be re-enabled.\n allowlistProviders,\n // Snapshot of the env-driven allowlist so the UI can render hints like\n // \"limited to: openai, anthropic\" without re-implementing the parser.\n allowlist: allowlistConfig,\n // Per-tenant allowlist snapshot (Phase 1780-6). `null` when no row has\n // been persisted yet \u2014 the runtime then falls back to env-only\n // enforcement. The UI uses this to drive the editable MultiSelect.\n tenantAllowlist: tenantAllowlistSnapshot,\n // Effective allowlist after intersecting env with tenant. The UI uses\n // this to render the \"what the runtime will actually accept\" summary\n // and to clip pickers without re-implementing the intersection.\n effectiveAllowlist,\n mcpKeyConfigured,\n resolvedDefault,\n tenantOverride,\n agents: agentResolutions,\n })\n } catch (error) {\n console.error('[AI Settings] GET error:', error)\n return NextResponse.json({ error: 'Failed to fetch settings' }, { status: 500 })\n }\n}\n\n/**\n * PUT /api/ai_assistant/settings\n *\n * Upserts the per-tenant AI runtime override (Phase 4a). Requires\n * `ai_assistant.settings.manage`. The body is Zod-validated; a `baseURL`\n * must match `AI_RUNTIME_BASEURL_ALLOWLIST` when that env var is set.\n */\nexport async function PUT(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Request body must be valid JSON.', code: 'validation_error' }, { status: 400 })\n }\n\n const bodyResult = runtimeOverrideUpsertSchema.safeParse(parsedBody)\n if (!bodyResult.success) {\n return NextResponse.json(\n { error: 'Invalid request body.', code: 'validation_error', issues: bodyResult.error.issues },\n { status: 400 },\n )\n }\n\n const { providerId: requestedProviderId, modelId, baseURL, agentId } = bodyResult.data\n const knownProviderIdsForRequest = llmProviderRegistry.list().map((p) => p.id)\n const providerId = requestedProviderId\n ? canonicalProviderId(requestedProviderId, knownProviderIdsForRequest) ?? requestedProviderId\n : requestedProviderId\n\n if (baseURL && baseURL.trim().length > 0) {\n const allowlist = readBaseurlAllowlist()\n if (!isBaseurlAllowlisted(baseURL.trim(), allowlist)) {\n return NextResponse.json(\n {\n error: `baseURL \"${baseURL}\" is not in AI_RUNTIME_BASEURL_ALLOWLIST.`,\n code: 'baseurl_not_allowlisted',\n },\n { status: 400 },\n )\n }\n }\n\n const allowedOverrideProviders = bodyResult.data.allowedOverrideProviders === undefined\n ? undefined\n : bodyResult.data.allowedOverrideProviders?.map((id) =>\n canonicalProviderId(id, knownProviderIdsForRequest) ?? id,\n ) ?? null\n const allowedOverrideModelsByProvider = bodyResult.data.allowedOverrideModelsByProvider === undefined\n ? undefined\n : Object.fromEntries(\n Object.entries(bodyResult.data.allowedOverrideModelsByProvider ?? {}).map(([id, models]) => [\n canonicalProviderId(id, knownProviderIdsForRequest) ?? id,\n models,\n ]),\n )\n const hasRuntimeOverrideAllowlistWrite =\n allowedOverrideProviders !== undefined || allowedOverrideModelsByProvider !== undefined\n\n // Env-driven provider/model allowlist (Phase 1780-5) intersected with the\n // per-tenant allowlist (Phase 1780-6): the EFFECTIVE allowlist clips which\n // (provider, model) pairs the runtime accepts. Reject settings PUT requests\n // for pairs outside that effective set so the settings UI never persists a\n // value the runtime would later refuse. Tenant-allowlist enforcement is\n // best-effort here: if the snapshot lookup fails we fall back to env-only\n // checks (the runtime still re-clips at resolution time).\n let putEffectiveAllowlist: ReturnType<typeof intersectAllowlists> | null = null\n if (providerId || hasRuntimeOverrideAllowlistWrite) {\n try {\n const previewContainer = await createRequestContainer()\n const knownIdsForCheck = [\n ...OPEN_CODE_PROVIDER_IDS,\n ...llmProviderRegistry\n .list()\n .map((p) => p.id)\n .filter((id) => !(OPEN_CODE_PROVIDER_IDS as readonly string[]).includes(id)),\n ]\n let snapshot: TenantAllowlistSnapshot | null = null\n if (auth.tenantId) {\n try {\n const em = previewContainer.resolve<EntityManager>('em')\n const allowlistRepo = new AiTenantModelAllowlistRepository(em)\n snapshot = await allowlistRepo.getSnapshot({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n })\n } catch {\n snapshot = null\n }\n }\n putEffectiveAllowlist = intersectAllowlists(\n process.env as Record<string, string | undefined>,\n knownIdsForCheck,\n snapshot,\n )\n } catch {\n putEffectiveAllowlist = null\n }\n\n if (providerId && putEffectiveAllowlist) {\n if (!isProviderAllowedInEffective(putEffectiveAllowlist, providerId)) {\n const source = putEffectiveAllowlist.tenantOverridesActive\n ? 'the effective allowlist (env \u2229 tenant)'\n : 'OM_AI_AVAILABLE_PROVIDERS'\n return NextResponse.json(\n {\n error: `Provider \"${providerId}\" is not in ${source}.`,\n code: 'provider_not_allowlisted',\n },\n { status: 400 },\n )\n }\n if (modelId && !isProviderModelAllowedInEffective(putEffectiveAllowlist, providerId, modelId)) {\n const source = putEffectiveAllowlist.tenantOverridesActive\n ? `the effective allowlist (env \u2229 tenant) for \"${providerId}\"`\n : modelAllowlistEnvVarName(providerId)\n return NextResponse.json(\n {\n error: `Model \"${modelId}\" is not in ${source}.`,\n code: 'model_not_allowlisted',\n },\n { status: 400 },\n )\n }\n } else if (providerId) {\n if (!isProviderAllowed(process.env, providerId)) {\n return NextResponse.json(\n {\n error: `Provider \"${requestedProviderId}\" is not in OM_AI_AVAILABLE_PROVIDERS.`,\n code: 'provider_not_allowlisted',\n },\n { status: 400 },\n )\n }\n if (modelId && !isProviderModelAllowed(process.env, providerId, modelId)) {\n return NextResponse.json(\n {\n error: `Model \"${modelId}\" is not in ${modelAllowlistEnvVarName(providerId)}.`,\n code: 'model_not_allowlisted',\n },\n { status: 400 },\n )\n }\n }\n }\n\n if (hasRuntimeOverrideAllowlistWrite && !agentId) {\n return NextResponse.json(\n {\n error: 'agentId is required when saving chat override allowlist settings.',\n code: 'agent_required',\n },\n { status: 400 },\n )\n }\n\n if (Array.isArray(allowedOverrideProviders)) {\n for (const id of allowedOverrideProviders) {\n if (putEffectiveAllowlist && !isProviderAllowedInEffective(putEffectiveAllowlist, id)) {\n return NextResponse.json(\n {\n error: `Provider \"${id}\" is not in the effective tenant allowlist; per-agent chat override choices may not widen it.`,\n code: 'provider_not_allowlisted',\n },\n { status: 400 },\n )\n }\n }\n }\n if (allowedOverrideModelsByProvider) {\n for (const [id, models] of Object.entries(allowedOverrideModelsByProvider)) {\n if (putEffectiveAllowlist && !isProviderAllowedInEffective(putEffectiveAllowlist, id)) {\n return NextResponse.json(\n {\n error: `Provider \"${id}\" is not in the effective tenant allowlist; cannot save per-agent model choices for it.`,\n code: 'provider_not_allowlisted',\n },\n { status: 400 },\n )\n }\n for (const allowedModelId of models) {\n if (\n putEffectiveAllowlist &&\n !isProviderModelAllowedInEffective(putEffectiveAllowlist, id, allowedModelId)\n ) {\n return NextResponse.json(\n {\n error: `Model \"${allowedModelId}\" is not in the effective tenant allowlist for \"${id}\".`,\n code: 'model_not_allowlisted',\n },\n { status: 400 },\n )\n }\n }\n }\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n const canManage = acl.isSuperAdmin || acl.features.includes('ai_assistant.settings.manage')\n if (!canManage) {\n return NextResponse.json({ error: 'Forbidden', code: 'forbidden' }, { status: 403 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const upsertInput = {\n agentId: agentId ?? null,\n ...(Object.prototype.hasOwnProperty.call(bodyResult.data, 'providerId')\n ? { providerId: providerId ?? null }\n : {}),\n ...(Object.prototype.hasOwnProperty.call(bodyResult.data, 'modelId')\n ? { modelId: modelId ?? null }\n : {}),\n ...(Object.prototype.hasOwnProperty.call(bodyResult.data, 'baseURL')\n ? { baseURL: baseURL ?? null }\n : {}),\n ...(allowedOverrideProviders !== undefined\n ? { allowedOverrideProviders }\n : {}),\n ...(allowedOverrideModelsByProvider !== undefined\n ? { allowedOverrideModelsByProvider }\n : {}),\n }\n const row = await repo.upsertDefault(\n upsertInput,\n { tenantId: auth.tenantId ?? '', organizationId: auth.orgId ?? null, userId: auth.sub },\n )\n return NextResponse.json({\n id: row.id,\n tenantId: row.tenantId,\n organizationId: row.organizationId,\n agentId: row.agentId,\n providerId: row.providerId,\n modelId: row.modelId,\n baseURL: row.baseUrl,\n allowedOverrideProviders: row.allowedOverrideProviders ?? null,\n allowedOverrideModelsByProvider: row.allowedOverrideModelsByProvider ?? {},\n updatedAt: row.updatedAt,\n })\n } catch (error) {\n if (error instanceof AiAgentRuntimeOverrideValidationError) {\n return NextResponse.json({ error: error.message, code: 'provider_unknown' }, { status: 400 })\n }\n console.error('[AI Settings] PUT error:', error)\n return NextResponse.json({ error: 'Failed to save runtime override.' }, { status: 500 })\n }\n}\n\n/**\n * DELETE /api/ai_assistant/settings\n *\n * Soft-deletes the active per-tenant AI runtime override (Phase 4a). Requires\n * `ai_assistant.settings.manage`. Pass `agentId` in the body to clear only\n * the agent-specific row; omit (or null) to clear the tenant-wide default.\n * Idempotent \u2014 returns `{ cleared: false }` when no active row was found.\n */\nexport async function DELETE(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let parsedBody: unknown = {}\n try {\n parsedBody = await req.json()\n } catch {\n // Body is optional for DELETE \u2014 empty body is fine\n }\n\n const bodyResult = runtimeOverrideClearSchema.safeParse(parsedBody)\n if (!bodyResult.success) {\n return NextResponse.json(\n { error: 'Invalid request body.', code: 'validation_error', issues: bodyResult.error.issues },\n { status: 400 },\n )\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n const canManage = acl.isSuperAdmin || acl.features.includes('ai_assistant.settings.manage')\n if (!canManage) {\n return NextResponse.json({ error: 'Forbidden', code: 'forbidden' }, { status: 403 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const cleared = await repo.clearDefault({\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n agentId: bodyResult.data.agentId ?? null,\n })\n return NextResponse.json({ cleared })\n } catch (error) {\n console.error('[AI Settings] DELETE error:', error)\n return NextResponse.json({ error: 'Failed to clear runtime override.' }, { status: 500 })\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport {\n OPEN_CODE_PROVIDER_IDS,\n OPEN_CODE_PROVIDERS,\n getOpenCodeProviderConfiguredEnvKey,\n isOpenCodeProviderConfigured,\n} from '@open-mercato/shared/lib/ai/opencode-provider'\nimport { AiAgentRuntimeOverrideRepository, AiAgentRuntimeOverrideValidationError } from '../../data/repositories/AiAgentRuntimeOverrideRepository'\nimport { AiTenantModelAllowlistRepository } from '../../data/repositories/AiTenantModelAllowlistRepository'\nimport { isBaseurlAllowlisted, readBaseurlAllowlist } from '../../lib/baseurl-allowlist'\nimport { loadAgentRegistry, listAgents } from '../../lib/agent-registry'\nimport { createModelFactory, resolveAllowRuntimeOverride } from '../../lib/model-factory'\nimport {\n agentOverrideModelAllowlistEnvVarName,\n agentOverrideProviderAllowlistEnvVarName,\n canonicalProviderId,\n hasAllowlistSnapshotRestrictions,\n intersectEffectiveAllowlistWithSnapshot,\n intersectAllowlists,\n isProviderAllowed,\n isProviderAllowedInEffective,\n isProviderModelAllowed,\n isProviderModelAllowedInEffective,\n modelAllowlistEnvVarName,\n readAgentRuntimeOverrideAllowlist,\n readAllowedModels,\n readAllowedProviders,\n readAllowlistConfig,\n type TenantAllowlistSnapshot,\n} from '../../lib/model-allowlist'\n\nconst logger = createLogger('ai_assistant')\n\nfunction modelCatalogWithAllowlistFallback(\n models: ReadonlyArray<{ id: string; name: string; contextWindow?: number | null; tags?: readonly string[] }>,\n allowlistModelIds: string[] | undefined,\n): ReadonlyArray<{ id: string; name: string; contextWindow?: number | null; tags?: readonly string[] }> {\n if (models.length > 0) return models\n return (allowlistModelIds ?? []).map((id) => ({ id, name: id }))\n}\n\nconst runtimeOverrideUpsertSchema = z.object({\n providerId: z.string().min(1).max(64).nullable().optional(),\n modelId: z.string().min(1).max(256).nullable().optional(),\n baseURL: z.string().url().max(2048).nullable().optional(),\n agentId: z.string().min(1).max(128).nullable().optional(),\n allowedOverrideProviders: z.array(z.string().min(1).max(64)).nullable().optional(),\n allowedOverrideModelsByProvider: z\n .record(z.string().min(1).max(64), z.array(z.string().min(1).max(256)))\n .optional(),\n})\n\nconst runtimeOverrideClearSchema = z.object({\n agentId: z.string().min(1).max(128).nullable().optional(),\n})\n\nexport type RuntimeOverrideUpsertBody = z.infer<typeof runtimeOverrideUpsertSchema>\nexport type RuntimeOverrideClearBody = z.infer<typeof runtimeOverrideClearSchema>\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'AI assistant settings',\n methods: {\n GET: { summary: 'Get AI provider configuration' },\n PUT: {\n summary: 'Upsert per-tenant AI runtime override',\n description:\n 'Creates or updates the per-tenant AI runtime override (provider, model, baseURL). ' +\n 'Optionally scoped to a specific agent via `agentId`. ' +\n 'Gated by `ai_assistant.settings.manage`. ' +\n 'baseURL must match AI_RUNTIME_BASEURL_ALLOWLIST when set.',\n requestBody: {\n contentType: 'application/json',\n description: 'Override payload. All fields nullable/optional; null explicitly clears the axis.',\n schema: runtimeOverrideUpsertSchema,\n },\n responses: [\n { status: 200, description: 'Override saved. Returns the saved row.' },\n ],\n errors: [\n { status: 400, description: 'Validation error: unknown provider, invalid URL, or baseURL not allowlisted.' },\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks ai_assistant.settings.manage.' },\n ],\n },\n DELETE: {\n summary: 'Clear per-tenant AI runtime override',\n description:\n 'Soft-deletes the active per-tenant runtime override. ' +\n 'Pass `agentId` to clear only the agent-specific row; omit to clear the tenant-wide default. ' +\n 'Gated by `ai_assistant.settings.manage`. Idempotent \u2014 returns 200 with `cleared: false` when no active row existed.',\n requestBody: {\n contentType: 'application/json',\n description: 'Optional agentId to scope the delete.',\n schema: runtimeOverrideClearSchema,\n },\n responses: [\n { status: 200, description: 'Returns `{ cleared: boolean }` indicating whether a row was found and removed.' },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks ai_assistant.settings.manage.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n PUT: { requireAuth: true, requireFeatures: ['ai_assistant.settings.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['ai_assistant.settings.manage'] },\n}\n\n/**\n * GET /api/ai_assistant/settings\n *\n * Returns the current OpenCode provider configuration from environment variables\n * plus the Phase 4a additive fields: resolvedDefault, tenantOverride, agents[],\n * and availableProviders[].defaultModels.\n */\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const env = process.env as Record<string, string | undefined>\n const configuredProviderHint = env.OM_AI_PROVIDER?.trim() || env.OPENCODE_PROVIDER?.trim() || null\n const registryProviders = llmProviderRegistry.list()\n const knownProviderIdsForAllowlist: string[] = [\n ...OPEN_CODE_PROVIDER_IDS,\n ...registryProviders\n .map((p) => p.id)\n .filter((id) => !(OPEN_CODE_PROVIDER_IDS as readonly string[]).includes(id)),\n ]\n const registryProviderId = configuredProviderHint\n ? canonicalProviderId(configuredProviderHint, registryProviders.map((provider) => provider.id))\n : null\n const registryProvider = registryProviderId ? llmProviderRegistry.get(registryProviderId) : null\n const fallbackOpenCodeProviderId = (\n (configuredProviderHint\n ? canonicalProviderId(configuredProviderHint, OPEN_CODE_PROVIDER_IDS as readonly string[])\n : null) ?? 'openai'\n ) as keyof typeof OPEN_CODE_PROVIDERS\n const fallbackOpenCodeProvider = OPEN_CODE_PROVIDERS[fallbackOpenCodeProviderId]\n\n const providerId = registryProvider?.id ?? fallbackOpenCodeProviderId\n const providerName = registryProvider?.name ?? fallbackOpenCodeProvider?.name ?? providerId\n const defaultProviderModel = registryProvider?.defaultModel ?? fallbackOpenCodeProvider?.defaultModel ?? ''\n const configuredModelHint = env.OM_AI_MODEL?.trim() || env.OPENCODE_MODEL?.trim() || defaultProviderModel\n const fallbackModelWithProvider = `${providerId}/${configuredModelHint}`\n const apiKeyConfigured = registryProvider\n ? registryProvider.isConfigured(env)\n : fallbackOpenCodeProvider\n ? isOpenCodeProviderConfigured(fallbackOpenCodeProviderId)\n : false\n const displayEnvKey = registryProvider\n ? registryProvider.getConfiguredEnvKey(env)\n : fallbackOpenCodeProvider\n ? getOpenCodeProviderConfiguredEnvKey(fallbackOpenCodeProviderId)\n : null\n\n // Check if MCP_SERVER_API_KEY is configured (required for MCP authentication)\n const mcpKeyConfigured = !!process.env.MCP_SERVER_API_KEY?.trim()\n\n // Phase 4a: resolve tenant override row and per-agent resolution matrix\n let tenantOverride: {\n providerId: string | null\n modelId: string | null\n baseURL: string | null\n agentId: string | null\n updatedAt: string\n } | null = null\n\n let agentResolutions: Array<{\n agentId: string\n moduleId: string\n allowRuntimeOverride: boolean\n allowRuntimeModelOverride: boolean\n codeDefaultProviderId: string | null\n codeDefaultModelId: string | null\n override: {\n providerId: string | null\n modelId: string | null\n baseURL: string | null\n updatedAt: string\n } | null\n runtimeOverrideAllowlist: {\n env: TenantAllowlistSnapshot | null\n tenant: TenantAllowlistSnapshot | null\n effective: ReturnType<typeof intersectAllowlists>\n envVarNames: {\n providers: string\n modelsByProvider: Record<string, string>\n }\n }\n providerId: string\n modelId: string\n baseURL: string | null\n source: string\n }> = []\n\n let resolvedDefault: {\n providerId: string\n modelId: string\n baseURL: string | null\n source: string\n } | null = null\n\n let tenantAllowlistSnapshot: TenantAllowlistSnapshot | null = null\n\n try {\n const container = await createRequestContainer()\n const tenantId = auth.tenantId ?? null\n const organizationId = auth.orgId ?? null\n\n if (tenantId) {\n const em = container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const overrideRow = await repo.getDefault({ tenantId, organizationId, agentId: null })\n if (overrideRow) {\n tenantOverride = {\n providerId: overrideRow.providerId ?? null,\n modelId: overrideRow.modelId ?? null,\n baseURL: overrideRow.baseUrl ?? null,\n agentId: overrideRow.agentId ?? null,\n updatedAt: overrideRow.updatedAt.toISOString(),\n }\n }\n\n const allowlistRepo = new AiTenantModelAllowlistRepository(em)\n tenantAllowlistSnapshot = await allowlistRepo.getSnapshot({\n tenantId,\n organizationId,\n })\n\n const factory = createModelFactory(container)\n const defaultResolution = factory.resolveModel({\n tenantAllowlist: tenantAllowlistSnapshot,\n tenantOverride: tenantOverride\n ? { providerId: tenantOverride.providerId, modelId: tenantOverride.modelId, baseURL: tenantOverride.baseURL }\n : undefined,\n })\n resolvedDefault = {\n providerId: defaultResolution.providerId,\n modelId: defaultResolution.modelId,\n baseURL: defaultResolution.baseURL ?? null,\n source: defaultResolution.source,\n }\n\n await loadAgentRegistry()\n const agents = listAgents()\n const agentResolutionPromises = agents.map(async (agent) => {\n const agentOverrideRow = await repo.getExact({\n tenantId,\n organizationId,\n agentId: agent.id,\n })\n const agentTenantOverride = agentOverrideRow\n ? {\n providerId: agentOverrideRow.providerId ?? null,\n modelId: agentOverrideRow.modelId ?? null,\n baseURL: agentOverrideRow.baseUrl ?? null,\n }\n : (tenantOverride ?? undefined)\n const agentResolution = factory.resolveModel({\n moduleId: agent.moduleId,\n agentDefaultModel: agent.defaultModel,\n agentDefaultProvider: agent.defaultProvider,\n agentDefaultBaseUrl: agent.defaultBaseUrl,\n allowRuntimeOverride: resolveAllowRuntimeOverride(agent),\n tenantOverride: agentTenantOverride,\n tenantAllowlist: tenantAllowlistSnapshot,\n })\n const agentEnvAllowlist = readAgentRuntimeOverrideAllowlist(\n env,\n agent.id,\n knownProviderIdsForAllowlist,\n )\n const agentTenantAllowlist = agentOverrideRow\n ? {\n allowedProviders: agentOverrideRow.allowedOverrideProviders ?? null,\n allowedModelsByProvider: agentOverrideRow.allowedOverrideModelsByProvider ?? {},\n }\n : null\n const baseEffectiveAllowlist = intersectAllowlists(\n env,\n knownProviderIdsForAllowlist,\n tenantAllowlistSnapshot,\n )\n const agentEffectiveAllowlist = intersectEffectiveAllowlistWithSnapshot(\n intersectEffectiveAllowlistWithSnapshot(\n baseEffectiveAllowlist,\n knownProviderIdsForAllowlist,\n agentEnvAllowlist,\n ),\n knownProviderIdsForAllowlist,\n agentTenantAllowlist,\n )\n const agentModelEnvVars = Object.fromEntries(\n knownProviderIdsForAllowlist.map((providerId) => [\n providerId,\n agentOverrideModelAllowlistEnvVarName(agent.id, providerId),\n ]),\n )\n return {\n agentId: agent.id,\n moduleId: agent.moduleId,\n allowRuntimeOverride: resolveAllowRuntimeOverride(agent),\n allowRuntimeModelOverride: resolveAllowRuntimeOverride(agent),\n codeDefaultProviderId: agent.defaultProvider ?? null,\n codeDefaultModelId: agent.defaultModel ?? null,\n override: agentOverrideRow\n ? {\n providerId: agentOverrideRow.providerId ?? null,\n modelId: agentOverrideRow.modelId ?? null,\n baseURL: agentOverrideRow.baseUrl ?? null,\n updatedAt: agentOverrideRow.updatedAt.toISOString(),\n }\n : null,\n runtimeOverrideAllowlist: {\n env: agentEnvAllowlist,\n tenant: hasAllowlistSnapshotRestrictions(agentTenantAllowlist)\n ? agentTenantAllowlist\n : null,\n effective: agentEffectiveAllowlist,\n envVarNames: {\n providers: agentOverrideProviderAllowlistEnvVarName(agent.id),\n modelsByProvider: agentModelEnvVars,\n },\n },\n providerId: agentResolution.providerId,\n modelId: agentResolution.modelId,\n baseURL: agentResolution.baseURL ?? null,\n source: agentResolution.source,\n }\n })\n agentResolutions = await Promise.all(agentResolutionPromises)\n }\n } catch (overrideError) {\n // Phase 4a fields are best-effort \u2014 log and continue returning the base response\n logger.warn('AI Settings \u2014 Failed to compute Phase 4a override fields', { err: overrideError })\n }\n\n // Build availableProviders with Phase 4a defaultModels, then clip to the\n // EFFECTIVE allowlist \u2014 env intersected with the per-tenant snapshot.\n // The env allowlist is the OUTER constraint; the tenant allowlist (Phase\n // 1780-6) narrows it further. The settings UI must never offer a value\n // the runtime would refuse to honor.\n const allowlistConfig = readAllowlistConfig(env, knownProviderIdsForAllowlist)\n const effectiveAllowlist = intersectAllowlists(\n env,\n knownProviderIdsForAllowlist,\n tenantAllowlistSnapshot,\n )\n\n const allRawProviders = [\n ...OPEN_CODE_PROVIDER_IDS.map((id) => {\n const info = OPEN_CODE_PROVIDERS[id]\n const registryProvider = llmProviderRegistry.get(id)\n return {\n id,\n name: info.name,\n defaultModel: info.defaultModel,\n envKey: getOpenCodeProviderConfiguredEnvKey(id),\n configured: isOpenCodeProviderConfigured(id),\n defaultModels: registryProvider?.defaultModels ?? [],\n }\n }),\n // Also surface any llmProviderRegistry providers not in OPEN_CODE_PROVIDER_IDS\n ...llmProviderRegistry.list()\n .filter((p) => !(OPEN_CODE_PROVIDER_IDS as readonly string[]).includes(p.id))\n .map((p) => ({\n id: p.id,\n name: p.name,\n defaultModel: p.defaultModels[0]?.id ?? '',\n envKey: null,\n configured: p.isConfigured(),\n defaultModels: p.defaultModels,\n })),\n ]\n\n const availableProviders = allRawProviders\n .filter((p) => isProviderAllowedInEffective(effectiveAllowlist, p.id))\n .map((p) => {\n const effectiveModelsList = effectiveAllowlist.modelsByProvider[p.id]\n const catalogModels = modelCatalogWithAllowlistFallback(\n p.defaultModels,\n effectiveModelsList,\n )\n const clippedDefaults = effectiveModelsList !== undefined\n ? catalogModels.filter((m) => effectiveModelsList.includes(m.id))\n : catalogModels\n return {\n ...p,\n defaultModel: effectiveModelsList && !effectiveModelsList.includes(p.defaultModel)\n ? (effectiveModelsList[0] ?? p.defaultModel)\n : p.defaultModel || clippedDefaults[0]?.id || '',\n defaultModels: clippedDefaults,\n }\n })\n\n const allowlistProviders = allRawProviders\n .filter((p) => isProviderAllowed(env, p.id))\n .map((p) => {\n const envModelsList = allowlistConfig.modelsByProvider[p.id]\n const catalogModels = modelCatalogWithAllowlistFallback(\n p.defaultModels,\n envModelsList,\n )\n const envClippedDefaults = envModelsList !== undefined\n ? catalogModels.filter((m) => envModelsList.includes(m.id))\n : catalogModels\n return {\n ...p,\n defaultModel: envModelsList && !envModelsList.includes(p.defaultModel)\n ? (envModelsList[0] ?? p.defaultModel)\n : p.defaultModel || envClippedDefaults[0]?.id || '',\n defaultModels: envClippedDefaults,\n }\n })\n\n return NextResponse.json({\n provider: {\n id: providerId,\n name: providerName,\n model: resolvedDefault\n ? `${resolvedDefault.providerId}/${resolvedDefault.modelId}`\n : fallbackModelWithProvider,\n defaultModel: defaultProviderModel,\n envKey: displayEnvKey,\n configured: apiKeyConfigured,\n },\n availableProviders,\n // Editable universe for the tenant allowlist page. This is clipped only\n // by env so tenant-hidden models remain visible and can be re-enabled.\n allowlistProviders,\n // Snapshot of the env-driven allowlist so the UI can render hints like\n // \"limited to: openai, anthropic\" without re-implementing the parser.\n allowlist: allowlistConfig,\n // Per-tenant allowlist snapshot (Phase 1780-6). `null` when no row has\n // been persisted yet \u2014 the runtime then falls back to env-only\n // enforcement. The UI uses this to drive the editable MultiSelect.\n tenantAllowlist: tenantAllowlistSnapshot,\n // Effective allowlist after intersecting env with tenant. The UI uses\n // this to render the \"what the runtime will actually accept\" summary\n // and to clip pickers without re-implementing the intersection.\n effectiveAllowlist,\n mcpKeyConfigured,\n resolvedDefault,\n tenantOverride,\n agents: agentResolutions,\n })\n } catch (error) {\n logger.error('AI Settings \u2014 GET error', { err: error })\n return NextResponse.json({ error: 'Failed to fetch settings' }, { status: 500 })\n }\n}\n\n/**\n * PUT /api/ai_assistant/settings\n *\n * Upserts the per-tenant AI runtime override (Phase 4a). Requires\n * `ai_assistant.settings.manage`. The body is Zod-validated; a `baseURL`\n * must match `AI_RUNTIME_BASEURL_ALLOWLIST` when that env var is set.\n */\nexport async function PUT(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Request body must be valid JSON.', code: 'validation_error' }, { status: 400 })\n }\n\n const bodyResult = runtimeOverrideUpsertSchema.safeParse(parsedBody)\n if (!bodyResult.success) {\n return NextResponse.json(\n { error: 'Invalid request body.', code: 'validation_error', issues: bodyResult.error.issues },\n { status: 400 },\n )\n }\n\n const { providerId: requestedProviderId, modelId, baseURL, agentId } = bodyResult.data\n const knownProviderIdsForRequest = llmProviderRegistry.list().map((p) => p.id)\n const providerId = requestedProviderId\n ? canonicalProviderId(requestedProviderId, knownProviderIdsForRequest) ?? requestedProviderId\n : requestedProviderId\n\n if (baseURL && baseURL.trim().length > 0) {\n const allowlist = readBaseurlAllowlist()\n if (!isBaseurlAllowlisted(baseURL.trim(), allowlist)) {\n return NextResponse.json(\n {\n error: `baseURL \"${baseURL}\" is not in AI_RUNTIME_BASEURL_ALLOWLIST.`,\n code: 'baseurl_not_allowlisted',\n },\n { status: 400 },\n )\n }\n }\n\n const allowedOverrideProviders = bodyResult.data.allowedOverrideProviders === undefined\n ? undefined\n : bodyResult.data.allowedOverrideProviders?.map((id) =>\n canonicalProviderId(id, knownProviderIdsForRequest) ?? id,\n ) ?? null\n const allowedOverrideModelsByProvider = bodyResult.data.allowedOverrideModelsByProvider === undefined\n ? undefined\n : Object.fromEntries(\n Object.entries(bodyResult.data.allowedOverrideModelsByProvider ?? {}).map(([id, models]) => [\n canonicalProviderId(id, knownProviderIdsForRequest) ?? id,\n models,\n ]),\n )\n const hasRuntimeOverrideAllowlistWrite =\n allowedOverrideProviders !== undefined || allowedOverrideModelsByProvider !== undefined\n\n // Env-driven provider/model allowlist (Phase 1780-5) intersected with the\n // per-tenant allowlist (Phase 1780-6): the EFFECTIVE allowlist clips which\n // (provider, model) pairs the runtime accepts. Reject settings PUT requests\n // for pairs outside that effective set so the settings UI never persists a\n // value the runtime would later refuse. Tenant-allowlist enforcement is\n // best-effort here: if the snapshot lookup fails we fall back to env-only\n // checks (the runtime still re-clips at resolution time).\n let putEffectiveAllowlist: ReturnType<typeof intersectAllowlists> | null = null\n if (providerId || hasRuntimeOverrideAllowlistWrite) {\n try {\n const previewContainer = await createRequestContainer()\n const knownIdsForCheck = [\n ...OPEN_CODE_PROVIDER_IDS,\n ...llmProviderRegistry\n .list()\n .map((p) => p.id)\n .filter((id) => !(OPEN_CODE_PROVIDER_IDS as readonly string[]).includes(id)),\n ]\n let snapshot: TenantAllowlistSnapshot | null = null\n if (auth.tenantId) {\n try {\n const em = previewContainer.resolve<EntityManager>('em')\n const allowlistRepo = new AiTenantModelAllowlistRepository(em)\n snapshot = await allowlistRepo.getSnapshot({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n })\n } catch {\n snapshot = null\n }\n }\n putEffectiveAllowlist = intersectAllowlists(\n process.env as Record<string, string | undefined>,\n knownIdsForCheck,\n snapshot,\n )\n } catch {\n putEffectiveAllowlist = null\n }\n\n if (providerId && putEffectiveAllowlist) {\n if (!isProviderAllowedInEffective(putEffectiveAllowlist, providerId)) {\n const source = putEffectiveAllowlist.tenantOverridesActive\n ? 'the effective allowlist (env \u2229 tenant)'\n : 'OM_AI_AVAILABLE_PROVIDERS'\n return NextResponse.json(\n {\n error: `Provider \"${providerId}\" is not in ${source}.`,\n code: 'provider_not_allowlisted',\n },\n { status: 400 },\n )\n }\n if (modelId && !isProviderModelAllowedInEffective(putEffectiveAllowlist, providerId, modelId)) {\n const source = putEffectiveAllowlist.tenantOverridesActive\n ? `the effective allowlist (env \u2229 tenant) for \"${providerId}\"`\n : modelAllowlistEnvVarName(providerId)\n return NextResponse.json(\n {\n error: `Model \"${modelId}\" is not in ${source}.`,\n code: 'model_not_allowlisted',\n },\n { status: 400 },\n )\n }\n } else if (providerId) {\n if (!isProviderAllowed(process.env, providerId)) {\n return NextResponse.json(\n {\n error: `Provider \"${requestedProviderId}\" is not in OM_AI_AVAILABLE_PROVIDERS.`,\n code: 'provider_not_allowlisted',\n },\n { status: 400 },\n )\n }\n if (modelId && !isProviderModelAllowed(process.env, providerId, modelId)) {\n return NextResponse.json(\n {\n error: `Model \"${modelId}\" is not in ${modelAllowlistEnvVarName(providerId)}.`,\n code: 'model_not_allowlisted',\n },\n { status: 400 },\n )\n }\n }\n }\n\n if (hasRuntimeOverrideAllowlistWrite && !agentId) {\n return NextResponse.json(\n {\n error: 'agentId is required when saving chat override allowlist settings.',\n code: 'agent_required',\n },\n { status: 400 },\n )\n }\n\n if (Array.isArray(allowedOverrideProviders)) {\n for (const id of allowedOverrideProviders) {\n if (putEffectiveAllowlist && !isProviderAllowedInEffective(putEffectiveAllowlist, id)) {\n return NextResponse.json(\n {\n error: `Provider \"${id}\" is not in the effective tenant allowlist; per-agent chat override choices may not widen it.`,\n code: 'provider_not_allowlisted',\n },\n { status: 400 },\n )\n }\n }\n }\n if (allowedOverrideModelsByProvider) {\n for (const [id, models] of Object.entries(allowedOverrideModelsByProvider)) {\n if (putEffectiveAllowlist && !isProviderAllowedInEffective(putEffectiveAllowlist, id)) {\n return NextResponse.json(\n {\n error: `Provider \"${id}\" is not in the effective tenant allowlist; cannot save per-agent model choices for it.`,\n code: 'provider_not_allowlisted',\n },\n { status: 400 },\n )\n }\n for (const allowedModelId of models) {\n if (\n putEffectiveAllowlist &&\n !isProviderModelAllowedInEffective(putEffectiveAllowlist, id, allowedModelId)\n ) {\n return NextResponse.json(\n {\n error: `Model \"${allowedModelId}\" is not in the effective tenant allowlist for \"${id}\".`,\n code: 'model_not_allowlisted',\n },\n { status: 400 },\n )\n }\n }\n }\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n const canManage = acl.isSuperAdmin || acl.features.includes('ai_assistant.settings.manage')\n if (!canManage) {\n return NextResponse.json({ error: 'Forbidden', code: 'forbidden' }, { status: 403 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const upsertInput = {\n agentId: agentId ?? null,\n ...(Object.prototype.hasOwnProperty.call(bodyResult.data, 'providerId')\n ? { providerId: providerId ?? null }\n : {}),\n ...(Object.prototype.hasOwnProperty.call(bodyResult.data, 'modelId')\n ? { modelId: modelId ?? null }\n : {}),\n ...(Object.prototype.hasOwnProperty.call(bodyResult.data, 'baseURL')\n ? { baseURL: baseURL ?? null }\n : {}),\n ...(allowedOverrideProviders !== undefined\n ? { allowedOverrideProviders }\n : {}),\n ...(allowedOverrideModelsByProvider !== undefined\n ? { allowedOverrideModelsByProvider }\n : {}),\n }\n const row = await repo.upsertDefault(\n upsertInput,\n { tenantId: auth.tenantId ?? '', organizationId: auth.orgId ?? null, userId: auth.sub },\n )\n return NextResponse.json({\n id: row.id,\n tenantId: row.tenantId,\n organizationId: row.organizationId,\n agentId: row.agentId,\n providerId: row.providerId,\n modelId: row.modelId,\n baseURL: row.baseUrl,\n allowedOverrideProviders: row.allowedOverrideProviders ?? null,\n allowedOverrideModelsByProvider: row.allowedOverrideModelsByProvider ?? {},\n updatedAt: row.updatedAt,\n })\n } catch (error) {\n if (error instanceof AiAgentRuntimeOverrideValidationError) {\n return NextResponse.json({ error: error.message, code: 'provider_unknown' }, { status: 400 })\n }\n logger.error('AI Settings \u2014 PUT error', { err: error })\n return NextResponse.json({ error: 'Failed to save runtime override.' }, { status: 500 })\n }\n}\n\n/**\n * DELETE /api/ai_assistant/settings\n *\n * Soft-deletes the active per-tenant AI runtime override (Phase 4a). Requires\n * `ai_assistant.settings.manage`. Pass `agentId` in the body to clear only\n * the agent-specific row; omit (or null) to clear the tenant-wide default.\n * Idempotent \u2014 returns `{ cleared: false }` when no active row was found.\n */\nexport async function DELETE(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let parsedBody: unknown = {}\n try {\n parsedBody = await req.json()\n } catch {\n // Body is optional for DELETE \u2014 empty body is fine\n }\n\n const bodyResult = runtimeOverrideClearSchema.safeParse(parsedBody)\n if (!bodyResult.success) {\n return NextResponse.json(\n { error: 'Invalid request body.', code: 'validation_error', issues: bodyResult.error.issues },\n { status: 400 },\n )\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n const canManage = acl.isSuperAdmin || acl.features.includes('ai_assistant.settings.manage')\n if (!canManage) {\n return NextResponse.json({ error: 'Forbidden', code: 'forbidden' }, { status: 403 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const cleared = await repo.clearDefault({\n tenantId: auth.tenantId ?? '',\n organizationId: auth.orgId ?? null,\n agentId: bodyResult.data.agentId ?? null,\n })\n return NextResponse.json({ cleared })\n } catch (error) {\n logger.error('AI Settings \u2014 DELETE error', { err: error })\n return NextResponse.json({ error: 'Failed to clear runtime override.' }, { status: 500 })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["providerId", "registryProvider"]
|
|
7
7
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
5
|
import { executeTool } from "../../../lib/tool-executor.js";
|
|
5
6
|
import { loadAllModuleTools } from "../../../lib/tool-loader.js";
|
|
7
|
+
const logger = createLogger("ai_assistant");
|
|
6
8
|
const openApi = {
|
|
7
9
|
tag: "AI Assistant",
|
|
8
10
|
summary: "Execute AI tool",
|
|
@@ -51,7 +53,7 @@ async function POST(req) {
|
|
|
51
53
|
result: result.result
|
|
52
54
|
});
|
|
53
55
|
} catch (error) {
|
|
54
|
-
|
|
56
|
+
logger.error("AI Tools \u2014 Error executing tool", { err: error });
|
|
55
57
|
return NextResponse.json(
|
|
56
58
|
{ success: false, error: "Tool execution failed" },
|
|
57
59
|
{ status: 500 }
|