Skip to main content

steer_core/app/domain/
action.rs

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}