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
123
124
125
126
127
128
129
130
131
132
//! The typed tool contract: [`ToolMeta`] (a tool's identity and effect) and
//! [`ToolHandler`] (its typed input, output, and behavior).
//!
//! These two traits are split along the seam the future `#[derive(Tool)]`
//! macro will cut, described under [the derive seam](#the-derive-seam).
use async_trait;
use Effect;
use JsonSchema;
use Serialize;
use DeserializeOwned;
use Value;
use crateToolCtx;
use crateHandlerError;
use crateToolOutcome;
/// A tool's static identity: the name a model calls it by, a human
/// description, and its side-effect [`Effect`] class.
///
/// This is the metadata half of the tool contract, split out from
/// [`ToolHandler`] on purpose (see [the derive seam](#the-derive-seam)). It
/// carries no behavior and no associated types, so a proc-macro can generate
/// it from struct-level attributes with nothing to infer.
///
/// The three members are associated constants because a native Rust tool knows
/// all three at compile time. (Tools whose identity is known only at runtime,
/// such as MCP-backed tools, do not implement this trait at all; they
/// implement the type-erased [`DynTool`](crate::DynTool) directly, whose
/// name/description/effect are methods.)
///
/// # The derive seam
///
/// Tool definition is a derive plus a hand-written impl:
///
/// ```ignore
/// #[derive(Tool)]
/// #[tool(effect = "write", description = "Create a Jira ticket")]
/// struct CreateTicket;
///
/// impl ToolHandler for CreateTicket {
/// type Input = TicketRequest;
/// type Output = TicketRef;
/// async fn call(&self, ctx: &ToolCtx, input: TicketRequest) -> Result<...> { ... }
/// }
/// ```
///
/// The split between these two traits is exactly the split between what the
/// macro writes and what the user writes:
///
/// - **`#[derive(Tool)]` generates the `ToolMeta` impl.** It reads the struct
/// name for [`NAME`](Self::NAME) (overridable by a `name = "..."` attribute),
/// the `description = "..."` attribute for [`DESCRIPTION`](Self::DESCRIPTION),
/// and the `effect = "read" | "idempotent" | "write"` attribute for
/// [`EFFECT`](Self::EFFECT). It generates no `call` body and touches none of
/// the typed input or output.
/// - **The user writes the `ToolHandler` impl.** They choose the `Input` and
/// `Output` types and write the async `call`. The input JSON Schema is not
/// hand-written: it is derived from `Input` by `schemars`, surfaced by the
/// provided [`ToolHandler::input_schema`] method.
///
/// Because the metadata lives in its own trait with no reference to `Input`,
/// `Output`, or `call`, the macro never has to parse or reason about the
/// handler body. That is the whole reason for the split, and it is why this
/// trait must stay behavior-free.
/// The behavior half of the tool contract: typed input, typed output, and the
/// async `call` that turns one into the other.
///
/// A type implements this by hand (the macro only generates its
/// [`ToolMeta`] supertrait; see [the derive seam](ToolMeta#the-derive-seam)).
/// The associated types carry the bounds the rest of the system needs:
///
/// - `Input: DeserializeOwned` so the type-erased layer can turn the model's
/// JSON into it, and `Input: JsonSchema` so a schema can be generated to
/// hand the model in the first place.
/// - `Output: Serialize` so the type-erased layer can turn the handler's
/// result back into JSON for the event log.
///
/// The `Send`/`Sync` bounds and the `Send` bound on `Input` are what let a
/// handler be wrapped as a [`DynTool`](crate::DynTool) and dispatched from an
/// async runtime that may move the work across threads.
///
/// `call` returns a [`ToolOutcome`], not a bare `Output`: a human-in-the-loop
/// tool may return [`ToolOutcome::Suspend`] to park the run. Its error type is
/// [`HandlerError`], the tool's own failure; a schema mismatch is impossible
/// here because `call` only ever receives an already-deserialized `Input`.