diff --git a/src/agents/pi-embedded-helpers.test.ts b/src/agents/pi-embedded-helpers.test.ts new file mode 100644 index 000000000..a709351e6 --- /dev/null +++ b/src/agents/pi-embedded-helpers.test.ts @@ -0,0 +1,32 @@ +import type { AssistantMessage } from "@mariozechner/pi-ai"; +import { describe, expect, it } from "vitest"; + +import { isRateLimitAssistantError } from "./pi-embedded-helpers.js"; + +const asAssistant = (overrides: Partial) => + ({ role: "assistant", stopReason: "error", ...overrides }) as AssistantMessage; + +describe("isRateLimitAssistantError", () => { + it("detects 429 rate limit payloads", () => { + const msg = asAssistant({ + errorMessage: + '429 {"type":"error","error":{"type":"rate_limit_error","message":"This request would exceed your account\'s rate limit. Please try again later."}}', + }); + expect(isRateLimitAssistantError(msg)).toBe(true); + }); + + it("detects human-readable rate limit messages", () => { + const msg = asAssistant({ + errorMessage: "Too many requests. Rate limit exceeded.", + }); + expect(isRateLimitAssistantError(msg)).toBe(true); + }); + + it("returns false for non-error messages", () => { + const msg = asAssistant({ + stopReason: "end_turn", + errorMessage: "rate limit", + }); + expect(isRateLimitAssistantError(msg)).toBe(false); + }); +}); diff --git a/src/agents/pi-embedded-helpers.ts b/src/agents/pi-embedded-helpers.ts index 8d2debc70..e4d598463 100644 --- a/src/agents/pi-embedded-helpers.ts +++ b/src/agents/pi-embedded-helpers.ts @@ -109,3 +109,12 @@ export function formatAssistantErrorText( // Keep it short for WhatsApp. return raw.length > 600 ? `${raw.slice(0, 600)}…` : raw; } + +export function isRateLimitAssistantError( + msg: AssistantMessage | undefined, +): boolean { + if (!msg || msg.stopReason !== "error") return false; + const raw = (msg.errorMessage ?? "").toLowerCase(); + if (!raw) return false; + return /rate[_ ]limit|too many requests|429/.test(raw); +} diff --git a/src/agents/pi-embedded-runner.ts b/src/agents/pi-embedded-runner.ts index 18c29d6e7..b20c9d83f 100644 --- a/src/agents/pi-embedded-runner.ts +++ b/src/agents/pi-embedded-runner.ts @@ -32,6 +32,7 @@ import { buildBootstrapContextFiles, ensureSessionHeader, formatAssistantErrorText, + isRateLimitAssistantError, sanitizeSessionMessagesImages, } from "./pi-embedded-helpers.js"; import { @@ -551,6 +552,16 @@ export async function runEmbeddedPiAgent(params: { | AssistantMessage | undefined; + const fallbackConfigured = + (params.config?.agent?.modelFallbacks?.length ?? 0) > 0; + if (fallbackConfigured && isRateLimitAssistantError(lastAssistant)) { + const message = + lastAssistant?.errorMessage?.trim() || + (lastAssistant ? formatAssistantErrorText(lastAssistant) : "") || + "LLM request rate limited."; + throw new Error(message); + } + const usage = lastAssistant?.usage; const agentMeta: EmbeddedPiAgentMeta = { sessionId: sessionIdUsed,