From 128e5bc317c963e73734731f3c851efb11f85a8b Mon Sep 17 00:00:00 2001 From: Squabble9 Date: Thu, 12 Mar 2026 02:15:32 +0700 Subject: [PATCH] fix: recognize Venice 402 billing errors for model fallback (#43205) Merged via squash. Prepared head SHA: 1f6b10b9d934235e71f279f888292139c4a85aa6 Co-authored-by: Squabble9 <194720422+Squabble9@users.noreply.github.com> Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com> Reviewed-by: @altaywtf --- CHANGELOG.md | 1 + ...-embedded-helpers.isbillingerrormessage.test.ts | 14 ++++++++++++++ src/agents/pi-embedded-helpers/failover-matches.ts | 1 + 3 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eba08595a4..ca28f0c9041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ Docs: https://docs.openclaw.ai - Agents/cooldowns: default cooldown windows with no recorded failure history to `unknown` instead of `rate_limit`, avoiding false API rate-limit warnings while preserving cooldown recovery probes. (#42911) Thanks @VibhorGautam. - Discord/config typing: expose channel-level `autoThread` on the canonical guild-channel config type so strict config loading matches the existing Discord schema and runtime behavior. (#35608) Thanks @ingyukoh. - Agents/error rendering: ignore stale assistant `errorMessage` fields on successful turns so background/tool-side failures no longer prepend synthetic billing errors over valid replies. (#40616) Thanks @ingyukoh. +- Agents/fallback: recognize Venice `402 Insufficient USD or Diem balance` billing errors so configured model fallbacks trigger instead of surfacing the raw provider error. (#43205) Thanks @Squabble9. ## 2026.3.8 diff --git a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts index 0430bd3814b..27c89afe425 100644 --- a/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts +++ b/src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts @@ -106,6 +106,9 @@ describe("isBillingErrorMessage", () => { "Payment Required", "HTTP 402 Payment Required", "plans & billing", + // Venice returns "Insufficient USD or Diem balance" which has extra words + // between "insufficient" and "balance" + "Insufficient USD or Diem balance to complete request. Visit https://venice.ai/settings/api to add credits.", ]; for (const sample of samples) { expect(isBillingErrorMessage(sample)).toBe(true); @@ -149,6 +152,11 @@ describe("isBillingErrorMessage", () => { expect(longResponse.length).toBeGreaterThan(512); expect(isBillingErrorMessage(longResponse)).toBe(false); }); + it("does not false-positive on short non-billing text that mentions insufficient and balance", () => { + const sample = "The evidence is insufficient to reconcile the final balance after compaction."; + expect(isBillingErrorMessage(sample)).toBe(false); + expect(classifyFailoverReason(sample)).toBeNull(); + }); it("still matches explicit 402 markers in long payloads", () => { const longStructuredError = '{"error":{"code":402,"message":"payment required","details":"' + "x".repeat(700) + '"}}'; @@ -650,6 +658,12 @@ describe("classifyFailoverReason", () => { expect(classifyFailoverReason(TOGETHER_ENGINE_OVERLOADED_MESSAGE)).toBe("overloaded"); expect(classifyFailoverReason(GROQ_TOO_MANY_REQUESTS_MESSAGE)).toBe("rate_limit"); expect(classifyFailoverReason(GROQ_SERVICE_UNAVAILABLE_MESSAGE)).toBe("overloaded"); + // Venice 402 billing error with extra words between "insufficient" and "balance" + expect( + classifyFailoverReason( + "Insufficient USD or Diem balance to complete request. Visit https://venice.ai/settings/api to add credits.", + ), + ).toBe("billing"); }); it("classifies internal and compatibility error messages", () => { diff --git a/src/agents/pi-embedded-helpers/failover-matches.ts b/src/agents/pi-embedded-helpers/failover-matches.ts index a7948703f39..a9f16fa6202 100644 --- a/src/agents/pi-embedded-helpers/failover-matches.ts +++ b/src/agents/pi-embedded-helpers/failover-matches.ts @@ -52,6 +52,7 @@ const ERROR_PATTERNS = { "credit balance", "plans & billing", "insufficient balance", + "insufficient usd or diem balance", ], authPermanent: [ /api[_ ]?key[_ ]?(?:revoked|invalid|deactivated|deleted)/i,