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
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch a tool call by name.
|
/// Dispatch a tool call by name through the registry.
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
pub async fn dispatch(
|
pub async fn dispatch(
|
||||||
name: &str,
|
name: &str,
|
||||||
args: &serde_json::Value,
|
args: &serde_json::Value,
|
||||||
) -> ToolOutput {
|
) -> ToolOutput {
|
||||||
// Agent-specific tools
|
dispatch_with_agent(name, args, None).await
|
||||||
let rich_result = match name {
|
}
|
||||||
"pause" => Some(control::pause(args)),
|
|
||||||
"switch_model" => Some(control::switch_model(args)),
|
/// Dispatch a tool call with optional agent context.
|
||||||
"yield_to_user" => Some(control::yield_to_user(args)),
|
pub async fn dispatch_with_agent(
|
||||||
"view_image" => Some(vision::view_image(args)),
|
name: &str,
|
||||||
_ => None,
|
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),
|
||||||
};
|
};
|
||||||
if let Some(result) = rich_result {
|
|
||||||
return result.unwrap_or_else(ToolOutput::error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(output) = dispatch_shared(name, args, None).await {
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolOutput::error(format!("Unknown tool: {}", name))
|
ToolOutput::error(format!("Unknown tool: {}", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch shared tools (memory, file, bash). Used by both the
|
/// Dispatch shared tools — used by subconscious agents.
|
||||||
/// interactive agent and subconscious agents. Provenance tracks
|
|
||||||
/// which agent made the call for memory attribution.
|
|
||||||
pub async fn dispatch_shared(
|
pub async fn dispatch_shared(
|
||||||
name: &str,
|
name: &str,
|
||||||
args: &serde_json::Value,
|
args: &serde_json::Value,
|
||||||
provenance: Option<&str>,
|
_provenance: Option<&str>,
|
||||||
) -> Option<ToolOutput> {
|
) -> Option<ToolOutput> {
|
||||||
// Memory and journal tools
|
for tool in tools() {
|
||||||
if name.starts_with("memory_") || name.starts_with("journal_") || name == "output" {
|
if tool.def.function.name == name {
|
||||||
let result = memory::dispatch(name, args, provenance);
|
return Some(match (tool.handler)(None, args.clone()).await {
|
||||||
return Some(match result {
|
|
||||||
Ok(s) => ToolOutput::text(s),
|
Ok(s) => ToolOutput::text(s),
|
||||||
Err(e) => ToolOutput::error(e),
|
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),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
None
|
||||||
// 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),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all registered tools with definitions + handlers.
|
/// Return all registered tools with definitions + handlers.
|
||||||
|
|
|
||||||
|
|
@ -68,34 +68,32 @@ fn notify(method: &str, params: Value) {
|
||||||
// ── Tool definitions ────────────────────────────────────────────
|
// ── Tool definitions ────────────────────────────────────────────
|
||||||
|
|
||||||
fn tool_definitions() -> Vec<Value> {
|
fn tool_definitions() -> Vec<Value> {
|
||||||
use poc_memory::agent::tools;
|
poc_memory::agent::tools::tools().into_iter()
|
||||||
|
.map(|t| json!({
|
||||||
let all_defs = tools::memory::definitions().into_iter()
|
"name": t.def.function.name,
|
||||||
.chain(tools::channels::definitions());
|
"description": t.def.function.description,
|
||||||
|
"inputSchema": t.def.function.parameters,
|
||||||
all_defs.map(|td| json!({
|
}))
|
||||||
"name": td.function.name,
|
.collect()
|
||||||
"description": td.function.description,
|
|
||||||
"inputSchema": td.function.parameters,
|
|
||||||
})).collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tool dispatch ───────────────────────────────────────────────
|
// ── Tool dispatch ───────────────────────────────────────────────
|
||||||
|
|
||||||
fn dispatch_tool(name: &str, args: &Value) -> Result<String, String> {
|
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" {
|
// Run async handler on a blocking runtime
|
||||||
return tools::memory::dispatch(name, args, None)
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
.map_err(|e| e.to_string());
|
.enable_all()
|
||||||
}
|
.build()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
if name.starts_with("channel_") {
|
let local = tokio::task::LocalSet::new();
|
||||||
return tools::channels::dispatch_blocking(name, args)
|
local.block_on(&rt, (tool.handler)(None, args.clone()))
|
||||||
.map_err(|e| e.to_string());
|
.map_err(|e| e.to_string())
|
||||||
}
|
|
||||||
|
|
||||||
Err(format!("unknown tool: {name}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Main loop ───────────────────────────────────────────────────
|
// ── Main loop ───────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue