tool-schema.ts
1 type JsonSchema = Record<string, any>; 2 3 function cloneValue<T>(value: T): T { 4 return JSON.parse(JSON.stringify(value)); 5 } 6 7 function unescapeJsonPointerSegment(segment: string): string { 8 return segment.replace(/~1/g, "/").replace(/~0/g, "~"); 9 } 10 11 function resolveLocalRef(root: unknown, ref: string): unknown { 12 if (!ref.startsWith("#/")) { 13 return undefined; 14 } 15 16 const segments = ref 17 .slice(2) 18 .split("/") 19 .map((segment) => unescapeJsonPointerSegment(segment)); 20 21 let current: any = root; 22 for (const segment of segments) { 23 if (current == null || typeof current !== "object" || !(segment in current)) { 24 return undefined; 25 } 26 current = current[segment]; 27 } 28 29 return current; 30 } 31 32 function sanitizeSchemaNode(node: unknown, root: unknown): unknown { 33 if (Array.isArray(node)) { 34 return node.map((item) => sanitizeSchemaNode(item, root)); 35 } 36 37 if (!node || typeof node !== "object") { 38 return node; 39 } 40 41 const schema = node as JsonSchema; 42 43 if (typeof schema.$ref === "string") { 44 const resolved = resolveLocalRef(root, schema.$ref); 45 if (resolved && typeof resolved === "object") { 46 const { $ref: _ignoredRef, ...siblings } = schema; 47 return sanitizeSchemaNode( 48 { 49 ...(cloneValue(resolved) as JsonSchema), 50 ...siblings, 51 }, 52 root 53 ); 54 } 55 } 56 57 const result: JsonSchema = {}; 58 for (const [key, value] of Object.entries(schema)) { 59 if (key === "$schema" || key === "$defs" || key === "definitions") { 60 continue; 61 } 62 result[key] = sanitizeSchemaNode(value, root); 63 } 64 65 return result; 66 } 67 68 export function sanitizeToolInputSchema(schema: JsonSchema): JsonSchema { 69 const cloned = cloneValue(schema); 70 const sanitized = sanitizeSchemaNode(cloned, cloned); 71 72 if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized)) { 73 return { 74 type: "object", 75 additionalProperties: false, 76 }; 77 } 78 79 const normalized = sanitized as JsonSchema; 80 if (normalized.type !== "object") { 81 normalized.type = "object"; 82 } 83 84 if (!("additionalProperties" in normalized)) { 85 normalized.additionalProperties = false; 86 } 87 88 return normalized; 89 } 90 91 export function createEmptyObjectSchema(): JsonSchema { 92 return { 93 type: "object", 94 properties: {}, 95 required: [], 96 additionalProperties: false, 97 }; 98 } 99 100 export function createPathArraySchema(description: string): JsonSchema { 101 return { 102 type: "array", 103 items: { 104 type: "string", 105 }, 106 minItems: 1, 107 description, 108 }; 109 }