1use serde::{Deserialize, Serialize};
15
16use super::candidate::PricingSku;
17use super::capabilities::{CapabilityState, RouteCapabilities};
18use super::ids::{ModelId, ProviderId, WireModelId};
19
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
26pub struct RouteLimits {
27 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub context_tokens: Option<u64>,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub input_tokens: Option<u64>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub output_tokens: Option<u64>,
36}
37
38impl RouteLimits {
39 #[must_use]
41 pub const fn has_known_limit(self) -> bool {
42 self.context_tokens.is_some() || self.input_tokens.is_some() || self.output_tokens.is_some()
43 }
44}
45
46#[derive(Debug, Clone, PartialEq)]
51pub struct ProviderModelOffering {
52 pub provider: ProviderId,
54 pub canonical_model: Option<ModelId>,
56 pub wire_model_id: WireModelId,
58 pub endpoint_key: String,
60 pub default_for_provider: bool,
62 pub limits: RouteLimits,
64 pub capabilities: RouteCapabilities,
67 pub pricing: PricingSku,
75}
76
77pub(crate) const OPENCODE_ZEN_RESPONSES_MODELS: &[&str] = &[
81 "gpt-5.6-sol",
82 "gpt-5.6-terra",
83 "gpt-5.6-luna",
84 "gpt-5.5",
85 "gpt-5.5-pro",
86 "gpt-5.4",
87 "gpt-5.4-pro",
88 "gpt-5.4-mini",
89 "gpt-5.4-nano",
90 "gpt-5.3-codex",
91 "gpt-5.3-codex-spark",
92 "gpt-5.2",
93 "gpt-5.2-codex",
94 "gpt-5.1",
95 "gpt-5.1-codex",
96 "gpt-5.1-codex-max",
97 "gpt-5.1-codex-mini",
98 "gpt-5",
99 "gpt-5-codex",
100 "gpt-5-nano",
101];
102
103pub(crate) const OPENCODE_ZEN_MESSAGES_MODELS: &[&str] = &[
104 "claude-fable-5",
105 "claude-opus-4-8",
106 "claude-opus-4-7",
107 "claude-opus-4-6",
108 "claude-opus-4-5",
109 "claude-sonnet-5",
110 "claude-sonnet-4-6",
111 "claude-sonnet-4-5",
112 "claude-haiku-4-5",
113 "qwen3.7-max",
114 "qwen3.7-plus",
115 "qwen3.6-plus",
116 "qwen3.5-plus",
117];
118
119pub(crate) const OPENCODE_ZEN_CHAT_MODELS: &[&str] = &[
120 "deepseek-v4-pro",
121 "deepseek-v4-flash",
122 "minimax-m3",
123 "minimax-m2.7",
124 "minimax-m2.5",
125 "glm-5.2",
126 "glm-5.1",
127 "glm-5",
128 "kimi-k2.5",
129 "kimi-k2.6",
130 "kimi-k2.7-code",
131 "grok-4.5",
132 "grok-build-0.1",
133 "big-pickle",
134 "mimo-v2.5-free",
135 "north-mini-code-free",
136 "nemotron-3-ultra-free",
137 "deepseek-v4-flash-free",
138];
139
140#[must_use]
146pub fn bundled_offerings() -> Vec<ProviderModelOffering> {
147 let deepseek = ProviderId::from("deepseek");
152 let documented_capabilities = RouteCapabilities {
153 image_input: CapabilityState::Unsupported,
154 reasoning: CapabilityState::Supported,
155 native_tool_calls: CapabilityState::Supported,
156 structured_output: CapabilityState::Supported,
157 parallel_tool_calls: CapabilityState::Supported,
158 streaming: CapabilityState::Supported,
159 prompt_caching: CapabilityState::Supported,
160 server_side_web_search: CapabilityState::Unknown,
164 ..RouteCapabilities::default()
165 };
166 let documented_limits = RouteLimits {
167 context_tokens: Some(1_000_000),
168 input_tokens: None,
169 output_tokens: Some(384_000),
170 };
171 let mut offerings = vec![
172 ProviderModelOffering {
173 provider: deepseek.clone(),
174 canonical_model: Some(ModelId::from("deepseek-v4-pro")),
175 wire_model_id: WireModelId::from("deepseek-v4-pro"),
176 endpoint_key: "chat".to_string(),
177 default_for_provider: true,
178 limits: documented_limits,
179 capabilities: documented_capabilities,
180 pricing: PricingSku::UnknownOrStale,
181 },
182 ProviderModelOffering {
183 provider: deepseek,
184 canonical_model: Some(ModelId::from("deepseek-v4-flash")),
185 wire_model_id: WireModelId::from("deepseek-v4-flash"),
186 endpoint_key: "responses".to_string(),
187 default_for_provider: false,
188 limits: documented_limits,
189 capabilities: documented_capabilities,
190 pricing: PricingSku::UnknownOrStale,
191 },
192 ];
193
194 let provider = ProviderId::from("opencode-zen");
195 let groups = [
196 ("responses", OPENCODE_ZEN_RESPONSES_MODELS),
197 ("messages", OPENCODE_ZEN_MESSAGES_MODELS),
198 ("chat", OPENCODE_ZEN_CHAT_MODELS),
199 ];
200
201 offerings.extend(groups.into_iter().flat_map(|(endpoint_key, models)| {
202 let provider = provider.clone();
203 models.iter().map(move |model| ProviderModelOffering {
204 provider: provider.clone(),
205 canonical_model: None,
206 wire_model_id: WireModelId::from(*model),
207 endpoint_key: endpoint_key.to_string(),
208 default_for_provider: *model == "gpt-5.5",
209 limits: RouteLimits::default(),
210 capabilities: RouteCapabilities::default(),
211 pricing: PricingSku::UnknownOrStale,
212 })
213 }));
214 offerings
215}