Skip to main content

keel_sessions/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// Events parsed from Claude's `--output-format stream-json` NDJSON output.
4#[derive(Debug, Clone, Serialize, Deserialize)]
5#[serde(tag = "type", rename_all = "snake_case")]
6pub enum ClaudeEvent {
7    #[serde(rename = "system")]
8    System {
9        subtype: Option<String>,
10        session_id: Option<String>,
11        #[serde(flatten)]
12        extra: serde_json::Value,
13    },
14    #[serde(rename = "assistant")]
15    Assistant {
16        subtype: Option<String>,
17        message: Option<AssistantMessage>,
18        #[serde(flatten)]
19        extra: serde_json::Value,
20    },
21    #[serde(rename = "user")]
22    User {
23        subtype: Option<String>,
24        message: Option<UserMessage>,
25        #[serde(flatten)]
26        extra: serde_json::Value,
27    },
28    #[serde(rename = "result")]
29    Result {
30        subtype: Option<String>,
31        result: Option<String>,
32        cost_usd: Option<f64>,
33        duration_ms: Option<u64>,
34        session_id: Option<String>,
35        #[serde(flatten)]
36        extra: serde_json::Value,
37    },
38    /// Streaming wrapper — Claude CLI wraps granular API events in this.
39    #[serde(rename = "stream_event")]
40    StreamEvent {
41        event: StreamEventInner,
42        session_id: Option<String>,
43        #[serde(flatten)]
44        extra: serde_json::Value,
45    },
46    /// Rate limit info — silently ignored.
47    #[serde(rename = "rate_limit_event")]
48    RateLimitEvent {
49        #[serde(flatten)]
50        extra: serde_json::Value,
51    },
52}
53
54/// Inner event from a stream_event wrapper.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct StreamEventInner {
57    #[serde(rename = "type")]
58    pub event_type: String,
59    pub delta: Option<StreamDelta>,
60    pub content_block: Option<ContentBlock>,
61    #[serde(flatten)]
62    pub extra: serde_json::Value,
63}
64
65/// Delta payload inside a content_block_delta stream event.
66/// Note: `message_delta` events also have a `delta` but without a `type` field,
67/// so `delta_type` must be optional.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct StreamDelta {
70    #[serde(rename = "type")]
71    pub delta_type: Option<String>,
72    pub text: Option<String>,
73    pub partial_json: Option<String>,
74    pub thinking: Option<String>,
75    #[serde(flatten)]
76    pub extra: serde_json::Value,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct AssistantMessage {
81    pub content: Option<Vec<ContentBlock>>,
82    #[serde(flatten)]
83    pub extra: serde_json::Value,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct UserMessage {
88    pub content: Option<serde_json::Value>,
89    #[serde(flatten)]
90    pub extra: serde_json::Value,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(tag = "type", rename_all = "snake_case")]
95pub enum ContentBlock {
96    #[serde(rename = "text")]
97    Text { text: String },
98    #[serde(rename = "thinking")]
99    Thinking { thinking: Option<String> },
100    #[serde(rename = "tool_use")]
101    ToolUse {
102        id: Option<String>,
103        name: Option<String>,
104        input: Option<serde_json::Value>,
105    },
106    #[serde(rename = "tool_result")]
107    ToolResult {
108        tool_use_id: Option<String>,
109        content: Option<serde_json::Value>,
110        is_error: Option<bool>,
111    },
112    #[serde(other)]
113    Unknown,
114}
115
116/// Processed feed items for rendering in the UI.
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118#[serde(tag = "feed_type", content = "data", rename_all = "snake_case")]
119pub enum FeedItem {
120    /// Text message from the assistant.
121    AssistantText(String),
122    /// Partial streaming text — replaces the last AssistantText in the feed.
123    AssistantTextStreaming(String),
124    /// Extended thinking — final content.
125    Thinking(String),
126    /// Extended thinking — streaming (accumulates progressively).
127    ThinkingStreaming(String),
128    /// Message from the user (follow-up prompt).
129    UserMessage(String),
130    /// Tool call made by the assistant.
131    ToolCall {
132        name: String,
133        input: serde_json::Value,
134    },
135    /// Result of a tool call.
136    ToolResult { content: String, is_error: bool },
137    /// System message (session start, etc.).
138    SystemMessage(String),
139    /// Session completed — cost/duration summary.
140    FinalResult {
141        result: String,
142        cost_usd: Option<f64>,
143        duration_ms: Option<u64>,
144    },
145}
146
147/// In-memory buffer for a live session's feed items.
148/// Tracks how many items were trimmed from the front to enforce the cap.
149#[derive(Default, Clone, PartialEq)]
150pub struct SessionFeedBuffer {
151    pub items: Vec<FeedItem>,
152    /// Number of events dropped from the front of `items` to stay within FEED_CAP.
153    pub dropped_count: usize,
154}
155
156/// Status of a Claude session.
157#[derive(Debug, Clone, PartialEq)]
158pub enum SessionStatus {
159    Starting,
160    Running,
161    Completed,
162    Error(String),
163}