codewhale_config/route/offering.rs
1//! Provider model offerings (#3084).
2//!
3//! A [`ProviderModelOffering`] binds a provider to a canonical model, the
4//! provider-owned wire id that serves it, and the endpoint key. This is the
5//! seam that proves the #2608 invariant: the SAME canonical model can be served
6//! by multiple providers under DIFFERENT wire ids (some aggregator-prefixed),
7//! and a prefix never implies provider ownership.
8//!
9//! [`BUNDLED_OFFERINGS`] is intentionally tiny: a couple DeepSeek-native rows
10//! plus a couple aggregator rows (Together / OpenRouter) whose wire ids carry
11//! prefixes such as `deepseek-ai/DeepSeek-V4-Pro`. It exists to exercise the
12//! seam, not to be the eventual catalog.
13
14use serde::{Deserialize, Serialize};
15
16use super::candidate::PricingSku;
17use super::ids::{ModelId, ProviderId, WireModelId};
18
19/// Token limits for one resolved route/offering.
20///
21/// These are optional because hosted catalogs, local runtimes, and custom
22/// endpoints can legitimately omit some or all limit facts. Callers should
23/// treat `None` as unknown, not zero.
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
25pub struct RouteLimits {
26 /// Total context window (input + output), in tokens.
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub context_tokens: Option<u64>,
29 /// Input-token limit, when the provider reports it separately.
30 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub input_tokens: Option<u64>,
32 /// Output-token cap for the route/offering, when known.
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub output_tokens: Option<u64>,
35}
36
37impl RouteLimits {
38 /// Whether at least one limit fact is known.
39 #[must_use]
40 pub const fn has_known_limit(self) -> bool {
41 self.context_tokens.is_some() || self.input_tokens.is_some() || self.output_tokens.is_some()
42 }
43}
44
45/// One provider's way of serving a (possibly canonical) model.
46///
47/// `Eq` is intentionally NOT derived: [`PricingSku::Token`] carries `f64` rates,
48/// so the offering is only `PartialEq`. No caller keys a set/map on offerings.
49#[derive(Debug, Clone, PartialEq)]
50pub struct ProviderModelOffering {
51 /// Provider serving this offering.
52 pub provider: ProviderId,
53 /// Canonical model identity, if this offering maps to one.
54 pub canonical_model: Option<ModelId>,
55 /// Provider-owned wire id sent on the request (verbatim).
56 pub wire_model_id: WireModelId,
57 /// Endpoint key the offering is served on.
58 pub endpoint_key: String,
59 /// Whether this is the provider's default offering.
60 pub default_for_provider: bool,
61 /// Provider/offering-scoped token limits, when known.
62 pub limits: RouteLimits,
63 /// Coarse route-facing pricing meter for this offering (#3085).
64 ///
65 /// Projected from the offering's sourced cost at the layer that owns it
66 /// (`CatalogOffering::to_offering` → [`crate::pricing::route_pricing_sku`]).
67 /// The resolver carries this verbatim onto the candidate; it is
68 /// [`PricingSku::UnknownOrStale`] whenever no price was sourced — never a
69 /// fabricated zero (the #2608 / #3085 honesty rule).
70 pub pricing: PricingSku,
71}
72
73/// A static, lazily-materialized seam catalog.
74///
75/// Each row binds a provider id, an optional canonical model id, the wire id
76/// it is served under, the endpoint key, and whether it is the provider
77/// default. Aggregator rows demonstrate prefixed wire ids.
78struct OfferingSeed {
79 provider: &'static str,
80 canonical_model: Option<&'static str>,
81 wire_model_id: &'static str,
82 endpoint_key: &'static str,
83 default_for_provider: bool,
84}
85
86const OFFERING_SEEDS: &[OfferingSeed] = &[
87 // DeepSeek-native: wire id equals the bare model name, no prefix.
88 OfferingSeed {
89 provider: "deepseek",
90 canonical_model: Some("deepseek-v4-pro"),
91 wire_model_id: "deepseek-v4-pro",
92 endpoint_key: "chat",
93 default_for_provider: true,
94 },
95 OfferingSeed {
96 provider: "deepseek",
97 canonical_model: Some("deepseek-v4-flash"),
98 wire_model_id: "deepseek-v4-flash",
99 endpoint_key: "chat",
100 default_for_provider: false,
101 },
102 // Together aggregator: same canonical model, prefixed wire id.
103 OfferingSeed {
104 provider: "together",
105 canonical_model: Some("deepseek-v4-pro"),
106 wire_model_id: "deepseek-ai/DeepSeek-V4-Pro",
107 endpoint_key: "chat",
108 default_for_provider: true,
109 },
110 // OpenRouter aggregator: same canonical model, different prefixed wire id.
111 OfferingSeed {
112 provider: "openrouter",
113 canonical_model: Some("deepseek-v4-pro"),
114 wire_model_id: "deepseek/deepseek-v4-pro",
115 endpoint_key: "chat",
116 default_for_provider: true,
117 },
118];
119
120/// Return the bundled offering seam as owned [`ProviderModelOffering`] rows.
121///
122/// Owned because the newtypes wrap `String`; the seed table stays `&'static`.
123#[must_use]
124pub fn bundled_offerings() -> Vec<ProviderModelOffering> {
125 OFFERING_SEEDS
126 .iter()
127 .map(|seed| ProviderModelOffering {
128 provider: ProviderId::from(seed.provider),
129 canonical_model: seed.canonical_model.map(ModelId::from),
130 wire_model_id: WireModelId::from(seed.wire_model_id),
131 endpoint_key: seed.endpoint_key.to_string(),
132 default_for_provider: seed.default_for_provider,
133 limits: RouteLimits::default(),
134 // The bundled seam carries no sourced cost, so pricing is honestly
135 // unknown here (never a fabricated zero).
136 pricing: PricingSku::UnknownOrStale,
137 })
138 .collect()
139}