Skip to main content

steer_core/app/domain/
event.rs

1use crate::app::conversation::Message;
2use crate::app::domain::action::{ApprovalDecision, ApprovalMemory, McpServerState};
3use crate::app::domain::types::{
4    CompactionRecord, MessageId, OpId, RequestId, SessionId, ToolCallId,
5};
6use crate::config::model::ModelId;
7use crate::session::state::SessionConfig;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use steer_tools::ToolCall;
11use steer_tools::result::ToolResult;
12
13pub use crate::app::domain::state::OperationKind;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub enum SessionEvent {
17    /// Session was created. For sub-agent sessions, `parent_session_id` links
18    /// to the parent session for auditability.
19    SessionCreated {
20        config: Box<SessionConfig>,
21        metadata: HashMap<String, String>,
22        /// If this is a sub-agent session, the parent session ID
23        #[serde(default, skip_serializing_if = "Option::is_none")]
24        parent_session_id: Option<SessionId>,
25    },
26
27    SessionConfigUpdated {
28        config: Box<SessionConfig>,
29        primary_agent_id: String,
30    },
31
32    /// Assistant-authored message; includes the model that produced it.
33    AssistantMessageAdded {
34        message: Message,
35        model: ModelId,
36    },
37
38    /// User-authored message; no model attribution.
39    UserMessageAdded {
40        message: Message,
41    },
42
43    /// Tool result message; no model attribution.
44    ToolMessageAdded {
45        message: Message,
46    },
47
48    MessageUpdated {
49        message: Message,
50    },
51
52    ToolCallStarted {
53        id: ToolCallId,
54        name: String,
55        parameters: serde_json::Value,
56        model: ModelId,
57    },
58
59    ToolCallCompleted {
60        id: ToolCallId,
61        name: String,
62        result: ToolResult,
63        model: ModelId,
64    },
65
66    ToolCallFailed {
67        id: ToolCallId,
68        name: String,
69        error: String,
70        model: ModelId,
71    },
72
73    ApprovalRequested {
74        request_id: RequestId,
75        tool_call: ToolCall,
76    },
77
78    ApprovalDecided {
79        request_id: RequestId,
80        decision: ApprovalDecision,
81        remember: Option<ApprovalMemory>,
82    },
83
84    OperationStarted {
85        op_id: OpId,
86        kind: OperationKind,
87    },
88
89    OperationCompleted {
90        op_id: OpId,
91    },
92
93    OperationCancelled {
94        op_id: OpId,
95        info: CancellationInfo,
96    },
97
98    CompactResult {
99        result: CompactResult,
100    },
101
102    ConversationCompacted {
103        record: CompactionRecord,
104    },
105
106    WorkspaceChanged,
107
108    QueueUpdated {
109        queue: Vec<QueuedWorkItemSnapshot>,
110    },
111
112    Error {
113        message: String,
114    },
115
116    McpServerStateChanged {
117        server_name: String,
118        state: McpServerState,
119    },
120}
121
122#[derive(Debug, Clone, PartialEq)]
123pub enum CompactResult {
124    Success(String),
125    Cancelled,
126    InsufficientMessages,
127}
128
129impl Serialize for CompactResult {
130    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131    where
132        S: serde::Serializer,
133    {
134        use serde::ser::SerializeStruct;
135
136        match self {
137            CompactResult::Success(summary) => {
138                let mut state = serializer.serialize_struct("CompactResult", 2)?;
139                state.serialize_field("result_type", "success")?;
140                state.serialize_field("summary", summary)?;
141                state.end()
142            }
143            CompactResult::Cancelled => {
144                let mut state = serializer.serialize_struct("CompactResult", 1)?;
145                state.serialize_field("result_type", "cancelled")?;
146                state.end()
147            }
148            CompactResult::InsufficientMessages => {
149                let mut state = serializer.serialize_struct("CompactResult", 1)?;
150                state.serialize_field("result_type", "insufficient_messages")?;
151                state.end()
152            }
153        }
154    }
155}
156
157impl<'de> Deserialize<'de> for CompactResult {
158    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159    where
160        D: serde::Deserializer<'de>,
161    {
162        #[derive(Deserialize)]
163        struct CompactResultPayload {
164            result_type: String,
165            #[serde(default)]
166            summary: Option<String>,
167            #[serde(default)]
168            success: Option<String>,
169        }
170
171        let payload = CompactResultPayload::deserialize(deserializer)?;
172        match payload.result_type.as_str() {
173            "success" => {
174                let summary = payload
175                    .summary
176                    .or(payload.success)
177                    .ok_or_else(|| serde::de::Error::missing_field("summary"))?;
178                Ok(CompactResult::Success(summary))
179            }
180            "cancelled" => Ok(CompactResult::Cancelled),
181            "insufficient_messages" => Ok(CompactResult::InsufficientMessages),
182            other => Err(serde::de::Error::unknown_variant(
183                other,
184                &["success", "cancelled", "insufficient_messages"],
185            )),
186        }
187    }
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct CancellationInfo {
192    pub pending_tool_calls: usize,
193    #[serde(default)]
194    pub popped_queued_item: Option<QueuedWorkItemSnapshot>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct QueuedWorkItemSnapshot {
199    pub kind: Option<QueuedWorkKind>,
200    pub content: String,
201    pub queued_at: u64,
202    pub model: Option<ModelId>,
203    pub op_id: OpId,
204    pub message_id: MessageId,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub enum QueuedWorkKind {
209    UserMessage,
210    DirectBash,
211}
212
213impl SessionEvent {
214    pub fn is_error(&self) -> bool {
215        matches!(
216            self,
217            SessionEvent::Error { .. } | SessionEvent::ToolCallFailed { .. }
218        )
219    }
220
221    pub fn operation_id(&self) -> Option<OpId> {
222        match self {
223            SessionEvent::OperationStarted { op_id, .. }
224            | SessionEvent::OperationCompleted { op_id }
225            | SessionEvent::OperationCancelled { op_id, .. } => Some(*op_id),
226            _ => None,
227        }
228    }
229
230    pub fn tool_call_id(&self) -> Option<&ToolCallId> {
231        match self {
232            SessionEvent::ToolCallStarted { id, .. }
233            | SessionEvent::ToolCallCompleted { id, .. }
234            | SessionEvent::ToolCallFailed { id, .. } => Some(id),
235            _ => None,
236        }
237    }
238}