Merge pull request #832 from danielz1z/fix/overloaded-error-handling
fix: handle Anthropic overloaded_error gracefully
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
- Agents: auto-recover from compaction context overflow by resetting the session and retrying; propagate overflow details from embedded runs so callers can recover.
|
- Agents: auto-recover from compaction context overflow by resetting the session and retrying; propagate overflow details from embedded runs so callers can recover.
|
||||||
- MiniMax: strip malformed tool invocation XML; include `MiniMax-VL-01` in implicit provider for image pairing.
|
- MiniMax: strip malformed tool invocation XML; include `MiniMax-VL-01` in implicit provider for image pairing.
|
||||||
- Onboarding/Auth: honor `CLAWDBOT_AGENT_DIR` / `PI_CODING_AGENT_DIR` when writing auth profiles (MiniMax). (#829) — thanks @roshanasingh4.
|
- Onboarding/Auth: honor `CLAWDBOT_AGENT_DIR` / `PI_CODING_AGENT_DIR` when writing auth profiles (MiniMax). (#829) — thanks @roshanasingh4.
|
||||||
|
- Anthropic: handle `overloaded_error` with a friendly message and failover classification. (#832) — thanks @danielz1z.
|
||||||
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid incorrect role errors.
|
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid incorrect role errors.
|
||||||
- Messaging: enforce context isolation for message tool sends; keep typing indicators alive during tool execution.
|
- Messaging: enforce context isolation for message tool sends; keep typing indicators alive during tool execution.
|
||||||
- Auto-reply: `/status` allowlist behavior, reasoning-tag enforcement on fallback, and system-event enqueueing for elevated/reasoning toggles.
|
- Auto-reply: `/status` allowlist behavior, reasoning-tag enforcement on fallback, and system-event enqueueing for elevated/reasoning toggles.
|
||||||
|
|||||||
@@ -209,6 +209,11 @@ describe("classifyFailoverReason", () => {
|
|||||||
expect(classifyFailoverReason("resource has been exhausted")).toBe(
|
expect(classifyFailoverReason("resource has been exhausted")).toBe(
|
||||||
"rate_limit",
|
"rate_limit",
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
classifyFailoverReason(
|
||||||
|
'{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}',
|
||||||
|
),
|
||||||
|
).toBe("rate_limit");
|
||||||
expect(classifyFailoverReason("invalid request format")).toBe("format");
|
expect(classifyFailoverReason("invalid request format")).toBe("format");
|
||||||
expect(classifyFailoverReason("credit balance too low")).toBe("billing");
|
expect(classifyFailoverReason("credit balance too low")).toBe("billing");
|
||||||
expect(classifyFailoverReason("deadline exceeded")).toBe("timeout");
|
expect(classifyFailoverReason("deadline exceeded")).toBe("timeout");
|
||||||
@@ -265,6 +270,15 @@ describe("formatAssistantErrorText", () => {
|
|||||||
"Message ordering conflict",
|
"Message ordering conflict",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns a friendly message for Anthropic overload errors", () => {
|
||||||
|
const msg = makeAssistantError(
|
||||||
|
'{"type":"error","error":{"details":null,"type":"overloaded_error","message":"Overloaded"},"request_id":"req_123"}',
|
||||||
|
);
|
||||||
|
expect(formatAssistantErrorText(msg)).toBe(
|
||||||
|
"The AI service is temporarily overloaded. Please try again in a moment.",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("sanitizeToolCallId", () => {
|
describe("sanitizeToolCallId", () => {
|
||||||
|
|||||||
@@ -392,6 +392,11 @@ export function formatAssistantErrorText(
|
|||||||
return `LLM request rejected: ${invalidRequest[1]}`;
|
return `LLM request rejected: ${invalidRequest[1]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for overloaded errors (Anthropic API capacity)
|
||||||
|
if (isOverloadedErrorMessage(raw)) {
|
||||||
|
return "The AI service is temporarily overloaded. Please try again in a moment.";
|
||||||
|
}
|
||||||
|
|
||||||
// Keep it short for WhatsApp.
|
// Keep it short for WhatsApp.
|
||||||
return raw.length > 600 ? `${raw.slice(0, 600)}…` : raw;
|
return raw.length > 600 ? `${raw.slice(0, 600)}…` : raw;
|
||||||
}
|
}
|
||||||
@@ -414,6 +419,10 @@ const ERROR_PATTERNS = {
|
|||||||
"resource_exhausted",
|
"resource_exhausted",
|
||||||
"usage limit",
|
"usage limit",
|
||||||
],
|
],
|
||||||
|
overloaded: [
|
||||||
|
/overloaded_error|"type"\s*:\s*"overloaded_error"/i,
|
||||||
|
"overloaded",
|
||||||
|
],
|
||||||
timeout: [
|
timeout: [
|
||||||
"timeout",
|
"timeout",
|
||||||
"timed out",
|
"timed out",
|
||||||
@@ -496,6 +505,10 @@ export function isAuthErrorMessage(raw: string): boolean {
|
|||||||
return matchesErrorPatterns(raw, ERROR_PATTERNS.auth);
|
return matchesErrorPatterns(raw, ERROR_PATTERNS.auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isOverloadedErrorMessage(raw: string): boolean {
|
||||||
|
return matchesErrorPatterns(raw, ERROR_PATTERNS.overloaded);
|
||||||
|
}
|
||||||
|
|
||||||
export function isCloudCodeAssistFormatError(raw: string): boolean {
|
export function isCloudCodeAssistFormatError(raw: string): boolean {
|
||||||
return matchesErrorPatterns(raw, ERROR_PATTERNS.format);
|
return matchesErrorPatterns(raw, ERROR_PATTERNS.format);
|
||||||
}
|
}
|
||||||
@@ -517,6 +530,7 @@ export type FailoverReason =
|
|||||||
|
|
||||||
export function classifyFailoverReason(raw: string): FailoverReason | null {
|
export function classifyFailoverReason(raw: string): FailoverReason | null {
|
||||||
if (isRateLimitErrorMessage(raw)) return "rate_limit";
|
if (isRateLimitErrorMessage(raw)) return "rate_limit";
|
||||||
|
if (isOverloadedErrorMessage(raw)) return "rate_limit"; // Treat overloaded as rate limit for failover
|
||||||
if (isCloudCodeAssistFormatError(raw)) return "format";
|
if (isCloudCodeAssistFormatError(raw)) return "format";
|
||||||
if (isBillingErrorMessage(raw)) return "billing";
|
if (isBillingErrorMessage(raw)) return "billing";
|
||||||
if (isTimeoutErrorMessage(raw)) return "timeout";
|
if (isTimeoutErrorMessage(raw)) return "timeout";
|
||||||
|
|||||||
@@ -1456,7 +1456,7 @@ describe("trigger handling", () => {
|
|||||||
|
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||||
expect(text).toBe(
|
expect(text).toBe(
|
||||||
"⚠️ Context overflow - conversation too long. Starting fresh might help!",
|
"⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model.",
|
||||||
);
|
);
|
||||||
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
expect(runEmbeddedPiAgent).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user