@open-mercato/ai-assistant 0.6.6-develop.6465.1.019f0fb26f → 0.6.6-develop.6471.1.9ab5bdd79a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frontend/hooks/useCommandPalette.js +16 -14
- package/dist/frontend/hooks/useCommandPalette.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/actions/[id]/cancel/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/actions/[id]/confirm/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/actions/[id]/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js +5 -3
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +4 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js +5 -3
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js +4 -2
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/agents/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/agents/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +6 -7
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/route.js +5 -3
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/conversations/import/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/conversations/import/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/conversations/route.js +4 -2
- package/dist/modules/ai_assistant/api/ai/conversations/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/run-object/route.js +3 -1
- package/dist/modules/ai_assistant/api/ai/run-object/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/chat/route.js +23 -14
- package/dist/modules/ai_assistant/api/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/health/route.js +3 -1
- package/dist/modules/ai_assistant/api/health/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/mcp-key/route.js +3 -1
- package/dist/modules/ai_assistant/api/mcp-key/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/route/route.js +11 -9
- package/dist/modules/ai_assistant/api/route/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/session-key/route.js +3 -1
- package/dist/modules/ai_assistant/api/session-key/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js +4 -2
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/route.js +6 -4
- package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/tools/execute/route.js +3 -1
- package/dist/modules/ai_assistant/api/tools/execute/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/tools/route.js +3 -1
- package/dist/modules/ai_assistant/api/tools/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/daily/route.js +3 -1
- package/dist/modules/ai_assistant/api/usage/daily/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js +3 -1
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/sessions/route.js +3 -1
- package/dist/modules/ai_assistant/api/usage/sessions/route.js.map +2 -2
- package/dist/modules/ai_assistant/cli.js +3 -0
- package/dist/modules/ai_assistant/cli.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-policy.js +7 -5
- package/dist/modules/ai_assistant/lib/agent-policy.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-registry.js +11 -13
- package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +21 -44
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-tools.js +5 -10
- package/dist/modules/ai_assistant/lib/agent-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-overrides.js +5 -9
- package/dist/modules/ai_assistant/lib/ai-overrides.js.map +2 -2
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js +22 -22
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +2 -2
- package/dist/modules/ai_assistant/lib/attachment-parts.js +10 -26
- package/dist/modules/ai_assistant/lib/attachment-parts.js.map +2 -2
- package/dist/modules/ai_assistant/lib/auth.js +3 -1
- package/dist/modules/ai_assistant/lib/auth.js.map +2 -2
- package/dist/modules/ai_assistant/lib/codemode-tools.js +15 -17
- package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/entity-index.js +7 -5
- package/dist/modules/ai_assistant/lib/entity-index.js.map +2 -2
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js +3 -4
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/http-server.js +45 -48
- package/dist/modules/ai_assistant/lib/http-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-bootstrap.js +5 -12
- package/dist/modules/ai_assistant/lib/llm-bootstrap.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-client.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-dev-key-resolution.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-dev-key-resolution.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-server.js +24 -19
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +3 -1
- package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-factory.js +4 -2
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/opencode-client.js +5 -3
- package/dist/modules/ai_assistant/lib/opencode-client.js.map +2 -2
- package/dist/modules/ai_assistant/lib/opencode-handlers.js +30 -12
- package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +2 -2
- package/dist/modules/ai_assistant/lib/pending-action-cancel.js +3 -1
- package/dist/modules/ai_assistant/lib/pending-action-cancel.js.map +2 -2
- package/dist/modules/ai_assistant/lib/pending-action-executor.js +3 -1
- package/dist/modules/ai_assistant/lib/pending-action-executor.js.map +2 -2
- package/dist/modules/ai_assistant/lib/prepare-mutation.js +4 -6
- package/dist/modules/ai_assistant/lib/prepare-mutation.js.map +2 -2
- package/dist/modules/ai_assistant/lib/schema-utils.js +3 -1
- package/dist/modules/ai_assistant/lib/schema-utils.js.map +2 -2
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js +4 -8
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-executor.js +3 -1
- package/dist/modules/ai_assistant/lib/tool-executor.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-loader.js +18 -25
- package/dist/modules/ai_assistant/lib/tool-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-registry.js +3 -1
- package/dist/modules/ai_assistant/lib/tool-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-test-runner.js +3 -1
- package/dist/modules/ai_assistant/lib/tool-test-runner.js.map +2 -2
- package/dist/modules/ai_assistant/setup.js +4 -8
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/dist/modules/ai_assistant/subscribers/conversation-shared-notify.js +3 -1
- package/dist/modules/ai_assistant/subscribers/conversation-shared-notify.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js +7 -19
- package/dist/modules/ai_assistant/workers/ai-pending-action-cleanup.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js +5 -11
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js.map +2 -2
- package/package.json +6 -6
- package/src/frontend/hooks/useCommandPalette.ts +17 -16
- package/src/modules/ai_assistant/__tests__/events.test.ts +23 -5
- package/src/modules/ai_assistant/__tests__/integration/ws-c-attachment-bridge.test.ts +21 -3
- package/src/modules/ai_assistant/__tests__/integration/ws-c-policy-and-tools.test.ts +22 -3
- package/src/modules/ai_assistant/__tests__/token-usage-recorder.test.ts +26 -5
- package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/actions/[id]/confirm/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/actions/[id]/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.ts +6 -3
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +5 -2
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/mutation-policy/route.ts +6 -3
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/prompt-override/route.ts +5 -2
- package/src/modules/ai_assistant/api/ai/agents/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/chat/route.ts +7 -7
- package/src/modules/ai_assistant/api/ai/conversations/[conversationId]/route.ts +6 -3
- package/src/modules/ai_assistant/api/ai/conversations/import/route.ts +4 -1
- package/src/modules/ai_assistant/api/ai/conversations/route.ts +5 -2
- package/src/modules/ai_assistant/api/ai/run-object/route.ts +4 -1
- package/src/modules/ai_assistant/api/chat/route.ts +24 -15
- package/src/modules/ai_assistant/api/health/route.ts +4 -1
- package/src/modules/ai_assistant/api/mcp-key/route.ts +4 -1
- package/src/modules/ai_assistant/api/route/route.ts +12 -9
- package/src/modules/ai_assistant/api/session-key/route.ts +4 -1
- package/src/modules/ai_assistant/api/settings/allowlist/route.ts +5 -2
- package/src/modules/ai_assistant/api/settings/route.ts +7 -4
- package/src/modules/ai_assistant/api/tools/execute/route.ts +4 -1
- package/src/modules/ai_assistant/api/tools/route.ts +4 -1
- package/src/modules/ai_assistant/api/usage/daily/route.ts +4 -1
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/route.ts +4 -1
- package/src/modules/ai_assistant/api/usage/sessions/route.ts +4 -1
- package/src/modules/ai_assistant/cli.ts +5 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-policy.mutation-override.test.ts +20 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-registry.test.ts +25 -5
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase0.test.ts +21 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-parity.test.ts +23 -3
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +20 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-tools.test.ts +22 -3
- package/src/modules/ai_assistant/lib/__tests__/ai-overrides.test.ts +23 -3
- package/src/modules/ai_assistant/lib/__tests__/attachment-parts.test.ts +25 -7
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +25 -5
- package/src/modules/ai_assistant/lib/__tests__/pending-action-cancel.test.ts +19 -1
- package/src/modules/ai_assistant/lib/__tests__/prepare-mutation.test.ts +22 -3
- package/src/modules/ai_assistant/lib/agent-policy.ts +8 -5
- package/src/modules/ai_assistant/lib/agent-registry.ts +12 -15
- package/src/modules/ai_assistant/lib/agent-runtime.ts +23 -46
- package/src/modules/ai_assistant/lib/agent-tools.ts +7 -11
- package/src/modules/ai_assistant/lib/ai-overrides.ts +6 -9
- package/src/modules/ai_assistant/lib/api-endpoint-index.ts +23 -22
- package/src/modules/ai_assistant/lib/attachment-parts.ts +14 -29
- package/src/modules/ai_assistant/lib/auth.ts +4 -1
- package/src/modules/ai_assistant/lib/codemode-tools.ts +17 -17
- package/src/modules/ai_assistant/lib/entity-index.ts +8 -5
- package/src/modules/ai_assistant/lib/generated-registry-loader.ts +4 -4
- package/src/modules/ai_assistant/lib/http-server.ts +46 -48
- package/src/modules/ai_assistant/lib/llm-bootstrap.ts +7 -13
- package/src/modules/ai_assistant/lib/mcp-client.ts +4 -1
- package/src/modules/ai_assistant/lib/mcp-dev-key-resolution.ts +4 -1
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +4 -1
- package/src/modules/ai_assistant/lib/mcp-server.ts +27 -19
- package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +4 -1
- package/src/modules/ai_assistant/lib/model-factory.ts +6 -3
- package/src/modules/ai_assistant/lib/opencode-client.ts +6 -3
- package/src/modules/ai_assistant/lib/opencode-handlers.ts +32 -12
- package/src/modules/ai_assistant/lib/pending-action-cancel.ts +4 -1
- package/src/modules/ai_assistant/lib/pending-action-executor.ts +4 -1
- package/src/modules/ai_assistant/lib/prepare-mutation.ts +5 -6
- package/src/modules/ai_assistant/lib/schema-utils.ts +4 -1
- package/src/modules/ai_assistant/lib/token-usage-recorder.ts +5 -8
- package/src/modules/ai_assistant/lib/tool-executor.ts +4 -1
- package/src/modules/ai_assistant/lib/tool-loader.ts +19 -25
- package/src/modules/ai_assistant/lib/tool-registry.ts +4 -1
- package/src/modules/ai_assistant/lib/tool-test-runner.ts +4 -1
- package/src/modules/ai_assistant/setup.ts +5 -8
- package/src/modules/ai_assistant/subscribers/conversation-shared-notify.ts +4 -1
- package/src/modules/ai_assistant/workers/ai-pending-action-cleanup.ts +8 -19
- package/src/modules/ai_assistant/workers/ai-token-usage-prune.ts +6 -11
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
} from "../../../../../lib/pending-action-executor.js";
|
|
17
18
|
import { AiAgentMutationPolicyOverrideRepository } from "../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository.js";
|
|
18
19
|
import { isKnownMutationPolicy } from "../../../../../lib/agent-policy.js";
|
|
20
|
+
const logger = createLogger("ai_assistant");
|
|
19
21
|
const REQUIRED_FEATURE = "ai_assistant.view";
|
|
20
22
|
const idParamSchema = z.object({
|
|
21
23
|
id: z.string().trim().min(1, "id must be a non-empty string").max(128, "id exceeds the maximum length of 128 characters")
|
|
@@ -158,7 +160,7 @@ async function POST(req, context) {
|
|
|
158
160
|
mutationResult: executed.executionResult
|
|
159
161
|
});
|
|
160
162
|
} catch (error) {
|
|
161
|
-
|
|
163
|
+
logger.error("AI Pending Action CONFIRM \u2014 Failure", { err: error });
|
|
162
164
|
return jsonError(
|
|
163
165
|
500,
|
|
164
166
|
error instanceof Error ? error.message : "Failed to confirm pending action.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/ai_assistant/api/ai/actions/%5Bid%5D/confirm/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\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 { AiPendingActionRepository } from '../../../../../data/repositories/AiPendingActionRepository'\nimport { hasRequiredFeatures } from '../../../../../lib/auth'\nimport { serializePendingActionForClient } from '../../../../../lib/pending-action-client'\nimport { getAgent, loadAgentRegistry } from '../../../../../lib/agent-registry'\nimport { toolRegistry } from '../../../../../lib/tool-registry'\nimport { loadAllModuleTools } from '../../../../../lib/tool-loader'\nimport type { AiToolDefinition } from '../../../../../lib/types'\nimport {\n runPendingActionRechecks,\n type PendingActionRecheckResult,\n} from '../../../../../lib/pending-action-recheck'\nimport {\n executePendingActionConfirm,\n type PendingActionExecuteContext,\n} from '../../../../../lib/pending-action-executor'\nimport { AiAgentMutationPolicyOverrideRepository } from '../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository'\nimport type { AiAgentMutationPolicy } from '../../../../../lib/ai-agent-definition'\nimport { isKnownMutationPolicy } from '../../../../../lib/agent-policy'\n\n/**\n * POST `/api/ai/actions/:id/confirm` \u2014 mutation approval gate confirm\n * endpoint (spec \u00A79.4, Step 5.8).\n *\n * Invariant: every check from spec \u00A79.4 is re-run on the server before the\n * tool handler executes. The original model-proposed tool arguments are\n * re-parsed through the CURRENT tool `inputSchema` so that a schema\n * migration between propose and confirm surfaces as `schema_drift`\n * (412) instead of silently passing through invalid data.\n *\n * Idempotency: calling this endpoint twice on the same pending-action id\n * returns the prior execution result without re-invoking the handler.\n */\n\nconst REQUIRED_FEATURE = 'ai_assistant.view'\n\nconst idParamSchema = z.object({\n id: z\n .string()\n .trim()\n .min(1, 'id must be a non-empty string')\n .max(128, 'id exceeds the maximum length of 128 characters'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Pending action (mutation approval gate) confirm',\n methods: {\n POST: {\n operationId: 'aiAssistantConfirmPendingAction',\n summary: 'Confirm an AI pending action, re-running every server-side check before execution.',\n description:\n 'Re-verifies the full contract from spec \u00A79.4 (status, expiry, agent registration, ' +\n 'required features, mutation policy, tool whitelist, attachment tenant scope, record ' +\n 'version, and schema drift), flips the pending-action state machine to `executing`, ' +\n 'invokes the wrapped tool handler, and persists `executionResult`. Idempotent: a ' +\n 'second call on a row already in `confirmed` state returns the prior result without ' +\n 're-executing the handler.',\n responses: [\n {\n status: 200,\n description: 'Confirmation complete; body includes the serialized pending action and the mutation result.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.view`, a required agent feature, tool whitelist, or accesses attachments outside their tenant.' },\n { status: 404, description: 'Pending action or agent not found in the caller scope.' },\n { status: 409, description: 'Pending action is not in `pending` status or has expired.' },\n { status: 412, description: 'Record version changed between propose and confirm, or the input schema no longer accepts the stored payload.' },\n { status: 500, description: 'Unexpected server failure during confirm.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\ninterface RouteContext {\n params: Promise<{ id: 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\nfunction fromRecheckFailure(result: PendingActionRecheckResult & { ok: false }): NextResponse {\n return jsonError(result.status, result.message, result.code, result.extra)\n}\n\nexport async function POST(req: NextRequest, context: RouteContext): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n\n const rawParams = await context.params\n const paramResult = idParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid pending action id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n const pendingActionId = paramResult.data.id\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n if (!hasRequiredFeatures([REQUIRED_FEATURE], acl.features, acl.isSuperAdmin, rbacService)) {\n return jsonError(403, `Caller lacks required feature \"${REQUIRED_FEATURE}\".`, 'forbidden')\n }\n\n if (!auth.tenantId) {\n return jsonError(\n 404,\n `No pending action \"${pendingActionId}\" accessible to the caller.`,\n 'pending_action_not_found',\n )\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiPendingActionRepository(em)\n const row = await repo.getById(pendingActionId, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n })\n if (!row) {\n return jsonError(\n 404,\n `No pending action \"${pendingActionId}\" accessible to the caller.`,\n 'pending_action_not_found',\n )\n }\n\n // Idempotency short-circuit \u2014 a prior confirm already ran.\n if (row.status === 'confirmed' || row.status === 'failed') {\n const executionResult = row.executionResult ?? null\n return NextResponse.json({\n ok: row.status === 'confirmed',\n pendingAction: serializePendingActionForClient(row),\n mutationResult: executionResult,\n })\n }\n\n await loadAgentRegistry()\n await loadAllModuleTools()\n const agent = getAgent(row.agentId)\n const tool = toolRegistry.getTool(row.toolName) as AiToolDefinition | undefined\n\n const policyOverrideRepo = new AiAgentMutationPolicyOverrideRepository(em)\n const overrideRow = await policyOverrideRepo.get(row.agentId, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n })\n const rawOverridePolicy = overrideRow?.mutationPolicy ?? null\n const mutationPolicyOverride: AiAgentMutationPolicy | null =\n rawOverridePolicy && isKnownMutationPolicy(rawOverridePolicy) ? rawOverridePolicy : null\n\n const recheckResult = await runPendingActionRechecks({\n action: row,\n agent,\n tool,\n ctx: {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n container,\n em,\n },\n mutationPolicyOverride,\n })\n if (!recheckResult.ok) {\n return fromRecheckFailure(recheckResult)\n }\n\n const executeCtx: PendingActionExecuteContext = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n container,\n }\n\n const executed = await executePendingActionConfirm({\n action: row,\n agent: agent!,\n tool: tool!,\n ctx: executeCtx,\n repo,\n failedRecords: recheckResult.failedRecords ?? null,\n })\n\n return NextResponse.json({\n ok: executed.ok,\n pendingAction: serializePendingActionForClient(executed.action),\n mutationResult: executed.executionResult,\n })\n } catch (error) {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAGlB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,iCAAiC;AAC1C,SAAS,2BAA2B;AACpC,SAAS,uCAAuC;AAChD,SAAS,UAAU,yBAAyB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AAEnC;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,+CAA+C;AAExD,SAAS,6BAA6B;
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport 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 { AiPendingActionRepository } from '../../../../../data/repositories/AiPendingActionRepository'\nimport { hasRequiredFeatures } from '../../../../../lib/auth'\nimport { serializePendingActionForClient } from '../../../../../lib/pending-action-client'\nimport { getAgent, loadAgentRegistry } from '../../../../../lib/agent-registry'\nimport { toolRegistry } from '../../../../../lib/tool-registry'\nimport { loadAllModuleTools } from '../../../../../lib/tool-loader'\nimport type { AiToolDefinition } from '../../../../../lib/types'\nimport {\n runPendingActionRechecks,\n type PendingActionRecheckResult,\n} from '../../../../../lib/pending-action-recheck'\nimport {\n executePendingActionConfirm,\n type PendingActionExecuteContext,\n} from '../../../../../lib/pending-action-executor'\nimport { AiAgentMutationPolicyOverrideRepository } from '../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository'\nimport type { AiAgentMutationPolicy } from '../../../../../lib/ai-agent-definition'\nimport { isKnownMutationPolicy } from '../../../../../lib/agent-policy'\n\nconst logger = createLogger('ai_assistant')\n\n/**\n * POST `/api/ai/actions/:id/confirm` \u2014 mutation approval gate confirm\n * endpoint (spec \u00A79.4, Step 5.8).\n *\n * Invariant: every check from spec \u00A79.4 is re-run on the server before the\n * tool handler executes. The original model-proposed tool arguments are\n * re-parsed through the CURRENT tool `inputSchema` so that a schema\n * migration between propose and confirm surfaces as `schema_drift`\n * (412) instead of silently passing through invalid data.\n *\n * Idempotency: calling this endpoint twice on the same pending-action id\n * returns the prior execution result without re-invoking the handler.\n */\n\nconst REQUIRED_FEATURE = 'ai_assistant.view'\n\nconst idParamSchema = z.object({\n id: z\n .string()\n .trim()\n .min(1, 'id must be a non-empty string')\n .max(128, 'id exceeds the maximum length of 128 characters'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Pending action (mutation approval gate) confirm',\n methods: {\n POST: {\n operationId: 'aiAssistantConfirmPendingAction',\n summary: 'Confirm an AI pending action, re-running every server-side check before execution.',\n description:\n 'Re-verifies the full contract from spec \u00A79.4 (status, expiry, agent registration, ' +\n 'required features, mutation policy, tool whitelist, attachment tenant scope, record ' +\n 'version, and schema drift), flips the pending-action state machine to `executing`, ' +\n 'invokes the wrapped tool handler, and persists `executionResult`. Idempotent: a ' +\n 'second call on a row already in `confirmed` state returns the prior result without ' +\n 're-executing the handler.',\n responses: [\n {\n status: 200,\n description: 'Confirmation complete; body includes the serialized pending action and the mutation result.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.view`, a required agent feature, tool whitelist, or accesses attachments outside their tenant.' },\n { status: 404, description: 'Pending action or agent not found in the caller scope.' },\n { status: 409, description: 'Pending action is not in `pending` status or has expired.' },\n { status: 412, description: 'Record version changed between propose and confirm, or the input schema no longer accepts the stored payload.' },\n { status: 500, description: 'Unexpected server failure during confirm.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\ninterface RouteContext {\n params: Promise<{ id: 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\nfunction fromRecheckFailure(result: PendingActionRecheckResult & { ok: false }): NextResponse {\n return jsonError(result.status, result.message, result.code, result.extra)\n}\n\nexport async function POST(req: NextRequest, context: RouteContext): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n\n const rawParams = await context.params\n const paramResult = idParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid pending action id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n const pendingActionId = paramResult.data.id\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n if (!hasRequiredFeatures([REQUIRED_FEATURE], acl.features, acl.isSuperAdmin, rbacService)) {\n return jsonError(403, `Caller lacks required feature \"${REQUIRED_FEATURE}\".`, 'forbidden')\n }\n\n if (!auth.tenantId) {\n return jsonError(\n 404,\n `No pending action \"${pendingActionId}\" accessible to the caller.`,\n 'pending_action_not_found',\n )\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiPendingActionRepository(em)\n const row = await repo.getById(pendingActionId, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n })\n if (!row) {\n return jsonError(\n 404,\n `No pending action \"${pendingActionId}\" accessible to the caller.`,\n 'pending_action_not_found',\n )\n }\n\n // Idempotency short-circuit \u2014 a prior confirm already ran.\n if (row.status === 'confirmed' || row.status === 'failed') {\n const executionResult = row.executionResult ?? null\n return NextResponse.json({\n ok: row.status === 'confirmed',\n pendingAction: serializePendingActionForClient(row),\n mutationResult: executionResult,\n })\n }\n\n await loadAgentRegistry()\n await loadAllModuleTools()\n const agent = getAgent(row.agentId)\n const tool = toolRegistry.getTool(row.toolName) as AiToolDefinition | undefined\n\n const policyOverrideRepo = new AiAgentMutationPolicyOverrideRepository(em)\n const overrideRow = await policyOverrideRepo.get(row.agentId, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n })\n const rawOverridePolicy = overrideRow?.mutationPolicy ?? null\n const mutationPolicyOverride: AiAgentMutationPolicy | null =\n rawOverridePolicy && isKnownMutationPolicy(rawOverridePolicy) ? rawOverridePolicy : null\n\n const recheckResult = await runPendingActionRechecks({\n action: row,\n agent,\n tool,\n ctx: {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n container,\n em,\n },\n mutationPolicyOverride,\n })\n if (!recheckResult.ok) {\n return fromRecheckFailure(recheckResult)\n }\n\n const executeCtx: PendingActionExecuteContext = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n container,\n }\n\n const executed = await executePendingActionConfirm({\n action: row,\n agent: agent!,\n tool: tool!,\n ctx: executeCtx,\n repo,\n failedRecords: recheckResult.failedRecords ?? null,\n })\n\n return NextResponse.json({\n ok: executed.ok,\n pendingAction: serializePendingActionForClient(executed.action),\n mutationResult: executed.executionResult,\n })\n } catch (error) {\n logger.error('AI Pending Action CONFIRM \u2014 Failure', { err: error })\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to confirm pending action.',\n 'confirm_internal_error',\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
@@ -5,6 +6,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
5
6
|
import { AiPendingActionRepository } from "../../../../data/repositories/AiPendingActionRepository.js";
|
|
6
7
|
import { hasRequiredFeatures } from "../../../../lib/auth.js";
|
|
7
8
|
import { serializePendingActionForClient } from "../../../../lib/pending-action-client.js";
|
|
9
|
+
const logger = createLogger("ai_assistant");
|
|
8
10
|
const REQUIRED_FEATURE = "ai_assistant.view";
|
|
9
11
|
const idParamSchema = z.object({
|
|
10
12
|
id: z.string().trim().min(1, "id must be a non-empty string").max(128, "id exceeds the maximum length of 128 characters")
|
|
@@ -85,7 +87,7 @@ async function GET(req, context) {
|
|
|
85
87
|
}
|
|
86
88
|
return NextResponse.json(serializePendingActionForClient(row));
|
|
87
89
|
} catch (error) {
|
|
88
|
-
|
|
90
|
+
logger.error("AI Pending Action GET \u2014 Failure", { err: error });
|
|
89
91
|
return jsonError(
|
|
90
92
|
500,
|
|
91
93
|
error instanceof Error ? error.message : "Failed to load pending action.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/ai_assistant/api/ai/actions/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\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 { AiPendingActionRepository } from '../../../../data/repositories/AiPendingActionRepository'\nimport { hasRequiredFeatures } from '../../../../lib/auth'\nimport { serializePendingActionForClient } from '../../../../lib/pending-action-client'\n\n/**\n * GET `/api/ai/actions/:id` \u2014 reconnect/polling endpoint for the Phase 3 WS-C\n * mutation approval gate (spec \u00A79, Step 5.7).\n *\n * When the chat UI bounces (reload, tab switch, SSE reconnect) it carries a\n * `pendingActionId` from an earlier `mutation-preview-card` UI part and needs\n * to re-hydrate the pending row's current state. This route is the authoritative\n * read-side; the confirm/cancel routes (Steps 5.8 / 5.9) use the same\n * whitelist serializer so the UI always sees the same shape.\n */\n\nconst REQUIRED_FEATURE = 'ai_assistant.view'\n\nconst idParamSchema = z.object({\n id: z\n .string()\n .trim()\n .min(1, 'id must be a non-empty string')\n .max(128, 'id exceeds the maximum length of 128 characters'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Pending action (mutation approval gate) read-side',\n methods: {\n GET: {\n operationId: 'aiAssistantGetPendingAction',\n summary: 'Fetch the current state of an AI pending action by id.',\n description:\n 'Returns the tenant-scoped {@link AiPendingAction} addressed by `:id`. Powers the ' +\n 'chat UI reconnect/polling path: after a page reload or SSE reconnect the client ' +\n 'carries the `pendingActionId` from an earlier `mutation-preview-card` UI part and ' +\n 'calls this route to re-hydrate the card. Server-internal fields (`normalizedInput`, ' +\n '`createdByUserId`, `idempotencyKey`) are stripped by a whitelist serializer. ' +\n 'Enforces tenant/org scoping via the repository.',\n responses: [\n {\n status: 200,\n description:\n 'Serialized pending action. Never includes normalizedInput, createdByUserId, or idempotencyKey.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks the `ai_assistant.view` feature.' },\n { status: 404, description: 'No pending action with the given id is accessible to the caller.' },\n { status: 500, description: 'Internal runtime failure.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\ninterface RouteContext {\n params: Promise<{ id: 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\nexport async function GET(req: NextRequest, context: RouteContext): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n\n const rawParams = await context.params\n const paramResult = idParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid pending action id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n const pendingActionId = paramResult.data.id\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n if (!hasRequiredFeatures([REQUIRED_FEATURE], acl.features, acl.isSuperAdmin, rbacService)) {\n return jsonError(403, `Caller lacks required feature \"${REQUIRED_FEATURE}\".`, 'forbidden')\n }\n\n if (!auth.tenantId) {\n return jsonError(\n 404,\n `No pending action \"${pendingActionId}\" accessible to the caller.`,\n 'pending_action_not_found',\n )\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiPendingActionRepository(em)\n const row = await repo.getById(pendingActionId, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n })\n\n if (!row) {\n return jsonError(\n 404,\n `No pending action \"${pendingActionId}\" accessible to the caller.`,\n 'pending_action_not_found',\n )\n }\n\n return NextResponse.json(serializePendingActionForClient(row))\n } catch (error) {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAGlB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,iCAAiC;AAC1C,SAAS,2BAA2B;AACpC,SAAS,uCAAuC;
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport 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 { AiPendingActionRepository } from '../../../../data/repositories/AiPendingActionRepository'\nimport { hasRequiredFeatures } from '../../../../lib/auth'\nimport { serializePendingActionForClient } from '../../../../lib/pending-action-client'\n\nconst logger = createLogger('ai_assistant')\n\n/**\n * GET `/api/ai/actions/:id` \u2014 reconnect/polling endpoint for the Phase 3 WS-C\n * mutation approval gate (spec \u00A79, Step 5.7).\n *\n * When the chat UI bounces (reload, tab switch, SSE reconnect) it carries a\n * `pendingActionId` from an earlier `mutation-preview-card` UI part and needs\n * to re-hydrate the pending row's current state. This route is the authoritative\n * read-side; the confirm/cancel routes (Steps 5.8 / 5.9) use the same\n * whitelist serializer so the UI always sees the same shape.\n */\n\nconst REQUIRED_FEATURE = 'ai_assistant.view'\n\nconst idParamSchema = z.object({\n id: z\n .string()\n .trim()\n .min(1, 'id must be a non-empty string')\n .max(128, 'id exceeds the maximum length of 128 characters'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Pending action (mutation approval gate) read-side',\n methods: {\n GET: {\n operationId: 'aiAssistantGetPendingAction',\n summary: 'Fetch the current state of an AI pending action by id.',\n description:\n 'Returns the tenant-scoped {@link AiPendingAction} addressed by `:id`. Powers the ' +\n 'chat UI reconnect/polling path: after a page reload or SSE reconnect the client ' +\n 'carries the `pendingActionId` from an earlier `mutation-preview-card` UI part and ' +\n 'calls this route to re-hydrate the card. Server-internal fields (`normalizedInput`, ' +\n '`createdByUserId`, `idempotencyKey`) are stripped by a whitelist serializer. ' +\n 'Enforces tenant/org scoping via the repository.',\n responses: [\n {\n status: 200,\n description:\n 'Serialized pending action. Never includes normalizedInput, createdByUserId, or idempotencyKey.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks the `ai_assistant.view` feature.' },\n { status: 404, description: 'No pending action with the given id is accessible to the caller.' },\n { status: 500, description: 'Internal runtime failure.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\ninterface RouteContext {\n params: Promise<{ id: 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\nexport async function GET(req: NextRequest, context: RouteContext): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n\n const rawParams = await context.params\n const paramResult = idParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid pending action id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n const pendingActionId = paramResult.data.id\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n if (!hasRequiredFeatures([REQUIRED_FEATURE], acl.features, acl.isSuperAdmin, rbacService)) {\n return jsonError(403, `Caller lacks required feature \"${REQUIRED_FEATURE}\".`, 'forbidden')\n }\n\n if (!auth.tenantId) {\n return jsonError(\n 404,\n `No pending action \"${pendingActionId}\" accessible to the caller.`,\n 'pending_action_not_found',\n )\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiPendingActionRepository(em)\n const row = await repo.getById(pendingActionId, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId ?? null,\n userId: auth.sub,\n })\n\n if (!row) {\n return jsonError(\n 404,\n `No pending action \"${pendingActionId}\" accessible to the caller.`,\n 'pending_action_not_found',\n )\n }\n\n return NextResponse.json(serializePendingActionForClient(row))\n } catch (error) {\n logger.error('AI Pending Action GET \u2014 Failure', { err: error })\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to load pending action.',\n 'internal_error',\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
AiAgentRuntimeOverrideRepository,
|
|
9
10
|
AiAgentRuntimeOverrideValidationError
|
|
10
11
|
} from "../../../../../data/repositories/AiAgentRuntimeOverrideRepository.js";
|
|
12
|
+
const logger = createLogger("ai_assistant");
|
|
11
13
|
const agentIdPattern = /^[a-z0-9_]+\.[a-z0-9_]+$/;
|
|
12
14
|
const agentIdParamSchema = z.object({
|
|
13
15
|
agentId: z.string().regex(agentIdPattern, 'agentId must match "<module>.<agent>" (lowercase, digits, underscores only)')
|
|
@@ -170,7 +172,7 @@ async function GET(req, context) {
|
|
|
170
172
|
override: hasLoopData ? serializeLoopOverride(row) : null
|
|
171
173
|
});
|
|
172
174
|
} catch (error) {
|
|
173
|
-
|
|
175
|
+
logger.error("AI Loop Override GET \u2014 Failure", { err: error });
|
|
174
176
|
return jsonError(
|
|
175
177
|
500,
|
|
176
178
|
error instanceof Error ? error.message : "Failed to load loop override.",
|
|
@@ -242,7 +244,7 @@ async function PUT(req, context) {
|
|
|
242
244
|
if (error instanceof AiAgentRuntimeOverrideValidationError) {
|
|
243
245
|
return jsonError(400, error.message, error.code);
|
|
244
246
|
}
|
|
245
|
-
|
|
247
|
+
logger.error("AI Loop Override PUT \u2014 Failure", { err: error });
|
|
246
248
|
return jsonError(
|
|
247
249
|
500,
|
|
248
250
|
error instanceof Error ? error.message : "Failed to save loop override.",
|
|
@@ -298,7 +300,7 @@ async function DELETE(req, context) {
|
|
|
298
300
|
);
|
|
299
301
|
return NextResponse.json({ ok: true, agentId: agent.id, cleared: true });
|
|
300
302
|
} catch (error) {
|
|
301
|
-
|
|
303
|
+
logger.error("AI Loop Override DELETE \u2014 Failure", { err: error });
|
|
302
304
|
return jsonError(
|
|
303
305
|
500,
|
|
304
306
|
error instanceof Error ? error.message : "Failed to clear loop override.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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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
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { 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 logger = createLogger('ai_assistant')\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 logger.error('AI Loop Override GET \u2014 Failure', { err: 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 logger.error('AI Loop Override PUT \u2014 Failure', { err: 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 logger.error('AI Loop Override DELETE \u2014 Failure', { err: 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": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,SAAS,UAAU,yBAAyB;AAC5C,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAGP,MAAM,SAAS,aAAa,cAAc;AAE1C,MAAM,iBAAiB;AAEvB,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,SAAS,EACN,OAAO,EACP,MAAM,gBAAgB,6EAA6E;AACxG,CAAC;AAED,MAAM,4BAA4B,EAAE,OAAO;AAAA,EACzC,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,EAC9C,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAI,EAAE,SAAS,EAAE,SAAS;AAAA,EACpE,kBAAkB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAK,EAAE,SAAS,EAAE,SAAS;AAAA,EACzE,oBAAoB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,IAAI,IAAS,EAAE,SAAS,EAAE,SAAS;AAAA,EACjF,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAU,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3E,kBAAkB,EACf;AAAA,IACC,EAAE,mBAAmB,QAAQ;AAAA,MAC3B,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,WAAW,GAAG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,MACzE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,aAAa,GAAG,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AAAA,IAC1E,CAAC;AAAA,EACH,EACC,SAAS,EACT,SAAS;AAAA,EACZ,qBAAqB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS;AACtE,CAAC;AAED,MAAM,eAAe;AACrB,MAAM,iBAAiB;AAEhB,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SACE;AAAA,MACF,aACE;AAAA,MAEF,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,QAChD,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,QACtD,EAAE,QAAQ,KAAK,aAAa,oCAAoC;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,MAClD;AAAA,IACF;AAAA,IACA,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aACE;AAAA,MAGF,aAAa;AAAA,QACX,aAAa;AAAA,QACb,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,wCAAwC;AAAA,QACpE,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,QACtD,EAAE,QAAQ,KAAK,aAAa,+CAA+C;AAAA,QAC3E,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,MAClD;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aACE;AAAA,MAGF,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,QAChD,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,QACtD,EAAE,QAAQ,KAAK,aAAa,+CAA+C;AAAA,QAC3E,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,YAAY,EAAE;AAAA,EAC1D,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,cAAc,EAAE;AAAA,EAC5D,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,cAAc,EAAE;AACjE;AAMA,SAAS,UACP,QACA,SACA,MACA,OACc;AACd,SAAO,aAAa,KAAK,EAAE,OAAO,SAAS,MAAM,GAAI,SAAS,CAAC,EAAG,GAAG,EAAE,OAAO,CAAC;AACjF;AAWA,eAAe,qBACb,KACA,iBACsC;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,UAAU,KAAK,gBAAgB,iBAAiB;AAAA,EACzD;AACA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,cAAc,UAAU,QAAqB,aAAa;AAChE,QAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK;AAAA,IAC9C,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,EACvB,CAAC;AACD,MAAI,CAAC,oBAAoB,CAAC,eAAe,GAAG,IAAI,UAAU,IAAI,cAAc,WAAW,GAAG;AACxF,WAAO,UAAU,KAAK,kCAAkC,eAAe,MAAM,WAAW;AAAA,EAC1F;AACA,SAAO;AAAA,IACL,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,KAAK,SAAS;AAAA,IAC9B,QAAQ,KAAK;AAAA,IACb,cAAc,IAAI;AAAA,IAClB,UAAU,IAAI;AAAA,IACd;AAAA,EACF;AACF;AAEA,SAAS,sBAAsB,KAA6B;AAC1D,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,SAAS,IAAI,WAAW;AAAA,IACxB,cAAc,IAAI,gBAAgB;AAAA,IAClC,cAAc,IAAI,gBAAgB;AAAA,IAClC,kBAAkB,IAAI,oBAAoB;AAAA,IAC1C,oBAAoB,IAAI,sBAAsB;AAAA,IAC9C,eAAe,IAAI,iBAAiB;AAAA,IACpC,kBAAkB,IAAI,oBAAoB;AAAA,IAC1C,qBAAqB,IAAI,uBAAuB;AAAA,IAChD,WAAW,IAAI,WAAW,cAAc,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtE;AACF;AAEA,eAAsB,IAAI,KAAkB,SAA0C;AACpF,QAAM,aAAa,MAAM,qBAAqB,KAAK,YAAY;AAC/D,MAAI,sBAAsB,aAAc,QAAO;AAE/C,QAAM,YAAY,MAAM,QAAQ;AAChC,QAAM,cAAc,mBAAmB,UAAU,SAAS;AAC1D,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,UAAU,KAAK,qBAAqB,oBAAoB;AAAA,MAC7D,QAAQ,YAAY,MAAM;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,MAAI;AACF,UAAM,kBAAkB;AACxB,UAAM,QAAQ,SAAS,YAAY,KAAK,OAAO;AAC/C,QAAI,CAAC,OAAO;AACV,aAAO,UAAU,KAAK,kBAAkB,YAAY,KAAK,OAAO,MAAM,eAAe;AAAA,IACvF;AAEA,QAAI,CAAC,WAAW,UAAU;AACxB,aAAO,aAAa,KAAK,EAAE,SAAS,MAAM,IAAI,UAAU,KAAK,CAAC;AAAA,IAChE;AAEA,UAAM,KAAK,WAAW,UAAU,QAAuB,IAAI;AAC3D,UAAM,OAAO,IAAI,iCAAiC,EAAE;AACpD,UAAM,MAAM,MAAM,KAAK,WAAW;AAAA,MAChC,UAAU,WAAW;AAAA,MACrB,gBAAgB,WAAW;AAAA,MAC3B,SAAS,MAAM;AAAA,IACjB,CAAC;AAED,UAAM,cACJ,QAAQ,SACP,IAAI,iBAAiB,QACpB,IAAI,iBAAiB,QACrB,IAAI,qBAAqB,QACzB,IAAI,uBAAuB,QAC3B,IAAI,kBAAkB,QACtB,IAAI,qBAAqB,QACzB,IAAI,wBAAwB;AAEhC,WAAO,aAAa,KAAK;AAAA,MACvB,SAAS,MAAM;AAAA,MACf,UAAU,cAAc,sBAAsB,GAAI,IAAI;AAAA,IACxD,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,MAAM,uCAAkC,EAAE,KAAK,MAAM,CAAC;AAC7D,WAAO;AAAA,MACL;AAAA,MACA,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,IAAI,KAAkB,SAA0C;AACpF,QAAM,aAAa,MAAM,qBAAqB,KAAK,cAAc;AACjE,MAAI,sBAAsB,aAAc,QAAO;AAE/C,QAAM,YAAY,MAAM,QAAQ;AAChC,QAAM,cAAc,mBAAmB,UAAU,SAAS;AAC1D,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,UAAU,KAAK,qBAAqB,oBAAoB;AAAA,MAC7D,QAAQ,YAAY,MAAM;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,MAAI;AACJ,MAAI;AACF,iBAAa,MAAM,IAAI,KAAK;AAAA,EAC9B,QAAQ;AACN,WAAO,UAAU,KAAK,oCAAoC,kBAAkB;AAAA,EAC9E;AAEA,QAAM,aAAa,0BAA0B,UAAU,UAAU;AACjE,MAAI,CAAC,WAAW,SAAS;AACvB,WAAO,UAAU,KAAK,yBAAyB,oBAAoB;AAAA,MACjE,QAAQ,WAAW,MAAM;AAAA,IAC3B,CAAC;AAAA,EACH;AAEA,MAAI;AACF,UAAM,kBAAkB;AACxB,UAAM,QAAQ,SAAS,YAAY,KAAK,OAAO;AAC/C,QAAI,CAAC,OAAO;AACV,aAAO,UAAU,KAAK,kBAAkB,YAAY,KAAK,OAAO,MAAM,eAAe;AAAA,IACvF;AAEA,QAAI,CAAC,WAAW,UAAU;AACxB,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,WAAW,UAAU,QAAuB,IAAI;AAC3D,UAAM,OAAO,IAAI,iCAAiC,EAAE;AACpD,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB;AAAA,QACE,SAAS,MAAM;AAAA,QACf,mBAAmB,MAAM;AAAA,QACzB,cAAc,WAAW,KAAK,gBAAgB;AAAA,QAC9C,cAAc,WAAW,KAAK,gBAAgB;AAAA,QAC9C,kBAAkB,WAAW,KAAK,oBAAoB;AAAA,QACtD,oBAAoB,WAAW,KAAK,sBAAsB;AAAA,QAC1D,eAAe,WAAW,KAAK,iBAAiB;AAAA,QAChD,kBAAkB,WAAW,KAAK,oBAAoB;AAAA,QACtD,qBAAqB,WAAW,KAAK,uBAAuB;AAAA,MAC9D;AAAA,MACA;AAAA,QACE,UAAU,WAAW;AAAA,QACrB,gBAAgB,WAAW;AAAA,QAC3B,QAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB,IAAI;AAAA,MACJ,SAAS,MAAM;AAAA,MACf,UAAU,sBAAsB,GAAG;AAAA,IACrC,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,iBAAiB,uCAAuC;AAC1D,aAAO,UAAU,KAAK,MAAM,SAAS,MAAM,IAAI;AAAA,IACjD;AACA,WAAO,MAAM,uCAAkC,EAAE,KAAK,MAAM,CAAC;AAC7D,WAAO;AAAA,MACL;AAAA,MACA,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,OAAO,KAAkB,SAA0C;AACvF,QAAM,aAAa,MAAM,qBAAqB,KAAK,cAAc;AACjE,MAAI,sBAAsB,aAAc,QAAO;AAE/C,QAAM,YAAY,MAAM,QAAQ;AAChC,QAAM,cAAc,mBAAmB,UAAU,SAAS;AAC1D,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,UAAU,KAAK,qBAAqB,oBAAoB;AAAA,MAC7D,QAAQ,YAAY,MAAM;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,MAAI;AACF,UAAM,kBAAkB;AACxB,UAAM,QAAQ,SAAS,YAAY,KAAK,OAAO;AAC/C,QAAI,CAAC,OAAO;AACV,aAAO,UAAU,KAAK,kBAAkB,YAAY,KAAK,OAAO,MAAM,eAAe;AAAA,IACvF;AAEA,QAAI,CAAC,WAAW,UAAU;AACxB,aAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,SAAS,MAAM,CAAC;AAAA,IAC1E;AAEA,UAAM,KAAK,WAAW,UAAU,QAAuB,IAAI;AAC3D,UAAM,OAAO,IAAI,iCAAiC,EAAE;AAEpD,UAAM,WAAW,MAAM,KAAK,WAAW;AAAA,MACrC,UAAU,WAAW;AAAA,MACrB,gBAAgB,WAAW;AAAA,MAC3B,SAAS,MAAM;AAAA,IACjB,CAAC;AAED,QAAI,CAAC,UAAU;AACb,aAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,SAAS,MAAM,CAAC;AAAA,IAC1E;AAEA,UAAM,KAAK;AAAA,MACT;AAAA,QACE,SAAS,MAAM;AAAA,QACf,cAAc;AAAA,QACd,cAAc;AAAA,QACd,kBAAkB;AAAA,QAClB,oBAAoB;AAAA,QACpB,eAAe;AAAA,QACf,kBAAkB;AAAA,QAClB,qBAAqB;AAAA,MACvB;AAAA,MACA;AAAA,QACE,UAAU,WAAW;AAAA,QACrB,gBAAgB,WAAW;AAAA,QAC3B,QAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AAEA,WAAO,aAAa,KAAK,EAAE,IAAI,MAAM,SAAS,MAAM,IAAI,SAAS,KAAK,CAAC;AAAA,EACzE,SAAS,OAAO;AACd,WAAO,MAAM,0CAAqC,EAAE,KAAK,MAAM,CAAC;AAChE,WAAO;AAAA,MACL;AAAA,MACA,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
} from "../../../../../lib/model-allowlist.js";
|
|
17
18
|
import { AiTenantModelAllowlistRepository } from "../../../../../data/repositories/AiTenantModelAllowlistRepository.js";
|
|
18
19
|
import { AiAgentRuntimeOverrideRepository } from "../../../../../data/repositories/AiAgentRuntimeOverrideRepository.js";
|
|
20
|
+
const logger = createLogger("ai_assistant");
|
|
19
21
|
function modelsForPicker(provider, allowedModelIds) {
|
|
20
22
|
if (provider.defaultModels.length > 0) return provider.defaultModels;
|
|
21
23
|
return (allowedModelIds ?? []).map((id) => ({ id, name: id }));
|
|
@@ -122,7 +124,7 @@ async function GET(req, { params }) {
|
|
|
122
124
|
} : null;
|
|
123
125
|
agentRuntimeOverrideAllowlist = hasAllowlistSnapshotRestrictions(tenantAgentAllowlist) ? tenantAgentAllowlist : null;
|
|
124
126
|
} catch (snapshotError) {
|
|
125
|
-
|
|
127
|
+
logger.error("AI Agents Models \u2014 Failed to load tenant allowlist", { err: snapshotError });
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
const factory = createModelFactory(container);
|
|
@@ -183,7 +185,7 @@ async function GET(req, { params }) {
|
|
|
183
185
|
providers
|
|
184
186
|
});
|
|
185
187
|
} catch (error) {
|
|
186
|
-
|
|
188
|
+
logger.error("AI Agents Models \u2014 GET error", { err: error });
|
|
187
189
|
return NextResponse.json({ error: "Failed to resolve agent models." }, { status: 500 });
|
|
188
190
|
}
|
|
189
191
|
}
|
|
@@ -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, 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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
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport 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\nconst logger = createLogger('ai_assistant')\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 logger.error('AI Agents Models \u2014 Failed to load tenant allowlist', { err: 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 logger.error('AI Agents Models \u2014 GET error', { err: 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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { NextResponse } from "next/server";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
isMutationPolicyEscalation
|
|
10
11
|
} from "../../../../../lib/agent-policy.js";
|
|
11
12
|
import { AiAgentMutationPolicyOverrideRepository } from "../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository.js";
|
|
13
|
+
const logger = createLogger("ai_assistant");
|
|
12
14
|
const agentIdPattern = /^[a-z0-9_]+\.[a-z0-9_]+$/;
|
|
13
15
|
const agentIdParamSchema = z.object({
|
|
14
16
|
agentId: z.string().regex(agentIdPattern, 'agentId must match "<module>.<agent>" (lowercase, digits, underscores only)')
|
|
@@ -172,7 +174,7 @@ async function GET(req, context) {
|
|
|
172
174
|
override: current ? serializeOverride(current) : null
|
|
173
175
|
});
|
|
174
176
|
} catch (error) {
|
|
175
|
-
|
|
177
|
+
logger.error("AI Mutation Policy GET \u2014 Failure", { err: error });
|
|
176
178
|
return jsonError(
|
|
177
179
|
500,
|
|
178
180
|
error instanceof Error ? error.message : "Failed to load mutationPolicy override.",
|
|
@@ -245,7 +247,7 @@ async function POST(req, context) {
|
|
|
245
247
|
override: serializeOverride(saved)
|
|
246
248
|
});
|
|
247
249
|
} catch (error) {
|
|
248
|
-
|
|
250
|
+
logger.error("AI Mutation Policy POST \u2014 Failure", { err: error });
|
|
249
251
|
return jsonError(
|
|
250
252
|
500,
|
|
251
253
|
error instanceof Error ? error.message : "Failed to save mutationPolicy override.",
|
|
@@ -293,7 +295,7 @@ async function DELETE(req, context) {
|
|
|
293
295
|
cleared
|
|
294
296
|
});
|
|
295
297
|
} catch (error) {
|
|
296
|
-
|
|
298
|
+
logger.error("AI Mutation Policy DELETE \u2014 Failure", { err: error });
|
|
297
299
|
return jsonError(
|
|
298
300
|
500,
|
|
299
301
|
error instanceof Error ? error.message : "Failed to clear mutationPolicy override.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/ai_assistant/api/ai/agents/%5BagentId%5D/mutation-policy/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 isKnownMutationPolicy,\n isMutationPolicyEscalation,\n} from '../../../../../lib/agent-policy'\nimport { AiAgentMutationPolicyOverrideRepository } from '../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository'\nimport type { AiAgentMutationPolicyOverride } from '../../../../../data/entities'\nimport type {\n AiAgentDefinition,\n AiAgentMutationPolicy,\n} from '../../../../../lib/ai-agent-definition'\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 mutationPolicySchema = z.enum([\n 'read-only',\n 'confirm-required',\n 'destructive-confirm-required',\n])\n\nconst mutationPolicyRequestSchema = z.object({\n mutationPolicy: mutationPolicySchema,\n notes: z.string().max(2000).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 mutationPolicy override for an AI agent',\n methods: {\n GET: {\n operationId: 'aiAssistantGetMutationPolicyOverride',\n summary:\n 'Read the effective mutationPolicy for an agent \u2014 code-declared value plus any tenant override.',\n description:\n 'Returns `{ agentId, codeDeclared, override }` where `codeDeclared` is the agent\\'s ' +\n 'compiled-in `mutationPolicy` and `override` is the persisted tenant-scoped override ' +\n '(or `null`). Requires `ai_assistant.view`.',\n responses: [\n {\n status: 200,\n description: 'Effective mutationPolicy 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 POST: {\n operationId: 'aiAssistantSaveMutationPolicyOverride',\n summary:\n 'Set (or replace) the tenant-scoped mutationPolicy override for this agent.',\n description:\n 'Body: `{ mutationPolicy: \"read-only\" | \"confirm-required\" | \"destructive-confirm-required\", notes? }`. ' +\n 'The override MUST NOT escalate beyond the agent\\'s code-declared policy. Escalation attempts ' +\n 'are rejected with 400 + `code: \"escalation_not_allowed\"`. Requires `ai_assistant.settings.manage`.',\n requestBody: {\n contentType: 'application/json',\n description: 'Body: `{ mutationPolicy, notes? }`.',\n schema: mutationPolicyRequestSchema,\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, malformed body, or escalation attempt.' },\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: 'aiAssistantClearMutationPolicyOverride',\n summary: 'Remove the tenant-scoped mutationPolicy override for this agent.',\n description:\n 'Deletes the override row if it exists; subsequent calls fall back to the agent\\'s ' +\n 'code-declared policy. Idempotent \u2014 returns 200 even when no override exists. Requires ' +\n '`ai_assistant.settings.manage`.',\n responses: [\n {\n status: 200,\n description: '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 POST: { 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 rbacService: RbacService\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 rbacService,\n }\n}\n\nfunction serializeOverride(row: AiAgentMutationPolicyOverride) {\n return {\n id: row.id,\n agentId: row.agentId,\n mutationPolicy: row.mutationPolicy,\n notes: row.notes ?? null,\n createdByUserId: row.createdByUserId ?? null,\n createdAt: row.createdAt?.toISOString?.() ?? new Date().toISOString(),\n updatedAt: row.updatedAt?.toISOString?.() ?? new Date().toISOString(),\n }\n}\n\nfunction codeDeclaredPolicy(agent: AiAgentDefinition): AiAgentMutationPolicy {\n const declared = agent.mutationPolicy\n return declared && isKnownMutationPolicy(declared) ? declared : 'read-only'\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 const declared = codeDeclaredPolicy(agent)\n if (!authResult.tenantId) {\n return NextResponse.json({\n agentId: agent.id,\n codeDeclared: declared,\n override: null,\n })\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentMutationPolicyOverrideRepository(em)\n const current = await repo.get(agent.id, {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n })\n\n return NextResponse.json({\n agentId: agent.id,\n codeDeclared: declared,\n override: current ? serializeOverride(current) : null,\n })\n } catch (error) {\n console.error('[AI Mutation Policy GET] Failure:', error)\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to load mutationPolicy override.',\n 'internal_error',\n )\n }\n}\n\nexport async function POST(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 = mutationPolicyRequestSchema.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 const declared = codeDeclaredPolicy(agent)\n if (isMutationPolicyEscalation(declared, bodyResult.data.mutationPolicy)) {\n return jsonError(\n 400,\n `Cannot set mutationPolicy=\"${bodyResult.data.mutationPolicy}\" for agent \"${agent.id}\": ` +\n `the agent\\'s code-declared policy is \"${declared}\". Upgrading beyond the declared ` +\n `policy is a code-level change, not a configuration change.`,\n 'escalation_not_allowed',\n { codeDeclared: declared, requested: bodyResult.data.mutationPolicy },\n )\n }\n\n if (!authResult.tenantId) {\n return jsonError(\n 400,\n 'Caller has no tenant context; cannot persist tenant-scoped mutationPolicy override.',\n 'tenant_required',\n )\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentMutationPolicyOverrideRepository(em)\n const saved = await repo.set(\n {\n agentId: agent.id,\n mutationPolicy: bodyResult.data.mutationPolicy,\n notes: bodyResult.data.notes ?? 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 codeDeclared: declared,\n override: serializeOverride(saved),\n })\n } catch (error) {\n console.error('[AI Mutation Policy POST] Failure:', error)\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to save mutationPolicy 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 const declared = codeDeclaredPolicy(agent)\n if (!authResult.tenantId) {\n return NextResponse.json({\n ok: true,\n agentId: agent.id,\n codeDeclared: declared,\n override: null,\n cleared: false,\n })\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentMutationPolicyOverrideRepository(em)\n const cleared = await repo.clear(agent.id, {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n })\n\n return NextResponse.json({\n ok: true,\n agentId: agent.id,\n codeDeclared: declared,\n override: null,\n cleared,\n })\n } catch (error) {\n console.error('[AI Mutation Policy DELETE] Failure:', error)\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to clear mutationPolicy override.',\n 'internal_error',\n )\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,SAAS,UAAU,yBAAyB;AAC5C,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,+CAA+C;AAOxD,MAAM,iBAAiB;AAEvB,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,SAAS,EACN,OAAO,EACP,MAAM,gBAAgB,6EAA6E;AACxG,CAAC;AAED,MAAM,uBAAuB,EAAE,KAAK;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAC3C,gBAAgB;AAAA,EAChB,OAAO,EAAE,OAAO,EAAE,IAAI,GAAI,EAAE,SAAS;AACvC,CAAC;AAED,MAAM,eAAe;AACrB,MAAM,iBAAiB;AAEhB,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SACE;AAAA,MACF,aACE;AAAA,MAGF,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,QAChD,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,QACtD,EAAE,QAAQ,KAAK,aAAa,oCAAoC;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,MAClD;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,aAAa;AAAA,MACb,SACE;AAAA,MACF,aACE;AAAA,MAGF,aAAa;AAAA,QACX,aAAa;AAAA,QACb,aAAa;AAAA,QACb,QAAQ;AAAA,MACV;AAAA,MACA,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,2DAA2D;AAAA,QACvF,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,QACtD,EAAE,QAAQ,KAAK,aAAa,+CAA+C;AAAA,QAC3E,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,MAClD;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aACE;AAAA,MAGF,WAAW;AAAA,QACT;AAAA,UACE,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,WAAW;AAAA,QACb;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,QAChD,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,QACtD,EAAE,QAAQ,KAAK,aAAa,+CAA+C;AAAA,QAC3E,EAAE,QAAQ,KAAK,aAAa,oBAAoB;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,YAAY,EAAE;AAAA,EAC1D,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,cAAc,EAAE;AAAA,EAC7D,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,cAAc,EAAE;AACjE;AAMA,SAAS,UACP,QACA,SACA,MACA,OACc;AACd,SAAO,aAAa,KAAK,EAAE,OAAO,SAAS,MAAM,GAAI,SAAS,CAAC,EAAG,GAAG,EAAE,OAAO,CAAC;AACjF;AAYA,eAAe,qBACb,KACA,iBACsC;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,UAAU,KAAK,gBAAgB,iBAAiB;AAAA,EACzD;AACA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,cAAc,UAAU,QAAqB,aAAa;AAChE,QAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK;AAAA,IAC9C,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,EACvB,CAAC;AACD,MAAI,CAAC,oBAAoB,CAAC,eAAe,GAAG,IAAI,UAAU,IAAI,cAAc,WAAW,GAAG;AACxF,WAAO,UAAU,KAAK,kCAAkC,eAAe,MAAM,WAAW;AAAA,EAC1F;AACA,SAAO;AAAA,IACL,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,KAAK,SAAS;AAAA,IAC9B,QAAQ,KAAK;AAAA,IACb,cAAc,IAAI;AAAA,IAClB,UAAU,IAAI;AAAA,IACd;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,KAAoC;AAC7D,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,SAAS,IAAI;AAAA,IACb,gBAAgB,IAAI;AAAA,IACpB,OAAO,IAAI,SAAS;AAAA,IACpB,iBAAiB,IAAI,mBAAmB;AAAA,IACxC,WAAW,IAAI,WAAW,cAAc,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpE,WAAW,IAAI,WAAW,cAAc,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtE;AACF;AAEA,SAAS,mBAAmB,OAAiD;AAC3E,QAAM,WAAW,MAAM;AACvB,SAAO,YAAY,sBAAsB,QAAQ,IAAI,WAAW;AAClE;AAEA,eAAsB,IAAI,KAAkB,SAA0C;AACpF,QAAM,aAAa,MAAM,qBAAqB,KAAK,YAAY;AAC/D,MAAI,sBAAsB,aAAc,QAAO;AAE/C,QAAM,YAAY,MAAM,QAAQ;AAChC,QAAM,cAAc,mBAAmB,UAAU,SAAS;AAC1D,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,UAAU,KAAK,qBAAqB,oBAAoB;AAAA,MAC7D,QAAQ,YAAY,MAAM;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,MAAI;AACF,UAAM,kBAAkB;AACxB,UAAM,QAAQ,SAAS,YAAY,KAAK,OAAO;AAC/C,QAAI,CAAC,OAAO;AACV,aAAO,UAAU,KAAK,kBAAkB,YAAY,KAAK,OAAO,MAAM,eAAe;AAAA,IACvF;AAEA,UAAM,WAAW,mBAAmB,KAAK;AACzC,QAAI,CAAC,WAAW,UAAU;AACxB,aAAO,aAAa,KAAK;AAAA,QACvB,SAAS,MAAM;AAAA,QACf,cAAc;AAAA,QACd,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAEA,UAAM,KAAK,WAAW,UAAU,QAAuB,IAAI;AAC3D,UAAM,OAAO,IAAI,wCAAwC,EAAE;AAC3D,UAAM,UAAU,MAAM,KAAK,IAAI,MAAM,IAAI;AAAA,MACvC,UAAU,WAAW;AAAA,MACrB,gBAAgB,WAAW;AAAA,IAC7B,CAAC;AAED,WAAO,aAAa,KAAK;AAAA,MACvB,SAAS,MAAM;AAAA,MACf,cAAc;AAAA,MACd,UAAU,UAAU,kBAAkB,OAAO,IAAI;AAAA,IACnD,CAAC;AAAA,EACH,SAAS,OAAO;AACd,
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { 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 isKnownMutationPolicy,\n isMutationPolicyEscalation,\n} from '../../../../../lib/agent-policy'\nimport { AiAgentMutationPolicyOverrideRepository } from '../../../../../data/repositories/AiAgentMutationPolicyOverrideRepository'\nimport type { AiAgentMutationPolicyOverride } from '../../../../../data/entities'\nimport type {\n AiAgentDefinition,\n AiAgentMutationPolicy,\n} from '../../../../../lib/ai-agent-definition'\n\nconst logger = createLogger('ai_assistant')\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 mutationPolicySchema = z.enum([\n 'read-only',\n 'confirm-required',\n 'destructive-confirm-required',\n])\n\nconst mutationPolicyRequestSchema = z.object({\n mutationPolicy: mutationPolicySchema,\n notes: z.string().max(2000).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 mutationPolicy override for an AI agent',\n methods: {\n GET: {\n operationId: 'aiAssistantGetMutationPolicyOverride',\n summary:\n 'Read the effective mutationPolicy for an agent \u2014 code-declared value plus any tenant override.',\n description:\n 'Returns `{ agentId, codeDeclared, override }` where `codeDeclared` is the agent\\'s ' +\n 'compiled-in `mutationPolicy` and `override` is the persisted tenant-scoped override ' +\n '(or `null`). Requires `ai_assistant.view`.',\n responses: [\n {\n status: 200,\n description: 'Effective mutationPolicy 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 POST: {\n operationId: 'aiAssistantSaveMutationPolicyOverride',\n summary:\n 'Set (or replace) the tenant-scoped mutationPolicy override for this agent.',\n description:\n 'Body: `{ mutationPolicy: \"read-only\" | \"confirm-required\" | \"destructive-confirm-required\", notes? }`. ' +\n 'The override MUST NOT escalate beyond the agent\\'s code-declared policy. Escalation attempts ' +\n 'are rejected with 400 + `code: \"escalation_not_allowed\"`. Requires `ai_assistant.settings.manage`.',\n requestBody: {\n contentType: 'application/json',\n description: 'Body: `{ mutationPolicy, notes? }`.',\n schema: mutationPolicyRequestSchema,\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, malformed body, or escalation attempt.' },\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: 'aiAssistantClearMutationPolicyOverride',\n summary: 'Remove the tenant-scoped mutationPolicy override for this agent.',\n description:\n 'Deletes the override row if it exists; subsequent calls fall back to the agent\\'s ' +\n 'code-declared policy. Idempotent \u2014 returns 200 even when no override exists. Requires ' +\n '`ai_assistant.settings.manage`.',\n responses: [\n {\n status: 200,\n description: '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 POST: { 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 rbacService: RbacService\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 rbacService,\n }\n}\n\nfunction serializeOverride(row: AiAgentMutationPolicyOverride) {\n return {\n id: row.id,\n agentId: row.agentId,\n mutationPolicy: row.mutationPolicy,\n notes: row.notes ?? null,\n createdByUserId: row.createdByUserId ?? null,\n createdAt: row.createdAt?.toISOString?.() ?? new Date().toISOString(),\n updatedAt: row.updatedAt?.toISOString?.() ?? new Date().toISOString(),\n }\n}\n\nfunction codeDeclaredPolicy(agent: AiAgentDefinition): AiAgentMutationPolicy {\n const declared = agent.mutationPolicy\n return declared && isKnownMutationPolicy(declared) ? declared : 'read-only'\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 const declared = codeDeclaredPolicy(agent)\n if (!authResult.tenantId) {\n return NextResponse.json({\n agentId: agent.id,\n codeDeclared: declared,\n override: null,\n })\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentMutationPolicyOverrideRepository(em)\n const current = await repo.get(agent.id, {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n })\n\n return NextResponse.json({\n agentId: agent.id,\n codeDeclared: declared,\n override: current ? serializeOverride(current) : null,\n })\n } catch (error) {\n logger.error('AI Mutation Policy GET \u2014 Failure', { err: error })\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to load mutationPolicy override.',\n 'internal_error',\n )\n }\n}\n\nexport async function POST(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 = mutationPolicyRequestSchema.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 const declared = codeDeclaredPolicy(agent)\n if (isMutationPolicyEscalation(declared, bodyResult.data.mutationPolicy)) {\n return jsonError(\n 400,\n `Cannot set mutationPolicy=\"${bodyResult.data.mutationPolicy}\" for agent \"${agent.id}\": ` +\n `the agent\\'s code-declared policy is \"${declared}\". Upgrading beyond the declared ` +\n `policy is a code-level change, not a configuration change.`,\n 'escalation_not_allowed',\n { codeDeclared: declared, requested: bodyResult.data.mutationPolicy },\n )\n }\n\n if (!authResult.tenantId) {\n return jsonError(\n 400,\n 'Caller has no tenant context; cannot persist tenant-scoped mutationPolicy override.',\n 'tenant_required',\n )\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentMutationPolicyOverrideRepository(em)\n const saved = await repo.set(\n {\n agentId: agent.id,\n mutationPolicy: bodyResult.data.mutationPolicy,\n notes: bodyResult.data.notes ?? 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 codeDeclared: declared,\n override: serializeOverride(saved),\n })\n } catch (error) {\n logger.error('AI Mutation Policy POST \u2014 Failure', { err: error })\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to save mutationPolicy 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 const declared = codeDeclaredPolicy(agent)\n if (!authResult.tenantId) {\n return NextResponse.json({\n ok: true,\n agentId: agent.id,\n codeDeclared: declared,\n override: null,\n cleared: false,\n })\n }\n\n const em = authResult.container.resolve<EntityManager>('em')\n const repo = new AiAgentMutationPolicyOverrideRepository(em)\n const cleared = await repo.clear(agent.id, {\n tenantId: authResult.tenantId,\n organizationId: authResult.organizationId,\n })\n\n return NextResponse.json({\n ok: true,\n agentId: agent.id,\n codeDeclared: declared,\n override: null,\n cleared,\n })\n } catch (error) {\n logger.error('AI Mutation Policy DELETE \u2014 Failure', { err: error })\n return jsonError(\n 500,\n error instanceof Error ? error.message : 'Failed to clear mutationPolicy override.',\n 'internal_error',\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|