/ src / utils / tool-schema.ts
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  }