201 lines · 7.9 KB · Rust
RawDownload
use agent_client_protocol::schema 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(),                }),            }        })    }}