Detailed changes
@@ -26,10 +26,11 @@ use gpui::{
use indoc::indoc;
use language_model::{
CompletionIntent, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
- LanguageModelId, LanguageModelProviderName, LanguageModelRegistry, LanguageModelRequest,
- LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolSchemaFormat,
- LanguageModelToolUse, MessageContent, Role, StopReason, TokenUsage,
- fake_provider::FakeLanguageModel,
+ LanguageModelId, LanguageModelProviderId, LanguageModelProviderName, LanguageModelRegistry,
+ LanguageModelRequest, LanguageModelRequestMessage, LanguageModelToolResult,
+ LanguageModelToolSchemaFormat, LanguageModelToolUse, MessageContent, Role, StopReason,
+ TokenUsage,
+ fake_provider::{FakeLanguageModel, FakeLanguageModelProvider},
};
use pretty_assertions::assert_eq;
use project::{
@@ -40,7 +41,7 @@ use reqwest_client::ReqwestClient;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
-use settings::{Settings, SettingsStore};
+use settings::{LanguageModelProviderSetting, LanguageModelSelection, Settings, SettingsStore};
use std::{
path::Path,
pin::Pin,
@@ -5495,6 +5496,90 @@ async fn test_subagent_thread_inherits_parent_thread_properties(cx: &mut TestApp
});
}
+#[gpui::test]
+async fn test_subagent_thread_uses_configured_subagent_model(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(path!("/test"), json!({})).await;
+ let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
+ let project_context = cx.new(|_cx| ProjectContext::default());
+ let context_server_store = project.read_with(cx, |project, _| project.context_server_store());
+ let context_server_registry =
+ cx.new(|cx| ContextServerRegistry::new(context_server_store.clone(), cx));
+ let parent_model = Arc::new(FakeLanguageModel::default());
+ let subagent_model = Arc::new(FakeLanguageModel::with_id_and_thinking(
+ "fake-corp",
+ "subagent-model",
+ "Subagent Model",
+ true,
+ ));
+
+ cx.update(|cx| {
+ LanguageModelRegistry::test(cx);
+
+ let provider = Arc::new(
+ FakeLanguageModelProvider::new(
+ LanguageModelProviderId::from("fake-corp".to_string()),
+ LanguageModelProviderName::from("Fake Corp".to_string()),
+ )
+ .with_models(vec![subagent_model.clone()]),
+ );
+ LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
+ registry.register_provider(provider, cx);
+ });
+
+ let mut settings = agent_settings::AgentSettings::get_global(cx).clone();
+ settings.subagent_model = Some(LanguageModelSelection {
+ provider: LanguageModelProviderSetting("fake-corp".to_string()),
+ model: "subagent-model".to_string(),
+ enable_thinking: true,
+ effort: Some("high".to_string()),
+ speed: None,
+ });
+ agent_settings::AgentSettings::override_global(settings, cx);
+ });
+
+ let parent_thread = cx.new(|cx| {
+ Thread::new(
+ project.clone(),
+ project_context,
+ context_server_registry,
+ Templates::new(),
+ Some(parent_model.clone()),
+ cx,
+ )
+ });
+
+ let subagent_thread = cx.new(|cx| Thread::new_subagent(&parent_thread, cx));
+ subagent_thread.read_with(cx, |subagent_thread, _cx| {
+ assert_eq!(
+ subagent_thread.model().map(|model| model.id()),
+ Some(subagent_model.id())
+ );
+ assert!(subagent_thread.thinking_enabled());
+ assert_eq!(subagent_thread.thinking_effort(), Some(&"high".to_string()));
+ });
+
+ parent_thread.update(cx, |parent_thread, _cx| {
+ parent_thread.register_running_subagent(subagent_thread.downgrade());
+ });
+ parent_thread.update(cx, |parent_thread, cx| {
+ parent_thread.set_model(parent_model.clone(), cx);
+ parent_thread.set_thinking_enabled(false, cx);
+ parent_thread.set_thinking_effort(None, cx);
+ });
+
+ subagent_thread.read_with(cx, |subagent_thread, _cx| {
+ assert_eq!(
+ subagent_thread.model().map(|model| model.id()),
+ Some(subagent_model.id())
+ );
+ assert!(subagent_thread.thinking_enabled());
+ assert_eq!(subagent_thread.thinking_effort(), Some(&"high".to_string()));
+ });
+}
+
#[gpui::test]
async fn test_max_subagent_depth_prevents_tool_registration(cx: &mut TestAppContext) {
init_test(cx);
@@ -984,6 +984,7 @@ pub struct Thread {
ui_scroll_position: Option<gpui::ListOffset>,
/// Weak references to running subagent threads for cancellation propagation
running_subagents: Vec<WeakEntity<Thread>>,
+ inherits_parent_model_settings: bool,
}
impl Thread {
@@ -1017,6 +1018,10 @@ impl Thread {
depth: parent_thread.read(cx).depth() + 1,
});
thread.inherit_parent_settings(parent_thread, cx);
+ if let Some(subagent_model) = AgentSettings::get_global(cx).subagent_model.clone() {
+ thread.inherits_parent_model_settings = false;
+ thread.apply_model_selection(&subagent_model, cx);
+ }
thread
}
@@ -1105,6 +1110,7 @@ impl Thread {
draft_prompt: None,
ui_scroll_position: None,
running_subagents: Vec::new(),
+ inherits_parent_model_settings: true,
}
}
@@ -1121,6 +1127,29 @@ impl Thread {
self.profile_id = parent.profile_id.clone();
}
+ fn apply_model_selection(
+ &mut self,
+ selection: &LanguageModelSelection,
+ cx: &mut Context<Self>,
+ ) {
+ let Some(model) = Self::resolve_model_from_selection(selection, cx) else {
+ log::warn!(
+ "failed to resolve configured subagent model: {}/{}",
+ selection.provider.0,
+ selection.model
+ );
+ return;
+ };
+
+ self.model = Some(model.clone());
+ self.thinking_enabled = selection.enable_thinking && model.supports_thinking();
+ self.thinking_effort = selection.effort.clone();
+ self.speed = selection.speed.filter(|_| model.supports_fast_mode());
+ self.prompt_capabilities_tx
+ .send(Self::prompt_capabilities(self.model.as_deref()))
+ .log_err();
+ }
+
pub fn id(&self) -> &acp::SessionId {
&self.id
}
@@ -1338,6 +1367,7 @@ impl Thread {
offset_in_item: gpui::px(sp.offset_in_item),
}),
running_subagents: Vec::new(),
+ inherits_parent_model_settings: true,
}
}
@@ -1441,7 +1471,11 @@ impl Thread {
for subagent in &self.running_subagents {
subagent
- .update(cx, |thread, cx| thread.set_model(model.clone(), cx))
+ .update(cx, |thread, cx| {
+ if thread.inherits_parent_model_settings {
+ thread.set_model(model.clone(), cx);
+ }
+ })
.ok();
}
@@ -1478,7 +1512,11 @@ impl Thread {
for subagent in &self.running_subagents {
subagent
- .update(cx, |thread, cx| thread.set_thinking_enabled(enabled, cx))
+ .update(cx, |thread, cx| {
+ if thread.inherits_parent_model_settings {
+ thread.set_thinking_enabled(enabled, cx);
+ }
+ })
.ok();
}
cx.notify();
@@ -1494,7 +1532,9 @@ impl Thread {
for subagent in &self.running_subagents {
subagent
.update(cx, |thread, cx| {
- thread.set_thinking_effort(effort.clone(), cx)
+ if thread.inherits_parent_model_settings {
+ thread.set_thinking_effort(effort.clone(), cx)
+ }
})
.ok();
}
@@ -1510,7 +1550,11 @@ impl Thread {
for subagent in &self.running_subagents {
subagent
- .update(cx, |thread, cx| thread.set_speed(speed, cx))
+ .update(cx, |thread, cx| {
+ if thread.inherits_parent_model_settings {
+ thread.set_speed(speed, cx);
+ }
+ })
.ok();
}
cx.notify();
@@ -576,6 +576,7 @@ mod tests {
default_height: px(600.),
max_content_width: Some(px(850.)),
default_model: None,
+ subagent_model: None,
inline_assistant_model: None,
inline_assistant_use_streaming_tools: false,
commit_message_model: None,
@@ -144,6 +144,7 @@ pub struct AgentSettings {
pub default_height: Pixels,
pub max_content_width: Option<Pixels>,
pub default_model: Option<LanguageModelSelection>,
+ pub subagent_model: Option<LanguageModelSelection>,
pub inline_assistant_model: Option<LanguageModelSelection>,
pub inline_assistant_use_streaming_tools: bool,
pub commit_message_model: Option<LanguageModelSelection>,
@@ -640,6 +641,7 @@ impl Settings for AgentSettings {
},
flexible: agent.flexible.unwrap(),
default_model: Some(agent.default_model.unwrap()),
+ subagent_model: agent.subagent_model,
inline_assistant_model: agent.inline_assistant_model,
inline_assistant_use_streaming_tools: agent
.inline_assistant_use_streaming_tools
@@ -694,6 +694,7 @@ mod tests {
default_height: px(600.),
max_content_width: Some(px(850.)),
default_model: None,
+ subagent_model: None,
inline_assistant_model: None,
inline_assistant_use_streaming_tools: false,
commit_message_model: None,
@@ -118,6 +118,8 @@ pub struct AgentSettingsContent {
pub max_content_width: Option<f32>,
/// The default model to use when creating new chats and for other features when a specific model is not specified.
pub default_model: Option<LanguageModelSelection>,
+ /// The model to use for subagents spawned via the `spawn_agent` tool. Defaults to the parent agent's model when not specified.
+ pub subagent_model: Option<LanguageModelSelection>,
/// Favorite models to show at the top of the model selector.
#[serde(default)]
pub favorite_models: Vec<LanguageModelSelection>,
@@ -31,6 +31,7 @@ You can assign distinct and specific models for the following AI-powered feature
- Thread summary model: Used for generating thread summaries
- Inline assistant model: Used for the inline assistant feature
- Commit message model: Used for generating Git commit messages
+- Subagent model: Used for subagents spawned by the Agent Panel. If not set, the subagent will inherit the model settings from the parent thread.
```json [settings]
{
@@ -50,6 +51,10 @@ You can assign distinct and specific models for the following AI-powered feature
"thread_summary_model": {
"provider": "google",
"model": "gemini-2.0-flash"
+ },
+ "subagent_model": {
+ "provider": "zed.dev",
+ "model": "gpt-5-mini"
}
}
}