1use crate::api::provider::TokenUsage;
2use crate::api::{ApiError, StreamError};
3use crate::app::conversation::UserContent;
4use crate::app::domain::types::{CompactionId, MessageId, OpId, RequestId, SessionId, ToolCallId};
5use crate::config::model::ModelId;
6use serde::{Deserialize, Serialize};
7use steer_tools::result::ToolResult;
8use steer_tools::{ToolCall, ToolError, ToolSchema};
9use thiserror::Error;
10
11use super::event::SessionEvent;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ModelCallRequestErrorKind {
15 Network,
16 AuthenticationFailed,
17 AuthError,
18 RateLimited,
19 InvalidRequest,
20 ServerError,
21 Timeout,
22 Cancelled,
23 ResponseParsingError,
24 NoChoices,
25 RequestBlocked,
26 Unknown,
27 Configuration,
28 UnsupportedFeature,
29 StreamError,
30}
31
32impl ModelCallRequestErrorKind {
33 pub const fn from_api_error(error: &ApiError) -> Self {
34 match error {
35 ApiError::Network(_) => Self::Network,
36 ApiError::AuthenticationFailed { .. } => Self::AuthenticationFailed,
37 ApiError::AuthError(_) => Self::AuthError,
38 ApiError::RateLimited { .. } => Self::RateLimited,
39 ApiError::InvalidRequest { .. } => Self::InvalidRequest,
40 ApiError::ServerError { .. } => Self::ServerError,
41 ApiError::Timeout { .. } => Self::Timeout,
42 ApiError::Cancelled { .. } => Self::Cancelled,
43 ApiError::ResponseParsingError { .. } => Self::ResponseParsingError,
44 ApiError::NoChoices { .. } => Self::NoChoices,
45 ApiError::RequestBlocked { .. } => Self::RequestBlocked,
46 ApiError::Unknown { .. } => Self::Unknown,
47 ApiError::Configuration(_) => Self::Configuration,
48 ApiError::UnsupportedFeature { .. } => Self::UnsupportedFeature,
49 ApiError::StreamError { .. } => Self::StreamError,
50 }
51 }
52}
53
54impl std::fmt::Display for ModelCallRequestErrorKind {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 let kind = match self {
57 Self::Network => "network",
58 Self::AuthenticationFailed => "authentication_failed",
59 Self::AuthError => "auth_error",
60 Self::RateLimited => "rate_limited",
61 Self::InvalidRequest => "invalid_request",
62 Self::ServerError => "server_error",
63 Self::Timeout => "timeout",
64 Self::Cancelled => "cancelled",
65 Self::ResponseParsingError => "response_parsing_error",
66 Self::NoChoices => "no_choices",
67 Self::RequestBlocked => "request_blocked",
68 Self::Unknown => "unknown",
69 Self::Configuration => "configuration",
70 Self::UnsupportedFeature => "unsupported_feature",
71 Self::StreamError => "stream_error",
72 };
73
74 f.write_str(kind)
75 }
76}
77
78#[derive(Debug, Clone, Error, PartialEq, Eq)]
79pub enum ModelCallError {
80 #[error("Failed to start model stream ({kind}): {message}")]
81 RequestStartFailed {
82 kind: ModelCallRequestErrorKind,
83 message: String,
84 },
85
86 #[error("Model stream failed: {0}")]
87 StreamFailed(StreamError),
88
89 #[error("Model stream ended without completion response")]
90 MissingCompletionResponse,
91}
92
93#[derive(Debug, Clone, Error, PartialEq, Eq)]
94pub enum SessionTitleGenerationError {
95 #[error(transparent)]
96 ModelCall(#[from] ModelCallError),
97}
98
99#[derive(Debug, Clone)]
100pub enum Action {
101 UserInput {
102 session_id: SessionId,
103 content: Vec<UserContent>,
104 op_id: OpId,
105 message_id: MessageId,
106 model: ModelId,
107 timestamp: u64,
108 },
109
110 UserEditedMessage {
111 session_id: SessionId,
112 message_id: MessageId,
113 new_content: Vec<UserContent>,
114 op_id: OpId,
115 new_message_id: MessageId,
116 model: ModelId,
117 timestamp: u64,
118 },
119
120 ToolApprovalRequested {
121 session_id: SessionId,
122 request_id: RequestId,
123 tool_call: ToolCall,
124 },
125
126 ToolApprovalDecided {
127 session_id: SessionId,
128 request_id: RequestId,
129 decision: ApprovalDecision,
130 remember: Option<ApprovalMemory>,
131 },
132
133 ToolExecutionStarted {
134 session_id: SessionId,
135 tool_call_id: ToolCallId,
136 tool_name: String,
137 tool_parameters: serde_json::Value,
138 },
139
140 ToolResult {
141 session_id: SessionId,
142 tool_call_id: ToolCallId,
143 tool_name: String,
144 result: Result<ToolResult, ToolError>,
145 },
146
147 ToolSchemasAvailable {
148 session_id: SessionId,
149 tools: Vec<ToolSchema>,
150 },
151
152 ToolSchemasUpdated {
153 session_id: SessionId,
154 schemas: Vec<ToolSchema>,
155 source: SchemaSource,
156 },
157
158 SwitchPrimaryAgent {
159 session_id: SessionId,
160 agent_id: String,
161 },
162
163 McpServerStateChanged {
164 session_id: SessionId,
165 server_name: String,
166 state: McpServerState,
167 },
168
169 ModelResponseComplete {
170 session_id: SessionId,
171 op_id: OpId,
172 message_id: MessageId,
173 content: Vec<crate::app::conversation::AssistantContent>,
174 usage: Option<TokenUsage>,
175 context_window_tokens: Option<u32>,
176 configured_max_output_tokens: Option<u32>,
177 timestamp: u64,
178 },
179
180 ModelResponseError {
181 session_id: SessionId,
182 op_id: OpId,
183 error: String,
184 },
185
186 SessionTitleGenerated {
187 session_id: SessionId,
188 title: String,
189 },
190
191 SessionTitleGenerationFailed {
192 session_id: SessionId,
193 error: SessionTitleGenerationError,
194 },
195
196 Cancel {
197 session_id: SessionId,
198 op_id: Option<OpId>,
199 },
200
201 DirectBashCommand {
202 session_id: SessionId,
203 op_id: OpId,
204 message_id: MessageId,
205 command: String,
206 timestamp: u64,
207 },
208
209 DequeueQueuedItem {
210 session_id: SessionId,
211 },
212
213 DrainQueuedWork {
214 session_id: SessionId,
215 },
216
217 RequestCompaction {
218 session_id: SessionId,
219 op_id: OpId,
220 model: ModelId,
221 },
222
223 Shutdown,
224
225 Hydrate {
226 session_id: SessionId,
227 events: Vec<SessionEvent>,
228 starting_sequence: u64,
229 },
230
231 WorkspaceFilesListed {
232 session_id: SessionId,
233 files: Vec<String>,
234 },
235
236 CompactionComplete {
237 session_id: SessionId,
238 op_id: OpId,
239 compaction_id: CompactionId,
240 summary_message_id: MessageId,
241 summary: String,
242 compacted_head_message_id: MessageId,
243 previous_active_message_id: Option<MessageId>,
244 model: String,
245 timestamp: u64,
246 },
247
248 CompactionFailed {
249 session_id: SessionId,
250 op_id: OpId,
251 error: String,
252 },
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
256pub enum ApprovalDecision {
257 Approved,
258 Denied,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub enum ApprovalMemory {
263 Tool(String),
264 BashPattern(String),
265 PendingTool,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub enum SchemaSource {
270 Workspace,
271 Mcp { server_name: String },
272 Backend { backend_name: String },
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub enum McpServerState {
277 Connecting,
278 Connected { tools: Vec<ToolSchema> },
279 Disconnected { error: Option<String> },
280 Failed { error: String },
281}
282
283impl Action {
284 pub fn session_id(&self) -> Option<SessionId> {
285 match self {
286 Action::UserInput { session_id, .. }
287 | Action::UserEditedMessage { session_id, .. }
288 | Action::ToolApprovalRequested { session_id, .. }
289 | Action::ToolApprovalDecided { session_id, .. }
290 | Action::ToolExecutionStarted { session_id, .. }
291 | Action::ToolResult { session_id, .. }
292 | Action::ToolSchemasAvailable { session_id, .. }
293 | Action::ToolSchemasUpdated { session_id, .. }
294 | Action::SwitchPrimaryAgent { session_id, .. }
295 | Action::McpServerStateChanged { session_id, .. }
296 | Action::ModelResponseComplete { session_id, .. }
297 | Action::ModelResponseError { session_id, .. }
298 | Action::SessionTitleGenerated { session_id, .. }
299 | Action::SessionTitleGenerationFailed { session_id, .. }
300 | Action::Cancel { session_id, .. }
301 | Action::DirectBashCommand { session_id, .. }
302 | Action::DequeueQueuedItem { session_id, .. }
303 | Action::DrainQueuedWork { session_id, .. }
304 | Action::RequestCompaction { session_id, .. }
305 | Action::Hydrate { session_id, .. }
306 | Action::WorkspaceFilesListed { session_id, .. }
307 | Action::CompactionComplete { session_id, .. }
308 | Action::CompactionFailed { session_id, .. } => Some(*session_id),
309 Action::Shutdown => None,
310 }
311 }
312
313 pub fn op_id(&self) -> Option<OpId> {
314 match self {
315 Action::UserInput { op_id, .. }
316 | Action::UserEditedMessage { op_id, .. }
317 | Action::DirectBashCommand { op_id, .. }
318 | Action::RequestCompaction { op_id, .. }
319 | Action::ModelResponseComplete { op_id, .. }
320 | Action::ModelResponseError { op_id, .. }
321 | Action::CompactionComplete { op_id, .. }
322 | Action::CompactionFailed { op_id, .. } => Some(*op_id),
323 Action::Cancel { op_id, .. } => *op_id,
324 _ => None,
325 }
326 }
327}