1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::Value;
3use std::fmt;
4use uuid::Uuid;
5
6use super::claude_output::ClaudeOutput;
7use super::content_blocks::{deserialize_content_blocks, ContentBlock};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum SystemSubtype {
16 Init,
17 Status,
18 CompactBoundary,
19 ThinkingTokens,
20 TaskStarted,
21 TaskProgress,
22 TaskUpdated,
23 TaskNotification,
24 ApiRetry,
25 ControlRequestProgress,
26 ModelRefusalFallback,
27 ModelRefusalNoFallback,
28 LocalCommandOutput,
29 HookStarted,
30 HookProgress,
31 HookResponse,
32 PluginInstall,
33 BackgroundTasksChanged,
34 SessionStateChanged,
35 WorkerShuttingDown,
36 CommandsChanged,
37 Notification,
38 FilesPersisted,
39 MemoryRecall,
40 ElicitationComplete,
41 PermissionDenied,
42 MirrorError,
43 Informational,
44 CodeChangePublished,
45 VcsStateChanged,
46 FeedbackDraftQueued,
47 CloudSessionDelta,
48 DevIntent,
49 TurnHandoffAvailable,
50 TurnPreempted,
51 PeerMessageHold,
52 Unknown(String),
54}
55
56impl SystemSubtype {
57 pub fn as_str(&self) -> &str {
58 match self {
59 Self::Init => "init",
60 Self::Status => "status",
61 Self::CompactBoundary => "compact_boundary",
62 Self::ThinkingTokens => "thinking_tokens",
63 Self::TaskStarted => "task_started",
64 Self::TaskProgress => "task_progress",
65 Self::TaskUpdated => "task_updated",
66 Self::TaskNotification => "task_notification",
67 Self::ApiRetry => "api_retry",
68 Self::ControlRequestProgress => "control_request_progress",
69 Self::ModelRefusalFallback => "model_refusal_fallback",
70 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
71 Self::LocalCommandOutput => "local_command_output",
72 Self::HookStarted => "hook_started",
73 Self::HookProgress => "hook_progress",
74 Self::HookResponse => "hook_response",
75 Self::PluginInstall => "plugin_install",
76 Self::BackgroundTasksChanged => "background_tasks_changed",
77 Self::SessionStateChanged => "session_state_changed",
78 Self::WorkerShuttingDown => "worker_shutting_down",
79 Self::CommandsChanged => "commands_changed",
80 Self::Notification => "notification",
81 Self::FilesPersisted => "files_persisted",
82 Self::MemoryRecall => "memory_recall",
83 Self::ElicitationComplete => "elicitation_complete",
84 Self::PermissionDenied => "permission_denied",
85 Self::MirrorError => "mirror_error",
86 Self::Informational => "informational",
87 Self::CodeChangePublished => "code_change_published",
88 Self::VcsStateChanged => "vcs_state_changed",
89 Self::FeedbackDraftQueued => "feedback_draft_queued",
90 Self::CloudSessionDelta => "cloud_session_delta",
91 Self::DevIntent => "dev_intent",
92 Self::TurnHandoffAvailable => "turn_handoff_available",
93 Self::TurnPreempted => "turn_preempted",
94 Self::PeerMessageHold => "peer_message_hold",
95 Self::Unknown(s) => s.as_str(),
96 }
97 }
98}
99
100impl fmt::Display for SystemSubtype {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.write_str(self.as_str())
103 }
104}
105
106impl From<&str> for SystemSubtype {
107 fn from(s: &str) -> Self {
108 match s {
109 "init" => Self::Init,
110 "status" => Self::Status,
111 "compact_boundary" => Self::CompactBoundary,
112 "thinking_tokens" => Self::ThinkingTokens,
113 "task_started" => Self::TaskStarted,
114 "task_progress" => Self::TaskProgress,
115 "task_updated" => Self::TaskUpdated,
116 "task_notification" => Self::TaskNotification,
117 "api_retry" => Self::ApiRetry,
118 "control_request_progress" => Self::ControlRequestProgress,
119 "model_refusal_fallback" => Self::ModelRefusalFallback,
120 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
121 "local_command_output" => Self::LocalCommandOutput,
122 "hook_started" => Self::HookStarted,
123 "hook_progress" => Self::HookProgress,
124 "hook_response" => Self::HookResponse,
125 "plugin_install" => Self::PluginInstall,
126 "background_tasks_changed" => Self::BackgroundTasksChanged,
127 "session_state_changed" => Self::SessionStateChanged,
128 "worker_shutting_down" => Self::WorkerShuttingDown,
129 "commands_changed" => Self::CommandsChanged,
130 "notification" => Self::Notification,
131 "files_persisted" => Self::FilesPersisted,
132 "memory_recall" => Self::MemoryRecall,
133 "elicitation_complete" => Self::ElicitationComplete,
134 "permission_denied" => Self::PermissionDenied,
135 "mirror_error" => Self::MirrorError,
136 "informational" => Self::Informational,
137 "code_change_published" => Self::CodeChangePublished,
138 "vcs_state_changed" => Self::VcsStateChanged,
139 "feedback_draft_queued" => Self::FeedbackDraftQueued,
140 "cloud_session_delta" => Self::CloudSessionDelta,
141 "dev_intent" => Self::DevIntent,
142 "turn_handoff_available" => Self::TurnHandoffAvailable,
143 "turn_preempted" => Self::TurnPreempted,
144 "peer_message_hold" => Self::PeerMessageHold,
145 other => Self::Unknown(other.to_string()),
146 }
147 }
148}
149
150impl Serialize for SystemSubtype {
151 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
152 serializer.serialize_str(self.as_str())
153 }
154}
155
156impl<'de> Deserialize<'de> for SystemSubtype {
157 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
158 let s = String::deserialize(deserializer)?;
159 Ok(Self::from(s.as_str()))
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub enum MessageRole {
169 User,
170 Assistant,
171 Unknown(String),
173}
174
175impl MessageRole {
176 pub fn as_str(&self) -> &str {
177 match self {
178 Self::User => "user",
179 Self::Assistant => "assistant",
180 Self::Unknown(s) => s.as_str(),
181 }
182 }
183}
184
185impl fmt::Display for MessageRole {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 f.write_str(self.as_str())
188 }
189}
190
191impl From<&str> for MessageRole {
192 fn from(s: &str) -> Self {
193 match s {
194 "user" => Self::User,
195 "assistant" => Self::Assistant,
196 other => Self::Unknown(other.to_string()),
197 }
198 }
199}
200
201impl Serialize for MessageRole {
202 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
203 serializer.serialize_str(self.as_str())
204 }
205}
206
207impl<'de> Deserialize<'de> for MessageRole {
208 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
209 let s = String::deserialize(deserializer)?;
210 Ok(Self::from(s.as_str()))
211 }
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Hash)]
216pub enum CompactionTrigger {
217 Auto,
219 Manual,
221 Unknown(String),
223}
224
225impl CompactionTrigger {
226 pub fn as_str(&self) -> &str {
227 match self {
228 Self::Auto => "auto",
229 Self::Manual => "manual",
230 Self::Unknown(s) => s.as_str(),
231 }
232 }
233}
234
235impl fmt::Display for CompactionTrigger {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 f.write_str(self.as_str())
238 }
239}
240
241impl From<&str> for CompactionTrigger {
242 fn from(s: &str) -> Self {
243 match s {
244 "auto" => Self::Auto,
245 "manual" => Self::Manual,
246 other => Self::Unknown(other.to_string()),
247 }
248 }
249}
250
251impl Serialize for CompactionTrigger {
252 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
253 serializer.serialize_str(self.as_str())
254 }
255}
256
257impl<'de> Deserialize<'de> for CompactionTrigger {
258 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
259 let s = String::deserialize(deserializer)?;
260 Ok(Self::from(s.as_str()))
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Eq, Hash)]
266pub enum StopReason {
267 EndTurn,
269 MaxTokens,
271 ToolUse,
273 Unknown(String),
275}
276
277impl StopReason {
278 pub fn as_str(&self) -> &str {
279 match self {
280 Self::EndTurn => "end_turn",
281 Self::MaxTokens => "max_tokens",
282 Self::ToolUse => "tool_use",
283 Self::Unknown(s) => s.as_str(),
284 }
285 }
286}
287
288impl fmt::Display for StopReason {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 f.write_str(self.as_str())
291 }
292}
293
294impl From<&str> for StopReason {
295 fn from(s: &str) -> Self {
296 match s {
297 "end_turn" => Self::EndTurn,
298 "max_tokens" => Self::MaxTokens,
299 "tool_use" => Self::ToolUse,
300 other => Self::Unknown(other.to_string()),
301 }
302 }
303}
304
305impl Serialize for StopReason {
306 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
307 serializer.serialize_str(self.as_str())
308 }
309}
310
311impl<'de> Deserialize<'de> for StopReason {
312 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
313 let s = String::deserialize(deserializer)?;
314 Ok(Self::from(s.as_str()))
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Hash)]
320pub enum ApiKeySource {
321 None,
323 User,
324 Project,
325 Org,
326 Temporary,
327 Oauth,
328 Unknown(String),
330}
331
332impl ApiKeySource {
333 pub fn as_str(&self) -> &str {
334 match self {
335 Self::None => "none",
336 Self::User => "user",
337 Self::Project => "project",
338 Self::Org => "org",
339 Self::Temporary => "temporary",
340 Self::Oauth => "oauth",
341 Self::Unknown(s) => s.as_str(),
342 }
343 }
344}
345
346impl fmt::Display for ApiKeySource {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 f.write_str(self.as_str())
349 }
350}
351
352impl From<&str> for ApiKeySource {
353 fn from(s: &str) -> Self {
354 match s {
355 "none" => Self::None,
356 "user" => Self::User,
357 "project" => Self::Project,
358 "org" => Self::Org,
359 "temporary" => Self::Temporary,
360 "oauth" => Self::Oauth,
361 other => Self::Unknown(other.to_string()),
362 }
363 }
364}
365
366impl Serialize for ApiKeySource {
367 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
368 serializer.serialize_str(self.as_str())
369 }
370}
371
372impl<'de> Deserialize<'de> for ApiKeySource {
373 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
374 let s = String::deserialize(deserializer)?;
375 Ok(Self::from(s.as_str()))
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, Hash)]
381pub enum OutputStyle {
382 Default,
384 Unknown(String),
386}
387
388impl OutputStyle {
389 pub fn as_str(&self) -> &str {
390 match self {
391 Self::Default => "default",
392 Self::Unknown(s) => s.as_str(),
393 }
394 }
395}
396
397impl fmt::Display for OutputStyle {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 f.write_str(self.as_str())
400 }
401}
402
403impl From<&str> for OutputStyle {
404 fn from(s: &str) -> Self {
405 match s {
406 "default" => Self::Default,
407 other => Self::Unknown(other.to_string()),
408 }
409 }
410}
411
412impl Serialize for OutputStyle {
413 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
414 serializer.serialize_str(self.as_str())
415 }
416}
417
418impl<'de> Deserialize<'de> for OutputStyle {
419 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
420 let s = String::deserialize(deserializer)?;
421 Ok(Self::from(s.as_str()))
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Hash)]
427pub enum InitPermissionMode {
428 Default,
430 AcceptEdits,
431 BypassPermissions,
432 Plan,
433 DontAsk,
434 Auto,
435 Unknown(String),
437}
438
439impl InitPermissionMode {
440 pub fn as_str(&self) -> &str {
441 match self {
442 Self::Default => "default",
443 Self::AcceptEdits => "acceptEdits",
444 Self::BypassPermissions => "bypassPermissions",
445 Self::Plan => "plan",
446 Self::DontAsk => "dontAsk",
447 Self::Auto => "auto",
448 Self::Unknown(s) => s.as_str(),
449 }
450 }
451}
452
453impl fmt::Display for InitPermissionMode {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 f.write_str(self.as_str())
456 }
457}
458
459impl From<&str> for InitPermissionMode {
460 fn from(s: &str) -> Self {
461 match s {
462 "default" => Self::Default,
463 "acceptEdits" => Self::AcceptEdits,
464 "bypassPermissions" => Self::BypassPermissions,
465 "plan" => Self::Plan,
466 "dontAsk" => Self::DontAsk,
467 "auto" => Self::Auto,
468 other => Self::Unknown(other.to_string()),
469 }
470 }
471}
472
473impl Serialize for InitPermissionMode {
474 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
475 serializer.serialize_str(self.as_str())
476 }
477}
478
479impl<'de> Deserialize<'de> for InitPermissionMode {
480 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
481 let s = String::deserialize(deserializer)?;
482 Ok(Self::from(s.as_str()))
483 }
484}
485
486#[derive(Debug, Clone, PartialEq, Eq, Hash)]
488pub enum StatusMessageStatus {
489 Compacting,
491 Requesting,
493 Unknown(String),
495}
496
497impl StatusMessageStatus {
498 pub fn as_str(&self) -> &str {
499 match self {
500 Self::Compacting => "compacting",
501 Self::Requesting => "requesting",
502 Self::Unknown(s) => s.as_str(),
503 }
504 }
505}
506
507impl fmt::Display for StatusMessageStatus {
508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509 f.write_str(self.as_str())
510 }
511}
512
513impl From<&str> for StatusMessageStatus {
514 fn from(s: &str) -> Self {
515 match s {
516 "compacting" => Self::Compacting,
517 "requesting" => Self::Requesting,
518 other => Self::Unknown(other.to_string()),
519 }
520 }
521}
522
523impl Serialize for StatusMessageStatus {
524 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
525 serializer.serialize_str(self.as_str())
526 }
527}
528
529impl<'de> Deserialize<'de> for StatusMessageStatus {
530 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
531 let s = String::deserialize(deserializer)?;
532 Ok(Self::from(s.as_str()))
533 }
534}
535
536pub(crate) fn serialize_optional_uuid<S>(
538 uuid: &Option<Uuid>,
539 serializer: S,
540) -> Result<S::Ok, S::Error>
541where
542 S: Serializer,
543{
544 match uuid {
545 Some(id) => serializer.serialize_str(&id.to_string()),
546 None => serializer.serialize_none(),
547 }
548}
549
550pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
552where
553 D: Deserializer<'de>,
554{
555 let opt_str: Option<String> = Option::deserialize(deserializer)?;
556 match opt_str {
557 Some(s) => Uuid::parse_str(&s)
558 .map(Some)
559 .map_err(serde::de::Error::custom),
560 None => Ok(None),
561 }
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
567pub struct MessageOrigin {
568 pub kind: String,
569 #[serde(flatten)]
570 pub extra: serde_json::Map<String, Value>,
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
575pub struct SummarizeMetadata {
576 pub messages_summarized: u64,
577 #[serde(default, skip_serializing_if = "Option::is_none")]
578 pub user_context: Option<String>,
579 #[serde(default, skip_serializing_if = "Option::is_none")]
580 pub direction: Option<String>,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
585pub struct McpMeta {
586 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
587 pub meta: Option<Value>,
588 #[serde(default, skip_serializing_if = "Option::is_none")]
589 pub structured_content: Option<Value>,
590 #[serde(default, skip_serializing_if = "Vec::is_empty")]
595 pub resource_links: Vec<ResourceLink>,
596}
597
598#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
602pub struct ResourceLink {
603 pub uri: String,
604 pub name: String,
605 #[serde(default, skip_serializing_if = "Option::is_none")]
606 pub title: Option<String>,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
608 pub description: Option<String>,
609 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
610 pub mime_type: Option<String>,
611 #[serde(default, skip_serializing_if = "Option::is_none")]
612 pub size: Option<u64>,
613 #[serde(default, skip_serializing_if = "Option::is_none")]
614 pub annotations: Option<Value>,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
619pub struct ToolResultMeta {
620 pub id: String,
622 pub non_execution_kind: String,
628 #[serde(default, skip_serializing_if = "Option::is_none")]
630 pub user_feedback: Option<String>,
631}
632
633#[derive(Debug, Clone, Serialize, Deserialize)]
635pub struct UserMessage {
636 pub message: MessageContent,
637 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
638 #[serde(
639 serialize_with = "serialize_optional_uuid",
640 deserialize_with = "deserialize_optional_uuid"
641 )]
642 pub session_id: Option<Uuid>,
643 #[serde(skip_serializing_if = "Option::is_none")]
645 pub parent_tool_use_id: Option<String>,
646 #[serde(skip_serializing_if = "Option::is_none")]
648 pub uuid: Option<String>,
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub timestamp: Option<String>,
652 #[serde(skip_serializing_if = "Option::is_none")]
659 pub tool_use_result: Option<serde_json::Value>,
660 #[serde(skip_serializing_if = "Option::is_none")]
663 pub subagent_type: Option<String>,
664 #[serde(skip_serializing_if = "Option::is_none")]
666 pub task_description: Option<String>,
667 #[serde(skip_serializing_if = "Option::is_none")]
668 pub origin: Option<MessageOrigin>,
669 #[serde(skip_serializing_if = "Option::is_none")]
670 pub priority: Option<String>,
671 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
672 pub is_synthetic: Option<bool>,
673 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
674 pub should_query: Option<bool>,
675 #[serde(default, skip_serializing_if = "Option::is_none")]
676 pub is_meta: Option<bool>,
677 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub is_visible_in_transcript_only: Option<bool>,
679 #[serde(default, skip_serializing_if = "Option::is_none")]
680 pub is_virtual: Option<bool>,
681 #[serde(default, skip_serializing_if = "Option::is_none")]
682 pub is_compact_summary: Option<bool>,
683 #[serde(skip_serializing_if = "Option::is_none")]
684 pub summarize_metadata: Option<SummarizeMetadata>,
685 #[serde(skip_serializing_if = "Option::is_none")]
686 pub mcp_meta: Option<McpMeta>,
687 #[serde(skip_serializing_if = "Option::is_none")]
690 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
691 #[serde(skip_serializing_if = "Option::is_none")]
692 pub source_tool_use_id: Option<String>,
693 #[serde(skip_serializing_if = "Option::is_none")]
694 pub source_tool_assistant_uuid: Option<String>,
695 #[serde(skip_serializing_if = "Option::is_none")]
696 pub image_paste_ids: Option<Vec<u64>>,
697 #[serde(skip_serializing_if = "Option::is_none")]
698 pub client_platform: Option<String>,
699 #[serde(skip_serializing_if = "Option::is_none")]
700 pub inbound_origin: Option<String>,
701 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
702 pub is_replay: Option<bool>,
703 #[serde(skip_serializing_if = "Option::is_none")]
704 pub file_attachments: Option<Vec<Value>>,
705 #[serde(default, skip_serializing_if = "Option::is_none")]
707 pub seeded_summon: Option<bool>,
708 #[serde(default, skip_serializing_if = "Option::is_none")]
711 pub client_composed: Option<bool>,
712 #[serde(default, skip_serializing_if = "Option::is_none")]
716 pub historical: Option<bool>,
717}
718
719impl UserMessage {
720 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
728 &self,
729 ) -> Option<Result<T, serde_json::Error>> {
730 self.tool_use_result
731 .as_ref()
732 .map(|v| serde_json::from_value(v.clone()))
733 }
734
735 pub fn subagent_result(&self) -> Option<SubagentResult> {
746 self.tool_use_result
747 .as_ref()
748 .and_then(|v| serde_json::from_value(v.clone()).ok())
749 }
750}
751
752#[derive(Debug, Clone, Serialize, Deserialize)]
763pub struct SubagentResult {
764 #[serde(skip_serializing_if = "Option::is_none")]
766 pub status: Option<String>,
767 #[serde(skip_serializing_if = "Option::is_none")]
769 pub prompt: Option<String>,
770 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
772 pub agent_id: Option<String>,
773 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
775 pub agent_type: Option<String>,
776 #[serde(
778 default,
779 deserialize_with = "deserialize_content_blocks",
780 skip_serializing_if = "Vec::is_empty"
781 )]
782 pub content: Vec<ContentBlock>,
783 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
785 pub resolved_model: Option<String>,
786 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
788 pub total_duration_ms: Option<u64>,
789 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
791 pub total_tokens: Option<u64>,
792 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
794 pub total_tool_use_count: Option<u64>,
795 #[serde(skip_serializing_if = "Option::is_none")]
797 pub usage: Option<super::result::UsageInfo>,
798 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
800 pub tool_stats: Option<SubagentToolStats>,
801}
802
803#[derive(Debug, Clone, Default, Serialize, Deserialize)]
808#[serde(rename_all = "camelCase")]
809pub struct SubagentToolStats {
810 #[serde(default)]
811 pub read_count: u64,
812 #[serde(default)]
813 pub search_count: u64,
814 #[serde(default)]
815 pub bash_count: u64,
816 #[serde(default)]
817 pub edit_file_count: u64,
818 #[serde(default)]
819 pub lines_added: u64,
820 #[serde(default)]
821 pub lines_removed: u64,
822 #[serde(default)]
823 pub other_tool_count: u64,
824 #[serde(flatten)]
825 pub extra: serde_json::Map<String, Value>,
826}
827
828#[derive(Debug, Clone, Default, PartialEq, Eq)]
855pub struct SubagentUsageRollup {
856 pub subagent_tokens: u64,
859 pub agent_count: u32,
861 pub tool_uses: u64,
863 pub duration_ms: u64,
865 seen_agent_ids: std::collections::BTreeSet<String>,
866}
867
868impl SubagentUsageRollup {
869 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
875 match output {
876 ClaudeOutput::User(user) => self.observe_user(user),
877 _ => false,
878 }
879 }
880
881 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
888 let Some(result) = user.subagent_result() else {
889 return false;
890 };
891 if result.total_tokens.is_none() && result.agent_id.is_none() {
892 return false;
893 }
894 if let Some(agent_id) = &result.agent_id {
895 if !self.seen_agent_ids.insert(agent_id.clone()) {
896 return false;
897 }
898 }
899 self.agent_count += 1;
900 self.subagent_tokens += result.total_tokens.unwrap_or(0);
901 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
902 self.duration_ms += result.total_duration_ms.unwrap_or(0);
903 true
904 }
905}
906
907#[derive(Debug, Clone, Serialize, Deserialize)]
909pub struct MessageContent {
910 pub role: MessageRole,
911 #[serde(deserialize_with = "deserialize_content_blocks")]
912 pub content: Vec<ContentBlock>,
913}
914
915#[derive(Debug, Clone, Serialize, Deserialize)]
917pub struct SystemMessage {
918 pub subtype: SystemSubtype,
919 #[serde(flatten)]
920 pub data: Value, }
922
923impl SystemMessage {
924 pub fn is_init(&self) -> bool {
926 self.subtype == SystemSubtype::Init
927 }
928
929 pub fn is_status(&self) -> bool {
931 self.subtype == SystemSubtype::Status
932 }
933
934 pub fn is_compact_boundary(&self) -> bool {
936 self.subtype == SystemSubtype::CompactBoundary
937 }
938
939 pub fn as_init(&self) -> Option<InitMessage> {
941 if self.subtype != SystemSubtype::Init {
942 return None;
943 }
944 serde_json::from_value(self.data.clone()).ok()
945 }
946
947 pub fn as_status(&self) -> Option<StatusMessage> {
949 if self.subtype != SystemSubtype::Status {
950 return None;
951 }
952 serde_json::from_value(self.data.clone()).ok()
953 }
954
955 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
957 if self.subtype != SystemSubtype::CompactBoundary {
958 return None;
959 }
960 serde_json::from_value(self.data.clone()).ok()
961 }
962
963 pub fn is_task_started(&self) -> bool {
965 self.subtype == SystemSubtype::TaskStarted
966 }
967
968 pub fn is_task_progress(&self) -> bool {
970 self.subtype == SystemSubtype::TaskProgress
971 }
972
973 pub fn is_task_notification(&self) -> bool {
975 self.subtype == SystemSubtype::TaskNotification
976 }
977
978 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
980 if self.subtype != SystemSubtype::TaskStarted {
981 return None;
982 }
983 serde_json::from_value(self.data.clone()).ok()
984 }
985
986 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
988 if self.subtype != SystemSubtype::TaskProgress {
989 return None;
990 }
991 serde_json::from_value(self.data.clone()).ok()
992 }
993
994 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
996 if self.subtype != SystemSubtype::TaskNotification {
997 return None;
998 }
999 serde_json::from_value(self.data.clone()).ok()
1000 }
1001
1002 pub fn is_task_updated(&self) -> bool {
1004 self.subtype == SystemSubtype::TaskUpdated
1005 }
1006
1007 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1009 if self.subtype != SystemSubtype::TaskUpdated {
1010 return None;
1011 }
1012 serde_json::from_value(self.data.clone()).ok()
1013 }
1014
1015 pub fn is_thinking_tokens(&self) -> bool {
1017 self.subtype == SystemSubtype::ThinkingTokens
1018 }
1019
1020 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1022 if self.subtype != SystemSubtype::ThinkingTokens {
1023 return None;
1024 }
1025 serde_json::from_value(self.data.clone()).ok()
1026 }
1027
1028 pub fn is_code_change_published(&self) -> bool {
1030 self.subtype == SystemSubtype::CodeChangePublished
1031 }
1032
1033 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1035 if self.subtype != SystemSubtype::CodeChangePublished {
1036 return None;
1037 }
1038 serde_json::from_value(self.data.clone()).ok()
1039 }
1040
1041 pub fn is_vcs_state_changed(&self) -> bool {
1043 self.subtype == SystemSubtype::VcsStateChanged
1044 }
1045
1046 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1048 if self.subtype != SystemSubtype::VcsStateChanged {
1049 return None;
1050 }
1051 serde_json::from_value(self.data.clone()).ok()
1052 }
1053
1054 pub fn is_feedback_draft_queued(&self) -> bool {
1056 self.subtype == SystemSubtype::FeedbackDraftQueued
1057 }
1058
1059 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1061 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1062 return None;
1063 }
1064 serde_json::from_value(self.data.clone()).ok()
1065 }
1066
1067 pub fn is_cloud_session_delta(&self) -> bool {
1069 self.subtype == SystemSubtype::CloudSessionDelta
1070 }
1071
1072 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1074 if self.subtype != SystemSubtype::CloudSessionDelta {
1075 return None;
1076 }
1077 serde_json::from_value(self.data.clone()).ok()
1078 }
1079
1080 pub fn is_dev_intent(&self) -> bool {
1082 self.subtype == SystemSubtype::DevIntent
1083 }
1084
1085 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1087 if self.subtype != SystemSubtype::DevIntent {
1088 return None;
1089 }
1090 serde_json::from_value(self.data.clone()).ok()
1091 }
1092
1093 pub fn is_turn_handoff_available(&self) -> bool {
1095 self.subtype == SystemSubtype::TurnHandoffAvailable
1096 }
1097
1098 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1100 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1101 return None;
1102 }
1103 serde_json::from_value(self.data.clone()).ok()
1104 }
1105
1106 pub fn is_turn_preempted(&self) -> bool {
1108 self.subtype == SystemSubtype::TurnPreempted
1109 }
1110
1111 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1113 if self.subtype != SystemSubtype::TurnPreempted {
1114 return None;
1115 }
1116 serde_json::from_value(self.data.clone()).ok()
1117 }
1118
1119 pub fn is_peer_message_hold(&self) -> bool {
1121 self.subtype == SystemSubtype::PeerMessageHold
1122 }
1123
1124 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1126 if self.subtype != SystemSubtype::PeerMessageHold {
1127 return None;
1128 }
1129 serde_json::from_value(self.data.clone()).ok()
1130 }
1131
1132 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1134 macro_rules! parse {
1135 ($variant:ident, $ty:ty) => {
1136 serde_json::from_value::<$ty>(self.data.clone())
1137 .ok()
1138 .map(KnownSystemEvent::$variant)
1139 };
1140 }
1141
1142 match self.subtype {
1143 SystemSubtype::Init => parse!(Init, InitMessage),
1144 SystemSubtype::Status => parse!(Status, StatusMessage),
1145 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1146 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1147 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1148 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1149 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1150 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1151 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1152 SystemSubtype::ControlRequestProgress => {
1153 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1154 }
1155 SystemSubtype::ModelRefusalFallback => {
1156 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1157 }
1158 SystemSubtype::ModelRefusalNoFallback => {
1159 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1160 }
1161 SystemSubtype::LocalCommandOutput => {
1162 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1163 }
1164 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1165 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1166 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1167 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1168 SystemSubtype::BackgroundTasksChanged => {
1169 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1170 }
1171 SystemSubtype::SessionStateChanged => {
1172 parse!(SessionStateChanged, SessionStateChangedMessage)
1173 }
1174 SystemSubtype::WorkerShuttingDown => {
1175 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1176 }
1177 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1178 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1179 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1180 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1181 SystemSubtype::ElicitationComplete => {
1182 parse!(ElicitationComplete, ElicitationCompleteMessage)
1183 }
1184 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1185 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1186 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1187 SystemSubtype::CodeChangePublished => {
1188 parse!(CodeChangePublished, CodeChangePublishedMessage)
1189 }
1190 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1191 SystemSubtype::FeedbackDraftQueued => {
1192 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1193 }
1194 SystemSubtype::CloudSessionDelta => {
1195 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1196 }
1197 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1198 SystemSubtype::TurnHandoffAvailable => {
1199 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1200 }
1201 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1202 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1203 SystemSubtype::Unknown(_) => None,
1204 }
1205 }
1206
1207 pub fn typed_value(&self) -> Option<Value> {
1216 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1217 parsed.and_then(|v| serde_json::to_value(v).ok())
1218 }
1219 match self.subtype {
1220 SystemSubtype::Init => reserialize(self.as_init()),
1221 SystemSubtype::Status => reserialize(self.as_status()),
1222 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1223 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1224 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1225 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1226 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1227 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1228 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1229 SystemSubtype::ControlRequestProgress => {
1230 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1231 }
1232 SystemSubtype::ModelRefusalFallback => {
1233 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1234 }
1235 SystemSubtype::ModelRefusalNoFallback => {
1236 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1237 }
1238 SystemSubtype::LocalCommandOutput => {
1239 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1240 }
1241 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1242 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1243 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1244 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1245 SystemSubtype::BackgroundTasksChanged => {
1246 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1247 }
1248 SystemSubtype::SessionStateChanged => {
1249 reserialize(parse_system::<SessionStateChangedMessage>(self))
1250 }
1251 SystemSubtype::WorkerShuttingDown => {
1252 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1253 }
1254 SystemSubtype::CommandsChanged => {
1255 reserialize(parse_system::<CommandsChangedMessage>(self))
1256 }
1257 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1258 SystemSubtype::FilesPersisted => {
1259 reserialize(parse_system::<FilesPersistedMessage>(self))
1260 }
1261 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1262 SystemSubtype::ElicitationComplete => {
1263 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1264 }
1265 SystemSubtype::PermissionDenied => {
1266 reserialize(parse_system::<PermissionDeniedMessage>(self))
1267 }
1268 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1269 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1270 SystemSubtype::CodeChangePublished => {
1271 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1272 }
1273 SystemSubtype::VcsStateChanged => {
1274 reserialize(parse_system::<VcsStateChangedMessage>(self))
1275 }
1276 SystemSubtype::FeedbackDraftQueued => {
1277 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1278 }
1279 SystemSubtype::CloudSessionDelta => {
1280 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1281 }
1282 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1283 SystemSubtype::TurnHandoffAvailable => {
1284 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1285 }
1286 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1287 SystemSubtype::PeerMessageHold => {
1288 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1289 }
1290 SystemSubtype::Unknown(_) => None,
1291 }
1292 }
1293}
1294
1295fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1296 serde_json::from_value(message.data.clone()).ok()
1297}
1298
1299#[allow(clippy::large_enum_variant)]
1305#[derive(Debug, Clone, Serialize, Deserialize)]
1306pub enum KnownSystemEvent {
1307 Init(InitMessage),
1308 Status(StatusMessage),
1309 CompactBoundary(CompactBoundaryMessage),
1310 ThinkingTokens(ThinkingTokensMessage),
1311 TaskStarted(TaskStartedMessage),
1312 TaskProgress(TaskProgressMessage),
1313 TaskUpdated(TaskUpdatedMessage),
1314 TaskNotification(TaskNotificationMessage),
1315 ApiRetry(ApiRetryMessage),
1316 ControlRequestProgress(ControlRequestProgressMessage),
1317 ModelRefusalFallback(ModelRefusalFallbackMessage),
1318 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1319 LocalCommandOutput(LocalCommandOutputMessage),
1320 HookStarted(HookStartedMessage),
1321 HookProgress(HookProgressMessage),
1322 HookResponse(HookResponseMessage),
1323 PluginInstall(PluginInstallMessage),
1324 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1325 SessionStateChanged(SessionStateChangedMessage),
1326 WorkerShuttingDown(WorkerShuttingDownMessage),
1327 CommandsChanged(CommandsChangedMessage),
1328 Notification(NotificationMessage),
1329 FilesPersisted(FilesPersistedMessage),
1330 MemoryRecall(MemoryRecallMessage),
1331 ElicitationComplete(ElicitationCompleteMessage),
1332 PermissionDenied(PermissionDeniedMessage),
1333 MirrorError(MirrorErrorMessage),
1334 Informational(InformationalMessage),
1335 CodeChangePublished(CodeChangePublishedMessage),
1336 VcsStateChanged(VcsStateChangedMessage),
1337 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1338 CloudSessionDelta(CloudSessionDeltaMessage),
1339 DevIntent(DevIntentMessage),
1340 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1341 TurnPreempted(TurnPreemptedMessage),
1342 PeerMessageHold(PeerMessageHoldMessage),
1343}
1344
1345#[derive(Debug, Clone, Serialize, Deserialize)]
1346pub struct ApiRetryMessage {
1347 pub attempt: u64,
1348 pub max_retries: u64,
1349 pub retry_delay_ms: u64,
1350 pub error_status: Option<u16>,
1351 pub error: String,
1352 #[serde(default, skip_serializing_if = "Option::is_none")]
1357 pub no_response: Option<ApiRetryNoResponse>,
1358 #[serde(default, skip_serializing_if = "Option::is_none")]
1359 pub uuid: Option<String>,
1360 #[serde(default, skip_serializing_if = "Option::is_none")]
1361 pub session_id: Option<String>,
1362}
1363
1364#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1367pub struct ApiRetryNoResponse {
1368 pub waited_ms: u64,
1370 pub retry_wait_ms: u64,
1372}
1373
1374#[derive(Debug, Clone, Serialize, Deserialize)]
1375pub struct ControlRequestProgressMessage {
1376 pub request_id: String,
1377 pub status: String,
1378 #[serde(default, skip_serializing_if = "Option::is_none")]
1379 pub attempt: Option<u64>,
1380 #[serde(default, skip_serializing_if = "Option::is_none")]
1381 pub max_retries: Option<u64>,
1382 #[serde(default, skip_serializing_if = "Option::is_none")]
1383 pub retry_delay_ms: Option<u64>,
1384 #[serde(default, skip_serializing_if = "Option::is_none")]
1385 pub error_status: Option<u16>,
1386 #[serde(default, skip_serializing_if = "Option::is_none")]
1387 pub error: Option<String>,
1388 #[serde(default, skip_serializing_if = "Option::is_none")]
1389 pub uuid: Option<String>,
1390 #[serde(default, skip_serializing_if = "Option::is_none")]
1391 pub session_id: Option<String>,
1392}
1393
1394#[derive(Debug, Clone, Serialize, Deserialize)]
1395pub struct ModelRefusalFallbackMessage {
1396 pub trigger: String,
1397 pub direction: String,
1398 #[serde(default, skip_serializing_if = "Option::is_none")]
1404 pub scope: Option<RefusalFallbackScope>,
1405 pub original_model: String,
1406 pub fallback_model: String,
1407 pub request_id: Option<String>,
1408 #[serde(default, skip_serializing_if = "Option::is_none")]
1409 pub api_refusal_category: Option<String>,
1410 #[serde(default, skip_serializing_if = "Option::is_none")]
1415 pub saw_cyber_refusal: Option<bool>,
1416 #[serde(default, skip_serializing_if = "Option::is_none")]
1417 pub api_refusal_explanation: Option<String>,
1418 #[serde(default, skip_serializing_if = "Option::is_none")]
1419 pub retracted_message_uuids: Option<Vec<String>>,
1420 #[serde(default, skip_serializing_if = "Option::is_none")]
1421 pub refused_user_message_uuid: Option<String>,
1422 pub content: Value,
1423 #[serde(default, skip_serializing_if = "Option::is_none")]
1424 pub uuid: Option<String>,
1425 #[serde(default, skip_serializing_if = "Option::is_none")]
1426 pub session_id: Option<String>,
1427}
1428
1429#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1433pub enum RefusalFallbackScope {
1434 Session,
1436 Local,
1439 Unknown(String),
1441}
1442
1443impl RefusalFallbackScope {
1444 pub fn as_str(&self) -> &str {
1445 match self {
1446 Self::Session => "session",
1447 Self::Local => "local",
1448 Self::Unknown(s) => s.as_str(),
1449 }
1450 }
1451}
1452
1453impl fmt::Display for RefusalFallbackScope {
1454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1455 f.write_str(self.as_str())
1456 }
1457}
1458
1459impl From<&str> for RefusalFallbackScope {
1460 fn from(s: &str) -> Self {
1461 match s {
1462 "session" => Self::Session,
1463 "local" => Self::Local,
1464 other => Self::Unknown(other.to_string()),
1465 }
1466 }
1467}
1468
1469impl Serialize for RefusalFallbackScope {
1470 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1471 serializer.serialize_str(self.as_str())
1472 }
1473}
1474
1475impl<'de> Deserialize<'de> for RefusalFallbackScope {
1476 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1477 let s = String::deserialize(deserializer)?;
1478 Ok(Self::from(s.as_str()))
1479 }
1480}
1481
1482#[derive(Debug, Clone, Serialize, Deserialize)]
1483pub struct ModelRefusalNoFallbackMessage {
1484 pub original_model: String,
1485 pub request_id: Option<String>,
1486 #[serde(default, skip_serializing_if = "Option::is_none")]
1487 pub api_refusal_category: Option<String>,
1488 #[serde(default, skip_serializing_if = "Option::is_none")]
1489 pub api_refusal_explanation: Option<String>,
1490 pub content: Value,
1491 #[serde(default, skip_serializing_if = "Option::is_none")]
1492 pub uuid: Option<String>,
1493 #[serde(default, skip_serializing_if = "Option::is_none")]
1494 pub session_id: Option<String>,
1495}
1496
1497#[derive(Debug, Clone, Serialize, Deserialize)]
1498pub struct LocalCommandOutputMessage {
1499 pub content: String,
1500 #[serde(default, skip_serializing_if = "Option::is_none")]
1501 pub uuid: Option<String>,
1502 #[serde(default, skip_serializing_if = "Option::is_none")]
1503 pub session_id: Option<String>,
1504}
1505
1506#[derive(Debug, Clone, Serialize, Deserialize)]
1507pub struct HookStartedMessage {
1508 pub hook_id: String,
1509 pub hook_name: String,
1510 pub hook_event: String,
1511 #[serde(default, skip_serializing_if = "Option::is_none")]
1512 pub uuid: Option<String>,
1513 #[serde(default, skip_serializing_if = "Option::is_none")]
1514 pub session_id: Option<String>,
1515}
1516
1517#[derive(Debug, Clone, Serialize, Deserialize)]
1518pub struct HookProgressMessage {
1519 pub hook_id: String,
1520 pub hook_name: String,
1521 pub hook_event: String,
1522 #[serde(default, skip_serializing_if = "Option::is_none")]
1523 pub stdout: Option<String>,
1524 #[serde(default, skip_serializing_if = "Option::is_none")]
1525 pub stderr: Option<String>,
1526 #[serde(default, skip_serializing_if = "Option::is_none")]
1527 pub output: Option<String>,
1528 #[serde(default, skip_serializing_if = "Option::is_none")]
1529 pub uuid: Option<String>,
1530 #[serde(default, skip_serializing_if = "Option::is_none")]
1531 pub session_id: Option<String>,
1532}
1533
1534#[derive(Debug, Clone, Serialize, Deserialize)]
1535pub struct HookResponseMessage {
1536 pub hook_id: String,
1537 pub hook_name: String,
1538 pub hook_event: String,
1539 #[serde(default, skip_serializing_if = "Option::is_none")]
1540 pub stdout: Option<String>,
1541 #[serde(default, skip_serializing_if = "Option::is_none")]
1542 pub stderr: Option<String>,
1543 #[serde(default, skip_serializing_if = "Option::is_none")]
1544 pub output: Option<String>,
1545 #[serde(default, skip_serializing_if = "Option::is_none")]
1546 pub exit_code: Option<i32>,
1547 pub outcome: String,
1548 #[serde(default, skip_serializing_if = "Option::is_none")]
1549 pub uuid: Option<String>,
1550 #[serde(default, skip_serializing_if = "Option::is_none")]
1551 pub session_id: Option<String>,
1552}
1553
1554#[derive(Debug, Clone, Serialize, Deserialize)]
1555pub struct PluginInstallMessage {
1556 pub status: String,
1557 #[serde(default, skip_serializing_if = "Option::is_none")]
1558 pub name: Option<String>,
1559 #[serde(default, skip_serializing_if = "Option::is_none")]
1560 pub error: Option<String>,
1561 #[serde(default, skip_serializing_if = "Option::is_none")]
1562 pub uuid: Option<String>,
1563 #[serde(default, skip_serializing_if = "Option::is_none")]
1564 pub session_id: Option<String>,
1565}
1566
1567#[derive(Debug, Clone, Serialize, Deserialize)]
1568pub struct BackgroundTasksChangedMessage {
1569 pub tasks: Vec<BackgroundTaskInfo>,
1570 #[serde(default, skip_serializing_if = "Option::is_none")]
1571 pub uuid: Option<String>,
1572 #[serde(default, skip_serializing_if = "Option::is_none")]
1573 pub session_id: Option<String>,
1574}
1575
1576#[derive(Debug, Clone, Serialize, Deserialize)]
1577pub struct BackgroundTaskInfo {
1578 pub task_id: String,
1579 pub task_type: String,
1580 pub description: String,
1581 #[serde(default, skip_serializing_if = "Option::is_none")]
1584 pub ambient: Option<bool>,
1585}
1586
1587#[derive(Debug, Clone, Serialize, Deserialize)]
1588pub struct SessionStateChangedMessage {
1589 pub state: String,
1590 #[serde(default, skip_serializing_if = "Option::is_none")]
1591 pub uuid: Option<String>,
1592 #[serde(default, skip_serializing_if = "Option::is_none")]
1593 pub session_id: Option<String>,
1594}
1595
1596#[derive(Debug, Clone, Serialize, Deserialize)]
1597pub struct WorkerShuttingDownMessage {
1598 pub reason: String,
1599 #[serde(default, skip_serializing_if = "Option::is_none")]
1600 pub uuid: Option<String>,
1601 #[serde(default, skip_serializing_if = "Option::is_none")]
1602 pub session_id: Option<String>,
1603}
1604
1605#[derive(Debug, Clone, Serialize, Deserialize)]
1606pub struct CommandsChangedMessage {
1607 pub commands: Vec<CommandInfo>,
1608 #[serde(default, skip_serializing_if = "Option::is_none")]
1609 pub uuid: Option<String>,
1610 #[serde(default, skip_serializing_if = "Option::is_none")]
1611 pub session_id: Option<String>,
1612}
1613
1614#[derive(Debug, Clone, Serialize, Deserialize)]
1615pub struct CommandInfo {
1616 pub name: String,
1617 pub description: String,
1618 #[serde(rename = "argumentHint")]
1619 pub argument_hint: String,
1620 #[serde(default, skip_serializing_if = "Option::is_none")]
1621 pub aliases: Option<Vec<String>>,
1622}
1623
1624#[derive(Debug, Clone, Serialize, Deserialize)]
1625pub struct NotificationMessage {
1626 pub key: String,
1627 pub text: String,
1628 pub priority: String,
1629 #[serde(default, skip_serializing_if = "Option::is_none")]
1630 pub color: Option<String>,
1631 #[serde(default, skip_serializing_if = "Option::is_none")]
1632 pub timeout_ms: Option<u64>,
1633 #[serde(default, skip_serializing_if = "Option::is_none")]
1634 pub uuid: Option<String>,
1635 #[serde(default, skip_serializing_if = "Option::is_none")]
1636 pub session_id: Option<String>,
1637}
1638
1639#[derive(Debug, Clone, Serialize, Deserialize)]
1640pub struct FilesPersistedMessage {
1641 pub files: Vec<PersistedFile>,
1642 pub failed: Vec<FailedPersistedFile>,
1643 pub processed_at: String,
1644 #[serde(default, skip_serializing_if = "Option::is_none")]
1645 pub uuid: Option<String>,
1646 #[serde(default, skip_serializing_if = "Option::is_none")]
1647 pub session_id: Option<String>,
1648}
1649
1650#[derive(Debug, Clone, Serialize, Deserialize)]
1651pub struct PersistedFile {
1652 pub filename: String,
1653 pub file_id: String,
1654}
1655
1656#[derive(Debug, Clone, Serialize, Deserialize)]
1657pub struct FailedPersistedFile {
1658 pub filename: String,
1659 pub error: String,
1660}
1661
1662#[derive(Debug, Clone, Serialize, Deserialize)]
1663pub struct MemoryRecallMessage {
1664 pub mode: String,
1665 pub memories: Vec<MemoryRecallItem>,
1666 #[serde(default, skip_serializing_if = "Option::is_none")]
1667 pub uuid: Option<String>,
1668 #[serde(default, skip_serializing_if = "Option::is_none")]
1669 pub session_id: Option<String>,
1670}
1671
1672#[derive(Debug, Clone, Serialize, Deserialize)]
1673pub struct MemoryRecallItem {
1674 pub path: String,
1675 pub scope: String,
1676 #[serde(default, skip_serializing_if = "Option::is_none")]
1677 pub content: Option<String>,
1678}
1679
1680#[derive(Debug, Clone, Serialize, Deserialize)]
1681pub struct ElicitationCompleteMessage {
1682 pub mcp_server_name: String,
1683 pub elicitation_id: String,
1684 #[serde(default, skip_serializing_if = "Option::is_none")]
1685 pub uuid: Option<String>,
1686 #[serde(default, skip_serializing_if = "Option::is_none")]
1687 pub session_id: Option<String>,
1688}
1689
1690#[derive(Debug, Clone, Serialize, Deserialize)]
1691pub struct PermissionDeniedMessage {
1692 pub tool_name: String,
1693 pub tool_use_id: String,
1694 #[serde(default, skip_serializing_if = "Option::is_none")]
1695 pub agent_id: Option<String>,
1696 #[serde(default, skip_serializing_if = "Option::is_none")]
1697 pub decision_reason_type: Option<String>,
1698 #[serde(default, skip_serializing_if = "Option::is_none")]
1699 pub decision_reason: Option<String>,
1700 pub message: String,
1701 #[serde(default, skip_serializing_if = "Option::is_none")]
1702 pub uuid: Option<String>,
1703 #[serde(default, skip_serializing_if = "Option::is_none")]
1704 pub session_id: Option<String>,
1705}
1706
1707#[derive(Debug, Clone, Serialize, Deserialize)]
1708pub struct MirrorErrorMessage {
1709 pub error: String,
1710 pub key: MirrorErrorKey,
1711 #[serde(default, skip_serializing_if = "Option::is_none")]
1712 pub uuid: Option<String>,
1713 #[serde(default, skip_serializing_if = "Option::is_none")]
1714 pub session_id: Option<String>,
1715}
1716
1717#[derive(Debug, Clone, Serialize, Deserialize)]
1718pub struct MirrorErrorKey {
1719 #[serde(rename = "projectKey")]
1720 pub project_key: String,
1721 #[serde(rename = "sessionId")]
1722 pub session_id: String,
1723 #[serde(default, skip_serializing_if = "Option::is_none")]
1724 pub subpath: Option<String>,
1725}
1726
1727#[derive(Debug, Clone, Serialize, Deserialize)]
1728pub struct InformationalMessage {
1729 pub content: String,
1730 pub level: String,
1731 #[serde(default, skip_serializing_if = "Option::is_none")]
1732 pub tool_use_id: Option<String>,
1733 #[serde(default, skip_serializing_if = "Option::is_none")]
1734 pub prevent_continuation: Option<bool>,
1735 #[serde(default, skip_serializing_if = "Option::is_none")]
1736 pub uuid: Option<String>,
1737 #[serde(default, skip_serializing_if = "Option::is_none")]
1738 pub session_id: Option<String>,
1739}
1740
1741#[derive(Debug, Clone, Serialize, Deserialize)]
1743pub struct PluginInfo {
1744 pub name: String,
1746 pub path: String,
1748 #[serde(skip_serializing_if = "Option::is_none")]
1750 pub source: Option<String>,
1751 #[serde(default, skip_serializing_if = "Option::is_none")]
1753 pub version: Option<String>,
1754}
1755
1756#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1758pub struct PluginDiagnostic {
1759 pub plugin: String,
1760 #[serde(rename = "type")]
1761 pub diagnostic_type: String,
1762 pub message: String,
1763}
1764
1765#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1767pub struct MemoryPaths {
1768 #[serde(default, skip_serializing_if = "Option::is_none")]
1769 pub auto: Option<String>,
1770 #[serde(default, skip_serializing_if = "Option::is_none")]
1771 pub team: Option<String>,
1772 #[serde(flatten)]
1773 pub extra: serde_json::Map<String, Value>,
1774}
1775
1776#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1780pub struct McpServerError {
1781 pub name: String,
1782 #[serde(rename = "type")]
1784 pub error_type: String,
1785 pub message: String,
1786}
1787
1788#[derive(Debug, Clone, Serialize, Deserialize)]
1790pub struct InitMessage {
1791 pub session_id: String,
1793 #[serde(skip_serializing_if = "Option::is_none")]
1795 pub cwd: Option<String>,
1796 #[serde(skip_serializing_if = "Option::is_none")]
1798 pub model: Option<String>,
1799 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1801 pub tools: Vec<String>,
1802 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1804 pub mcp_servers: Vec<Value>,
1805 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1807 pub slash_commands: Vec<String>,
1808 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1811 pub terminal_slash_commands: Vec<String>,
1812 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1814 pub agents: Vec<String>,
1815 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1817 pub plugins: Vec<PluginInfo>,
1818 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1820 pub skills: Vec<Value>,
1821 #[serde(skip_serializing_if = "Option::is_none")]
1823 pub claude_code_version: Option<String>,
1824 #[serde(skip_serializing_if = "Option::is_none")]
1827 pub messaging_socket_path: Option<String>,
1828 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
1830 pub api_key_source: Option<ApiKeySource>,
1831 #[serde(skip_serializing_if = "Option::is_none")]
1833 pub output_style: Option<OutputStyle>,
1834 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
1836 pub permission_mode: Option<InitPermissionMode>,
1837
1838 #[serde(skip_serializing_if = "Option::is_none")]
1840 pub uuid: Option<String>,
1841
1842 #[serde(skip_serializing_if = "Option::is_none")]
1844 pub memory_paths: Option<MemoryPaths>,
1845
1846 #[serde(skip_serializing_if = "Option::is_none")]
1848 pub fast_mode_state: Option<String>,
1849
1850 #[serde(default, skip_serializing_if = "Option::is_none")]
1852 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
1853
1854 #[serde(default, skip_serializing_if = "Option::is_none")]
1857 pub mcp_server_errors: Option<Vec<McpServerError>>,
1858
1859 #[serde(default, skip_serializing_if = "Option::is_none")]
1861 pub analytics_disabled: Option<bool>,
1862
1863 #[serde(default, skip_serializing_if = "Option::is_none")]
1865 pub product_feedback_disabled: Option<bool>,
1866
1867 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1869 pub betas: Vec<String>,
1870
1871 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1873 pub capabilities: Vec<String>,
1874
1875 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1877 pub plugin_errors: Vec<PluginDiagnostic>,
1878
1879 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1881 pub plugin_warnings: Vec<PluginDiagnostic>,
1882
1883 #[serde(default, skip_serializing_if = "Option::is_none")]
1888 pub effort: Option<String>,
1889
1890 #[serde(default, skip_serializing_if = "Option::is_none")]
1895 pub cloud_session: Option<Value>,
1896
1897 #[serde(default, skip_serializing_if = "Option::is_none")]
1901 pub footer_indicator: Option<FooterIndicator>,
1902
1903 #[serde(default, skip_serializing_if = "Option::is_none")]
1907 pub worker_epoch: Option<u64>,
1908
1909 #[serde(default, skip_serializing_if = "Option::is_none")]
1913 pub powershell_path: Option<Option<String>>,
1914
1915 #[serde(default, skip_serializing_if = "Option::is_none")]
1919 pub startup_timing: Option<serde_json::Map<String, Value>>,
1920}
1921
1922#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1926pub struct FooterIndicator {
1927 pub text: String,
1930}
1931
1932#[derive(Debug, Clone, Serialize, Deserialize)]
1934pub struct StatusMessage {
1935 pub session_id: String,
1937 pub status: Option<StatusMessageStatus>,
1939 #[serde(skip_serializing_if = "Option::is_none")]
1941 pub uuid: Option<String>,
1942 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
1944 pub permission_mode: Option<InitPermissionMode>,
1945 #[serde(skip_serializing_if = "Option::is_none")]
1946 pub compact_result: Option<String>,
1947 #[serde(skip_serializing_if = "Option::is_none")]
1948 pub compact_error: Option<String>,
1949}
1950
1951#[derive(Debug, Clone, Serialize, Deserialize)]
1953pub struct CompactBoundaryMessage {
1954 pub session_id: String,
1956 pub compact_metadata: CompactMetadata,
1958 #[serde(
1962 default,
1963 skip_serializing_if = "Option::is_none",
1964 alias = "content",
1965 alias = "text"
1966 )]
1967 pub summary: Option<String>,
1968 #[serde(
1972 default,
1973 skip_serializing_if = "Option::is_none",
1974 alias = "message_count"
1975 )]
1976 pub leaf_message_count: Option<u32>,
1977 #[serde(default, skip_serializing_if = "Option::is_none")]
1979 pub duration_ms: Option<u64>,
1980 #[serde(skip_serializing_if = "Option::is_none")]
1982 pub uuid: Option<String>,
1983 #[serde(skip_serializing_if = "Option::is_none")]
1985 pub logical_parent_uuid: Option<Option<String>>,
1986 #[serde(default, skip_serializing_if = "Option::is_none")]
1990 pub historical: Option<bool>,
1991}
1992
1993#[derive(Debug, Clone, Serialize, Deserialize)]
1995pub struct CompactMetadata {
1996 pub pre_tokens: u64,
1998 pub trigger: CompactionTrigger,
2000 #[serde(default, skip_serializing_if = "Option::is_none")]
2001 pub post_tokens: Option<u64>,
2002 #[serde(default, skip_serializing_if = "Option::is_none")]
2003 pub cumulative_dropped_tokens: Option<u64>,
2004 #[serde(default, skip_serializing_if = "Option::is_none")]
2005 pub duration_ms: Option<u64>,
2006 #[serde(default, skip_serializing_if = "Option::is_none")]
2007 pub user_context: Option<String>,
2008 #[serde(default, skip_serializing_if = "Option::is_none")]
2009 pub messages_summarized: Option<u64>,
2010 #[serde(default, skip_serializing_if = "Option::is_none")]
2011 pub precomputed: Option<bool>,
2012 #[serde(default, skip_serializing_if = "Option::is_none")]
2013 pub pre_compact_discovered_tools: Option<Vec<String>>,
2014 #[serde(default, skip_serializing_if = "Option::is_none")]
2015 pub preserved_segment: Option<PreservedSegment>,
2016 #[serde(default, skip_serializing_if = "Option::is_none")]
2017 pub preserved_messages: Option<PreservedMessages>,
2018}
2019
2020#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2021pub struct PreservedSegment {
2022 pub head_uuid: String,
2023 pub anchor_uuid: String,
2024 pub tail_uuid: String,
2025}
2026
2027#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2028pub struct PreservedMessages {
2029 pub anchor_uuid: String,
2030 pub uuids: Vec<String>,
2031 #[serde(default, skip_serializing_if = "Option::is_none")]
2032 pub all_uuids: Option<Vec<String>>,
2033}
2034
2035#[derive(Debug, Clone, Serialize, Deserialize)]
2041pub struct TaskUsage {
2042 pub duration_ms: u64,
2044 pub tool_uses: u64,
2046 pub total_tokens: u64,
2048}
2049
2050#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2052pub enum TaskType {
2053 LocalAgent,
2055 LocalBash,
2057 LocalWorkflow,
2059 Unknown(String),
2061}
2062
2063impl TaskType {
2064 pub fn as_str(&self) -> &str {
2065 match self {
2066 Self::LocalAgent => "local_agent",
2067 Self::LocalBash => "local_bash",
2068 Self::LocalWorkflow => "local_workflow",
2069 Self::Unknown(s) => s.as_str(),
2070 }
2071 }
2072}
2073
2074impl fmt::Display for TaskType {
2075 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2076 f.write_str(self.as_str())
2077 }
2078}
2079
2080impl From<&str> for TaskType {
2081 fn from(s: &str) -> Self {
2082 match s {
2083 "local_agent" => Self::LocalAgent,
2084 "local_bash" => Self::LocalBash,
2085 "local_workflow" => Self::LocalWorkflow,
2086 other => Self::Unknown(other.to_string()),
2087 }
2088 }
2089}
2090
2091impl Serialize for TaskType {
2092 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2093 serializer.serialize_str(self.as_str())
2094 }
2095}
2096
2097impl<'de> Deserialize<'de> for TaskType {
2098 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2099 let s = String::deserialize(deserializer)?;
2100 Ok(Self::from(s.as_str()))
2101 }
2102}
2103
2104#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2106pub enum TaskStatus {
2107 Pending,
2108 Running,
2109 Completed,
2110 Failed,
2111 Killed,
2112 Paused,
2113 Stopped,
2114 Unknown(String),
2115}
2116
2117impl TaskStatus {
2118 pub fn as_str(&self) -> &str {
2119 match self {
2120 Self::Pending => "pending",
2121 Self::Running => "running",
2122 Self::Completed => "completed",
2123 Self::Failed => "failed",
2124 Self::Killed => "killed",
2125 Self::Paused => "paused",
2126 Self::Stopped => "stopped",
2127 Self::Unknown(s) => s.as_str(),
2128 }
2129 }
2130}
2131
2132impl fmt::Display for TaskStatus {
2133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2134 f.write_str(self.as_str())
2135 }
2136}
2137
2138impl From<&str> for TaskStatus {
2139 fn from(s: &str) -> Self {
2140 match s {
2141 "pending" => Self::Pending,
2142 "running" => Self::Running,
2143 "completed" => Self::Completed,
2144 "failed" => Self::Failed,
2145 "killed" => Self::Killed,
2146 "paused" => Self::Paused,
2147 "stopped" => Self::Stopped,
2148 other => Self::Unknown(other.to_string()),
2149 }
2150 }
2151}
2152
2153impl Serialize for TaskStatus {
2154 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2155 serializer.serialize_str(self.as_str())
2156 }
2157}
2158
2159impl<'de> Deserialize<'de> for TaskStatus {
2160 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2161 let s = String::deserialize(deserializer)?;
2162 Ok(Self::from(s.as_str()))
2163 }
2164}
2165
2166#[derive(Debug, Clone, Serialize, Deserialize)]
2168pub struct TaskStartedMessage {
2169 pub session_id: String,
2170 pub task_id: String,
2171 #[serde(default, skip_serializing_if = "Option::is_none")]
2172 pub task_type: Option<TaskType>,
2173 #[serde(default, skip_serializing_if = "Option::is_none")]
2174 pub tool_use_id: Option<String>,
2175 pub description: String,
2176 #[serde(default, skip_serializing_if = "Option::is_none")]
2179 pub subagent_type: Option<String>,
2180 #[serde(default, skip_serializing_if = "Option::is_none")]
2186 pub is_backgrounded: Option<bool>,
2187 #[serde(default, skip_serializing_if = "Option::is_none")]
2191 pub spawn_depth: Option<u32>,
2192 #[serde(default, skip_serializing_if = "Option::is_none")]
2194 pub prompt: Option<String>,
2195 #[serde(default, skip_serializing_if = "Option::is_none")]
2196 pub workflow_name: Option<String>,
2197 #[serde(default, skip_serializing_if = "Option::is_none")]
2198 pub skip_transcript: Option<bool>,
2199 #[serde(default, skip_serializing_if = "Option::is_none")]
2204 pub ambient: Option<bool>,
2205 pub uuid: String,
2206}
2207
2208#[derive(Debug, Clone, Serialize, Deserialize)]
2212pub struct TaskUpdatedMessage {
2213 pub session_id: String,
2214 pub task_id: String,
2215 pub patch: TaskPatch,
2216 pub uuid: String,
2217}
2218
2219#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2222pub struct TaskPatch {
2223 #[serde(default, skip_serializing_if = "Option::is_none")]
2224 pub status: Option<TaskStatus>,
2225 #[serde(default, skip_serializing_if = "Option::is_none")]
2228 pub end_time: Option<u64>,
2229 #[serde(default, skip_serializing_if = "Option::is_none")]
2230 pub description: Option<String>,
2231 #[serde(default, skip_serializing_if = "Option::is_none")]
2232 pub total_paused_ms: Option<u64>,
2233 #[serde(default, skip_serializing_if = "Option::is_none")]
2234 pub error: Option<String>,
2235 #[serde(default, skip_serializing_if = "Option::is_none")]
2236 pub is_backgrounded: Option<bool>,
2237}
2238
2239#[derive(Debug, Clone, Serialize, Deserialize)]
2242pub struct ThinkingTokensMessage {
2243 pub session_id: String,
2244 pub estimated_tokens: u64,
2246 pub estimated_tokens_delta: u64,
2248 #[serde(default, skip_serializing_if = "Option::is_none")]
2255 pub user_message_uuid: Option<String>,
2256 pub uuid: String,
2257}
2258
2259#[derive(Debug, Clone, Serialize, Deserialize)]
2262pub struct TaskProgressMessage {
2263 pub session_id: String,
2264 pub task_id: String,
2265 #[serde(default, skip_serializing_if = "Option::is_none")]
2266 pub tool_use_id: Option<String>,
2267 pub description: String,
2268 #[serde(default, skip_serializing_if = "Option::is_none")]
2269 pub last_tool_name: Option<String>,
2270 pub usage: TaskUsage,
2271 #[serde(default, skip_serializing_if = "Option::is_none")]
2273 pub subagent_type: Option<String>,
2274 #[serde(default, skip_serializing_if = "Option::is_none")]
2275 pub summary: Option<String>,
2276 pub uuid: String,
2277}
2278
2279#[derive(Debug, Clone, Serialize, Deserialize)]
2282pub struct TaskNotificationMessage {
2283 pub session_id: String,
2284 pub task_id: String,
2285 pub status: TaskStatus,
2286 #[serde(default, skip_serializing_if = "Option::is_none")]
2289 pub reason: Option<TaskEndReason>,
2290 pub summary: String,
2291 pub output_file: Option<String>,
2292 #[serde(skip_serializing_if = "Option::is_none")]
2293 pub tool_use_id: Option<String>,
2294 #[serde(skip_serializing_if = "Option::is_none")]
2295 pub usage: Option<TaskUsage>,
2296 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2302 pub resource_links: Vec<ResourceLink>,
2303 #[serde(default, skip_serializing_if = "Option::is_none")]
2304 pub skip_transcript: Option<bool>,
2305 #[serde(default, skip_serializing_if = "Option::is_none")]
2308 pub ambient: Option<bool>,
2309 #[serde(skip_serializing_if = "Option::is_none")]
2310 pub uuid: Option<String>,
2311}
2312
2313#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2315pub enum AssistantErrorKind {
2316 AuthenticationFailed,
2317 OauthOrgNotAllowed,
2318 AccountOnHold,
2319 BillingError,
2320 RateLimit,
2321 Overloaded,
2322 InvalidRequest,
2323 ModelNotFound,
2324 ServerError,
2325 UnknownError,
2326 MaxOutputTokens,
2327 Unknown(String),
2328}
2329
2330impl AssistantErrorKind {
2331 pub fn as_str(&self) -> &str {
2332 match self {
2333 Self::AuthenticationFailed => "authentication_failed",
2334 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
2335 Self::AccountOnHold => "account_on_hold",
2336 Self::BillingError => "billing_error",
2337 Self::RateLimit => "rate_limit",
2338 Self::Overloaded => "overloaded",
2339 Self::InvalidRequest => "invalid_request",
2340 Self::ModelNotFound => "model_not_found",
2341 Self::ServerError => "server_error",
2342 Self::UnknownError => "unknown",
2343 Self::MaxOutputTokens => "max_output_tokens",
2344 Self::Unknown(s) => s.as_str(),
2345 }
2346 }
2347}
2348
2349impl fmt::Display for AssistantErrorKind {
2350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2351 f.write_str(self.as_str())
2352 }
2353}
2354
2355impl From<&str> for AssistantErrorKind {
2356 fn from(s: &str) -> Self {
2357 match s {
2358 "authentication_failed" => Self::AuthenticationFailed,
2359 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
2360 "account_on_hold" => Self::AccountOnHold,
2361 "billing_error" => Self::BillingError,
2362 "rate_limit" => Self::RateLimit,
2363 "overloaded" => Self::Overloaded,
2364 "invalid_request" => Self::InvalidRequest,
2365 "model_not_found" => Self::ModelNotFound,
2366 "server_error" => Self::ServerError,
2367 "unknown" => Self::UnknownError,
2368 "max_output_tokens" => Self::MaxOutputTokens,
2369 other => Self::Unknown(other.to_string()),
2370 }
2371 }
2372}
2373
2374impl Serialize for AssistantErrorKind {
2375 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2376 serializer.serialize_str(self.as_str())
2377 }
2378}
2379
2380impl<'de> Deserialize<'de> for AssistantErrorKind {
2381 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2382 let s = String::deserialize(deserializer)?;
2383 Ok(Self::from(s.as_str()))
2384 }
2385}
2386
2387#[derive(Debug, Clone, Serialize, Deserialize)]
2394pub struct CodeChangePublishedMessage {
2395 pub provider: String,
2399 pub url: String,
2401 pub repo: String,
2404 pub identifier: String,
2406 #[serde(default, skip_serializing_if = "Option::is_none")]
2415 pub action: Option<String>,
2416 #[serde(default, skip_serializing_if = "Option::is_none")]
2424 pub branch: Option<String>,
2425 pub uuid: String,
2426 pub session_id: String,
2427}
2428
2429#[derive(Debug, Clone, Serialize, Deserialize)]
2434pub struct VcsStateChangedMessage {
2435 pub kind: VcsMutationKind,
2438 pub cwd: String,
2441 #[serde(default, skip_serializing_if = "Option::is_none")]
2446 pub branch: Option<String>,
2447 pub uuid: String,
2448 pub session_id: String,
2449}
2450
2451#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2453pub enum VcsMutationKind {
2454 Commit,
2455 Push,
2456 Merge,
2457 Rebase,
2458 Unknown(String),
2460}
2461
2462impl VcsMutationKind {
2463 pub fn as_str(&self) -> &str {
2464 match self {
2465 Self::Commit => "commit",
2466 Self::Push => "push",
2467 Self::Merge => "merge",
2468 Self::Rebase => "rebase",
2469 Self::Unknown(s) => s.as_str(),
2470 }
2471 }
2472}
2473
2474impl fmt::Display for VcsMutationKind {
2475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2476 f.write_str(self.as_str())
2477 }
2478}
2479
2480impl From<&str> for VcsMutationKind {
2481 fn from(s: &str) -> Self {
2482 match s {
2483 "commit" => Self::Commit,
2484 "push" => Self::Push,
2485 "merge" => Self::Merge,
2486 "rebase" => Self::Rebase,
2487 other => Self::Unknown(other.to_string()),
2488 }
2489 }
2490}
2491
2492impl Serialize for VcsMutationKind {
2493 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2494 serializer.serialize_str(self.as_str())
2495 }
2496}
2497
2498impl<'de> Deserialize<'de> for VcsMutationKind {
2499 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2500 let s = String::deserialize(deserializer)?;
2501 Ok(Self::from(s.as_str()))
2502 }
2503}
2504
2505#[derive(Debug, Clone, Serialize, Deserialize)]
2507pub struct FeedbackDraftQueuedMessage {
2508 pub draft_id: String,
2509 pub draft_type: String,
2510 pub title: String,
2511 pub details_preview: String,
2512 #[serde(default, skip_serializing_if = "Option::is_none")]
2513 pub uuid: Option<String>,
2514 #[serde(default, skip_serializing_if = "Option::is_none")]
2515 pub session_id: Option<String>,
2516 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2517 pub extra: serde_json::Map<String, Value>,
2518}
2519
2520#[derive(Debug, Clone, Serialize, Deserialize)]
2528pub struct CloudSessionDeltaMessage {
2529 pub seq: u64,
2533 pub changed: Vec<String>,
2537 pub cloud_session: Value,
2541 pub uuid: String,
2542 pub session_id: String,
2543 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2544 pub extra: serde_json::Map<String, Value>,
2545}
2546
2547#[derive(Debug, Clone, Serialize, Deserialize)]
2563pub struct DevIntentMessage {
2564 pub kind: DevIntentKind,
2566 #[serde(default, skip_serializing_if = "Option::is_none")]
2572 pub trigger: Option<DevIntentTrigger>,
2573 pub uuid: String,
2574 pub session_id: String,
2575 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2576 pub extra: serde_json::Map<String, Value>,
2577}
2578
2579#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2583pub enum DevIntentKind {
2584 IosApp,
2590 AndroidApp,
2597 Unknown(String),
2599}
2600
2601impl DevIntentKind {
2602 pub fn as_str(&self) -> &str {
2603 match self {
2604 Self::IosApp => "ios_app",
2605 Self::AndroidApp => "android_app",
2606 Self::Unknown(s) => s.as_str(),
2607 }
2608 }
2609}
2610
2611impl fmt::Display for DevIntentKind {
2612 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2613 f.write_str(self.as_str())
2614 }
2615}
2616
2617impl From<&str> for DevIntentKind {
2618 fn from(s: &str) -> Self {
2619 match s {
2620 "ios_app" => Self::IosApp,
2621 "android_app" => Self::AndroidApp,
2622 other => Self::Unknown(other.to_string()),
2623 }
2624 }
2625}
2626
2627impl Serialize for DevIntentKind {
2628 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2629 serializer.serialize_str(self.as_str())
2630 }
2631}
2632
2633impl<'de> Deserialize<'de> for DevIntentKind {
2634 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2635 let s = String::deserialize(deserializer)?;
2636 Ok(Self::from(s.as_str()))
2637 }
2638}
2639
2640#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2644pub enum DevIntentTrigger {
2645 SwiftEdit,
2647 UikitImport,
2649 XcodeProject,
2652 IosCommand,
2655 KotlinEdit,
2657 JavaEdit,
2659 AndroidImport,
2661 AndroidManifest,
2663 GradlePlugin,
2665 AndroidCommand,
2668 ProjectScan,
2671 Unknown(String),
2673}
2674
2675impl DevIntentTrigger {
2676 pub fn as_str(&self) -> &str {
2677 match self {
2678 Self::SwiftEdit => "swift_edit",
2679 Self::UikitImport => "uikit_import",
2680 Self::XcodeProject => "xcode_project",
2681 Self::IosCommand => "ios_command",
2682 Self::KotlinEdit => "kotlin_edit",
2683 Self::JavaEdit => "java_edit",
2684 Self::AndroidImport => "android_import",
2685 Self::AndroidManifest => "android_manifest",
2686 Self::GradlePlugin => "gradle_plugin",
2687 Self::AndroidCommand => "android_command",
2688 Self::ProjectScan => "project_scan",
2689 Self::Unknown(s) => s.as_str(),
2690 }
2691 }
2692}
2693
2694impl fmt::Display for DevIntentTrigger {
2695 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2696 f.write_str(self.as_str())
2697 }
2698}
2699
2700impl From<&str> for DevIntentTrigger {
2701 fn from(s: &str) -> Self {
2702 match s {
2703 "swift_edit" => Self::SwiftEdit,
2704 "uikit_import" => Self::UikitImport,
2705 "xcode_project" => Self::XcodeProject,
2706 "ios_command" => Self::IosCommand,
2707 "kotlin_edit" => Self::KotlinEdit,
2708 "java_edit" => Self::JavaEdit,
2709 "android_import" => Self::AndroidImport,
2710 "android_manifest" => Self::AndroidManifest,
2711 "gradle_plugin" => Self::GradlePlugin,
2712 "android_command" => Self::AndroidCommand,
2713 "project_scan" => Self::ProjectScan,
2714 other => Self::Unknown(other.to_string()),
2715 }
2716 }
2717}
2718
2719impl Serialize for DevIntentTrigger {
2720 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2721 serializer.serialize_str(self.as_str())
2722 }
2723}
2724
2725impl<'de> Deserialize<'de> for DevIntentTrigger {
2726 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2727 let s = String::deserialize(deserializer)?;
2728 Ok(Self::from(s.as_str()))
2729 }
2730}
2731
2732#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2736pub enum TaskEndReason {
2737 WorkerRestart,
2740 Unknown(String),
2742}
2743
2744impl TaskEndReason {
2745 pub fn as_str(&self) -> &str {
2746 match self {
2747 Self::WorkerRestart => "worker_restart",
2748 Self::Unknown(s) => s.as_str(),
2749 }
2750 }
2751}
2752
2753impl fmt::Display for TaskEndReason {
2754 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2755 f.write_str(self.as_str())
2756 }
2757}
2758
2759impl From<&str> for TaskEndReason {
2760 fn from(s: &str) -> Self {
2761 match s {
2762 "worker_restart" => Self::WorkerRestart,
2763 other => Self::Unknown(other.to_string()),
2764 }
2765 }
2766}
2767
2768impl Serialize for TaskEndReason {
2769 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2770 serializer.serialize_str(self.as_str())
2771 }
2772}
2773
2774impl<'de> Deserialize<'de> for TaskEndReason {
2775 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2776 let s = String::deserialize(deserializer)?;
2777 Ok(Self::from(s.as_str()))
2778 }
2779}
2780
2781#[derive(Debug, Clone, Serialize, Deserialize)]
2790pub struct TurnHandoffAvailableMessage {
2791 pub v: u64,
2793 pub tools: Vec<String>,
2795 pub worker_epoch: u64,
2797 #[serde(default, skip_serializing_if = "Option::is_none")]
2801 pub relay_marker: Option<bool>,
2802 pub uuid: String,
2803 pub session_id: String,
2804 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2805 pub extra: serde_json::Map<String, Value>,
2806}
2807
2808#[derive(Debug, Clone, Serialize, Deserialize)]
2820pub struct TurnPreemptedMessage {
2821 pub reason: String,
2825 pub preempted_by_uuid: String,
2829 #[serde(default)]
2832 pub preempted_message_uuids: Vec<String>,
2833 pub uuid: String,
2834 pub session_id: String,
2835 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2836 pub extra: serde_json::Map<String, Value>,
2837}
2838
2839#[derive(Debug, Clone, Serialize, Deserialize)]
2847pub struct PeerMessageHoldMessage {
2848 pub state: PeerMessageHoldState,
2854 #[serde(default, skip_serializing_if = "Option::is_none")]
2861 pub message_uuid: Option<String>,
2862 pub lane: PeerMessageLane,
2864 pub from: String,
2869 #[serde(default, skip_serializing_if = "Option::is_none")]
2872 pub from_name: Option<String>,
2873 #[serde(default, skip_serializing_if = "Option::is_none")]
2875 pub cause: Option<PeerMessageHoldCause>,
2876 #[serde(default, skip_serializing_if = "Option::is_none")]
2878 pub outcome: Option<PeerMessageHoldOutcome>,
2879 pub uuid: String,
2880 pub session_id: String,
2881 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2882 pub extra: serde_json::Map<String, Value>,
2883}
2884
2885#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2887pub enum PeerMessageHoldState {
2888 Held,
2890 Released,
2892 Dropped,
2894 Unknown(String),
2896}
2897
2898impl PeerMessageHoldState {
2899 pub fn as_str(&self) -> &str {
2900 match self {
2901 Self::Held => "held",
2902 Self::Released => "released",
2903 Self::Dropped => "dropped",
2904 Self::Unknown(s) => s.as_str(),
2905 }
2906 }
2907}
2908
2909impl fmt::Display for PeerMessageHoldState {
2910 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2911 f.write_str(self.as_str())
2912 }
2913}
2914
2915impl From<&str> for PeerMessageHoldState {
2916 fn from(s: &str) -> Self {
2917 match s {
2918 "held" => Self::Held,
2919 "released" => Self::Released,
2920 "dropped" => Self::Dropped,
2921 other => Self::Unknown(other.to_string()),
2922 }
2923 }
2924}
2925
2926impl Serialize for PeerMessageHoldState {
2927 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2928 serializer.serialize_str(self.as_str())
2929 }
2930}
2931
2932impl<'de> Deserialize<'de> for PeerMessageHoldState {
2933 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2934 let s = String::deserialize(deserializer)?;
2935 Ok(Self::from(s.as_str()))
2936 }
2937}
2938
2939#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2941pub enum PeerMessageLane {
2942 Bridge,
2944 Stdin,
2946 Socket,
2948 Unknown(String),
2950}
2951
2952impl PeerMessageLane {
2953 pub fn as_str(&self) -> &str {
2954 match self {
2955 Self::Bridge => "bridge",
2956 Self::Stdin => "stdin",
2957 Self::Socket => "socket",
2958 Self::Unknown(s) => s.as_str(),
2959 }
2960 }
2961}
2962
2963impl fmt::Display for PeerMessageLane {
2964 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2965 f.write_str(self.as_str())
2966 }
2967}
2968
2969impl From<&str> for PeerMessageLane {
2970 fn from(s: &str) -> Self {
2971 match s {
2972 "bridge" => Self::Bridge,
2973 "stdin" => Self::Stdin,
2974 "socket" => Self::Socket,
2975 other => Self::Unknown(other.to_string()),
2976 }
2977 }
2978}
2979
2980impl Serialize for PeerMessageLane {
2981 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2982 serializer.serialize_str(self.as_str())
2983 }
2984}
2985
2986impl<'de> Deserialize<'de> for PeerMessageLane {
2987 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2988 let s = String::deserialize(deserializer)?;
2989 Ok(Self::from(s.as_str()))
2990 }
2991}
2992
2993#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3000pub enum PeerMessageHoldCause {
3001 ExplicitSetting,
3002 ManagedSetting,
3003 RepoSetting,
3004 InvalidSetting,
3005 BypassDefault,
3006 ModeUnknown,
3007 ModeMismatch,
3008 NoModeAsserted,
3009 Unknown(String),
3011}
3012
3013impl PeerMessageHoldCause {
3014 pub fn as_str(&self) -> &str {
3015 match self {
3016 Self::ExplicitSetting => "explicit-setting",
3017 Self::ManagedSetting => "managed-setting",
3018 Self::RepoSetting => "repo-setting",
3019 Self::InvalidSetting => "invalid-setting",
3020 Self::BypassDefault => "bypass-default",
3021 Self::ModeUnknown => "mode-unknown",
3022 Self::ModeMismatch => "mode-mismatch",
3023 Self::NoModeAsserted => "no-mode-asserted",
3024 Self::Unknown(s) => s.as_str(),
3025 }
3026 }
3027}
3028
3029impl fmt::Display for PeerMessageHoldCause {
3030 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3031 f.write_str(self.as_str())
3032 }
3033}
3034
3035impl From<&str> for PeerMessageHoldCause {
3036 fn from(s: &str) -> Self {
3037 match s {
3038 "explicit-setting" => Self::ExplicitSetting,
3039 "managed-setting" => Self::ManagedSetting,
3040 "repo-setting" => Self::RepoSetting,
3041 "invalid-setting" => Self::InvalidSetting,
3042 "bypass-default" => Self::BypassDefault,
3043 "mode-unknown" => Self::ModeUnknown,
3044 "mode-mismatch" => Self::ModeMismatch,
3045 "no-mode-asserted" => Self::NoModeAsserted,
3046 other => Self::Unknown(other.to_string()),
3047 }
3048 }
3049}
3050
3051impl Serialize for PeerMessageHoldCause {
3052 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3053 serializer.serialize_str(self.as_str())
3054 }
3055}
3056
3057impl<'de> Deserialize<'de> for PeerMessageHoldCause {
3058 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3059 let s = String::deserialize(deserializer)?;
3060 Ok(Self::from(s.as_str()))
3061 }
3062}
3063
3064#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3067pub enum PeerMessageHoldOutcome {
3068 Expired,
3072 Refused,
3074 Dropped,
3077 Discarded,
3079 Unknown(String),
3081}
3082
3083impl PeerMessageHoldOutcome {
3084 pub fn as_str(&self) -> &str {
3085 match self {
3086 Self::Expired => "expired",
3087 Self::Refused => "refused",
3088 Self::Dropped => "dropped",
3089 Self::Discarded => "discarded",
3090 Self::Unknown(s) => s.as_str(),
3091 }
3092 }
3093}
3094
3095impl fmt::Display for PeerMessageHoldOutcome {
3096 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3097 f.write_str(self.as_str())
3098 }
3099}
3100
3101impl From<&str> for PeerMessageHoldOutcome {
3102 fn from(s: &str) -> Self {
3103 match s {
3104 "expired" => Self::Expired,
3105 "refused" => Self::Refused,
3106 "dropped" => Self::Dropped,
3107 "discarded" => Self::Discarded,
3108 other => Self::Unknown(other.to_string()),
3109 }
3110 }
3111}
3112
3113impl Serialize for PeerMessageHoldOutcome {
3114 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3115 serializer.serialize_str(self.as_str())
3116 }
3117}
3118
3119impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
3120 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3121 let s = String::deserialize(deserializer)?;
3122 Ok(Self::from(s.as_str()))
3123 }
3124}
3125
3126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3129pub struct LocalCommandRun {
3130 pub command: String,
3132 pub args: String,
3135}
3136
3137#[derive(Debug, Clone, Serialize, Deserialize)]
3143pub struct UsageReport {
3144 pub session: super::control::UsageSession,
3146 pub rate_limits: Option<UsageReportRateLimits>,
3150 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3151 pub extra: serde_json::Map<String, Value>,
3152}
3153
3154#[derive(Debug, Clone, Serialize, Deserialize)]
3156pub struct UsageReportRateLimits {
3157 pub limits: Option<Vec<UsageReportLimit>>,
3165 #[serde(default, skip_serializing_if = "Option::is_none")]
3168 pub extra_usage: Option<UsageReportExtraUsage>,
3169 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3170 pub extra: serde_json::Map<String, Value>,
3171}
3172
3173#[derive(Debug, Clone, Serialize, Deserialize)]
3175pub struct UsageReportLimit {
3176 pub kind: String,
3179 pub group: String,
3182 pub percent: f64,
3184 pub resets_at: Option<String>,
3186 #[serde(default, skip_serializing_if = "Option::is_none")]
3189 pub scope: Option<UsageReportScope>,
3190 #[serde(default, skip_serializing_if = "Option::is_none")]
3194 pub severity: Option<String>,
3195 #[serde(default, skip_serializing_if = "Option::is_none")]
3197 pub is_active: Option<bool>,
3198 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3199 pub extra: serde_json::Map<String, Value>,
3200}
3201
3202#[derive(Debug, Clone, Serialize, Deserialize)]
3204pub struct UsageReportScope {
3205 #[serde(default, skip_serializing_if = "Option::is_none")]
3206 pub model: Option<UsageReportScopeLabel>,
3207 #[serde(default, skip_serializing_if = "Option::is_none")]
3208 pub surface: Option<UsageReportScopeLabel>,
3209 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3210 pub extra: serde_json::Map<String, Value>,
3211}
3212
3213#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3215pub struct UsageReportScopeLabel {
3216 pub display_name: String,
3217}
3218
3219#[derive(Debug, Clone, Serialize, Deserialize)]
3223pub struct UsageReportExtraUsage {
3224 pub is_enabled: bool,
3226 pub monthly_limit: Option<f64>,
3227 pub used_credits: Option<f64>,
3228 pub utilization: Option<f64>,
3229 #[serde(default, skip_serializing_if = "Option::is_none")]
3230 pub currency: Option<String>,
3231 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3232 pub extra: serde_json::Map<String, Value>,
3233}
3234
3235#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3238pub struct BatchToolUse {
3239 pub id: String,
3240 pub name: String,
3241}
3242
3243#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3248pub struct ContextUsage {
3249 pub model: String,
3251 pub total_tokens: u64,
3254 pub raw_max_tokens: u64,
3257 pub percentage: u64,
3259 #[serde(default, skip_serializing_if = "Option::is_none")]
3261 pub over_limit: Option<ContextOverLimit>,
3262 #[serde(default)]
3264 pub categories: Vec<ContextCategory>,
3265 #[serde(default)]
3267 pub mcp_tools: Vec<ContextMcpTool>,
3268 #[serde(default)]
3270 pub memory_files: Vec<ContextMemoryFile>,
3271 #[serde(default)]
3273 pub agents: Vec<ContextAgent>,
3274 #[serde(default, skip_serializing_if = "Option::is_none")]
3276 pub skills: Option<Vec<ContextSkill>>,
3277}
3278
3279#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3281pub struct ContextOverLimit {
3282 pub tokens_over: u64,
3283 pub kind: String,
3286}
3287
3288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3290pub struct ContextCategory {
3291 pub name: String,
3294 pub tokens: u64,
3295 pub kind: String,
3299}
3300
3301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3303pub struct ContextMcpTool {
3304 pub name: String,
3306 pub server_name: String,
3307 pub tokens: u64,
3308}
3309
3310#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3312pub struct ContextMemoryFile {
3313 pub path: String,
3314 #[serde(rename = "type")]
3316 pub file_type: String,
3317 pub tokens: u64,
3318}
3319
3320#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3322pub struct ContextAgent {
3323 pub agent_type: String,
3324 pub source: String,
3326 pub tokens: u64,
3327}
3328
3329#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3331pub struct ContextSkill {
3332 pub name: String,
3333 pub source: String,
3335 #[serde(default, skip_serializing_if = "Option::is_none")]
3336 pub plugin_name: Option<String>,
3337 pub tokens: u64,
3338}
3339
3340#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3342pub struct ToolUseMeta {
3343 pub id: String,
3344 pub display_name: String,
3345 #[serde(default, skip_serializing_if = "Option::is_none")]
3346 pub server_display_name: Option<String>,
3347 #[serde(default, skip_serializing_if = "Option::is_none")]
3348 pub icon_url: Option<String>,
3349}
3350
3351#[derive(Debug, Clone, Serialize, Deserialize)]
3353pub struct AssistantMessage {
3354 pub message: AssistantMessageContent,
3355 #[serde(alias = "sessionId")]
3356 pub session_id: String,
3357 #[serde(skip_serializing_if = "Option::is_none")]
3358 pub uuid: Option<String>,
3359 #[serde(skip_serializing_if = "Option::is_none")]
3360 pub parent_tool_use_id: Option<String>,
3361 #[serde(skip_serializing_if = "Option::is_none")]
3363 pub request_id: Option<String>,
3364 #[serde(default, skip_serializing_if = "Option::is_none")]
3373 pub user_message_uuid: Option<String>,
3374 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3380 pub user_message_uuids: Vec<String>,
3381 #[serde(skip_serializing_if = "Option::is_none")]
3384 pub subagent_type: Option<String>,
3385 #[serde(skip_serializing_if = "Option::is_none")]
3387 pub task_description: Option<String>,
3388 #[serde(skip_serializing_if = "Option::is_none")]
3389 pub error: Option<AssistantErrorKind>,
3390 #[serde(default, skip_serializing_if = "Option::is_none")]
3394 pub aborted: Option<bool>,
3395 #[serde(default, skip_serializing_if = "Option::is_none")]
3400 pub resumed_from_incomplete_thinking: Option<bool>,
3401 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3402 pub supersedes: Vec<String>,
3403 #[serde(skip_serializing_if = "Option::is_none")]
3404 pub timestamp: Option<String>,
3405 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3406 pub tool_use_meta: Vec<ToolUseMeta>,
3407 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3412 pub batch_tool_uses: Vec<BatchToolUse>,
3413 #[serde(default, skip_serializing_if = "Option::is_none")]
3419 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
3420 #[serde(default, skip_serializing_if = "Option::is_none")]
3426 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
3427 #[serde(default, skip_serializing_if = "Option::is_none")]
3431 pub historical: Option<bool>,
3432 #[serde(default, skip_serializing_if = "Option::is_none")]
3440 pub resume_reason: Option<String>,
3441 #[serde(default, skip_serializing_if = "Option::is_none")]
3447 pub local_command_source: Option<String>,
3448 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3457 pub narration_block_indexes: Vec<usize>,
3458 #[serde(default, skip_serializing_if = "Option::is_none")]
3462 pub context_usage: Option<ContextUsage>,
3463 #[serde(default, skip_serializing_if = "Option::is_none")]
3471 pub usage_report: Option<Box<UsageReport>>,
3472 #[serde(default, skip_serializing_if = "Option::is_none")]
3479 pub local_command_run: Option<LocalCommandRun>,
3480 #[serde(default, skip_serializing_if = "Option::is_none")]
3481 pub is_meta: Option<bool>,
3482 #[serde(default, skip_serializing_if = "Option::is_none")]
3483 pub is_virtual: Option<bool>,
3484 #[serde(default, skip_serializing_if = "Option::is_none")]
3485 pub is_api_error_message: Option<bool>,
3486 #[serde(skip_serializing_if = "Option::is_none")]
3487 pub api_error_status: Option<u16>,
3488 #[serde(skip_serializing_if = "Option::is_none")]
3489 pub api_error: Option<String>,
3490 #[serde(skip_serializing_if = "Option::is_none")]
3491 pub error_details: Option<String>,
3492 #[serde(skip_serializing_if = "Option::is_none")]
3493 pub advisor_model: Option<String>,
3494 #[serde(skip_serializing_if = "Option::is_none")]
3495 pub attribution_agent: Option<String>,
3496 #[serde(skip_serializing_if = "Option::is_none")]
3497 pub attribution_skill: Option<String>,
3498 #[serde(skip_serializing_if = "Option::is_none")]
3499 pub attribution_plugin: Option<String>,
3500 #[serde(skip_serializing_if = "Option::is_none")]
3501 pub attribution_mcp_server: Option<String>,
3502 #[serde(skip_serializing_if = "Option::is_none")]
3503 pub attribution_mcp_tool: Option<String>,
3504}
3505
3506#[derive(Debug, Clone, Serialize, Deserialize)]
3508pub struct AssistantMessageContent {
3509 pub id: String,
3510 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
3512 pub message_type: Option<String>,
3513 pub role: MessageRole,
3514 pub model: String,
3515 pub content: Vec<ContentBlock>,
3516 #[serde(skip_serializing_if = "Option::is_none")]
3517 pub stop_reason: Option<StopReason>,
3518 #[serde(skip_serializing_if = "Option::is_none")]
3519 pub stop_sequence: Option<String>,
3520 #[serde(skip_serializing_if = "Option::is_none")]
3521 pub usage: Option<AssistantUsage>,
3522 #[serde(skip_serializing_if = "Option::is_none")]
3524 pub stop_details: Option<Value>,
3525 #[serde(skip_serializing_if = "Option::is_none")]
3527 pub context_management: Option<Value>,
3528}
3529
3530#[derive(Debug, Clone, Serialize, Deserialize)]
3532pub struct AssistantUsage {
3533 #[serde(default)]
3535 pub input_tokens: u32,
3536
3537 #[serde(default)]
3539 pub output_tokens: u32,
3540
3541 #[serde(default)]
3543 pub cache_creation_input_tokens: u32,
3544
3545 #[serde(default)]
3547 pub cache_read_input_tokens: u32,
3548
3549 #[serde(skip_serializing_if = "Option::is_none")]
3551 pub service_tier: Option<String>,
3552
3553 #[serde(skip_serializing_if = "Option::is_none")]
3555 pub cache_creation: Option<CacheCreationDetails>,
3556
3557 #[serde(skip_serializing_if = "Option::is_none")]
3559 pub inference_geo: Option<String>,
3560}
3561
3562#[derive(Debug, Clone, Serialize, Deserialize)]
3564pub struct CacheCreationDetails {
3565 #[serde(default)]
3567 pub ephemeral_1h_input_tokens: u32,
3568
3569 #[serde(default)]
3571 pub ephemeral_5m_input_tokens: u32,
3572}
3573
3574#[cfg(test)]
3575mod tests {
3576 use crate::io::ClaudeOutput;
3577
3578 #[test]
3579 fn test_subagent_usage_rollup_accumulates_task_results() {
3580 use super::SubagentUsageRollup;
3581
3582 let mut rollup = SubagentUsageRollup::default();
3583
3584 let task_result = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1","tool_use_result":{"status":"completed","prompt":"Compute 6 times 7.","agentId":"ab52f22445470d454","agentType":"general-purpose","resolvedModel":"claude-sonnet-4-6","totalDurationMs":1853,"totalTokens":10201,"totalToolUseCount":3}}"#;
3585 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
3586 assert!(rollup.observe(&output));
3587 assert_eq!(rollup.subagent_tokens, 10201);
3588 assert_eq!(rollup.agent_count, 1);
3589 assert_eq!(rollup.tool_uses, 3);
3590 assert_eq!(rollup.duration_ms, 1853);
3591
3592 assert!(!rollup.observe(&output));
3594 assert_eq!(rollup.agent_count, 1);
3595 assert_eq!(rollup.subagent_tokens, 10201);
3596
3597 let second = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1","tool_use_result":{"status":"completed","agentId":"ffff00001111","totalDurationMs":100,"totalTokens":500,"totalToolUseCount":1}}"#;
3599 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
3600 assert!(rollup.observe(&output));
3601 assert_eq!(rollup.agent_count, 2);
3602 assert_eq!(rollup.subagent_tokens, 10701);
3603 }
3604
3605 #[test]
3606 fn test_subagent_usage_rollup_ignores_non_task_results() {
3607 use super::SubagentUsageRollup;
3608
3609 let mut rollup = SubagentUsageRollup::default();
3610
3611 let tool_search = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1","tool_use_result":{"matches":["TaskCreate"],"query":"select:TaskCreate","total_deferred_tools":27}}"#;
3614 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
3615 assert!(!rollup.observe(&output));
3616
3617 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
3619 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
3620 assert!(!rollup.observe(&output));
3621
3622 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
3624 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
3625 assert!(!rollup.observe(&output));
3626
3627 assert_eq!(rollup, SubagentUsageRollup::default());
3628 }
3629
3630 #[test]
3631 fn test_subagent_usage_rollup_over_captured_session() {
3632 use super::SubagentUsageRollup;
3633
3634 let mut rollup = SubagentUsageRollup::default();
3635 let fixture =
3636 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
3637 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
3638 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
3639 rollup.observe(&output);
3640 }
3641 }
3642 assert_eq!(rollup.agent_count, 1);
3643 assert_eq!(rollup.subagent_tokens, 10201);
3644 }
3645
3646 #[test]
3647 fn test_system_message_init() {
3648 let json = r#"{
3649 "type": "system",
3650 "subtype": "init",
3651 "session_id": "test-session-123",
3652 "cwd": "/home/user/project",
3653 "model": "claude-sonnet-4",
3654 "tools": ["Bash", "Read", "Write"],
3655 "mcp_servers": [],
3656 "slash_commands": ["compact", "cost", "review"],
3657 "agents": ["Bash", "Explore", "Plan"],
3658 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
3659 "skills": [],
3660 "claude_code_version": "2.1.15",
3661 "apiKeySource": "none",
3662 "output_style": "default",
3663 "permissionMode": "default"
3664 }"#;
3665
3666 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3667 if let ClaudeOutput::System(sys) = output {
3668 assert!(sys.is_init());
3669 assert!(!sys.is_status());
3670 assert!(!sys.is_compact_boundary());
3671
3672 let init = sys.as_init().expect("Should parse as init");
3673 assert_eq!(init.session_id, "test-session-123");
3674 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
3675 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
3676 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
3677 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
3678 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
3679 assert_eq!(init.plugins.len(), 1);
3680 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
3681 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
3682 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
3683 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
3684 assert_eq!(
3685 init.permission_mode,
3686 Some(super::InitPermissionMode::Default)
3687 );
3688 } else {
3689 panic!("Expected System message");
3690 }
3691 }
3692
3693 #[test]
3694 fn test_system_message_init_from_real_capture() {
3695 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
3696 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3697 if let ClaudeOutput::System(sys) = output {
3698 let init = sys.as_init().expect("Should parse real init capture");
3699 assert_eq!(init.slash_commands.len(), 8);
3700 assert!(init.slash_commands.contains(&"compact".to_string()));
3701 assert!(init.slash_commands.contains(&"review".to_string()));
3702 assert_eq!(init.agents.len(), 5);
3703 assert!(init.agents.contains(&"Bash".to_string()));
3704 assert!(init.agents.contains(&"Explore".to_string()));
3705 assert_eq!(init.plugins.len(), 1);
3706 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
3707 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
3708 } else {
3709 panic!("Expected System message");
3710 }
3711 }
3712
3713 #[test]
3714 fn test_system_message_status() {
3715 let json = r#"{
3716 "type": "system",
3717 "subtype": "status",
3718 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
3719 "status": "compacting",
3720 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
3721 }"#;
3722
3723 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3724 if let ClaudeOutput::System(sys) = output {
3725 assert!(sys.is_status());
3726 assert!(!sys.is_init());
3727
3728 let status = sys.as_status().expect("Should parse as status");
3729 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
3730 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
3731 assert_eq!(
3732 status.uuid,
3733 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
3734 );
3735 } else {
3736 panic!("Expected System message");
3737 }
3738 }
3739
3740 #[test]
3741 fn test_system_message_status_null() {
3742 let json = r#"{
3743 "type": "system",
3744 "subtype": "status",
3745 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
3746 "status": null,
3747 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
3748 }"#;
3749
3750 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3751 if let ClaudeOutput::System(sys) = output {
3752 let status = sys.as_status().expect("Should parse as status");
3753 assert_eq!(status.status, None);
3754 } else {
3755 panic!("Expected System message");
3756 }
3757 }
3758
3759 #[test]
3760 fn test_system_message_task_started() {
3761 let json = r#"{
3762 "type": "system",
3763 "subtype": "task_started",
3764 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
3765 "task_id": "b6daf3f",
3766 "task_type": "local_bash",
3767 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
3768 "description": "Wait for CI on PR #12",
3769 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
3770 }"#;
3771
3772 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3773 if let ClaudeOutput::System(sys) = output {
3774 assert!(sys.is_task_started());
3775 assert!(!sys.is_task_progress());
3776 assert!(!sys.is_task_notification());
3777
3778 let task = sys.as_task_started().expect("Should parse as task_started");
3779 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
3780 assert_eq!(task.task_id, "b6daf3f");
3781 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
3782 assert_eq!(
3783 task.tool_use_id.as_deref(),
3784 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
3785 );
3786 assert_eq!(task.description, "Wait for CI on PR #12");
3787 } else {
3788 panic!("Expected System message");
3789 }
3790 }
3791
3792 #[test]
3793 fn test_system_message_task_started_agent() {
3794 let json = r#"{
3795 "type": "system",
3796 "subtype": "task_started",
3797 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
3798 "task_id": "a4a7e0906e5fc64cc",
3799 "task_type": "local_agent",
3800 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
3801 "description": "Explore Scene/ArrayScene duplication",
3802 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
3803 }"#;
3804
3805 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3806 if let ClaudeOutput::System(sys) = output {
3807 let task = sys.as_task_started().expect("Should parse as task_started");
3808 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
3809 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
3810 } else {
3811 panic!("Expected System message");
3812 }
3813 }
3814
3815 #[test]
3816 fn test_system_message_task_progress() {
3817 let json = r#"{
3818 "type": "system",
3819 "subtype": "task_progress",
3820 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
3821 "task_id": "a4a7e0906e5fc64cc",
3822 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
3823 "description": "Reading src/jplephem/chebyshev.rs",
3824 "last_tool_name": "Read",
3825 "usage": {
3826 "duration_ms": 13996,
3827 "tool_uses": 9,
3828 "total_tokens": 38779
3829 },
3830 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
3831 }"#;
3832
3833 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3834 if let ClaudeOutput::System(sys) = output {
3835 assert!(sys.is_task_progress());
3836 assert!(!sys.is_task_started());
3837
3838 let progress = sys
3839 .as_task_progress()
3840 .expect("Should parse as task_progress");
3841 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
3842 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
3843 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
3844 assert_eq!(progress.usage.duration_ms, 13996);
3845 assert_eq!(progress.usage.tool_uses, 9);
3846 assert_eq!(progress.usage.total_tokens, 38779);
3847 } else {
3848 panic!("Expected System message");
3849 }
3850 }
3851
3852 #[test]
3853 fn test_system_message_task_notification_completed() {
3854 let json = r#"{
3855 "type": "system",
3856 "subtype": "task_notification",
3857 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
3858 "task_id": "a0ba761e9dc9c316f",
3859 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
3860 "status": "completed",
3861 "summary": "Agent \"Write Hipparcos data source doc\" completed",
3862 "output_file": "",
3863 "usage": {
3864 "duration_ms": 172300,
3865 "tool_uses": 11,
3866 "total_tokens": 42005
3867 },
3868 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
3869 }"#;
3870
3871 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3872 if let ClaudeOutput::System(sys) = output {
3873 assert!(sys.is_task_notification());
3874
3875 let notif = sys
3876 .as_task_notification()
3877 .expect("Should parse as task_notification");
3878 assert_eq!(notif.status, super::TaskStatus::Completed);
3879 assert_eq!(
3880 notif.summary,
3881 "Agent \"Write Hipparcos data source doc\" completed"
3882 );
3883 assert_eq!(notif.output_file, Some("".to_string()));
3884 assert_eq!(
3885 notif.tool_use_id,
3886 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
3887 );
3888 let usage = notif.usage.expect("Should have usage");
3889 assert_eq!(usage.duration_ms, 172300);
3890 assert_eq!(usage.tool_uses, 11);
3891 assert_eq!(usage.total_tokens, 42005);
3892 } else {
3893 panic!("Expected System message");
3894 }
3895 }
3896
3897 #[test]
3898 fn test_system_message_task_notification_failed_no_usage() {
3899 let json = r#"{
3900 "type": "system",
3901 "subtype": "task_notification",
3902 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
3903 "task_id": "b98f6a3",
3904 "status": "failed",
3905 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
3906 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
3907 }"#;
3908
3909 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3910 if let ClaudeOutput::System(sys) = output {
3911 let notif = sys
3912 .as_task_notification()
3913 .expect("Should parse as task_notification");
3914 assert_eq!(notif.status, super::TaskStatus::Failed);
3915 assert!(notif.tool_use_id.is_none());
3916 assert!(notif.usage.is_none());
3917 assert_eq!(
3918 notif.output_file,
3919 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
3920 );
3921 } else {
3922 panic!("Expected System message");
3923 }
3924 }
3925
3926 #[test]
3932 fn test_task_messages_roundtrip_through_value() {
3933 let cases = [
3934 r#"{"type":"system","subtype":"task_started","session_id":"s1",
3935 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
3936 "description":"Sleep 3s","uuid":"u1"}"#,
3937 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
3938 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
3939 "last_tool_name":"Bash",
3940 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
3941 "uuid":"u2"}"#,
3942 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
3943 "task_id":"t1","tool_use_id":"tu1","status":"completed",
3944 "summary":"done","output_file":"",
3945 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
3946 "uuid":"u3"}"#,
3947 ];
3948
3949 for json in cases {
3950 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3951 let value = serde_json::to_value(&output).unwrap();
3952 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
3953
3954 let ClaudeOutput::System(sys) = reparsed else {
3955 panic!("Expected System variant after round-trip");
3956 };
3957
3958 match sys.subtype {
3959 super::SystemSubtype::TaskStarted => {
3960 assert!(
3961 sys.as_task_started().is_some(),
3962 "as_task_started failed after round-trip"
3963 );
3964 }
3965 super::SystemSubtype::TaskProgress => {
3966 assert!(
3967 sys.as_task_progress().is_some(),
3968 "as_task_progress failed after round-trip"
3969 );
3970 }
3971 super::SystemSubtype::TaskNotification => {
3972 assert!(
3973 sys.as_task_notification().is_some(),
3974 "as_task_notification failed after round-trip"
3975 );
3976 }
3977 other => panic!("unexpected subtype after round-trip: {other:?}"),
3978 }
3979 }
3980 }
3981
3982 #[test]
3983 fn test_system_message_compact_boundary() {
3984 let json = r#"{
3985 "type": "system",
3986 "subtype": "compact_boundary",
3987 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
3988 "compact_metadata": {
3989 "pre_tokens": 155285,
3990 "trigger": "auto"
3991 },
3992 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
3993 }"#;
3994
3995 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3996 if let ClaudeOutput::System(sys) = output {
3997 assert!(sys.is_compact_boundary());
3998 assert!(!sys.is_init());
3999 assert!(!sys.is_status());
4000
4001 let compact = sys
4002 .as_compact_boundary()
4003 .expect("Should parse as compact_boundary");
4004 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4005 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
4006 assert_eq!(
4007 compact.compact_metadata.trigger,
4008 super::CompactionTrigger::Auto
4009 );
4010 assert!(compact.summary.is_none());
4012 assert!(compact.leaf_message_count.is_none());
4013 assert!(compact.duration_ms.is_none());
4014 } else {
4015 panic!("Expected System message");
4016 }
4017 }
4018
4019 #[test]
4020 fn test_compact_boundary_with_summary_stats() {
4021 let json = r#"{
4023 "type": "system",
4024 "subtype": "compact_boundary",
4025 "session_id": "s1",
4026 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
4027 "summary": "Summarized the earlier exploration.",
4028 "leaf_message_count": 42,
4029 "duration_ms": 1234,
4030 "uuid": "u1"
4031 }"#;
4032 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4033 let ClaudeOutput::System(sys) = output else {
4034 panic!("Expected System message");
4035 };
4036 let compact = sys.as_compact_boundary().expect("compact_boundary");
4037 assert_eq!(
4038 compact.summary.as_deref(),
4039 Some("Summarized the earlier exploration.")
4040 );
4041 assert_eq!(compact.leaf_message_count, Some(42));
4042 assert_eq!(compact.duration_ms, Some(1234));
4043
4044 let json_alt = r#"{
4047 "type": "system",
4048 "subtype": "compact_boundary",
4049 "session_id": "s2",
4050 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
4051 "content": "alt-key summary",
4052 "message_count": 7
4053 }"#;
4054 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
4055 let ClaudeOutput::System(sys) = output else {
4056 panic!("Expected System message");
4057 };
4058 let compact = sys.as_compact_boundary().expect("compact_boundary");
4059 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
4060 assert_eq!(compact.leaf_message_count, Some(7));
4061 }
4062
4063 #[test]
4064 fn test_init_message_with_new_fields() {
4065 let json = r#"{
4066 "type": "system",
4067 "subtype": "init",
4068 "session_id": "test-session",
4069 "cwd": "/home/user",
4070 "model": "claude-opus-4-7",
4071 "tools": ["Bash"],
4072 "mcp_servers": [],
4073 "permissionMode": "default",
4074 "apiKeySource": "none",
4075 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
4076 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
4077 "fast_mode_state": "off",
4078 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
4079 "claude_code_version": "2.1.117"
4080 }"#;
4081
4082 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4083 if let ClaudeOutput::System(sys) = output {
4084 let init = sys.as_init().expect("Should parse as init");
4085 assert_eq!(
4086 init.uuid.as_deref(),
4087 Some("44841a0d-182d-493a-86b5-79800d3d9665")
4088 );
4089 assert!(init.memory_paths.is_some());
4090 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
4091 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
4092 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
4093 } else {
4094 panic!("Expected System message");
4095 }
4096 }
4097
4098 #[test]
4099 fn test_assistant_message_with_new_fields() {
4100 let json = r#"{
4101 "type": "assistant",
4102 "message": {
4103 "id": "msg_1",
4104 "type": "message",
4105 "role": "assistant",
4106 "model": "claude-opus-4-7",
4107 "content": [{"type": "text", "text": "Hello"}],
4108 "stop_reason": "end_turn",
4109 "stop_details": null,
4110 "context_management": null,
4111 "usage": {
4112 "input_tokens": 100,
4113 "output_tokens": 10,
4114 "cache_creation_input_tokens": 50,
4115 "cache_read_input_tokens": 0,
4116 "service_tier": "standard",
4117 "inference_geo": "not_available"
4118 }
4119 },
4120 "session_id": "abc",
4121 "uuid": "msg-uuid-123"
4122 }"#;
4123
4124 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4125 if let ClaudeOutput::Assistant(asst) = output {
4126 assert_eq!(asst.message.stop_details, None);
4127 assert_eq!(asst.message.context_management, None);
4128 let usage = asst.message.usage.unwrap();
4129 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
4130 } else {
4131 panic!("Expected Assistant message");
4132 }
4133 }
4134
4135 #[test]
4136 fn test_user_message_with_new_fields() {
4137 let json = r#"{
4138 "type": "user",
4139 "message": {
4140 "role": "user",
4141 "content": [{"type": "text", "text": "Hello"}]
4142 },
4143 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4144 "parent_tool_use_id": "toolu_123",
4145 "uuid": "user-msg-456"
4146 }"#;
4147
4148 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4149 if let ClaudeOutput::User(user) = output {
4150 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
4151 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
4152 } else {
4153 panic!("Expected User message");
4154 }
4155 }
4156
4157 #[test]
4163 fn test_user_message_preserves_tool_use_result_and_timestamp() {
4164 let json = r#"{
4165 "type":"user",
4166 "message":{"role":"user","content":[{"type":"tool_result","content":"User has answered your questions: . You can now continue with the user's answers in mind.","tool_use_id":"toolu_01331duMqP2PrRaqR2yWa8e4"}]},
4167 "parent_tool_use_id":null,
4168 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4169 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
4170 "timestamp":"2026-05-12T23:12:04.121Z",
4171 "tool_use_result":{"questions":[{"question":"Which color do you prefer?","header":"Color","options":[{"label":"Red","description":"A warm color"},{"label":"Blue","description":"A cool color"}],"multiSelect":false}],"answers":{"Color":"Blue"}}
4172 }"#;
4173
4174 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4175 let user = match output {
4176 ClaudeOutput::User(u) => u,
4177 other => panic!("Expected User message, got {:?}", other.message_type()),
4178 };
4179
4180 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
4181 let raw = user
4182 .tool_use_result
4183 .as_ref()
4184 .expect("tool_use_result must be captured");
4185 assert_eq!(raw["answers"]["Color"], "Blue");
4186 assert_eq!(raw["questions"][0]["header"], "Color");
4187
4188 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
4192 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
4193 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
4194 assert_eq!(
4195 reser["tool_use_result"]["questions"][0]["question"],
4196 "Which color do you prefer?"
4197 );
4198
4199 let typed: crate::AskUserQuestionInput = user
4202 .tool_use_result_as::<crate::AskUserQuestionInput>()
4203 .expect("tool_use_result present")
4204 .expect("AskUserQuestionInput parses");
4205 assert_eq!(typed.questions.len(), 1);
4206 assert_eq!(typed.questions[0].header, "Color");
4207 let answers = typed.answers.expect("answers populated");
4208 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
4209 }
4210
4211 #[test]
4214 fn test_user_message_without_tool_use_result_omits_field() {
4215 let json = r#"{
4216 "type":"user",
4217 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
4218 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
4219 }"#;
4220
4221 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4222 let user = match output {
4223 ClaudeOutput::User(u) => u,
4224 _ => panic!("Expected User message"),
4225 };
4226 assert!(user.tool_use_result.is_none());
4227 assert!(user.timestamp.is_none());
4228
4229 let reser = serde_json::to_value(&user).unwrap();
4230 assert!(reser.get("tool_use_result").is_none());
4231 assert!(reser.get("timestamp").is_none());
4232 }
4233
4234 #[test]
4238 fn test_subagent_result_exposes_token_accounting() {
4239 let json = r#"{
4240 "type":"user",
4241 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
4242 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
4243 "tool_use_result":{
4244 "status":"completed",
4245 "prompt":"Count the .rs files.",
4246 "agentId":"ac4f0276e9d4b6232",
4247 "agentType":"Explore",
4248 "content":[{"type":"text","text":"21"}],
4249 "resolvedModel":"claude-haiku-4-5-20251001",
4250 "totalDurationMs":6869,
4251 "totalTokens":7834,
4252 "totalToolUseCount":1,
4253 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
4254 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
4255 }
4256 }"#;
4257
4258 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4259 let user = match output {
4260 ClaudeOutput::User(u) => u,
4261 _ => panic!("Expected User message"),
4262 };
4263
4264 let result = user.subagent_result().expect("subagent result parses");
4265 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
4266 assert_eq!(
4267 result.resolved_model.as_deref(),
4268 Some("claude-haiku-4-5-20251001")
4269 );
4270 assert_eq!(result.total_tokens, Some(7834));
4271 assert_eq!(result.total_duration_ms, Some(6869));
4272 assert_eq!(result.total_tool_use_count, Some(1));
4273
4274 let usage = result.usage.expect("nested usage present");
4275 assert_eq!(usage.input_tokens, 6);
4276 assert_eq!(usage.cache_read_input_tokens, 7699);
4277
4278 let stats = result.tool_stats.expect("toolStats present");
4279 assert_eq!(stats.bash_count, 1);
4280 }
4281
4282 #[test]
4286 fn test_subagent_result_absent_for_non_task_result() {
4287 let json = r#"{
4288 "type":"user",
4289 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
4290 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4291 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
4292 }"#;
4293
4294 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4295 let user = match output {
4296 ClaudeOutput::User(u) => u,
4297 _ => panic!("Expected User message"),
4298 };
4299
4300 let result = user.subagent_result().expect("lenient parse");
4301 assert_eq!(result.total_tokens, None);
4302 assert_eq!(result.agent_type, None);
4303 }
4304
4305 #[test]
4306 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
4307 use serde_json::Value;
4308
4309 let raw: Value = serde_json::from_str(
4310 r#"{
4311 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
4312 "fast_mode_state":"off",
4313 "fast_mode_disabled_reason":"not_first_party",
4314 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
4315 }"#,
4316 )
4317 .unwrap();
4318 crate::io::assert_fully_wrapped(&raw);
4319
4320 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4321 let ClaudeOutput::System(sys) = output else {
4322 panic!("expected System");
4323 };
4324 let init = sys.as_init().expect("parses as init");
4325 assert_eq!(
4326 init.fast_mode_disabled_reason,
4327 Some(crate::FastModeDisabledReason::NotFirstParty)
4328 );
4329 let errs = init.mcp_server_errors.unwrap();
4330 assert_eq!(errs.len(), 1);
4331 assert_eq!(errs[0].name, "broken");
4332 assert_eq!(errs[0].error_type, "invalid_config");
4333 }
4334
4335 #[test]
4336 fn test_code_change_published_fully_wrapped() {
4337 use super::{KnownSystemEvent, SystemSubtype};
4338 use serde_json::Value;
4339
4340 let raw: Value = serde_json::from_str(
4341 r#"{
4342 "type":"system","subtype":"code_change_published",
4343 "provider":"github","url":"https://github.com/owner/repo/pull/42",
4344 "repo":"owner/repo","identifier":"42",
4345 "uuid":"u1","session_id":"s1"
4346 }"#,
4347 )
4348 .unwrap();
4349 crate::io::assert_fully_wrapped(&raw);
4350
4351 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4352 let ClaudeOutput::System(sys) = output else {
4353 panic!("expected System");
4354 };
4355 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
4356 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
4357 panic!("expected CodeChangePublished event");
4358 };
4359 assert_eq!(msg.provider, "github");
4360 assert_eq!(msg.repo, "owner/repo");
4361 assert_eq!(msg.identifier, "42");
4362
4363 assert!(sys.is_code_change_published());
4364 assert!(!sys.is_vcs_state_changed());
4365 let direct = sys.as_code_change_published().expect("direct accessor");
4366 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
4367 assert!(sys.as_vcs_state_changed().is_none());
4368 }
4369
4370 #[test]
4371 fn test_feedback_draft_queued_fully_wrapped() {
4372 use super::{KnownSystemEvent, SystemSubtype};
4373 use serde_json::Value;
4374
4375 let raw: Value = serde_json::from_str(
4376 r#"{
4377 "type":"system","subtype":"feedback_draft_queued",
4378 "draft_id":"draft-1","draft_type":"bug_report",
4379 "title":"Tool output was truncated",
4380 "details_preview":"The last command omitted its final lines",
4381 "uuid":"u1","session_id":"s1","future_field":"preserved"
4382 }"#,
4383 )
4384 .unwrap();
4385 crate::io::assert_fully_wrapped(&raw);
4386
4387 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4388 let ClaudeOutput::System(sys) = output else {
4389 panic!("expected System");
4390 };
4391 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
4392 assert!(sys.is_feedback_draft_queued());
4393 assert!(!sys.is_vcs_state_changed());
4394
4395 let direct = sys
4396 .as_feedback_draft_queued()
4397 .expect("direct typed accessor");
4398 assert_eq!(direct.draft_id, "draft-1");
4399 assert_eq!(direct.draft_type, "bug_report");
4400 assert_eq!(direct.extra["future_field"], "preserved");
4401
4402 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
4403 panic!("expected FeedbackDraftQueued event");
4404 };
4405 assert_eq!(known.title, "Tool output was truncated");
4406 assert_eq!(
4407 sys.typed_value().expect("typed value")["future_field"],
4408 "preserved"
4409 );
4410 }
4411
4412 #[test]
4413 fn test_cloud_session_delta_fully_wrapped() {
4414 use super::{KnownSystemEvent, SystemSubtype};
4415 use serde_json::Value;
4416
4417 let raw: Value = serde_json::from_str(
4418 r#"{
4419 "type":"system","subtype":"cloud_session_delta",
4420 "seq":3,"changed":["serving","connection"],
4421 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
4422 "serving":{"state":"on"},"connection":{"state":"live"}},
4423 "uuid":"u1","session_id":"s1","future_field":"preserved"
4424 }"#,
4425 )
4426 .unwrap();
4427 crate::io::assert_fully_wrapped(&raw);
4428
4429 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4430 let ClaudeOutput::System(sys) = output else {
4431 panic!("expected System");
4432 };
4433 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
4434 assert!(sys.is_cloud_session_delta());
4435 assert!(!sys.is_feedback_draft_queued());
4436
4437 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
4438 assert_eq!(direct.seq, 3);
4439 assert_eq!(direct.changed, vec!["serving", "connection"]);
4440 assert_eq!(direct.cloud_session["id"], "session_abc");
4441 assert_eq!(direct.extra["future_field"], "preserved");
4442
4443 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
4444 panic!("expected CloudSessionDelta event");
4445 };
4446 assert_eq!(known.session_id, "s1");
4447 assert_eq!(
4448 sys.typed_value().expect("typed value")["future_field"],
4449 "preserved"
4450 );
4451 }
4452
4453 #[test]
4454 fn test_vcs_state_changed_fully_wrapped() {
4455 use super::{KnownSystemEvent, VcsMutationKind};
4456 use serde_json::Value;
4457
4458 for kind in ["commit", "push", "merge", "rebase"] {
4459 let raw: Value = serde_json::from_str(&format!(
4460 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
4461 kind
4462 ))
4463 .unwrap();
4464 crate::io::assert_fully_wrapped(&raw);
4465
4466 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4467 let ClaudeOutput::System(sys) = output else {
4468 panic!("expected System");
4469 };
4470 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
4471 panic!("expected VcsStateChanged event");
4472 };
4473 assert_eq!(msg.kind.as_str(), kind);
4474 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
4475 }
4476
4477 let raw: Value = serde_json::from_str(
4479 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
4480 )
4481 .unwrap();
4482 crate::io::assert_fully_wrapped(&raw);
4483 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4484 let ClaudeOutput::System(sys) = output else {
4485 panic!("expected System");
4486 };
4487 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
4488 panic!("expected VcsStateChanged event");
4489 };
4490 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
4491
4492 assert!(sys.is_vcs_state_changed());
4493 let direct = sys.as_vcs_state_changed().expect("direct accessor");
4494 assert_eq!(direct.cwd, "/repo");
4495 assert!(sys.as_code_change_published().is_none());
4496 }
4497
4498 #[test]
4499 fn test_assistant_aborted_and_resume_flags_roundtrip() {
4500 let json = r#"{
4501 "type":"assistant",
4502 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
4503 "session_id":"s1",
4504 "aborted":true,
4505 "resumed_from_incomplete_thinking":true
4506 }"#;
4507 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4508 let ClaudeOutput::Assistant(msg) = &output else {
4509 panic!("expected Assistant");
4510 };
4511 assert_eq!(msg.aborted, Some(true));
4512 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
4513 let reserialized = serde_json::to_string(&output).unwrap();
4514 assert!(reserialized.contains("\"aborted\":true"));
4515 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
4516
4517 let json = r#"{
4519 "type":"assistant",
4520 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
4521 "session_id":"s2"
4522 }"#;
4523 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4524 let reserialized = serde_json::to_string(&output).unwrap();
4525 assert!(!reserialized.contains("aborted"));
4526 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
4527 }
4528
4529 #[test]
4530 fn test_user_tool_result_meta_roundtrip() {
4531 let json = r#"{
4532 "type":"user",
4533 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
4534 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4535 "tool_result_meta":[
4536 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
4537 {"id":"toolu_2","non_execution_kind":"permission-rule"}
4538 ]
4539 }"#;
4540 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4541 let ClaudeOutput::User(user) = &output else {
4542 panic!("expected User");
4543 };
4544 let meta = user.tool_result_meta.as_ref().unwrap();
4545 assert_eq!(meta.len(), 2);
4546 assert_eq!(meta[0].non_execution_kind, "user-rejected");
4547 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
4548 assert_eq!(meta[1].user_feedback, None);
4549
4550 let reserialized = serde_json::to_string(&output).unwrap();
4551 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
4552 assert!(!reserialized.contains("\"user_feedback\":null"));
4553 }
4554
4555 #[test]
4559 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
4560 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
4561 let with_scope = serde_json::json!({
4562 "trigger": "refusal",
4563 "direction": "retry",
4564 "scope": "local",
4565 "original_model": "claude-fable-5",
4566 "fallback_model": "claude-opus-5",
4567 "request_id": null,
4568 "content": "Refused; retried on fallback model.",
4569 "uuid": "u1",
4570 "session_id": "s1"
4571 });
4572 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
4573 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
4574 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
4575
4576 let mut without = with_scope.clone();
4579 without.as_object_mut().unwrap().remove("scope");
4580 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
4581 assert_eq!(msg.scope, None);
4582 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
4583
4584 assert_eq!(
4586 RefusalFallbackScope::from("workspace").as_str(),
4587 "workspace"
4588 );
4589 }
4590}