From 66fb7ef2266cb443440a156c0061eb850cecbb47 Mon Sep 17 00:00:00 2001 From: Thomas Davis Date: Tue, 10 Feb 2026 01:38:12 +1000 Subject: [PATCH] fix(omega): pass tool inputSchema to LLM for proper parameter generation The search API wasn't returning inputSchema, so dynamic tools were created with empty schemas. The LLM saw tools with no parameters and called them with {}. Now the search API returns inputSchema, and if it's null in the database, we fetch it from the executor's loadAndDescribe endpoint. Co-Authored-By: Claude Opus 4.6 --- .../conversations/[id]/messages/route.ts | 53 ++++++++++++++++++- apps/web/src/app/api/tools/search/route.ts | 1 + 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/api/omega/conversations/[id]/messages/route.ts b/apps/web/src/app/api/omega/conversations/[id]/messages/route.ts index 708ae9a..1c1a841 100644 --- a/apps/web/src/app/api/omega/conversations/[id]/messages/route.ts +++ b/apps/web/src/app/api/omega/conversations/[id]/messages/route.ts @@ -203,6 +203,49 @@ async function searchRelevantTools( })); } +/** + * Fetch the tool's inputSchema from the executor's loadAndDescribe endpoint. + * This is used when the schema isn't available in the database yet. + */ +async function fetchSchemaFromExecutor(toolMeta: { + packageName: string; + name: string; + version: string; + importUrl: string; +}): Promise { + try { + console.log(`📋 Fetching schema from executor for ${toolMeta.packageName}/${toolMeta.name}`); + const response = await fetch(`${EXECUTOR_URL}/load-and-describe`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + packageName: toolMeta.packageName, + name: toolMeta.name, + version: toolMeta.version, + importUrl: toolMeta.importUrl, + }), + }); + + if (!response.ok) { + console.warn( + `⚠️ Schema fetch failed (${response.status}) for ${toolMeta.packageName}/${toolMeta.name}` + ); + return null; + } + + // biome-ignore lint/suspicious/noExplicitAny: Executor response format varies + const result = (await response.json()) as any; + if (result.success && result.tool?.inputSchema) { + console.log(`✅ Got schema from executor for ${toolMeta.packageName}/${toolMeta.name}`); + return result.tool.inputSchema; + } + return null; + } catch (error) { + console.warn(`⚠️ Schema fetch error for ${toolMeta.packageName}/${toolMeta.name}:`, error); + return null; + } +} + /** * Create a dynamic tool wrapper that executes via the sandbox executor */ @@ -221,10 +264,16 @@ async function createDynamicTool( // Import tool() dynamically to avoid top-level await const { tool } = await import('ai'); + // If inputSchema is missing from the database, fetch it from the executor + let schema = toolMeta.inputSchema; + if (!schema) { + schema = await fetchSchemaFromExecutor(toolMeta); + } + return tool({ description: toolMeta.description, - inputSchema: toolMeta.inputSchema - ? jsonSchema(toolMeta.inputSchema as Parameters[0]) + inputSchema: schema + ? jsonSchema(schema as Parameters[0]) : jsonSchema({ type: 'object', properties: {}, diff --git a/apps/web/src/app/api/tools/search/route.ts b/apps/web/src/app/api/tools/search/route.ts index 7a88f60..9f04a01 100644 --- a/apps/web/src/app/api/tools/search/route.ts +++ b/apps/web/src/app/api/tools/search/route.ts @@ -235,6 +235,7 @@ export async function GET(request: NextRequest) { id: tool.id, name: tool.name, description: tool.description, + inputSchema: tool.inputSchema, qualityScore: tool.qualityScore, importHealth: tool.importHealth, executionHealth: tool.executionHealth,