@open-mercato/ai-assistant 0.4.2-canary-c02407ff85

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.
Files changed (193) hide show
  1. package/AGENTS.md +1090 -0
  2. package/README.md +607 -0
  3. package/build.mjs +92 -0
  4. package/dist/di.js +8 -0
  5. package/dist/di.js.map +7 -0
  6. package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
  7. package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
  8. package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
  9. package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
  10. package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
  11. package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
  12. package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
  13. package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
  14. package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
  15. package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
  16. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
  17. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
  18. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
  19. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
  20. package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
  21. package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
  22. package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
  23. package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
  24. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
  25. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
  26. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
  27. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
  28. package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
  29. package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
  30. package/dist/frontend/components/CommandPalette/index.js +28 -0
  31. package/dist/frontend/components/CommandPalette/index.js.map +7 -0
  32. package/dist/frontend/constants.js +41 -0
  33. package/dist/frontend/constants.js.map +7 -0
  34. package/dist/frontend/hooks/index.js +13 -0
  35. package/dist/frontend/hooks/index.js.map +7 -0
  36. package/dist/frontend/hooks/useCommandPalette.js +1094 -0
  37. package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
  38. package/dist/frontend/hooks/useMcpTools.js +66 -0
  39. package/dist/frontend/hooks/useMcpTools.js.map +7 -0
  40. package/dist/frontend/hooks/usePageContext.js +48 -0
  41. package/dist/frontend/hooks/usePageContext.js.map +7 -0
  42. package/dist/frontend/hooks/useRecentActions.js +56 -0
  43. package/dist/frontend/hooks/useRecentActions.js.map +7 -0
  44. package/dist/frontend/hooks/useRecentTools.js +55 -0
  45. package/dist/frontend/hooks/useRecentTools.js.map +7 -0
  46. package/dist/frontend/index.js +35 -0
  47. package/dist/frontend/index.js.map +7 -0
  48. package/dist/frontend/types.js +1 -0
  49. package/dist/frontend/types.js.map +7 -0
  50. package/dist/frontend/utils/index.js +7 -0
  51. package/dist/frontend/utils/index.js.map +7 -0
  52. package/dist/frontend/utils/toolMatcher.js +95 -0
  53. package/dist/frontend/utils/toolMatcher.js.map +7 -0
  54. package/dist/index.js +57 -0
  55. package/dist/index.js.map +7 -0
  56. package/dist/modules/ai_assistant/acl.js +14 -0
  57. package/dist/modules/ai_assistant/acl.js.map +7 -0
  58. package/dist/modules/ai_assistant/api/chat/route.js +152 -0
  59. package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
  60. package/dist/modules/ai_assistant/api/health/route.js +27 -0
  61. package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
  62. package/dist/modules/ai_assistant/api/route/route.js +123 -0
  63. package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
  64. package/dist/modules/ai_assistant/api/settings/route.js +60 -0
  65. package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
  66. package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
  67. package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
  68. package/dist/modules/ai_assistant/api/tools/route.js +48 -0
  69. package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
  70. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
  71. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
  72. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
  73. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
  74. package/dist/modules/ai_assistant/cli.js +192 -0
  75. package/dist/modules/ai_assistant/cli.js.map +7 -0
  76. package/dist/modules/ai_assistant/di.js +11 -0
  77. package/dist/modules/ai_assistant/di.js.map +7 -0
  78. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
  79. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
  80. package/dist/modules/ai_assistant/index.js +13 -0
  81. package/dist/modules/ai_assistant/index.js.map +7 -0
  82. package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
  83. package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
  84. package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
  85. package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
  86. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
  87. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
  88. package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
  89. package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
  90. package/dist/modules/ai_assistant/lib/auth.js +87 -0
  91. package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
  92. package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
  93. package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
  94. package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
  95. package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
  96. package/dist/modules/ai_assistant/lib/http-server.js +367 -0
  97. package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
  98. package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
  99. package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
  100. package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
  101. package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
  102. package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
  103. package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
  104. package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
  105. package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
  106. package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
  107. package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
  108. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
  109. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
  110. package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
  111. package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
  112. package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
  113. package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
  114. package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
  115. package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
  116. package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
  117. package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
  118. package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
  119. package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
  120. package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
  121. package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
  122. package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
  123. package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
  124. package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
  125. package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
  126. package/dist/modules/ai_assistant/lib/types.js +1 -0
  127. package/dist/modules/ai_assistant/lib/types.js.map +7 -0
  128. package/package.json +108 -0
  129. package/src/di.ts +11 -0
  130. package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
  131. package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
  132. package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
  133. package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
  134. package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
  135. package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
  136. package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
  137. package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
  138. package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
  139. package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
  140. package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
  141. package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
  142. package/src/frontend/components/CommandPalette/index.ts +14 -0
  143. package/src/frontend/constants.ts +35 -0
  144. package/src/frontend/hooks/index.ts +5 -0
  145. package/src/frontend/hooks/useCommandPalette.ts +1389 -0
  146. package/src/frontend/hooks/useMcpTools.ts +73 -0
  147. package/src/frontend/hooks/usePageContext.ts +61 -0
  148. package/src/frontend/hooks/useRecentActions.ts +64 -0
  149. package/src/frontend/hooks/useRecentTools.ts +69 -0
  150. package/src/frontend/index.ts +39 -0
  151. package/src/frontend/types.ts +260 -0
  152. package/src/frontend/utils/index.ts +1 -0
  153. package/src/frontend/utils/toolMatcher.ts +127 -0
  154. package/src/index.ts +92 -0
  155. package/src/modules/ai_assistant/acl.ts +10 -0
  156. package/src/modules/ai_assistant/api/chat/route.ts +213 -0
  157. package/src/modules/ai_assistant/api/health/route.ts +30 -0
  158. package/src/modules/ai_assistant/api/route/route.ts +149 -0
  159. package/src/modules/ai_assistant/api/settings/route.ts +73 -0
  160. package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
  161. package/src/modules/ai_assistant/api/tools/route.ts +57 -0
  162. package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
  163. package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
  164. package/src/modules/ai_assistant/cli.ts +233 -0
  165. package/src/modules/ai_assistant/di.ts +9 -0
  166. package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
  167. package/src/modules/ai_assistant/index.ts +11 -0
  168. package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
  169. package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
  170. package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
  171. package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
  172. package/src/modules/ai_assistant/lib/auth.ts +185 -0
  173. package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
  174. package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
  175. package/src/modules/ai_assistant/lib/http-server.ts +498 -0
  176. package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
  177. package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
  178. package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
  179. package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
  180. package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
  181. package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
  182. package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
  183. package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
  184. package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
  185. package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
  186. package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
  187. package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
  188. package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
  189. package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
  190. package/src/modules/ai_assistant/lib/types.ts +147 -0
  191. package/test-schema.ts +37 -0
  192. package/tsconfig.json +10 -0
  193. package/watch.mjs +6 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/frontend/utils/toolMatcher.ts"],
