@open-mercato/ai-assistant 0.4.11-develop.2384.32517eccbc → 0.4.11-develop.2516.30653cfe18

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.
@@ -5,6 +5,13 @@ import {
5
5
  searchEndpoints,
6
6
  simplifyRequestBodySchema
7
7
  } from "./api-endpoint-index.js";
8
+ import { fetchWithTimeout, resolveTimeoutMs } from "@open-mercato/shared/lib/http/fetchWithTimeout";
9
+ const DEFAULT_AI_API_REQUEST_TIMEOUT_MS = 3e4;
10
+ function resolveAiApiRequestTimeoutMs() {
11
+ const raw = process.env.AI_API_REQUEST_TIMEOUT_MS;
12
+ const parsed = raw ? Number.parseInt(raw, 10) : void 0;
13
+ return resolveTimeoutMs(parsed, DEFAULT_AI_API_REQUEST_TIMEOUT_MS);
14
+ }
8
15
  async function loadApiDiscoveryTools() {
9
16
  const endpoints = await getApiEndpoints();
10
17
  console.error(`[API Discovery] ${endpoints.length} endpoints available for discovery`);
@@ -119,10 +126,11 @@ Confirm with user before POST/PUT/DELETE operations.`,
119
126
  if (ctx.tenantId) headers["X-Tenant-Id"] = ctx.tenantId;
120
127
  if (ctx.organizationId) headers["X-Organization-Id"] = ctx.organizationId;
121
128
  try {
122
- const response = await fetch(url, {
129
+ const response = await fetchWithTimeout(url, {
123
130
  method,
124
131
  headers,
125
- body: requestBody ? JSON.stringify(requestBody) : void 0
132
+ body: requestBody ? JSON.stringify(requestBody) : void 0,
133
+ timeoutMs: resolveAiApiRequestTimeoutMs()
126
134
  });
127
135
  const responseText = await response.text();
128
136
  if (!response.ok) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/api-discovery-tools.ts"],
4
- "sourcesContent": ["/**\n * API Discovery Tools\n *\n * Meta-tools for discovering and executing API endpoints via search.\n *\n * 2 tools total:\n * - find_api: Search for endpoints (includes schema summary)\n * - call_api: Execute an endpoint\n */\n\nimport { z } from 'zod'\nimport { registerMcpTool } from './tool-registry'\nimport type { McpToolContext } from './types'\nimport {\n getApiEndpoints,\n searchEndpoints,\n simplifyRequestBodySchema,\n} from './api-endpoint-index'\n\n/**\n * Load API discovery tools into the registry\n */\nexport async function loadApiDiscoveryTools(): Promise<number> {\n // Ensure endpoints are parsed and cached\n const endpoints = await getApiEndpoints()\n console.error(`[API Discovery] ${endpoints.length} endpoints available for discovery`)\n\n // Register the two discovery tools\n registerApiDiscoverTool()\n registerApiExecuteTool()\n\n return 2\n}\n\n/**\n * find_api - Find relevant API endpoints based on a query.\n * Enhanced to include request body schema summary.\n */\nfunction registerApiDiscoverTool(): void {\n registerMcpTool(\n {\n name: 'find_api',\n description: `Search for API endpoints. Use discover_schema first to understand entity fields, then find_api to get the endpoint schema.\n\nReturns: path, method, operationId, parameters, and requestBody schema showing required fields and structure.\n\nWorkflow: discover_schema(\"Company\") \u2192 understand fields \u2192 find_api(\"update company\") \u2192 see request body schema \u2192 call_api`,\n inputSchema: z.object({\n query: z\n .string()\n .describe('Natural language query to find endpoints (e.g., \"update company\", \"create order\", \"list customers\")'),\n method: z\n .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])\n .optional()\n .describe('Filter by HTTP method'),\n limit: z.number().optional().default(10).describe('Max results to return (default: 10)'),\n }),\n requiredFeatures: [], // Available to all authenticated users\n handler: async (input: { query: string; method?: string; limit?: number }, ctx) => {\n const { query, method, limit = 10 } = input\n\n // Search for matching endpoints\n const searchService = ctx.container?.resolve<any>('searchService')\n const matches = await searchEndpoints(searchService, query, { limit, method })\n\n if (matches.length === 0) {\n return {\n success: true,\n message: 'No matching endpoints found. Try different search terms.',\n endpoints: [],\n suggestions: [\n 'Try broader terms like \"customer\", \"company\", \"order\", or \"sales\"',\n 'Use method filter to narrow results (e.g., method: \"PUT\" for updates)',\n 'Use discover_schema to find entity names first, then search for those entity names',\n ],\n }\n }\n\n // Format results for LLM consumption - now includes schema\n const results = matches.map((endpoint) => {\n const result: Record<string, unknown> = {\n operationId: endpoint.operationId,\n method: endpoint.method,\n path: endpoint.path,\n description: endpoint.description || endpoint.summary,\n tags: endpoint.tags,\n parameters: endpoint.parameters.map((p) => ({\n name: p.name,\n in: p.in,\n required: p.required,\n type: p.type,\n })),\n }\n\n // Include request body schema for mutation endpoints\n if (endpoint.requestBodySchema) {\n const simplifiedSchema = simplifyRequestBodySchema(endpoint.requestBodySchema)\n if (simplifiedSchema) {\n result.requestBody = simplifiedSchema\n }\n }\n\n return result\n })\n\n return {\n success: true,\n message: `Found ${results.length} matching endpoint(s)`,\n endpoints: results,\n hint: 'Use call_api with the method, path, and body structure shown in requestBody schema above.',\n }\n },\n },\n { moduleId: 'api' }\n )\n}\n\n/**\n * call_api - Execute an API endpoint\n */\nfunction registerApiExecuteTool(): void {\n registerMcpTool(\n {\n name: 'call_api',\n description: `Execute an API call using path and body structure from find_api results.\n\nConfirm with user before POST/PUT/DELETE operations.`,\n inputSchema: z.object({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).describe('HTTP method from find_api result'),\n path: z\n .string()\n .describe('API path from find_api result (e.g., /api/customers/companies)'),\n query: z\n .record(z.string(), z.string())\n .optional()\n .describe('Query parameters as key-value pairs'),\n body: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Request body matching the schema from find_api'),\n }),\n requiredFeatures: [], // ACL checked at API level\n handler: async (\n input: {\n method: string\n path: string\n query?: Record<string, string>\n body?: Record<string, unknown>\n },\n ctx: McpToolContext\n ) => {\n const { method, path, query, body } = input\n\n // Build URL\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n // Ensure path starts with /api\n const apiPath = path.startsWith('/api') ? path : `/api${path}`\n let url = `${baseUrl}${apiPath}`\n\n // Add query parameters\n const queryParams = { ...query }\n\n // Add context to query for GET, to body for mutations\n if (method === 'GET') {\n if (ctx.tenantId) queryParams.tenantId = ctx.tenantId\n if (ctx.organizationId) queryParams.organizationId = ctx.organizationId\n }\n\n if (Object.keys(queryParams).length > 0) {\n const separator = url.includes('?') ? '&' : '?'\n url += separator + new URLSearchParams(queryParams).toString()\n }\n\n // Build body with context\n let requestBody: Record<string, unknown> | undefined\n if (['POST', 'PUT', 'PATCH'].includes(method)) {\n requestBody = { ...body }\n if (ctx.tenantId) requestBody.tenantId = ctx.tenantId\n if (ctx.organizationId) requestBody.organizationId = ctx.organizationId\n }\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n if (ctx.apiKeySecret) headers['X-API-Key'] = ctx.apiKeySecret\n if (ctx.tenantId) headers['X-Tenant-Id'] = ctx.tenantId\n if (ctx.organizationId) headers['X-Organization-Id'] = ctx.organizationId\n\n // Execute request\n try {\n const response = await fetch(url, {\n method,\n headers,\n body: requestBody ? JSON.stringify(requestBody) : undefined,\n })\n\n const responseText = await response.text()\n\n if (!response.ok) {\n return {\n success: false,\n statusCode: response.status,\n error: `API error ${response.status}`,\n details: tryParseJson(responseText),\n }\n }\n\n return {\n success: true,\n statusCode: response.status,\n data: tryParseJson(responseText),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Request failed',\n }\n }\n },\n },\n { moduleId: 'api' }\n )\n}\n\n/**\n * Try to parse JSON, return original string if fails\n */\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * API Discovery Tools\n *\n * Meta-tools for discovering and executing API endpoints via search.\n *\n * 2 tools total:\n * - find_api: Search for endpoints (includes schema summary)\n * - call_api: Execute an endpoint\n */\n\nimport { z } from 'zod'\nimport { registerMcpTool } from './tool-registry'\nimport type { McpToolContext } from './types'\nimport {\n getApiEndpoints,\n searchEndpoints,\n simplifyRequestBodySchema,\n} from './api-endpoint-index'\nimport { fetchWithTimeout, resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\n\nconst DEFAULT_AI_API_REQUEST_TIMEOUT_MS = 30_000\n\nfunction resolveAiApiRequestTimeoutMs(): number {\n const raw = process.env.AI_API_REQUEST_TIMEOUT_MS\n const parsed = raw ? Number.parseInt(raw, 10) : undefined\n return resolveTimeoutMs(parsed, DEFAULT_AI_API_REQUEST_TIMEOUT_MS)\n}\n\n/**\n * Load API discovery tools into the registry\n */\nexport async function loadApiDiscoveryTools(): Promise<number> {\n // Ensure endpoints are parsed and cached\n const endpoints = await getApiEndpoints()\n console.error(`[API Discovery] ${endpoints.length} endpoints available for discovery`)\n\n // Register the two discovery tools\n registerApiDiscoverTool()\n registerApiExecuteTool()\n\n return 2\n}\n\n/**\n * find_api - Find relevant API endpoints based on a query.\n * Enhanced to include request body schema summary.\n */\nfunction registerApiDiscoverTool(): void {\n registerMcpTool(\n {\n name: 'find_api',\n description: `Search for API endpoints. Use discover_schema first to understand entity fields, then find_api to get the endpoint schema.\n\nReturns: path, method, operationId, parameters, and requestBody schema showing required fields and structure.\n\nWorkflow: discover_schema(\"Company\") \u2192 understand fields \u2192 find_api(\"update company\") \u2192 see request body schema \u2192 call_api`,\n inputSchema: z.object({\n query: z\n .string()\n .describe('Natural language query to find endpoints (e.g., \"update company\", \"create order\", \"list customers\")'),\n method: z\n .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])\n .optional()\n .describe('Filter by HTTP method'),\n limit: z.number().optional().default(10).describe('Max results to return (default: 10)'),\n }),\n requiredFeatures: [], // Available to all authenticated users\n handler: async (input: { query: string; method?: string; limit?: number }, ctx) => {\n const { query, method, limit = 10 } = input\n\n // Search for matching endpoints\n const searchService = ctx.container?.resolve<any>('searchService')\n const matches = await searchEndpoints(searchService, query, { limit, method })\n\n if (matches.length === 0) {\n return {\n success: true,\n message: 'No matching endpoints found. Try different search terms.',\n endpoints: [],\n suggestions: [\n 'Try broader terms like \"customer\", \"company\", \"order\", or \"sales\"',\n 'Use method filter to narrow results (e.g., method: \"PUT\" for updates)',\n 'Use discover_schema to find entity names first, then search for those entity names',\n ],\n }\n }\n\n // Format results for LLM consumption - now includes schema\n const results = matches.map((endpoint) => {\n const result: Record<string, unknown> = {\n operationId: endpoint.operationId,\n method: endpoint.method,\n path: endpoint.path,\n description: endpoint.description || endpoint.summary,\n tags: endpoint.tags,\n parameters: endpoint.parameters.map((p) => ({\n name: p.name,\n in: p.in,\n required: p.required,\n type: p.type,\n })),\n }\n\n // Include request body schema for mutation endpoints\n if (endpoint.requestBodySchema) {\n const simplifiedSchema = simplifyRequestBodySchema(endpoint.requestBodySchema)\n if (simplifiedSchema) {\n result.requestBody = simplifiedSchema\n }\n }\n\n return result\n })\n\n return {\n success: true,\n message: `Found ${results.length} matching endpoint(s)`,\n endpoints: results,\n hint: 'Use call_api with the method, path, and body structure shown in requestBody schema above.',\n }\n },\n },\n { moduleId: 'api' }\n )\n}\n\n/**\n * call_api - Execute an API endpoint\n */\nfunction registerApiExecuteTool(): void {\n registerMcpTool(\n {\n name: 'call_api',\n description: `Execute an API call using path and body structure from find_api results.\n\nConfirm with user before POST/PUT/DELETE operations.`,\n inputSchema: z.object({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).describe('HTTP method from find_api result'),\n path: z\n .string()\n .describe('API path from find_api result (e.g., /api/customers/companies)'),\n query: z\n .record(z.string(), z.string())\n .optional()\n .describe('Query parameters as key-value pairs'),\n body: z\n .record(z.string(), z.unknown())\n .optional()\n .describe('Request body matching the schema from find_api'),\n }),\n requiredFeatures: [], // ACL checked at API level\n handler: async (\n input: {\n method: string\n path: string\n query?: Record<string, string>\n body?: Record<string, unknown>\n },\n ctx: McpToolContext\n ) => {\n const { method, path, query, body } = input\n\n // Build URL\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n // Ensure path starts with /api\n const apiPath = path.startsWith('/api') ? path : `/api${path}`\n let url = `${baseUrl}${apiPath}`\n\n // Add query parameters\n const queryParams = { ...query }\n\n // Add context to query for GET, to body for mutations\n if (method === 'GET') {\n if (ctx.tenantId) queryParams.tenantId = ctx.tenantId\n if (ctx.organizationId) queryParams.organizationId = ctx.organizationId\n }\n\n if (Object.keys(queryParams).length > 0) {\n const separator = url.includes('?') ? '&' : '?'\n url += separator + new URLSearchParams(queryParams).toString()\n }\n\n // Build body with context\n let requestBody: Record<string, unknown> | undefined\n if (['POST', 'PUT', 'PATCH'].includes(method)) {\n requestBody = { ...body }\n if (ctx.tenantId) requestBody.tenantId = ctx.tenantId\n if (ctx.organizationId) requestBody.organizationId = ctx.organizationId\n }\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n if (ctx.apiKeySecret) headers['X-API-Key'] = ctx.apiKeySecret\n if (ctx.tenantId) headers['X-Tenant-Id'] = ctx.tenantId\n if (ctx.organizationId) headers['X-Organization-Id'] = ctx.organizationId\n\n // Execute request\n try {\n const response = await fetchWithTimeout(url, {\n method,\n headers,\n body: requestBody ? JSON.stringify(requestBody) : undefined,\n timeoutMs: resolveAiApiRequestTimeoutMs(),\n })\n\n const responseText = await response.text()\n\n if (!response.ok) {\n return {\n success: false,\n statusCode: response.status,\n error: `API error ${response.status}`,\n details: tryParseJson(responseText),\n }\n }\n\n return {\n success: true,\n statusCode: response.status,\n data: tryParseJson(responseText),\n }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Request failed',\n }\n }\n },\n },\n { moduleId: 'api' }\n )\n}\n\n/**\n * Try to parse JSON, return original string if fails\n */\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { buildOpenApiDocument } from "@open-mercato/shared/lib/openapi";
2
+ import { fetchWithTimeout, resolveTimeoutMs } from "@open-mercato/shared/lib/http/fetchWithTimeout";
2
3
  import {
3
4
  API_ENDPOINT_ENTITY_ID,
4
5
  GLOBAL_TENANT_ID,
@@ -6,6 +7,12 @@ import {
6
7
  endpointToIndexableRecord,
7
8
  computeEndpointsChecksum
8
9
  } from "./api-endpoint-index-config.js";
10
+ const DEFAULT_OPENAPI_FETCH_TIMEOUT_MS = 1e4;
11
+ function resolveOpenapiFetchTimeoutMs() {
12
+ const raw = process.env.AI_OPENAPI_FETCH_TIMEOUT_MS;
13
+ const parsed = raw ? Number.parseInt(raw, 10) : void 0;
14
+ return resolveTimeoutMs(parsed, DEFAULT_OPENAPI_FETCH_TIMEOUT_MS);
15
+ }
9
16
  const API_ENDPOINT_ENTITY = API_ENDPOINT_ENTITY_ID;
10
17
  let endpointsCache = null;
11
18
  let endpointsByOperationId = null;
@@ -105,7 +112,9 @@ async function loadRawOpenApiSpec() {
105
112
  }
106
113
  const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || process.env.NEXT_PUBLIC_APP_URL || process.env.APP_URL || "http://localhost:3000";
107
114
  try {
108
- const response = await fetch(`${baseUrl}/api/docs/openapi`);
115
+ const response = await fetchWithTimeout(`${baseUrl}/api/docs/openapi`, {
116
+ timeoutMs: resolveOpenapiFetchTimeoutMs()
117
+ });
109
118
  if (response.ok) {
110
119
  const doc = await response.json();
111
120
  console.error("[API Index] Raw OpenAPI spec fetched via HTTP");
@@ -181,7 +190,9 @@ async function parseApiEndpointsFromHttp() {
181
190
  const openApiUrl = `${baseUrl}/api/docs/openapi`;
182
191
  try {
183
192
  console.error(`[API Index] Fetching OpenAPI spec from ${openApiUrl}...`);
184
- const response = await fetch(openApiUrl);
193
+ const response = await fetchWithTimeout(openApiUrl, {
194
+ timeoutMs: resolveOpenapiFetchTimeoutMs()
195
+ });
185
196
  if (!response.ok) {
186
197
  console.error(`[API Index] Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`);
187
198
  return [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/api-endpoint-index.ts"],
4
- "sourcesContent": ["/**\n * API Endpoint Index\n *\n * Parses OpenAPI spec and indexes endpoints for discovery via hybrid search.\n */\n\nimport type { OpenApiDocument } from '@open-mercato/shared/lib/openapi'\nimport { buildOpenApiDocument } from '@open-mercato/shared/lib/openapi'\nimport type { Module } from '@open-mercato/shared/modules/registry'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { IndexableRecord } from '@open-mercato/search/types'\nimport {\n API_ENDPOINT_ENTITY_ID,\n GLOBAL_TENANT_ID,\n API_ENDPOINT_SEARCH_CONFIG,\n endpointToIndexableRecord,\n computeEndpointsChecksum,\n} from './api-endpoint-index-config'\n\n/**\n * Indexed API endpoint structure\n */\nexport interface ApiEndpoint {\n id: string\n operationId: string\n method: string\n path: string\n summary: string\n description: string\n tags: string[]\n requiredFeatures: string[]\n parameters: ApiParameter[]\n requestBodySchema: Record<string, unknown> | null\n deprecated: boolean\n}\n\nexport interface ApiParameter {\n name: string\n in: 'path' | 'query' | 'header'\n required: boolean\n type: string\n description: string\n}\n\n/**\n * Entity type for API endpoints in search index\n * @deprecated Use API_ENDPOINT_ENTITY_ID from api-endpoint-index-config.ts\n */\nexport const API_ENDPOINT_ENTITY = API_ENDPOINT_ENTITY_ID\n\n/**\n * In-memory cache of parsed endpoints (avoid re-parsing on each request)\n */\nlet endpointsCache: ApiEndpoint[] | null = null\nlet endpointsByOperationId: Map<string, ApiEndpoint> | null = null\n\n/**\n * In-memory cache of the raw OpenAPI spec document (for Code Mode search tool)\n */\nlet rawSpecCache: OpenApiDocument | null = null\n\n/**\n * Get all parsed API endpoints (cached)\n */\nexport async function getApiEndpoints(): Promise<ApiEndpoint[]> {\n if (endpointsCache) {\n return endpointsCache\n }\n\n endpointsCache = await parseApiEndpoints()\n endpointsByOperationId = new Map(endpointsCache.map((e) => [e.operationId, e]))\n\n return endpointsCache\n}\n\n/**\n * Get endpoint by operationId\n */\nexport async function getEndpointByOperationId(operationId: string): Promise<ApiEndpoint | null> {\n await getApiEndpoints() // Ensure cache is populated\n return endpointsByOperationId?.get(operationId) ?? null\n}\n\n/**\n * Get the raw OpenAPI spec document (cached).\n * Uses the same 3-tier loading strategy as parseApiEndpoints():\n * generated JSON \u2192 module registry \u2192 HTTP fetch.\n */\nexport async function getRawOpenApiSpec(): Promise<OpenApiDocument | null> {\n if (rawSpecCache) return rawSpecCache\n rawSpecCache = await loadRawOpenApiSpec()\n return rawSpecCache\n}\n\n/**\n * Set the raw OpenAPI spec cache directly.\n * Used by servers that want to inject a pre-built spec.\n */\nexport function setRawSpecCache(doc: OpenApiDocument): void {\n rawSpecCache = doc\n}\n\n/**\n * Clear the raw OpenAPI spec cache.\n */\nexport function clearRawSpecCache(): void {\n rawSpecCache = null\n}\n\n/**\n * Load the rich OpenAPI spec, skipping Tier 1 (static JSON) which lacks requestBody schemas.\n * Prefers Tier 2 (runtime module registry) which has full Zod-converted schemas.\n * Falls back to Tier 1 then Tier 3 if needed.\n */\nexport async function loadRichOpenApiSpec(): Promise<OpenApiDocument | null> {\n if (rawSpecCache) return rawSpecCache\n\n // Tier 2 first: Module registry (has full Zod-converted schemas)\n try {\n const { getModules } = await import('@open-mercato/shared/lib/modules/registry')\n const modules: Module[] = getModules()\n const modulesWithApis = modules.filter((m) => m.apis && m.apis.length > 0)\n\n if (modulesWithApis.length > 0) {\n const doc = buildOpenApiDocument(modules, {\n title: 'Open Mercato API',\n version: '1.0.0',\n servers: [{ url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' }],\n })\n if (!doc.paths || Object.keys(doc.paths).length === 0) {\n return null\n }\n console.error(`[API Index] Rich OpenAPI spec built from ${modulesWithApis.length} modules (Tier 2)`)\n rawSpecCache = doc\n return doc\n }\n } catch {\n // Registry not available \u2014 fall through\n }\n\n // Fall back to standard 3-tier loading (Tier 1 \u2192 Tier 3)\n rawSpecCache = await loadRawOpenApiSpec()\n return rawSpecCache\n}\n\n/**\n * Load raw OpenAPI spec using the 3-tier strategy.\n */\nasync function loadRawOpenApiSpec(): Promise<OpenApiDocument | null> {\n // Tier 1: Generated JSON file\n try {\n const fs = await import('node:fs')\n const path = await import('node:path')\n const { findAppRoot, findAllApps } = await import('@open-mercato/shared/lib/bootstrap/appResolver')\n\n let appRoot = findAppRoot()\n if (!appRoot) {\n let current = process.cwd()\n while (current !== path.dirname(current)) {\n const appsDir = path.join(current, 'apps')\n if (fs.existsSync(appsDir)) {\n const apps = findAllApps(current)\n if (apps.length > 0) {\n appRoot = apps[0]\n break\n }\n }\n current = path.dirname(current)\n }\n }\n\n if (appRoot) {\n const jsonPath = path.join(appRoot.generatedDir, 'openapi.generated.json')\n if (fs.existsSync(jsonPath)) {\n const doc = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')) as OpenApiDocument\n console.error(`[API Index] Raw OpenAPI spec loaded from ${jsonPath}`)\n return doc\n }\n }\n } catch (error) {\n console.error('[API Index] Raw spec from JSON failed:', error instanceof Error ? error.message : error)\n }\n\n // Tier 2: Module registry\n try {\n const { getModules } = await import('@open-mercato/shared/lib/modules/registry')\n const modules: Module[] = getModules()\n const modulesWithApis = modules.filter((m) => m.apis && m.apis.length > 0)\n\n if (modulesWithApis.length > 0) {\n const doc = buildOpenApiDocument(modules, {\n title: 'Open Mercato API',\n version: '1.0.0',\n servers: [{ url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' }],\n })\n console.error(`[API Index] Raw OpenAPI spec built from ${modulesWithApis.length} modules`)\n return doc\n }\n } catch {\n // Registry not available\n }\n\n // Tier 3: HTTP fetch\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n try {\n const response = await fetch(`${baseUrl}/api/docs/openapi`)\n if (response.ok) {\n const doc = (await response.json()) as OpenApiDocument\n console.error('[API Index] Raw OpenAPI spec fetched via HTTP')\n return doc\n }\n } catch (error) {\n console.error('[API Index] Raw spec HTTP fetch failed:', error instanceof Error ? error.message : error)\n }\n\n return null\n}\n\n/**\n * Parse endpoints from generated OpenAPI JSON file (for CLI context).\n * This is generated by `yarn generate`.\n */\nasync function parseApiEndpointsFromGeneratedJson(): Promise<ApiEndpoint[]> {\n try {\n const fs = await import('node:fs')\n const path = await import('node:path')\n const { findAppRoot, findAllApps } = await import('@open-mercato/shared/lib/bootstrap/appResolver')\n\n let appRoot = findAppRoot()\n\n // Try monorepo structure if not found - walk up to find monorepo root\n if (!appRoot) {\n let current = process.cwd()\n // Walk up until we find a directory containing 'apps' folder\n while (current !== path.dirname(current)) {\n const appsDir = path.join(current, 'apps')\n if (fs.existsSync(appsDir)) {\n const apps = findAllApps(current)\n if (apps.length > 0) {\n appRoot = apps[0]\n break\n }\n }\n current = path.dirname(current)\n }\n }\n\n if (!appRoot) {\n console.error('[API Index] Could not find app root')\n return []\n }\n\n const jsonPath = path.join(appRoot.generatedDir, 'openapi.generated.json')\n if (!fs.existsSync(jsonPath)) {\n console.error('[API Index] openapi.generated.json not found - run yarn generate')\n return []\n }\n\n const doc = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')) as OpenApiDocument\n console.error(`[API Index] Loaded OpenAPI from ${jsonPath}`)\n return extractEndpoints(doc)\n } catch (error) {\n console.error('[API Index] Error reading generated JSON:', error instanceof Error ? error.message : error)\n return []\n }\n}\n\n/**\n * Parse endpoints from registered modules (works in Next.js context).\n */\nasync function parseApiEndpointsFromModules(): Promise<ApiEndpoint[]> {\n try {\n const { getModules } = await import('@open-mercato/shared/lib/modules/registry')\n const modules: Module[] = getModules()\n\n // Count how many modules have APIs defined\n const modulesWithApis = modules.filter((m) => m.apis && m.apis.length > 0)\n\n if (modulesWithApis.length > 0) {\n console.error(\n `[API Index] Found ${modules.length} modules, ${modulesWithApis.length} with APIs`\n )\n\n // Generate OpenAPI spec from modules\n const doc = buildOpenApiDocument(modules, {\n title: 'Open Mercato API',\n version: '1.0.0',\n servers: [{ url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' }],\n })\n if (!doc.paths || Object.keys(doc.paths).length === 0) {\n return []\n }\n\n return extractEndpoints(doc)\n }\n } catch {\n // Registry not available\n }\n\n return []\n}\n\n/**\n * Parse OpenAPI spec via HTTP fetch.\n * Fetches the OpenAPI spec from the running app's /api/docs/openapi endpoint.\n */\nasync function parseApiEndpointsFromHttp(): Promise<ApiEndpoint[]> {\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n const openApiUrl = `${baseUrl}/api/docs/openapi`\n\n try {\n console.error(`[API Index] Fetching OpenAPI spec from ${openApiUrl}...`)\n const response = await fetch(openApiUrl)\n\n if (!response.ok) {\n console.error(`[API Index] Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`)\n return []\n }\n\n const doc = (await response.json()) as OpenApiDocument\n console.error(`[API Index] Successfully fetched OpenAPI spec`)\n return extractEndpoints(doc)\n } catch (error) {\n console.error('[API Index] Could not fetch OpenAPI spec:', error instanceof Error ? error.message : error)\n console.error('[API Index] Make sure the app is running at', baseUrl)\n return []\n }\n}\n\n/**\n * Parse API endpoints - tries generated JSON first (CLI), then modules (Next.js), then HTTP.\n */\nasync function parseApiEndpoints(): Promise<ApiEndpoint[]> {\n // Try generated JSON first (works in CLI context without Next.js)\n const fromJson = await parseApiEndpointsFromGeneratedJson()\n if (fromJson.length > 0) {\n console.error(`[API Index] Loaded ${fromJson.length} endpoints from generated JSON`)\n return fromJson\n }\n\n // Try loading from module registry (works in Next.js context)\n const fromModules = await parseApiEndpointsFromModules()\n if (fromModules.length > 0) {\n console.error(`[API Index] Loaded ${fromModules.length} endpoints from modules registry`)\n return fromModules\n }\n\n // Fall back to HTTP fetch (requires running Next.js app)\n console.error('[API Index] Generated JSON and modules not available, falling back to HTTP fetch...')\n return parseApiEndpointsFromHttp()\n}\n\n/**\n * Extract endpoints from OpenAPI document\n */\nfunction extractEndpoints(doc: OpenApiDocument): ApiEndpoint[] {\n const endpoints: ApiEndpoint[] = []\n const validMethods = ['get', 'post', 'put', 'patch', 'delete']\n\n if (!doc.paths) {\n return endpoints\n }\n\n for (const [path, pathItem] of Object.entries(doc.paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue\n\n for (const [method, operation] of Object.entries(pathItem)) {\n if (!validMethods.includes(method.toLowerCase())) continue\n if (!operation || typeof operation !== 'object') continue\n\n const op = operation as any\n\n // Generate operationId if not present\n const operationId = op.operationId || generateOperationId(path, method)\n\n const endpoint: ApiEndpoint = {\n id: operationId,\n operationId,\n method: method.toUpperCase(),\n path,\n summary: op.summary || '',\n description: op.description || op.summary || `${method.toUpperCase()} ${path}`,\n tags: op.tags || [],\n requiredFeatures: op['x-require-features'] || [],\n deprecated: op.deprecated || false,\n parameters: extractParameters(op.parameters || []),\n requestBodySchema: extractRequestBodySchema(op.requestBody, doc.components?.schemas),\n }\n\n endpoints.push(endpoint)\n }\n }\n\n console.error(`[API Index] Parsed ${endpoints.length} endpoints from OpenAPI spec`)\n return endpoints\n}\n\n/**\n * Generate operationId from path and method\n */\nfunction generateOperationId(path: string, method: string): string {\n const pathParts = path\n .replace(/^\\//, '')\n .replace(/\\{([^}]+)\\}/g, 'by_$1')\n .split('/')\n .filter(Boolean)\n .join('_')\n\n return `${method.toLowerCase()}_${pathParts}`\n}\n\n/**\n * Extract parameter info\n */\nfunction extractParameters(params: any[]): ApiParameter[] {\n return params\n .filter((p) => p.in === 'path' || p.in === 'query')\n .map((p) => ({\n name: p.name,\n in: p.in,\n required: p.required ?? false,\n type: p.schema?.type || 'string',\n description: p.description || '',\n }))\n}\n\n/**\n * Extract request body schema (simplified)\n */\nfunction extractRequestBodySchema(\n requestBody: any,\n schemas?: Record<string, any>\n): Record<string, unknown> | null {\n if (!requestBody?.content?.['application/json']?.schema) {\n return null\n }\n\n const schema = requestBody.content['application/json'].schema\n\n // Resolve $ref if present\n if (schema.$ref && schemas) {\n const refPath = schema.$ref.replace('#/components/schemas/', '')\n return schemas[refPath] || schema\n }\n\n return schema\n}\n\n/**\n * Checksum from last indexing operation\n */\nlet lastIndexChecksum: string | null = null\n\n/**\n * Index endpoints for search discovery using hybrid search strategies.\n * Uses checksum-based change detection to avoid unnecessary re-indexing.\n *\n * @param searchService - The search service to use for indexing\n * @param force - Force re-indexing even if checksum hasn't changed\n * @returns Number of endpoints indexed\n */\nexport async function indexApiEndpoints(\n searchService: SearchService,\n force = false\n): Promise<number> {\n const endpoints = await getApiEndpoints()\n\n if (endpoints.length === 0) {\n console.error('[API Index] No endpoints to index')\n return 0\n }\n\n // Compute checksum to detect changes\n const checksum = computeEndpointsChecksum(\n endpoints.map((e) => ({ operationId: e.operationId, method: e.method, path: e.path }))\n )\n\n // Skip if checksum matches and not forced\n if (!force && lastIndexChecksum === checksum) {\n console.error(`[API Index] Skipping indexing - ${endpoints.length} endpoints unchanged`)\n return 0\n }\n\n // Convert to indexable records using the proper format\n const records: IndexableRecord[] = endpoints.map((endpoint) =>\n endpointToIndexableRecord(endpoint)\n )\n\n try {\n console.error(`[API Index] Starting bulk index of ${records.length} endpoints...`)\n // Bulk index using all available strategies (fulltext + vector)\n // Use Promise.race with timeout to prevent hanging\n const timeoutMs = 60000 // 60 second timeout\n const indexPromise = searchService.bulkIndex(records)\n const timeoutPromise = new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error(`Bulk index timed out after ${timeoutMs}ms`)), timeoutMs)\n )\n\n await Promise.race([indexPromise, timeoutPromise])\n lastIndexChecksum = checksum\n console.error(`[API Index] Indexed ${records.length} API endpoints for hybrid search`)\n return records.length\n } catch (error) {\n console.error('[API Index] Failed to index endpoints:', error)\n // Still return the count - some strategies may have succeeded\n lastIndexChecksum = checksum\n return records.length\n }\n}\n\n/**\n * Build searchable content from endpoint\n */\nfunction buildSearchableContent(endpoint: ApiEndpoint): string {\n const parts = [\n endpoint.operationId,\n endpoint.method,\n endpoint.path,\n endpoint.summary,\n endpoint.description,\n ...endpoint.tags,\n ...endpoint.parameters.map((p) => `${p.name} ${p.description}`),\n ]\n\n return parts.filter(Boolean).join(' ')\n}\n\n/**\n * Search endpoints using hybrid search (fulltext + vector).\n * Falls back to in-memory search if search service is not available.\n */\nexport async function searchEndpoints(\n searchService: SearchService | null,\n query: string,\n options: { limit?: number; method?: string } = {}\n): Promise<ApiEndpoint[]> {\n const { limit = API_ENDPOINT_SEARCH_CONFIG.defaultLimit, method } = options\n\n // Ensure endpoints are loaded\n await getApiEndpoints()\n\n // Try hybrid search first if search service is available\n if (searchService) {\n try {\n // Use hybrid search (fulltext + vector)\n const results = await searchService.search(query, {\n tenantId: GLOBAL_TENANT_ID,\n organizationId: null,\n entityTypes: [API_ENDPOINT_ENTITY_ID],\n limit: limit * 2, // Get extra to account for filtering\n })\n\n // Map search results back to ApiEndpoint objects\n const endpoints: ApiEndpoint[] = []\n for (const result of results) {\n if (endpoints.length >= limit) break\n\n const endpoint = endpointsByOperationId?.get(result.recordId)\n if (endpoint) {\n // Apply method filter if not handled by search\n if (method && endpoint.method !== method.toUpperCase()) continue\n endpoints.push(endpoint)\n }\n }\n\n if (endpoints.length > 0) {\n return endpoints\n }\n\n // Fall through to fallback if no results from hybrid search\n console.error('[API Index] No hybrid search results, falling back to in-memory search')\n } catch (error) {\n console.error('[API Index] Hybrid search failed, falling back to in-memory:', error)\n }\n }\n\n // Fallback: Simple in-memory text matching\n return searchEndpointsFallback(query, { limit, method })\n}\n\n/**\n * Fallback in-memory search when hybrid search is not available.\n */\nfunction searchEndpointsFallback(\n query: string,\n options: { limit?: number; method?: string } = {}\n): ApiEndpoint[] {\n const { limit = API_ENDPOINT_SEARCH_CONFIG.defaultLimit, method } = options\n\n if (!endpointsCache) {\n return []\n }\n\n const queryLower = query.toLowerCase()\n const queryTerms = queryLower.split(/\\s+/).filter(Boolean)\n\n let matches = endpointsCache.filter((endpoint) => {\n const content = buildSearchableContent(endpoint).toLowerCase()\n return queryTerms.some((term) => content.includes(term))\n })\n\n // Filter by method if specified\n if (method) {\n matches = matches.filter((e) => e.method === method.toUpperCase())\n }\n\n // Sort by relevance (number of matching terms)\n matches.sort((a, b) => {\n const aContent = buildSearchableContent(a).toLowerCase()\n const bContent = buildSearchableContent(b).toLowerCase()\n const aScore = queryTerms.filter((t) => aContent.includes(t)).length\n const bScore = queryTerms.filter((t) => bContent.includes(t)).length\n return bScore - aScore\n })\n\n return matches.slice(0, limit)\n}\n\n/**\n * Clear endpoint cache (for testing)\n */\nexport function clearEndpointCache(): void {\n endpointsCache = null\n endpointsByOperationId = null\n rawSpecCache = null\n}\n\n/**\n * Extract simplified request body schema for LLM consumption.\n * Returns required fields and basic property info without deep nesting.\n */\nexport function simplifyRequestBodySchema(\n schema: Record<string, unknown> | null\n): { required: string[]; properties: Record<string, { type: string; format?: string; enum?: string[] }> } | null {\n if (!schema) return null\n\n const properties: Record<string, { type: string; format?: string; enum?: string[] }> = {}\n const required: string[] = (schema.required as string[]) || []\n\n const schemaProps = (schema.properties || schema) as Record<string, unknown>\n\n for (const [key, value] of Object.entries(schemaProps)) {\n if (typeof value !== 'object' || value === null) continue\n const propSchema = value as Record<string, unknown>\n\n const prop: { type: string; format?: string; enum?: string[] } = {\n type: (propSchema.type as string) || 'unknown',\n }\n\n if (propSchema.format) prop.format = propSchema.format as string\n if (propSchema.enum && Array.isArray(propSchema.enum)) {\n prop.enum = propSchema.enum.slice(0, 10) as string[] // Limit enum values\n }\n\n properties[key] = prop\n }\n\n return { required, properties }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * API Endpoint Index\n *\n * Parses OpenAPI spec and indexes endpoints for discovery via hybrid search.\n */\n\nimport type { OpenApiDocument } from '@open-mercato/shared/lib/openapi'\nimport { buildOpenApiDocument } from '@open-mercato/shared/lib/openapi'\nimport type { Module } from '@open-mercato/shared/modules/registry'\nimport type { SearchService } from '@open-mercato/search/service'\nimport type { IndexableRecord } from '@open-mercato/search/types'\nimport { fetchWithTimeout, resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\nimport {\n API_ENDPOINT_ENTITY_ID,\n GLOBAL_TENANT_ID,\n API_ENDPOINT_SEARCH_CONFIG,\n endpointToIndexableRecord,\n computeEndpointsChecksum,\n} from './api-endpoint-index-config'\n\nconst DEFAULT_OPENAPI_FETCH_TIMEOUT_MS = 10_000\n\nfunction resolveOpenapiFetchTimeoutMs(): number {\n const raw = process.env.AI_OPENAPI_FETCH_TIMEOUT_MS\n const parsed = raw ? Number.parseInt(raw, 10) : undefined\n return resolveTimeoutMs(parsed, DEFAULT_OPENAPI_FETCH_TIMEOUT_MS)\n}\n\n/**\n * Indexed API endpoint structure\n */\nexport interface ApiEndpoint {\n id: string\n operationId: string\n method: string\n path: string\n summary: string\n description: string\n tags: string[]\n requiredFeatures: string[]\n parameters: ApiParameter[]\n requestBodySchema: Record<string, unknown> | null\n deprecated: boolean\n}\n\nexport interface ApiParameter {\n name: string\n in: 'path' | 'query' | 'header'\n required: boolean\n type: string\n description: string\n}\n\n/**\n * Entity type for API endpoints in search index\n * @deprecated Use API_ENDPOINT_ENTITY_ID from api-endpoint-index-config.ts\n */\nexport const API_ENDPOINT_ENTITY = API_ENDPOINT_ENTITY_ID\n\n/**\n * In-memory cache of parsed endpoints (avoid re-parsing on each request)\n */\nlet endpointsCache: ApiEndpoint[] | null = null\nlet endpointsByOperationId: Map<string, ApiEndpoint> | null = null\n\n/**\n * In-memory cache of the raw OpenAPI spec document (for Code Mode search tool)\n */\nlet rawSpecCache: OpenApiDocument | null = null\n\n/**\n * Get all parsed API endpoints (cached)\n */\nexport async function getApiEndpoints(): Promise<ApiEndpoint[]> {\n if (endpointsCache) {\n return endpointsCache\n }\n\n endpointsCache = await parseApiEndpoints()\n endpointsByOperationId = new Map(endpointsCache.map((e) => [e.operationId, e]))\n\n return endpointsCache\n}\n\n/**\n * Get endpoint by operationId\n */\nexport async function getEndpointByOperationId(operationId: string): Promise<ApiEndpoint | null> {\n await getApiEndpoints() // Ensure cache is populated\n return endpointsByOperationId?.get(operationId) ?? null\n}\n\n/**\n * Get the raw OpenAPI spec document (cached).\n * Uses the same 3-tier loading strategy as parseApiEndpoints():\n * generated JSON \u2192 module registry \u2192 HTTP fetch.\n */\nexport async function getRawOpenApiSpec(): Promise<OpenApiDocument | null> {\n if (rawSpecCache) return rawSpecCache\n rawSpecCache = await loadRawOpenApiSpec()\n return rawSpecCache\n}\n\n/**\n * Set the raw OpenAPI spec cache directly.\n * Used by servers that want to inject a pre-built spec.\n */\nexport function setRawSpecCache(doc: OpenApiDocument): void {\n rawSpecCache = doc\n}\n\n/**\n * Clear the raw OpenAPI spec cache.\n */\nexport function clearRawSpecCache(): void {\n rawSpecCache = null\n}\n\n/**\n * Load the rich OpenAPI spec, skipping Tier 1 (static JSON) which lacks requestBody schemas.\n * Prefers Tier 2 (runtime module registry) which has full Zod-converted schemas.\n * Falls back to Tier 1 then Tier 3 if needed.\n */\nexport async function loadRichOpenApiSpec(): Promise<OpenApiDocument | null> {\n if (rawSpecCache) return rawSpecCache\n\n // Tier 2 first: Module registry (has full Zod-converted schemas)\n try {\n const { getModules } = await import('@open-mercato/shared/lib/modules/registry')\n const modules: Module[] = getModules()\n const modulesWithApis = modules.filter((m) => m.apis && m.apis.length > 0)\n\n if (modulesWithApis.length > 0) {\n const doc = buildOpenApiDocument(modules, {\n title: 'Open Mercato API',\n version: '1.0.0',\n servers: [{ url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' }],\n })\n if (!doc.paths || Object.keys(doc.paths).length === 0) {\n return null\n }\n console.error(`[API Index] Rich OpenAPI spec built from ${modulesWithApis.length} modules (Tier 2)`)\n rawSpecCache = doc\n return doc\n }\n } catch {\n // Registry not available \u2014 fall through\n }\n\n // Fall back to standard 3-tier loading (Tier 1 \u2192 Tier 3)\n rawSpecCache = await loadRawOpenApiSpec()\n return rawSpecCache\n}\n\n/**\n * Load raw OpenAPI spec using the 3-tier strategy.\n */\nasync function loadRawOpenApiSpec(): Promise<OpenApiDocument | null> {\n // Tier 1: Generated JSON file\n try {\n const fs = await import('node:fs')\n const path = await import('node:path')\n const { findAppRoot, findAllApps } = await import('@open-mercato/shared/lib/bootstrap/appResolver')\n\n let appRoot = findAppRoot()\n if (!appRoot) {\n let current = process.cwd()\n while (current !== path.dirname(current)) {\n const appsDir = path.join(current, 'apps')\n if (fs.existsSync(appsDir)) {\n const apps = findAllApps(current)\n if (apps.length > 0) {\n appRoot = apps[0]\n break\n }\n }\n current = path.dirname(current)\n }\n }\n\n if (appRoot) {\n const jsonPath = path.join(appRoot.generatedDir, 'openapi.generated.json')\n if (fs.existsSync(jsonPath)) {\n const doc = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')) as OpenApiDocument\n console.error(`[API Index] Raw OpenAPI spec loaded from ${jsonPath}`)\n return doc\n }\n }\n } catch (error) {\n console.error('[API Index] Raw spec from JSON failed:', error instanceof Error ? error.message : error)\n }\n\n // Tier 2: Module registry\n try {\n const { getModules } = await import('@open-mercato/shared/lib/modules/registry')\n const modules: Module[] = getModules()\n const modulesWithApis = modules.filter((m) => m.apis && m.apis.length > 0)\n\n if (modulesWithApis.length > 0) {\n const doc = buildOpenApiDocument(modules, {\n title: 'Open Mercato API',\n version: '1.0.0',\n servers: [{ url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' }],\n })\n console.error(`[API Index] Raw OpenAPI spec built from ${modulesWithApis.length} modules`)\n return doc\n }\n } catch {\n // Registry not available\n }\n\n // Tier 3: HTTP fetch\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n try {\n const response = await fetchWithTimeout(`${baseUrl}/api/docs/openapi`, {\n timeoutMs: resolveOpenapiFetchTimeoutMs(),\n })\n if (response.ok) {\n const doc = (await response.json()) as OpenApiDocument\n console.error('[API Index] Raw OpenAPI spec fetched via HTTP')\n return doc\n }\n } catch (error) {\n console.error('[API Index] Raw spec HTTP fetch failed:', error instanceof Error ? error.message : error)\n }\n\n return null\n}\n\n/**\n * Parse endpoints from generated OpenAPI JSON file (for CLI context).\n * This is generated by `yarn generate`.\n */\nasync function parseApiEndpointsFromGeneratedJson(): Promise<ApiEndpoint[]> {\n try {\n const fs = await import('node:fs')\n const path = await import('node:path')\n const { findAppRoot, findAllApps } = await import('@open-mercato/shared/lib/bootstrap/appResolver')\n\n let appRoot = findAppRoot()\n\n // Try monorepo structure if not found - walk up to find monorepo root\n if (!appRoot) {\n let current = process.cwd()\n // Walk up until we find a directory containing 'apps' folder\n while (current !== path.dirname(current)) {\n const appsDir = path.join(current, 'apps')\n if (fs.existsSync(appsDir)) {\n const apps = findAllApps(current)\n if (apps.length > 0) {\n appRoot = apps[0]\n break\n }\n }\n current = path.dirname(current)\n }\n }\n\n if (!appRoot) {\n console.error('[API Index] Could not find app root')\n return []\n }\n\n const jsonPath = path.join(appRoot.generatedDir, 'openapi.generated.json')\n if (!fs.existsSync(jsonPath)) {\n console.error('[API Index] openapi.generated.json not found - run yarn generate')\n return []\n }\n\n const doc = JSON.parse(fs.readFileSync(jsonPath, 'utf-8')) as OpenApiDocument\n console.error(`[API Index] Loaded OpenAPI from ${jsonPath}`)\n return extractEndpoints(doc)\n } catch (error) {\n console.error('[API Index] Error reading generated JSON:', error instanceof Error ? error.message : error)\n return []\n }\n}\n\n/**\n * Parse endpoints from registered modules (works in Next.js context).\n */\nasync function parseApiEndpointsFromModules(): Promise<ApiEndpoint[]> {\n try {\n const { getModules } = await import('@open-mercato/shared/lib/modules/registry')\n const modules: Module[] = getModules()\n\n // Count how many modules have APIs defined\n const modulesWithApis = modules.filter((m) => m.apis && m.apis.length > 0)\n\n if (modulesWithApis.length > 0) {\n console.error(\n `[API Index] Found ${modules.length} modules, ${modulesWithApis.length} with APIs`\n )\n\n // Generate OpenAPI spec from modules\n const doc = buildOpenApiDocument(modules, {\n title: 'Open Mercato API',\n version: '1.0.0',\n servers: [{ url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' }],\n })\n if (!doc.paths || Object.keys(doc.paths).length === 0) {\n return []\n }\n\n return extractEndpoints(doc)\n }\n } catch {\n // Registry not available\n }\n\n return []\n}\n\n/**\n * Parse OpenAPI spec via HTTP fetch.\n * Fetches the OpenAPI spec from the running app's /api/docs/openapi endpoint.\n */\nasync function parseApiEndpointsFromHttp(): Promise<ApiEndpoint[]> {\n const baseUrl =\n process.env.NEXT_PUBLIC_API_BASE_URL ||\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.APP_URL ||\n 'http://localhost:3000'\n\n const openApiUrl = `${baseUrl}/api/docs/openapi`\n\n try {\n console.error(`[API Index] Fetching OpenAPI spec from ${openApiUrl}...`)\n const response = await fetchWithTimeout(openApiUrl, {\n timeoutMs: resolveOpenapiFetchTimeoutMs(),\n })\n\n if (!response.ok) {\n console.error(`[API Index] Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`)\n return []\n }\n\n const doc = (await response.json()) as OpenApiDocument\n console.error(`[API Index] Successfully fetched OpenAPI spec`)\n return extractEndpoints(doc)\n } catch (error) {\n console.error('[API Index] Could not fetch OpenAPI spec:', error instanceof Error ? error.message : error)\n console.error('[API Index] Make sure the app is running at', baseUrl)\n return []\n }\n}\n\n/**\n * Parse API endpoints - tries generated JSON first (CLI), then modules (Next.js), then HTTP.\n */\nasync function parseApiEndpoints(): Promise<ApiEndpoint[]> {\n // Try generated JSON first (works in CLI context without Next.js)\n const fromJson = await parseApiEndpointsFromGeneratedJson()\n if (fromJson.length > 0) {\n console.error(`[API Index] Loaded ${fromJson.length} endpoints from generated JSON`)\n return fromJson\n }\n\n // Try loading from module registry (works in Next.js context)\n const fromModules = await parseApiEndpointsFromModules()\n if (fromModules.length > 0) {\n console.error(`[API Index] Loaded ${fromModules.length} endpoints from modules registry`)\n return fromModules\n }\n\n // Fall back to HTTP fetch (requires running Next.js app)\n console.error('[API Index] Generated JSON and modules not available, falling back to HTTP fetch...')\n return parseApiEndpointsFromHttp()\n}\n\n/**\n * Extract endpoints from OpenAPI document\n */\nfunction extractEndpoints(doc: OpenApiDocument): ApiEndpoint[] {\n const endpoints: ApiEndpoint[] = []\n const validMethods = ['get', 'post', 'put', 'patch', 'delete']\n\n if (!doc.paths) {\n return endpoints\n }\n\n for (const [path, pathItem] of Object.entries(doc.paths)) {\n if (!pathItem || typeof pathItem !== 'object') continue\n\n for (const [method, operation] of Object.entries(pathItem)) {\n if (!validMethods.includes(method.toLowerCase())) continue\n if (!operation || typeof operation !== 'object') continue\n\n const op = operation as any\n\n // Generate operationId if not present\n const operationId = op.operationId || generateOperationId(path, method)\n\n const endpoint: ApiEndpoint = {\n id: operationId,\n operationId,\n method: method.toUpperCase(),\n path,\n summary: op.summary || '',\n description: op.description || op.summary || `${method.toUpperCase()} ${path}`,\n tags: op.tags || [],\n requiredFeatures: op['x-require-features'] || [],\n deprecated: op.deprecated || false,\n parameters: extractParameters(op.parameters || []),\n requestBodySchema: extractRequestBodySchema(op.requestBody, doc.components?.schemas),\n }\n\n endpoints.push(endpoint)\n }\n }\n\n console.error(`[API Index] Parsed ${endpoints.length} endpoints from OpenAPI spec`)\n return endpoints\n}\n\n/**\n * Generate operationId from path and method\n */\nfunction generateOperationId(path: string, method: string): string {\n const pathParts = path\n .replace(/^\\//, '')\n .replace(/\\{([^}]+)\\}/g, 'by_$1')\n .split('/')\n .filter(Boolean)\n .join('_')\n\n return `${method.toLowerCase()}_${pathParts}`\n}\n\n/**\n * Extract parameter info\n */\nfunction extractParameters(params: any[]): ApiParameter[] {\n return params\n .filter((p) => p.in === 'path' || p.in === 'query')\n .map((p) => ({\n name: p.name,\n in: p.in,\n required: p.required ?? false,\n type: p.schema?.type || 'string',\n description: p.description || '',\n }))\n}\n\n/**\n * Extract request body schema (simplified)\n */\nfunction extractRequestBodySchema(\n requestBody: any,\n schemas?: Record<string, any>\n): Record<string, unknown> | null {\n if (!requestBody?.content?.['application/json']?.schema) {\n return null\n }\n\n const schema = requestBody.content['application/json'].schema\n\n // Resolve $ref if present\n if (schema.$ref && schemas) {\n const refPath = schema.$ref.replace('#/components/schemas/', '')\n return schemas[refPath] || schema\n }\n\n return schema\n}\n\n/**\n * Checksum from last indexing operation\n */\nlet lastIndexChecksum: string | null = null\n\n/**\n * Index endpoints for search discovery using hybrid search strategies.\n * Uses checksum-based change detection to avoid unnecessary re-indexing.\n *\n * @param searchService - The search service to use for indexing\n * @param force - Force re-indexing even if checksum hasn't changed\n * @returns Number of endpoints indexed\n */\nexport async function indexApiEndpoints(\n searchService: SearchService,\n force = false\n): Promise<number> {\n const endpoints = await getApiEndpoints()\n\n if (endpoints.length === 0) {\n console.error('[API Index] No endpoints to index')\n return 0\n }\n\n // Compute checksum to detect changes\n const checksum = computeEndpointsChecksum(\n endpoints.map((e) => ({ operationId: e.operationId, method: e.method, path: e.path }))\n )\n\n // Skip if checksum matches and not forced\n if (!force && lastIndexChecksum === checksum) {\n console.error(`[API Index] Skipping indexing - ${endpoints.length} endpoints unchanged`)\n return 0\n }\n\n // Convert to indexable records using the proper format\n const records: IndexableRecord[] = endpoints.map((endpoint) =>\n endpointToIndexableRecord(endpoint)\n )\n\n try {\n console.error(`[API Index] Starting bulk index of ${records.length} endpoints...`)\n // Bulk index using all available strategies (fulltext + vector)\n // Use Promise.race with timeout to prevent hanging\n const timeoutMs = 60000 // 60 second timeout\n const indexPromise = searchService.bulkIndex(records)\n const timeoutPromise = new Promise<never>((_, reject) =>\n setTimeout(() => reject(new Error(`Bulk index timed out after ${timeoutMs}ms`)), timeoutMs)\n )\n\n await Promise.race([indexPromise, timeoutPromise])\n lastIndexChecksum = checksum\n console.error(`[API Index] Indexed ${records.length} API endpoints for hybrid search`)\n return records.length\n } catch (error) {\n console.error('[API Index] Failed to index endpoints:', error)\n // Still return the count - some strategies may have succeeded\n lastIndexChecksum = checksum\n return records.length\n }\n}\n\n/**\n * Build searchable content from endpoint\n */\nfunction buildSearchableContent(endpoint: ApiEndpoint): string {\n const parts = [\n endpoint.operationId,\n endpoint.method,\n endpoint.path,\n endpoint.summary,\n endpoint.description,\n ...endpoint.tags,\n ...endpoint.parameters.map((p) => `${p.name} ${p.description}`),\n ]\n\n return parts.filter(Boolean).join(' ')\n}\n\n/**\n * Search endpoints using hybrid search (fulltext + vector).\n * Falls back to in-memory search if search service is not available.\n */\nexport async function searchEndpoints(\n searchService: SearchService | null,\n query: string,\n options: { limit?: number; method?: string } = {}\n): Promise<ApiEndpoint[]> {\n const { limit = API_ENDPOINT_SEARCH_CONFIG.defaultLimit, method } = options\n\n // Ensure endpoints are loaded\n await getApiEndpoints()\n\n // Try hybrid search first if search service is available\n if (searchService) {\n try {\n // Use hybrid search (fulltext + vector)\n const results = await searchService.search(query, {\n tenantId: GLOBAL_TENANT_ID,\n organizationId: null,\n entityTypes: [API_ENDPOINT_ENTITY_ID],\n limit: limit * 2, // Get extra to account for filtering\n })\n\n // Map search results back to ApiEndpoint objects\n const endpoints: ApiEndpoint[] = []\n for (const result of results) {\n if (endpoints.length >= limit) break\n\n const endpoint = endpointsByOperationId?.get(result.recordId)\n if (endpoint) {\n // Apply method filter if not handled by search\n if (method && endpoint.method !== method.toUpperCase()) continue\n endpoints.push(endpoint)\n }\n }\n\n if (endpoints.length > 0) {\n return endpoints\n }\n\n // Fall through to fallback if no results from hybrid search\n console.error('[API Index] No hybrid search results, falling back to in-memory search')\n } catch (error) {\n console.error('[API Index] Hybrid search failed, falling back to in-memory:', error)\n }\n }\n\n // Fallback: Simple in-memory text matching\n return searchEndpointsFallback(query, { limit, method })\n}\n\n/**\n * Fallback in-memory search when hybrid search is not available.\n */\nfunction searchEndpointsFallback(\n query: string,\n options: { limit?: number; method?: string } = {}\n): ApiEndpoint[] {\n const { limit = API_ENDPOINT_SEARCH_CONFIG.defaultLimit, method } = options\n\n if (!endpointsCache) {\n return []\n }\n\n const queryLower = query.toLowerCase()\n const queryTerms = queryLower.split(/\\s+/).filter(Boolean)\n\n let matches = endpointsCache.filter((endpoint) => {\n const content = buildSearchableContent(endpoint).toLowerCase()\n return queryTerms.some((term) => content.includes(term))\n })\n\n // Filter by method if specified\n if (method) {\n matches = matches.filter((e) => e.method === method.toUpperCase())\n }\n\n // Sort by relevance (number of matching terms)\n matches.sort((a, b) => {\n const aContent = buildSearchableContent(a).toLowerCase()\n const bContent = buildSearchableContent(b).toLowerCase()\n const aScore = queryTerms.filter((t) => aContent.includes(t)).length\n const bScore = queryTerms.filter((t) => bContent.includes(t)).length\n return bScore - aScore\n })\n\n return matches.slice(0, limit)\n}\n\n/**\n * Clear endpoint cache (for testing)\n */\nexport function clearEndpointCache(): void {\n endpointsCache = null\n endpointsByOperationId = null\n rawSpecCache = null\n}\n\n/**\n * Extract simplified request body schema for LLM consumption.\n * Returns required fields and basic property info without deep nesting.\n */\nexport function simplifyRequestBodySchema(\n schema: Record<string, unknown> | null\n): { required: string[]; properties: Record<string, { type: string; format?: string; enum?: string[] }> } | null {\n if (!schema) return null\n\n const properties: Record<string, { type: string; format?: string; enum?: string[] }> = {}\n const required: string[] = (schema.required as string[]) || []\n\n const schemaProps = (schema.properties || schema) as Record<string, unknown>\n\n for (const [key, value] of Object.entries(schemaProps)) {\n if (typeof value !== 'object' || value === null) continue\n const propSchema = value as Record<string, unknown>\n\n const prop: { type: string; format?: string; enum?: string[] } = {\n type: (propSchema.type as string) || 'unknown',\n }\n\n if (propSchema.format) prop.format = propSchema.format as string\n if (propSchema.enum && Array.isArray(propSchema.enum)) {\n prop.enum = propSchema.enum.slice(0, 10) as string[] // Limit enum values\n }\n\n properties[key] = prop\n }\n\n return { required, properties }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -15,6 +15,13 @@ import {
15
15
  buildSearchLabel,
16
16
  incrementToolCallCount
17
17
  } from "./session-memory.js";
18
+ import { fetchWithTimeout, resolveTimeoutMs } from "@open-mercato/shared/lib/http/fetchWithTimeout";
19
+ const DEFAULT_AI_API_REQUEST_TIMEOUT_MS = 3e4;
20
+ function resolveAiApiRequestTimeoutMs() {
21
+ const raw = process.env.AI_API_REQUEST_TIMEOUT_MS;
22
+ const parsed = raw ? Number.parseInt(raw, 10) : void 0;
23
+ return resolveTimeoutMs(parsed, DEFAULT_AI_API_REQUEST_TIMEOUT_MS);
24
+ }
18
25
  let cachedCodeModeSpec = null;
19
26
  let cachedCommonTypes = null;
20
27
  const CODE_MODE_REQUIRED_FEATURES = ["ai_assistant.view"];
@@ -572,10 +579,11 @@ function createApiRequestFn(ctx, onCall) {
572
579
  if (ctx.apiKeySecret) headers["X-API-Key"] = ctx.apiKeySecret;
573
580
  if (ctx.tenantId) headers["X-Tenant-Id"] = ctx.tenantId;
574
581
  if (ctx.organizationId) headers["X-Organization-Id"] = ctx.organizationId;
575
- const response = await globalThis.fetch(url, {
582
+ const response = await fetchWithTimeout(url, {
576
583
  method: normalizedMethod,
577
584
  headers,
578
- body: requestBody ? JSON.stringify(requestBody) : void 0
585
+ body: requestBody ? JSON.stringify(requestBody) : void 0,
586
+ timeoutMs: resolveAiApiRequestTimeoutMs()
579
587
  });
580
588
  const responseText = await response.text();
581
589
  const data = tryParseJson(responseText);