diff --git a/crates/ide-agent/src/lib.rs b/crates/ide-agent/src/lib.rs index 7116047f1..708ac8f11 100644 --- a/crates/ide-agent/src/lib.rs +++ b/crates/ide-agent/src/lib.rs @@ -125,6 +125,11 @@ impl Shared { fn known_config(&self, provider: &str) -> Vec { let mut config = self.known.lock().get(provider).map(|k| k.config.clone()).unwrap_or_default(); + // A thinking level's values depend on the model (low/medium/high on one, none or a + // different set on another), but what ran last is remembered wholesale — after a + // model switch the New Task page would offer the previous model's levels. Until the + // session reports the new model's own, don't show the option at all. + config.retain(|o| o.category.as_deref() != Some("thought_level")); config::add_client_options(provider, &mut config); if let Some((o, v)) = config::bypass_mode(&config).map(|(o, v)| (o.id.clone(), v.value.clone())) && let Some(o) = config.iter_mut().find(|c| c.id == o) diff --git a/crates/ide-agent/src/tests.rs b/crates/ide-agent/src/tests.rs index e1da00d19..d92ad84f3 100644 --- a/crates/ide-agent/src/tests.rs +++ b/crates/ide-agent/src/tests.rs @@ -11,6 +11,8 @@ struct FakeAgent { calls: Mutex>, /// Without `loadSession` (a session lives only as long as its process). no_load: bool, + /// Config options `session/new` reports after the model one (per-model efforts, say). + extra_options: Mutex>, } impl FakeAgent { @@ -94,10 +96,15 @@ impl Handler for FakeAgent { Ok(json!({"protocolVersion": 1, "agentCapabilities": {"loadSession": !self.no_load, "sessionCapabilities": {"fork": {}}, "_meta": {"openidea": {"rollback": true, "steer": true}}}})) } - "session/new" => Ok(json!({"sessionId": "s1", + "session/new" => { + let mut options = vec![json!({"id": "model", "category": "model", "type": "select", + "currentValue": "big", + "options": [{"value": "big", "name": "Big"}, {"value": "small", "name": "Small"}]})]; + options.extend(self.extra_options.lock().iter().cloned()); + Ok(json!({"sessionId": "s1", "modes": {"currentModeId": "ask", "availableModes": [{"id": "ask", "name": "Ask"}, {"id": "code", "name": "Code"}]}, - "configOptions": [{"id": "model", "category": "model", "type": "select", "currentValue": "big", - "options": [{"value": "big", "name": "Big"}, {"value": "small", "name": "Small"}]}]})), + "configOptions": options})) + } "session/fork" => Ok(json!({"sessionId": "s2"})), "_openidea/rollback" | "_openidea/steer" => { let detail = params.get("turns").cloned().unwrap_or_else(|| params["prompt"][0]["text"].clone()); @@ -788,6 +795,35 @@ async fn usage_limits_and_late_settings() { assert_eq!(config.iter().find(|o| o.id == "effort").unwrap().current.as_deref(), Some("high")); } +#[tokio::test(flavor = "multi_thread")] +async fn remembered_settings_drop_per_model_choices_for_the_next_task() { + let tmp = tempfile::tempdir().unwrap(); + let agent = Arc::new(FakeAgent::default()); + // The effort list depends on the model: what the last session ran means nothing once + // the user picks another model, so the New Task page shows the option without a choice. + *agent.extra_options.lock() = vec![json!({"id": "effort", "category": "thought_level", + "type": "select", "currentValue": "high", + "options": [{"value": "low", "name": "Low"}, {"value": "high", "name": "High"}]})]; + let mgr = manager(tmp.path(), agent.clone()); + let task = mgr + .create(NewAgentTask { + project_root: None, + prompt: text("again"), + provider: "claude".into(), + config: vec![], + isolation: Isolation::Local, + base: None, + }) + .await + .unwrap(); + wait_for(&mgr, &task.id, AgentStatus::Ready).await; + + let provider = mgr.providers().into_iter().find(|p| p.id == "claude").unwrap(); + assert!(provider.config.iter().all(|o| o.id != "effort"), "a per-model option isn't offered stale"); + let model = provider.config.iter().find(|o| o.id == "model").unwrap(); + assert_eq!(model.current.as_deref(), Some("big"), "the model choice survives"); +} + #[test] fn retry_updates_become_a_countdown() { let update =