mind/mod.rs and mind/dmn.rs fully migrated to AST types. user/context.rs, user/widgets.rs, user/chat.rs partially migrated. Killed working_stack tool, tokenize_conv_entry, context_old.rs. Remaining: learn.rs (22), oneshot.rs (5), subconscious.rs (3), chat.rs (3), widgets.rs (1), context.rs (1). Co-Authored-By: Proof of Concept <poc@bcachefs.org>
197 lines
5.9 KiB
Rust
197 lines
5.9 KiB
Rust
// tools/mod.rs — Agent-specific tool dispatch
|
|
//
|
|
// Shared tools (memory, files, bash, journal) live in thought/.
|
|
// This module handles agent-specific tools (control, vision,
|
|
// working_stack) and delegates everything else to thought::dispatch.
|
|
|
|
// Core tools
|
|
mod bash;
|
|
pub mod channels;
|
|
mod edit;
|
|
mod glob;
|
|
mod grep;
|
|
pub mod memory;
|
|
mod read;
|
|
mod web;
|
|
mod write;
|
|
|
|
// Agent-specific tools
|
|
mod control;
|
|
mod vision;
|
|
|
|
use std::future::Future;
|
|
use std::pin::Pin;
|
|
use std::sync::Arc;
|
|
use std::time::Instant;
|
|
|
|
fn default_timeout() -> u64 { 120 }
|
|
|
|
/// Async tool handler function.
|
|
/// Agent is None when called from contexts without an agent (MCP server, subconscious).
|
|
pub type ToolHandler = fn(
|
|
Option<std::sync::Arc<tokio::sync::Mutex<super::Agent>>>,
|
|
serde_json::Value,
|
|
) -> Pin<Box<dyn Future<Output = anyhow::Result<String>> + Send>>;
|
|
|
|
/// A tool with its definition and handler — single source of truth.
|
|
/// Strings are static — the tool list JSON can be built without
|
|
/// serialization by interpolating these directly.
|
|
#[derive(Clone, Copy)]
|
|
pub struct Tool {
|
|
pub name: &'static str,
|
|
pub description: &'static str,
|
|
pub parameters_json: &'static str,
|
|
pub handler: ToolHandler,
|
|
}
|
|
|
|
impl Tool {
|
|
/// Build the JSON for this tool's definition (for the API tools array).
|
|
pub fn to_json(&self) -> String {
|
|
format!(
|
|
r#"{{"type":"function","function":{{"name":"{}","description":"{}","parameters":{}}}}}"#,
|
|
self.name,
|
|
self.description.replace('"', r#"\""#),
|
|
self.parameters_json,
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// A tool call in flight — metadata for TUI + JoinHandle for
|
|
/// result collection and cancellation.
|
|
pub struct ActiveToolCall {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub detail: String,
|
|
pub started: Instant,
|
|
pub background: bool,
|
|
pub handle: tokio::task::JoinHandle<(super::context::PendingToolCall, String)>,
|
|
}
|
|
|
|
/// Shared active tool calls — agent spawns, TUI reads metadata / aborts.
|
|
pub type SharedActiveTools = Arc<std::sync::Mutex<Vec<ActiveToolCall>>>;
|
|
|
|
pub fn shared_active_tools() -> SharedActiveTools {
|
|
Arc::new(std::sync::Mutex::new(Vec::new()))
|
|
}
|
|
|
|
/// Truncate output if it exceeds max length, appending a truncation notice.
|
|
pub fn truncate_output(mut s: String, max: usize) -> String {
|
|
if s.len() > max {
|
|
s.truncate(max);
|
|
s.push_str("\n... (output truncated)");
|
|
}
|
|
s
|
|
}
|
|
|
|
/// Dispatch a tool call by name through the registry.
|
|
/// Dispatch a tool call by name. Returns the result text,
|
|
/// or an error string prefixed with "Error: ".
|
|
pub async fn dispatch(
|
|
name: &str,
|
|
args: &serde_json::Value,
|
|
) -> String {
|
|
dispatch_with_agent(name, args, None).await
|
|
}
|
|
|
|
/// Dispatch a tool call with optional agent context.
|
|
/// If agent is provided, uses the agent's tool list.
|
|
pub async fn dispatch_with_agent(
|
|
name: &str,
|
|
args: &serde_json::Value,
|
|
agent: Option<std::sync::Arc<tokio::sync::Mutex<super::Agent>>>,
|
|
) -> String {
|
|
// Look up in agent's tools if available, otherwise global
|
|
let tool = if let Some(ref a) = agent {
|
|
let guard = a.lock().await;
|
|
guard.tools.iter().find(|t| t.name == name).copied()
|
|
} else {
|
|
None
|
|
};
|
|
let tool = tool.or_else(|| tools().into_iter().find(|t| t.name == name));
|
|
match tool {
|
|
Some(t) => (t.handler)(agent, args.clone()).await
|
|
.unwrap_or_else(|e| format!("Error: {}", e)),
|
|
None => format!("Error: Unknown tool: {}", name),
|
|
}
|
|
}
|
|
|
|
/// Return all registered tools with definitions + handlers.
|
|
pub fn tools() -> Vec<Tool> {
|
|
let mut all = vec![
|
|
read::tool(), write::tool(), edit::tool(),
|
|
grep::tool(), glob::tool(), bash::tool(),
|
|
vision::tool(),
|
|
];
|
|
all.extend(web::tools());
|
|
all.extend(memory::memory_tools());
|
|
all.extend(memory::journal_tools());
|
|
all.extend(channels::tools());
|
|
all.extend(control::tools());
|
|
all
|
|
}
|
|
|
|
/// Memory + journal tools only — for subconscious agents.
|
|
pub fn memory_and_journal_tools() -> Vec<Tool> {
|
|
let mut all = memory::memory_tools().to_vec();
|
|
all.extend(memory::journal_tools());
|
|
all
|
|
}
|
|
|
|
/// Create a short summary of tool args for the tools pane header.
|
|
pub fn summarize_args(tool_name: &str, args: &serde_json::Value) -> String {
|
|
match tool_name {
|
|
"read_file" | "write_file" | "edit_file" => args["file_path"]
|
|
.as_str()
|
|
.unwrap_or("")
|
|
.to_string(),
|
|
"bash" => {
|
|
let cmd = args["command"].as_str().unwrap_or("");
|
|
if cmd.len() > 60 {
|
|
let end = cmd.char_indices()
|
|
.map(|(i, _)| i)
|
|
.take_while(|&i| i <= 60)
|
|
.last()
|
|
.unwrap_or(0);
|
|
format!("{}...", &cmd[..end])
|
|
} else {
|
|
cmd.to_string()
|
|
}
|
|
}
|
|
"grep" => {
|
|
let pattern = args["pattern"].as_str().unwrap_or("");
|
|
let path = args["path"].as_str().unwrap_or(".");
|
|
format!("{} in {}", pattern, path)
|
|
}
|
|
"glob" => args["pattern"]
|
|
.as_str()
|
|
.unwrap_or("")
|
|
.to_string(),
|
|
"view_image" => {
|
|
if let Some(pane) = args["pane_id"].as_str() {
|
|
format!("pane {}", pane)
|
|
} else {
|
|
args["file_path"].as_str().unwrap_or("").to_string()
|
|
}
|
|
}
|
|
"journal" => {
|
|
let entry = args["entry"].as_str().unwrap_or("");
|
|
if entry.len() > 60 {
|
|
format!("{}...", &entry[..60])
|
|
} else {
|
|
entry.to_string()
|
|
}
|
|
}
|
|
"yield_to_user" => args["message"]
|
|
.as_str()
|
|
.unwrap_or("")
|
|
.to_string(),
|
|
"switch_model" => args["model"]
|
|
.as_str()
|
|
.unwrap_or("")
|
|
.to_string(),
|
|
"pause" => String::new(),
|
|
_ => String::new(),
|
|
}
|
|
}
|