tools: static string definitions, no runtime JSON construction

Tool definitions are now &'static str (name, description,
parameters_json) instead of runtime-constructed serde_json::Value.
No more json!() macro, no more ToolDef::new() for tool definitions.

The JSON schema strings are written directly as string literals.
When sent to the API, they can be interpolated without
serialization/deserialization.

Multi-tool modules return fixed-size arrays instead of Vecs:
- memory: [Tool; 12], journal: [Tool; 3]
- channels: [Tool; 4]
- control: [Tool; 3]
- web: [Tool; 2]

ToolDef/FunctionDef remain for backward compat (API wire format,
summarize_args) but are no longer used in tool definitions.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
ProofOfConcept 2026-04-04 15:50:14 -04:00 committed by Kent Overstreet
parent ed150df628
commit 53ad8cc9df
13 changed files with 205 additions and 561 deletions

View file

@ -35,11 +35,40 @@ pub type ToolHandler = fn(
) -> 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.
pub struct Tool {
pub def: ToolDef,
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,
)
}
/// Build a ToolDef (for backward compat where ToolDef is still used).
pub fn to_tool_def(&self) -> ToolDef {
ToolDef {
tool_type: "function".to_string(),
function: FunctionDef {
name: self.name.to_string(),
description: self.description.to_string(),
parameters: serde_json::from_str(self.parameters_json)
.expect("invalid JSON in tool parameters"),
},
}
}
}
/// Function call within a tool call — name + JSON arguments.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionCall {
@ -55,6 +84,7 @@ pub struct FunctionDef {
pub parameters: serde_json::Value,
}
/// Partial function call within a streaming delta.
#[derive(Debug, Deserialize)]
pub struct FunctionCallDelta {
@ -173,7 +203,7 @@ pub async fn dispatch_with_agent(
agent: Option<std::sync::Arc<tokio::sync::Mutex<super::Agent>>>,
) -> ToolOutput {
for tool in tools() {
if tool.def.function.name == name {
if tool.name == name {
return match (tool.handler)(agent, args.clone()).await {
Ok(s) => ToolOutput::text(s),
Err(e) => ToolOutput::error(e),
@ -190,7 +220,7 @@ pub async fn dispatch_shared(
_provenance: Option<&str>,
) -> Option<ToolOutput> {
for tool in tools() {
if tool.def.function.name == name {
if tool.name == name {
return Some(match (tool.handler)(None, args.clone()).await {
Ok(s) => ToolOutput::text(s),
Err(e) => ToolOutput::error(e),
@ -214,16 +244,16 @@ pub fn tools() -> Vec<Tool> {
all
}
/// Return all tool definitions (extracted from tools()).
/// Return all tool definitions for the API.
pub fn definitions() -> Vec<ToolDef> {
tools().into_iter().map(|t| t.def).collect()
tools().into_iter().map(|t| t.to_tool_def()).collect()
}
/// Return memory + journal tool definitions only.
pub fn memory_and_journal_definitions() -> Vec<ToolDef> {
memory::memory_tools().into_iter()
.chain(memory::journal_tools())
.map(|t| t.def)
.map(|t| t.to_tool_def())
.collect()
}