tools: route all dispatch through Tool registry
dispatch() and dispatch_shared() now look up tools by name in the registry and call the handler directly. No more match-on-name-strings. MCP server also uses the registry for both definitions and dispatch, eliminating the last duplicated tool logic. dispatch_with_agent() passes the optional Arc<Mutex<Agent>> through for tools that need agent context (control tools, working stack). Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
3e6c77e31e
commit
03cf13e9eb
2 changed files with 46 additions and 83 deletions
|
|
@ -158,81 +158,46 @@ pub fn truncate_output(mut s: String, max: usize) -> String {
|
|||
s
|
||||
}
|
||||
|
||||
/// Dispatch a tool call by name.
|
||||
///
|
||||
/// Tries agent-specific tools first (control, vision), then
|
||||
/// delegates to thought::dispatch for shared tools.
|
||||
///
|
||||
/// Note: working_stack is handled in runner.rs before reaching this
|
||||
/// function (it needs mutable context access).
|
||||
/// Dispatch a tool call by name. Handles all tools:
|
||||
/// agent-specific (control, vision), memory/journal, file/bash.
|
||||
/// Dispatch a tool call by name through the registry.
|
||||
pub async fn dispatch(
|
||||
name: &str,
|
||||
args: &serde_json::Value,
|
||||
) -> ToolOutput {
|
||||
// Agent-specific tools
|
||||
let rich_result = match name {
|
||||
"pause" => Some(control::pause(args)),
|
||||
"switch_model" => Some(control::switch_model(args)),
|
||||
"yield_to_user" => Some(control::yield_to_user(args)),
|
||||
"view_image" => Some(vision::view_image(args)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(result) = rich_result {
|
||||
return result.unwrap_or_else(ToolOutput::error);
|
||||
}
|
||||
dispatch_with_agent(name, args, None).await
|
||||
}
|
||||
|
||||
if let Some(output) = dispatch_shared(name, args, None).await {
|
||||
return output;
|
||||
/// Dispatch a tool call with optional agent context.
|
||||
pub async fn dispatch_with_agent(
|
||||
name: &str,
|
||||
args: &serde_json::Value,
|
||||
agent: Option<std::sync::Arc<tokio::sync::Mutex<super::Agent>>>,
|
||||
) -> ToolOutput {
|
||||
for tool in tools() {
|
||||
if tool.def.function.name == name {
|
||||
return match (tool.handler)(agent, args.clone()).await {
|
||||
Ok(s) => ToolOutput::text(s),
|
||||
Err(e) => ToolOutput::error(e),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ToolOutput::error(format!("Unknown tool: {}", name))
|
||||
}
|
||||
|
||||
/// Dispatch shared tools (memory, file, bash). Used by both the
|
||||
/// interactive agent and subconscious agents. Provenance tracks
|
||||
/// which agent made the call for memory attribution.
|
||||
/// Dispatch shared tools — used by subconscious agents.
|
||||
pub async fn dispatch_shared(
|
||||
name: &str,
|
||||
args: &serde_json::Value,
|
||||
provenance: Option<&str>,
|
||||
_provenance: Option<&str>,
|
||||
) -> Option<ToolOutput> {
|
||||
// Memory and journal tools
|
||||
if name.starts_with("memory_") || name.starts_with("journal_") || name == "output" {
|
||||
let result = memory::dispatch(name, args, provenance);
|
||||
return Some(match result {
|
||||
Ok(s) => ToolOutput::text(s),
|
||||
Err(e) => ToolOutput::error(e),
|
||||
});
|
||||
for tool in tools() {
|
||||
if tool.def.function.name == name {
|
||||
return Some(match (tool.handler)(None, args.clone()).await {
|
||||
Ok(s) => ToolOutput::text(s),
|
||||
Err(e) => ToolOutput::error(e),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Channel tools
|
||||
if name.starts_with("channel_") {
|
||||
let result = channels::dispatch(name, args).await;
|
||||
return Some(match result {
|
||||
Ok(s) => ToolOutput::text(s),
|
||||
Err(e) => ToolOutput::error(e),
|
||||
});
|
||||
}
|
||||
|
||||
// File and execution tools
|
||||
let result = match name {
|
||||
"read_file" => read::read_file(args),
|
||||
"write_file" => write::write_file(args),
|
||||
"edit_file" => edit::edit_file(args),
|
||||
"bash" => bash::run_bash(args).await,
|
||||
"web_fetch" => web::web_fetch(args).await,
|
||||
"web_search" => web::web_search(args).await,
|
||||
"grep" => grep::grep(args),
|
||||
"glob" => glob::glob_search(args),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(match result {
|
||||
Ok(s) => ToolOutput::text(s),
|
||||
Err(e) => ToolOutput::error(e),
|
||||
})
|
||||
None
|
||||
}
|
||||
|
||||
/// Return all registered tools with definitions + handlers.
|
||||
|
|
|
|||
|
|
@ -68,34 +68,32 @@ fn notify(method: &str, params: Value) {
|
|||
// ── Tool definitions ────────────────────────────────────────────
|
||||
|
||||
fn tool_definitions() -> Vec<Value> {
|
||||
use poc_memory::agent::tools;
|
||||
|
||||
let all_defs = tools::memory::definitions().into_iter()
|
||||
.chain(tools::channels::definitions());
|
||||
|
||||
all_defs.map(|td| json!({
|
||||
"name": td.function.name,
|
||||
"description": td.function.description,
|
||||
"inputSchema": td.function.parameters,
|
||||
})).collect()
|
||||
poc_memory::agent::tools::tools().into_iter()
|
||||
.map(|t| json!({
|
||||
"name": t.def.function.name,
|
||||
"description": t.def.function.description,
|
||||
"inputSchema": t.def.function.parameters,
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ── Tool dispatch ───────────────────────────────────────────────
|
||||
|
||||
fn dispatch_tool(name: &str, args: &Value) -> Result<String, String> {
|
||||
use poc_memory::agent::tools;
|
||||
let tools = poc_memory::agent::tools::tools();
|
||||
let tool = tools.iter().find(|t| t.def.function.name == name);
|
||||
let Some(tool) = tool else {
|
||||
return Err(format!("unknown tool: {name}"));
|
||||
};
|
||||
|
||||
if name.starts_with("memory_") || name.starts_with("journal_") || name == "output" {
|
||||
return tools::memory::dispatch(name, args, None)
|
||||
.map_err(|e| e.to_string());
|
||||
}
|
||||
|
||||
if name.starts_with("channel_") {
|
||||
return tools::channels::dispatch_blocking(name, args)
|
||||
.map_err(|e| e.to_string());
|
||||
}
|
||||
|
||||
Err(format!("unknown tool: {name}"))
|
||||
// Run async handler on a blocking runtime
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local.block_on(&rt, (tool.handler)(None, args.clone()))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// ── Main loop ───────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue