use agent_client_protocol::schema::v1 as acp;use anyhow::Result;use gpui::{App, SharedString, Task};use language_model::LanguageModelToolResultContent;use schemars::JsonSchema;use serde::{Deserialize, Serialize};use std::rc::Rc;use std::sync::Arc; use crate::{AgentTool, SiblingThreadRequest, ThreadEnvironment, ToolCallEventStream, ToolInput}; /// Create a new agent thread that runs in parallel with this one.////// Use this to kick off separable pieces of work without interrupting the current/// conversation. The new thread appears in the agent sidebar just like a thread/// the user created themselves, and runs independently — you will NOT receive/// its output and you cannot interact with it afterwards. Use `spawn_agent`/// instead if you need the results back.////// A successful call returns only the title, agent ID, and model used; there is/// currently no way to look up or control a sibling thread by session ID.////// ### When to use/// - The user asks you to start another thread, investigation, or exploration on the side./// - You notice a separable task (refactor, bug fix, investigation) that shouldn't/// derail the current conversation but is worth pursuing.////// ### Prompt design/// The new thread has no access to this conversation's history. Include in `prompt`/// everything the new agent needs: goals, relevant file paths, constraints, and/// context. Assume the new thread starts from a blank slate in the same project.////// ### Agent and model selection/// - If you don't know what agents or models are available, call `list_agents_and_models`./// - For bulk / lightweight work (e.g., spawning many parallel threads), prefer a/// cheaper / faster model over the default./// - Leave `agent` and `model` unset to use the user's current defaults.////// ### Worktree support/// Set `use_new_worktree` to true to spawn the sibling inside a brand-new/// workspace (a new tab) backed by linked git worktrees of each git/// repository in the current project. This mirrors what the user gets when/// they manually pick "Create worktree" from the worktree picker.////// - The new workspace opens in its own tab; switch to it manually to see/// the sibling's progress./// - The new worktrees start in detached HEAD state. Use `base_ref` to base/// them off a specific branch, tag, or commit; omit it to base off `HEAD`./// The agent in the sibling thread can attach to a branch by running/// `git switch -c <branch>` in its terminal if needed./// - `worktree_name` overrides the autogenerated directory name. Omit it to/// let the editor pick a random non-colliding name./// - The project must contain at least one git repository, otherwise the/// call fails.////// Use this when the sibling needs to make changes that shouldn't touch the/// user's current working tree (e.g., risky refactors, parallel experiments,/// or work the user wants to review independently).#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub struct CreateThreadToolInput { /// Short descriptive title for the new thread, shown in the sidebar /// (e.g., "Investigate flaky login test"). pub title: String, /// The initial prompt to send to the new thread. Include all the context the /// new agent needs — files, goals, constraints — because it has no access to /// the current conversation's history. pub prompt: String, /// Optional agent ID to use. Omit to use the user's currently selected agent. /// Call `list_agents_and_models` if you need to see what's available. #[serde(default, skip_serializing_if = "Option::is_none")] pub agent: Option<String>, /// Optional model override as `provider/model-id` (e.g., /// `anthropic/claude-haiku-4-latest`). Only meaningful for Zed's native /// agent. Omit to use the user's configured default. #[serde(default, skip_serializing_if = "Option::is_none")] pub model: Option<String>, /// If true, create the thread in a new git worktree rather than sharing /// the parent's worktree. The project must contain a git repository. #[serde(default)] pub use_new_worktree: bool, /// Optional name for the new worktree directory. When omitted, the /// editor generates a random non-colliding name (matching the /// manual "Create worktree" UI behavior). Only used when /// `use_new_worktree` is true. #[serde(default, skip_serializing_if = "Option::is_none")] pub worktree_name: Option<String>, /// Git ref (branch, tag, or commit) to base the new worktree on. Only /// used when `use_new_worktree` is true. Defaults to `HEAD`. #[serde(default, skip_serializing_if = "Option::is_none")] pub base_ref: Option<String>,} #[derive(Debug, Clone, Serialize, Deserialize)]#[serde(untagged)]pub enum CreateThreadToolOutput { Success { title: String, agent_id: String, #[serde(skip_serializing_if = "Option::is_none")] model: Option<String>, /// A non-fatal heads-up about the created thread (e.g., the project's /// worktree layout was unusual and the new worktree may not match /// expectations). Present only when there's something to flag. #[serde(skip_serializing_if = "Option::is_none")] warning: Option<String>, }, Error { error: String, },} impl From<CreateThreadToolOutput> for LanguageModelToolResultContent { fn from(output: CreateThreadToolOutput) -> Self { serde_json::to_string(&output) .unwrap_or_else(|e| format!("Failed to serialize create_thread output: {e}")) .into() }} pub struct CreateThreadTool { environment: Rc<dyn ThreadEnvironment>,} impl CreateThreadTool { pub fn new(environment: Rc<dyn ThreadEnvironment>) -> Self { Self { environment } }} impl AgentTool for CreateThreadTool { type Input = CreateThreadToolInput; type Output = CreateThreadToolOutput; const NAME: &'static str = "create_thread"; fn kind() -> acp::ToolKind { acp::ToolKind::Other } fn initial_title( &self, input: Result<Self::Input, serde_json::Value>, _cx: &mut App, ) -> SharedString { match input { Ok(i) => format!("Create thread: {}", i.title).into(), Err(value) => value .get("title") .and_then(|v| v.as_str()) .map(|s| format!("Create thread: {s}").into()) .unwrap_or_else(|| "Create thread".into()), } } fn run( self: Arc<Self>, input: ToolInput<Self::Input>, _event_stream: ToolCallEventStream, cx: &mut App, ) -> Task<Result<Self::Output, Self::Output>> { cx.spawn(async move |cx| { let input = input .recv() .await .map_err(|e| CreateThreadToolOutput::Error { error: format!("Failed to receive tool input: {e}"), })?; let title: SharedString = input.title.clone().into(); let request = SiblingThreadRequest { title: title.clone(), prompt: input.prompt, agent_id: input.agent, model: input.model, use_new_worktree: input.use_new_worktree, worktree_name: input.worktree_name, base_ref: input.base_ref, }; let task = self.environment.create_sibling_thread(request, cx); match task.await { Ok(info) => Ok(CreateThreadToolOutput::Success { title: info.title.to_string(), agent_id: info.agent_id, model: info.model, warning: info.warning, }), Err(error) => Err(CreateThreadToolOutput::Error { error: error.to_string(), }), } }) }}
use agent_client_protocol::schema::v1 as acp;
use anyhow::Result;
use gpui::{App, SharedString, Task};
use language_model::LanguageModelToolResultContent;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::rc::Rc;
use std::sync::Arc;
use crate::{AgentTool, SiblingThreadRequest, ThreadEnvironment, ToolCallEventStream, ToolInput};
/// Create a new agent thread that runs in parallel with this one.
///
/// Use this to kick off separable pieces of work without interrupting the current
/// conversation. The new thread appears in the agent sidebar just like a thread
/// the user created themselves, and runs independently — you will NOT receive
/// its output and you cannot interact with it afterwards. Use `spawn_agent`
/// instead if you need the results back.
///
/// A successful call returns only the title, agent ID, and model used; there is
/// currently no way to look up or control a sibling thread by session ID.
///
/// ### When to use
/// - The user asks you to start another thread, investigation, or exploration on the side.
/// - You notice a separable task (refactor, bug fix, investigation) that shouldn't
/// derail the current conversation but is worth pursuing.
///
/// ### Prompt design
/// The new thread has no access to this conversation's history. Include in `prompt`
/// everything the new agent needs: goals, relevant file paths, constraints, and
/// context. Assume the new thread starts from a blank slate in the same project.
///
/// ### Agent and model selection
/// - If you don't know what agents or models are available, call `list_agents_and_models`.
/// - For bulk / lightweight work (e.g., spawning many parallel threads), prefer a
/// cheaper / faster model over the default.
/// - Leave `agent` and `model` unset to use the user's current defaults.
///
/// ### Worktree support
/// Set `use_new_worktree` to true to spawn the sibling inside a brand-new
/// workspace (a new tab) backed by linked git worktrees of each git
/// repository in the current project. This mirrors what the user gets when
/// they manually pick "Create worktree" from the worktree picker.
///
/// - The new workspace opens in its own tab; switch to it manually to see
/// the sibling's progress.
/// - The new worktrees start in detached HEAD state. Use `base_ref` to base
/// them off a specific branch, tag, or commit; omit it to base off `HEAD`.
/// The agent in the sibling thread can attach to a branch by running
/// `git switch -c <branch>` in its terminal if needed.
/// - `worktree_name` overrides the autogenerated directory name. Omit it to
/// let the editor pick a random non-colliding name.
/// - The project must contain at least one git repository, otherwise the
/// call fails.
///
/// Use this when the sibling needs to make changes that shouldn't touch the
/// user's current working tree (e.g., risky refactors, parallel experiments,
/// or work the user wants to review independently).
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct CreateThreadToolInput {
/// Short descriptive title for the new thread, shown in the sidebar
/// (e.g., "Investigate flaky login test").
pub title: String,
/// The initial prompt to send to the new thread. Include all the context the
/// new agent needs — files, goals, constraints — because it has no access to
/// the current conversation's history.
pub prompt: String,
/// Optional agent ID to use. Omit to use the user's currently selected agent.
/// Call `list_agents_and_models` if you need to see what's available.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent: Option<String>,
/// Optional model override as `provider/model-id` (e.g.,
/// `anthropic/claude-haiku-4-latest`). Only meaningful for Zed's native
/// agent. Omit to use the user's configured default.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
/// If true, create the thread in a new git worktree rather than sharing
/// the parent's worktree. The project must contain a git repository.
#[serde(default)]
pub use_new_worktree: bool,
/// Optional name for the new worktree directory. When omitted, the
/// editor generates a random non-colliding name (matching the
/// manual "Create worktree" UI behavior). Only used when
/// `use_new_worktree` is true.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub worktree_name: Option<String>,
/// Git ref (branch, tag, or commit) to base the new worktree on. Only
/// used when `use_new_worktree` is true. Defaults to `HEAD`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_ref: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CreateThreadToolOutput {
Success {
title: String,
agent_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
model: Option<String>,
/// A non-fatal heads-up about the created thread (e.g., the project's
/// worktree layout was unusual and the new worktree may not match
/// expectations). Present only when there's something to flag.
#[serde(skip_serializing_if = "Option::is_none")]
warning: Option<String>,
},
Error {
error: String,
},
}
impl From<CreateThreadToolOutput> for LanguageModelToolResultContent {
fn from(output: CreateThreadToolOutput) -> Self {
serde_json::to_string(&output)
.unwrap_or_else(|e| format!("Failed to serialize create_thread output: {e}"))
.into()
}
}
pub struct CreateThreadTool {
environment: Rc<dyn ThreadEnvironment>,
}
impl CreateThreadTool {
pub fn new(environment: Rc<dyn ThreadEnvironment>) -> Self {
Self { environment }
}
}
impl AgentTool for CreateThreadTool {
type Input = CreateThreadToolInput;
type Output = CreateThreadToolOutput;
const NAME: &'static str = "create_thread";
fn kind() -> acp::ToolKind {
acp::ToolKind::Other
}
fn initial_title(
&self,
input: Result<Self::Input, serde_json::Value>,
_cx: &mut App,
) -> SharedString {
match input {
Ok(i) => format!("Create thread: {}", i.title).into(),
Err(value) => value
.get("title")
.and_then(|v| v.as_str())
.map(|s| format!("Create thread: {s}").into())
.unwrap_or_else(|| "Create thread".into()),
}
}
fn run(
self: Arc<Self>,
input: ToolInput<Self::Input>,
_event_stream: ToolCallEventStream,
cx: &mut App,
) -> Task<Result<Self::Output, Self::Output>> {
cx.spawn(async move |cx| {
let input = input
.recv()
.await
.map_err(|e| CreateThreadToolOutput::Error {
error: format!("Failed to receive tool input: {e}"),
})?;
let title: SharedString = input.title.clone().into();
let request = SiblingThreadRequest {
title: title.clone(),
prompt: input.prompt,
agent_id: input.agent,
model: input.model,
use_new_worktree: input.use_new_worktree,
worktree_name: input.worktree_name,
base_ref: input.base_ref,
};
let task = self.environment.create_sibling_thread(request, cx);
match task.await {
Ok(info) => Ok(CreateThreadToolOutput::Success {
title: info.title.to_string(),
agent_id: info.agent_id,
model: info.model,
warning: info.warning,
}),
Err(error) => Err(CreateThreadToolOutput::Error {
error: error.to_string(),
}),
}
})
}
}