4
+ "sourcesContent": ["import type { ToolInfo } from '../types'\n\nfunction normalizeString(str: string): string {\n return str.toLowerCase().replace(/[._-]/g, ' ')\n}\n\nfunction fuzzyMatch(query: string, target: string): boolean {\n const normalizedQuery = normalizeString(query)\n const normalizedTarget = normalizeString(target)\n\n // Check if all query chars appear in order in target\n let queryIndex = 0\n for (let i = 0; i < normalizedTarget.length && queryIndex < normalizedQuery.length; i++) {\n if (normalizedTarget[i] === normalizedQuery[queryIndex]) {\n queryIndex++\n }\n }\n return queryIndex === normalizedQuery.length\n}\n\nfunction scoreMatch(query: string, tool: ToolInfo): number {\n const normalizedQuery = normalizeString(query)\n const normalizedName = normalizeString(tool.name)\n const normalizedDesc = normalizeString(tool.description)\n\n let score = 0\n\n // Exact match in name = highest score\n if (normalizedName.includes(normalizedQuery)) {\n score += 100\n }\n\n // Starts with query\n if (normalizedName.startsWith(normalizedQuery)) {\n score += 50\n }\n\n // Word boundary match\n const nameWords = normalizedName.split(' ')\n if (nameWords.some((word) => word.startsWith(normalizedQuery))) {\n score += 30\n }\n\n // Fuzzy match in name\n if (fuzzyMatch(normalizedQuery, normalizedName)) {\n score += 20\n }\n\n // Match in description\n if (normalizedDesc.includes(normalizedQuery)) {\n score += 10\n }\n\n return score\n}\n\nexport function filterTools(tools: ToolInfo[], query: string): ToolInfo[] {\n if (!query.trim()) {\n return tools\n }\n\n const normalizedQuery = query.trim().toLowerCase()\n\n // Score and filter tools\n const scoredTools = tools\n .map((tool) => ({\n tool,\n score: scoreMatch(normalizedQuery, tool),\n }))\n .filter(({ score }) => score > 0)\n .sort((a, b) => b.score - a.score)\n\n return scoredTools.map(({ tool }) => tool)\n}\n\nexport function groupToolsByModule(tools: ToolInfo[]): Map<string, ToolInfo[]> {\n const grouped = new Map<string, ToolInfo[]>()\n\n for (const tool of tools) {\n const module = tool.module || extractModuleFromName(tool.name)\n const existing = grouped.get(module) || []\n existing.push(tool)\n grouped.set(module, existing)\n }\n\n return grouped\n}\n\nfunction extractModuleFromName(name: string): string {\n const parts = name.split('.')\n return parts[0] || 'other'\n}\n\nexport function humanizeToolName(name: string): string {\n // customers.people.create -> Create Person\n const parts = name.split('.')\n if (parts.length < 2) return name\n\n const action = parts[parts.length - 1]\n const resource = parts[parts.length - 2]\n\n const humanAction = capitalize(action)\n const humanResource = singularize(humanize(resource))\n\n return `${humanAction} ${humanResource}`\n}\n\nfunction capitalize(str: string): string {\n return str.charAt(0).toUpperCase() + str.slice(1)\n}\n\nfunction humanize(str: string): string {\n return str.replace(/[_-]/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2')\n}\n\nfunction singularize(str: string): string {\n if (str.endsWith('ies')) {\n return str.slice(0, -3) + 'y'\n }\n if (str.endsWith('es') && !str.endsWith('ses')) {\n return str.slice(0, -2)\n }\n if (str.endsWith('s') && !str.endsWith('ss')) {\n return str.slice(0, -1)\n }\n return str\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ export * from "./modules/ai_assistant/lib/types.js";
2
+ import {
3
+ registerMcpTool,
4
+ getToolRegistry,
5
+ unregisterMcpTool,
6
+ toolRegistry
7
+ } from "./modules/ai_assistant/lib/tool-registry.js";
8
+ import { executeTool } from "./modules/ai_assistant/lib/tool-executor.js";
9
+ import { createMcpServer, runMcpServer } from "./modules/ai_assistant/lib/mcp-server.js";
10
+ import { runMcpHttpServer } from "./modules/ai_assistant/lib/http-server.js";
11
+ import {
12
+ authenticateMcpRequest,
13
+ extractApiKeyFromHeaders
14
+ } from "./modules/ai_assistant/lib/auth.js";
15
+ import { loadAllModuleTools, indexToolsForSearch } from "./modules/ai_assistant/lib/tool-loader.js";
16
+ import {
17
+ OpenCodeClient,
18
+ createOpenCodeClient
19
+ } from "./modules/ai_assistant/lib/opencode-client.js";
20
+ import {
21
+ handleOpenCodeMessage,
22
+ handleOpenCodeHealth,
23
+ handleOpenCodeMessageStreaming,
24
+ handleOpenCodeAnswer,
25
+ getPendingQuestions,
26
+ extractTextFromResponse,
27
+ extractAllPartsFromResponse,
28
+ extractMetadataFromResponse
29
+ } from "./modules/ai_assistant/lib/opencode-handlers.js";
30
+ import { metadata, features } from "./modules/ai_assistant/index.js";
31
+ export {
32
+ OpenCodeClient,
33
+ authenticateMcpRequest,
34
+ createMcpServer,
35
+ createOpenCodeClient,
36
+ executeTool,
37
+ extractAllPartsFromResponse,
38
+ extractApiKeyFromHeaders,
39
+ extractMetadataFromResponse,
40
+ extractTextFromResponse,
41
+ features,
42
+ getPendingQuestions,
43
+ getToolRegistry,
44
+ handleOpenCodeAnswer,
45
+ handleOpenCodeHealth,
46
+ handleOpenCodeMessage,
47
+ handleOpenCodeMessageStreaming,
48
+ indexToolsForSearch,
49
+ loadAllModuleTools,
50
+ metadata,
51
+ registerMcpTool,
52
+ runMcpHttpServer,
53
+ runMcpServer,
54
+ toolRegistry,
55
+ unregisterMcpTool
56
+ };
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["/**\n * @open-mercato/ai-assistant\n *\n * MCP (Model Context Protocol) server module for AI assistant integration.\n *\n * This module provides:\n * - MCP server with stdio transport for Claude Desktop integration\n * - Tool registry for modules to register AI-callable tools\n * - ACL-based permission filtering for tools\n * - Multi-tenant execution context\n *\n * @example\n * ```typescript\n * import { registerMcpTool } from '@open-mercato/ai-assistant/tools'\n * import { z } from 'zod'\n *\n * registerMcpTool({\n * name: 'customers.search',\n * description: 'Search for customers',\n * inputSchema: z.object({ query: z.string() }),\n * requiredFeatures: ['customers.people.view'],\n * handler: async (input, ctx) => {\n * // Implementation\n * }\n * }, { moduleId: 'customers' })\n * ```\n */\n\n// Re-export types\nexport * from './modules/ai_assistant/lib/types'\n\n// Tool registry\nexport {\n registerMcpTool,\n getToolRegistry,\n unregisterMcpTool,\n toolRegistry,\n} from './modules/ai_assistant/lib/tool-registry'\n\n// Tool executor\nexport { executeTool } from './modules/ai_assistant/lib/tool-executor'\n\n// MCP server (stdio)\nexport { createMcpServer, runMcpServer } from './modules/ai_assistant/lib/mcp-server'\n\n// MCP HTTP server\nexport { runMcpHttpServer, type McpHttpServerOptions } from './modules/ai_assistant/lib/http-server'\n\n// MCP auth\nexport {\n authenticateMcpRequest,\n extractApiKeyFromHeaders,\n type McpAuthResult,\n type McpAuthSuccess,\n type McpAuthFailure,\n} from './modules/ai_assistant/lib/auth'\n\n// Tool loader\nexport { loadAllModuleTools, indexToolsForSearch } from './modules/ai_assistant/lib/tool-loader'\n\n// OpenCode client\nexport {\n OpenCodeClient,\n createOpenCodeClient,\n type OpenCodeClientConfig,\n type OpenCodeSession,\n type OpenCodeMessage,\n type OpenCodeHealth,\n type OpenCodeMcpStatus,\n} from './modules/ai_assistant/lib/opencode-client'\n\n// OpenCode route handlers\nexport {\n handleOpenCodeMessage,\n handleOpenCodeHealth,\n handleOpenCodeMessageStreaming,\n handleOpenCodeAnswer,\n getPendingQuestions,\n extractTextFromResponse,\n extractAllPartsFromResponse,\n extractMetadataFromResponse,\n type OpenCodeTestRequest,\n type OpenCodeTestResponse,\n type OpenCodeHealthResponse,\n type OpenCodeResponsePart,\n type OpenCodeResponseMetadata,\n type OpenCodeStreamEvent,\n type OpenCodeQuestion,\n} from './modules/ai_assistant/lib/opencode-handlers'\n\n// Module metadata\nexport { metadata, features } from './modules/ai_assistant'\n"],
5
+ "mappings": "AA6BA,cAAc;AAGd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,mBAAmB;AAG5B,SAAS,iBAAiB,oBAAoB;AAG9C,SAAS,wBAAmD;AAG5D;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAGP,SAAS,oBAAoB,2BAA2B;AAGxD;AAAA,EACE;AAAA,EACA;AAAA,OAMK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AAGP,SAAS,UAAU,gBAAgB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,14 @@
1
+ const features = [
2
+ { id: "ai_assistant.view", title: "View AI Assistant Settings", module: "ai_assistant" },
3
+ { id: "ai_assistant.settings.manage", title: "Manage AI Assistant Settings", module: "ai_assistant" },
4
+ { id: "ai_assistant.mcp.serve", title: "Start MCP Server", module: "ai_assistant" },
5
+ { id: "ai_assistant.tools.list", title: "List MCP Tools", module: "ai_assistant" },
6
+ { id: "ai_assistant.mcp_servers.view", title: "View MCP Server Configurations", module: "ai_assistant" },
7
+ { id: "ai_assistant.mcp_servers.manage", title: "Manage MCP Server Configurations", module: "ai_assistant" }
8
+ ];
9
+ var acl_default = features;
10
+ export {
11
+ acl_default as default,
12
+ features
13
+ };
14
+ //# sourceMappingURL=acl.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/ai_assistant/acl.ts"],
4
+ "sourcesContent": ["export const features = [\n { id: 'ai_assistant.view', title: 'View AI Assistant Settings', module: 'ai_assistant' },\n { id: 'ai_assistant.settings.manage', title: 'Manage AI Assistant Settings', module: 'ai_assistant' },\n { id: 'ai_assistant.mcp.serve', title: 'Start MCP Server', module: 'ai_assistant' },\n { id: 'ai_assistant.tools.list', title: 'List MCP Tools', module: 'ai_assistant' },\n { id: 'ai_assistant.mcp_servers.view', title: 'View MCP Server Configurations', module: 'ai_assistant' },\n { id: 'ai_assistant.mcp_servers.manage', title: 'Manage MCP Server Configurations', module: 'ai_assistant' },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,qBAAqB,OAAO,8BAA8B,QAAQ,eAAe;AAAA,EACvF,EAAE,IAAI,gCAAgC,OAAO,gCAAgC,QAAQ,eAAe;AAAA,EACpG,EAAE,IAAI,0BAA0B,OAAO,oBAAoB,QAAQ,eAAe;AAAA,EAClF,EAAE,IAAI,2BAA2B,OAAO,kBAAkB,QAAQ,eAAe;AAAA,EACjF,EAAE,IAAI,iCAAiC,OAAO,kCAAkC,QAAQ,eAAe;AAAA,EACvG,EAAE,IAAI,mCAAmC,OAAO,oCAAoC,QAAQ,eAAe;AAC7G;AAEA,IAAO,cAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,152 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
+ import {
4
+ handleOpenCodeMessageStreaming
5
+ } from "../../lib/opencode-handlers.js";
6
+ import { createOpenCodeClient } from "../../lib/opencode-client.js";
7
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
8
+ import {
9
+ generateSessionToken,
10
+ createSessionApiKey
11
+ } from "@open-mercato/core/modules/api_keys/services/apiKeyService";
12
+ import { UserRole } from "@open-mercato/core/modules/auth/data/entities";
13
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
14
+ const metadata = {
15
+ POST: { requireAuth: true, requireFeatures: ["ai_assistant.view"] }
16
+ };
17
+ async function getUserRoleIds(em, userId, tenantId) {
18
+ if (!tenantId) return [];
19
+ const links = await findWithDecryption(
20
+ em,
21
+ UserRole,
22
+ { user: userId, role: { tenantId } },
23
+ { populate: ["role"] },
24
+ { tenantId, organizationId: null }
25
+ );
26
+ const linkList = Array.isArray(links) ? links : [];
27
+ return linkList.map((l) => l.role?.id).filter((id) => typeof id === "string" && id.length > 0);
28
+ }
29
+ async function POST(req) {
30
+ const auth = await getAuthFromRequest(req);
31
+ if (!auth) {
32
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
33
+ }
34
+ try {
35
+ const body = await req.json();
36
+ const { messages, sessionId, answerQuestion } = body;
37
+ const encoder = new TextEncoder();
38
+ const stream = new TransformStream();
39
+ const writer = stream.writable.getWriter();
40
+ let writerClosed = false;
41
+ const writeSSE = async (event) => {
42
+ if (writerClosed) return;
43
+ try {
44
+ const jsonStr = JSON.stringify(event);
45
+ await writer.write(encoder.encode(`data: ${jsonStr}
46
+
47
+ `));
48
+ } catch (err) {
49
+ console.warn("[AI Chat] Failed to write SSE event:", event.type);
50
+ }
51
+ };
52
+ const closeWriter = async () => {
53
+ if (writerClosed) return;
54
+ writerClosed = true;
55
+ try {
56
+ await writer.close();
57
+ } catch {
58
+ }
59
+ };
60
+ if (answerQuestion) {
61
+ try {
62
+ const client = createOpenCodeClient();
63
+ await client.answerQuestion(answerQuestion.questionId, answerQuestion.answer);
64
+ return NextResponse.json({ success: true });
65
+ } catch (error) {
66
+ console.error("[AI Chat] Answer error:", error);
67
+ return NextResponse.json(
68
+ { error: error instanceof Error ? error.message : "Failed to answer question" },
69
+ { status: 500 }
70
+ );
71
+ }
72
+ }
73
+ if (!messages || !Array.isArray(messages)) {
74
+ return NextResponse.json({ error: "messages array is required" }, { status: 400 });
75
+ }
76
+ const lastUserMessage = messages.filter((m) => m.role === "user").pop()?.content;
77
+ if (!lastUserMessage) {
78
+ return NextResponse.json({ error: "No user message found" }, { status: 400 });
79
+ }
80
+ let sessionToken = null;
81
+ if (!sessionId) {
82
+ try {
83
+ const container = await createRequestContainer();
84
+ const em = container.resolve("em");
85
+ const userRoleIds = await getUserRoleIds(em, auth.sub, auth.tenantId);
86
+ sessionToken = generateSessionToken();
87
+ await createSessionApiKey(em, {
88
+ sessionToken,
89
+ userId: auth.sub,
90
+ userRoles: userRoleIds,
91
+ tenantId: auth.tenantId,
92
+ organizationId: auth.orgId,
93
+ ttlMinutes: 120
94
+ });
95
+ console.log("[AI Chat] Created session token:", sessionToken.slice(0, 12) + "...");
96
+ } catch (error) {
97
+ console.error("[AI Chat] Failed to create session key:", error);
98
+ }
99
+ }
100
+ let messageToSend = lastUserMessage;
101
+ if (sessionToken) {
102
+ messageToSend = `[SYSTEM: Your session token is "${sessionToken}". You MUST include "_sessionToken": "${sessionToken}" in EVERY tool call argument object. Without this, tools will fail with authorization errors.]
103
+
104
+ ${lastUserMessage}`;
105
+ }
106
+ ;
107
+ (async () => {
108
+ try {
109
+ if (sessionToken) {
110
+ console.log("[AI Chat] Emitting session-authorized event");
111
+ await writeSSE({
112
+ type: "session-authorized",
113
+ sessionToken: sessionToken.slice(0, 12) + "..."
114
+ });
115
+ }
116
+ await writeSSE({ type: "thinking" });
117
+ await handleOpenCodeMessageStreaming(
118
+ {
119
+ message: messageToSend,
120
+ sessionId
121
+ },
122
+ async (event) => {
123
+ await writeSSE(event);
124
+ }
125
+ );
126
+ } catch (error) {
127
+ console.error("[AI Chat] OpenCode error:", error);
128
+ await writeSSE({
129
+ type: "error",
130
+ error: error instanceof Error ? error.message : "OpenCode request failed"
131
+ });
132
+ } finally {
133
+ await closeWriter();
134
+ }
135
+ })();
136
+ return new Response(stream.readable, {
137
+ headers: {
138
+ "Content-Type": "text/event-stream",
139
+ "Cache-Control": "no-cache",
140
+ Connection: "keep-alive"
141
+ }
142
+ });
143
+ } catch (error) {
144
+ console.error("[AI Chat] Error:", error);
145
+ return NextResponse.json({ error: "Chat request failed" }, { status: 500 });
146
+ }
147
+ }
148
+ export {
149
+ POST,
150
+ metadata
151
+ };
152
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/ai_assistant/api/chat/route.ts"],
4
+ "sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport {\n handleOpenCodeMessageStreaming,\n type OpenCodeStreamEvent,\n} from '../../lib/opencode-handlers'\nimport { createOpenCodeClient } from '../../lib/opencode-client'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n generateSessionToken,\n createSessionApiKey,\n} from '@open-mercato/core/modules/api_keys/services/apiKeyService'\nimport { UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\n/**\n * Get user's role IDs from the database.\n */\nasync function getUserRoleIds(\n em: EntityManager,\n userId: string,\n tenantId: string | null\n): Promise<string[]> {\n if (!tenantId) return []\n\n const links = await findWithDecryption(\n em,\n UserRole,\n { user: userId as any, role: { tenantId } } as any,\n { populate: ['role'] },\n { tenantId, organizationId: null },\n )\n const linkList = Array.isArray(links) ? links : []\n return linkList\n .map((l) => (l.role as any)?.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n}\n\n/**\n * Chat endpoint that routes messages to OpenCode agent.\n * OpenCode connects to MCP server for tool access (api_discover, api_execute, api_schema).\n *\n * Emits verbose SSE events for debugging:\n * - thinking: Agent started processing\n * - metadata: Model, tokens, timing info\n * - tool-call: Tool invocation with args\n * - tool-result: Tool response\n * - text: Response text\n * - question: Confirmation question from agent\n * - done: Complete with session ID\n * - error: Error occurred\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 { messages, sessionId, answerQuestion } = body as {\n messages?: Array<{ role: string; content: string }>\n sessionId?: string\n // For answering a question\n answerQuestion?: {\n questionId: string\n answer: number\n sessionId: string\n }\n }\n\n // Create SSE stream for frontend compatibility\n const encoder = new TextEncoder()\n const stream = new TransformStream()\n const writer = stream.writable.getWriter()\n let writerClosed = false\n\n const writeSSE = async (event: OpenCodeStreamEvent | { type: string; [key: string]: unknown }) => {\n if (writerClosed) return // Guard against writes after close\n try {\n const jsonStr = JSON.stringify(event)\n await writer.write(encoder.encode(`data: ${jsonStr}\\n\\n`))\n } catch (err) {\n // Writer may have been closed by client disconnect\n console.warn('[AI Chat] Failed to write SSE event:', event.type)\n }\n }\n\n const closeWriter = async () => {\n if (writerClosed) return\n writerClosed = true\n try {\n await writer.close()\n } catch {\n // Already closed\n }\n }\n\n // Handle question answer - simple JSON response, not SSE\n // The original SSE stream continues and will receive the follow-up response\n if (answerQuestion) {\n try {\n const client = createOpenCodeClient()\n await client.answerQuestion(answerQuestion.questionId, answerQuestion.answer)\n return NextResponse.json({ success: true })\n } catch (error) {\n console.error('[AI Chat] Answer error:', error)\n return NextResponse.json(\n { error: error instanceof Error ? error.message : 'Failed to answer question' },\n { status: 500 }\n )\n }\n }\n\n // Handle regular message\n if (!messages || !Array.isArray(messages)) {\n return NextResponse.json({ error: 'messages array is required' }, { status: 400 })\n }\n\n // Get the latest user message\n const lastUserMessage = messages.filter((m) => m.role === 'user').pop()?.content\n if (!lastUserMessage) {\n return NextResponse.json({ error: 'No user message found' }, { status: 400 })\n }\n\n // For new sessions, create an ephemeral API key that inherits user permissions\n let sessionToken: string | null = null\n if (!sessionId) {\n try {\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n // Get user's role IDs from database\n const userRoleIds = await getUserRoleIds(em, auth.sub, auth.tenantId)\n\n // Generate session token and create ephemeral key\n sessionToken = generateSessionToken()\n await createSessionApiKey(em, {\n sessionToken,\n userId: auth.sub,\n userRoles: userRoleIds,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n ttlMinutes: 120,\n })\n console.log('[AI Chat] Created session token:', sessionToken.slice(0, 12) + '...')\n } catch (error) {\n console.error('[AI Chat] Failed to create session key:', error)\n // Continue without session key - tools will use static API key auth\n }\n }\n\n // Build the message to send to OpenCode\n // If we have a session token, prepend explicit instructions for the AI to include it in tool calls\n let messageToSend = lastUserMessage\n if (sessionToken) {\n messageToSend = `[SYSTEM: Your session token is \"${sessionToken}\". You MUST include \"_sessionToken\": \"${sessionToken}\" in EVERY tool call argument object. Without this, tools will fail with authorization errors.]\\n\\n${lastUserMessage}`\n }\n\n // Process in background - starts AFTER Response is returned so there's a reader for the stream\n ;(async () => {\n try {\n // Emit session-authorized event first (if we have a token)\n if (sessionToken) {\n console.log('[AI Chat] Emitting session-authorized event')\n await writeSSE({\n type: 'session-authorized',\n sessionToken: sessionToken.slice(0, 12) + '...',\n })\n }\n\n // Emit thinking event for UX feedback\n await writeSSE({ type: 'thinking' })\n\n // Use streaming handler that supports questions\n await handleOpenCodeMessageStreaming(\n {\n message: messageToSend,\n sessionId,\n },\n async (event) => {\n await writeSSE(event)\n }\n )\n } catch (error) {\n console.error('[AI Chat] OpenCode error:', error)\n await writeSSE({\n type: 'error',\n error: error instanceof Error ? error.message : 'OpenCode request failed',\n })\n } finally {\n await closeWriter()\n }\n })()\n\n return new Response(stream.readable, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n })\n } catch (error) {\n console.error('[AI Chat] Error:', error)\n return NextResponse.json({ error: 'Chat request failed' }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,27 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
+ import { handleOpenCodeHealth } from "../../lib/opencode-handlers.js";
4
+ const metadata = {
5
+ GET: { requireAuth: true, requireFeatures: ["ai_assistant.view"] }
6
+ };
7
+ async function GET(req) {
8
+ const auth = await getAuthFromRequest(req);
9
+ if (!auth?.sub) {
10
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
11
+ }
12
+ try {
13
+ const health = await handleOpenCodeHealth();
14
+ return NextResponse.json(health);
15
+ } catch (error) {
16
+ console.error("[AI Health] Error:", error);
17
+ return NextResponse.json(
18
+ { error: "Failed to check health", message: error instanceof Error ? error.message : "Unknown error" },
19
+ { status: 500 }
20
+ );
21
+ }
22
+ }
23
+ export {
24
+ GET,
25
+ metadata
26
+ };
27
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/ai_assistant/api/health/route.ts"],
4
+ "sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { handleOpenCodeHealth } from '../../lib/opencode-handlers'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\n/**\n * GET /api/ai_assistant/health\n *\n * Returns OpenCode and MCP connection status.\n */\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const health = await handleOpenCodeHealth()\n return NextResponse.json(health)\n } catch (error) {\n console.error('[AI Health] Error:', error)\n return NextResponse.json(\n { error: 'Failed to check health', message: error instanceof Error ? error.message : 'Unknown error' },\n { status: 500 }\n )\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,0BAA0B;AACnC,SAAS,4BAA4B;AAE9B,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAOA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,KAAK;AACd,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,MAAI;AACF,UAAM,SAAS,MAAM,qBAAqB;AAC1C,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,OAAO;AACd,YAAQ,MAAM,sBAAsB,KAAK;AACzC,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,0BAA0B,SAAS,iBAAiB,QAAQ,MAAM,UAAU,gBAAgB;AAAA,MACrG,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,123 @@
1
+ import { NextResponse } from "next/server";
2
+ import { generateObject } from "../../lib/ai-sdk.js";
3
+ import {
4
+ createOpenAI,
5
+ createAnthropic,
6
+ createGoogleGenerativeAI
7
+ } from "../../lib/ai-sdk.js";
8
+ import { z } from "zod";
9
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
10
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
11
+ import {
12
+ resolveChatConfig,
13
+ isProviderConfigured
14
+ } from "../../lib/chat-config.js";
15
+ const metadata = {
16
+ POST: { requireAuth: true, requireFeatures: ["ai_assistant.view"] }
17
+ };
18
+ const RouteResultSchema = z.object({
19
+ intent: z.enum(["tool", "general_chat"]),
20
+ toolName: z.string().optional(),
21
+ confidence: z.number().min(0).max(1),
22
+ reasoning: z.string()
23
+ });
24
+ const ROUTING_MODELS = {
25
+ anthropic: "claude-3-5-haiku-20241022",
26
+ openai: "gpt-4o-mini",
27
+ google: "gemini-1.5-flash"
28
+ };
29
+ function createRoutingModel(providerId) {
30
+ const modelId = ROUTING_MODELS[providerId];
31
+ switch (providerId) {
32
+ case "openai": {
33
+ const apiKey = process.env.OPENAI_API_KEY;
34
+ if (!apiKey) throw new Error("OPENAI_API_KEY not configured");
35
+ const openai = createOpenAI({ apiKey });
36
+ return openai(modelId);
37
+ }
38
+ case "anthropic": {
39
+ const apiKey = process.env.ANTHROPIC_API_KEY;
40
+ if (!apiKey) throw new Error("ANTHROPIC_API_KEY not configured");
41
+ const anthropic = createAnthropic({ apiKey });
42
+ return anthropic(modelId);
43
+ }
44
+ case "google": {
45
+ const apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY;
46
+ if (!apiKey) throw new Error("GOOGLE_GENERATIVE_AI_API_KEY not configured");
47
+ const google = createGoogleGenerativeAI({ apiKey });
48
+ return google(modelId);
49
+ }
50
+ default:
51
+ throw new Error(`Unknown provider: ${providerId}`);
52
+ }
53
+ }
54
+ async function POST(req) {
55
+ const auth = await getAuthFromRequest(req);
56
+ if (!auth) {
57
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
58
+ }
59
+ try {
60
+ const body = await req.json();
61
+ const { query, availableTools } = body;
62
+ console.log("[AI Route] Routing query:", query);
63
+ console.log("[AI Route] Available tools count:", availableTools?.length);
64
+ if (!query || typeof query !== "string") {
65
+ return NextResponse.json({ error: "query is required" }, { status: 400 });
66
+ }
67
+ if (!availableTools || !Array.isArray(availableTools)) {
68
+ return NextResponse.json({ error: "availableTools array is required" }, { status: 400 });
69
+ }
70
+ const container = await createRequestContainer();
71
+ let config = await resolveChatConfig(container);
72
+ if (!config) {
73
+ const providers = ["openai", "anthropic", "google"];
74
+ const configuredProvider = providers.find((p) => isProviderConfigured(p));
75
+ if (!configuredProvider) {
76
+ return NextResponse.json(
77
+ { error: "No AI provider configured. Please set an API key for OpenAI, Anthropic, or Google." },
78
+ { status: 503 }
79
+ );
80
+ }
81
+ config = { providerId: configuredProvider, model: "", updatedAt: "" };
82
+ }
83
+ console.log("[AI Route] Using provider:", config.providerId);
84
+ if (!isProviderConfigured(config.providerId)) {
85
+ return NextResponse.json(
86
+ { error: `Configured provider ${config.providerId} is no longer available. Please update settings.` },
87
+ { status: 503 }
88
+ );
89
+ }
90
+ const model = createRoutingModel(config.providerId);
91
+ const toolList = availableTools.map((t) => `- ${t.name}: ${t.description}`).join("\n");
92
+ console.log("[AI Route] Calling generateObject with", ROUTING_MODELS[config.providerId]);
93
+ const result = await generateObject({
94
+ model,
95
+ schema: RouteResultSchema,
96
+ prompt: `You are a routing assistant. Given a user query, determine if they want to use a specific tool or have a general conversation.
97
+
98
+ Available tools:
99
+ ${toolList}
100
+
101
+ User query: "${query}"
102
+
103
+ Respond with:
104
+ - intent: "tool" if user wants to perform an action with a specific tool, "general_chat" otherwise
105
+ - toolName: the exact tool name if intent is "tool"
106
+ - confidence: 0-1 how confident you are
107
+ - reasoning: brief explanation`
108
+ });
109
+ console.log("[AI Route] Result:", result.object);
110
+ return NextResponse.json(result.object);
111
+ } catch (error) {
112
+ console.error("[AI Route] Error routing query:", error);
113
+ return NextResponse.json(
114
+ { error: "Routing request failed" },
115
+ { status: 500 }
116
+ );
117
+ }
118
+ }
119
+ export {
120
+ POST,
121
+ metadata
122
+ };
123
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/ai_assistant/api/route/route.ts"],
4
+ "sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { generateObject } from '../../lib/ai-sdk'\nimport {\n createOpenAI,\n createAnthropic,\n createGoogleGenerativeAI,\n} from '../../lib/ai-sdk'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport {\n resolveChatConfig,\n isProviderConfigured,\n type ChatProviderId,\n} from '../../lib/chat-config'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\nconst RouteResultSchema = z.object({\n intent: z.enum(['tool', 'general_chat']),\n toolName: z.string().optional(),\n confidence: z.number().min(0).max(1),\n reasoning: z.string(),\n})\n\n// Fast/cheap models for each provider\nconst ROUTING_MODELS: Record<ChatProviderId, string> = {\n anthropic: 'claude-3-5-haiku-20241022',\n openai: 'gpt-4o-mini',\n google: 'gemini-1.5-flash',\n}\n\nfunction createRoutingModel(providerId: ChatProviderId) {\n const modelId = ROUTING_MODELS[providerId]\n\n switch (providerId) {\n case 'openai': {\n const apiKey = process.env.OPENAI_API_KEY\n if (!apiKey) throw new Error('OPENAI_API_KEY not configured')\n const openai = createOpenAI({ apiKey })\n return openai(modelId) as unknown as Parameters<typeof generateObject>[0]['model']\n }\n case 'anthropic': {\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (!apiKey) throw new Error('ANTHROPIC_API_KEY not configured')\n const anthropic = createAnthropic({ apiKey })\n return anthropic(modelId) as unknown as Parameters<typeof generateObject>[0]['model']\n }\n case 'google': {\n const apiKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY\n if (!apiKey) throw new Error('GOOGLE_GENERATIVE_AI_API_KEY not configured')\n const google = createGoogleGenerativeAI({ apiKey })\n return google(modelId) as unknown as Parameters<typeof generateObject>[0]['model']\n }\n default:\n throw new Error(`Unknown provider: ${providerId}`)\n }\n}\n\nexport async function POST(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const body = await req.json()\n const { query, availableTools } = body as {\n query: string\n availableTools: Array<{ name: string; description: string }>\n }\n\n console.log('[AI Route] Routing query:', query)\n console.log('[AI Route] Available tools count:', availableTools?.length)\n\n if (!query || typeof query !== 'string') {\n return NextResponse.json({ error: 'query is required' }, { status: 400 })\n }\n\n if (!availableTools || !Array.isArray(availableTools)) {\n return NextResponse.json({ error: 'availableTools array is required' }, { status: 400 })\n }\n\n // Get user's configured provider\n const container = await createRequestContainer()\n let config = await resolveChatConfig(container)\n\n // Fallback to first configured provider\n if (!config) {\n const providers: ChatProviderId[] = ['openai', 'anthropic', 'google']\n const configuredProvider = providers.find((p) => isProviderConfigured(p))\n if (!configuredProvider) {\n return NextResponse.json(\n { error: 'No AI provider configured. Please set an API key for OpenAI, Anthropic, or Google.' },\n { status: 503 }\n )\n }\n config = { providerId: configuredProvider, model: '', updatedAt: '' }\n }\n\n console.log('[AI Route] Using provider:', config.providerId)\n\n // Verify the configured provider is still available\n if (!isProviderConfigured(config.providerId)) {\n return NextResponse.json(\n { error: `Configured provider ${config.providerId} is no longer available. Please update settings.` },\n { status: 503 }\n )\n }\n\n // Use fast model for the configured provider\n const model = createRoutingModel(config.providerId)\n\n const toolList = availableTools\n .map((t) => `- ${t.name}: ${t.description}`)\n .join('\\n')\n\n console.log('[AI Route] Calling generateObject with', ROUTING_MODELS[config.providerId])\n\n const result = await generateObject({\n model,\n schema: RouteResultSchema,\n prompt: `You are a routing assistant. Given a user query, determine if they want to use a specific tool or have a general conversation.\n\nAvailable tools:\n${toolList}\n\nUser query: \"${query}\"\n\nRespond with:\n- intent: \"tool\" if user wants to perform an action with a specific tool, \"general_chat\" otherwise\n- toolName: the exact tool name if intent is \"tool\"\n- confidence: 0-1 how confident you are\n- reasoning: brief explanation`,\n })\n\n console.log('[AI Route] Result:', result.object)\n return NextResponse.json(result.object)\n } catch (error) {\n console.error('[AI Route] Error routing query:', error)\n return NextResponse.json(\n { error: 'Routing request failed' },\n { status: 500 }\n )\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,60 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
+ const metadata = {
4
+ GET: { requireAuth: true, requireFeatures: ["ai_assistant.view"] }
5
+ };
6
+ const PROVIDERS = {
7
+ anthropic: {
8
+ name: "Anthropic",
9
+ defaultModel: "claude-haiku-4-5-20251001",
10
+ envKey: "OPENCODE_ANTHROPIC_API_KEY"
11
+ },
12
+ openai: {
13
+ name: "OpenAI",
14
+ defaultModel: "gpt-4o-mini",
15
+ envKey: "OPENCODE_OPENAI_API_KEY"
16
+ },
17
+ google: {
18
+ name: "Google",
19
+ defaultModel: "gemini-2.0-flash",
20
+ envKey: "OPENCODE_GOOGLE_API_KEY"
21
+ }
22
+ };
23
+ async function GET(req) {
24
+ const auth = await getAuthFromRequest(req);
25
+ if (!auth?.sub) {
26
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
27
+ }
28
+ try {
29
+ const providerId = process.env.OPENCODE_PROVIDER || "anthropic";
30
+ const providerInfo = PROVIDERS[providerId] || PROVIDERS.anthropic;
31
+ const apiKeyConfigured = !!process.env[providerInfo.envKey];
32
+ const customModel = process.env.OPENCODE_MODEL;
33
+ const model = customModel || `${providerId}/${providerInfo.defaultModel}`;
34
+ return NextResponse.json({
35
+ provider: {
36
+ id: providerId,
37
+ name: providerInfo.name,
38
+ model,
39
+ defaultModel: providerInfo.defaultModel,
40
+ envKey: providerInfo.envKey,
41
+ configured: apiKeyConfigured
42
+ },
43
+ availableProviders: Object.entries(PROVIDERS).map(([id, info]) => ({
44
+ id,
45
+ name: info.name,
46
+ defaultModel: info.defaultModel,
47
+ envKey: info.envKey,
48
+ configured: !!process.env[info.envKey]
49
+ }))
50
+ });
51
+ } catch (error) {
52
+ console.error("[AI Settings] GET error:", error);
53
+ return NextResponse.json({ error: "Failed to fetch settings" }, { status: 500 });
54
+ }
55
+ }
56
+ export {
57
+ GET,
58
+ metadata
59
+ };
60
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/ai_assistant/api/settings/route.ts"],
4
+ "sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },\n}\n\n// Provider information\nconst PROVIDERS = {\n anthropic: {\n name: 'Anthropic',\n defaultModel: 'claude-haiku-4-5-20251001',\n envKey: 'OPENCODE_ANTHROPIC_API_KEY',\n },\n openai: {\n name: 'OpenAI',\n defaultModel: 'gpt-4o-mini',\n envKey: 'OPENCODE_OPENAI_API_KEY',\n },\n google: {\n name: 'Google',\n defaultModel: 'gemini-2.0-flash',\n envKey: 'OPENCODE_GOOGLE_API_KEY',\n },\n} as const\n\ntype ProviderId = keyof typeof PROVIDERS\n\n/**\n * GET /api/ai_assistant/settings\n *\n * Returns the current OpenCode provider configuration from environment variables.\n */\nexport async function GET(req: NextRequest) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n // Read provider config from environment\n const providerId = (process.env.OPENCODE_PROVIDER || 'anthropic') as ProviderId\n const providerInfo = PROVIDERS[providerId] || PROVIDERS.anthropic\n\n // Check if the provider's API key is configured\n const apiKeyConfigured = !!process.env[providerInfo.envKey]\n\n // Get model (custom or default)\n const customModel = process.env.OPENCODE_MODEL\n const model = customModel || `${providerId}/${providerInfo.defaultModel}`\n\n return NextResponse.json({\n provider: {\n id: providerId,\n name: providerInfo.name,\n model,\n defaultModel: providerInfo.defaultModel,\n envKey: providerInfo.envKey,\n configured: apiKeyConfigured,\n },\n availableProviders: Object.entries(PROVIDERS).map(([id, info]) => ({\n id,\n name: info.name,\n defaultModel: info.defaultModel,\n envKey: info.envKey,\n configured: !!process.env[info.envKey],\n })),\n })\n } catch (error) {\n console.error('[AI Settings] GET error:', error)\n return NextResponse.json({ error: 'Failed to fetch settings' }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,0BAA0B;AAE5B,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACnE;AAGA,MAAM,YAAY;AAAA,EAChB,WAAW;AAAA,IACT,MAAM;AAAA,IACN,cAAc;AAAA,IACd,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,cAAc;AAAA,IACd,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,cAAc;AAAA,IACd,QAAQ;AAAA,EACV;AACF;AASA,eAAsB,IAAI,KAAkB;AAC1C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,KAAK;AACd,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,MAAI;AAEF,UAAM,aAAc,QAAQ,IAAI,qBAAqB;AACrD,UAAM,eAAe,UAAU,UAAU,KAAK,UAAU;AAGxD,UAAM,mBAAmB,CAAC,CAAC,QAAQ,IAAI,aAAa,MAAM;AAG1D,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,QAAQ,eAAe,GAAG,UAAU,IAAI,aAAa,YAAY;AAEvE,WAAO,aAAa,KAAK;AAAA,MACvB,UAAU;AAAA,QACR,IAAI;AAAA,QACJ,MAAM,aAAa;AAAA,QACnB;AAAA,QACA,cAAc,aAAa;AAAA,QAC3B,QAAQ,aAAa;AAAA,QACrB,YAAY;AAAA,MACd;AAAA,MACA,oBAAoB,OAAO,QAAQ,SAAS,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO;AAAA,QACjE;AAAA,QACA,MAAM,KAAK;AAAA,QACX,cAAc,KAAK;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb,YAAY,CAAC,CAAC,QAAQ,IAAI,KAAK,MAAM;AAAA,MACvC,EAAE;AAAA,IACJ,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,4BAA4B,KAAK;AAC/C,WAAO,aAAa,KAAK,EAAE,OAAO,2BAA2B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,58 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { executeTool } from "../../../lib/tool-executor.js";
5
+ import { loadAllModuleTools } from "../../../lib/tool-loader.js";
6
+ const metadata = {
7
+ POST: { requireAuth: true, requireFeatures: ["ai_assistant.view"] }
8
+ };
9
+ async function POST(req) {
10
+ const auth = await getAuthFromRequest(req);
11
+ if (!auth) {
12
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
13
+ }
14
+ try {
15
+ const body = await req.json();
16
+ const { toolName, args = {} } = body;
17
+ if (!toolName || typeof toolName !== "string") {
18
+ return NextResponse.json({ error: "toolName is required" }, { status: 400 });
19
+ }
20
+ const container = await createRequestContainer();
21
+ const rbacService = container.resolve("rbacService");
22
+ const acl = await rbacService.loadAcl(auth.sub, {
23
+ tenantId: auth.tenantId,
24
+ organizationId: auth.orgId
25
+ });
26
+ await loadAllModuleTools();
27
+ const toolContext = {
28
+ tenantId: auth.tenantId,
29
+ organizationId: auth.orgId,
30
+ userId: auth.sub,
31
+ container,
32
+ userFeatures: acl.features,
33
+ isSuperAdmin: acl.isSuperAdmin
34
+ };
35
+ const result = await executeTool(toolName, args, toolContext);
36
+ if (!result.success) {
37
+ return NextResponse.json(
38
+ { success: false, error: result.error },
39
+ { status: result.errorCode === "UNAUTHORIZED" ? 403 : 400 }
40
+ );
41
+ }
42
+ return NextResponse.json({
43
+ success: true,
44
+ result: result.result
45
+ });
46
+ } catch (error) {
47
+ console.error("[AI Tools] Error executing tool:", error);
48
+ return NextResponse.json(
49
+ { success: false, error: "Tool execution failed" },
50
+ { status: 500 }
51
+ );
52
+ }
53
+ }
54
+ export {
55
+ POST,
56
+ metadata
57
+ };
58
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/ai_assistant/api/tools/execute/route.ts"],
4
+ "sourcesContent": ["import { NextResponse, type NextRequest } from 'next/server'\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 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 console.error('[AI Tools] Error executing tool:', error)\n return NextResponse.json(\n { success: false, error: 'Tool execution failed' },\n { status: 500 }\n )\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAsC;AAC/C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AAI5B,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,YAAQ,MAAM,oCAAoC,KAAK;AACvD,WAAO,aAAa;AAAA,MAClB,EAAE,SAAS,OAAO,OAAO,wBAAwB;AAAA,MACjD,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }