Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions crates/language_models/src/provider/opencode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use language_model::{
SubPageProviderSettings, env_var,
};
use opencode::{ApiProtocol, OPENCODE_API_URL, OpenCodeSubscription};
pub use settings::OpenCodeApiProtocol;
pub use settings::OpenCodeAvailableModel as AvailableModel;
use settings::{Settings, SettingsStore, update_settings_file};
use std::sync::{Arc, LazyLock};
Expand Down Expand Up @@ -263,12 +264,12 @@ impl LanguageModelProvider for OpenCodeLanguageModelProvider {
}

for model in &settings.available_models {
let protocol = match model.protocol.as_str() {
"anthropic" => ApiProtocol::Anthropic,
"openai_responses" => ApiProtocol::OpenAiResponses,
"openai_chat" => ApiProtocol::OpenAiChat,
"google" => ApiProtocol::Google,
_ => ApiProtocol::OpenAiChat, // default fallback
let protocol = match model.protocol {
Some(OpenCodeApiProtocol::Anthropic) => ApiProtocol::Anthropic,
Some(OpenCodeApiProtocol::OpenAiResponses) => ApiProtocol::OpenAiResponses,
Some(OpenCodeApiProtocol::OpenAiChat) => ApiProtocol::OpenAiChat,
Some(OpenCodeApiProtocol::Google) => ApiProtocol::Google,
None => ApiProtocol::OpenAiChat, // default fallback
};
let subscription = match model.subscription {
Some(settings::OpenCodeModelSubscription::Go) => OpenCodeSubscription::Go,
Expand Down Expand Up @@ -574,6 +575,12 @@ impl LanguageModel for OpenCodeLanguageModel {
.is_some_and(|levels| levels.iter().any(|effort| *effort != ReasoningEffort::None))
}

fn supports_disabling_thinking(&self) -> bool {
self.model
.supported_reasoning_effort_levels()
.is_some_and(|levels| levels.contains(&ReasoningEffort::None))
}

fn supported_effort_levels(&self) -> Vec<LanguageModelEffortLevel> {
self.model
.supported_reasoning_effort_levels()
Expand Down Expand Up @@ -692,7 +699,7 @@ impl LanguageModel for OpenCodeLanguageModel {
let openai_request = into_open_ai(
request,
self.model.id(),
false,
true,
false,
self.model.max_output_tokens(self.subscription),
ChatCompletionMaxTokensParameter::MaxCompletionTokens,
Expand All @@ -715,7 +722,7 @@ impl LanguageModel for OpenCodeLanguageModel {
let response_request = into_open_ai_response(
request,
self.model.id(),
false,
true,
false,
self.model.max_output_tokens(self.subscription),
None,
Expand All @@ -730,11 +737,14 @@ impl LanguageModel for OpenCodeLanguageModel {
.boxed()
}
ApiProtocol::Google => {
let google_request = into_google(
request,
self.model.id().to_string(),
google_ai::GoogleModelMode::Default,
);
let mode = if self.supports_thinking() && request.thinking_allowed {
google_ai::GoogleModelMode::Thinking {
budget_tokens: None,
}
} else {
google_ai::GoogleModelMode::Default
};
let google_request = into_google(request, self.model.id().to_string(), mode);
let stream = self.stream_google(google_request, http_client, extra_headers, cx);
async move {
let mapper = GoogleEventMapper::new();
Expand Down
162 changes: 141 additions & 21 deletions crates/opencode/src/opencode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ pub enum Model {
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.5")]
Expand Down Expand Up @@ -203,23 +207,24 @@ impl Model {
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
| Self::KimiK2_7Code
| Self::Glm5_2
| Self::MiniMaxM3 => &[OpenCodeSubscription::Go],
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 => &[OpenCodeSubscription::Zen],
Self::Glm5 | Self::KimiK2_5 | Self::MiniMaxM2_5 | Self::Qwen3_5Plus => {
&[OpenCodeSubscription::Zen]
}

// Free models
Self::Nemotron3UltraFree | Self::BigPickle => &[OpenCodeSubscription::Free],
Expand All @@ -243,6 +248,8 @@ impl Model {
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_5 => "gpt-5.5",
Self::Gpt5_5Pro => "gpt-5.5-pro",
Expand Down Expand Up @@ -302,6 +309,8 @@ impl Model {
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_5 => "GPT 5.5",
Self::Gpt5_5Pro => "GPT 5.5 Pro",
Expand Down Expand Up @@ -356,19 +365,21 @@ impl Model {
match self {
// Models offered by OpenCode have the same configuration across subscriptions
// with one outlier: non-free MiniMax models
Self::MiniMaxM2_7 | Self::MiniMaxM2_5 => {
Self::MiniMaxM3 | Self::MiniMaxM2_7 | Self::MiniMaxM2_5 => {
if subscription == OpenCodeSubscription::Zen {
ApiProtocol::OpenAiChat
} else {
ApiProtocol::Anthropic
}
}

Self::ClaudeOpus4_8
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
Expand All @@ -394,9 +405,9 @@ impl Model {

Self::Gemini3_1Pro | Self::Gemini3Flash | Self::Gemini3_5Flash => ApiProtocol::Google,

Self::Qwen3_7Max | Self::Qwen3_7Plus => ApiProtocol::Anthropic,

Self::MiniMaxM3 => ApiProtocol::Anthropic,
Self::Qwen3_7Max | Self::Qwen3_7Plus | Self::Qwen3_6Plus | Self::Qwen3_5Plus => {
ApiProtocol::Anthropic
}

Self::Glm5
| Self::Glm5_1
Expand All @@ -407,8 +418,6 @@ impl Model {
| Self::KimiK2_7Code
| Self::MimoV2_5Pro
| Self::MimoV2_5
| Self::Qwen3_5Plus
| Self::Qwen3_6Plus
| Self::DeepSeekV4Pro
| Self::DeepSeekV4Flash
| Self::BigPickle
Expand All @@ -432,6 +441,7 @@ impl Model {
| Self::Glm5_2
| Self::MiniMaxM2_5
| Self::MiniMaxM2_7
| Self::MiniMaxM3
| Self::Nemotron3UltraFree
| Self::BigPickle => true,

Expand All @@ -453,6 +463,8 @@ impl Model {
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_5 | Self::Gpt5_5Pro => 1_050_000,
Expand All @@ -473,7 +485,13 @@ impl Model {

// OpenAI-compatible models
Self::MiniMaxM2_7 => 204_800,
Self::MiniMaxM3 => 512_000,
Self::MiniMaxM3 => {
if subscription == OpenCodeSubscription::Go {
1_000_000
} else {
512_000
}
}
Self::MiniMaxM2_5 => 204_800,
Self::Glm5 | Self::Glm5_1 => {
if subscription == OpenCodeSubscription::Go {
Expand Down Expand Up @@ -514,6 +532,8 @@ impl Model {
| Self::ClaudeHaiku4_5
| Self::ClaudeSonnet4 => Some(64_000),
Self::ClaudeOpus4_1 => Some(32_000),
Self::ClaudeSonnet5 => Some(128_000),
Self::ClaudeFable5 => Some(128_000),

// OpenAI models
Self::Gpt5_5
Expand All @@ -539,7 +559,13 @@ impl Model {

// OpenAI-compatible models
Self::MiniMaxM2_7 => Some(131_072),
Self::MiniMaxM3 => Some(131_072),
Self::MiniMaxM3 => {
if subscription == OpenCodeSubscription::Go {
Some(131_072)
} else {
Some(128_000)
}
}
Self::MiniMaxM2_5 => {
if subscription == OpenCodeSubscription::Go {
Some(65_536)
Expand Down Expand Up @@ -587,7 +613,9 @@ impl Model {
| Self::ClaudeSonnet4_6
| Self::ClaudeSonnet4_5
| Self::ClaudeSonnet4
| Self::ClaudeHaiku4_5 => true,
| Self::ClaudeHaiku4_5
| Self::ClaudeSonnet5
| Self::ClaudeFable5 => true,

// OpenAI models support images
Self::Gpt5_5
Expand Down Expand Up @@ -649,27 +677,119 @@ impl Model {

pub fn supported_reasoning_effort_levels(&self) -> Option<Vec<ReasoningEffort>> {
match self {
Self::ClaudeOpus4_8 => Some(vec![
// Anthropic models
Self::ClaudeFable5
| Self::ClaudeOpus4_8
| Self::ClaudeOpus4_7
| Self::ClaudeSonnet5 => Some(vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::XHigh,
ReasoningEffort::Max,
]),

Self::Nemotron3UltraFree | Self::MimoV2_5Pro | Self::MimoV2_5 => Some(vec![
Self::ClaudeOpus4_6 | Self::ClaudeSonnet4_6 => Some(vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::Max,
]),

Self::DeepSeekV4Pro | Self::DeepSeekV4Flash => Some(vec![
Self::ClaudeOpus4_5 => Some(vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
]),

// OpenAI models
Self::Gpt5_5
| Self::Gpt5_4
| Self::Gpt5_4Mini
| Self::Gpt5_4Nano
| Self::Gpt5_3Codex
| Self::Gpt5_2 => Some(vec![
ReasoningEffort::None,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::XHigh,
]),

Self::Gpt5_5Pro | Self::Gpt5_4Pro => Some(vec![
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::XHigh,
]),

Self::Gpt5_2Codex | Self::Gpt5_3Spark | Self::Gpt5_1CodexMax => Some(vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
ReasoningEffort::XHigh,
]),

Self::Gpt5_1 => Some(vec![
ReasoningEffort::None,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
]),

Self::Gpt5Codex | Self::Gpt5_1Codex | Self::Gpt5_1CodexMini => Some(vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
]),

Self::Gpt5 | Self::Gpt5Nano => Some(vec![
ReasoningEffort::Minimal,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
]),

// Google models
Self::Gemini3Flash | Self::Gemini3_5Flash => Some(vec![
ReasoningEffort::Minimal,
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
]),

Self::Gemini3_1Pro => Some(vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
]),

// DeepSeek models
Self::DeepSeekV4Pro | Self::DeepSeekV4Flash => Some(vec![
// OpenCode also supports Low&Medium but as per DeepSeek those are mapped to High
ReasoningEffort::High,
ReasoningEffort::Max,
]),

// MiniMax models
Self::MiniMaxM3 => Some(vec![ReasoningEffort::None]),

// NVIDIA models
Self::Nemotron3UltraFree => Some(vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
]),

// Xiaomi MiMo models
Self::MimoV2_5Pro | Self::MimoV2_5 => Some(vec![
ReasoningEffort::Low,
ReasoningEffort::Medium,
ReasoningEffort::High,
]),

// Z AI models
Self::Glm5_2 => Some(vec![ReasoningEffort::High, ReasoningEffort::Max]),

Self::Custom {
reasoning_effort_levels,
..
Expand Down
Loading
Loading