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")]
3503 pub api_error: Option<String>,
3504 #[serde(default, skip_serializing_if = "Option::is_none")]
3511 pub api_error_code: Option<String>,
3512 #[serde(default, skip_serializing_if = "Option::is_none")]
3515 pub api_error_params: Option<ApiErrorParams>,
3516 #[serde(skip_serializing_if = "Option::is_none")]
3517 pub error_details: Option<String>,
3518 #[serde(skip_serializing_if = "Option::is_none")]
3519 pub advisor_model: Option<String>,
3520 #[serde(skip_serializing_if = "Option::is_none")]
3521 pub attribution_agent: Option<String>,
3522 #[serde(skip_serializing_if = "Option::is_none")]
3523 pub attribution_skill: Option<String>,
3524 #[serde(skip_serializing_if = "Option::is_none")]
3525 pub attribution_plugin: Option<String>,
3526 #[serde(skip_serializing_if = "Option::is_none")]
3527 pub attribution_mcp_server: Option<String>,
3528 #[serde(skip_serializing_if = "Option::is_none")]
3529 pub attribution_mcp_tool: Option<String>,
3530}
3531
3532#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
3536pub struct ApiErrorParams {
3537 #[serde(default, skip_serializing_if = "Option::is_none")]
3539 pub effort: Option<String>,
3540 #[serde(default, skip_serializing_if = "Option::is_none")]
3543 pub provider: Option<ApiErrorProvider>,
3544 #[serde(default, skip_serializing_if = "Option::is_none")]
3547 pub remedy: Option<ApiErrorRemedy>,
3548}
3549
3550#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3552pub enum ApiErrorProvider {
3553 Bedrock,
3554 AnthropicAws,
3555 Mantle,
3556 AnthropicGoogleCloud,
3557 Vertex,
3558 Foundry,
3559 Gateway,
3560 Unknown(String),
3562}
3563
3564impl ApiErrorProvider {
3565 pub fn as_str(&self) -> &str {
3566 match self {
3567 Self::Bedrock => "bedrock",
3568 Self::AnthropicAws => "anthropicAws",
3569 Self::Mantle => "mantle",
3570 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
3571 Self::Vertex => "vertex",
3572 Self::Foundry => "foundry",
3573 Self::Gateway => "gateway",
3574 Self::Unknown(s) => s.as_str(),
3575 }
3576 }
3577}
3578
3579impl fmt::Display for ApiErrorProvider {
3580 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3581 f.write_str(self.as_str())
3582 }
3583}
3584
3585impl From<&str> for ApiErrorProvider {
3586 fn from(s: &str) -> Self {
3587 match s {
3588 "bedrock" => Self::Bedrock,
3589 "anthropicAws" => Self::AnthropicAws,
3590 "mantle" => Self::Mantle,
3591 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
3592 "vertex" => Self::Vertex,
3593 "foundry" => Self::Foundry,
3594 "gateway" => Self::Gateway,
3595 other => Self::Unknown(other.to_string()),
3596 }
3597 }
3598}
3599
3600impl Serialize for ApiErrorProvider {
3601 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3602 serializer.serialize_str(self.as_str())
3603 }
3604}
3605
3606impl<'de> Deserialize<'de> for ApiErrorProvider {
3607 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3608 let s = String::deserialize(deserializer)?;
3609 Ok(Self::from(s.as_str()))
3610 }
3611}
3612
3613#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3615pub enum ApiErrorRemedy {
3616 RefreshCommand,
3619 RefreshCredentials,
3621 Adc,
3624 GatewayToken,
3627 HostManaged,
3629 ModelAccess,
3632 Unknown(String),
3634}
3635
3636impl ApiErrorRemedy {
3637 pub fn as_str(&self) -> &str {
3638 match self {
3639 Self::RefreshCommand => "refresh_command",
3640 Self::RefreshCredentials => "refresh_credentials",
3641 Self::Adc => "adc",
3642 Self::GatewayToken => "gateway_token",
3643 Self::HostManaged => "host_managed",
3644 Self::ModelAccess => "model_access",
3645 Self::Unknown(s) => s.as_str(),
3646 }
3647 }
3648}
3649
3650impl fmt::Display for ApiErrorRemedy {
3651 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3652 f.write_str(self.as_str())
3653 }
3654}
3655
3656impl From<&str> for ApiErrorRemedy {
3657 fn from(s: &str) -> Self {
3658 match s {
3659 "refresh_command" => Self::RefreshCommand,
3660 "refresh_credentials" => Self::RefreshCredentials,
3661 "adc" => Self::Adc,
3662 "gateway_token" => Self::GatewayToken,
3663 "host_managed" => Self::HostManaged,
3664 "model_access" => Self::ModelAccess,
3665 other => Self::Unknown(other.to_string()),
3666 }
3667 }
3668}
3669
3670impl Serialize for ApiErrorRemedy {
3671 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3672 serializer.serialize_str(self.as_str())
3673 }
3674}
3675
3676impl<'de> Deserialize<'de> for ApiErrorRemedy {
3677 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3678 let s = String::deserialize(deserializer)?;
3679 Ok(Self::from(s.as_str()))
3680 }
3681}
3682
3683#[derive(Debug, Clone, Serialize, Deserialize)]
3685pub struct AssistantMessageContent {
3686 pub id: String,
3687 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
3689 pub message_type: Option<String>,
3690 pub role: MessageRole,
3691 pub model: String,
3692 pub content: Vec<ContentBlock>,
3693 #[serde(skip_serializing_if = "Option::is_none")]
3694 pub stop_reason: Option<StopReason>,
3695 #[serde(skip_serializing_if = "Option::is_none")]
3696 pub stop_sequence: Option<String>,
3697 #[serde(skip_serializing_if = "Option::is_none")]
3698 pub usage: Option<AssistantUsage>,
3699 #[serde(skip_serializing_if = "Option::is_none")]
3701 pub stop_details: Option<Value>,
3702 #[serde(skip_serializing_if = "Option::is_none")]
3704 pub context_management: Option<Value>,
3705}
3706
3707#[derive(Debug, Clone, Serialize, Deserialize)]
3709pub struct AssistantUsage {
3710 #[serde(default)]
3712 pub input_tokens: u32,
3713
3714 #[serde(default)]
3716 pub output_tokens: u32,
3717
3718 #[serde(default)]
3720 pub cache_creation_input_tokens: u32,
3721
3722 #[serde(default)]
3724 pub cache_read_input_tokens: u32,
3725
3726 #[serde(skip_serializing_if = "Option::is_none")]
3728 pub service_tier: Option<String>,
3729
3730 #[serde(skip_serializing_if = "Option::is_none")]
3732 pub cache_creation: Option<CacheCreationDetails>,
3733
3734 #[serde(skip_serializing_if = "Option::is_none")]
3736 pub inference_geo: Option<String>,
3737}
3738
3739#[derive(Debug, Clone, Serialize, Deserialize)]
3741pub struct CacheCreationDetails {
3742 #[serde(default)]
3744 pub ephemeral_1h_input_tokens: u32,
3745
3746 #[serde(default)]
3748 pub ephemeral_5m_input_tokens: u32,
3749}
3750
3751#[cfg(test)]
3752mod tests {
3753 use crate::io::ClaudeOutput;
3754
3755 #[test]
3756 fn test_subagent_usage_rollup_accumulates_task_results() {
3757 use super::SubagentUsageRollup;
3758
3759 let mut rollup = SubagentUsageRollup::default();
3760
3761 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}}"#;
3762 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
3763 assert!(rollup.observe(&output));
3764 assert_eq!(rollup.subagent_tokens, 10201);
3765 assert_eq!(rollup.agent_count, 1);
3766 assert_eq!(rollup.tool_uses, 3);
3767 assert_eq!(rollup.duration_ms, 1853);
3768
3769 assert!(!rollup.observe(&output));
3771 assert_eq!(rollup.agent_count, 1);
3772 assert_eq!(rollup.subagent_tokens, 10201);
3773
3774 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}}"#;
3776 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
3777 assert!(rollup.observe(&output));
3778 assert_eq!(rollup.agent_count, 2);
3779 assert_eq!(rollup.subagent_tokens, 10701);
3780 }
3781
3782 #[test]
3783 fn test_subagent_usage_rollup_ignores_non_task_results() {
3784 use super::SubagentUsageRollup;
3785
3786 let mut rollup = SubagentUsageRollup::default();
3787
3788 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}}"#;
3791 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
3792 assert!(!rollup.observe(&output));
3793
3794 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
3796 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
3797 assert!(!rollup.observe(&output));
3798
3799 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
3801 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
3802 assert!(!rollup.observe(&output));
3803
3804 assert_eq!(rollup, SubagentUsageRollup::default());
3805 }
3806
3807 #[test]
3808 fn test_subagent_usage_rollup_over_captured_session() {
3809 use super::SubagentUsageRollup;
3810
3811 let mut rollup = SubagentUsageRollup::default();
3812 let fixture =
3813 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
3814 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
3815 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
3816 rollup.observe(&output);
3817 }
3818 }
3819 assert_eq!(rollup.agent_count, 1);
3820 assert_eq!(rollup.subagent_tokens, 10201);
3821 }
3822
3823 #[test]
3824 fn test_system_message_init() {
3825 let json = r#"{
3826 "type": "system",
3827 "subtype": "init",
3828 "session_id": "test-session-123",
3829 "cwd": "/home/user/project",
3830 "model": "claude-sonnet-4",
3831 "tools": ["Bash", "Read", "Write"],
3832 "mcp_servers": [],
3833 "slash_commands": ["compact", "cost", "review"],
3834 "agents": ["Bash", "Explore", "Plan"],
3835 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
3836 "skills": [],
3837 "claude_code_version": "2.1.15",
3838 "apiKeySource": "none",
3839 "output_style": "default",
3840 "permissionMode": "default"
3841 }"#;
3842
3843 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3844 if let ClaudeOutput::System(sys) = output {
3845 assert!(sys.is_init());
3846 assert!(!sys.is_status());
3847 assert!(!sys.is_compact_boundary());
3848
3849 let init = sys.as_init().expect("Should parse as init");
3850 assert_eq!(init.session_id, "test-session-123");
3851 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
3852 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
3853 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
3854 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
3855 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
3856 assert_eq!(init.plugins.len(), 1);
3857 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
3858 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
3859 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
3860 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
3861 assert_eq!(
3862 init.permission_mode,
3863 Some(super::InitPermissionMode::Default)
3864 );
3865 } else {
3866 panic!("Expected System message");
3867 }
3868 }
3869
3870 #[test]
3871 fn test_system_message_init_from_real_capture() {
3872 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
3873 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3874 if let ClaudeOutput::System(sys) = output {
3875 let init = sys.as_init().expect("Should parse real init capture");
3876 assert_eq!(init.slash_commands.len(), 8);
3877 assert!(init.slash_commands.contains(&"compact".to_string()));
3878 assert!(init.slash_commands.contains(&"review".to_string()));
3879 assert_eq!(init.agents.len(), 5);
3880 assert!(init.agents.contains(&"Bash".to_string()));
3881 assert!(init.agents.contains(&"Explore".to_string()));
3882 assert_eq!(init.plugins.len(), 1);
3883 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
3884 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
3885 } else {
3886 panic!("Expected System message");
3887 }
3888 }
3889
3890 #[test]
3891 fn test_system_message_status() {
3892 let json = r#"{
3893 "type": "system",
3894 "subtype": "status",
3895 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
3896 "status": "compacting",
3897 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
3898 }"#;
3899
3900 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3901 if let ClaudeOutput::System(sys) = output {
3902 assert!(sys.is_status());
3903 assert!(!sys.is_init());
3904
3905 let status = sys.as_status().expect("Should parse as status");
3906 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
3907 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
3908 assert_eq!(
3909 status.uuid,
3910 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
3911 );
3912 } else {
3913 panic!("Expected System message");
3914 }
3915 }
3916
3917 #[test]
3918 fn test_system_message_status_null() {
3919 let json = r#"{
3920 "type": "system",
3921 "subtype": "status",
3922 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
3923 "status": null,
3924 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
3925 }"#;
3926
3927 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3928 if let ClaudeOutput::System(sys) = output {
3929 let status = sys.as_status().expect("Should parse as status");
3930 assert_eq!(status.status, None);
3931 } else {
3932 panic!("Expected System message");
3933 }
3934 }
3935
3936 #[test]
3937 fn test_system_message_task_started() {
3938 let json = r#"{
3939 "type": "system",
3940 "subtype": "task_started",
3941 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
3942 "task_id": "b6daf3f",
3943 "task_type": "local_bash",
3944 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
3945 "description": "Wait for CI on PR #12",
3946 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
3947 }"#;
3948
3949 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3950 if let ClaudeOutput::System(sys) = output {
3951 assert!(sys.is_task_started());
3952 assert!(!sys.is_task_progress());
3953 assert!(!sys.is_task_notification());
3954
3955 let task = sys.as_task_started().expect("Should parse as task_started");
3956 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
3957 assert_eq!(task.task_id, "b6daf3f");
3958 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
3959 assert_eq!(
3960 task.tool_use_id.as_deref(),
3961 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
3962 );
3963 assert_eq!(task.description, "Wait for CI on PR #12");
3964 } else {
3965 panic!("Expected System message");
3966 }
3967 }
3968
3969 #[test]
3970 fn test_system_message_task_started_agent() {
3971 let json = r#"{
3972 "type": "system",
3973 "subtype": "task_started",
3974 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
3975 "task_id": "a4a7e0906e5fc64cc",
3976 "task_type": "local_agent",
3977 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
3978 "description": "Explore Scene/ArrayScene duplication",
3979 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
3980 }"#;
3981
3982 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
3983 if let ClaudeOutput::System(sys) = output {
3984 let task = sys.as_task_started().expect("Should parse as task_started");
3985 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
3986 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
3987 } else {
3988 panic!("Expected System message");
3989 }
3990 }
3991
3992 #[test]
3993 fn test_system_message_task_progress() {
3994 let json = r#"{
3995 "type": "system",
3996 "subtype": "task_progress",
3997 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
3998 "task_id": "a4a7e0906e5fc64cc",
3999 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4000 "description": "Reading src/jplephem/chebyshev.rs",
4001 "last_tool_name": "Read",
4002 "usage": {
4003 "duration_ms": 13996,
4004 "tool_uses": 9,
4005 "total_tokens": 38779
4006 },
4007 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4008 }"#;
4009
4010 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4011 if let ClaudeOutput::System(sys) = output {
4012 assert!(sys.is_task_progress());
4013 assert!(!sys.is_task_started());
4014
4015 let progress = sys
4016 .as_task_progress()
4017 .expect("Should parse as task_progress");
4018 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
4019 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
4020 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
4021 assert_eq!(progress.usage.duration_ms, 13996);
4022 assert_eq!(progress.usage.tool_uses, 9);
4023 assert_eq!(progress.usage.total_tokens, 38779);
4024 } else {
4025 panic!("Expected System message");
4026 }
4027 }
4028
4029 #[test]
4030 fn test_system_message_task_notification_completed() {
4031 let json = r#"{
4032 "type": "system",
4033 "subtype": "task_notification",
4034 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4035 "task_id": "a0ba761e9dc9c316f",
4036 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
4037 "status": "completed",
4038 "summary": "Agent \"Write Hipparcos data source doc\" completed",
4039 "output_file": "",
4040 "usage": {
4041 "duration_ms": 172300,
4042 "tool_uses": 11,
4043 "total_tokens": 42005
4044 },
4045 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
4046 }"#;
4047
4048 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4049 if let ClaudeOutput::System(sys) = output {
4050 assert!(sys.is_task_notification());
4051
4052 let notif = sys
4053 .as_task_notification()
4054 .expect("Should parse as task_notification");
4055 assert_eq!(notif.status, super::TaskStatus::Completed);
4056 assert_eq!(
4057 notif.summary,
4058 "Agent \"Write Hipparcos data source doc\" completed"
4059 );
4060 assert_eq!(notif.output_file, Some("".to_string()));
4061 assert_eq!(
4062 notif.tool_use_id,
4063 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
4064 );
4065 let usage = notif.usage.expect("Should have usage");
4066 assert_eq!(usage.duration_ms, 172300);
4067 assert_eq!(usage.tool_uses, 11);
4068 assert_eq!(usage.total_tokens, 42005);
4069 } else {
4070 panic!("Expected System message");
4071 }
4072 }
4073
4074 #[test]
4075 fn test_system_message_task_notification_failed_no_usage() {
4076 let json = r#"{
4077 "type": "system",
4078 "subtype": "task_notification",
4079 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
4080 "task_id": "b98f6a3",
4081 "status": "failed",
4082 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
4083 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
4084 }"#;
4085
4086 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4087 if let ClaudeOutput::System(sys) = output {
4088 let notif = sys
4089 .as_task_notification()
4090 .expect("Should parse as task_notification");
4091 assert_eq!(notif.status, super::TaskStatus::Failed);
4092 assert!(notif.tool_use_id.is_none());
4093 assert!(notif.usage.is_none());
4094 assert_eq!(
4095 notif.output_file,
4096 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
4097 );
4098 } else {
4099 panic!("Expected System message");
4100 }
4101 }
4102
4103 #[test]
4109 fn test_task_messages_roundtrip_through_value() {
4110 let cases = [
4111 r#"{"type":"system","subtype":"task_started","session_id":"s1",
4112 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
4113 "description":"Sleep 3s","uuid":"u1"}"#,
4114 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
4115 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
4116 "last_tool_name":"Bash",
4117 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4118 "uuid":"u2"}"#,
4119 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
4120 "task_id":"t1","tool_use_id":"tu1","status":"completed",
4121 "summary":"done","output_file":"",
4122 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4123 "uuid":"u3"}"#,
4124 ];
4125
4126 for json in cases {
4127 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4128 let value = serde_json::to_value(&output).unwrap();
4129 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
4130
4131 let ClaudeOutput::System(sys) = reparsed else {
4132 panic!("Expected System variant after round-trip");
4133 };
4134
4135 match sys.subtype {
4136 super::SystemSubtype::TaskStarted => {
4137 assert!(
4138 sys.as_task_started().is_some(),
4139 "as_task_started failed after round-trip"
4140 );
4141 }
4142 super::SystemSubtype::TaskProgress => {
4143 assert!(
4144 sys.as_task_progress().is_some(),
4145 "as_task_progress failed after round-trip"
4146 );
4147 }
4148 super::SystemSubtype::TaskNotification => {
4149 assert!(
4150 sys.as_task_notification().is_some(),
4151 "as_task_notification failed after round-trip"
4152 );
4153 }
4154 other => panic!("unexpected subtype after round-trip: {other:?}"),
4155 }
4156 }
4157 }
4158
4159 #[test]
4160 fn test_system_message_compact_boundary() {
4161 let json = r#"{
4162 "type": "system",
4163 "subtype": "compact_boundary",
4164 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4165 "compact_metadata": {
4166 "pre_tokens": 155285,
4167 "trigger": "auto"
4168 },
4169 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
4170 }"#;
4171
4172 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4173 if let ClaudeOutput::System(sys) = output {
4174 assert!(sys.is_compact_boundary());
4175 assert!(!sys.is_init());
4176 assert!(!sys.is_status());
4177
4178 let compact = sys
4179 .as_compact_boundary()
4180 .expect("Should parse as compact_boundary");
4181 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4182 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
4183 assert_eq!(
4184 compact.compact_metadata.trigger,
4185 super::CompactionTrigger::Auto
4186 );
4187 assert!(compact.summary.is_none());
4189 assert!(compact.leaf_message_count.is_none());
4190 assert!(compact.duration_ms.is_none());
4191 } else {
4192 panic!("Expected System message");
4193 }
4194 }
4195
4196 #[test]
4197 fn test_compact_boundary_with_summary_stats() {
4198 let json = r#"{
4200 "type": "system",
4201 "subtype": "compact_boundary",
4202 "session_id": "s1",
4203 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
4204 "summary": "Summarized the earlier exploration.",
4205 "leaf_message_count": 42,
4206 "duration_ms": 1234,
4207 "uuid": "u1"
4208 }"#;
4209 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4210 let ClaudeOutput::System(sys) = output else {
4211 panic!("Expected System message");
4212 };
4213 let compact = sys.as_compact_boundary().expect("compact_boundary");
4214 assert_eq!(
4215 compact.summary.as_deref(),
4216 Some("Summarized the earlier exploration.")
4217 );
4218 assert_eq!(compact.leaf_message_count, Some(42));
4219 assert_eq!(compact.duration_ms, Some(1234));
4220
4221 let json_alt = r#"{
4224 "type": "system",
4225 "subtype": "compact_boundary",
4226 "session_id": "s2",
4227 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
4228 "content": "alt-key summary",
4229 "message_count": 7
4230 }"#;
4231 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
4232 let ClaudeOutput::System(sys) = output else {
4233 panic!("Expected System message");
4234 };
4235 let compact = sys.as_compact_boundary().expect("compact_boundary");
4236 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
4237 assert_eq!(compact.leaf_message_count, Some(7));
4238 }
4239
4240 #[test]
4241 fn test_init_message_with_new_fields() {
4242 let json = r#"{
4243 "type": "system",
4244 "subtype": "init",
4245 "session_id": "test-session",
4246 "cwd": "/home/user",
4247 "model": "claude-opus-4-7",
4248 "tools": ["Bash"],
4249 "mcp_servers": [],
4250 "permissionMode": "default",
4251 "apiKeySource": "none",
4252 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
4253 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
4254 "fast_mode_state": "off",
4255 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
4256 "claude_code_version": "2.1.117"
4257 }"#;
4258
4259 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4260 if let ClaudeOutput::System(sys) = output {
4261 let init = sys.as_init().expect("Should parse as init");
4262 assert_eq!(
4263 init.uuid.as_deref(),
4264 Some("44841a0d-182d-493a-86b5-79800d3d9665")
4265 );
4266 assert!(init.memory_paths.is_some());
4267 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
4268 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
4269 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
4270 } else {
4271 panic!("Expected System message");
4272 }
4273 }
4274
4275 #[test]
4276 fn test_assistant_message_with_new_fields() {
4277 let json = r#"{
4278 "type": "assistant",
4279 "message": {
4280 "id": "msg_1",
4281 "type": "message",
4282 "role": "assistant",
4283 "model": "claude-opus-4-7",
4284 "content": [{"type": "text", "text": "Hello"}],
4285 "stop_reason": "end_turn",
4286 "stop_details": null,
4287 "context_management": null,
4288 "usage": {
4289 "input_tokens": 100,
4290 "output_tokens": 10,
4291 "cache_creation_input_tokens": 50,
4292 "cache_read_input_tokens": 0,
4293 "service_tier": "standard",
4294 "inference_geo": "not_available"
4295 }
4296 },
4297 "session_id": "abc",
4298 "uuid": "msg-uuid-123"
4299 }"#;
4300
4301 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4302 if let ClaudeOutput::Assistant(asst) = output {
4303 assert_eq!(asst.message.stop_details, None);
4304 assert_eq!(asst.message.context_management, None);
4305 let usage = asst.message.usage.unwrap();
4306 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
4307 } else {
4308 panic!("Expected Assistant message");
4309 }
4310 }
4311
4312 #[test]
4313 fn test_user_message_with_new_fields() {
4314 let json = r#"{
4315 "type": "user",
4316 "message": {
4317 "role": "user",
4318 "content": [{"type": "text", "text": "Hello"}]
4319 },
4320 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4321 "parent_tool_use_id": "toolu_123",
4322 "uuid": "user-msg-456"
4323 }"#;
4324
4325 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4326 if let ClaudeOutput::User(user) = output {
4327 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
4328 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
4329 } else {
4330 panic!("Expected User message");
4331 }
4332 }
4333
4334 #[test]
4340 fn test_user_message_preserves_tool_use_result_and_timestamp() {
4341 let json = r#"{
4342 "type":"user",
4343 "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"}]},
4344 "parent_tool_use_id":null,
4345 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4346 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
4347 "timestamp":"2026-05-12T23:12:04.121Z",
4348 "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"}}
4349 }"#;
4350
4351 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4352 let user = match output {
4353 ClaudeOutput::User(u) => u,
4354 other => panic!("Expected User message, got {:?}", other.message_type()),
4355 };
4356
4357 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
4358 let raw = user
4359 .tool_use_result
4360 .as_ref()
4361 .expect("tool_use_result must be captured");
4362 assert_eq!(raw["answers"]["Color"], "Blue");
4363 assert_eq!(raw["questions"][0]["header"], "Color");
4364
4365 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
4369 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
4370 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
4371 assert_eq!(
4372 reser["tool_use_result"]["questions"][0]["question"],
4373 "Which color do you prefer?"
4374 );
4375
4376 let typed: crate::AskUserQuestionInput = user
4379 .tool_use_result_as::<crate::AskUserQuestionInput>()
4380 .expect("tool_use_result present")
4381 .expect("AskUserQuestionInput parses");
4382 assert_eq!(typed.questions.len(), 1);
4383 assert_eq!(typed.questions[0].header, "Color");
4384 let answers = typed.answers.expect("answers populated");
4385 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
4386 }
4387
4388 #[test]
4391 fn test_user_message_without_tool_use_result_omits_field() {
4392 let json = r#"{
4393 "type":"user",
4394 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
4395 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
4396 }"#;
4397
4398 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4399 let user = match output {
4400 ClaudeOutput::User(u) => u,
4401 _ => panic!("Expected User message"),
4402 };
4403 assert!(user.tool_use_result.is_none());
4404 assert!(user.timestamp.is_none());
4405
4406 let reser = serde_json::to_value(&user).unwrap();
4407 assert!(reser.get("tool_use_result").is_none());
4408 assert!(reser.get("timestamp").is_none());
4409 }
4410
4411 #[test]
4415 fn test_subagent_result_exposes_token_accounting() {
4416 let json = r#"{
4417 "type":"user",
4418 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
4419 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
4420 "tool_use_result":{
4421 "status":"completed",
4422 "prompt":"Count the .rs files.",
4423 "agentId":"ac4f0276e9d4b6232",
4424 "agentType":"Explore",
4425 "content":[{"type":"text","text":"21"}],
4426 "resolvedModel":"claude-haiku-4-5-20251001",
4427 "totalDurationMs":6869,
4428 "totalTokens":7834,
4429 "totalToolUseCount":1,
4430 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
4431 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
4432 }
4433 }"#;
4434
4435 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4436 let user = match output {
4437 ClaudeOutput::User(u) => u,
4438 _ => panic!("Expected User message"),
4439 };
4440
4441 let result = user.subagent_result().expect("subagent result parses");
4442 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
4443 assert_eq!(
4444 result.resolved_model.as_deref(),
4445 Some("claude-haiku-4-5-20251001")
4446 );
4447 assert_eq!(result.total_tokens, Some(7834));
4448 assert_eq!(result.total_duration_ms, Some(6869));
4449 assert_eq!(result.total_tool_use_count, Some(1));
4450
4451 let usage = result.usage.expect("nested usage present");
4452 assert_eq!(usage.input_tokens, 6);
4453 assert_eq!(usage.cache_read_input_tokens, 7699);
4454
4455 let stats = result.tool_stats.expect("toolStats present");
4456 assert_eq!(stats.bash_count, 1);
4457 }
4458
4459 #[test]
4463 fn test_subagent_result_absent_for_non_task_result() {
4464 let json = r#"{
4465 "type":"user",
4466 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
4467 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4468 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
4469 }"#;
4470
4471 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4472 let user = match output {
4473 ClaudeOutput::User(u) => u,
4474 _ => panic!("Expected User message"),
4475 };
4476
4477 let result = user.subagent_result().expect("lenient parse");
4478 assert_eq!(result.total_tokens, None);
4479 assert_eq!(result.agent_type, None);
4480 }
4481
4482 #[test]
4483 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
4484 use serde_json::Value;
4485
4486 let raw: Value = serde_json::from_str(
4487 r#"{
4488 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
4489 "fast_mode_state":"off",
4490 "fast_mode_disabled_reason":"not_first_party",
4491 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
4492 }"#,
4493 )
4494 .unwrap();
4495 crate::io::assert_fully_wrapped(&raw);
4496
4497 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4498 let ClaudeOutput::System(sys) = output else {
4499 panic!("expected System");
4500 };
4501 let init = sys.as_init().expect("parses as init");
4502 assert_eq!(
4503 init.fast_mode_disabled_reason,
4504 Some(crate::FastModeDisabledReason::NotFirstParty)
4505 );
4506 let errs = init.mcp_server_errors.unwrap();
4507 assert_eq!(errs.len(), 1);
4508 assert_eq!(errs[0].name, "broken");
4509 assert_eq!(errs[0].error_type, "invalid_config");
4510 }
4511
4512 #[test]
4513 fn test_code_change_published_fully_wrapped() {
4514 use super::{KnownSystemEvent, SystemSubtype};
4515 use serde_json::Value;
4516
4517 let raw: Value = serde_json::from_str(
4518 r#"{
4519 "type":"system","subtype":"code_change_published",
4520 "provider":"github","url":"https://github.com/owner/repo/pull/42",
4521 "repo":"owner/repo","identifier":"42",
4522 "uuid":"u1","session_id":"s1"
4523 }"#,
4524 )
4525 .unwrap();
4526 crate::io::assert_fully_wrapped(&raw);
4527
4528 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4529 let ClaudeOutput::System(sys) = output else {
4530 panic!("expected System");
4531 };
4532 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
4533 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
4534 panic!("expected CodeChangePublished event");
4535 };
4536 assert_eq!(msg.provider, "github");
4537 assert_eq!(msg.repo, "owner/repo");
4538 assert_eq!(msg.identifier, "42");
4539
4540 assert!(sys.is_code_change_published());
4541 assert!(!sys.is_vcs_state_changed());
4542 let direct = sys.as_code_change_published().expect("direct accessor");
4543 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
4544 assert!(sys.as_vcs_state_changed().is_none());
4545 }
4546
4547 #[test]
4548 fn test_feedback_draft_queued_fully_wrapped() {
4549 use super::{KnownSystemEvent, SystemSubtype};
4550 use serde_json::Value;
4551
4552 let raw: Value = serde_json::from_str(
4553 r#"{
4554 "type":"system","subtype":"feedback_draft_queued",
4555 "draft_id":"draft-1","draft_type":"bug_report",
4556 "title":"Tool output was truncated",
4557 "details_preview":"The last command omitted its final lines",
4558 "uuid":"u1","session_id":"s1","future_field":"preserved"
4559 }"#,
4560 )
4561 .unwrap();
4562 crate::io::assert_fully_wrapped(&raw);
4563
4564 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4565 let ClaudeOutput::System(sys) = output else {
4566 panic!("expected System");
4567 };
4568 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
4569 assert!(sys.is_feedback_draft_queued());
4570 assert!(!sys.is_vcs_state_changed());
4571
4572 let direct = sys
4573 .as_feedback_draft_queued()
4574 .expect("direct typed accessor");
4575 assert_eq!(direct.draft_id, "draft-1");
4576 assert_eq!(direct.draft_type, "bug_report");
4577 assert_eq!(direct.extra["future_field"], "preserved");
4578
4579 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
4580 panic!("expected FeedbackDraftQueued event");
4581 };
4582 assert_eq!(known.title, "Tool output was truncated");
4583 assert_eq!(
4584 sys.typed_value().expect("typed value")["future_field"],
4585 "preserved"
4586 );
4587 }
4588
4589 #[test]
4590 fn test_cloud_session_delta_fully_wrapped() {
4591 use super::{KnownSystemEvent, SystemSubtype};
4592 use serde_json::Value;
4593
4594 let raw: Value = serde_json::from_str(
4595 r#"{
4596 "type":"system","subtype":"cloud_session_delta",
4597 "seq":3,"changed":["serving","connection"],
4598 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
4599 "serving":{"state":"on"},"connection":{"state":"live"}},
4600 "uuid":"u1","session_id":"s1","future_field":"preserved"
4601 }"#,
4602 )
4603 .unwrap();
4604 crate::io::assert_fully_wrapped(&raw);
4605
4606 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4607 let ClaudeOutput::System(sys) = output else {
4608 panic!("expected System");
4609 };
4610 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
4611 assert!(sys.is_cloud_session_delta());
4612 assert!(!sys.is_feedback_draft_queued());
4613
4614 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
4615 assert_eq!(direct.seq, 3);
4616 assert_eq!(direct.changed, vec!["serving", "connection"]);
4617 assert_eq!(direct.cloud_session["id"], "session_abc");
4618 assert_eq!(direct.extra["future_field"], "preserved");
4619
4620 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
4621 panic!("expected CloudSessionDelta event");
4622 };
4623 assert_eq!(known.session_id, "s1");
4624 assert_eq!(
4625 sys.typed_value().expect("typed value")["future_field"],
4626 "preserved"
4627 );
4628 }
4629
4630 #[test]
4631 fn test_vcs_state_changed_fully_wrapped() {
4632 use super::{KnownSystemEvent, VcsMutationKind};
4633 use serde_json::Value;
4634
4635 for kind in ["commit", "push", "merge", "rebase"] {
4636 let raw: Value = serde_json::from_str(&format!(
4637 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
4638 kind
4639 ))
4640 .unwrap();
4641 crate::io::assert_fully_wrapped(&raw);
4642
4643 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4644 let ClaudeOutput::System(sys) = output else {
4645 panic!("expected System");
4646 };
4647 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
4648 panic!("expected VcsStateChanged event");
4649 };
4650 assert_eq!(msg.kind.as_str(), kind);
4651 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
4652 }
4653
4654 let raw: Value = serde_json::from_str(
4656 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
4657 )
4658 .unwrap();
4659 crate::io::assert_fully_wrapped(&raw);
4660 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4661 let ClaudeOutput::System(sys) = output else {
4662 panic!("expected System");
4663 };
4664 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
4665 panic!("expected VcsStateChanged event");
4666 };
4667 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
4668
4669 assert!(sys.is_vcs_state_changed());
4670 let direct = sys.as_vcs_state_changed().expect("direct accessor");
4671 assert_eq!(direct.cwd, "/repo");
4672 assert!(sys.as_code_change_published().is_none());
4673 }
4674
4675 #[test]
4676 fn test_assistant_aborted_and_resume_flags_roundtrip() {
4677 let json = r#"{
4678 "type":"assistant",
4679 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
4680 "session_id":"s1",
4681 "aborted":true,
4682 "resumed_from_incomplete_thinking":true
4683 }"#;
4684 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4685 let ClaudeOutput::Assistant(msg) = &output else {
4686 panic!("expected Assistant");
4687 };
4688 assert_eq!(msg.aborted, Some(true));
4689 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
4690 let reserialized = serde_json::to_string(&output).unwrap();
4691 assert!(reserialized.contains("\"aborted\":true"));
4692 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
4693
4694 let json = r#"{
4696 "type":"assistant",
4697 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
4698 "session_id":"s2"
4699 }"#;
4700 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4701 let reserialized = serde_json::to_string(&output).unwrap();
4702 assert!(!reserialized.contains("aborted"));
4703 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
4704 }
4705
4706 #[test]
4707 fn test_user_tool_result_meta_roundtrip() {
4708 let json = r#"{
4709 "type":"user",
4710 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
4711 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4712 "tool_result_meta":[
4713 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
4714 {"id":"toolu_2","non_execution_kind":"permission-rule"}
4715 ]
4716 }"#;
4717 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4718 let ClaudeOutput::User(user) = &output else {
4719 panic!("expected User");
4720 };
4721 let meta = user.tool_result_meta.as_ref().unwrap();
4722 assert_eq!(meta.len(), 2);
4723 assert_eq!(meta[0].non_execution_kind, "user-rejected");
4724 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
4725 assert_eq!(meta[1].user_feedback, None);
4726
4727 let reserialized = serde_json::to_string(&output).unwrap();
4728 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
4729 assert!(!reserialized.contains("\"user_feedback\":null"));
4730 }
4731
4732 #[test]
4736 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
4737 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
4738 let with_scope = serde_json::json!({
4739 "trigger": "refusal",
4740 "direction": "retry",
4741 "scope": "local",
4742 "original_model": "claude-fable-5",
4743 "fallback_model": "claude-opus-5",
4744 "request_id": null,
4745 "content": "Refused; retried on fallback model.",
4746 "uuid": "u1",
4747 "session_id": "s1"
4748 });
4749 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
4750 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
4751 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
4752
4753 let mut without = with_scope.clone();
4756 without.as_object_mut().unwrap().remove("scope");
4757 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
4758 assert_eq!(msg.scope, None);
4759 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
4760
4761 assert_eq!(
4763 RefusalFallbackScope::from("workspace").as_str(),
4764 "workspace"
4765 );
4766 }
4767}