Skip to main content

rskit_agent/hooks/
mod.rs

1//! Domain-specific hook event types for the agentic pipeline.
2//!
3//! Each struct implements [`rskit_hook::Event`] so it can be emitted through
4//! a [`rskit_hook::HookRegistry`].
5
6use rskit_hook::{Event, EventType};
7use rskit_llm::types::{AssistantMessage, CompletionRequest, CompletionResponse};
8use rskit_tool::ToolInput;
9use rskit_tool::ToolResult;
10
11// ── Event type constants ────────────────────────────────────────────────────
12
13/// Event type for canonical `on_tool_call` observations.
14pub fn on_tool_call_type() -> EventType {
15    EventType::new("on_tool_call")
16}
17
18/// Event type for [`PreToolCall`].
19pub fn pre_tool_call_type() -> EventType {
20    on_tool_call_type()
21}
22
23/// Event type for canonical `on_tool_result` observations.
24pub fn on_tool_result_type() -> EventType {
25    EventType::new("on_tool_result")
26}
27
28/// Event type for tool-result observations.
29pub fn post_tool_call_type() -> EventType {
30    on_tool_result_type()
31}
32
33/// Event type for canonical `on_llm_call` observations.
34pub fn on_llm_call_type() -> EventType {
35    EventType::new("on_llm_call")
36}
37
38/// Event type for [`PreLLMCall`].
39pub fn pre_llm_call_type() -> EventType {
40    on_llm_call_type()
41}
42
43/// Event type for canonical `on_llm_response` observations.
44pub fn on_llm_response_type() -> EventType {
45    EventType::new("on_llm_response")
46}
47
48/// Event type for LLM-response observations.
49pub fn post_llm_call_type() -> EventType {
50    on_llm_response_type()
51}
52
53/// Event type for [`OnError`].
54pub fn on_error_type() -> EventType {
55    EventType::new("on_error")
56}
57
58/// Event type for canonical [`TurnStart`].
59pub fn on_turn_start_type() -> EventType {
60    EventType::new("on_turn_start")
61}
62
63/// Event type for [`TurnStart`].
64pub fn turn_start_type() -> EventType {
65    on_turn_start_type()
66}
67
68/// Event type for canonical [`TurnEnd`].
69pub fn on_turn_complete_type() -> EventType {
70    EventType::new("on_turn_complete")
71}
72
73/// Event type for [`TurnEnd`].
74pub fn turn_end_type() -> EventType {
75    on_turn_complete_type()
76}
77
78/// Event type for canonical stream/event observations.
79pub fn on_event_type() -> EventType {
80    EventType::new("on_event")
81}
82
83/// Event type for canonical MCP call observations.
84pub fn on_mcp_call_type() -> EventType {
85    EventType::new("on_mcp_call")
86}
87
88/// Event type for canonical MCP result observations.
89pub fn on_mcp_result_type() -> EventType {
90    EventType::new("on_mcp_result")
91}
92
93// ── Event structs ───────────────────────────────────────────────────────────
94
95/// Canonical observe-only event stream observation.
96#[derive(Debug, Clone)]
97pub struct OnEvent {
98    /// Observed event payload.
99    pub event: rskit_ai::StreamEventRef,
100}
101
102impl Event for OnEvent {
103    fn event_type(&self) -> EventType {
104        on_event_type()
105    }
106}
107
108/// Canonical observe-only MCP call event.
109#[derive(Debug, Clone)]
110pub struct OnMCPCall {
111    /// MCP method or operation name.
112    pub method: String,
113}
114
115impl Event for OnMCPCall {
116    fn event_type(&self) -> EventType {
117        on_mcp_call_type()
118    }
119}
120
121/// Canonical observe-only MCP result event.
122#[derive(Debug, Clone)]
123pub struct OnMCPResult {
124    /// MCP method or operation name.
125    pub method: String,
126    /// Optional result payload.
127    pub result: Option<serde_json::Value>,
128    /// Optional error text.
129    pub error: Option<String>,
130}
131
132impl Event for OnMCPResult {
133    fn event_type(&self) -> EventType {
134        on_mcp_result_type()
135    }
136}
137
138/// Fired before a tool call is executed.
139#[derive(Debug, Clone)]
140pub struct PreToolCall {
141    /// Tool name requested by the model.
142    pub name: String,
143    /// Tool input payload passed to the executor.
144    pub input: ToolInput,
145}
146
147impl Event for PreToolCall {
148    fn event_type(&self) -> EventType {
149        pre_tool_call_type()
150    }
151}
152
153/// Fired after a tool call completes.
154#[derive(Debug, Clone)]
155pub struct PostToolCall {
156    /// Tool name requested by the model.
157    pub name: String,
158    /// Tool input payload passed to the executor.
159    pub input: ToolInput,
160    /// Successful tool result, when execution completed.
161    pub result: Option<ToolResult>,
162    /// Error text, when execution failed.
163    pub error: Option<String>,
164}
165
166impl Event for PostToolCall {
167    fn event_type(&self) -> EventType {
168        post_tool_call_type()
169    }
170}
171
172/// Fired before an LLM completion request is sent.
173#[derive(Debug, Clone)]
174pub struct PreLLMCall {
175    /// Completion request that will be sent to the provider.
176    pub request: CompletionRequest,
177}
178
179impl Event for PreLLMCall {
180    fn event_type(&self) -> EventType {
181        pre_llm_call_type()
182    }
183}
184
185/// Fired after an LLM completion response is received.
186#[derive(Debug, Clone)]
187pub struct PostLLMCall {
188    /// Completion response returned by the provider.
189    pub response: CompletionResponse,
190    /// Error text, when the provider call failed.
191    pub error: Option<String>,
192}
193
194impl Event for PostLLMCall {
195    fn event_type(&self) -> EventType {
196        post_llm_call_type()
197    }
198}
199
200/// Fired when an error occurs anywhere in the pipeline.
201#[derive(Debug, Clone)]
202pub struct OnError {
203    /// Error message reported by the failing operation.
204    pub error: String,
205    /// Pipeline stage or component that reported the error.
206    pub source: String,
207}
208
209impl Event for OnError {
210    fn event_type(&self) -> EventType {
211        on_error_type()
212    }
213}
214
215/// Fired at the start of an agent turn.
216#[derive(Debug, Clone)]
217pub struct TurnStart {
218    /// Turn number being started.
219    pub turn: u32,
220}
221
222impl Event for TurnStart {
223    fn event_type(&self) -> EventType {
224        turn_start_type()
225    }
226}
227
228/// Fired at the end of an agent turn.
229#[derive(Debug, Clone)]
230pub struct TurnEnd {
231    /// Turn number that completed.
232    pub turn: u32,
233    /// Assistant message produced during the turn.
234    pub message: AssistantMessage,
235}
236
237impl Event for TurnEnd {
238    fn event_type(&self) -> EventType {
239        turn_end_type()
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use rskit_hook::EventType;
247    use rskit_tool::ToolInput;
248
249    #[test]
250    fn test_pre_tool_call_event() {
251        let event = PreToolCall {
252            name: "calculator".to_string(),
253            input: ToolInput::new(serde_json::json!({"x": 1})).unwrap(),
254        };
255        assert_eq!(event.event_type(), EventType::new("on_tool_call"));
256        assert_eq!(event.name, "calculator");
257    }
258
259    #[test]
260    fn test_post_tool_call_event() {
261        let event = PostToolCall {
262            name: "calculator".to_string(),
263            input: ToolInput::new(serde_json::json!({})).unwrap(),
264            result: None,
265            error: Some("timeout".to_string()),
266        };
267        assert_eq!(event.event_type(), EventType::new("on_tool_result"));
268    }
269
270    #[test]
271    fn test_pre_llm_call_event() {
272        let event = PreLLMCall {
273            request: CompletionRequest {
274                model: "test".to_string(),
275                messages: vec![],
276                max_tokens: None,
277                temperature: None,
278                stream: false,
279                tools: None,
280                tool_choice: None,
281            },
282        };
283        assert_eq!(event.event_type(), EventType::new("on_llm_call"));
284    }
285
286    #[test]
287    fn test_on_error_event() {
288        let event = OnError {
289            error: "boom".to_string(),
290            source: "agent".to_string(),
291        };
292        assert_eq!(event.event_type(), EventType::new("on_error"));
293    }
294
295    #[test]
296    fn test_turn_start_event() {
297        let event = TurnStart { turn: 0 };
298        assert_eq!(event.event_type(), EventType::new("on_turn_start"));
299        assert_eq!(event.turn, 0);
300    }
301
302    #[test]
303    fn test_turn_end_event() {
304        use rskit_llm::types;
305        let event = TurnEnd {
306            turn: 3,
307            message: AssistantMessage {
308                content: types::text_content("done"),
309                tool_calls: vec![],
310                usage: None,
311            },
312        };
313        assert_eq!(event.event_type(), EventType::new("on_turn_complete"));
314    }
315
316    #[test]
317    fn test_event_type_constants() {
318        assert_eq!(pre_tool_call_type(), EventType::new("on_tool_call"));
319        assert_eq!(post_tool_call_type(), EventType::new("on_tool_result"));
320        assert_eq!(pre_llm_call_type(), EventType::new("on_llm_call"));
321        assert_eq!(post_llm_call_type(), EventType::new("on_llm_response"));
322        assert_eq!(on_error_type(), EventType::new("on_error"));
323        assert_eq!(turn_start_type(), EventType::new("on_turn_start"));
324        assert_eq!(turn_end_type(), EventType::new("on_turn_complete"));
325    }
326}