@open-mercato/ai-assistant 0.6.1-develop.3246.1.dbef9d7392 → 0.6.1-develop.3256.1.fe3dec2464
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +82 -18
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +370 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +194 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/route.js +4 -0
- package/dist/modules/ai_assistant/api/ai/agents/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +169 -5
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/route/route.js +38 -19
- package/dist/modules/ai_assistant/api/route/route.js.map +3 -3
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js +195 -0
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/route.js +537 -22
- package/dist/modules/ai_assistant/api/settings/route.js.map +3 -3
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +701 -147
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +338 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js +1 -1
- package/dist/modules/ai_assistant/backend/config/ai-assistant/legacy/page.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +75 -26
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js +503 -168
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities/AiTenantModelAllowlist.js +5 -0
- package/dist/modules/ai_assistant/data/entities/AiTenantModelAllowlist.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities.js +123 -1
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +157 -0
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +7 -0
- package/dist/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.js +77 -0
- package/dist/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.js.map +7 -0
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +1 -1
- package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +90 -1
- package/dist/modules/ai_assistant/i18n/en.json +90 -1
- package/dist/modules/ai_assistant/i18n/es.json +90 -1
- package/dist/modules/ai_assistant/i18n/pl.json +90 -1
- package/dist/modules/ai_assistant/lib/agent-registry.js +17 -1
- package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +133 -36
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/baseurl-allowlist.js +29 -0
- package/dist/modules/ai_assistant/lib/baseurl-allowlist.js.map +7 -0
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +4 -1
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-adapters/google.js +4 -1
- package/dist/modules/ai_assistant/lib/llm-adapters/google.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-allowlist.js +211 -0
- package/dist/modules/ai_assistant/lib/model-allowlist.js.map +7 -0
- package/dist/modules/ai_assistant/lib/model-factory.js +203 -31
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +32 -1
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +2 -2
- package/dist/modules/ai_assistant/migrations/Migration20260508140000.js +18 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508140000.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512090000.js +16 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512090000.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512130000.js +15 -0
- package/dist/modules/ai_assistant/migrations/Migration20260512130000.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +13 -0
- package/generated/entities/ai_tenant_model_allowlist/index.ts +9 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +26 -0
- package/jest.config.cjs +2 -0
- package/package.json +4 -4
- package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +477 -0
- package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +116 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +240 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +251 -0
- package/src/modules/ai_assistant/api/ai/agents/route.ts +4 -0
- package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +273 -0
- package/src/modules/ai_assistant/api/ai/chat/route.ts +211 -2
- package/src/modules/ai_assistant/api/route/route.ts +49 -25
- package/src/modules/ai_assistant/api/settings/__tests__/route.test.ts +408 -0
- package/src/modules/ai_assistant/api/settings/allowlist/route.ts +221 -0
- package/src/modules/ai_assistant/api/settings/route.ts +721 -27
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +858 -177
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +458 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/page.tsx +12 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/legacy/page.tsx +1 -1
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +89 -12
- package/src/modules/ai_assistant/backend/config/ai-assistant/settings/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/settings/page.tsx +18 -0
- package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +617 -209
- package/src/modules/ai_assistant/data/entities/AiAgentRuntimeOverride.ts +7 -0
- package/src/modules/ai_assistant/data/entities/AiTenantModelAllowlist.ts +2 -0
- package/src/modules/ai_assistant/data/entities.ts +164 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +227 -0
- package/src/modules/ai_assistant/data/repositories/AiTenantModelAllowlistRepository.ts +132 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +337 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiTenantModelAllowlistRepository.test.ts +181 -0
- package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +1 -1
- package/src/modules/ai_assistant/i18n/de.json +90 -1
- package/src/modules/ai_assistant/i18n/en.json +90 -1
- package/src/modules/ai_assistant/i18n/es.json +90 -1
- package/src/modules/ai_assistant/i18n/pl.json +90 -1
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +396 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +60 -6
- package/src/modules/ai_assistant/lib/__tests__/ai-api-operation-runner.test.ts +4 -2
- package/src/modules/ai_assistant/lib/__tests__/baseurl-allowlist.test.ts +75 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +18 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-google.test.ts +18 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +150 -4
- package/src/modules/ai_assistant/lib/__tests__/model-allowlist.test.ts +290 -0
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +634 -0
- package/src/modules/ai_assistant/lib/agent-registry.ts +20 -1
- package/src/modules/ai_assistant/lib/agent-runtime.ts +220 -44
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +48 -0
- package/src/modules/ai_assistant/lib/baseurl-allowlist.ts +64 -0
- package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +11 -1
- package/src/modules/ai_assistant/lib/llm-adapters/google.ts +4 -1
- package/src/modules/ai_assistant/lib/model-allowlist.ts +407 -0
- package/src/modules/ai_assistant/lib/model-factory.ts +486 -58
- package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +44 -0
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +704 -235
- package/src/modules/ai_assistant/migrations/Migration20260508140000.ts +18 -0
- package/src/modules/ai_assistant/migrations/Migration20260512090000.ts +16 -0
- package/src/modules/ai_assistant/migrations/Migration20260512130000.ts +13 -0
|
@@ -4,14 +4,12 @@ import { z } from "zod";
|
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
5
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
6
6
|
import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
|
|
7
|
-
import {
|
|
8
|
-
resolveAiProviderIdFromEnv,
|
|
9
|
-
resolveOpenCodeModel
|
|
10
|
-
} from "@open-mercato/shared/lib/ai/opencode-provider";
|
|
7
|
+
import { resolveOpenCodeModel } from "@open-mercato/shared/lib/ai/opencode-provider";
|
|
11
8
|
import {
|
|
12
9
|
resolveChatConfig,
|
|
13
10
|
isProviderConfigured
|
|
14
11
|
} from "../../lib/chat-config.js";
|
|
12
|
+
import { createModelFactory, AiModelFactoryError } from "../../lib/model-factory.js";
|
|
15
13
|
const openApi = {
|
|
16
14
|
tag: "AI Assistant",
|
|
17
15
|
summary: "AI query routing",
|
|
@@ -73,22 +71,43 @@ async function POST(req) {
|
|
|
73
71
|
const container = await createRequestContainer();
|
|
74
72
|
let config = await resolveChatConfig(container);
|
|
75
73
|
if (!config) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
let factoryResolution;
|
|
75
|
+
try {
|
|
76
|
+
factoryResolution = createModelFactory(container).resolveModel({
|
|
77
|
+
callerOverride: void 0
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error instanceof AiModelFactoryError && error.code === "no_provider_configured") {
|
|
81
|
+
return NextResponse.json(
|
|
82
|
+
{
|
|
83
|
+
error: "No AI provider configured. Please set an API key for one of the registered providers (Anthropic, OpenAI, Google, DeepInfra, Groq, \u2026)."
|
|
84
|
+
},
|
|
85
|
+
{ status: 503 }
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
90
89
|
}
|
|
91
|
-
|
|
90
|
+
console.log("[AI Route] Using provider:", factoryResolution.providerId);
|
|
91
|
+
const modelWithProvider2 = `${factoryResolution.providerId}/${factoryResolution.modelId}`;
|
|
92
|
+
console.log("[AI Route] Calling generateObject with", modelWithProvider2);
|
|
93
|
+
const result2 = await generateObject({
|
|
94
|
+
model: factoryResolution.model,
|
|
95
|
+
schema: RouteResultSchema,
|
|
96
|
+
prompt: `You are a routing assistant. Given a user query, determine if they want to use a specific tool or have a general conversation.
|
|
97
|
+
|
|
98
|
+
Available tools:
|
|
99
|
+
${availableTools.map((t) => `- ${t.name}: ${t.description}`).join("\n")}
|
|
100
|
+
|
|
101
|
+
User query: "${query}"
|
|
102
|
+
|
|
103
|
+
Respond with:
|
|
104
|
+
- intent: "tool" if user wants to perform an action with a specific tool, "general_chat" otherwise
|
|
105
|
+
- toolName: the exact tool name if intent is "tool"
|
|
106
|
+
- confidence: 0-1 how confident you are
|
|
107
|
+
- reasoning: brief explanation`
|
|
108
|
+
});
|
|
109
|
+
console.log("[AI Route] Result:", result2.object);
|
|
110
|
+
return NextResponse.json(result2.object);
|
|
92
111
|
}
|
|
93
112
|
console.log("[AI Route] Using provider:", config.providerId);
|
|
94
113
|
if (!isProviderConfigured(config.providerId)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/api/route/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { generateObject } from '../../lib/ai-sdk'\nimport { 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 {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAE/C,SAAS,sBAAsB;AAC/B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,
|
|
6
|
-
"names": []
|
|
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 console.log('[AI Route] Routing query:', query)\n console.log('[AI Route] Available tools count:', availableTools?.length)\n\n if (!query || typeof query !== 'string') {\n return NextResponse.json({ error: 'query is required' }, { status: 400 })\n }\n\n if (!availableTools || !Array.isArray(availableTools)) {\n return NextResponse.json({ error: 'availableTools array is required' }, { status: 400 })\n }\n\n // Get user's configured provider\n const container = await createRequestContainer()\n let config = await resolveChatConfig(container)\n\n // 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 console.log('[AI Route] Using provider:', factoryResolution.providerId)\n\n const modelWithProvider = `${factoryResolution.providerId}/${factoryResolution.modelId}`\n console.log('[AI Route] Calling generateObject with', 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 console.log('[AI Route] Result:', result.object)\n return NextResponse.json(result.object)\n }\n\n console.log('[AI Route] Using provider:', config.providerId)\n\n // Verify the configured provider is still available\n if (!isProviderConfigured(config.providerId)) {\n return NextResponse.json(\n { error: `Configured provider ${config.providerId} is no longer available. Please update settings.` },\n { status: 503 }\n )\n }\n\n // Use fast model for the configured provider\n const { model, modelWithProvider } = createRoutingModel(config.providerId, config.model)\n\n const toolList = availableTools\n .map((t) => `- ${t.name}: ${t.description}`)\n .join('\\n')\n\n console.log('[AI Route] Calling generateObject with', modelWithProvider)\n\n const result = await generateObject({\n model,\n schema: RouteResultSchema,\n prompt: `You are a routing assistant. Given a user query, determine if they want to use a specific tool or have a general conversation.\n\nAvailable tools:\n${toolList}\n\nUser query: \"${query}\"\n\nRespond with:\n- intent: \"tool\" if user wants to perform an action with a specific tool, \"general_chat\" otherwise\n- toolName: the exact tool name if intent is \"tool\"\n- confidence: 0-1 how confident you are\n- reasoning: brief explanation`,\n })\n\n console.log('[AI Route] Result:', result.object)\n return NextResponse.json(result.object)\n } catch (error) {\n console.error('[AI Route] Error routing query:', error)\n return NextResponse.json(\n { error: 'Routing request failed' },\n { status: 500 }\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["modelWithProvider", "result"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
|
|
6
|
+
import { AiTenantModelAllowlistRepository } from "../../../data/repositories/AiTenantModelAllowlistRepository.js";
|
|
7
|
+
import {
|
|
8
|
+
canonicalProviderId,
|
|
9
|
+
isModelAllowedForProvider,
|
|
10
|
+
isProviderAllowed,
|
|
11
|
+
modelAllowlistEnvVarName
|
|
12
|
+
} from "../../../lib/model-allowlist.js";
|
|
13
|
+
const allowlistUpsertSchema = z.object({
|
|
14
|
+
allowedProviders: z.array(z.string().min(1).max(64)).nullable().optional(),
|
|
15
|
+
allowedModelsByProvider: z.record(z.string().min(1).max(64), z.array(z.string().min(1).max(256))).optional()
|
|
16
|
+
});
|
|
17
|
+
const openApi = {
|
|
18
|
+
tag: "AI Assistant",
|
|
19
|
+
summary: "AI assistant tenant allowlist",
|
|
20
|
+
methods: {
|
|
21
|
+
PUT: {
|
|
22
|
+
summary: "Upsert per-tenant AI provider/model allowlist",
|
|
23
|
+
description: "Persists the per-tenant allowlist of providers and models. The runtime intersects this with the env allowlist (`OM_AI_AVAILABLE_*`) at resolution time. Tenant values that fall outside the env allowlist are rejected with `provider_not_in_env_allowlist` / `model_not_in_env_allowlist` 400 codes. Gated by `ai_assistant.settings.manage`.",
|
|
24
|
+
requestBody: {
|
|
25
|
+
contentType: "application/json",
|
|
26
|
+
description: "Allowlist payload. `allowedProviders: null` clears tenant provider restriction (inherit env). Missing key in `allowedModelsByProvider` inherits env for that provider.",
|
|
27
|
+
schema: allowlistUpsertSchema
|
|
28
|
+
},
|
|
29
|
+
responses: [
|
|
30
|
+
{ status: 200, description: "Allowlist saved. Returns the saved snapshot." }
|
|
31
|
+
],
|
|
32
|
+
errors: [
|
|
33
|
+
{ status: 400, description: "Validation error or values outside env allowlist." },
|
|
34
|
+
{ status: 401, description: "Unauthenticated." },
|
|
35
|
+
{ status: 403, description: "Caller lacks ai_assistant.settings.manage." }
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
DELETE: {
|
|
39
|
+
summary: "Clear per-tenant AI provider/model allowlist",
|
|
40
|
+
description: "Soft-deletes the tenant allowlist row. Tenant overrides revert to env-only enforcement. Idempotent \u2014 returns `{ cleared: false }` when no active row existed.",
|
|
41
|
+
responses: [
|
|
42
|
+
{ status: 200, description: "Returns `{ cleared: boolean }`." }
|
|
43
|
+
],
|
|
44
|
+
errors: [
|
|
45
|
+
{ status: 401, description: "Unauthenticated." },
|
|
46
|
+
{ status: 403, description: "Caller lacks ai_assistant.settings.manage." }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const metadata = {
|
|
52
|
+
PUT: { requireAuth: true, requireFeatures: ["ai_assistant.settings.manage"] },
|
|
53
|
+
DELETE: { requireAuth: true, requireFeatures: ["ai_assistant.settings.manage"] }
|
|
54
|
+
};
|
|
55
|
+
async function PUT(req) {
|
|
56
|
+
const auth = await getAuthFromRequest(req);
|
|
57
|
+
if (!auth?.sub) {
|
|
58
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
59
|
+
}
|
|
60
|
+
let parsedBody;
|
|
61
|
+
try {
|
|
62
|
+
parsedBody = await req.json();
|
|
63
|
+
} catch {
|
|
64
|
+
return NextResponse.json(
|
|
65
|
+
{ error: "Request body must be valid JSON.", code: "validation_error" },
|
|
66
|
+
{ status: 400 }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const bodyResult = allowlistUpsertSchema.safeParse(parsedBody);
|
|
70
|
+
if (!bodyResult.success) {
|
|
71
|
+
return NextResponse.json(
|
|
72
|
+
{ error: "Invalid request body.", code: "validation_error", issues: bodyResult.error.issues },
|
|
73
|
+
{ status: 400 }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const env = process.env;
|
|
77
|
+
const knownProviderIds = llmProviderRegistry.list().map((provider) => provider.id);
|
|
78
|
+
const canonicalize = (providerId) => canonicalProviderId(providerId, knownProviderIds) ?? providerId;
|
|
79
|
+
const allowedProviders = bodyResult.data.allowedProviders === void 0 ? null : bodyResult.data.allowedProviders?.map(canonicalize) ?? null;
|
|
80
|
+
const allowedModelsByProvider = Object.fromEntries(
|
|
81
|
+
Object.entries(bodyResult.data.allowedModelsByProvider ?? {}).map(([providerId, models]) => [
|
|
82
|
+
canonicalize(providerId),
|
|
83
|
+
models
|
|
84
|
+
])
|
|
85
|
+
);
|
|
86
|
+
if (Array.isArray(allowedProviders)) {
|
|
87
|
+
for (const providerId of allowedProviders) {
|
|
88
|
+
if (!isProviderAllowed(env, providerId)) {
|
|
89
|
+
return NextResponse.json(
|
|
90
|
+
{
|
|
91
|
+
error: `Provider "${providerId}" is not in OM_AI_AVAILABLE_PROVIDERS; tenant allowlist may not widen the env allowlist.`,
|
|
92
|
+
code: "provider_not_in_env_allowlist"
|
|
93
|
+
},
|
|
94
|
+
{ status: 400 }
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const providerId of Object.keys(allowedModelsByProvider)) {
|
|
100
|
+
if (!isProviderAllowed(env, providerId)) {
|
|
101
|
+
return NextResponse.json(
|
|
102
|
+
{
|
|
103
|
+
error: `Provider "${providerId}" is not in OM_AI_AVAILABLE_PROVIDERS; cannot save tenant model allowlist for it.`,
|
|
104
|
+
code: "provider_not_in_env_allowlist"
|
|
105
|
+
},
|
|
106
|
+
{ status: 400 }
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
for (const modelId of allowedModelsByProvider[providerId] ?? []) {
|
|
110
|
+
if (!isModelAllowedForProvider(env, providerId, modelId)) {
|
|
111
|
+
return NextResponse.json(
|
|
112
|
+
{
|
|
113
|
+
error: `Model "${modelId}" is not in ${modelAllowlistEnvVarName(providerId)}; tenant allowlist may not widen the env allowlist.`,
|
|
114
|
+
code: "model_not_in_env_allowlist"
|
|
115
|
+
},
|
|
116
|
+
{ status: 400 }
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const container = await createRequestContainer();
|
|
123
|
+
const rbacService = container.resolve("rbacService");
|
|
124
|
+
const acl = await rbacService.loadAcl(auth.sub, {
|
|
125
|
+
tenantId: auth.tenantId,
|
|
126
|
+
organizationId: auth.orgId
|
|
127
|
+
});
|
|
128
|
+
const canManage = acl.isSuperAdmin || acl.features.includes("ai_assistant.settings.manage");
|
|
129
|
+
if (!canManage) {
|
|
130
|
+
return NextResponse.json({ error: "Forbidden", code: "forbidden" }, { status: 403 });
|
|
131
|
+
}
|
|
132
|
+
const em = container.resolve("em");
|
|
133
|
+
const repo = new AiTenantModelAllowlistRepository(em);
|
|
134
|
+
const row = await repo.upsert(
|
|
135
|
+
{ allowedProviders, allowedModelsByProvider },
|
|
136
|
+
{
|
|
137
|
+
tenantId: auth.tenantId ?? "",
|
|
138
|
+
organizationId: auth.orgId ?? null,
|
|
139
|
+
userId: auth.sub
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
return NextResponse.json({
|
|
143
|
+
id: row.id,
|
|
144
|
+
tenantId: row.tenantId,
|
|
145
|
+
organizationId: row.organizationId,
|
|
146
|
+
allowedProviders: row.allowedProviders ?? null,
|
|
147
|
+
allowedModelsByProvider: row.allowedModelsByProvider ?? {},
|
|
148
|
+
updatedAt: row.updatedAt
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("[AI Settings Allowlist] PUT error:", error);
|
|
152
|
+
return NextResponse.json(
|
|
153
|
+
{ error: "Failed to save tenant allowlist." },
|
|
154
|
+
{ status: 500 }
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function DELETE(req) {
|
|
159
|
+
const auth = await getAuthFromRequest(req);
|
|
160
|
+
if (!auth?.sub) {
|
|
161
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const container = await createRequestContainer();
|
|
165
|
+
const rbacService = container.resolve("rbacService");
|
|
166
|
+
const acl = await rbacService.loadAcl(auth.sub, {
|
|
167
|
+
tenantId: auth.tenantId,
|
|
168
|
+
organizationId: auth.orgId
|
|
169
|
+
});
|
|
170
|
+
const canManage = acl.isSuperAdmin || acl.features.includes("ai_assistant.settings.manage");
|
|
171
|
+
if (!canManage) {
|
|
172
|
+
return NextResponse.json({ error: "Forbidden", code: "forbidden" }, { status: 403 });
|
|
173
|
+
}
|
|
174
|
+
const em = container.resolve("em");
|
|
175
|
+
const repo = new AiTenantModelAllowlistRepository(em);
|
|
176
|
+
const cleared = await repo.clear({
|
|
177
|
+
tenantId: auth.tenantId ?? "",
|
|
178
|
+
organizationId: auth.orgId ?? null
|
|
179
|
+
});
|
|
180
|
+
return NextResponse.json({ cleared });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error("[AI Settings Allowlist] DELETE error:", error);
|
|
183
|
+
return NextResponse.json(
|
|
184
|
+
{ error: "Failed to clear tenant allowlist." },
|
|
185
|
+
{ status: 500 }
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
export {
|
|
190
|
+
DELETE,
|
|
191
|
+
PUT,
|
|
192
|
+
metadata,
|
|
193
|
+
openApi
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 console.error('[AI Settings Allowlist] PUT error:', 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 console.error('[AI Settings Allowlist] DELETE error:', 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
|
+
"names": []
|
|
7
|
+
}
|