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
133
134
135
136
137
138
139
140
141
//! The two error surfaces of the tool layer: [`HandlerError`], which a tool's
//! own code returns, and [`ToolError`], which the type-erased dispatch layer
//! returns.
//!
//! Keeping them separate is the point. The runtime loop treats a bad
//! input from the model differently from a tool that ran and failed: the first
//! is fed back to the model so it can correct its arguments, the second is
//! recorded and surfaced per the effect's retry policy. That difference is
//! encoded as distinct [`ToolError`] variants, not left to string matching.
use Error;
/// The error a [`ToolHandler`](crate::ToolHandler) returns from its own code.
///
/// A handler is only ever called with an already-typed, already-validated
/// input, so it never has to report a schema mismatch. What it reports is a
/// genuine failure of the work it does: a provider rejected the request, a
/// precondition did not hold, and so on. This type carries a human-readable
/// message and an optional underlying source error.
///
/// The dispatch layer wraps a returned `HandlerError` in
/// [`ToolError::Handler`], tagging it with the tool name.
/// The error the type-erased dispatch layer
/// ([`DynTool::call_json`](crate::DynTool::call_json)) returns.
///
/// The variants are deliberately distinct so the runtime loop can route on them:
///
/// - [`ToolError::InvalidInput`] means the JSON the model produced did not
/// deserialize into the tool's `Input` type. The handler was **not** called.
/// The loop feeds this back to the model to let it fix its arguments.
/// - [`ToolError::Handler`] means the handler ran and returned a
/// [`HandlerError`]. This is a real execution failure, subject to the
/// effect's [retry policy](crate::RetryPolicy).
/// - [`ToolError::MalformedResult`] means the tool ran and returned something
/// this layer could not read as a result. Like `OutputSerialization` it is
/// the tool author's own bug, so it is not retryable: the same bytes decode
/// the same way on a second attempt, and the only thing a retry buys is a
/// slower answer to a question already settled.
/// - [`ToolError::OutputSerialization`] means the handler succeeded but its
/// `Output` value could not be serialized to JSON. This is an internal fault
/// in the tool definition, not the model's doing and not a retryable failure.
/// - [`ToolError::MissingIdempotencyKey`] means the operator declared which
/// input field identifies this tool's calls and the call does not carry it.
/// The tool was **not** called, and it will not be called unkeyed. Like
/// `InvalidInput` this is the arguments being wrong, so the loop feeds it back
/// to the model rather than retrying it.