kernel/error/provider.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What kind of provider failure an `E_PROVIDER` error reports, and
7//! whether the account that met a failure can still take the request.
8//! The part `crates/kernel/spec/Error.lean` specifies this module with the
9//! rest of `kernel::error`.
10
11use serde::{Deserialize, Serialize};
12
13/// The kind of one provider failure, as the call site that saw it named
14/// it. It travels on the wire so a page can say it in the reader's own
15/// language; the city's recovery sentence stays beside it for the fold.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(tag = "kind", rename_all = "snake_case")]
18#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
19pub enum ProviderFailureKind {
20 /// The exchange never completed: the request did not reach the
21 /// provider, the answer stopped before its end, or the transport's
22 /// deadline passed.
23 Exchange,
24 /// A streamed body could not be read further off the socket.
25 Cut,
26 /// A stream stayed open and silent past the bound set for silence.
27 Silence,
28 /// The provider answered, and its status refuses the request. Held
29 /// as `u32` because the wire's schema subset has no upper bound to
30 /// spell a `u16` with.
31 Refused { status: u32 },
32 /// The provider refused the request because this account's quota is
33 /// used up (gateway D32). Asked again it answers the same; another
34 /// account, or credit added to this one, is the way out.
35 Quota { status: u32 },
36 /// The provider refused the request because it no longer fits the
37 /// model's context window.
38 Overflow { status: u32 },
39 /// The provider answered 2xx with a body this city cannot read.
40 Unreadable,
41 /// A stream that already answered 2xx carried an error frame. The
42 /// provider's type for it stays in the error's subject.
43 Reported,
44 /// The request could not be built on this side.
45 Unbuilt,
46}
47
48/// Whether the account a request went out on can still take it
49/// (`crates/kernel/spec/Error.lean` D53).
50///
51/// A second answer beside `Retry` rather than a reading of it:
52/// a rejected key and a request the provider calls malformed are both
53/// `Retry::No`, and only the first is mended by another account. On the
54/// wire `"keep"` or `"advance"`; `Keep` is left out, so a record written
55/// before this field existed reads as `Keep` and keeps its bytes.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
57#[serde(rename_all = "lowercase")]
58#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
59pub enum AccountDisposition {
60 /// This failure says nothing about the account: whether the request
61 /// goes out again is `retry`'s answer alone.
62 #[default]
63 Keep,
64 /// This account cannot take the request - its key was rejected, its
65 /// quota is used up, or its credential is missing - and the next
66 /// account may.
67 Advance,
68}
69
70impl AccountDisposition {
71 /// The serde skip test: `Keep` is the absence of the field.
72 pub(crate) fn is_keep(&self) -> bool {
73 *self == AccountDisposition::Keep
74 }
75}