@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.
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js +10 -2
- package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js +13 -2
- package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +2 -2
- package/dist/modules/ai_assistant/lib/codemode-tools.js +10 -2
- package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/opencode-client.js +73 -25
- package/dist/modules/ai_assistant/lib/opencode-client.js.map +2 -2
- package/package.json +4 -4
- package/src/modules/ai_assistant/lib/__tests__/opencode-client.test.ts +76 -0
- package/src/modules/ai_assistant/lib/api-discovery-tools.ts +11 -1
- package/src/modules/ai_assistant/lib/api-endpoint-index.ts +15 -2
- package/src/modules/ai_assistant/lib/codemode-tools.ts +11 -1
- package/src/modules/ai_assistant/lib/opencode-client.ts +64 -14
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/codemode-tools.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Code Mode Tools\n *\n * Two meta-tools that replace all individual API/schema/module tools:\n * - search: Query the OpenAPI spec + entity graph programmatically\n * - execute: Make API calls via a sandboxed api.request() wrapper\n *\n * The AI writes JavaScript that runs in a node:vm sandbox with injected globals.\n */\n\nimport { z } from 'zod'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { registerMcpTool } from './tool-registry'\nimport type { McpToolContext } from './types'\nimport { createSandbox } from './sandbox'\nimport { truncateResult } from './truncate'\nimport { hasRequiredFeatures } from './auth'\nimport { getApiEndpoints, getRawOpenApiSpec, type ApiEndpoint } from './api-endpoint-index'\nimport {\n getCachedEntityGraph,\n inferModuleFromEntity,\n type EntityGraph,\n} from './entity-graph'\nimport {\n lookupSearchCache,\n storeSearchResult,\n buildMemoryContext,\n buildSearchLabel,\n incrementToolCallCount,\n} from './session-memory'\n\n/**\n * Cached spec object combining OpenAPI paths + entity schemas.\n */\nlet cachedCodeModeSpec: Record<string, unknown> | null = null\n\n/**\n * Cached TypeScript type stubs for common CRUD endpoints.\n * Generated once at startup from the OpenAPI spec.\n */\nlet cachedCommonTypes: string | null = null\n\nexport const CODE_MODE_REQUIRED_FEATURES = ['ai_assistant.view'] as const\n\n/**\n * Build the merged spec object for the search tool.\n */\nasync function getCodeModeSpec(): Promise<Record<string, unknown>> {\n if (cachedCodeModeSpec) return cachedCodeModeSpec\n\n const rawSpec = await getRawOpenApiSpec()\n const graph = getCachedEntityGraph()\n\n const paths = (rawSpec?.paths ?? {}) as Record<string, Record<string, unknown>>\n const entitySchemas = graph ? buildEntitySchemas(graph) : []\n\n const spec: Record<string, unknown> = {\n paths,\n info: rawSpec?.info,\n components: rawSpec?.components,\n entitySchemas,\n }\n\n // --- Helper functions injected into sandbox ---\n\n /**\n * spec.findEndpoints(keyword) \u2014 find all endpoints matching a keyword.\n * Returns compact list: [{ path, methods }]\n */\n spec.findEndpoints = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return Object.entries(paths)\n .filter(([path]) => path.toLowerCase().includes(kw))\n .map(([path, methods]) => ({\n path,\n methods: Object.keys(methods).filter((m) => m !== 'parameters'),\n }))\n }\n\n /**\n * spec.describeEndpoint(path, method) \u2014 compact endpoint profile with working example.\n * Returns: { path, method, summary, requiredFields, optionalFields, nestedCollections, example, relatedEndpoints, relatedEntity }\n * For full schema access, use: spec.paths[path][method].requestBody\n */\n spec.describeEndpoint = (path: string, method: string) => {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) return null\n\n const endpoint = pathObj[method.toLowerCase()] as Record<string, unknown> | undefined\n if (!endpoint) return null\n\n // Extract requestBody JSON Schema\n const bodySchema = extractRequestBodySchema(endpoint)\n const bodyProps = (bodySchema?.properties ?? {}) as Record<string, Record<string, unknown>>\n const bodyRequired = (bodySchema?.required ?? []) as string[]\n\n // Split fields into required (with types) vs optional (names only)\n const requiredFields: Array<{ name: string; type: string; format?: string }> = []\n const optionalFields: string[] = []\n const nestedCollections: Array<{\n field: string\n type: string\n requiredFields: Array<{ name: string; type: string }>\n commonFields: string[]\n }> = []\n\n for (const [name, prop] of Object.entries(bodyProps)) {\n const propType = (prop.type as string) || 'string'\n\n // Detect nested array collections (e.g. lines, items, addresses)\n if (propType === 'array' && prop.items && (prop.items as Record<string, unknown>).type === 'object') {\n const itemSchema = prop.items as Record<string, unknown>\n const itemProps = (itemSchema.properties ?? {}) as Record<string, Record<string, unknown>>\n const itemRequired = (itemSchema.required ?? []) as string[]\n\n const nestedRequired = itemRequired.map((n) => ({\n name: n,\n type: ((itemProps[n]?.type as string) || 'string'),\n }))\n\n // Common fields: first few non-required fields that are likely user-provided\n const nestedOptional = Object.keys(itemProps).filter((n) => !itemRequired.includes(n))\n const commonFields = nestedOptional.slice(0, 6)\n\n nestedCollections.push({\n field: name,\n type: 'array',\n requiredFields: nestedRequired,\n commonFields,\n })\n continue\n }\n\n if (bodyRequired.includes(name)) {\n const field: { name: string; type: string; format?: string } = { name, type: propType }\n if (prop.format) field.format = prop.format as string\n requiredFields.push(field)\n } else {\n optionalFields.push(name)\n }\n }\n\n // Generate minimal working example from required fields + nested collections\n const example: Record<string, unknown> = {}\n for (const field of requiredFields) {\n example[field.name] = generatePlaceholder(field.type, field.format)\n }\n for (const collection of nestedCollections) {\n const itemExample: Record<string, unknown> = {}\n for (const field of collection.requiredFields) {\n itemExample[field.name] = generatePlaceholder(field.type)\n }\n // Add first 2 common fields to the example\n for (const name of collection.commonFields.slice(0, 2)) {\n itemExample[name] = '<value>'\n }\n example[collection.field] = [itemExample]\n }\n\n // Find related endpoints sharing the same module prefix\n const segments = path.replace('/api/', '').split('/')\n const moduleSegment = segments[0]\n const resourceName = segments[1] || segments[0]\n const modulePrefix = `/api/${moduleSegment}/`\n const relatedEndpoints = Object.entries(paths)\n .filter(([p]) => p.startsWith(modulePrefix) && p !== path && !p.includes('{'))\n .map(([p, methods]) => ({\n path: p,\n methods: Object.keys(methods as Record<string, unknown>).filter((m) => m !== 'parameters'),\n }))\n .slice(0, 8)\n\n // Compact entity: className + relationship summary\n const resourceNorm = resourceName.replace(/-/g, '_')\n const resourceSingular = resourceNorm.endsWith('s') ? resourceNorm.slice(0, -1) : resourceNorm\n const moduleSingular = moduleSegment.endsWith('s') ? moduleSegment.slice(0, -1) : moduleSegment\n const prefixedTable = `${moduleSingular}_${resourceNorm}`\n\n const entity = entitySchemas.find((e: Record<string, unknown>) => {\n const table = ((e.tableName as string) || '').toLowerCase()\n const cls = ((e.className as string) || '').toLowerCase()\n const mod = ((e.module as string) || '').toLowerCase()\n if (table === resourceNorm || table === prefixedTable) return true\n if (cls.includes(moduleSingular) && cls.includes(resourceSingular)) return true\n if (mod === moduleSegment && cls.includes(resourceSingular)) return true\n if (cls === resourceSingular || cls.includes(resourceSingular)) return true\n return false\n }) || null\n\n let relatedEntity: string | null = null\n if (entity) {\n const ent = entity as Record<string, unknown>\n const rels = (ent.relationships as Array<{ relationship: string; target: string }>) || []\n const relSummary = rels.map((r) => `${r.relationship}: ${r.target}`).join(', ')\n relatedEntity = `${ent.className}${relSummary ? ` (${relSummary})` : ''}`\n }\n\n // GET endpoints: include query parameters compactly\n const parameters = method.toLowerCase() === 'get'\n ? (endpoint.parameters as Array<Record<string, unknown>> || [])\n .filter((p) => p.in === 'query')\n .map((p) => p.name as string)\n : undefined\n\n return {\n path,\n method: method.toUpperCase(),\n summary: endpoint.summary || endpoint.description,\n ...(parameters && parameters.length > 0 ? { queryParams: parameters } : {}),\n ...(requiredFields.length > 0 ? { requiredFields } : {}),\n ...(optionalFields.length > 0 ? { optionalFields } : {}),\n ...(nestedCollections.length > 0 ? { nestedCollections } : {}),\n ...(Object.keys(example).length > 0 ? { example } : {}),\n ...(relatedEndpoints.length > 0 ? { relatedEndpoints } : {}),\n relatedEntity,\n }\n }\n\n /**\n * spec.describeEntity(keyword) \u2014 find entity by keyword and return its full schema.\n * Returns: { className, tableName, module, fields, relationships }\n */\n spec.describeEntity = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return entitySchemas.find((e: Record<string, unknown>) => {\n const cls = (e.className as string || '').toLowerCase()\n const table = (e.tableName as string || '').toLowerCase()\n return cls.includes(kw) || table.includes(kw)\n }) || null\n }\n\n cachedCodeModeSpec = spec\n return spec\n}\n\n/**\n * Extract the JSON Schema from an OpenAPI endpoint's requestBody.\n * Handles the common `content['application/json'].schema` path.\n */\nfunction extractRequestBodySchema(\n endpoint: Record<string, unknown>\n): Record<string, unknown> | null {\n const requestBody = endpoint.requestBody as Record<string, unknown> | undefined\n if (!requestBody) return null\n\n const content = requestBody.content as Record<string, Record<string, unknown>> | undefined\n if (!content) return null\n\n const jsonContent = content['application/json']\n if (!jsonContent) return null\n\n return (jsonContent.schema as Record<string, unknown>) || null\n}\n\n/**\n * Generate a placeholder value for a given JSON Schema type.\n */\nfunction generatePlaceholder(type: string, format?: string): unknown {\n if (format === 'uuid' || format === 'objectId') return '<uuid>'\n if (format === 'date-time' || format === 'date') return '<date>'\n if (format === 'email') return '<email>'\n switch (type) {\n case 'string': return '<string>'\n case 'number':\n case 'integer': return 0\n case 'boolean': return false\n case 'array': return []\n default: return '<value>'\n }\n}\n\n/**\n * Common CRUD endpoints to pre-generate types for.\n * These are the endpoints the agent uses most and where debug spirals happen.\n */\nconst COMMON_ENDPOINTS: Array<{ path: string; method: string; typeName: string }> = [\n { path: '/api/sales/quotes', method: 'post', typeName: 'CreateQuote' },\n { path: '/api/sales/orders', method: 'post', typeName: 'CreateOrder' },\n { path: '/api/sales/invoices', method: 'post', typeName: 'CreateInvoice' },\n { path: '/api/customers/companies', method: 'post', typeName: 'CreateCompany' },\n { path: '/api/customers/people', method: 'post', typeName: 'CreatePerson' },\n { path: '/api/customers/deals', method: 'post', typeName: 'CreateDeal' },\n { path: '/api/catalog/products', method: 'post', typeName: 'CreateProduct' },\n { path: '/api/customers/companies', method: 'put', typeName: 'UpdateCompany' },\n { path: '/api/customers/people', method: 'put', typeName: 'UpdatePerson' },\n { path: '/api/sales/quotes', method: 'put', typeName: 'UpdateQuote' },\n]\n\n/**\n * Generate TypeScript-like type stubs from the OpenAPI spec for common endpoints.\n * This runs once at startup and injects types into the execute tool description\n * so the LLM sees the correct payload shape without needing to call describeEndpoint.\n */\nasync function generateCommonTypes(): Promise<string> {\n if (cachedCommonTypes) return cachedCommonTypes\n\n const rawSpec = await getRawOpenApiSpec()\n if (!rawSpec?.paths) {\n cachedCommonTypes = ''\n return ''\n }\n\n const paths = rawSpec.paths as Record<string, Record<string, unknown>>\n const typeLines: string[] = ['Available types for api.request() body:\\n']\n\n for (const { path, method, typeName } of COMMON_ENDPOINTS) {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) continue\n\n const endpoint = pathObj[method] as Record<string, unknown> | undefined\n if (!endpoint) continue\n\n const bodySchema = extractRequestBodySchema(endpoint)\n if (!bodySchema?.properties) continue\n\n const typeStr = schemaToTypeString(\n typeName,\n bodySchema,\n `${method.toUpperCase()} ${path}`,\n )\n if (typeStr) typeLines.push(typeStr)\n }\n\n if (typeLines.length <= 1) {\n cachedCommonTypes = ''\n return ''\n }\n\n cachedCommonTypes = typeLines.join('\\n')\n console.error(`[Code Mode] Generated ${typeLines.length - 1} common type stubs`)\n return cachedCommonTypes\n}\n\n/**\n * Convert a JSON Schema object to a compact TypeScript-like type string.\n * Produces a single-line or multi-line type declaration the LLM can use directly.\n */\nfunction schemaToTypeString(\n typeName: string,\n schema: Record<string, unknown>,\n comment: string,\n): string | null {\n const props = schema.properties as Record<string, Record<string, unknown>> | undefined\n if (!props) return null\n\n const required = new Set((schema.required as string[]) || [])\n\n // Skip internal fields that the sandbox injects automatically\n const skipFields = new Set(['tenantId', 'organizationId'])\n\n const fields: string[] = []\n const nestedTypes: string[] = []\n\n for (const [name, prop] of Object.entries(props)) {\n if (skipFields.has(name)) continue\n if (!prop || typeof prop !== 'object') continue\n\n const isRequired = required.has(name)\n const optMark = isRequired ? '' : '?'\n\n // Detect nested array of objects \u2192 extract as separate type\n if (\n prop.type === 'array' &&\n prop.items &&\n (prop.items as Record<string, unknown>).type === 'object'\n ) {\n const itemTypeName = `${typeName}${capitalize(singularize(name))}`\n const itemSchema = prop.items as Record<string, unknown>\n const nestedType = schemaToTypeString(itemTypeName, itemSchema, '')\n if (nestedType) nestedTypes.push(nestedType)\n fields.push(`${name}${optMark}: ${itemTypeName}[]`)\n continue\n }\n\n const propType = resolvePropertyType(prop)\n fields.push(`${name}${optMark}: ${propType}`)\n }\n\n if (fields.length === 0) return null\n\n const commentLine = comment ? `// ${comment}\\n` : ''\n const nested = nestedTypes.length > 0 ? nestedTypes.join('\\n') + '\\n' : ''\n return `${nested}${commentLine}type ${typeName} = { ${fields.join('; ')} }`\n}\n\n/**\n * Resolve a JSON Schema property to a compact TypeScript type string.\n */\nfunction resolvePropertyType(prop: Record<string, unknown>): string {\n // Handle anyOf (nullable types)\n if (prop.anyOf && Array.isArray(prop.anyOf)) {\n const variants = (prop.anyOf as Array<Record<string, unknown> | null>).filter(\n (s): s is Record<string, unknown> => s != null,\n )\n const nonNull = variants.filter((s) => s.type !== 'null')\n if (nonNull.length === 1) {\n return resolvePropertyType(nonNull[0]) + ' | null'\n }\n if (nonNull.length > 1) {\n return nonNull.map((s) => resolvePropertyType(s)).join(' | ')\n }\n }\n\n // Handle enum\n if (prop.enum && Array.isArray(prop.enum)) {\n return (prop.enum as string[]).map((v) => `'${v}'`).join(' | ')\n }\n\n const type = prop.type as string\n const format = prop.format as string | undefined\n\n if (type === 'array') {\n const items = prop.items as Record<string, unknown> | undefined\n if (items) return `${resolvePropertyType(items)}[]`\n return 'unknown[]'\n }\n\n if (type === 'object') return 'object'\n\n if (format === 'uuid') return 'string /*uuid*/'\n if (format === 'date-time') return 'string /*ISO date*/'\n if (format === 'date') return 'string /*date*/'\n if (format === 'email') return 'string /*email*/'\n\n switch (type) {\n case 'string': return 'string'\n case 'number':\n case 'integer': return 'number'\n case 'boolean': return 'boolean'\n default: return 'unknown'\n }\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction singularize(s: string): string {\n if (s.endsWith('ies')) return s.slice(0, -3) + 'y'\n if (s.endsWith('ses')) return s.slice(0, -2)\n if (s.endsWith('s') && !s.endsWith('ss')) return s.slice(0, -1)\n return s\n}\n\n/**\n * Format a 400 API error response into a human-readable fix instruction.\n * Parses Zod-style validation errors and produces a concise message the LLM can act on.\n */\nfunction formatValidationError(data: unknown): string {\n if (!data || typeof data !== 'object') {\n return `Validation error: ${JSON.stringify(data)}`\n }\n\n // Raw Zod v4 array format: [{ expected, code, path, message }]\n if (Array.isArray(data)) {\n const issues = data as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || `expected ${issue.expected}` || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n const obj = data as Record<string, unknown>\n\n // Zod v4 flat format: { fieldErrors: { field: [messages] }, formErrors: [messages] }\n if (obj.fieldErrors && typeof obj.fieldErrors === 'object') {\n const fieldErrors = obj.fieldErrors as Record<string, string[]>\n const parts: string[] = []\n for (const [field, messages] of Object.entries(fieldErrors)) {\n if (Array.isArray(messages) && messages.length > 0) {\n parts.push(`${field}: ${messages[0]}`)\n }\n }\n const formErrors = obj.formErrors as string[] | undefined\n if (Array.isArray(formErrors) && formErrors.length > 0) {\n parts.push(formErrors[0])\n }\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n // Zod v3 format: { issues: [{ path: [...], message, code }] }\n if (obj.issues && Array.isArray(obj.issues)) {\n const issues = obj.issues as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n\n // Our API error format: { error: string, details: ... }\n if (obj.error && typeof obj.error === 'string') {\n const details = obj.details\n if (details && typeof details === 'object') {\n return formatValidationError(details)\n }\n return obj.error\n }\n\n // Generic: { message: string }\n if (obj.message && typeof obj.message === 'string') {\n return obj.message\n }\n\n // Fallback: compact JSON\n const json = JSON.stringify(data)\n if (json.length > 500) {\n return `Validation error (truncated): ${json.slice(0, 500)}...`\n }\n return `Validation error: ${json}`\n}\n\n/**\n * Build entity schema array from the entity graph.\n * Same structure as buildEntityResult in entity-graph-tools.ts.\n */\nfunction buildEntitySchemas(graph: EntityGraph) {\n return graph.nodes.map((node) => {\n const relationships = graph.edges\n .filter((edge) => edge.source === node.className)\n .map((edge) => ({\n relationship: edge.relationship,\n target: edge.target,\n property: edge.property,\n nullable: edge.nullable,\n }))\n\n return {\n className: node.className,\n tableName: node.tableName,\n module: inferModuleFromEntity(node.className, node.tableName),\n fields: node.properties,\n relationships,\n }\n })\n}\n\n/**\n * Detect mutation HTTP methods in code via static analysis.\n * Returns which methods were found (POST, PUT, PATCH, DELETE).\n */\nfunction detectMutationInCode(code: string): { hasMutation: boolean; methods: string[] } {\n const methods: string[] = []\n const pattern = /method:\\s*['\"](\\w+)['\"]/gi\n let match\n while ((match = pattern.exec(code)) !== null) {\n const method = match[1].toUpperCase()\n if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n methods.push(method)\n }\n }\n return { hasMutation: methods.length > 0, methods }\n}\n\n/**\n * Load and register the two Code Mode tools.\n * Generates TypeScript type stubs for common endpoints at startup.\n * @returns Number of tools registered (always 2)\n */\nexport async function loadCodeModeTools(): Promise<number> {\n const commonTypes = await generateCommonTypes()\n registerSearchTool()\n registerExecuteTool(commonTypes)\n return 2\n}\n\n/**\n * search \u2014 Query the OpenAPI spec and entity graph programmatically.\n */\nfunction registerSearchTool(): void {\n registerMcpTool(\n {\n name: 'search',\n description: `Query the OpenAPI spec and entity schemas. READ-ONLY, no side effects.\nGlobals: spec.findEndpoints(keyword), spec.describeEndpoint(path, method), spec.describeEntity(keyword), spec.paths, spec.entitySchemas.\nUse BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common paths (companies, people, orders, quotes, products).`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'An async arrow function that queries spec, e.g. async () => spec.paths[\"/api/customers/companies\"]'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n const codePreview = input.code.slice(0, 120).replace(/\\n/g, ' ')\n console.error(`[AI Usage] search: code=\"${codePreview}${input.code.length > 120 ? '...' : ''}\"`)\n\n // Check session memory for cached result\n if (ctx.sessionId) {\n const cached = lookupSearchCache(ctx.sessionId, input.code)\n if (cached) {\n console.error(`[AI Usage] search: CACHE HIT (label=\"${cached.label}\")`)\n const memoryContext = buildMemoryContext(ctx.sessionId)\n return {\n success: true,\n result: cached.result,\n fromCache: true,\n _memoryContext: memoryContext,\n }\n }\n\n // Enforce tool call limit\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n console.error(`[AI Usage] search: TOOL CALL LIMIT EXCEEDED (count=${count})`)\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n const spec = await getCodeModeSpec()\n const sandbox = createSandbox({ spec })\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n console.error(`[AI Usage] search: ERROR in ${result.durationMs}ms \u2014 ${result.error}`)\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n }\n }\n\n const truncated = truncateResult(result.result)\n console.error(`[AI Usage] search: OK in ${result.durationMs}ms \u2014 ${truncated.length} chars`)\n\n // Store in session memory\n if (ctx.sessionId) {\n const label = buildSearchLabel(input.code)\n storeSearchResult(ctx.sessionId, input.code, truncated, label)\n }\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * execute \u2014 Run JavaScript that can make API calls via api.request().\n */\nfunction registerExecuteTool(commonTypes: string): void {\n const typesBlock = commonTypes\n ? `\\n\\n${commonTypes}`\n : ''\n\n registerMcpTool(\n {\n name: 'execute',\n description: `Make API calls. Returns JSON.\nGlobals: api.request({ method, path, query?, body? }) \u2192 { success, statusCode, data }, context { tenantId, organizationId, userId }.\nRULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collection path with id in BODY. NEVER PUT/POST/DELETE unless user explicitly asked to change data. Before ANY write operation (POST/PUT/DELETE), you MUST use the AskUserQuestion tool to get explicit user confirmation. Do NOT just ask in text \u2014 use the tool so execution pauses until the user responds.${typesBlock}`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'Async arrow function. For reads: async () => api.request({ method: \"GET\", path: \"/api/customers/companies\" }). For updates: async () => api.request({ method: \"PUT\", path: \"/api/customers/companies\", body: { id: \"<uuid>\", name: \"New Name\" } }). id goes in BODY not URL.'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n const codePreview = input.code.slice(0, 120).replace(/\\n/g, ' ')\n console.error(`[AI Usage] execute: code=\"${codePreview}${input.code.length > 120 ? '...' : ''}\" user=${ctx.userId || 'unknown'}`)\n\n // Enforce tool call limit\n if (ctx.sessionId) {\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n console.error(`[AI Usage] execute: TOOL CALL LIMIT EXCEEDED (count=${count})`)\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n // Detect mutations via static analysis \u2014 cap API calls for safety\n const mutationInfo = detectMutationInCode(input.code)\n const maxApiCalls = mutationInfo.hasMutation ? 20 : 50\n if (mutationInfo.hasMutation) {\n console.error(`[AI Usage] execute: MUTATION DETECTED (${mutationInfo.methods.join(',')}) \u2014 capping API calls to ${maxApiCalls}`)\n }\n let apiCallCount = 0\n\n const apiRequestFn = createApiRequestFn(ctx, () => {\n apiCallCount++\n if (apiCallCount > maxApiCalls) {\n throw new Error(`API call limit exceeded (max ${maxApiCalls})`)\n }\n })\n\n const context = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n }\n\n const sandbox = createSandbox(\n { api: { request: apiRequestFn }, context },\n { maxApiCalls }\n )\n\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n console.error(`[AI Usage] execute: ERROR in ${result.durationMs}ms \u2014 apiCalls=${apiCallCount} \u2014 ${result.error}`)\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n }\n }\n\n const truncated = truncateResult(result.result)\n console.error(`[AI Usage] execute: OK in ${result.durationMs}ms \u2014 apiCalls=${apiCallCount} \u2014 ${truncated.length} chars`)\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * Create the api.request() function for the execute sandbox.\n * Replicates the authenticated API call logic from api-discovery-tools.ts.\n */\nfunction createApiRequestFn(\n ctx: McpToolContext,\n onCall: () => void\n): (params: {\n method: string\n path: string\n query?: Record<string, string>\n body?: Record<string, unknown>\n}) => Promise<unknown> {\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 return async (params) => {\n onCall()\n\n const { method, path, query, body } = params\n const callStart = Date.now()\n const normalizedMethod = method.toUpperCase()\n const apiPath = normalizeApiRequestPath(path)\n const authorization = await authorizeCodeModeApiRequest(ctx, normalizedMethod, apiPath)\n\n if (!authorization.allowed) {\n const callDuration = Date.now() - callStart\n console.error(\n `[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${authorization.statusCode} in ${callDuration}ms (blocked by Code Mode RBAC)`\n )\n return {\n success: false,\n statusCode: authorization.statusCode,\n error: authorization.error,\n details: authorization.details,\n }\n }\n\n let url = `${baseUrl}${apiPath}`\n\n // Build query parameters\n const queryParams: Record<string, string> = { ...query }\n\n if (normalizedMethod === '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 request body with context injection\n let requestBody: Record<string, unknown> | undefined\n if (['POST', 'PUT', 'PATCH'].includes(normalizedMethod)) {\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 using host fetch (not sandbox)\n const response = await globalThis.fetch(url, {\n method: normalizedMethod,\n headers,\n body: requestBody ? JSON.stringify(requestBody) : undefined,\n })\n\n const responseText = await response.text()\n const data = tryParseJson(responseText)\n const callDuration = Date.now() - callStart\n\n if (!response.ok) {\n console.error(`[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${response.status} in ${callDuration}ms`)\n\n // Format 400 validation errors into a clear fix instruction for the LLM\n if (response.status === 400) {\n return {\n success: false,\n statusCode: 400,\n error: formatValidationError(data),\n }\n }\n\n return {\n success: false,\n statusCode: response.status,\n error: `API error ${response.status}`,\n details: data,\n }\n }\n\n console.error(`[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${response.status} in ${callDuration}ms (${responseText.length} bytes)`)\n\n // Add mutation warning for non-GET calls\n if (!['GET', 'HEAD', 'OPTIONS'].includes(normalizedMethod)) {\n return {\n success: true,\n statusCode: response.status,\n data,\n _note: 'WRITE operation performed. Only do writes when user explicitly requested data modification.',\n }\n }\n\n return {\n success: true,\n statusCode: response.status,\n data,\n }\n }\n}\n\ntype CodeModeApiAuthorization =\n | { allowed: true; endpoint: ApiEndpoint }\n | { allowed: false; statusCode: number; error: string; details?: Record<string, unknown> }\n\nexport async function authorizeCodeModeApiRequest(\n ctx: McpToolContext,\n method: string,\n path: string\n): Promise<CodeModeApiAuthorization> {\n const normalizedMethod = method.toUpperCase()\n const normalizedPath = normalizeApiRequestPath(path)\n const endpoint = await findCodeModeApiEndpoint(normalizedMethod, normalizedPath)\n\n if (!endpoint) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call undocumented API endpoint ${normalizedMethod} ${normalizedPath}`,\n }\n }\n\n const rbacService = resolveRbacService(ctx)\n const requiredFeatures = endpoint.requiredFeatures ?? []\n\n if (requiredFeatures.length > 0) {\n if (hasRequiredFeatures(requiredFeatures, ctx.userFeatures, ctx.isSuperAdmin, rbacService)) {\n return { allowed: true, endpoint }\n }\n\n return {\n allowed: false,\n statusCode: 403,\n error: `Insufficient permissions for ${normalizedMethod} ${normalizedPath}`,\n details: { requiredFeatures, operationId: endpoint.operationId },\n }\n }\n\n if (isUnsafeHttpMethod(normalizedMethod)) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call mutation endpoint without declared required features: ${normalizedMethod} ${normalizedPath}`,\n details: { operationId: endpoint.operationId },\n }\n }\n\n return { allowed: true, endpoint }\n}\n\nfunction resolveRbacService(ctx: McpToolContext): RbacService | undefined {\n try {\n return ctx.container.resolve('rbacService') as RbacService\n } catch {\n return undefined\n }\n}\n\nasync function findCodeModeApiEndpoint(\n method: string,\n path: string\n): Promise<ApiEndpoint | null> {\n const endpoints = await getApiEndpoints()\n const exactMatch = endpoints.find((endpoint) => endpoint.method === method && endpoint.path === path)\n if (exactMatch) {\n return exactMatch\n }\n\n return endpoints.find((endpoint) => endpoint.method === method && matchApiEndpointPath(endpoint.path, path)) ?? null\n}\n\nexport function matchApiEndpointPath(endpointPath: string, requestPath: string): boolean {\n const normalizedEndpointPath = normalizeApiRequestPath(endpointPath)\n const normalizedRequestPath = normalizeApiRequestPath(requestPath)\n\n if (normalizedEndpointPath === normalizedRequestPath) {\n return true\n }\n\n const endpointSegments = normalizedEndpointPath.split('/').filter(Boolean)\n const requestSegments = normalizedRequestPath.split('/').filter(Boolean)\n\n if (endpointSegments.length !== requestSegments.length) {\n return false\n }\n\n return endpointSegments.every((segment, index) => {\n if (isPathParameterSegment(segment)) {\n return requestSegments[index].length > 0\n }\n return segment === requestSegments[index]\n })\n}\n\nfunction normalizeApiRequestPath(path: string): string {\n const [rawPath] = path.split('?')\n const normalizedPath = rawPath.startsWith('/api')\n ? rawPath\n : `/api${rawPath.startsWith('/') ? rawPath : `/${rawPath}`}`\n\n if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {\n return normalizedPath.slice(0, -1)\n }\n\n return normalizedPath\n}\n\nfunction isPathParameterSegment(segment: string): boolean {\n return (\n (segment.startsWith('{') && segment.endsWith('}')) ||\n (segment.startsWith('[') && segment.endsWith(']')) ||\n segment.startsWith(':')\n )\n}\n\nfunction isUnsafeHttpMethod(method: string): boolean {\n return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase())\n}\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 * Code Mode Tools\n *\n * Two meta-tools that replace all individual API/schema/module tools:\n * - search: Query the OpenAPI spec + entity graph programmatically\n * - execute: Make API calls via a sandboxed api.request() wrapper\n *\n * The AI writes JavaScript that runs in a node:vm sandbox with injected globals.\n */\n\nimport { z } from 'zod'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { registerMcpTool } from './tool-registry'\nimport type { McpToolContext } from './types'\nimport { createSandbox } from './sandbox'\nimport { truncateResult } from './truncate'\nimport { hasRequiredFeatures } from './auth'\nimport { getApiEndpoints, getRawOpenApiSpec, type ApiEndpoint } from './api-endpoint-index'\nimport {\n getCachedEntityGraph,\n inferModuleFromEntity,\n type EntityGraph,\n} from './entity-graph'\nimport {\n lookupSearchCache,\n storeSearchResult,\n buildMemoryContext,\n buildSearchLabel,\n incrementToolCallCount,\n} from './session-memory'\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 * Cached spec object combining OpenAPI paths + entity schemas.\n */\nlet cachedCodeModeSpec: Record<string, unknown> | null = null\n\n/**\n * Cached TypeScript type stubs for common CRUD endpoints.\n * Generated once at startup from the OpenAPI spec.\n */\nlet cachedCommonTypes: string | null = null\n\nexport const CODE_MODE_REQUIRED_FEATURES = ['ai_assistant.view'] as const\n\n/**\n * Build the merged spec object for the search tool.\n */\nasync function getCodeModeSpec(): Promise<Record<string, unknown>> {\n if (cachedCodeModeSpec) return cachedCodeModeSpec\n\n const rawSpec = await getRawOpenApiSpec()\n const graph = getCachedEntityGraph()\n\n const paths = (rawSpec?.paths ?? {}) as Record<string, Record<string, unknown>>\n const entitySchemas = graph ? buildEntitySchemas(graph) : []\n\n const spec: Record<string, unknown> = {\n paths,\n info: rawSpec?.info,\n components: rawSpec?.components,\n entitySchemas,\n }\n\n // --- Helper functions injected into sandbox ---\n\n /**\n * spec.findEndpoints(keyword) \u2014 find all endpoints matching a keyword.\n * Returns compact list: [{ path, methods }]\n */\n spec.findEndpoints = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return Object.entries(paths)\n .filter(([path]) => path.toLowerCase().includes(kw))\n .map(([path, methods]) => ({\n path,\n methods: Object.keys(methods).filter((m) => m !== 'parameters'),\n }))\n }\n\n /**\n * spec.describeEndpoint(path, method) \u2014 compact endpoint profile with working example.\n * Returns: { path, method, summary, requiredFields, optionalFields, nestedCollections, example, relatedEndpoints, relatedEntity }\n * For full schema access, use: spec.paths[path][method].requestBody\n */\n spec.describeEndpoint = (path: string, method: string) => {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) return null\n\n const endpoint = pathObj[method.toLowerCase()] as Record<string, unknown> | undefined\n if (!endpoint) return null\n\n // Extract requestBody JSON Schema\n const bodySchema = extractRequestBodySchema(endpoint)\n const bodyProps = (bodySchema?.properties ?? {}) as Record<string, Record<string, unknown>>\n const bodyRequired = (bodySchema?.required ?? []) as string[]\n\n // Split fields into required (with types) vs optional (names only)\n const requiredFields: Array<{ name: string; type: string; format?: string }> = []\n const optionalFields: string[] = []\n const nestedCollections: Array<{\n field: string\n type: string\n requiredFields: Array<{ name: string; type: string }>\n commonFields: string[]\n }> = []\n\n for (const [name, prop] of Object.entries(bodyProps)) {\n const propType = (prop.type as string) || 'string'\n\n // Detect nested array collections (e.g. lines, items, addresses)\n if (propType === 'array' && prop.items && (prop.items as Record<string, unknown>).type === 'object') {\n const itemSchema = prop.items as Record<string, unknown>\n const itemProps = (itemSchema.properties ?? {}) as Record<string, Record<string, unknown>>\n const itemRequired = (itemSchema.required ?? []) as string[]\n\n const nestedRequired = itemRequired.map((n) => ({\n name: n,\n type: ((itemProps[n]?.type as string) || 'string'),\n }))\n\n // Common fields: first few non-required fields that are likely user-provided\n const nestedOptional = Object.keys(itemProps).filter((n) => !itemRequired.includes(n))\n const commonFields = nestedOptional.slice(0, 6)\n\n nestedCollections.push({\n field: name,\n type: 'array',\n requiredFields: nestedRequired,\n commonFields,\n })\n continue\n }\n\n if (bodyRequired.includes(name)) {\n const field: { name: string; type: string; format?: string } = { name, type: propType }\n if (prop.format) field.format = prop.format as string\n requiredFields.push(field)\n } else {\n optionalFields.push(name)\n }\n }\n\n // Generate minimal working example from required fields + nested collections\n const example: Record<string, unknown> = {}\n for (const field of requiredFields) {\n example[field.name] = generatePlaceholder(field.type, field.format)\n }\n for (const collection of nestedCollections) {\n const itemExample: Record<string, unknown> = {}\n for (const field of collection.requiredFields) {\n itemExample[field.name] = generatePlaceholder(field.type)\n }\n // Add first 2 common fields to the example\n for (const name of collection.commonFields.slice(0, 2)) {\n itemExample[name] = '<value>'\n }\n example[collection.field] = [itemExample]\n }\n\n // Find related endpoints sharing the same module prefix\n const segments = path.replace('/api/', '').split('/')\n const moduleSegment = segments[0]\n const resourceName = segments[1] || segments[0]\n const modulePrefix = `/api/${moduleSegment}/`\n const relatedEndpoints = Object.entries(paths)\n .filter(([p]) => p.startsWith(modulePrefix) && p !== path && !p.includes('{'))\n .map(([p, methods]) => ({\n path: p,\n methods: Object.keys(methods as Record<string, unknown>).filter((m) => m !== 'parameters'),\n }))\n .slice(0, 8)\n\n // Compact entity: className + relationship summary\n const resourceNorm = resourceName.replace(/-/g, '_')\n const resourceSingular = resourceNorm.endsWith('s') ? resourceNorm.slice(0, -1) : resourceNorm\n const moduleSingular = moduleSegment.endsWith('s') ? moduleSegment.slice(0, -1) : moduleSegment\n const prefixedTable = `${moduleSingular}_${resourceNorm}`\n\n const entity = entitySchemas.find((e: Record<string, unknown>) => {\n const table = ((e.tableName as string) || '').toLowerCase()\n const cls = ((e.className as string) || '').toLowerCase()\n const mod = ((e.module as string) || '').toLowerCase()\n if (table === resourceNorm || table === prefixedTable) return true\n if (cls.includes(moduleSingular) && cls.includes(resourceSingular)) return true\n if (mod === moduleSegment && cls.includes(resourceSingular)) return true\n if (cls === resourceSingular || cls.includes(resourceSingular)) return true\n return false\n }) || null\n\n let relatedEntity: string | null = null\n if (entity) {\n const ent = entity as Record<string, unknown>\n const rels = (ent.relationships as Array<{ relationship: string; target: string }>) || []\n const relSummary = rels.map((r) => `${r.relationship}: ${r.target}`).join(', ')\n relatedEntity = `${ent.className}${relSummary ? ` (${relSummary})` : ''}`\n }\n\n // GET endpoints: include query parameters compactly\n const parameters = method.toLowerCase() === 'get'\n ? (endpoint.parameters as Array<Record<string, unknown>> || [])\n .filter((p) => p.in === 'query')\n .map((p) => p.name as string)\n : undefined\n\n return {\n path,\n method: method.toUpperCase(),\n summary: endpoint.summary || endpoint.description,\n ...(parameters && parameters.length > 0 ? { queryParams: parameters } : {}),\n ...(requiredFields.length > 0 ? { requiredFields } : {}),\n ...(optionalFields.length > 0 ? { optionalFields } : {}),\n ...(nestedCollections.length > 0 ? { nestedCollections } : {}),\n ...(Object.keys(example).length > 0 ? { example } : {}),\n ...(relatedEndpoints.length > 0 ? { relatedEndpoints } : {}),\n relatedEntity,\n }\n }\n\n /**\n * spec.describeEntity(keyword) \u2014 find entity by keyword and return its full schema.\n * Returns: { className, tableName, module, fields, relationships }\n */\n spec.describeEntity = (keyword: string) => {\n const kw = keyword.toLowerCase()\n return entitySchemas.find((e: Record<string, unknown>) => {\n const cls = (e.className as string || '').toLowerCase()\n const table = (e.tableName as string || '').toLowerCase()\n return cls.includes(kw) || table.includes(kw)\n }) || null\n }\n\n cachedCodeModeSpec = spec\n return spec\n}\n\n/**\n * Extract the JSON Schema from an OpenAPI endpoint's requestBody.\n * Handles the common `content['application/json'].schema` path.\n */\nfunction extractRequestBodySchema(\n endpoint: Record<string, unknown>\n): Record<string, unknown> | null {\n const requestBody = endpoint.requestBody as Record<string, unknown> | undefined\n if (!requestBody) return null\n\n const content = requestBody.content as Record<string, Record<string, unknown>> | undefined\n if (!content) return null\n\n const jsonContent = content['application/json']\n if (!jsonContent) return null\n\n return (jsonContent.schema as Record<string, unknown>) || null\n}\n\n/**\n * Generate a placeholder value for a given JSON Schema type.\n */\nfunction generatePlaceholder(type: string, format?: string): unknown {\n if (format === 'uuid' || format === 'objectId') return '<uuid>'\n if (format === 'date-time' || format === 'date') return '<date>'\n if (format === 'email') return '<email>'\n switch (type) {\n case 'string': return '<string>'\n case 'number':\n case 'integer': return 0\n case 'boolean': return false\n case 'array': return []\n default: return '<value>'\n }\n}\n\n/**\n * Common CRUD endpoints to pre-generate types for.\n * These are the endpoints the agent uses most and where debug spirals happen.\n */\nconst COMMON_ENDPOINTS: Array<{ path: string; method: string; typeName: string }> = [\n { path: '/api/sales/quotes', method: 'post', typeName: 'CreateQuote' },\n { path: '/api/sales/orders', method: 'post', typeName: 'CreateOrder' },\n { path: '/api/sales/invoices', method: 'post', typeName: 'CreateInvoice' },\n { path: '/api/customers/companies', method: 'post', typeName: 'CreateCompany' },\n { path: '/api/customers/people', method: 'post', typeName: 'CreatePerson' },\n { path: '/api/customers/deals', method: 'post', typeName: 'CreateDeal' },\n { path: '/api/catalog/products', method: 'post', typeName: 'CreateProduct' },\n { path: '/api/customers/companies', method: 'put', typeName: 'UpdateCompany' },\n { path: '/api/customers/people', method: 'put', typeName: 'UpdatePerson' },\n { path: '/api/sales/quotes', method: 'put', typeName: 'UpdateQuote' },\n]\n\n/**\n * Generate TypeScript-like type stubs from the OpenAPI spec for common endpoints.\n * This runs once at startup and injects types into the execute tool description\n * so the LLM sees the correct payload shape without needing to call describeEndpoint.\n */\nasync function generateCommonTypes(): Promise<string> {\n if (cachedCommonTypes) return cachedCommonTypes\n\n const rawSpec = await getRawOpenApiSpec()\n if (!rawSpec?.paths) {\n cachedCommonTypes = ''\n return ''\n }\n\n const paths = rawSpec.paths as Record<string, Record<string, unknown>>\n const typeLines: string[] = ['Available types for api.request() body:\\n']\n\n for (const { path, method, typeName } of COMMON_ENDPOINTS) {\n const pathObj = paths[path] as Record<string, unknown> | undefined\n if (!pathObj) continue\n\n const endpoint = pathObj[method] as Record<string, unknown> | undefined\n if (!endpoint) continue\n\n const bodySchema = extractRequestBodySchema(endpoint)\n if (!bodySchema?.properties) continue\n\n const typeStr = schemaToTypeString(\n typeName,\n bodySchema,\n `${method.toUpperCase()} ${path}`,\n )\n if (typeStr) typeLines.push(typeStr)\n }\n\n if (typeLines.length <= 1) {\n cachedCommonTypes = ''\n return ''\n }\n\n cachedCommonTypes = typeLines.join('\\n')\n console.error(`[Code Mode] Generated ${typeLines.length - 1} common type stubs`)\n return cachedCommonTypes\n}\n\n/**\n * Convert a JSON Schema object to a compact TypeScript-like type string.\n * Produces a single-line or multi-line type declaration the LLM can use directly.\n */\nfunction schemaToTypeString(\n typeName: string,\n schema: Record<string, unknown>,\n comment: string,\n): string | null {\n const props = schema.properties as Record<string, Record<string, unknown>> | undefined\n if (!props) return null\n\n const required = new Set((schema.required as string[]) || [])\n\n // Skip internal fields that the sandbox injects automatically\n const skipFields = new Set(['tenantId', 'organizationId'])\n\n const fields: string[] = []\n const nestedTypes: string[] = []\n\n for (const [name, prop] of Object.entries(props)) {\n if (skipFields.has(name)) continue\n if (!prop || typeof prop !== 'object') continue\n\n const isRequired = required.has(name)\n const optMark = isRequired ? '' : '?'\n\n // Detect nested array of objects \u2192 extract as separate type\n if (\n prop.type === 'array' &&\n prop.items &&\n (prop.items as Record<string, unknown>).type === 'object'\n ) {\n const itemTypeName = `${typeName}${capitalize(singularize(name))}`\n const itemSchema = prop.items as Record<string, unknown>\n const nestedType = schemaToTypeString(itemTypeName, itemSchema, '')\n if (nestedType) nestedTypes.push(nestedType)\n fields.push(`${name}${optMark}: ${itemTypeName}[]`)\n continue\n }\n\n const propType = resolvePropertyType(prop)\n fields.push(`${name}${optMark}: ${propType}`)\n }\n\n if (fields.length === 0) return null\n\n const commentLine = comment ? `// ${comment}\\n` : ''\n const nested = nestedTypes.length > 0 ? nestedTypes.join('\\n') + '\\n' : ''\n return `${nested}${commentLine}type ${typeName} = { ${fields.join('; ')} }`\n}\n\n/**\n * Resolve a JSON Schema property to a compact TypeScript type string.\n */\nfunction resolvePropertyType(prop: Record<string, unknown>): string {\n // Handle anyOf (nullable types)\n if (prop.anyOf && Array.isArray(prop.anyOf)) {\n const variants = (prop.anyOf as Array<Record<string, unknown> | null>).filter(\n (s): s is Record<string, unknown> => s != null,\n )\n const nonNull = variants.filter((s) => s.type !== 'null')\n if (nonNull.length === 1) {\n return resolvePropertyType(nonNull[0]) + ' | null'\n }\n if (nonNull.length > 1) {\n return nonNull.map((s) => resolvePropertyType(s)).join(' | ')\n }\n }\n\n // Handle enum\n if (prop.enum && Array.isArray(prop.enum)) {\n return (prop.enum as string[]).map((v) => `'${v}'`).join(' | ')\n }\n\n const type = prop.type as string\n const format = prop.format as string | undefined\n\n if (type === 'array') {\n const items = prop.items as Record<string, unknown> | undefined\n if (items) return `${resolvePropertyType(items)}[]`\n return 'unknown[]'\n }\n\n if (type === 'object') return 'object'\n\n if (format === 'uuid') return 'string /*uuid*/'\n if (format === 'date-time') return 'string /*ISO date*/'\n if (format === 'date') return 'string /*date*/'\n if (format === 'email') return 'string /*email*/'\n\n switch (type) {\n case 'string': return 'string'\n case 'number':\n case 'integer': return 'number'\n case 'boolean': return 'boolean'\n default: return 'unknown'\n }\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction singularize(s: string): string {\n if (s.endsWith('ies')) return s.slice(0, -3) + 'y'\n if (s.endsWith('ses')) return s.slice(0, -2)\n if (s.endsWith('s') && !s.endsWith('ss')) return s.slice(0, -1)\n return s\n}\n\n/**\n * Format a 400 API error response into a human-readable fix instruction.\n * Parses Zod-style validation errors and produces a concise message the LLM can act on.\n */\nfunction formatValidationError(data: unknown): string {\n if (!data || typeof data !== 'object') {\n return `Validation error: ${JSON.stringify(data)}`\n }\n\n // Raw Zod v4 array format: [{ expected, code, path, message }]\n if (Array.isArray(data)) {\n const issues = data as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || `expected ${issue.expected}` || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n const obj = data as Record<string, unknown>\n\n // Zod v4 flat format: { fieldErrors: { field: [messages] }, formErrors: [messages] }\n if (obj.fieldErrors && typeof obj.fieldErrors === 'object') {\n const fieldErrors = obj.fieldErrors as Record<string, string[]>\n const parts: string[] = []\n for (const [field, messages] of Object.entries(fieldErrors)) {\n if (Array.isArray(messages) && messages.length > 0) {\n parts.push(`${field}: ${messages[0]}`)\n }\n }\n const formErrors = obj.formErrors as string[] | undefined\n if (Array.isArray(formErrors) && formErrors.length > 0) {\n parts.push(formErrors[0])\n }\n if (parts.length > 0) {\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n }\n\n // Zod v3 format: { issues: [{ path: [...], message, code }] }\n if (obj.issues && Array.isArray(obj.issues)) {\n const issues = obj.issues as Array<Record<string, unknown>>\n const parts = issues.slice(0, 5).map((issue) => {\n const path = Array.isArray(issue.path) ? issue.path.join('.') : ''\n const msg = issue.message as string || issue.code as string || 'invalid'\n return path ? `${path}: ${msg}` : msg\n })\n return `Validation failed \u2014 ${parts.join('; ')}. Fix the listed fields and retry.`\n }\n\n // Our API error format: { error: string, details: ... }\n if (obj.error && typeof obj.error === 'string') {\n const details = obj.details\n if (details && typeof details === 'object') {\n return formatValidationError(details)\n }\n return obj.error\n }\n\n // Generic: { message: string }\n if (obj.message && typeof obj.message === 'string') {\n return obj.message\n }\n\n // Fallback: compact JSON\n const json = JSON.stringify(data)\n if (json.length > 500) {\n return `Validation error (truncated): ${json.slice(0, 500)}...`\n }\n return `Validation error: ${json}`\n}\n\n/**\n * Build entity schema array from the entity graph.\n * Same structure as buildEntityResult in entity-graph-tools.ts.\n */\nfunction buildEntitySchemas(graph: EntityGraph) {\n return graph.nodes.map((node) => {\n const relationships = graph.edges\n .filter((edge) => edge.source === node.className)\n .map((edge) => ({\n relationship: edge.relationship,\n target: edge.target,\n property: edge.property,\n nullable: edge.nullable,\n }))\n\n return {\n className: node.className,\n tableName: node.tableName,\n module: inferModuleFromEntity(node.className, node.tableName),\n fields: node.properties,\n relationships,\n }\n })\n}\n\n/**\n * Detect mutation HTTP methods in code via static analysis.\n * Returns which methods were found (POST, PUT, PATCH, DELETE).\n */\nfunction detectMutationInCode(code: string): { hasMutation: boolean; methods: string[] } {\n const methods: string[] = []\n const pattern = /method:\\s*['\"](\\w+)['\"]/gi\n let match\n while ((match = pattern.exec(code)) !== null) {\n const method = match[1].toUpperCase()\n if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {\n methods.push(method)\n }\n }\n return { hasMutation: methods.length > 0, methods }\n}\n\n/**\n * Load and register the two Code Mode tools.\n * Generates TypeScript type stubs for common endpoints at startup.\n * @returns Number of tools registered (always 2)\n */\nexport async function loadCodeModeTools(): Promise<number> {\n const commonTypes = await generateCommonTypes()\n registerSearchTool()\n registerExecuteTool(commonTypes)\n return 2\n}\n\n/**\n * search \u2014 Query the OpenAPI spec and entity graph programmatically.\n */\nfunction registerSearchTool(): void {\n registerMcpTool(\n {\n name: 'search',\n description: `Query the OpenAPI spec and entity schemas. READ-ONLY, no side effects.\nGlobals: spec.findEndpoints(keyword), spec.describeEndpoint(path, method), spec.describeEntity(keyword), spec.paths, spec.entitySchemas.\nUse BEFORE execute to learn endpoint schemas for CREATE/UPDATE. Skip for common paths (companies, people, orders, quotes, products).`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'An async arrow function that queries spec, e.g. async () => spec.paths[\"/api/customers/companies\"]'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n const codePreview = input.code.slice(0, 120).replace(/\\n/g, ' ')\n console.error(`[AI Usage] search: code=\"${codePreview}${input.code.length > 120 ? '...' : ''}\"`)\n\n // Check session memory for cached result\n if (ctx.sessionId) {\n const cached = lookupSearchCache(ctx.sessionId, input.code)\n if (cached) {\n console.error(`[AI Usage] search: CACHE HIT (label=\"${cached.label}\")`)\n const memoryContext = buildMemoryContext(ctx.sessionId)\n return {\n success: true,\n result: cached.result,\n fromCache: true,\n _memoryContext: memoryContext,\n }\n }\n\n // Enforce tool call limit\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n console.error(`[AI Usage] search: TOOL CALL LIMIT EXCEEDED (count=${count})`)\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n const spec = await getCodeModeSpec()\n const sandbox = createSandbox({ spec })\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n console.error(`[AI Usage] search: ERROR in ${result.durationMs}ms \u2014 ${result.error}`)\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n }\n }\n\n const truncated = truncateResult(result.result)\n console.error(`[AI Usage] search: OK in ${result.durationMs}ms \u2014 ${truncated.length} chars`)\n\n // Store in session memory\n if (ctx.sessionId) {\n const label = buildSearchLabel(input.code)\n storeSearchResult(ctx.sessionId, input.code, truncated, label)\n }\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * execute \u2014 Run JavaScript that can make API calls via api.request().\n */\nfunction registerExecuteTool(commonTypes: string): void {\n const typesBlock = commonTypes\n ? `\\n\\n${commonTypes}`\n : ''\n\n registerMcpTool(\n {\n name: 'execute',\n description: `Make API calls. Returns JSON.\nGlobals: api.request({ method, path, query?, body? }) \u2192 { success, statusCode, data }, context { tenantId, organizationId, userId }.\nRULES: For FIND/LIST \u2192 GET only (1 call). For UPDATE \u2192 PUT to collection path with id in BODY. NEVER PUT/POST/DELETE unless user explicitly asked to change data. Before ANY write operation (POST/PUT/DELETE), you MUST use the AskUserQuestion tool to get explicit user confirmation. Do NOT just ask in text \u2014 use the tool so execution pauses until the user responds.${typesBlock}`,\n inputSchema: z.object({\n code: z\n .string()\n .describe(\n 'Async arrow function. For reads: async () => api.request({ method: \"GET\", path: \"/api/customers/companies\" }). For updates: async () => api.request({ method: \"PUT\", path: \"/api/customers/companies\", body: { id: \"<uuid>\", name: \"New Name\" } }). id goes in BODY not URL.'\n ),\n }),\n requiredFeatures: [...CODE_MODE_REQUIRED_FEATURES],\n handler: async (input: { code: string }, ctx: McpToolContext) => {\n const codePreview = input.code.slice(0, 120).replace(/\\n/g, ' ')\n console.error(`[AI Usage] execute: code=\"${codePreview}${input.code.length > 120 ? '...' : ''}\" user=${ctx.userId || 'unknown'}`)\n\n // Enforce tool call limit\n if (ctx.sessionId) {\n const { count, exceeded } = incrementToolCallCount(ctx.sessionId)\n if (exceeded) {\n console.error(`[AI Usage] execute: TOOL CALL LIMIT EXCEEDED (count=${count})`)\n return {\n success: false,\n error: 'Tool call limit exceeded. Summarize what you know and respond to the user.',\n }\n }\n }\n\n // Detect mutations via static analysis \u2014 cap API calls for safety\n const mutationInfo = detectMutationInCode(input.code)\n const maxApiCalls = mutationInfo.hasMutation ? 20 : 50\n if (mutationInfo.hasMutation) {\n console.error(`[AI Usage] execute: MUTATION DETECTED (${mutationInfo.methods.join(',')}) \u2014 capping API calls to ${maxApiCalls}`)\n }\n let apiCallCount = 0\n\n const apiRequestFn = createApiRequestFn(ctx, () => {\n apiCallCount++\n if (apiCallCount > maxApiCalls) {\n throw new Error(`API call limit exceeded (max ${maxApiCalls})`)\n }\n })\n\n const context = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n }\n\n const sandbox = createSandbox(\n { api: { request: apiRequestFn }, context },\n { maxApiCalls }\n )\n\n const result = await sandbox.execute(input.code)\n\n if (result.error) {\n console.error(`[AI Usage] execute: ERROR in ${result.durationMs}ms \u2014 apiCalls=${apiCallCount} \u2014 ${result.error}`)\n return {\n success: false,\n error: result.error,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n }\n }\n\n const truncated = truncateResult(result.result)\n console.error(`[AI Usage] execute: OK in ${result.durationMs}ms \u2014 apiCalls=${apiCallCount} \u2014 ${truncated.length} chars`)\n\n const memoryContext = ctx.sessionId ? buildMemoryContext(ctx.sessionId) : undefined\n return {\n success: true,\n result: truncated,\n logs: result.logs,\n durationMs: result.durationMs,\n apiCallCount,\n _memoryContext: memoryContext,\n }\n },\n },\n { moduleId: 'codemode' }\n )\n}\n\n/**\n * Create the api.request() function for the execute sandbox.\n * Replicates the authenticated API call logic from api-discovery-tools.ts.\n */\nfunction createApiRequestFn(\n ctx: McpToolContext,\n onCall: () => void\n): (params: {\n method: string\n path: string\n query?: Record<string, string>\n body?: Record<string, unknown>\n}) => Promise<unknown> {\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 return async (params) => {\n onCall()\n\n const { method, path, query, body } = params\n const callStart = Date.now()\n const normalizedMethod = method.toUpperCase()\n const apiPath = normalizeApiRequestPath(path)\n const authorization = await authorizeCodeModeApiRequest(ctx, normalizedMethod, apiPath)\n\n if (!authorization.allowed) {\n const callDuration = Date.now() - callStart\n console.error(\n `[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${authorization.statusCode} in ${callDuration}ms (blocked by Code Mode RBAC)`\n )\n return {\n success: false,\n statusCode: authorization.statusCode,\n error: authorization.error,\n details: authorization.details,\n }\n }\n\n let url = `${baseUrl}${apiPath}`\n\n // Build query parameters\n const queryParams: Record<string, string> = { ...query }\n\n if (normalizedMethod === '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 request body with context injection\n let requestBody: Record<string, unknown> | undefined\n if (['POST', 'PUT', 'PATCH'].includes(normalizedMethod)) {\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 using host fetch (not sandbox)\n const response = await fetchWithTimeout(url, {\n method: normalizedMethod,\n headers,\n body: requestBody ? JSON.stringify(requestBody) : undefined,\n timeoutMs: resolveAiApiRequestTimeoutMs(),\n })\n\n const responseText = await response.text()\n const data = tryParseJson(responseText)\n const callDuration = Date.now() - callStart\n\n if (!response.ok) {\n console.error(`[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${response.status} in ${callDuration}ms`)\n\n // Format 400 validation errors into a clear fix instruction for the LLM\n if (response.status === 400) {\n return {\n success: false,\n statusCode: 400,\n error: formatValidationError(data),\n }\n }\n\n return {\n success: false,\n statusCode: response.status,\n error: `API error ${response.status}`,\n details: data,\n }\n }\n\n console.error(`[AI Usage] api.request: ${normalizedMethod} ${apiPath} \u2192 ${response.status} in ${callDuration}ms (${responseText.length} bytes)`)\n\n // Add mutation warning for non-GET calls\n if (!['GET', 'HEAD', 'OPTIONS'].includes(normalizedMethod)) {\n return {\n success: true,\n statusCode: response.status,\n data,\n _note: 'WRITE operation performed. Only do writes when user explicitly requested data modification.',\n }\n }\n\n return {\n success: true,\n statusCode: response.status,\n data,\n }\n }\n}\n\ntype CodeModeApiAuthorization =\n | { allowed: true; endpoint: ApiEndpoint }\n | { allowed: false; statusCode: number; error: string; details?: Record<string, unknown> }\n\nexport async function authorizeCodeModeApiRequest(\n ctx: McpToolContext,\n method: string,\n path: string\n): Promise<CodeModeApiAuthorization> {\n const normalizedMethod = method.toUpperCase()\n const normalizedPath = normalizeApiRequestPath(path)\n const endpoint = await findCodeModeApiEndpoint(normalizedMethod, normalizedPath)\n\n if (!endpoint) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call undocumented API endpoint ${normalizedMethod} ${normalizedPath}`,\n }\n }\n\n const rbacService = resolveRbacService(ctx)\n const requiredFeatures = endpoint.requiredFeatures ?? []\n\n if (requiredFeatures.length > 0) {\n if (hasRequiredFeatures(requiredFeatures, ctx.userFeatures, ctx.isSuperAdmin, rbacService)) {\n return { allowed: true, endpoint }\n }\n\n return {\n allowed: false,\n statusCode: 403,\n error: `Insufficient permissions for ${normalizedMethod} ${normalizedPath}`,\n details: { requiredFeatures, operationId: endpoint.operationId },\n }\n }\n\n if (isUnsafeHttpMethod(normalizedMethod)) {\n return {\n allowed: false,\n statusCode: 403,\n error: `Code Mode cannot call mutation endpoint without declared required features: ${normalizedMethod} ${normalizedPath}`,\n details: { operationId: endpoint.operationId },\n }\n }\n\n return { allowed: true, endpoint }\n}\n\nfunction resolveRbacService(ctx: McpToolContext): RbacService | undefined {\n try {\n return ctx.container.resolve('rbacService') as RbacService\n } catch {\n return undefined\n }\n}\n\nasync function findCodeModeApiEndpoint(\n method: string,\n path: string\n): Promise<ApiEndpoint | null> {\n const endpoints = await getApiEndpoints()\n const exactMatch = endpoints.find((endpoint) => endpoint.method === method && endpoint.path === path)\n if (exactMatch) {\n return exactMatch\n }\n\n return endpoints.find((endpoint) => endpoint.method === method && matchApiEndpointPath(endpoint.path, path)) ?? null\n}\n\nexport function matchApiEndpointPath(endpointPath: string, requestPath: string): boolean {\n const normalizedEndpointPath = normalizeApiRequestPath(endpointPath)\n const normalizedRequestPath = normalizeApiRequestPath(requestPath)\n\n if (normalizedEndpointPath === normalizedRequestPath) {\n return true\n }\n\n const endpointSegments = normalizedEndpointPath.split('/').filter(Boolean)\n const requestSegments = normalizedRequestPath.split('/').filter(Boolean)\n\n if (endpointSegments.length !== requestSegments.length) {\n return false\n }\n\n return endpointSegments.every((segment, index) => {\n if (isPathParameterSegment(segment)) {\n return requestSegments[index].length > 0\n }\n return segment === requestSegments[index]\n })\n}\n\nfunction normalizeApiRequestPath(path: string): string {\n const [rawPath] = path.split('?')\n const normalizedPath = rawPath.startsWith('/api')\n ? rawPath\n : `/api${rawPath.startsWith('/') ? rawPath : `/${rawPath}`}`\n\n if (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {\n return normalizedPath.slice(0, -1)\n }\n\n return normalizedPath\n}\n\nfunction isPathParameterSegment(segment: string): boolean {\n return (\n (segment.startsWith('{') && segment.endsWith('}')) ||\n (segment.startsWith('[') && segment.endsWith(']')) ||\n segment.startsWith(':')\n )\n}\n\nfunction isUnsafeHttpMethod(method: string): boolean {\n return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase())\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,SAAS;AAElB,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAC9B,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AACpC,SAAS,iBAAiB,yBAA2C;AACrE;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB,wBAAwB;AAEnD,MAAM,oCAAoC;AAE1C,SAAS,+BAAuC;AAC9C,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,SAAS,MAAM,OAAO,SAAS,KAAK,EAAE,IAAI;AAChD,SAAO,iBAAiB,QAAQ,iCAAiC;AACnE;AAKA,IAAI,qBAAqD;AAMzD,IAAI,oBAAmC;AAEhC,MAAM,8BAA8B,CAAC,mBAAmB;AAK/D,eAAe,kBAAoD;AACjE,MAAI,mBAAoB,QAAO;AAE/B,QAAM,UAAU,MAAM,kBAAkB;AACxC,QAAM,QAAQ,qBAAqB;AAEnC,QAAM,QAAS,SAAS,SAAS,CAAC;AAClC,QAAM,gBAAgB,QAAQ,mBAAmB,KAAK,IAAI,CAAC;AAE3D,QAAM,OAAgC;AAAA,IACpC;AAAA,IACA,MAAM,SAAS;AAAA,IACf,YAAY,SAAS;AAAA,IACrB;AAAA,EACF;AAQA,OAAK,gBAAgB,CAAC,YAAoB;AACxC,UAAM,KAAK,QAAQ,YAAY;AAC/B,WAAO,OAAO,QAAQ,KAAK,EACxB,OAAO,CAAC,CAAC,IAAI,MAAM,KAAK,YAAY,EAAE,SAAS,EAAE,CAAC,EAClD,IAAI,CAAC,CAAC,MAAM,OAAO,OAAO;AAAA,MACzB;AAAA,MACA,SAAS,OAAO,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,MAAM,YAAY;AAAA,IAChE,EAAE;AAAA,EACN;AAOA,OAAK,mBAAmB,CAAC,MAAc,WAAmB;AACxD,UAAM,UAAU,MAAM,IAAI;AAC1B,QAAI,CAAC,QAAS,QAAO;AAErB,UAAM,WAAW,QAAQ,OAAO,YAAY,CAAC;AAC7C,QAAI,CAAC,SAAU,QAAO;AAGtB,UAAM,aAAa,yBAAyB,QAAQ;AACpD,UAAM,YAAa,YAAY,cAAc,CAAC;AAC9C,UAAM,eAAgB,YAAY,YAAY,CAAC;AAG/C,UAAM,iBAAyE,CAAC;AAChF,UAAM,iBAA2B,CAAC;AAClC,UAAM,oBAKD,CAAC;AAEN,eAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,YAAM,WAAY,KAAK,QAAmB;AAG1C,UAAI,aAAa,WAAW,KAAK,SAAU,KAAK,MAAkC,SAAS,UAAU;AACnG,cAAM,aAAa,KAAK;AACxB,cAAM,YAAa,WAAW,cAAc,CAAC;AAC7C,cAAM,eAAgB,WAAW,YAAY,CAAC;AAE9C,cAAM,iBAAiB,aAAa,IAAI,CAAC,OAAO;AAAA,UAC9C,MAAM;AAAA,UACN,MAAQ,UAAU,CAAC,GAAG,QAAmB;AAAA,QAC3C,EAAE;AAGF,cAAM,iBAAiB,OAAO,KAAK,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,SAAS,CAAC,CAAC;AACrF,cAAM,eAAe,eAAe,MAAM,GAAG,CAAC;AAE9C,0BAAkB,KAAK;AAAA,UACrB,OAAO;AAAA,UACP,MAAM;AAAA,UACN,gBAAgB;AAAA,UAChB;AAAA,QACF,CAAC;AACD;AAAA,MACF;AAEA,UAAI,aAAa,SAAS,IAAI,GAAG;AAC/B,cAAM,QAAyD,EAAE,MAAM,MAAM,SAAS;AACtF,YAAI,KAAK,OAAQ,OAAM,SAAS,KAAK;AACrC,uBAAe,KAAK,KAAK;AAAA,MAC3B,OAAO;AACL,uBAAe,KAAK,IAAI;AAAA,MAC1B;AAAA,IACF;AAGA,UAAM,UAAmC,CAAC;AAC1C,eAAW,SAAS,gBAAgB;AAClC,cAAQ,MAAM,IAAI,IAAI,oBAAoB,MAAM,MAAM,MAAM,MAAM;AAAA,IACpE;AACA,eAAW,cAAc,mBAAmB;AAC1C,YAAM,cAAuC,CAAC;AAC9C,iBAAW,SAAS,WAAW,gBAAgB;AAC7C,oBAAY,MAAM,IAAI,IAAI,oBAAoB,MAAM,IAAI;AAAA,MAC1D;AAEA,iBAAW,QAAQ,WAAW,aAAa,MAAM,GAAG,CAAC,GAAG;AACtD,oBAAY,IAAI,IAAI;AAAA,MACtB;AACA,cAAQ,WAAW,KAAK,IAAI,CAAC,WAAW;AAAA,IAC1C;AAGA,UAAM,WAAW,KAAK,QAAQ,SAAS,EAAE,EAAE,MAAM,GAAG;AACpD,UAAM,gBAAgB,SAAS,CAAC;AAChC,UAAM,eAAe,SAAS,CAAC,KAAK,SAAS,CAAC;AAC9C,UAAM,eAAe,QAAQ,aAAa;AAC1C,UAAM,mBAAmB,OAAO,QAAQ,KAAK,EAC1C,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,WAAW,YAAY,KAAK,MAAM,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,EAC5E,IAAI,CAAC,CAAC,GAAG,OAAO,OAAO;AAAA,MACtB,MAAM;AAAA,MACN,SAAS,OAAO,KAAK,OAAkC,EAAE,OAAO,CAAC,MAAM,MAAM,YAAY;AAAA,IAC3F,EAAE,EACD,MAAM,GAAG,CAAC;AAGb,UAAM,eAAe,aAAa,QAAQ,MAAM,GAAG;AACnD,UAAM,mBAAmB,aAAa,SAAS,GAAG,IAAI,aAAa,MAAM,GAAG,EAAE,IAAI;AAClF,UAAM,iBAAiB,cAAc,SAAS,GAAG,IAAI,cAAc,MAAM,GAAG,EAAE,IAAI;AAClF,UAAM,gBAAgB,GAAG,cAAc,IAAI,YAAY;AAEvD,UAAM,SAAS,cAAc,KAAK,CAAC,MAA+B;AAChE,YAAM,SAAU,EAAE,aAAwB,IAAI,YAAY;AAC1D,YAAM,OAAQ,EAAE,aAAwB,IAAI,YAAY;AACxD,YAAM,OAAQ,EAAE,UAAqB,IAAI,YAAY;AACrD,UAAI,UAAU,gBAAgB,UAAU,cAAe,QAAO;AAC9D,UAAI,IAAI,SAAS,cAAc,KAAK,IAAI,SAAS,gBAAgB,EAAG,QAAO;AAC3E,UAAI,QAAQ,iBAAiB,IAAI,SAAS,gBAAgB,EAAG,QAAO;AACpE,UAAI,QAAQ,oBAAoB,IAAI,SAAS,gBAAgB,EAAG,QAAO;AACvE,aAAO;AAAA,IACT,CAAC,KAAK;AAEN,QAAI,gBAA+B;AACnC,QAAI,QAAQ;AACV,YAAM,MAAM;AACZ,YAAM,OAAQ,IAAI,iBAAqE,CAAC;AACxF,YAAM,aAAa,KAAK,IAAI,CAAC,MAAM,GAAG,EAAE,YAAY,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI;AAC9E,sBAAgB,GAAG,IAAI,SAAS,GAAG,aAAa,KAAK,UAAU,MAAM,EAAE;AAAA,IACzE;AAGA,UAAM,aAAa,OAAO,YAAY,MAAM,SACvC,SAAS,cAAgD,CAAC,GACxD,OAAO,CAAC,MAAM,EAAE,OAAO,OAAO,EAC9B,IAAI,CAAC,MAAM,EAAE,IAAc,IAC9B;AAEJ,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,OAAO,YAAY;AAAA,MAC3B,SAAS,SAAS,WAAW,SAAS;AAAA,MACtC,GAAI,cAAc,WAAW,SAAS,IAAI,EAAE,aAAa,WAAW,IAAI,CAAC;AAAA,MACzE,GAAI,eAAe,SAAS,IAAI,EAAE,eAAe,IAAI,CAAC;AAAA,MACtD,GAAI,eAAe,SAAS,IAAI,EAAE,eAAe,IAAI,CAAC;AAAA,MACtD,GAAI,kBAAkB,SAAS,IAAI,EAAE,kBAAkB,IAAI,CAAC;AAAA,MAC5D,GAAI,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,MACrD,GAAI,iBAAiB,SAAS,IAAI,EAAE,iBAAiB,IAAI,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AAMA,OAAK,iBAAiB,CAAC,YAAoB;AACzC,UAAM,KAAK,QAAQ,YAAY;AAC/B,WAAO,cAAc,KAAK,CAAC,MAA+B;AACxD,YAAM,OAAO,EAAE,aAAuB,IAAI,YAAY;AACtD,YAAM,SAAS,EAAE,aAAuB,IAAI,YAAY;AACxD,aAAO,IAAI,SAAS,EAAE,KAAK,MAAM,SAAS,EAAE;AAAA,IAC9C,CAAC,KAAK;AAAA,EACR;AAEA,uBAAqB;AACrB,SAAO;AACT;AAMA,SAAS,yBACP,UACgC;AAChC,QAAM,cAAc,SAAS;AAC7B,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,UAAU,YAAY;AAC5B,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,cAAc,QAAQ,kBAAkB;AAC9C,MAAI,CAAC,YAAa,QAAO;AAEzB,SAAQ,YAAY,UAAsC;AAC5D;AAKA,SAAS,oBAAoB,MAAc,QAA0B;AACnE,MAAI,WAAW,UAAU,WAAW,WAAY,QAAO;AACvD,MAAI,WAAW,eAAe,WAAW,OAAQ,QAAO;AACxD,MAAI,WAAW,QAAS,QAAO;AAC/B,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAA,IACL,KAAK;AAAW,aAAO;AAAA,IACvB,KAAK;AAAW,aAAO;AAAA,IACvB,KAAK;AAAS,aAAO,CAAC;AAAA,IACtB;AAAS,aAAO;AAAA,EAClB;AACF;AAMA,MAAM,mBAA8E;AAAA,EAClF,EAAE,MAAM,qBAAqB,QAAQ,QAAQ,UAAU,cAAc;AAAA,EACrE,EAAE,MAAM,qBAAqB,QAAQ,QAAQ,UAAU,cAAc;AAAA,EACrE,EAAE,MAAM,uBAAuB,QAAQ,QAAQ,UAAU,gBAAgB;AAAA,EACzE,EAAE,MAAM,4BAA4B,QAAQ,QAAQ,UAAU,gBAAgB;AAAA,EAC9E,EAAE,MAAM,yBAAyB,QAAQ,QAAQ,UAAU,eAAe;AAAA,EAC1E,EAAE,MAAM,wBAAwB,QAAQ,QAAQ,UAAU,aAAa;AAAA,EACvE,EAAE,MAAM,yBAAyB,QAAQ,QAAQ,UAAU,gBAAgB;AAAA,EAC3E,EAAE,MAAM,4BAA4B,QAAQ,OAAO,UAAU,gBAAgB;AAAA,EAC7E,EAAE,MAAM,yBAAyB,QAAQ,OAAO,UAAU,eAAe;AAAA,EACzE,EAAE,MAAM,qBAAqB,QAAQ,OAAO,UAAU,cAAc;AACtE;AAOA,eAAe,sBAAuC;AACpD,MAAI,kBAAmB,QAAO;AAE9B,QAAM,UAAU,MAAM,kBAAkB;AACxC,MAAI,CAAC,SAAS,OAAO;AACnB,wBAAoB;AACpB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,QAAQ;AACtB,QAAM,YAAsB,CAAC,2CAA2C;AAExE,aAAW,EAAE,MAAM,QAAQ,SAAS,KAAK,kBAAkB;AACzD,UAAM,UAAU,MAAM,IAAI;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,WAAW,QAAQ,MAAM;AAC/B,QAAI,CAAC,SAAU;AAEf,UAAM,aAAa,yBAAyB,QAAQ;AACpD,QAAI,CAAC,YAAY,WAAY;AAE7B,UAAM,UAAU;AAAA,MACd;AAAA,MACA;AAAA,MACA,GAAG,OAAO,YAAY,CAAC,IAAI,IAAI;AAAA,IACjC;AACA,QAAI,QAAS,WAAU,KAAK,OAAO;AAAA,EACrC;AAEA,MAAI,UAAU,UAAU,GAAG;AACzB,wBAAoB;AACpB,WAAO;AAAA,EACT;AAEA,sBAAoB,UAAU,KAAK,IAAI;AACvC,UAAQ,MAAM,yBAAyB,UAAU,SAAS,CAAC,oBAAoB;AAC/E,SAAO;AACT;AAMA,SAAS,mBACP,UACA,QACA,SACe;AACf,QAAM,QAAQ,OAAO;AACrB,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,WAAW,IAAI,IAAK,OAAO,YAAyB,CAAC,CAAC;AAG5D,QAAM,aAAa,oBAAI,IAAI,CAAC,YAAY,gBAAgB,CAAC;AAEzD,QAAM,SAAmB,CAAC;AAC1B,QAAM,cAAwB,CAAC;AAE/B,aAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,WAAW,IAAI,IAAI,EAAG;AAC1B,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AAEvC,UAAM,aAAa,SAAS,IAAI,IAAI;AACpC,UAAM,UAAU,aAAa,KAAK;AAGlC,QACE,KAAK,SAAS,WACd,KAAK,SACJ,KAAK,MAAkC,SAAS,UACjD;AACA,YAAM,eAAe,GAAG,QAAQ,GAAG,WAAW,YAAY,IAAI,CAAC,CAAC;AAChE,YAAM,aAAa,KAAK;AACxB,YAAM,aAAa,mBAAmB,cAAc,YAAY,EAAE;AAClE,UAAI,WAAY,aAAY,KAAK,UAAU;AAC3C,aAAO,KAAK,GAAG,IAAI,GAAG,OAAO,KAAK,YAAY,IAAI;AAClD;AAAA,IACF;AAEA,UAAM,WAAW,oBAAoB,IAAI;AACzC,WAAO,KAAK,GAAG,IAAI,GAAG,OAAO,KAAK,QAAQ,EAAE;AAAA,EAC9C;AAEA,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAM,cAAc,UAAU,MAAM,OAAO;AAAA,IAAO;AAClD,QAAM,SAAS,YAAY,SAAS,IAAI,YAAY,KAAK,IAAI,IAAI,OAAO;AACxE,SAAO,GAAG,MAAM,GAAG,WAAW,QAAQ,QAAQ,QAAQ,OAAO,KAAK,IAAI,CAAC;AACzE;AAKA,SAAS,oBAAoB,MAAuC;AAElE,MAAI,KAAK,SAAS,MAAM,QAAQ,KAAK,KAAK,GAAG;AAC3C,UAAM,WAAY,KAAK,MAAgD;AAAA,MACrE,CAAC,MAAoC,KAAK;AAAA,IAC5C;AACA,UAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM;AACxD,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO,oBAAoB,QAAQ,CAAC,CAAC,IAAI;AAAA,IAC3C;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO,QAAQ,IAAI,CAAC,MAAM,oBAAoB,CAAC,CAAC,EAAE,KAAK,KAAK;AAAA,IAC9D;AAAA,EACF;AAGA,MAAI,KAAK,QAAQ,MAAM,QAAQ,KAAK,IAAI,GAAG;AACzC,WAAQ,KAAK,KAAkB,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,KAAK;AAAA,EAChE;AAEA,QAAM,OAAO,KAAK;AAClB,QAAM,SAAS,KAAK;AAEpB,MAAI,SAAS,SAAS;AACpB,UAAM,QAAQ,KAAK;AACnB,QAAI,MAAO,QAAO,GAAG,oBAAoB,KAAK,CAAC;AAC/C,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,SAAU,QAAO;AAE9B,MAAI,WAAW,OAAQ,QAAO;AAC9B,MAAI,WAAW,YAAa,QAAO;AACnC,MAAI,WAAW,OAAQ,QAAO;AAC9B,MAAI,WAAW,QAAS,QAAO;AAE/B,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAA,IACL,KAAK;AAAW,aAAO;AAAA,IACvB,KAAK;AAAW,aAAO;AAAA,IACvB;AAAS,aAAO;AAAA,EAClB;AACF;AAEA,SAAS,WAAW,GAAmB;AACrC,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC;AAC9C;AAEA,SAAS,YAAY,GAAmB;AACtC,MAAI,EAAE,SAAS,KAAK,EAAG,QAAO,EAAE,MAAM,GAAG,EAAE,IAAI;AAC/C,MAAI,EAAE,SAAS,KAAK,EAAG,QAAO,EAAE,MAAM,GAAG,EAAE;AAC3C,MAAI,EAAE,SAAS,GAAG,KAAK,CAAC,EAAE,SAAS,IAAI,EAAG,QAAO,EAAE,MAAM,GAAG,EAAE;AAC9D,SAAO;AACT;AAMA,SAAS,sBAAsB,MAAuB;AACpD,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,WAAO,qBAAqB,KAAK,UAAU,IAAI,CAAC;AAAA,EAClD;AAGA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,UAAM,SAAS;AACf,UAAM,QAAQ,OAAO,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,UAAU;AAC9C,YAAM,OAAO,MAAM,QAAQ,MAAM,IAAI,IAAI,MAAM,KAAK,KAAK,GAAG,IAAI;AAChE,YAAM,MAAM,MAAM,WAAqB,YAAY,MAAM,QAAQ,MAAM,MAAM,QAAkB;AAC/F,aAAO,OAAO,GAAG,IAAI,KAAK,GAAG,KAAK;AAAA,IACpC,CAAC;AACD,QAAI,MAAM,SAAS,GAAG;AACpB,aAAO,4BAAuB,MAAM,KAAK,IAAI,CAAC;AAAA,IAChD;AAAA,EACF;AAEA,QAAM,MAAM;AAGZ,MAAI,IAAI,eAAe,OAAO,IAAI,gBAAgB,UAAU;AAC1D,UAAM,cAAc,IAAI;AACxB,UAAM,QAAkB,CAAC;AACzB,eAAW,CAAC,OAAO,QAAQ,KAAK,OAAO,QAAQ,WAAW,GAAG;AAC3D,UAAI,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS,GAAG;AAClD,cAAM,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,EAAE;AAAA,MACvC;AAAA,IACF;AACA,UAAM,aAAa,IAAI;AACvB,QAAI,MAAM,QAAQ,UAAU,KAAK,WAAW,SAAS,GAAG;AACtD,YAAM,KAAK,WAAW,CAAC,CAAC;AAAA,IAC1B;AACA,QAAI,MAAM,SAAS,GAAG;AACpB,aAAO,4BAAuB,MAAM,KAAK,IAAI,CAAC;AAAA,IAChD;AAAA,EACF;AAGA,MAAI,IAAI,UAAU,MAAM,QAAQ,IAAI,MAAM,GAAG;AAC3C,UAAM,SAAS,IAAI;AACnB,UAAM,QAAQ,OAAO,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,UAAU;AAC9C,YAAM,OAAO,MAAM,QAAQ,MAAM,IAAI,IAAI,MAAM,KAAK,KAAK,GAAG,IAAI;AAChE,YAAM,MAAM,MAAM,WAAqB,MAAM,QAAkB;AAC/D,aAAO,OAAO,GAAG,IAAI,KAAK,GAAG,KAAK;AAAA,IACpC,CAAC;AACD,WAAO,4BAAuB,MAAM,KAAK,IAAI,CAAC;AAAA,EAChD;AAGA,MAAI,IAAI,SAAS,OAAO,IAAI,UAAU,UAAU;AAC9C,UAAM,UAAU,IAAI;AACpB,QAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,aAAO,sBAAsB,OAAO;AAAA,IACtC;AACA,WAAO,IAAI;AAAA,EACb;AAGA,MAAI,IAAI,WAAW,OAAO,IAAI,YAAY,UAAU;AAClD,WAAO,IAAI;AAAA,EACb;AAGA,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,MAAI,KAAK,SAAS,KAAK;AACrB,WAAO,iCAAiC,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,EAC5D;AACA,SAAO,qBAAqB,IAAI;AAClC;AAMA,SAAS,mBAAmB,OAAoB;AAC9C,SAAO,MAAM,MAAM,IAAI,CAAC,SAAS;AAC/B,UAAM,gBAAgB,MAAM,MACzB,OAAO,CAAC,SAAS,KAAK,WAAW,KAAK,SAAS,EAC/C,IAAI,CAAC,UAAU;AAAA,MACd,cAAc,KAAK;AAAA,MACnB,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,IACjB,EAAE;AAEJ,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,MAChB,QAAQ,sBAAsB,KAAK,WAAW,KAAK,SAAS;AAAA,MAC5D,QAAQ,KAAK;AAAA,MACb;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAMA,SAAS,qBAAqB,MAA2D;AACvF,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAU;AAChB,MAAI;AACJ,UAAQ,QAAQ,QAAQ,KAAK,IAAI,OAAO,MAAM;AAC5C,UAAM,SAAS,MAAM,CAAC,EAAE,YAAY;AACpC,QAAI,CAAC,QAAQ,OAAO,SAAS,QAAQ,EAAE,SAAS,MAAM,GAAG;AACvD,cAAQ,KAAK,MAAM;AAAA,IACrB;AAAA,EACF;AACA,SAAO,EAAE,aAAa,QAAQ,SAAS,GAAG,QAAQ;AACpD;AAOA,eAAsB,oBAAqC;AACzD,QAAM,cAAc,MAAM,oBAAoB;AAC9C,qBAAmB;AACnB,sBAAoB,WAAW;AAC/B,SAAO;AACT;AAKA,SAAS,qBAA2B;AAClC;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA;AAAA;AAAA,MAGb,aAAa,EAAE,OAAO;AAAA,QACpB,MAAM,EACH,OAAO,EACP;AAAA,UACC;AAAA,QACF;AAAA,MACJ,CAAC;AAAA,MACD,kBAAkB,CAAC,GAAG,2BAA2B;AAAA,MACjD,SAAS,OAAO,OAAyB,QAAwB;AAC/D,cAAM,cAAc,MAAM,KAAK,MAAM,GAAG,GAAG,EAAE,QAAQ,OAAO,GAAG;AAC/D,gBAAQ,MAAM,4BAA4B,WAAW,GAAG,MAAM,KAAK,SAAS,MAAM,QAAQ,EAAE,GAAG;AAG/F,YAAI,IAAI,WAAW;AACjB,gBAAM,SAAS,kBAAkB,IAAI,WAAW,MAAM,IAAI;AAC1D,cAAI,QAAQ;AACV,oBAAQ,MAAM,wCAAwC,OAAO,KAAK,IAAI;AACtE,kBAAMA,iBAAgB,mBAAmB,IAAI,SAAS;AACtD,mBAAO;AAAA,cACL,SAAS;AAAA,cACT,QAAQ,OAAO;AAAA,cACf,WAAW;AAAA,cACX,gBAAgBA;AAAA,YAClB;AAAA,UACF;AAGA,gBAAM,EAAE,OAAO,SAAS,IAAI,uBAAuB,IAAI,SAAS;AAChE,cAAI,UAAU;AACZ,oBAAQ,MAAM,sDAAsD,KAAK,GAAG;AAC5E,mBAAO;AAAA,cACL,SAAS;AAAA,cACT,OAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAEA,cAAM,OAAO,MAAM,gBAAgB;AACnC,cAAM,UAAU,cAAc,EAAE,KAAK,CAAC;AACtC,cAAM,SAAS,MAAM,QAAQ,QAAQ,MAAM,IAAI;AAE/C,YAAI,OAAO,OAAO;AAChB,kBAAQ,MAAM,+BAA+B,OAAO,UAAU,aAAQ,OAAO,KAAK,EAAE;AACpF,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO,OAAO;AAAA,YACd,MAAM,OAAO;AAAA,YACb,YAAY,OAAO;AAAA,UACrB;AAAA,QACF;AAEA,cAAM,YAAY,eAAe,OAAO,MAAM;AAC9C,gBAAQ,MAAM,4BAA4B,OAAO,UAAU,aAAQ,UAAU,MAAM,QAAQ;AAG3F,YAAI,IAAI,WAAW;AACjB,gBAAM,QAAQ,iBAAiB,MAAM,IAAI;AACzC,4BAAkB,IAAI,WAAW,MAAM,MAAM,WAAW,KAAK;AAAA,QAC/D;AAEA,cAAM,gBAAgB,IAAI,YAAY,mBAAmB,IAAI,SAAS,IAAI;AAC1E,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,MAAM,OAAO;AAAA,UACb,YAAY,OAAO;AAAA,UACnB,gBAAgB;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,IACA,EAAE,UAAU,WAAW;AAAA,EACzB;AACF;AAKA,SAAS,oBAAoB,aAA2B;AACtD,QAAM,aAAa,cACf;AAAA;AAAA,EAAO,WAAW,KAClB;AAEJ;AAAA,IACE;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA;AAAA,6XAE2V,UAAU;AAAA,MAClX,aAAa,EAAE,OAAO;AAAA,QACpB,MAAM,EACH,OAAO,EACP;AAAA,UACC;AAAA,QACF;AAAA,MACJ,CAAC;AAAA,MACD,kBAAkB,CAAC,GAAG,2BAA2B;AAAA,MACjD,SAAS,OAAO,OAAyB,QAAwB;AAC/D,cAAM,cAAc,MAAM,KAAK,MAAM,GAAG,GAAG,EAAE,QAAQ,OAAO,GAAG;AAC/D,gBAAQ,MAAM,6BAA6B,WAAW,GAAG,MAAM,KAAK,SAAS,MAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,SAAS,EAAE;AAGhI,YAAI,IAAI,WAAW;AACjB,gBAAM,EAAE,OAAO,SAAS,IAAI,uBAAuB,IAAI,SAAS;AAChE,cAAI,UAAU;AACZ,oBAAQ,MAAM,uDAAuD,KAAK,GAAG;AAC7E,mBAAO;AAAA,cACL,SAAS;AAAA,cACT,OAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAGA,cAAM,eAAe,qBAAqB,MAAM,IAAI;AACpD,cAAM,cAAc,aAAa,cAAc,KAAK;AACpD,YAAI,aAAa,aAAa;AAC5B,kBAAQ,MAAM,0CAA0C,aAAa,QAAQ,KAAK,GAAG,CAAC,iCAA4B,WAAW,EAAE;AAAA,QACjI;AACA,YAAI,eAAe;AAEnB,cAAM,eAAe,mBAAmB,KAAK,MAAM;AACjD;AACA,cAAI,eAAe,aAAa;AAC9B,kBAAM,IAAI,MAAM,gCAAgC,WAAW,GAAG;AAAA,UAChE;AAAA,QACF,CAAC;AAED,cAAM,UAAU;AAAA,UACd,UAAU,IAAI;AAAA,UACd,gBAAgB,IAAI;AAAA,UACpB,QAAQ,IAAI;AAAA,QACd;AAEA,cAAM,UAAU;AAAA,UACd,EAAE,KAAK,EAAE,SAAS,aAAa,GAAG,QAAQ;AAAA,UAC1C,EAAE,YAAY;AAAA,QAChB;AAEA,cAAM,SAAS,MAAM,QAAQ,QAAQ,MAAM,IAAI;AAE/C,YAAI,OAAO,OAAO;AAChB,kBAAQ,MAAM,gCAAgC,OAAO,UAAU,sBAAiB,YAAY,WAAM,OAAO,KAAK,EAAE;AAChH,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO,OAAO;AAAA,YACd,MAAM,OAAO;AAAA,YACb,YAAY,OAAO;AAAA,YACnB;AAAA,UACF;AAAA,QACF;AAEA,cAAM,YAAY,eAAe,OAAO,MAAM;AAC9C,gBAAQ,MAAM,6BAA6B,OAAO,UAAU,sBAAiB,YAAY,WAAM,UAAU,MAAM,QAAQ;AAEvH,cAAM,gBAAgB,IAAI,YAAY,mBAAmB,IAAI,SAAS,IAAI;AAC1E,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,MAAM,OAAO;AAAA,UACb,YAAY,OAAO;AAAA,UACnB;AAAA,UACA,gBAAgB;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,IACA,EAAE,UAAU,WAAW;AAAA,EACzB;AACF;AAMA,SAAS,mBACP,KACA,QAMqB;AACrB,QAAM,UACJ,QAAQ,IAAI,4BACZ,QAAQ,IAAI,uBACZ,QAAQ,IAAI,WACZ;AAEF,SAAO,OAAO,WAAW;AACvB,WAAO;AAEP,UAAM,EAAE,QAAQ,MAAM,OAAO,KAAK,IAAI;AACtC,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,mBAAmB,OAAO,YAAY;AAC5C,UAAM,UAAU,wBAAwB,IAAI;AAC5C,UAAM,gBAAgB,MAAM,4BAA4B,KAAK,kBAAkB,OAAO;AAEtF,QAAI,CAAC,cAAc,SAAS;AAC1B,YAAMC,gBAAe,KAAK,IAAI,IAAI;AAClC,cAAQ;AAAA,QACN,2BAA2B,gBAAgB,IAAI,OAAO,WAAM,cAAc,UAAU,OAAOA,aAAY;AAAA,MACzG;AACA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY,cAAc;AAAA,QAC1B,OAAO,cAAc;AAAA,QACrB,SAAS,cAAc;AAAA,MACzB;AAAA,IACF;AAEA,QAAI,MAAM,GAAG,OAAO,GAAG,OAAO;AAG9B,UAAM,cAAsC,EAAE,GAAG,MAAM;AAEvD,QAAI,qBAAqB,OAAO;AAC9B,UAAI,IAAI,SAAU,aAAY,WAAW,IAAI;AAC7C,UAAI,IAAI,eAAgB,aAAY,iBAAiB,IAAI;AAAA,IAC3D;AAEA,QAAI,OAAO,KAAK,WAAW,EAAE,SAAS,GAAG;AACvC,YAAM,YAAY,IAAI,SAAS,GAAG,IAAI,MAAM;AAC5C,aAAO,YAAY,IAAI,gBAAgB,WAAW,EAAE,SAAS;AAAA,IAC/D;AAGA,QAAI;AACJ,QAAI,CAAC,QAAQ,OAAO,OAAO,EAAE,SAAS,gBAAgB,GAAG;AACvD,oBAAc,EAAE,GAAG,KAAK;AACxB,UAAI,IAAI,SAAU,aAAY,WAAW,IAAI;AAC7C,UAAI,IAAI,eAAgB,aAAY,iBAAiB,IAAI;AAAA,IAC3D;AAGA,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AACA,QAAI,IAAI,aAAc,SAAQ,WAAW,IAAI,IAAI;AACjD,QAAI,IAAI,SAAU,SAAQ,aAAa,IAAI,IAAI;AAC/C,QAAI,IAAI,eAAgB,SAAQ,mBAAmB,IAAI,IAAI;AAG3D,UAAM,WAAW,MAAM,iBAAiB,KAAK;AAAA,MAC3C,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,cAAc,KAAK,UAAU,WAAW,IAAI;AAAA,MAClD,WAAW,6BAA6B;AAAA,IAC1C,CAAC;AAED,UAAM,eAAe,MAAM,SAAS,KAAK;AACzC,UAAM,OAAO,aAAa,YAAY;AACtC,UAAM,eAAe,KAAK,IAAI,IAAI;AAElC,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ,MAAM,2BAA2B,gBAAgB,IAAI,OAAO,WAAM,SAAS,MAAM,OAAO,YAAY,IAAI;AAGhH,UAAI,SAAS,WAAW,KAAK;AAC3B,eAAO;AAAA,UACL,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,OAAO,sBAAsB,IAAI;AAAA,QACnC;AAAA,MACF;AAEA,aAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY,SAAS;AAAA,QACrB,OAAO,aAAa,SAAS,MAAM;AAAA,QACnC,SAAS;AAAA,MACX;AAAA,IACF;AAEA,YAAQ,MAAM,2BAA2B,gBAAgB,IAAI,OAAO,WAAM,SAAS,MAAM,OAAO,YAAY,OAAO,aAAa,MAAM,SAAS;AAG/I,QAAI,CAAC,CAAC,OAAO,QAAQ,SAAS,EAAE,SAAS,gBAAgB,GAAG;AAC1D,aAAO;AAAA,QACL,SAAS;AAAA,QACT,YAAY,SAAS;AAAA,QACrB;AAAA,QACA,OAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,YAAY,SAAS;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACF;AAMA,eAAsB,4BACpB,KACA,QACA,MACmC;AACnC,QAAM,mBAAmB,OAAO,YAAY;AAC5C,QAAM,iBAAiB,wBAAwB,IAAI;AACnD,QAAM,WAAW,MAAM,wBAAwB,kBAAkB,cAAc;AAE/E,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,MACL,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,OAAO,mDAAmD,gBAAgB,IAAI,cAAc;AAAA,IAC9F;AAAA,EACF;AAEA,QAAM,cAAc,mBAAmB,GAAG;AAC1C,QAAM,mBAAmB,SAAS,oBAAoB,CAAC;AAEvD,MAAI,iBAAiB,SAAS,GAAG;AAC/B,QAAI,oBAAoB,kBAAkB,IAAI,cAAc,IAAI,cAAc,WAAW,GAAG;AAC1F,aAAO,EAAE,SAAS,MAAM,SAAS;AAAA,IACnC;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,OAAO,gCAAgC,gBAAgB,IAAI,cAAc;AAAA,MACzE,SAAS,EAAE,kBAAkB,aAAa,SAAS,YAAY;AAAA,IACjE;AAAA,EACF;AAEA,MAAI,mBAAmB,gBAAgB,GAAG;AACxC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,OAAO,+EAA+E,gBAAgB,IAAI,cAAc;AAAA,MACxH,SAAS,EAAE,aAAa,SAAS,YAAY;AAAA,IAC/C;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,MAAM,SAAS;AACnC;AAEA,SAAS,mBAAmB,KAA8C;AACxE,MAAI;AACF,WAAO,IAAI,UAAU,QAAQ,aAAa;AAAA,EAC5C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,wBACb,QACA,MAC6B;AAC7B,QAAM,YAAY,MAAM,gBAAgB;AACxC,QAAM,aAAa,UAAU,KAAK,CAAC,aAAa,SAAS,WAAW,UAAU,SAAS,SAAS,IAAI;AACpG,MAAI,YAAY;AACd,WAAO;AAAA,EACT;AAEA,SAAO,UAAU,KAAK,CAAC,aAAa,SAAS,WAAW,UAAU,qBAAqB,SAAS,MAAM,IAAI,CAAC,KAAK;AAClH;AAEO,SAAS,qBAAqB,cAAsB,aAA8B;AACvF,QAAM,yBAAyB,wBAAwB,YAAY;AACnE,QAAM,wBAAwB,wBAAwB,WAAW;AAEjE,MAAI,2BAA2B,uBAAuB;AACpD,WAAO;AAAA,EACT;AAEA,QAAM,mBAAmB,uBAAuB,MAAM,GAAG,EAAE,OAAO,OAAO;AACzE,QAAM,kBAAkB,sBAAsB,MAAM,GAAG,EAAE,OAAO,OAAO;AAEvE,MAAI,iBAAiB,WAAW,gBAAgB,QAAQ;AACtD,WAAO;AAAA,EACT;AAEA,SAAO,iBAAiB,MAAM,CAAC,SAAS,UAAU;AAChD,QAAI,uBAAuB,OAAO,GAAG;AACnC,aAAO,gBAAgB,KAAK,EAAE,SAAS;AAAA,IACzC;AACA,WAAO,YAAY,gBAAgB,KAAK;AAAA,EAC1C,CAAC;AACH;AAEA,SAAS,wBAAwB,MAAsB;AACrD,QAAM,CAAC,OAAO,IAAI,KAAK,MAAM,GAAG;AAChC,QAAM,iBAAiB,QAAQ,WAAW,MAAM,IAC5C,UACA,OAAO,QAAQ,WAAW,GAAG,IAAI,UAAU,IAAI,OAAO,EAAE;AAE5D,MAAI,eAAe,SAAS,KAAK,eAAe,SAAS,GAAG,GAAG;AAC7D,WAAO,eAAe,MAAM,GAAG,EAAE;AAAA,EACnC;AAEA,SAAO;AACT;AAEA,SAAS,uBAAuB,SAA0B;AACxD,SACG,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,KAC/C,QAAQ,WAAW,GAAG,KAAK,QAAQ,SAAS,GAAG,KAChD,QAAQ,WAAW,GAAG;AAE1B;AAEA,SAAS,mBAAmB,QAAyB;AACnD,SAAO,CAAC,CAAC,OAAO,QAAQ,SAAS,EAAE,SAAS,OAAO,YAAY,CAAC;AAClE;AAEA,SAAS,aAAa,MAAuB;AAC3C,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": ["memoryContext", "callDuration"]
|
|
7
7
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
|
|
2
|
+
import { fetchWithTimeout, resolveTimeoutMs } from "@open-mercato/shared/lib/http/fetchWithTimeout";
|
|
3
|
+
const DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS = 3e4;
|
|
4
|
+
const DEFAULT_OPENCODE_SSE_CONNECT_TIMEOUT_MS = 15e3;
|
|
5
|
+
function resolveOpencodeTimeoutMs(envVar, fallback) {
|
|
6
|
+
const raw = process.env[envVar];
|
|
7
|
+
const parsed = raw ? Number.parseInt(raw, 10) : void 0;
|
|
8
|
+
return resolveTimeoutMs(parsed, fallback);
|
|
9
|
+
}
|
|
2
10
|
class OpenCodeClient {
|
|
3
11
|
constructor(config) {
|
|
4
12
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
@@ -16,6 +24,22 @@ class OpenCodeClient {
|
|
|
16
24
|
*/
|
|
17
25
|
subscribeToEvents(onEvent, onError) {
|
|
18
26
|
const controller = new AbortController();
|
|
27
|
+
const connectTimeoutMs = resolveOpencodeTimeoutMs(
|
|
28
|
+
"OPENCODE_SSE_CONNECT_TIMEOUT_MS",
|
|
29
|
+
DEFAULT_OPENCODE_SSE_CONNECT_TIMEOUT_MS
|
|
30
|
+
);
|
|
31
|
+
const connectTimeoutReason = new Error(
|
|
32
|
+
`OpenCode SSE connection timed out after ${connectTimeoutMs}ms`
|
|
33
|
+
);
|
|
34
|
+
let connectTimer = setTimeout(() => {
|
|
35
|
+
controller.abort(connectTimeoutReason);
|
|
36
|
+
}, connectTimeoutMs);
|
|
37
|
+
const clearConnectTimer = () => {
|
|
38
|
+
if (connectTimer !== null) {
|
|
39
|
+
clearTimeout(connectTimer);
|
|
40
|
+
connectTimer = null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
19
43
|
const connect = async () => {
|
|
20
44
|
try {
|
|
21
45
|
const res = await fetch(`${this.baseUrl}/event`, {
|
|
@@ -25,6 +49,7 @@ class OpenCodeClient {
|
|
|
25
49
|
},
|
|
26
50
|
signal: controller.signal
|
|
27
51
|
});
|
|
52
|
+
clearConnectTimer();
|
|
28
53
|
if (!res.ok || !res.body) {
|
|
29
54
|
throw new Error(`SSE connection failed: ${res.status}`);
|
|
30
55
|
}
|
|
@@ -48,20 +73,30 @@ class OpenCodeClient {
|
|
|
48
73
|
}
|
|
49
74
|
}
|
|
50
75
|
} catch (error) {
|
|
51
|
-
|
|
52
|
-
|
|
76
|
+
clearConnectTimer();
|
|
77
|
+
if (error.name === "AbortError") {
|
|
78
|
+
const reason = controller.signal.reason;
|
|
79
|
+
if (reason === connectTimeoutReason) {
|
|
80
|
+
onError?.(connectTimeoutReason);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
53
83
|
}
|
|
84
|
+
onError?.(error);
|
|
54
85
|
}
|
|
55
86
|
};
|
|
56
87
|
connect();
|
|
57
|
-
return () =>
|
|
88
|
+
return () => {
|
|
89
|
+
clearConnectTimer();
|
|
90
|
+
controller.abort();
|
|
91
|
+
};
|
|
58
92
|
}
|
|
59
93
|
/**
|
|
60
94
|
* Check OpenCode server health.
|
|
61
95
|
*/
|
|
62
96
|
async health() {
|
|
63
|
-
const res = await
|
|
64
|
-
headers: this.headers
|
|
97
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/global/health`, {
|
|
98
|
+
headers: this.headers,
|
|
99
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
65
100
|
});
|
|
66
101
|
if (!res.ok) {
|
|
67
102
|
throw new Error(`Health check failed: ${res.status}`);
|
|
@@ -74,8 +109,9 @@ class OpenCodeClient {
|
|
|
74
109
|
* Get MCP server connection status.
|
|
75
110
|
*/
|
|
76
111
|
async mcpStatus() {
|
|
77
|
-
const res = await
|
|
78
|
-
headers: this.headers
|
|
112
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/mcp`, {
|
|
113
|
+
headers: this.headers,
|
|
114
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
79
115
|
});
|
|
80
116
|
if (!res.ok) {
|
|
81
117
|
throw new Error(`MCP status check failed: ${res.status}`);
|
|
@@ -88,10 +124,11 @@ class OpenCodeClient {
|
|
|
88
124
|
* Create a new conversation session.
|
|
89
125
|
*/
|
|
90
126
|
async createSession() {
|
|
91
|
-
const res = await
|
|
127
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/session`, {
|
|
92
128
|
method: "POST",
|
|
93
129
|
headers: this.headers,
|
|
94
|
-
body: JSON.stringify({})
|
|
130
|
+
body: JSON.stringify({}),
|
|
131
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
95
132
|
});
|
|
96
133
|
if (!res.ok) {
|
|
97
134
|
const error = await res.text();
|
|
@@ -105,8 +142,9 @@ class OpenCodeClient {
|
|
|
105
142
|
* Get an existing session by ID.
|
|
106
143
|
*/
|
|
107
144
|
async getSession(sessionId) {
|
|
108
|
-
const res = await
|
|
109
|
-
headers: this.headers
|
|
145
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}`, {
|
|
146
|
+
headers: this.headers,
|
|
147
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
110
148
|
});
|
|
111
149
|
if (!res.ok) {
|
|
112
150
|
throw new Error(`Failed to get session: ${res.status}`);
|
|
@@ -125,10 +163,14 @@ class OpenCodeClient {
|
|
|
125
163
|
if (options?.model) {
|
|
126
164
|
body.model = options.model;
|
|
127
165
|
}
|
|
128
|
-
const res = await
|
|
166
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}/message`, {
|
|
129
167
|
method: "POST",
|
|
130
168
|
headers: this.headers,
|
|
131
|
-
body: JSON.stringify(body)
|
|
169
|
+
body: JSON.stringify(body),
|
|
170
|
+
timeoutMs: resolveOpencodeTimeoutMs(
|
|
171
|
+
"OPENCODE_SEND_MESSAGE_TIMEOUT_MS",
|
|
172
|
+
resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
173
|
+
)
|
|
132
174
|
});
|
|
133
175
|
if (!res.ok) {
|
|
134
176
|
const error = await res.text();
|
|
@@ -142,10 +184,11 @@ class OpenCodeClient {
|
|
|
142
184
|
* Set authentication credentials for a provider.
|
|
143
185
|
*/
|
|
144
186
|
async setAuth(providerId, apiKey) {
|
|
145
|
-
const res = await
|
|
187
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/auth/${providerId}`, {
|
|
146
188
|
method: "PUT",
|
|
147
189
|
headers: this.headers,
|
|
148
|
-
body: JSON.stringify({ type: "api", key: apiKey })
|
|
190
|
+
body: JSON.stringify({ type: "api", key: apiKey }),
|
|
191
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
149
192
|
});
|
|
150
193
|
if (!res.ok) {
|
|
151
194
|
throw new Error(`Failed to set auth: ${res.status}`);
|
|
@@ -155,8 +198,9 @@ class OpenCodeClient {
|
|
|
155
198
|
* Get current configuration.
|
|
156
199
|
*/
|
|
157
200
|
async getConfig() {
|
|
158
|
-
const res = await
|
|
159
|
-
headers: this.headers
|
|
201
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/config`, {
|
|
202
|
+
headers: this.headers,
|
|
203
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
160
204
|
});
|
|
161
205
|
if (!res.ok) {
|
|
162
206
|
throw new Error(`Failed to get config: ${res.status}`);
|
|
@@ -169,8 +213,9 @@ class OpenCodeClient {
|
|
|
169
213
|
* Get pending questions that need user response.
|
|
170
214
|
*/
|
|
171
215
|
async getPendingQuestions() {
|
|
172
|
-
const res = await
|
|
173
|
-
headers: this.headers
|
|
216
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/question`, {
|
|
217
|
+
headers: this.headers,
|
|
218
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
174
219
|
});
|
|
175
220
|
if (!res.ok) {
|
|
176
221
|
throw new Error(`Failed to get questions: ${res.status}`);
|
|
@@ -199,10 +244,11 @@ class OpenCodeClient {
|
|
|
199
244
|
}
|
|
200
245
|
const body = { answers };
|
|
201
246
|
console.log("[OpenCode Client] Answering question", questionId, "with body:", JSON.stringify(body));
|
|
202
|
-
const res = await
|
|
247
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/question/${questionId}/reply`, {
|
|
203
248
|
method: "POST",
|
|
204
249
|
headers: this.headers,
|
|
205
|
-
body: JSON.stringify(body)
|
|
250
|
+
body: JSON.stringify(body),
|
|
251
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
206
252
|
});
|
|
207
253
|
const responseText = await res.text();
|
|
208
254
|
console.log("[OpenCode Client] Answer response:", res.status, responseText.substring(0, 200));
|
|
@@ -215,9 +261,10 @@ class OpenCodeClient {
|
|
|
215
261
|
*/
|
|
216
262
|
async rejectQuestion(questionId) {
|
|
217
263
|
console.log("[OpenCode Client] Rejecting question", questionId);
|
|
218
|
-
const res = await
|
|
264
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/question/${questionId}/reject`, {
|
|
219
265
|
method: "POST",
|
|
220
|
-
headers: this.headers
|
|
266
|
+
headers: this.headers,
|
|
267
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
221
268
|
});
|
|
222
269
|
if (!res.ok) {
|
|
223
270
|
const responseText = await res.text();
|
|
@@ -230,8 +277,9 @@ class OpenCodeClient {
|
|
|
230
277
|
*/
|
|
231
278
|
async getSessionStatus(sessionId) {
|
|
232
279
|
try {
|
|
233
|
-
const res = await
|
|
234
|
-
headers: this.headers
|
|
280
|
+
const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}/status`, {
|
|
281
|
+
headers: this.headers,
|
|
282
|
+
timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
|
|
235
283
|
});
|
|
236
284
|
if (res.ok) {
|
|
237
285
|
const contentType = res.headers.get("content-type");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/opencode-client.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * OpenCode Agent Client\n *\n * Client for communicating with OpenCode server running in headless mode.\n * OpenCode is used as an AI agent that can execute MCP tools.\n */\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\n\nexport type OpenCodeClientConfig = {\n baseUrl: string\n password?: string\n}\n\nexport type OpenCodeSession = {\n id: string\n slug: string\n version: string\n projectID: string\n directory: string\n title: string\n time: {\n created: number\n updated: number\n }\n}\n\nexport type OpenCodeMessagePart = {\n type: 'text'\n text: string\n}\n\nexport type OpenCodeMessageInfo = {\n id: string\n sessionID: string\n role: 'user' | 'assistant'\n time: {\n created: number\n completed?: number\n }\n modelID?: string\n providerID?: string\n tokens?: {\n input: number\n output: number\n }\n error?: {\n name: string\n data: Record<string, unknown>\n }\n}\n\nexport type OpenCodeMessage = {\n info: OpenCodeMessageInfo\n parts: Array<{\n id: string\n type: string\n text?: string\n [key: string]: unknown\n }>\n}\n\nexport type OpenCodeHealth = {\n healthy: boolean\n version: string\n}\n\nexport type OpenCodeMcpStatus = Record<\n string,\n {\n status: 'connected' | 'failed' | 'connecting'\n error?: string\n }\n>\n\nexport type OpenCodeQuestionOption = {\n label: string\n description: string\n}\n\nexport type OpenCodeQuestion = {\n id: string\n sessionID: string\n questions: Array<{\n question: string\n header: string\n options: OpenCodeQuestionOption[]\n }>\n tool: {\n messageID: string\n callID: string\n }\n}\n\n/**\n * SSE Event from OpenCode event stream.\n */\nexport type OpenCodeSSEEvent = {\n type: string\n properties: Record<string, unknown>\n}\n\n/**\n * Callback for SSE events.\n */\nexport type OpenCodeSSECallback = (event: OpenCodeSSEEvent) => void\n\n/**\n * Client for OpenCode server API.\n */\nexport class OpenCodeClient {\n private baseUrl: string\n private headers: Record<string, string>\n\n constructor(config: OpenCodeClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '')\n this.headers = {\n 'Content-Type': 'application/json',\n }\n\n if (config.password) {\n const credentials = Buffer.from(`opencode:${config.password}`).toString('base64')\n this.headers['Authorization'] = `Basic ${credentials}`\n }\n }\n\n /**\n * Subscribe to SSE event stream for real-time updates.\n * Returns an abort function to stop the stream.\n */\n subscribeToEvents(\n onEvent: OpenCodeSSECallback,\n onError?: (error: Error) => void\n ): () => void {\n const controller = new AbortController()\n\n const connect = async () => {\n try {\n const res = await fetch(`${this.baseUrl}/event`, {\n headers: {\n ...this.headers,\n Accept: 'text/event-stream',\n },\n signal: controller.signal,\n })\n\n if (!res.ok || !res.body) {\n throw new Error(`SSE connection failed: ${res.status}`)\n }\n\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n\n // Process complete SSE messages\n const lines = buffer.split('\\n')\n buffer = lines.pop() || '' // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6))\n onEvent(data)\n } catch {\n // Ignore parse errors\n }\n }\n }\n }\n } catch (error) {\n if ((error as Error).name !== 'AbortError') {\n onError?.(error as Error)\n }\n }\n }\n\n connect()\n\n return () => controller.abort()\n }\n\n /**\n * Check OpenCode server health.\n */\n async health(): Promise<OpenCodeHealth> {\n const res = await fetch(`${this.baseUrl}/global/health`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`Health check failed: ${res.status}`)\n }\n\n const data = await readJsonSafe<OpenCodeHealth>(res, null)\n if (!data) throw new Error('Health check returned invalid JSON response')\n return data\n }\n\n /**\n * Get MCP server connection status.\n */\n async mcpStatus(): Promise<OpenCodeMcpStatus> {\n const res = await fetch(`${this.baseUrl}/mcp`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`MCP status check failed: ${res.status}`)\n }\n\n const data = await readJsonSafe<OpenCodeMcpStatus>(res, null)\n if (!data) throw new Error('MCP status check returned invalid JSON response')\n return data\n }\n\n /**\n * Create a new conversation session.\n */\n async createSession(): Promise<OpenCodeSession> {\n const res = await fetch(`${this.baseUrl}/session`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({}),\n })\n\n if (!res.ok) {\n const error = await res.text()\n throw new Error(`Failed to create session: ${error}`)\n }\n\n const data = await readJsonSafe<OpenCodeSession>(res, null)\n if (!data) throw new Error('Create session returned invalid JSON response')\n return data\n }\n\n /**\n * Get an existing session by ID.\n */\n async getSession(sessionId: string): Promise<OpenCodeSession> {\n const res = await fetch(`${this.baseUrl}/session/${sessionId}`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get session: ${res.status}`)\n }\n\n const data = await readJsonSafe<OpenCodeSession>(res, null)\n if (!data) throw new Error('Get session returned invalid JSON response')\n return data\n }\n\n /**\n * Send a message to a session and wait for response.\n */\n async sendMessage(\n sessionId: string,\n message: string,\n options?: {\n model?: { providerID: string; modelID: string }\n }\n ): Promise<OpenCodeMessage> {\n const body: Record<string, unknown> = {\n parts: [{ type: 'text', text: message }],\n }\n\n if (options?.model) {\n body.model = options.model\n }\n\n const res = await fetch(`${this.baseUrl}/session/${sessionId}/message`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(body),\n })\n\n if (!res.ok) {\n const error = await res.text()\n throw new Error(`Failed to send message: ${error}`)\n }\n\n const data = await readJsonSafe<OpenCodeMessage>(res, null)\n if (!data) throw new Error('Send message returned invalid JSON response')\n return data\n }\n\n /**\n * Set authentication credentials for a provider.\n */\n async setAuth(providerId: string, apiKey: string): Promise<void> {\n const res = await fetch(`${this.baseUrl}/auth/${providerId}`, {\n method: 'PUT',\n headers: this.headers,\n body: JSON.stringify({ type: 'api', key: apiKey }),\n })\n\n if (!res.ok) {\n throw new Error(`Failed to set auth: ${res.status}`)\n }\n }\n\n /**\n * Get current configuration.\n */\n async getConfig(): Promise<Record<string, unknown>> {\n const res = await fetch(`${this.baseUrl}/config`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get config: ${res.status}`)\n }\n\n const data = await readJsonSafe<Record<string, unknown>>(res, null)\n if (!data) throw new Error('Get config returned invalid JSON response')\n return data\n }\n\n /**\n * Get pending questions that need user response.\n */\n async getPendingQuestions(): Promise<OpenCodeQuestion[]> {\n const res = await fetch(`${this.baseUrl}/question`, {\n headers: this.headers,\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get questions: ${res.status}`)\n }\n\n const data = await readJsonSafe<OpenCodeQuestion[]>(res, null)\n if (!data) throw new Error('Get questions returned invalid JSON response')\n return data\n }\n\n /**\n * Answer a pending question.\n * OpenCode expects: POST /question/{requestID}/reply with { answers: [[\"label\"]] }\n * Each answer is an array of selected option labels (for multi-select support).\n */\n async answerQuestion(questionId: string, answerIndex: number): Promise<void> {\n // First get the question to find the selected option label\n const questions = await this.getPendingQuestions()\n const question = questions.find((q) => q.id === questionId)\n\n if (!question) {\n throw new Error(`Question ${questionId} not found`)\n }\n\n // Build answers array - each question's answer is an array of selected labels\n const answers: string[][] = []\n for (const q of question.questions) {\n const selectedOption = q.options[answerIndex]\n if (selectedOption) {\n // Each answer is an array of selected labels (supports multi-select)\n answers.push([selectedOption.label])\n }\n }\n\n const body = { answers }\n\n console.log('[OpenCode Client] Answering question', questionId, 'with body:', JSON.stringify(body))\n\n const res = await fetch(`${this.baseUrl}/question/${questionId}/reply`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(body),\n })\n\n const responseText = await res.text()\n console.log('[OpenCode Client] Answer response:', res.status, responseText.substring(0, 200))\n\n if (!res.ok) {\n throw new Error(`Failed to answer question: ${res.status} - ${responseText}`)\n }\n }\n\n /**\n * Reject a pending question.\n */\n async rejectQuestion(questionId: string): Promise<void> {\n console.log('[OpenCode Client] Rejecting question', questionId)\n\n const res = await fetch(`${this.baseUrl}/question/${questionId}/reject`, {\n method: 'POST',\n headers: this.headers,\n })\n\n if (!res.ok) {\n const responseText = await res.text()\n throw new Error(`Failed to reject question: ${res.status} - ${responseText}`)\n }\n }\n\n /**\n * Get session status (idle, busy, waiting for question).\n * Falls back to inferring status from pending questions if endpoint doesn't exist.\n */\n async getSessionStatus(sessionId: string): Promise<{ status: string; questionId?: string }> {\n try {\n const res = await fetch(`${this.baseUrl}/session/${sessionId}/status`, {\n headers: this.headers,\n })\n\n if (res.ok) {\n const contentType = res.headers.get('content-type')\n if (contentType && contentType.includes('application/json')) {\n const data = await readJsonSafe<{ status: string; questionId?: string }>(res, null)\n if (data) return data\n }\n }\n } catch {\n // Endpoint doesn't exist or network error - fall through to inference\n }\n\n // Fall back to inferring status from pending questions\n // Note: We can't tell if OpenCode is busy without the status endpoint\n // Return 'unknown' to let SSE events determine actual state\n const questions = await this.getPendingQuestions()\n const sessionQuestion = questions.find((q) => q.sessionID === sessionId)\n if (sessionQuestion) {\n return { status: 'waiting', questionId: sessionQuestion.id }\n }\n // Don't assume idle - we can't know without SSE events\n return { status: 'unknown' }\n }\n}\n\n/**\n * Create an OpenCode client with default configuration from environment.\n */\nexport function createOpenCodeClient(config?: Partial<OpenCodeClientConfig>): OpenCodeClient {\n return new OpenCodeClient({\n baseUrl: config?.baseUrl ?? process.env.OPENCODE_URL ?? 'http://localhost:4096',\n password: config?.password ?? process.env.OPENCODE_PASSWORD,\n })\n}\n"],
|
|
5
|
-
"mappings": "AAMA,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["/**\n * OpenCode Agent Client\n *\n * Client for communicating with OpenCode server running in headless mode.\n * OpenCode is used as an AI agent that can execute MCP tools.\n */\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { fetchWithTimeout, resolveTimeoutMs } from '@open-mercato/shared/lib/http/fetchWithTimeout'\n\nconst DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS = 30_000\nconst DEFAULT_OPENCODE_SSE_CONNECT_TIMEOUT_MS = 15_000\n\nfunction resolveOpencodeTimeoutMs(envVar: string, fallback: number): number {\n const raw = process.env[envVar]\n const parsed = raw ? Number.parseInt(raw, 10) : undefined\n return resolveTimeoutMs(parsed, fallback)\n}\n\nexport type OpenCodeClientConfig = {\n baseUrl: string\n password?: string\n}\n\nexport type OpenCodeSession = {\n id: string\n slug: string\n version: string\n projectID: string\n directory: string\n title: string\n time: {\n created: number\n updated: number\n }\n}\n\nexport type OpenCodeMessagePart = {\n type: 'text'\n text: string\n}\n\nexport type OpenCodeMessageInfo = {\n id: string\n sessionID: string\n role: 'user' | 'assistant'\n time: {\n created: number\n completed?: number\n }\n modelID?: string\n providerID?: string\n tokens?: {\n input: number\n output: number\n }\n error?: {\n name: string\n data: Record<string, unknown>\n }\n}\n\nexport type OpenCodeMessage = {\n info: OpenCodeMessageInfo\n parts: Array<{\n id: string\n type: string\n text?: string\n [key: string]: unknown\n }>\n}\n\nexport type OpenCodeHealth = {\n healthy: boolean\n version: string\n}\n\nexport type OpenCodeMcpStatus = Record<\n string,\n {\n status: 'connected' | 'failed' | 'connecting'\n error?: string\n }\n>\n\nexport type OpenCodeQuestionOption = {\n label: string\n description: string\n}\n\nexport type OpenCodeQuestion = {\n id: string\n sessionID: string\n questions: Array<{\n question: string\n header: string\n options: OpenCodeQuestionOption[]\n }>\n tool: {\n messageID: string\n callID: string\n }\n}\n\n/**\n * SSE Event from OpenCode event stream.\n */\nexport type OpenCodeSSEEvent = {\n type: string\n properties: Record<string, unknown>\n}\n\n/**\n * Callback for SSE events.\n */\nexport type OpenCodeSSECallback = (event: OpenCodeSSEEvent) => void\n\n/**\n * Client for OpenCode server API.\n */\nexport class OpenCodeClient {\n private baseUrl: string\n private headers: Record<string, string>\n\n constructor(config: OpenCodeClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '')\n this.headers = {\n 'Content-Type': 'application/json',\n }\n\n if (config.password) {\n const credentials = Buffer.from(`opencode:${config.password}`).toString('base64')\n this.headers['Authorization'] = `Basic ${credentials}`\n }\n }\n\n /**\n * Subscribe to SSE event stream for real-time updates.\n * Returns an abort function to stop the stream.\n */\n subscribeToEvents(\n onEvent: OpenCodeSSECallback,\n onError?: (error: Error) => void\n ): () => void {\n const controller = new AbortController()\n const connectTimeoutMs = resolveOpencodeTimeoutMs(\n 'OPENCODE_SSE_CONNECT_TIMEOUT_MS',\n DEFAULT_OPENCODE_SSE_CONNECT_TIMEOUT_MS,\n )\n const connectTimeoutReason = new Error(\n `OpenCode SSE connection timed out after ${connectTimeoutMs}ms`,\n )\n let connectTimer: ReturnType<typeof setTimeout> | null = setTimeout(() => {\n controller.abort(connectTimeoutReason)\n }, connectTimeoutMs)\n const clearConnectTimer = () => {\n if (connectTimer !== null) {\n clearTimeout(connectTimer)\n connectTimer = null\n }\n }\n\n const connect = async () => {\n try {\n const res = await fetch(`${this.baseUrl}/event`, {\n headers: {\n ...this.headers,\n Accept: 'text/event-stream',\n },\n signal: controller.signal,\n })\n clearConnectTimer()\n\n if (!res.ok || !res.body) {\n throw new Error(`SSE connection failed: ${res.status}`)\n }\n\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n\n // Process complete SSE messages\n const lines = buffer.split('\\n')\n buffer = lines.pop() || '' // Keep incomplete line in buffer\n\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const data = JSON.parse(line.slice(6))\n onEvent(data)\n } catch {\n // Ignore parse errors\n }\n }\n }\n }\n } catch (error) {\n clearConnectTimer()\n if ((error as Error).name === 'AbortError') {\n const reason = (controller.signal as AbortSignal & { reason?: unknown }).reason\n if (reason === connectTimeoutReason) {\n onError?.(connectTimeoutReason)\n }\n return\n }\n onError?.(error as Error)\n }\n }\n\n connect()\n\n return () => {\n clearConnectTimer()\n controller.abort()\n }\n }\n\n /**\n * Check OpenCode server health.\n */\n async health(): Promise<OpenCodeHealth> {\n const res = await fetchWithTimeout(`${this.baseUrl}/global/health`, {\n headers: this.headers,\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (!res.ok) {\n throw new Error(`Health check failed: ${res.status}`)\n }\n\n const data = await readJsonSafe<OpenCodeHealth>(res, null)\n if (!data) throw new Error('Health check returned invalid JSON response')\n return data\n }\n\n /**\n * Get MCP server connection status.\n */\n async mcpStatus(): Promise<OpenCodeMcpStatus> {\n const res = await fetchWithTimeout(`${this.baseUrl}/mcp`, {\n headers: this.headers,\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (!res.ok) {\n throw new Error(`MCP status check failed: ${res.status}`)\n }\n\n const data = await readJsonSafe<OpenCodeMcpStatus>(res, null)\n if (!data) throw new Error('MCP status check returned invalid JSON response')\n return data\n }\n\n /**\n * Create a new conversation session.\n */\n async createSession(): Promise<OpenCodeSession> {\n const res = await fetchWithTimeout(`${this.baseUrl}/session`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify({}),\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (!res.ok) {\n const error = await res.text()\n throw new Error(`Failed to create session: ${error}`)\n }\n\n const data = await readJsonSafe<OpenCodeSession>(res, null)\n if (!data) throw new Error('Create session returned invalid JSON response')\n return data\n }\n\n /**\n * Get an existing session by ID.\n */\n async getSession(sessionId: string): Promise<OpenCodeSession> {\n const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}`, {\n headers: this.headers,\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get session: ${res.status}`)\n }\n\n const data = await readJsonSafe<OpenCodeSession>(res, null)\n if (!data) throw new Error('Get session returned invalid JSON response')\n return data\n }\n\n /**\n * Send a message to a session and wait for response.\n */\n async sendMessage(\n sessionId: string,\n message: string,\n options?: {\n model?: { providerID: string; modelID: string }\n }\n ): Promise<OpenCodeMessage> {\n const body: Record<string, unknown> = {\n parts: [{ type: 'text', text: message }],\n }\n\n if (options?.model) {\n body.model = options.model\n }\n\n const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}/message`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(body),\n timeoutMs: resolveOpencodeTimeoutMs(\n 'OPENCODE_SEND_MESSAGE_TIMEOUT_MS',\n resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n ),\n })\n\n if (!res.ok) {\n const error = await res.text()\n throw new Error(`Failed to send message: ${error}`)\n }\n\n const data = await readJsonSafe<OpenCodeMessage>(res, null)\n if (!data) throw new Error('Send message returned invalid JSON response')\n return data\n }\n\n /**\n * Set authentication credentials for a provider.\n */\n async setAuth(providerId: string, apiKey: string): Promise<void> {\n const res = await fetchWithTimeout(`${this.baseUrl}/auth/${providerId}`, {\n method: 'PUT',\n headers: this.headers,\n body: JSON.stringify({ type: 'api', key: apiKey }),\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (!res.ok) {\n throw new Error(`Failed to set auth: ${res.status}`)\n }\n }\n\n /**\n * Get current configuration.\n */\n async getConfig(): Promise<Record<string, unknown>> {\n const res = await fetchWithTimeout(`${this.baseUrl}/config`, {\n headers: this.headers,\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get config: ${res.status}`)\n }\n\n const data = await readJsonSafe<Record<string, unknown>>(res, null)\n if (!data) throw new Error('Get config returned invalid JSON response')\n return data\n }\n\n /**\n * Get pending questions that need user response.\n */\n async getPendingQuestions(): Promise<OpenCodeQuestion[]> {\n const res = await fetchWithTimeout(`${this.baseUrl}/question`, {\n headers: this.headers,\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (!res.ok) {\n throw new Error(`Failed to get questions: ${res.status}`)\n }\n\n const data = await readJsonSafe<OpenCodeQuestion[]>(res, null)\n if (!data) throw new Error('Get questions returned invalid JSON response')\n return data\n }\n\n /**\n * Answer a pending question.\n * OpenCode expects: POST /question/{requestID}/reply with { answers: [[\"label\"]] }\n * Each answer is an array of selected option labels (for multi-select support).\n */\n async answerQuestion(questionId: string, answerIndex: number): Promise<void> {\n // First get the question to find the selected option label\n const questions = await this.getPendingQuestions()\n const question = questions.find((q) => q.id === questionId)\n\n if (!question) {\n throw new Error(`Question ${questionId} not found`)\n }\n\n // Build answers array - each question's answer is an array of selected labels\n const answers: string[][] = []\n for (const q of question.questions) {\n const selectedOption = q.options[answerIndex]\n if (selectedOption) {\n // Each answer is an array of selected labels (supports multi-select)\n answers.push([selectedOption.label])\n }\n }\n\n const body = { answers }\n\n console.log('[OpenCode Client] Answering question', questionId, 'with body:', JSON.stringify(body))\n\n const res = await fetchWithTimeout(`${this.baseUrl}/question/${questionId}/reply`, {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(body),\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n const responseText = await res.text()\n console.log('[OpenCode Client] Answer response:', res.status, responseText.substring(0, 200))\n\n if (!res.ok) {\n throw new Error(`Failed to answer question: ${res.status} - ${responseText}`)\n }\n }\n\n /**\n * Reject a pending question.\n */\n async rejectQuestion(questionId: string): Promise<void> {\n console.log('[OpenCode Client] Rejecting question', questionId)\n\n const res = await fetchWithTimeout(`${this.baseUrl}/question/${questionId}/reject`, {\n method: 'POST',\n headers: this.headers,\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (!res.ok) {\n const responseText = await res.text()\n throw new Error(`Failed to reject question: ${res.status} - ${responseText}`)\n }\n }\n\n /**\n * Get session status (idle, busy, waiting for question).\n * Falls back to inferring status from pending questions if endpoint doesn't exist.\n */\n async getSessionStatus(sessionId: string): Promise<{ status: string; questionId?: string }> {\n try {\n const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}/status`, {\n headers: this.headers,\n timeoutMs: resolveOpencodeTimeoutMs('OPENCODE_REQUEST_TIMEOUT_MS', DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS),\n })\n\n if (res.ok) {\n const contentType = res.headers.get('content-type')\n if (contentType && contentType.includes('application/json')) {\n const data = await readJsonSafe<{ status: string; questionId?: string }>(res, null)\n if (data) return data\n }\n }\n } catch {\n // Endpoint doesn't exist or network error - fall through to inference\n }\n\n // Fall back to inferring status from pending questions\n // Note: We can't tell if OpenCode is busy without the status endpoint\n // Return 'unknown' to let SSE events determine actual state\n const questions = await this.getPendingQuestions()\n const sessionQuestion = questions.find((q) => q.sessionID === sessionId)\n if (sessionQuestion) {\n return { status: 'waiting', questionId: sessionQuestion.id }\n }\n // Don't assume idle - we can't know without SSE events\n return { status: 'unknown' }\n }\n}\n\n/**\n * Create an OpenCode client with default configuration from environment.\n */\nexport function createOpenCodeClient(config?: Partial<OpenCodeClientConfig>): OpenCodeClient {\n return new OpenCodeClient({\n baseUrl: config?.baseUrl ?? process.env.OPENCODE_URL ?? 'http://localhost:4096',\n password: config?.password ?? process.env.OPENCODE_PASSWORD,\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ai-assistant",
|
|
3
|
-
"version": "0.4.11-develop.
|
|
3
|
+
"version": "0.4.11-develop.2516.30653cfe18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -98,12 +98,12 @@
|
|
|
98
98
|
"zod-to-json-schema": "^3.25.1"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
|
-
"@open-mercato/shared": "0.4.11-develop.
|
|
102
|
-
"@open-mercato/ui": "0.4.11-develop.
|
|
101
|
+
"@open-mercato/shared": "0.4.11-develop.2516.30653cfe18",
|
|
102
|
+
"@open-mercato/ui": "0.4.11-develop.2516.30653cfe18",
|
|
103
103
|
"zod": ">=3.23.0"
|
|
104
104
|
},
|
|
105
105
|
"devDependencies": {
|
|
106
|
-
"@open-mercato/cli": "0.4.11-develop.
|
|
106
|
+
"@open-mercato/cli": "0.4.11-develop.2516.30653cfe18",
|
|
107
107
|
"tsx": "^4.21.0"
|
|
108
108
|
},
|
|
109
109
|
"publishConfig": {
|