@open-mercato/ai-assistant 0.6.6-develop.6339.1.193c6c7c71 → 0.6.6-develop.6344.1.c4b17c07c4

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.
@@ -152,11 +152,11 @@ Registered MCP Tools (${toolNames.length}):
152
152
  list.push(name);
153
153
  byModule.set(module, list);
154
154
  }
155
- const sortedModules = Array.from(byModule.keys()).sort();
155
+ const sortedModules = Array.from(byModule.keys()).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
156
156
  for (const module of sortedModules) {
157
157
  const tools = byModule.get(module);
158
158
  console.log(`${module} (${tools.length} tools):`);
159
- for (const name of tools.sort()) {
159
+ for (const name of tools.sort((a, b) => a < b ? -1 : a > b ? 1 : 0)) {
160
160
  const tool = registry.getTool(name);
161
161
  if (!tool) continue;
162
162
  if (verbose) {
@@ -256,7 +256,7 @@ const testTools = {
256
256
  list.push(record);
257
257
  byModule.set(record.module, list);
258
258
  }
259
- const sortedModules = Array.from(byModule.keys()).sort();
259
+ const sortedModules = Array.from(byModule.keys()).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
260
260
  for (const moduleId of sortedModules) {
261
261
  const list = byModule.get(moduleId);
262
262
  console.log("");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/ai_assistant/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\n/**\n * Ensure app bootstrap is called before creating DI container.\n * Uses the shared generated-bootstrap loader so the command works both from\n * the monorepo app and from standalone apps installed through npm packages.\n */\nasync function ensureBootstrap(): Promise<void> {\n // First check if DI is already available\n try {\n const { getDiRegistrars } = await import('@open-mercato/shared/lib/di/container')\n getDiRegistrars()\n return // DI already available\n } catch {\n // DI not available, need to bootstrap\n }\n\n try {\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n await bootstrapFromAppRoot()\n } catch (error) {\n console.error('[MCP] Bootstrap failed:', error instanceof Error ? error.message : error)\n // Continue - some contexts may not have bootstrap available\n }\n}\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n if (!arg?.startsWith('--')) continue\n\n const [key, value] = arg.replace(/^--/, '').split('=')\n if (value !== undefined) {\n args[key] = value\n } else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) {\n args[key] = rest[i + 1]!\n i++\n } else {\n args[key] = true\n }\n }\n return args\n}\n\nconst mcpServe: ModuleCli = {\n command: 'mcp:serve',\n async run(rest) {\n const args = parseArgs(rest)\n // Prefer the OPEN_MERCATO_API_KEY env var so the secret never has to be\n // placed on the command line (argv is world-readable via ps / /proc).\n // The --api-key flag stays supported for backward compatibility.\n const apiKey = String(args['api-key'] ?? args.apiKey ?? '') || process.env.OPEN_MERCATO_API_KEY || null\n const tenantId = String(args.tenant ?? args.tenantId ?? '') || null\n const organizationId = String(args.org ?? args.organizationId ?? '') || null\n const userId = String(args.user ?? args.userId ?? '') || null\n const debug = args.debug === true || args.debug === 'true'\n const allowUnauthenticatedSuperadmin =\n args['allow-unauthenticated-superadmin'] === true ||\n args['allow-unauthenticated-superadmin'] === 'true'\n\n // Either API key or tenant is required\n if (!apiKey && !tenantId) {\n console.error('Usage: mercato ai_assistant mcp:serve [options]')\n console.error('')\n console.error('Authentication (choose one):')\n console.error(' OPEN_MERCATO_API_KEY env API key secret (recommended \u2014 keeps the secret off argv)')\n console.error(' --api-key <secret> API key secret for authentication (visible in process listings)')\n console.error(' --tenant <id> Tenant ID (for manual context)')\n console.error('')\n console.error('Options (with --tenant):')\n console.error(' --org <id> Organization ID (optional)')\n console.error(' --user <id> User ID for ACL (required unless --allow-unauthenticated-superadmin is set)')\n console.error('')\n console.error('Common options:')\n console.error(' --debug Enable debug logging')\n console.error(' --allow-unauthenticated-superadmin')\n console.error(' DEV/TEST ONLY: run as superadmin with no per-user ACL when')\n console.error(' no --user (and no --api-key) is supplied. Never use in production.')\n console.error('')\n console.error('Examples:')\n console.error(' OPEN_MERCATO_API_KEY=omk_xxxx.yyyy... mercato ai_assistant mcp:serve')\n console.error(' mercato ai_assistant mcp:serve --api-key omk_xxxx.yyyy...')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --user <user-id>')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --allow-unauthenticated-superadmin')\n return\n }\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpServer } = await import('./lib/mcp-server')\n\n if (apiKey) {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n apiKeySecret: apiKey,\n })\n } else {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n context: {\n tenantId,\n organizationId,\n userId,\n },\n allowUnauthenticatedSuperadmin,\n })\n }\n },\n}\n\nconst MCP_DEFAULT_PORT = 3001\n\nconst mcpServeHttp: ModuleCli = {\n command: 'mcp:serve-http',\n async run(rest) {\n const args = parseArgs(rest)\n const portArg = parseInt(String(args.port ?? ''), 10)\n const port = !portArg || isNaN(portArg) ? MCP_DEFAULT_PORT : portArg\n const debug = args.debug === true || args.debug === 'true'\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpHttpServer } = await import('./lib/http-server')\n\n await runMcpHttpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n port,\n })\n },\n}\n\nconst mcpDev: ModuleCli = {\n command: 'mcp:dev',\n async run() {\n await ensureBootstrap()\n const { runMcpDevServer } = await import('./lib/mcp-dev-server')\n await runMcpDevServer()\n },\n}\n\nconst listTools: ModuleCli = {\n command: 'mcp:list-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const verbose = args.verbose === true || args.verbose === 'true'\n\n // Ensure bootstrap runs so modules are registered for API discovery\n await ensureBootstrap()\n\n const { loadAllModuleTools } = await import('./lib/tool-loader')\n await loadAllModuleTools()\n\n const { getToolRegistry } = await import('./lib/tool-registry')\n const registry = getToolRegistry()\n const toolNames = registry.listToolNames()\n\n if (toolNames.length === 0) {\n console.log('\\nNo MCP tools registered.')\n console.log('Tools can be registered by modules using registerMcpTool().\\n')\n return\n }\n\n console.log(`\\nRegistered MCP Tools (${toolNames.length}):\\n`)\n\n // Group tools by module\n const byModule = new Map<string, string[]>()\n for (const name of toolNames) {\n const [module] = name.split('.')\n const list = byModule.get(module) ?? []\n list.push(name)\n byModule.set(module, list)\n }\n\n // Sort modules alphabetically\n const sortedModules = Array.from(byModule.keys()).sort()\n\n for (const module of sortedModules) {\n const tools = byModule.get(module)!\n console.log(`${module} (${tools.length} tools):`)\n\n for (const name of tools.sort()) {\n const tool = registry.getTool(name)\n if (!tool) continue\n\n if (verbose) {\n console.log(` ${name}`)\n console.log(` ${tool.description}`)\n if (tool.requiredFeatures?.length) {\n console.log(` Requires: ${tool.requiredFeatures.join(', ')}`)\n }\n } else {\n console.log(` - ${name}`)\n }\n }\n console.log('')\n }\n },\n}\n\nconst entityGraph: ModuleCli = {\n command: 'entity-graph',\n async run(rest) {\n const args = parseArgs(rest)\n const format = String(args.format ?? 'triples') as 'json' | 'triples'\n const entity = args.entity ? String(args.entity) : undefined\n const module = args.module ? String(args.module) : undefined\n\n await ensureBootstrap()\n\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n const { extractEntityGraph, formatGraphAsTriples, filterGraphByEntity, filterGraphByModule } = await import(\n './lib/entity-graph'\n )\n\n console.log('[Entity Graph] Extracting from MikroORM metadata...')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n\n // Apply filters\n let edges = graph.edges\n\n if (entity) {\n edges = filterGraphByEntity(graph, entity)\n console.log(`[Entity Graph] Filtered by entity: ${entity}`)\n }\n\n if (module) {\n const filteredGraph = { ...graph, edges }\n edges = filterGraphByModule(filteredGraph, module)\n console.log(`[Entity Graph] Filtered by module: ${module}`)\n }\n\n const filteredGraph = { ...graph, edges }\n\n if (format === 'json') {\n console.log(JSON.stringify(filteredGraph, null, 2))\n } else {\n const triples = formatGraphAsTriples(filteredGraph)\n console.log('')\n for (const triple of triples) {\n console.log(triple)\n }\n }\n\n console.log(`\\n[Entity Graph] ${graph.nodes.length} entities, ${edges.length} relationships`)\n },\n}\n\nconst runPendingActionCleanup: ModuleCli = {\n command: 'run-pending-action-cleanup',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runPendingActionCleanup: runCleanup } = await import(\n './workers/ai-pending-action-cleanup'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runCleanup({ em })\n\n console.log('[ai-pending-action-cleanup] Sweep complete:', summary)\n },\n}\n\nconst testTools: ModuleCli = {\n command: 'test-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const json = args.json === true || args.json === 'true'\n const moduleFilter =\n typeof args.module === 'string' && args.module.length > 0 ? args.module : null\n const includeMutations = args['no-mutations'] !== true && args['no-mutations'] !== 'true'\n const tenantId =\n typeof args.tenant === 'string' && args.tenant.length > 0 ? args.tenant : null\n const organizationId =\n typeof args.org === 'string' && args.org.length > 0 ? args.org : null\n\n await ensureBootstrap()\n const { runToolTests } = await import('./lib/tool-test-runner')\n const report = await runToolTests({\n tenantId,\n organizationId,\n moduleFilter,\n includeMutations,\n })\n\n if (json) {\n // Wrap in markers so a Playwright spec (or any caller) can extract the\n // JSON payload without being thrown off by bootstrap log lines emitted\n // to stdout by other modules during DI container creation.\n console.log('---TOOL_TEST_REPORT_BEGIN---')\n console.log(JSON.stringify(report))\n console.log('---TOOL_TEST_REPORT_END---')\n } else {\n console.log('')\n console.log(\n `AI tool test report \u2014 tenant=${report.tenantId ?? '<none>'} org=${\n report.organizationId ?? '<none>'\n }`,\n )\n console.log(\n `total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,\n )\n const byModule = new Map<string, typeof report.records>()\n for (const record of report.records) {\n const list = byModule.get(record.module) ?? []\n list.push(record)\n byModule.set(record.module, list)\n }\n const sortedModules = Array.from(byModule.keys()).sort()\n for (const moduleId of sortedModules) {\n const list = byModule.get(moduleId)!\n console.log('')\n console.log(`${moduleId} (${list.length}):`)\n for (const record of list) {\n const marker =\n record.status === 'pass' ? '\u2713' : record.status === 'fail' ? '\u2717' : '\u00B7'\n const reason = record.reason ? ` \u2014 ${record.reason}` : ''\n const mutation = record.isMutation ? ' [mutation]' : ''\n console.log(\n ` ${marker} ${record.tool}${mutation} (${record.durationMs}ms)${reason}`,\n )\n }\n }\n console.log('')\n }\n\n if (report.failed > 0) {\n process.exitCode = 1\n }\n },\n}\n\nconst runTokenUsagePrune: ModuleCli = {\n command: 'run-token-usage-prune',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runTokenUsagePrune: runPrune } = await import(\n './workers/ai-token-usage-prune'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runPrune({ em })\n\n console.log('[ai-token-usage-prune] Prune complete:', summary)\n },\n}\n\nexport default [\n mcpServe,\n mcpServeHttp,\n mcpDev,\n listTools,\n entityGraph,\n runPendingActionCleanup,\n runTokenUsagePrune,\n testTools,\n]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\n/**\n * Ensure app bootstrap is called before creating DI container.\n * Uses the shared generated-bootstrap loader so the command works both from\n * the monorepo app and from standalone apps installed through npm packages.\n */\nasync function ensureBootstrap(): Promise<void> {\n // First check if DI is already available\n try {\n const { getDiRegistrars } = await import('@open-mercato/shared/lib/di/container')\n getDiRegistrars()\n return // DI already available\n } catch {\n // DI not available, need to bootstrap\n }\n\n try {\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n await bootstrapFromAppRoot()\n } catch (error) {\n console.error('[MCP] Bootstrap failed:', error instanceof Error ? error.message : error)\n // Continue - some contexts may not have bootstrap available\n }\n}\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i]\n if (!arg?.startsWith('--')) continue\n\n const [key, value] = arg.replace(/^--/, '').split('=')\n if (value !== undefined) {\n args[key] = value\n } else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) {\n args[key] = rest[i + 1]!\n i++\n } else {\n args[key] = true\n }\n }\n return args\n}\n\nconst mcpServe: ModuleCli = {\n command: 'mcp:serve',\n async run(rest) {\n const args = parseArgs(rest)\n // Prefer the OPEN_MERCATO_API_KEY env var so the secret never has to be\n // placed on the command line (argv is world-readable via ps / /proc).\n // The --api-key flag stays supported for backward compatibility.\n const apiKey = String(args['api-key'] ?? args.apiKey ?? '') || process.env.OPEN_MERCATO_API_KEY || null\n const tenantId = String(args.tenant ?? args.tenantId ?? '') || null\n const organizationId = String(args.org ?? args.organizationId ?? '') || null\n const userId = String(args.user ?? args.userId ?? '') || null\n const debug = args.debug === true || args.debug === 'true'\n const allowUnauthenticatedSuperadmin =\n args['allow-unauthenticated-superadmin'] === true ||\n args['allow-unauthenticated-superadmin'] === 'true'\n\n // Either API key or tenant is required\n if (!apiKey && !tenantId) {\n console.error('Usage: mercato ai_assistant mcp:serve [options]')\n console.error('')\n console.error('Authentication (choose one):')\n console.error(' OPEN_MERCATO_API_KEY env API key secret (recommended \u2014 keeps the secret off argv)')\n console.error(' --api-key <secret> API key secret for authentication (visible in process listings)')\n console.error(' --tenant <id> Tenant ID (for manual context)')\n console.error('')\n console.error('Options (with --tenant):')\n console.error(' --org <id> Organization ID (optional)')\n console.error(' --user <id> User ID for ACL (required unless --allow-unauthenticated-superadmin is set)')\n console.error('')\n console.error('Common options:')\n console.error(' --debug Enable debug logging')\n console.error(' --allow-unauthenticated-superadmin')\n console.error(' DEV/TEST ONLY: run as superadmin with no per-user ACL when')\n console.error(' no --user (and no --api-key) is supplied. Never use in production.')\n console.error('')\n console.error('Examples:')\n console.error(' OPEN_MERCATO_API_KEY=omk_xxxx.yyyy... mercato ai_assistant mcp:serve')\n console.error(' mercato ai_assistant mcp:serve --api-key omk_xxxx.yyyy...')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --user <user-id>')\n console.error(' mercato ai_assistant mcp:serve --tenant 123e4567-e89b-12d3-a456-426614174000 --allow-unauthenticated-superadmin')\n return\n }\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpServer } = await import('./lib/mcp-server')\n\n if (apiKey) {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n apiKeySecret: apiKey,\n })\n } else {\n await runMcpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n context: {\n tenantId,\n organizationId,\n userId,\n },\n allowUnauthenticatedSuperadmin,\n })\n }\n },\n}\n\nconst MCP_DEFAULT_PORT = 3001\n\nconst mcpServeHttp: ModuleCli = {\n command: 'mcp:serve-http',\n async run(rest) {\n const args = parseArgs(rest)\n const portArg = parseInt(String(args.port ?? ''), 10)\n const port = !portArg || isNaN(portArg) ? MCP_DEFAULT_PORT : portArg\n const debug = args.debug === true || args.debug === 'true'\n\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runMcpHttpServer } = await import('./lib/http-server')\n\n await runMcpHttpServer({\n config: {\n name: 'open-mercato-mcp',\n version: '0.1.0',\n debug,\n },\n container,\n port,\n })\n },\n}\n\nconst mcpDev: ModuleCli = {\n command: 'mcp:dev',\n async run() {\n await ensureBootstrap()\n const { runMcpDevServer } = await import('./lib/mcp-dev-server')\n await runMcpDevServer()\n },\n}\n\nconst listTools: ModuleCli = {\n command: 'mcp:list-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const verbose = args.verbose === true || args.verbose === 'true'\n\n // Ensure bootstrap runs so modules are registered for API discovery\n await ensureBootstrap()\n\n const { loadAllModuleTools } = await import('./lib/tool-loader')\n await loadAllModuleTools()\n\n const { getToolRegistry } = await import('./lib/tool-registry')\n const registry = getToolRegistry()\n const toolNames = registry.listToolNames()\n\n if (toolNames.length === 0) {\n console.log('\\nNo MCP tools registered.')\n console.log('Tools can be registered by modules using registerMcpTool().\\n')\n return\n }\n\n console.log(`\\nRegistered MCP Tools (${toolNames.length}):\\n`)\n\n // Group tools by module\n const byModule = new Map<string, string[]>()\n for (const name of toolNames) {\n const [module] = name.split('.')\n const list = byModule.get(module) ?? []\n list.push(name)\n byModule.set(module, list)\n }\n\n // Sort modules alphabetically\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n\n for (const module of sortedModules) {\n const tools = byModule.get(module)!\n console.log(`${module} (${tools.length} tools):`)\n\n for (const name of tools.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))) {\n const tool = registry.getTool(name)\n if (!tool) continue\n\n if (verbose) {\n console.log(` ${name}`)\n console.log(` ${tool.description}`)\n if (tool.requiredFeatures?.length) {\n console.log(` Requires: ${tool.requiredFeatures.join(', ')}`)\n }\n } else {\n console.log(` - ${name}`)\n }\n }\n console.log('')\n }\n },\n}\n\nconst entityGraph: ModuleCli = {\n command: 'entity-graph',\n async run(rest) {\n const args = parseArgs(rest)\n const format = String(args.format ?? 'triples') as 'json' | 'triples'\n const entity = args.entity ? String(args.entity) : undefined\n const module = args.module ? String(args.module) : undefined\n\n await ensureBootstrap()\n\n const { getOrm } = await import('@open-mercato/shared/lib/db/mikro')\n const { extractEntityGraph, formatGraphAsTriples, filterGraphByEntity, filterGraphByModule } = await import(\n './lib/entity-graph'\n )\n\n console.log('[Entity Graph] Extracting from MikroORM metadata...')\n\n const orm = await getOrm()\n const graph = await extractEntityGraph(orm)\n\n // Apply filters\n let edges = graph.edges\n\n if (entity) {\n edges = filterGraphByEntity(graph, entity)\n console.log(`[Entity Graph] Filtered by entity: ${entity}`)\n }\n\n if (module) {\n const filteredGraph = { ...graph, edges }\n edges = filterGraphByModule(filteredGraph, module)\n console.log(`[Entity Graph] Filtered by module: ${module}`)\n }\n\n const filteredGraph = { ...graph, edges }\n\n if (format === 'json') {\n console.log(JSON.stringify(filteredGraph, null, 2))\n } else {\n const triples = formatGraphAsTriples(filteredGraph)\n console.log('')\n for (const triple of triples) {\n console.log(triple)\n }\n }\n\n console.log(`\\n[Entity Graph] ${graph.nodes.length} entities, ${edges.length} relationships`)\n },\n}\n\nconst runPendingActionCleanup: ModuleCli = {\n command: 'run-pending-action-cleanup',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runPendingActionCleanup: runCleanup } = await import(\n './workers/ai-pending-action-cleanup'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runCleanup({ em })\n\n console.log('[ai-pending-action-cleanup] Sweep complete:', summary)\n },\n}\n\nconst testTools: ModuleCli = {\n command: 'test-tools',\n async run(rest) {\n const args = parseArgs(rest)\n const json = args.json === true || args.json === 'true'\n const moduleFilter =\n typeof args.module === 'string' && args.module.length > 0 ? args.module : null\n const includeMutations = args['no-mutations'] !== true && args['no-mutations'] !== 'true'\n const tenantId =\n typeof args.tenant === 'string' && args.tenant.length > 0 ? args.tenant : null\n const organizationId =\n typeof args.org === 'string' && args.org.length > 0 ? args.org : null\n\n await ensureBootstrap()\n const { runToolTests } = await import('./lib/tool-test-runner')\n const report = await runToolTests({\n tenantId,\n organizationId,\n moduleFilter,\n includeMutations,\n })\n\n if (json) {\n // Wrap in markers so a Playwright spec (or any caller) can extract the\n // JSON payload without being thrown off by bootstrap log lines emitted\n // to stdout by other modules during DI container creation.\n console.log('---TOOL_TEST_REPORT_BEGIN---')\n console.log(JSON.stringify(report))\n console.log('---TOOL_TEST_REPORT_END---')\n } else {\n console.log('')\n console.log(\n `AI tool test report \u2014 tenant=${report.tenantId ?? '<none>'} org=${\n report.organizationId ?? '<none>'\n }`,\n )\n console.log(\n `total=${report.total} pass=${report.passed} fail=${report.failed} skip=${report.skipped}`,\n )\n const byModule = new Map<string, typeof report.records>()\n for (const record of report.records) {\n const list = byModule.get(record.module) ?? []\n list.push(record)\n byModule.set(record.module, list)\n }\n const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n for (const moduleId of sortedModules) {\n const list = byModule.get(moduleId)!\n console.log('')\n console.log(`${moduleId} (${list.length}):`)\n for (const record of list) {\n const marker =\n record.status === 'pass' ? '\u2713' : record.status === 'fail' ? '\u2717' : '\u00B7'\n const reason = record.reason ? ` \u2014 ${record.reason}` : ''\n const mutation = record.isMutation ? ' [mutation]' : ''\n console.log(\n ` ${marker} ${record.tool}${mutation} (${record.durationMs}ms)${reason}`,\n )\n }\n }\n console.log('')\n }\n\n if (report.failed > 0) {\n process.exitCode = 1\n }\n },\n}\n\nconst runTokenUsagePrune: ModuleCli = {\n command: 'run-token-usage-prune',\n async run() {\n await ensureBootstrap()\n const container = await createRequestContainer()\n\n const { runTokenUsagePrune: runPrune } = await import(\n './workers/ai-token-usage-prune'\n )\n\n const em = container.resolve<import('@mikro-orm/postgresql').EntityManager>('em')\n const summary = await runPrune({ em })\n\n console.log('[ai-token-usage-prune] Prune complete:', summary)\n },\n}\n\nexport default [\n mcpServe,\n mcpServeHttp,\n mcpDev,\n listTools,\n entityGraph,\n runPendingActionCleanup,\n runTokenUsagePrune,\n testTools,\n]\n"],
5
+ "mappings": "AACA,SAAS,8BAA8B;AAMvC,eAAe,kBAAiC;AAE9C,MAAI;AACF,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,uCAAuC;AAChF,oBAAgB;AAChB;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,kDAAkD;AAChG,UAAM,qBAAqB;AAAA,EAC7B,SAAS,OAAO;AACd,YAAQ,MAAM,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,KAAK;AAAA,EAEzF;AACF;AAEA,SAAS,UAAU,MAAkD;AACnE,QAAM,OAAyC,CAAC;AAChD,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,CAAC,KAAK,WAAW,IAAI,EAAG;AAE5B,UAAM,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG;AACrD,QAAI,UAAU,QAAW;AACvB,WAAK,GAAG,IAAI;AAAA,IACd,WAAW,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,EAAG,WAAW,IAAI,GAAG;AACxD,WAAK,GAAG,IAAI,KAAK,IAAI,CAAC;AACtB;AAAA,IACF,OAAO;AACL,WAAK,GAAG,IAAI;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,WAAsB;AAAA,EAC1B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAI3B,UAAM,SAAS,OAAO,KAAK,SAAS,KAAK,KAAK,UAAU,EAAE,KAAK,QAAQ,IAAI,wBAAwB;AACnG,UAAM,WAAW,OAAO,KAAK,UAAU,KAAK,YAAY,EAAE,KAAK;AAC/D,UAAM,iBAAiB,OAAO,KAAK,OAAO,KAAK,kBAAkB,EAAE,KAAK;AACxE,UAAM,SAAS,OAAO,KAAK,QAAQ,KAAK,UAAU,EAAE,KAAK;AACzD,UAAM,QAAQ,KAAK,UAAU,QAAQ,KAAK,UAAU;AACpD,UAAM,iCACJ,KAAK,kCAAkC,MAAM,QAC7C,KAAK,kCAAkC,MAAM;AAG/C,QAAI,CAAC,UAAU,CAAC,UAAU;AACxB,cAAQ,MAAM,iDAAiD;AAC/D,cAAQ,MAAM,EAAE;AAChB,cAAQ,MAAM,8BAA8B;AAC5C,cAAQ,MAAM,4FAAuF;AACrG,cAAQ,MAAM,8FAA8F;AAC5G,cAAQ,MAAM,6DAA6D;AAC3E,cAAQ,MAAM,EAAE;AAChB,cAAQ,MAAM,0BAA0B;AACxC,cAAQ,MAAM,mDAAmD;AACjE,cAAQ,MAAM,oGAAoG;AAClH,cAAQ,MAAM,EAAE;AAChB,cAAQ,MAAM,iBAAiB;AAC/B,cAAQ,MAAM,6CAA6C;AAC3D,cAAQ,MAAM,sCAAsC;AACpD,cAAQ,MAAM,mFAAmF;AACjG,cAAQ,MAAM,2FAA2F;AACzG,cAAQ,MAAM,EAAE;AAChB,cAAQ,MAAM,WAAW;AACzB,cAAQ,MAAM,wEAAwE;AACtF,cAAQ,MAAM,6DAA6D;AAC3E,cAAQ,MAAM,iGAAiG;AAC/G,cAAQ,MAAM,mHAAmH;AACjI;AAAA,IACF;AAEA,UAAM,gBAAgB;AACtB,UAAM,YAAY,MAAM,uBAAuB;AAE/C,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,kBAAkB;AAExD,QAAI,QAAQ;AACV,YAAM,aAAa;AAAA,QACjB,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT;AAAA,QACF;AAAA,QACA;AAAA,QACA,cAAc;AAAA,MAChB,CAAC;AAAA,IACH,OAAO;AACL,YAAM,aAAa;AAAA,QACjB,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT;AAAA,QACF;AAAA,QACA;AAAA,QACA,SAAS;AAAA,UACP;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,MAAM,mBAAmB;AAEzB,MAAM,eAA0B;AAAA,EAC9B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,UAAU,SAAS,OAAO,KAAK,QAAQ,EAAE,GAAG,EAAE;AACpD,UAAM,OAAO,CAAC,WAAW,MAAM,OAAO,IAAI,mBAAmB;AAC7D,UAAM,QAAQ,KAAK,UAAU,QAAQ,KAAK,UAAU;AAEpD,UAAM,gBAAgB;AACtB,UAAM,YAAY,MAAM,uBAAuB;AAE/C,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,mBAAmB;AAE7D,UAAM,iBAAiB;AAAA,MACrB,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,SAAS;AAAA,QACT;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,MAAM,SAAoB;AAAA,EACxB,SAAS;AAAA,EACT,MAAM,MAAM;AACV,UAAM,gBAAgB;AACtB,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,sBAAsB;AAC/D,UAAM,gBAAgB;AAAA,EACxB;AACF;AAEA,MAAM,YAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,UAAU,KAAK,YAAY,QAAQ,KAAK,YAAY;AAG1D,UAAM,gBAAgB;AAEtB,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,mBAAmB;AAC/D,UAAM,mBAAmB;AAEzB,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,qBAAqB;AAC9D,UAAM,WAAW,gBAAgB;AACjC,UAAM,YAAY,SAAS,cAAc;AAEzC,QAAI,UAAU,WAAW,GAAG;AAC1B,cAAQ,IAAI,4BAA4B;AACxC,cAAQ,IAAI,+DAA+D;AAC3E;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,wBAA2B,UAAU,MAAM;AAAA,CAAM;AAG7D,UAAM,WAAW,oBAAI,IAAsB;AAC3C,eAAW,QAAQ,WAAW;AAC5B,YAAM,CAAC,MAAM,IAAI,KAAK,MAAM,GAAG;AAC/B,YAAM,OAAO,SAAS,IAAI,MAAM,KAAK,CAAC;AACtC,WAAK,KAAK,IAAI;AACd,eAAS,IAAI,QAAQ,IAAI;AAAA,IAC3B;AAGA,UAAM,gBAAgB,MAAM,KAAK,SAAS,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;AAE7F,eAAW,UAAU,eAAe;AAClC,YAAM,QAAQ,SAAS,IAAI,MAAM;AACjC,cAAQ,IAAI,GAAG,MAAM,KAAK,MAAM,MAAM,UAAU;AAEhD,iBAAW,QAAQ,MAAM,KAAK,CAAC,GAAG,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE,GAAG;AACrE,cAAM,OAAO,SAAS,QAAQ,IAAI;AAClC,YAAI,CAAC,KAAM;AAEX,YAAI,SAAS;AACX,kBAAQ,IAAI,KAAK,IAAI,EAAE;AACvB,kBAAQ,IAAI,OAAO,KAAK,WAAW,EAAE;AACrC,cAAI,KAAK,kBAAkB,QAAQ;AACjC,oBAAQ,IAAI,iBAAiB,KAAK,iBAAiB,KAAK,IAAI,CAAC,EAAE;AAAA,UACjE;AAAA,QACF,OAAO;AACL,kBAAQ,IAAI,OAAO,IAAI,EAAE;AAAA,QAC3B;AAAA,MACF;AACA,cAAQ,IAAI,EAAE;AAAA,IAChB;AAAA,EACF;AACF;AAEA,MAAM,cAAyB;AAAA,EAC7B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,SAAS,OAAO,KAAK,UAAU,SAAS;AAC9C,UAAM,SAAS,KAAK,SAAS,OAAO,KAAK,MAAM,IAAI;AACnD,UAAM,SAAS,KAAK,SAAS,OAAO,KAAK,MAAM,IAAI;AAEnD,UAAM,gBAAgB;AAEtB,UAAM,EAAE,OAAO,IAAI,MAAM,OAAO,mCAAmC;AACnE,UAAM,EAAE,oBAAoB,sBAAsB,qBAAqB,oBAAoB,IAAI,MAAM,OACnG,oBACF;AAEA,YAAQ,IAAI,qDAAqD;AAEjE,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,QAAQ,MAAM,mBAAmB,GAAG;AAG1C,QAAI,QAAQ,MAAM;AAElB,QAAI,QAAQ;AACV,cAAQ,oBAAoB,OAAO,MAAM;AACzC,cAAQ,IAAI,sCAAsC,MAAM,EAAE;AAAA,IAC5D;AAEA,QAAI,QAAQ;AACV,YAAMA,iBAAgB,EAAE,GAAG,OAAO,MAAM;AACxC,cAAQ,oBAAoBA,gBAAe,MAAM;AACjD,cAAQ,IAAI,sCAAsC,MAAM,EAAE;AAAA,IAC5D;AAEA,UAAM,gBAAgB,EAAE,GAAG,OAAO,MAAM;AAExC,QAAI,WAAW,QAAQ;AACrB,cAAQ,IAAI,KAAK,UAAU,eAAe,MAAM,CAAC,CAAC;AAAA,IACpD,OAAO;AACL,YAAM,UAAU,qBAAqB,aAAa;AAClD,cAAQ,IAAI,EAAE;AACd,iBAAW,UAAU,SAAS;AAC5B,gBAAQ,IAAI,MAAM;AAAA,MACpB;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA,iBAAoB,MAAM,MAAM,MAAM,cAAc,MAAM,MAAM,gBAAgB;AAAA,EAC9F;AACF;AAEA,MAAM,0BAAqC;AAAA,EACzC,SAAS;AAAA,EACT,MAAM,MAAM;AACV,UAAM,gBAAgB;AACtB,UAAM,YAAY,MAAM,uBAAuB;AAE/C,UAAM,EAAE,yBAAyB,WAAW,IAAI,MAAM,OACpD,qCACF;AAEA,UAAM,KAAK,UAAU,QAAuD,IAAI;AAChF,UAAM,UAAU,MAAM,WAAW,EAAE,GAAG,CAAC;AAEvC,YAAQ,IAAI,+CAA+C,OAAO;AAAA,EACpE;AACF;AAEA,MAAM,YAAuB;AAAA,EAC3B,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,OAAO,KAAK,SAAS,QAAQ,KAAK,SAAS;AACjD,UAAM,eACJ,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,SAAS,IAAI,KAAK,SAAS;AAC5E,UAAM,mBAAmB,KAAK,cAAc,MAAM,QAAQ,KAAK,cAAc,MAAM;AACnF,UAAM,WACJ,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,SAAS,IAAI,KAAK,SAAS;AAC5E,UAAM,iBACJ,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,SAAS,IAAI,KAAK,MAAM;AAEnE,UAAM,gBAAgB;AACtB,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,wBAAwB;AAC9D,UAAM,SAAS,MAAM,aAAa;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,MAAM;AAIR,cAAQ,IAAI,8BAA8B;AAC1C,cAAQ,IAAI,KAAK,UAAU,MAAM,CAAC;AAClC,cAAQ,IAAI,4BAA4B;AAAA,IAC1C,OAAO;AACL,cAAQ,IAAI,EAAE;AACd,cAAQ;AAAA,QACN,qCAAgC,OAAO,YAAY,QAAQ,QACzD,OAAO,kBAAkB,QAC3B;AAAA,MACF;AACA,cAAQ;AAAA,QACN,SAAS,OAAO,KAAK,SAAS,OAAO,MAAM,SAAS,OAAO,MAAM,SAAS,OAAO,OAAO;AAAA,MAC1F;AACA,YAAM,WAAW,oBAAI,IAAmC;AACxD,iBAAW,UAAU,OAAO,SAAS;AACnC,cAAM,OAAO,SAAS,IAAI,OAAO,MAAM,KAAK,CAAC;AAC7C,aAAK,KAAK,MAAM;AAChB,iBAAS,IAAI,OAAO,QAAQ,IAAI;AAAA,MAClC;AACA,YAAM,gBAAgB,MAAM,KAAK,SAAS,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;AAC7F,iBAAW,YAAY,eAAe;AACpC,cAAM,OAAO,SAAS,IAAI,QAAQ;AAClC,gBAAQ,IAAI,EAAE;AACd,gBAAQ,IAAI,GAAG,QAAQ,KAAK,KAAK,MAAM,IAAI;AAC3C,mBAAW,UAAU,MAAM;AACzB,gBAAM,SACJ,OAAO,WAAW,SAAS,WAAM,OAAO,WAAW,SAAS,WAAM;AACpE,gBAAM,SAAS,OAAO,SAAS,WAAM,OAAO,MAAM,KAAK;AACvD,gBAAM,WAAW,OAAO,aAAa,gBAAgB;AACrD,kBAAQ;AAAA,YACN,KAAK,MAAM,IAAI,OAAO,IAAI,GAAG,QAAQ,KAAK,OAAO,UAAU,MAAM,MAAM;AAAA,UACzE;AAAA,QACF;AAAA,MACF;AACA,cAAQ,IAAI,EAAE;AAAA,IAChB;AAEA,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF;AACF;AAEA,MAAM,qBAAgC;AAAA,EACpC,SAAS;AAAA,EACT,MAAM,MAAM;AACV,UAAM,gBAAgB;AACtB,UAAM,YAAY,MAAM,uBAAuB;AAE/C,UAAM,EAAE,oBAAoB,SAAS,IAAI,MAAM,OAC7C,gCACF;AAEA,UAAM,KAAK,UAAU,QAAuD,IAAI;AAChF,UAAM,UAAU,MAAM,SAAS,EAAE,GAAG,CAAC;AAErC,YAAQ,IAAI,0CAA0C,OAAO;AAAA,EAC/D;AACF;AAEA,IAAO,cAAQ;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
6
6
  "names": ["filteredGraph"]
7
7
  }
@@ -175,7 +175,7 @@ function getGraphStats(graph) {
175
175
  return {
176
176
  totalEntities: graph.nodes.length,
177
177
  totalRelationships: graph.edges.length,
178
- modules: Array.from(byModule.keys()).sort()
178
+ modules: Array.from(byModule.keys()).sort((a, b) => a < b ? -1 : a > b ? 1 : 0)
179
179
  };
180
180
  }
181
181
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/entity-graph.ts"],
4
- "sourcesContent": ["/**\n * Entity Relationship Graph Extraction\n *\n * Extracts entity relationships from MikroORM metadata and provides\n * them in a format suitable for AI tools to query.\n */\n\nimport { ReferenceKind, type MikroORM } from '@mikro-orm/core'\nimport type { PostgreSqlDriver } from '@mikro-orm/postgresql'\n\n/**\n * Relationship types mapped from MikroORM reference kinds.\n */\nexport type RelationshipType =\n | 'BELONGS_TO' // ManyToOne\n | 'HAS_MANY' // OneToMany\n | 'HAS_ONE' // OneToOne (owner)\n | 'BELONGS_TO_ONE' // OneToOne (inverse)\n | 'HAS_MANY_MANY' // ManyToMany (owner)\n | 'BELONGS_TO_MANY' // ManyToMany (inverse)\n\n/**\n * A relationship triple representing a connection between two entities.\n */\nexport interface EntityTriple {\n source: string\n relationship: RelationshipType\n target: string\n property: string\n nullable?: boolean\n}\n\n/**\n * An entity node with its properties.\n */\nexport interface EntityNode {\n className: string\n tableName: string\n properties: Array<{ name: string; type: string; nullable: boolean }>\n}\n\n/**\n * The complete entity graph with nodes and edges.\n */\nexport interface EntityGraph {\n nodes: EntityNode[]\n edges: EntityTriple[]\n generatedAt: string\n}\n\n/**\n * In-memory cache for the entity graph.\n */\nlet cachedGraph: EntityGraph | null = null\n\n/**\n * Map MikroORM ReferenceKind to our RelationshipType.\n */\nfunction mapReferenceKind(kind: ReferenceKind, mappedBy?: string): RelationshipType {\n switch (kind) {\n case ReferenceKind.MANY_TO_ONE:\n return 'BELONGS_TO'\n case ReferenceKind.ONE_TO_MANY:\n return 'HAS_MANY'\n case ReferenceKind.ONE_TO_ONE:\n // If mappedBy is set, this is the inverse side\n return mappedBy ? 'BELONGS_TO_ONE' : 'HAS_ONE'\n case ReferenceKind.MANY_TO_MANY:\n // If mappedBy is set, this is the inverse side\n return mappedBy ? 'BELONGS_TO_MANY' : 'HAS_MANY_MANY'\n default:\n return 'BELONGS_TO'\n }\n}\n\n/**\n * Get a simple type name from MikroORM property type.\n */\nfunction getSimpleTypeName(type: string | ((...args: unknown[]) => unknown) | undefined): string {\n if (!type) return 'unknown'\n if (typeof type === 'function') return type.name || 'unknown'\n return type\n}\n\n/**\n * Extract the entity graph from MikroORM metadata.\n */\nexport async function extractEntityGraph(orm: MikroORM<PostgreSqlDriver>): Promise<EntityGraph> {\n const metadata = orm.getMetadata()\n const allMetadata = metadata.getAll()\n\n const nodes: EntityNode[] = []\n const edges: EntityTriple[] = []\n\n for (const [, entityMeta] of Object.entries(allMetadata)) {\n // Skip abstract entities and embeddables\n if (entityMeta.abstract || entityMeta.embeddable) continue\n\n // Skip internal MikroORM entities\n if (entityMeta.className.startsWith('MikroORM')) continue\n\n const properties: Array<{ name: string; type: string; nullable: boolean }> = []\n\n for (const prop of entityMeta.props) {\n // Skip internal properties\n if (prop.name.startsWith('_')) continue\n\n // Handle relationships\n if (prop.kind !== undefined && prop.kind !== ReferenceKind.SCALAR) {\n // This is a relationship property\n const targetEntity = prop.type\n if (targetEntity && targetEntity !== entityMeta.className) {\n const relationship = mapReferenceKind(prop.kind, prop.mappedBy)\n\n edges.push({\n source: entityMeta.className,\n relationship,\n target: targetEntity,\n property: prop.name,\n nullable: prop.nullable ?? false,\n })\n }\n } else {\n // Regular scalar property\n properties.push({\n name: prop.name,\n type: getSimpleTypeName(prop.type),\n nullable: prop.nullable ?? false,\n })\n }\n }\n\n nodes.push({\n className: entityMeta.className,\n tableName: entityMeta.tableName,\n properties,\n })\n }\n\n // Sort for consistent output\n nodes.sort((a, b) => a.className.localeCompare(b.className))\n edges.sort((a, b) => {\n const sourceCompare = a.source.localeCompare(b.source)\n if (sourceCompare !== 0) return sourceCompare\n return a.property.localeCompare(b.property)\n })\n\n return {\n nodes,\n edges,\n generatedAt: new Date().toISOString(),\n }\n}\n\n/**\n * Format the entity graph as readable triples.\n *\n * Example output:\n * (CustomerEntity)-[HAS_MANY:deals]->(CustomerDeal)\n * (SalesOrder)-[BELONGS_TO:channel]->(SalesChannel)\n */\nexport function formatGraphAsTriples(graph: EntityGraph): string[] {\n return graph.edges.map((edge) => {\n const nullable = edge.nullable ? '?' : ''\n return `(${edge.source})-[${edge.relationship}${nullable}:${edge.property}]->(${edge.target})`\n })\n}\n\n/**\n * Cache the entity graph in memory.\n */\nexport function cacheEntityGraph(graph: EntityGraph): void {\n cachedGraph = graph\n}\n\n/**\n * Retrieve the cached entity graph.\n */\nexport function getCachedEntityGraph(): EntityGraph | null {\n return cachedGraph\n}\n\n/**\n * Filter graph edges by entity name (source or target).\n */\nexport function filterGraphByEntity(graph: EntityGraph, entityName: string): EntityTriple[] {\n const lowerEntity = entityName.toLowerCase()\n return graph.edges.filter(\n (edge) => edge.source.toLowerCase().includes(lowerEntity) || edge.target.toLowerCase().includes(lowerEntity)\n )\n}\n\n/**\n * Filter graph edges by module (inferred from table name prefix).\n */\nexport function filterGraphByModule(graph: EntityGraph, moduleName: string): EntityTriple[] {\n const lowerModule = moduleName.toLowerCase()\n\n // Find entities that belong to this module (by table name prefix or class name)\n const moduleEntities = new Set<string>()\n for (const node of graph.nodes) {\n if (node.tableName.startsWith(lowerModule) || node.className.toLowerCase().includes(lowerModule)) {\n moduleEntities.add(node.className)\n }\n }\n\n return graph.edges.filter((edge) => moduleEntities.has(edge.source) || moduleEntities.has(edge.target))\n}\n\n/**\n * Filter graph edges by relationship type.\n */\nexport function filterGraphByType(graph: EntityGraph, type: RelationshipType): EntityTriple[] {\n return graph.edges.filter((edge) => edge.relationship === type)\n}\n\n/**\n * Get entity fields for a specific entity.\n */\nexport function getEntityFields(graph: EntityGraph, entityName: string): EntityNode | undefined {\n const lowerEntity = entityName.toLowerCase()\n return graph.nodes.find((node) => node.className.toLowerCase() === lowerEntity)\n}\n\n/**\n * List all entities grouped by inferred module.\n */\nexport function listEntitiesByModule(graph: EntityGraph): Map<string, string[]> {\n const byModule = new Map<string, string[]>()\n\n for (const node of graph.nodes) {\n // Infer module from table name prefix (e.g., 'sales_orders' -> 'sales')\n const module = inferModuleFromEntity(node.className, node.tableName)\n\n const existing = byModule.get(module) ?? []\n existing.push(node.className)\n byModule.set(module, existing)\n }\n\n return byModule\n}\n\n/**\n * Infer module name from entity class name or table name.\n *\n * Patterns:\n * - Table prefix: 'sales_orders' \u2192 'sales'\n * - Class prefix: 'SalesOrder' \u2192 'sales' (PascalCase to module)\n * - Common mappings: CustomerEntity \u2192 'customers', CatalogProduct \u2192 'catalog'\n */\nexport function inferModuleFromEntity(className: string, tableName: string): string {\n // First try table name prefix (most reliable)\n const tableParts = tableName.split('_')\n if (tableParts.length > 1) {\n return tableParts[0]\n }\n\n // Try to extract from class name (e.g., SalesOrder \u2192 sales)\n // Handle common entity suffixes\n const nameWithoutSuffix = className\n .replace(/Entity$/, '')\n .replace(/Model$/, '')\n\n // Extract the first word from PascalCase\n const match = nameWithoutSuffix.match(/^([A-Z][a-z]+)/)\n if (match) {\n const prefix = match[1].toLowerCase()\n // Map common prefixes to module names\n const moduleMap: Record<string, string> = {\n sales: 'sales',\n customer: 'customers',\n catalog: 'catalog',\n product: 'catalog',\n order: 'sales',\n invoice: 'sales',\n quote: 'sales',\n auth: 'auth',\n user: 'auth',\n tenant: 'auth',\n organization: 'auth',\n workflow: 'workflows',\n config: 'configs',\n dictionary: 'dictionaries',\n entity: 'entities',\n search: 'search',\n attachment: 'attachments',\n audit: 'audit_logs',\n api: 'api_keys',\n dashboard: 'dashboards',\n widget: 'widgets',\n feature: 'feature_toggles',\n perspective: 'perspectives',\n currency: 'currencies',\n content: 'content',\n onboarding: 'onboarding',\n }\n if (moduleMap[prefix]) {\n return moduleMap[prefix]\n }\n return prefix\n }\n\n return 'core'\n}\n\n/**\n * Get both outgoing and incoming relationships for an entity.\n */\nexport function getEntityRelationships(\n graph: EntityGraph,\n entityName: string\n): { outgoing: EntityTriple[]; incoming: EntityTriple[] } {\n const outgoing = graph.edges.filter(\n (edge) => edge.source.toLowerCase() === entityName.toLowerCase()\n )\n const incoming = graph.edges.filter(\n (edge) =>\n edge.target.toLowerCase() === entityName.toLowerCase() &&\n edge.source.toLowerCase() !== entityName.toLowerCase()\n )\n return { outgoing, incoming }\n}\n\n/**\n * Format a single relationship triple as a string.\n */\nexport function formatTriple(edge: EntityTriple): string {\n const nullable = edge.nullable ? '?' : ''\n return `(${edge.source})-[${edge.relationship}${nullable}:${edge.property}]->(${edge.target})`\n}\n\n/**\n * Get graph statistics.\n */\nexport function getGraphStats(graph: EntityGraph): {\n totalEntities: number\n totalRelationships: number\n modules: string[]\n} {\n const byModule = listEntitiesByModule(graph)\n return {\n totalEntities: graph.nodes.length,\n totalRelationships: graph.edges.length,\n modules: Array.from(byModule.keys()).sort(),\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Entity Relationship Graph Extraction\n *\n * Extracts entity relationships from MikroORM metadata and provides\n * them in a format suitable for AI tools to query.\n */\n\nimport { ReferenceKind, type MikroORM } from '@mikro-orm/core'\nimport type { PostgreSqlDriver } from '@mikro-orm/postgresql'\n\n/**\n * Relationship types mapped from MikroORM reference kinds.\n */\nexport type RelationshipType =\n | 'BELONGS_TO' // ManyToOne\n | 'HAS_MANY' // OneToMany\n | 'HAS_ONE' // OneToOne (owner)\n | 'BELONGS_TO_ONE' // OneToOne (inverse)\n | 'HAS_MANY_MANY' // ManyToMany (owner)\n | 'BELONGS_TO_MANY' // ManyToMany (inverse)\n\n/**\n * A relationship triple representing a connection between two entities.\n */\nexport interface EntityTriple {\n source: string\n relationship: RelationshipType\n target: string\n property: string\n nullable?: boolean\n}\n\n/**\n * An entity node with its properties.\n */\nexport interface EntityNode {\n className: string\n tableName: string\n properties: Array<{ name: string; type: string; nullable: boolean }>\n}\n\n/**\n * The complete entity graph with nodes and edges.\n */\nexport interface EntityGraph {\n nodes: EntityNode[]\n edges: EntityTriple[]\n generatedAt: string\n}\n\n/**\n * In-memory cache for the entity graph.\n */\nlet cachedGraph: EntityGraph | null = null\n\n/**\n * Map MikroORM ReferenceKind to our RelationshipType.\n */\nfunction mapReferenceKind(kind: ReferenceKind, mappedBy?: string): RelationshipType {\n switch (kind) {\n case ReferenceKind.MANY_TO_ONE:\n return 'BELONGS_TO'\n case ReferenceKind.ONE_TO_MANY:\n return 'HAS_MANY'\n case ReferenceKind.ONE_TO_ONE:\n // If mappedBy is set, this is the inverse side\n return mappedBy ? 'BELONGS_TO_ONE' : 'HAS_ONE'\n case ReferenceKind.MANY_TO_MANY:\n // If mappedBy is set, this is the inverse side\n return mappedBy ? 'BELONGS_TO_MANY' : 'HAS_MANY_MANY'\n default:\n return 'BELONGS_TO'\n }\n}\n\n/**\n * Get a simple type name from MikroORM property type.\n */\nfunction getSimpleTypeName(type: string | ((...args: unknown[]) => unknown) | undefined): string {\n if (!type) return 'unknown'\n if (typeof type === 'function') return type.name || 'unknown'\n return type\n}\n\n/**\n * Extract the entity graph from MikroORM metadata.\n */\nexport async function extractEntityGraph(orm: MikroORM<PostgreSqlDriver>): Promise<EntityGraph> {\n const metadata = orm.getMetadata()\n const allMetadata = metadata.getAll()\n\n const nodes: EntityNode[] = []\n const edges: EntityTriple[] = []\n\n for (const [, entityMeta] of Object.entries(allMetadata)) {\n // Skip abstract entities and embeddables\n if (entityMeta.abstract || entityMeta.embeddable) continue\n\n // Skip internal MikroORM entities\n if (entityMeta.className.startsWith('MikroORM')) continue\n\n const properties: Array<{ name: string; type: string; nullable: boolean }> = []\n\n for (const prop of entityMeta.props) {\n // Skip internal properties\n if (prop.name.startsWith('_')) continue\n\n // Handle relationships\n if (prop.kind !== undefined && prop.kind !== ReferenceKind.SCALAR) {\n // This is a relationship property\n const targetEntity = prop.type\n if (targetEntity && targetEntity !== entityMeta.className) {\n const relationship = mapReferenceKind(prop.kind, prop.mappedBy)\n\n edges.push({\n source: entityMeta.className,\n relationship,\n target: targetEntity,\n property: prop.name,\n nullable: prop.nullable ?? false,\n })\n }\n } else {\n // Regular scalar property\n properties.push({\n name: prop.name,\n type: getSimpleTypeName(prop.type),\n nullable: prop.nullable ?? false,\n })\n }\n }\n\n nodes.push({\n className: entityMeta.className,\n tableName: entityMeta.tableName,\n properties,\n })\n }\n\n // Sort for consistent output\n nodes.sort((a, b) => a.className.localeCompare(b.className))\n edges.sort((a, b) => {\n const sourceCompare = a.source.localeCompare(b.source)\n if (sourceCompare !== 0) return sourceCompare\n return a.property.localeCompare(b.property)\n })\n\n return {\n nodes,\n edges,\n generatedAt: new Date().toISOString(),\n }\n}\n\n/**\n * Format the entity graph as readable triples.\n *\n * Example output:\n * (CustomerEntity)-[HAS_MANY:deals]->(CustomerDeal)\n * (SalesOrder)-[BELONGS_TO:channel]->(SalesChannel)\n */\nexport function formatGraphAsTriples(graph: EntityGraph): string[] {\n return graph.edges.map((edge) => {\n const nullable = edge.nullable ? '?' : ''\n return `(${edge.source})-[${edge.relationship}${nullable}:${edge.property}]->(${edge.target})`\n })\n}\n\n/**\n * Cache the entity graph in memory.\n */\nexport function cacheEntityGraph(graph: EntityGraph): void {\n cachedGraph = graph\n}\n\n/**\n * Retrieve the cached entity graph.\n */\nexport function getCachedEntityGraph(): EntityGraph | null {\n return cachedGraph\n}\n\n/**\n * Filter graph edges by entity name (source or target).\n */\nexport function filterGraphByEntity(graph: EntityGraph, entityName: string): EntityTriple[] {\n const lowerEntity = entityName.toLowerCase()\n return graph.edges.filter(\n (edge) => edge.source.toLowerCase().includes(lowerEntity) || edge.target.toLowerCase().includes(lowerEntity)\n )\n}\n\n/**\n * Filter graph edges by module (inferred from table name prefix).\n */\nexport function filterGraphByModule(graph: EntityGraph, moduleName: string): EntityTriple[] {\n const lowerModule = moduleName.toLowerCase()\n\n // Find entities that belong to this module (by table name prefix or class name)\n const moduleEntities = new Set<string>()\n for (const node of graph.nodes) {\n if (node.tableName.startsWith(lowerModule) || node.className.toLowerCase().includes(lowerModule)) {\n moduleEntities.add(node.className)\n }\n }\n\n return graph.edges.filter((edge) => moduleEntities.has(edge.source) || moduleEntities.has(edge.target))\n}\n\n/**\n * Filter graph edges by relationship type.\n */\nexport function filterGraphByType(graph: EntityGraph, type: RelationshipType): EntityTriple[] {\n return graph.edges.filter((edge) => edge.relationship === type)\n}\n\n/**\n * Get entity fields for a specific entity.\n */\nexport function getEntityFields(graph: EntityGraph, entityName: string): EntityNode | undefined {\n const lowerEntity = entityName.toLowerCase()\n return graph.nodes.find((node) => node.className.toLowerCase() === lowerEntity)\n}\n\n/**\n * List all entities grouped by inferred module.\n */\nexport function listEntitiesByModule(graph: EntityGraph): Map<string, string[]> {\n const byModule = new Map<string, string[]>()\n\n for (const node of graph.nodes) {\n // Infer module from table name prefix (e.g., 'sales_orders' -> 'sales')\n const module = inferModuleFromEntity(node.className, node.tableName)\n\n const existing = byModule.get(module) ?? []\n existing.push(node.className)\n byModule.set(module, existing)\n }\n\n return byModule\n}\n\n/**\n * Infer module name from entity class name or table name.\n *\n * Patterns:\n * - Table prefix: 'sales_orders' \u2192 'sales'\n * - Class prefix: 'SalesOrder' \u2192 'sales' (PascalCase to module)\n * - Common mappings: CustomerEntity \u2192 'customers', CatalogProduct \u2192 'catalog'\n */\nexport function inferModuleFromEntity(className: string, tableName: string): string {\n // First try table name prefix (most reliable)\n const tableParts = tableName.split('_')\n if (tableParts.length > 1) {\n return tableParts[0]\n }\n\n // Try to extract from class name (e.g., SalesOrder \u2192 sales)\n // Handle common entity suffixes\n const nameWithoutSuffix = className\n .replace(/Entity$/, '')\n .replace(/Model$/, '')\n\n // Extract the first word from PascalCase\n const match = nameWithoutSuffix.match(/^([A-Z][a-z]+)/)\n if (match) {\n const prefix = match[1].toLowerCase()\n // Map common prefixes to module names\n const moduleMap: Record<string, string> = {\n sales: 'sales',\n customer: 'customers',\n catalog: 'catalog',\n product: 'catalog',\n order: 'sales',\n invoice: 'sales',\n quote: 'sales',\n auth: 'auth',\n user: 'auth',\n tenant: 'auth',\n organization: 'auth',\n workflow: 'workflows',\n config: 'configs',\n dictionary: 'dictionaries',\n entity: 'entities',\n search: 'search',\n attachment: 'attachments',\n audit: 'audit_logs',\n api: 'api_keys',\n dashboard: 'dashboards',\n widget: 'widgets',\n feature: 'feature_toggles',\n perspective: 'perspectives',\n currency: 'currencies',\n content: 'content',\n onboarding: 'onboarding',\n }\n if (moduleMap[prefix]) {\n return moduleMap[prefix]\n }\n return prefix\n }\n\n return 'core'\n}\n\n/**\n * Get both outgoing and incoming relationships for an entity.\n */\nexport function getEntityRelationships(\n graph: EntityGraph,\n entityName: string\n): { outgoing: EntityTriple[]; incoming: EntityTriple[] } {\n const outgoing = graph.edges.filter(\n (edge) => edge.source.toLowerCase() === entityName.toLowerCase()\n )\n const incoming = graph.edges.filter(\n (edge) =>\n edge.target.toLowerCase() === entityName.toLowerCase() &&\n edge.source.toLowerCase() !== entityName.toLowerCase()\n )\n return { outgoing, incoming }\n}\n\n/**\n * Format a single relationship triple as a string.\n */\nexport function formatTriple(edge: EntityTriple): string {\n const nullable = edge.nullable ? '?' : ''\n return `(${edge.source})-[${edge.relationship}${nullable}:${edge.property}]->(${edge.target})`\n}\n\n/**\n * Get graph statistics.\n */\nexport function getGraphStats(graph: EntityGraph): {\n totalEntities: number\n totalRelationships: number\n modules: string[]\n} {\n const byModule = listEntitiesByModule(graph)\n return {\n totalEntities: graph.nodes.length,\n totalRelationships: graph.edges.length,\n modules: Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),\n }\n}\n"],
5
+ "mappings": "AAOA,SAAS,qBAAoC;AA8C7C,IAAI,cAAkC;AAKtC,SAAS,iBAAiB,MAAqB,UAAqC;AAClF,UAAQ,MAAM;AAAA,IACZ,KAAK,cAAc;AACjB,aAAO;AAAA,IACT,KAAK,cAAc;AACjB,aAAO;AAAA,IACT,KAAK,cAAc;AAEjB,aAAO,WAAW,mBAAmB;AAAA,IACvC,KAAK,cAAc;AAEjB,aAAO,WAAW,oBAAoB;AAAA,IACxC;AACE,aAAO;AAAA,EACX;AACF;AAKA,SAAS,kBAAkB,MAAsE;AAC/F,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,OAAO,SAAS,WAAY,QAAO,KAAK,QAAQ;AACpD,SAAO;AACT;AAKA,eAAsB,mBAAmB,KAAuD;AAC9F,QAAM,WAAW,IAAI,YAAY;AACjC,QAAM,cAAc,SAAS,OAAO;AAEpC,QAAM,QAAsB,CAAC;AAC7B,QAAM,QAAwB,CAAC;AAE/B,aAAW,CAAC,EAAE,UAAU,KAAK,OAAO,QAAQ,WAAW,GAAG;AAExD,QAAI,WAAW,YAAY,WAAW,WAAY;AAGlD,QAAI,WAAW,UAAU,WAAW,UAAU,EAAG;AAEjD,UAAM,aAAuE,CAAC;AAE9E,eAAW,QAAQ,WAAW,OAAO;AAEnC,UAAI,KAAK,KAAK,WAAW,GAAG,EAAG;AAG/B,UAAI,KAAK,SAAS,UAAa,KAAK,SAAS,cAAc,QAAQ;AAEjE,cAAM,eAAe,KAAK;AAC1B,YAAI,gBAAgB,iBAAiB,WAAW,WAAW;AACzD,gBAAM,eAAe,iBAAiB,KAAK,MAAM,KAAK,QAAQ;AAE9D,gBAAM,KAAK;AAAA,YACT,QAAQ,WAAW;AAAA,YACnB;AAAA,YACA,QAAQ;AAAA,YACR,UAAU,KAAK;AAAA,YACf,UAAU,KAAK,YAAY;AAAA,UAC7B,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AAEL,mBAAW,KAAK;AAAA,UACd,MAAM,KAAK;AAAA,UACX,MAAM,kBAAkB,KAAK,IAAI;AAAA,UACjC,UAAU,KAAK,YAAY;AAAA,QAC7B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,UAAM,KAAK;AAAA,MACT,WAAW,WAAW;AAAA,MACtB,WAAW,WAAW;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAC3D,QAAM,KAAK,CAAC,GAAG,MAAM;AACnB,UAAM,gBAAgB,EAAE,OAAO,cAAc,EAAE,MAAM;AACrD,QAAI,kBAAkB,EAAG,QAAO;AAChC,WAAO,EAAE,SAAS,cAAc,EAAE,QAAQ;AAAA,EAC5C,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtC;AACF;AASO,SAAS,qBAAqB,OAA8B;AACjE,SAAO,MAAM,MAAM,IAAI,CAAC,SAAS;AAC/B,UAAM,WAAW,KAAK,WAAW,MAAM;AACvC,WAAO,IAAI,KAAK,MAAM,MAAM,KAAK,YAAY,GAAG,QAAQ,IAAI,KAAK,QAAQ,OAAO,KAAK,MAAM;AAAA,EAC7F,CAAC;AACH;AAKO,SAAS,iBAAiB,OAA0B;AACzD,gBAAc;AAChB;AAKO,SAAS,uBAA2C;AACzD,SAAO;AACT;AAKO,SAAS,oBAAoB,OAAoB,YAAoC;AAC1F,QAAM,cAAc,WAAW,YAAY;AAC3C,SAAO,MAAM,MAAM;AAAA,IACjB,CAAC,SAAS,KAAK,OAAO,YAAY,EAAE,SAAS,WAAW,KAAK,KAAK,OAAO,YAAY,EAAE,SAAS,WAAW;AAAA,EAC7G;AACF;AAKO,SAAS,oBAAoB,OAAoB,YAAoC;AAC1F,QAAM,cAAc,WAAW,YAAY;AAG3C,QAAM,iBAAiB,oBAAI,IAAY;AACvC,aAAW,QAAQ,MAAM,OAAO;AAC9B,QAAI,KAAK,UAAU,WAAW,WAAW,KAAK,KAAK,UAAU,YAAY,EAAE,SAAS,WAAW,GAAG;AAChG,qBAAe,IAAI,KAAK,SAAS;AAAA,IACnC;AAAA,EACF;AAEA,SAAO,MAAM,MAAM,OAAO,CAAC,SAAS,eAAe,IAAI,KAAK,MAAM,KAAK,eAAe,IAAI,KAAK,MAAM,CAAC;AACxG;AAKO,SAAS,kBAAkB,OAAoB,MAAwC;AAC5F,SAAO,MAAM,MAAM,OAAO,CAAC,SAAS,KAAK,iBAAiB,IAAI;AAChE;AAKO,SAAS,gBAAgB,OAAoB,YAA4C;AAC9F,QAAM,cAAc,WAAW,YAAY;AAC3C,SAAO,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,UAAU,YAAY,MAAM,WAAW;AAChF;AAKO,SAAS,qBAAqB,OAA2C;AAC9E,QAAM,WAAW,oBAAI,IAAsB;AAE3C,aAAW,QAAQ,MAAM,OAAO;AAE9B,UAAM,SAAS,sBAAsB,KAAK,WAAW,KAAK,SAAS;AAEnE,UAAM,WAAW,SAAS,IAAI,MAAM,KAAK,CAAC;AAC1C,aAAS,KAAK,KAAK,SAAS;AAC5B,aAAS,IAAI,QAAQ,QAAQ;AAAA,EAC/B;AAEA,SAAO;AACT;AAUO,SAAS,sBAAsB,WAAmB,WAA2B;AAElF,QAAM,aAAa,UAAU,MAAM,GAAG;AACtC,MAAI,WAAW,SAAS,GAAG;AACzB,WAAO,WAAW,CAAC;AAAA,EACrB;AAIA,QAAM,oBAAoB,UACvB,QAAQ,WAAW,EAAE,EACrB,QAAQ,UAAU,EAAE;AAGvB,QAAM,QAAQ,kBAAkB,MAAM,gBAAgB;AACtD,MAAI,OAAO;AACT,UAAM,SAAS,MAAM,CAAC,EAAE,YAAY;AAEpC,UAAM,YAAoC;AAAA,MACxC,OAAO;AAAA,MACP,UAAU;AAAA,MACV,SAAS;AAAA,MACT,SAAS;AAAA,MACT,OAAO;AAAA,MACP,SAAS;AAAA,MACT,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,OAAO;AAAA,MACP,KAAK;AAAA,MACL,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,aAAa;AAAA,MACb,UAAU;AAAA,MACV,SAAS;AAAA,MACT,YAAY;AAAA,IACd;AACA,QAAI,UAAU,MAAM,GAAG;AACrB,aAAO,UAAU,MAAM;AAAA,IACzB;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAKO,SAAS,uBACd,OACA,YACwD;AACxD,QAAM,WAAW,MAAM,MAAM;AAAA,IAC3B,CAAC,SAAS,KAAK,OAAO,YAAY,MAAM,WAAW,YAAY;AAAA,EACjE;AACA,QAAM,WAAW,MAAM,MAAM;AAAA,IAC3B,CAAC,SACC,KAAK,OAAO,YAAY,MAAM,WAAW,YAAY,KACrD,KAAK,OAAO,YAAY,MAAM,WAAW,YAAY;AAAA,EACzD;AACA,SAAO,EAAE,UAAU,SAAS;AAC9B;AAKO,SAAS,aAAa,MAA4B;AACvD,QAAM,WAAW,KAAK,WAAW,MAAM;AACvC,SAAO,IAAI,KAAK,MAAM,MAAM,KAAK,YAAY,GAAG,QAAQ,IAAI,KAAK,QAAQ,OAAO,KAAK,MAAM;AAC7F;AAKO,SAAS,cAAc,OAI5B;AACA,QAAM,WAAW,qBAAqB,KAAK;AAC3C,SAAO;AAAA,IACL,eAAe,MAAM,MAAM;AAAA,IAC3B,oBAAoB,MAAM,MAAM;AAAA,IAChC,SAAS,MAAM,KAAK,SAAS,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;AAAA,EAClF;AACF;",
6
6
  "names": []
7
7
  }
@@ -120,7 +120,7 @@ function entityToIndexableRecord(entity) {
120
120
  };
121
121
  }
122
122
  function computeEntitiesChecksum(entities) {
123
- const content = entities.map((e) => `${e.className}:${e.tableName}:${e.fieldCount}`).sort().join("|");
123
+ const content = entities.map((e) => `${e.className}:${e.tableName}:${e.fieldCount}`).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).join("|");
124
124
  let hash = 0;
125
125
  for (let i = 0; i < content.length; i++) {
126
126
  const char = content.charCodeAt(i);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/entity-index-config.ts"],
4
- "sourcesContent": ["/**\n * Entity Schema Index Configuration\n *\n * Defines how database entity schemas are indexed in Meilisearch\n * for discovery via the discover_schema MCP tool.\n */\n\nimport type {\n SearchEntityConfig,\n SearchResultPresenter,\n IndexableRecord,\n} from '@open-mercato/search/types'\nimport type { EntityNode, EntityTriple } from './entity-graph'\n\n/**\n * Entity ID for entity schemas in the search index.\n * Following the module:entity naming convention.\n */\nexport const ENTITY_SCHEMA_ENTITY_ID = 'ai_assistant:entity_schema' as const\n\n/**\n * Tenant ID for global entity schemas.\n * Entity schemas are not tenant-scoped, so we use a special \"system\" UUID.\n * This is the nil UUID (all zeros) reserved for system-wide resources.\n */\nexport const GLOBAL_TENANT_ID = '00000000-0000-0000-0000-000000000000'\n\n/**\n * Default configuration for entity schema search.\n */\nexport const ENTITY_SCHEMA_SEARCH_CONFIG = {\n /** Maximum entities to return from search */\n defaultLimit: 10,\n /** Minimum relevance score (0-1) */\n minScore: 0.15,\n /** Strategies to use (in priority order) */\n strategies: ['fulltext', 'vector'] as const,\n} as const\n\n/**\n * Indexed entity structure with full schema information.\n */\nexport interface IndexedEntity {\n className: string\n tableName: string\n module: string\n fields: Array<{ name: string; type: string; nullable: boolean }>\n relationships: Array<{ relationship: string; target: string; property: string; nullable?: boolean }>\n}\n\n/**\n * Search entity configuration for entity schemas.\n * This configures how entities are indexed and searched.\n */\nexport const entitySchemaEntityConfig: SearchEntityConfig = {\n entityId: ENTITY_SCHEMA_ENTITY_ID,\n enabled: true,\n priority: 95, // High priority, above API endpoints\n\n /**\n * Build searchable content from an entity schema.\n */\n buildSource: (ctx) => {\n const entity = ctx.record as unknown as IndexedEntity\n const className = entity.className || ''\n const tableName = entity.tableName || ''\n const module = entity.module || ''\n const fields = entity.fields || []\n const relationships = entity.relationships || []\n\n // Build text content for embedding and fulltext search\n const textParts = [\n className,\n tableName.replace(/_/g, ' '),\n module,\n // Include field names for searchability\n ...fields.map((f) => f.name),\n // Include relationship targets for searchability\n ...relationships.map((r) => r.target),\n ]\n\n return {\n text: textParts.filter(Boolean).join(' | '),\n fields: {\n className,\n tableName,\n module,\n fieldCount: fields.length,\n relationshipCount: relationships.length,\n // Store full schema as JSON for retrieval\n schema: JSON.stringify({\n fields,\n relationships,\n }),\n },\n presenter: {\n title: className,\n subtitle: `${module} \u2022 ${fields.length} fields`,\n icon: 'lucide:database',\n },\n checksumSource: { className, tableName, fieldCount: fields.length },\n }\n },\n\n /**\n * Format result for display in search UI.\n */\n formatResult: (ctx) => {\n const entity = ctx.record as unknown as IndexedEntity\n return {\n title: entity.className,\n subtitle: `${entity.module} \u2022 ${entity.fields?.length ?? 0} fields`,\n icon: 'lucide:database',\n }\n },\n\n /**\n * Field policy for search strategies.\n */\n fieldPolicy: {\n searchable: ['className', 'tableName', 'module'],\n hashOnly: [],\n excluded: ['schema'], // Don't index the full JSON schema\n },\n}\n\n/**\n * Build search text from entity node and relationships.\n */\nfunction buildSearchText(entity: IndexedEntity): string {\n const parts = [\n entity.className,\n entity.tableName.replace(/_/g, ' '),\n entity.module,\n // Include all field names\n ...entity.fields.map((f) => f.name),\n // Include relationship targets\n ...entity.relationships.map((r) => `${r.relationship} ${r.target}`),\n ]\n return parts.filter(Boolean).join(' | ')\n}\n\n/**\n * Convert an entity schema to an indexable record for search.\n *\n * @param entity - The entity with full schema info\n * @returns IndexableRecord ready for search indexing\n */\nexport function entityToIndexableRecord(entity: IndexedEntity): IndexableRecord {\n const presenter: SearchResultPresenter = {\n title: entity.className,\n subtitle: `${entity.module} \u2022 ${entity.fields.length} fields`,\n icon: 'lucide:database',\n }\n\n return {\n entityId: ENTITY_SCHEMA_ENTITY_ID,\n recordId: entity.className,\n tenantId: GLOBAL_TENANT_ID,\n organizationId: null,\n fields: {\n className: entity.className,\n tableName: entity.tableName,\n module: entity.module,\n fieldCount: entity.fields.length,\n relationshipCount: entity.relationships.length,\n // Store full schema as JSON for retrieval\n schema: JSON.stringify({\n fields: entity.fields,\n relationships: entity.relationships,\n }),\n },\n presenter,\n text: buildSearchText(entity),\n checksumSource: {\n className: entity.className,\n tableName: entity.tableName,\n fieldCount: entity.fields.length,\n },\n }\n}\n\n/**\n * Compute a simple checksum for entity definitions.\n * Used to detect changes and avoid unnecessary re-indexing.\n */\nexport function computeEntitiesChecksum(\n entities: Array<{ className: string; tableName: string; fieldCount: number }>\n): string {\n const content = entities\n .map((e) => `${e.className}:${e.tableName}:${e.fieldCount}`)\n .sort()\n .join('|')\n\n // Simple hash using string code points\n let hash = 0\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i)\n hash = ((hash << 5) - hash + char) | 0\n }\n return hash.toString(16)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Entity Schema Index Configuration\n *\n * Defines how database entity schemas are indexed in Meilisearch\n * for discovery via the discover_schema MCP tool.\n */\n\nimport type {\n SearchEntityConfig,\n SearchResultPresenter,\n IndexableRecord,\n} from '@open-mercato/search/types'\nimport type { EntityNode, EntityTriple } from './entity-graph'\n\n/**\n * Entity ID for entity schemas in the search index.\n * Following the module:entity naming convention.\n */\nexport const ENTITY_SCHEMA_ENTITY_ID = 'ai_assistant:entity_schema' as const\n\n/**\n * Tenant ID for global entity schemas.\n * Entity schemas are not tenant-scoped, so we use a special \"system\" UUID.\n * This is the nil UUID (all zeros) reserved for system-wide resources.\n */\nexport const GLOBAL_TENANT_ID = '00000000-0000-0000-0000-000000000000'\n\n/**\n * Default configuration for entity schema search.\n */\nexport const ENTITY_SCHEMA_SEARCH_CONFIG = {\n /** Maximum entities to return from search */\n defaultLimit: 10,\n /** Minimum relevance score (0-1) */\n minScore: 0.15,\n /** Strategies to use (in priority order) */\n strategies: ['fulltext', 'vector'] as const,\n} as const\n\n/**\n * Indexed entity structure with full schema information.\n */\nexport interface IndexedEntity {\n className: string\n tableName: string\n module: string\n fields: Array<{ name: string; type: string; nullable: boolean }>\n relationships: Array<{ relationship: string; target: string; property: string; nullable?: boolean }>\n}\n\n/**\n * Search entity configuration for entity schemas.\n * This configures how entities are indexed and searched.\n */\nexport const entitySchemaEntityConfig: SearchEntityConfig = {\n entityId: ENTITY_SCHEMA_ENTITY_ID,\n enabled: true,\n priority: 95, // High priority, above API endpoints\n\n /**\n * Build searchable content from an entity schema.\n */\n buildSource: (ctx) => {\n const entity = ctx.record as unknown as IndexedEntity\n const className = entity.className || ''\n const tableName = entity.tableName || ''\n const module = entity.module || ''\n const fields = entity.fields || []\n const relationships = entity.relationships || []\n\n // Build text content for embedding and fulltext search\n const textParts = [\n className,\n tableName.replace(/_/g, ' '),\n module,\n // Include field names for searchability\n ...fields.map((f) => f.name),\n // Include relationship targets for searchability\n ...relationships.map((r) => r.target),\n ]\n\n return {\n text: textParts.filter(Boolean).join(' | '),\n fields: {\n className,\n tableName,\n module,\n fieldCount: fields.length,\n relationshipCount: relationships.length,\n // Store full schema as JSON for retrieval\n schema: JSON.stringify({\n fields,\n relationships,\n }),\n },\n presenter: {\n title: className,\n subtitle: `${module} \u2022 ${fields.length} fields`,\n icon: 'lucide:database',\n },\n checksumSource: { className, tableName, fieldCount: fields.length },\n }\n },\n\n /**\n * Format result for display in search UI.\n */\n formatResult: (ctx) => {\n const entity = ctx.record as unknown as IndexedEntity\n return {\n title: entity.className,\n subtitle: `${entity.module} \u2022 ${entity.fields?.length ?? 0} fields`,\n icon: 'lucide:database',\n }\n },\n\n /**\n * Field policy for search strategies.\n */\n fieldPolicy: {\n searchable: ['className', 'tableName', 'module'],\n hashOnly: [],\n excluded: ['schema'], // Don't index the full JSON schema\n },\n}\n\n/**\n * Build search text from entity node and relationships.\n */\nfunction buildSearchText(entity: IndexedEntity): string {\n const parts = [\n entity.className,\n entity.tableName.replace(/_/g, ' '),\n entity.module,\n // Include all field names\n ...entity.fields.map((f) => f.name),\n // Include relationship targets\n ...entity.relationships.map((r) => `${r.relationship} ${r.target}`),\n ]\n return parts.filter(Boolean).join(' | ')\n}\n\n/**\n * Convert an entity schema to an indexable record for search.\n *\n * @param entity - The entity with full schema info\n * @returns IndexableRecord ready for search indexing\n */\nexport function entityToIndexableRecord(entity: IndexedEntity): IndexableRecord {\n const presenter: SearchResultPresenter = {\n title: entity.className,\n subtitle: `${entity.module} \u2022 ${entity.fields.length} fields`,\n icon: 'lucide:database',\n }\n\n return {\n entityId: ENTITY_SCHEMA_ENTITY_ID,\n recordId: entity.className,\n tenantId: GLOBAL_TENANT_ID,\n organizationId: null,\n fields: {\n className: entity.className,\n tableName: entity.tableName,\n module: entity.module,\n fieldCount: entity.fields.length,\n relationshipCount: entity.relationships.length,\n // Store full schema as JSON for retrieval\n schema: JSON.stringify({\n fields: entity.fields,\n relationships: entity.relationships,\n }),\n },\n presenter,\n text: buildSearchText(entity),\n checksumSource: {\n className: entity.className,\n tableName: entity.tableName,\n fieldCount: entity.fields.length,\n },\n }\n}\n\n/**\n * Compute a simple checksum for entity definitions.\n * Used to detect changes and avoid unnecessary re-indexing.\n */\nexport function computeEntitiesChecksum(\n entities: Array<{ className: string; tableName: string; fieldCount: number }>\n): string {\n const content = entities\n .map((e) => `${e.className}:${e.tableName}:${e.fieldCount}`)\n .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n .join('|')\n\n // Simple hash using string code points\n let hash = 0\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i)\n hash = ((hash << 5) - hash + char) | 0\n }\n return hash.toString(16)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -207,6 +207,21 @@ const OPENROUTER_PRESET = {
207
207
  }
208
208
  ]
209
209
  };
210
+ const REQUESTY_PRESET = {
211
+ id: "requesty",
212
+ name: "Requesty",
213
+ baseURL: "https://router.requesty.ai/v1",
214
+ baseURLEnvKeys: ["REQUESTY_BASE_URL"],
215
+ envKeys: ["REQUESTY_API_KEY"],
216
+ defaultModel: "openai/gpt-4o-mini",
217
+ defaultModels: [
218
+ {
219
+ id: "openai/gpt-4o-mini",
220
+ name: "GPT-4o mini",
221
+ contextWindow: 128e3
222
+ }
223
+ ]
224
+ };
210
225
  const LM_STUDIO_PRESET = {
211
226
  id: "lm-studio",
212
227
  name: "LM Studio (local)",
@@ -226,6 +241,7 @@ const OPENAI_COMPATIBLE_PRESETS = [
226
241
  LITELLM_PRESET,
227
242
  OLLAMA_PRESET,
228
243
  OPENROUTER_PRESET,
244
+ REQUESTY_PRESET,
229
245
  LM_STUDIO_PRESET
230
246
  ];
231
247
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/openai-compatible-presets.ts"],
4
- "sourcesContent": ["/**\n * Curated registry of OpenAI-compatible LLM backends.\n *\n * Each preset is plain data \u2014 adding a new backend takes one entry in\n * this array, zero new adapter files, and zero changes to route handlers.\n * The {@link createOpenAICompatibleProvider} factory in `./llm-adapters/openai.ts`\n * turns each preset into a concrete `LlmProvider` at bootstrap time.\n *\n * Preset model catalogs are curated snapshots as of 2026-04-14 and should\n * be updated as upstream catalogs evolve. Users can always override the\n * selected model via the `OPENCODE_MODEL` env var without editing this\n * file.\n *\n * @see ./llm-adapters/openai.ts\n * @see .ai/specs/implemented/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport type { OpenAICompatiblePreset } from './llm-adapters/openai'\n\n/**\n * Standard OpenAI \u2014 default OpenAI API at api.openai.com.\n */\nconst OPENAI_PRESET: OpenAICompatiblePreset = {\n id: 'openai',\n name: 'OpenAI',\n baseURL: undefined,\n baseURLEnvKeys: ['OPENAI_BASE_URL'],\n envKeys: ['OPENAI_API_KEY', 'OPENCODE_OPENAI_API_KEY'],\n defaultModel: 'gpt-5-mini',\n defaultModels: [\n {\n id: 'gpt-5-mini',\n name: 'GPT-5 Mini',\n contextWindow: 128000,\n tags: ['budget'],\n },\n {\n id: 'gpt-5',\n name: 'GPT-5',\n contextWindow: 128000,\n tags: ['flagship'],\n },\n {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini',\n contextWindow: 128000,\n tags: ['budget'],\n },\n {\n id: 'gpt-4o',\n name: 'GPT-4o',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * DeepInfra \u2014 hosts open-weight flagship models at 3-12\u00D7 lower cost than\n * the native APIs. The curated catalog targets the AI Assistant use case\n * (routing + tool use + conversational chat).\n */\nconst DEEPINFRA_PRESET: OpenAICompatiblePreset = {\n id: 'deepinfra',\n name: 'DeepInfra',\n baseURL: 'https://api.deepinfra.com/v1/openai',\n baseURLEnvKeys: ['DEEPINFRA_BASE_URL'],\n envKeys: ['DEEPINFRA_API_KEY'],\n defaultModel: 'zai-org/GLM-5.1',\n defaultModels: [\n {\n id: 'zai-org/GLM-5.1',\n name: 'GLM-5.1 (Zhipu)',\n contextWindow: 202752,\n tags: ['flagship'],\n },\n {\n id: 'zai-org/GLM-4.7-Flash',\n name: 'GLM-4.7 Flash',\n contextWindow: 202752,\n tags: ['budget'],\n },\n {\n id: 'Qwen/Qwen3-235B-A22B-Instruct-2507',\n name: 'Qwen3 235B (MoE)',\n contextWindow: 262144,\n tags: ['flagship'],\n },\n {\n id: 'meta-llama/Llama-4-Scout-17B-16E-Instruct',\n name: 'Llama 4 Scout',\n contextWindow: 327680,\n },\n {\n id: 'deepseek-ai/DeepSeek-V3.2-Exp',\n name: 'DeepSeek V3.2',\n contextWindow: 163840,\n tags: ['reasoning'],\n },\n {\n id: 'Qwen/Qwen3-Coder-30B-A3B-Instruct',\n name: 'Qwen3 Coder 30B',\n contextWindow: 262144,\n tags: ['coding'],\n },\n ],\n}\n\n/**\n * Groq \u2014 specializes in low-latency inference on LPU hardware.\n * Best suited for snappy tool-use and routing, less so for long reasoning.\n */\nconst GROQ_PRESET: OpenAICompatiblePreset = {\n id: 'groq',\n name: 'Groq',\n baseURL: 'https://api.groq.com/openai/v1',\n baseURLEnvKeys: ['GROQ_BASE_URL'],\n envKeys: ['GROQ_API_KEY'],\n defaultModel: 'llama-3.3-70b-versatile',\n defaultModels: [\n {\n id: 'llama-3.3-70b-versatile',\n name: 'Llama 3.3 70B Versatile',\n contextWindow: 131072,\n },\n {\n id: 'llama-4-scout-17b',\n name: 'Llama 4 Scout 17B',\n contextWindow: 131072,\n },\n {\n id: 'mixtral-8x22b-32768',\n name: 'Mixtral 8x22B',\n contextWindow: 32768,\n },\n ],\n}\n\n/**\n * Together AI \u2014 broad catalog of open-weight models with per-model pricing.\n */\nconst TOGETHER_PRESET: OpenAICompatiblePreset = {\n id: 'together',\n name: 'Together AI',\n baseURL: 'https://api.together.xyz/v1',\n baseURLEnvKeys: ['TOGETHER_BASE_URL'],\n envKeys: ['TOGETHER_API_KEY'],\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n defaultModels: [\n {\n id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n name: 'Llama 3.3 70B Turbo',\n contextWindow: 131072,\n },\n {\n id: 'Qwen/Qwen2.5-72B-Instruct-Turbo',\n name: 'Qwen 2.5 72B Turbo',\n contextWindow: 32768,\n },\n ],\n}\n\n/**\n * Fireworks AI \u2014 fast inference with a curated catalog.\n */\nconst FIREWORKS_PRESET: OpenAICompatiblePreset = {\n id: 'fireworks',\n name: 'Fireworks AI',\n baseURL: 'https://api.fireworks.ai/inference/v1',\n baseURLEnvKeys: ['FIREWORKS_BASE_URL'],\n envKeys: ['FIREWORKS_API_KEY'],\n defaultModel: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n defaultModels: [\n {\n id: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n name: 'Llama 3.3 70B',\n contextWindow: 131072,\n },\n ],\n}\n\n/**\n * Azure OpenAI \u2014 enterprise Azure deployments. Base URL is deployment-\n * specific and must be provided via `AZURE_OPENAI_BASE_URL`.\n */\nconst AZURE_PRESET: OpenAICompatiblePreset = {\n id: 'azure',\n name: 'Azure OpenAI',\n baseURL: undefined,\n baseURLEnvKeys: ['AZURE_OPENAI_BASE_URL'],\n envKeys: ['AZURE_OPENAI_API_KEY'],\n defaultModel: 'gpt-5-mini',\n defaultModels: [\n {\n id: 'gpt-5-mini',\n name: 'GPT-5 Mini',\n contextWindow: 128000,\n },\n {\n id: 'gpt-5',\n name: 'GPT-5',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * LiteLLM proxy \u2014 self-hosted router for arbitrary upstream providers.\n * Base URL must be supplied via `LITELLM_BASE_URL`.\n */\nconst LITELLM_PRESET: OpenAICompatiblePreset = {\n id: 'litellm',\n name: 'LiteLLM',\n baseURL: 'http://localhost:4000/v1',\n baseURLEnvKeys: ['LITELLM_BASE_URL'],\n envKeys: ['LITELLM_API_KEY'],\n defaultModel: 'gpt-4o-mini',\n defaultModels: [\n {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini (via LiteLLM)',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * Ollama \u2014 local model runner for development and offline use.\n * Default port 11434 can be overridden via `OLLAMA_BASE_URL`.\n */\nconst OLLAMA_PRESET: OpenAICompatiblePreset = {\n id: 'ollama',\n name: 'Ollama (local)',\n baseURL: 'http://localhost:11434/v1',\n baseURLEnvKeys: ['OLLAMA_BASE_URL'],\n envKeys: ['OLLAMA_API_KEY'],\n defaultModel: 'llama3.3',\n defaultModels: [\n {\n id: 'llama3.3',\n name: 'Llama 3.3 (local)',\n contextWindow: 131072,\n },\n {\n id: 'qwen2.5-coder',\n name: 'Qwen 2.5 Coder (local)',\n contextWindow: 131072,\n tags: ['coding'],\n },\n ],\n}\n\n/**\n * OpenRouter \u2014 unified API gateway providing access to hundreds of models\n * from Anthropic, OpenAI, Google, Meta, and others via a single OpenAI-\n * compatible endpoint.\n */\nconst OPENROUTER_PRESET: OpenAICompatiblePreset = {\n id: 'openrouter',\n name: 'OpenRouter',\n baseURL: 'https://openrouter.ai/api/v1',\n baseURLEnvKeys: ['OPENROUTER_BASE_URL'],\n envKeys: ['OPENROUTER_API_KEY'],\n defaultModel: 'meta-llama/llama-3.3-70b-instruct',\n defaultModels: [\n {\n id: 'meta-llama/llama-3.3-70b-instruct',\n name: 'Llama 3.3 70B Instruct',\n contextWindow: 131072,\n },\n ],\n}\n\n/**\n * LM Studio \u2014 local model server for development and offline use.\n * Default port 1234 can be overridden via `LM_STUDIO_BASE_URL`.\n * `defaultModel` is intentionally empty \u2014 LM Studio auto-detects\n * the loaded model when the request body's `model` field is empty.\n */\nconst LM_STUDIO_PRESET: OpenAICompatiblePreset = {\n id: 'lm-studio',\n name: 'LM Studio (local)',\n baseURL: 'http://localhost:1234/v1',\n baseURLEnvKeys: ['LM_STUDIO_BASE_URL'],\n envKeys: ['LM_STUDIO_API_KEY'],\n defaultModel: '',\n defaultModels: [],\n}\n\n/**\n * Built-in presets registered at bootstrap time. Order matters \u2014 it\n * determines the default iteration order of\n * `llmProviderRegistry.resolveFirstConfigured()` when no explicit order\n * is supplied.\n */\nexport const OPENAI_COMPATIBLE_PRESETS: readonly OpenAICompatiblePreset[] = [\n OPENAI_PRESET,\n DEEPINFRA_PRESET,\n GROQ_PRESET,\n TOGETHER_PRESET,\n FIREWORKS_PRESET,\n AZURE_PRESET,\n LITELLM_PRESET,\n OLLAMA_PRESET,\n OPENROUTER_PRESET,\n LM_STUDIO_PRESET,\n]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Curated registry of OpenAI-compatible LLM backends.\n *\n * Each preset is plain data \u2014 adding a new backend takes one entry in\n * this array, zero new adapter files, and zero changes to route handlers.\n * The {@link createOpenAICompatibleProvider} factory in `./llm-adapters/openai.ts`\n * turns each preset into a concrete `LlmProvider` at bootstrap time.\n *\n * Preset model catalogs are curated snapshots as of 2026-04-14 and should\n * be updated as upstream catalogs evolve. Users can always override the\n * selected model via the `OPENCODE_MODEL` env var without editing this\n * file.\n *\n * @see ./llm-adapters/openai.ts\n * @see .ai/specs/implemented/2026-04-14-llm-provider-ports-and-adapters.md\n */\n\nimport type { OpenAICompatiblePreset } from './llm-adapters/openai'\n\n/**\n * Standard OpenAI \u2014 default OpenAI API at api.openai.com.\n */\nconst OPENAI_PRESET: OpenAICompatiblePreset = {\n id: 'openai',\n name: 'OpenAI',\n baseURL: undefined,\n baseURLEnvKeys: ['OPENAI_BASE_URL'],\n envKeys: ['OPENAI_API_KEY', 'OPENCODE_OPENAI_API_KEY'],\n defaultModel: 'gpt-5-mini',\n defaultModels: [\n {\n id: 'gpt-5-mini',\n name: 'GPT-5 Mini',\n contextWindow: 128000,\n tags: ['budget'],\n },\n {\n id: 'gpt-5',\n name: 'GPT-5',\n contextWindow: 128000,\n tags: ['flagship'],\n },\n {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini',\n contextWindow: 128000,\n tags: ['budget'],\n },\n {\n id: 'gpt-4o',\n name: 'GPT-4o',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * DeepInfra \u2014 hosts open-weight flagship models at 3-12\u00D7 lower cost than\n * the native APIs. The curated catalog targets the AI Assistant use case\n * (routing + tool use + conversational chat).\n */\nconst DEEPINFRA_PRESET: OpenAICompatiblePreset = {\n id: 'deepinfra',\n name: 'DeepInfra',\n baseURL: 'https://api.deepinfra.com/v1/openai',\n baseURLEnvKeys: ['DEEPINFRA_BASE_URL'],\n envKeys: ['DEEPINFRA_API_KEY'],\n defaultModel: 'zai-org/GLM-5.1',\n defaultModels: [\n {\n id: 'zai-org/GLM-5.1',\n name: 'GLM-5.1 (Zhipu)',\n contextWindow: 202752,\n tags: ['flagship'],\n },\n {\n id: 'zai-org/GLM-4.7-Flash',\n name: 'GLM-4.7 Flash',\n contextWindow: 202752,\n tags: ['budget'],\n },\n {\n id: 'Qwen/Qwen3-235B-A22B-Instruct-2507',\n name: 'Qwen3 235B (MoE)',\n contextWindow: 262144,\n tags: ['flagship'],\n },\n {\n id: 'meta-llama/Llama-4-Scout-17B-16E-Instruct',\n name: 'Llama 4 Scout',\n contextWindow: 327680,\n },\n {\n id: 'deepseek-ai/DeepSeek-V3.2-Exp',\n name: 'DeepSeek V3.2',\n contextWindow: 163840,\n tags: ['reasoning'],\n },\n {\n id: 'Qwen/Qwen3-Coder-30B-A3B-Instruct',\n name: 'Qwen3 Coder 30B',\n contextWindow: 262144,\n tags: ['coding'],\n },\n ],\n}\n\n/**\n * Groq \u2014 specializes in low-latency inference on LPU hardware.\n * Best suited for snappy tool-use and routing, less so for long reasoning.\n */\nconst GROQ_PRESET: OpenAICompatiblePreset = {\n id: 'groq',\n name: 'Groq',\n baseURL: 'https://api.groq.com/openai/v1',\n baseURLEnvKeys: ['GROQ_BASE_URL'],\n envKeys: ['GROQ_API_KEY'],\n defaultModel: 'llama-3.3-70b-versatile',\n defaultModels: [\n {\n id: 'llama-3.3-70b-versatile',\n name: 'Llama 3.3 70B Versatile',\n contextWindow: 131072,\n },\n {\n id: 'llama-4-scout-17b',\n name: 'Llama 4 Scout 17B',\n contextWindow: 131072,\n },\n {\n id: 'mixtral-8x22b-32768',\n name: 'Mixtral 8x22B',\n contextWindow: 32768,\n },\n ],\n}\n\n/**\n * Together AI \u2014 broad catalog of open-weight models with per-model pricing.\n */\nconst TOGETHER_PRESET: OpenAICompatiblePreset = {\n id: 'together',\n name: 'Together AI',\n baseURL: 'https://api.together.xyz/v1',\n baseURLEnvKeys: ['TOGETHER_BASE_URL'],\n envKeys: ['TOGETHER_API_KEY'],\n defaultModel: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n defaultModels: [\n {\n id: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',\n name: 'Llama 3.3 70B Turbo',\n contextWindow: 131072,\n },\n {\n id: 'Qwen/Qwen2.5-72B-Instruct-Turbo',\n name: 'Qwen 2.5 72B Turbo',\n contextWindow: 32768,\n },\n ],\n}\n\n/**\n * Fireworks AI \u2014 fast inference with a curated catalog.\n */\nconst FIREWORKS_PRESET: OpenAICompatiblePreset = {\n id: 'fireworks',\n name: 'Fireworks AI',\n baseURL: 'https://api.fireworks.ai/inference/v1',\n baseURLEnvKeys: ['FIREWORKS_BASE_URL'],\n envKeys: ['FIREWORKS_API_KEY'],\n defaultModel: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n defaultModels: [\n {\n id: 'accounts/fireworks/models/llama-v3p3-70b-instruct',\n name: 'Llama 3.3 70B',\n contextWindow: 131072,\n },\n ],\n}\n\n/**\n * Azure OpenAI \u2014 enterprise Azure deployments. Base URL is deployment-\n * specific and must be provided via `AZURE_OPENAI_BASE_URL`.\n */\nconst AZURE_PRESET: OpenAICompatiblePreset = {\n id: 'azure',\n name: 'Azure OpenAI',\n baseURL: undefined,\n baseURLEnvKeys: ['AZURE_OPENAI_BASE_URL'],\n envKeys: ['AZURE_OPENAI_API_KEY'],\n defaultModel: 'gpt-5-mini',\n defaultModels: [\n {\n id: 'gpt-5-mini',\n name: 'GPT-5 Mini',\n contextWindow: 128000,\n },\n {\n id: 'gpt-5',\n name: 'GPT-5',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * LiteLLM proxy \u2014 self-hosted router for arbitrary upstream providers.\n * Base URL must be supplied via `LITELLM_BASE_URL`.\n */\nconst LITELLM_PRESET: OpenAICompatiblePreset = {\n id: 'litellm',\n name: 'LiteLLM',\n baseURL: 'http://localhost:4000/v1',\n baseURLEnvKeys: ['LITELLM_BASE_URL'],\n envKeys: ['LITELLM_API_KEY'],\n defaultModel: 'gpt-4o-mini',\n defaultModels: [\n {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini (via LiteLLM)',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * Ollama \u2014 local model runner for development and offline use.\n * Default port 11434 can be overridden via `OLLAMA_BASE_URL`.\n */\nconst OLLAMA_PRESET: OpenAICompatiblePreset = {\n id: 'ollama',\n name: 'Ollama (local)',\n baseURL: 'http://localhost:11434/v1',\n baseURLEnvKeys: ['OLLAMA_BASE_URL'],\n envKeys: ['OLLAMA_API_KEY'],\n defaultModel: 'llama3.3',\n defaultModels: [\n {\n id: 'llama3.3',\n name: 'Llama 3.3 (local)',\n contextWindow: 131072,\n },\n {\n id: 'qwen2.5-coder',\n name: 'Qwen 2.5 Coder (local)',\n contextWindow: 131072,\n tags: ['coding'],\n },\n ],\n}\n\n/**\n * OpenRouter \u2014 unified API gateway providing access to hundreds of models\n * from Anthropic, OpenAI, Google, Meta, and others via a single OpenAI-\n * compatible endpoint.\n */\nconst OPENROUTER_PRESET: OpenAICompatiblePreset = {\n id: 'openrouter',\n name: 'OpenRouter',\n baseURL: 'https://openrouter.ai/api/v1',\n baseURLEnvKeys: ['OPENROUTER_BASE_URL'],\n envKeys: ['OPENROUTER_API_KEY'],\n defaultModel: 'meta-llama/llama-3.3-70b-instruct',\n defaultModels: [\n {\n id: 'meta-llama/llama-3.3-70b-instruct',\n name: 'Llama 3.3 70B Instruct',\n contextWindow: 131072,\n },\n ],\n}\n\n/**\n * Requesty \u2014 unified OpenAI-compatible LLM gateway providing access to\n * models from many providers via a single endpoint, using `provider/model`\n * naming (e.g. `openai/gpt-4o-mini`).\n */\nconst REQUESTY_PRESET: OpenAICompatiblePreset = {\n id: 'requesty',\n name: 'Requesty',\n baseURL: 'https://router.requesty.ai/v1',\n baseURLEnvKeys: ['REQUESTY_BASE_URL'],\n envKeys: ['REQUESTY_API_KEY'],\n defaultModel: 'openai/gpt-4o-mini',\n defaultModels: [\n {\n id: 'openai/gpt-4o-mini',\n name: 'GPT-4o mini',\n contextWindow: 128000,\n },\n ],\n}\n\n/**\n * LM Studio \u2014 local model server for development and offline use.\n * Default port 1234 can be overridden via `LM_STUDIO_BASE_URL`.\n * `defaultModel` is intentionally empty \u2014 LM Studio auto-detects\n * the loaded model when the request body's `model` field is empty.\n */\nconst LM_STUDIO_PRESET: OpenAICompatiblePreset = {\n id: 'lm-studio',\n name: 'LM Studio (local)',\n baseURL: 'http://localhost:1234/v1',\n baseURLEnvKeys: ['LM_STUDIO_BASE_URL'],\n envKeys: ['LM_STUDIO_API_KEY'],\n defaultModel: '',\n defaultModels: [],\n}\n\n/**\n * Built-in presets registered at bootstrap time. Order matters \u2014 it\n * determines the default iteration order of\n * `llmProviderRegistry.resolveFirstConfigured()` when no explicit order\n * is supplied.\n */\nexport const OPENAI_COMPATIBLE_PRESETS: readonly OpenAICompatiblePreset[] = [\n OPENAI_PRESET,\n DEEPINFRA_PRESET,\n GROQ_PRESET,\n TOGETHER_PRESET,\n FIREWORKS_PRESET,\n AZURE_PRESET,\n LITELLM_PRESET,\n OLLAMA_PRESET,\n OPENROUTER_PRESET,\n REQUESTY_PRESET,\n LM_STUDIO_PRESET,\n]\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -105,7 +105,7 @@ function toolToIndexableRecord(tool, moduleId) {
105
105
  };
106
106
  }
107
107
  function computeToolsChecksum(tools) {
108
- const content = tools.map((t) => `${t.name}:${t.description}`).sort().join("|");
108
+ const content = tools.map((t) => `${t.name}:${t.description}`).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).join("|");
109
109
  let hash = 0;
110
110
  for (let i = 0; i < content.length; i++) {
111
111
  const char = content.charCodeAt(i);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/tool-index-config.ts"],
4
- "sourcesContent": ["import type {\n SearchEntityConfig,\n SearchResultPresenter,\n IndexableRecord,\n} from '@open-mercato/search/types'\nimport type { McpToolDefinition } from './types'\n\n/**\n * Entity ID for MCP tools in the search index.\n * Following the module:entity naming convention.\n */\nexport const TOOL_ENTITY_ID = 'ai_assistant:mcp_tool' as const\n\n/**\n * Tenant ID for global tools.\n * Tools are not tenant-scoped, so we use a special \"system\" UUID.\n * This is the nil UUID (all zeros) reserved for system-wide resources.\n */\nexport const GLOBAL_TENANT_ID = '00000000-0000-0000-0000-000000000000'\n\n/**\n * Essential tools that should always be available regardless of search results.\n * These provide fundamental functionality for the AI assistant.\n */\nexport const ESSENTIAL_TOOLS = [\n 'context_whoami', // Auth context awareness\n 'search_query', // Universal search\n 'search_schema', // Entity discovery\n 'search_status', // Check integrations\n] as const\n\n/**\n * Default configuration for tool search.\n */\nexport const TOOL_SEARCH_CONFIG = {\n /** Maximum tools to return from search */\n defaultLimit: 12,\n /** Minimum relevance score (0-1) */\n minScore: 0.2,\n /** Strategies to use (in priority order) */\n strategies: ['fulltext', 'vector', 'tokens'] as const,\n} as const\n\n/**\n * Search entity configuration for MCP tools.\n * This configures how tools are indexed and searched.\n */\nexport const toolSearchEntityConfig: SearchEntityConfig = {\n entityId: TOOL_ENTITY_ID,\n enabled: true,\n priority: 100, // High priority for tool results\n\n /**\n * Build searchable content from a tool definition.\n */\n buildSource: (ctx) => {\n const tool = ctx.record as unknown as McpToolDefinition\n const name = tool.name || ''\n const description = tool.description || ''\n const moduleId = (ctx.record as Record<string, unknown>).moduleId as string | undefined\n\n // Normalize name: replace underscores/dots with spaces for better search\n const normalizedName = name.replace(/[_.-]/g, ' ')\n\n // Build text content for embedding and fulltext search\n const textContent = [\n normalizedName,\n description,\n moduleId ? `module ${moduleId}` : '',\n ]\n .filter(Boolean)\n .join(' | ')\n\n return {\n text: textContent,\n fields: {\n name: normalizedName,\n originalName: name,\n description,\n moduleId: moduleId ?? null,\n requiredFeatures: tool.requiredFeatures ?? [],\n },\n presenter: {\n title: name,\n subtitle: description.slice(0, 100),\n icon: 'tool',\n },\n checksumSource: { name, description, moduleId },\n }\n },\n\n /**\n * Format result for display in search UI.\n */\n formatResult: (ctx) => {\n const tool = ctx.record as unknown as McpToolDefinition\n return {\n title: tool.name || 'Unknown Tool',\n subtitle: (tool.description || '').slice(0, 100),\n icon: 'tool',\n }\n },\n\n /**\n * Field policy for search strategies.\n */\n fieldPolicy: {\n searchable: ['name', 'description', 'moduleId'],\n hashOnly: [],\n excluded: ['requiredFeatures', 'inputSchema', 'handler'],\n },\n}\n\n/**\n * Convert an MCP tool definition to an indexable record for search.\n *\n * @param tool - The tool definition to index\n * @param moduleId - The module that registered this tool\n * @returns IndexableRecord ready for search indexing\n */\nexport function toolToIndexableRecord(\n tool: McpToolDefinition,\n moduleId?: string\n): IndexableRecord {\n const normalizedName = tool.name.replace(/[_.-]/g, ' ')\n const description = tool.description || ''\n\n // Build text for vector embedding\n const embeddingText = `${normalizedName} | ${description}`\n\n const presenter: SearchResultPresenter = {\n title: tool.name,\n subtitle: description.slice(0, 100),\n icon: 'tool',\n }\n\n return {\n entityId: TOOL_ENTITY_ID,\n recordId: tool.name,\n tenantId: GLOBAL_TENANT_ID,\n organizationId: null,\n fields: {\n name: normalizedName,\n originalName: tool.name,\n description,\n moduleId: moduleId ?? null,\n requiredFeatures: tool.requiredFeatures ?? [],\n },\n presenter,\n text: embeddingText,\n checksumSource: {\n name: tool.name,\n description,\n moduleId,\n },\n }\n}\n\n/**\n * Compute a simple checksum for tool definitions.\n * Used to detect changes and avoid unnecessary re-indexing.\n */\nexport function computeToolsChecksum(\n tools: Array<{ name: string; description: string }>\n): string {\n const content = tools\n .map((t) => `${t.name}:${t.description}`)\n .sort()\n .join('|')\n\n // Simple hash using string code points\n let hash = 0\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i)\n hash = ((hash << 5) - hash + char) | 0\n }\n return hash.toString(16)\n}\n"],
5
- "mappings": "AAWO,MAAM,iBAAiB;AAOvB,MAAM,mBAAmB;AAMzB,MAAM,kBAAkB;AAAA,EAC7B;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAKO,MAAM,qBAAqB;AAAA;AAAA,EAEhC,cAAc;AAAA;AAAA,EAEd,UAAU;AAAA;AAAA,EAEV,YAAY,CAAC,YAAY,UAAU,QAAQ;AAC7C;AAMO,MAAM,yBAA6C;AAAA,EACxD,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,EAKV,aAAa,CAAC,QAAQ;AACpB,UAAM,OAAO,IAAI;AACjB,UAAM,OAAO,KAAK,QAAQ;AAC1B,UAAM,cAAc,KAAK,eAAe;AACxC,UAAM,WAAY,IAAI,OAAmC;AAGzD,UAAM,iBAAiB,KAAK,QAAQ,UAAU,GAAG;AAGjD,UAAM,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,MACA,WAAW,UAAU,QAAQ,KAAK;AAAA,IACpC,EACG,OAAO,OAAO,EACd,KAAK,KAAK;AAEb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,cAAc;AAAA,QACd;AAAA,QACA,UAAU,YAAY;AAAA,QACtB,kBAAkB,KAAK,oBAAoB,CAAC;AAAA,MAC9C;AAAA,MACA,WAAW;AAAA,QACT,OAAO;AAAA,QACP,UAAU,YAAY,MAAM,GAAG,GAAG;AAAA,QAClC,MAAM;AAAA,MACR;AAAA,MACA,gBAAgB,EAAE,MAAM,aAAa,SAAS;AAAA,IAChD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,CAAC,QAAQ;AACrB,UAAM,OAAO,IAAI;AACjB,WAAO;AAAA,MACL,OAAO,KAAK,QAAQ;AAAA,MACpB,WAAW,KAAK,eAAe,IAAI,MAAM,GAAG,GAAG;AAAA,MAC/C,MAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa;AAAA,IACX,YAAY,CAAC,QAAQ,eAAe,UAAU;AAAA,IAC9C,UAAU,CAAC;AAAA,IACX,UAAU,CAAC,oBAAoB,eAAe,SAAS;AAAA,EACzD;AACF;AASO,SAAS,sBACd,MACA,UACiB;AACjB,QAAM,iBAAiB,KAAK,KAAK,QAAQ,UAAU,GAAG;AACtD,QAAM,cAAc,KAAK,eAAe;AAGxC,QAAM,gBAAgB,GAAG,cAAc,MAAM,WAAW;AAExD,QAAM,YAAmC;AAAA,IACvC,OAAO,KAAK;AAAA,IACZ,UAAU,YAAY,MAAM,GAAG,GAAG;AAAA,IAClC,MAAM;AAAA,EACR;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,UAAU,KAAK;AAAA,IACf,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,cAAc,KAAK;AAAA,MACnB;AAAA,MACA,UAAU,YAAY;AAAA,MACtB,kBAAkB,KAAK,oBAAoB,CAAC;AAAA,IAC9C;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN,gBAAgB;AAAA,MACd,MAAM,KAAK;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAMO,SAAS,qBACd,OACQ;AACR,QAAM,UAAU,MACb,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,WAAW,EAAE,EACvC,KAAK,EACL,KAAK,GAAG;AAGX,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,OAAO,QAAQ,WAAW,CAAC;AACjC,YAAS,QAAQ,KAAK,OAAO,OAAQ;AAAA,EACvC;AACA,SAAO,KAAK,SAAS,EAAE;AACzB;",
4
+ "sourcesContent": ["import type {\n SearchEntityConfig,\n SearchResultPresenter,\n IndexableRecord,\n} from '@open-mercato/search/types'\nimport type { McpToolDefinition } from './types'\n\n/**\n * Entity ID for MCP tools in the search index.\n * Following the module:entity naming convention.\n */\nexport const TOOL_ENTITY_ID = 'ai_assistant:mcp_tool' as const\n\n/**\n * Tenant ID for global tools.\n * Tools are not tenant-scoped, so we use a special \"system\" UUID.\n * This is the nil UUID (all zeros) reserved for system-wide resources.\n */\nexport const GLOBAL_TENANT_ID = '00000000-0000-0000-0000-000000000000'\n\n/**\n * Essential tools that should always be available regardless of search results.\n * These provide fundamental functionality for the AI assistant.\n */\nexport const ESSENTIAL_TOOLS = [\n 'context_whoami', // Auth context awareness\n 'search_query', // Universal search\n 'search_schema', // Entity discovery\n 'search_status', // Check integrations\n] as const\n\n/**\n * Default configuration for tool search.\n */\nexport const TOOL_SEARCH_CONFIG = {\n /** Maximum tools to return from search */\n defaultLimit: 12,\n /** Minimum relevance score (0-1) */\n minScore: 0.2,\n /** Strategies to use (in priority order) */\n strategies: ['fulltext', 'vector', 'tokens'] as const,\n} as const\n\n/**\n * Search entity configuration for MCP tools.\n * This configures how tools are indexed and searched.\n */\nexport const toolSearchEntityConfig: SearchEntityConfig = {\n entityId: TOOL_ENTITY_ID,\n enabled: true,\n priority: 100, // High priority for tool results\n\n /**\n * Build searchable content from a tool definition.\n */\n buildSource: (ctx) => {\n const tool = ctx.record as unknown as McpToolDefinition\n const name = tool.name || ''\n const description = tool.description || ''\n const moduleId = (ctx.record as Record<string, unknown>).moduleId as string | undefined\n\n // Normalize name: replace underscores/dots with spaces for better search\n const normalizedName = name.replace(/[_.-]/g, ' ')\n\n // Build text content for embedding and fulltext search\n const textContent = [\n normalizedName,\n description,\n moduleId ? `module ${moduleId}` : '',\n ]\n .filter(Boolean)\n .join(' | ')\n\n return {\n text: textContent,\n fields: {\n name: normalizedName,\n originalName: name,\n description,\n moduleId: moduleId ?? null,\n requiredFeatures: tool.requiredFeatures ?? [],\n },\n presenter: {\n title: name,\n subtitle: description.slice(0, 100),\n icon: 'tool',\n },\n checksumSource: { name, description, moduleId },\n }\n },\n\n /**\n * Format result for display in search UI.\n */\n formatResult: (ctx) => {\n const tool = ctx.record as unknown as McpToolDefinition\n return {\n title: tool.name || 'Unknown Tool',\n subtitle: (tool.description || '').slice(0, 100),\n icon: 'tool',\n }\n },\n\n /**\n * Field policy for search strategies.\n */\n fieldPolicy: {\n searchable: ['name', 'description', 'moduleId'],\n hashOnly: [],\n excluded: ['requiredFeatures', 'inputSchema', 'handler'],\n },\n}\n\n/**\n * Convert an MCP tool definition to an indexable record for search.\n *\n * @param tool - The tool definition to index\n * @param moduleId - The module that registered this tool\n * @returns IndexableRecord ready for search indexing\n */\nexport function toolToIndexableRecord(\n tool: McpToolDefinition,\n moduleId?: string\n): IndexableRecord {\n const normalizedName = tool.name.replace(/[_.-]/g, ' ')\n const description = tool.description || ''\n\n // Build text for vector embedding\n const embeddingText = `${normalizedName} | ${description}`\n\n const presenter: SearchResultPresenter = {\n title: tool.name,\n subtitle: description.slice(0, 100),\n icon: 'tool',\n }\n\n return {\n entityId: TOOL_ENTITY_ID,\n recordId: tool.name,\n tenantId: GLOBAL_TENANT_ID,\n organizationId: null,\n fields: {\n name: normalizedName,\n originalName: tool.name,\n description,\n moduleId: moduleId ?? null,\n requiredFeatures: tool.requiredFeatures ?? [],\n },\n presenter,\n text: embeddingText,\n checksumSource: {\n name: tool.name,\n description,\n moduleId,\n },\n }\n}\n\n/**\n * Compute a simple checksum for tool definitions.\n * Used to detect changes and avoid unnecessary re-indexing.\n */\nexport function computeToolsChecksum(\n tools: Array<{ name: string; description: string }>\n): string {\n const content = tools\n .map((t) => `${t.name}:${t.description}`)\n .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\n .join('|')\n\n // Simple hash using string code points\n let hash = 0\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i)\n hash = ((hash << 5) - hash + char) | 0\n }\n return hash.toString(16)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ai-assistant",
3
- "version": "0.6.6-develop.6339.1.193c6c7c71",
3
+ "version": "0.6.6-develop.6344.1.c4b17c07c4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -99,16 +99,16 @@
99
99
  "zod-to-json-schema": "^3.25.2"
100
100
  },
101
101
  "peerDependencies": {
102
- "@open-mercato/shared": "0.6.6-develop.6339.1.193c6c7c71",
103
- "@open-mercato/ui": "0.6.6-develop.6339.1.193c6c7c71",
102
+ "@open-mercato/shared": "0.6.6-develop.6344.1.c4b17c07c4",
103
+ "@open-mercato/ui": "0.6.6-develop.6344.1.c4b17c07c4",
104
104
  "react": "^19.0.0",
105
105
  "react-dom": "^19.0.0",
106
106
  "zod": ">=3.23.0"
107
107
  },
108
108
  "devDependencies": {
109
- "@open-mercato/cli": "0.6.6-develop.6339.1.193c6c7c71",
110
- "@open-mercato/shared": "0.6.6-develop.6339.1.193c6c7c71",
111
- "@open-mercato/ui": "0.6.6-develop.6339.1.193c6c7c71",
109
+ "@open-mercato/cli": "0.6.6-develop.6344.1.c4b17c07c4",
110
+ "@open-mercato/shared": "0.6.6-develop.6344.1.c4b17c07c4",
111
+ "@open-mercato/ui": "0.6.6-develop.6344.1.c4b17c07c4",
112
112
  "@types/react": "^19.2.17",
113
113
  "@types/react-dom": "^19.2.3",
114
114
  "react": "19.2.7",
@@ -190,13 +190,13 @@ const listTools: ModuleCli = {
190
190
  }
191
191
 
192
192
  // Sort modules alphabetically
193
- const sortedModules = Array.from(byModule.keys()).sort()
193
+ const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
194
194
 
195
195
  for (const module of sortedModules) {
196
196
  const tools = byModule.get(module)!
197
197
  console.log(`${module} (${tools.length} tools):`)
198
198
 
199
- for (const name of tools.sort()) {
199
+ for (const name of tools.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))) {
200
200
  const tool = registry.getTool(name)
201
201
  if (!tool) continue
202
202
 
@@ -327,7 +327,7 @@ const testTools: ModuleCli = {
327
327
  list.push(record)
328
328
  byModule.set(record.module, list)
329
329
  }
330
- const sortedModules = Array.from(byModule.keys()).sort()
330
+ const sortedModules = Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
331
331
  for (const moduleId of sortedModules) {
332
332
  const list = byModule.get(moduleId)!
333
333
  console.log('')
@@ -341,6 +341,6 @@ export function getGraphStats(graph: EntityGraph): {
341
341
  return {
342
342
  totalEntities: graph.nodes.length,
343
343
  totalRelationships: graph.edges.length,
344
- modules: Array.from(byModule.keys()).sort(),
344
+ modules: Array.from(byModule.keys()).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),
345
345
  }
346
346
  }
@@ -189,7 +189,7 @@ export function computeEntitiesChecksum(
189
189
  ): string {
190
190
  const content = entities
191
191
  .map((e) => `${e.className}:${e.tableName}:${e.fieldCount}`)
192
- .sort()
192
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
193
193
  .join('|')
194
194
 
195
195
  // Simple hash using string code points
@@ -270,6 +270,27 @@ const OPENROUTER_PRESET: OpenAICompatiblePreset = {
270
270
  ],
271
271
  }
272
272
 
273
+ /**
274
+ * Requesty — unified OpenAI-compatible LLM gateway providing access to
275
+ * models from many providers via a single endpoint, using `provider/model`
276
+ * naming (e.g. `openai/gpt-4o-mini`).
277
+ */
278
+ const REQUESTY_PRESET: OpenAICompatiblePreset = {
279
+ id: 'requesty',
280
+ name: 'Requesty',
281
+ baseURL: 'https://router.requesty.ai/v1',
282
+ baseURLEnvKeys: ['REQUESTY_BASE_URL'],
283
+ envKeys: ['REQUESTY_API_KEY'],
284
+ defaultModel: 'openai/gpt-4o-mini',
285
+ defaultModels: [
286
+ {
287
+ id: 'openai/gpt-4o-mini',
288
+ name: 'GPT-4o mini',
289
+ contextWindow: 128000,
290
+ },
291
+ ],
292
+ }
293
+
273
294
  /**
274
295
  * LM Studio — local model server for development and offline use.
275
296
  * Default port 1234 can be overridden via `LM_STUDIO_BASE_URL`.
@@ -302,5 +323,6 @@ export const OPENAI_COMPATIBLE_PRESETS: readonly OpenAICompatiblePreset[] = [
302
323
  LITELLM_PRESET,
303
324
  OLLAMA_PRESET,
304
325
  OPENROUTER_PRESET,
326
+ REQUESTY_PRESET,
305
327
  LM_STUDIO_PRESET,
306
328
  ]
@@ -165,7 +165,7 @@ export function computeToolsChecksum(
165
165
  ): string {
166
166
  const content = tools
167
167
  .map((t) => `${t.name}:${t.description}`)
168
- .sort()
168
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
169
169
  .join('|')
170
170
 
171
171
  // Simple hash using string code points