@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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/ai_assistant/api/tools/execute/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\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 { executeTool } from '../../../lib/tool-executor'\nimport { loadAllModuleTools } from '../../../lib/tool-loader'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { McpToolContext } from '../../../lib/types'\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Execute AI tool',\n methods: {\n POST: { summary: 'Execute a specific MCP tool by name' },\n },\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function POST(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const body = await req.json()\n const { toolName, args = {} } = body\n\n if (!toolName || typeof toolName !== 'string') {\n return NextResponse.json({ error: 'toolName is required' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n\n // Load ACL for user\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n // Ensure tools are loaded\n await loadAllModuleTools()\n\n // Build tool context\n const toolContext: McpToolContext = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n }\n\n // Execute the tool\n const result = await executeTool(toolName, args, toolContext)\n\n if (!result.success) {\n return NextResponse.json(\n { success: false, error: result.error },\n { status: result.errorCode === 'UNAUTHORIZED' ? 403 : 400 }\n )\n }\n\n return NextResponse.json({\n success: true,\n result: result.result,\n })\n } catch (error) {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAE/C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { executeTool } from '../../../lib/tool-executor'\nimport { loadAllModuleTools } from '../../../lib/tool-loader'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { McpToolContext } from '../../../lib/types'\n\nconst logger = createLogger('ai_assistant')\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Execute AI tool',\n methods: {\n POST: { summary: 'Execute a specific MCP tool by name' },\n },\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function POST(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const body = await req.json()\n const { toolName, args = {} } = body\n\n if (!toolName || typeof toolName !== 'string') {\n return NextResponse.json({ error: 'toolName is required' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n\n // Load ACL for user\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n // Ensure tools are loaded\n await loadAllModuleTools()\n\n // Build tool context\n const toolContext: McpToolContext = {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n container,\n userFeatures: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n }\n\n // Execute the tool\n const result = await executeTool(toolName, args, toolContext)\n\n if (!result.success) {\n return NextResponse.json(\n { success: false, error: result.error },\n { status: result.errorCode === 'UNAUTHORIZED' ? 403 : 400 }\n )\n }\n\n return NextResponse.json({\n success: true,\n result: result.result,\n })\n } catch (error) {\n logger.error('AI Tools \u2014 Error executing tool', { err: error })\n return NextResponse.json(\n { success: false, error: 'Tool execution failed' },\n { status: 500 }\n )\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAsC;AAE/C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AAInC,MAAM,SAAS,aAAa,cAAc;AAEnC,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM,EAAE,SAAS,sCAAsC;AAAA,EACzD;AACF;AAEO,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAkB;AAC3C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AAEzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAM,EAAE,UAAU,OAAO,CAAC,EAAE,IAAI;AAEhC,QAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,aAAO,aAAa,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7E;AAEA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,cAAc,UAAU,QAAqB,aAAa;AAGhE,UAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK;AAAA,MAC9C,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAGD,UAAM,mBAAmB;AAGzB,UAAM,cAA8B;AAAA,MAClC,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,MACrB,QAAQ,KAAK;AAAA,MACb;AAAA,MACA,cAAc,IAAI;AAAA,MAClB,cAAc,IAAI;AAAA,IACpB;AAGA,UAAM,SAAS,MAAM,YAAY,UAAU,MAAM,WAAW;AAE5D,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,aAAa;AAAA,QAClB,EAAE,SAAS,OAAO,OAAO,OAAO,MAAM;AAAA,QACtC,EAAE,QAAQ,OAAO,cAAc,iBAAiB,MAAM,IAAI;AAAA,MAC5D;AAAA,IACF;AAEA,WAAO,aAAa,KAAK;AAAA,MACvB,SAAS;AAAA,MACT,QAAQ,OAAO;AAAA,IACjB,CAAC;AAAA,EACH,SAAS,OAAO;AACd,WAAO,MAAM,wCAAmC,EAAE,KAAK,MAAM,CAAC;AAC9D,WAAO,aAAa;AAAA,MAClB,EAAE,SAAS,OAAO,OAAO,wBAAwB;AAAA,MACjD,EAAE,QAAQ,IAAI;AAAA,IAChB;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 { zodToJsonSchema } from "zod-to-json-schema";
|
|
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 { getToolRegistry } from "../../lib/tool-registry.js";
|
|
6
7
|
import { loadAllModuleTools } from "../../lib/tool-loader.js";
|
|
7
8
|
import { hasRequiredFeatures } from "../../lib/auth.js";
|
|
9
|
+
const logger = createLogger("ai_assistant");
|
|
8
10
|
const openApi = {
|
|
9
11
|
tag: "AI Assistant",
|
|
10
12
|
summary: "List AI tools",
|
|
@@ -44,7 +46,7 @@ async function GET(req) {
|
|
|
44
46
|
});
|
|
45
47
|
return NextResponse.json({ tools });
|
|
46
48
|
} catch (error) {
|
|
47
|
-
|
|
49
|
+
logger.error("AI Tools \u2014 Error listing tools", { err: error });
|
|
48
50
|
return NextResponse.json({ error: "Failed to list tools" }, { status: 500 });
|
|
49
51
|
}
|
|
50
52
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/api/tools/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getToolRegistry } from '../../lib/tool-registry'\nimport { loadAllModuleTools } from '../../lib/tool-loader'\nimport { hasRequiredFeatures } from '../../lib/auth'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'List AI tools',\n methods: {\n GET: { summary: 'List available MCP tools filtered by user permissions' },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n\n // Load ACL for user\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n // Ensure tools are loaded\n await loadAllModuleTools()\n\n // Get tools filtered by ACL\n const registry = getToolRegistry()\n const allTools = Array.from(registry.getTools().values())\n\n const accessibleTools = allTools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, acl.features, acl.isSuperAdmin, rbacService)\n )\n\n const tools = accessibleTools.map((tool) => {\n const nameParts = tool.name.split('.')\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: zodToJsonSchema(tool.inputSchema as any) as Record<string, unknown>,\n module: nameParts[0] || 'other',\n }\n })\n\n return NextResponse.json({ tools })\n } catch (error) {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAE/C,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getToolRegistry } from '../../lib/tool-registry'\nimport { loadAllModuleTools } from '../../lib/tool-loader'\nimport { hasRequiredFeatures } from '../../lib/auth'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\nconst logger = createLogger('ai_assistant')\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'List AI tools',\n methods: {\n GET: { summary: 'List available MCP tools filtered by user permissions' },\n },\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const container = await createRequestContainer()\n const rbacService = container.resolve<RbacService>('rbacService')\n\n // Load ACL for user\n const acl = await rbacService.loadAcl(auth.sub, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n })\n\n // Ensure tools are loaded\n await loadAllModuleTools()\n\n // Get tools filtered by ACL\n const registry = getToolRegistry()\n const allTools = Array.from(registry.getTools().values())\n\n const accessibleTools = allTools.filter((tool) =>\n hasRequiredFeatures(tool.requiredFeatures, acl.features, acl.isSuperAdmin, rbacService)\n )\n\n const tools = accessibleTools.map((tool) => {\n const nameParts = tool.name.split('.')\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: zodToJsonSchema(tool.inputSchema as any) as Record<string, unknown>,\n module: nameParts[0] || 'other',\n }\n })\n\n return NextResponse.json({ tools })\n } catch (error) {\n logger.error('AI Tools \u2014 Error listing tools', { err: error })\n return NextResponse.json({ error: 'Failed to list tools' }, { status: 500 })\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAsC;AAE/C,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AAGpC,MAAM,SAAS,aAAa,cAAc;AAEnC,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK,EAAE,SAAS,wDAAwD;AAAA,EAC1E;AACF;AAEO,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAEA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AAEzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,cAAc,UAAU,QAAqB,aAAa;AAGhE,UAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK;AAAA,MAC9C,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAGD,UAAM,mBAAmB;AAGzB,UAAM,WAAW,gBAAgB;AACjC,UAAM,WAAW,MAAM,KAAK,SAAS,SAAS,EAAE,OAAO,CAAC;AAExD,UAAM,kBAAkB,SAAS;AAAA,MAAO,CAAC,SACvC,oBAAoB,KAAK,kBAAkB,IAAI,UAAU,IAAI,cAAc,WAAW;AAAA,IACxF;AAEA,UAAM,QAAQ,gBAAgB,IAAI,CAAC,SAAS;AAC1C,YAAM,YAAY,KAAK,KAAK,MAAM,GAAG;AACrC,aAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,aAAa,gBAAgB,KAAK,WAAkB;AAAA,QACpD,QAAQ,UAAU,CAAC,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AAED,WAAO,aAAa,KAAK,EAAE,MAAM,CAAC;AAAA,EACpC,SAAS,OAAO;AACd,WAAO,MAAM,uCAAkC,EAAE,KAAK,MAAM,CAAC;AAC7D,WAAO,aAAa,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC7E;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";
|
|
@@ -5,6 +6,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
5
6
|
import { AiTokenUsageRepository } from "../../../data/repositories/AiTokenUsageRepository.js";
|
|
6
7
|
import { hasRequiredFeatures } from "../../../lib/auth.js";
|
|
7
8
|
import { toDateString, toIntegerString, toIsoString } from "../../../lib/usage-serialization.js";
|
|
9
|
+
const logger = createLogger("ai_assistant");
|
|
8
10
|
const REQUIRED_FEATURE = "ai_assistant.settings.manage";
|
|
9
11
|
const querySchema = z.object({
|
|
10
12
|
from: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "from must be a date in YYYY-MM-DD format"),
|
|
@@ -99,7 +101,7 @@ async function GET(req) {
|
|
|
99
101
|
}));
|
|
100
102
|
return NextResponse.json({ rows: serialized, total: serialized.length });
|
|
101
103
|
} catch (error) {
|
|
102
|
-
|
|
104
|
+
logger.error("AI Usage Daily \u2014 GET error", { err: error });
|
|
103
105
|
return jsonError(500, "Failed to fetch daily usage data.", "internal_error");
|
|
104
106
|
}
|
|
105
107
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/ai_assistant/api/usage/daily/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 { AiTokenUsageRepository } from '../../../data/repositories/AiTokenUsageRepository'\nimport { hasRequiredFeatures } from '../../../lib/auth'\nimport { toDateString, toIntegerString, toIsoString } from '../../../lib/usage-serialization'\n\nconst REQUIRED_FEATURE = 'ai_assistant.settings.manage'\n\nconst querySchema = z.object({\n from: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'from must be a date in YYYY-MM-DD format'),\n to: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'to must be a date in YYYY-MM-DD format'),\n agentId: z.string().min(1).max(256).optional(),\n modelId: z.string().min(1).max(256).optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Token usage daily rollup',\n methods: {\n GET: {\n operationId: 'aiAssistantUsageDaily',\n summary: 'Fetch daily token-usage rollup rows for a date window.',\n description:\n 'Returns aggregated token-usage data from `ai_token_usage_daily` for the given ' +\n 'date window. Tenant-scoped. Optionally filtered by `agentId` and/or `modelId`. ' +\n 'Requires `ai_assistant.settings.manage`.',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Array of daily rollup rows.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters.' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },\n { status: 500, description: 'Internal failure.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n path: '/ai_assistant/usage/daily',\n GET: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\nfunction jsonError(status: number, message: string, code: string, extra?: Record<string, unknown>): NextResponse {\n return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })\n}\n\nexport async function GET(req: NextRequest): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n\n const { searchParams } = new URL(req.url)\n const rawQuery = {\n from: searchParams.get('from') ?? undefined,\n to: searchParams.get('to') ?? undefined,\n agentId: searchParams.get('agentId') ?? undefined,\n modelId: searchParams.get('modelId') ?? undefined,\n }\n\n const queryResult = querySchema.safeParse(rawQuery)\n if (!queryResult.success) {\n return jsonError(400, 'Invalid query parameters.', 'validation_error', {\n issues: queryResult.error.issues,\n })\n }\n\n const { from, to, agentId, modelId } = queryResult.data\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 NextResponse.json({ rows: [], total: 0 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiTokenUsageRepository(em)\n const rows = await repo.listDailyRollup(auth.tenantId, from, to, { agentId, modelId })\n\n const serialized = rows.map((row) => ({\n id: row.id,\n tenantId: row.tenantId,\n organizationId: row.organizationId ?? null,\n day: toDateString(row.day),\n agentId: row.agentId,\n modelId: row.modelId,\n providerId: row.providerId,\n inputTokens: toIntegerString(row.inputTokens),\n outputTokens: toIntegerString(row.outputTokens),\n cachedInputTokens: toIntegerString(row.cachedInputTokens),\n reasoningTokens: toIntegerString(row.reasoningTokens),\n stepCount: toIntegerString(row.stepCount),\n turnCount: toIntegerString(row.turnCount),\n sessionCount: toIntegerString(row.sessionCount),\n createdAt: toIsoString(row.createdAt),\n updatedAt: toIsoString(row.updatedAt),\n }))\n\n return NextResponse.json({ rows: serialized, total: serialized.length })\n } catch (error) {\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { 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 { AiTokenUsageRepository } from '../../../data/repositories/AiTokenUsageRepository'\nimport { hasRequiredFeatures } from '../../../lib/auth'\nimport { toDateString, toIntegerString, toIsoString } from '../../../lib/usage-serialization'\n\nconst logger = createLogger('ai_assistant')\n\nconst REQUIRED_FEATURE = 'ai_assistant.settings.manage'\n\nconst querySchema = z.object({\n from: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'from must be a date in YYYY-MM-DD format'),\n to: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'to must be a date in YYYY-MM-DD format'),\n agentId: z.string().min(1).max(256).optional(),\n modelId: z.string().min(1).max(256).optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Token usage daily rollup',\n methods: {\n GET: {\n operationId: 'aiAssistantUsageDaily',\n summary: 'Fetch daily token-usage rollup rows for a date window.',\n description:\n 'Returns aggregated token-usage data from `ai_token_usage_daily` for the given ' +\n 'date window. Tenant-scoped. Optionally filtered by `agentId` and/or `modelId`. ' +\n 'Requires `ai_assistant.settings.manage`.',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Array of daily rollup rows.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters.' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },\n { status: 500, description: 'Internal failure.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n path: '/ai_assistant/usage/daily',\n GET: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\nfunction jsonError(status: number, message: string, code: string, extra?: Record<string, unknown>): NextResponse {\n return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })\n}\n\nexport async function GET(req: NextRequest): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n\n const { searchParams } = new URL(req.url)\n const rawQuery = {\n from: searchParams.get('from') ?? undefined,\n to: searchParams.get('to') ?? undefined,\n agentId: searchParams.get('agentId') ?? undefined,\n modelId: searchParams.get('modelId') ?? undefined,\n }\n\n const queryResult = querySchema.safeParse(rawQuery)\n if (!queryResult.success) {\n return jsonError(400, 'Invalid query parameters.', 'validation_error', {\n issues: queryResult.error.issues,\n })\n }\n\n const { from, to, agentId, modelId } = queryResult.data\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 NextResponse.json({ rows: [], total: 0 })\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiTokenUsageRepository(em)\n const rows = await repo.listDailyRollup(auth.tenantId, from, to, { agentId, modelId })\n\n const serialized = rows.map((row) => ({\n id: row.id,\n tenantId: row.tenantId,\n organizationId: row.organizationId ?? null,\n day: toDateString(row.day),\n agentId: row.agentId,\n modelId: row.modelId,\n providerId: row.providerId,\n inputTokens: toIntegerString(row.inputTokens),\n outputTokens: toIntegerString(row.outputTokens),\n cachedInputTokens: toIntegerString(row.cachedInputTokens),\n reasoningTokens: toIntegerString(row.reasoningTokens),\n stepCount: toIntegerString(row.stepCount),\n turnCount: toIntegerString(row.turnCount),\n sessionCount: toIntegerString(row.sessionCount),\n createdAt: toIsoString(row.createdAt),\n updatedAt: toIsoString(row.updatedAt),\n }))\n\n return NextResponse.json({ rows: serialized, total: serialized.length })\n } catch (error) {\n logger.error('AI Usage Daily \u2014 GET error', { err: error })\n return jsonError(500, 'Failed to fetch daily usage data.', 'internal_error')\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 { AiTokenUsageRepository } from "../../../../data/repositories/AiTokenUsageRepository.js";
|
|
6
7
|
import { hasRequiredFeatures } from "../../../../lib/auth.js";
|
|
7
8
|
import { toInteger, toIsoString } from "../../../../lib/usage-serialization.js";
|
|
9
|
+
const logger = createLogger("ai_assistant");
|
|
8
10
|
const REQUIRED_FEATURE = "ai_assistant.settings.manage";
|
|
9
11
|
const sessionIdParamSchema = z.object({
|
|
10
12
|
sessionId: z.string().trim().uuid("sessionId must be a valid UUID")
|
|
@@ -96,7 +98,7 @@ async function GET(req, context) {
|
|
|
96
98
|
}));
|
|
97
99
|
return NextResponse.json({ events: serialized, total: serialized.length, sessionId });
|
|
98
100
|
} catch (error) {
|
|
99
|
-
|
|
101
|
+
logger.error("AI Usage Session Detail \u2014 GET error", { err: error });
|
|
100
102
|
return jsonError(500, "Failed to fetch session event data.", "internal_error");
|
|
101
103
|
}
|
|
102
104
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/ai_assistant/api/usage/sessions/%5BsessionId%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 { AiTokenUsageRepository } from '../../../../data/repositories/AiTokenUsageRepository'\nimport { hasRequiredFeatures } from '../../../../lib/auth'\nimport { toInteger, toIsoString } from '../../../../lib/usage-serialization'\n\nconst REQUIRED_FEATURE = 'ai_assistant.settings.manage'\n\nconst sessionIdParamSchema = z.object({\n sessionId: z\n .string()\n .trim()\n .uuid('sessionId must be a valid UUID'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Per-step token usage events for a session',\n methods: {\n GET: {\n operationId: 'aiAssistantUsageSessionDetail',\n summary: 'Fetch per-step token usage event rows for a single session.',\n description:\n 'Returns up to 200 raw `ai_token_usage_events` rows for the given `sessionId`, ' +\n 'ordered by `created_at ASC, step_index ASC`. Tenant-scoped. ' +\n 'Requires `ai_assistant.settings.manage`.',\n responses: [\n {\n status: 200,\n description: 'Array of per-step event rows for the session.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid session id (must be a UUID).' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },\n { status: 404, description: 'No events found for the given session id in the caller\\'s tenant.' },\n { status: 500, description: 'Internal failure.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n path: '/ai_assistant/usage/sessions/[sessionId]',\n GET: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\ninterface RouteContext {\n params: Promise<{ sessionId: string }>\n}\n\nfunction jsonError(status: number, message: string, code: string, extra?: Record<string, unknown>): 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 = sessionIdParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid session id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n\n const { sessionId } = paramResult.data\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(404, `No events found for session \"${sessionId}\".`, 'session_not_found')\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiTokenUsageRepository(em)\n const events = await repo.listEventsForSession(auth.tenantId, sessionId)\n\n if (events.length === 0) {\n return jsonError(404, `No events found for session \"${sessionId}\".`, 'session_not_found')\n }\n\n const serialized = events.map((event) => ({\n id: event.id,\n tenantId: event.tenantId,\n organizationId: event.organizationId ?? null,\n userId: event.userId,\n agentId: event.agentId,\n moduleId: event.moduleId,\n sessionId: event.sessionId,\n turnId: event.turnId,\n stepIndex: toInteger(event.stepIndex),\n providerId: event.providerId,\n modelId: event.modelId,\n inputTokens: toInteger(event.inputTokens),\n outputTokens: toInteger(event.outputTokens),\n cachedInputTokens: event.cachedInputTokens == null ? null : toInteger(event.cachedInputTokens),\n reasoningTokens: event.reasoningTokens == null ? null : toInteger(event.reasoningTokens),\n finishReason: event.finishReason ?? null,\n loopAbortReason: event.loopAbortReason ?? null,\n createdAt: toIsoString(event.createdAt),\n updatedAt: toIsoString(event.updatedAt),\n }))\n\n return NextResponse.json({ events: serialized, total: serialized.length, sessionId })\n } catch (error) {\n
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { NextResponse, type NextRequest } from 'next/server'\nimport { z } from 'zod'\nimport type { 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 { AiTokenUsageRepository } from '../../../../data/repositories/AiTokenUsageRepository'\nimport { hasRequiredFeatures } from '../../../../lib/auth'\nimport { toInteger, toIsoString } from '../../../../lib/usage-serialization'\n\nconst logger = createLogger('ai_assistant')\n\nconst REQUIRED_FEATURE = 'ai_assistant.settings.manage'\n\nconst sessionIdParamSchema = z.object({\n sessionId: z\n .string()\n .trim()\n .uuid('sessionId must be a valid UUID'),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Per-step token usage events for a session',\n methods: {\n GET: {\n operationId: 'aiAssistantUsageSessionDetail',\n summary: 'Fetch per-step token usage event rows for a single session.',\n description:\n 'Returns up to 200 raw `ai_token_usage_events` rows for the given `sessionId`, ' +\n 'ordered by `created_at ASC, step_index ASC`. Tenant-scoped. ' +\n 'Requires `ai_assistant.settings.manage`.',\n responses: [\n {\n status: 200,\n description: 'Array of per-step event rows for the session.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid session id (must be a UUID).' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },\n { status: 404, description: 'No events found for the given session id in the caller\\'s tenant.' },\n { status: 500, description: 'Internal failure.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n path: '/ai_assistant/usage/sessions/[sessionId]',\n GET: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\ninterface RouteContext {\n params: Promise<{ sessionId: string }>\n}\n\nfunction jsonError(status: number, message: string, code: string, extra?: Record<string, unknown>): 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 = sessionIdParamSchema.safeParse(rawParams)\n if (!paramResult.success) {\n return jsonError(400, 'Invalid session id.', 'validation_error', {\n issues: paramResult.error.issues,\n })\n }\n\n const { sessionId } = paramResult.data\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(404, `No events found for session \"${sessionId}\".`, 'session_not_found')\n }\n\n const em = container.resolve<EntityManager>('em')\n const repo = new AiTokenUsageRepository(em)\n const events = await repo.listEventsForSession(auth.tenantId, sessionId)\n\n if (events.length === 0) {\n return jsonError(404, `No events found for session \"${sessionId}\".`, 'session_not_found')\n }\n\n const serialized = events.map((event) => ({\n id: event.id,\n tenantId: event.tenantId,\n organizationId: event.organizationId ?? null,\n userId: event.userId,\n agentId: event.agentId,\n moduleId: event.moduleId,\n sessionId: event.sessionId,\n turnId: event.turnId,\n stepIndex: toInteger(event.stepIndex),\n providerId: event.providerId,\n modelId: event.modelId,\n inputTokens: toInteger(event.inputTokens),\n outputTokens: toInteger(event.outputTokens),\n cachedInputTokens: event.cachedInputTokens == null ? null : toInteger(event.cachedInputTokens),\n reasoningTokens: event.reasoningTokens == null ? null : toInteger(event.reasoningTokens),\n finishReason: event.finishReason ?? null,\n loopAbortReason: event.loopAbortReason ?? null,\n createdAt: toIsoString(event.createdAt),\n updatedAt: toIsoString(event.updatedAt),\n }))\n\n return NextResponse.json({ events: serialized, total: serialized.length, sessionId })\n } catch (error) {\n logger.error('AI Usage Session Detail \u2014 GET error', { err: error })\n return jsonError(500, 'Failed to fetch session event data.', 'internal_error')\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
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";
|
|
4
5
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
6
|
import { hasRequiredFeatures } from "../../../lib/auth.js";
|
|
6
7
|
import { toInteger, toIsoString } from "../../../lib/usage-serialization.js";
|
|
8
|
+
const logger = createLogger("ai_assistant");
|
|
7
9
|
const REQUIRED_FEATURE = "ai_assistant.settings.manage";
|
|
8
10
|
const MAX_PAGE_SIZE = 100;
|
|
9
11
|
const querySchema = z.object({
|
|
@@ -141,7 +143,7 @@ async function GET(req) {
|
|
|
141
143
|
})) : [];
|
|
142
144
|
return NextResponse.json({ sessions, total, limit, offset });
|
|
143
145
|
} catch (error) {
|
|
144
|
-
|
|
146
|
+
logger.error("AI Usage Sessions \u2014 GET error", { err: error });
|
|
145
147
|
return jsonError(500, "Failed to fetch session usage data.", "internal_error");
|
|
146
148
|
}
|
|
147
149
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/ai_assistant/api/usage/sessions/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 { hasRequiredFeatures } from '../../../lib/auth'\nimport { toInteger, toIsoString } from '../../../lib/usage-serialization'\n\nconst REQUIRED_FEATURE = 'ai_assistant.settings.manage'\n\nconst MAX_PAGE_SIZE = 100\n\nconst querySchema = z.object({\n from: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'from must be a date in YYYY-MM-DD format'),\n to: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'to must be a date in YYYY-MM-DD format'),\n agentId: z.string().min(1).max(256).optional(),\n limit: z\n .string()\n .optional()\n .transform((val) => (val !== undefined ? parseInt(val, 10) : MAX_PAGE_SIZE))\n .refine((val) => !isNaN(val) && val > 0 && val <= MAX_PAGE_SIZE, {\n message: `limit must be between 1 and ${MAX_PAGE_SIZE}`,\n }),\n offset: z\n .string()\n .optional()\n .transform((val) => (val !== undefined ? parseInt(val, 10) : 0))\n .refine((val) => !isNaN(val) && val >= 0, { message: 'offset must be a non-negative integer' }),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Per-session token usage totals',\n methods: {\n GET: {\n operationId: 'aiAssistantUsageSessions',\n summary: 'List per-session token usage totals for a date window.',\n description:\n 'Returns aggregated token-usage data grouped by `session_id` from `ai_token_usage_events` ' +\n 'for the given date window. Tenant-scoped. Optionally filtered by `agentId`. ' +\n 'Paginated via `limit` / `offset`. Requires `ai_assistant.settings.manage`.',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Array of session-level usage summaries plus pagination metadata.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters.' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },\n { status: 500, description: 'Internal failure.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n path: '/ai_assistant/usage/sessions',\n GET: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\nfunction jsonError(status: number, message: string, code: string, extra?: Record<string, unknown>): NextResponse {\n return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })\n}\n\nexport async function GET(req: NextRequest): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n\n const { searchParams } = new URL(req.url)\n const rawQuery = {\n from: searchParams.get('from') ?? undefined,\n to: searchParams.get('to') ?? undefined,\n agentId: searchParams.get('agentId') ?? undefined,\n limit: searchParams.get('limit') ?? undefined,\n offset: searchParams.get('offset') ?? undefined,\n }\n\n const queryResult = querySchema.safeParse(rawQuery)\n if (!queryResult.success) {\n return jsonError(400, 'Invalid query parameters.', 'validation_error', {\n issues: queryResult.error.issues,\n })\n }\n\n const { from, to, agentId, limit, offset } = queryResult.data\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 NextResponse.json({ sessions: [], total: 0, limit, offset })\n }\n\n const em = container.resolve<EntityManager>('em')\n const connection = em.getConnection()\n\n const params: unknown[] = [auth.tenantId, from, to]\n let agentFilter = ''\n if (agentId) {\n agentFilter = 'and agent_id = ?'\n params.push(agentId)\n }\n\n const countParams = [...params]\n const countSql = `\n select count(distinct session_id)::bigint as total\n from ai_token_usage_events\n where tenant_id = ?\n and created_at >= ?::date\n and created_at < (?::date + interval '1 day')\n ${agentFilter}\n `\n const countRows = await connection.execute(countSql, countParams, 'all')\n const totalRaw = Array.isArray(countRows) && countRows.length > 0\n ? (countRows[0] as Record<string, unknown>).total\n : '0'\n const total = toInteger(totalRaw)\n\n params.push(limit, offset)\n const dataSql = `\n select\n session_id,\n agent_id,\n module_id,\n user_id,\n min(created_at) as started_at,\n max(created_at) as last_event_at,\n count(*)::bigint as step_count,\n count(distinct turn_id)::bigint as turn_count,\n sum(input_tokens)::bigint as input_tokens,\n sum(output_tokens)::bigint as output_tokens,\n sum(coalesce(cached_input_tokens, 0))::bigint as cached_input_tokens,\n sum(coalesce(reasoning_tokens, 0))::bigint as reasoning_tokens\n from ai_token_usage_events\n where tenant_id = ?\n and created_at >= ?::date\n and created_at < (?::date + interval '1 day')\n ${agentFilter}\n group by session_id, agent_id, module_id, user_id\n order by started_at desc\n limit ? offset ?\n `\n const dataRows = await connection.execute(dataSql, params, 'all')\n\n const sessions = Array.isArray(dataRows)\n ? (dataRows as Array<Record<string, unknown>>).map((row) => ({\n sessionId: row.session_id as string,\n agentId: row.agent_id as string,\n moduleId: row.module_id as string,\n userId: row.user_id as string,\n startedAt: toIsoString(row.started_at),\n lastEventAt: toIsoString(row.last_event_at),\n stepCount: toInteger(row.step_count),\n turnCount: toInteger(row.turn_count),\n inputTokens: toInteger(row.input_tokens),\n outputTokens: toInteger(row.output_tokens),\n cachedInputTokens: toInteger(row.cached_input_tokens),\n reasoningTokens: toInteger(row.reasoning_tokens),\n }))\n : []\n\n return NextResponse.json({ sessions, total, limit, offset })\n } catch (error) {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAGlB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,2BAA2B;AACpC,SAAS,WAAW,mBAAmB;AAEvC,MAAM,mBAAmB;AAEzB,MAAM,gBAAgB;AAEtB,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,MAAM,EAAE,OAAO,EAAE,MAAM,uBAAuB,0CAA0C;AAAA,EACxF,IAAI,EAAE,OAAO,EAAE,MAAM,uBAAuB,wCAAwC;AAAA,EACpF,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC7C,OAAO,EACJ,OAAO,EACP,SAAS,EACT,UAAU,CAAC,QAAS,QAAQ,SAAY,SAAS,KAAK,EAAE,IAAI,aAAc,EAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,KAAK,MAAM,KAAK,OAAO,eAAe;AAAA,IAC/D,SAAS,+BAA+B,aAAa;AAAA,EACvD,CAAC;AAAA,EACH,QAAQ,EACL,OAAO,EACP,SAAS,EACT,UAAU,CAAC,QAAS,QAAQ,SAAY,SAAS,KAAK,EAAE,IAAI,CAAE,EAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,KAAK,OAAO,GAAG,EAAE,SAAS,wCAAwC,CAAC;AAClG,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aACE;AAAA,MAGF,OAAO;AAAA,MACP,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,4BAA4B;AAAA,QACxD,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,MAAM;AAAA,EACN,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,gBAAgB,EAAE;AAChE;AAEA,SAAS,UAAU,QAAgB,SAAiB,MAAc,OAA+C;AAC/G,SAAO,aAAa,KAAK,EAAE,OAAO,SAAS,MAAM,GAAI,SAAS,CAAC,EAAG,GAAG,EAAE,OAAO,CAAC;AACjF;AAEA,eAAsB,IAAI,KAAqC;AAC7D,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,UAAU,KAAK,gBAAgB,iBAAiB;AAAA,EACzD;AAEA,QAAM,EAAE,aAAa,IAAI,IAAI,IAAI,IAAI,GAAG;AACxC,QAAM,WAAW;AAAA,IACf,MAAM,aAAa,IAAI,MAAM,KAAK;AAAA,IAClC,IAAI,aAAa,IAAI,IAAI,KAAK;AAAA,IAC9B,SAAS,aAAa,IAAI,SAAS,KAAK;AAAA,IACxC,OAAO,aAAa,IAAI,OAAO,KAAK;AAAA,IACpC,QAAQ,aAAa,IAAI,QAAQ,KAAK;AAAA,EACxC;AAEA,QAAM,cAAc,YAAY,UAAU,QAAQ;AAClD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,UAAU,KAAK,6BAA6B,oBAAoB;AAAA,MACrE,QAAQ,YAAY,MAAM;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,QAAM,EAAE,MAAM,IAAI,SAAS,OAAO,OAAO,IAAI,YAAY;AAEzD,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,cAAc,UAAU,QAAqB,aAAa;AAChE,UAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK;AAAA,MAC9C,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAED,QAAI,CAAC,oBAAoB,CAAC,gBAAgB,GAAG,IAAI,UAAU,IAAI,cAAc,WAAW,GAAG;AACzF,aAAO,UAAU,KAAK,kCAAkC,gBAAgB,MAAM,WAAW;AAAA,IAC3F;AAEA,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,aAAa,KAAK,EAAE,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,OAAO,CAAC;AAAA,IACpE;AAEA,UAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,UAAM,aAAa,GAAG,cAAc;AAEpC,UAAM,SAAoB,CAAC,KAAK,UAAU,MAAM,EAAE;AAClD,QAAI,cAAc;AAClB,QAAI,SAAS;AACX,oBAAc;AACd,aAAO,KAAK,OAAO;AAAA,IACrB;AAEA,UAAM,cAAc,CAAC,GAAG,MAAM;AAC9B,UAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMX,WAAW;AAAA;AAEjB,UAAM,YAAY,MAAM,WAAW,QAAQ,UAAU,aAAa,KAAK;AACvE,UAAM,WAAW,MAAM,QAAQ,SAAS,KAAK,UAAU,SAAS,IAC3D,UAAU,CAAC,EAA8B,QAC1C;AACJ,UAAM,QAAQ,UAAU,QAAQ;AAEhC,WAAO,KAAK,OAAO,MAAM;AACzB,UAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAkBV,WAAW;AAAA;AAAA;AAAA;AAAA;AAKjB,UAAM,WAAW,MAAM,WAAW,QAAQ,SAAS,QAAQ,KAAK;AAEhE,UAAM,WAAW,MAAM,QAAQ,QAAQ,IAClC,SAA4C,IAAI,CAAC,SAAS;AAAA,MACzD,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,UAAU,IAAI;AAAA,MACd,QAAQ,IAAI;AAAA,MACZ,WAAW,YAAY,IAAI,UAAU;AAAA,MACrC,aAAa,YAAY,IAAI,aAAa;AAAA,MAC1C,WAAW,UAAU,IAAI,UAAU;AAAA,MACnC,WAAW,UAAU,IAAI,UAAU;AAAA,MACnC,aAAa,UAAU,IAAI,YAAY;AAAA,MACvC,cAAc,UAAU,IAAI,aAAa;AAAA,MACzC,mBAAmB,UAAU,IAAI,mBAAmB;AAAA,MACpD,iBAAiB,UAAU,IAAI,gBAAgB;AAAA,IACjD,EAAE,IACF,CAAC;AAEL,WAAO,aAAa,KAAK,EAAE,UAAU,OAAO,OAAO,OAAO,CAAC;AAAA,EAC7D,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 { 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 { hasRequiredFeatures } from '../../../lib/auth'\nimport { toInteger, toIsoString } from '../../../lib/usage-serialization'\n\nconst logger = createLogger('ai_assistant')\n\nconst REQUIRED_FEATURE = 'ai_assistant.settings.manage'\n\nconst MAX_PAGE_SIZE = 100\n\nconst querySchema = z.object({\n from: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'from must be a date in YYYY-MM-DD format'),\n to: z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'to must be a date in YYYY-MM-DD format'),\n agentId: z.string().min(1).max(256).optional(),\n limit: z\n .string()\n .optional()\n .transform((val) => (val !== undefined ? parseInt(val, 10) : MAX_PAGE_SIZE))\n .refine((val) => !isNaN(val) && val > 0 && val <= MAX_PAGE_SIZE, {\n message: `limit must be between 1 and ${MAX_PAGE_SIZE}`,\n }),\n offset: z\n .string()\n .optional()\n .transform((val) => (val !== undefined ? parseInt(val, 10) : 0))\n .refine((val) => !isNaN(val) && val >= 0, { message: 'offset must be a non-negative integer' }),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'AI Assistant',\n summary: 'Per-session token usage totals',\n methods: {\n GET: {\n operationId: 'aiAssistantUsageSessions',\n summary: 'List per-session token usage totals for a date window.',\n description:\n 'Returns aggregated token-usage data grouped by `session_id` from `ai_token_usage_events` ' +\n 'for the given date window. Tenant-scoped. Optionally filtered by `agentId`. ' +\n 'Paginated via `limit` / `offset`. Requires `ai_assistant.settings.manage`.',\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Array of session-level usage summaries plus pagination metadata.',\n mediaType: 'application/json',\n },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters.' },\n { status: 401, description: 'Unauthenticated caller.' },\n { status: 403, description: 'Caller lacks `ai_assistant.settings.manage`.' },\n { status: 500, description: 'Internal failure.' },\n ],\n },\n },\n}\n\nexport const metadata = {\n path: '/ai_assistant/usage/sessions',\n GET: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] },\n}\n\nfunction jsonError(status: number, message: string, code: string, extra?: Record<string, unknown>): NextResponse {\n return NextResponse.json({ error: message, code, ...(extra ?? {}) }, { status })\n}\n\nexport async function GET(req: NextRequest): Promise<Response> {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return jsonError(401, 'Unauthorized', 'unauthenticated')\n }\n\n const { searchParams } = new URL(req.url)\n const rawQuery = {\n from: searchParams.get('from') ?? undefined,\n to: searchParams.get('to') ?? undefined,\n agentId: searchParams.get('agentId') ?? undefined,\n limit: searchParams.get('limit') ?? undefined,\n offset: searchParams.get('offset') ?? undefined,\n }\n\n const queryResult = querySchema.safeParse(rawQuery)\n if (!queryResult.success) {\n return jsonError(400, 'Invalid query parameters.', 'validation_error', {\n issues: queryResult.error.issues,\n })\n }\n\n const { from, to, agentId, limit, offset } = queryResult.data\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 NextResponse.json({ sessions: [], total: 0, limit, offset })\n }\n\n const em = container.resolve<EntityManager>('em')\n const connection = em.getConnection()\n\n const params: unknown[] = [auth.tenantId, from, to]\n let agentFilter = ''\n if (agentId) {\n agentFilter = 'and agent_id = ?'\n params.push(agentId)\n }\n\n const countParams = [...params]\n const countSql = `\n select count(distinct session_id)::bigint as total\n from ai_token_usage_events\n where tenant_id = ?\n and created_at >= ?::date\n and created_at < (?::date + interval '1 day')\n ${agentFilter}\n `\n const countRows = await connection.execute(countSql, countParams, 'all')\n const totalRaw = Array.isArray(countRows) && countRows.length > 0\n ? (countRows[0] as Record<string, unknown>).total\n : '0'\n const total = toInteger(totalRaw)\n\n params.push(limit, offset)\n const dataSql = `\n select\n session_id,\n agent_id,\n module_id,\n user_id,\n min(created_at) as started_at,\n max(created_at) as last_event_at,\n count(*)::bigint as step_count,\n count(distinct turn_id)::bigint as turn_count,\n sum(input_tokens)::bigint as input_tokens,\n sum(output_tokens)::bigint as output_tokens,\n sum(coalesce(cached_input_tokens, 0))::bigint as cached_input_tokens,\n sum(coalesce(reasoning_tokens, 0))::bigint as reasoning_tokens\n from ai_token_usage_events\n where tenant_id = ?\n and created_at >= ?::date\n and created_at < (?::date + interval '1 day')\n ${agentFilter}\n group by session_id, agent_id, module_id, user_id\n order by started_at desc\n limit ? offset ?\n `\n const dataRows = await connection.execute(dataSql, params, 'all')\n\n const sessions = Array.isArray(dataRows)\n ? (dataRows as Array<Record<string, unknown>>).map((row) => ({\n sessionId: row.session_id as string,\n agentId: row.agent_id as string,\n moduleId: row.module_id as string,\n userId: row.user_id as string,\n startedAt: toIsoString(row.started_at),\n lastEventAt: toIsoString(row.last_event_at),\n stepCount: toInteger(row.step_count),\n turnCount: toInteger(row.turn_count),\n inputTokens: toInteger(row.input_tokens),\n outputTokens: toInteger(row.output_tokens),\n cachedInputTokens: toInteger(row.cached_input_tokens),\n reasoningTokens: toInteger(row.reasoning_tokens),\n }))\n : []\n\n return NextResponse.json({ sessions, total, limit, offset })\n } catch (error) {\n logger.error('AI Usage Sessions \u2014 GET error', { err: error })\n return jsonError(500, 'Failed to fetch session usage data.', 'internal_error')\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,oBAAsC;AAC/C,SAAS,SAAS;AAGlB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,2BAA2B;AACpC,SAAS,WAAW,mBAAmB;AAEvC,MAAM,SAAS,aAAa,cAAc;AAE1C,MAAM,mBAAmB;AAEzB,MAAM,gBAAgB;AAEtB,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,MAAM,EAAE,OAAO,EAAE,MAAM,uBAAuB,0CAA0C;AAAA,EACxF,IAAI,EAAE,OAAO,EAAE,MAAM,uBAAuB,wCAAwC;AAAA,EACpF,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC7C,OAAO,EACJ,OAAO,EACP,SAAS,EACT,UAAU,CAAC,QAAS,QAAQ,SAAY,SAAS,KAAK,EAAE,IAAI,aAAc,EAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,KAAK,MAAM,KAAK,OAAO,eAAe;AAAA,IAC/D,SAAS,+BAA+B,aAAa;AAAA,EACvD,CAAC;AAAA,EACH,QAAQ,EACL,OAAO,EACP,SAAS,EACT,UAAU,CAAC,QAAS,QAAQ,SAAY,SAAS,KAAK,EAAE,IAAI,CAAE,EAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,KAAK,OAAO,GAAG,EAAE,SAAS,wCAAwC,CAAC;AAClG,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,aAAa;AAAA,MACb,SAAS;AAAA,MACT,aACE;AAAA,MAGF,OAAO;AAAA,MACP,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,4BAA4B;AAAA,QACxD,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,MAAM;AAAA,EACN,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,gBAAgB,EAAE;AAChE;AAEA,SAAS,UAAU,QAAgB,SAAiB,MAAc,OAA+C;AAC/G,SAAO,aAAa,KAAK,EAAE,OAAO,SAAS,MAAM,GAAI,SAAS,CAAC,EAAG,GAAG,EAAE,OAAO,CAAC;AACjF;AAEA,eAAsB,IAAI,KAAqC;AAC7D,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,UAAU,KAAK,gBAAgB,iBAAiB;AAAA,EACzD;AAEA,QAAM,EAAE,aAAa,IAAI,IAAI,IAAI,IAAI,GAAG;AACxC,QAAM,WAAW;AAAA,IACf,MAAM,aAAa,IAAI,MAAM,KAAK;AAAA,IAClC,IAAI,aAAa,IAAI,IAAI,KAAK;AAAA,IAC9B,SAAS,aAAa,IAAI,SAAS,KAAK;AAAA,IACxC,OAAO,aAAa,IAAI,OAAO,KAAK;AAAA,IACpC,QAAQ,aAAa,IAAI,QAAQ,KAAK;AAAA,EACxC;AAEA,QAAM,cAAc,YAAY,UAAU,QAAQ;AAClD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,UAAU,KAAK,6BAA6B,oBAAoB;AAAA,MACrE,QAAQ,YAAY,MAAM;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,QAAM,EAAE,MAAM,IAAI,SAAS,OAAO,OAAO,IAAI,YAAY;AAEzD,MAAI;AACF,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,cAAc,UAAU,QAAqB,aAAa;AAChE,UAAM,MAAM,MAAM,YAAY,QAAQ,KAAK,KAAK;AAAA,MAC9C,UAAU,KAAK;AAAA,MACf,gBAAgB,KAAK;AAAA,IACvB,CAAC;AAED,QAAI,CAAC,oBAAoB,CAAC,gBAAgB,GAAG,IAAI,UAAU,IAAI,cAAc,WAAW,GAAG;AACzF,aAAO,UAAU,KAAK,kCAAkC,gBAAgB,MAAM,WAAW;AAAA,IAC3F;AAEA,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,aAAa,KAAK,EAAE,UAAU,CAAC,GAAG,OAAO,GAAG,OAAO,OAAO,CAAC;AAAA,IACpE;AAEA,UAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,UAAM,aAAa,GAAG,cAAc;AAEpC,UAAM,SAAoB,CAAC,KAAK,UAAU,MAAM,EAAE;AAClD,QAAI,cAAc;AAClB,QAAI,SAAS;AACX,oBAAc;AACd,aAAO,KAAK,OAAO;AAAA,IACrB;AAEA,UAAM,cAAc,CAAC,GAAG,MAAM;AAC9B,UAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMX,WAAW;AAAA;AAEjB,UAAM,YAAY,MAAM,WAAW,QAAQ,UAAU,aAAa,KAAK;AACvE,UAAM,WAAW,MAAM,QAAQ,SAAS,KAAK,UAAU,SAAS,IAC3D,UAAU,CAAC,EAA8B,QAC1C;AACJ,UAAM,QAAQ,UAAU,QAAQ;AAEhC,WAAO,KAAK,OAAO,MAAM;AACzB,UAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAkBV,WAAW;AAAA;AAAA;AAAA;AAAA;AAKjB,UAAM,WAAW,MAAM,WAAW,QAAQ,SAAS,QAAQ,KAAK;AAEhE,UAAM,WAAW,MAAM,QAAQ,QAAQ,IAClC,SAA4C,IAAI,CAAC,SAAS;AAAA,MACzD,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,UAAU,IAAI;AAAA,MACd,QAAQ,IAAI;AAAA,MACZ,WAAW,YAAY,IAAI,UAAU;AAAA,MACrC,aAAa,YAAY,IAAI,aAAa;AAAA,MAC1C,WAAW,UAAU,IAAI,UAAU;AAAA,MACnC,WAAW,UAAU,IAAI,UAAU;AAAA,MACnC,aAAa,UAAU,IAAI,YAAY;AAAA,MACvC,cAAc,UAAU,IAAI,aAAa;AAAA,MACzC,mBAAmB,UAAU,IAAI,mBAAmB;AAAA,MACpD,iBAAiB,UAAU,IAAI,gBAAgB;AAAA,IACjD,EAAE,IACF,CAAC;AAEL,WAAO,aAAa,KAAK,EAAE,UAAU,OAAO,OAAO,OAAO,CAAC;AAAA,EAC7D,SAAS,OAAO;AACd,WAAO,MAAM,sCAAiC,EAAE,KAAK,MAAM,CAAC;AAC5D,WAAO,UAAU,KAAK,uCAAuC,gBAAgB;AAAA,EAC/E;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
2
|
+
import { resetServerLoggerCache } from "@open-mercato/shared/lib/logger";
|
|
2
3
|
async function ensureBootstrap() {
|
|
3
4
|
try {
|
|
4
5
|
const { getDiRegistrars } = await import("@open-mercato/shared/lib/di/container");
|
|
@@ -33,6 +34,8 @@ function parseArgs(rest) {
|
|
|
33
34
|
const mcpServe = {
|
|
34
35
|
command: "mcp:serve",
|
|
35
36
|
async run(rest) {
|
|
37
|
+
process.env.OM_LOG_DESTINATION = "stderr";
|
|
38
|
+
resetServerLoggerCache();
|
|
36
39
|
const args = parseArgs(rest);
|
|
37
40
|
const apiKey = String(args["api-key"] ?? args.apiKey ?? "") || process.env.OPEN_MERCATO_API_KEY || null;
|
|
38
41
|
const tenantId = String(args.tenant ?? args.tenantId ?? "") || null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/ai_assistant/cli.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\n/**\n * Ensure app bootstrap is called before creating DI container.\n * Uses the shared generated-bootstrap loader so the command works both from\n * the monorepo app and from standalone apps installed through npm packages.\n */\nasync function ensureBootstrap(): Promise<void> {\n // First check if DI is already available\n try {\n const { getDiRegistrars } = await import('@open-mercato/shared/lib/di/container')\n getDiRegistrars()\n return // DI already available\n } catch {\n // DI not available, need to bootstrap\n }\n\n try {\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n await bootstrapFromAppRoot()\n } catch (error) {\n console.error('[MCP] Bootstrap failed:', error instanceof Error ? error.message : error)\n // Continue - some contexts may not have bootstrap available\n }\n}\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n if (!arg?.startsWith('--')) continue\n\n const [key, value] = arg.replace(/^--/, '').split('=')\n if (value !== undefined) {\n args[key] = value\n } else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) {\n args[key] = rest[i + 1]!\n i++\n } else {\n args[key] = true\n }\n }\n return args\n}\n\nconst mcpServe: ModuleCli = {\n command: 'mcp:serve',\n async run(rest) {\n const args = parseArgs(rest)\n // Prefer the OPEN_MERCATO_API_KEY env var so the secret never has to be\n // placed on the command line (argv is world-readable via ps / /proc).\n // The --api-key flag stays supported for backward compatibility.\n const apiKey = String(args['api-key'] ?? args.apiKey ?? '') || process.env.OPEN_MERCATO_API_KEY || null\n const tenantId = String(args.tenant ?? args.tenantId ?? '') || null\n const organizationId = String(args.org ?? args.organizationId ?? '') || null\n const userId = String(args.user ?? args.userId ?? '') || null\n const debug = args.debug === true || args.debug === 'true'\n const allowUnauthenticatedSuperadmin =\n args['allow-unauthenticated-superadmin'] === true ||\n args['allow-unauthenticated-superadmin'] === 'true'\n\n // Either API key or tenant is required\n if (!apiKey && !tenantId) {\n console.error('Usage: mercato ai_assistant mcp:serve [options]')\n console.error('')\n console.error('Authentication (choose one):')\n console.error(' OPEN_MERCATO_API_KEY env API key secret (recommended \u2014 keeps the secret off argv)')\n console.error(' --api-key <secret> API key secret for authentication (visible in process listings)')\n console.error(' --tenant <id> Tenant ID (for manual context)')\n console.error('')\n console.error('Options (with --tenant):')\n console.error(' --org <id> Organization ID (optional)')\n console.error(' --user <id> User ID for ACL (required unless --allow-unauthenticated-superadmin is set)')\n console.error('')\n console.error('Common options:')\n console.error(' --debug Enable debug logging')\n console.error(' --allow-unauthenticated-superadmin')\n console.error(' DEV/TEST ONLY: run as superadmin with no per-user ACL when')\n console.error(' no --user (and no --api-key) is supplied. Never use in production.')\n console.error('')\n console.error('Examples:')\n console.error(' OPEN_MERCATO_API_KEY=omk_xxxx.yyyy... mercato ai_assistant mcp:serve')\n console.error(' mercato ai_assistant mcp:serve --api-key omk_xxxx.yyyy...')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --user <user-id>')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --allow-unauthenticated-superadmin')\n return\n }\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpServer } = await import('./lib/mcp-server')\n\n if (apiKey) {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n apiKeySecret: apiKey,\n })\n } else {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n context: {\n tenantId,\n organizationId,\n userId,\n },\n allowUnauthenticatedSuperadmin,\n })\n }\n },\n}\n\nconst MCP_DEFAULT_PORT = 3001\n\nconst mcpServeHttp: ModuleCli = {\n command: 'mcp:serve-http',\n async run(rest) {\n const args = parseArgs(rest)\n const portArg = parseInt(String(args.port ?? ''), 10)\n const port = !portArg || isNaN(portArg) ? MCP_DEFAULT_PORT : portArg\n const debug = args.debug === true || args.debug === 'true'\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpHttpServer } = await import('./lib/http-server')\n\n await runMcpHttpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n port,\n })\n },\n}\n\nconst mcpDev: ModuleCli = {\n command: 'mcp:dev',\n async run() {\n await ensureBootstrap()\n const { runMcpDevServer } = await import('./lib/mcp-dev-server')\n await runMcpDevServer()\n },\n}\n\nconst listTools: ModuleCli = {\n command: 'mcp:list-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const verbose = args.verbose === true || args.verbose === 'true'\n\n // Ensure bootstrap runs so modules are registered for API discovery\n await ensureBootstrap()\n\n const { loadAllModuleTools } = await import('./lib/tool-loader')\n await loadAllModuleTools()\n\n const { getToolRegistry } = await import('./lib/tool-registry')\n const registry = getToolRegistry()\n const toolNames = registry.listToolNames()\n\n if (toolNames.length === 0) {\n console.log('\\nNo MCP tools registered.')\n console.log('Tools can be registered by modules using registerMcpTool().\\n')\n return\n }\n\n console.log(`\\nRegistered MCP Tools (${toolNames.length}):\\n`)\n\n // Group tools by module\n const byModule = new Map<string, string[]>()\n for (const name of toolNames) {\n const [module] = name.split('.')\n const list = byModule.get(module) ?? []\n list.push(name)\n byModule.set(module, list)\n }\n\n // Sort modules alphabetically\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n\n for (const module of sortedModules) {\n const tools = byModule.get(module)!\n console.log(`${module} (${tools.length} tools):`)\n\n for (const name of tools.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))) {\n const tool = registry.getTool(name)\n if (!tool) continue\n\n if (verbose) {\n console.log(` ${name}`)\n console.log(` ${tool.description}`)\n if (tool.requiredFeatures?.length) {\n console.log(` Requires: ${tool.requiredFeatures.join(', ')}`)\n }\n } else {\n console.log(` - ${name}`)\n }\n }\n console.log('')\n }\n },\n}\n\nconst entityGraph: ModuleCli = {\n command: 'entity-graph',\n async run(rest) {\n const args = parseArgs(rest)\n const format = String(args.format ?? 'triples') as 'json' | 'triples'\n const entity = args.entity ? String(args.entity) : undefined\n const module = args.module ? String(args.module) : undefined\n\n await ensureBootstrap()\n\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n const { extractEntityGraph, formatGraphAsTriples, filterGraphByEntity, filterGraphByModule } = await import(\n './lib/entity-graph'\n )\n\n console.log('[Entity Graph] Extracting from MikroORM metadata...')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n\n // Apply filters\n let edges = graph.edges\n\n if (entity) {\n edges = filterGraphByEntity(graph, entity)\n console.log(`[Entity Graph] Filtered by entity: ${entity}`)\n }\n\n if (module) {\n const filteredGraph = { ...graph, edges }\n edges = filterGraphByModule(filteredGraph, module)\n console.log(`[Entity Graph] Filtered by module: ${module}`)\n }\n\n const filteredGraph = { ...graph, edges }\n\n if (format === 'json') {\n console.log(JSON.stringify(filteredGraph, null, 2))\n } else {\n const triples = formatGraphAsTriples(filteredGraph)\n console.log('')\n for (const triple of triples) {\n console.log(triple)\n }\n }\n\n console.log(`\\n[Entity Graph] ${graph.nodes.length} entities, ${edges.length} relationships`)\n },\n}\n\nconst runPendingActionCleanup: ModuleCli = {\n command: 'run-pending-action-cleanup',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runPendingActionCleanup: runCleanup } = await import(\n './workers/ai-pending-action-cleanup'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runCleanup({ em })\n\n console.log('[ai-pending-action-cleanup] Sweep complete:', summary)\n },\n}\n\nconst testTools: ModuleCli = {\n command: 'test-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const json = args.json === true || args.json === 'true'\n const moduleFilter =\n typeof args.module === 'string' && args.module.length > 0 ? args.module : null\n const includeMutations = args['no-mutations'] !== true && args['no-mutations'] !== 'true'\n const tenantId =\n typeof args.tenant === 'string' && args.tenant.length > 0 ? args.tenant : null\n const organizationId =\n typeof args.org === 'string' && args.org.length > 0 ? args.org : null\n\n await ensureBootstrap()\n const { runToolTests } = await import('./lib/tool-test-runner')\n const report = await runToolTests({\n tenantId,\n organizationId,\n moduleFilter,\n includeMutations,\n })\n\n if (json) {\n // Wrap in markers so a Playwright spec (or any caller) can extract the\n // JSON payload without being thrown off by bootstrap log lines emitted\n // to stdout by other modules during DI container creation.\n console.log('---TOOL_TEST_REPORT_BEGIN---')\n console.log(JSON.stringify(report))\n console.log('---TOOL_TEST_REPORT_END---')\n } else {\n console.log('')\n console.log(\n `AI tool test report \u2014 tenant=${report.tenantId ?? '<none>'} org=${\n report.organizationId ?? '<none>'\n }`,\n )\n console.log(\n `total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,\n )\n const byModule = new Map<string, typeof report.records>()\n for (const record of report.records) {\n const list = byModule.get(record.module) ?? []\n list.push(record)\n byModule.set(record.module, list)\n }\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n for (const moduleId of sortedModules) {\n const list = byModule.get(moduleId)!\n console.log('')\n console.log(`${moduleId} (${list.length}):`)\n for (const record of list) {\n const marker =\n record.status === 'pass' ? '\u2713' : record.status === 'fail' ? '\u2717' : '\u00B7'\n const reason = record.reason ? ` \u2014 ${record.reason}` : ''\n const mutation = record.isMutation ? ' [mutation]' : ''\n console.log(\n ` ${marker} ${record.tool}${mutation} (${record.durationMs}ms)${reason}`,\n )\n }\n }\n console.log('')\n }\n\n if (report.failed > 0) {\n process.exitCode = 1\n }\n },\n}\n\nconst runTokenUsagePrune: ModuleCli = {\n command: 'run-token-usage-prune',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runTokenUsagePrune: runPrune } = await import(\n './workers/ai-token-usage-prune'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runPrune({ em })\n\n console.log('[ai-token-usage-prune] Prune complete:', summary)\n },\n}\n\nexport default [\n mcpServe,\n mcpServeHttp,\n mcpDev,\n listTools,\n entityGraph,\n runPendingActionCleanup,\n runTokenUsagePrune,\n testTools,\n]\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,8BAA8B;AAMvC,eAAe,kBAAiC;AAE9C,MAAI;AACF,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,uCAAuC;AAChF,oBAAgB;AAChB;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,kDAAkD;AAChG,UAAM,qBAAqB;AAAA,EAC7B,SAAS,OAAO;AACd,YAAQ,MAAM,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,EAEzF;AACF;AAEA,SAAS,UAAU,MAAkD;AACnE,QAAM,OAAyC,CAAC;AAChD,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,CAAC,KAAK,WAAW,IAAI,EAAG;AAE5B,UAAM,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG;AACrD,QAAI,UAAU,QAAW;AACvB,WAAK,GAAG,IAAI;AAAA,IACd,WAAW,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,EAAG,WAAW,IAAI,GAAG;AACxD,WAAK,GAAG,IAAI,KAAK,IAAI,CAAC;AACtB;AAAA,IACF,OAAO;AACL,WAAK,GAAG,IAAI;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,WAAsB;AAAA,EAC1B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;
|
|
4
|
+
"sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resetServerLoggerCache } from '@open-mercato/shared/lib/logger'\n/**\n * Ensure app bootstrap is called before creating DI container.\n * Uses the shared generated-bootstrap loader so the command works both from\n * the monorepo app and from standalone apps installed through npm packages.\n */\nasync function ensureBootstrap(): Promise<void> {\n // First check if DI is already available\n try {\n const { getDiRegistrars } = await import('@open-mercato/shared/lib/di/container')\n getDiRegistrars()\n return // DI already available\n } catch {\n // DI not available, need to bootstrap\n }\n\n try {\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n await bootstrapFromAppRoot()\n } catch (error) {\n console.error('[MCP] Bootstrap failed:', error instanceof Error ? error.message : error)\n // Continue - some contexts may not have bootstrap available\n }\n}\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n if (!arg?.startsWith('--')) continue\n\n const [key, value] = arg.replace(/^--/, '').split('=')\n if (value !== undefined) {\n args[key] = value\n } else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) {\n args[key] = rest[i + 1]!\n i++\n } else {\n args[key] = true\n }\n }\n return args\n}\n\nconst mcpServe: ModuleCli = {\n command: 'mcp:serve',\n async run(rest) {\n // stdout carries the MCP JSON-RPC protocol (StdioServerTransport) \u2014 route all\n // facade logs to stderr and rebuild any pino root created earlier in this process.\n process.env.OM_LOG_DESTINATION = 'stderr'\n resetServerLoggerCache()\n const args = parseArgs(rest)\n // Prefer the OPEN_MERCATO_API_KEY env var so the secret never has to be\n // placed on the command line (argv is world-readable via ps / /proc).\n // The --api-key flag stays supported for backward compatibility.\n const apiKey = String(args['api-key'] ?? args.apiKey ?? '') || process.env.OPEN_MERCATO_API_KEY || null\n const tenantId = String(args.tenant ?? args.tenantId ?? '') || null\n const organizationId = String(args.org ?? args.organizationId ?? '') || null\n const userId = String(args.user ?? args.userId ?? '') || null\n const debug = args.debug === true || args.debug === 'true'\n const allowUnauthenticatedSuperadmin =\n args['allow-unauthenticated-superadmin'] === true ||\n args['allow-unauthenticated-superadmin'] === 'true'\n\n // Either API key or tenant is required\n if (!apiKey && !tenantId) {\n console.error('Usage: mercato ai_assistant mcp:serve [options]')\n console.error('')\n console.error('Authentication (choose one):')\n console.error(' OPEN_MERCATO_API_KEY env API key secret (recommended \u2014 keeps the secret off argv)')\n console.error(' --api-key <secret> API key secret for authentication (visible in process listings)')\n console.error(' --tenant <id> Tenant ID (for manual context)')\n console.error('')\n console.error('Options (with --tenant):')\n console.error(' --org <id> Organization ID (optional)')\n console.error(' --user <id> User ID for ACL (required unless --allow-unauthenticated-superadmin is set)')\n console.error('')\n console.error('Common options:')\n console.error(' --debug Enable debug logging')\n console.error(' --allow-unauthenticated-superadmin')\n console.error(' DEV/TEST ONLY: run as superadmin with no per-user ACL when')\n console.error(' no --user (and no --api-key) is supplied. Never use in production.')\n console.error('')\n console.error('Examples:')\n console.error(' OPEN_MERCATO_API_KEY=omk_xxxx.yyyy... mercato ai_assistant mcp:serve')\n console.error(' mercato ai_assistant mcp:serve --api-key omk_xxxx.yyyy...')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --user <user-id>')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --allow-unauthenticated-superadmin')\n return\n }\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpServer } = await import('./lib/mcp-server')\n\n if (apiKey) {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n apiKeySecret: apiKey,\n })\n } else {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n context: {\n tenantId,\n organizationId,\n userId,\n },\n allowUnauthenticatedSuperadmin,\n })\n }\n },\n}\n\nconst MCP_DEFAULT_PORT = 3001\n\nconst mcpServeHttp: ModuleCli = {\n command: 'mcp:serve-http',\n async run(rest) {\n const args = parseArgs(rest)\n const portArg = parseInt(String(args.port ?? ''), 10)\n const port = !portArg || isNaN(portArg) ? MCP_DEFAULT_PORT : portArg\n const debug = args.debug === true || args.debug === 'true'\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpHttpServer } = await import('./lib/http-server')\n\n await runMcpHttpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n port,\n })\n },\n}\n\nconst mcpDev: ModuleCli = {\n command: 'mcp:dev',\n async run() {\n await ensureBootstrap()\n const { runMcpDevServer } = await import('./lib/mcp-dev-server')\n await runMcpDevServer()\n },\n}\n\nconst listTools: ModuleCli = {\n command: 'mcp:list-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const verbose = args.verbose === true || args.verbose === 'true'\n\n // Ensure bootstrap runs so modules are registered for API discovery\n await ensureBootstrap()\n\n const { loadAllModuleTools } = await import('./lib/tool-loader')\n await loadAllModuleTools()\n\n const { getToolRegistry } = await import('./lib/tool-registry')\n const registry = getToolRegistry()\n const toolNames = registry.listToolNames()\n\n if (toolNames.length === 0) {\n console.log('\\nNo MCP tools registered.')\n console.log('Tools can be registered by modules using registerMcpTool().\\n')\n return\n }\n\n console.log(`\\nRegistered MCP Tools (${toolNames.length}):\\n`)\n\n // Group tools by module\n const byModule = new Map<string, string[]>()\n for (const name of toolNames) {\n const [module] = name.split('.')\n const list = byModule.get(module) ?? []\n list.push(name)\n byModule.set(module, list)\n }\n\n // Sort modules alphabetically\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n\n for (const module of sortedModules) {\n const tools = byModule.get(module)!\n console.log(`${module} (${tools.length} tools):`)\n\n for (const name of tools.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))) {\n const tool = registry.getTool(name)\n if (!tool) continue\n\n if (verbose) {\n console.log(` ${name}`)\n console.log(` ${tool.description}`)\n if (tool.requiredFeatures?.length) {\n console.log(` Requires: ${tool.requiredFeatures.join(', ')}`)\n }\n } else {\n console.log(` - ${name}`)\n }\n }\n console.log('')\n }\n },\n}\n\nconst entityGraph: ModuleCli = {\n command: 'entity-graph',\n async run(rest) {\n const args = parseArgs(rest)\n const format = String(args.format ?? 'triples') as 'json' | 'triples'\n const entity = args.entity ? String(args.entity) : undefined\n const module = args.module ? String(args.module) : undefined\n\n await ensureBootstrap()\n\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n const { extractEntityGraph, formatGraphAsTriples, filterGraphByEntity, filterGraphByModule } = await import(\n './lib/entity-graph'\n )\n\n console.log('[Entity Graph] Extracting from MikroORM metadata...')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n\n // Apply filters\n let edges = graph.edges\n\n if (entity) {\n edges = filterGraphByEntity(graph, entity)\n console.log(`[Entity Graph] Filtered by entity: ${entity}`)\n }\n\n if (module) {\n const filteredGraph = { ...graph, edges }\n edges = filterGraphByModule(filteredGraph, module)\n console.log(`[Entity Graph] Filtered by module: ${module}`)\n }\n\n const filteredGraph = { ...graph, edges }\n\n if (format === 'json') {\n console.log(JSON.stringify(filteredGraph, null, 2))\n } else {\n const triples = formatGraphAsTriples(filteredGraph)\n console.log('')\n for (const triple of triples) {\n console.log(triple)\n }\n }\n\n console.log(`\\n[Entity Graph] ${graph.nodes.length} entities, ${edges.length} relationships`)\n },\n}\n\nconst runPendingActionCleanup: ModuleCli = {\n command: 'run-pending-action-cleanup',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runPendingActionCleanup: runCleanup } = await import(\n './workers/ai-pending-action-cleanup'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runCleanup({ em })\n\n console.log('[ai-pending-action-cleanup] Sweep complete:', summary)\n },\n}\n\nconst testTools: ModuleCli = {\n command: 'test-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const json = args.json === true || args.json === 'true'\n const moduleFilter =\n typeof args.module === 'string' && args.module.length > 0 ? args.module : null\n const includeMutations = args['no-mutations'] !== true && args['no-mutations'] !== 'true'\n const tenantId =\n typeof args.tenant === 'string' && args.tenant.length > 0 ? args.tenant : null\n const organizationId =\n typeof args.org === 'string' && args.org.length > 0 ? args.org : null\n\n await ensureBootstrap()\n const { runToolTests } = await import('./lib/tool-test-runner')\n const report = await runToolTests({\n tenantId,\n organizationId,\n moduleFilter,\n includeMutations,\n })\n\n if (json) {\n // Wrap in markers so a Playwright spec (or any caller) can extract the\n // JSON payload without being thrown off by bootstrap log lines emitted\n // to stdout by other modules during DI container creation.\n console.log('---TOOL_TEST_REPORT_BEGIN---')\n console.log(JSON.stringify(report))\n console.log('---TOOL_TEST_REPORT_END---')\n } else {\n console.log('')\n console.log(\n `AI tool test report \u2014 tenant=${report.tenantId ?? '<none>'} org=${\n report.organizationId ?? '<none>'\n }`,\n )\n console.log(\n `total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,\n )\n const byModule = new Map<string, typeof report.records>()\n for (const record of report.records) {\n const list = byModule.get(record.module) ?? []\n list.push(record)\n byModule.set(record.module, list)\n }\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n for (const moduleId of sortedModules) {\n const list = byModule.get(moduleId)!\n console.log('')\n console.log(`${moduleId} (${list.length}):`)\n for (const record of list) {\n const marker =\n record.status === 'pass' ? '\u2713' : record.status === 'fail' ? '\u2717' : '\u00B7'\n const reason = record.reason ? ` \u2014 ${record.reason}` : ''\n const mutation = record.isMutation ? ' [mutation]' : ''\n console.log(\n ` ${marker} ${record.tool}${mutation} (${record.durationMs}ms)${reason}`,\n )\n }\n }\n console.log('')\n }\n\n if (report.failed > 0) {\n process.exitCode = 1\n }\n },\n}\n\nconst runTokenUsagePrune: ModuleCli = {\n command: 'run-token-usage-prune',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runTokenUsagePrune: runPrune } = await import(\n './workers/ai-token-usage-prune'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runPrune({ em })\n\n console.log('[ai-token-usage-prune] Prune complete:', summary)\n },\n}\n\nexport default [\n mcpServe,\n mcpServeHttp,\n mcpDev,\n listTools,\n entityGraph,\n runPendingActionCleanup,\n runTokenUsagePrune,\n testTools,\n]\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["filteredGraph"]
|
|
7
7
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
1
2
|
import { getAgent } from "./agent-registry.js";
|
|
2
3
|
import { hasRequiredFeatures } from "./auth.js";
|
|
3
4
|
import { toolRegistry } from "./tool-registry.js";
|
|
5
|
+
const logger = createLogger("ai_assistant");
|
|
4
6
|
const POLICY_RESTRICTIVENESS = {
|
|
5
7
|
"read-only": 0,
|
|
6
8
|
"destructive-confirm-required": 1,
|
|
@@ -13,11 +15,11 @@ function resolveEffectiveMutationPolicy(codeDeclared, override, agentId) {
|
|
|
13
15
|
const base = codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : "read-only";
|
|
14
16
|
if (override === void 0 || override === null) return base;
|
|
15
17
|
if (!isKnownMutationPolicy(override)) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
18
|
+
logger.warn("Ignoring corrupt mutationPolicy override; falling back to code-declared policy", {
|
|
19
|
+
agentId: agentId ?? "<unknown>",
|
|
20
|
+
override: String(override),
|
|
21
|
+
base
|
|
22
|
+
});
|
|
21
23
|
return base;
|
|
22
24
|
}
|
|
23
25
|
const baseRank = POLICY_RESTRICTIVENESS[base];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/agent-policy.ts"],
|
|
4
|
-
"sourcesContent": ["import { getAgent } from './agent-registry'\nimport { hasRequiredFeatures } from './auth'\nimport { toolRegistry } from './tool-registry'\nimport type {\n AiAgentAcceptedMediaType,\n AiAgentDefinition,\n AiAgentMutationPolicy,\n} from './ai-agent-definition'\nimport type { AiToolDefinition } from './types'\n\nexport type AgentPolicyDenyCode =\n | 'agent_unknown'\n | 'agent_features_denied'\n | 'tool_not_whitelisted'\n | 'tool_unknown'\n | 'tool_features_denied'\n | 'mutation_blocked_by_readonly'\n | 'mutation_blocked_by_policy'\n | 'execution_mode_not_supported'\n | 'attachment_type_not_accepted'\n // Loop policy codes \u2014 Phase 0 of spec 2026-04-28-ai-agents-agentic-loop-controls\n /** Object-mode rejects loop primitives that the SDK ignores for generateObject. */\n | 'loop_unsupported_in_object_mode'\n /** User prepareStep returned a tools map with a raw (unwrapped) mutation handler. */\n | 'loop_violates_mutation_policy'\n /** loop.activeTools contained names outside agent.allowedTools (thrown for caller-supplied overrides; warning-only for agent-declared). */\n | 'loop_active_tools_outside_allowlist'\n /** agent.loop.allowRuntimeOverride is false and a per-call loop override was supplied. */\n | 'loop_runtime_override_disabled'\n\nexport type AgentPolicyDecision =\n | { ok: true; agent: AiAgentDefinition; tool?: AiToolDefinition }\n | { ok: false; code: AgentPolicyDenyCode; message: string }\n\nexport interface AgentPolicyAuthContext {\n userFeatures: string[]\n isSuperAdmin: boolean\n}\n\nexport interface AgentPolicyCheckInput {\n agentId: string\n authContext: AgentPolicyAuthContext\n toolName?: string\n attachmentMediaTypes?: string[]\n requestedExecutionMode?: 'chat' | 'object'\n /**\n * Optional tenant-scoped downgrade for the agent's code-declared\n * `mutationPolicy`. When supplied, the effective policy is the MOST\n * RESTRICTIVE of `{ code-declared, override }` \u2014 escalation is never\n * allowed through this channel (that is enforced at the route layer).\n * Callers that omit this field get the exact pre-Step-5.4 behavior.\n */\n mutationPolicyOverride?: AiAgentMutationPolicy | null\n}\n\n/**\n * Restrictiveness ranking of `AiAgentMutationPolicy` (most restrictive first).\n * `read-only` blocks all mutation tools. `destructive-confirm-required` forces\n * confirmation for every write (including non-destructive ones). `confirm-required`\n * is the least restrictive policy \u2014 writes go through a single confirmation.\n *\n * This ordering is load-bearing for both the runtime's effective-policy\n * computation AND the route-layer escalation guard. Changing the order is a\n * security-sensitive change.\n */\nconst POLICY_RESTRICTIVENESS: Record<AiAgentMutationPolicy, number> = {\n 'read-only': 0,\n 'destructive-confirm-required': 1,\n 'confirm-required': 2,\n}\n\nexport function isKnownMutationPolicy(value: unknown): value is AiAgentMutationPolicy {\n return (\n value === 'read-only' ||\n value === 'confirm-required' ||\n value === 'destructive-confirm-required'\n )\n}\n\n/**\n * Returns the effective mutation policy \u2014 the MOST RESTRICTIVE of\n * `{ codeDeclared, override }`. Missing override \u2192 `codeDeclared`. A corrupt\n * override value (unknown string from DB) is logged and falls back to\n * `codeDeclared` so the system fails SAFE when a schema drift leaks through.\n */\nexport function resolveEffectiveMutationPolicy(\n codeDeclared: AiAgentMutationPolicy | undefined,\n override: AiAgentMutationPolicy | null | undefined,\n agentId?: string,\n): AiAgentMutationPolicy {\n const base: AiAgentMutationPolicy =\n codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : 'read-only'\n if (override === undefined || override === null) return base\n if (!isKnownMutationPolicy(override)) {\n console.warn(\n `[AI Agents] Ignoring corrupt mutationPolicy override for agent \"${agentId ?? '<unknown>'}\": ${String(\n override,\n )}. Falling back to code-declared policy \"${base}\".`,\n )\n return base\n }\n const baseRank = POLICY_RESTRICTIVENESS[base]\n const overrideRank = POLICY_RESTRICTIVENESS[override]\n return overrideRank < baseRank ? override : base\n}\n\n/**\n * Returns `true` when `candidate` would WIDEN `codeDeclared` \u2014 i.e. would\n * grant the agent more mutation surface than its code declares. Used by the\n * mutation-policy override route to reject escalation attempts with 400.\n */\nexport function isMutationPolicyEscalation(\n codeDeclared: AiAgentMutationPolicy | undefined,\n candidate: AiAgentMutationPolicy,\n): boolean {\n const base: AiAgentMutationPolicy =\n codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : 'read-only'\n return POLICY_RESTRICTIVENESS[candidate] > POLICY_RESTRICTIVENESS[base]\n}\n\nfunction classifyMediaType(value: string): AiAgentAcceptedMediaType {\n const normalized = value.trim().toLowerCase()\n if (normalized.startsWith('image/')) return 'image'\n if (normalized === 'application/pdf') return 'pdf'\n return 'file'\n}\n\nfunction isAgentReadOnly(agent: AiAgentDefinition): boolean {\n if (typeof agent.readOnly === 'boolean') return agent.readOnly\n return true\n}\n\n/**\n * Returns the effective mutation policy for a policy-check invocation \u2014 the\n * most restrictive of `{ agent.mutationPolicy, input.mutationPolicyOverride }`.\n * Pure-lookup helper; no I/O. Callers that need to know the same value outside\n * of a policy check should use {@link resolveEffectiveMutationPolicy} directly.\n */\nfunction resolvePolicyCheckMutationPolicy(\n agent: AiAgentDefinition,\n override: AiAgentMutationPolicy | null | undefined,\n): AiAgentMutationPolicy {\n return resolveEffectiveMutationPolicy(agent.mutationPolicy, override, agent.id)\n}\n\nfunction hasAgentStructuredOutput(agent: AiAgentDefinition): boolean {\n return Boolean(agent.output)\n}\n\nfunction agentExecutionMode(agent: AiAgentDefinition): 'chat' | 'object' {\n return agent.executionMode ?? 'chat'\n}\n\nexport function checkAgentPolicy(input: AgentPolicyCheckInput): AgentPolicyDecision {\n const {\n agentId,\n authContext,\n toolName,\n attachmentMediaTypes,\n requestedExecutionMode,\n mutationPolicyOverride,\n } = input\n\n const agent = getAgent(agentId)\n if (!agent) {\n return {\n ok: false,\n code: 'agent_unknown',\n message: `Agent \"${agentId}\" is not registered.`,\n }\n }\n\n const agentFeatures = agent.requiredFeatures ?? []\n if (\n !hasRequiredFeatures(agentFeatures, authContext.userFeatures, authContext.isSuperAdmin)\n ) {\n return {\n ok: false,\n code: 'agent_features_denied',\n message: `Access to agent \"${agentId}\" requires features: ${agentFeatures.join(', ')}`,\n }\n }\n\n let resolvedTool: AiToolDefinition | undefined\n if (typeof toolName === 'string') {\n if (!agent.allowedTools.includes(toolName)) {\n return {\n ok: false,\n code: 'tool_not_whitelisted',\n message: `Tool \"${toolName}\" is not whitelisted for agent \"${agentId}\".`,\n }\n }\n\n const toolRecord = toolRegistry.getTool(toolName) as AiToolDefinition | undefined\n if (!toolRecord) {\n return {\n ok: false,\n code: 'tool_unknown',\n message: `Tool \"${toolName}\" is not registered in the tool registry.`,\n }\n }\n\n const toolFeatures = toolRecord.requiredFeatures ?? []\n if (\n !hasRequiredFeatures(toolFeatures, authContext.userFeatures, authContext.isSuperAdmin)\n ) {\n return {\n ok: false,\n code: 'tool_features_denied',\n message: `Access to tool \"${toolName}\" requires features: ${toolFeatures.join(', ')}`,\n }\n }\n\n if (toolRecord.isMutation === true) {\n if (isAgentReadOnly(agent)) {\n return {\n ok: false,\n code: 'mutation_blocked_by_readonly',\n message: `Mutation tool \"${toolName}\" cannot be executed by read-only agent \"${agentId}\".`,\n }\n }\n const effectivePolicy = resolvePolicyCheckMutationPolicy(agent, mutationPolicyOverride)\n if (effectivePolicy === 'read-only') {\n return {\n ok: false,\n code: 'mutation_blocked_by_policy',\n message: `Mutation tool \"${toolName}\" is blocked by agent \"${agentId}\" mutationPolicy=read-only.`,\n }\n }\n }\n\n resolvedTool = toolRecord\n }\n\n if (requestedExecutionMode) {\n const declaredMode = agentExecutionMode(agent)\n if (requestedExecutionMode === 'object') {\n if (declaredMode !== 'object' && !hasAgentStructuredOutput(agent)) {\n return {\n ok: false,\n code: 'execution_mode_not_supported',\n message: `Agent \"${agentId}\" does not support execution mode \"object\" (no output schema declared).`,\n }\n }\n } else if (requestedExecutionMode === 'chat') {\n if (declaredMode === 'object' && hasAgentStructuredOutput(agent)) {\n return {\n ok: false,\n code: 'execution_mode_not_supported',\n message: `Agent \"${agentId}\" is declared as object-mode and cannot run via chat transport.`,\n }\n }\n }\n }\n\n if (Array.isArray(attachmentMediaTypes) && attachmentMediaTypes.length > 0) {\n const accepted = agent.acceptedMediaTypes\n if (!accepted || accepted.length === 0) {\n return {\n ok: false,\n code: 'attachment_type_not_accepted',\n message: `Agent \"${agentId}\" does not accept attachments.`,\n }\n }\n const acceptedSet = new Set<AiAgentAcceptedMediaType>(accepted)\n for (const raw of attachmentMediaTypes) {\n const kind = classifyMediaType(raw)\n if (!acceptedSet.has(kind)) {\n return {\n ok: false,\n code: 'attachment_type_not_accepted',\n message: `Agent \"${agentId}\" does not accept media type \"${raw}\" (classified as \"${kind}\").`,\n }\n }\n }\n }\n\n return { ok: true, agent, tool: resolvedTool }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getAgent } from './agent-registry'\nimport { hasRequiredFeatures } from './auth'\nimport { toolRegistry } from './tool-registry'\nimport type {\n AiAgentAcceptedMediaType,\n AiAgentDefinition,\n AiAgentMutationPolicy,\n} from './ai-agent-definition'\nimport type { AiToolDefinition } from './types'\n\nconst logger = createLogger('ai_assistant')\n\nexport type AgentPolicyDenyCode =\n | 'agent_unknown'\n | 'agent_features_denied'\n | 'tool_not_whitelisted'\n | 'tool_unknown'\n | 'tool_features_denied'\n | 'mutation_blocked_by_readonly'\n | 'mutation_blocked_by_policy'\n | 'execution_mode_not_supported'\n | 'attachment_type_not_accepted'\n // Loop policy codes \u2014 Phase 0 of spec 2026-04-28-ai-agents-agentic-loop-controls\n /** Object-mode rejects loop primitives that the SDK ignores for generateObject. */\n | 'loop_unsupported_in_object_mode'\n /** User prepareStep returned a tools map with a raw (unwrapped) mutation handler. */\n | 'loop_violates_mutation_policy'\n /** loop.activeTools contained names outside agent.allowedTools (thrown for caller-supplied overrides; warning-only for agent-declared). */\n | 'loop_active_tools_outside_allowlist'\n /** agent.loop.allowRuntimeOverride is false and a per-call loop override was supplied. */\n | 'loop_runtime_override_disabled'\n\nexport type AgentPolicyDecision =\n | { ok: true; agent: AiAgentDefinition; tool?: AiToolDefinition }\n | { ok: false; code: AgentPolicyDenyCode; message: string }\n\nexport interface AgentPolicyAuthContext {\n userFeatures: string[]\n isSuperAdmin: boolean\n}\n\nexport interface AgentPolicyCheckInput {\n agentId: string\n authContext: AgentPolicyAuthContext\n toolName?: string\n attachmentMediaTypes?: string[]\n requestedExecutionMode?: 'chat' | 'object'\n /**\n * Optional tenant-scoped downgrade for the agent's code-declared\n * `mutationPolicy`. When supplied, the effective policy is the MOST\n * RESTRICTIVE of `{ code-declared, override }` \u2014 escalation is never\n * allowed through this channel (that is enforced at the route layer).\n * Callers that omit this field get the exact pre-Step-5.4 behavior.\n */\n mutationPolicyOverride?: AiAgentMutationPolicy | null\n}\n\n/**\n * Restrictiveness ranking of `AiAgentMutationPolicy` (most restrictive first).\n * `read-only` blocks all mutation tools. `destructive-confirm-required` forces\n * confirmation for every write (including non-destructive ones). `confirm-required`\n * is the least restrictive policy \u2014 writes go through a single confirmation.\n *\n * This ordering is load-bearing for both the runtime's effective-policy\n * computation AND the route-layer escalation guard. Changing the order is a\n * security-sensitive change.\n */\nconst POLICY_RESTRICTIVENESS: Record<AiAgentMutationPolicy, number> = {\n 'read-only': 0,\n 'destructive-confirm-required': 1,\n 'confirm-required': 2,\n}\n\nexport function isKnownMutationPolicy(value: unknown): value is AiAgentMutationPolicy {\n return (\n value === 'read-only' ||\n value === 'confirm-required' ||\n value === 'destructive-confirm-required'\n )\n}\n\n/**\n * Returns the effective mutation policy \u2014 the MOST RESTRICTIVE of\n * `{ codeDeclared, override }`. Missing override \u2192 `codeDeclared`. A corrupt\n * override value (unknown string from DB) is logged and falls back to\n * `codeDeclared` so the system fails SAFE when a schema drift leaks through.\n */\nexport function resolveEffectiveMutationPolicy(\n codeDeclared: AiAgentMutationPolicy | undefined,\n override: AiAgentMutationPolicy | null | undefined,\n agentId?: string,\n): AiAgentMutationPolicy {\n const base: AiAgentMutationPolicy =\n codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : 'read-only'\n if (override === undefined || override === null) return base\n if (!isKnownMutationPolicy(override)) {\n logger.warn('Ignoring corrupt mutationPolicy override; falling back to code-declared policy', {\n agentId: agentId ?? '<unknown>',\n override: String(override),\n base,\n })\n return base\n }\n const baseRank = POLICY_RESTRICTIVENESS[base]\n const overrideRank = POLICY_RESTRICTIVENESS[override]\n return overrideRank < baseRank ? override : base\n}\n\n/**\n * Returns `true` when `candidate` would WIDEN `codeDeclared` \u2014 i.e. would\n * grant the agent more mutation surface than its code declares. Used by the\n * mutation-policy override route to reject escalation attempts with 400.\n */\nexport function isMutationPolicyEscalation(\n codeDeclared: AiAgentMutationPolicy | undefined,\n candidate: AiAgentMutationPolicy,\n): boolean {\n const base: AiAgentMutationPolicy =\n codeDeclared && isKnownMutationPolicy(codeDeclared) ? codeDeclared : 'read-only'\n return POLICY_RESTRICTIVENESS[candidate] > POLICY_RESTRICTIVENESS[base]\n}\n\nfunction classifyMediaType(value: string): AiAgentAcceptedMediaType {\n const normalized = value.trim().toLowerCase()\n if (normalized.startsWith('image/')) return 'image'\n if (normalized === 'application/pdf') return 'pdf'\n return 'file'\n}\n\nfunction isAgentReadOnly(agent: AiAgentDefinition): boolean {\n if (typeof agent.readOnly === 'boolean') return agent.readOnly\n return true\n}\n\n/**\n * Returns the effective mutation policy for a policy-check invocation \u2014 the\n * most restrictive of `{ agent.mutationPolicy, input.mutationPolicyOverride }`.\n * Pure-lookup helper; no I/O. Callers that need to know the same value outside\n * of a policy check should use {@link resolveEffectiveMutationPolicy} directly.\n */\nfunction resolvePolicyCheckMutationPolicy(\n agent: AiAgentDefinition,\n override: AiAgentMutationPolicy | null | undefined,\n): AiAgentMutationPolicy {\n return resolveEffectiveMutationPolicy(agent.mutationPolicy, override, agent.id)\n}\n\nfunction hasAgentStructuredOutput(agent: AiAgentDefinition): boolean {\n return Boolean(agent.output)\n}\n\nfunction agentExecutionMode(agent: AiAgentDefinition): 'chat' | 'object' {\n return agent.executionMode ?? 'chat'\n}\n\nexport function checkAgentPolicy(input: AgentPolicyCheckInput): AgentPolicyDecision {\n const {\n agentId,\n authContext,\n toolName,\n attachmentMediaTypes,\n requestedExecutionMode,\n mutationPolicyOverride,\n } = input\n\n const agent = getAgent(agentId)\n if (!agent) {\n return {\n ok: false,\n code: 'agent_unknown',\n message: `Agent \"${agentId}\" is not registered.`,\n }\n }\n\n const agentFeatures = agent.requiredFeatures ?? []\n if (\n !hasRequiredFeatures(agentFeatures, authContext.userFeatures, authContext.isSuperAdmin)\n ) {\n return {\n ok: false,\n code: 'agent_features_denied',\n message: `Access to agent \"${agentId}\" requires features: ${agentFeatures.join(', ')}`,\n }\n }\n\n let resolvedTool: AiToolDefinition | undefined\n if (typeof toolName === 'string') {\n if (!agent.allowedTools.includes(toolName)) {\n return {\n ok: false,\n code: 'tool_not_whitelisted',\n message: `Tool \"${toolName}\" is not whitelisted for agent \"${agentId}\".`,\n }\n }\n\n const toolRecord = toolRegistry.getTool(toolName) as AiToolDefinition | undefined\n if (!toolRecord) {\n return {\n ok: false,\n code: 'tool_unknown',\n message: `Tool \"${toolName}\" is not registered in the tool registry.`,\n }\n }\n\n const toolFeatures = toolRecord.requiredFeatures ?? []\n if (\n !hasRequiredFeatures(toolFeatures, authContext.userFeatures, authContext.isSuperAdmin)\n ) {\n return {\n ok: false,\n code: 'tool_features_denied',\n message: `Access to tool \"${toolName}\" requires features: ${toolFeatures.join(', ')}`,\n }\n }\n\n if (toolRecord.isMutation === true) {\n if (isAgentReadOnly(agent)) {\n return {\n ok: false,\n code: 'mutation_blocked_by_readonly',\n message: `Mutation tool \"${toolName}\" cannot be executed by read-only agent \"${agentId}\".`,\n }\n }\n const effectivePolicy = resolvePolicyCheckMutationPolicy(agent, mutationPolicyOverride)\n if (effectivePolicy === 'read-only') {\n return {\n ok: false,\n code: 'mutation_blocked_by_policy',\n message: `Mutation tool \"${toolName}\" is blocked by agent \"${agentId}\" mutationPolicy=read-only.`,\n }\n }\n }\n\n resolvedTool = toolRecord\n }\n\n if (requestedExecutionMode) {\n const declaredMode = agentExecutionMode(agent)\n if (requestedExecutionMode === 'object') {\n if (declaredMode !== 'object' && !hasAgentStructuredOutput(agent)) {\n return {\n ok: false,\n code: 'execution_mode_not_supported',\n message: `Agent \"${agentId}\" does not support execution mode \"object\" (no output schema declared).`,\n }\n }\n } else if (requestedExecutionMode === 'chat') {\n if (declaredMode === 'object' && hasAgentStructuredOutput(agent)) {\n return {\n ok: false,\n code: 'execution_mode_not_supported',\n message: `Agent \"${agentId}\" is declared as object-mode and cannot run via chat transport.`,\n }\n }\n }\n }\n\n if (Array.isArray(attachmentMediaTypes) && attachmentMediaTypes.length > 0) {\n const accepted = agent.acceptedMediaTypes\n if (!accepted || accepted.length === 0) {\n return {\n ok: false,\n code: 'attachment_type_not_accepted',\n message: `Agent \"${agentId}\" does not accept attachments.`,\n }\n }\n const acceptedSet = new Set<AiAgentAcceptedMediaType>(accepted)\n for (const raw of attachmentMediaTypes) {\n const kind = classifyMediaType(raw)\n if (!acceptedSet.has(kind)) {\n return {\n ok: false,\n code: 'attachment_type_not_accepted',\n message: `Agent \"${agentId}\" does not accept media type \"${raw}\" (classified as \"${kind}\").`,\n }\n }\n }\n }\n\n return { ok: true, agent, tool: resolvedTool }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|