use anyhow::{anyhow, Result};
use serde::Serialize;
use serde_json::Value;
use crate::{
runtime::RuntimeHandle,
tool::{apply_patch::ApplyPatchSurface, ToolCall, ToolResult, ToolSpec},
types::{ToolCapabilityFamily, TrustLevel},
};
pub(crate) mod agent_get;
pub(crate) mod apply_patch_tool;
pub(crate) mod cancel_external_trigger;
pub(crate) mod complete_work_item;
pub(crate) mod create_external_trigger;
pub(crate) mod create_work_item;
pub(crate) mod enqueue;
pub(crate) mod exec_command;
pub(crate) mod exec_command_batch;
pub(crate) mod get_work_item;
pub(crate) mod list_work_items;
pub(crate) mod memory_get;
pub(crate) mod memory_search;
pub(crate) mod notify_operator;
pub(crate) mod pick_work_item;
pub(crate) mod sleep;
pub(crate) mod spawn_agent;
pub(crate) mod task_input;
pub(crate) mod task_list;
pub(crate) mod task_output;
pub(crate) mod task_status;
pub(crate) mod task_stop;
pub(crate) mod update_work_item;
pub(crate) mod use_workspace;
pub(crate) mod web_fetch;
pub(crate) mod web_search;
pub(crate) mod work_item_action;
pub(crate) mod work_item_query;
pub(crate) struct BuiltinToolDefinition {
pub(crate) family: ToolCapabilityFamily,
pub(crate) spec: ToolSpec,
}
pub(crate) fn builtin_tool_definitions() -> Result<Vec<BuiltinToolDefinition>> {
Ok(vec![
sleep::definition()?,
agent_get::definition()?,
notify_operator::definition()?,
enqueue::definition()?,
spawn_agent::definition()?,
task_list::definition()?,
task_status::definition()?,
task_input::definition()?,
task_output::definition()?,
task_stop::definition()?,
create_work_item::definition()?,
pick_work_item::definition()?,
get_work_item::definition()?,
list_work_items::definition()?,
update_work_item::definition()?,
complete_work_item::definition()?,
memory_search::definition()?,
memory_get::definition()?,
apply_patch_tool::definition()?,
exec_command::definition()?,
exec_command_batch::definition()?,
use_workspace::definition()?,
web_fetch::definition()?,
web_search::definition()?,
])
}
pub(crate) fn builtin_tool_definitions_for_apply_patch_surface(
surface: ApplyPatchSurface,
) -> Result<Vec<BuiltinToolDefinition>> {
builtin_tool_definitions()?
.into_iter()
.map(|definition| {
if definition.spec.name == apply_patch_tool::NAME {
apply_patch_tool::definition_for_surface(surface)
} else {
Ok(definition)
}
})
.collect()
}
pub(crate) async fn execute_builtin_tool(
runtime: &RuntimeHandle,
agent_id: &str,
trust: &TrustLevel,
call: &ToolCall,
) -> Result<ToolResult> {
match call.name.as_str() {
sleep::NAME => sleep::execute(runtime, agent_id, trust, &call.input).await,
agent_get::NAME => agent_get::execute(runtime, agent_id, trust, &call.input).await,
notify_operator::NAME => {
notify_operator::execute(runtime, agent_id, trust, &call.input).await
}
enqueue::NAME => enqueue::execute(runtime, agent_id, trust, &call.input).await,
spawn_agent::NAME => spawn_agent::execute(runtime, agent_id, trust, &call.input).await,
task_list::NAME => task_list::execute(runtime, agent_id, trust, &call.input).await,
task_status::NAME => task_status::execute(runtime, agent_id, trust, &call.input).await,
task_input::NAME => task_input::execute(runtime, agent_id, trust, &call.input).await,
task_output::NAME => task_output::execute(runtime, agent_id, trust, &call.input).await,
task_stop::NAME => task_stop::execute(runtime, agent_id, trust, &call.input).await,
create_work_item::NAME => {
create_work_item::execute(runtime, agent_id, trust, &call.input).await
}
pick_work_item::NAME => {
pick_work_item::execute(runtime, agent_id, trust, &call.input).await
}
get_work_item::NAME => get_work_item::execute(runtime, agent_id, trust, &call.input).await,
list_work_items::NAME => {
list_work_items::execute(runtime, agent_id, trust, &call.input).await
}
update_work_item::NAME => {
update_work_item::execute(runtime, agent_id, trust, &call.input).await
}
complete_work_item::NAME => {
complete_work_item::execute(runtime, agent_id, trust, &call.input).await
}
memory_search::NAME => memory_search::execute(runtime, agent_id, trust, &call.input).await,
memory_get::NAME => memory_get::execute(runtime, agent_id, trust, &call.input).await,
create_external_trigger::NAME => {
create_external_trigger::execute(runtime, agent_id, trust, &call.input).await
}
cancel_external_trigger::NAME => {
cancel_external_trigger::execute(runtime, agent_id, trust, &call.input).await
}
apply_patch_tool::NAME => {
apply_patch_tool::execute(runtime, agent_id, trust, &call.input).await
}
exec_command::NAME => exec_command::execute(runtime, agent_id, trust, &call.input).await,
exec_command_batch::NAME => {
exec_command_batch::execute(runtime, agent_id, trust, &call.input).await
}
use_workspace::NAME => use_workspace::execute(runtime, agent_id, trust, &call.input).await,
web_fetch::NAME => web_fetch::execute(runtime, agent_id, trust, &call.input).await,
web_search::NAME => web_search::execute(runtime, agent_id, trust, &call.input).await,
_ => Err(anyhow!("unknown builtin tool {}", call.name)),
}
}
pub(crate) fn render_tool_result_for_model(result: &ToolResult) -> Result<String> {
if result.is_error() {
let error = result
.tool_error()
.ok_or_else(|| anyhow!("tool error result missing error payload"))?;
return Ok(error.render_for_model(Some(&result.envelope.tool_name)));
}
match result.envelope.tool_name.as_str() {
apply_patch_tool::NAME => apply_patch_tool::render_for_model(result),
exec_command::NAME => exec_command::render_for_model(result),
exec_command_batch::NAME => exec_command_batch::render_for_model(result),
task_output::NAME => task_output::render_for_model(result),
_ => canonical_json_render(result),
}
}
pub(crate) fn canonical_json_render(result: &ToolResult) -> Result<String> {
serde_json::to_string(&result.envelope).map_err(Into::into)
}
pub(crate) fn serialize_success<T: Serialize>(tool_name: &str, value: &T) -> Result<ToolResult> {
let value = serde_json::to_value(value)?;
Ok(success_from_value(tool_name, value))
}
pub(crate) fn success_from_value(tool_name: &str, mut value: Value) -> ToolResult {
let summary_text = match &mut value {
Value::Object(map) => map
.remove("summary_text")
.and_then(|value| value.as_str().map(ToString::to_string)),
_ => None,
};
ToolResult::success(tool_name, value, summary_text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn non_command_tools_default_to_canonical_json_render() {
let result = ToolResult::success(
"AgentGet",
serde_json::json!({"agent": {"id": "default"}}),
None,
);
let rendered = render_tool_result_for_model(&result).unwrap();
assert!(rendered.starts_with("{\"tool_name\":\"AgentGet\""));
}
#[test]
fn tool_errors_use_shared_model_visible_receipt() {
let result = ToolResult::error(
"ExecCommand",
crate::tool::ToolError::new("invalid_tool_input", "missing required field")
.with_details(serde_json::json!({ "field": "cmd" }))
.with_recovery_hint("provide `cmd`"),
);
let rendered = render_tool_result_for_model(&result).unwrap();
let receipt: serde_json::Value = serde_json::from_str(&rendered).unwrap();
assert_eq!(receipt["ok"], false);
assert_eq!(receipt["tool_name"], "ExecCommand");
assert_eq!(receipt["kind"], "invalid_tool_input");
assert_eq!(receipt["field"], "cmd");
assert_eq!(receipt["hint"], "provide `cmd`");
}
}