use anyhow::{Result, anyhow};use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream};use http_client::{ AsyncBody, CustomHeaders, HttpClient, Method, Request as HttpRequest, RequestBuilderExt,};use language_model_core::ReasoningEffort;use serde::{Deserialize, Serialize};use strum::EnumIter; pub const OPENCODE_API_URL: &str = "https://opencode.ai/zen"; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]#[serde(rename_all = "snake_case")]pub enum ApiProtocol { #[default] Anthropic, OpenAiResponses, OpenAiChat, Google,} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]#[serde(rename_all = "snake_case")]pub enum OpenCodeSubscription { Zen, Go, Free,} impl OpenCodeSubscription { pub fn display_name(&self) -> &'static str { match self { Self::Zen => "Zen", Self::Go => "Go", Self::Free => "Free", } } pub fn id_prefix(&self) -> &'static str { match self { Self::Zen => "zen", Self::Go => "go", Self::Free => "free", } } pub fn api_path_suffix(&self) -> &'static str { match self { Self::Zen | Self::Free => "", Self::Go => "/go", } }} #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]pub enum Model { // -- Anthropic protocol models -- #[serde(rename = "claude-opus-4-8")] ClaudeOpus4_8, #[serde(rename = "claude-opus-4-7")] ClaudeOpus4_7, #[serde(rename = "claude-opus-4-6")] ClaudeOpus4_6, #[serde(rename = "claude-opus-4-5")] ClaudeOpus4_5, #[serde(rename = "claude-opus-4-1")] ClaudeOpus4_1, #[default] #[serde(rename = "claude-sonnet-4-6")] ClaudeSonnet4_6, #[serde(rename = "claude-sonnet-4-5")] ClaudeSonnet4_5, #[serde(rename = "claude-sonnet-4")] ClaudeSonnet4, #[serde(rename = "claude-haiku-4-5")] ClaudeHaiku4_5, #[serde(rename = "claude-sonnet-5")] ClaudeSonnet5, #[serde(rename = "claude-fable-5")] ClaudeFable5, // -- OpenAI Responses API models -- #[serde(rename = "gpt-5.6-sol")] Gpt5_6Sol, #[serde(rename = "gpt-5.6-terra")] Gpt5_6Terra, #[serde(rename = "gpt-5.6-luna")] Gpt5_6Luna, #[serde(rename = "gpt-5.5")] Gpt5_5, #[serde(rename = "gpt-5.5-pro")] Gpt5_5Pro, #[serde(rename = "gpt-5.4")] Gpt5_4, #[serde(rename = "gpt-5.4-pro")] Gpt5_4Pro, #[serde(rename = "gpt-5.4-mini")] Gpt5_4Mini, #[serde(rename = "gpt-5.4-nano")] Gpt5_4Nano, #[serde(rename = "gpt-5.3-codex")] Gpt5_3Codex, #[serde(rename = "gpt-5.3-codex-spark")] Gpt5_3Spark, #[serde(rename = "gpt-5.2")] Gpt5_2, #[serde(rename = "gpt-5.2-codex")] Gpt5_2Codex, #[serde(rename = "gpt-5.1")] Gpt5_1, #[serde(rename = "gpt-5.1-codex")] Gpt5_1Codex, #[serde(rename = "gpt-5.1-codex-max")] Gpt5_1CodexMax, #[serde(rename = "gpt-5.1-codex-mini")] Gpt5_1CodexMini, #[serde(rename = "gpt-5")] Gpt5, #[serde(rename = "gpt-5-codex")] Gpt5Codex, #[serde(rename = "gpt-5-nano")] Gpt5Nano, // -- Google protocol models -- #[serde(rename = "gemini-3.1-pro")] Gemini3_1Pro, #[serde(rename = "gemini-3-flash")] Gemini3Flash, #[serde(rename = "gemini-3.5-flash")] Gemini3_5Flash, // -- OpenAI Chat Completions protocol models -- #[serde(rename = "deepseek-v4-pro")] DeepSeekV4Pro, #[serde(rename = "deepseek-v4-flash")] DeepSeekV4Flash, #[serde(rename = "minimax-m2.5")] MiniMaxM2_5, #[serde(rename = "glm-5")] Glm5, #[serde(rename = "glm-5.1")] Glm5_1, #[serde(rename = "glm-5.2")] Glm5_2, #[serde(rename = "grok-build-0.1")] GrokBuild0_1, #[serde(rename = "grok-4.5")] Grok4_5, #[serde(rename = "kimi-k2.5")] KimiK2_5, #[serde(rename = "kimi-k2.6")] KimiK2_6, #[serde(rename = "kimi-k2.7-code")] KimiK2_7Code, #[serde(rename = "minimax-m2.7")] MiniMaxM2_7, #[serde(rename = "minimax-m3")] MiniMaxM3, #[serde(rename = "mimo-v2.5-pro")] MimoV2_5Pro, #[serde(rename = "mimo-v2.5")] MimoV2_5, #[serde(rename = "big-pickle")] BigPickle, #[serde(rename = "nemotron-3-ultra-free")] Nemotron3UltraFree, #[serde(rename = "qwen3.5-plus")] Qwen3_5Plus, #[serde(rename = "qwen3.6-plus")] Qwen3_6Plus, #[serde(rename = "qwen3.7-plus")] Qwen3_7Plus, #[serde(rename = "qwen3.7-max")] Qwen3_7Max, // -- Custom model -- #[serde(rename = "custom")] Custom { name: String, display_name: Option<String>, max_tokens: u64, max_output_tokens: Option<u64>, protocol: ApiProtocol, reasoning_effort_levels: Option<Vec<ReasoningEffort>>, custom_model_api_url: Option<String>, interleaved_reasoning: bool, },} impl Model { pub fn default_fast() -> Self { Self::ClaudeHaiku4_5 } pub fn default_go() -> Self { Self::KimiK2_6 } pub fn default_go_fast() -> Self { Self::MiniMaxM2_7 } pub fn default_free() -> Self { Self::BigPickle } pub fn default_free_fast() -> Self { Self::Nemotron3UltraFree } pub fn available_subscriptions(&self) -> &'static [OpenCodeSubscription] { match self { // Models available in both Zen and Go Self::Glm5_1 | Self::Glm5_2 | Self::KimiK2_6 | Self::KimiK2_7Code | Self::MiniMaxM2_7 | Self::MiniMaxM3 | Self::DeepSeekV4Pro | Self::DeepSeekV4Flash | Self::Qwen3_6Plus => &[OpenCodeSubscription::Zen, OpenCodeSubscription::Go], // Go-only models Self::MimoV2_5Pro | Self::MimoV2_5 | Self::Qwen3_7Plus | Self::Qwen3_7Max => { &[OpenCodeSubscription::Go] } // Deprecated on Go (per models.dev); still offered on Zen Self::Glm5 | Self::KimiK2_5 | Self::MiniMaxM2_5 | Self::Qwen3_5Plus => { &[OpenCodeSubscription::Zen] } // Free models Self::Nemotron3UltraFree | Self::BigPickle => &[OpenCodeSubscription::Free], // Custom models get their subscription from settings, not from here Self::Custom { .. } => &[], // All other built-in models are Zen-only _ => &[OpenCodeSubscription::Zen], } } pub fn id(&self) -> &str { match self { Self::ClaudeOpus4_8 => "claude-opus-4-8", Self::ClaudeOpus4_7 => "claude-opus-4-7", Self::ClaudeOpus4_6 => "claude-opus-4-6", Self::ClaudeOpus4_5 => "claude-opus-4-5", Self::ClaudeOpus4_1 => "claude-opus-4-1", Self::ClaudeSonnet4_6 => "claude-sonnet-4-6", Self::ClaudeSonnet4_5 => "claude-sonnet-4-5", Self::ClaudeSonnet4 => "claude-sonnet-4", Self::ClaudeHaiku4_5 => "claude-haiku-4-5", Self::ClaudeSonnet5 => "claude-sonnet-5", Self::ClaudeFable5 => "claude-fable-5", Self::Gpt5_6Sol => "gpt-5.6-sol", Self::Gpt5_6Terra => "gpt-5.6-terra", Self::Gpt5_6Luna => "gpt-5.6-luna", Self::Gpt5_5 => "gpt-5.5", Self::Gpt5_5Pro => "gpt-5.5-pro", Self::Gpt5_4 => "gpt-5.4", Self::Gpt5_4Pro => "gpt-5.4-pro", Self::Gpt5_4Mini => "gpt-5.4-mini", Self::Gpt5_4Nano => "gpt-5.4-nano", Self::Gpt5_3Codex => "gpt-5.3-codex", Self::Gpt5_3Spark => "gpt-5.3-codex-spark", Self::Gpt5_2 => "gpt-5.2", Self::Gpt5_2Codex => "gpt-5.2-codex", Self::Gpt5_1 => "gpt-5.1", Self::Gpt5_1Codex => "gpt-5.1-codex", Self::Gpt5_1CodexMax => "gpt-5.1-codex-max", Self::Gpt5_1CodexMini => "gpt-5.1-codex-mini", Self::Gpt5 => "gpt-5", Self::Gpt5Codex => "gpt-5-codex", Self::Gpt5Nano => "gpt-5-nano", Self::Gemini3_1Pro => "gemini-3.1-pro", Self::Gemini3Flash => "gemini-3-flash", Self::Gemini3_5Flash => "gemini-3.5-flash", Self::DeepSeekV4Pro => "deepseek-v4-pro", Self::DeepSeekV4Flash => "deepseek-v4-flash", Self::MiniMaxM2_5 => "minimax-m2.5", Self::Glm5 => "glm-5", Self::Glm5_1 => "glm-5.1", Self::Glm5_2 => "glm-5.2", Self::GrokBuild0_1 => "grok-build-0.1", Self::Grok4_5 => "grok-4.5", Self::KimiK2_5 => "kimi-k2.5", Self::KimiK2_6 => "kimi-k2.6", Self::KimiK2_7Code => "kimi-k2.7-code", Self::MiniMaxM2_7 => "minimax-m2.7", Self::MiniMaxM3 => "minimax-m3", Self::MimoV2_5Pro => "mimo-v2.5-pro", Self::MimoV2_5 => "mimo-v2.5", Self::Qwen3_5Plus => "qwen3.5-plus", Self::Qwen3_6Plus => "qwen3.6-plus", Self::Qwen3_7Plus => "qwen3.7-plus", Self::Qwen3_7Max => "qwen3.7-max", Self::BigPickle => "big-pickle", Self::Nemotron3UltraFree => "nemotron-3-ultra-free", Self::Custom { name, .. } => name, } } pub fn display_name(&self) -> &str { match self { Self::ClaudeOpus4_8 => "Claude Opus 4.8", Self::ClaudeOpus4_7 => "Claude Opus 4.7", Self::ClaudeOpus4_6 => "Claude Opus 4.6", Self::ClaudeOpus4_5 => "Claude Opus 4.5", Self::ClaudeOpus4_1 => "Claude Opus 4.1", Self::ClaudeSonnet4_6 => "Claude Sonnet 4.6", Self::ClaudeSonnet4_5 => "Claude Sonnet 4.5", Self::ClaudeSonnet4 => "Claude Sonnet 4", Self::ClaudeHaiku4_5 => "Claude Haiku 4.5", Self::ClaudeSonnet5 => "Claude Sonnet 5", Self::ClaudeFable5 => "Claude Fable 5", Self::Gpt5_6Sol => "GPT 5.6 Sol", Self::Gpt5_6Terra => "GPT 5.6 Terra", Self::Gpt5_6Luna => "GPT 5.6 Luna", Self::Gpt5_5 => "GPT 5.5", Self::Gpt5_5Pro => "GPT 5.5 Pro", Self::Gpt5_4 => "GPT 5.4", Self::Gpt5_4Pro => "GPT 5.4 Pro", Self::Gpt5_4Mini => "GPT 5.4 Mini", Self::Gpt5_4Nano => "GPT 5.4 Nano", Self::Gpt5_3Codex => "GPT 5.3 Codex", Self::Gpt5_3Spark => "GPT 5.3 Codex Spark", Self::Gpt5_2 => "GPT 5.2", Self::Gpt5_2Codex => "GPT 5.2 Codex", Self::Gpt5_1 => "GPT 5.1", Self::Gpt5_1Codex => "GPT 5.1 Codex", Self::Gpt5_1CodexMax => "GPT 5.1 Codex Max", Self::Gpt5_1CodexMini => "GPT 5.1 Codex Mini", Self::Gpt5 => "GPT 5", Self::Gpt5Codex => "GPT 5 Codex", Self::Gpt5Nano => "GPT 5 Nano", Self::Gemini3_1Pro => "Gemini 3.1 Pro", Self::Gemini3Flash => "Gemini 3 Flash", Self::Gemini3_5Flash => "Gemini 3.5 Flash", Self::DeepSeekV4Pro => "DeepSeek V4 Pro", Self::DeepSeekV4Flash => "DeepSeek V4 Flash", Self::MiniMaxM2_5 => "MiniMax M2.5", Self::Glm5 => "GLM 5", Self::Glm5_1 => "GLM 5.1", Self::Glm5_2 => "GLM 5.2", Self::GrokBuild0_1 => "Grok Build 0.1", Self::Grok4_5 => "Grok 4.5", Self::KimiK2_5 => "Kimi K2.5", Self::KimiK2_6 => "Kimi K2.6", Self::KimiK2_7Code => "Kimi K2.7 Code", Self::MiniMaxM2_7 => "MiniMax M2.7", Self::MiniMaxM3 => "MiniMax M3", Self::MimoV2_5Pro => "MiMo V2.5 Pro", Self::MimoV2_5 => "MiMo V2.5", Self::Qwen3_5Plus => "Qwen3.5 Plus", Self::Qwen3_6Plus => "Qwen3.6 Plus", Self::Qwen3_7Plus => "Qwen3.7 Plus", Self::Qwen3_7Max => "Qwen3.7 Max", Self::BigPickle => "Big Pickle", Self::Nemotron3UltraFree => "Nemotron 3 Ultra Free", Self::Custom { name, display_name, .. } => display_name.as_deref().unwrap_or(name), } } pub fn protocol(&self, subscription: OpenCodeSubscription) -> ApiProtocol { match self { // Models offered by OpenCode have the same configuration across subscriptions // with one outlier: non-free MiniMax models Self::MiniMaxM3 | Self::MiniMaxM2_7 | Self::MiniMaxM2_5 => { if subscription == OpenCodeSubscription::Zen { ApiProtocol::OpenAiChat } else { ApiProtocol::Anthropic } } Self::ClaudeFable5 | Self::ClaudeOpus4_8 | Self::ClaudeOpus4_7 | Self::ClaudeOpus4_6 | Self::ClaudeOpus4_5 | Self::ClaudeOpus4_1 | Self::ClaudeSonnet5 | Self::ClaudeSonnet4_6 | Self::ClaudeSonnet4_5 | Self::ClaudeSonnet4 | Self::ClaudeHaiku4_5 => ApiProtocol::Anthropic, Self::Gpt5_6Sol | Self::Gpt5_6Terra | Self::Gpt5_6Luna | Self::Gpt5_5 | Self::Gpt5_5Pro | Self::Gpt5_4 | Self::Gpt5_4Pro | Self::Gpt5_4Mini | Self::Gpt5_4Nano | Self::Gpt5_3Codex | Self::Gpt5_3Spark | Self::Gpt5_2 | Self::Gpt5_2Codex | Self::Gpt5_1 | Self::Gpt5_1Codex | Self::Gpt5_1CodexMax | Self::Gpt5_1CodexMini | Self::Gpt5 | Self::Gpt5Codex | Self::Gpt5Nano => ApiProtocol::OpenAiResponses, Self::Gemini3_1Pro | Self::Gemini3Flash | Self::Gemini3_5Flash => ApiProtocol::Google, Self::Qwen3_7Max | Self::Qwen3_7Plus | Self::Qwen3_6Plus | Self::Qwen3_5Plus => { ApiProtocol::Anthropic } Self::Glm5 | Self::Glm5_1 | Self::Glm5_2 | Self::GrokBuild0_1 | Self::Grok4_5 | Self::KimiK2_5 | Self::KimiK2_6 | Self::KimiK2_7Code | Self::MimoV2_5Pro | Self::MimoV2_5 | Self::DeepSeekV4Pro | Self::DeepSeekV4Flash | Self::BigPickle | Self::Nemotron3UltraFree => ApiProtocol::OpenAiChat, Self::Custom { protocol, .. } => *protocol, } } pub fn interleaved_reasoning(&self) -> bool { match self { Self::DeepSeekV4Pro | Self::DeepSeekV4Flash | Self::KimiK2_5 | Self::KimiK2_6 | Self::KimiK2_7Code | Self::MimoV2_5 | Self::MimoV2_5Pro | Self::Glm5 | Self::Glm5_1 | Self::Glm5_2 | Self::MiniMaxM2_5 | Self::MiniMaxM2_7 | Self::MiniMaxM3 | Self::Nemotron3UltraFree | Self::BigPickle => true, Self::Custom { interleaved_reasoning, .. } => *interleaved_reasoning, _ => false, } } pub fn max_token_count(&self, subscription: OpenCodeSubscription) -> u64 { match self { // Anthropic models Self::ClaudeOpus4_8 | Self::ClaudeOpus4_7 => 1_000_000, Self::ClaudeOpus4_6 | Self::ClaudeSonnet4_6 => 1_000_000, Self::ClaudeSonnet4_5 => 1_000_000, Self::ClaudeOpus4_5 | Self::ClaudeHaiku4_5 => 200_000, Self::ClaudeOpus4_1 => 200_000, Self::ClaudeSonnet4 => 1_000_000, Self::ClaudeSonnet5 => 1_000_000, Self::ClaudeFable5 => 1_000_000, // OpenAI models Self::Gpt5_6Sol | Self::Gpt5_6Terra | Self::Gpt5_6Luna => 1_050_000, Self::Gpt5_5 | Self::Gpt5_5Pro => 1_050_000, Self::Gpt5_4 | Self::Gpt5_4Pro => 1_050_000, Self::Gpt5_4Mini | Self::Gpt5_4Nano => 400_000, Self::Gpt5_3Codex => 400_000, Self::Gpt5_3Spark => 128_000, Self::Gpt5_2 | Self::Gpt5_2Codex => 400_000, Self::Gpt5_1 | Self::Gpt5_1Codex | Self::Gpt5_1CodexMax | Self::Gpt5_1CodexMini => { 400_000 } Self::Gpt5 | Self::Gpt5Codex | Self::Gpt5Nano => 400_000,Lines 1–500 of 906. Load more
use anyhow::{Result, anyhow}; use futures::{AsyncBufReadExt, AsyncReadExt, StreamExt, io::BufReader, stream::BoxStream}; use http_client::{ AsyncBody, CustomHeaders, HttpClient, Method, Request as HttpRequest, RequestBuilderExt, }; use language_model_core::ReasoningEffort; use serde::{Deserialize, Serialize}; use strum::EnumIter; pub const OPENCODE_API_URL: &str = "https://opencode.ai/zen"; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] pub enum ApiProtocol { #[default] Anthropic, OpenAiResponses, OpenAiChat, Google, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "snake_case")] pub enum OpenCodeSubscription { Zen, Go, Free, } impl OpenCodeSubscription { pub fn display_name(&self) -> &'static str { match self { Self::Zen => "Zen", Self::Go => "Go", Self::Free => "Free", } } pub fn id_prefix(&self) -> &'static str { match self { Self::Zen => "zen", Self::Go => "go", Self::Free => "free", } } pub fn api_path_suffix(&self) -> &'static str { match self { Self::Zen | Self::Free => "", Self::Go => "/go", } } } #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)] pub enum Model { // -- Anthropic protocol models -- #[serde(rename = "claude-opus-4-8")] ClaudeOpus4_8, #[serde(rename = "claude-opus-4-7")] ClaudeOpus4_7, #[serde(rename = "claude-opus-4-6")] ClaudeOpus4_6, #[serde(rename = "claude-opus-4-5")] ClaudeOpus4_5, #[serde(rename = "claude-opus-4-1")] ClaudeOpus4_1, #[default] #[serde(rename = "claude-sonnet-4-6")] ClaudeSonnet4_6, #[serde(rename = "claude-sonnet-4-5")] ClaudeSonnet4_5, #[serde(rename = "claude-sonnet-4")] ClaudeSonnet4, #[serde(rename = "claude-haiku-4-5")] ClaudeHaiku4_5, #[serde(rename = "claude-sonnet-5")] ClaudeSonnet5, #[serde(rename = "claude-fable-5")] ClaudeFable5, // -- OpenAI Responses API models -- #[serde(rename = "gpt-5.6-sol")] Gpt5_6Sol, #[serde(rename = "gpt-5.6-terra")] Gpt5_6Terra, #[serde(rename = "gpt-5.6-luna")] Gpt5_6Luna, #[serde(rename = "gpt-5.5")] Gpt5_5, #[serde(rename = "gpt-5.5-pro")] Gpt5_5Pro, #[serde(rename = "gpt-5.4")] Gpt5_4, #[serde(rename = "gpt-5.4-pro")] Gpt5_4Pro, #[serde(rename = "gpt-5.4-mini")] Gpt5_4Mini, #[serde(rename = "gpt-5.4-nano")] Gpt5_4Nano, #[serde(rename = "gpt-5.3-codex")] Gpt5_3Codex, #[serde(rename = "gpt-5.3-codex-spark")] Gpt5_3Spark, #[serde(rename = "gpt-5.2")] Gpt5_2, #[serde(rename = "gpt-5.2-codex")] Gpt5_2Codex, #[serde(rename = "gpt-5.1")] Gpt5_1, #[serde(rename = "gpt-5.1-codex")] Gpt5_1Codex, #[serde(rename = "gpt-5.1-codex-max")] Gpt5_1CodexMax, #[serde(rename = "gpt-5.1-codex-mini")] Gpt5_1CodexMini, #[serde(rename = "gpt-5")] Gpt5, #[serde(rename = "gpt-5-codex")] Gpt5Codex, #[serde(rename = "gpt-5-nano")] Gpt5Nano, // -- Google protocol models -- #[serde(rename = "gemini-3.1-pro")] Gemini3_1Pro, #[serde(rename = "gemini-3-flash")] Gemini3Flash, #[serde(rename = "gemini-3.5-flash")] Gemini3_5Flash, // -- OpenAI Chat Completions protocol models -- #[serde(rename = "deepseek-v4-pro")] DeepSeekV4Pro, #[serde(rename = "deepseek-v4-flash")] DeepSeekV4Flash, #[serde(rename = "minimax-m2.5")] MiniMaxM2_5, #[serde(rename = "glm-5")] Glm5, #[serde(rename = "glm-5.1")] Glm5_1, #[serde(rename = "glm-5.2")] Glm5_2, #[serde(rename = "grok-build-0.1")] GrokBuild0_1, #[serde(rename = "grok-4.5")] Grok4_5, #[serde(rename = "kimi-k2.5")] KimiK2_5, #[serde(rename = "kimi-k2.6")] KimiK2_6, #[serde(rename = "kimi-k2.7-code")] KimiK2_7Code, #[serde(rename = "minimax-m2.7")] MiniMaxM2_7, #[serde(rename = "minimax-m3")] MiniMaxM3, #[serde(rename = "mimo-v2.5-pro")] MimoV2_5Pro, #[serde(rename = "mimo-v2.5")] MimoV2_5, #[serde(rename = "big-pickle")] BigPickle, #[serde(rename = "nemotron-3-ultra-free")] Nemotron3UltraFree, #[serde(rename = "qwen3.5-plus")] Qwen3_5Plus, #[serde(rename = "qwen3.6-plus")] Qwen3_6Plus, #[serde(rename = "qwen3.7-plus")] Qwen3_7Plus, #[serde(rename = "qwen3.7-max")] Qwen3_7Max, // -- Custom model -- #[serde(rename = "custom")] Custom { name: String, display_name: Option<String>, max_tokens: u64, max_output_tokens: Option<u64>, protocol: ApiProtocol, reasoning_effort_levels: Option<Vec<ReasoningEffort>>, custom_model_api_url: Option<String>, interleaved_reasoning: bool, }, } impl Model { pub fn default_fast() -> Self { Self::ClaudeHaiku4_5 } pub fn default_go() -> Self { Self::KimiK2_6 } pub fn default_go_fast() -> Self { Self::MiniMaxM2_7 } pub fn default_free() -> Self { Self::BigPickle } pub fn default_free_fast() -> Self { Self::Nemotron3UltraFree } pub fn available_subscriptions(&self) -> &'static [OpenCodeSubscription] { match self { // Models available in both Zen and Go Self::Glm5_1 | Self::Glm5_2 | Self::KimiK2_6 | Self::KimiK2_7Code | Self::MiniMaxM2_7 | Self::MiniMaxM3 | Self::DeepSeekV4Pro | Self::DeepSeekV4Flash | Self::Qwen3_6Plus => &[OpenCodeSubscription::Zen, OpenCodeSubscription::Go], // Go-only models Self::MimoV2_5Pro | Self::MimoV2_5 | Self::Qwen3_7Plus | Self::Qwen3_7Max => { &[OpenCodeSubscription::Go] } // Deprecated on Go (per models.dev); still offered on Zen Self::Glm5 | Self::KimiK2_5 | Self::MiniMaxM2_5 | Self::Qwen3_5Plus => { &[OpenCodeSubscription::Zen] } // Free models Self::Nemotron3UltraFree | Self::BigPickle => &[OpenCodeSubscription::Free], // Custom models get their subscription from settings, not from here Self::Custom { .. } => &[], // All other built-in models are Zen-only _ => &[OpenCodeSubscription::Zen], } } pub fn id(&self) -> &str { match self { Self::ClaudeOpus4_8 => "claude-opus-4-8", Self::ClaudeOpus4_7 => "claude-opus-4-7", Self::ClaudeOpus4_6 => "claude-opus-4-6", Self::ClaudeOpus4_5 => "claude-opus-4-5", Self::ClaudeOpus4_1 => "claude-opus-4-1", Self::ClaudeSonnet4_6 => "claude-sonnet-4-6", Self::ClaudeSonnet4_5 => "claude-sonnet-4-5", Self::ClaudeSonnet4 => "claude-sonnet-4", Self::ClaudeHaiku4_5 => "claude-haiku-4-5", Self::ClaudeSonnet5 => "claude-sonnet-5", Self::ClaudeFable5 => "claude-fable-5", Self::Gpt5_6Sol => "gpt-5.6-sol", Self::Gpt5_6Terra => "gpt-5.6-terra", Self::Gpt5_6Luna => "gpt-5.6-luna", Self::Gpt5_5 => "gpt-5.5", Self::Gpt5_5Pro => "gpt-5.5-pro", Self::Gpt5_4 => "gpt-5.4", Self::Gpt5_4Pro => "gpt-5.4-pro", Self::Gpt5_4Mini => "gpt-5.4-mini", Self::Gpt5_4Nano => "gpt-5.4-nano", Self::Gpt5_3Codex => "gpt-5.3-codex", Self::Gpt5_3Spark => "gpt-5.3-codex-spark", Self::Gpt5_2 => "gpt-5.2", Self::Gpt5_2Codex => "gpt-5.2-codex", Self::Gpt5_1 => "gpt-5.1", Self::Gpt5_1Codex => "gpt-5.1-codex", Self::Gpt5_1CodexMax => "gpt-5.1-codex-max", Self::Gpt5_1CodexMini => "gpt-5.1-codex-mini", Self::Gpt5 => "gpt-5", Self::Gpt5Codex => "gpt-5-codex", Self::Gpt5Nano => "gpt-5-nano", Self::Gemini3_1Pro => "gemini-3.1-pro", Self::Gemini3Flash => "gemini-3-flash", Self::Gemini3_5Flash => "gemini-3.5-flash", Self::DeepSeekV4Pro => "deepseek-v4-pro", Self::DeepSeekV4Flash => "deepseek-v4-flash", Self::MiniMaxM2_5 => "minimax-m2.5", Self::Glm5 => "glm-5", Self::Glm5_1 => "glm-5.1", Self::Glm5_2 => "glm-5.2", Self::GrokBuild0_1 => "grok-build-0.1", Self::Grok4_5 => "grok-4.5", Self::KimiK2_5 => "kimi-k2.5", Self::KimiK2_6 => "kimi-k2.6", Self::KimiK2_7Code => "kimi-k2.7-code", Self::MiniMaxM2_7 => "minimax-m2.7", Self::MiniMaxM3 => "minimax-m3", Self::MimoV2_5Pro => "mimo-v2.5-pro", Self::MimoV2_5 => "mimo-v2.5", Self::Qwen3_5Plus => "qwen3.5-plus", Self::Qwen3_6Plus => "qwen3.6-plus", Self::Qwen3_7Plus => "qwen3.7-plus", Self::Qwen3_7Max => "qwen3.7-max", Self::BigPickle => "big-pickle", Self::Nemotron3UltraFree => "nemotron-3-ultra-free", Self::Custom { name, .. } => name, } } pub fn display_name(&self) -> &str { match self { Self::ClaudeOpus4_8 => "Claude Opus 4.8", Self::ClaudeOpus4_7 => "Claude Opus 4.7", Self::ClaudeOpus4_6 => "Claude Opus 4.6", Self::ClaudeOpus4_5 => "Claude Opus 4.5", Self::ClaudeOpus4_1 => "Claude Opus 4.1", Self::ClaudeSonnet4_6 => "Claude Sonnet 4.6", Self::ClaudeSonnet4_5 => "Claude Sonnet 4.5", Self::ClaudeSonnet4 => "Claude Sonnet 4", Self::ClaudeHaiku4_5 => "Claude Haiku 4.5", Self::ClaudeSonnet5 => "Claude Sonnet 5", Self::ClaudeFable5 => "Claude Fable 5", Self::Gpt5_6Sol => "GPT 5.6 Sol", Self::Gpt5_6Terra => "GPT 5.6 Terra", Self::Gpt5_6Luna => "GPT 5.6 Luna", Self::Gpt5_5 => "GPT 5.5", Self::Gpt5_5Pro => "GPT 5.5 Pro", Self::Gpt5_4 => "GPT 5.4", Self::Gpt5_4Pro => "GPT 5.4 Pro", Self::Gpt5_4Mini => "GPT 5.4 Mini", Self::Gpt5_4Nano => "GPT 5.4 Nano", Self::Gpt5_3Codex => "GPT 5.3 Codex", Self::Gpt5_3Spark => "GPT 5.3 Codex Spark", Self::Gpt5_2 => "GPT 5.2", Self::Gpt5_2Codex => "GPT 5.2 Codex", Self::Gpt5_1 => "GPT 5.1", Self::Gpt5_1Codex => "GPT 5.1 Codex", Self::Gpt5_1CodexMax => "GPT 5.1 Codex Max", Self::Gpt5_1CodexMini => "GPT 5.1 Codex Mini", Self::Gpt5 => "GPT 5", Self::Gpt5Codex => "GPT 5 Codex", Self::Gpt5Nano => "GPT 5 Nano", Self::Gemini3_1Pro => "Gemini 3.1 Pro", Self::Gemini3Flash => "Gemini 3 Flash", Self::Gemini3_5Flash => "Gemini 3.5 Flash", Self::DeepSeekV4Pro => "DeepSeek V4 Pro", Self::DeepSeekV4Flash => "DeepSeek V4 Flash", Self::MiniMaxM2_5 => "MiniMax M2.5", Self::Glm5 => "GLM 5", Self::Glm5_1 => "GLM 5.1", Self::Glm5_2 => "GLM 5.2", Self::GrokBuild0_1 => "Grok Build 0.1", Self::Grok4_5 => "Grok 4.5", Self::KimiK2_5 => "Kimi K2.5", Self::KimiK2_6 => "Kimi K2.6", Self::KimiK2_7Code => "Kimi K2.7 Code", Self::MiniMaxM2_7 => "MiniMax M2.7", Self::MiniMaxM3 => "MiniMax M3", Self::MimoV2_5Pro => "MiMo V2.5 Pro", Self::MimoV2_5 => "MiMo V2.5", Self::Qwen3_5Plus => "Qwen3.5 Plus", Self::Qwen3_6Plus => "Qwen3.6 Plus", Self::Qwen3_7Plus => "Qwen3.7 Plus", Self::Qwen3_7Max => "Qwen3.7 Max", Self::BigPickle => "Big Pickle", Self::Nemotron3UltraFree => "Nemotron 3 Ultra Free", Self::Custom { name, display_name, .. } => display_name.as_deref().unwrap_or(name), } } pub fn protocol(&self, subscription: OpenCodeSubscription) -> ApiProtocol { match self { // Models offered by OpenCode have the same configuration across subscriptions // with one outlier: non-free MiniMax models Self::MiniMaxM3 | Self::MiniMaxM2_7 | Self::MiniMaxM2_5 => { if subscription == OpenCodeSubscription::Zen { ApiProtocol::OpenAiChat } else { ApiProtocol::Anthropic } } Self::ClaudeFable5 | Self::ClaudeOpus4_8 | Self::ClaudeOpus4_7 | Self::ClaudeOpus4_6 | Self::ClaudeOpus4_5 | Self::ClaudeOpus4_1 | Self::ClaudeSonnet5 | Self::ClaudeSonnet4_6 | Self::ClaudeSonnet4_5 | Self::ClaudeSonnet4 | Self::ClaudeHaiku4_5 => ApiProtocol::Anthropic, Self::Gpt5_6Sol | Self::Gpt5_6Terra | Self::Gpt5_6Luna | Self::Gpt5_5 | Self::Gpt5_5Pro | Self::Gpt5_4 | Self::Gpt5_4Pro | Self::Gpt5_4Mini | Self::Gpt5_4Nano | Self::Gpt5_3Codex | Self::Gpt5_3Spark | Self::Gpt5_2 | Self::Gpt5_2Codex | Self::Gpt5_1 | Self::Gpt5_1Codex | Self::Gpt5_1CodexMax | Self::Gpt5_1CodexMini | Self::Gpt5 | Self::Gpt5Codex | Self::Gpt5Nano => ApiProtocol::OpenAiResponses, Self::Gemini3_1Pro | Self::Gemini3Flash | Self::Gemini3_5Flash => ApiProtocol::Google, Self::Qwen3_7Max | Self::Qwen3_7Plus | Self::Qwen3_6Plus | Self::Qwen3_5Plus => { ApiProtocol::Anthropic } Self::Glm5 | Self::Glm5_1 | Self::Glm5_2 | Self::GrokBuild0_1 | Self::Grok4_5 | Self::KimiK2_5 | Self::KimiK2_6 | Self::KimiK2_7Code | Self::MimoV2_5Pro | Self::MimoV2_5 | Self::DeepSeekV4Pro | Self::DeepSeekV4Flash | Self::BigPickle | Self::Nemotron3UltraFree => ApiProtocol::OpenAiChat, Self::Custom { protocol, .. } => *protocol, } } pub fn interleaved_reasoning(&self) -> bool { match self { Self::DeepSeekV4Pro | Self::DeepSeekV4Flash | Self::KimiK2_5 | Self::KimiK2_6 | Self::KimiK2_7Code | Self::MimoV2_5 | Self::MimoV2_5Pro | Self::Glm5 | Self::Glm5_1 | Self::Glm5_2 | Self::MiniMaxM2_5 | Self::MiniMaxM2_7 | Self::MiniMaxM3 | Self::Nemotron3UltraFree | Self::BigPickle => true, Self::Custom { interleaved_reasoning, .. } => *interleaved_reasoning, _ => false, } } pub fn max_token_count(&self, subscription: OpenCodeSubscription) -> u64 { match self { // Anthropic models Self::ClaudeOpus4_8 | Self::ClaudeOpus4_7 => 1_000_000, Self::ClaudeOpus4_6 | Self::ClaudeSonnet4_6 => 1_000_000, Self::ClaudeSonnet4_5 => 1_000_000, Self::ClaudeOpus4_5 | Self::ClaudeHaiku4_5 => 200_000, Self::ClaudeOpus4_1 => 200_000, Self::ClaudeSonnet4 => 1_000_000, Self::ClaudeSonnet5 => 1_000_000, Self::ClaudeFable5 => 1_000_000, // OpenAI models Self::Gpt5_6Sol | Self::Gpt5_6Terra | Self::Gpt5_6Luna => 1_050_000, Self::Gpt5_5 | Self::Gpt5_5Pro => 1_050_000, Self::Gpt5_4 | Self::Gpt5_4Pro => 1_050_000, Self::Gpt5_4Mini | Self::Gpt5_4Nano => 400_000, Self::Gpt5_3Codex => 400_000, Self::Gpt5_3Spark => 128_000, Self::Gpt5_2 | Self::Gpt5_2Codex => 400_000, Self::Gpt5_1 | Self::Gpt5_1Codex | Self::Gpt5_1CodexMax | Self::Gpt5_1CodexMini => { 400_000 } Self::Gpt5 | Self::Gpt5Codex | Self::Gpt5Nano => 400_000,