Merge pull request #225 from multica-ai/fix/format-error-recovery
fix(agent): auto-recover from persistent 400 format errors
This commit is contained in:
commit
61bbf8fa6b
3 changed files with 50 additions and 8 deletions
|
|
@ -5,35 +5,43 @@
|
|||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./agent": {
|
||||
"types": "./dist/agent/index.d.ts",
|
||||
"import": "./dist/agent/index.js"
|
||||
"import": "./dist/agent/index.js",
|
||||
"default": "./dist/agent/index.js"
|
||||
},
|
||||
"./hub": {
|
||||
"types": "./dist/hub/index.d.ts",
|
||||
"import": "./dist/hub/index.js"
|
||||
"import": "./dist/hub/index.js",
|
||||
"default": "./dist/hub/index.js"
|
||||
},
|
||||
"./channels": {
|
||||
"types": "./dist/channels/index.d.ts",
|
||||
"import": "./dist/channels/index.js"
|
||||
"import": "./dist/channels/index.js",
|
||||
"default": "./dist/channels/index.js"
|
||||
},
|
||||
"./cron": {
|
||||
"types": "./dist/cron/index.d.ts",
|
||||
"import": "./dist/cron/index.js"
|
||||
"import": "./dist/cron/index.js",
|
||||
"default": "./dist/cron/index.js"
|
||||
},
|
||||
"./heartbeat": {
|
||||
"types": "./dist/heartbeat/index.d.ts",
|
||||
"import": "./dist/heartbeat/index.js"
|
||||
"import": "./dist/heartbeat/index.js",
|
||||
"default": "./dist/heartbeat/index.js"
|
||||
},
|
||||
"./media": {
|
||||
"types": "./dist/media/index.d.ts",
|
||||
"import": "./dist/media/index.js"
|
||||
"import": "./dist/media/index.js",
|
||||
"default": "./dist/media/index.js"
|
||||
},
|
||||
"./client": {
|
||||
"types": "./dist/client/index.d.ts",
|
||||
"import": "./dist/client/index.js"
|
||||
"import": "./dist/client/index.js",
|
||||
"default": "./dist/client/index.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ describe("classifyError", () => {
|
|||
expect(classifyError(new Error("Schema validation failed"))).toBe("format");
|
||||
});
|
||||
|
||||
it("classifies tool_call_id 400 errors as format (recoverable via transcript repair)", () => {
|
||||
expect(
|
||||
classifyError(new Error("400 tool_call_id is not found in the list of tool calls")),
|
||||
).toBe("format");
|
||||
expect(classifyError(new Error("400 Bad Request: tool_call_id not found"))).toBe("format");
|
||||
});
|
||||
|
||||
it("classifies 429/rate limit as rate_limit", () => {
|
||||
expect(classifyError(new Error("429 Too Many Requests"))).toBe("rate_limit");
|
||||
expect(classifyError(new Error("Rate limit exceeded"))).toBe("rate_limit");
|
||||
|
|
|
|||
|
|
@ -794,6 +794,8 @@ export class Agent {
|
|||
|
||||
const MAX_OVERFLOW_COMPACTION_ATTEMPTS = 2;
|
||||
let overflowAttempts = 0;
|
||||
const MAX_FORMAT_REPAIR_ATTEMPTS = 1;
|
||||
let formatRepairAttempts = 0;
|
||||
|
||||
// Loop to exhaust all candidate profiles on rotatable errors
|
||||
while (true) {
|
||||
|
|
@ -860,6 +862,31 @@ export class Agent {
|
|||
reason,
|
||||
rotatable: isRotatableError(reason),
|
||||
});
|
||||
|
||||
// Format error recovery: reload sanitized messages from disk and retry.
|
||||
// This handles corrupted in-memory state (e.g. orphaned tool_call_id references)
|
||||
// that causes persistent 400 errors until process restart.
|
||||
if (reason === "format" && formatRepairAttempts < MAX_FORMAT_REPAIR_ATTEMPTS) {
|
||||
formatRepairAttempts++;
|
||||
this.stderr.write(
|
||||
`[format-repair] Format error detected (attempt ${formatRepairAttempts}/${MAX_FORMAT_REPAIR_ATTEMPTS}), reloading messages from disk...\n`,
|
||||
);
|
||||
this.runLog.log("format_repair", {
|
||||
attempt: formatRepairAttempts,
|
||||
error: errorMsg.slice(0, 200),
|
||||
messages_before: this.agent.state.messages.length,
|
||||
});
|
||||
const repairedMessages = this.session.loadMessages();
|
||||
if (repairedMessages.length > 0) {
|
||||
this.runLog.log("format_repair_reloaded", {
|
||||
messages_after: repairedMessages.length,
|
||||
});
|
||||
this.agent.replaceMessages(repairedMessages);
|
||||
this.output.state.lastAssistantText = "";
|
||||
continue; // retry with sanitized messages
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentProfileId && isRotatableError(reason)) {
|
||||
markAuthProfileFailure(this.currentProfileId, reason);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue