Skip to content

fix(ai): classify gateway account limits as quota and keep 4xx non-retryable - #49195

Merged
rekram1-node merged 1 commit into
v2from
zen-errors
Sep 16, 2026
Merged

rekram1-node merged 1 commit into
v2from
zen-errors

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • classify HTTP 402 as QuotaExceeded
  • add OpenCode Zen's typed account-limit codes (GoUsageLimitError, FreeUsageLimitError, CreditLimitExceeded) to QUOTA_CODES
  • extend the 429-only quota phrasing with budget exceeded and usage limit
  • stop letting SERVER_CODES / exhausted / unavailable codes classify a 4xx response as retryable ProviderInternal; they still decide when there is no HTTP status (stream error events) or the status is <400 (xAI JSON errors on HTTP 200)

Why

OpenCode Zen (inference-next) emits its own account and routing errors through errorResponse. Errors with a configured public type keep it; every other one is wrapped as {"error":{"type":"server_error",...}} (OpenAI skins) or api_error (Anthropic skin) regardless of status. Its adapters do the same substitution for upstream codes outside their allow-lists. Fed through classifyProviderFailure on v2:

Zen error Status Before Retried
GoUsageLimitError 429 RateLimit yes
FreeUsageLimitError (daily cap, retry-after up to 24h) 429 RateLimit yes
CreditLimitExceeded 402 InvalidRequest no
InsufficientFunds / invoice overdue as server_error 402 ProviderInternal yes
LimitExceeded "Account budget exceeded" as server_error 429 RateLimit yes
RegionUnavailable, ModelNotFound as server_error 400 ProviderInternal yes

All now classify as QuotaExceeded or InvalidRequest and are not retried. This mirrors the status-first rule the OpenAI and Anthropic SDKs already apply: a 4xx is a rejection of this request whatever label a gateway attached.

Behavior change worth reviewing

executor.test.ts previously asserted that {"code":"resource_exhausted"} on HTTP 400 classifies as ProviderInternal. That case was ported from V1 commit 91db82c138, where the xAI check lived in the branch for errors without an HTTP status; the 400 was the port's assumption, not observed provider behavior. The status-less and HTTP-200 shapes remain covered in provider-error.test.ts; the executor test now asserts the inverse. core/test/aisdk.test.ts had the same 400 + api_error pairing to prove data-only codes are read; it now uses rate_limit_error, which is honored regardless of status, so the assertion still depends on the code being read. Bedrock's genuine throttling-on-400 is in the rate-limit branch and is unchanged.

Verification

  • bun test in packages/ai: 1,392 pass / 28 skip; new tests cover each Zen shape above plus the no-status and HTTP-200 server-code cases
  • bun run check

@rekram1-node
rekram1-node merged commit 4a27842 into v2 Sep 16, 2026
8 checks passed
@rekram1-node
rekram1-node deleted the zen-errors branch September 16, 2026 21:14
jinhuang712 pushed a commit to jinhuang712/opencode that referenced this pull request Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant