Skip to main content

funera_orchestrate/
event.rs

1use std::sync::Arc;
2
3use serde_json::Value as JsonValue;
4
5use funera_core::chat::message::{MsgVariant, Role, TextMessage};
6#[cfg(feature = "tool")]
7use funera_core::chat::message::{ToolRequestMessage, ToolResponseMessage};
8use funera_core::event_bus::env_state_bus::EnvStateEvent;
9use funera_core::event_bus::react_bus::ReactEvent;
10use funera_core::event_bus::token_bus::TokenEvent;
11use funera_core::middleware::MiddlewareEvent;
12#[cfg(feature = "tool")]
13use funera_core::re_act::tool::ToolType;
14
15#[derive(Debug, Clone)]
16pub enum AgentEvent {
17    Text(String),
18    Reasoning(String),
19    ToolCallRequest {
20        index: usize,
21        call_id: Arc<str>,
22        name: String,
23        args: JsonValue,
24    },
25    ToolCallResult {
26        call_id: Arc<str>,
27        name: String,
28        result: Result<String, String>,
29    },
30    TurnStart,
31    TurnEnd {
32        finish_reason: Option<String>,
33    },
34    Error(String),
35    ToolApprovalRequired {
36        call_id: Arc<str>,
37        tool_name: String,
38        reason: String,
39    },
40    Done,
41}
42
43impl MiddlewareEvent for AgentEvent {
44    type Error = String;
45
46    fn assistant_text(content: String, _reasoning: Option<String>) -> Self {
47        AgentEvent::Text(content)
48    }
49
50    fn tool_call_request(call_id: Arc<str>, name: String, args: JsonValue) -> Self {
51        AgentEvent::ToolCallRequest {
52            index: 0,
53            call_id,
54            name,
55            args,
56        }
57    }
58
59    fn tool_response(call_id: Arc<str>, name: String, result: Result<String, String>) -> Self {
60        AgentEvent::ToolCallResult {
61            call_id,
62            name,
63            result,
64        }
65    }
66
67    fn turn_start() -> Self {
68        AgentEvent::TurnStart
69    }
70
71    fn turn_end(finish_reason: Option<String>) -> Self {
72        AgentEvent::TurnEnd { finish_reason }
73    }
74
75    fn done() -> Self {
76        AgentEvent::Done
77    }
78
79    fn into_session_message(self) -> Option<(Role, MsgVariant)> {
80        match self {
81            AgentEvent::Text(text) => Some((
82                Role::Assistant,
83                MsgVariant::Text(TextMessage {
84                    text: text.into(),
85                    reasoning_content: None,
86                }),
87            )),
88            #[cfg(feature = "tool")]
89            AgentEvent::ToolCallRequest {
90                call_id,
91                name,
92                args,
93                ..
94            } => Some((
95                Role::Assistant,
96                MsgVariant::ToolRequest(ToolRequestMessage {
97                    tool_call_id: call_id,
98                    tool_type: ToolType::Function,
99                    function_name: name.into(),
100                    function_args: args,
101                    reasoning_content: None,
102                }),
103            )),
104            #[cfg(feature = "tool")]
105            AgentEvent::ToolCallResult {
106                call_id,
107                name: _,
108                result,
109            } => Some((
110                Role::Tool,
111                MsgVariant::ToolResponse(ToolResponseMessage {
112                    tool_call_id: call_id,
113                    result: result.unwrap_or_default().into(),
114                }),
115            )),
116            _ => None,
117        }
118    }
119}
120
121/// Wraps a raw underlying event from the core event buses.
122///
123/// Returned by [`Agent::subscribe_raw_events`](crate::Agent::subscribe_raw_events).
124#[derive(Debug, Clone)]
125pub enum RawAgentEvent {
126    Token(TokenEvent),
127    React(ReactEvent),
128    EnvState(EnvStateEvent),
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn agent_event_text() {
137        let e = AgentEvent::Text("hello".into());
138        assert!(matches!(e, AgentEvent::Text(t) if t == "hello"));
139    }
140
141    #[test]
142    fn agent_event_tool_call_start() {
143        let e = AgentEvent::ToolCallRequest {
144            index: 0,
145            call_id: "call_abc".into(),
146            name: "test".into(),
147            args: serde_json::json!({"x": 1}),
148        };
149        assert!(matches!(e, AgentEvent::ToolCallRequest { name, .. } if name == "test"));
150    }
151
152    #[test]
153    fn agent_event_tool_call_result_ok() {
154        let e = AgentEvent::ToolCallResult {
155            call_id: "call_abc".into(),
156            name: "test".into(),
157            result: Ok("done".into()),
158        };
159        assert!(matches!(e, AgentEvent::ToolCallResult { .. }));
160    }
161
162    #[test]
163    fn agent_event_tool_call_result_err() {
164        let e = AgentEvent::ToolCallResult {
165            call_id: "call_abc".into(),
166            name: "test".into(),
167            result: Err("fail".into()),
168        };
169        assert!(matches!(
170            e,
171            AgentEvent::ToolCallResult { result: Err(_), .. }
172        ));
173    }
174
175    #[test]
176    fn agent_event_clone() {
177        let e = AgentEvent::Text("hi".into());
178        let cloned = e.clone();
179        assert!(matches!(cloned, AgentEvent::Text(t) if t == "hi"));
180    }
181
182    #[test]
183    fn agent_event_turn_end() {
184        let e = AgentEvent::TurnEnd {
185            finish_reason: Some("Stop".into()),
186        };
187        assert!(matches!(e, AgentEvent::TurnEnd { finish_reason: Some(ref r) } if r == "Stop"));
188    }
189
190    #[test]
191    fn middleware_event_into_session() {
192        let event: AgentEvent = MiddlewareEvent::assistant_text("hi".into(), None);
193        let (role, variant) = event.into_session_message().unwrap();
194        assert_eq!(role.to_string(), "assistant");
195        assert!(matches!(variant, MsgVariant::Text(_)));
196    }
197
198    #[test]
199    fn raw_token_wraps_text() {
200        let raw = RawAgentEvent::Token(TokenEvent::Text("hello".into()));
201        assert!(matches!(raw, RawAgentEvent::Token(TokenEvent::Text(t)) if t == "hello"));
202    }
203
204    #[test]
205    fn raw_react_wraps_turn_start() {
206        let raw = RawAgentEvent::React(ReactEvent::TurnStart);
207        assert!(matches!(raw, RawAgentEvent::React(ReactEvent::TurnStart)));
208    }
209
210    #[test]
211    fn raw_env_state_wraps_session_start() {
212        let raw = RawAgentEvent::EnvState(EnvStateEvent::SessionStart);
213        assert!(matches!(
214            raw,
215            RawAgentEvent::EnvState(EnvStateEvent::SessionStart)
216        ));
217    }
218
219    #[test]
220    fn raw_clone() {
221        let raw = RawAgentEvent::Token(TokenEvent::Text("x".into()));
222        let cloned = raw.clone();
223        assert!(matches!(cloned, RawAgentEvent::Token(TokenEvent::Text(t)) if t == "x"));
224    }
225
226    #[test]
227    fn raw_debug() {
228        let raw = RawAgentEvent::Token(TokenEvent::Text("x".into()));
229        let _ = format!("{raw:?}");
230    }
231}