use serde::{Deserialize, Serialize};
use super::candidate::PricingSku;
use super::capabilities::{CapabilityState, RouteCapabilities};
use super::ids::{ModelId, ProviderId, WireModelId};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RouteLimits {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub input_tokens: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_tokens: Option<u64>,
}
impl RouteLimits {
#[must_use]
pub const fn has_known_limit(self) -> bool {
self.context_tokens.is_some() || self.input_tokens.is_some() || self.output_tokens.is_some()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProviderModelOffering {
pub provider: ProviderId,
pub canonical_model: Option<ModelId>,
pub wire_model_id: WireModelId,
pub endpoint_key: String,
pub default_for_provider: bool,
pub limits: RouteLimits,
pub capabilities: RouteCapabilities,
pub pricing: PricingSku,
}
pub(crate) const OPENCODE_ZEN_RESPONSES_MODELS: &[&str] = &[
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
"gpt-5.5",
"gpt-5.5-pro",
"gpt-5.4",
"gpt-5.4-pro",
"gpt-5.4-mini",
"gpt-5.4-nano",
"gpt-5.3-codex",
"gpt-5.3-codex-spark",
"gpt-5.2",
"gpt-5.2-codex",
"gpt-5.1",
"gpt-5.1-codex",
"gpt-5.1-codex-max",
"gpt-5.1-codex-mini",
"gpt-5",
"gpt-5-codex",
"gpt-5-nano",
];
pub(crate) const OPENCODE_ZEN_MESSAGES_MODELS: &[&str] = &[
"claude-fable-5",
"claude-opus-4-8",
"claude-opus-4-7",
"claude-opus-4-6",
"claude-opus-4-5",
"claude-sonnet-5",
"claude-sonnet-4-6",
"claude-sonnet-4-5",
"claude-haiku-4-5",
"qwen3.7-max",
"qwen3.7-plus",
"qwen3.6-plus",
"qwen3.5-plus",
];
pub(crate) const OPENCODE_ZEN_CHAT_MODELS: &[&str] = &[
"deepseek-v4-pro",
"deepseek-v4-flash",
"minimax-m3",
"minimax-m2.7",
"minimax-m2.5",
"glm-5.2",
"glm-5.1",
"glm-5",
"kimi-k2.5",
"kimi-k2.6",
"kimi-k2.7-code",
"grok-4.5",
"grok-build-0.1",
"big-pickle",
"mimo-v2.5-free",
"north-mini-code-free",
"nemotron-3-ultra-free",
"deepseek-v4-flash-free",
];
#[must_use]
pub fn bundled_offerings() -> Vec<ProviderModelOffering> {
let deepseek = ProviderId::from("deepseek");
let documented_capabilities = RouteCapabilities {
image_input: CapabilityState::Unsupported,
reasoning: CapabilityState::Supported,
native_tool_calls: CapabilityState::Supported,
structured_output: CapabilityState::Supported,
parallel_tool_calls: CapabilityState::Supported,
streaming: CapabilityState::Supported,
prompt_caching: CapabilityState::Supported,
server_side_web_search: CapabilityState::Unknown,
..RouteCapabilities::default()
};
let documented_limits = RouteLimits {
context_tokens: Some(1_000_000),
input_tokens: None,
output_tokens: Some(384_000),
};
let mut offerings = vec![
ProviderModelOffering {
provider: deepseek.clone(),
canonical_model: Some(ModelId::from("deepseek-v4-pro")),
wire_model_id: WireModelId::from("deepseek-v4-pro"),
endpoint_key: "chat".to_string(),
default_for_provider: true,
limits: documented_limits,
capabilities: documented_capabilities,
pricing: PricingSku::UnknownOrStale,
},
ProviderModelOffering {
provider: deepseek,
canonical_model: Some(ModelId::from("deepseek-v4-flash")),
wire_model_id: WireModelId::from("deepseek-v4-flash"),
endpoint_key: "responses".to_string(),
default_for_provider: false,
limits: documented_limits,
capabilities: documented_capabilities,
pricing: PricingSku::UnknownOrStale,
},
];
let provider = ProviderId::from("opencode-zen");
let groups = [
("responses", OPENCODE_ZEN_RESPONSES_MODELS),
("messages", OPENCODE_ZEN_MESSAGES_MODELS),
("chat", OPENCODE_ZEN_CHAT_MODELS),
];
offerings.extend(groups.into_iter().flat_map(|(endpoint_key, models)| {
let provider = provider.clone();
models.iter().map(move |model| ProviderModelOffering {
provider: provider.clone(),
canonical_model: None,
wire_model_id: WireModelId::from(*model),
endpoint_key: endpoint_key.to_string(),
default_for_provider: *model == "gpt-5.5",
limits: RouteLimits::default(),
capabilities: RouteCapabilities::default(),
pricing: PricingSku::UnknownOrStale,
})
}));
offerings
}