Skip to repository content202 lines · 8.1 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T05:34:56.861Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
create_thread_tool.rs
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