Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:34:56.861Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

create_thread_tool.rs

202 lines · 8.1 KB · rust
1use agent_client_protocol::schema::v1 as acp;
2use anyhow::Result;
3use gpui::{App, SharedString, Task};
4use language_model::LanguageModelToolResultContent;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::rc::Rc;
8use std::sync::Arc;
9
10use crate::{AgentTool, SiblingThreadRequest, ThreadEnvironment, ToolCallEventStream, ToolInput};
11
12/// Create a new agent thread that runs in parallel with this one.
13///
14/// Use this to kick off separable pieces of work without interrupting the current
15/// conversation. The new thread appears in the agent sidebar just like a thread
16/// the user created themselves, and runs independently — you will NOT receive
17/// its output and you cannot interact with it afterwards. Use `spawn_agent`
18/// instead if you need the results back.
19///
20/// A successful call returns only the title, agent ID, and model used; there is
21/// currently no way to look up or control a sibling thread by session ID.
22///
23/// ### When to use
24/// - The user asks you to start another thread, investigation, or exploration on the side.
25/// - You notice a separable task (refactor, bug fix, investigation) that shouldn't
26///   derail the current conversation but is worth pursuing.
27///
28/// ### Prompt design
29/// The new thread has no access to this conversation's history. Include in `prompt`
30/// everything the new agent needs: goals, relevant file paths, constraints, and
31/// context. Assume the new thread starts from a blank slate in the same project.
32///
33/// ### Agent and model selection
34/// - If you don't know what agents or models are available, call `list_agents_and_models`.
35/// - For bulk / lightweight work (e.g., spawning many parallel threads), prefer a
36///   cheaper / faster model over the default.
37/// - Leave `agent` and `model` unset to use the user's current defaults.
38///
39/// ### Worktree support
40/// Set `use_new_worktree` to true to spawn the sibling inside a brand-new
41/// workspace (a new tab) backed by linked git worktrees of each git
42/// repository in the current project. This mirrors what the user gets when
43/// they manually pick "Create worktree" from the worktree picker.
44///
45/// - The new workspace opens in its own tab; switch to it manually to see
46///   the sibling's progress.
47/// - The new worktrees start in detached HEAD state. Use `base_ref` to base
48///   them off a specific branch, tag, or commit; omit it to base off `HEAD`.
49///   The agent in the sibling thread can attach to a branch by running
50///   `git switch -c <branch>` in its terminal if needed.
51/// - `worktree_name` overrides the autogenerated directory name. Omit it to
52///   let the editor pick a random non-colliding name.
53/// - The project must contain at least one git repository, otherwise the
54///   call fails.
55///
56/// Use this when the sibling needs to make changes that shouldn't touch the
57/// user's current working tree (e.g., risky refactors, parallel experiments,
58/// or work the user wants to review independently).
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60#[serde(rename_all = "snake_case")]
61pub struct CreateThreadToolInput {
62    /// Short descriptive title for the new thread, shown in the sidebar
63    /// (e.g., "Investigate flaky login test").
64    pub title: String,
65
66    /// The initial prompt to send to the new thread. Include all the context the
67    /// new agent needs — files, goals, constraints — because it has no access to
68    /// the current conversation's history.
69    pub prompt: String,
70
71    /// Optional agent ID to use. Omit to use the user's currently selected agent.
72    /// Call `list_agents_and_models` if you need to see what's available.
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub agent: Option<String>,
75
76    /// Optional model override as `provider/model-id` (e.g.,
77    /// `anthropic/claude-haiku-4-latest`). Only meaningful for Omega's native
78    /// agent. Omit to use the user's configured default.
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub model: Option<String>,
81
82    /// If true, create the thread in a new git worktree rather than sharing
83    /// the parent's worktree. The project must contain a git repository.
84    #[serde(default)]
85    pub use_new_worktree: bool,
86
87    /// Optional name for the new worktree directory. When omitted, the
88    /// editor generates a random non-colliding name (matching the
89    /// manual "Create worktree" UI behavior). Only used when
90    /// `use_new_worktree` is true.
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub worktree_name: Option<String>,
93
94    /// Git ref (branch, tag, or commit) to base the new worktree on. Only
95    /// used when `use_new_worktree` is true. Defaults to `HEAD`.
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    pub base_ref: Option<String>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(untagged)]
102pub enum CreateThreadToolOutput {
103    Success {
104        title: String,
105        agent_id: String,
106        #[serde(skip_serializing_if = "Option::is_none")]
107        model: Option<String>,
108        /// A non-fatal heads-up about the created thread (e.g., the project's
109        /// worktree layout was unusual and the new worktree may not match
110        /// expectations). Present only when there's something to flag.
111        #[serde(skip_serializing_if = "Option::is_none")]
112        warning: Option<String>,
113    },
114    Error {
115        error: String,
116    },
117}
118
119impl From<CreateThreadToolOutput> for LanguageModelToolResultContent {
120    fn from(output: CreateThreadToolOutput) -> Self {
121        serde_json::to_string(&output)
122            .unwrap_or_else(|e| format!("Failed to serialize create_thread output: {e}"))
123            .into()
124    }
125}
126
127pub struct CreateThreadTool {
128    environment: Rc<dyn ThreadEnvironment>,
129}
130
131impl CreateThreadTool {
132    pub fn new(environment: Rc<dyn ThreadEnvironment>) -> Self {
133        Self { environment }
134    }
135}
136
137impl AgentTool for CreateThreadTool {
138    type Input = CreateThreadToolInput;
139    type Output = CreateThreadToolOutput;
140
141    const NAME: &'static str = "create_thread";
142
143    fn kind() -> acp::ToolKind {
144        acp::ToolKind::Other
145    }
146
147    fn initial_title(
148        &self,
149        input: Result<Self::Input, serde_json::Value>,
150        _cx: &mut App,
151    ) -> SharedString {
152        match input {
153            Ok(i) => format!("Create thread: {}", i.title).into(),
154            Err(value) => value
155                .get("title")
156                .and_then(|v| v.as_str())
157                .map(|s| format!("Create thread: {s}").into())
158                .unwrap_or_else(|| "Create thread".into()),
159        }
160    }
161
162    fn run(
163        self: Arc<Self>,
164        input: ToolInput<Self::Input>,
165        _event_stream: ToolCallEventStream,
166        cx: &mut App,
167    ) -> Task<Result<Self::Output, Self::Output>> {
168        cx.spawn(async move |cx| {
169            let input = input
170                .recv()
171                .await
172                .map_err(|e| CreateThreadToolOutput::Error {
173                    error: format!("Failed to receive tool input: {e}"),
174                })?;
175
176            let title: SharedString = input.title.clone().into();
177            let request = SiblingThreadRequest {
178                title: title.clone(),
179                prompt: input.prompt,
180                agent_id: input.agent,
181                model: input.model,
182                use_new_worktree: input.use_new_worktree,
183                worktree_name: input.worktree_name,
184                base_ref: input.base_ref,
185            };
186
187            let task = self.environment.create_sibling_thread(request, cx);
188            match task.await {
189                Ok(info) => Ok(CreateThreadToolOutput::Success {
190                    title: info.title.to_string(),
191                    agent_id: info.agent_id,
192                    model: info.model,
193                    warning: info.warning,
194                }),
195                Err(error) => Err(CreateThreadToolOutput::Error {
196                    error: error.to_string(),
197                }),
198            }
199        })
200    }
201}
202
Served at tenant.openagents/omega Member data and write actions are omitted.