@open-mercato/ai-assistant 0.6.1-develop.3278.1.a4d26475e4 → 0.6.1-develop.3291.1.6fad645fd0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +39 -14
- package/README.md +2 -1
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js +0 -111
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +2 -2
- package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/http-server.js +0 -5
- package/dist/modules/ai_assistant/lib/http-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js +0 -5
- package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
- package/dist/modules/ai_assistant/lib/mcp-server.js +0 -5
- package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
- package/package.json +4 -4
- package/src/modules/ai_assistant/lib/__tests__/mcp-startup-no-dead-index.test.ts +65 -0
- package/src/modules/ai_assistant/lib/api-endpoint-index.ts +5 -186
- package/src/modules/ai_assistant/lib/codemode-tools.ts +0 -2
- package/src/modules/ai_assistant/lib/http-server.ts +2 -9
- package/src/modules/ai_assistant/lib/mcp-dev-server.ts +2 -8
- package/src/modules/ai_assistant/lib/mcp-server.ts +1 -10
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js +0 -170
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +0 -7
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +0 -177
- package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +0 -7
- package/dist/modules/ai_assistant/lib/entity-graph-tools.js +0 -127
- package/dist/modules/ai_assistant/lib/entity-graph-tools.js.map +0 -7
- package/src/modules/ai_assistant/lib/api-discovery-tools.ts +0 -250
- package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +0 -243
- package/src/modules/ai_assistant/lib/entity-graph-tools.ts +0 -192
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:ai-assistant] found
|
|
1
|
+
[build:ai-assistant] found 165 entry points
|
|
2
2
|
[build:ai-assistant] built successfully
|
package/AGENTS.md
CHANGED
|
@@ -466,9 +466,7 @@ packages/ai-assistant/
|
|
|
466
466
|
│ │ │ ├── codemode-tools.ts # Code Mode search + execute tools
|
|
467
467
|
│ │ │ ├── sandbox.ts # node:vm sandbox executor
|
|
468
468
|
│ │ │ ├── truncate.ts # Response size limiter
|
|
469
|
-
│ │ │ ├── api-endpoint-index.ts # OpenAPI endpoint
|
|
470
|
-
│ │ │ ├── api-discovery-tools.ts # (legacy, unused) old find_api/call_api
|
|
471
|
-
│ │ │ ├── entity-graph-tools.ts # (legacy, unused) old discover_schema
|
|
469
|
+
│ │ │ ├── api-endpoint-index.ts # OpenAPI endpoint parsing + raw spec cache for Code Mode
|
|
472
470
|
│ │ │ ├── http-server.ts # MCP HTTP server implementation
|
|
473
471
|
│ │ │ ├── mcp-server.ts # MCP stdio server implementation
|
|
474
472
|
│ │ │ ├── tool-registry.ts # Global tool registration
|
|
@@ -889,21 +887,28 @@ normalizeCode(code: string): string // Strip markdown fences, validate shape
|
|
|
889
887
|
truncateResult(value, maxChars?): string // Default 40K chars (~10K tokens)
|
|
890
888
|
```
|
|
891
889
|
|
|
892
|
-
**Legacy files kept but unused**: `lib/api-discovery-tools.ts` (old find_api/call_api) and `lib/entity-graph-tools.ts` (old discover_schema) remain in the tree but are no longer imported.
|
|
893
|
-
|
|
894
890
|
## Rules for the API Endpoint Index
|
|
895
891
|
|
|
896
|
-
Located in `lib/api-endpoint-index.ts`.
|
|
892
|
+
Located in `lib/api-endpoint-index.ts`. The module exposes pure functions — never instantiate. The Code Mode `search` and `execute` tools consume `getRawOpenApiSpec()` / `loadRichOpenApiSpec()` and `getApiEndpoints()`; no other live consumer exists.
|
|
897
893
|
|
|
898
894
|
```typescript
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
895
|
+
// Endpoint parsing (cached per process)
|
|
896
|
+
getApiEndpoints(): Promise<ApiEndpoint[]>
|
|
897
|
+
getEndpointByOperationId(operationId: string): Promise<ApiEndpoint | null>
|
|
898
|
+
clearEndpointCache(): void
|
|
899
|
+
|
|
900
|
+
// Raw spec for Code Mode
|
|
901
|
+
getRawOpenApiSpec(): Promise<OpenApiDocument | null>
|
|
902
|
+
loadRichOpenApiSpec(): Promise<OpenApiDocument | null>
|
|
903
|
+
setRawSpecCache(doc: OpenApiDocument): void
|
|
904
|
+
clearRawSpecCache(): void
|
|
905
|
+
|
|
906
|
+
// Helper for request body schema flattening
|
|
907
|
+
simplifyRequestBodySchema(schema): { required, properties } | null
|
|
905
908
|
```
|
|
906
909
|
|
|
910
|
+
The module deliberately **does not** call `searchService.bulkIndex(...)` on boot — it has no live reader, and on a large OpenAPI surface (~600 operations) the fan-out triggered the embedding storm fixed by #1876.
|
|
911
|
+
|
|
907
912
|
## Docker Configuration
|
|
908
913
|
|
|
909
914
|
### Rules for the OpenCode Container
|
|
@@ -1407,6 +1412,26 @@ if (tool.requiredFeatures?.length) {
|
|
|
1407
1412
|
|
|
1408
1413
|
## Changelog
|
|
1409
1414
|
|
|
1415
|
+
### 2026-05-13 - Remove dead `indexApiEndpoints` from MCP boot (#1876)
|
|
1416
|
+
|
|
1417
|
+
**What changed**:
|
|
1418
|
+
- MCP HTTP / stdio / dev entry points no longer call `indexApiEndpoints(searchService)` at startup. The Code Mode rewrite (2026-02-22) deleted the only readers (`find_api` / `call_api` / `discover_schema`), so the call was indexing into fulltext + tokens + vector indexes that nothing queried. On large specs (≳200 ops + remote embedding latency) the search-service fan-out also burned an `OpenAI` embedding storm + pgvector load on every boot — Code Mode reads the OpenAPI document directly via `getRawOpenApiSpec()` / `loadRichOpenApiSpec()`, in-memory.
|
|
1419
|
+
- Deleted `lib/api-discovery-tools.ts`, `lib/entity-graph-tools.ts`, `lib/api-endpoint-index-config.ts` — all dead since the 2026-02-22 rewrite, kept only by the boot-time indexing call we removed.
|
|
1420
|
+
- Pruned `lib/api-endpoint-index.ts`: removed `indexApiEndpoints`, `searchEndpoints`, `searchEndpointsFallback`, `buildSearchableContent`, `lastIndexChecksum`, and the `API_ENDPOINT_ENTITY` deprecated alias. Kept `parseApiEndpoints` (private), `getApiEndpoints`, `getEndpointByOperationId`, `getRawOpenApiSpec`, `loadRichOpenApiSpec`, `setRawSpecCache`, `clearRawSpecCache`, `clearEndpointCache`, `simplifyRequestBodySchema` — those still serve Code Mode.
|
|
1421
|
+
|
|
1422
|
+
**Files modified**:
|
|
1423
|
+
- `lib/http-server.ts`, `lib/mcp-server.ts`, `lib/mcp-dev-server.ts` — removed the `indexApiEndpoints` import + call
|
|
1424
|
+
- `lib/api-endpoint-index.ts` — pruned dead code
|
|
1425
|
+
|
|
1426
|
+
**Files deleted**:
|
|
1427
|
+
- `lib/api-discovery-tools.ts`
|
|
1428
|
+
- `lib/entity-graph-tools.ts`
|
|
1429
|
+
- `lib/api-endpoint-index-config.ts`
|
|
1430
|
+
|
|
1431
|
+
**Backward compatibility**: All removed symbols (`indexApiEndpoints`, `searchEndpoints`, `searchEndpointsFallback`, `endpointToIndexableRecord`, `API_ENDPOINT_ENTITY`, `API_ENDPOINT_SEARCH_CONFIG`, `apiEndpointEntityConfig`, `computeEndpointsChecksum`, `API_ENDPOINT_ENTITY_ID`, `GLOBAL_TENANT_ID` from `api-endpoint-index-config`) live inside the module's internal `lib/` path and are not part of the documented developer contract surface (see `BACKWARD_COMPATIBILITY.md`). They were already documented as legacy and unused.
|
|
1432
|
+
|
|
1433
|
+
**Operator cleanup (optional)**: If a deployment previously booted MCP and accumulated rows in fulltext/vector/tokens under `entityId: 'ai_assistant:api_endpoint'` / `tenantId: '00000000-0000-0000-0000-000000000000'`, they are orphaned but inert — no live workflow reads them. Manual purge is purely cosmetic.
|
|
1434
|
+
|
|
1410
1435
|
### 2026-05-08 - Phase 3 call-site cleanup (spec 2026-04-27-ai-agents-provider-model-baseurl-overrides)
|
|
1411
1436
|
|
|
1412
1437
|
**What changed**:
|
|
@@ -1438,8 +1463,8 @@ if (tool.requiredFeatures?.length) {
|
|
|
1438
1463
|
- `lib/mcp-server.ts` — Generates entity graph and caches spec for stdio mode
|
|
1439
1464
|
|
|
1440
1465
|
**Files kept but unused**:
|
|
1441
|
-
- `lib/api-discovery-tools.ts` — Old find_api/call_api (no longer imported)
|
|
1442
|
-
- `lib/entity-graph-tools.ts` — Old discover_schema (no longer imported)
|
|
1466
|
+
- `lib/api-discovery-tools.ts` — Old find_api/call_api (no longer imported, deleted in #1876)
|
|
1467
|
+
- `lib/entity-graph-tools.ts` — Old discover_schema (no longer imported, deleted in #1876)
|
|
1443
1468
|
|
|
1444
1469
|
### 2026-01-17 - Session Persistence Fix
|
|
1445
1470
|
|
package/README.md
CHANGED
|
@@ -512,7 +512,8 @@ packages/ai-assistant/
|
|
|
512
512
|
│ │ ├── lib/
|
|
513
513
|
│ │ │ ├── opencode-client.ts # OpenCode API client
|
|
514
514
|
│ │ │ ├── opencode-handlers.ts # Request handlers
|
|
515
|
-
│ │ │ ├──
|
|
515
|
+
│ │ │ ├── codemode-tools.ts # Code Mode meta-tools (search + execute)
|
|
516
|
+
│ │ │ ├── api-endpoint-index.ts # OpenAPI parsing + raw spec cache
|
|
516
517
|
│ │ │ ├── http-server.ts # MCP HTTP server
|
|
517
518
|
│ │ │ ├── mcp-dev-server.ts # Development MCP server
|
|
518
519
|
│ │ │ └── tool-registry.ts # Tool registration
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { buildOpenApiDocument } from "@open-mercato/shared/lib/openapi";
|
|
2
2
|
import { fetchWithTimeout, resolveTimeoutMs } from "@open-mercato/shared/lib/http/fetchWithTimeout";
|
|
3
|
-
import {
|
|
4
|
-
API_ENDPOINT_ENTITY_ID,
|
|
5
|
-
GLOBAL_TENANT_ID,
|
|
6
|
-
API_ENDPOINT_SEARCH_CONFIG,
|
|
7
|
-
endpointToIndexableRecord,
|
|
8
|
-
computeEndpointsChecksum
|
|
9
|
-
} from "./api-endpoint-index-config.js";
|
|
10
3
|
const DEFAULT_OPENAPI_FETCH_TIMEOUT_MS = 1e4;
|
|
11
4
|
function resolveOpenapiFetchTimeoutMs() {
|
|
12
5
|
const raw = process.env.AI_OPENAPI_FETCH_TIMEOUT_MS;
|
|
13
6
|
const parsed = raw ? Number.parseInt(raw, 10) : void 0;
|
|
14
7
|
return resolveTimeoutMs(parsed, DEFAULT_OPENAPI_FETCH_TIMEOUT_MS);
|
|
15
8
|
}
|
|
16
|
-
const API_ENDPOINT_ENTITY = API_ENDPOINT_ENTITY_ID;
|
|
17
9
|
let endpointsCache = null;
|
|
18
10
|
let endpointsByOperationId = null;
|
|
19
11
|
let rawSpecCache = null;
|
|
@@ -276,106 +268,6 @@ function extractRequestBodySchema(requestBody, schemas) {
|
|
|
276
268
|
}
|
|
277
269
|
return schema;
|
|
278
270
|
}
|
|
279
|
-
let lastIndexChecksum = null;
|
|
280
|
-
async function indexApiEndpoints(searchService, force = false) {
|
|
281
|
-
const endpoints = await getApiEndpoints();
|
|
282
|
-
if (endpoints.length === 0) {
|
|
283
|
-
console.error("[API Index] No endpoints to index");
|
|
284
|
-
return 0;
|
|
285
|
-
}
|
|
286
|
-
const checksum = computeEndpointsChecksum(
|
|
287
|
-
endpoints.map((e) => ({ operationId: e.operationId, method: e.method, path: e.path }))
|
|
288
|
-
);
|
|
289
|
-
if (!force && lastIndexChecksum === checksum) {
|
|
290
|
-
console.error(`[API Index] Skipping indexing - ${endpoints.length} endpoints unchanged`);
|
|
291
|
-
return 0;
|
|
292
|
-
}
|
|
293
|
-
const records = endpoints.map(
|
|
294
|
-
(endpoint) => endpointToIndexableRecord(endpoint)
|
|
295
|
-
);
|
|
296
|
-
try {
|
|
297
|
-
console.error(`[API Index] Starting bulk index of ${records.length} endpoints...`);
|
|
298
|
-
const timeoutMs = 6e4;
|
|
299
|
-
const indexPromise = searchService.bulkIndex(records);
|
|
300
|
-
const timeoutPromise = new Promise(
|
|
301
|
-
(_, reject) => setTimeout(() => reject(new Error(`Bulk index timed out after ${timeoutMs}ms`)), timeoutMs)
|
|
302
|
-
);
|
|
303
|
-
await Promise.race([indexPromise, timeoutPromise]);
|
|
304
|
-
lastIndexChecksum = checksum;
|
|
305
|
-
console.error(`[API Index] Indexed ${records.length} API endpoints for hybrid search`);
|
|
306
|
-
return records.length;
|
|
307
|
-
} catch (error) {
|
|
308
|
-
console.error("[API Index] Failed to index endpoints:", error);
|
|
309
|
-
lastIndexChecksum = checksum;
|
|
310
|
-
return records.length;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
function buildSearchableContent(endpoint) {
|
|
314
|
-
const parts = [
|
|
315
|
-
endpoint.operationId,
|
|
316
|
-
endpoint.method,
|
|
317
|
-
endpoint.path,
|
|
318
|
-
endpoint.summary,
|
|
319
|
-
endpoint.description,
|
|
320
|
-
...endpoint.tags,
|
|
321
|
-
...endpoint.parameters.map((p) => `${p.name} ${p.description}`)
|
|
322
|
-
];
|
|
323
|
-
return parts.filter(Boolean).join(" ");
|
|
324
|
-
}
|
|
325
|
-
async function searchEndpoints(searchService, query, options = {}) {
|
|
326
|
-
const { limit = API_ENDPOINT_SEARCH_CONFIG.defaultLimit, method } = options;
|
|
327
|
-
await getApiEndpoints();
|
|
328
|
-
if (searchService) {
|
|
329
|
-
try {
|
|
330
|
-
const results = await searchService.search(query, {
|
|
331
|
-
tenantId: GLOBAL_TENANT_ID,
|
|
332
|
-
organizationId: null,
|
|
333
|
-
entityTypes: [API_ENDPOINT_ENTITY_ID],
|
|
334
|
-
limit: limit * 2
|
|
335
|
-
// Get extra to account for filtering
|
|
336
|
-
});
|
|
337
|
-
const endpoints = [];
|
|
338
|
-
for (const result of results) {
|
|
339
|
-
if (endpoints.length >= limit) break;
|
|
340
|
-
const endpoint = endpointsByOperationId?.get(result.recordId);
|
|
341
|
-
if (endpoint) {
|
|
342
|
-
if (method && endpoint.method !== method.toUpperCase()) continue;
|
|
343
|
-
endpoints.push(endpoint);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (endpoints.length > 0) {
|
|
347
|
-
return endpoints;
|
|
348
|
-
}
|
|
349
|
-
console.error("[API Index] No hybrid search results, falling back to in-memory search");
|
|
350
|
-
} catch (error) {
|
|
351
|
-
console.error("[API Index] Hybrid search failed, falling back to in-memory:", error);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return searchEndpointsFallback(query, { limit, method });
|
|
355
|
-
}
|
|
356
|
-
function searchEndpointsFallback(query, options = {}) {
|
|
357
|
-
const { limit = API_ENDPOINT_SEARCH_CONFIG.defaultLimit, method } = options;
|
|
358
|
-
if (!endpointsCache) {
|
|
359
|
-
return [];
|
|
360
|
-
}
|
|
361
|
-
const queryLower = query.toLowerCase();
|
|
362
|
-
const queryTerms = queryLower.split(/\s+/).filter(Boolean);
|
|
363
|
-
let matches = endpointsCache.filter((endpoint) => {
|
|
364
|
-
const content = buildSearchableContent(endpoint).toLowerCase();
|
|
365
|
-
return queryTerms.some((term) => content.includes(term));
|
|
366
|
-
});
|
|
367
|
-
if (method) {
|
|
368
|
-
matches = matches.filter((e) => e.method === method.toUpperCase());
|
|
369
|
-
}
|
|
370
|
-
matches.sort((a, b) => {
|
|
371
|
-
const aContent = buildSearchableContent(a).toLowerCase();
|
|
372
|
-
const bContent = buildSearchableContent(b).toLowerCase();
|
|
373
|
-
const aScore = queryTerms.filter((t) => aContent.includes(t)).length;
|
|
374
|
-
const bScore = queryTerms.filter((t) => bContent.includes(t)).length;
|
|
375
|
-
return bScore - aScore;
|
|
376
|
-
});
|
|
377
|
-
return matches.slice(0, limit);
|
|
378
|
-
}
|
|
379
271
|
function clearEndpointCache() {
|
|
380
272
|
endpointsCache = null;
|
|
381
273
|
endpointsByOperationId = null;
|
|
@@ -401,15 +293,12 @@ function simplifyRequestBodySchema(schema) {
|
|
|
401
293
|
return { required, properties };
|
|
402
294
|
}
|
|
403
295
|
export {
|
|
404
|
-
API_ENDPOINT_ENTITY,
|
|
405
296
|
clearEndpointCache,
|
|
406
297
|
clearRawSpecCache,
|
|
407
298
|
getApiEndpoints,
|
|
408
299
|
getEndpointByOperationId,
|
|
409
300
|
getRawOpenApiSpec,
|
|
410
|
-
indexApiEndpoints,
|
|
411
301
|
loadRichOpenApiSpec,
|
|
412
|
-
searchEndpoints,
|
|
413
302
|
setRawSpecCache,
|
|
414
303
|
simplifyRequestBodySchema
|
|
415
304
|
};
|
|
@@ -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 { 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": "
|
|
4
|
+
"sourcesContent": ["/**\n * API Endpoint Index\n *\n * Parses the OpenAPI spec into a cached, in-memory list of endpoints and\n * exposes the raw OpenAPI document to Code Mode's `search` tool. The Code\n * Mode rewrite (2026-02-22) made this the only consumer \u2014 the legacy\n * `find_api` / `call_api` / `discover_schema` tools and their search-index\n * fan-out have been removed.\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 { fetchWithTimeout, resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\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 * 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 * 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
|
}
|