From feeb3cf3e8e205f17e2f686e252ec51345861464 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Mon, 15 Dec 2025 13:07:42 +1000 Subject: [PATCH] refactor: move schema updates to executor - Executor now updates TPM.js database directly when loading tools - Update /api/tools/update-schema to use packageName+exportName lookup - Remove schema update logic from HLLM proxy (no longer needed) --- apps/railway-executor/server.ts | 46 +++++++++++ .../src/app/api/tools/update-schema/route.ts | 82 +++++++++++++++---- 2 files changed, 110 insertions(+), 18 deletions(-) diff --git a/apps/railway-executor/server.ts b/apps/railway-executor/server.ts index a47578c..b9f3488 100644 --- a/apps/railway-executor/server.ts +++ b/apps/railway-executor/server.ts @@ -48,6 +48,45 @@ async function reportToolHealth( } } +/** + * Update tool schema in TPM.js database + * Non-blocking - fires and forgets to keep schemas up to date + */ +async function updateToolSchema( + packageName: string, + exportName: string, + description: string, + // biome-ignore lint/suspicious/noExplicitAny: JSON Schema can have any structure + inputSchema: any +): Promise { + try { + const response = await fetch(`${TPMJS_API_URL}/api/tools/update-schema`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + packageName, + exportName, + description, + inputSchema, + }), + }); + + if (response.ok) { + const data = await response.json(); + console.log( + `📋 Schema updated for ${packageName}/${exportName}:`, + data.updated ? 'UPDATED' : 'NO CHANGE' + ); + } else { + const errorText = await response.text(); + console.warn(`⚠️ Failed to update schema: ${response.status}`, errorText); + } + } catch (err) { + // Non-blocking - just log + console.error('❌ Failed to update tool schema:', err); + } +} + /** * Sanitize JSON Schema to fix common issues * - Replaces invalid type "None" with "object" @@ -354,6 +393,13 @@ async function loadAndDescribe(req: Request): Promise { // Sanitize schema - fix common issues with invalid schemas const sanitizedSchema = sanitizeJsonSchema(rawJsonSchema); + // Update TPM.js database with the schema (async, non-blocking) + updateToolSchema(packageName, exportName, toolModule.description, sanitizedSchema).catch( + (err) => { + console.warn('⚠️ Failed to update schema in database:', err); + } + ); + return Response.json({ success: true, tool: { diff --git a/apps/web/src/app/api/tools/update-schema/route.ts b/apps/web/src/app/api/tools/update-schema/route.ts index 75c189a..4bfadac 100644 --- a/apps/web/src/app/api/tools/update-schema/route.ts +++ b/apps/web/src/app/api/tools/update-schema/route.ts @@ -8,21 +8,25 @@ export const dynamic = 'force-dynamic'; * POST /api/tools/update-schema * Update a tool's input schema (parameters field) * - * This is called when a tool is executed and we discover its actual schema - * from the executor's /load-and-describe endpoint. + * Called by the executor when it loads a tool and discovers its schema. + * Looks up tool by packageName + exportName (unique constraint). * * Body: - * - toolId: The tool's database ID + * - packageName: npm package name + * - exportName: exported function name * - inputSchema: The JSON Schema for the tool's input parameters * - description: Optional updated description from the tool */ export async function POST(request: NextRequest) { try { const body = await request.json(); - const { toolId, inputSchema, description } = body; + const { packageName, exportName, inputSchema, description } = body; - if (!toolId) { - return NextResponse.json({ success: false, error: 'toolId is required' }, { status: 400 }); + if (!packageName || !exportName) { + return NextResponse.json( + { success: false, error: 'packageName and exportName are required' }, + { status: 400 } + ); } if (!inputSchema) { @@ -32,12 +36,37 @@ export async function POST(request: NextRequest) { ); } - console.log('[Update Schema] Updating tool:', toolId); - console.log('[Update Schema] Schema properties:', Object.keys(inputSchema.properties || {})); + console.log('[Update Schema] Looking up tool:', { packageName, exportName }); + + // Find the tool by package name and export name + const tool = await prisma.tool.findFirst({ + where: { + exportName, + package: { + npmPackageName: packageName, + }, + }, + select: { + id: true, + parameters: true, + }, + }); + + if (!tool) { + console.log('[Update Schema] Tool not found:', { packageName, exportName }); + return NextResponse.json( + { success: false, error: 'Tool not found', updated: false }, + { status: 404 } + ); + } // Convert JSON Schema to our parameters format - // The parameters field stores an array of parameter objects - const parameters = []; + const parameters: Array<{ + name: string; + type: string; + required: boolean; + description: string; + }> = []; if (inputSchema.properties) { for (const [name, prop] of Object.entries(inputSchema.properties)) { const propDef = prop as { type?: string; description?: string }; @@ -50,18 +79,38 @@ export async function POST(request: NextRequest) { } } - // Update the tool in the database + // Check if parameters already match (avoid unnecessary updates) + const existingParams = tool.parameters as Array<{ name: string }> | null; + const existingParamNames = + existingParams + ?.map((p) => p.name) + .sort() + .join(',') || ''; + const newParamNames = parameters + .map((p) => p.name) + .sort() + .join(','); + + if (existingParamNames === newParamNames && parameters.length > 0) { + console.log('[Update Schema] Schema already up to date:', { packageName, exportName }); + return NextResponse.json({ + success: true, + updated: false, + message: 'Schema already up to date', + }); + } + + // Update the tool const updateData: { parameters: typeof parameters; description?: string } = { parameters, }; - // Only update description if provided and different if (description) { updateData.description = description; } const updatedTool = await prisma.tool.update({ - where: { id: toolId }, + where: { id: tool.id }, data: updateData, select: { id: true, @@ -75,20 +124,17 @@ export async function POST(request: NextRequest) { id: updatedTool.id, exportName: updatedTool.exportName, parameterCount: parameters.length, + parameterNames: parameters.map((p) => p.name), }); return NextResponse.json({ success: true, + updated: true, tool: updatedTool, }); } catch (error) { console.error('[Update Schema] Error:', error); - // Handle not found error - if (error instanceof Error && error.message.includes('Record to update not found')) { - return NextResponse.json({ success: false, error: 'Tool not found' }, { status: 404 }); - } - return NextResponse.json( { success: false,