FreeLLMAPI / server /dist /lib /tool-args.js
Nryn215's picture
Upload folder using huggingface_hub
077865a verified
Raw
History Blame Contribute Delete
3.57 kB
// 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