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 ` 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, /// 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, /// 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, /// 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, } #[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, /// 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, }, Error { error: String, }, } impl From 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, } impl CreateThreadTool { pub fn new(environment: Rc) -> 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, _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, input: ToolInput, _event_stream: ToolCallEventStream, cx: &mut App, ) -> Task> { 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(), }), } }) } }