@open-mercato/ai-assistant 0.4.9-develop-8c36c096d5 → 0.4.9-develop-be6e1cf49c

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 (41) hide show
  1. package/AGENTS.md +78 -37
  2. package/dist/modules/ai_assistant/api/chat/route.js +104 -13
  3. package/dist/modules/ai_assistant/api/chat/route.js.map +2 -2
  4. package/dist/modules/ai_assistant/lib/api-endpoint-index.js +97 -0
  5. package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +2 -2
  6. package/dist/modules/ai_assistant/lib/codemode-tools.js +610 -0
  7. package/dist/modules/ai_assistant/lib/codemode-tools.js.map +7 -0
  8. package/dist/modules/ai_assistant/lib/http-server.js +65 -7
  9. package/dist/modules/ai_assistant/lib/http-server.js.map +2 -2
  10. package/dist/modules/ai_assistant/lib/mcp-dev-server.js +16 -1
  11. package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
  12. package/dist/modules/ai_assistant/lib/mcp-server.js +17 -0
  13. package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
  14. package/dist/modules/ai_assistant/lib/opencode-handlers.js +32 -0
  15. package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +2 -2
  16. package/dist/modules/ai_assistant/lib/sandbox.js +124 -0
  17. package/dist/modules/ai_assistant/lib/sandbox.js.map +7 -0
  18. package/dist/modules/ai_assistant/lib/session-memory.js +103 -0
  19. package/dist/modules/ai_assistant/lib/session-memory.js.map +7 -0
  20. package/dist/modules/ai_assistant/lib/tool-loader.js +4 -114
  21. package/dist/modules/ai_assistant/lib/tool-loader.js.map +3 -3
  22. package/dist/modules/ai_assistant/lib/truncate.js +26 -0
  23. package/dist/modules/ai_assistant/lib/truncate.js.map +7 -0
  24. package/jest.config.cjs +23 -0
  25. package/package.json +6 -5
  26. package/src/modules/ai_assistant/api/chat/route.ts +110 -14
  27. package/src/modules/ai_assistant/lib/__tests__/auth.test.ts +129 -0
  28. package/src/modules/ai_assistant/lib/__tests__/sandbox.test.ts +642 -0
  29. package/src/modules/ai_assistant/lib/__tests__/session-memory.test.ts +82 -0
  30. package/src/modules/ai_assistant/lib/__tests__/truncate.test.ts +76 -0
  31. package/src/modules/ai_assistant/lib/api-endpoint-index.ts +143 -0
  32. package/src/modules/ai_assistant/lib/codemode-tools.ts +864 -0
  33. package/src/modules/ai_assistant/lib/http-server.ts +86 -9
  34. package/src/modules/ai_assistant/lib/mcp-dev-server.ts +19 -0
  35. package/src/modules/ai_assistant/lib/mcp-server.ts +21 -0
  36. package/src/modules/ai_assistant/lib/opencode-handlers.ts +40 -0
  37. package/src/modules/ai_assistant/lib/sandbox.ts +192 -0
  38. package/src/modules/ai_assistant/lib/session-memory.ts +174 -0
  39. package/src/modules/ai_assistant/lib/tool-loader.ts +11 -145
  40. package/src/modules/ai_assistant/lib/truncate.ts +45 -0
  41. package/src/modules/ai_assistant/lib/types.ts +2 -0
@@ -3,7 +3,6 @@ import type { SearchService } from '@open-mercato/search/service'
3
3
  import { registerMcpTool, getToolRegistry } from './tool-registry'
4
4
  import type { McpToolDefinition, McpToolContext } from './types'
5
5
  import { ToolSearchService } from './tool-search'
6
- import { loadApiDiscoveryTools } from './api-discovery-tools'
7
6
 
