906 lines · 29.8 KB · Rust
RawDownload
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.