use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use serde_json::Value;
use tokio::sync::Mutex;
use crate::events::EventSink;
use crate::mcp::McpRegistry;
use crate::sandbox::SandboxSession;
use crate::skills::SkillRegistry;
use crate::terminal::TerminalManager;
use crate::types::ToolDefinition;
pub mod edit;
pub mod exec_command;
pub mod goal;
pub mod read;
pub mod terminal;
pub mod thread;
pub mod workset;
pub mod write;
pub struct ToolResult {
pub content: String,
pub is_error: bool,
}
#[derive(Clone)]
pub struct ToolRuntime {
pub store_path: PathBuf,
pub session_id: Option<String>,
pub worker_executable: Option<PathBuf>,
pub active_threads: Arc<Mutex<HashSet<String>>>,
pub event_sink: EventSink,
pub sandbox: Option<SandboxSession>,
pub mcp: Option<Arc<McpRegistry>>,
pub skills: Option<Arc<SkillRegistry>>,
pub activated_skills: Arc<Mutex<HashSet<String>>>,
pub terminal_manager: TerminalManager,
pub thread_timeout_secs: u64,
}
static WRITE_LOCK: Mutex<()> = Mutex::const_new(());
pub async fn acquire_write_lock() -> tokio::sync::MutexGuard<'static, ()> {
WRITE_LOCK.lock().await
}
pub fn worker_tool_definitions() -> Vec<ToolDefinition> {
use serde_json::json;
let mut tools = vec![
def(
"read",
"Read file contents with line numbers. Supports offset and limit.",
json!({
"type": "object",
"properties": {
"path": { "type": "string", "description": "Path to file" },
"offset": { "type": "integer", "description": "Line number to start from (0-indexed, optional)" },
"limit": { "type": "integer", "description": "Max lines to read (optional, default 2000)" }
},
"required": ["path"]
}),
),
def(
"write",
"Create a new file or completely overwrite an existing file. Creates parent directories automatically.\n\nUse this tool to:\n- Create new files that don't exist yet\n- Completely replace a file's content when most of it changes\n\nPrefer the `edit` tool for partial modifications to existing files.",
json!({
"type": "object",
"properties": {
"path": { "type": "string", "description": "Path to file" },
"content": { "type": "string", "description": "Content to write" }
},
"required": ["path", "content"]
}),
),
def(
"edit",
"Replace exact text in a file. This is your PRIMARY tool for modifying existing files.\n\nUsage:\n- old_text must be an EXACT substring match of the file's current content (whitespace-sensitive)\n- old_text must appear exactly ONCE in the file. If it appears multiple times, include more surrounding context lines to make it unique\n- Include complete lines in old_text, not partial lines\n- For multiple changes in one file, call edit multiple times\n\nPrefer this tool over exec_command with sed/python for all file modifications.",
json!({
"type": "object",
"properties": {
"path": { "type": "string", "description": "Path to file" },
"old_text": { "type": "string", "description": "The exact text to find in the file. Must be unique — include enough surrounding lines for a unique match. Use complete lines, not fragments." },
"new_text": { "type": "string", "description": "The replacement text. Can be empty to delete the matched text." }
},
"required": ["path", "old_text", "new_text"]
}),
),
];
tools.push(exec_command::exec_command_definition());
tools.push(exec_command::write_stdin_definition());
tools.push(terminal::terminal_definition());
tools
}
pub fn orchestrator_tool_definitions() -> Vec<ToolDefinition> {
vec![
thread::dispatch_definition(),
thread::threads_definition(),
thread::thread_read_definition(),
thread::thread_delete_definition(),
workset::define_definition(),
workset::read_definition(),
workset::list_definition(),
workset::update_item_definition(),
goal::get_goal_definition(),
goal::create_goal_definition(),
goal::update_goal_definition(),
]
}
fn def(name: &str, description: &str, parameters: Value) -> ToolDefinition {
ToolDefinition {
def_type: "function".to_string(),
function: crate::types::FunctionDef {
name: name.to_string(),
description: description.to_string(),
parameters,
},
}
}
pub fn require_str(args: &Value, key: &str) -> Result<String, ToolResult> {
args.get(key)
.and_then(|value| value.as_str())
.map(|value| value.to_string())
.ok_or_else(|| ToolResult {
content: format!("Error: '{}' argument required", key),
is_error: true,
})
}
pub fn require_string_array(args: &Value, key: &str) -> Result<Vec<String>, ToolResult> {
let Some(value) = args.get(key) else {
return Ok(Vec::new());
};
let Some(items) = value.as_array() else {
return Err(ToolResult {
content: format!("Error: '{}' must be an array of strings", key),
is_error: true,
});
};
let mut out = Vec::with_capacity(items.len());
for item in items {
let Some(value) = item.as_str() else {
return Err(ToolResult {
content: format!("Error: '{}' must be an array of strings", key),
is_error: true,
});
};
out.push(value.to_string());
}
Ok(out)
}
pub async fn execute_tool(
name: &str,
args: Value,
runtime: &ToolRuntime,
client: &crate::model::ModelClient,
) -> ToolResult {
if name.starts_with("mcp__") {
let Some(registry) = &runtime.mcp else {
return ToolResult {
content: format!("Error: MCP tool '{}' is not available", name),
is_error: true,
};
};
return registry.call_tool(name, args).await;
}
match name {
"activate_skill" => crate::skills::execute_activate_skill(args, runtime).await,
"read" => read::execute(args, runtime).await,
"write" => write::execute(args, runtime).await,
"edit" => edit::execute(args, runtime).await,
"exec_command" => match exec_command::execute_exec_command(&args, runtime).await {
Ok(content) => ToolResult {
content,
is_error: false,
},
Err(e) => ToolResult {
content: format!("Error: {:#}", e),
is_error: true,
},
},
"write_stdin" => match exec_command::execute_write_stdin(&args, runtime).await {
Ok(content) => ToolResult {
content,
is_error: false,
},
Err(e) => ToolResult {
content: format!("Error: {:#}", e),
is_error: true,
},
},
"terminal" => match terminal::execute_terminal(&args, runtime).await {
Ok(content) => ToolResult {
content,
is_error: false,
},
Err(e) => ToolResult {
content: format!("Error: {:#}", e),
is_error: true,
},
},
"thread" => thread::execute_dispatch(args, runtime, client).await,
"threads" => thread::execute_threads(runtime).await,
"thread_read" => thread::execute_thread_read(args, runtime).await,
"thread_delete" => thread::execute_thread_delete(args, runtime).await,
"workset_define" => workset::execute_define(args, runtime).await,
"workset_read" => workset::execute_read(args, runtime).await,
"workset_list" => workset::execute_list(args, runtime).await,
"workset_update_item" => workset::execute_update_item(args, runtime).await,
"get_goal" => goal::execute_get_goal(args, runtime).await,
"create_goal" => goal::execute_create_goal(args, runtime).await,
"update_goal" => goal::execute_update_goal(args, runtime).await,
unknown => ToolResult {
content: format!("Error: unknown tool '{}'", unknown),
is_error: true,
},
}
}