@open-mercato/ai-assistant 0.6.1-develop.3291.1.6fad645fd0 → 0.6.1
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 +30 -4
- package/dist/frontend/components/AiChatButton.js +3 -2
- package/dist/frontend/components/AiChatButton.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js +364 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +7 -7
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js +182 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js +316 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +8 -7
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +43 -20
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/route.js +4 -3
- package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/daily/route.js +111 -0
- package/dist/modules/ai_assistant/api/usage/daily/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js +108 -0
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/usage/sessions/route.js +153 -0
- package/dist/modules/ai_assistant/api/usage/sessions/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +335 -38
- 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 +2 -7
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +44 -35
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js +282 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +12 -0
- package/dist/modules/ai_assistant/cli.js.map +2 -2
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +1 -1
- package/dist/modules/ai_assistant/data/entities.js +177 -1
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +104 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js +168 -0
- package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js.map +7 -0
- package/dist/modules/ai_assistant/events.js +8 -0
- package/dist/modules/ai_assistant/events.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +74 -1
- package/dist/modules/ai_assistant/i18n/en.json +74 -1
- package/dist/modules/ai_assistant/i18n/es.json +75 -2
- package/dist/modules/ai_assistant/i18n/pl.json +74 -1
- package/dist/modules/ai_assistant/lib/agent-policy.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +588 -23
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +3 -3
- package/dist/modules/ai_assistant/lib/agent-tools.js +6 -1
- package/dist/modules/ai_assistant/lib/agent-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-factory.js +63 -22
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js +78 -0
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js.map +7 -0
- package/dist/modules/ai_assistant/lib/usage-serialization.js +33 -0
- package/dist/modules/ai_assistant/lib/usage-serialization.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js +25 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js +88 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js.map +7 -0
- package/dist/modules/ai_assistant/setup.js +34 -0
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js +114 -0
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +7 -0
- package/generated/entities/ai_token_usage_daily/index.ts +16 -0
- package/generated/entities/ai_token_usage_event/index.ts +19 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +47 -1
- package/package.json +15 -7
- package/src/frontend/components/AiChatButton.tsx +3 -2
- package/src/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.ts +521 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +8 -8
- package/src/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.ts +231 -0
- package/src/modules/ai_assistant/__tests__/events.test.ts +4 -3
- package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +5 -5
- package/src/modules/ai_assistant/__tests__/token-usage-recorder.test.ts +109 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.ts +388 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +5 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +8 -7
- package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +102 -5
- package/src/modules/ai_assistant/api/ai/chat/route.ts +55 -18
- package/src/modules/ai_assistant/api/settings/route.ts +5 -3
- package/src/modules/ai_assistant/api/usage/daily/__tests__/route.test.ts +159 -0
- package/src/modules/ai_assistant/api/usage/daily/route.ts +126 -0
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/__tests__/route.test.ts +143 -0
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/route.ts +130 -0
- package/src/modules/ai_assistant/api/usage/sessions/__tests__/route.test.ts +123 -0
- package/src/modules/ai_assistant/api/usage/sessions/route.ts +184 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +372 -16
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +1 -4
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +26 -9
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.tsx +469 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +18 -0
- package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +1 -1
- package/src/modules/ai_assistant/data/entities.ts +237 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +135 -3
- package/src/modules/ai_assistant/data/repositories/AiTokenUsageRepository.ts +213 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +223 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiTokenUsageRepository.test.ts +58 -0
- package/src/modules/ai_assistant/events.ts +8 -0
- package/src/modules/ai_assistant/i18n/de.json +74 -1
- package/src/modules/ai_assistant/i18n/en.json +74 -1
- package/src/modules/ai_assistant/i18n/es.json +75 -2
- package/src/modules/ai_assistant/i18n/pl.json +74 -1
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase0.test.ts +439 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase1.test.ts +243 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase2.test.ts +388 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase3.test.ts +359 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +2 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +2 -1
- package/src/modules/ai_assistant/lib/__tests__/max-steps-budget.integration.test.ts +12 -13
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +77 -14
- package/src/modules/ai_assistant/lib/agent-policy.ts +9 -0
- package/src/modules/ai_assistant/lib/agent-runtime.ts +1148 -43
- package/src/modules/ai_assistant/lib/agent-tools.ts +5 -1
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +289 -2
- package/src/modules/ai_assistant/lib/model-factory.ts +128 -43
- package/src/modules/ai_assistant/lib/token-usage-recorder.ts +122 -0
- package/src/modules/ai_assistant/lib/usage-serialization.ts +29 -0
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +791 -0
- package/src/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.ts +25 -0
- package/src/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.ts +89 -0
- package/src/modules/ai_assistant/setup.ts +49 -0
- package/src/modules/ai_assistant/workers/__tests__/ai-token-usage-prune.test.ts +144 -0
- package/src/modules/ai_assistant/workers/ai-token-usage-prune.ts +188 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
+
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
+
import { getAgent, loadAgentRegistry } from "../../../../../lib/agent-registry.js";
|
|
6
|
+
import { hasRequiredFeatures } from "../../../../../lib/auth.js";
|
|
7
|
+
import {
|
|
8
|
+
AiAgentRuntimeOverrideRepository,
|
|
9
|
+
AiAgentRuntimeOverrideValidationError
|
|
10
|
+
} from "../../../../../data/repositories/AiAgentRuntimeOverrideRepository.js";
|
|
11
|
+
const agentIdPattern = /^[a-z0-9_]+\.[a-z0-9_]+$/;
|
|
12
|
+
const agentIdParamSchema = z.object({
|
|
13
|
+
agentId: z.string().regex(agentIdPattern, 'agentId must match "<module>.<agent>" (lowercase, digits, underscores only)')
|
|
14
|
+
});
|
|
15
|
+
const loopOverrideRequestSchema = z.object({
|
|
16
|
+
loopDisabled: z.boolean().nullable().optional(),
|
|
17
|
+
loopMaxSteps: z.number().int().min(1).max(1e3).nullable().optional(),
|
|
18
|
+
loopMaxToolCalls: z.number().int().min(1).max(1e4).nullable().optional(),
|
|
19
|
+
loopMaxWallClockMs: z.number().int().min(100).max(36e5).nullable().optional(),
|
|
20
|
+
loopMaxTokens: z.number().int().min(1).max(1e7).nullable().optional(),
|
|
21
|
+
loopStopWhenJson: z.array(
|
|
22
|
+
z.discriminatedUnion("kind", [
|
|
23
|
+
z.object({ kind: z.literal("stepCount"), count: z.number().int().min(1) }),
|
|
24
|
+
z.object({ kind: z.literal("hasToolCall"), toolName: z.string().min(1) })
|
|
25
|
+
])
|
|
26
|
+
).nullable().optional(),
|
|
27
|
+
loopActiveToolsJson: z.array(z.string().min(1)).nullable().optional()
|
|
28
|
+
});
|
|
29
|
+
const VIEW_FEATURE = "ai_assistant.view";
|
|
30
|
+
const MANAGE_FEATURE = "ai_assistant.settings.manage";
|
|
31
|
+
const openApi = {
|
|
32
|
+
tag: "AI Assistant",
|
|
33
|
+
summary: "Tenant-scoped loop-policy override for an AI agent",
|
|
34
|
+
methods: {
|
|
35
|
+
GET: {
|
|
36
|
+
operationId: "aiAssistantGetLoopOverride",
|
|
37
|
+
summary: "Read the current loop-policy override for this agent, if any.",
|
|
38
|
+
description: "Returns `{ agentId, override }` where `override` is the agent-scoped loop-policy row from `ai_agent_runtime_overrides` (or `null`). Requires `ai_assistant.view`.",
|
|
39
|
+
responses: [
|
|
40
|
+
{
|
|
41
|
+
status: 200,
|
|
42
|
+
description: "Loop override payload.",
|
|
43
|
+
mediaType: "application/json"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
errors: [
|
|
47
|
+
{ status: 400, description: "Invalid agent id." },
|
|
48
|
+
{ status: 401, description: "Unauthenticated caller." },
|
|
49
|
+
{ status: 403, description: "Caller lacks `ai_assistant.view`." },
|
|
50
|
+
{ status: 404, description: "Unknown agent id." }
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
PUT: {
|
|
54
|
+
operationId: "aiAssistantSaveLoopOverride",
|
|
55
|
+
summary: "Set (or replace) the tenant-scoped loop-policy override for this agent.",
|
|
56
|
+
description: "Body: loop columns. All fields are nullable/optional; `null` explicitly clears that axis. Validates `loopStopWhenJson` items and `loopActiveToolsJson` membership. Requires `ai_assistant.settings.manage`.",
|
|
57
|
+
requestBody: {
|
|
58
|
+
contentType: "application/json",
|
|
59
|
+
description: "Loop override payload.",
|
|
60
|
+
schema: loopOverrideRequestSchema
|
|
61
|
+
},
|
|
62
|
+
responses: [
|
|
63
|
+
{
|
|
64
|
+
status: 200,
|
|
65
|
+
description: "Override persisted.",
|
|
66
|
+
mediaType: "application/json"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
errors: [
|
|
70
|
+
{ status: 400, description: "Invalid agent id or validation error." },
|
|
71
|
+
{ status: 401, description: "Unauthenticated caller." },
|
|
72
|
+
{ status: 403, description: "Caller lacks `ai_assistant.settings.manage`." },
|
|
73
|
+
{ status: 404, description: "Unknown agent id." }
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
DELETE: {
|
|
77
|
+
operationId: "aiAssistantClearLoopOverride",
|
|
78
|
+
summary: "Remove the loop-policy columns from the agent-scoped runtime override row.",
|
|
79
|
+
description: "Nulls out all seven loop columns on the agent-scoped `ai_agent_runtime_overrides` row. Idempotent \u2014 returns 200 even when no override exists. Requires `ai_assistant.settings.manage`.",
|
|
80
|
+
responses: [
|
|
81
|
+
{
|
|
82
|
+
status: 200,
|
|
83
|
+
description: "Loop override cleared (or already absent).",
|
|
84
|
+
mediaType: "application/json"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
errors: [
|
|
88
|
+
{ status: 400, description: "Invalid agent id." },
|
|
89
|
+
{ status: 401, description: "Unauthenticated caller." },
|
|
90
|
+
{ status: 403, description: "Caller lacks `ai_assistant.settings.manage`." },
|
|
91
|
+
{ status: 404, description: "Unknown agent id." }
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const metadata = {
|
|
97
|
+
GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },
|
|
98
|
+
PUT: { requireAuth: true, requireFeatures: [MANAGE_FEATURE] },
|
|
99
|
+
DELETE: { requireAuth: true, requireFeatures: [MANAGE_FEATURE] }
|
|
100
|
+
};
|
|
101
|
+
function jsonError(status, message, code, extra) {
|
|
102
|
+
return NextResponse.json({ error: message, code, ...extra ?? {} }, { status });
|
|
103
|
+
}
|
|
104
|
+
async function resolveAuthOrRespond(req, requiredFeature) {
|
|
105
|
+
const auth = await getAuthFromRequest(req);
|
|
106
|
+
if (!auth) {
|
|
107
|
+
return jsonError(401, "Unauthorized", "unauthenticated");
|
|
108
|
+
}
|
|
109
|
+
const container = await createRequestContainer();
|
|
110
|
+
const rbacService = container.resolve("rbacService");
|
|
111
|
+
const acl = await rbacService.loadAcl(auth.sub, {
|
|
112
|
+
tenantId: auth.tenantId,
|
|
113
|
+
organizationId: auth.orgId
|
|
114
|
+
});
|
|
115
|
+
if (!hasRequiredFeatures([requiredFeature], acl.features, acl.isSuperAdmin, rbacService)) {
|
|
116
|
+
return jsonError(403, `Caller lacks required feature "${requiredFeature}".`, "forbidden");
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
tenantId: auth.tenantId ?? null,
|
|
120
|
+
organizationId: auth.orgId ?? null,
|
|
121
|
+
userId: auth.sub,
|
|
122
|
+
isSuperAdmin: acl.isSuperAdmin,
|
|
123
|
+
features: acl.features,
|
|
124
|
+
container
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function serializeLoopOverride(row) {
|
|
128
|
+
return {
|
|
129
|
+
id: row.id,
|
|
130
|
+
agentId: row.agentId ?? null,
|
|
131
|
+
loopDisabled: row.loopDisabled ?? null,
|
|
132
|
+
loopMaxSteps: row.loopMaxSteps ?? null,
|
|
133
|
+
loopMaxToolCalls: row.loopMaxToolCalls ?? null,
|
|
134
|
+
loopMaxWallClockMs: row.loopMaxWallClockMs ?? null,
|
|
135
|
+
loopMaxTokens: row.loopMaxTokens ?? null,
|
|
136
|
+
loopStopWhenJson: row.loopStopWhenJson ?? null,
|
|
137
|
+
loopActiveToolsJson: row.loopActiveToolsJson ?? null,
|
|
138
|
+
updatedAt: row.updatedAt?.toISOString?.() ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function GET(req, context) {
|
|
142
|
+
const authResult = await resolveAuthOrRespond(req, VIEW_FEATURE);
|
|
143
|
+
if (authResult instanceof NextResponse) return authResult;
|
|
144
|
+
const rawParams = await context.params;
|
|
145
|
+
const paramResult = agentIdParamSchema.safeParse(rawParams);
|
|
146
|
+
if (!paramResult.success) {
|
|
147
|
+
return jsonError(400, "Invalid agent id.", "validation_error", {
|
|
148
|
+
issues: paramResult.error.issues
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await loadAgentRegistry();
|
|
153
|
+
const agent = getAgent(paramResult.data.agentId);
|
|
154
|
+
if (!agent) {
|
|
155
|
+
return jsonError(404, `Unknown agent "${paramResult.data.agentId}".`, "agent_unknown");
|
|
156
|
+
}
|
|
157
|
+
if (!authResult.tenantId) {
|
|
158
|
+
return NextResponse.json({ agentId: agent.id, override: null });
|
|
159
|
+
}
|
|
160
|
+
const em = authResult.container.resolve("em");
|
|
161
|
+
const repo = new AiAgentRuntimeOverrideRepository(em);
|
|
162
|
+
const row = await repo.getDefault({
|
|
163
|
+
tenantId: authResult.tenantId,
|
|
164
|
+
organizationId: authResult.organizationId,
|
|
165
|
+
agentId: agent.id
|
|
166
|
+
});
|
|
167
|
+
const hasLoopData = row !== null && (row.loopDisabled !== null || row.loopMaxSteps !== null || row.loopMaxToolCalls !== null || row.loopMaxWallClockMs !== null || row.loopMaxTokens !== null || row.loopStopWhenJson !== null || row.loopActiveToolsJson !== null);
|
|
168
|
+
return NextResponse.json({
|
|
169
|
+
agentId: agent.id,
|
|
170
|
+
override: hasLoopData ? serializeLoopOverride(row) : null
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error("[AI Loop Override GET] Failure:", error);
|
|
174
|
+
return jsonError(
|
|
175
|
+
500,
|
|
176
|
+
error instanceof Error ? error.message : "Failed to load loop override.",
|
|
177
|
+
"internal_error"
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function PUT(req, context) {
|
|
182
|
+
const authResult = await resolveAuthOrRespond(req, MANAGE_FEATURE);
|
|
183
|
+
if (authResult instanceof NextResponse) return authResult;
|
|
184
|
+
const rawParams = await context.params;
|
|
185
|
+
const paramResult = agentIdParamSchema.safeParse(rawParams);
|
|
186
|
+
if (!paramResult.success) {
|
|
187
|
+
return jsonError(400, "Invalid agent id.", "validation_error", {
|
|
188
|
+
issues: paramResult.error.issues
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
let parsedBody;
|
|
192
|
+
try {
|
|
193
|
+
parsedBody = await req.json();
|
|
194
|
+
} catch {
|
|
195
|
+
return jsonError(400, "Request body must be valid JSON.", "validation_error");
|
|
196
|
+
}
|
|
197
|
+
const bodyResult = loopOverrideRequestSchema.safeParse(parsedBody);
|
|
198
|
+
if (!bodyResult.success) {
|
|
199
|
+
return jsonError(400, "Invalid request body.", "validation_error", {
|
|
200
|
+
issues: bodyResult.error.issues
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
await loadAgentRegistry();
|
|
205
|
+
const agent = getAgent(paramResult.data.agentId);
|
|
206
|
+
if (!agent) {
|
|
207
|
+
return jsonError(404, `Unknown agent "${paramResult.data.agentId}".`, "agent_unknown");
|
|
208
|
+
}
|
|
209
|
+
if (!authResult.tenantId) {
|
|
210
|
+
return jsonError(
|
|
211
|
+
400,
|
|
212
|
+
"Caller has no tenant context; cannot persist tenant-scoped loop override.",
|
|
213
|
+
"tenant_required"
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const em = authResult.container.resolve("em");
|
|
217
|
+
const repo = new AiAgentRuntimeOverrideRepository(em);
|
|
218
|
+
const row = await repo.upsertDefault(
|
|
219
|
+
{
|
|
220
|
+
agentId: agent.id,
|
|
221
|
+
agentAllowedTools: agent.allowedTools,
|
|
222
|
+
loopDisabled: bodyResult.data.loopDisabled ?? null,
|
|
223
|
+
loopMaxSteps: bodyResult.data.loopMaxSteps ?? null,
|
|
224
|
+
loopMaxToolCalls: bodyResult.data.loopMaxToolCalls ?? null,
|
|
225
|
+
loopMaxWallClockMs: bodyResult.data.loopMaxWallClockMs ?? null,
|
|
226
|
+
loopMaxTokens: bodyResult.data.loopMaxTokens ?? null,
|
|
227
|
+
loopStopWhenJson: bodyResult.data.loopStopWhenJson ?? null,
|
|
228
|
+
loopActiveToolsJson: bodyResult.data.loopActiveToolsJson ?? null
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
tenantId: authResult.tenantId,
|
|
232
|
+
organizationId: authResult.organizationId,
|
|
233
|
+
userId: authResult.userId
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
return NextResponse.json({
|
|
237
|
+
ok: true,
|
|
238
|
+
agentId: agent.id,
|
|
239
|
+
override: serializeLoopOverride(row)
|
|
240
|
+
});
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (error instanceof AiAgentRuntimeOverrideValidationError) {
|
|
243
|
+
return jsonError(400, error.message, error.code);
|
|
244
|
+
}
|
|
245
|
+
console.error("[AI Loop Override PUT] Failure:", error);
|
|
246
|
+
return jsonError(
|
|
247
|
+
500,
|
|
248
|
+
error instanceof Error ? error.message : "Failed to save loop override.",
|
|
249
|
+
"internal_error"
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function DELETE(req, context) {
|
|
254
|
+
const authResult = await resolveAuthOrRespond(req, MANAGE_FEATURE);
|
|
255
|
+
if (authResult instanceof NextResponse) return authResult;
|
|
256
|
+
const rawParams = await context.params;
|
|
257
|
+
const paramResult = agentIdParamSchema.safeParse(rawParams);
|
|
258
|
+
if (!paramResult.success) {
|
|
259
|
+
return jsonError(400, "Invalid agent id.", "validation_error", {
|
|
260
|
+
issues: paramResult.error.issues
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
await loadAgentRegistry();
|
|
265
|
+
const agent = getAgent(paramResult.data.agentId);
|
|
266
|
+
if (!agent) {
|
|
267
|
+
return jsonError(404, `Unknown agent "${paramResult.data.agentId}".`, "agent_unknown");
|
|
268
|
+
}
|
|
269
|
+
if (!authResult.tenantId) {
|
|
270
|
+
return NextResponse.json({ ok: true, agentId: agent.id, cleared: false });
|
|
271
|
+
}
|
|
272
|
+
const em = authResult.container.resolve("em");
|
|
273
|
+
const repo = new AiAgentRuntimeOverrideRepository(em);
|
|
274
|
+
const existing = await repo.getDefault({
|
|
275
|
+
tenantId: authResult.tenantId,
|
|
276
|
+
organizationId: authResult.organizationId,
|
|
277
|
+
agentId: agent.id
|
|
278
|
+
});
|
|
279
|
+
if (!existing) {
|
|
280
|
+
return NextResponse.json({ ok: true, agentId: agent.id, cleared: false });
|
|
281
|
+
}
|
|
282
|
+
await repo.upsertDefault(
|
|
283
|
+
{
|
|
284
|
+
agentId: agent.id,
|
|
285
|
+
loopDisabled: null,
|
|
286
|
+
loopMaxSteps: null,
|
|
287
|
+
loopMaxToolCalls: null,
|
|
288
|
+
loopMaxWallClockMs: null,
|
|
289
|
+
loopMaxTokens: null,
|
|
290
|
+
loopStopWhenJson: null,
|
|
291
|
+
loopActiveToolsJson: null
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
tenantId: authResult.tenantId,
|
|
295
|
+
organizationId: authResult.organizationId,
|
|
296
|
+
userId: authResult.userId
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
return NextResponse.json({ ok: true, agentId: agent.id, cleared: true });
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error("[AI Loop Override DELETE] Failure:", error);
|
|
302
|
+
return jsonError(
|
|
303
|
+
500,
|
|
304
|
+
error instanceof Error ? error.message : "Failed to clear loop override.",
|
|
305
|
+
"internal_error"
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
export {
|
|
310
|
+
DELETE,
|
|
311
|
+
GET,
|
|
312
|
+
PUT,
|
|
313
|
+
metadata,
|
|
314
|
+
openApi
|
|
315
|
+
};
|
|
316
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/ai_assistant/api/ai/agents/%5BagentId%5D/loop-override/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAgent, loadAgentRegistry } from '../../../../../lib/agent-registry'\nimport { hasRequiredFeatures } from '../../../../../lib/auth'\nimport {\n AiAgentRuntimeOverrideRepository,\n AiAgentRuntimeOverrideValidationError,\n} from '../../../../../data/repositories/AiAgentRuntimeOverrideRepository'\nimport type { AiAgentRuntimeOverride } from '../../../../../data/entities'\n\nconst agentIdPattern = /^[a-z0-9_]+\\.[a-z0-9_]+$/\n\nconst agentIdParamSchema = z.object({\n agentId: z\n .string()\n .regex(agentIdPattern, 'agentId must match \"<module>.<agent>\" (lowercase, digits, underscores only)'),\n})\n\nconst loopOverrideRequestSchema = z.object({\n loopDisabled: z.boolean().nullable().optional(),\n loopMaxSteps: z.number().int().min(1).max(1000).nullable().optional(),\n loopMaxToolCalls: z.number().int().min(1).max(10000).nullable().optional(),\n loopMaxWallClockMs: z.number().int().min(100).max(3_600_000).nullable().optional(),\n loopMaxTokens: z.number().int().min(1).max(10_000_000).nullable().optional(),\n loopStopWhenJson: z\n .array(\n z.discriminatedUnion('kind', [\n z.object({ kind: z.literal('stepCount'), count: z.number().int().min(1) }),\n z.object({ kind: z.literal('hasToolCall'), toolName: z.string().min(1) }),\n ]),\n )\n .nullable()\n .optional(),\n loopActiveToolsJson: z.array(z.string().min(1)).nullable().optional(),\n})\n\nconst VIEW_FEATURE = 'ai_assistant.view'\nconst MANAGE_FEATURE = 'ai_assistant.settings.manage'\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Tenant-scoped loop-policy override for an AI agent',\n methods: {\n GET: {\n operationId: 'aiAssistantGetLoopOverride',\n summary:\n 'Read the current loop-policy override for this agent, if any.',\n description:\n 'Returns `{ agentId, override }` where `override` is the agent-scoped loop-policy ' +\n 'row from `ai_agent_runtime_overrides` (or `null`). Requires `ai_assistant.view`.',\n responses: [\n {\n status: 200,\n description: 'Loop override payload.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid agent id.' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.view`.' },\n { status: 404, description: 'Unknown agent id.' },\n ],\n },\n PUT: {\n operationId: 'aiAssistantSaveLoopOverride',\n summary: 'Set (or replace) the tenant-scoped loop-policy override for this agent.',\n description:\n 'Body: loop columns. All fields are nullable/optional; `null` explicitly clears ' +\n 'that axis. Validates `loopStopWhenJson` items and `loopActiveToolsJson` membership. ' +\n 'Requires `ai_assistant.settings.manage`.',\n requestBody: {\n contentType: 'application/json',\n description: 'Loop override payload.',\n schema: loopOverrideRequestSchema,\n },\n responses: [\n {\n status: 200,\n description: 'Override persisted.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid agent id or validation error.' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },\n { status: 404, description: 'Unknown agent id.' },\n ],\n },\n DELETE: {\n operationId: 'aiAssistantClearLoopOverride',\n summary: 'Remove the loop-policy columns from the agent-scoped runtime override row.',\n description:\n 'Nulls out all seven loop columns on the agent-scoped `ai_agent_runtime_overrides` row. ' +\n 'Idempotent \u2014 returns 200 even when no override exists. ' +\n 'Requires `ai_assistant.settings.manage`.',\n responses: [\n {\n status: 200,\n description: 'Loop override cleared (or already absent).',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid agent id.' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },\n { status: 404, description: 'Unknown agent id.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },\n PUT: { requireAuth: true, requireFeatures: [MANAGE_FEATURE] },\n DELETE: { requireAuth: true, requireFeatures: [MANAGE_FEATURE] },\n}\n\ninterface RouteContext {\n params: Promise<{ agentId: string }>\n}\n\nfunction jsonError(\n status: number,\n message: string,\n code: string,\n extra?: Record<string, unknown>,\n): NextResponse {\n return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })\n}\n\ninterface ResolvedAuth {\n tenantId: string | null\n organizationId: string | null\n userId: string\n isSuperAdmin: boolean\n features: string[]\n container: Awaited<ReturnType<typeof createRequestContainer>>\n}\n\nasync function resolveAuthOrRespond(\n req: NextRequest,\n requiredFeature: string,\n): Promise<ResolvedAuth | NextResponse> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n if (!hasRequiredFeatures([requiredFeature], acl.features, acl.isSuperAdmin, rbacService)) {\n return jsonError(403, `Caller lacks required feature \"${requiredFeature}\".`, 'forbidden')\n }\n return {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n isSuperAdmin: acl.isSuperAdmin,\n features: acl.features,\n container,\n }\n}\n\nfunction serializeLoopOverride(row: AiAgentRuntimeOverride) {\n return {\n id: row.id,\n agentId: row.agentId ?? null,\n loopDisabled: row.loopDisabled ?? null,\n loopMaxSteps: row.loopMaxSteps ?? null,\n loopMaxToolCalls: row.loopMaxToolCalls ?? null,\n loopMaxWallClockMs: row.loopMaxWallClockMs ?? null,\n loopMaxTokens: row.loopMaxTokens ?? null,\n loopStopWhenJson: row.loopStopWhenJson ?? null,\n loopActiveToolsJson: row.loopActiveToolsJson ?? null,\n updatedAt: row.updatedAt?.toISOString?.() ?? new Date().toISOString(),\n }\n}\n\nexport async function GET(req: NextRequest, context: RouteContext): Promise<Response> {\n const authResult = await resolveAuthOrRespond(req, VIEW_FEATURE)\n if (authResult instanceof NextResponse) return authResult\n\n const rawParams = await context.params\n const paramResult = agentIdParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid agent id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n\n try {\n await loadAgentRegistry()\n const agent = getAgent(paramResult.data.agentId)\n if (!agent) {\n return jsonError(404, `Unknown agent \"${paramResult.data.agentId}\".`, 'agent_unknown')\n }\n\n if (!authResult.tenantId) {\n return NextResponse.json({ agentId: agent.id, override: null })\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const row = await repo.getDefault({\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n agentId: agent.id,\n })\n\n const hasLoopData =\n row !== null &&\n (row.loopDisabled !== null ||\n row.loopMaxSteps !== null ||\n row.loopMaxToolCalls !== null ||\n row.loopMaxWallClockMs !== null ||\n row.loopMaxTokens !== null ||\n row.loopStopWhenJson !== null ||\n row.loopActiveToolsJson !== null)\n\n return NextResponse.json({\n agentId: agent.id,\n override: hasLoopData ? serializeLoopOverride(row!) : null,\n })\n } catch (error) {\n console.error('[AI Loop Override GET] Failure:', error)\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to load loop override.',\n 'internal_error',\n )\n }\n}\n\nexport async function PUT(req: NextRequest, context: RouteContext): Promise<Response> {\n const authResult = await resolveAuthOrRespond(req, MANAGE_FEATURE)\n if (authResult instanceof NextResponse) return authResult\n\n const rawParams = await context.params\n const paramResult = agentIdParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid agent id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return jsonError(400, 'Request body must be valid JSON.', 'validation_error')\n }\n\n const bodyResult = loopOverrideRequestSchema.safeParse(parsedBody)\n if (!bodyResult.success) {\n return jsonError(400, 'Invalid request body.', 'validation_error', {\n issues: bodyResult.error.issues,\n })\n }\n\n try {\n await loadAgentRegistry()\n const agent = getAgent(paramResult.data.agentId)\n if (!agent) {\n return jsonError(404, `Unknown agent \"${paramResult.data.agentId}\".`, 'agent_unknown')\n }\n\n if (!authResult.tenantId) {\n return jsonError(\n 400,\n 'Caller has no tenant context; cannot persist tenant-scoped loop override.',\n 'tenant_required',\n )\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n const row = await repo.upsertDefault(\n {\n agentId: agent.id,\n agentAllowedTools: agent.allowedTools,\n loopDisabled: bodyResult.data.loopDisabled ?? null,\n loopMaxSteps: bodyResult.data.loopMaxSteps ?? null,\n loopMaxToolCalls: bodyResult.data.loopMaxToolCalls ?? null,\n loopMaxWallClockMs: bodyResult.data.loopMaxWallClockMs ?? null,\n loopMaxTokens: bodyResult.data.loopMaxTokens ?? null,\n loopStopWhenJson: bodyResult.data.loopStopWhenJson ?? null,\n loopActiveToolsJson: bodyResult.data.loopActiveToolsJson ?? null,\n },\n {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n userId: authResult.userId,\n },\n )\n\n return NextResponse.json({\n ok: true,\n agentId: agent.id,\n override: serializeLoopOverride(row),\n })\n } catch (error) {\n if (error instanceof AiAgentRuntimeOverrideValidationError) {\n return jsonError(400, error.message, error.code)\n }\n console.error('[AI Loop Override PUT] Failure:', error)\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to save loop override.',\n 'internal_error',\n )\n }\n}\n\nexport async function DELETE(req: NextRequest, context: RouteContext): Promise<Response> {\n const authResult = await resolveAuthOrRespond(req, MANAGE_FEATURE)\n if (authResult instanceof NextResponse) return authResult\n\n const rawParams = await context.params\n const paramResult = agentIdParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid agent id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n\n try {\n await loadAgentRegistry()\n const agent = getAgent(paramResult.data.agentId)\n if (!agent) {\n return jsonError(404, `Unknown agent \"${paramResult.data.agentId}\".`, 'agent_unknown')\n }\n\n if (!authResult.tenantId) {\n return NextResponse.json({ ok: true, agentId: agent.id, cleared: false })\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentRuntimeOverrideRepository(em)\n\n const existing = await repo.getDefault({\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n agentId: agent.id,\n })\n\n if (!existing) {\n return NextResponse.json({ ok: true, agentId: agent.id, cleared: false })\n }\n\n await repo.upsertDefault(\n {\n agentId: agent.id,\n loopDisabled: null,\n loopMaxSteps: null,\n loopMaxToolCalls: null,\n loopMaxWallClockMs: null,\n loopMaxTokens: null,\n loopStopWhenJson: null,\n loopActiveToolsJson: null,\n },\n {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n userId: authResult.userId,\n },\n )\n\n return NextResponse.json({ ok: true, agentId: agent.id, cleared: true })\n } catch (error) {\n console.error('[AI Loop Override DELETE] Failure:', error)\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to clear loop override.',\n 'internal_error',\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -5,7 +5,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
5
5
|
import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
|
|
6
6
|
import { getAgent, loadAgentRegistry } from "../../../../../lib/agent-registry.js";
|
|
7
7
|
import { hasRequiredFeatures } from "../../../../../lib/auth.js";
|
|
8
|
-
import { createModelFactory } from "../../../../../lib/model-factory.js";
|
|
8
|
+
import { createModelFactory, resolveAllowRuntimeOverride } from "../../../../../lib/model-factory.js";
|
|
9
9
|
import {
|
|
10
10
|
hasAllowlistSnapshotRestrictions,
|
|
11
11
|
intersectEffectiveAllowlistWithSnapshot,
|
|
@@ -31,11 +31,11 @@ const openApi = {
|
|
|
31
31
|
GET: {
|
|
32
32
|
operationId: "aiAssistantGetAgentModels",
|
|
33
33
|
summary: "Get the providers and curated models available for the chat-UI picker for this agent",
|
|
34
|
-
description: 'Returns all configured providers with their curated model catalogs, filtered to providers that have an API key configured in the current environment. When the agent declares `
|
|
34
|
+
description: 'Returns all configured providers with their curated model catalogs, filtered to providers that have an API key configured in the current environment. When the agent declares `allowRuntimeOverride: false`, the response reflects that constraint so the UI picker can hide itself. Includes the agent\'s resolved default provider/model so the picker can render a "(default)" badge next to the right entry. RBAC: requires the same features as the agent itself (typically `ai_assistant.view`).',
|
|
35
35
|
responses: [
|
|
36
36
|
{
|
|
37
37
|
status: 200,
|
|
38
|
-
description: "Providers and curated models available for the agent picker. Empty `providers` array when `
|
|
38
|
+
description: "Providers and curated models available for the agent picker. Empty `providers` array when `allowRuntimeOverride` is false."
|
|
39
39
|
}
|
|
40
40
|
],
|
|
41
41
|
errors: [
|
|
@@ -88,7 +88,7 @@ async function GET(req, { params }) {
|
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
const
|
|
91
|
+
const allowRuntimeOverride = resolveAllowRuntimeOverride(agent);
|
|
92
92
|
let tenantAllowlistSnapshot = null;
|
|
93
93
|
let agentRuntimeOverrideAllowlist = null;
|
|
94
94
|
let tenantRuntimeOverride = null;
|
|
@@ -131,7 +131,7 @@ async function GET(req, { params }) {
|
|
|
131
131
|
agentDefaultModel: agent.defaultModel,
|
|
132
132
|
agentDefaultProvider: agent.defaultProvider,
|
|
133
133
|
agentDefaultBaseUrl: agent.defaultBaseUrl,
|
|
134
|
-
|
|
134
|
+
allowRuntimeOverride,
|
|
135
135
|
tenantOverride: tenantRuntimeOverride ?? void 0,
|
|
136
136
|
tenantAllowlist: tenantAllowlistSnapshot
|
|
137
137
|
});
|
|
@@ -154,7 +154,7 @@ async function GET(req, { params }) {
|
|
|
154
154
|
knownProviderIds,
|
|
155
155
|
agentRuntimeOverrideAllowlist
|
|
156
156
|
);
|
|
157
|
-
const providers =
|
|
157
|
+
const providers = allowRuntimeOverride ? llmProviderRegistry.list().filter((provider) => provider.isConfigured()).filter((provider) => isProviderAllowedInEffective(effectiveAllowlist, provider.id)).map((provider) => {
|
|
158
158
|
const allowedModelIds = effectiveAllowlist.modelsByProvider[provider.id];
|
|
159
159
|
const filteredModels = modelsForPicker(provider, allowedModelIds).filter(
|
|
160
160
|
(model) => isModelAllowedForProviderInEffective(effectiveAllowlist, provider.id, model.id)
|
|
@@ -174,7 +174,8 @@ async function GET(req, { params }) {
|
|
|
174
174
|
}) : [];
|
|
175
175
|
return NextResponse.json({
|
|
176
176
|
agentId,
|
|
177
|
-
|
|
177
|
+
allowRuntimeOverride,
|
|
178
|
+
allowRuntimeModelOverride: allowRuntimeOverride,
|
|
178
179
|
defaultProviderId,
|
|
179
180
|
defaultModelId,
|
|
180
181
|
defaultProviderName: llmProviderRegistry.get(defaultProviderId)?.name ?? defaultProviderId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/ai_assistant/api/ai/agents/%5BagentId%5D/models/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { getAgent, loadAgentRegistry } from '../../../../../lib/agent-registry'\nimport { hasRequiredFeatures } from '../../../../../lib/auth'\nimport { createModelFactory } from '../../../../../lib/model-factory'\nimport {\n hasAllowlistSnapshotRestrictions,\n intersectEffectiveAllowlistWithSnapshot,\n intersectAllowlists,\n isModelAllowedForProviderInEffective,\n isProviderAllowedInEffective,\n readAgentRuntimeOverrideAllowlist,\n type TenantAllowlistSnapshot,\n} from '../../../../../lib/model-allowlist'\nimport { AiTenantModelAllowlistRepository } from '../../../../../data/repositories/AiTenantModelAllowlistRepository'\nimport { AiAgentRuntimeOverrideRepository } from '../../../../../data/repositories/AiAgentRuntimeOverrideRepository'\n\nfunction modelsForPicker(\n provider: ReturnType<typeof llmProviderRegistry.list>[number],\n allowedModelIds: string[] | undefined,\n): ReadonlyArray<{ id: string; name: string; contextWindow?: number | null; tags?: readonly string[] }> {\n if (provider.defaultModels.length > 0) return provider.defaultModels\n return (allowedModelIds ?? []).map((id) => ({ id, name: id }))\n}\n\nconst agentIdPattern = /^[a-z0-9_]+\\.[a-z0-9_]+$/\n\nconst agentIdParamSchema = z.object({\n agentId: z\n .string()\n .regex(agentIdPattern, 'agentId must match \"<module>.<agent>\" (lowercase, digits, underscores only)'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Available models for an AI agent',\n methods: {\n GET: {\n operationId: 'aiAssistantGetAgentModels',\n summary: 'Get the providers and curated models available for the chat-UI picker for this agent',\n description:\n 'Returns all configured providers with their curated model catalogs, filtered to providers ' +\n 'that have an API key configured in the current environment. When the agent declares ' +\n '`allowRuntimeModelOverride: false`, the response reflects that constraint so the ' +\n 'UI picker can hide itself. Includes the agent\\'s resolved default provider/model so ' +\n 'the picker can render a \"(default)\" badge next to the right entry. ' +\n 'RBAC: requires the same features as the agent itself (typically `ai_assistant.view`).',\n responses: [\n {\n status: 200,\n description:\n 'Providers and curated models available for the agent picker. ' +\n 'Empty `providers` array when `allowRuntimeModelOverride` is false.',\n },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks the agent\\'s required features.' },\n { status: 404, description: 'Unknown agent id.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function GET(\n req: NextRequest,\n { params }: { params: Promise<{ agentId: string }> },\n): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await params\n const paramResult = agentIdParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return NextResponse.json(\n { error: 'Invalid agentId path parameter.', code: 'validation_error', issues: paramResult.error.issues },\n { status: 400 },\n )\n }\n const agentId = paramResult.data.agentId\n\n try {\n await loadAgentRegistry()\n\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n const agent = getAgent(agentId)\n if (!agent) {\n return NextResponse.json({ error: `Agent \"${agentId}\" not found.`, code: 'agent_unknown' }, { status: 404 })\n }\n\n const agentFeatures = agent.requiredFeatures ?? []\n if (agentFeatures.length > 0) {\n const permitted = hasRequiredFeatures(agentFeatures, acl.features, acl.isSuperAdmin)\n if (!permitted) {\n return NextResponse.json(\n {\n error: `Access to agent \"${agentId}\" requires features: ${agentFeatures.join(', ')}.`,\n code: 'agent_features_denied',\n },\n { status: 403 },\n )\n }\n }\n\n const allowRuntimeModelOverride = agent.allowRuntimeModelOverride !== false\n\n // Load the per-tenant allowlist snapshot so the picker reflects both env\n // and admin-edited tenant constraints (Phase 1780-6).\n let tenantAllowlistSnapshot: TenantAllowlistSnapshot | null = null\n let agentRuntimeOverrideAllowlist: TenantAllowlistSnapshot | null = null\n let tenantRuntimeOverride: {\n providerId: string | null\n modelId: string | null\n baseURL: string | null\n } | null = null\n if (auth.tenantId) {\n try {\n const em = container.resolve<EntityManager>('em')\n const allowlistRepo = new AiTenantModelAllowlistRepository(em)\n tenantAllowlistSnapshot = await allowlistRepo.getSnapshot({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n })\n const runtimeOverrideRepo = new AiAgentRuntimeOverrideRepository(em)\n const runtimeOverrideDefaultRow = await runtimeOverrideRepo.getDefault({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n agentId,\n })\n tenantRuntimeOverride = runtimeOverrideDefaultRow\n ? {\n providerId: runtimeOverrideDefaultRow.providerId ?? null,\n modelId: runtimeOverrideDefaultRow.modelId ?? null,\n baseURL: runtimeOverrideDefaultRow.baseUrl ?? null,\n }\n : null\n const runtimeOverrideRow = await runtimeOverrideRepo.getExact({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n agentId,\n })\n const tenantAgentAllowlist = runtimeOverrideRow\n ? {\n allowedProviders: runtimeOverrideRow.allowedOverrideProviders ?? null,\n allowedModelsByProvider: runtimeOverrideRow.allowedOverrideModelsByProvider ?? {},\n }\n : null\n agentRuntimeOverrideAllowlist = hasAllowlistSnapshotRestrictions(tenantAgentAllowlist)\n ? tenantAgentAllowlist\n : null\n } catch (snapshotError) {\n // Picker still renders against env-only so the UI does not break, but log at\n // error level so an outage is operationally visible. The chat dispatcher\n // refuses to dispatch when this lookup fails, so writes stay safe.\n console.error('[AI Agents Models] Failed to load tenant allowlist:', snapshotError)\n }\n }\n\n // Resolve the agent's current default provider/model for the \"(default)\" badge\n const factory = createModelFactory(container)\n const defaultResolution = factory.resolveModel({\n moduleId: agent.moduleId,\n agentDefaultModel: agent.defaultModel,\n agentDefaultProvider: agent.defaultProvider,\n agentDefaultBaseUrl: agent.defaultBaseUrl,\n allowRuntimeModelOverride,\n tenantOverride: tenantRuntimeOverride ?? undefined,\n tenantAllowlist: tenantAllowlistSnapshot,\n })\n const defaultProviderId = defaultResolution.providerId\n const defaultModelId = defaultResolution.modelId\n\n // Build provider list \u2014 only configured providers, with curated model\n // catalogs, clipped to the EFFECTIVE allowlist (env \u2229 tenant) so the\n // chat-UI picker can never offer a value the runtime would refuse.\n const env = process.env as Record<string, string | undefined>\n const knownProviderIds = llmProviderRegistry.list().map((p) => p.id)\n const baseEffectiveAllowlist = intersectAllowlists(\n env,\n knownProviderIds,\n tenantAllowlistSnapshot,\n )\n const envAgentAllowlist = readAgentRuntimeOverrideAllowlist(env, agentId, knownProviderIds)\n const effectiveAllowlist = intersectEffectiveAllowlistWithSnapshot(\n intersectEffectiveAllowlistWithSnapshot(\n baseEffectiveAllowlist,\n knownProviderIds,\n envAgentAllowlist,\n ),\n knownProviderIds,\n agentRuntimeOverrideAllowlist,\n )\n const providers = allowRuntimeModelOverride\n ? llmProviderRegistry.list()\n .filter((provider) => provider.isConfigured())\n .filter((provider) => isProviderAllowedInEffective(effectiveAllowlist, provider.id))\n .map((provider) => {\n const allowedModelIds = effectiveAllowlist.modelsByProvider[provider.id]\n const filteredModels = modelsForPicker(provider, allowedModelIds).filter((model) =>\n isModelAllowedForProviderInEffective(effectiveAllowlist, provider.id, model.id),\n )\n return {\n id: provider.id,\n name: provider.name,\n isDefault: provider.id === defaultProviderId,\n models: filteredModels.map((model) => ({\n id: model.id,\n name: model.name,\n contextWindow: model.contextWindow,\n tags: model.tags,\n isDefault: provider.id === defaultProviderId && model.id === defaultModelId,\n })),\n }\n })\n : []\n\n return NextResponse.json({\n agentId,\n allowRuntimeModelOverride,\n defaultProviderId,\n defaultModelId,\n defaultProviderName: llmProviderRegistry.get(defaultProviderId)?.name ?? defaultProviderId,\n defaultModelName:\n llmProviderRegistry\n .get(defaultProviderId)\n ?.defaultModels.find((model) => model.id === defaultModelId)?.name ?? defaultModelId,\n providers,\n })\n } catch (error) {\n console.error('[AI Agents Models] GET error:', error)\n return NextResponse.json({ error: 'Failed to resolve agent models.' }, { status: 500 })\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAGlB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,2BAA2B;AACpC,SAAS,UAAU,yBAAyB;AAC5C,SAAS,2BAA2B;AACpC,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { getAgent, loadAgentRegistry } from '../../../../../lib/agent-registry'\nimport { hasRequiredFeatures } from '../../../../../lib/auth'\nimport { createModelFactory, resolveAllowRuntimeOverride } from '../../../../../lib/model-factory'\nimport {\n hasAllowlistSnapshotRestrictions,\n intersectEffectiveAllowlistWithSnapshot,\n intersectAllowlists,\n isModelAllowedForProviderInEffective,\n isProviderAllowedInEffective,\n readAgentRuntimeOverrideAllowlist,\n type TenantAllowlistSnapshot,\n} from '../../../../../lib/model-allowlist'\nimport { AiTenantModelAllowlistRepository } from '../../../../../data/repositories/AiTenantModelAllowlistRepository'\nimport { AiAgentRuntimeOverrideRepository } from '../../../../../data/repositories/AiAgentRuntimeOverrideRepository'\n\nfunction modelsForPicker(\n provider: ReturnType<typeof llmProviderRegistry.list>[number],\n allowedModelIds: string[] | undefined,\n): ReadonlyArray<{ id: string; name: string; contextWindow?: number | null; tags?: readonly string[] }> {\n if (provider.defaultModels.length > 0) return provider.defaultModels\n return (allowedModelIds ?? []).map((id) => ({ id, name: id }))\n}\n\nconst agentIdPattern = /^[a-z0-9_]+\\.[a-z0-9_]+$/\n\nconst agentIdParamSchema = z.object({\n agentId: z\n .string()\n .regex(agentIdPattern, 'agentId must match \"<module>.<agent>\" (lowercase, digits, underscores only)'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Available models for an AI agent',\n methods: {\n GET: {\n operationId: 'aiAssistantGetAgentModels',\n summary: 'Get the providers and curated models available for the chat-UI picker for this agent',\n description:\n 'Returns all configured providers with their curated model catalogs, filtered to providers ' +\n 'that have an API key configured in the current environment. When the agent declares ' +\n '`allowRuntimeOverride: false`, the response reflects that constraint so the ' +\n 'UI picker can hide itself. Includes the agent\\'s resolved default provider/model so ' +\n 'the picker can render a \"(default)\" badge next to the right entry. ' +\n 'RBAC: requires the same features as the agent itself (typically `ai_assistant.view`).',\n responses: [\n {\n status: 200,\n description:\n 'Providers and curated models available for the agent picker. ' +\n 'Empty `providers` array when `allowRuntimeOverride` is false.',\n },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated.' },\n { status: 403, description: 'Caller lacks the agent\\'s required features.' },\n { status: 404, description: 'Unknown agent id.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function GET(\n req: NextRequest,\n { params }: { params: Promise<{ agentId: string }> },\n): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await params\n const paramResult = agentIdParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return NextResponse.json(\n { error: 'Invalid agentId path parameter.', code: 'validation_error', issues: paramResult.error.issues },\n { status: 400 },\n )\n }\n const agentId = paramResult.data.agentId\n\n try {\n await loadAgentRegistry()\n\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n const agent = getAgent(agentId)\n if (!agent) {\n return NextResponse.json({ error: `Agent \"${agentId}\" not found.`, code: 'agent_unknown' }, { status: 404 })\n }\n\n const agentFeatures = agent.requiredFeatures ?? []\n if (agentFeatures.length > 0) {\n const permitted = hasRequiredFeatures(agentFeatures, acl.features, acl.isSuperAdmin)\n if (!permitted) {\n return NextResponse.json(\n {\n error: `Access to agent \"${agentId}\" requires features: ${agentFeatures.join(', ')}.`,\n code: 'agent_features_denied',\n },\n { status: 403 },\n )\n }\n }\n\n const allowRuntimeOverride = resolveAllowRuntimeOverride(agent)\n\n // Load the per-tenant allowlist snapshot so the picker reflects both env\n // and admin-edited tenant constraints (Phase 1780-6).\n let tenantAllowlistSnapshot: TenantAllowlistSnapshot | null = null\n let agentRuntimeOverrideAllowlist: TenantAllowlistSnapshot | null = null\n let tenantRuntimeOverride: {\n providerId: string | null\n modelId: string | null\n baseURL: string | null\n } | null = null\n if (auth.tenantId) {\n try {\n const em = container.resolve<EntityManager>('em')\n const allowlistRepo = new AiTenantModelAllowlistRepository(em)\n tenantAllowlistSnapshot = await allowlistRepo.getSnapshot({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n })\n const runtimeOverrideRepo = new AiAgentRuntimeOverrideRepository(em)\n const runtimeOverrideDefaultRow = await runtimeOverrideRepo.getDefault({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n agentId,\n })\n tenantRuntimeOverride = runtimeOverrideDefaultRow\n ? {\n providerId: runtimeOverrideDefaultRow.providerId ?? null,\n modelId: runtimeOverrideDefaultRow.modelId ?? null,\n baseURL: runtimeOverrideDefaultRow.baseUrl ?? null,\n }\n : null\n const runtimeOverrideRow = await runtimeOverrideRepo.getExact({\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n agentId,\n })\n const tenantAgentAllowlist = runtimeOverrideRow\n ? {\n allowedProviders: runtimeOverrideRow.allowedOverrideProviders ?? null,\n allowedModelsByProvider: runtimeOverrideRow.allowedOverrideModelsByProvider ?? {},\n }\n : null\n agentRuntimeOverrideAllowlist = hasAllowlistSnapshotRestrictions(tenantAgentAllowlist)\n ? tenantAgentAllowlist\n : null\n } catch (snapshotError) {\n // Picker still renders against env-only so the UI does not break, but log at\n // error level so an outage is operationally visible. The chat dispatcher\n // refuses to dispatch when this lookup fails, so writes stay safe.\n console.error('[AI Agents Models] Failed to load tenant allowlist:', snapshotError)\n }\n }\n\n // Resolve the agent's current default provider/model for the \"(default)\" badge\n const factory = createModelFactory(container)\n const defaultResolution = factory.resolveModel({\n moduleId: agent.moduleId,\n agentDefaultModel: agent.defaultModel,\n agentDefaultProvider: agent.defaultProvider,\n agentDefaultBaseUrl: agent.defaultBaseUrl,\n allowRuntimeOverride,\n tenantOverride: tenantRuntimeOverride ?? undefined,\n tenantAllowlist: tenantAllowlistSnapshot,\n })\n const defaultProviderId = defaultResolution.providerId\n const defaultModelId = defaultResolution.modelId\n\n // Build provider list \u2014 only configured providers, with curated model\n // catalogs, clipped to the EFFECTIVE allowlist (env \u2229 tenant) so the\n // chat-UI picker can never offer a value the runtime would refuse.\n const env = process.env as Record<string, string | undefined>\n const knownProviderIds = llmProviderRegistry.list().map((p) => p.id)\n const baseEffectiveAllowlist = intersectAllowlists(\n env,\n knownProviderIds,\n tenantAllowlistSnapshot,\n )\n const envAgentAllowlist = readAgentRuntimeOverrideAllowlist(env, agentId, knownProviderIds)\n const effectiveAllowlist = intersectEffectiveAllowlistWithSnapshot(\n intersectEffectiveAllowlistWithSnapshot(\n baseEffectiveAllowlist,\n knownProviderIds,\n envAgentAllowlist,\n ),\n knownProviderIds,\n agentRuntimeOverrideAllowlist,\n )\n const providers = allowRuntimeOverride\n ? llmProviderRegistry.list()\n .filter((provider) => provider.isConfigured())\n .filter((provider) => isProviderAllowedInEffective(effectiveAllowlist, provider.id))\n .map((provider) => {\n const allowedModelIds = effectiveAllowlist.modelsByProvider[provider.id]\n const filteredModels = modelsForPicker(provider, allowedModelIds).filter((model) =>\n isModelAllowedForProviderInEffective(effectiveAllowlist, provider.id, model.id),\n )\n return {\n id: provider.id,\n name: provider.name,\n isDefault: provider.id === defaultProviderId,\n models: filteredModels.map((model) => ({\n id: model.id,\n name: model.name,\n contextWindow: model.contextWindow,\n tags: model.tags,\n isDefault: provider.id === defaultProviderId && model.id === defaultModelId,\n })),\n }\n })\n : []\n\n return NextResponse.json({\n agentId,\n allowRuntimeOverride,\n allowRuntimeModelOverride: allowRuntimeOverride,\n defaultProviderId,\n defaultModelId,\n defaultProviderName: llmProviderRegistry.get(defaultProviderId)?.name ?? defaultProviderId,\n defaultModelName:\n llmProviderRegistry\n .get(defaultProviderId)\n ?.defaultModels.find((model) => model.id === defaultModelId)?.name ?? defaultModelId,\n providers,\n })\n } catch (error) {\n console.error('[AI Agents Models] GET error:', error)\n return NextResponse.json({ error: 'Failed to resolve agent models.' }, { status: 500 })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|