From 89564af03058046836af171a76b2101e57738197 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Thu, 4 Dec 2025 18:02:10 +1000 Subject: [PATCH] fix: sanitize invalid JSON schemas with type 'None' to valid object schemas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add sanitizeJsonSchema() function to fix common schema issues: - Replaces invalid type 'None' (common in Python tools) with 'object' - Ensures all schemas have a valid type field - Recursively sanitizes nested schemas in properties, items, anyOf/oneOf/allOf - Prevents OpenAI API errors from malformed tool schemas This fixes the error with @superagent-ai/ai-sdk guard tool which returns type: 'None' instead of a valid JSON Schema type. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/railway-executor/server.ts | 69 ++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/apps/railway-executor/server.ts b/apps/railway-executor/server.ts index e4ab807..dabc676 100644 --- a/apps/railway-executor/server.ts +++ b/apps/railway-executor/server.ts @@ -10,6 +10,70 @@ import { zodToJsonSchema } from 'https://esm.sh/zod-to-json-schema@3.25.0'; // biome-ignore lint/suspicious/noExplicitAny: Tool types are dynamic and vary by package const moduleCache = new Map(); +/** + * Sanitize JSON Schema to fix common issues + * - Replaces invalid type "None" with "object" + * - Ensures type is always set + * - Ensures object schemas have properties + */ +// biome-ignore lint/suspicious/noExplicitAny: JSON Schema can have any structure +function sanitizeJsonSchema(schema: any): any { + if (!schema || typeof schema !== 'object') { + console.warn('⚠️ Invalid schema (not an object), returning default object schema'); + return { type: 'object', properties: {}, additionalProperties: false }; + } + + // Clone the schema to avoid mutating the original + const sanitized = { ...schema }; + + // Fix invalid type "None" (common in Python-based tools) + if (sanitized.type === 'None' || sanitized.type === 'none' || sanitized.type === null) { + console.warn(`⚠️ Invalid schema type "${sanitized.type}", replacing with "object"`); + sanitized.type = 'object'; + if (!sanitized.properties) { + sanitized.properties = {}; + } + if (sanitized.additionalProperties === undefined) { + sanitized.additionalProperties = false; + } + } + + // Ensure type is set + if (!sanitized.type) { + console.warn('⚠️ Schema missing type, defaulting to "object"'); + sanitized.type = 'object'; + if (!sanitized.properties) { + sanitized.properties = {}; + } + if (sanitized.additionalProperties === undefined) { + sanitized.additionalProperties = false; + } + } + + // Recursively sanitize nested schemas + if (sanitized.properties && typeof sanitized.properties === 'object') { + for (const [key, value] of Object.entries(sanitized.properties)) { + if (value && typeof value === 'object') { + sanitized.properties[key] = sanitizeJsonSchema(value); + } + } + } + + // Sanitize array items + if (sanitized.items && typeof sanitized.items === 'object') { + sanitized.items = sanitizeJsonSchema(sanitized.items); + } + + // Sanitize anyOf/oneOf/allOf + for (const key of ['anyOf', 'oneOf', 'allOf']) { + if (Array.isArray(sanitized[key])) { + sanitized[key] = sanitized[key].map((s: any) => sanitizeJsonSchema(s)); + } + } + + return sanitized; +} + /** * Load and describe a tool from esm.sh */ @@ -249,12 +313,15 @@ async function loadAndDescribe(req: Request): Promise { console.log(`✅ Extracted schema for ${cacheKey}`); + // Sanitize schema - fix common issues with invalid schemas + const sanitizedSchema = sanitizeJsonSchema(rawJsonSchema); + return Response.json({ success: true, tool: { exportName, description: toolModule.description, - inputSchema: rawJsonSchema, // Plain JSON Schema - fully serializable + inputSchema: sanitizedSchema, // Plain JSON Schema - fully serializable }, }); } catch (error) {