utils.ts•5.67 kB
import z, { type ZodTypeAny } from "zod";
type JsonSchema = any;
function jsonSchemaPropToZod(schema: JsonSchema): ZodTypeAny {
    if (!schema) return z.any();
    if (Array.isArray(schema.enum)) {
        const literals = schema.enum.map((v: any) => z.literal(v));
        return literals.length === 1 ? literals[0] : z.union(literals as any);
    }
    if (schema.const !== undefined) {
        return z.literal(schema.const);
    }
    switch (schema.type) {
        case "string": {
            let s = z.string();
            if (typeof schema.minLength === "number") s = s.min(schema.minLength);
            if (typeof schema.maxLength === "number") s = s.max(schema.maxLength);
            if (typeof schema.pattern === "string") {
                try {
                    const rx = new RegExp(schema.pattern);
                    s = s.regex(rx);
                } catch {
                    // ignore invalid regex
                }
            }
            return s;
        }
        case "number": {
            let n = z.number();
            if (typeof schema.minimum === "number") n = n.min(schema.minimum);
            if (typeof schema.maximum === "number") n = n.max(schema.maximum);
            return n;
        }
        case "integer": {
            let n = z.number().int();
            if (typeof schema.minimum === "number") n = n.min(schema.minimum);
            if (typeof schema.maximum === "number") n = n.max(schema.maximum);
            return n;
        }
        case "boolean":
            return z.boolean();
        case "array": {
            const itemSchema = schema.items ? jsonSchemaPropToZod(schema.items) : z.any();
            let arr = z.array(itemSchema);
            if (typeof schema.minItems === "number") arr = arr.min(schema.minItems);
            if (typeof schema.maxItems === "number") arr = arr.max(schema.maxItems);
            return arr;
        }
        case "object": {
            const props = schema.properties ?? {};
            const required: string[] = Array.isArray(schema.required) ? schema.required : [];
            const shape: Record<string, ZodTypeAny> = {};
            for (const [k, v] of Object.entries(props)) {
                const child = jsonSchemaPropToZod(v);
                shape[k] = required.includes(k) ? child : child.optional();
            }
            let obj = z.object(shape).passthrough();
            if (schema.additionalProperties === true) {
                obj = obj.extend({});
            } else if (typeof schema.additionalProperties === "object") {
                obj = obj.passthrough();
            } else {
                obj = obj.passthrough();
            }
            return obj;
        }
        default:
            if (schema.properties) {
                return jsonSchemaPropToZod({ type: "object", ...schema });
            }
            return z.any();
    }
}
export function jsonSchemaToZodRoot(schema: JsonSchema): ZodTypeAny {
    if (!schema) return z.object({});
    if (typeof (schema as any)?._parse === "function" || typeof (schema as any)?.parse === "function") {
        return schema;
    }
    if (schema.type === "object" || schema.properties) {
        const props = schema.properties ?? {};
        const required: string[] = Array.isArray(schema.required) ? schema.required : [];
        const shape: Record<string, ZodTypeAny> = {};
        for (const [key, propSchema] of Object.entries(props)) {
            if (
                propSchema &&
                typeof propSchema === 'object' &&
                'type' in propSchema &&
                'additionalProperties' in propSchema &&
                'properties' in propSchema &&
                propSchema.type === "object" &&
                propSchema.additionalProperties === true &&
                (!propSchema.properties || Object.keys(propSchema.properties).length === 0)
            ) {
                shape[key] = z.record(z.any());
                if (!required.includes(key)) shape[key] = shape[key].optional();
                continue;
            }
            const propZod = jsonSchemaPropToZod(propSchema);
            shape[key] = required.includes(key) ? propZod : propZod.optional();
        }
        let root = z.object(shape);
        if (schema.additionalProperties === true) {
            root = root.strip();
        } else {
            root = root.strip();
        }
        return root;
    }
    return z.object({});
}
export function buildToolZodMap(listOfTools: Array<any>): Map<string, ZodTypeAny> {
    const m = new Map<string, ZodTypeAny>();
    for (const t of listOfTools) {
        try {
            const raw = t?.inputSchema ?? t;
            const zodSchema = jsonSchemaToZodRoot(raw);
            const finalSchema = zodSchema._def?.typeName?.startsWith?.("ZodObject") ? zodSchema : z.object({});
            m.set(t.name, finalSchema);
        } catch (e) {
            m.set(t.name, z.object({}));
        }
    }
    return m;
}
export function parseToolInput(toolSchemaMap: Map<string, ZodTypeAny>, toolName: string, input: any) {
    const schema = toolSchemaMap.get(toolName);
    if (!schema) {
        return {};
    }
    try {
        const parsed = schema.parse(input ?? {});
        return parsed;
    } catch (err) {
        if (err instanceof z.ZodError) {
            const details = err.errors.map(e => {
                const path = e.path.length ? e.path.join(".") : "<root>";
                return `${path}: ${e.message}`;
            }).join("; ");
            throw new Error(`Input validation failed for tool "${toolName}": ${details}`);
        }
        throw err;
    }
}