返回 CodeWhale
candidate.rs
根目录 / crates / config / src / route / candidate.rs
1 //! The runtime-resolved executable route (#3384).
2 //!
3 //! A [`ReadyRouteCandidate`] is the concrete form of the #2608 contract:
4 //!
5 //! > Execution requires a `ReadyRouteCandidate`.
6 //! > A `ReadyRouteCandidate` can only be produced by `RouteResolver`.
7 //!
8 //! Fields are private and exposed only through read-only getters, so the type
9 //! can neither be *constructed* nor *mutated* outside this crate, and it
10 //! deliberately does not derive `Deserialize` (so it cannot be fabricated from
11 //! JSON either). The only constructor is `ReadyRouteCandidate::new`
12 //! (`pub(super)`), and [`super::resolver::RouteResolver::resolve`] is its sole
13 //! caller. A candidate's existence is therefore proof it passed the resolver,
14 //! and a candidate's limits are therefore exactly what the resolver produced
15 //! (including any [`SourcedLimitOverride`]s recorded on it).
16 //!
17 //! The route-owned, three-state capability profile lives in this crate and is
18 //! carried on the candidate. A full `config_snapshot: Config` remains deferred
19 //! because embedding it would couple the candidate to the full config model.
20
21 use serde::{Deserialize, Serialize};
22
23 use super::RequestProtocol;
24 use super::capabilities::RouteCapabilities;
25 use super::ids::{LogicalModelRef, ModelId, ProviderId, WireModelId};
26 use super::offering::RouteLimits;
27 use crate::ProviderKind;
28
29 /// A concrete, resolved endpoint the route will talk to.
30 #[derive(Debug, Clone, Serialize, Deserialize)]
31 pub struct ResolvedEndpoint {
32 /// Resolved base URL (after any override).
33 pub base_url: String,
34 /// Endpoint key (e.g. `"chat"`, `"responses"`).
35 pub endpoint_key: String,
36 /// Wire protocol spoken at this endpoint.
37 pub protocol: RequestProtocol,
38 }
39
40 /// The CLASS of auth source resolved for the route.
41 ///
42 /// This records only *where* a credential comes from, never the credential
43 /// value itself. There is intentionally no field that could hold a secret.
44 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45 #[serde(rename_all = "snake_case")]
46 pub enum ResolvedAuthSource {
47 /// Supplied via CLI flag/argument.
48 Cli,
49 /// Read from a config file.
50 ConfigFile,
51 /// Read from the OS keyring.
52 Keyring,
53 /// Read from an environment variable.
54 Env,
55 /// Produced by running a command.
56 Command,
57 /// Resolved from a named secret.
58 Secret,
59 /// No credential resolved.
60 Missing,
61 /// Auth resolution has not been performed for this candidate.
62 ///
63 /// The route resolver never inspects credentials, so a freshly resolved
64 /// candidate honestly reports `Unresolved` rather than claiming `Missing`
65 /// (which would assert a lookup that never happened).
66 Unresolved,
67 }
68
69 /// Which token limit a [`SourcedLimitOverride`] targets.
70 #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
71 #[serde(rename_all = "snake_case")]
72 pub enum LimitField {
73 /// [`RouteLimits::context_tokens`](super::offering::RouteLimits).
74 ContextTokens,
75 /// [`RouteLimits::input_tokens`](super::offering::RouteLimits).
76 InputTokens,
77 /// [`RouteLimits::output_tokens`](super::offering::RouteLimits).
78 OutputTokens,
79 }
80
81 /// Why a limit override was requested.
82 ///
83 /// This is provenance, not policy: the resolver applies whatever the caller
84 /// requested and records the source on the candidate so every consumer can see
85 /// where an effective limit came from.
86 #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
87 #[serde(rename_all = "snake_case")]
88 pub enum OverrideSource {
89 /// Operator-configured context window.
90 UserContextWindow,
91 /// Catalog limits describe the public API offering, not the account-scoped
92 /// Codex route; the API-only limits are stripped.
93 CodexPublicApiLimitStrip,
94 /// Per-model context from the fresh Codex account roster.
95 CodexRosterCorrection,
96 /// Fresh, route-scoped provider-reported context metadata.
97 ProviderReportedContextWindow,
98 /// Conservative all-plan safe floor for a membership-plan route.
99 MembershipPlanSafeFloor,
100 /// Maximum output documented for one exact provider endpoint/model route.
101 DocumentedRouteOutputMaximum,
102 }
103
104 /// One sourced limit override, applied by the resolver BEFORE the candidate is
105 /// constructed and recorded on the candidate as provenance.
106 #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
107 pub struct SourcedLimitOverride {
108 /// The limit field to override.
109 pub field: LimitField,
110 /// The value to set (`None` clears the limit to "unknown").
111 pub value: Option<u64>,
112 /// Why the override was requested.
113 pub source: OverrideSource,
114 }
115
116 /// Pricing/quota class for the resolved route.
117 ///
118 /// Carries only coarse, non-sensitive shape; never secrets or account ids.
119 ///
120 /// `PartialEq` (but not `Eq`: the `Token` rates are `f64`) lets offerings and
121 /// candidates be compared in tests and lets
122 /// [`super::offering::ProviderModelOffering`] carry a pricing meter.
123 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124 #[serde(rename_all = "snake_case")]
125 pub enum PricingSku {
126 /// Per-token pricing.
127 Token {
128 /// Input price per million tokens, if known.
129 input_per_mtok: Option<f64>,
130 /// Output price per million tokens, if known.
131 output_per_mtok: Option<f64>,
132 },
133 /// Subscription quota usage.
134 SubscriptionQuota {
135 /// Percent of quota used, if known.
136 used_pct: Option<f32>,
137 /// When the quota resets, if known.
138 resets_at: Option<String>,
139 },
140 /// Prepaid account credits.
141 AccountCredits {
142 /// Remaining balance, if known.
143 balance: Option<f64>,
144 },
145 /// Local or otherwise not billed.
146 LocalOrNotApplicable,
147 /// Pricing unknown or stale.
148 UnknownOrStale,
149 }
150
151 /// Outcome of route validation.
152 #[derive(Debug, Clone, Serialize, Deserialize)]
153 pub struct ValidationReport {
154 /// Whether the route passed validation.
155 pub ok: bool,
156 /// Human-readable diagnostics (advisory; secret-free).
157 pub messages: Vec<String>,
158 }
159
160 /// A runtime-resolved, executable route.
161 ///
162 /// The candidate is IMMUTABLE once minted: every field is private and exposed
163 /// only through read-only getters, the type cannot be constructed outside this
164 /// crate (private fields + no `Deserialize`), and there are no setters. The
165 /// only constructor is `Self::new`, which is `pub(super)`; see module docs.
166 /// Post-resolution limit adjustments must instead be requested up front via
167 /// [`super::resolver::RouteRequest::limit_overrides`], which the resolver
168 /// applies BEFORE construction and records in [`Self::applied_limit_overrides`].
169 ///
170 /// Immutability is compile-time enforced — this does not build:
171 ///
172 /// ```compile_fail
173 /// use codewhale_config::route::{RouteRequest, RouteResolver};
174 ///
175 /// let mut candidate = RouteResolver::new()
176 /// .resolve(&RouteRequest::default())
177 /// .unwrap();
178 /// // ERROR: field `limits` of `ReadyRouteCandidate` is private
179 /// candidate.limits.context_tokens = Some(1);
180 /// ```
181 #[derive(Debug, Clone, Serialize)]
182 #[non_exhaustive]
183 pub struct ReadyRouteCandidate {
184 /// Resolved provider id.
185 provider_id: ProviderId,
186 /// Resolved provider kind.
187 provider_kind: ProviderKind,
188 /// The selector the user/route requested.
189 logical_model: LogicalModelRef,
190 /// Canonical model identity, if one was resolved.
191 canonical_model: Option<ModelId>,
192 /// Provider-owned wire id put on the request.
193 wire_model_id: WireModelId,
194 /// Resolved endpoint transport facts.
195 endpoint: ResolvedEndpoint,
196 /// Resolved auth source CLASS (never a secret value).
197 auth: ResolvedAuthSource,
198 /// Selected wire protocol.
199 protocol: RequestProtocol,
200 /// Route/offering-scoped token limits, when known (overrides applied).
201 limits: RouteLimits,
202 /// Capability facts for the exact provider/model offering.
203 capabilities: RouteCapabilities,
204 /// Pricing/quota class, if known.
205 pricing: Option<PricingSku>,
206 /// Validation outcome.
207 validation: ValidationReport,
208 /// Provenance of every limit override applied at resolution time.
209 #[serde(skip_serializing_if = "Vec::is_empty")]
210 applied_limit_overrides: Vec<SourcedLimitOverride>,
211 }
212
213 impl ReadyRouteCandidate {
214 /// Mint a candidate. Restricted to [`super::resolver`] so the resolver is
215 /// the sole producer of executable routes (the #2608 mutation gate).
216 #[allow(clippy::too_many_arguments)]
217 pub(super) fn new(
218 provider_id: ProviderId,
219 provider_kind: ProviderKind,
220 logical_model: LogicalModelRef,
221 canonical_model: Option<ModelId>,
222 wire_model_id: WireModelId,
223 endpoint: ResolvedEndpoint,
224 auth: ResolvedAuthSource,
225 protocol: RequestProtocol,
226 limits: RouteLimits,
227 capabilities: RouteCapabilities,
228 pricing: Option<PricingSku>,
229 validation: ValidationReport,
230 applied_limit_overrides: Vec<SourcedLimitOverride>,
231 ) -> Self {
232 Self {
233 provider_id,
234 provider_kind,
235 logical_model,
236 canonical_model,
237 wire_model_id,
238 endpoint,
239 auth,
240 protocol,
241 limits,
242 capabilities,
243 pricing,
244 validation,
245 applied_limit_overrides,
246 }
247 }
248
249 /// Resolved provider id.
250 #[must_use]
251 pub fn provider_id(&self) -> &ProviderId {
252 &self.provider_id
253 }
254
255 /// Resolved provider kind.
256 #[must_use]
257 pub fn provider_kind(&self) -> ProviderKind {
258 self.provider_kind
259 }
260
261 /// The selector the user/route requested.
262 #[must_use]
263 pub fn logical_model(&self) -> &LogicalModelRef {
264 &self.logical_model
265 }
266
267 /// Canonical model identity, if one was resolved.
268 #[must_use]
269 pub fn canonical_model(&self) -> Option<&ModelId> {
270 self.canonical_model.as_ref()
271 }
272
273 /// Provider-owned wire id put on the request.
274 #[must_use]
275 pub fn wire_model_id(&self) -> &WireModelId {
276 &self.wire_model_id
277 }
278
279 /// Resolved endpoint transport facts.
280 #[must_use]
281 pub fn endpoint(&self) -> &ResolvedEndpoint {
282 &self.endpoint
283 }
284
285 /// Resolved auth source CLASS (never a secret value).
286 #[must_use]
287 pub fn auth(&self) -> &ResolvedAuthSource {
288 &self.auth
289 }
290
291 /// Selected wire protocol.
292 #[must_use]
293 pub fn protocol(&self) -> RequestProtocol {
294 self.protocol
295 }
296
297 /// Route/offering-scoped token limits, when known (overrides applied).
298 #[must_use]
299 pub fn limits(&self) -> RouteLimits {
300 self.limits
301 }
302
303 /// Capability facts for the exact provider/model offering.
304 #[must_use]
305 pub fn capabilities(&self) -> RouteCapabilities {
306 self.capabilities
307 }
308
309 /// Pricing/quota class, if known.
310 #[must_use]
311 pub fn pricing(&self) -> Option<&PricingSku> {
312 self.pricing.as_ref()
313 }
314
315 /// Validation outcome.
316 #[must_use]
317 pub fn validation(&self) -> &ValidationReport {
318 &self.validation
319 }
320
321 /// Provenance of every limit override applied at resolution time.
322 #[must_use]
323 pub fn applied_limit_overrides(&self) -> &[SourcedLimitOverride] {
324 &self.applied_limit_overrides
325 }
326 }
327
327 lines RUST