File size: 3,573 Bytes
077865a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Schema-aware repair of double-encoded tool-call arguments.
//
// Several free-tier models (GLM family prominently) emit NESTED JSON inside
// tool arguments as a string: `{"plan": "[{\"step\":...}]"}` instead of
// `{"plan": [{"step":...}]}`. Strict clients reject the call — observed in
// production as Codex `failed to parse function arguments: invalid type:
// string ..., expected a sequence`, which killed the agent turn right at its
// status-update call. The gateway has the request's tool schemas, so it can
// repair this principled-ly: only when the schema says a parameter is an
// array/object AND the string value parses to exactly that JSON type. A
// parameter whose schema says "string" is never touched, even if it looks
// like JSON.
//
// Also handles whole-arguments double encoding (the arguments field itself
// being a JSON-encoded string of a JSON object), which needs no schema.
/**
 * Repair a tool call's `arguments` JSON string against the tool's parameter
 * schema. Returns the original string untouched whenever anything doesn't
 * parse or doesn't match — this must never corrupt a valid call.
 */
export function repairToolArguments(args, paramSchema) {
    let parsed;
    try {
        parsed = JSON.parse(args);
    }
    catch {
        return args;
    }
    let changed = false;
    // Whole-arguments double encoding: `"{\"a\":1}"` parses to a string that is
    // itself JSON of an object. Unwrap one level.
    if (typeof parsed === 'string') {
        try {
            const inner = JSON.parse(parsed);
            if (inner !== null && typeof inner === 'object' && !Array.isArray(inner)) {
                parsed = inner;
                changed = true;
            }
            else {
                return args;
            }
        }
        catch {
            return args;
        }
    }
    if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
        return changed ? JSON.stringify(parsed) : args;
    }
    const props = paramSchema?.properties;
    if (props) {
        const obj = parsed;
        for (const [key, value] of Object.entries(obj)) {
            if (typeof value !== 'string')
                continue;
            const want = props[key]?.type;
            if (want !== 'array' && want !== 'object')
                continue;
            const trimmed = value.trim();
            if (!(trimmed.startsWith('[') || trimmed.startsWith('{')))
                continue;
            try {
                const inner = JSON.parse(trimmed);
                const isMatch = want === 'array'
                    ? Array.isArray(inner)
                    : inner !== null && typeof inner === 'object' && !Array.isArray(inner);
                if (isMatch) {
                    obj[key] = inner;
                    changed = true;
                }
            }
            catch {
                // Not actually JSON — leave the string alone.
            }
        }
    }
    return changed ? JSON.stringify(parsed) : args;
}
/**
 * Build a tool-name → parameter-schema map from an OpenAI-style tools array
 * (chat-completions shape: {type:'function', function:{name, parameters}}).
 */
export function toolSchemaMap(tools) {
    const map = new Map();
    for (const t of tools ?? []) {
        const name = t.function?.name;
        if (t.type === 'function' && name && t.function?.parameters && typeof t.function.parameters === 'object') {
            map.set(name, t.function.parameters);
        }
    }
    return map;
}
//# sourceMappingURL=tool-args.js.map