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}