Skip to main content

claude_stream/
event.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use thiserror::Error;
4
5/// Errors from parsing one event.
6#[derive(Debug, Error)]
7pub enum ParseError {
8    /// JSON in the `data:` line couldn't be decoded.
9    #[error("invalid event JSON: {0}")]
10    Json(#[from] serde_json::Error),
11    /// Missing required field in the event payload.
12    #[error("malformed event: {0}")]
13    Malformed(String),
14}
15
16/// One Anthropic streaming event.
17///
18/// Mirrors [the `messages` streaming wire format][docs] as of late 2025.
19/// Unknown event types fall through to [`Event::Unknown`] so the parser
20/// never panics on a new event Anthropic adds.
21///
22/// [docs]: https://docs.anthropic.com/en/api/messages-streaming
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "type", rename_all = "snake_case")]
25pub enum Event {
26    /// Sent first; carries the response shell (id, role, model, initial usage).
27    MessageStart {
28        /// The shell message: id, role, model, etc.
29        message: Message,
30    },
31    /// A new content block is starting (text, tool_use, thinking, etc.).
32    ContentBlockStart {
33        /// Index in the message's `content` array.
34        index: u32,
35        /// The (initially empty) content block.
36        content_block: ContentBlock,
37    },
38    /// A delta extending an in-progress content block.
39    ContentBlockDelta {
40        /// Index in the message's `content` array.
41        index: u32,
42        /// What changed.
43        delta: Delta,
44    },
45    /// A content block is complete.
46    ContentBlockStop {
47        /// Index in the message's `content` array.
48        index: u32,
49    },
50    /// Top-level message delta (final stop_reason, usage so far, etc.).
51    MessageDelta {
52        /// What changed about the top-level message.
53        delta: MessageDelta,
54        /// Cumulative token usage.
55        usage: Usage,
56    },
57    /// Final event for a message.
58    MessageStop,
59    /// Heartbeat keep-alive; ignore.
60    Ping,
61    /// Provider-side error; usually means the connection will close.
62    #[serde(rename = "error")]
63    Error {
64        /// Error payload as Anthropic sent it.
65        error: Value,
66    },
67    /// Any event type we don't know about. Forward-compatible escape hatch.
68    #[serde(other)]
69    Unknown,
70}
71
72/// The message shell carried by [`Event::MessageStart`].
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct Message {
75    /// Anthropic message id (`msg_…`).
76    pub id: String,
77    /// Always `"assistant"` in v1.
78    pub role: String,
79    /// Model used.
80    #[serde(default)]
81    pub model: Option<String>,
82    /// Stop reason if known at start time (usually null).
83    #[serde(default)]
84    pub stop_reason: Option<String>,
85    /// Token usage as known at start.
86    #[serde(default)]
87    pub usage: Option<Usage>,
88}
89
90/// One content block (passed by [`Event::ContentBlockStart`]).
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(tag = "type", rename_all = "snake_case")]
93pub enum ContentBlock {
94    /// Plain text block.
95    Text {
96        /// Initial text (often empty; deltas append).
97        #[serde(default)]
98        text: String,
99    },
100    /// Tool-use block.
101    ToolUse {
102        /// Tool-call id (`toolu_…`).
103        id: String,
104        /// Tool name.
105        name: String,
106        /// Tool args (often `{}` initially; populated by partial JSON deltas).
107        #[serde(default)]
108        input: Value,
109    },
110    /// Extended-thinking block.
111    Thinking {
112        /// Initial thinking text.
113        #[serde(default)]
114        thinking: String,
115    },
116    /// Server-tool-use block (web_search, code_execution, etc.).
117    ServerToolUse {
118        /// Server tool name.
119        name: String,
120        /// Args passed to the tool.
121        #[serde(default)]
122        input: Value,
123    },
124    /// Anything else.
125    #[serde(other)]
126    Other,
127}
128
129/// One delta extending an in-progress content block.
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(tag = "type", rename_all = "snake_case")]
132pub enum Delta {
133    /// Text-block delta.
134    TextDelta {
135        /// Appended text fragment.
136        text: String,
137    },
138    /// Tool-use input streaming as partial JSON.
139    InputJsonDelta {
140        /// Partial JSON fragment to append.
141        partial_json: String,
142    },
143    /// Extended-thinking text delta.
144    ThinkingDelta {
145        /// Appended thinking text.
146        thinking: String,
147    },
148    /// Signature delta for the thinking block.
149    SignatureDelta {
150        /// Cryptographic signature fragment.
151        signature: String,
152    },
153    /// Anything else.
154    #[serde(other)]
155    Other,
156}
157
158/// Top-level message delta.
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct MessageDelta {
161    /// Final stop reason once known.
162    #[serde(default)]
163    pub stop_reason: Option<String>,
164    /// Stop sequence that was hit, if any.
165    #[serde(default)]
166    pub stop_sequence: Option<String>,
167}
168
169/// Token usage. Field set evolves; we keep optional everywhere.
170#[derive(Debug, Clone, Default, Serialize, Deserialize)]
171pub struct Usage {
172    /// Plain input tokens.
173    #[serde(default)]
174    pub input_tokens: Option<u64>,
175    /// Output tokens generated.
176    #[serde(default)]
177    pub output_tokens: Option<u64>,
178    /// Tokens served from prompt cache.
179    #[serde(default)]
180    pub cache_read_input_tokens: Option<u64>,
181    /// Tokens written to prompt cache.
182    #[serde(default)]
183    pub cache_creation_input_tokens: Option<u64>,
184}