@open-mercato/ai-assistant 0.6.5-develop.5240.2.bbd9d30275 → 0.6.5-develop.5266.2.9acdca82ec
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/ai_assistant/lib/agent-registry.js +16 -6
- package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js +91 -0
- package/dist/modules/ai_assistant/lib/generated-registry-loader.js.map +7 -0
- package/dist/modules/ai_assistant/lib/tool-loader.js +30 -3
- package/dist/modules/ai_assistant/lib/tool-loader.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-test-runner.js +7 -92
- package/dist/modules/ai_assistant/lib/tool-test-runner.js.map +2 -2
- package/package.json +6 -6
- package/src/modules/ai_assistant/lib/__tests__/generated-registry-loader.test.ts +140 -0
- package/src/modules/ai_assistant/lib/agent-registry.ts +43 -14
- package/src/modules/ai_assistant/lib/generated-registry-loader.ts +161 -0
- package/src/modules/ai_assistant/lib/tool-loader.ts +60 -9
- package/src/modules/ai_assistant/lib/tool-test-runner.ts +8 -134
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
} from './ai-overrides'
|
|
9
9
|
import type { McpToolDefinition, McpToolContext } from './types'
|
|
10
10
|
import { ToolSearchService } from './tool-search'
|
|
11
|
+
import {
|
|
12
|
+
findGeneratedFile,
|
|
13
|
+
compileAndImportGenerated,
|
|
14
|
+
ensureApiRouteManifestsRegistered,
|
|
15
|
+
} from './generated-registry-loader'
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* Module tool definition as exported from ai-tools.ts files.
|
|
@@ -143,6 +148,33 @@ export function applyAiToolOverrideEntries(
|
|
|
143
148
|
}
|
|
144
149
|
}
|
|
145
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Import the generated `ai-tools.generated.ts` registry.
|
|
153
|
+
*
|
|
154
|
+
* Dual-strategy so the same code works in every runtime without changing the
|
|
155
|
+
* existing Next.js / agents-framework behavior:
|
|
156
|
+
* 1. Prefer the `@/` path-alias import. Inside the Next.js bundler this
|
|
157
|
+
* resolves at build time exactly as before — the in-app agents framework
|
|
158
|
+
* path is untouched.
|
|
159
|
+
* 2. Fall back to locating + compiling the file from disk when the alias
|
|
160
|
+
* import throws. That only happens in a standalone Node process (the
|
|
161
|
+
* `mcp:dev` / `mcp:serve` MCP servers), where `@/` is not a real package
|
|
162
|
+
* specifier and Node throws `ERR_MODULE_NOT_FOUND`.
|
|
163
|
+
*
|
|
164
|
+
* Returns `null` when no generated file exists (pre-generate builds, tests).
|
|
165
|
+
*/
|
|
166
|
+
async function importGeneratedAiToolsModule(): Promise<Record<string, unknown> | null> {
|
|
167
|
+
try {
|
|
168
|
+
return (await import(
|
|
169
|
+
'@/.mercato/generated/ai-tools.generated'
|
|
170
|
+
)) as Record<string, unknown>
|
|
171
|
+
} catch {
|
|
172
|
+
const tsPath = findGeneratedFile('ai-tools.generated.ts')
|
|
173
|
+
if (!tsPath) return null
|
|
174
|
+
return compileAndImportGenerated(tsPath)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
146
178
|
/**
|
|
147
179
|
* Read `aiToolOverrideEntries` from `ai-tools.generated.ts` and apply
|
|
148
180
|
* them on top of the live tool registry. Safe to call when the file is
|
|
@@ -152,12 +184,13 @@ export function applyAiToolOverrideEntries(
|
|
|
152
184
|
export async function loadGeneratedAiToolOverrides(): Promise<void> {
|
|
153
185
|
let entries: AiToolOverrideConfigEntry[] = []
|
|
154
186
|
try {
|
|
155
|
-
const mod = (await
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
entries =
|
|
159
|
-
|
|
160
|
-
|
|
187
|
+
const mod = (await importGeneratedAiToolsModule()) as {
|
|
188
|
+
aiToolOverrideEntries?: unknown[]
|
|
189
|
+
} | null
|
|
190
|
+
entries =
|
|
191
|
+
mod && Array.isArray(mod.aiToolOverrideEntries)
|
|
192
|
+
? (mod.aiToolOverrideEntries as AiToolOverrideConfigEntry[])
|
|
193
|
+
: []
|
|
161
194
|
} catch {
|
|
162
195
|
// No override file generated.
|
|
163
196
|
}
|
|
@@ -172,9 +205,13 @@ export async function loadGeneratedAiToolOverrides(): Promise<void> {
|
|
|
172
205
|
*/
|
|
173
206
|
export async function loadGeneratedModuleAiTools(): Promise<number> {
|
|
174
207
|
try {
|
|
175
|
-
const mod = (await
|
|
176
|
-
|
|
177
|
-
|
|
208
|
+
const mod = (await importGeneratedAiToolsModule()) as {
|
|
209
|
+
aiToolConfigEntries?: AiToolConfigEntry[]
|
|
210
|
+
} | null
|
|
211
|
+
if (!mod) {
|
|
212
|
+
// No generated file (pre-generate build or tests).
|
|
213
|
+
return 0
|
|
214
|
+
}
|
|
178
215
|
const entries = Array.isArray(mod.aiToolConfigEntries)
|
|
179
216
|
? mod.aiToolConfigEntries
|
|
180
217
|
: []
|
|
@@ -228,6 +265,20 @@ export async function loadAllModuleTools(): Promise<void> {
|
|
|
228
265
|
} catch (error) {
|
|
229
266
|
console.error('[MCP Tools] Could not load module AI tools:', error)
|
|
230
267
|
}
|
|
268
|
+
|
|
269
|
+
// 4. Register the API route manifest so API-backed module tools can run.
|
|
270
|
+
// Their handlers delegate to createAiApiOperationRunner, which fails closed
|
|
271
|
+
// with "No API route manifest registered" outside the Next.js bootstrap.
|
|
272
|
+
try {
|
|
273
|
+
const routeCount = await ensureApiRouteManifestsRegistered()
|
|
274
|
+
if (routeCount > 0) {
|
|
275
|
+
console.error(
|
|
276
|
+
`[MCP Tools] Registered ${routeCount} API route manifests for API-backed tools`
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('[MCP Tools] Could not register API route manifests:', error)
|
|
281
|
+
}
|
|
231
282
|
}
|
|
232
283
|
|
|
233
284
|
/**
|
|
@@ -14,9 +14,6 @@
|
|
|
14
14
|
* - Tools without an explicit fixture entry are skipped with reason
|
|
15
15
|
* `'no fixture'` rather than failing.
|
|
16
16
|
*/
|
|
17
|
-
import path from 'node:path'
|
|
18
|
-
import fs from 'node:fs'
|
|
19
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
20
17
|
import type { AwilixContainer } from 'awilix'
|
|
21
18
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
22
19
|
import type { McpToolContext, AiToolDefinition } from './types'
|
|
@@ -24,6 +21,11 @@ import type { AiAgentDefinition, AiAgentMutationPolicy } from './ai-agent-defini
|
|
|
24
21
|
import { getFixture, type ToolFixture } from './tool-test-fixtures'
|
|
25
22
|
import { prepareMutation } from './prepare-mutation'
|
|
26
23
|
import { executePendingActionConfirm } from './pending-action-executor'
|
|
24
|
+
import {
|
|
25
|
+
findGeneratedFile,
|
|
26
|
+
compileAndImportGenerated,
|
|
27
|
+
ensureApiRouteManifestsRegistered,
|
|
28
|
+
} from './generated-registry-loader'
|
|
27
29
|
|
|
28
30
|
export type ToolTestStatus = 'pass' | 'fail' | 'skip'
|
|
29
31
|
|
|
@@ -63,143 +65,15 @@ function isToolDefinition(value: unknown): value is AiToolDefinition {
|
|
|
63
65
|
)
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
/**
|
|
67
|
-
* Locate `apps/mercato/.mercato/generated/ai-tools.generated.ts` without
|
|
68
|
-
* hardcoding the workspace layout. Searches upward from this file's
|
|
69
|
-
* compiled location; in the monorepo dist this is
|
|
70
|
-
* `packages/ai-assistant/dist/...` so we walk up until we hit a directory
|
|
71
|
-
* containing `apps/mercato/.mercato/generated`.
|
|
72
|
-
*/
|
|
73
|
-
function findGeneratedAiToolsPath(): string | null {
|
|
74
|
-
const here = (() => {
|
|
75
|
-
try {
|
|
76
|
-
return fileURLToPath(import.meta.url)
|
|
77
|
-
} catch {
|
|
78
|
-
return null
|
|
79
|
-
}
|
|
80
|
-
})()
|
|
81
|
-
if (!here) return null
|
|
82
|
-
let cursor = path.dirname(here)
|
|
83
|
-
for (let i = 0; i < 12; i++) {
|
|
84
|
-
const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', 'ai-tools.generated.ts')
|
|
85
|
-
if (fs.existsSync(candidate)) return candidate
|
|
86
|
-
const next = path.dirname(cursor)
|
|
87
|
-
if (next === cursor) break
|
|
88
|
-
cursor = next
|
|
89
|
-
}
|
|
90
|
-
// Fallback: cwd-based lookup (when CLI is invoked from apps/mercato).
|
|
91
|
-
const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', 'ai-tools.generated.ts')
|
|
92
|
-
if (fs.existsSync(fromCwd)) return fromCwd
|
|
93
|
-
const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', 'ai-tools.generated.ts')
|
|
94
|
-
if (fs.existsSync(fromCwdDirect)) return fromCwdDirect
|
|
95
|
-
return null
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Compile-and-import `ai-tools.generated.ts` on the fly. Mirrors the approach
|
|
100
|
-
* used by `loadBootstrapData` in `@open-mercato/shared/lib/bootstrap/dynamicLoader`:
|
|
101
|
-
* resolves the `@/` alias to the app root, marks every other package import as
|
|
102
|
-
* external, and emits a sibling `.mjs` we can `import()` from Node. Cached on
|
|
103
|
-
* mtime so repeat runs in the same process don't recompile.
|
|
104
|
-
*/
|
|
105
|
-
async function compileAndImportGenerated(tsPath: string): Promise<RawAiToolsModule> {
|
|
106
|
-
const jsPath = tsPath.replace(/\.ts$/, '.mjs')
|
|
107
|
-
// appRoot is two directories up from `.mercato/generated/<file>.ts`.
|
|
108
|
-
const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))
|
|
109
|
-
const tsExists = fs.existsSync(tsPath)
|
|
110
|
-
if (!tsExists) {
|
|
111
|
-
throw new Error(`Generated file not found: ${tsPath}`)
|
|
112
|
-
}
|
|
113
|
-
const jsExists = fs.existsSync(jsPath)
|
|
114
|
-
const needsCompile =
|
|
115
|
-
!jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs
|
|
116
|
-
if (needsCompile) {
|
|
117
|
-
const esbuild = await import('esbuild')
|
|
118
|
-
// Transpile-only: don't bundle. Generated registry files only declare an
|
|
119
|
-
// array literal whose entries are static `import("…")` arrow functions —
|
|
120
|
-
// we want those `import()` strings to stay as runtime imports so Node
|
|
121
|
-
// resolves them lazily through the workspace's normal module resolution.
|
|
122
|
-
// Eagerly bundling them pulls Next.js / route handler internals into the
|
|
123
|
-
// .mjs and breaks at runtime (e.g. `next/server` package-exports map).
|
|
124
|
-
const tsSource = fs.readFileSync(tsPath, 'utf-8')
|
|
125
|
-
// Rewrite `@/...` aliases to absolute paths so Node can resolve them.
|
|
126
|
-
const aliasRewritten = tsSource.replace(
|
|
127
|
-
/from\s+["']@\/([^"']+)["']/g,
|
|
128
|
-
(_match, p1: string) => {
|
|
129
|
-
const target = path.join(appRoot, p1)
|
|
130
|
-
const candidate = fs.existsSync(target)
|
|
131
|
-
? target
|
|
132
|
-
: fs.existsSync(target + '.ts')
|
|
133
|
-
? target + '.ts'
|
|
134
|
-
: target
|
|
135
|
-
return `from ${JSON.stringify(pathToFileURL(candidate).href)}`
|
|
136
|
-
},
|
|
137
|
-
).replace(
|
|
138
|
-
/import\s*\(\s*["']@\/([^"']+)["']\s*\)/g,
|
|
139
|
-
(_match, p1: string) => {
|
|
140
|
-
const target = path.join(appRoot, p1)
|
|
141
|
-
const candidate = fs.existsSync(target)
|
|
142
|
-
? target
|
|
143
|
-
: fs.existsSync(target + '.ts')
|
|
144
|
-
? target + '.ts'
|
|
145
|
-
: target
|
|
146
|
-
return `import(${JSON.stringify(pathToFileURL(candidate).href)})`
|
|
147
|
-
},
|
|
148
|
-
)
|
|
149
|
-
const result = await esbuild.transform(aliasRewritten, {
|
|
150
|
-
loader: 'ts',
|
|
151
|
-
format: 'esm',
|
|
152
|
-
target: 'node18',
|
|
153
|
-
sourcemap: false,
|
|
154
|
-
sourcefile: tsPath,
|
|
155
|
-
})
|
|
156
|
-
fs.writeFileSync(jsPath, result.code)
|
|
157
|
-
}
|
|
158
|
-
return (await import(pathToFileURL(jsPath).href)) as RawAiToolsModule
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Compile-and-import `api-routes.generated.ts` and register its manifest with
|
|
163
|
-
* the shared registry. Many tool handlers delegate to `aiApiOperationRunner`
|
|
164
|
-
* which fails closed when no manifest is registered. Idempotent: registering
|
|
165
|
-
* the same array twice is safe.
|
|
166
|
-
*/
|
|
167
|
-
async function ensureApiRouteManifestsRegistered(generatedDir: string): Promise<void> {
|
|
168
|
-
const tsPath = path.join(generatedDir, 'api-routes.generated.ts')
|
|
169
|
-
if (!fs.existsSync(tsPath)) return
|
|
170
|
-
try {
|
|
171
|
-
const mod = (await compileAndImportGenerated(tsPath)) as Record<string, unknown>
|
|
172
|
-
const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes
|
|
173
|
-
if (!Array.isArray(apiRoutes)) {
|
|
174
|
-
console.warn('[tool-test-runner] api-routes.generated.mjs returned no apiRoutes array')
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
const registry = await import('@open-mercato/shared/modules/registry')
|
|
178
|
-
registry.registerApiRouteManifests(
|
|
179
|
-
apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],
|
|
180
|
-
)
|
|
181
|
-
if (process.env.OM_TOOL_TEST_DEBUG === '1') {
|
|
182
|
-
console.log(
|
|
183
|
-
`[tool-test-runner] Registered ${apiRoutes.length} api-route manifests; getApiRouteManifests().length=${registry.getApiRouteManifests().length}`,
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
} catch (error) {
|
|
187
|
-
console.warn(
|
|
188
|
-
'[tool-test-runner] Could not register api-routes manifest:',
|
|
189
|
-
error instanceof Error ? error.message : error,
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
68
|
async function loadGeneratedTools(): Promise<{ moduleId: string; tools: AiToolDefinition[] }[]> {
|
|
195
|
-
const tsPath =
|
|
69
|
+
const tsPath = findGeneratedFile('ai-tools.generated.ts')
|
|
196
70
|
if (!tsPath) {
|
|
197
71
|
throw new Error(
|
|
198
72
|
'Could not locate apps/<app>/.mercato/generated/ai-tools.generated.ts. Run `yarn generate` first.',
|
|
199
73
|
)
|
|
200
74
|
}
|
|
201
|
-
await ensureApiRouteManifestsRegistered(
|
|
202
|
-
const mod = await compileAndImportGenerated(tsPath)
|
|
75
|
+
await ensureApiRouteManifestsRegistered()
|
|
76
|
+
const mod = (await compileAndImportGenerated(tsPath)) as RawAiToolsModule
|
|
203
77
|
const entries = mod.aiToolConfigEntriesRaw ?? mod.aiToolConfigEntries ?? []
|
|
204
78
|
const result: { moduleId: string; tools: AiToolDefinition[] }[] = []
|
|
205
79
|
for (const entry of entries) {
|