@open-mercato/ai-assistant 0.6.5-develop.5240.1.bbd9d30275 → 0.6.5-develop.5266.1.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
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:ai-assistant] found
|
|
1
|
+
[build:ai-assistant] found 213 entry points
|
|
2
2
|
[build:ai-assistant] built successfully
|
|
@@ -5,8 +5,18 @@ import {
|
|
|
5
5
|
composeAgentOverrideMap
|
|
6
6
|
} from "./ai-overrides.js";
|
|
7
7
|
import { TASK_PLAN_TOOL_NAME } from "./task-plan-labels.js";
|
|
8
|
+
import { findGeneratedFile, compileAndImportGenerated } from "./generated-registry-loader.js";
|
|
8
9
|
const agentsById = /* @__PURE__ */ new Map();
|
|
9
10
|
let loaded = false;
|
|
11
|
+
async function importGeneratedAiAgentsModule() {
|
|
12
|
+
try {
|
|
13
|
+
return await import("@/.mercato/generated/ai-agents.generated");
|
|
14
|
+
} catch {
|
|
15
|
+
const tsPath = findGeneratedFile("ai-agents.generated.ts");
|
|
16
|
+
if (!tsPath) return null;
|
|
17
|
+
return compileAndImportGenerated(tsPath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
10
20
|
function isStringArray(value) {
|
|
11
21
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
12
22
|
}
|
|
@@ -108,8 +118,8 @@ function populateFromAgents(agents) {
|
|
|
108
118
|
}
|
|
109
119
|
async function loadOverrideEntries() {
|
|
110
120
|
try {
|
|
111
|
-
const mod = await
|
|
112
|
-
return Array.isArray(mod.aiAgentOverrideEntries) ? mod.aiAgentOverrideEntries : [];
|
|
121
|
+
const mod = await importGeneratedAiAgentsModule();
|
|
122
|
+
return mod && Array.isArray(mod.aiAgentOverrideEntries) ? mod.aiAgentOverrideEntries : [];
|
|
113
123
|
} catch {
|
|
114
124
|
return [];
|
|
115
125
|
}
|
|
@@ -127,8 +137,8 @@ function applyOverridesToRegistry(entries) {
|
|
|
127
137
|
}
|
|
128
138
|
async function loadExtensionEntries() {
|
|
129
139
|
try {
|
|
130
|
-
const mod = await
|
|
131
|
-
const entries = Array.isArray(mod.aiAgentExtensionEntries) ? mod.aiAgentExtensionEntries : [];
|
|
140
|
+
const mod = await importGeneratedAiAgentsModule();
|
|
141
|
+
const entries = mod && Array.isArray(mod.aiAgentExtensionEntries) ? mod.aiAgentExtensionEntries : [];
|
|
132
142
|
return composeAgentExtensionEntries(entries).filter(isAiAgentExtension);
|
|
133
143
|
} catch {
|
|
134
144
|
return [];
|
|
@@ -173,8 +183,8 @@ ${appendSystemPrompt}` : systemPrompt,
|
|
|
173
183
|
async function loadAgentRegistry() {
|
|
174
184
|
if (loaded) return;
|
|
175
185
|
try {
|
|
176
|
-
const mod = await
|
|
177
|
-
const agents = Array.isArray(mod.allAiAgents) ? mod.allAiAgents : [];
|
|
186
|
+
const mod = await importGeneratedAiAgentsModule();
|
|
187
|
+
const agents = mod && Array.isArray(mod.allAiAgents) ? mod.allAiAgents : [];
|
|
178
188
|
populateFromAgents(agents);
|
|
179
189
|
} catch (error) {
|
|
180
190
|
console.error(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/agent-registry.ts"],
|
|
4
|
-
"sourcesContent": ["import { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport type { AiAgentDefinition, AiAgentExtension, AiAgentSuggestion } from './ai-agent-definition'\nimport {\n applyAgentOverrideMap,\n composeAgentExtensionEntries,\n composeAgentOverrideMap,\n type AiAgentExtensionConfigEntry,\n type AiAgentOverrideConfigEntry,\n} from './ai-overrides'\nimport { TASK_PLAN_TOOL_NAME } from './task-plan-labels'\n\nconst agentsById = new Map<string, AiAgentDefinition>()\nlet loaded = false\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\nfunction isAiAgentSuggestion(value: unknown): value is AiAgentSuggestion {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return typeof candidate.label === 'string' && typeof candidate.prompt === 'string'\n}\n\nfunction isAiAgentExtension(value: unknown): value is AiAgentExtension {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.targetAgentId === 'string' &&\n (!('replaceAllowedTools' in candidate) || isStringArray(candidate.replaceAllowedTools)) &&\n (!('deleteAllowedTools' in candidate) || isStringArray(candidate.deleteAllowedTools)) &&\n (!('appendAllowedTools' in candidate) || isStringArray(candidate.appendAllowedTools)) &&\n (!('taskPlan' in candidate) ||\n (candidate.taskPlan != null &&\n typeof candidate.taskPlan === 'object' &&\n (!('enabled' in candidate.taskPlan) ||\n typeof (candidate.taskPlan as { enabled?: unknown }).enabled === 'boolean'))) &&\n (!('replaceSystemPrompt' in candidate) || typeof candidate.replaceSystemPrompt === 'string') &&\n (!('appendSystemPrompt' in candidate) || typeof candidate.appendSystemPrompt === 'string') &&\n (!('replaceSuggestions' in candidate) ||\n (Array.isArray(candidate.replaceSuggestions) && candidate.replaceSuggestions.every(isAiAgentSuggestion))) &&\n (!('deleteSuggestions' in candidate) || isStringArray(candidate.deleteSuggestions)) &&\n (!('appendSuggestions' in candidate) ||\n (Array.isArray(candidate.appendSuggestions) && candidate.appendSuggestions.every(isAiAgentSuggestion))) &&\n (!('suggestions' in candidate) ||\n (Array.isArray(candidate.suggestions) && candidate.suggestions.every(isAiAgentSuggestion)))\n )\n}\n\nfunction isAiAgentDefinition(value: unknown): value is AiAgentDefinition {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.id === 'string' &&\n typeof candidate.moduleId === 'string' &&\n typeof candidate.label === 'string' &&\n typeof candidate.description === 'string' &&\n typeof candidate.systemPrompt === 'string' &&\n isStringArray(candidate.allowedTools)\n )\n}\n\nfunction uniqueStrings(values: readonly string[]): string[] {\n return Array.from(new Set(values.filter((value) => typeof value === 'string' && value.length > 0)))\n}\n\nexport function isAgentTaskPlanEnabled(agent: Pick<AiAgentDefinition, 'taskPlan'>): boolean {\n return agent.taskPlan?.enabled === true\n}\n\nfunction normalizeTaskPlanTool(agent: AiAgentDefinition): AiAgentDefinition {\n const enabled = isAgentTaskPlanEnabled(agent)\n const withoutInternalTool = agent.allowedTools.filter((toolName) => toolName !== TASK_PLAN_TOOL_NAME)\n const allowedTools = enabled\n ? uniqueStrings([...withoutInternalTool, TASK_PLAN_TOOL_NAME])\n : uniqueStrings(withoutInternalTool)\n if (\n allowedTools.length === agent.allowedTools.length &&\n allowedTools.every((toolName, index) => toolName === agent.allowedTools[index])\n ) {\n return agent\n }\n return {\n ...agent,\n allowedTools,\n }\n}\n\nfunction applyStringListPatch(\n current: readonly string[],\n patch: {\n replace?: readonly string[]\n delete?: readonly string[]\n append?: readonly string[]\n },\n): string[] {\n const deleted = new Set(patch.delete ?? [])\n return uniqueStrings([\n ...(patch.replace ?? current).filter((value) => !deleted.has(value)),\n ...(patch.append ?? []),\n ])\n}\n\nfunction suggestionDeleteKey(suggestion: AiAgentSuggestion): string[] {\n return [suggestion.label, suggestion.prompt].filter((value) => value.length > 0)\n}\n\nfunction applySuggestionPatch(\n current: readonly AiAgentSuggestion[],\n patch: {\n replace?: readonly AiAgentSuggestion[]\n delete?: readonly string[]\n append?: readonly AiAgentSuggestion[]\n },\n): AiAgentSuggestion[] {\n const deleted = new Set(patch.delete ?? [])\n const base = patch.replace ?? current\n const out: AiAgentSuggestion[] = []\n const seen = new Set<string>()\n for (const suggestion of base) {\n if (suggestionDeleteKey(suggestion).some((key) => deleted.has(key))) continue\n const key = `${suggestion.label}\\n${suggestion.prompt}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push(suggestion)\n }\n for (const suggestion of patch.append ?? []) {\n const key = `${suggestion.label}\\n${suggestion.prompt}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push(suggestion)\n }\n return out\n}\n\nfunction validateAndNormalizeAgent(candidate: AiAgentDefinition): AiAgentDefinition {\n const taskPlanNormalized = normalizeTaskPlanTool(candidate)\n const rawProvider = candidate.defaultProvider\n if (typeof rawProvider !== 'string' || rawProvider.trim().length === 0) {\n return taskPlanNormalized\n }\n const providerHint = rawProvider.trim()\n const registered = llmProviderRegistry.get(providerHint)\n if (!registered) {\n console.warn(\n `[AI Agents] Agent \"${candidate.id}\" declares defaultProvider \"${providerHint}\" which is not registered in llmProviderRegistry. ` +\n `The agent will be registered with defaultProvider: undefined so the resolution chain still works. ` +\n `Built-in provider ids: anthropic, google, openai, deepinfra, groq, together, fireworks, azure, litellm, ollama.`,\n )\n return { ...taskPlanNormalized, defaultProvider: undefined }\n }\n return taskPlanNormalized\n}\n\nfunction populateFromAgents(agents: unknown[]): void {\n for (const candidate of agents) {\n if (!isAiAgentDefinition(candidate)) {\n console.warn('[AI Agents] Skipping malformed agent entry in ai-agents.generated.ts')\n continue\n }\n const existing = agentsById.get(candidate.id)\n if (existing) {\n throw new Error(\n `[AI Agents] Duplicate agent id \"${candidate.id}\" \u2014 already registered by module \"${existing.moduleId}\", conflicts with module \"${candidate.moduleId}\". Export \\`aiAgentOverrides\\` from your module's \\`ai-agents.ts\\` (or set it on the modules.ts entry) to replace an agent across modules.`\n )\n }\n agentsById.set(candidate.id, validateAndNormalizeAgent(candidate))\n }\n}\n\nasync function loadOverrideEntries(): Promise<AiAgentOverrideConfigEntry[]> {\n try {\n const mod = (await import(\n '@/.mercato/generated/ai-agents.generated'\n )) as { aiAgentOverrideEntries?: unknown[] }\n return Array.isArray(mod.aiAgentOverrideEntries)\n ? (mod.aiAgentOverrideEntries as AiAgentOverrideConfigEntry[])\n : []\n } catch {\n // No generated file yet \u2014 pre-generate builds and tests fall through.\n return []\n }\n}\n\nfunction applyOverridesToRegistry(entries: readonly AiAgentOverrideConfigEntry[]): void {\n const overrideMap = composeAgentOverrideMap(entries)\n if (Object.keys(overrideMap).length === 0) return\n const overridden = applyAgentOverrideMap(Array.from(agentsById.values()), overrideMap)\n agentsById.clear()\n for (const agent of overridden) agentsById.set(agent.id, agent)\n for (const [id, value] of Object.entries(overrideMap)) {\n const verb = value === null ? 'disabled' : 'replaced'\n console.info(`[AI Overrides] Agent \"${id}\" ${verb} by override.`)\n }\n}\n\nasync function loadExtensionEntries(): Promise<AiAgentExtension[]> {\n try {\n const mod = (await import(\n '@/.mercato/generated/ai-agents.generated'\n )) as { aiAgentExtensionEntries?: unknown[] }\n const entries = Array.isArray(mod.aiAgentExtensionEntries)\n ? (mod.aiAgentExtensionEntries as AiAgentExtensionConfigEntry[])\n : []\n return composeAgentExtensionEntries(entries).filter(isAiAgentExtension)\n } catch {\n return []\n }\n}\n\nfunction applyExtensionsToRegistry(extensions: readonly AiAgentExtension[]): void {\n if (extensions.length === 0) return\n for (const extension of extensions) {\n const agent = agentsById.get(extension.targetAgentId)\n if (!agent) {\n console.warn(\n `[AI Agents] Skipping extension for unknown agent \"${extension.targetAgentId}\".`,\n )\n continue\n }\n\n const replacementSystemPrompt = extension.replaceSystemPrompt?.trim()\n const appendSystemPrompt = extension.appendSystemPrompt?.trim()\n const systemPrompt = replacementSystemPrompt ?? agent.systemPrompt.trim()\n const patchedAgent: AiAgentDefinition = {\n ...agent,\n allowedTools: applyStringListPatch(agent.allowedTools, {\n replace: extension.replaceAllowedTools,\n delete: extension.deleteAllowedTools,\n append: extension.appendAllowedTools,\n }),\n taskPlan: extension.taskPlan !== undefined ? extension.taskPlan : agent.taskPlan,\n systemPrompt: appendSystemPrompt\n ? `${systemPrompt}\\n\\n${appendSystemPrompt}`\n : systemPrompt,\n suggestions: applySuggestionPatch(agent.suggestions ?? [], {\n replace: extension.replaceSuggestions,\n delete: extension.deleteSuggestions,\n append: [\n ...(extension.appendSuggestions ?? []),\n ...(extension.suggestions ?? []),\n ],\n }),\n }\n agentsById.set(agent.id, validateAndNormalizeAgent(patchedAgent))\n }\n}\n\nexport async function loadAgentRegistry(): Promise<void> {\n if (loaded) return\n try {\n const mod = (await import(\n '@/.mercato/generated/ai-agents.generated'\n )) as { allAiAgents?: unknown[] }\n const agents = Array.isArray(mod.allAiAgents) ? mod.allAiAgents : []\n populateFromAgents(agents)\n } catch (error) {\n console.error(\n '[AI Agents] Could not load ai-agents.generated.ts (agent registry empty):',\n error\n )\n } finally {\n try {\n const overrideEntries = await loadOverrideEntries()\n applyOverridesToRegistry(overrideEntries)\n const extensionEntries = await loadExtensionEntries()\n applyExtensionsToRegistry(extensionEntries)\n } catch (error) {\n console.error('[AI Agents] Failed to apply agent overrides/extensions:', error)\n }\n loaded = true\n }\n}\n\nexport function getAgent(id: string): AiAgentDefinition | undefined {\n return agentsById.get(id)\n}\n\nexport function listAgents(): AiAgentDefinition[] {\n return Array.from(agentsById.values()).sort((a, b) => a.id.localeCompare(b.id))\n}\n\nexport function listAgentsByModule(moduleId: string): AiAgentDefinition[] {\n return listAgents().filter((agent) => agent.moduleId === moduleId)\n}\n\n/**\n * @__internal\n * Test-only hook \u2014 clears the cached registry so `loadAgentRegistry` re-evaluates its source.\n */\nexport function resetAgentRegistryForTests(): void {\n agentsById.clear()\n loaded = false\n}\n\n/**\n * @__internal\n * Test-only hook \u2014 seeds the registry directly from a fixture array without going through\n * the dynamic generated-file import. Used by the registry's own unit tests.\n */\nexport function seedAgentRegistryForTests(agents: unknown[]): void {\n agentsById.clear()\n populateFromAgents(agents)\n loaded = true\n}\n\n/**\n * @__internal\n * Test-only hook \u2014 apply override entries against the seeded registry to\n * exercise the override pipeline without round-tripping through the\n * generated file.\n */\nexport function applyAgentOverrideEntriesForTests(\n entries: readonly AiAgentOverrideConfigEntry[],\n): void {\n applyOverridesToRegistry(entries)\n}\n\n/**\n * @__internal Test-only hook \u2014 apply additive extension entries against the\n * seeded registry without round-tripping through the generated file.\n */\nexport function applyAgentExtensionEntriesForTests(\n entries: readonly AiAgentExtension[],\n): void {\n applyExtensionsToRegistry(entries)\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,2BAA2B;AAEpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,2BAA2B;
|
|
4
|
+
"sourcesContent": ["import { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport type { AiAgentDefinition, AiAgentExtension, AiAgentSuggestion } from './ai-agent-definition'\nimport {\n applyAgentOverrideMap,\n composeAgentExtensionEntries,\n composeAgentOverrideMap,\n type AiAgentExtensionConfigEntry,\n type AiAgentOverrideConfigEntry,\n} from './ai-overrides'\nimport { TASK_PLAN_TOOL_NAME } from './task-plan-labels'\nimport { findGeneratedFile, compileAndImportGenerated } from './generated-registry-loader'\n\nconst agentsById = new Map<string, AiAgentDefinition>()\nlet loaded = false\n\n/**\n * Import the generated `ai-agents.generated.ts` registry.\n *\n * Dual-strategy, mirroring `tool-loader`'s `importGeneratedAiToolsModule`:\n * 1. Prefer the `@/` path-alias import \u2014 the Next.js bundler resolves it at\n * build time, so the in-app agent runtime is unchanged.\n * 2. Fall back to locating + compiling the file from disk when the alias\n * import throws. That only happens in a standalone Node process (the\n * `mcp:dev` / `mcp:serve` MCP servers), where `@/` is not a real package\n * specifier and Node throws `ERR_MODULE_NOT_FOUND`. Without this the\n * `meta.list_agents` / `meta.describe_agent` tools return an empty agent\n * registry over MCP.\n *\n * Returns `null` when no generated file exists (pre-generate builds, tests).\n */\nasync function importGeneratedAiAgentsModule(): Promise<Record<string, unknown> | null> {\n try {\n return (await import(\n '@/.mercato/generated/ai-agents.generated'\n )) as Record<string, unknown>\n } catch {\n const tsPath = findGeneratedFile('ai-agents.generated.ts')\n if (!tsPath) return null\n return compileAndImportGenerated(tsPath)\n }\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\nfunction isAiAgentSuggestion(value: unknown): value is AiAgentSuggestion {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return typeof candidate.label === 'string' && typeof candidate.prompt === 'string'\n}\n\nfunction isAiAgentExtension(value: unknown): value is AiAgentExtension {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.targetAgentId === 'string' &&\n (!('replaceAllowedTools' in candidate) || isStringArray(candidate.replaceAllowedTools)) &&\n (!('deleteAllowedTools' in candidate) || isStringArray(candidate.deleteAllowedTools)) &&\n (!('appendAllowedTools' in candidate) || isStringArray(candidate.appendAllowedTools)) &&\n (!('taskPlan' in candidate) ||\n (candidate.taskPlan != null &&\n typeof candidate.taskPlan === 'object' &&\n (!('enabled' in candidate.taskPlan) ||\n typeof (candidate.taskPlan as { enabled?: unknown }).enabled === 'boolean'))) &&\n (!('replaceSystemPrompt' in candidate) || typeof candidate.replaceSystemPrompt === 'string') &&\n (!('appendSystemPrompt' in candidate) || typeof candidate.appendSystemPrompt === 'string') &&\n (!('replaceSuggestions' in candidate) ||\n (Array.isArray(candidate.replaceSuggestions) && candidate.replaceSuggestions.every(isAiAgentSuggestion))) &&\n (!('deleteSuggestions' in candidate) || isStringArray(candidate.deleteSuggestions)) &&\n (!('appendSuggestions' in candidate) ||\n (Array.isArray(candidate.appendSuggestions) && candidate.appendSuggestions.every(isAiAgentSuggestion))) &&\n (!('suggestions' in candidate) ||\n (Array.isArray(candidate.suggestions) && candidate.suggestions.every(isAiAgentSuggestion)))\n )\n}\n\nfunction isAiAgentDefinition(value: unknown): value is AiAgentDefinition {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.id === 'string' &&\n typeof candidate.moduleId === 'string' &&\n typeof candidate.label === 'string' &&\n typeof candidate.description === 'string' &&\n typeof candidate.systemPrompt === 'string' &&\n isStringArray(candidate.allowedTools)\n )\n}\n\nfunction uniqueStrings(values: readonly string[]): string[] {\n return Array.from(new Set(values.filter((value) => typeof value === 'string' && value.length > 0)))\n}\n\nexport function isAgentTaskPlanEnabled(agent: Pick<AiAgentDefinition, 'taskPlan'>): boolean {\n return agent.taskPlan?.enabled === true\n}\n\nfunction normalizeTaskPlanTool(agent: AiAgentDefinition): AiAgentDefinition {\n const enabled = isAgentTaskPlanEnabled(agent)\n const withoutInternalTool = agent.allowedTools.filter((toolName) => toolName !== TASK_PLAN_TOOL_NAME)\n const allowedTools = enabled\n ? uniqueStrings([...withoutInternalTool, TASK_PLAN_TOOL_NAME])\n : uniqueStrings(withoutInternalTool)\n if (\n allowedTools.length === agent.allowedTools.length &&\n allowedTools.every((toolName, index) => toolName === agent.allowedTools[index])\n ) {\n return agent\n }\n return {\n ...agent,\n allowedTools,\n }\n}\n\nfunction applyStringListPatch(\n current: readonly string[],\n patch: {\n replace?: readonly string[]\n delete?: readonly string[]\n append?: readonly string[]\n },\n): string[] {\n const deleted = new Set(patch.delete ?? [])\n return uniqueStrings([\n ...(patch.replace ?? current).filter((value) => !deleted.has(value)),\n ...(patch.append ?? []),\n ])\n}\n\nfunction suggestionDeleteKey(suggestion: AiAgentSuggestion): string[] {\n return [suggestion.label, suggestion.prompt].filter((value) => value.length > 0)\n}\n\nfunction applySuggestionPatch(\n current: readonly AiAgentSuggestion[],\n patch: {\n replace?: readonly AiAgentSuggestion[]\n delete?: readonly string[]\n append?: readonly AiAgentSuggestion[]\n },\n): AiAgentSuggestion[] {\n const deleted = new Set(patch.delete ?? [])\n const base = patch.replace ?? current\n const out: AiAgentSuggestion[] = []\n const seen = new Set<string>()\n for (const suggestion of base) {\n if (suggestionDeleteKey(suggestion).some((key) => deleted.has(key))) continue\n const key = `${suggestion.label}\\n${suggestion.prompt}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push(suggestion)\n }\n for (const suggestion of patch.append ?? []) {\n const key = `${suggestion.label}\\n${suggestion.prompt}`\n if (seen.has(key)) continue\n seen.add(key)\n out.push(suggestion)\n }\n return out\n}\n\nfunction validateAndNormalizeAgent(candidate: AiAgentDefinition): AiAgentDefinition {\n const taskPlanNormalized = normalizeTaskPlanTool(candidate)\n const rawProvider = candidate.defaultProvider\n if (typeof rawProvider !== 'string' || rawProvider.trim().length === 0) {\n return taskPlanNormalized\n }\n const providerHint = rawProvider.trim()\n const registered = llmProviderRegistry.get(providerHint)\n if (!registered) {\n console.warn(\n `[AI Agents] Agent \"${candidate.id}\" declares defaultProvider \"${providerHint}\" which is not registered in llmProviderRegistry. ` +\n `The agent will be registered with defaultProvider: undefined so the resolution chain still works. ` +\n `Built-in provider ids: anthropic, google, openai, deepinfra, groq, together, fireworks, azure, litellm, ollama.`,\n )\n return { ...taskPlanNormalized, defaultProvider: undefined }\n }\n return taskPlanNormalized\n}\n\nfunction populateFromAgents(agents: unknown[]): void {\n for (const candidate of agents) {\n if (!isAiAgentDefinition(candidate)) {\n console.warn('[AI Agents] Skipping malformed agent entry in ai-agents.generated.ts')\n continue\n }\n const existing = agentsById.get(candidate.id)\n if (existing) {\n throw new Error(\n `[AI Agents] Duplicate agent id \"${candidate.id}\" \u2014 already registered by module \"${existing.moduleId}\", conflicts with module \"${candidate.moduleId}\". Export \\`aiAgentOverrides\\` from your module's \\`ai-agents.ts\\` (or set it on the modules.ts entry) to replace an agent across modules.`\n )\n }\n agentsById.set(candidate.id, validateAndNormalizeAgent(candidate))\n }\n}\n\nasync function loadOverrideEntries(): Promise<AiAgentOverrideConfigEntry[]> {\n try {\n const mod = (await importGeneratedAiAgentsModule()) as {\n aiAgentOverrideEntries?: unknown[]\n } | null\n return mod && Array.isArray(mod.aiAgentOverrideEntries)\n ? (mod.aiAgentOverrideEntries as AiAgentOverrideConfigEntry[])\n : []\n } catch {\n // No generated file yet \u2014 pre-generate builds and tests fall through.\n return []\n }\n}\n\nfunction applyOverridesToRegistry(entries: readonly AiAgentOverrideConfigEntry[]): void {\n const overrideMap = composeAgentOverrideMap(entries)\n if (Object.keys(overrideMap).length === 0) return\n const overridden = applyAgentOverrideMap(Array.from(agentsById.values()), overrideMap)\n agentsById.clear()\n for (const agent of overridden) agentsById.set(agent.id, agent)\n for (const [id, value] of Object.entries(overrideMap)) {\n const verb = value === null ? 'disabled' : 'replaced'\n console.info(`[AI Overrides] Agent \"${id}\" ${verb} by override.`)\n }\n}\n\nasync function loadExtensionEntries(): Promise<AiAgentExtension[]> {\n try {\n const mod = (await importGeneratedAiAgentsModule()) as {\n aiAgentExtensionEntries?: unknown[]\n } | null\n const entries =\n mod && Array.isArray(mod.aiAgentExtensionEntries)\n ? (mod.aiAgentExtensionEntries as AiAgentExtensionConfigEntry[])\n : []\n return composeAgentExtensionEntries(entries).filter(isAiAgentExtension)\n } catch {\n return []\n }\n}\n\nfunction applyExtensionsToRegistry(extensions: readonly AiAgentExtension[]): void {\n if (extensions.length === 0) return\n for (const extension of extensions) {\n const agent = agentsById.get(extension.targetAgentId)\n if (!agent) {\n console.warn(\n `[AI Agents] Skipping extension for unknown agent \"${extension.targetAgentId}\".`,\n )\n continue\n }\n\n const replacementSystemPrompt = extension.replaceSystemPrompt?.trim()\n const appendSystemPrompt = extension.appendSystemPrompt?.trim()\n const systemPrompt = replacementSystemPrompt ?? agent.systemPrompt.trim()\n const patchedAgent: AiAgentDefinition = {\n ...agent,\n allowedTools: applyStringListPatch(agent.allowedTools, {\n replace: extension.replaceAllowedTools,\n delete: extension.deleteAllowedTools,\n append: extension.appendAllowedTools,\n }),\n taskPlan: extension.taskPlan !== undefined ? extension.taskPlan : agent.taskPlan,\n systemPrompt: appendSystemPrompt\n ? `${systemPrompt}\\n\\n${appendSystemPrompt}`\n : systemPrompt,\n suggestions: applySuggestionPatch(agent.suggestions ?? [], {\n replace: extension.replaceSuggestions,\n delete: extension.deleteSuggestions,\n append: [\n ...(extension.appendSuggestions ?? []),\n ...(extension.suggestions ?? []),\n ],\n }),\n }\n agentsById.set(agent.id, validateAndNormalizeAgent(patchedAgent))\n }\n}\n\nexport async function loadAgentRegistry(): Promise<void> {\n if (loaded) return\n try {\n const mod = (await importGeneratedAiAgentsModule()) as {\n allAiAgents?: unknown[]\n } | null\n const agents = mod && Array.isArray(mod.allAiAgents) ? mod.allAiAgents : []\n populateFromAgents(agents)\n } catch (error) {\n console.error(\n '[AI Agents] Could not load ai-agents.generated.ts (agent registry empty):',\n error\n )\n } finally {\n try {\n const overrideEntries = await loadOverrideEntries()\n applyOverridesToRegistry(overrideEntries)\n const extensionEntries = await loadExtensionEntries()\n applyExtensionsToRegistry(extensionEntries)\n } catch (error) {\n console.error('[AI Agents] Failed to apply agent overrides/extensions:', error)\n }\n loaded = true\n }\n}\n\nexport function getAgent(id: string): AiAgentDefinition | undefined {\n return agentsById.get(id)\n}\n\nexport function listAgents(): AiAgentDefinition[] {\n return Array.from(agentsById.values()).sort((a, b) => a.id.localeCompare(b.id))\n}\n\nexport function listAgentsByModule(moduleId: string): AiAgentDefinition[] {\n return listAgents().filter((agent) => agent.moduleId === moduleId)\n}\n\n/**\n * @__internal\n * Test-only hook \u2014 clears the cached registry so `loadAgentRegistry` re-evaluates its source.\n */\nexport function resetAgentRegistryForTests(): void {\n agentsById.clear()\n loaded = false\n}\n\n/**\n * @__internal\n * Test-only hook \u2014 seeds the registry directly from a fixture array without going through\n * the dynamic generated-file import. Used by the registry's own unit tests.\n */\nexport function seedAgentRegistryForTests(agents: unknown[]): void {\n agentsById.clear()\n populateFromAgents(agents)\n loaded = true\n}\n\n/**\n * @__internal\n * Test-only hook \u2014 apply override entries against the seeded registry to\n * exercise the override pipeline without round-tripping through the\n * generated file.\n */\nexport function applyAgentOverrideEntriesForTests(\n entries: readonly AiAgentOverrideConfigEntry[],\n): void {\n applyOverridesToRegistry(entries)\n}\n\n/**\n * @__internal Test-only hook \u2014 apply additive extension entries against the\n * seeded registry without round-tripping through the generated file.\n */\nexport function applyAgentExtensionEntriesForTests(\n entries: readonly AiAgentExtension[],\n): void {\n applyExtensionsToRegistry(entries)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["key"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
function findGeneratedFile(fileName) {
|
|
5
|
+
const here = (() => {
|
|
6
|
+
try {
|
|
7
|
+
return fileURLToPath(import.meta.url);
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
})();
|
|
12
|
+
if (here) {
|
|
13
|
+
let cursor = path.dirname(here);
|
|
14
|
+
for (let i = 0; i < 12; i++) {
|
|
15
|
+
const candidate = path.join(cursor, "apps", "mercato", ".mercato", "generated", fileName);
|
|
16
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
17
|
+
const next = path.dirname(cursor);
|
|
18
|
+
if (next === cursor) break;
|
|
19
|
+
cursor = next;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const fromCwd = path.resolve(process.cwd(), "apps", "mercato", ".mercato", "generated", fileName);
|
|
23
|
+
if (fs.existsSync(fromCwd)) return fromCwd;
|
|
24
|
+
const fromCwdDirect = path.resolve(process.cwd(), ".mercato", "generated", fileName);
|
|
25
|
+
if (fs.existsSync(fromCwdDirect)) return fromCwdDirect;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
async function compileAndImportGenerated(tsPath) {
|
|
29
|
+
const jsPath = tsPath.replace(/\.ts$/, ".mjs");
|
|
30
|
+
const appRoot = path.dirname(path.dirname(path.dirname(tsPath)));
|
|
31
|
+
if (!fs.existsSync(tsPath)) {
|
|
32
|
+
throw new Error(`Generated file not found: ${tsPath}`);
|
|
33
|
+
}
|
|
34
|
+
const jsExists = fs.existsSync(jsPath);
|
|
35
|
+
const needsCompile = !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs;
|
|
36
|
+
if (needsCompile) {
|
|
37
|
+
const esbuild = await import("esbuild");
|
|
38
|
+
const tsSource = fs.readFileSync(tsPath, "utf-8");
|
|
39
|
+
const aliasRewritten = rewriteGeneratedAliasImports(tsSource, appRoot);
|
|
40
|
+
const result = await esbuild.transform(aliasRewritten, {
|
|
41
|
+
loader: "ts",
|
|
42
|
+
format: "esm",
|
|
43
|
+
target: "node18",
|
|
44
|
+
sourcemap: false,
|
|
45
|
+
sourcefile: tsPath
|
|
46
|
+
});
|
|
47
|
+
fs.writeFileSync(jsPath, result.code);
|
|
48
|
+
}
|
|
49
|
+
return await import(pathToFileURL(jsPath).href);
|
|
50
|
+
}
|
|
51
|
+
function rewriteGeneratedAliasImports(source, appRoot) {
|
|
52
|
+
const resolveAlias = (relativePath) => {
|
|
53
|
+
const target = path.join(appRoot, relativePath);
|
|
54
|
+
const candidate = fs.existsSync(target) ? target : fs.existsSync(target + ".ts") ? target + ".ts" : target;
|
|
55
|
+
return pathToFileURL(candidate).href;
|
|
56
|
+
};
|
|
57
|
+
return source.replace(/from\s+["']@\/([^"']+)["']/g, (_match, relativePath) => {
|
|
58
|
+
return `from ${JSON.stringify(resolveAlias(relativePath))}`;
|
|
59
|
+
}).replace(/import\s*\(\s*["']@\/([^"']+)["']\s*\)/g, (_match, relativePath) => {
|
|
60
|
+
return `import(${JSON.stringify(resolveAlias(relativePath))})`;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async function ensureApiRouteManifestsRegistered() {
|
|
64
|
+
const registry = await import("@open-mercato/shared/modules/registry");
|
|
65
|
+
const existing = registry.getApiRouteManifests();
|
|
66
|
+
if (existing.length > 0) return existing.length;
|
|
67
|
+
const tsPath = findGeneratedFile("api-routes.generated.ts");
|
|
68
|
+
if (!tsPath) return 0;
|
|
69
|
+
try {
|
|
70
|
+
const mod = await compileAndImportGenerated(tsPath);
|
|
71
|
+
const apiRoutes = mod.apiRoutes;
|
|
72
|
+
if (!Array.isArray(apiRoutes)) return 0;
|
|
73
|
+
registry.registerApiRouteManifests(
|
|
74
|
+
apiRoutes
|
|
75
|
+
);
|
|
76
|
+
return apiRoutes.length;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.warn(
|
|
79
|
+
"[MCP Tools] Could not register api-routes manifest:",
|
|
80
|
+
error instanceof Error ? error.message : error
|
|
81
|
+
);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
compileAndImportGenerated,
|
|
87
|
+
ensureApiRouteManifestsRegistered,
|
|
88
|
+
findGeneratedFile,
|
|
89
|
+
rewriteGeneratedAliasImports
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=generated-registry-loader.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ai_assistant/lib/generated-registry-loader.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Runtime loader for `.mercato/generated/*.generated.ts` registry files.\n *\n * The generated registries import their entries through the `@/` path alias\n * (e.g. `@/.mercato/generated/ai-tools.generated`). That alias is only\n * understood by the Next.js bundler \u2014 in a standalone Node process (the\n * `mcp:dev` / `mcp:serve` MCP servers, the CLI tool-test runner) a raw\n * `import('@/.mercato/...')` throws `ERR_MODULE_NOT_FOUND: Cannot find\n * package '@/.mercato'` because Node treats `@/` as a package specifier.\n *\n * These helpers locate the generated `.ts` file on disk and compile-and-import\n * it with esbuild (transpile-only), rewriting `@/` aliases to absolute paths.\n * This mirrors `loadBootstrapData` in\n * `@open-mercato/shared/lib/bootstrap/dynamicLoader` and works in both the\n * monorepo and standalone apps.\n */\nimport path from 'node:path'\nimport fs from 'node:fs'\nimport { fileURLToPath, pathToFileURL } from 'node:url'\n\n/**\n * Locate a generated registry file (e.g. `ai-tools.generated.ts`) without\n * hardcoding the workspace layout. Searches upward from this module's compiled\n * location for a `apps/mercato/.mercato/generated/<fileName>` (monorepo), then\n * falls back to cwd-relative lookups (standalone apps run from the app dir).\n */\nexport function findGeneratedFile(fileName: string): string | null {\n const here = (() => {\n try {\n return fileURLToPath(import.meta.url)\n } catch {\n return null\n }\n })()\n\n if (here) {\n let cursor = path.dirname(here)\n for (let i = 0; i < 12; i++) {\n const candidate = path.join(cursor, 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(candidate)) return candidate\n const next = path.dirname(cursor)\n if (next === cursor) break\n cursor = next\n }\n }\n // Fallbacks: cwd-based lookup (CLI invoked from apps/mercato, or a standalone\n // app whose root holds `.mercato/generated`).\n const fromCwd = path.resolve(process.cwd(), 'apps', 'mercato', '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwd)) return fromCwd\n const fromCwdDirect = path.resolve(process.cwd(), '.mercato', 'generated', fileName)\n if (fs.existsSync(fromCwdDirect)) return fromCwdDirect\n return null\n}\n\n/**\n * Compile-and-import a generated registry file on the fly. Resolves the `@/`\n * alias to the app root, transpiles TS \u2192 ESM, and emits a sibling `.mjs` we can\n * `import()` from Node. Cached on mtime so repeat calls in the same process\n * don't recompile.\n *\n * Transpile-only (no bundling): the generated registries declare an array\n * literal whose entries are static `import(\"\u2026\")` arrow functions \u2014 we want\n * those `import()` strings to stay as runtime imports so Node resolves them\n * lazily through the workspace's normal module resolution. Eagerly bundling\n * pulls Next.js / route-handler internals into the `.mjs` and breaks at runtime\n * (e.g. `next/server` package-exports map).\n */\nexport async function compileAndImportGenerated(tsPath: string): Promise<Record<string, unknown>> {\n const jsPath = tsPath.replace(/\\.ts$/, '.mjs')\n // appRoot is two directories up from `.mercato/generated/<file>.ts`.\n const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))\n\n if (!fs.existsSync(tsPath)) {\n throw new Error(`Generated file not found: ${tsPath}`)\n }\n\n const jsExists = fs.existsSync(jsPath)\n const needsCompile =\n !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs\n\n if (needsCompile) {\n const esbuild = await import('esbuild')\n const tsSource = fs.readFileSync(tsPath, 'utf-8')\n const aliasRewritten = rewriteGeneratedAliasImports(tsSource, appRoot)\n const result = await esbuild.transform(aliasRewritten, {\n loader: 'ts',\n format: 'esm',\n target: 'node18',\n sourcemap: false,\n sourcefile: tsPath,\n })\n fs.writeFileSync(jsPath, result.code)\n }\n\n return (await import(pathToFileURL(jsPath).href)) as Record<string, unknown>\n}\n\n/**\n * Rewrite `@/...` path-alias imports (both `from \"@/x\"` and dynamic\n * `import(\"@/x\")`) in generated-registry source to absolute `file://` URLs\n * rooted at `appRoot`. The `@/` alias is a Next.js bundler convention; outside\n * the bundler Node treats `@/...` as a bare package specifier and throws\n * `ERR_MODULE_NOT_FOUND`. Exported for unit testing.\n */\nexport function rewriteGeneratedAliasImports(source: string, appRoot: string): string {\n const resolveAlias = (relativePath: string): string => {\n const target = path.join(appRoot, relativePath)\n const candidate = fs.existsSync(target)\n ? target\n : fs.existsSync(target + '.ts')\n ? target + '.ts'\n : target\n return pathToFileURL(candidate).href\n }\n return source\n .replace(/from\\s+[\"']@\\/([^\"']+)[\"']/g, (_match, relativePath: string) => {\n return `from ${JSON.stringify(resolveAlias(relativePath))}`\n })\n .replace(/import\\s*\\(\\s*[\"']@\\/([^\"']+)[\"']\\s*\\)/g, (_match, relativePath: string) => {\n return `import(${JSON.stringify(resolveAlias(relativePath))})`\n })\n}\n\n/**\n * Compile-and-import `api-routes.generated.ts` and register its manifest with\n * the shared registry. Many module tools are \"API-backed\" \u2014 their handlers\n * delegate to `createAiApiOperationRunner`, which fails closed with\n * \"No API route manifest registered\" unless the manifest is present. In the\n * Next.js app this is wired at bootstrap, but the standalone MCP servers\n * (`mcp:dev` / `mcp:serve`) bootstrap DI without it, so we register it here.\n *\n * Idempotent: `registerApiRouteManifests` replaces the stored manifest, so\n * calling this repeatedly (e.g. per-request HTTP handlers) is safe. Returns the\n * number of registered routes (0 when the generated file is absent).\n */\nexport async function ensureApiRouteManifestsRegistered(): Promise<number> {\n const registry = await import('@open-mercato/shared/modules/registry')\n // Already wired (e.g. the Next.js app bootstrap, or a prior call). Leave the\n // existing manifest untouched so we never interfere with the in-app agents\n // framework, which registers it at bootstrap with its own override pipeline.\n const existing = registry.getApiRouteManifests()\n if (existing.length > 0) return existing.length\n\n const tsPath = findGeneratedFile('api-routes.generated.ts')\n if (!tsPath) return 0\n try {\n const mod = await compileAndImportGenerated(tsPath)\n const apiRoutes = (mod as { apiRoutes?: unknown }).apiRoutes\n if (!Array.isArray(apiRoutes)) return 0\n registry.registerApiRouteManifests(\n apiRoutes as Parameters<typeof registry.registerApiRouteManifests>[0],\n )\n return apiRoutes.length\n } catch (error) {\n console.warn(\n '[MCP Tools] Could not register api-routes manifest:',\n error instanceof Error ? error.message : error,\n )\n return 0\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
composeToolOverrideMap
|
|
6
6
|
} from "./ai-overrides.js";
|
|
7
7
|
import { ToolSearchService } from "./tool-search.js";
|
|
8
|
+
import {
|
|
9
|
+
findGeneratedFile,
|
|
10
|
+
compileAndImportGenerated,
|
|
11
|
+
ensureApiRouteManifestsRegistered
|
|
12
|
+
} from "./generated-registry-loader.js";
|
|
8
13
|
function isModuleAiTool(value) {
|
|
9
14
|
if (!value || typeof value !== "object") return false;
|
|
10
15
|
const candidate = value;
|
|
@@ -67,18 +72,30 @@ function applyAiToolOverrideEntries(entries) {
|
|
|
67
72
|
console.info(`[AI Overrides] Tool "${name}" replaced by override.`);
|
|
68
73
|
}
|
|
69
74
|
}
|
|
75
|
+
async function importGeneratedAiToolsModule() {
|
|
76
|
+
try {
|
|
77
|
+
return await import("@/.mercato/generated/ai-tools.generated");
|
|
78
|
+
} catch {
|
|
79
|
+
const tsPath = findGeneratedFile("ai-tools.generated.ts");
|
|
80
|
+
if (!tsPath) return null;
|
|
81
|
+
return compileAndImportGenerated(tsPath);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
70
84
|
async function loadGeneratedAiToolOverrides() {
|
|
71
85
|
let entries = [];
|
|
72
86
|
try {
|
|
73
|
-
const mod = await
|
|
74
|
-
entries = Array.isArray(mod.aiToolOverrideEntries) ? mod.aiToolOverrideEntries : [];
|
|
87
|
+
const mod = await importGeneratedAiToolsModule();
|
|
88
|
+
entries = mod && Array.isArray(mod.aiToolOverrideEntries) ? mod.aiToolOverrideEntries : [];
|
|
75
89
|
} catch {
|
|
76
90
|
}
|
|
77
91
|
applyAiToolOverrideEntries(entries);
|
|
78
92
|
}
|
|
79
93
|
async function loadGeneratedModuleAiTools() {
|
|
80
94
|
try {
|
|
81
|
-
const mod = await
|
|
95
|
+
const mod = await importGeneratedAiToolsModule();
|
|
96
|
+
if (!mod) {
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
82
99
|
const entries = Array.isArray(mod.aiToolConfigEntries) ? mod.aiToolConfigEntries : [];
|
|
83
100
|
const count = registerGeneratedAiToolEntries(entries);
|
|
84
101
|
try {
|
|
@@ -113,6 +130,16 @@ async function loadAllModuleTools() {
|
|
|
113
130
|
} catch (error) {
|
|
114
131
|
console.error("[MCP Tools] Could not load module AI tools:", error);
|
|
115
132
|
}
|
|
133
|
+
try {
|
|
134
|
+
const routeCount = await ensureApiRouteManifestsRegistered();
|
|
135
|
+
if (routeCount > 0) {
|
|
136
|
+
console.error(
|
|
137
|
+
`[MCP Tools] Registered ${routeCount} API route manifests for API-backed tools`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error("[MCP Tools] Could not register API route manifests:", error);
|
|
142
|
+
}
|
|
116
143
|
}
|
|
117
144
|
async function indexToolsForSearch(searchService, force = false) {
|
|
118
145
|
const registry = getToolRegistry();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/tool-loader.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport type { SearchService } from '@open-mercato/search/service'\nimport { registerMcpTool, getToolRegistry, toolRegistry, unregisterMcpTool } from './tool-registry'\nimport {\n applyToolOverrideMap,\n composeToolOverrideMap,\n type AiToolOverrideConfigEntry,\n} from './ai-overrides'\nimport type { McpToolDefinition, McpToolContext } from './types'\nimport { ToolSearchService } from './tool-search'\n\n/**\n * Module tool definition as exported from ai-tools.ts files.\n */\ntype ModuleAiTool = {\n name: string\n description: string\n inputSchema: any\n requiredFeatures?: string[]\n handler: (input: any, ctx: any) => Promise<unknown>\n}\n\n/**\n * Shape of a single entry inside `ai-tools.generated.ts`.\n * Matches the structural contract emitted by\n * `packages/cli/src/lib/generators/extensions/ai-tools.ts`.\n */\nexport type AiToolConfigEntry = {\n moduleId: string\n tools: unknown[]\n}\n\nfunction isModuleAiTool(value: unknown): value is ModuleAiTool {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.name === 'string' &&\n typeof candidate.description === 'string' &&\n candidate.inputSchema !== undefined &&\n typeof candidate.handler === 'function'\n )\n}\n\n/**\n * Built-in context.whoami tool that returns the current authentication context.\n * This is useful for AI to understand its current tenant/org scope.\n */\nconst contextWhoamiTool: McpToolDefinition = {\n name: 'context_whoami',\n description:\n 'Get the current authentication context including tenant ID, organization ID, user ID, and available features. Use this to understand your current scope before performing operations.',\n inputSchema: z.object({}),\n requiredFeatures: [], // No specific feature required - available to all authenticated users\n handler: async (_input: unknown, ctx: McpToolContext) => {\n return {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n isSuperAdmin: ctx.isSuperAdmin,\n features: ctx.userFeatures,\n featureCount: ctx.userFeatures.length,\n }\n },\n}\n\n/**\n * Load and register AI tools from a module's ai-tools.ts export.\n *\n * @param moduleId - The module identifier (e.g., 'search', 'customers')\n * @param tools - Array of tool definitions from the module\n *\n * IMPORTANT: We register the full typed tool (spread) \u2014 never reconstruct a\n * minimal `{ name, description, inputSchema, requiredFeatures, handler }`\n * payload. Stripping fields like `isMutation`, `displayName`, `isBulk`,\n * `loadBeforeRecord(s)` makes the agent settings UI report mutation tools\n * as read-only and prevents the chat dispatcher's mutation interceptor\n * from firing.\n */\nexport function loadModuleTools(moduleId: string, tools: ModuleAiTool[]): void {\n for (const tool of tools) {\n registerMcpTool(tool as McpToolDefinition, { moduleId })\n }\n}\n\n/**\n * Register the generated `aiToolConfigEntries` shape emitted by the\n * `ai-tools.generated.ts` generator extension. Entries with an empty\n * `tools` array stay silent (the generator already filters those out).\n * Invalid tool objects are skipped with a warning instead of throwing.\n *\n * Returns the number of tools actually registered so callers can log it.\n */\nexport function registerGeneratedAiToolEntries(entries: AiToolConfigEntry[]): number {\n let registered = 0\n for (const entry of entries) {\n if (!entry || typeof entry.moduleId !== 'string') continue\n if (!Array.isArray(entry.tools) || entry.tools.length === 0) continue\n for (const candidate of entry.tools) {\n if (!isModuleAiTool(candidate)) {\n console.warn(\n `[MCP Tools] Skipping malformed AI tool in module \"${entry.moduleId}\"`\n )\n continue\n }\n // Register the full typed tool \u2014 see comment on `loadModuleTools`.\n registerMcpTool(candidate as McpToolDefinition, { moduleId: entry.moduleId })\n registered += 1\n }\n }\n return registered\n}\n\n/**\n * Apply the list of `aiToolOverrides` exports collected by the generator\n * (one entry per module) to the live tool registry. Tools mapped to\n * `null` are unregistered; tools mapped to a full definition replace the\n * existing registration. Module load order controls precedence \u2014 last\n * entry wins. The `modules.ts`-tier and programmatic overrides (set via\n * {@link applyAiOverridesFromEnabledModules} and\n * {@link applyAiToolOverrides}) supersede file-based entries.\n *\n * Safe to call when no override file is present (the entries array is\n * empty); it is a no-op then.\n */\nexport function applyAiToolOverrideEntries(\n entries: readonly AiToolOverrideConfigEntry[],\n): void {\n const overrideMap = composeToolOverrideMap(entries)\n if (Object.keys(overrideMap).length === 0) return\n const baseTools = toolRegistry.getTools() as Map<string, McpToolDefinition>\n const overridden = applyToolOverrideMap<McpToolDefinition>(baseTools, overrideMap)\n for (const [name, value] of Object.entries(overrideMap)) {\n if (value === null) {\n unregisterMcpTool(name)\n console.info(`[AI Overrides] Tool \"${name}\" disabled by override.`)\n continue\n }\n const next = overridden.get(name)\n if (!next) continue\n // Re-register through the public path so moduleMap stays consistent.\n registerMcpTool(next as McpToolDefinition, { moduleId: 'ai_overrides' })\n console.info(`[AI Overrides] Tool \"${name}\" replaced by override.`)\n }\n}\n\n/**\n * Read `aiToolOverrideEntries` from `ai-tools.generated.ts` and apply\n * them on top of the live tool registry. Safe to call when the file is\n * missing (pre-generate builds, tests) \u2014 applies only the\n * `modules.ts`-tier and programmatic overrides in that case.\n */\nexport async function loadGeneratedAiToolOverrides(): Promise<void> {\n let entries: AiToolOverrideConfigEntry[] = []\n try {\n const mod = (await import(\n '@/.mercato/generated/ai-tools.generated'\n )) as { aiToolOverrideEntries?: unknown[] }\n entries = Array.isArray(mod.aiToolOverrideEntries)\n ? (mod.aiToolOverrideEntries as AiToolOverrideConfigEntry[])\n : []\n } catch {\n // No override file generated.\n }\n applyAiToolOverrideEntries(entries)\n}\n\n/**\n * Load the generated `ai-tools.generated.ts` file emitted by\n * `yarn generate` and register every declared module tool through the\n * existing `registerMcpTool` path. Safe to call when the generated file\n * is missing (e.g., tests or pre-generate builds) \u2014 returns 0.\n */\nexport async function loadGeneratedModuleAiTools(): Promise<number> {\n try {\n const mod = (await import(\n '@/.mercato/generated/ai-tools.generated'\n )) as { aiToolConfigEntries?: AiToolConfigEntry[] }\n const entries = Array.isArray(mod.aiToolConfigEntries)\n ? mod.aiToolConfigEntries\n : []\n const count = registerGeneratedAiToolEntries(entries)\n // Apply module-to-module + programmatic tool overrides AFTER the base\n // registrations so disable / replace semantics work end-to-end.\n try {\n await loadGeneratedAiToolOverrides()\n } catch (error) {\n console.error('[AI Overrides] Failed to apply tool overrides:', error)\n }\n return count\n } catch (error) {\n console.error(\n '[MCP Tools] Could not load ai-tools.generated.ts (module tools unavailable):',\n error\n )\n return 0\n }\n}\n\n/**\n * Dynamically load tools from known module paths.\n * This is called during MCP server startup.\n */\nexport async function loadAllModuleTools(): Promise<void> {\n // 1. Register built-in tools\n registerMcpTool(contextWhoamiTool, { moduleId: 'context' })\n console.error('[MCP Tools] Registered built-in context_whoami tool')\n\n // 2. Register Code Mode tools (search + execute)\n // These two tools replace the previous api_discover, call_api, discover_schema,\n // and all module-specific AI tools. The AI writes JavaScript that runs in a\n // node:vm sandbox with access to the OpenAPI spec and api.request().\n try {\n const { loadCodeModeTools } = await import('./codemode-tools')\n const toolCount = await loadCodeModeTools()\n console.error(`[MCP Tools] Registered ${toolCount} Code Mode tools`)\n } catch (error) {\n console.error('[MCP Tools] Could not load Code Mode tools:', error)\n }\n\n // 3. Register module-contributed tools from ai-tools.generated.ts.\n // Code Mode stays untouched; module tools are additive. Missing\n // generated file is not fatal (pre-generate builds, tests).\n try {\n const moduleToolCount = await loadGeneratedModuleAiTools()\n console.error(\n `[MCP Tools] Registered ${moduleToolCount} module-contributed AI tools from ai-tools.generated.ts`\n )\n } catch (error) {\n console.error('[MCP Tools] Could not load module AI tools:', error)\n }\n}\n\n/**\n * Index all registered tools for hybrid search discovery.\n * This should be called after loadAllModuleTools() when the search service is available.\n *\n * @param searchService - The search service from DI container\n * @param force - Force re-indexing even if checksums match\n * @returns Indexing result with statistics\n */\nexport async function indexToolsForSearch(\n searchService: SearchService,\n force = false\n): Promise<{\n indexed: number\n skipped: number\n strategies: string[]\n checksum: string\n}> {\n const registry = getToolRegistry()\n const toolSearchService = new ToolSearchService(searchService, registry)\n\n try {\n const result = await toolSearchService.indexTools(force)\n\n console.error(`[MCP Tools] Indexed ${result.indexed} tools for search`)\n console.error(`[MCP Tools] Search strategies available: ${result.strategies.join(', ')}`)\n\n if (result.skipped > 0) {\n console.error(`[MCP Tools] Skipped ${result.skipped} tools (unchanged)`)\n }\n\n return result\n } catch (error) {\n console.error('[MCP Tools] Failed to index tools for search:', error)\n throw error\n }\n}\n\n/**\n * Create a ToolSearchService instance for tool discovery.\n * Use this to get a configured service for discovering relevant tools.\n *\n * @param searchService - The search service from DI container\n * @returns Configured ToolSearchService\n */\nexport function createToolSearchService(searchService: SearchService): ToolSearchService {\n const registry = getToolRegistry()\n return new ToolSearchService(searchService, registry)\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,iBAAiB,iBAAiB,cAAc,yBAAyB;AAClF;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAEP,SAAS,yBAAyB;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { SearchService } from '@open-mercato/search/service'\nimport { registerMcpTool, getToolRegistry, toolRegistry, unregisterMcpTool } from './tool-registry'\nimport {\n applyToolOverrideMap,\n composeToolOverrideMap,\n type AiToolOverrideConfigEntry,\n} from './ai-overrides'\nimport type { McpToolDefinition, McpToolContext } from './types'\nimport { ToolSearchService } from './tool-search'\nimport {\n findGeneratedFile,\n compileAndImportGenerated,\n ensureApiRouteManifestsRegistered,\n} from './generated-registry-loader'\n\n/**\n * Module tool definition as exported from ai-tools.ts files.\n */\ntype ModuleAiTool = {\n name: string\n description: string\n inputSchema: any\n requiredFeatures?: string[]\n handler: (input: any, ctx: any) => Promise<unknown>\n}\n\n/**\n * Shape of a single entry inside `ai-tools.generated.ts`.\n * Matches the structural contract emitted by\n * `packages/cli/src/lib/generators/extensions/ai-tools.ts`.\n */\nexport type AiToolConfigEntry = {\n moduleId: string\n tools: unknown[]\n}\n\nfunction isModuleAiTool(value: unknown): value is ModuleAiTool {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return (\n typeof candidate.name === 'string' &&\n typeof candidate.description === 'string' &&\n candidate.inputSchema !== undefined &&\n typeof candidate.handler === 'function'\n )\n}\n\n/**\n * Built-in context.whoami tool that returns the current authentication context.\n * This is useful for AI to understand its current tenant/org scope.\n */\nconst contextWhoamiTool: McpToolDefinition = {\n name: 'context_whoami',\n description:\n 'Get the current authentication context including tenant ID, organization ID, user ID, and available features. Use this to understand your current scope before performing operations.',\n inputSchema: z.object({}),\n requiredFeatures: [], // No specific feature required - available to all authenticated users\n handler: async (_input: unknown, ctx: McpToolContext) => {\n return {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n isSuperAdmin: ctx.isSuperAdmin,\n features: ctx.userFeatures,\n featureCount: ctx.userFeatures.length,\n }\n },\n}\n\n/**\n * Load and register AI tools from a module's ai-tools.ts export.\n *\n * @param moduleId - The module identifier (e.g., 'search', 'customers')\n * @param tools - Array of tool definitions from the module\n *\n * IMPORTANT: We register the full typed tool (spread) \u2014 never reconstruct a\n * minimal `{ name, description, inputSchema, requiredFeatures, handler }`\n * payload. Stripping fields like `isMutation`, `displayName`, `isBulk`,\n * `loadBeforeRecord(s)` makes the agent settings UI report mutation tools\n * as read-only and prevents the chat dispatcher's mutation interceptor\n * from firing.\n */\nexport function loadModuleTools(moduleId: string, tools: ModuleAiTool[]): void {\n for (const tool of tools) {\n registerMcpTool(tool as McpToolDefinition, { moduleId })\n }\n}\n\n/**\n * Register the generated `aiToolConfigEntries` shape emitted by the\n * `ai-tools.generated.ts` generator extension. Entries with an empty\n * `tools` array stay silent (the generator already filters those out).\n * Invalid tool objects are skipped with a warning instead of throwing.\n *\n * Returns the number of tools actually registered so callers can log it.\n */\nexport function registerGeneratedAiToolEntries(entries: AiToolConfigEntry[]): number {\n let registered = 0\n for (const entry of entries) {\n if (!entry || typeof entry.moduleId !== 'string') continue\n if (!Array.isArray(entry.tools) || entry.tools.length === 0) continue\n for (const candidate of entry.tools) {\n if (!isModuleAiTool(candidate)) {\n console.warn(\n `[MCP Tools] Skipping malformed AI tool in module \"${entry.moduleId}\"`\n )\n continue\n }\n // Register the full typed tool \u2014 see comment on `loadModuleTools`.\n registerMcpTool(candidate as McpToolDefinition, { moduleId: entry.moduleId })\n registered += 1\n }\n }\n return registered\n}\n\n/**\n * Apply the list of `aiToolOverrides` exports collected by the generator\n * (one entry per module) to the live tool registry. Tools mapped to\n * `null` are unregistered; tools mapped to a full definition replace the\n * existing registration. Module load order controls precedence \u2014 last\n * entry wins. The `modules.ts`-tier and programmatic overrides (set via\n * {@link applyAiOverridesFromEnabledModules} and\n * {@link applyAiToolOverrides}) supersede file-based entries.\n *\n * Safe to call when no override file is present (the entries array is\n * empty); it is a no-op then.\n */\nexport function applyAiToolOverrideEntries(\n entries: readonly AiToolOverrideConfigEntry[],\n): void {\n const overrideMap = composeToolOverrideMap(entries)\n if (Object.keys(overrideMap).length === 0) return\n const baseTools = toolRegistry.getTools() as Map<string, McpToolDefinition>\n const overridden = applyToolOverrideMap<McpToolDefinition>(baseTools, overrideMap)\n for (const [name, value] of Object.entries(overrideMap)) {\n if (value === null) {\n unregisterMcpTool(name)\n console.info(`[AI Overrides] Tool \"${name}\" disabled by override.`)\n continue\n }\n const next = overridden.get(name)\n if (!next) continue\n // Re-register through the public path so moduleMap stays consistent.\n registerMcpTool(next as McpToolDefinition, { moduleId: 'ai_overrides' })\n console.info(`[AI Overrides] Tool \"${name}\" replaced by override.`)\n }\n}\n\n/**\n * Import the generated `ai-tools.generated.ts` registry.\n *\n * Dual-strategy so the same code works in every runtime without changing the\n * existing Next.js / agents-framework behavior:\n * 1. Prefer the `@/` path-alias import. Inside the Next.js bundler this\n * resolves at build time exactly as before \u2014 the in-app agents framework\n * path is untouched.\n * 2. Fall back to locating + compiling the file from disk when the alias\n * import throws. That only happens in a standalone Node process (the\n * `mcp:dev` / `mcp:serve` MCP servers), where `@/` is not a real package\n * specifier and Node throws `ERR_MODULE_NOT_FOUND`.\n *\n * Returns `null` when no generated file exists (pre-generate builds, tests).\n */\nasync function importGeneratedAiToolsModule(): Promise<Record<string, unknown> | null> {\n try {\n return (await import(\n '@/.mercato/generated/ai-tools.generated'\n )) as Record<string, unknown>\n } catch {\n const tsPath = findGeneratedFile('ai-tools.generated.ts')\n if (!tsPath) return null\n return compileAndImportGenerated(tsPath)\n }\n}\n\n/**\n * Read `aiToolOverrideEntries` from `ai-tools.generated.ts` and apply\n * them on top of the live tool registry. Safe to call when the file is\n * missing (pre-generate builds, tests) \u2014 applies only the\n * `modules.ts`-tier and programmatic overrides in that case.\n */\nexport async function loadGeneratedAiToolOverrides(): Promise<void> {\n let entries: AiToolOverrideConfigEntry[] = []\n try {\n const mod = (await importGeneratedAiToolsModule()) as {\n aiToolOverrideEntries?: unknown[]\n } | null\n entries =\n mod && Array.isArray(mod.aiToolOverrideEntries)\n ? (mod.aiToolOverrideEntries as AiToolOverrideConfigEntry[])\n : []\n } catch {\n // No override file generated.\n }\n applyAiToolOverrideEntries(entries)\n}\n\n/**\n * Load the generated `ai-tools.generated.ts` file emitted by\n * `yarn generate` and register every declared module tool through the\n * existing `registerMcpTool` path. Safe to call when the generated file\n * is missing (e.g., tests or pre-generate builds) \u2014 returns 0.\n */\nexport async function loadGeneratedModuleAiTools(): Promise<number> {\n try {\n const mod = (await importGeneratedAiToolsModule()) as {\n aiToolConfigEntries?: AiToolConfigEntry[]\n } | null\n if (!mod) {\n // No generated file (pre-generate build or tests).\n return 0\n }\n const entries = Array.isArray(mod.aiToolConfigEntries)\n ? mod.aiToolConfigEntries\n : []\n const count = registerGeneratedAiToolEntries(entries)\n // Apply module-to-module + programmatic tool overrides AFTER the base\n // registrations so disable / replace semantics work end-to-end.\n try {\n await loadGeneratedAiToolOverrides()\n } catch (error) {\n console.error('[AI Overrides] Failed to apply tool overrides:', error)\n }\n return count\n } catch (error) {\n console.error(\n '[MCP Tools] Could not load ai-tools.generated.ts (module tools unavailable):',\n error\n )\n return 0\n }\n}\n\n/**\n * Dynamically load tools from known module paths.\n * This is called during MCP server startup.\n */\nexport async function loadAllModuleTools(): Promise<void> {\n // 1. Register built-in tools\n registerMcpTool(contextWhoamiTool, { moduleId: 'context' })\n console.error('[MCP Tools] Registered built-in context_whoami tool')\n\n // 2. Register Code Mode tools (search + execute)\n // These two tools replace the previous api_discover, call_api, discover_schema,\n // and all module-specific AI tools. The AI writes JavaScript that runs in a\n // node:vm sandbox with access to the OpenAPI spec and api.request().\n try {\n const { loadCodeModeTools } = await import('./codemode-tools')\n const toolCount = await loadCodeModeTools()\n console.error(`[MCP Tools] Registered ${toolCount} Code Mode tools`)\n } catch (error) {\n console.error('[MCP Tools] Could not load Code Mode tools:', error)\n }\n\n // 3. Register module-contributed tools from ai-tools.generated.ts.\n // Code Mode stays untouched; module tools are additive. Missing\n // generated file is not fatal (pre-generate builds, tests).\n try {\n const moduleToolCount = await loadGeneratedModuleAiTools()\n console.error(\n `[MCP Tools] Registered ${moduleToolCount} module-contributed AI tools from ai-tools.generated.ts`\n )\n } catch (error) {\n console.error('[MCP Tools] Could not load module AI tools:', error)\n }\n\n // 4. Register the API route manifest so API-backed module tools can run.\n // Their handlers delegate to createAiApiOperationRunner, which fails closed\n // with \"No API route manifest registered\" outside the Next.js bootstrap.\n try {\n const routeCount = await ensureApiRouteManifestsRegistered()\n if (routeCount > 0) {\n console.error(\n `[MCP Tools] Registered ${routeCount} API route manifests for API-backed tools`\n )\n }\n } catch (error) {\n console.error('[MCP Tools] Could not register API route manifests:', error)\n }\n}\n\n/**\n * Index all registered tools for hybrid search discovery.\n * This should be called after loadAllModuleTools() when the search service is available.\n *\n * @param searchService - The search service from DI container\n * @param force - Force re-indexing even if checksums match\n * @returns Indexing result with statistics\n */\nexport async function indexToolsForSearch(\n searchService: SearchService,\n force = false\n): Promise<{\n indexed: number\n skipped: number\n strategies: string[]\n checksum: string\n}> {\n const registry = getToolRegistry()\n const toolSearchService = new ToolSearchService(searchService, registry)\n\n try {\n const result = await toolSearchService.indexTools(force)\n\n console.error(`[MCP Tools] Indexed ${result.indexed} tools for search`)\n console.error(`[MCP Tools] Search strategies available: ${result.strategies.join(', ')}`)\n\n if (result.skipped > 0) {\n console.error(`[MCP Tools] Skipped ${result.skipped} tools (unchanged)`)\n }\n\n return result\n } catch (error) {\n console.error('[MCP Tools] Failed to index tools for search:', error)\n throw error\n }\n}\n\n/**\n * Create a ToolSearchService instance for tool discovery.\n * Use this to get a configured service for discovering relevant tools.\n *\n * @param searchService - The search service from DI container\n * @returns Configured ToolSearchService\n */\nexport function createToolSearchService(searchService: SearchService): ToolSearchService {\n const registry = getToolRegistry()\n return new ToolSearchService(searchService, registry)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,110 +1,25 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
1
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
2
|
import { getFixture } from "./tool-test-fixtures.js";
|
|
6
3
|
import { prepareMutation } from "./prepare-mutation.js";
|
|
7
4
|
import { executePendingActionConfirm } from "./pending-action-executor.js";
|
|
5
|
+
import {
|
|
6
|
+
findGeneratedFile,
|
|
7
|
+
compileAndImportGenerated,
|
|
8
|
+
ensureApiRouteManifestsRegistered
|
|
9
|
+
} from "./generated-registry-loader.js";
|
|
8
10
|
function isToolDefinition(value) {
|
|
9
11
|
if (!value || typeof value !== "object") return false;
|
|
10
12
|
const candidate = value;
|
|
11
13
|
return typeof candidate.name === "string" && typeof candidate.description === "string" && candidate.inputSchema !== void 0 && typeof candidate.handler === "function";
|
|
12
14
|
}
|
|
13
|
-
function findGeneratedAiToolsPath() {
|
|
14
|
-
const here = (() => {
|
|
15
|
-
try {
|
|
16
|
-
return fileURLToPath(import.meta.url);
|
|
17
|
-
} catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
})();
|
|
21
|
-
if (!here) return null;
|
|
22
|
-
let cursor = path.dirname(here);
|
|
23
|
-
for (let i = 0; i < 12; i++) {
|
|
24
|
-
const candidate = path.join(cursor, "apps", "mercato", ".mercato", "generated", "ai-tools.generated.ts");
|
|
25
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
26
|
-
const next = path.dirname(cursor);
|
|
27
|
-
if (next === cursor) break;
|
|
28
|
-
cursor = next;
|
|
29
|
-
}
|
|
30
|
-
const fromCwd = path.resolve(process.cwd(), "apps", "mercato", ".mercato", "generated", "ai-tools.generated.ts");
|
|
31
|
-
if (fs.existsSync(fromCwd)) return fromCwd;
|
|
32
|
-
const fromCwdDirect = path.resolve(process.cwd(), ".mercato", "generated", "ai-tools.generated.ts");
|
|
33
|
-
if (fs.existsSync(fromCwdDirect)) return fromCwdDirect;
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
async function compileAndImportGenerated(tsPath) {
|
|
37
|
-
const jsPath = tsPath.replace(/\.ts$/, ".mjs");
|
|
38
|
-
const appRoot = path.dirname(path.dirname(path.dirname(tsPath)));
|
|
39
|
-
const tsExists = fs.existsSync(tsPath);
|
|
40
|
-
if (!tsExists) {
|
|
41
|
-
throw new Error(`Generated file not found: ${tsPath}`);
|
|
42
|
-
}
|
|
43
|
-
const jsExists = fs.existsSync(jsPath);
|
|
44
|
-
const needsCompile = !jsExists || fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs;
|
|
45
|
-
if (needsCompile) {
|
|
46
|
-
const esbuild = await import("esbuild");
|
|
47
|
-
const tsSource = fs.readFileSync(tsPath, "utf-8");
|
|
48
|
-
const aliasRewritten = tsSource.replace(
|
|
49
|
-
/from\s+["']@\/([^"']+)["']/g,
|
|
50
|
-
(_match, p1) => {
|
|
51
|
-
const target = path.join(appRoot, p1);
|
|
52
|
-
const candidate = fs.existsSync(target) ? target : fs.existsSync(target + ".ts") ? target + ".ts" : target;
|
|
53
|
-
return `from ${JSON.stringify(pathToFileURL(candidate).href)}`;
|
|
54
|
-
}
|
|
55
|
-
).replace(
|
|
56
|
-
/import\s*\(\s*["']@\/([^"']+)["']\s*\)/g,
|
|
57
|
-
(_match, p1) => {
|
|
58
|
-
const target = path.join(appRoot, p1);
|
|
59
|
-
const candidate = fs.existsSync(target) ? target : fs.existsSync(target + ".ts") ? target + ".ts" : target;
|
|
60
|
-
return `import(${JSON.stringify(pathToFileURL(candidate).href)})`;
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
const result = await esbuild.transform(aliasRewritten, {
|
|
64
|
-
loader: "ts",
|
|
65
|
-
format: "esm",
|
|
66
|
-
target: "node18",
|
|
67
|
-
sourcemap: false,
|
|
68
|
-
sourcefile: tsPath
|
|
69
|
-
});
|
|
70
|
-
fs.writeFileSync(jsPath, result.code);
|
|
71
|
-
}
|
|
72
|
-
return await import(pathToFileURL(jsPath).href);
|
|
73
|
-
}
|
|
74
|
-
async function ensureApiRouteManifestsRegistered(generatedDir) {
|
|
75
|
-
const tsPath = path.join(generatedDir, "api-routes.generated.ts");
|
|
76
|
-
if (!fs.existsSync(tsPath)) return;
|
|
77
|
-
try {
|
|
78
|
-
const mod = await compileAndImportGenerated(tsPath);
|
|
79
|
-
const apiRoutes = mod.apiRoutes;
|
|
80
|
-
if (!Array.isArray(apiRoutes)) {
|
|
81
|
-
console.warn("[tool-test-runner] api-routes.generated.mjs returned no apiRoutes array");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const registry = await import("@open-mercato/shared/modules/registry");
|
|
85
|
-
registry.registerApiRouteManifests(
|
|
86
|
-
apiRoutes
|
|
87
|
-
);
|
|
88
|
-
if (process.env.OM_TOOL_TEST_DEBUG === "1") {
|
|
89
|
-
console.log(
|
|
90
|
-
`[tool-test-runner] Registered ${apiRoutes.length} api-route manifests; getApiRouteManifests().length=${registry.getApiRouteManifests().length}`
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.warn(
|
|
95
|
-
"[tool-test-runner] Could not register api-routes manifest:",
|
|
96
|
-
error instanceof Error ? error.message : error
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
15
|
async function loadGeneratedTools() {
|
|
101
|
-
const tsPath =
|
|
16
|
+
const tsPath = findGeneratedFile("ai-tools.generated.ts");
|
|
102
17
|
if (!tsPath) {
|
|
103
18
|
throw new Error(
|
|
104
19
|
"Could not locate apps/<app>/.mercato/generated/ai-tools.generated.ts. Run `yarn generate` first."
|
|
105
20
|
);
|
|
106
21
|
}
|
|
107
|
-
await ensureApiRouteManifestsRegistered(
|
|
22
|
+
await ensureApiRouteManifestsRegistered();
|
|
108
23
|
const mod = await compileAndImportGenerated(tsPath);
|
|
109
24
|
const entries = mod.aiToolConfigEntriesRaw ?? mod.aiToolConfigEntries ?? [];
|
|
110
25
|
const result = [];
|