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
//! User-approval gate for tool calls.
//!
//! Where [`ToolPolicy`](crate::ToolPolicy) is the *static* gate (which
//! tools are permitted at all), [`ApprovalHandler`] is the *dynamic*,
//! per-call gate. It runs after policy and before the tool actually
//! executes; the model never sees the call directly — only the
//! resulting tool_result, success or denial.
//!
//! ## Decision shape
//!
//! [`ApprovalDecision::Deny`] is a *recoverable* outcome: it surfaces
//! to the model as `is_error: true` `tool_result` so the LLM can pick
//! a different approach. It is **not** an `AgentError` — the loop
//! continues. This is consistent with how policy denials and missing
//! tools are handled.
//!
//! ## How consumers use it
//!
//! ```ignore
//! struct MyTuiApproval { tx: mpsc::Sender<PendingDecision> }
//!
//! #[async_trait]
//! impl ApprovalHandler for MyTuiApproval {
//! async fn approve(&self, name: &str, input: &Value, class: ToolClass)
//! -> ApprovalDecision
//! {
//! // Cheap path: blanket-allow read-only tools without a prompt.
//! if class == ToolClass::ReadOnly { return ApprovalDecision::Allow; }
//! // Otherwise hand off to the UI thread and await its answer.
//! let (tx, rx) = oneshot::channel();
//! self.tx.send(PendingDecision { name, input, reply: tx }).await.ok();
//! rx.await.unwrap_or(ApprovalDecision::Deny("UI gone".into()))
//! }
//! }
//! ```
//!
//! ## Cancellation
//!
//! The executor wraps `approve()` in `tokio::select!` against
//! `ctx.cancel.cancelled()`. If the caller cancels while the handler
//! is awaiting a user click, the executor short-circuits with a
//! cancelled tool_result rather than waiting forever.
//!
//! ## Concurrent invocation
//!
//! When the consumer opts a tool into the concurrent-mutator pool via
//! [`crate::AgentBuilder::tool_concurrency`], a single LLM batch may
//! produce multiple promoted-mutator calls executing in parallel.
//! Each task invokes `approve()` independently, so an
//! [`ApprovalHandler`] implementation may be called from several
//! tasks at once. The trait already requires `Send + Sync`, so type
//! safety is unconditional. UI-bound implementations (TUI modal, web
//! confirmation dialog, etc.) should serialise their UI access
//! internally — typically by funnelling all incoming
//! `PendingDecision`s through a single `mpsc::channel` into the UI
//! thread, which the example pattern below already shows. Stateless
//! handlers (such as [`AutoApprove`] or any pure-policy decision
//! function) are concurrent-safe out of the box.
use async_trait;
use Value;
use crateToolClass;
/// What an [`ApprovalHandler`] returns for a given tool call.
///
/// `Deny(reason)` is recoverable — the runtime emits an
/// `is_error: true` tool_result containing the reason and lets the
/// model adapt. It is **not** an `AgentError`.
/// Gate that decides per-call whether a tool may run.
///
/// Implementations are typically UI-bound (TUI modal, web confirmation
/// dialog, Slack interactive button, etc.) and may block until the
/// human responds. The runtime races the wait against the agent's
/// cancellation token — the handler does not need to plumb cancel
/// itself, but it *should* take care to avoid leaking resources if
/// dropped mid-await.
///
/// The `class` parameter is provided so handlers can fast-path
/// read-only tools without bothering the user.
///
/// Concurrent invocation: when the consumer enables concurrent
/// mutators via [`crate::AgentBuilder::tool_concurrency`], `approve`
/// may be called from several executor tasks at once. UI-bound
/// implementations should serialise their UI access internally; see
/// the module-level "Concurrent invocation" section for the
/// recommended `mpsc`-into-the-UI-thread pattern.
/// Default handler: allow every tool unconditionally.
///
/// Used when `AgentBuilder::approval(...)` was not called — preserves
/// pre-approval-flow behaviour exactly. Headless / batch / CI agents
/// typically want this; interactive consumers replace it with their
/// own implementation.
;