1use serde::{Deserialize, Serialize};
6use serde_json::{Value, json};
7
8use crate::types::{ClaudeMessageId, ClaudeRequestId, ClaudeToolUseId};
9
10#[derive(Debug, Deserialize)]
12#[serde(tag = "type", rename_all = "snake_case")]
13pub enum Line {
14 ControlRequest {
16 request_id: ClaudeRequestId,
17 request: ControlRequest,
18 },
19 ControlResponse {
21 response: ControlResponse,
22 },
23 System(System),
24 Assistant(Assistant),
25 User(User),
26 StreamEvent {
27 event: StreamEvent,
28 },
29 RateLimitEvent {
30 rate_limit_info: RateLimit,
31 },
32 Result(Finished),
33 #[serde(other)]
34 Other,
35}
36
37#[derive(Debug, Deserialize)]
38#[serde(tag = "subtype", rename_all = "snake_case")]
39pub enum ControlRequest {
40 McpMessage {
41 server_name: String,
42 message: Rpc,
43 },
44 #[serde(other)]
45 Other,
46}
47
48#[derive(Debug, Deserialize)]
51#[serde(untagged)]
52pub enum Rpc {
53 Known(McpRequest),
54 Unknown {
55 #[serde(default)]
56 id: Option<RpcId>,
57 method: String,
58 },
59}
60
61#[derive(Debug, Deserialize)]
62#[serde(tag = "method")]
63pub enum McpRequest {
64 #[serde(rename = "initialize")]
65 Initialize { id: RpcId, params: InitializeParams },
66 #[serde(rename = "tools/list")]
67 ToolsList { id: RpcId },
68 #[serde(rename = "tools/call")]
69 ToolsCall { id: RpcId, params: CallParams },
70 #[serde(rename = "ping")]
71 Ping { id: RpcId },
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(untagged)]
77pub enum RpcId {
78 Number(i64),
79 Text(String),
80}
81
82#[derive(Debug, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct InitializeParams {
85 pub protocol_version: String,
86}
87
88#[derive(Debug, Deserialize)]
89pub struct CallParams {
90 pub name: String,
91 #[serde(default)]
92 pub arguments: Option<Value>,
93}
94
95#[derive(Debug, Deserialize)]
96#[serde(tag = "subtype", rename_all = "snake_case")]
97pub enum ControlResponse {
98 Success {
99 request_id: ClaudeRequestId,
100 #[serde(default)]
101 response: Option<Initialized>,
102 },
103 Error {
104 request_id: ClaudeRequestId,
105 #[serde(default)]
106 error: String,
107 },
108}
109
110#[derive(Debug, Default, Deserialize)]
113pub struct Initialized {
114 #[serde(default)]
115 pub models: Vec<Model>,
116 #[serde(default)]
117 pub account: Option<Account>,
118 #[serde(default)]
119 pub claude_code_version: Option<String>,
120}
121
122#[derive(Debug, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct Model {
125 pub value: String,
127 #[serde(default)]
129 pub resolved_model: Option<String>,
130 #[serde(default)]
131 pub display_name: Option<String>,
132}
133
134#[derive(Debug, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct Account {
137 #[serde(default)]
138 pub subscription_type: Option<String>,
139 #[serde(default)]
140 pub api_provider: Option<String>,
141}
142
143#[derive(Debug, Deserialize)]
144#[serde(tag = "subtype", rename_all = "snake_case")]
145pub enum System {
146 Init {
148 #[serde(default)]
149 model: Option<String>,
150 },
151 #[serde(other)]
152 Other,
153}
154
155#[derive(Debug, Deserialize)]
156pub struct Assistant {
157 pub message: AssistantMessage,
158 #[serde(default)]
159 pub error: Option<AssistantError>,
160 #[serde(default)]
161 pub parent_tool_use_id: Option<String>,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
166#[serde(rename_all = "snake_case")]
167pub enum AssistantError {
168 AuthenticationFailed,
169 BillingError,
170 RateLimit,
171 InvalidRequest,
172 ServerError,
173 #[serde(other)]
174 Unknown,
175}
176
177#[derive(Debug, Deserialize)]
178pub struct AssistantMessage {
179 #[serde(default, deserialize_with = "nonempty")]
181 pub id: Option<ClaudeMessageId>,
182 #[serde(default)]
183 pub model: String,
184 #[serde(default)]
185 pub content: Vec<Block>,
186 #[serde(default)]
187 pub usage: Option<Usage>,
188}
189
190#[derive(Debug, Clone, PartialEq, Deserialize)]
191#[serde(tag = "type", rename_all = "snake_case")]
192pub enum Block {
193 Text {
194 text: String,
195 },
196 Thinking {
197 #[serde(default)]
198 thinking: String,
199 #[serde(default)]
200 signature: String,
201 },
202 ToolUse {
203 id: ClaudeToolUseId,
204 name: String,
205 #[serde(default)]
206 input: Value,
207 },
208 #[serde(other)]
209 Other,
210}
211
212#[derive(Debug, Deserialize)]
213pub struct User {
214 pub message: UserMessage,
215 #[serde(default)]
216 pub parent_tool_use_id: Option<String>,
217}
218
219#[derive(Debug, Deserialize)]
220pub struct UserMessage {
221 pub content: UserContent,
222}
223
224#[derive(Debug, Deserialize)]
225#[serde(untagged)]
226pub enum UserContent {
227 Text(String),
228 Blocks(Vec<UserBlock>),
229}
230
231#[derive(Debug, Deserialize)]
232#[serde(tag = "type", rename_all = "snake_case")]
233pub enum UserBlock {
234 ToolResult {
235 tool_use_id: ClaudeToolUseId,
236 #[serde(default)]
237 content: ResultContent,
238 #[serde(default)]
239 is_error: bool,
240 },
241 #[serde(other)]
242 Other,
243}
244
245#[derive(Debug, Deserialize)]
246#[serde(untagged)]
247pub enum ResultContent {
248 Text(String),
249 Blocks(Vec<ResultBlock>),
250}
251
252impl Default for ResultContent {
253 fn default() -> Self {
254 Self::Text(String::new())
255 }
256}
257
258#[derive(Debug, Deserialize)]
259#[serde(tag = "type", rename_all = "snake_case")]
260pub enum ResultBlock {
261 Text {
262 text: String,
263 },
264 Image {
265 source: ImageSource,
266 },
267 #[serde(other)]
268 Other,
269}
270
271#[derive(Debug, Deserialize)]
272pub struct ImageSource {
273 pub media_type: String,
274 pub data: String,
275}
276
277#[derive(Debug, Deserialize)]
279#[serde(tag = "type", rename_all = "snake_case")]
280pub enum StreamEvent {
281 MessageStart {
282 message: StartedMessage,
283 },
284 ContentBlockStart {
285 index: usize,
286 content_block: Block,
287 },
288 ContentBlockDelta {
289 index: usize,
290 delta: Delta,
291 },
292 ContentBlockStop {
293 index: usize,
294 },
295 MessageDelta {
296 #[serde(default)]
297 usage: Option<Usage>,
298 },
299 MessageStop,
300 #[serde(other)]
301 Other,
302}
303
304#[derive(Debug, Deserialize)]
305pub struct StartedMessage {
306 pub id: ClaudeMessageId,
307 #[serde(default)]
308 pub model: String,
309 #[serde(default)]
310 pub usage: Option<Usage>,
311}
312
313#[derive(Debug, Deserialize)]
314#[serde(tag = "type", rename_all = "snake_case")]
315pub enum Delta {
316 TextDelta {
317 text: String,
318 },
319 ThinkingDelta {
320 thinking: String,
321 },
322 #[serde(other)]
323 Other,
324}
325
326#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
328pub struct Usage {
329 #[serde(default)]
330 pub input_tokens: Option<u64>,
331 #[serde(default)]
332 pub output_tokens: Option<u64>,
333 #[serde(default)]
334 pub cache_read_input_tokens: Option<u64>,
335 #[serde(default)]
336 pub cache_creation_input_tokens: Option<u64>,
337 #[serde(default)]
338 pub cache_creation: Option<CacheCreation>,
339 #[serde(default)]
340 pub output_tokens_details: Option<OutputDetails>,
341}
342
343#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
344pub struct CacheCreation {
345 #[serde(default)]
346 pub ephemeral_1h_input_tokens: Option<u64>,
347}
348
349#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
350pub struct OutputDetails {
351 #[serde(default)]
352 pub thinking_tokens: Option<u64>,
353}
354
355#[derive(Debug, Clone, Deserialize)]
356#[serde(rename_all = "camelCase")]
357pub struct RateLimit {
358 pub status: LimitStatus,
359 #[serde(default, deserialize_with = "unix_time")]
361 pub resets_at: Option<time::OffsetDateTime>,
362 #[serde(default)]
364 pub rate_limit_type: Option<String>,
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
368#[serde(rename_all = "snake_case")]
369pub enum LimitStatus {
370 Allowed,
371 AllowedWarning,
372 Rejected,
373 #[serde(other)]
374 Other,
375}
376
377#[derive(Debug)]
379pub enum Finished {
380 Structured(Value),
382 Text(String),
384 Failed {
385 ending: Ending,
386 errors: Vec<String>,
387 },
388}
389
390#[derive(Debug, Clone, PartialEq, Eq)]
392pub enum Ending {
393 NoStructuredOutput,
395 Other(String),
397}
398
399const SUCCESS: &str = "success";
401const MAX_STRUCTURED_OUTPUT_RETRIES: &str = "error_max_structured_output_retries";
403
404impl<'de> Deserialize<'de> for Finished {
405 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
406 #[derive(Deserialize)]
407 struct Raw {
408 #[serde(default)]
409 subtype: String,
410 #[serde(default)]
411 is_error: bool,
412 #[serde(default)]
413 result: Option<String>,
414 #[serde(default)]
415 structured_output: Option<Value>,
416 #[serde(default)]
417 errors: Vec<String>,
418 }
419 let raw = Raw::deserialize(deserializer)?;
420 Ok(match raw {
421 Raw {
422 ref subtype,
423 is_error: false,
424 ..
425 } if subtype == SUCCESS => match raw.structured_output {
426 Some(output) => Self::Structured(output),
427 None => Self::Text(raw.result.unwrap_or_default()),
428 },
429 Raw {
430 subtype, errors, ..
431 } => Self::Failed {
432 ending: if subtype == MAX_STRUCTURED_OUTPUT_RETRIES {
433 Ending::NoStructuredOutput
434 } else {
435 Ending::Other(subtype)
436 },
437 errors,
438 },
439 })
440 }
441}
442
443fn nonempty<'de, D: serde::Deserializer<'de>>(
445 deserializer: D,
446) -> Result<Option<ClaudeMessageId>, D::Error> {
447 Option::<String>::deserialize(deserializer)?
448 .filter(|id| !id.is_empty())
449 .map(|id| id.parse().map_err(serde::de::Error::custom))
450 .transpose()
451}
452
453fn unix_time<'de, D: serde::Deserializer<'de>>(
455 deserializer: D,
456) -> Result<Option<time::OffsetDateTime>, D::Error> {
457 Ok(Option::<i64>::deserialize(deserializer)?
458 .and_then(|seconds| time::OffsetDateTime::from_unix_timestamp(seconds).ok()))
459}
460
461pub const INITIALIZE: &str = "initialize";
463
464pub fn initialize(server: Option<&str>) -> Value {
466 json!({
467 "type": "control_request",
468 "request_id": INITIALIZE,
469 "request": {"subtype": "initialize", "sdkMcpServers": server.into_iter().collect::<Vec<_>>()},
470 })
471}
472
473pub fn user(text: &str) -> Value {
475 json!({
476 "type": "user",
477 "session_id": "",
478 "message": {"role": "user", "content": [{"type": "text", "text": text}]},
479 "parent_tool_use_id": null,
480 "client_composed": true,
481 })
482}
483
484pub fn mcp_reply(request_id: &ClaudeRequestId, reply: Value) -> Value {
486 json!({
487 "type": "control_response",
488 "response": {"subtype": "success", "request_id": request_id, "response": {"mcp_response": reply}},
489 })
490}
491
492pub fn refusal(request_id: &ClaudeRequestId, error: &str) -> Value {
494 json!({
495 "type": "control_response",
496 "response": {"subtype": "error", "request_id": request_id, "error": error},
497 })
498}