From aa5103ded55b2af334548d6e491e5e6d20baaa97 Mon Sep 17 00:00:00 2001 From: Yanuar Date: Thu, 6 Aug 2026 13:15:56 +0700 Subject: [PATCH 1/2] fix(acp): report model context window --- src/acp/service.rs | 49 ++++++++++++++++++++++++++++++---- src/model/catalog.rs | 1 + src/model/discovery.rs | 7 +++++ src/model/effective_catalog.rs | 5 ++++ src/model/extensions/mod.rs | 2 ++ src/model/extensions/ollama.rs | 8 ++++++ src/model/types.rs | 3 +++ 7 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/acp/service.rs b/src/acp/service.rs index fd82fda..1418942 100644 --- a/src/acp/service.rs +++ b/src/acp/service.rs @@ -293,7 +293,7 @@ impl AcpService { let reasoning = model_reasoning(&config, &models, &provider, &model); let reasoning_selection = reasoning.unwrap_or(crate::model::reasoning::ReasoningEffort::None); - let context_window = model_context_window(&config, &provider, &model); + let context_window = model_context_window(&config, &models, &provider, &model); let agent = config .merged_config .default_agent @@ -474,8 +474,12 @@ impl AcpService { session.provider.clone_from(&model.provider_id); session.model.clone_from(&model.id); session.reasoning = resolved_reasoning(session, session.reasoning_selection); - session.context_window = - model_context_window(&session.config, &session.provider, &session.model); + session.context_window = model_context_window( + &session.config, + &session.models, + &session.provider, + &session.model, + ); Ok(SetSessionConfigOptionResponse::new(session_config_options( session, ))) @@ -551,7 +555,7 @@ impl AcpService { let reasoning = model_reasoning(&config, &models, &provider, &model); let reasoning_selection = reasoning.unwrap_or(crate::model::reasoning::ReasoningEffort::None); - let context_window = model_context_window(&config, &provider, &model); + let context_window = model_context_window(&config, &models, &provider, &model); let skills = crate::skill::SkillStore::load(&config.xdg_config_home, &config.project_root); let session = AcpSession { cwd, @@ -969,7 +973,20 @@ fn model_reasoning_capability( .filter(|capability| !capability.values().is_empty()) } -fn model_context_window(config: &LoadedConfig, provider: &str, model: &str) -> Option { +fn model_context_window( + config: &LoadedConfig, + models: &[crate::model::types::Model], + provider: &str, + model: &str, +) -> Option { + if let Some(context_window) = models + .iter() + .find(|candidate| candidate.provider_id == provider && candidate.id == model) + .and_then(|model| model.context_window) + { + return Some(context_window); + } + let discovery = crate::model::discovery::Discovery::new_with_custom(Some( config.merged_config.custom_providers.clone(), )) @@ -1476,6 +1493,7 @@ mod tests { free: false, local: false, reasoning_options: Vec::new(), + context_window: None, } } @@ -1568,6 +1586,12 @@ mod tests { fn config_with_command(command: crate::command::custom::CustomCommand) -> LoadedConfig { let mut merged_config = crate::config::configuration::MergedConfig::default(); merged_config.commands.push(command); + config_with_merged(merged_config) + } + + fn config_with_merged( + merged_config: crate::config::configuration::MergedConfig, + ) -> LoadedConfig { LoadedConfig { merged_config, raw_merged: serde_json::Value::Null, @@ -1579,6 +1603,10 @@ mod tests { } } + fn empty_config() -> LoadedConfig { + config_with_merged(crate::config::configuration::MergedConfig::default()) + } + fn session_with_config(config: LoadedConfig) -> AcpSession { let skills = crate::skill::SkillStore::load(&config.xdg_config_home, &config.project_root); AcpSession { @@ -1802,6 +1830,17 @@ mod tests { assert!(find_selectable_model(&models, "other/gpt-5").is_err()); } + #[test] + fn resolves_context_window_from_selectable_models() { + let mut model = model("example", "Example", "large-context", "Large Context"); + model.context_window = Some(1_090_000); + + assert_eq!( + model_context_window(&empty_config(), &[model], "example", "large-context"), + Some(1_090_000) + ); + } + #[test] fn preserves_selected_reasoning_effort_when_model_cannot_apply_it() { let model = model("example", "Example", "chat", "Chat"); diff --git a/src/model/catalog.rs b/src/model/catalog.rs index 385fe9c..121303d 100644 --- a/src/model/catalog.rs +++ b/src/model/catalog.rs @@ -124,6 +124,7 @@ mod tests { free: false, local: false, reasoning_options: Vec::new(), + context_window: None, }; assert_eq!(model_ref(&model), "openai/gpt-5"); diff --git a/src/model/discovery.rs b/src/model/discovery.rs index 08098f0..58924f3 100644 --- a/src/model/discovery.rs +++ b/src/model/discovery.rs @@ -283,6 +283,7 @@ impl Discovery { free: false, local: false, reasoning_options: Vec::new(), + context_window: custom_model.context_window, }); } } @@ -714,6 +715,11 @@ impl Discovery { free, local: false, reasoning_options: model.reasoning_options.clone(), + context_window: model + .limit + .as_ref() + .map(|limit| limit.context) + .filter(|context| *context > 0), }); } } @@ -882,6 +888,7 @@ mod tests { free: false, local: false, reasoning_options: Vec::new(), + context_window: None, }; let connected_provider_ids = std::collections::HashSet::new(); let configured_provider_ids = diff --git a/src/model/effective_catalog.rs b/src/model/effective_catalog.rs index 890e662..eb6b2ea 100644 --- a/src/model/effective_catalog.rs +++ b/src/model/effective_catalog.rs @@ -27,6 +27,8 @@ struct SnapshotModel { free: bool, local: bool, reasoning_options: Vec, + #[serde(default)] + context_window: Option, } impl From for SnapshotModel { @@ -42,6 +44,7 @@ impl From for SnapshotModel { free: model.free, local: model.local, reasoning_options: model.reasoning_options, + context_window: model.context_window, } } } @@ -59,6 +62,7 @@ impl From for Model { free: model.free, local: model.local, reasoning_options: model.reasoning_options, + context_window: model.context_window, } } } @@ -179,6 +183,7 @@ mod tests { free: false, local: false, reasoning_options: Vec::new(), + context_window: None, } } diff --git a/src/model/extensions/mod.rs b/src/model/extensions/mod.rs index 98e4a17..8aa9a99 100644 --- a/src/model/extensions/mod.rs +++ b/src/model/extensions/mod.rs @@ -390,6 +390,7 @@ mod tests { free: true, local: false, reasoning_options: Vec::new(), + context_window: None, }; let paid_model = crate::model::types::Model { id: "gpt-5.3-codex".to_string(), @@ -402,6 +403,7 @@ mod tests { free: false, local: false, reasoning_options: Vec::new(), + context_window: None, }; assert!(ModelExtensions::is_available_without_connection( diff --git a/src/model/extensions/ollama.rs b/src/model/extensions/ollama.rs index f48be64..81ab76f 100644 --- a/src/model/extensions/ollama.rs +++ b/src/model/extensions/ollama.rs @@ -161,11 +161,19 @@ pub fn model_for_dialog(model: OllamaModel) -> crate::model::types::Model { free: false, local: true, reasoning_options: Vec::new(), + context_window: discovery_model_for_dialog(&model.id) + .and_then(|model| model.limit) + .map(|limit| limit.context) + .filter(|context| *context > 0), id: model.id, name: model.name, } } +fn discovery_model_for_dialog(id: &str) -> Option { + cached_discovery_models().and_then(|models| models.get(id).cloned()) +} + fn cached_discovery_models( ) -> Option> { let models = cache().lock().ok().and_then(|guard| match guard.clone() { diff --git a/src/model/types.rs b/src/model/types.rs index 8f68c90..ec66c64 100644 --- a/src/model/types.rs +++ b/src/model/types.rs @@ -17,6 +17,8 @@ pub struct Model { pub local: bool, /// Mirrors models.dev `reasoning_options`. pub reasoning_options: Vec, + /// Mirrors models.dev `limit.context` when available. + pub context_window: Option, } impl Model { @@ -142,6 +144,7 @@ mod tests { kind: "effort".to_string(), values: vec!["low".to_string()], }], + context_window: Some(128_000), }; let description = model.dialog_description(); From 912c2ccf4133b05cf4b3123df5315ca9bda6b21a Mon Sep 17 00:00:00 2001 From: Yanuar Date: Thu, 6 Aug 2026 13:27:28 +0700 Subject: [PATCH 2/2] fix(config): align provider filter types --- src/config/configuration.rs | 23 +++++++++++------------ src/model/catalog.rs | 5 ++++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/config/configuration.rs b/src/config/configuration.rs index 7d3ebf2..c7885ea 100644 --- a/src/config/configuration.rs +++ b/src/config/configuration.rs @@ -4,7 +4,7 @@ use crate::tools::{ use anyhow::{anyhow, Context, Result}; use regex::Regex; use serde_json::Value; -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; @@ -110,15 +110,15 @@ fn parse_provider_id_set( value: Option<&Value>, diagnostics: &mut ConfigDiagnostics, key: &str, -) -> BTreeSet { +) -> HashSet { let Some(value) = value else { - return BTreeSet::new(); + return HashSet::new(); }; let Some(entries) = value.as_array() else { diagnostics .warnings .push(format!("{key} must be an array of provider IDs")); - return BTreeSet::new(); + return HashSet::new(); }; entries @@ -422,8 +422,8 @@ pub struct MergedConfig { pub agent_permission_rules: HashMap, pub agent_steps: HashMap, pub provider_timeouts: HashMap, - pub enabled_providers: BTreeSet, - pub disabled_providers: BTreeSet, + pub disabled_providers: HashSet, + pub enabled_providers: Option>, pub custom_providers: HashMap, pub notifications: NotificationsConfig, pub images: ImagesConfig, @@ -1260,12 +1260,11 @@ fn parse_merged_config(merged: &Value, diagnostics: &mut ConfigDiagnostics) -> M ); out.sync_agent_derived_fields(); out.provider_timeouts = parse_provider_timeouts(obj.get("provider"), diagnostics); - out.enabled_providers = parse_provider_id_set( - obj.get("enabled_providers") - .or_else(|| obj.get("enabledProviders")), - diagnostics, - "enabled_providers", - ); + let enabled_providers = obj + .get("enabled_providers") + .or_else(|| obj.get("enabledProviders")); + out.enabled_providers = enabled_providers + .map(|value| parse_provider_id_set(Some(value), diagnostics, "enabled_providers")); out.disabled_providers = parse_provider_id_set( obj.get("disabled_providers") .or_else(|| obj.get("disabledProviders")), diff --git a/src/model/catalog.rs b/src/model/catalog.rs index 121303d..7b2babf 100644 --- a/src/model/catalog.rs +++ b/src/model/catalog.rs @@ -104,7 +104,10 @@ fn provider_is_enabled( provider_id: &str, ) -> bool { !config.disabled_providers.contains(provider_id) - && (config.enabled_providers.is_empty() || config.enabled_providers.contains(provider_id)) + && config + .enabled_providers + .as_ref() + .is_none_or(|enabled| enabled.contains(provider_id)) } #[cfg(test)]