@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createContainer } from "awilix";
|
|
1
2
|
import {
|
|
2
3
|
convertToModelMessages,
|
|
3
4
|
generateObject,
|
|
@@ -5,8 +6,7 @@ import {
|
|
|
5
6
|
streamObject,
|
|
6
7
|
streamText
|
|
7
8
|
} from "ai";
|
|
8
|
-
import {
|
|
9
|
-
import { resolveAiProviderIdFromEnv } from "@open-mercato/shared/lib/ai/opencode-provider";
|
|
9
|
+
import { createModelFactory } from "./model-factory.js";
|
|
10
10
|
import { resolveAiAgentTools, AgentPolicyError } from "./agent-tools.js";
|
|
11
11
|
import { resolveEffectiveMutationPolicy } from "./agent-policy.js";
|
|
12
12
|
import { toolRegistry } from "./tool-registry.js";
|
|
@@ -17,32 +17,55 @@ import {
|
|
|
17
17
|
} from "./attachment-parts.js";
|
|
18
18
|
import { AiAgentPromptOverrideRepository } from "../data/repositories/AiAgentPromptOverrideRepository.js";
|
|
19
19
|
import { AiAgentMutationPolicyOverrideRepository } from "../data/repositories/AiAgentMutationPolicyOverrideRepository.js";
|
|
20
|
+
import { AiAgentRuntimeOverrideRepository } from "../data/repositories/AiAgentRuntimeOverrideRepository.js";
|
|
21
|
+
import { AiTenantModelAllowlistRepository } from "../data/repositories/AiTenantModelAllowlistRepository.js";
|
|
20
22
|
import { composeSystemPromptWithOverride } from "./prompt-override-merge.js";
|
|
21
23
|
import { isKnownMutationPolicy } from "./agent-policy.js";
|
|
22
24
|
import "./llm-bootstrap.js";
|
|
23
|
-
function resolveAgentModel(agent, modelOverride) {
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
function resolveAgentModel(agent, modelOverride, providerOverride, container, baseUrlOverride, tenantOverride, requestOverride, tenantAllowlist) {
|
|
26
|
+
const effectiveContainer = container ?? createContainer();
|
|
27
|
+
const allowRuntimeModelOverride = agent.allowRuntimeModelOverride !== false;
|
|
28
|
+
const resolution = createModelFactory(effectiveContainer).resolveModel({
|
|
29
|
+
moduleId: agent.moduleId,
|
|
30
|
+
agentDefaultModel: agent.defaultModel,
|
|
31
|
+
agentDefaultProvider: agent.defaultProvider,
|
|
32
|
+
agentDefaultBaseUrl: agent.defaultBaseUrl,
|
|
33
|
+
callerOverride: modelOverride,
|
|
34
|
+
providerOverride,
|
|
35
|
+
baseUrlOverride,
|
|
36
|
+
allowRuntimeModelOverride,
|
|
37
|
+
tenantOverride: tenantOverride ?? void 0,
|
|
38
|
+
requestOverride: requestOverride ?? void 0,
|
|
39
|
+
tenantAllowlist: tenantAllowlist ?? null
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
model: resolution.model,
|
|
43
|
+
modelId: resolution.modelId,
|
|
44
|
+
providerId: resolution.providerId
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function resolveTenantAllowlistSnapshot(container, tenantId, organizationId) {
|
|
48
|
+
if (!tenantId || !container) return null;
|
|
49
|
+
let em = null;
|
|
50
|
+
try {
|
|
51
|
+
em = container.resolve("em");
|
|
52
|
+
} catch {
|
|
53
|
+
em = null;
|
|
35
54
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
if (!em) return null;
|
|
56
|
+
try {
|
|
57
|
+
const repo = new AiTenantModelAllowlistRepository(em);
|
|
58
|
+
return await repo.getSnapshot({
|
|
59
|
+
tenantId,
|
|
60
|
+
organizationId: organizationId ?? null
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn(
|
|
64
|
+
"[AI Agents] Tenant allowlist lookup failed; falling back to env-only enforcement.",
|
|
65
|
+
error
|
|
40
66
|
);
|
|
67
|
+
return null;
|
|
41
68
|
}
|
|
42
|
-
const globalEnvModel = (env.OM_AI_MODEL ?? env.OPENCODE_MODEL ?? "").trim();
|
|
43
|
-
const modelId = (modelOverride && modelOverride.trim().length > 0 ? modelOverride : void 0) ?? (globalEnvModel.length > 0 ? globalEnvModel : void 0) ?? agent.defaultModel ?? provider.defaultModel;
|
|
44
|
-
const model = provider.createModel({ modelId, apiKey });
|
|
45
|
-
return { model, modelId, providerId: provider.id };
|
|
46
69
|
}
|
|
47
70
|
async function composeSystemPrompt(agent, pageContext, container, tenantId, organizationId) {
|
|
48
71
|
const baseFromOverride = await resolveBaseSystemPromptWithOverride(
|
|
@@ -142,6 +165,36 @@ async function resolveMutationPolicyOverride(agentId, container, tenantId, organ
|
|
|
142
165
|
return null;
|
|
143
166
|
}
|
|
144
167
|
}
|
|
168
|
+
async function resolveRuntimeModelOverride(agentId, container, tenantId, organizationId) {
|
|
169
|
+
if (!tenantId || !container) return null;
|
|
170
|
+
let em = null;
|
|
171
|
+
try {
|
|
172
|
+
em = container.resolve("em");
|
|
173
|
+
} catch {
|
|
174
|
+
em = null;
|
|
175
|
+
}
|
|
176
|
+
if (!em) return null;
|
|
177
|
+
try {
|
|
178
|
+
const repo = new AiAgentRuntimeOverrideRepository(em);
|
|
179
|
+
const row = await repo.getDefault({
|
|
180
|
+
tenantId,
|
|
181
|
+
organizationId: organizationId ?? null,
|
|
182
|
+
agentId
|
|
183
|
+
});
|
|
184
|
+
if (!row) return null;
|
|
185
|
+
return {
|
|
186
|
+
providerId: row.providerId ?? null,
|
|
187
|
+
modelId: row.modelId ?? null,
|
|
188
|
+
baseURL: row.baseUrl ?? null
|
|
189
|
+
};
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.warn(
|
|
192
|
+
`[AI Agents] Runtime model override lookup failed for agent "${agentId}"; falling back to lower-priority sources.`,
|
|
193
|
+
error
|
|
194
|
+
);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
145
198
|
function ensureUiMessageShape(messages) {
|
|
146
199
|
return messages.map((message, index) => {
|
|
147
200
|
const raw = message;
|
|
@@ -281,12 +334,25 @@ function appendRuntimeMutationPolicy(systemPrompt, agent, mutationPolicyOverride
|
|
|
281
334
|
${block}`;
|
|
282
335
|
}
|
|
283
336
|
async function runAiAgentText(input) {
|
|
284
|
-
const mutationPolicyOverride = await
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
337
|
+
const [mutationPolicyOverride, tenantRuntimeOverride, tenantAllowlistSnapshot] = await Promise.all([
|
|
338
|
+
resolveMutationPolicyOverride(
|
|
339
|
+
input.agentId,
|
|
340
|
+
input.container,
|
|
341
|
+
input.authContext.tenantId,
|
|
342
|
+
input.authContext.organizationId
|
|
343
|
+
),
|
|
344
|
+
resolveRuntimeModelOverride(
|
|
345
|
+
input.agentId,
|
|
346
|
+
input.container,
|
|
347
|
+
input.authContext.tenantId,
|
|
348
|
+
input.authContext.organizationId
|
|
349
|
+
),
|
|
350
|
+
resolveTenantAllowlistSnapshot(
|
|
351
|
+
input.container,
|
|
352
|
+
input.authContext.tenantId,
|
|
353
|
+
input.authContext.organizationId
|
|
354
|
+
)
|
|
355
|
+
]);
|
|
290
356
|
const { agent, tools } = await resolveAiAgentTools({
|
|
291
357
|
agentId: input.agentId,
|
|
292
358
|
authContext: input.authContext,
|
|
@@ -314,7 +380,16 @@ async function runAiAgentText(input) {
|
|
|
314
380
|
agent,
|
|
315
381
|
mutationPolicyOverride
|
|
316
382
|
);
|
|
317
|
-
const { model } = resolveAgentModel(
|
|
383
|
+
const { model } = resolveAgentModel(
|
|
384
|
+
agent,
|
|
385
|
+
input.modelOverride,
|
|
386
|
+
input.providerOverride,
|
|
387
|
+
input.container,
|
|
388
|
+
input.baseUrlOverride,
|
|
389
|
+
tenantRuntimeOverride,
|
|
390
|
+
input.requestOverride,
|
|
391
|
+
tenantAllowlistSnapshot
|
|
392
|
+
);
|
|
318
393
|
const normalizedMessages = ensureUiMessageShape(input.messages);
|
|
319
394
|
const hydratedMessages = attachAttachmentsToMessages(normalizedMessages, resolvedAttachments);
|
|
320
395
|
const modelMessages = await convertToModelMessages(hydratedMessages);
|
|
@@ -370,12 +445,25 @@ function resolveStructuredOutput(agent, override) {
|
|
|
370
445
|
};
|
|
371
446
|
}
|
|
372
447
|
async function runAiAgentObject(input) {
|
|
373
|
-
const mutationPolicyOverride = await
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
448
|
+
const [mutationPolicyOverride, tenantRuntimeOverride, tenantAllowlistSnapshot] = await Promise.all([
|
|
449
|
+
resolveMutationPolicyOverride(
|
|
450
|
+
input.agentId,
|
|
451
|
+
input.container,
|
|
452
|
+
input.authContext.tenantId,
|
|
453
|
+
input.authContext.organizationId
|
|
454
|
+
),
|
|
455
|
+
resolveRuntimeModelOverride(
|
|
456
|
+
input.agentId,
|
|
457
|
+
input.container,
|
|
458
|
+
input.authContext.tenantId,
|
|
459
|
+
input.authContext.organizationId
|
|
460
|
+
),
|
|
461
|
+
resolveTenantAllowlistSnapshot(
|
|
462
|
+
input.container,
|
|
463
|
+
input.authContext.tenantId,
|
|
464
|
+
input.authContext.organizationId
|
|
465
|
+
)
|
|
466
|
+
]);
|
|
379
467
|
const { agent, tools } = await resolveAiAgentTools({
|
|
380
468
|
agentId: input.agentId,
|
|
381
469
|
authContext: input.authContext,
|
|
@@ -404,7 +492,16 @@ async function runAiAgentObject(input) {
|
|
|
404
492
|
agent,
|
|
405
493
|
mutationPolicyOverride
|
|
406
494
|
);
|
|
407
|
-
const { model } = resolveAgentModel(
|
|
495
|
+
const { model } = resolveAgentModel(
|
|
496
|
+
agent,
|
|
497
|
+
input.modelOverride,
|
|
498
|
+
input.providerOverride,
|
|
499
|
+
input.container,
|
|
500
|
+
input.baseUrlOverride,
|
|
501
|
+
tenantRuntimeOverride,
|
|
502
|
+
input.requestOverride,
|
|
503
|
+
tenantAllowlistSnapshot
|
|
504
|
+
);
|
|
408
505
|
const normalizedMessages = ensureUiMessageShape(normalizeObjectMessages(input.input));
|
|
409
506
|
const hydratedMessages = attachAttachmentsToMessages(
|
|
410
507
|
normalizedMessages,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/agent-runtime.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { LanguageModel, UIMessage } from 'ai'\nimport {\n convertToModelMessages,\n generateObject,\n stepCountIs,\n streamObject,\n streamText,\n} from 'ai'\nimport type { ZodTypeAny } from 'zod'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { resolveAiProviderIdFromEnv } from '@open-mercato/shared/lib/ai/opencode-provider'\nimport type {\n AiAgentDefinition,\n AiAgentPageContextInput,\n AiAgentStructuredOutput,\n} from './ai-agent-definition'\nimport type {\n AiChatRequestContext,\n AiResolvedAttachmentPart,\n} from './attachment-bridge-types'\nimport { resolveAiAgentTools, AgentPolicyError } from './agent-tools'\nimport { resolveEffectiveMutationPolicy } from './agent-policy'\nimport { toolRegistry } from './tool-registry'\nimport {\n attachmentPartsToUiFileParts,\n resolveAttachmentPartsForAgent,\n summarizeAttachmentPartsForPrompt,\n} from './attachment-parts'\nimport { AiAgentPromptOverrideRepository } from '../data/repositories/AiAgentPromptOverrideRepository'\nimport { AiAgentMutationPolicyOverrideRepository } from '../data/repositories/AiAgentMutationPolicyOverrideRepository'\nimport { composeSystemPromptWithOverride } from './prompt-override-merge'\nimport { isKnownMutationPolicy } from './agent-policy'\nimport type { AiAgentMutationPolicy } from './ai-agent-definition'\n\n// Ensure built-in LLM providers are registered. Side-effect import; identical to\n// what `./ai-sdk.ts` consumers already rely on.\nimport './llm-bootstrap'\n\nexport interface AgentRequestPageContext {\n pageId?: string | null\n entityType?: string | null\n recordId?: string | null\n [key: string]: unknown\n}\n\nexport interface RunAiAgentTextInput {\n agentId: string\n messages: UIMessage[]\n attachmentIds?: string[]\n pageContext?: AgentRequestPageContext\n debug?: boolean\n /**\n * Phase 1 exposes the caller-supplied auth context directly on the helper\n * input. Phase 4 may wrap this behind a thinner public API once a global\n * request-context resolver exists. Helpers running inside the HTTP\n * dispatcher receive the same `AiChatRequestContext` used by `checkAgentPolicy`.\n */\n authContext: AiChatRequestContext\n /**\n * Optional per-call model id override that wins over `agent.defaultModel`.\n * The production model-factory extraction lives in Step 5.1; this Step\n * accepts a literal model id string so the Phase 1 runtime already honors\n * `agent.defaultModel` without inventing a new indirection layer.\n */\n modelOverride?: string\n /**\n * Optional DI container used by `resolvePageContext` callbacks. When omitted\n * and the agent declares a `resolvePageContext`, hydration is skipped with a\n * warning (callbacks that need database/DI cannot run safely without one).\n */\n container?: AwilixContainer\n /**\n * Optional stable chat-turn conversation id forwarded from `<AiChat>`.\n * Bridged into the Step 5.6 `prepareMutation` idempotency hash so repeated\n * turns within the same chat collapse onto the same pending action. When\n * omitted, the idempotency hash falls back to `null` which still preserves\n * per-tenant/org uniqueness within the TTL window.\n */\n conversationId?: string | null\n}\n\ninterface ResolvedAgentModel {\n model: LanguageModel\n modelId: string\n providerId: string\n}\n\nfunction resolveAgentModel(\n agent: AiAgentDefinition,\n modelOverride: string | undefined,\n): ResolvedAgentModel {\n const env = process.env\n // Honor the operator-selected provider (new OM_AI_PROVIDER, with legacy\n // OPENCODE_PROVIDER as the BC fallback) so deployments that have stale\n // keys for other providers still hit the intended target.\n const omProvider = (env.OM_AI_PROVIDER ?? '').trim()\n const opencodeProvider = (env.OPENCODE_PROVIDER ?? '').trim()\n const providerHint = omProvider.length > 0 || opencodeProvider.length > 0\n ? [resolveAiProviderIdFromEnv(env)]\n : undefined\n const provider = llmProviderRegistry.resolveFirstConfigured(\n providerHint ? { order: providerHint } : undefined,\n )\n if (!provider) {\n throw new Error(\n 'No LLM provider is configured. Set OM_AI_PROVIDER (or the legacy OPENCODE_PROVIDER) plus a matching API key such as OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY, then restart the app. See https://docs.openmercato.com/framework/ai-assistant/overview.',\n )\n }\n const apiKey = provider.resolveApiKey()\n if (!apiKey) {\n throw new Error(\n `LLM provider \"${provider.id}\" is advertised as configured but resolveApiKey() returned empty.`,\n )\n }\n const globalEnvModel = ((env.OM_AI_MODEL ?? env.OPENCODE_MODEL) ?? '').trim()\n const modelId =\n (modelOverride && modelOverride.trim().length > 0 ? modelOverride : undefined) ??\n (globalEnvModel.length > 0 ? globalEnvModel : undefined) ??\n agent.defaultModel ??\n provider.defaultModel\n const model = provider.createModel({ modelId, apiKey }) as LanguageModel\n return { model, modelId, providerId: provider.id }\n}\n\n/**\n * Composes the effective system prompt for a run. When the agent declares a\n * `resolvePageContext` callback AND the incoming request carries both\n * `entityType` and `recordId`, the callback is invoked and its return value\n * is appended to `agent.systemPrompt`. Throwing callbacks are caught and\n * logged without failing the request \u2014 the spec allows hydration to be\n * best-effort until Step 5.2 wires a stricter contract.\n */\nexport async function composeSystemPrompt(\n agent: AiAgentDefinition,\n pageContext: AgentRequestPageContext | undefined,\n container: AwilixContainer | undefined,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<string> {\n const baseFromOverride = await resolveBaseSystemPromptWithOverride(\n agent,\n container,\n tenantId,\n organizationId,\n )\n const resolve = agent.resolvePageContext\n if (!resolve) return baseFromOverride\n const entityType = pageContext?.entityType\n const recordId = pageContext?.recordId\n if (typeof entityType !== 'string' || entityType.length === 0) return baseFromOverride\n if (typeof recordId !== 'string' || recordId.length === 0) return baseFromOverride\n if (!container) {\n console.warn(\n `[AI Agents] Agent \"${agent.id}\" declares resolvePageContext but no container was passed to runAiAgentText; skipping hydration.`,\n )\n return baseFromOverride\n }\n const hydrationInput: AiAgentPageContextInput = {\n entityType,\n recordId,\n container,\n tenantId,\n organizationId,\n }\n try {\n const hydrated = await resolve(hydrationInput)\n if (typeof hydrated === 'string' && hydrated.trim().length > 0) {\n return `${baseFromOverride}\\n\\n${hydrated}`\n }\n } catch (error) {\n console.error(\n `[AI Agents] resolvePageContext for agent \"${agent.id}\" failed; continuing without hydration:`,\n error,\n )\n }\n return baseFromOverride\n}\n\n/**\n * Fetches the latest tenant-scoped prompt override for `agent` (if any) and\n * layers it onto the built-in `systemPrompt` via the additive merge helper.\n *\n * BC + fail-open: every failure mode \u2014 missing container, missing `em`\n * registration, repository throw, missing migration \u2014 is logged at `warn`\n * and falls back to `agent.systemPrompt`. A chat turn MUST never fail on\n * override lookup (per Step 5.3 spec: \"If the repo call throws, log and\n * fall back to the built-in prompt \u2014 never fail the chat request\").\n */\nasync function resolveBaseSystemPromptWithOverride(\n agent: AiAgentDefinition,\n container: AwilixContainer | undefined,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<string> {\n const base = agent.systemPrompt\n if (!tenantId || !container) return base\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) return base\n try {\n const repo = new AiAgentPromptOverrideRepository(em)\n const latest = await repo.getLatest(agent.id, {\n tenantId,\n organizationId: organizationId ?? null,\n })\n if (!latest || !latest.sections || Object.keys(latest.sections).length === 0) {\n return base\n }\n return composeSystemPromptWithOverride(base, { sections: latest.sections })\n } catch (error) {\n console.warn(\n `[AI Agents] Prompt-override lookup failed for agent \"${agent.id}\"; falling back to built-in prompt.`,\n error,\n )\n return base\n }\n}\n\n/**\n * Looks up the tenant-scoped `mutationPolicy` override for `agentId` (Step\n * 5.4). Fails SAFE: any repo error, missing container, missing `em`\n * registration, or corrupt enum value returns `null`, which causes the\n * runtime to fall back to the agent's code-declared policy. A chat turn\n * MUST never fail on override lookup.\n */\nasync function resolveMutationPolicyOverride(\n agentId: string,\n container: AwilixContainer | undefined,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<AiAgentMutationPolicy | null> {\n if (!tenantId || !container) return null\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) return null\n try {\n const repo = new AiAgentMutationPolicyOverrideRepository(em)\n const row = await repo.get(agentId, { tenantId, organizationId: organizationId ?? null })\n if (!row) return null\n const raw = row.mutationPolicy\n if (!isKnownMutationPolicy(raw)) {\n console.warn(\n `[AI Agents] Ignoring corrupt mutationPolicy override row for agent \"${agentId}\": \"${raw}\". Falling back to code-declared policy.`,\n )\n return null\n }\n return raw\n } catch (error) {\n console.warn(\n `[AI Agents] mutationPolicy override lookup failed for agent \"${agentId}\"; falling back to code-declared policy.`,\n error,\n )\n return null\n }\n}\n\n/**\n * Normalizes simple `{ role, content }` chat messages into the AI SDK\n * `UIMessage` shape that `convertToModelMessages` requires. When the\n * incoming message already carries a `parts` array it is left untouched;\n * otherwise a single `TextUIPart` is synthesized from `content`.\n */\nfunction ensureUiMessageShape(messages: UIMessage[]): UIMessage[] {\n return messages.map((message, index) => {\n const raw = message as unknown as { id?: string; role?: string; content?: string; parts?: unknown[] }\n if (Array.isArray(raw.parts) && raw.parts.length > 0) {\n // Already has parts \u2014 only ensure `id` is present\n return { ...message, id: raw.id ?? `msg-${index}` } as UIMessage\n }\n const textContent = typeof raw.content === 'string' ? raw.content : ''\n return {\n id: raw.id ?? `msg-${index}`,\n role: raw.role ?? 'user',\n parts: [{ type: 'text', text: textContent }],\n } as unknown as UIMessage\n })\n}\n\n/**\n * Appends AI SDK v6 `FileUIPart` entries to the last user message in the\n * request so resolved attachment bytes / signed URLs reach the model. Pure\n * helper so chat-mode and object-mode share identical behavior \u2014 any\n * divergence here breaks the Step 3.6 parity contract.\n */\nfunction attachAttachmentsToMessages(\n messages: UIMessage[],\n parts: readonly AiResolvedAttachmentPart[],\n): UIMessage[] {\n if (parts.length === 0) return messages\n const fileParts = attachmentPartsToUiFileParts(parts)\n if (fileParts.length === 0) return messages\n const next = messages.slice()\n let lastUserIndex = -1\n for (let index = next.length - 1; index >= 0; index -= 1) {\n const candidate = next[index] as unknown as { role?: string }\n if (candidate?.role === 'user') {\n lastUserIndex = index\n break\n }\n }\n if (lastUserIndex === -1) {\n next.push({\n id: 'ai-runtime-attachments',\n role: 'user',\n parts: fileParts as unknown as UIMessage['parts'],\n } as unknown as UIMessage)\n return next\n }\n const source = next[lastUserIndex] as unknown as { parts?: unknown[] }\n const existingParts = Array.isArray(source.parts) ? source.parts : []\n next[lastUserIndex] = {\n ...(next[lastUserIndex] as object),\n parts: [...existingParts, ...fileParts],\n } as UIMessage\n return next\n}\n\nfunction appendAttachmentSummary(\n systemPrompt: string,\n parts: readonly AiResolvedAttachmentPart[],\n): string {\n const summary = summarizeAttachmentPartsForPrompt(parts)\n if (!summary) return systemPrompt\n return `${systemPrompt}\\n\\n${summary}`\n}\n\n/**\n * Builds a runtime \"MUTATION POLICY (RUNTIME)\" block describing the\n * EFFECTIVE policy for this turn \u2014 what the model should expect when it\n * calls each whitelisted mutation tool. Generated dynamically because:\n *\n * - the agent's static prompt cannot know which per-tenant override is\n * in force (`destructive-confirm-required` flips most writes to\n * run-direct) and would otherwise mislead the operator with stale\n * \"this requires approval\" copy;\n * - the per-tool `isDestructive` flag determines whether each\n * whitelisted write goes through the approval card or runs inline.\n *\n * Without this block, the model parrots its hardcoded \"always route\n * through the approval card\" prompt language and tells the user \"your\n * change is awaiting approval\" when in fact the dispatcher already\n * applied the change directly. The injected block flips the model to\n * report results accurately (\"applied\", \"pending your approval\", or\n * \"blocked because read-only\") tool-by-tool.\n */\nfunction buildRuntimeMutationPolicySection(\n agent: { id: string; mutationPolicy?: string | null; allowedTools: string[] },\n mutationPolicyOverride: string | null,\n): string | null {\n const effective = resolveEffectiveMutationPolicy(\n (agent.mutationPolicy ?? null) as never,\n (mutationPolicyOverride ?? null) as never,\n agent.id,\n )\n const lines: string[] = []\n lines.push('MUTATION POLICY (RUNTIME)')\n lines.push(`Declared agent policy: ${agent.mutationPolicy ?? 'read-only'}.`)\n if (mutationPolicyOverride && mutationPolicyOverride !== agent.mutationPolicy) {\n lines.push(`Tenant override active: ${mutationPolicyOverride}.`)\n }\n lines.push(`Effective policy: ${effective}.`)\n\n // Bucket the agent's allowlisted tools into \"gated\" / \"direct\" / \"conditional\"\n // / \"blocked\" so the model can phrase outcomes correctly per tool.\n // `conditional` covers tools whose `isDestructive` is a predicate function:\n // their gate-vs-direct decision depends on the per-call input (e.g.\n // `customers.manage_deal_comment` gates only its delete branch under\n // `destructive-confirm-required`).\n const direct: string[] = []\n const gated: string[] = []\n const conditional: string[] = []\n const blocked: string[] = []\n for (const toolName of agent.allowedTools) {\n const tool = toolRegistry.getTool(toolName) as\n | { isMutation?: boolean; isDestructive?: boolean | ((input: unknown) => boolean) }\n | undefined\n if (!tool || tool.isMutation !== true) continue\n if (effective === 'read-only') {\n blocked.push(toolName)\n continue\n }\n if (effective === 'confirm-required') {\n gated.push(toolName)\n continue\n }\n // destructive-confirm-required\n if (typeof tool.isDestructive === 'function') {\n conditional.push(toolName)\n } else if (tool.isDestructive === true) {\n gated.push(toolName)\n } else {\n direct.push(toolName)\n }\n }\n\n if (\n direct.length === 0 &&\n gated.length === 0 &&\n conditional.length === 0 &&\n blocked.length === 0\n ) {\n // Read-only agent with no mutation tools \u2014 no runtime policy block needed.\n return null\n }\n if (direct.length > 0) {\n lines.push('')\n lines.push(\n `Tools that WILL RUN DIRECTLY (no approval card, no pending action) under the effective policy: ${direct.join(', ')}.`,\n )\n lines.push(\n 'When you call any of these and the call returns successfully, the change has ALREADY BEEN APPLIED. Report it in the past tense (\"Updated \u2026\", \"Added \u2026\", \"Created \u2026\"). Do NOT tell the operator the action is \"pending your approval\" or \"awaiting confirmation\" \u2014 that would be a false statement under the current policy.',\n )\n }\n if (gated.length > 0) {\n lines.push('')\n lines.push(\n `Tools that REQUIRE APPROVAL under the effective policy: ${gated.join(', ')}.`,\n )\n lines.push(\n 'When you call any of these, the dispatcher returns an \"awaiting confirmation\" envelope and renders an inline approval card. Tell the operator the change is pending their confirmation; do NOT claim it has been applied.',\n )\n }\n if (conditional.length > 0) {\n lines.push('')\n lines.push(\n `Tools whose approval requirement DEPENDS ON THE INPUT under the effective policy: ${conditional.join(', ')}.`,\n )\n lines.push(\n 'These multi-operation tools gate ONLY the destructive branches (typically `operation: \"delete\"` or similar). Read the tool result envelope: if it carries `status: \"pending-confirmation\"` then the change is pending \u2014 tell the operator it needs their approval. If it carries direct success data, the change has ALREADY BEEN APPLIED \u2014 report it in the past tense. Never assume one branch behaves like another.',\n )\n }\n if (blocked.length > 0) {\n lines.push('')\n lines.push(\n `Tools that are BLOCKED under the effective policy (read-only): ${blocked.join(', ')}.`,\n )\n lines.push(\n 'Calls to these tools are refused before the handler runs. Do not attempt them; instead direct the operator to the matching backoffice page or to switch the tenant policy if they have permission.',\n )\n }\n lines.push('')\n lines.push(\n 'This RUNTIME policy block always wins over any conflicting \"approval card\" language earlier in the prompt \u2014 the static prompt is written for the most restrictive case but real behavior depends on the per-call policy described here.',\n )\n return lines.join('\\n')\n}\n\nfunction appendRuntimeMutationPolicy(\n systemPrompt: string,\n agent: { id: string; mutationPolicy?: string | null; allowedTools: string[] },\n mutationPolicyOverride: string | null,\n): string {\n const block = buildRuntimeMutationPolicySection(agent, mutationPolicyOverride)\n if (!block) return systemPrompt\n return `${systemPrompt}\\n\\n${block}`\n}\n\n/**\n * Server-side helper that runs an Open Mercato agent in chat mode via the\n * Vercel AI SDK and returns a streaming `Response` ready to be emitted from a\n * route handler. Shares the same policy gate and tool resolution path as the\n * HTTP dispatcher \u2014 a caller using this helper can never bypass the agent's\n * `requiredFeatures`, `allowedTools`, `executionMode`, or `mutationPolicy`.\n *\n * Attachment-to-model conversion (Step 3.7): resolved\n * {@link AiResolvedAttachmentPart}s are materialized inline as AI SDK v6\n * `FileUIPart` entries on the last user message (images/PDFs) and as a\n * structured `[ATTACHMENTS]` block appended to the system prompt (text\n * extracts + metadata-only summaries). The existing `attachmentIds`\n * pass-through into `resolveAiAgentTools` is preserved \u2014 Step 3.6 parity\n * invariant #7 still holds.\n */\nexport async function runAiAgentText(input: RunAiAgentTextInput): Promise<Response> {\n const mutationPolicyOverride = await resolveMutationPolicyOverride(\n input.agentId,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n )\n const { agent, tools } = await resolveAiAgentTools({\n agentId: input.agentId,\n authContext: input.authContext,\n pageContext: input.pageContext,\n attachmentIds: input.attachmentIds,\n mutationPolicyOverride,\n container: input.container,\n conversationId: input.conversationId ?? null,\n })\n\n const resolvedAttachments = await resolveAttachmentPartsForAgent({\n agent,\n attachmentIds: input.attachmentIds,\n authContext: input.authContext,\n container: input.container,\n })\n\n const baseSystemPrompt = await composeSystemPrompt(\n agent,\n input.pageContext,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n )\n const systemPrompt = appendRuntimeMutationPolicy(\n appendAttachmentSummary(baseSystemPrompt, resolvedAttachments),\n agent,\n mutationPolicyOverride,\n )\n\n const { model } = resolveAgentModel(agent, input.modelOverride)\n const normalizedMessages = ensureUiMessageShape(input.messages)\n const hydratedMessages = attachAttachmentsToMessages(normalizedMessages, resolvedAttachments)\n const modelMessages = await convertToModelMessages(hydratedMessages)\n // Default to 10 agentic steps when the agent does not declare maxSteps.\n // Without stopWhen the AI SDK runs a single model call and never executes\n // tool calls, which makes every tool-using query return an empty stream.\n const effectiveMaxSteps = typeof agent.maxSteps === 'number' && agent.maxSteps > 0\n ? agent.maxSteps\n : 10\n const stopWhen = stepCountIs(effectiveMaxSteps)\n\n const streamArgs: Parameters<typeof streamText>[0] = {\n model,\n system: systemPrompt,\n messages: modelMessages,\n tools,\n stopWhen,\n }\n\n const result = streamText(streamArgs)\n return result.toUIMessageStreamResponse({\n sendReasoning: true,\n headers: {\n 'Cache-Control': 'no-cache, no-transform',\n Connection: 'keep-alive',\n },\n })\n}\n\n/**\n * Runtime override for the structured-output schema used by {@link runAiAgentObject}.\n * When the agent itself declares no `output` block, the caller MUST supply this;\n * otherwise the helper rejects with {@link AgentPolicyError} code\n * `execution_mode_not_supported`.\n */\nexport interface RunAiAgentObjectOutputOverride<TSchema = ZodTypeAny> {\n schemaName: string\n schema: TSchema\n /**\n * `'generate'` (default) calls AI SDK `generateObject` and resolves to the\n * parsed object. `'stream'` calls `streamObject` and returns the SDK's\n * streaming handle so callers can consume partial objects / text deltas.\n */\n mode?: 'generate' | 'stream'\n}\n\nexport interface RunAiAgentObjectInput<TSchema = ZodTypeAny> {\n agentId: string\n /**\n * Accepts either a bare user prompt (wrapped as `[{ role: 'user', content }]`)\n * or a prebuilt `UIMessage[]` array \u2014 matches the source spec's\n * `RunAiAgentObjectInput` contract (\u00A71149\u20131160).\n */\n input: string | UIMessage[]\n attachmentIds?: string[]\n pageContext?: AgentRequestPageContext\n /**\n * Same Phase-1 shim as {@link RunAiAgentTextInput.authContext}. Required until\n * a global request-context resolver lands (Phase 4).\n */\n authContext: AiChatRequestContext\n modelOverride?: string\n output?: RunAiAgentObjectOutputOverride<TSchema>\n debug?: boolean\n container?: AwilixContainer\n}\n\nexport type RunAiAgentObjectGenerateResult<TSchema> = {\n mode: 'generate'\n object: TSchema\n finishReason?: string\n usage?: { inputTokens?: number; outputTokens?: number }\n}\n\nexport type RunAiAgentObjectStreamResult<TSchema> = {\n mode: 'stream'\n /** Full parsed object once the stream completes. */\n object: Promise<TSchema>\n /** Async iterator of partial (progressively hydrated) objects. */\n partialObjectStream: AsyncIterable<Partial<TSchema>>\n /** Async iterator of the raw text deltas the model emitted. */\n textStream: AsyncIterable<string>\n finishReason?: Promise<string | undefined>\n usage?: Promise<{ inputTokens?: number; outputTokens?: number } | undefined>\n}\n\nexport type RunAiAgentObjectResult<TSchema> =\n | RunAiAgentObjectGenerateResult<TSchema>\n | RunAiAgentObjectStreamResult<TSchema>\n\nfunction normalizeObjectMessages(input: string | UIMessage[]): UIMessage[] {\n if (typeof input === 'string') {\n return [\n {\n id: 'user-input',\n role: 'user',\n parts: [{ type: 'text', text: input }],\n } as unknown as UIMessage,\n ]\n }\n return input\n}\n\nfunction resolveStructuredOutput<TSchema>(\n agent: AiAgentDefinition,\n override: RunAiAgentObjectOutputOverride<TSchema> | undefined,\n): { schemaName: string; schema: unknown; mode: 'generate' | 'stream' } {\n if (override) {\n return {\n schemaName: override.schemaName,\n schema: override.schema as unknown,\n mode: override.mode ?? 'generate',\n }\n }\n const declared = agent.output as AiAgentStructuredOutput | undefined\n if (!declared) {\n throw new AgentPolicyError(\n 'execution_mode_not_supported',\n `Agent \"${agent.id}\" does not declare a structured-output schema; pass runAiAgentObject({ output }) or declare agent.output.`,\n )\n }\n return {\n schemaName: declared.schemaName,\n schema: declared.schema as unknown,\n mode: declared.mode ?? 'generate',\n }\n}\n\n/**\n * Server-side helper that runs an Open Mercato agent in structured-output mode\n * via the Vercel AI SDK. Shares the same policy gate, tool resolution path,\n * system-prompt composition, and model resolution as {@link runAiAgentText} \u2014\n * object-mode and chat-mode CANNOT diverge.\n *\n * Attachment-to-model conversion (Step 3.7): resolved\n * {@link AiResolvedAttachmentPart}s are materialized inline as AI SDK v6\n * `FileUIPart` entries on the last user message (images/PDFs) and as a\n * structured `[ATTACHMENTS]` block appended to the system prompt (text\n * extracts + metadata-only summaries). Matches {@link runAiAgentText} byte-\n * for-byte so the Step 3.6 parity contract is preserved.\n */\nexport async function runAiAgentObject<TSchema = unknown>(\n input: RunAiAgentObjectInput<TSchema>,\n): Promise<RunAiAgentObjectResult<TSchema>> {\n const mutationPolicyOverride = await resolveMutationPolicyOverride(\n input.agentId,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n )\n const { agent, tools } = await resolveAiAgentTools({\n agentId: input.agentId,\n authContext: input.authContext,\n pageContext: input.pageContext,\n attachmentIds: input.attachmentIds,\n requestedExecutionMode: 'object',\n mutationPolicyOverride,\n container: input.container,\n })\n\n const resolvedOutput = resolveStructuredOutput(agent, input.output)\n\n const resolvedAttachments = await resolveAttachmentPartsForAgent({\n agent,\n attachmentIds: input.attachmentIds,\n authContext: input.authContext,\n container: input.container,\n })\n\n const baseSystemPrompt = await composeSystemPrompt(\n agent,\n input.pageContext,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n )\n const systemPrompt = appendRuntimeMutationPolicy(\n appendAttachmentSummary(baseSystemPrompt, resolvedAttachments),\n agent,\n mutationPolicyOverride,\n )\n\n const { model } = resolveAgentModel(agent, input.modelOverride)\n const normalizedMessages = ensureUiMessageShape(normalizeObjectMessages(input.input))\n const hydratedMessages = attachAttachmentsToMessages(\n normalizedMessages,\n resolvedAttachments,\n )\n const modelMessages = await convertToModelMessages(hydratedMessages)\n const stopWhen = typeof agent.maxSteps === 'number' && agent.maxSteps > 0\n ? stepCountIs(agent.maxSteps)\n : undefined\n\n if (resolvedOutput.mode === 'stream') {\n const streamArgs: Parameters<typeof streamObject>[0] = {\n model,\n system: systemPrompt,\n messages: modelMessages,\n schema: resolvedOutput.schema as never,\n schemaName: resolvedOutput.schemaName,\n }\n const result = streamObject(streamArgs) as unknown as {\n object: Promise<TSchema>\n partialObjectStream: AsyncIterable<Partial<TSchema>>\n textStream: AsyncIterable<string>\n finishReason?: Promise<string | undefined>\n usage?: Promise<{ inputTokens?: number; outputTokens?: number } | undefined>\n }\n return {\n mode: 'stream',\n object: result.object,\n partialObjectStream: result.partialObjectStream,\n textStream: result.textStream,\n finishReason: result.finishReason,\n usage: result.usage,\n }\n }\n\n const generateArgs: Parameters<typeof generateObject>[0] = {\n model,\n system: systemPrompt,\n messages: modelMessages,\n schema: resolvedOutput.schema as never,\n schemaName: resolvedOutput.schemaName,\n }\n if (stopWhen) {\n // generateObject shares `CallSettings` with generateText; stopWhen is ignored\n // by the typed surface but harmless for providers that respect it. Tools\n // flow through the system prompt only in object mode today \u2014 the whitelist\n // has already been resolved via `resolveAiAgentTools` above, even if we\n // don't hand it to generateObject.\n ;(generateArgs as Record<string, unknown>).stopWhen = stopWhen\n }\n void tools\n\n const result = await generateObject(generateArgs)\n return {\n mode: 'generate',\n object: (result as { object: unknown }).object as TSchema,\n finishReason: (result as { finishReason?: string }).finishReason,\n usage: (result as { usage?: { inputTokens?: number; outputTokens?: number } }).usage,\n }\n}\n\nexport { AgentPolicyError }\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { createContainer } from 'awilix'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { LanguageModel, UIMessage } from 'ai'\nimport {\n convertToModelMessages,\n generateObject,\n stepCountIs,\n streamObject,\n streamText,\n} from 'ai'\nimport type { ZodTypeAny } from 'zod'\nimport { createModelFactory } from './model-factory'\nimport type {\n AiAgentDefinition,\n AiAgentPageContextInput,\n AiAgentStructuredOutput,\n} from './ai-agent-definition'\nimport type {\n AiChatRequestContext,\n AiResolvedAttachmentPart,\n} from './attachment-bridge-types'\nimport { resolveAiAgentTools, AgentPolicyError } from './agent-tools'\nimport { resolveEffectiveMutationPolicy } from './agent-policy'\nimport { toolRegistry } from './tool-registry'\nimport {\n attachmentPartsToUiFileParts,\n resolveAttachmentPartsForAgent,\n summarizeAttachmentPartsForPrompt,\n} from './attachment-parts'\nimport { AiAgentPromptOverrideRepository } from '../data/repositories/AiAgentPromptOverrideRepository'\nimport { AiAgentMutationPolicyOverrideRepository } from '../data/repositories/AiAgentMutationPolicyOverrideRepository'\nimport { AiAgentRuntimeOverrideRepository } from '../data/repositories/AiAgentRuntimeOverrideRepository'\nimport { AiTenantModelAllowlistRepository } from '../data/repositories/AiTenantModelAllowlistRepository'\nimport type { TenantAllowlistSnapshot } from './model-allowlist'\nimport { composeSystemPromptWithOverride } from './prompt-override-merge'\nimport { isKnownMutationPolicy } from './agent-policy'\nimport type { AiAgentMutationPolicy } from './ai-agent-definition'\n\n// Ensure built-in LLM providers are registered. Side-effect import; identical to\n// what `./ai-sdk.ts` consumers already rely on.\nimport './llm-bootstrap'\n\nexport interface AgentRequestPageContext {\n pageId?: string | null\n entityType?: string | null\n recordId?: string | null\n [key: string]: unknown\n}\n\nexport interface RunAiAgentTextInput {\n agentId: string\n messages: UIMessage[]\n attachmentIds?: string[]\n pageContext?: AgentRequestPageContext\n debug?: boolean\n /**\n * Phase 1 exposes the caller-supplied auth context directly on the helper\n * input. Phase 4 may wrap this behind a thinner public API once a global\n * request-context resolver exists. Helpers running inside the HTTP\n * dispatcher receive the same `AiChatRequestContext` used by `checkAgentPolicy`.\n */\n authContext: AiChatRequestContext\n /**\n * Optional per-call model id override that wins over `agent.defaultModel`.\n * The production model-factory extraction lives in Step 5.1; this Step\n * accepts a literal model id string so the Phase 1 runtime already honors\n * `agent.defaultModel` without inventing a new indirection layer.\n */\n modelOverride?: string\n /**\n * Optional request-time provider override. When non-empty, wins for the\n * provider axis at the same priority as `modelOverride` for the model axis.\n * A value that does not match any registered provider id is silently ignored.\n *\n * Phase 1 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n providerOverride?: string\n /**\n * Optional per-call base URL override. Wins over every other source in the\n * baseURL resolution chain. Intended for programmatic callers only \u2014 the\n * HTTP query-param baseUrl and the AI_RUNTIME_BASEURL_ALLOWLIST arrive in\n * Phase 4a and MUST NOT be exposed here.\n *\n * Phase 2 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n baseUrlOverride?: string\n /**\n * Per-request HTTP dispatcher override (query params `?provider=`, `?model=`,\n * `?baseUrl=`). Validated by the dispatcher route before being forwarded\n * here. Wins over tenantOverride and all lower-priority sources when\n * `agent.allowRuntimeModelOverride !== false`.\n *\n * Phase 4a of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n requestOverride?: {\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n }\n /**\n * Optional DI container used by `resolvePageContext` callbacks. When omitted\n * and the agent declares a `resolvePageContext`, hydration is skipped with a\n * warning (callbacks that need database/DI cannot run safely without one).\n */\n container?: AwilixContainer\n /**\n * Optional stable chat-turn conversation id forwarded from `<AiChat>`.\n * Bridged into the Step 5.6 `prepareMutation` idempotency hash so repeated\n * turns within the same chat collapse onto the same pending action. When\n * omitted, the idempotency hash falls back to `null` which still preserves\n * per-tenant/org uniqueness within the TTL window.\n */\n conversationId?: string | null\n}\n\ninterface ResolvedAgentModel {\n model: LanguageModel\n modelId: string\n providerId: string\n}\n\nfunction resolveAgentModel(\n agent: AiAgentDefinition,\n modelOverride: string | undefined,\n providerOverride: string | undefined,\n container: AwilixContainer | undefined,\n baseUrlOverride?: string,\n tenantOverride?: { providerId?: string | null; modelId?: string | null; baseURL?: string | null } | null,\n requestOverride?: { providerId?: string | null; modelId?: string | null; baseURL?: string | null } | null,\n tenantAllowlist?: TenantAllowlistSnapshot | null,\n): ResolvedAgentModel {\n const effectiveContainer = container ?? createContainer()\n const allowRuntimeModelOverride = agent.allowRuntimeModelOverride !== false\n const resolution = createModelFactory(effectiveContainer).resolveModel({\n moduleId: agent.moduleId,\n agentDefaultModel: agent.defaultModel,\n agentDefaultProvider: agent.defaultProvider,\n agentDefaultBaseUrl: agent.defaultBaseUrl,\n callerOverride: modelOverride,\n providerOverride,\n baseUrlOverride,\n allowRuntimeModelOverride,\n tenantOverride: tenantOverride ?? undefined,\n requestOverride: requestOverride ?? undefined,\n tenantAllowlist: tenantAllowlist ?? null,\n })\n return {\n model: resolution.model as LanguageModel,\n modelId: resolution.modelId,\n providerId: resolution.providerId,\n }\n}\n\nasync function resolveTenantAllowlistSnapshot(\n container: AwilixContainer | undefined,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<TenantAllowlistSnapshot | null> {\n if (!tenantId || !container) return null\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) return null\n try {\n const repo = new AiTenantModelAllowlistRepository(em)\n return await repo.getSnapshot({\n tenantId,\n organizationId: organizationId ?? null,\n })\n } catch (error) {\n console.warn(\n '[AI Agents] Tenant allowlist lookup failed; falling back to env-only enforcement.',\n error,\n )\n return null\n }\n}\n\n/**\n * Composes the effective system prompt for a run. When the agent declares a\n * `resolvePageContext` callback AND the incoming request carries both\n * `entityType` and `recordId`, the callback is invoked and its return value\n * is appended to `agent.systemPrompt`. Throwing callbacks are caught and\n * logged without failing the request \u2014 the spec allows hydration to be\n * best-effort until Step 5.2 wires a stricter contract.\n */\nexport async function composeSystemPrompt(\n agent: AiAgentDefinition,\n pageContext: AgentRequestPageContext | undefined,\n container: AwilixContainer | undefined,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<string> {\n const baseFromOverride = await resolveBaseSystemPromptWithOverride(\n agent,\n container,\n tenantId,\n organizationId,\n )\n const resolve = agent.resolvePageContext\n if (!resolve) return baseFromOverride\n const entityType = pageContext?.entityType\n const recordId = pageContext?.recordId\n if (typeof entityType !== 'string' || entityType.length === 0) return baseFromOverride\n if (typeof recordId !== 'string' || recordId.length === 0) return baseFromOverride\n if (!container) {\n console.warn(\n `[AI Agents] Agent \"${agent.id}\" declares resolvePageContext but no container was passed to runAiAgentText; skipping hydration.`,\n )\n return baseFromOverride\n }\n const hydrationInput: AiAgentPageContextInput = {\n entityType,\n recordId,\n container,\n tenantId,\n organizationId,\n }\n try {\n const hydrated = await resolve(hydrationInput)\n if (typeof hydrated === 'string' && hydrated.trim().length > 0) {\n return `${baseFromOverride}\\n\\n${hydrated}`\n }\n } catch (error) {\n console.error(\n `[AI Agents] resolvePageContext for agent \"${agent.id}\" failed; continuing without hydration:`,\n error,\n )\n }\n return baseFromOverride\n}\n\n/**\n * Fetches the latest tenant-scoped prompt override for `agent` (if any) and\n * layers it onto the built-in `systemPrompt` via the additive merge helper.\n *\n * BC + fail-open: every failure mode \u2014 missing container, missing `em`\n * registration, repository throw, missing migration \u2014 is logged at `warn`\n * and falls back to `agent.systemPrompt`. A chat turn MUST never fail on\n * override lookup (per Step 5.3 spec: \"If the repo call throws, log and\n * fall back to the built-in prompt \u2014 never fail the chat request\").\n */\nasync function resolveBaseSystemPromptWithOverride(\n agent: AiAgentDefinition,\n container: AwilixContainer | undefined,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<string> {\n const base = agent.systemPrompt\n if (!tenantId || !container) return base\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) return base\n try {\n const repo = new AiAgentPromptOverrideRepository(em)\n const latest = await repo.getLatest(agent.id, {\n tenantId,\n organizationId: organizationId ?? null,\n })\n if (!latest || !latest.sections || Object.keys(latest.sections).length === 0) {\n return base\n }\n return composeSystemPromptWithOverride(base, { sections: latest.sections })\n } catch (error) {\n console.warn(\n `[AI Agents] Prompt-override lookup failed for agent \"${agent.id}\"; falling back to built-in prompt.`,\n error,\n )\n return base\n }\n}\n\n/**\n * Looks up the tenant-scoped `mutationPolicy` override for `agentId` (Step\n * 5.4). Fails SAFE: any repo error, missing container, missing `em`\n * registration, or corrupt enum value returns `null`, which causes the\n * runtime to fall back to the agent's code-declared policy. A chat turn\n * MUST never fail on override lookup.\n */\nasync function resolveMutationPolicyOverride(\n agentId: string,\n container: AwilixContainer | undefined,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<AiAgentMutationPolicy | null> {\n if (!tenantId || !container) return null\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) return null\n try {\n const repo = new AiAgentMutationPolicyOverrideRepository(em)\n const row = await repo.get(agentId, { tenantId, organizationId: organizationId ?? null })\n if (!row) return null\n const raw = row.mutationPolicy\n if (!isKnownMutationPolicy(raw)) {\n console.warn(\n `[AI Agents] Ignoring corrupt mutationPolicy override row for agent \"${agentId}\": \"${raw}\". Falling back to code-declared policy.`,\n )\n return null\n }\n return raw\n } catch (error) {\n console.warn(\n `[AI Agents] mutationPolicy override lookup failed for agent \"${agentId}\"; falling back to code-declared policy.`,\n error,\n )\n return null\n }\n}\n\n/**\n * Looks up the per-tenant AI runtime override (provider / model / baseURL) for\n * the given agent (Phase 4a of spec\n * `2026-04-27-ai-agents-provider-model-baseurl-overrides`).\n *\n * Mirrors the fail-open contract of {@link resolveMutationPolicyOverride}: any\n * error \u2014 missing container, missing `em`, repository throw, missing migration\n * \u2014 is logged at `warn` level and returns null so the model factory falls\n * through to lower-priority sources. A chat turn MUST never fail on override\n * lookup.\n */\nasync function resolveRuntimeModelOverride(\n agentId: string,\n container: AwilixContainer | undefined,\n tenantId: string | null,\n organizationId: string | null,\n): Promise<{ providerId?: string | null; modelId?: string | null; baseURL?: string | null } | null> {\n if (!tenantId || !container) return null\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) return null\n try {\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const row = await repo.getDefault({\n tenantId,\n organizationId: organizationId ?? null,\n agentId,\n })\n if (!row) return null\n return {\n providerId: row.providerId ?? null,\n modelId: row.modelId ?? null,\n baseURL: row.baseUrl ?? null,\n }\n } catch (error) {\n console.warn(\n `[AI Agents] Runtime model override lookup failed for agent \"${agentId}\"; falling back to lower-priority sources.`,\n error,\n )\n return null\n }\n}\n\n/**\n * Normalizes simple `{ role, content }` chat messages into the AI SDK\n * `UIMessage` shape that `convertToModelMessages` requires. When the\n * incoming message already carries a `parts` array it is left untouched;\n * otherwise a single `TextUIPart` is synthesized from `content`.\n */\nfunction ensureUiMessageShape(messages: UIMessage[]): UIMessage[] {\n return messages.map((message, index) => {\n const raw = message as unknown as { id?: string; role?: string; content?: string; parts?: unknown[] }\n if (Array.isArray(raw.parts) && raw.parts.length > 0) {\n // Already has parts \u2014 only ensure `id` is present\n return { ...message, id: raw.id ?? `msg-${index}` } as UIMessage\n }\n const textContent = typeof raw.content === 'string' ? raw.content : ''\n return {\n id: raw.id ?? `msg-${index}`,\n role: raw.role ?? 'user',\n parts: [{ type: 'text', text: textContent }],\n } as unknown as UIMessage\n })\n}\n\n/**\n * Appends AI SDK v6 `FileUIPart` entries to the last user message in the\n * request so resolved attachment bytes / signed URLs reach the model. Pure\n * helper so chat-mode and object-mode share identical behavior \u2014 any\n * divergence here breaks the Step 3.6 parity contract.\n */\nfunction attachAttachmentsToMessages(\n messages: UIMessage[],\n parts: readonly AiResolvedAttachmentPart[],\n): UIMessage[] {\n if (parts.length === 0) return messages\n const fileParts = attachmentPartsToUiFileParts(parts)\n if (fileParts.length === 0) return messages\n const next = messages.slice()\n let lastUserIndex = -1\n for (let index = next.length - 1; index >= 0; index -= 1) {\n const candidate = next[index] as unknown as { role?: string }\n if (candidate?.role === 'user') {\n lastUserIndex = index\n break\n }\n }\n if (lastUserIndex === -1) {\n next.push({\n id: 'ai-runtime-attachments',\n role: 'user',\n parts: fileParts as unknown as UIMessage['parts'],\n } as unknown as UIMessage)\n return next\n }\n const source = next[lastUserIndex] as unknown as { parts?: unknown[] }\n const existingParts = Array.isArray(source.parts) ? source.parts : []\n next[lastUserIndex] = {\n ...(next[lastUserIndex] as object),\n parts: [...existingParts, ...fileParts],\n } as UIMessage\n return next\n}\n\nfunction appendAttachmentSummary(\n systemPrompt: string,\n parts: readonly AiResolvedAttachmentPart[],\n): string {\n const summary = summarizeAttachmentPartsForPrompt(parts)\n if (!summary) return systemPrompt\n return `${systemPrompt}\\n\\n${summary}`\n}\n\n/**\n * Builds a runtime \"MUTATION POLICY (RUNTIME)\" block describing the\n * EFFECTIVE policy for this turn \u2014 what the model should expect when it\n * calls each whitelisted mutation tool. Generated dynamically because:\n *\n * - the agent's static prompt cannot know which per-tenant override is\n * in force (`destructive-confirm-required` flips most writes to\n * run-direct) and would otherwise mislead the operator with stale\n * \"this requires approval\" copy;\n * - the per-tool `isDestructive` flag determines whether each\n * whitelisted write goes through the approval card or runs inline.\n *\n * Without this block, the model parrots its hardcoded \"always route\n * through the approval card\" prompt language and tells the user \"your\n * change is awaiting approval\" when in fact the dispatcher already\n * applied the change directly. The injected block flips the model to\n * report results accurately (\"applied\", \"pending your approval\", or\n * \"blocked because read-only\") tool-by-tool.\n */\nfunction buildRuntimeMutationPolicySection(\n agent: { id: string; mutationPolicy?: string | null; allowedTools: string[] },\n mutationPolicyOverride: string | null,\n): string | null {\n const effective = resolveEffectiveMutationPolicy(\n (agent.mutationPolicy ?? null) as never,\n (mutationPolicyOverride ?? null) as never,\n agent.id,\n )\n const lines: string[] = []\n lines.push('MUTATION POLICY (RUNTIME)')\n lines.push(`Declared agent policy: ${agent.mutationPolicy ?? 'read-only'}.`)\n if (mutationPolicyOverride && mutationPolicyOverride !== agent.mutationPolicy) {\n lines.push(`Tenant override active: ${mutationPolicyOverride}.`)\n }\n lines.push(`Effective policy: ${effective}.`)\n\n // Bucket the agent's allowlisted tools into \"gated\" / \"direct\" / \"conditional\"\n // / \"blocked\" so the model can phrase outcomes correctly per tool.\n // `conditional` covers tools whose `isDestructive` is a predicate function:\n // their gate-vs-direct decision depends on the per-call input (e.g.\n // `customers.manage_deal_comment` gates only its delete branch under\n // `destructive-confirm-required`).\n const direct: string[] = []\n const gated: string[] = []\n const conditional: string[] = []\n const blocked: string[] = []\n for (const toolName of agent.allowedTools) {\n const tool = toolRegistry.getTool(toolName) as\n | { isMutation?: boolean; isDestructive?: boolean | ((input: unknown) => boolean) }\n | undefined\n if (!tool || tool.isMutation !== true) continue\n if (effective === 'read-only') {\n blocked.push(toolName)\n continue\n }\n if (effective === 'confirm-required') {\n gated.push(toolName)\n continue\n }\n // destructive-confirm-required\n if (typeof tool.isDestructive === 'function') {\n conditional.push(toolName)\n } else if (tool.isDestructive === true) {\n gated.push(toolName)\n } else {\n direct.push(toolName)\n }\n }\n\n if (\n direct.length === 0 &&\n gated.length === 0 &&\n conditional.length === 0 &&\n blocked.length === 0\n ) {\n // Read-only agent with no mutation tools \u2014 no runtime policy block needed.\n return null\n }\n if (direct.length > 0) {\n lines.push('')\n lines.push(\n `Tools that WILL RUN DIRECTLY (no approval card, no pending action) under the effective policy: ${direct.join(', ')}.`,\n )\n lines.push(\n 'When you call any of these and the call returns successfully, the change has ALREADY BEEN APPLIED. Report it in the past tense (\"Updated \u2026\", \"Added \u2026\", \"Created \u2026\"). Do NOT tell the operator the action is \"pending your approval\" or \"awaiting confirmation\" \u2014 that would be a false statement under the current policy.',\n )\n }\n if (gated.length > 0) {\n lines.push('')\n lines.push(\n `Tools that REQUIRE APPROVAL under the effective policy: ${gated.join(', ')}.`,\n )\n lines.push(\n 'When you call any of these, the dispatcher returns an \"awaiting confirmation\" envelope and renders an inline approval card. Tell the operator the change is pending their confirmation; do NOT claim it has been applied.',\n )\n }\n if (conditional.length > 0) {\n lines.push('')\n lines.push(\n `Tools whose approval requirement DEPENDS ON THE INPUT under the effective policy: ${conditional.join(', ')}.`,\n )\n lines.push(\n 'These multi-operation tools gate ONLY the destructive branches (typically `operation: \"delete\"` or similar). Read the tool result envelope: if it carries `status: \"pending-confirmation\"` then the change is pending \u2014 tell the operator it needs their approval. If it carries direct success data, the change has ALREADY BEEN APPLIED \u2014 report it in the past tense. Never assume one branch behaves like another.',\n )\n }\n if (blocked.length > 0) {\n lines.push('')\n lines.push(\n `Tools that are BLOCKED under the effective policy (read-only): ${blocked.join(', ')}.`,\n )\n lines.push(\n 'Calls to these tools are refused before the handler runs. Do not attempt them; instead direct the operator to the matching backoffice page or to switch the tenant policy if they have permission.',\n )\n }\n lines.push('')\n lines.push(\n 'This RUNTIME policy block always wins over any conflicting \"approval card\" language earlier in the prompt \u2014 the static prompt is written for the most restrictive case but real behavior depends on the per-call policy described here.',\n )\n return lines.join('\\n')\n}\n\nfunction appendRuntimeMutationPolicy(\n systemPrompt: string,\n agent: { id: string; mutationPolicy?: string | null; allowedTools: string[] },\n mutationPolicyOverride: string | null,\n): string {\n const block = buildRuntimeMutationPolicySection(agent, mutationPolicyOverride)\n if (!block) return systemPrompt\n return `${systemPrompt}\\n\\n${block}`\n}\n\n/**\n * Server-side helper that runs an Open Mercato agent in chat mode via the\n * Vercel AI SDK and returns a streaming `Response` ready to be emitted from a\n * route handler. Shares the same policy gate and tool resolution path as the\n * HTTP dispatcher \u2014 a caller using this helper can never bypass the agent's\n * `requiredFeatures`, `allowedTools`, `executionMode`, or `mutationPolicy`.\n *\n * Attachment-to-model conversion (Step 3.7): resolved\n * {@link AiResolvedAttachmentPart}s are materialized inline as AI SDK v6\n * `FileUIPart` entries on the last user message (images/PDFs) and as a\n * structured `[ATTACHMENTS]` block appended to the system prompt (text\n * extracts + metadata-only summaries). The existing `attachmentIds`\n * pass-through into `resolveAiAgentTools` is preserved \u2014 Step 3.6 parity\n * invariant #7 still holds.\n */\nexport async function runAiAgentText(input: RunAiAgentTextInput): Promise<Response> {\n const [mutationPolicyOverride, tenantRuntimeOverride, tenantAllowlistSnapshot] = await Promise.all([\n resolveMutationPolicyOverride(\n input.agentId,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n ),\n resolveRuntimeModelOverride(\n input.agentId,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n ),\n resolveTenantAllowlistSnapshot(\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n ),\n ])\n const { agent, tools } = await resolveAiAgentTools({\n agentId: input.agentId,\n authContext: input.authContext,\n pageContext: input.pageContext,\n attachmentIds: input.attachmentIds,\n mutationPolicyOverride,\n container: input.container,\n conversationId: input.conversationId ?? null,\n })\n\n const resolvedAttachments = await resolveAttachmentPartsForAgent({\n agent,\n attachmentIds: input.attachmentIds,\n authContext: input.authContext,\n container: input.container,\n })\n\n const baseSystemPrompt = await composeSystemPrompt(\n agent,\n input.pageContext,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n )\n const systemPrompt = appendRuntimeMutationPolicy(\n appendAttachmentSummary(baseSystemPrompt, resolvedAttachments),\n agent,\n mutationPolicyOverride,\n )\n\n const { model } = resolveAgentModel(\n agent,\n input.modelOverride,\n input.providerOverride,\n input.container,\n input.baseUrlOverride,\n tenantRuntimeOverride,\n input.requestOverride,\n tenantAllowlistSnapshot,\n )\n const normalizedMessages = ensureUiMessageShape(input.messages)\n const hydratedMessages = attachAttachmentsToMessages(normalizedMessages, resolvedAttachments)\n const modelMessages = await convertToModelMessages(hydratedMessages)\n // Default to 10 agentic steps when the agent does not declare maxSteps.\n // Without stopWhen the AI SDK runs a single model call and never executes\n // tool calls, which makes every tool-using query return an empty stream.\n const effectiveMaxSteps = typeof agent.maxSteps === 'number' && agent.maxSteps > 0\n ? agent.maxSteps\n : 10\n const stopWhen = stepCountIs(effectiveMaxSteps)\n\n const streamArgs: Parameters<typeof streamText>[0] = {\n model,\n system: systemPrompt,\n messages: modelMessages,\n tools,\n stopWhen,\n }\n\n const result = streamText(streamArgs)\n return result.toUIMessageStreamResponse({\n sendReasoning: true,\n headers: {\n 'Cache-Control': 'no-cache, no-transform',\n Connection: 'keep-alive',\n },\n })\n}\n\n/**\n * Runtime override for the structured-output schema used by {@link runAiAgentObject}.\n * When the agent itself declares no `output` block, the caller MUST supply this;\n * otherwise the helper rejects with {@link AgentPolicyError} code\n * `execution_mode_not_supported`.\n */\nexport interface RunAiAgentObjectOutputOverride<TSchema = ZodTypeAny> {\n schemaName: string\n schema: TSchema\n /**\n * `'generate'` (default) calls AI SDK `generateObject` and resolves to the\n * parsed object. `'stream'` calls `streamObject` and returns the SDK's\n * streaming handle so callers can consume partial objects / text deltas.\n */\n mode?: 'generate' | 'stream'\n}\n\nexport interface RunAiAgentObjectInput<TSchema = ZodTypeAny> {\n agentId: string\n /**\n * Accepts either a bare user prompt (wrapped as `[{ role: 'user', content }]`)\n * or a prebuilt `UIMessage[]` array \u2014 matches the source spec's\n * `RunAiAgentObjectInput` contract (\u00A71149\u20131160).\n */\n input: string | UIMessage[]\n attachmentIds?: string[]\n pageContext?: AgentRequestPageContext\n /**\n * Same Phase-1 shim as {@link RunAiAgentTextInput.authContext}. Required until\n * a global request-context resolver lands (Phase 4).\n */\n authContext: AiChatRequestContext\n modelOverride?: string\n /**\n * Optional request-time provider override. When non-empty, wins for the\n * provider axis at the same priority as `modelOverride` for the model axis.\n *\n * Phase 1 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n providerOverride?: string\n /**\n * Optional per-call base URL override. Wins over every other source in the\n * baseURL resolution chain. Intended for programmatic callers only \u2014 the\n * HTTP query-param baseUrl and the AI_RUNTIME_BASEURL_ALLOWLIST arrive in\n * Phase 4a and MUST NOT be exposed here.\n *\n * Phase 2 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n baseUrlOverride?: string\n /**\n * Per-request HTTP dispatcher override (query params `?provider=`, `?model=`,\n * `?baseUrl=`). Validated by the dispatcher route before being forwarded\n * here. Wins over tenantOverride and all lower-priority sources when\n * `agent.allowRuntimeModelOverride !== false`.\n *\n * Phase 4a of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n requestOverride?: {\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n }\n output?: RunAiAgentObjectOutputOverride<TSchema>\n debug?: boolean\n container?: AwilixContainer\n}\n\nexport type RunAiAgentObjectGenerateResult<TSchema> = {\n mode: 'generate'\n object: TSchema\n finishReason?: string\n usage?: { inputTokens?: number; outputTokens?: number }\n}\n\nexport type RunAiAgentObjectStreamResult<TSchema> = {\n mode: 'stream'\n /** Full parsed object once the stream completes. */\n object: Promise<TSchema>\n /** Async iterator of partial (progressively hydrated) objects. */\n partialObjectStream: AsyncIterable<Partial<TSchema>>\n /** Async iterator of the raw text deltas the model emitted. */\n textStream: AsyncIterable<string>\n finishReason?: Promise<string | undefined>\n usage?: Promise<{ inputTokens?: number; outputTokens?: number } | undefined>\n}\n\nexport type RunAiAgentObjectResult<TSchema> =\n | RunAiAgentObjectGenerateResult<TSchema>\n | RunAiAgentObjectStreamResult<TSchema>\n\nfunction normalizeObjectMessages(input: string | UIMessage[]): UIMessage[] {\n if (typeof input === 'string') {\n return [\n {\n id: 'user-input',\n role: 'user',\n parts: [{ type: 'text', text: input }],\n } as unknown as UIMessage,\n ]\n }\n return input\n}\n\nfunction resolveStructuredOutput<TSchema>(\n agent: AiAgentDefinition,\n override: RunAiAgentObjectOutputOverride<TSchema> | undefined,\n): { schemaName: string; schema: unknown; mode: 'generate' | 'stream' } {\n if (override) {\n return {\n schemaName: override.schemaName,\n schema: override.schema as unknown,\n mode: override.mode ?? 'generate',\n }\n }\n const declared = agent.output as AiAgentStructuredOutput | undefined\n if (!declared) {\n throw new AgentPolicyError(\n 'execution_mode_not_supported',\n `Agent \"${agent.id}\" does not declare a structured-output schema; pass runAiAgentObject({ output }) or declare agent.output.`,\n )\n }\n return {\n schemaName: declared.schemaName,\n schema: declared.schema as unknown,\n mode: declared.mode ?? 'generate',\n }\n}\n\n/**\n * Server-side helper that runs an Open Mercato agent in structured-output mode\n * via the Vercel AI SDK. Shares the same policy gate, tool resolution path,\n * system-prompt composition, and model resolution as {@link runAiAgentText} \u2014\n * object-mode and chat-mode CANNOT diverge.\n *\n * Attachment-to-model conversion (Step 3.7): resolved\n * {@link AiResolvedAttachmentPart}s are materialized inline as AI SDK v6\n * `FileUIPart` entries on the last user message (images/PDFs) and as a\n * structured `[ATTACHMENTS]` block appended to the system prompt (text\n * extracts + metadata-only summaries). Matches {@link runAiAgentText} byte-\n * for-byte so the Step 3.6 parity contract is preserved.\n */\nexport async function runAiAgentObject<TSchema = unknown>(\n input: RunAiAgentObjectInput<TSchema>,\n): Promise<RunAiAgentObjectResult<TSchema>> {\n const [mutationPolicyOverride, tenantRuntimeOverride, tenantAllowlistSnapshot] = await Promise.all([\n resolveMutationPolicyOverride(\n input.agentId,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n ),\n resolveRuntimeModelOverride(\n input.agentId,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n ),\n resolveTenantAllowlistSnapshot(\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n ),\n ])\n const { agent, tools } = await resolveAiAgentTools({\n agentId: input.agentId,\n authContext: input.authContext,\n pageContext: input.pageContext,\n attachmentIds: input.attachmentIds,\n requestedExecutionMode: 'object',\n mutationPolicyOverride,\n container: input.container,\n })\n\n const resolvedOutput = resolveStructuredOutput(agent, input.output)\n\n const resolvedAttachments = await resolveAttachmentPartsForAgent({\n agent,\n attachmentIds: input.attachmentIds,\n authContext: input.authContext,\n container: input.container,\n })\n\n const baseSystemPrompt = await composeSystemPrompt(\n agent,\n input.pageContext,\n input.container,\n input.authContext.tenantId,\n input.authContext.organizationId,\n )\n const systemPrompt = appendRuntimeMutationPolicy(\n appendAttachmentSummary(baseSystemPrompt, resolvedAttachments),\n agent,\n mutationPolicyOverride,\n )\n\n const { model } = resolveAgentModel(\n agent,\n input.modelOverride,\n input.providerOverride,\n input.container,\n input.baseUrlOverride,\n tenantRuntimeOverride,\n input.requestOverride,\n tenantAllowlistSnapshot,\n )\n const normalizedMessages = ensureUiMessageShape(normalizeObjectMessages(input.input))\n const hydratedMessages = attachAttachmentsToMessages(\n normalizedMessages,\n resolvedAttachments,\n )\n const modelMessages = await convertToModelMessages(hydratedMessages)\n const stopWhen = typeof agent.maxSteps === 'number' && agent.maxSteps > 0\n ? stepCountIs(agent.maxSteps)\n : undefined\n\n if (resolvedOutput.mode === 'stream') {\n const streamArgs: Parameters<typeof streamObject>[0] = {\n model,\n system: systemPrompt,\n messages: modelMessages,\n schema: resolvedOutput.schema as never,\n schemaName: resolvedOutput.schemaName,\n }\n const result = streamObject(streamArgs) as unknown as {\n object: Promise<TSchema>\n partialObjectStream: AsyncIterable<Partial<TSchema>>\n textStream: AsyncIterable<string>\n finishReason?: Promise<string | undefined>\n usage?: Promise<{ inputTokens?: number; outputTokens?: number } | undefined>\n }\n return {\n mode: 'stream',\n object: result.object,\n partialObjectStream: result.partialObjectStream,\n textStream: result.textStream,\n finishReason: result.finishReason,\n usage: result.usage,\n }\n }\n\n const generateArgs: Parameters<typeof generateObject>[0] = {\n model,\n system: systemPrompt,\n messages: modelMessages,\n schema: resolvedOutput.schema as never,\n schemaName: resolvedOutput.schemaName,\n }\n if (stopWhen) {\n // generateObject shares `CallSettings` with generateText; stopWhen is ignored\n // by the typed surface but harmless for providers that respect it. Tools\n // flow through the system prompt only in object mode today \u2014 the whitelist\n // has already been resolved via `resolveAiAgentTools` above, even if we\n // don't hand it to generateObject.\n ;(generateArgs as Record<string, unknown>).stopWhen = stopWhen\n }\n void tools\n\n const result = await generateObject(generateArgs)\n return {\n mode: 'generate',\n object: (result as { object: unknown }).object as TSchema,\n finishReason: (result as { finishReason?: string }).finishReason,\n usage: (result as { usage?: { inputTokens?: number; outputTokens?: number } }).usage,\n }\n}\n\nexport { AgentPolicyError }\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["result"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/ai-agent-definition.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { ZodTypeAny } from 'zod'\n\nexport type AiAgentExecutionMode = 'chat' | 'object'\n\nexport type AiAgentMutationPolicy =\n | 'read-only'\n | 'confirm-required'\n | 'destructive-confirm-required'\n\nexport type AiAgentAcceptedMediaType = 'image' | 'pdf' | 'file'\n\nexport type AiAgentDataOperation = 'read' | 'search' | 'aggregate'\n\nexport interface AiAgentPageContextInput {\n entityType: string\n recordId: string\n container: AwilixContainer\n tenantId: string | null\n organizationId: string | null\n}\n\nexport interface AiAgentStructuredOutput<TSchema = ZodTypeAny> {\n schemaName: string\n schema: TSchema\n mode?: 'generate' | 'stream'\n}\n\nexport interface AiAgentDataCapabilities {\n entities?: string[]\n operations?: AiAgentDataOperation[]\n searchableFields?: string[]\n}\n\nexport interface AiAgentSuggestion {\n label: string\n prompt: string\n}\n\nexport interface AiAgentDefinition {\n id: string\n moduleId: string\n label: string\n description: string\n systemPrompt: string\n allowedTools: string[]\n suggestions?: AiAgentSuggestion[]\n executionMode?: AiAgentExecutionMode\n defaultModel?: string\n acceptedMediaTypes?: AiAgentAcceptedMediaType[]\n requiredFeatures?: string[]\n uiParts?: string[]\n readOnly?: boolean\n mutationPolicy?: AiAgentMutationPolicy\n maxSteps?: number\n output?: AiAgentStructuredOutput\n resolvePageContext?: (ctx: AiAgentPageContextInput) => Promise<string | null>\n keywords?: string[]\n domain?: string\n dataCapabilities?: AiAgentDataCapabilities\n}\n\nexport interface AiAgentExtension {\n targetAgentId: string\n replaceAllowedTools?: string[]\n deleteAllowedTools?: string[]\n appendAllowedTools?: string[]\n replaceSystemPrompt?: string\n appendSystemPrompt?: string\n replaceSuggestions?: AiAgentSuggestion[]\n deleteSuggestions?: string[]\n appendSuggestions?: AiAgentSuggestion[]\n /**\n * @deprecated Use `appendSuggestions` for new code. Preserved as the\n * original append-only field for backward compatibility.\n */\n suggestions?: AiAgentSuggestion[]\n}\n\nexport function defineAiAgent(definition: AiAgentDefinition): AiAgentDefinition {\n return definition\n}\n\nexport function defineAiAgentExtension(extension: AiAgentExtension): AiAgentExtension {\n return extension\n}\n"],
|
|
5
|
-
"mappings": "AA+
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { ZodTypeAny } from 'zod'\n\nexport type AiAgentExecutionMode = 'chat' | 'object'\n\nexport type AiAgentMutationPolicy =\n | 'read-only'\n | 'confirm-required'\n | 'destructive-confirm-required'\n\nexport type AiAgentAcceptedMediaType = 'image' | 'pdf' | 'file'\n\nexport type AiAgentDataOperation = 'read' | 'search' | 'aggregate'\n\nexport interface AiAgentPageContextInput {\n entityType: string\n recordId: string\n container: AwilixContainer\n tenantId: string | null\n organizationId: string | null\n}\n\nexport interface AiAgentStructuredOutput<TSchema = ZodTypeAny> {\n schemaName: string\n schema: TSchema\n mode?: 'generate' | 'stream'\n}\n\nexport interface AiAgentDataCapabilities {\n entities?: string[]\n operations?: AiAgentDataOperation[]\n searchableFields?: string[]\n}\n\nexport interface AiAgentSuggestion {\n label: string\n prompt: string\n}\n\nexport interface AiAgentDefinition {\n id: string\n moduleId: string\n label: string\n description: string\n systemPrompt: string\n allowedTools: string[]\n suggestions?: AiAgentSuggestion[]\n executionMode?: AiAgentExecutionMode\n /**\n * Optional provider id this agent prefers (e.g. `'openai'`, `'anthropic'`).\n * Must match a registered `LlmProvider.id`. When the named provider is\n * registered but unconfigured at runtime the factory falls through\n * transparently to the next configured provider.\n *\n * Phase 1 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n defaultProvider?: string\n /**\n * Optional model id fed through `createModelFactory` for this agent.\n * Accepts either a plain model id (`claude-haiku-4-5-20251001`) or a\n * slash-qualified `<provider>/<model>` shorthand (e.g. `openai/gpt-5-mini`).\n * When the slash form is used the prefix must match a registered provider id;\n * the registry-membership guard prevents mis-splitting model ids that already\n * contain slashes (DeepInfra: `meta-llama/Llama-3.3-70B-Instruct-Turbo`).\n *\n * A higher-priority provider source still wins over the slash hint, but a\n * lower-priority one cannot overwrite a slash-qualified model (cross-axis\n * tie-break rule from spec \u00A7Phase-1).\n *\n * Phase 0 and Phase 1 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n defaultModel?: string\n /**\n * Optional base URL this agent prefers for its chosen provider.\n * Sits between the `<MODULE>_AI_BASE_URL` env (step 2 of the public 5-step\n * baseURL hierarchy) and the preset env override (`baseURLEnvKeys`, step 4).\n * Only honoured by adapters that support baseURL (Anthropic Messages-\n * protocol relays, all OpenAI-compatible adapters, Google via\n * @ai-sdk/google \u22653.0). See `packages/ai-assistant/AGENTS.md` \u2192\n * \"baseURL override hierarchy\" for the full numbered chain.\n *\n * Phase 2 of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n defaultBaseUrl?: string\n /**\n * When false, per-request HTTP overrides (query params `provider`, `model`,\n * `baseUrl`) and the per-tenant settings override stored in\n * `ai_agent_runtime_overrides` are both suppressed. Steps 1 and 3 of the\n * model-factory resolution chain are skipped for this agent.\n *\n * Default is `true` (permissive). Agents that pin a specific model for\n * correctness reasons (e.g. a structured-output agent whose JSON-mode schema\n * only works with one provider) should set this to `false`.\n *\n * Phase 4a of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`.\n */\n allowRuntimeModelOverride?: boolean\n acceptedMediaTypes?: AiAgentAcceptedMediaType[]\n requiredFeatures?: string[]\n uiParts?: string[]\n readOnly?: boolean\n mutationPolicy?: AiAgentMutationPolicy\n maxSteps?: number\n output?: AiAgentStructuredOutput\n resolvePageContext?: (ctx: AiAgentPageContextInput) => Promise<string | null>\n keywords?: string[]\n domain?: string\n dataCapabilities?: AiAgentDataCapabilities\n}\n\nexport interface AiAgentExtension {\n targetAgentId: string\n replaceAllowedTools?: string[]\n deleteAllowedTools?: string[]\n appendAllowedTools?: string[]\n replaceSystemPrompt?: string\n appendSystemPrompt?: string\n replaceSuggestions?: AiAgentSuggestion[]\n deleteSuggestions?: string[]\n appendSuggestions?: AiAgentSuggestion[]\n /**\n * @deprecated Use `appendSuggestions` for new code. Preserved as the\n * original append-only field for backward compatibility.\n */\n suggestions?: AiAgentSuggestion[]\n}\n\nexport function defineAiAgent(definition: AiAgentDefinition): AiAgentDefinition {\n return definition\n}\n\nexport function defineAiAgentExtension(extension: AiAgentExtension): AiAgentExtension {\n return extension\n}\n"],
|
|
5
|
+
"mappings": "AA+HO,SAAS,cAAc,YAAkD;AAC9E,SAAO;AACT;AAEO,SAAS,uBAAuB,WAA+C;AACpF,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function readBaseurlAllowlist(env = process.env) {
|
|
2
|
+
const raw = env.AI_RUNTIME_BASEURL_ALLOWLIST ?? "";
|
|
3
|
+
return raw.split(",").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
|
|
4
|
+
}
|
|
5
|
+
function isBaseurlAllowlisted(baseUrl, allowlist) {
|
|
6
|
+
const trimmed = baseUrl.trim();
|
|
7
|
+
if (!trimmed) return true;
|
|
8
|
+
if (allowlist.length === 0) return false;
|
|
9
|
+
let parsedHostname;
|
|
10
|
+
try {
|
|
11
|
+
parsedHostname = new URL(trimmed).hostname.toLowerCase();
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
for (const pattern of allowlist) {
|
|
16
|
+
if (pattern.startsWith("*.")) {
|
|
17
|
+
const suffix = pattern.slice(2);
|
|
18
|
+
if (parsedHostname.endsWith(`.${suffix}`)) return true;
|
|
19
|
+
} else if (pattern === parsedHostname) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
isBaseurlAllowlisted,
|
|
27
|
+
readBaseurlAllowlist
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=baseurl-allowlist.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/lib/baseurl-allowlist.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `AI_RUNTIME_BASEURL_ALLOWLIST` guard helper.\n *\n * Phase 4a of spec `2026-04-27-ai-agents-provider-model-baseurl-overrides`\n * (R6 mitigation). An operator who wants to expose the `?baseUrl=` query\n * parameter over HTTP MUST set this env var to a comma-separated list of\n * allowed host patterns. When the env var is absent or empty, ANY non-empty\n * `baseUrl` value received by the dispatcher is rejected with a typed 400\n * error \u2014 ensuring that a misconfigured deployment cannot be tricked into\n * forwarding API calls to an attacker-controlled endpoint.\n *\n * Host patterns use a simple glob: a leading `*` matches any subdomain.\n * Examples:\n * `openrouter.ai` \u2014 exact host\n * `*.openrouter.ai` \u2014 any subdomain of openrouter.ai\n * `openrouter.ai,api.myproxy.io` \u2014 two hosts (comma-separated, no spaces)\n *\n * The comparison is case-insensitive and ignores the URL path/query \u2014\n * only the `hostname` portion of the submitted URL is checked.\n */\n\n/**\n * Returns the parsed allowlist from `AI_RUNTIME_BASEURL_ALLOWLIST`.\n * An empty array means the allowlist is empty (reject all).\n */\nexport function readBaseurlAllowlist(env: Record<string, string | undefined> = process.env): string[] {\n const raw = env.AI_RUNTIME_BASEURL_ALLOWLIST ?? ''\n return raw\n .split(',')\n .map((entry) => entry.trim().toLowerCase())\n .filter((entry) => entry.length > 0)\n}\n\n/**\n * Returns true when `baseUrl` is permitted under the current allowlist.\n *\n * Rules:\n * - An empty `baseUrl` is always allowed (no override requested).\n * - When the allowlist is empty, any non-empty `baseUrl` is rejected.\n * - A pattern `*.example.com` matches `foo.example.com` but NOT `example.com`.\n * - A pattern `example.com` matches only `example.com` (exact, case-insensitive).\n */\nexport function isBaseurlAllowlisted(baseUrl: string, allowlist: string[]): boolean {\n const trimmed = baseUrl.trim()\n if (!trimmed) return true\n if (allowlist.length === 0) return false\n\n let parsedHostname: string\n try {\n parsedHostname = new URL(trimmed).hostname.toLowerCase()\n } catch {\n return false\n }\n\n for (const pattern of allowlist) {\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(2)\n if (parsedHostname.endsWith(`.${suffix}`)) return true\n } else if (pattern === parsedHostname) {\n return true\n }\n }\n return false\n}\n"],
|
|
5
|
+
"mappings": "AAyBO,SAAS,qBAAqB,MAA0C,QAAQ,KAAe;AACpG,QAAM,MAAM,IAAI,gCAAgC;AAChD,SAAO,IACJ,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,EAAE,YAAY,CAAC,EACzC,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AACvC;AAWO,SAAS,qBAAqB,SAAiB,WAA8B;AAClF,QAAM,UAAU,QAAQ,KAAK;AAC7B,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,UAAU,WAAW,EAAG,QAAO;AAEnC,MAAI;AACJ,MAAI;AACF,qBAAiB,IAAI,IAAI,OAAO,EAAE,SAAS,YAAY;AAAA,EACzD,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,aAAW,WAAW,WAAW;AAC/B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC5B,YAAM,SAAS,QAAQ,MAAM,CAAC;AAC9B,UAAI,eAAe,SAAS,IAAI,MAAM,EAAE,EAAG,QAAO;AAAA,IACpD,WAAW,YAAY,gBAAgB;AACrC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -54,7 +54,10 @@ function createAnthropicAdapter() {
|
|
|
54
54
|
return envKeys[0];
|
|
55
55
|
},
|
|
56
56
|
createModel(options) {
|
|
57
|
-
const anthropic = createAnthropic({
|
|
57
|
+
const anthropic = createAnthropic({
|
|
58
|
+
apiKey: options.apiKey,
|
|
59
|
+
...options.baseURL ? { baseURL: options.baseURL } : {}
|
|
60
|
+
});
|
|
58
61
|
return anthropic(options.modelId);
|
|
59
62
|
}
|
|
60
63
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/lib/llm-adapters/anthropic.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * AnthropicAdapter \u2014 implements the LlmProvider port for Anthropic's\n * Messages API (Claude Haiku, Sonnet, Opus).\n *\n * Wraps `createAnthropic({ apiKey })` from `@ai-sdk/anthropic` and exposes\n * a curated model list for the AI Assistant UI dropdown.\n *\n * @see packages/shared/src/lib/ai/llm-provider.ts\n * @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport type {\n EnvLookup,\n LlmCreateModelOptions,\n LlmModelInfo,\n LlmProvider,\n} from '@open-mercato/shared/lib/ai/llm-provider'\n\nconst DEFAULT_MODEL = 'claude-haiku-4-5-20251001'\n\nconst DEFAULT_MODELS: readonly LlmModelInfo[] = [\n {\n id: 'claude-haiku-4-5-20251001',\n name: 'Claude Haiku 4.5',\n contextWindow: 200000,\n tags: ['budget'],\n },\n {\n id: 'claude-sonnet-4-6-20260107',\n name: 'Claude Sonnet 4.6',\n contextWindow: 200000,\n tags: ['flagship'],\n },\n {\n id: 'claude-opus-4-6-20260107',\n name: 'Claude Opus 4.6',\n contextWindow: 1000000,\n tags: ['flagship', 'reasoning'],\n },\n] as const\n\n/**\n * Factory returning a fresh `AnthropicAdapter` instance. The adapter is\n * stateless \u2014 caller is free to reuse the returned object.\n */\nexport function createAnthropicAdapter(): LlmProvider {\n const envKeys = ['ANTHROPIC_API_KEY', 'OPENCODE_ANTHROPIC_API_KEY'] as const\n\n function resolveApiKey(env?: EnvLookup): string | null {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n }\n\n return {\n id: 'anthropic',\n name: 'Anthropic',\n envKeys,\n defaultModel: DEFAULT_MODEL,\n defaultModels: DEFAULT_MODELS,\n\n isConfigured(env?: EnvLookup): boolean {\n return resolveApiKey(env) !== null\n },\n\n resolveApiKey,\n\n getConfiguredEnvKey(env?: EnvLookup): string {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return key\n }\n }\n return envKeys[0]\n },\n\n createModel(options: LlmCreateModelOptions): unknown {\n const anthropic = createAnthropic({
|
|
5
|
-
"mappings": "AAWA,SAAS,uBAAuB;AAQhC,MAAM,gBAAgB;AAEtB,MAAM,iBAA0C;AAAA,EAC9C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,QAAQ;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,UAAU;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,YAAY,WAAW;AAAA,EAChC;AACF;
|
|
4
|
+
"sourcesContent": ["/**\n * AnthropicAdapter \u2014 implements the LlmProvider port for Anthropic's\n * Messages API (Claude Haiku, Sonnet, Opus).\n *\n * Wraps `createAnthropic({ apiKey })` from `@ai-sdk/anthropic` and exposes\n * a curated model list for the AI Assistant UI dropdown.\n *\n * @see packages/shared/src/lib/ai/llm-provider.ts\n * @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic'\nimport type {\n EnvLookup,\n LlmCreateModelOptions,\n LlmModelInfo,\n LlmProvider,\n} from '@open-mercato/shared/lib/ai/llm-provider'\n\nconst DEFAULT_MODEL = 'claude-haiku-4-5-20251001'\n\nconst DEFAULT_MODELS: readonly LlmModelInfo[] = [\n {\n id: 'claude-haiku-4-5-20251001',\n name: 'Claude Haiku 4.5',\n contextWindow: 200000,\n tags: ['budget'],\n },\n {\n id: 'claude-sonnet-4-6-20260107',\n name: 'Claude Sonnet 4.6',\n contextWindow: 200000,\n tags: ['flagship'],\n },\n {\n id: 'claude-opus-4-6-20260107',\n name: 'Claude Opus 4.6',\n contextWindow: 1000000,\n tags: ['flagship', 'reasoning'],\n },\n] as const\n\n/**\n * Factory returning a fresh `AnthropicAdapter` instance. The adapter is\n * stateless \u2014 caller is free to reuse the returned object.\n *\n * `createModel` accepts an optional `baseURL` from {@link LlmCreateModelOptions}\n * and forwards it to `createAnthropic({ apiKey, baseURL })`.\n *\n * baseURL only works for Anthropic Messages-protocol relays (Cloudflare AI\n * Gateway in Anthropic mode, Helicone proxy). For OpenAI-format gateways,\n * use the OpenAI / OpenRouter presets.\n */\nexport function createAnthropicAdapter(): LlmProvider {\n const envKeys = ['ANTHROPIC_API_KEY', 'OPENCODE_ANTHROPIC_API_KEY'] as const\n\n function resolveApiKey(env?: EnvLookup): string | null {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n }\n\n return {\n id: 'anthropic',\n name: 'Anthropic',\n envKeys,\n defaultModel: DEFAULT_MODEL,\n defaultModels: DEFAULT_MODELS,\n\n isConfigured(env?: EnvLookup): boolean {\n return resolveApiKey(env) !== null\n },\n\n resolveApiKey,\n\n getConfiguredEnvKey(env?: EnvLookup): string {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return key\n }\n }\n return envKeys[0]\n },\n\n createModel(options: LlmCreateModelOptions): unknown {\n const anthropic = createAnthropic({\n apiKey: options.apiKey,\n ...(options.baseURL ? { baseURL: options.baseURL } : {}),\n })\n return anthropic(options.modelId)\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "AAWA,SAAS,uBAAuB;AAQhC,MAAM,gBAAgB;AAEtB,MAAM,iBAA0C;AAAA,EAC9C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,QAAQ;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,UAAU;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,YAAY,WAAW;AAAA,EAChC;AACF;AAaO,SAAS,yBAAsC;AACpD,QAAM,UAAU,CAAC,qBAAqB,4BAA4B;AAElE,WAAS,cAAc,KAAgC;AACrD,UAAM,SAAS,OAAO,QAAQ;AAC9B,eAAW,OAAO,SAAS;AACzB,YAAM,QAAQ,OAAO,GAAG;AACxB,UAAI,OAAO,UAAU,UAAU;AAC7B,cAAM,UAAU,MAAM,KAAK;AAC3B,YAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,MACjC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN;AAAA,IACA,cAAc;AAAA,IACd,eAAe;AAAA,IAEf,aAAa,KAA0B;AACrC,aAAO,cAAc,GAAG,MAAM;AAAA,IAChC;AAAA,IAEA;AAAA,IAEA,oBAAoB,KAAyB;AAC3C,YAAM,SAAS,OAAO,QAAQ;AAC9B,iBAAW,OAAO,SAAS;AACzB,cAAM,QAAQ,OAAO,GAAG;AACxB,YAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO,QAAQ,CAAC;AAAA,IAClB;AAAA,IAEA,YAAY,SAAyC;AACnD,YAAM,YAAY,gBAAgB;AAAA,QAChC,QAAQ,QAAQ;AAAA,QAChB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,MACxD,CAAC;AACD,aAAO,UAAU,QAAQ,OAAO;AAAA,IAClC;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -48,7 +48,10 @@ function createGoogleAdapter() {
|
|
|
48
48
|
return envKeys[0];
|
|
49
49
|
},
|
|
50
50
|
createModel(options) {
|
|
51
|
-
const google = createGoogleGenerativeAI({
|
|
51
|
+
const google = createGoogleGenerativeAI({
|
|
52
|
+
apiKey: options.apiKey,
|
|
53
|
+
...options.baseURL ? { baseURL: options.baseURL } : {}
|
|
54
|
+
});
|
|
52
55
|
return google(options.modelId);
|
|
53
56
|
}
|
|
54
57
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/lib/llm-adapters/google.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * GoogleAdapter \u2014 implements the LlmProvider port for Google's Generative\n * AI API (Gemini Flash, Pro).\n *\n * Wraps `createGoogleGenerativeAI({ apiKey })` from `@ai-sdk/google`.\n *\n * @see packages/shared/src/lib/ai/llm-provider.ts\n * @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport type {\n EnvLookup,\n LlmCreateModelOptions,\n LlmModelInfo,\n LlmProvider,\n} from '@open-mercato/shared/lib/ai/llm-provider'\n\nconst DEFAULT_MODEL = 'gemini-3-flash'\n\nconst DEFAULT_MODELS: readonly LlmModelInfo[] = [\n {\n id: 'gemini-3-flash',\n name: 'Gemini 3 Flash',\n contextWindow: 1048576,\n tags: ['budget'],\n },\n {\n id: 'gemini-3-pro',\n name: 'Gemini 3 Pro',\n contextWindow: 1048576,\n tags: ['flagship'],\n },\n] as const\n\nexport function createGoogleAdapter(): LlmProvider {\n const envKeys = ['GOOGLE_GENERATIVE_AI_API_KEY', 'OPENCODE_GOOGLE_API_KEY'] as const\n\n function resolveApiKey(env?: EnvLookup): string | null {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n }\n\n return {\n id: 'google',\n name: 'Google',\n envKeys,\n defaultModel: DEFAULT_MODEL,\n defaultModels: DEFAULT_MODELS,\n\n isConfigured(env?: EnvLookup): boolean {\n return resolveApiKey(env) !== null\n },\n\n resolveApiKey,\n\n getConfiguredEnvKey(env?: EnvLookup): string {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return key\n }\n }\n return envKeys[0]\n },\n\n createModel(options: LlmCreateModelOptions): unknown {\n const google = createGoogleGenerativeAI({
|
|
5
|
-
"mappings": "AAUA,SAAS,gCAAgC;AAQzC,MAAM,gBAAgB;AAEtB,MAAM,iBAA0C;AAAA,EAC9C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,QAAQ;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,UAAU;AAAA,EACnB;AACF;AAEO,SAAS,sBAAmC;AACjD,QAAM,UAAU,CAAC,gCAAgC,yBAAyB;AAE1E,WAAS,cAAc,KAAgC;AACrD,UAAM,SAAS,OAAO,QAAQ;AAC9B,eAAW,OAAO,SAAS;AACzB,YAAM,QAAQ,OAAO,GAAG;AACxB,UAAI,OAAO,UAAU,UAAU;AAC7B,cAAM,UAAU,MAAM,KAAK;AAC3B,YAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,MACjC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN;AAAA,IACA,cAAc;AAAA,IACd,eAAe;AAAA,IAEf,aAAa,KAA0B;AACrC,aAAO,cAAc,GAAG,MAAM;AAAA,IAChC;AAAA,IAEA;AAAA,IAEA,oBAAoB,KAAyB;AAC3C,YAAM,SAAS,OAAO,QAAQ;AAC9B,iBAAW,OAAO,SAAS;AACzB,cAAM,QAAQ,OAAO,GAAG;AACxB,YAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO,QAAQ,CAAC;AAAA,IAClB;AAAA,IAEA,YAAY,SAAyC;AACnD,YAAM,SAAS,yBAAyB,EAAE,QAAQ,QAAQ,
|
|
4
|
+
"sourcesContent": ["/**\n * GoogleAdapter \u2014 implements the LlmProvider port for Google's Generative\n * AI API (Gemini Flash, Pro).\n *\n * Wraps `createGoogleGenerativeAI({ apiKey })` from `@ai-sdk/google`.\n *\n * @see packages/shared/src/lib/ai/llm-provider.ts\n * @see .ai/specs/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport { createGoogleGenerativeAI } from '@ai-sdk/google'\nimport type {\n EnvLookup,\n LlmCreateModelOptions,\n LlmModelInfo,\n LlmProvider,\n} from '@open-mercato/shared/lib/ai/llm-provider'\n\nconst DEFAULT_MODEL = 'gemini-3-flash'\n\nconst DEFAULT_MODELS: readonly LlmModelInfo[] = [\n {\n id: 'gemini-3-flash',\n name: 'Gemini 3 Flash',\n contextWindow: 1048576,\n tags: ['budget'],\n },\n {\n id: 'gemini-3-pro',\n name: 'Gemini 3 Pro',\n contextWindow: 1048576,\n tags: ['flagship'],\n },\n] as const\n\nexport function createGoogleAdapter(): LlmProvider {\n const envKeys = ['GOOGLE_GENERATIVE_AI_API_KEY', 'OPENCODE_GOOGLE_API_KEY'] as const\n\n function resolveApiKey(env?: EnvLookup): string | null {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length > 0) return trimmed\n }\n }\n return null\n }\n\n return {\n id: 'google',\n name: 'Google',\n envKeys,\n defaultModel: DEFAULT_MODEL,\n defaultModels: DEFAULT_MODELS,\n\n isConfigured(env?: EnvLookup): boolean {\n return resolveApiKey(env) !== null\n },\n\n resolveApiKey,\n\n getConfiguredEnvKey(env?: EnvLookup): string {\n const lookup = env ?? process.env\n for (const key of envKeys) {\n const value = lookup[key]\n if (typeof value === 'string' && value.trim().length > 0) {\n return key\n }\n }\n return envKeys[0]\n },\n\n createModel(options: LlmCreateModelOptions): unknown {\n const google = createGoogleGenerativeAI({\n apiKey: options.apiKey,\n ...(options.baseURL ? { baseURL: options.baseURL } : {}),\n })\n return google(options.modelId)\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,gCAAgC;AAQzC,MAAM,gBAAgB;AAEtB,MAAM,iBAA0C;AAAA,EAC9C;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,QAAQ;AAAA,EACjB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,IACf,MAAM,CAAC,UAAU;AAAA,EACnB;AACF;AAEO,SAAS,sBAAmC;AACjD,QAAM,UAAU,CAAC,gCAAgC,yBAAyB;AAE1E,WAAS,cAAc,KAAgC;AACrD,UAAM,SAAS,OAAO,QAAQ;AAC9B,eAAW,OAAO,SAAS;AACzB,YAAM,QAAQ,OAAO,GAAG;AACxB,UAAI,OAAO,UAAU,UAAU;AAC7B,cAAM,UAAU,MAAM,KAAK;AAC3B,YAAI,QAAQ,SAAS,EAAG,QAAO;AAAA,MACjC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN;AAAA,IACA,cAAc;AAAA,IACd,eAAe;AAAA,IAEf,aAAa,KAA0B;AACrC,aAAO,cAAc,GAAG,MAAM;AAAA,IAChC;AAAA,IAEA;AAAA,IAEA,oBAAoB,KAAyB;AAC3C,YAAM,SAAS,OAAO,QAAQ;AAC9B,iBAAW,OAAO,SAAS;AACzB,cAAM,QAAQ,OAAO,GAAG;AACxB,YAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO,QAAQ,CAAC;AAAA,IAClB;AAAA,IAEA,YAAY,SAAyC;AACnD,YAAM,SAAS,yBAAyB;AAAA,QACtC,QAAQ,QAAQ;AAAA,QAChB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,MACxD,CAAC;AACD,aAAO,OAAO,QAAQ,OAAO;AAAA,IAC/B;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|