8
7
  /**
9
8
  * Module tool definition as exported from ai-tools.ts files.
@@ -68,154 +67,21 @@ export async function loadAllModuleTools(): Promise<void> {
68
67
  registerMcpTool(contextWhoamiTool, { moduleId: 'context' })
69
68
  console.error('[MCP Tools] Registered built-in context_whoami tool')
70
69
 
71
- // 2. Register entity graph tools
70
+ // 2. Register Code Mode tools (search + execute)
71
+ // These two tools replace the previous api_discover, call_api, discover_schema,
72
+ // and all module-specific AI tools. The AI writes JavaScript that runs in a
73
+ // node:vm sandbox with access to the OpenAPI spec and api.request().
72
74
  try {
73
- const { entityGraphTools } = await import('./entity-graph-tools')
74
- for (const tool of entityGraphTools) {
75
- registerMcpTool(tool, { moduleId: 'schema' })
76
- }
77
- console.error(`[MCP Tools] Registered ${entityGraphTools.length} entity graph tools`)
78
- } catch (error) {
79
- console.error('[MCP Tools] Could not load entity graph tools:', error)
80
- }
81
-
82
- // 3. Load auto-discovered ai-tools.ts files from modules
83
- // Tools are discovered by the generator and registered in ai-tools.generated.ts
84
- try {
85
- const pathModule = await import('node:path')
86
- const path = pathModule.default
87
- const { pathToFileURL } = await import('node:url')
88
- const fsModule = await import('node:fs')
89
- const fs = fsModule.default
90
- const { findAppRoot, findAllApps } = await import('@open-mercato/shared/lib/bootstrap/appResolver')
91
-
92
- // Find the app root (contains .mercato/generated/)
93
- let appRoot = findAppRoot()
94
-
95
- // Fallback: Try monorepo structure if not found
96
- if (!appRoot) {
97
- let current = process.cwd()
98
- while (current !== path.dirname(current)) {
99
- const appsDir = path.join(current, 'apps')
100
- if (fs.existsSync(appsDir)) {
101
- const apps = findAllApps(current)
102
- if (apps.length > 0) {
103
- appRoot = apps[0]
104
- break
105
- }
106
- }
107
- current = path.dirname(current)
108
- }
109
- }
110
-
111
- if (!appRoot) {
112
- console.error(`[MCP Tools] Could not find app root with .mercato/generated directory`)
113
- return
114
- }
115
-
116
- const tsPath = path.join(appRoot.generatedDir, 'ai-tools.generated.ts')
117
-
118
- // Check if file exists
119
- if (!fs.existsSync(tsPath)) {
120
- console.error(`[MCP Tools] No auto-discovered tools (run npm run modules:prepare to generate)`)
121
- } else {
122
- // Compile TypeScript to JavaScript using esbuild (same approach as dynamicLoader)
123
- const jsPath = tsPath.replace(/\.ts$/, '.mjs')
124
- const jsExists = fs.existsSync(jsPath)
125
-
126
- const needsCompile = !jsExists ||
127
- fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs
128
-
129
- if (needsCompile) {
130
- console.error(`[MCP Tools] Compiling ai-tools.generated.ts...`)
131
- const esbuild = await import('esbuild')
132
- const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))
133
-
134
- // Plugin to resolve @/ alias to app root
135
- const aliasPlugin: import('esbuild').Plugin = {
136
- name: 'alias-resolver',
137
- setup(build) {
138
- build.onResolve({ filter: /^@\// }, (args) => {
139
- const resolved = path.join(appRoot, args.path.slice(2))
140
- if (!fs.existsSync(resolved) && fs.existsSync(resolved + '.ts')) {
141
- return { path: resolved + '.ts' }
142
- }
143
- if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory() && fs.existsSync(path.join(resolved, 'index.ts'))) {
144
- return { path: path.join(resolved, 'index.ts') }
145
- }
146
- return { path: resolved }
147
- })
148
- },
149
- }
150
-
151
- // Plugin to mark non-JSON package imports as external
152
- const externalNonJsonPlugin: import('esbuild').Plugin = {
153
- name: 'external-non-json',
154
- setup(build) {
155
- // Filter matches paths that don't start with . or / (package imports)
156
- build.onResolve({ filter: /^[^./]/ }, (args) => {
157
- // Skip Windows absolute paths (e.g., C:\...) - they're local files, not packages
158
- if (/^[a-zA-Z]:/.test(args.path)) {
159
- return null
160
- }
161
- if (args.path.endsWith('.json')) {
162
- return null
163
- }
164
- return { path: args.path, external: true }
165
- })
166
- },
167
- }
168
-
169
- await esbuild.build({
170
- entryPoints: [tsPath],
171
- outfile: jsPath,
172
- bundle: true,
173
- format: 'esm',
174
- platform: 'node',
175
- target: 'node18',
176
- plugins: [aliasPlugin, externalNonJsonPlugin],
177
- loader: { '.json': 'json' },
178
- })
179
- console.error(`[MCP Tools] Compiled to ${jsPath}`)
180
- }
181
-
182
- // Import the compiled JavaScript
183
- const fileUrl = pathToFileURL(jsPath).href
184
- const { aiToolConfigEntries } = (await import(fileUrl)) as {
185
- aiToolConfigEntries: Array<{ moduleId: string; tools: unknown[] }>
186
- }
187
-
188
- let totalTools = 0
189
- for (const { moduleId, tools } of aiToolConfigEntries) {
190
- if (Array.isArray(tools) && tools.length > 0) {
191
- loadModuleTools(moduleId, tools as ModuleAiTool[])
192
- totalTools += tools.length
193
- console.error(`[MCP Tools] Loaded ${tools.length} tools from ${moduleId}`)
194
- }
195
- }
196
- if (totalTools === 0) {
197
- console.error(`[MCP Tools] No module tools found in generated registry`)
198
- }
199
- }
75
+ const { loadCodeModeTools } = await import('./codemode-tools')
76
+ const toolCount = await loadCodeModeTools()
77
+ console.error(`[MCP Tools] Registered ${toolCount} Code Mode tools`)
200
78
  } catch (error) {
201
- // Generated file might not exist yet (first run) or be empty
202
- // This is expected when no modules have ai-tools.ts files
203
- const errorMessage = error instanceof Error ? error.message : String(error)
204
- console.error(`[MCP Tools] Error loading auto-discovered tools:`, errorMessage)
205
- if (errorMessage.includes('Cannot find module') || errorMessage.includes('ENOENT')) {
206
- console.error(`[MCP Tools] No auto-discovered tools (run npm run modules:prepare to generate)`)
207
- } else {
208
- console.error(`[MCP Tools] Could not load auto-discovered tools:`, error)
209
- }
79
+ console.error('[MCP Tools] Could not load Code Mode tools:', error)
210
80
  }
211
81
 
212
- // 4. Load API discovery tools (api_discover, api_execute)
213
- try {
214
- const apiToolCount = await loadApiDiscoveryTools()
215
- console.error(`[MCP Tools] Loaded ${apiToolCount} API discovery tools`)
216
- } catch (error) {
217
- console.error('[MCP Tools] Could not load API discovery tools:', error)
218
- }
82
+ // Note: Auto-discovered module AI tools (from ai-tools.generated.ts) and
83
+ // legacy API discovery tools (find_api, call_api, discover_schema) are no
84
+ // longer loaded. Code Mode's search + execute tools cover all use cases.
219
85
  }
220
86
 
221
87
  /**
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Response Size Limiter
3
+ *
4
+ * Serializes values with circular reference handling and truncates
5
+ * to a maximum character count suitable for LLM consumption.
6
+ */
7
+
8
+ const DEFAULT_MAX_CHARS = 40_000 // ~10K tokens
9
+
10
+ /**
11
+ * Serialize and truncate a value for LLM consumption.
12
+ *
13
+ * Handles circular references, non-serializable values, and oversized output.
14
+ */
15
+ export function truncateResult(value: unknown, maxChars = DEFAULT_MAX_CHARS): string {
16
+ if (value === undefined) return 'undefined'
17
+
18
+ let serialized: string
19
+ try {
20
+ serialized = JSON.stringify(value, circularReplacer(), 2)
21
+ } catch {
22
+ serialized = String(value)
23
+ }
24
+
25
+ if (serialized.length <= maxChars) return serialized
26
+
27
+ return (
28
+ serialized.slice(0, maxChars) +
29
+ '\n\n... (truncated — refine your code to return less data)'
30
+ )
31
+ }
32
+
33
+ /**
34
+ * JSON.stringify replacer that replaces circular references with "[Circular]".
35
+ */
36
+ function circularReplacer() {
37
+ const seen = new WeakSet()
38
+ return (_key: string, value: unknown) => {
39
+ if (typeof value === 'object' && value !== null) {
40
+ if (seen.has(value)) return '[Circular]'
41
+ seen.add(value)
42
+ }
43
+ return value
44
+ }
45
+ }
@@ -14,6 +14,8 @@ export interface McpToolContext {
14
14
  isSuperAdmin: boolean
15
15
  /** API key secret for authenticating HTTP requests to internal APIs */
16
16
  apiKeySecret?: string
17
+ /** Session token for memory layer (deduplication of search/GET calls) */
18
+ sessionId?: string
17
19
  }
18
20
 
19
21
  /**