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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Built-in tool trait with lifecycle management.
//!
//! Every built-in tool implements `BuiltinTool`. The LLM handler manages
//! the lifecycle:
//!
//! ```text
//! Pipeline build:
//! handler.add_tool(tool) → register_all() called
//!
//! StartFrame:
//! tool.on_start(cancel_token) → connect, cache, spawn background tasks
//!
//! Running:
//! handlers execute via registry → OnceCell reads, pool connections
//!
//! EndFrame:
//! tool.on_stop() → flush caches, return connections, log
//!
//! CancelFrame:
//! tool.on_cancel() → cancel in-flight work, then on_stop()
//! (also: cancel_token triggers → background tasks exit via select!)
//! ```
//!
//! # Cancellation token
//!
//! The handler creates a `CancellationToken` and passes a child to each
//! tool's `on_start()`. Background tasks (`tokio::spawn`) should select
//! on the token so they exit cleanly:
//!
//! ```rust,ignore
//! tokio::spawn(async move {
//! tokio::select! {
//! _ = cancel.cancelled() => {
//! log::info!("background task cancelled");
//! }
//! result = connection.await => {
//! if let Err(e) = result {
//! log::error!("connection error: {}", e);
//! }
//! }
//! }
//! });
//! ```
//!
//! # Cacheable vs non-cacheable
//!
//! - **Cacheable** (`is_cacheable() → true`): needs async init on StartFrame.
//! Example: Postgres (connect + schema introspection).
//! - **Non-cacheable** (`is_cacheable() → false`): ready immediately.
//! `on_start()` is a no-op. Example: a calculator tool.
//!
//! Both types receive lifecycle hooks — a non-cacheable tool might still
//! need `on_stop()` to flush metrics or log stats.
use async_trait;
use CancellationToken;
use crateFunctionSchema;
use crateResult;
use crateFunctionRegistry;
// ---------------------------------------------------------------------------
// Lifecycle state (for debug / Drop warnings)
// ---------------------------------------------------------------------------
/// Tracks which lifecycle hooks have been called.
///
/// Tools can use this to log warnings in `Drop` if `on_stop` was never
/// called (i.e., someone forgot to handle EndFrame).
// ---------------------------------------------------------------------------
// BuiltinTool trait
// ---------------------------------------------------------------------------
/// A built-in tool that can be attached to an LLM handler.
///
/// Implements lifecycle hooks for startup, shutdown, and cancellation.
/// The handler orchestrates — tools manage their own resources.