fix: harden Cloud Code Assist failover (#544) (thanks @jeffersonwarrior)
This commit is contained in:
@@ -75,6 +75,7 @@
|
|||||||
- Discord: stop provider when gateway reconnects are exhausted and surface errors. (#514) — thanks @joshp123
|
- Discord: stop provider when gateway reconnects are exhausted and surface errors. (#514) — thanks @joshp123
|
||||||
- Agents: strip empty assistant text blocks from session history to avoid Claude API 400s. (#210)
|
- Agents: strip empty assistant text blocks from session history to avoid Claude API 400s. (#210)
|
||||||
- Agents: scrub unsupported JSON Schema keywords from tool schemas for Cloud Code Assist API compatibility. (#567) — thanks @erikpr1994
|
- Agents: scrub unsupported JSON Schema keywords from tool schemas for Cloud Code Assist API compatibility. (#567) — thanks @erikpr1994
|
||||||
|
- Agents: sanitize Cloud Code Assist tool call IDs and detect format/quota errors for failover. (#544) — thanks @jeffersonwarrior
|
||||||
- Agents: simplify session tool schemas for Gemini compatibility. (#599) — thanks @mcinteerj
|
- Agents: simplify session tool schemas for Gemini compatibility. (#599) — thanks @mcinteerj
|
||||||
- Agents: add `session_status` agent tool for `/status`-equivalent status (incl. usage/cost) + per-session model overrides. — thanks @steipete
|
- Agents: add `session_status` agent tool for `/status`-equivalent status (incl. usage/cost) + per-session model overrides. — thanks @steipete
|
||||||
- Auto-reply: preserve block reply ordering with timeout fallback for streaming. (#503) — thanks @joshp123
|
- Auto-reply: preserve block reply ordering with timeout fallback for streaming. (#503) — thanks @joshp123
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import {
|
|||||||
classifyFailoverReason,
|
classifyFailoverReason,
|
||||||
formatAssistantErrorText,
|
formatAssistantErrorText,
|
||||||
isBillingErrorMessage,
|
isBillingErrorMessage,
|
||||||
|
isCloudCodeAssistFormatError,
|
||||||
isContextOverflowError,
|
isContextOverflowError,
|
||||||
isFailoverErrorMessage,
|
isFailoverErrorMessage,
|
||||||
isMessagingToolDuplicate,
|
isMessagingToolDuplicate,
|
||||||
normalizeTextForComparison,
|
normalizeTextForComparison,
|
||||||
sanitizeGoogleTurnOrdering,
|
sanitizeGoogleTurnOrdering,
|
||||||
sanitizeSessionMessagesImages,
|
sanitizeSessionMessagesImages,
|
||||||
|
sanitizeToolCallId,
|
||||||
validateGeminiTurns,
|
validateGeminiTurns,
|
||||||
} from "./pi-embedded-helpers.js";
|
} from "./pi-embedded-helpers.js";
|
||||||
import {
|
import {
|
||||||
@@ -258,12 +260,34 @@ describe("classifyFailoverReason", () => {
|
|||||||
it("returns a stable reason", () => {
|
it("returns a stable reason", () => {
|
||||||
expect(classifyFailoverReason("invalid api key")).toBe("auth");
|
expect(classifyFailoverReason("invalid api key")).toBe("auth");
|
||||||
expect(classifyFailoverReason("429 too many requests")).toBe("rate_limit");
|
expect(classifyFailoverReason("429 too many requests")).toBe("rate_limit");
|
||||||
|
expect(classifyFailoverReason("resource has been exhausted")).toBe(
|
||||||
|
"rate_limit",
|
||||||
|
);
|
||||||
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");
|
||||||
|
expect(classifyFailoverReason("string should match pattern")).toBeNull();
|
||||||
expect(classifyFailoverReason("bad request")).toBeNull();
|
expect(classifyFailoverReason("bad request")).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isCloudCodeAssistFormatError", () => {
|
||||||
|
it("matches format errors", () => {
|
||||||
|
const samples = [
|
||||||
|
"INVALID_REQUEST_ERROR: string should match pattern",
|
||||||
|
"messages.1.content.1.tool_use.id",
|
||||||
|
"tool_use.id should match pattern",
|
||||||
|
"invalid request format",
|
||||||
|
];
|
||||||
|
for (const sample of samples) {
|
||||||
|
expect(isCloudCodeAssistFormatError(sample)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores unrelated errors", () => {
|
||||||
|
expect(isCloudCodeAssistFormatError("rate limit exceeded")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("formatAssistantErrorText", () => {
|
describe("formatAssistantErrorText", () => {
|
||||||
const makeAssistantError = (errorMessage: string): AssistantMessage =>
|
const makeAssistantError = (errorMessage: string): AssistantMessage =>
|
||||||
({
|
({
|
||||||
@@ -277,6 +301,20 @@ describe("formatAssistantErrorText", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("sanitizeToolCallId", () => {
|
||||||
|
it("keeps valid tool call IDs", () => {
|
||||||
|
expect(sanitizeToolCallId("call_abc-123")).toBe("call_abc-123");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaces invalid characters with underscores", () => {
|
||||||
|
expect(sanitizeToolCallId("call_abc|item:456")).toBe("call_abc_item_456");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns default for empty IDs", () => {
|
||||||
|
expect(sanitizeToolCallId("")).toBe("default_tool_id");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("sanitizeGoogleTurnOrdering", () => {
|
describe("sanitizeGoogleTurnOrdering", () => {
|
||||||
it("prepends a synthetic user turn when history starts with assistant", () => {
|
it("prepends a synthetic user turn when history starts with assistant", () => {
|
||||||
const input = [
|
const input = [
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ import {
|
|||||||
|
|
||||||
// Optional features can be implemented as Pi extensions that run in the same Node process.
|
// Optional features can be implemented as Pi extensions that run in the same Node process.
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve provider-specific extraParams from model config.
|
* Resolve provider-specific extraParams from model config.
|
||||||
* Auto-enables thinking mode for GLM-4.x models unless explicitly disabled.
|
* Auto-enables thinking mode for GLM-4.x models unless explicitly disabled.
|
||||||
@@ -1530,8 +1529,7 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
|
|
||||||
// Treat timeout as potential rate limit (Antigravity hangs on rate limit)
|
// Treat timeout as potential rate limit (Antigravity hangs on rate limit)
|
||||||
const shouldRotate =
|
const shouldRotate =
|
||||||
(!aborted &&
|
(!aborted && (failoverFailure || cloudCodeAssistFormatError)) ||
|
||||||
(failoverFailure || cloudCodeAssistFormatError)) ||
|
|
||||||
timedOut;
|
timedOut;
|
||||||
|
|
||||||
if (shouldRotate) {
|
if (shouldRotate) {
|
||||||
|
|||||||
Reference in New Issue
Block a user