1use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::Duration;
5
6use talos_core::message::{Message, ToolCall};
7use talos_core::provider::ProviderError;
8use talos_core::tool::ToolResult;
9use talos_permission::PermissionDecision;
10
11static NEXT_TURN_ID: AtomicU64 = AtomicU64::new(1);
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct TurnId(pub u64);
16
17impl TurnId {
18 #[must_use]
20 pub fn new() -> Self {
21 Self(NEXT_TURN_ID.fetch_add(1, Ordering::Relaxed))
22 }
23}
24
25impl Default for TurnId {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum TurnStatus {
34 Success,
36 ProviderError,
38 UnexpectedEvent,
40 BudgetExceeded,
42 DoomLoopDetected,
44 Denied,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum TurnEndReason {
51 EndTurn,
53 ToolUse,
55 MaxTokens,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum BudgetKind {
62 ToolCalls,
64}
65
66#[derive(Debug, Clone)]
68pub struct ToolObservation {
69 pub call: ToolCall,
71 pub result: ToolResult,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77pub enum HookEventKind {
78 TurnStart,
80 OnSystemPromptBuilt,
82 BeforeProviderCall,
84 AfterProviderCall,
86 OnTextDelta,
88 OnToolCallProposed,
90 BeforeToolBatch,
92 BeforePermissionCheck,
94 AfterPermissionCheck,
96 BeforeBashSandboxExec,
98 AfterBashSandboxExec,
100 BeforeToolCall,
102 AfterToolCall,
104 OnToolResultObserved,
106 AfterToolBatch,
108 OnDoomLoopDetected,
110 OnBudgetExceeded,
112 OnProviderError,
114 OnTurnEnd,
116 TurnComplete,
118}
119
120impl std::fmt::Display for HookEventKind {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "{self:?}")
123 }
124}
125
126pub const ALL_HOOK_EVENT_KINDS: [HookEventKind; 20] = [
128 HookEventKind::TurnStart,
129 HookEventKind::OnSystemPromptBuilt,
130 HookEventKind::BeforeProviderCall,
131 HookEventKind::AfterProviderCall,
132 HookEventKind::OnTextDelta,
133 HookEventKind::OnToolCallProposed,
134 HookEventKind::BeforeToolBatch,
135 HookEventKind::BeforePermissionCheck,
136 HookEventKind::AfterPermissionCheck,
137 HookEventKind::BeforeBashSandboxExec,
138 HookEventKind::AfterBashSandboxExec,
139 HookEventKind::BeforeToolCall,
140 HookEventKind::AfterToolCall,
141 HookEventKind::OnToolResultObserved,
142 HookEventKind::AfterToolBatch,
143 HookEventKind::OnDoomLoopDetected,
144 HookEventKind::OnBudgetExceeded,
145 HookEventKind::OnProviderError,
146 HookEventKind::OnTurnEnd,
147 HookEventKind::TurnComplete,
148];
149
150#[derive(Debug)]
152#[non_exhaustive]
153pub enum HookEvent<'a> {
154 TurnStart {
156 turn_id: TurnId,
158 },
159 OnSystemPromptBuilt {
161 prompt: &'a str,
163 },
164 BeforeProviderCall {
166 messages: &'a [Message],
168 },
169 AfterProviderCall {
171 tokens_in: u32,
173 tokens_out: u32,
175 },
176 OnTextDelta {
178 text: &'a str,
180 },
181 OnToolCallProposed {
183 call: &'a ToolCall,
185 },
186 BeforeToolBatch {
188 calls: &'a [ToolCall],
190 },
191 BeforePermissionCheck {
193 call: &'a ToolCall,
195 },
196 AfterPermissionCheck {
198 call: &'a ToolCall,
200 decision: PermissionDecision,
202 },
203 BeforeBashSandboxExec {
205 command: &'a str,
207 },
208 AfterBashSandboxExec {
210 exit: i32,
212 duration: Duration,
214 },
215 BeforeToolCall {
217 call: &'a ToolCall,
219 },
220 AfterToolCall {
222 call: &'a ToolCall,
224 result: &'a ToolResult,
226 },
227 OnToolResultObserved {
229 observation: &'a ToolObservation,
231 },
232 AfterToolBatch {
234 results: &'a [ToolResult],
236 },
237 OnDoomLoopDetected {
239 signature: &'a str,
241 },
242 OnBudgetExceeded {
244 kind: BudgetKind,
246 used: u64,
248 limit: u64,
250 },
251 OnProviderError {
253 error: &'a ProviderError,
255 },
256 OnTurnEnd {
258 reason: TurnEndReason,
260 },
261 TurnComplete {
263 turn_id: TurnId,
265 status: TurnStatus,
267 },
268}
269
270impl HookEvent<'_> {
271 #[must_use]
273 pub fn kind(&self) -> HookEventKind {
274 match self {
275 Self::TurnStart { .. } => HookEventKind::TurnStart,
276 Self::OnSystemPromptBuilt { .. } => HookEventKind::OnSystemPromptBuilt,
277 Self::BeforeProviderCall { .. } => HookEventKind::BeforeProviderCall,
278 Self::AfterProviderCall { .. } => HookEventKind::AfterProviderCall,
279 Self::OnTextDelta { .. } => HookEventKind::OnTextDelta,
280 Self::OnToolCallProposed { .. } => HookEventKind::OnToolCallProposed,
281 Self::BeforeToolBatch { .. } => HookEventKind::BeforeToolBatch,
282 Self::BeforePermissionCheck { .. } => HookEventKind::BeforePermissionCheck,
283 Self::AfterPermissionCheck { .. } => HookEventKind::AfterPermissionCheck,
284 Self::BeforeBashSandboxExec { .. } => HookEventKind::BeforeBashSandboxExec,
285 Self::AfterBashSandboxExec { .. } => HookEventKind::AfterBashSandboxExec,
286 Self::BeforeToolCall { .. } => HookEventKind::BeforeToolCall,
287 Self::AfterToolCall { .. } => HookEventKind::AfterToolCall,
288 Self::OnToolResultObserved { .. } => HookEventKind::OnToolResultObserved,
289 Self::AfterToolBatch { .. } => HookEventKind::AfterToolBatch,
290 Self::OnDoomLoopDetected { .. } => HookEventKind::OnDoomLoopDetected,
291 Self::OnBudgetExceeded { .. } => HookEventKind::OnBudgetExceeded,
292 Self::OnProviderError { .. } => HookEventKind::OnProviderError,
293 Self::OnTurnEnd { .. } => HookEventKind::OnTurnEnd,
294 Self::TurnComplete { .. } => HookEventKind::TurnComplete,
295 }
296 }
297
298 #[must_use]
300 pub fn is_permission_boundary(&self) -> bool {
301 matches!(
302 self,
303 Self::OnToolCallProposed { .. }
304 | Self::BeforePermissionCheck { .. }
305 | Self::AfterPermissionCheck { .. }
306 )
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn kind_round_trip() {
316 let turn_id = TurnId::new();
317 let event = HookEvent::TurnStart { turn_id };
318 assert_eq!(event.kind(), HookEventKind::TurnStart);
319 assert_eq!(ALL_HOOK_EVENT_KINDS.len(), 20);
320 }
321}