Adds two built-in agent tools, behind the staff-only `create-thread-tool` feature flag, that let the agent spawn independent sibling threads: - **`create_thread`** creates a new thread with a title and initial prompt. It runs on the user's behalf and shows up in the agent sidebar like a thread the user started themselves, but it runs in the background — the active thread is never interrupted — and it does **not** report results back to the caller. (Use `spawn_agent` when you want the results.) - **`list_agents_and_models`** enumerates the configured agents and, for the native agent, the available models, so the agent can pick a cheaper/faster model for bulk work. The motivating stress test was "spawn 100 threads that each write a haiku, using Haiku instead of Opus." By default the sibling shares the parent's project and worktree. If `use_new_worktree` is set, `create_thread` instead opens a new background workspace backed by fresh linked git worktrees of the project's repos — the same flow you get from the worktree picker's "Create worktree" — and starts the thread there. The new workspace opens as a background tab so it doesn't pull you away from what you're doing. A few notes on the design: - The `agent` crate can't depend on the UI, so there's a `SiblingThreadHost` trait that `agent_ui::AgentPanel` implements and installs on the `NativeAgent`. Tool calls in a native-agent thread route through it. - Sibling threads go through the same `create_agent_thread` → `ConversationView` path as the user-facing "new thread" action, so eviction, persistence, and sidebar rendering match user-created threads. - An explicit `agent`/`model` override doesn't clobber the panel's selected agent or the last-used-agent preference — those are snapshotted and restored around the call. - The worktree path reuses the existing "Create worktree" machinery rather than reimplementing it; the agent flow just opens the new workspace in the background instead of activating it. A couple of things I left out or want to call out: - There's no tool to operate on an already-created sibling by session id. The success payload deliberately omits it, since the session is populated asynchronously and nothing consumes it yet. - When a project has multiple worktrees that are linked worktrees of the *same* underlying repo, they collapse into a single new worktree (they resolve to the same target path). The tool returns a warning in that case rather than failing — and this also fixes a latent collision the manual "Create worktree" flow would hit in the same situation. Release Notes: - N/A
199 lines
5.8 KiB
Rust
199 lines
5.8 KiB
Rust
mod apply_code_action_tool;
|
|
mod context_server_registry;
|
|
mod copy_path_tool;
|
|
mod create_directory_tool;
|
|
mod create_thread_tool;
|
|
mod delete_path_tool;
|
|
mod diagnostics_tool;
|
|
mod edit_file_tool;
|
|
mod edit_session;
|
|
#[cfg(all(test, feature = "unit-eval"))]
|
|
mod evals;
|
|
mod fetch_tool;
|
|
mod find_path_tool;
|
|
mod find_references_tool;
|
|
mod get_code_actions_tool;
|
|
mod go_to_definition_tool;
|
|
mod grep_tool;
|
|
mod list_agents_and_models_tool;
|
|
mod list_directory_tool;
|
|
mod move_path_tool;
|
|
mod read_file_tool;
|
|
mod rename_tool;
|
|
mod skill_tool;
|
|
mod spawn_agent_tool;
|
|
mod symbol_locator;
|
|
mod terminal_tool;
|
|
mod tool_permissions;
|
|
mod update_plan_tool;
|
|
mod update_title_tool;
|
|
mod web_search_tool;
|
|
mod write_file_tool;
|
|
|
|
use crate::AgentTool;
|
|
use language_model::{LanguageModelRequestTool, LanguageModelToolSchemaFormat};
|
|
use serde::{
|
|
Deserialize, Deserializer,
|
|
de::{DeserializeOwned, Error as _},
|
|
};
|
|
|
|
/// Deserialize a value that may have been provided as a JSON-encoded string
|
|
/// instead of the structured value. Some models occasionally stringify nested
|
|
/// arguments, so we accept either form.
|
|
pub(crate) fn deserialize_maybe_stringified<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
|
where
|
|
T: DeserializeOwned,
|
|
D: Deserializer<'de>,
|
|
{
|
|
#[derive(Deserialize)]
|
|
#[serde(untagged)]
|
|
enum ValueOrJsonString<T> {
|
|
Value(T),
|
|
String(String),
|
|
}
|
|
|
|
match ValueOrJsonString::<T>::deserialize(deserializer)? {
|
|
ValueOrJsonString::Value(value) => Ok(value),
|
|
ValueOrJsonString::String(string) => serde_json::from_str::<T>(&string).map_err(|error| {
|
|
D::Error::custom(format!("failed to parse stringified value: {error}"))
|
|
}),
|
|
}
|
|
}
|
|
|
|
pub use apply_code_action_tool::*;
|
|
pub use context_server_registry::*;
|
|
pub use copy_path_tool::*;
|
|
pub use create_directory_tool::*;
|
|
pub use create_thread_tool::*;
|
|
pub use delete_path_tool::*;
|
|
pub use diagnostics_tool::*;
|
|
pub use edit_file_tool::*;
|
|
pub use fetch_tool::*;
|
|
pub use find_path_tool::*;
|
|
pub use find_references_tool::*;
|
|
pub use get_code_actions_tool::*;
|
|
pub use go_to_definition_tool::*;
|
|
pub use grep_tool::*;
|
|
pub use list_agents_and_models_tool::*;
|
|
pub use list_directory_tool::*;
|
|
pub use move_path_tool::*;
|
|
pub use read_file_tool::*;
|
|
pub use rename_tool::*;
|
|
pub use skill_tool::*;
|
|
pub use spawn_agent_tool::*;
|
|
pub use symbol_locator::*;
|
|
pub use terminal_tool::*;
|
|
pub use tool_permissions::*;
|
|
pub use update_plan_tool::*;
|
|
pub use update_title_tool::*;
|
|
pub use web_search_tool::*;
|
|
pub use write_file_tool::*;
|
|
|
|
macro_rules! tools {
|
|
($($tool:ty),* $(,)?) => {
|
|
/// Every built-in tool name, determined at compile time.
|
|
pub const ALL_TOOL_NAMES: &[&str] = &[
|
|
$(<$tool>::NAME,)*
|
|
];
|
|
|
|
const _: () = {
|
|
const fn str_eq(a: &str, b: &str) -> bool {
|
|
let a = a.as_bytes();
|
|
let b = b.as_bytes();
|
|
if a.len() != b.len() {
|
|
return false;
|
|
}
|
|
let mut i = 0;
|
|
while i < a.len() {
|
|
if a[i] != b[i] {
|
|
return false;
|
|
}
|
|
i += 1;
|
|
}
|
|
true
|
|
}
|
|
|
|
const NAMES: &[&str] = ALL_TOOL_NAMES;
|
|
let mut i = 0;
|
|
while i < NAMES.len() {
|
|
let mut j = i + 1;
|
|
while j < NAMES.len() {
|
|
if str_eq(NAMES[i], NAMES[j]) {
|
|
panic!("Duplicate tool name in tools! macro");
|
|
}
|
|
j += 1;
|
|
}
|
|
i += 1;
|
|
}
|
|
};
|
|
|
|
/// Returns whether the tool with the given name supports the given provider.
|
|
pub fn tool_supports_provider(name: &str, provider: &language_model::LanguageModelProviderId) -> bool {
|
|
$(
|
|
if name == <$tool>::NAME {
|
|
return <$tool>::supports_provider(provider);
|
|
}
|
|
)*
|
|
false
|
|
}
|
|
|
|
/// A list of all built-in tools
|
|
pub fn built_in_tools() -> impl Iterator<Item = LanguageModelRequestTool> {
|
|
fn language_model_tool<T: AgentTool>() -> LanguageModelRequestTool {
|
|
LanguageModelRequestTool {
|
|
name: T::NAME.to_string(),
|
|
description: T::description().to_string(),
|
|
input_schema: T::input_schema(LanguageModelToolSchemaFormat::JsonSchema).to_value(),
|
|
use_input_streaming: T::supports_input_streaming(),
|
|
}
|
|
}
|
|
[
|
|
$(
|
|
language_model_tool::<$tool>(),
|
|
)*
|
|
]
|
|
.into_iter()
|
|
}
|
|
};
|
|
}
|
|
|
|
// Adding a tool here (and constructing it in `Thread::add_default_tools`) is
|
|
// not enough to make the model actually receive it. Two further gates will
|
|
// silently drop the tool rather than fail to compile:
|
|
//
|
|
// 1. `assets/settings/default.json`: the `write` and `ask` agent profiles each
|
|
// carry an explicit `tools` allowlist. `Thread::enabled_tools` filters out
|
|
// any tool not present there with value `true`, so it never reaches the
|
|
// model.
|
|
// 2. `test_all_tools_are_in_tool_info_or_excluded` in
|
|
// `crates/settings_ui/src/pages/tool_permissions_setup.rs`: every tool must
|
|
// be in the permission-UI `TOOLS` list (if it calls
|
|
// `decide_permission_from_settings`) or in `EXCLUDED_TOOLS`.
|
|
tools! {
|
|
ApplyCodeActionTool,
|
|
CopyPathTool,
|
|
CreateDirectoryTool,
|
|
CreateThreadTool,
|
|
DeletePathTool,
|
|
DiagnosticsTool,
|
|
EditFileTool,
|
|
FetchTool,
|
|
FindPathTool,
|
|
FindReferencesTool,
|
|
GetCodeActionsTool,
|
|
GoToDefinitionTool,
|
|
GrepTool,
|
|
ListAgentsAndModelsTool,
|
|
ListDirectoryTool,
|
|
MovePathTool,
|
|
ReadFileTool,
|
|
RenameTool,
|
|
SkillTool,
|
|
SpawnAgentTool,
|
|
TerminalTool,
|
|
UpdatePlanTool,
|
|
UpdateTitleTool,
|
|
WebSearchTool,
|
|
WriteFileTool,
|
|
}
|