1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! Tool trait — implementations live in caliban-tools-builtin (D) and downstream.
use std::sync::Arc;
use async_trait::async_trait;
use caliban_provider::ContentBlock;
use tokio_util::sync::CancellationToken;
/// Context passed to a Tool's `invoke` method.
#[derive(Clone)]
pub struct ToolContext {
/// The model-assigned `tool_use_id` this invocation corresponds to.
pub tool_use_id: String,
/// Cancellation token; tools must honor this for long-running work.
pub cancel: CancellationToken,
/// Hooks handle so tools can fire `FileChanged`, `Notification`, etc. on
/// successful side effects. Falls back to a no-op when unset (which is the
/// case in unit tests that don't construct an `Agent`).
pub hooks: Option<Arc<dyn crate::hooks::Hooks + Send + Sync>>,
/// Zero-based turn index — surfaced to hooks for correlation with the
/// surrounding turn.
pub turn_index: u32,
}
impl std::fmt::Debug for ToolContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ToolContext")
.field("tool_use_id", &self.tool_use_id)
.field("turn_index", &self.turn_index)
.field("hooks", &self.hooks.is_some())
.finish_non_exhaustive()
}
}
/// Errors a `Tool::invoke` can return.
#[derive(thiserror::Error, Debug)]
pub enum ToolError {
/// The JSON input did not match the expected schema.
#[error("invalid input: {0}")]
InvalidInput(String),
/// The tool encountered a runtime failure.
#[error("execution failed: {0}")]
Execution(#[source] Box<dyn std::error::Error + Send + Sync>),
/// The tool was cancelled before it could complete.
#[error("cancelled")]
Cancelled,
}
impl ToolError {
/// Construct an `Execution` variant from any error type.
pub fn execution(e: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::Execution(Box::new(e))
}
/// Construct an `InvalidInput` variant.
pub fn invalid_input(msg: impl Into<String>) -> Self {
Self::InvalidInput(msg.into())
}
}
/// Tool implementations register with `ToolRegistry`; the agent dispatches
/// `Provider`-emitted `tool_use` blocks to the matching `Tool::invoke`.
///
/// # Errors
///
/// Implementors of `invoke` should return [`ToolError::InvalidInput`] when the
/// provided JSON does not conform to the declared schema, and
/// [`ToolError::Execution`] for runtime failures.
#[async_trait]
pub trait Tool: Send + Sync {
/// Stable, unique-within-registry name. Must match the model's
/// expected tool name in the system prompt or schema.
fn name(&self) -> &str;
/// Description sent to the model.
fn description(&self) -> &str;
/// JSON Schema for the input. Returned by reference to avoid cloning
/// per request.
fn input_schema(&self) -> &serde_json::Value;
/// Execute the tool. Returns the content blocks to splice into the
/// `ToolResult` message.
///
/// # Errors
///
/// Returns [`ToolError::InvalidInput`] if the input does not match the
/// schema, [`ToolError::Execution`] on runtime failure, or
/// [`ToolError::Cancelled`] if the cancellation token was fired.
async fn invoke(
&self,
input: serde_json::Value,
cx: ToolContext,
) -> std::result::Result<Vec<ContentBlock>, ToolError>;
/// Returns `Some(key)` when this tool call has a conflict identity that
/// must not run in parallel with another call sharing the same key.
/// Returns `None` (the default) when the tool is fully parallel-safe.
///
/// The dispatcher groups batched calls by key: the `None` group runs
/// fully in parallel (subject to the existing `parallel_tools` semaphore);
/// each non-`None` key group runs serially in submission order. Groups
/// run in parallel against each other.
///
/// Override for tools whose effect is keyed to a specific target —
/// typically the canonicalized path of a file the tool writes, or a
/// scope+topic string for memory-tier writes. See ADR 0016 (Revised
/// 2026-05-26) for the rationale.
fn parallel_conflict_key(&self, _input: &serde_json::Value) -> Option<String> {
None
}
/// Optional downcast hook for recovering a tool's concrete type at
/// runtime. The default returns `None`; tools that expose extra
/// session metadata beyond the trait (e.g. the `Skill` tool surfacing
/// its loaded skill names) override it to return `Some(self)`.
fn as_any(&self) -> Option<&dyn std::any::Any> {
None
}
}