zed/crates/language_models_cloud
Conrad Irwin a73ab4f2a3 Unify provider rejections behind a shared error category
`LanguageModelCompletionError` had eight near-duplicate variants
(PromptTooLarge, RateLimitExceeded, ServerOverloaded, InvalidEncryptedContent,
AuthenticationError, PermissionError, ApiEndpointNotFound, PaymentRequired)
that each independently encoded a provider-semantic outcome, alongside
ProviderRejection for everything else. Providers had to choose between two
places to report the same kind of failure, and retry logic had to special-case
each variant even though the underlying decision (retry or not, how long)
depended only on the HTTP status.

This collapses all of those into ProviderRejection, adding a `category:
ProviderErrorCategory` field that's derived once, centrally, in
`ProviderErrorCategory::classify` (status + code + message in, category out).
Provider-specific wire parsing (Anthropic's ApiErrorCode, OpenRouter's
ApiErrorCode) stays local, but now maps into this shared category instead of
duplicating the same classification. `status`, `code`, `message`, and
`retry_after` are still preserved verbatim, including for known categories, so
nothing that inspected the raw wire details loses information.

While touching OpenRouter's error path, fixed a real bug: `ApiErrorCode::
from_status` defaulted any unmapped HTTP status to `ApiError` (502), silently
replacing e.g. a real 500 with a fabricated 502. `ApiError` now carries the
real status separately from the (optional) documented code, so unmapped
statuses are preserved and classified as `Other` instead of being coerced into
a wrong, documented one.

Restored the retry-strategy enum in `agent::Thread` (a prior commit on this
branch had flattened it into a single-shape struct, losing the distinction
between "wait a fixed amount" and "back off exponentially"). ProviderRejection
now retries only for the same status set Delta already uses (408, 425, 429,
and all 5xx including the unofficial 529): a provider-given `retry_after`
becomes a fixed delay, otherwise attempts back off exponentially from
BASE_RETRY_DELAY. A rejection with no status (e.g. a content-policy code like
`cyber_policy`) or a non-retryable status never retries, since resending the
same request would just repeat the same rejection.

Testing:
- `cargo nextest run` across every touched crate (agent, agent_ui, anthropic,
  open_ai, open_router, copilot_chat, language_model_core, language_models,
  language_models_cloud): 1430 passed, 11 skipped (network-gated unit evals).
- `cargo check --workspace --all-targets`: clean.
- `cargo fmt --check`: clean.
- Added unit tests for the new retry rule (status-less and non-retryable
  rejections don't retry; retryable statuses use exponential backoff without
  `retry_after` and a fixed delay with it) and for OpenRouter preserving an
  unmapped status instead of fabricating 502.

Environment note: `corgi` cannot build anything depending on `gpui` from this
worktree (it rejects gpui's build output for embedding an absolute path); used
plain `cargo`/`cargo nextest` for those crates instead, logged in
~/.corgi-feedback per instructions.
2026-08-23 14:18:21 -06:00
..
src Unify provider rejections behind a shared error category 2026-08-23 14:18:21 -06:00
Cargo.toml Add explicit OpenAI conversation compaction and fix Anthropic compaction (#61370) 2026-07-24 16:53:45 +00:00
LICENSE-GPL language_models: Refactor deps and extract cloud (#53270) 2026-04-07 12:28:19 -03:00