use std::sync::{Arc, Mutex};
use kernel::{
AxCode, AxError, CostTier, Effect, Payload, RenderIntent, Temporal, Tool, ToolCall, ToolMeta,
ToolName, ToolOutcome,
};
use crate::catalog::Catalog;
pub struct CallTool {
catalog: Arc<Mutex<Catalog>>,
meta: ToolMeta,
}
impl CallTool {
pub const NAME: &'static str = "call";
pub fn new(catalog: Arc<Mutex<Catalog>>) -> Result<CallTool, AxError> {
Ok(CallTool {
catalog,
meta: ToolMeta {
name: ToolName::parse(Self::NAME)?,
disclosure: "Run a tool from the dormant index by its name. `describe` it first \
for its input schema; arguments that do not fit are refused with \
the schema."
.to_owned(),
params: Payload::of(&serde_json::json!({
"type": "object",
"properties": {
"name": { "type": "string", "description": "the tool to run, as the \
dormant index or `describe` spells it" },
"args": { "type": "object", "description": "the tool's own \
arguments, as its input schema asks" },
},
"required": ["name", "args"],
}))?,
effect: Effect::Read,
cost_tier: CostTier::Free,
timeout: None,
render: RenderIntent::Generic,
temporal: Temporal::Timeless,
},
})
}
}
impl Tool for CallTool {
fn meta(&self) -> &ToolMeta {
&self.meta
}
fn invoke(&self, call: &ToolCall) -> Result<ToolOutcome, AxError> {
if call.name != self.meta.name {
return Err(AxError::failure(
AxCode::InvalidArgs,
"call a dormant tool",
format!("call routed to the wrong tool: {}", call.name.as_str()),
)
.with_recovery(format!(
"call `{}`, the name this tool answers to",
Self::NAME
)));
}
let resolved = self
.catalog
.lock()
.map_err(|_| super::describe::poisoned())?
.resolve_call(call)?;
Err(AxError::failure(
AxCode::ToolUnavailable,
"call a dormant tool",
format!("`{}` was not resolved before it ran", resolved.name),
)
.with_recovery(
"report this against the tool face driving this run: it must resolve a `call` \
before admitting it",
))
}
}