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};
8use super::rate_limit::RateLimitInfo;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum SystemSubtype {
17 Init,
18 Status,
19 CompactBoundary,
20 ThinkingTokens,
21 TaskStarted,
22 TaskProgress,
23 TaskUpdated,
24 TaskNotification,
25 ApiRetry,
26 ControlRequestProgress,
27 ModelRefusalFallback,
28 ModelRefusalNoFallback,
29 LocalCommandOutput,
30 HookStarted,
31 HookProgress,
32 HookResponse,
33 PluginInstall,
34 BackgroundTasksChanged,
35 SessionStateChanged,
36 WorkerShuttingDown,
37 CommandsChanged,
38 Notification,
39 FilesPersisted,
40 MemoryRecall,
41 ElicitationComplete,
42 PermissionDenied,
43 MirrorError,
44 Informational,
45 CodeChangePublished,
46 VcsStateChanged,
47 FeedbackDraftQueued,
48 CloudSessionDelta,
49 DevIntent,
50 TurnHandoffAvailable,
51 TurnPreempted,
52 PeerMessageHold,
53 PerTurnEffortChanged,
54 SessionTitleChanged,
55 InstructionSizeWarning,
56 FileAttachmentsMissing,
57 PermissionCheckStatus,
58 UiFocus,
59 UiInvalidate,
60 UiLog,
61 UiPanes,
62 UiScroll,
63 UiStatus,
64 UiToast,
65 Unknown(String),
67}
68
69impl SystemSubtype {
70 pub fn as_str(&self) -> &str {
71 match self {
72 Self::Init => "init",
73 Self::Status => "status",
74 Self::CompactBoundary => "compact_boundary",
75 Self::ThinkingTokens => "thinking_tokens",
76 Self::TaskStarted => "task_started",
77 Self::TaskProgress => "task_progress",
78 Self::TaskUpdated => "task_updated",
79 Self::TaskNotification => "task_notification",
80 Self::ApiRetry => "api_retry",
81 Self::ControlRequestProgress => "control_request_progress",
82 Self::ModelRefusalFallback => "model_refusal_fallback",
83 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
84 Self::LocalCommandOutput => "local_command_output",
85 Self::HookStarted => "hook_started",
86 Self::HookProgress => "hook_progress",
87 Self::HookResponse => "hook_response",
88 Self::PluginInstall => "plugin_install",
89 Self::BackgroundTasksChanged => "background_tasks_changed",
90 Self::SessionStateChanged => "session_state_changed",
91 Self::WorkerShuttingDown => "worker_shutting_down",
92 Self::CommandsChanged => "commands_changed",
93 Self::Notification => "notification",
94 Self::FilesPersisted => "files_persisted",
95 Self::MemoryRecall => "memory_recall",
96 Self::ElicitationComplete => "elicitation_complete",
97 Self::PermissionDenied => "permission_denied",
98 Self::MirrorError => "mirror_error",
99 Self::Informational => "informational",
100 Self::CodeChangePublished => "code_change_published",
101 Self::VcsStateChanged => "vcs_state_changed",
102 Self::FeedbackDraftQueued => "feedback_draft_queued",
103 Self::CloudSessionDelta => "cloud_session_delta",
104 Self::DevIntent => "dev_intent",
105 Self::TurnHandoffAvailable => "turn_handoff_available",
106 Self::TurnPreempted => "turn_preempted",
107 Self::PeerMessageHold => "peer_message_hold",
108 Self::PerTurnEffortChanged => "per_turn_effort_changed",
109 Self::SessionTitleChanged => "session_title_changed",
110 Self::InstructionSizeWarning => "instruction_size_warning",
111 Self::FileAttachmentsMissing => "file_attachments_missing",
112 Self::PermissionCheckStatus => "permission_check_status",
113 Self::UiFocus => "ui_focus",
114 Self::UiInvalidate => "ui_invalidate",
115 Self::UiLog => "ui_log",
116 Self::UiPanes => "ui_panes",
117 Self::UiScroll => "ui_scroll",
118 Self::UiStatus => "ui_status",
119 Self::UiToast => "ui_toast",
120 Self::Unknown(s) => s.as_str(),
121 }
122 }
123}
124
125impl fmt::Display for SystemSubtype {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.write_str(self.as_str())
128 }
129}
130
131impl From<&str> for SystemSubtype {
132 fn from(s: &str) -> Self {
133 match s {
134 "init" => Self::Init,
135 "status" => Self::Status,
136 "compact_boundary" => Self::CompactBoundary,
137 "thinking_tokens" => Self::ThinkingTokens,
138 "task_started" => Self::TaskStarted,
139 "task_progress" => Self::TaskProgress,
140 "task_updated" => Self::TaskUpdated,
141 "task_notification" => Self::TaskNotification,
142 "api_retry" => Self::ApiRetry,
143 "control_request_progress" => Self::ControlRequestProgress,
144 "model_refusal_fallback" => Self::ModelRefusalFallback,
145 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
146 "local_command_output" => Self::LocalCommandOutput,
147 "hook_started" => Self::HookStarted,
148 "hook_progress" => Self::HookProgress,
149 "hook_response" => Self::HookResponse,
150 "plugin_install" => Self::PluginInstall,
151 "background_tasks_changed" => Self::BackgroundTasksChanged,
152 "session_state_changed" => Self::SessionStateChanged,
153 "worker_shutting_down" => Self::WorkerShuttingDown,
154 "commands_changed" => Self::CommandsChanged,
155 "notification" => Self::Notification,
156 "files_persisted" => Self::FilesPersisted,
157 "memory_recall" => Self::MemoryRecall,
158 "elicitation_complete" => Self::ElicitationComplete,
159 "permission_denied" => Self::PermissionDenied,
160 "mirror_error" => Self::MirrorError,
161 "informational" => Self::Informational,
162 "code_change_published" => Self::CodeChangePublished,
163 "vcs_state_changed" => Self::VcsStateChanged,
164 "feedback_draft_queued" => Self::FeedbackDraftQueued,
165 "cloud_session_delta" => Self::CloudSessionDelta,
166 "dev_intent" => Self::DevIntent,
167 "turn_handoff_available" => Self::TurnHandoffAvailable,
168 "turn_preempted" => Self::TurnPreempted,
169 "peer_message_hold" => Self::PeerMessageHold,
170 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
171 "session_title_changed" => Self::SessionTitleChanged,
172 "instruction_size_warning" => Self::InstructionSizeWarning,
173 "file_attachments_missing" => Self::FileAttachmentsMissing,
174 "permission_check_status" => Self::PermissionCheckStatus,
175 "ui_focus" => Self::UiFocus,
176 "ui_invalidate" => Self::UiInvalidate,
177 "ui_log" => Self::UiLog,
178 "ui_panes" => Self::UiPanes,
179 "ui_scroll" => Self::UiScroll,
180 "ui_status" => Self::UiStatus,
181 "ui_toast" => Self::UiToast,
182 other => Self::Unknown(other.to_string()),
183 }
184 }
185}
186
187impl Serialize for SystemSubtype {
188 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
189 serializer.serialize_str(self.as_str())
190 }
191}
192
193impl<'de> Deserialize<'de> for SystemSubtype {
194 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
195 let s = String::deserialize(deserializer)?;
196 Ok(Self::from(s.as_str()))
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Hash)]
205pub enum MessageRole {
206 User,
207 Assistant,
208 Unknown(String),
210}
211
212impl MessageRole {
213 pub fn as_str(&self) -> &str {
214 match self {
215 Self::User => "user",
216 Self::Assistant => "assistant",
217 Self::Unknown(s) => s.as_str(),
218 }
219 }
220}
221
222impl fmt::Display for MessageRole {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 f.write_str(self.as_str())
225 }
226}
227
228impl From<&str> for MessageRole {
229 fn from(s: &str) -> Self {
230 match s {
231 "user" => Self::User,
232 "assistant" => Self::Assistant,
233 other => Self::Unknown(other.to_string()),
234 }
235 }
236}
237
238impl Serialize for MessageRole {
239 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
240 serializer.serialize_str(self.as_str())
241 }
242}
243
244impl<'de> Deserialize<'de> for MessageRole {
245 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
246 let s = String::deserialize(deserializer)?;
247 Ok(Self::from(s.as_str()))
248 }
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Hash)]
253pub enum CompactionTrigger {
254 Auto,
256 Manual,
258 Unknown(String),
260}
261
262impl CompactionTrigger {
263 pub fn as_str(&self) -> &str {
264 match self {
265 Self::Auto => "auto",
266 Self::Manual => "manual",
267 Self::Unknown(s) => s.as_str(),
268 }
269 }
270}
271
272impl fmt::Display for CompactionTrigger {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 f.write_str(self.as_str())
275 }
276}
277
278impl From<&str> for CompactionTrigger {
279 fn from(s: &str) -> Self {
280 match s {
281 "auto" => Self::Auto,
282 "manual" => Self::Manual,
283 other => Self::Unknown(other.to_string()),
284 }
285 }
286}
287
288impl Serialize for CompactionTrigger {
289 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
290 serializer.serialize_str(self.as_str())
291 }
292}
293
294impl<'de> Deserialize<'de> for CompactionTrigger {
295 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
296 let s = String::deserialize(deserializer)?;
297 Ok(Self::from(s.as_str()))
298 }
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Hash)]
303pub enum StopReason {
304 EndTurn,
306 MaxTokens,
308 ToolUse,
310 Unknown(String),
312}
313
314impl StopReason {
315 pub fn as_str(&self) -> &str {
316 match self {
317 Self::EndTurn => "end_turn",
318 Self::MaxTokens => "max_tokens",
319 Self::ToolUse => "tool_use",
320 Self::Unknown(s) => s.as_str(),
321 }
322 }
323}
324
325impl fmt::Display for StopReason {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 f.write_str(self.as_str())
328 }
329}
330
331impl From<&str> for StopReason {
332 fn from(s: &str) -> Self {
333 match s {
334 "end_turn" => Self::EndTurn,
335 "max_tokens" => Self::MaxTokens,
336 "tool_use" => Self::ToolUse,
337 other => Self::Unknown(other.to_string()),
338 }
339 }
340}
341
342impl Serialize for StopReason {
343 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
344 serializer.serialize_str(self.as_str())
345 }
346}
347
348impl<'de> Deserialize<'de> for StopReason {
349 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
350 let s = String::deserialize(deserializer)?;
351 Ok(Self::from(s.as_str()))
352 }
353}
354
355#[derive(Debug, Clone, PartialEq, Eq, Hash)]
357pub enum ApiKeySource {
358 None,
360 User,
361 Project,
362 Org,
363 Temporary,
364 Oauth,
365 Unknown(String),
367}
368
369impl ApiKeySource {
370 pub fn as_str(&self) -> &str {
371 match self {
372 Self::None => "none",
373 Self::User => "user",
374 Self::Project => "project",
375 Self::Org => "org",
376 Self::Temporary => "temporary",
377 Self::Oauth => "oauth",
378 Self::Unknown(s) => s.as_str(),
379 }
380 }
381}
382
383impl fmt::Display for ApiKeySource {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 f.write_str(self.as_str())
386 }
387}
388
389impl From<&str> for ApiKeySource {
390 fn from(s: &str) -> Self {
391 match s {
392 "none" => Self::None,
393 "user" => Self::User,
394 "project" => Self::Project,
395 "org" => Self::Org,
396 "temporary" => Self::Temporary,
397 "oauth" => Self::Oauth,
398 other => Self::Unknown(other.to_string()),
399 }
400 }
401}
402
403impl Serialize for ApiKeySource {
404 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
405 serializer.serialize_str(self.as_str())
406 }
407}
408
409impl<'de> Deserialize<'de> for ApiKeySource {
410 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
411 let s = String::deserialize(deserializer)?;
412 Ok(Self::from(s.as_str()))
413 }
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Hash)]
418pub enum OutputStyle {
419 Default,
421 Unknown(String),
423}
424
425impl OutputStyle {
426 pub fn as_str(&self) -> &str {
427 match self {
428 Self::Default => "default",
429 Self::Unknown(s) => s.as_str(),
430 }
431 }
432}
433
434impl fmt::Display for OutputStyle {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 f.write_str(self.as_str())
437 }
438}
439
440impl From<&str> for OutputStyle {
441 fn from(s: &str) -> Self {
442 match s {
443 "default" => Self::Default,
444 other => Self::Unknown(other.to_string()),
445 }
446 }
447}
448
449impl Serialize for OutputStyle {
450 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
451 serializer.serialize_str(self.as_str())
452 }
453}
454
455impl<'de> Deserialize<'de> for OutputStyle {
456 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
457 let s = String::deserialize(deserializer)?;
458 Ok(Self::from(s.as_str()))
459 }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Hash)]
464pub enum InitPermissionMode {
465 Default,
467 AcceptEdits,
468 BypassPermissions,
469 Plan,
470 DontAsk,
471 Auto,
472 Unknown(String),
474}
475
476impl InitPermissionMode {
477 pub fn as_str(&self) -> &str {
478 match self {
479 Self::Default => "default",
480 Self::AcceptEdits => "acceptEdits",
481 Self::BypassPermissions => "bypassPermissions",
482 Self::Plan => "plan",
483 Self::DontAsk => "dontAsk",
484 Self::Auto => "auto",
485 Self::Unknown(s) => s.as_str(),
486 }
487 }
488}
489
490impl fmt::Display for InitPermissionMode {
491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492 f.write_str(self.as_str())
493 }
494}
495
496impl From<&str> for InitPermissionMode {
497 fn from(s: &str) -> Self {
498 match s {
499 "default" => Self::Default,
500 "acceptEdits" => Self::AcceptEdits,
501 "bypassPermissions" => Self::BypassPermissions,
502 "plan" => Self::Plan,
503 "dontAsk" => Self::DontAsk,
504 "auto" => Self::Auto,
505 other => Self::Unknown(other.to_string()),
506 }
507 }
508}
509
510impl Serialize for InitPermissionMode {
511 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
512 serializer.serialize_str(self.as_str())
513 }
514}
515
516impl<'de> Deserialize<'de> for InitPermissionMode {
517 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
518 let s = String::deserialize(deserializer)?;
519 Ok(Self::from(s.as_str()))
520 }
521}
522
523#[derive(Debug, Clone, PartialEq, Eq, Hash)]
525pub enum StatusMessageStatus {
526 Compacting,
528 Requesting,
530 Unknown(String),
532}
533
534impl StatusMessageStatus {
535 pub fn as_str(&self) -> &str {
536 match self {
537 Self::Compacting => "compacting",
538 Self::Requesting => "requesting",
539 Self::Unknown(s) => s.as_str(),
540 }
541 }
542}
543
544impl fmt::Display for StatusMessageStatus {
545 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546 f.write_str(self.as_str())
547 }
548}
549
550impl From<&str> for StatusMessageStatus {
551 fn from(s: &str) -> Self {
552 match s {
553 "compacting" => Self::Compacting,
554 "requesting" => Self::Requesting,
555 other => Self::Unknown(other.to_string()),
556 }
557 }
558}
559
560impl Serialize for StatusMessageStatus {
561 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
562 serializer.serialize_str(self.as_str())
563 }
564}
565
566impl<'de> Deserialize<'de> for StatusMessageStatus {
567 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
568 let s = String::deserialize(deserializer)?;
569 Ok(Self::from(s.as_str()))
570 }
571}
572
573pub(crate) fn serialize_optional_uuid<S>(
575 uuid: &Option<Uuid>,
576 serializer: S,
577) -> Result<S::Ok, S::Error>
578where
579 S: Serializer,
580{
581 match uuid {
582 Some(id) => serializer.serialize_str(&id.to_string()),
583 None => serializer.serialize_none(),
584 }
585}
586
587pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
589where
590 D: Deserializer<'de>,
591{
592 let opt_str: Option<String> = Option::deserialize(deserializer)?;
593 match opt_str {
594 Some(s) => Uuid::parse_str(&s)
595 .map(Some)
596 .map_err(serde::de::Error::custom),
597 None => Ok(None),
598 }
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
604pub struct MessageOrigin {
605 pub kind: String,
606 #[serde(flatten)]
607 pub extra: serde_json::Map<String, Value>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
612pub struct SummarizeMetadata {
613 pub messages_summarized: u64,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 pub user_context: Option<String>,
616 #[serde(default, skip_serializing_if = "Option::is_none")]
617 pub direction: Option<String>,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
622pub struct McpMeta {
623 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
624 pub meta: Option<Value>,
625 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub structured_content: Option<Value>,
627 #[serde(default, skip_serializing_if = "Vec::is_empty")]
632 pub resource_links: Vec<ResourceLink>,
633}
634
635#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
639pub struct ResourceLink {
640 pub uri: String,
641 pub name: String,
642 #[serde(default, skip_serializing_if = "Option::is_none")]
643 pub title: Option<String>,
644 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub description: Option<String>,
646 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
647 pub mime_type: Option<String>,
648 #[serde(default, skip_serializing_if = "Option::is_none")]
649 pub size: Option<u64>,
650 #[serde(default, skip_serializing_if = "Option::is_none")]
651 pub annotations: Option<Value>,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
656pub struct ToolResultMeta {
657 pub id: String,
659 #[serde(default, skip_serializing_if = "Option::is_none")]
666 pub non_execution_kind: Option<String>,
667 #[serde(default, skip_serializing_if = "Option::is_none")]
669 pub user_feedback: Option<String>,
670 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub remedy: Option<ToolResultRemedy>,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
683 pub permission_decision: Option<ToolPermissionDecision>,
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
690pub struct ToolPermissionDecision {
691 pub decision: PermissionDecisionOutcome,
693 pub source: String,
701 #[serde(default, skip_serializing_if = "Option::is_none")]
707 pub reason_type: Option<PermissionDecisionReasonType>,
708}
709
710#[derive(Debug, Clone, PartialEq, Eq, Hash)]
712pub enum PermissionDecisionOutcome {
713 Accept,
715 Reject,
717 Cancelled,
719 Unknown(String),
721}
722
723impl PermissionDecisionOutcome {
724 pub fn as_str(&self) -> &str {
725 match self {
726 Self::Accept => "accept",
727 Self::Reject => "reject",
728 Self::Cancelled => "cancelled",
729 Self::Unknown(s) => s.as_str(),
730 }
731 }
732}
733
734impl fmt::Display for PermissionDecisionOutcome {
735 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736 f.write_str(self.as_str())
737 }
738}
739
740impl From<&str> for PermissionDecisionOutcome {
741 fn from(s: &str) -> Self {
742 match s {
743 "accept" => Self::Accept,
744 "reject" => Self::Reject,
745 "cancelled" => Self::Cancelled,
746 other => Self::Unknown(other.to_string()),
747 }
748 }
749}
750
751impl Serialize for PermissionDecisionOutcome {
752 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
753 serializer.serialize_str(self.as_str())
754 }
755}
756
757impl<'de> Deserialize<'de> for PermissionDecisionOutcome {
758 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
759 let s = String::deserialize(deserializer)?;
760 Ok(Self::from(s.as_str()))
761 }
762}
763
764#[derive(Debug, Clone, PartialEq, Eq, Hash)]
767pub enum PermissionDecisionReasonType {
768 Rule,
770 Mode,
772 SubcommandResults,
774 PermissionPromptTool,
776 Hook,
778 AsyncAgent,
780 SandboxOverride,
782 WorkingDir,
784 SafetyCheck,
786 Classifier,
788 Other,
790 Unknown(String),
792}
793
794impl PermissionDecisionReasonType {
795 pub fn as_str(&self) -> &str {
796 match self {
797 Self::Rule => "rule",
798 Self::Mode => "mode",
799 Self::SubcommandResults => "subcommandResults",
800 Self::PermissionPromptTool => "permissionPromptTool",
801 Self::Hook => "hook",
802 Self::AsyncAgent => "asyncAgent",
803 Self::SandboxOverride => "sandboxOverride",
804 Self::WorkingDir => "workingDir",
805 Self::SafetyCheck => "safetyCheck",
806 Self::Classifier => "classifier",
807 Self::Other => "other",
808 Self::Unknown(s) => s.as_str(),
809 }
810 }
811}
812
813impl fmt::Display for PermissionDecisionReasonType {
814 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815 f.write_str(self.as_str())
816 }
817}
818
819impl From<&str> for PermissionDecisionReasonType {
820 fn from(s: &str) -> Self {
821 match s {
822 "rule" => Self::Rule,
823 "mode" => Self::Mode,
824 "subcommandResults" => Self::SubcommandResults,
825 "permissionPromptTool" => Self::PermissionPromptTool,
826 "hook" => Self::Hook,
827 "asyncAgent" => Self::AsyncAgent,
828 "sandboxOverride" => Self::SandboxOverride,
829 "workingDir" => Self::WorkingDir,
830 "safetyCheck" => Self::SafetyCheck,
831 "classifier" => Self::Classifier,
832 "other" => Self::Other,
833 other => Self::Unknown(other.to_string()),
834 }
835 }
836}
837
838impl Serialize for PermissionDecisionReasonType {
839 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
840 serializer.serialize_str(self.as_str())
841 }
842}
843
844impl<'de> Deserialize<'de> for PermissionDecisionReasonType {
845 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
846 let s = String::deserialize(deserializer)?;
847 Ok(Self::from(s.as_str()))
848 }
849}
850
851#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
856pub struct ToolResultRemedy {
857 pub kind: ToolResultRemedyKind,
860 #[serde(default, skip_serializing_if = "Option::is_none")]
864 pub servers: Option<Vec<String>>,
865 #[serde(default, skip_serializing_if = "Option::is_none")]
868 pub provider: Option<RemedyLoginProvider>,
869 #[serde(default, skip_serializing_if = "Option::is_none")]
873 pub path: Option<String>,
874 #[serde(default, skip_serializing_if = "Option::is_none")]
879 pub managed: Option<bool>,
880 #[serde(default, skip_serializing_if = "Option::is_none")]
882 pub feature: Option<RemedyFeature>,
883 #[serde(default, skip_serializing_if = "Option::is_none")]
885 pub cause: Option<RemedyFeatureCause>,
886 #[serde(default, skip_serializing_if = "Option::is_none")]
888 pub policy_kind: Option<RemedyPolicyKind>,
889}
890
891#[derive(Debug, Clone, PartialEq, Eq, Hash)]
893pub enum ToolResultRemedyKind {
894 McpNeedsAuth,
896 McpDisabled,
898 McpRequiredMissing,
900 AuthExpired,
902 AuthOverridden,
904 AuthMissingScope,
906 DesignNeedsAuthorization,
908 SandboxViolation,
910 OutsideReadsBlocked,
912 MemoryPaused,
914 FeatureDisabled,
916 PolicyDenied,
919 SpawnArgLimit,
921 StagedForReview,
923 Unknown(String),
925}
926
927impl ToolResultRemedyKind {
928 pub fn as_str(&self) -> &str {
929 match self {
930 Self::McpNeedsAuth => "mcp_needs_auth",
931 Self::McpDisabled => "mcp_disabled",
932 Self::McpRequiredMissing => "mcp_required_missing",
933 Self::AuthExpired => "auth_expired",
934 Self::AuthOverridden => "auth_overridden",
935 Self::AuthMissingScope => "auth_missing_scope",
936 Self::DesignNeedsAuthorization => "design_needs_authorization",
937 Self::SandboxViolation => "sandbox_violation",
938 Self::OutsideReadsBlocked => "outside_reads_blocked",
939 Self::MemoryPaused => "memory_paused",
940 Self::FeatureDisabled => "feature_disabled",
941 Self::PolicyDenied => "policy_denied",
942 Self::SpawnArgLimit => "spawn_arg_limit",
943 Self::StagedForReview => "staged_for_review",
944 Self::Unknown(s) => s.as_str(),
945 }
946 }
947}
948
949impl fmt::Display for ToolResultRemedyKind {
950 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
951 f.write_str(self.as_str())
952 }
953}
954
955impl From<&str> for ToolResultRemedyKind {
956 fn from(s: &str) -> Self {
957 match s {
958 "mcp_needs_auth" => Self::McpNeedsAuth,
959 "mcp_disabled" => Self::McpDisabled,
960 "mcp_required_missing" => Self::McpRequiredMissing,
961 "auth_expired" => Self::AuthExpired,
962 "auth_overridden" => Self::AuthOverridden,
963 "auth_missing_scope" => Self::AuthMissingScope,
964 "design_needs_authorization" => Self::DesignNeedsAuthorization,
965 "sandbox_violation" => Self::SandboxViolation,
966 "outside_reads_blocked" => Self::OutsideReadsBlocked,
967 "memory_paused" => Self::MemoryPaused,
968 "feature_disabled" => Self::FeatureDisabled,
969 "policy_denied" => Self::PolicyDenied,
970 "spawn_arg_limit" => Self::SpawnArgLimit,
971 "staged_for_review" => Self::StagedForReview,
972 other => Self::Unknown(other.to_string()),
973 }
974 }
975}
976
977impl Serialize for ToolResultRemedyKind {
978 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
979 serializer.serialize_str(self.as_str())
980 }
981}
982
983impl<'de> Deserialize<'de> for ToolResultRemedyKind {
984 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
985 let s = String::deserialize(deserializer)?;
986 Ok(Self::from(s.as_str()))
987 }
988}
989
990#[derive(Debug, Clone, PartialEq, Eq, Hash)]
992pub enum RemedyLoginProvider {
993 ClaudeAi,
995 ClaudeDesign,
997 Unknown(String),
999}
1000
1001impl RemedyLoginProvider {
1002 pub fn as_str(&self) -> &str {
1003 match self {
1004 Self::ClaudeAi => "claude_ai",
1005 Self::ClaudeDesign => "claude_design",
1006 Self::Unknown(s) => s.as_str(),
1007 }
1008 }
1009}
1010
1011impl fmt::Display for RemedyLoginProvider {
1012 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1013 f.write_str(self.as_str())
1014 }
1015}
1016
1017impl From<&str> for RemedyLoginProvider {
1018 fn from(s: &str) -> Self {
1019 match s {
1020 "claude_ai" => Self::ClaudeAi,
1021 "claude_design" => Self::ClaudeDesign,
1022 other => Self::Unknown(other.to_string()),
1023 }
1024 }
1025}
1026
1027impl Serialize for RemedyLoginProvider {
1028 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1029 serializer.serialize_str(self.as_str())
1030 }
1031}
1032
1033impl<'de> Deserialize<'de> for RemedyLoginProvider {
1034 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1035 let s = String::deserialize(deserializer)?;
1036 Ok(Self::from(s.as_str()))
1037 }
1038}
1039
1040#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1043pub enum RemedyFeature {
1044 Workflows,
1045 Artifacts,
1046 Unknown(String),
1048}
1049
1050impl RemedyFeature {
1051 pub fn as_str(&self) -> &str {
1052 match self {
1053 Self::Workflows => "workflows",
1054 Self::Artifacts => "artifacts",
1055 Self::Unknown(s) => s.as_str(),
1056 }
1057 }
1058}
1059
1060impl fmt::Display for RemedyFeature {
1061 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1062 f.write_str(self.as_str())
1063 }
1064}
1065
1066impl From<&str> for RemedyFeature {
1067 fn from(s: &str) -> Self {
1068 match s {
1069 "workflows" => Self::Workflows,
1070 "artifacts" => Self::Artifacts,
1071 other => Self::Unknown(other.to_string()),
1072 }
1073 }
1074}
1075
1076impl Serialize for RemedyFeature {
1077 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1078 serializer.serialize_str(self.as_str())
1079 }
1080}
1081
1082impl<'de> Deserialize<'de> for RemedyFeature {
1083 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1084 let s = String::deserialize(deserializer)?;
1085 Ok(Self::from(s.as_str()))
1086 }
1087}
1088
1089#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1092pub enum RemedyFeatureCause {
1093 ManagedSettings,
1096 OrgPolicy,
1098 Unavailable,
1100 UserSetting,
1103 UnsupportedRuntime,
1107 Unknown(String),
1109}
1110
1111impl RemedyFeatureCause {
1112 pub fn as_str(&self) -> &str {
1113 match self {
1114 Self::ManagedSettings => "managed_settings",
1115 Self::OrgPolicy => "org_policy",
1116 Self::Unavailable => "unavailable",
1117 Self::UserSetting => "user_setting",
1118 Self::UnsupportedRuntime => "unsupported_runtime",
1119 Self::Unknown(s) => s.as_str(),
1120 }
1121 }
1122}
1123
1124impl fmt::Display for RemedyFeatureCause {
1125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1126 f.write_str(self.as_str())
1127 }
1128}
1129
1130impl From<&str> for RemedyFeatureCause {
1131 fn from(s: &str) -> Self {
1132 match s {
1133 "managed_settings" => Self::ManagedSettings,
1134 "org_policy" => Self::OrgPolicy,
1135 "unavailable" => Self::Unavailable,
1136 "user_setting" => Self::UserSetting,
1137 "unsupported_runtime" => Self::UnsupportedRuntime,
1138 other => Self::Unknown(other.to_string()),
1139 }
1140 }
1141}
1142
1143impl Serialize for RemedyFeatureCause {
1144 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1145 serializer.serialize_str(self.as_str())
1146 }
1147}
1148
1149impl<'de> Deserialize<'de> for RemedyFeatureCause {
1150 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1151 let s = String::deserialize(deserializer)?;
1152 Ok(Self::from(s.as_str()))
1153 }
1154}
1155
1156#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1159pub enum RemedyPolicyKind {
1160 OrgDenied,
1162 Latched,
1165 Unknown(String),
1167}
1168
1169impl RemedyPolicyKind {
1170 pub fn as_str(&self) -> &str {
1171 match self {
1172 Self::OrgDenied => "org_denied",
1173 Self::Latched => "latched",
1174 Self::Unknown(s) => s.as_str(),
1175 }
1176 }
1177}
1178
1179impl fmt::Display for RemedyPolicyKind {
1180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1181 f.write_str(self.as_str())
1182 }
1183}
1184
1185impl From<&str> for RemedyPolicyKind {
1186 fn from(s: &str) -> Self {
1187 match s {
1188 "org_denied" => Self::OrgDenied,
1189 "latched" => Self::Latched,
1190 other => Self::Unknown(other.to_string()),
1191 }
1192 }
1193}
1194
1195impl Serialize for RemedyPolicyKind {
1196 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1197 serializer.serialize_str(self.as_str())
1198 }
1199}
1200
1201impl<'de> Deserialize<'de> for RemedyPolicyKind {
1202 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1203 let s = String::deserialize(deserializer)?;
1204 Ok(Self::from(s.as_str()))
1205 }
1206}
1207
1208#[derive(Debug, Clone, Serialize, Deserialize)]
1210pub struct UserMessage {
1211 pub message: MessageContent,
1212 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1213 #[serde(
1214 serialize_with = "serialize_optional_uuid",
1215 deserialize_with = "deserialize_optional_uuid"
1216 )]
1217 pub session_id: Option<Uuid>,
1218 #[serde(skip_serializing_if = "Option::is_none")]
1220 pub parent_tool_use_id: Option<String>,
1221 #[serde(skip_serializing_if = "Option::is_none")]
1223 pub uuid: Option<String>,
1224 #[serde(skip_serializing_if = "Option::is_none")]
1226 pub timestamp: Option<String>,
1227 #[serde(skip_serializing_if = "Option::is_none")]
1234 pub tool_use_result: Option<serde_json::Value>,
1235 #[serde(default, skip_serializing_if = "Option::is_none")]
1240 pub agent_id: Option<String>,
1241 #[serde(skip_serializing_if = "Option::is_none")]
1244 pub subagent_type: Option<String>,
1245 #[serde(skip_serializing_if = "Option::is_none")]
1247 pub task_description: Option<String>,
1248 #[serde(skip_serializing_if = "Option::is_none")]
1249 pub origin: Option<MessageOrigin>,
1250 #[serde(skip_serializing_if = "Option::is_none")]
1251 pub priority: Option<String>,
1252 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1253 pub is_synthetic: Option<bool>,
1254 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1255 pub should_query: Option<bool>,
1256 #[serde(default, skip_serializing_if = "Option::is_none")]
1257 pub is_meta: Option<bool>,
1258 #[serde(default, skip_serializing_if = "Option::is_none")]
1259 pub is_visible_in_transcript_only: Option<bool>,
1260 #[serde(default, skip_serializing_if = "Option::is_none")]
1261 pub is_virtual: Option<bool>,
1262 #[serde(default, skip_serializing_if = "Option::is_none")]
1263 pub is_compact_summary: Option<bool>,
1264 #[serde(skip_serializing_if = "Option::is_none")]
1265 pub summarize_metadata: Option<SummarizeMetadata>,
1266 #[serde(skip_serializing_if = "Option::is_none")]
1267 pub mcp_meta: Option<McpMeta>,
1268 #[serde(skip_serializing_if = "Option::is_none")]
1271 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1272 #[serde(skip_serializing_if = "Option::is_none")]
1273 pub source_tool_use_id: Option<String>,
1274 #[serde(skip_serializing_if = "Option::is_none")]
1275 pub source_tool_assistant_uuid: Option<String>,
1276 #[serde(skip_serializing_if = "Option::is_none")]
1277 pub image_paste_ids: Option<Vec<u64>>,
1278 #[serde(skip_serializing_if = "Option::is_none")]
1279 pub client_platform: Option<String>,
1280 #[serde(skip_serializing_if = "Option::is_none")]
1281 pub inbound_origin: Option<String>,
1282 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1283 pub is_replay: Option<bool>,
1284 #[serde(skip_serializing_if = "Option::is_none")]
1285 pub file_attachments: Option<Vec<Value>>,
1286 #[serde(default, skip_serializing_if = "Option::is_none")]
1288 pub seeded_summon: Option<bool>,
1289 #[serde(default, skip_serializing_if = "Option::is_none")]
1292 pub client_composed: Option<bool>,
1293 #[serde(default, skip_serializing_if = "Option::is_none")]
1302 pub text_not_typed: Option<bool>,
1303 #[serde(default, skip_serializing_if = "Option::is_none")]
1308 pub initiator: Option<String>,
1309 #[serde(default, skip_serializing_if = "Option::is_none")]
1315 pub pasted_content: Option<Vec<Value>>,
1316 #[serde(default, skip_serializing_if = "Option::is_none")]
1320 pub historical: Option<bool>,
1321 #[serde(default, skip_serializing_if = "Option::is_none")]
1327 pub server_received_wall_ms: Option<f64>,
1328 #[serde(default, skip_serializing_if = "Option::is_none")]
1332 pub refused_message_id: Option<String>,
1333 #[serde(default, skip_serializing_if = "Option::is_none")]
1340 pub requeue_if_interrupted_within_ms: Option<u64>,
1341}
1342
1343impl UserMessage {
1344 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1352 &self,
1353 ) -> Option<Result<T, serde_json::Error>> {
1354 self.tool_use_result
1355 .as_ref()
1356 .map(|v| serde_json::from_value(v.clone()))
1357 }
1358
1359 pub fn subagent_result(&self) -> Option<SubagentResult> {
1370 self.tool_use_result
1371 .as_ref()
1372 .and_then(|v| serde_json::from_value(v.clone()).ok())
1373 }
1374}
1375
1376#[derive(Debug, Clone, Serialize, Deserialize)]
1387pub struct SubagentResult {
1388 #[serde(skip_serializing_if = "Option::is_none")]
1390 pub status: Option<String>,
1391 #[serde(skip_serializing_if = "Option::is_none")]
1393 pub prompt: Option<String>,
1394 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1396 pub agent_id: Option<String>,
1397 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1399 pub agent_type: Option<String>,
1400 #[serde(
1402 default,
1403 deserialize_with = "deserialize_content_blocks",
1404 skip_serializing_if = "Vec::is_empty"
1405 )]
1406 pub content: Vec<ContentBlock>,
1407 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1409 pub resolved_model: Option<String>,
1410 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1412 pub total_duration_ms: Option<u64>,
1413 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1415 pub total_tokens: Option<u64>,
1416 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1418 pub total_tool_use_count: Option<u64>,
1419 #[serde(skip_serializing_if = "Option::is_none")]
1421 pub usage: Option<super::result::UsageInfo>,
1422 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1424 pub tool_stats: Option<SubagentToolStats>,
1425}
1426
1427#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1432#[serde(rename_all = "camelCase")]
1433pub struct SubagentToolStats {
1434 #[serde(default)]
1435 pub read_count: u64,
1436 #[serde(default)]
1437 pub search_count: u64,
1438 #[serde(default)]
1439 pub bash_count: u64,
1440 #[serde(default)]
1441 pub edit_file_count: u64,
1442 #[serde(default)]
1443 pub lines_added: u64,
1444 #[serde(default)]
1445 pub lines_removed: u64,
1446 #[serde(default)]
1447 pub other_tool_count: u64,
1448 #[serde(flatten)]
1449 pub extra: serde_json::Map<String, Value>,
1450}
1451
1452#[derive(Debug, Clone, Default, PartialEq, Eq)]
1479pub struct SubagentUsageRollup {
1480 pub subagent_tokens: u64,
1483 pub agent_count: u32,
1485 pub tool_uses: u64,
1487 pub duration_ms: u64,
1489 seen_agent_ids: std::collections::BTreeSet<String>,
1490}
1491
1492impl SubagentUsageRollup {
1493 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1499 match output {
1500 ClaudeOutput::User(user) => self.observe_user(user),
1501 _ => false,
1502 }
1503 }
1504
1505 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1512 let Some(result) = user.subagent_result() else {
1513 return false;
1514 };
1515 if result.total_tokens.is_none() && result.agent_id.is_none() {
1516 return false;
1517 }
1518 if let Some(agent_id) = &result.agent_id {
1519 if !self.seen_agent_ids.insert(agent_id.clone()) {
1520 return false;
1521 }
1522 }
1523 self.agent_count += 1;
1524 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1525 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1526 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1527 true
1528 }
1529}
1530
1531#[derive(Debug, Clone, Serialize, Deserialize)]
1533pub struct MessageContent {
1534 pub role: MessageRole,
1535 #[serde(deserialize_with = "deserialize_content_blocks")]
1536 pub content: Vec<ContentBlock>,
1537}
1538
1539#[derive(Debug, Clone, Serialize, Deserialize)]
1541pub struct SystemMessage {
1542 pub subtype: SystemSubtype,
1543 #[serde(flatten)]
1544 pub data: Value, }
1546
1547impl SystemMessage {
1548 pub fn is_init(&self) -> bool {
1550 self.subtype == SystemSubtype::Init
1551 }
1552
1553 pub fn is_status(&self) -> bool {
1555 self.subtype == SystemSubtype::Status
1556 }
1557
1558 pub fn is_compact_boundary(&self) -> bool {
1560 self.subtype == SystemSubtype::CompactBoundary
1561 }
1562
1563 pub fn as_init(&self) -> Option<InitMessage> {
1565 if self.subtype != SystemSubtype::Init {
1566 return None;
1567 }
1568 serde_json::from_value(self.data.clone()).ok()
1569 }
1570
1571 pub fn as_status(&self) -> Option<StatusMessage> {
1573 if self.subtype != SystemSubtype::Status {
1574 return None;
1575 }
1576 serde_json::from_value(self.data.clone()).ok()
1577 }
1578
1579 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1581 if self.subtype != SystemSubtype::CompactBoundary {
1582 return None;
1583 }
1584 serde_json::from_value(self.data.clone()).ok()
1585 }
1586
1587 pub fn is_task_started(&self) -> bool {
1589 self.subtype == SystemSubtype::TaskStarted
1590 }
1591
1592 pub fn is_task_progress(&self) -> bool {
1594 self.subtype == SystemSubtype::TaskProgress
1595 }
1596
1597 pub fn is_task_notification(&self) -> bool {
1599 self.subtype == SystemSubtype::TaskNotification
1600 }
1601
1602 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1604 if self.subtype != SystemSubtype::TaskStarted {
1605 return None;
1606 }
1607 serde_json::from_value(self.data.clone()).ok()
1608 }
1609
1610 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1612 if self.subtype != SystemSubtype::TaskProgress {
1613 return None;
1614 }
1615 serde_json::from_value(self.data.clone()).ok()
1616 }
1617
1618 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1620 if self.subtype != SystemSubtype::TaskNotification {
1621 return None;
1622 }
1623 serde_json::from_value(self.data.clone()).ok()
1624 }
1625
1626 pub fn is_task_updated(&self) -> bool {
1628 self.subtype == SystemSubtype::TaskUpdated
1629 }
1630
1631 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1633 if self.subtype != SystemSubtype::TaskUpdated {
1634 return None;
1635 }
1636 serde_json::from_value(self.data.clone()).ok()
1637 }
1638
1639 pub fn is_thinking_tokens(&self) -> bool {
1641 self.subtype == SystemSubtype::ThinkingTokens
1642 }
1643
1644 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1646 if self.subtype != SystemSubtype::ThinkingTokens {
1647 return None;
1648 }
1649 serde_json::from_value(self.data.clone()).ok()
1650 }
1651
1652 pub fn is_code_change_published(&self) -> bool {
1654 self.subtype == SystemSubtype::CodeChangePublished
1655 }
1656
1657 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1659 if self.subtype != SystemSubtype::CodeChangePublished {
1660 return None;
1661 }
1662 serde_json::from_value(self.data.clone()).ok()
1663 }
1664
1665 pub fn is_vcs_state_changed(&self) -> bool {
1667 self.subtype == SystemSubtype::VcsStateChanged
1668 }
1669
1670 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1672 if self.subtype != SystemSubtype::VcsStateChanged {
1673 return None;
1674 }
1675 serde_json::from_value(self.data.clone()).ok()
1676 }
1677
1678 pub fn is_feedback_draft_queued(&self) -> bool {
1680 self.subtype == SystemSubtype::FeedbackDraftQueued
1681 }
1682
1683 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1685 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1686 return None;
1687 }
1688 serde_json::from_value(self.data.clone()).ok()
1689 }
1690
1691 pub fn is_cloud_session_delta(&self) -> bool {
1693 self.subtype == SystemSubtype::CloudSessionDelta
1694 }
1695
1696 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1698 if self.subtype != SystemSubtype::CloudSessionDelta {
1699 return None;
1700 }
1701 serde_json::from_value(self.data.clone()).ok()
1702 }
1703
1704 pub fn is_dev_intent(&self) -> bool {
1706 self.subtype == SystemSubtype::DevIntent
1707 }
1708
1709 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1711 if self.subtype != SystemSubtype::DevIntent {
1712 return None;
1713 }
1714 serde_json::from_value(self.data.clone()).ok()
1715 }
1716
1717 pub fn is_turn_handoff_available(&self) -> bool {
1719 self.subtype == SystemSubtype::TurnHandoffAvailable
1720 }
1721
1722 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1724 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1725 return None;
1726 }
1727 serde_json::from_value(self.data.clone()).ok()
1728 }
1729
1730 pub fn is_turn_preempted(&self) -> bool {
1732 self.subtype == SystemSubtype::TurnPreempted
1733 }
1734
1735 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1737 if self.subtype != SystemSubtype::TurnPreempted {
1738 return None;
1739 }
1740 serde_json::from_value(self.data.clone()).ok()
1741 }
1742
1743 pub fn is_peer_message_hold(&self) -> bool {
1745 self.subtype == SystemSubtype::PeerMessageHold
1746 }
1747
1748 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1750 if self.subtype != SystemSubtype::PeerMessageHold {
1751 return None;
1752 }
1753 serde_json::from_value(self.data.clone()).ok()
1754 }
1755
1756 pub fn is_per_turn_effort_changed(&self) -> bool {
1758 self.subtype == SystemSubtype::PerTurnEffortChanged
1759 }
1760
1761 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1763 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1764 return None;
1765 }
1766 serde_json::from_value(self.data.clone()).ok()
1767 }
1768
1769 pub fn is_session_title_changed(&self) -> bool {
1771 self.subtype == SystemSubtype::SessionTitleChanged
1772 }
1773
1774 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1776 if self.subtype != SystemSubtype::SessionTitleChanged {
1777 return None;
1778 }
1779 serde_json::from_value(self.data.clone()).ok()
1780 }
1781
1782 pub fn is_instruction_size_warning(&self) -> bool {
1784 self.subtype == SystemSubtype::InstructionSizeWarning
1785 }
1786
1787 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1789 if self.subtype != SystemSubtype::InstructionSizeWarning {
1790 return None;
1791 }
1792 serde_json::from_value(self.data.clone()).ok()
1793 }
1794
1795 pub fn is_permission_check_status(&self) -> bool {
1797 self.subtype == SystemSubtype::PermissionCheckStatus
1798 }
1799
1800 pub fn as_permission_check_status(&self) -> Option<PermissionCheckStatusMessage> {
1802 if self.subtype != SystemSubtype::PermissionCheckStatus {
1803 return None;
1804 }
1805 serde_json::from_value(self.data.clone()).ok()
1806 }
1807
1808 pub fn is_file_attachments_missing(&self) -> bool {
1810 self.subtype == SystemSubtype::FileAttachmentsMissing
1811 }
1812
1813 pub fn as_file_attachments_missing(&self) -> Option<FileAttachmentsMissingMessage> {
1815 if self.subtype != SystemSubtype::FileAttachmentsMissing {
1816 return None;
1817 }
1818 serde_json::from_value(self.data.clone()).ok()
1819 }
1820
1821 pub fn is_ui_focus(&self) -> bool {
1823 self.subtype == SystemSubtype::UiFocus
1824 }
1825
1826 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1828 if self.subtype != SystemSubtype::UiFocus {
1829 return None;
1830 }
1831 serde_json::from_value(self.data.clone()).ok()
1832 }
1833
1834 pub fn is_ui_invalidate(&self) -> bool {
1836 self.subtype == SystemSubtype::UiInvalidate
1837 }
1838
1839 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1841 if self.subtype != SystemSubtype::UiInvalidate {
1842 return None;
1843 }
1844 serde_json::from_value(self.data.clone()).ok()
1845 }
1846
1847 pub fn is_ui_log(&self) -> bool {
1849 self.subtype == SystemSubtype::UiLog
1850 }
1851
1852 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1854 if self.subtype != SystemSubtype::UiLog {
1855 return None;
1856 }
1857 serde_json::from_value(self.data.clone()).ok()
1858 }
1859
1860 pub fn is_ui_panes(&self) -> bool {
1862 self.subtype == SystemSubtype::UiPanes
1863 }
1864
1865 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1867 if self.subtype != SystemSubtype::UiPanes {
1868 return None;
1869 }
1870 serde_json::from_value(self.data.clone()).ok()
1871 }
1872
1873 pub fn is_ui_scroll(&self) -> bool {
1875 self.subtype == SystemSubtype::UiScroll
1876 }
1877
1878 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1880 if self.subtype != SystemSubtype::UiScroll {
1881 return None;
1882 }
1883 serde_json::from_value(self.data.clone()).ok()
1884 }
1885
1886 pub fn is_ui_status(&self) -> bool {
1888 self.subtype == SystemSubtype::UiStatus
1889 }
1890
1891 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1893 if self.subtype != SystemSubtype::UiStatus {
1894 return None;
1895 }
1896 serde_json::from_value(self.data.clone()).ok()
1897 }
1898
1899 pub fn is_ui_toast(&self) -> bool {
1901 self.subtype == SystemSubtype::UiToast
1902 }
1903
1904 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
1906 if self.subtype != SystemSubtype::UiToast {
1907 return None;
1908 }
1909 serde_json::from_value(self.data.clone()).ok()
1910 }
1911
1912 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1914 macro_rules! parse {
1915 ($variant:ident, $ty:ty) => {
1916 serde_json::from_value::<$ty>(self.data.clone())
1917 .ok()
1918 .map(KnownSystemEvent::$variant)
1919 };
1920 }
1921
1922 match self.subtype {
1923 SystemSubtype::Init => parse!(Init, InitMessage),
1924 SystemSubtype::Status => parse!(Status, StatusMessage),
1925 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1926 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1927 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1928 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1929 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1930 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1931 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1932 SystemSubtype::ControlRequestProgress => {
1933 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1934 }
1935 SystemSubtype::ModelRefusalFallback => {
1936 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1937 }
1938 SystemSubtype::ModelRefusalNoFallback => {
1939 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1940 }
1941 SystemSubtype::LocalCommandOutput => {
1942 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1943 }
1944 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1945 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1946 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1947 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1948 SystemSubtype::BackgroundTasksChanged => {
1949 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1950 }
1951 SystemSubtype::SessionStateChanged => {
1952 parse!(SessionStateChanged, SessionStateChangedMessage)
1953 }
1954 SystemSubtype::WorkerShuttingDown => {
1955 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1956 }
1957 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1958 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1959 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1960 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1961 SystemSubtype::ElicitationComplete => {
1962 parse!(ElicitationComplete, ElicitationCompleteMessage)
1963 }
1964 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1965 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1966 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1967 SystemSubtype::CodeChangePublished => {
1968 parse!(CodeChangePublished, CodeChangePublishedMessage)
1969 }
1970 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1971 SystemSubtype::FeedbackDraftQueued => {
1972 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1973 }
1974 SystemSubtype::CloudSessionDelta => {
1975 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1976 }
1977 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1978 SystemSubtype::TurnHandoffAvailable => {
1979 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1980 }
1981 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1982 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1983 SystemSubtype::PerTurnEffortChanged => {
1984 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1985 }
1986 SystemSubtype::SessionTitleChanged => {
1987 parse!(SessionTitleChanged, SessionTitleChangedMessage)
1988 }
1989 SystemSubtype::InstructionSizeWarning => {
1990 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
1991 }
1992 SystemSubtype::FileAttachmentsMissing => {
1993 parse!(FileAttachmentsMissing, FileAttachmentsMissingMessage)
1994 }
1995 SystemSubtype::PermissionCheckStatus => {
1996 parse!(PermissionCheckStatus, PermissionCheckStatusMessage)
1997 }
1998 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
1999 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
2000 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
2001 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
2002 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
2003 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
2004 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
2005 SystemSubtype::Unknown(_) => None,
2006 }
2007 }
2008
2009 pub fn typed_value(&self) -> Option<Value> {
2018 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
2019 parsed.and_then(|v| serde_json::to_value(v).ok())
2020 }
2021 match self.subtype {
2022 SystemSubtype::Init => reserialize(self.as_init()),
2023 SystemSubtype::Status => reserialize(self.as_status()),
2024 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
2025 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
2026 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
2027 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
2028 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
2029 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
2030 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
2031 SystemSubtype::ControlRequestProgress => {
2032 reserialize(parse_system::<ControlRequestProgressMessage>(self))
2033 }
2034 SystemSubtype::ModelRefusalFallback => {
2035 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
2036 }
2037 SystemSubtype::ModelRefusalNoFallback => {
2038 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
2039 }
2040 SystemSubtype::LocalCommandOutput => {
2041 reserialize(parse_system::<LocalCommandOutputMessage>(self))
2042 }
2043 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
2044 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
2045 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
2046 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
2047 SystemSubtype::BackgroundTasksChanged => {
2048 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
2049 }
2050 SystemSubtype::SessionStateChanged => {
2051 reserialize(parse_system::<SessionStateChangedMessage>(self))
2052 }
2053 SystemSubtype::WorkerShuttingDown => {
2054 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
2055 }
2056 SystemSubtype::CommandsChanged => {
2057 reserialize(parse_system::<CommandsChangedMessage>(self))
2058 }
2059 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
2060 SystemSubtype::FilesPersisted => {
2061 reserialize(parse_system::<FilesPersistedMessage>(self))
2062 }
2063 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
2064 SystemSubtype::ElicitationComplete => {
2065 reserialize(parse_system::<ElicitationCompleteMessage>(self))
2066 }
2067 SystemSubtype::PermissionDenied => {
2068 reserialize(parse_system::<PermissionDeniedMessage>(self))
2069 }
2070 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
2071 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
2072 SystemSubtype::CodeChangePublished => {
2073 reserialize(parse_system::<CodeChangePublishedMessage>(self))
2074 }
2075 SystemSubtype::VcsStateChanged => {
2076 reserialize(parse_system::<VcsStateChangedMessage>(self))
2077 }
2078 SystemSubtype::FeedbackDraftQueued => {
2079 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
2080 }
2081 SystemSubtype::CloudSessionDelta => {
2082 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
2083 }
2084 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
2085 SystemSubtype::TurnHandoffAvailable => {
2086 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
2087 }
2088 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
2089 SystemSubtype::PeerMessageHold => {
2090 reserialize(parse_system::<PeerMessageHoldMessage>(self))
2091 }
2092 SystemSubtype::PerTurnEffortChanged => {
2093 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
2094 }
2095 SystemSubtype::SessionTitleChanged => {
2096 reserialize(parse_system::<SessionTitleChangedMessage>(self))
2097 }
2098 SystemSubtype::InstructionSizeWarning => {
2099 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
2100 }
2101 SystemSubtype::FileAttachmentsMissing => {
2102 reserialize(parse_system::<FileAttachmentsMissingMessage>(self))
2103 }
2104 SystemSubtype::PermissionCheckStatus => {
2105 reserialize(parse_system::<PermissionCheckStatusMessage>(self))
2106 }
2107 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
2108 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
2109 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
2110 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
2111 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
2112 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
2113 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
2114 SystemSubtype::Unknown(_) => None,
2115 }
2116 }
2117}
2118
2119fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
2120 serde_json::from_value(message.data.clone()).ok()
2121}
2122
2123#[allow(clippy::large_enum_variant)]
2129#[derive(Debug, Clone, Serialize, Deserialize)]
2130pub enum KnownSystemEvent {
2131 Init(InitMessage),
2132 Status(StatusMessage),
2133 CompactBoundary(CompactBoundaryMessage),
2134 ThinkingTokens(ThinkingTokensMessage),
2135 TaskStarted(TaskStartedMessage),
2136 TaskProgress(TaskProgressMessage),
2137 TaskUpdated(TaskUpdatedMessage),
2138 TaskNotification(TaskNotificationMessage),
2139 ApiRetry(ApiRetryMessage),
2140 ControlRequestProgress(ControlRequestProgressMessage),
2141 ModelRefusalFallback(ModelRefusalFallbackMessage),
2142 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
2143 LocalCommandOutput(LocalCommandOutputMessage),
2144 HookStarted(HookStartedMessage),
2145 HookProgress(HookProgressMessage),
2146 HookResponse(HookResponseMessage),
2147 PluginInstall(PluginInstallMessage),
2148 BackgroundTasksChanged(BackgroundTasksChangedMessage),
2149 SessionStateChanged(SessionStateChangedMessage),
2150 WorkerShuttingDown(WorkerShuttingDownMessage),
2151 CommandsChanged(CommandsChangedMessage),
2152 Notification(NotificationMessage),
2153 FilesPersisted(FilesPersistedMessage),
2154 MemoryRecall(MemoryRecallMessage),
2155 ElicitationComplete(ElicitationCompleteMessage),
2156 PermissionDenied(PermissionDeniedMessage),
2157 MirrorError(MirrorErrorMessage),
2158 Informational(InformationalMessage),
2159 CodeChangePublished(CodeChangePublishedMessage),
2160 VcsStateChanged(VcsStateChangedMessage),
2161 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
2162 CloudSessionDelta(CloudSessionDeltaMessage),
2163 DevIntent(DevIntentMessage),
2164 TurnHandoffAvailable(TurnHandoffAvailableMessage),
2165 TurnPreempted(TurnPreemptedMessage),
2166 PeerMessageHold(PeerMessageHoldMessage),
2167 PerTurnEffortChanged(PerTurnEffortChangedMessage),
2168 SessionTitleChanged(SessionTitleChangedMessage),
2169 InstructionSizeWarning(InstructionSizeWarningMessage),
2170 FileAttachmentsMissing(FileAttachmentsMissingMessage),
2171 PermissionCheckStatus(PermissionCheckStatusMessage),
2172 UiFocus(UiFocusMessage),
2173 UiInvalidate(UiInvalidateMessage),
2174 UiLog(UiLogMessage),
2175 UiPanes(UiPanesMessage),
2176 UiScroll(UiScrollMessage),
2177 UiStatus(UiStatusMessage),
2178 UiToast(UiToastMessage),
2179}
2180
2181#[derive(Debug, Clone, Serialize, Deserialize)]
2182pub struct ApiRetryMessage {
2183 pub attempt: u64,
2184 pub max_retries: u64,
2185 pub retry_delay_ms: u64,
2186 pub error_status: Option<u16>,
2187 pub error: String,
2188 #[serde(default, skip_serializing_if = "Option::is_none")]
2193 pub no_response: Option<ApiRetryNoResponse>,
2194 #[serde(default, skip_serializing_if = "Option::is_none")]
2195 pub uuid: Option<String>,
2196 #[serde(default, skip_serializing_if = "Option::is_none")]
2197 pub session_id: Option<String>,
2198}
2199
2200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2203pub struct ApiRetryNoResponse {
2204 pub waited_ms: u64,
2206 pub retry_wait_ms: u64,
2208}
2209
2210#[derive(Debug, Clone, Serialize, Deserialize)]
2211pub struct ControlRequestProgressMessage {
2212 pub request_id: String,
2213 pub status: String,
2214 #[serde(default, skip_serializing_if = "Option::is_none")]
2215 pub attempt: Option<u64>,
2216 #[serde(default, skip_serializing_if = "Option::is_none")]
2217 pub max_retries: Option<u64>,
2218 #[serde(default, skip_serializing_if = "Option::is_none")]
2219 pub retry_delay_ms: Option<u64>,
2220 #[serde(default, skip_serializing_if = "Option::is_none")]
2221 pub error_status: Option<u16>,
2222 #[serde(default, skip_serializing_if = "Option::is_none")]
2223 pub error: Option<String>,
2224 #[serde(default, skip_serializing_if = "Option::is_none")]
2225 pub uuid: Option<String>,
2226 #[serde(default, skip_serializing_if = "Option::is_none")]
2227 pub session_id: Option<String>,
2228}
2229
2230#[derive(Debug, Clone, Serialize, Deserialize)]
2231pub struct ModelRefusalFallbackMessage {
2232 pub trigger: String,
2233 pub direction: String,
2234 #[serde(default, skip_serializing_if = "Option::is_none")]
2240 pub scope: Option<RefusalFallbackScope>,
2241 pub original_model: String,
2242 pub fallback_model: String,
2243 pub request_id: Option<String>,
2244 #[serde(default, skip_serializing_if = "Option::is_none")]
2245 pub api_refusal_category: Option<String>,
2246 #[serde(default, skip_serializing_if = "Option::is_none")]
2251 pub saw_cyber_refusal: Option<bool>,
2252 #[serde(default, skip_serializing_if = "Option::is_none")]
2253 pub api_refusal_explanation: Option<String>,
2254 #[serde(default, skip_serializing_if = "Option::is_none")]
2255 pub retracted_message_uuids: Option<Vec<String>>,
2256 #[serde(default, skip_serializing_if = "Option::is_none")]
2257 pub refused_user_message_uuid: Option<String>,
2258 pub content: Value,
2259 #[serde(default, skip_serializing_if = "Option::is_none")]
2260 pub uuid: Option<String>,
2261 #[serde(default, skip_serializing_if = "Option::is_none")]
2262 pub session_id: Option<String>,
2263}
2264
2265#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2269pub enum RefusalFallbackScope {
2270 Session,
2272 Local,
2275 Unknown(String),
2277}
2278
2279impl RefusalFallbackScope {
2280 pub fn as_str(&self) -> &str {
2281 match self {
2282 Self::Session => "session",
2283 Self::Local => "local",
2284 Self::Unknown(s) => s.as_str(),
2285 }
2286 }
2287}
2288
2289impl fmt::Display for RefusalFallbackScope {
2290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2291 f.write_str(self.as_str())
2292 }
2293}
2294
2295impl From<&str> for RefusalFallbackScope {
2296 fn from(s: &str) -> Self {
2297 match s {
2298 "session" => Self::Session,
2299 "local" => Self::Local,
2300 other => Self::Unknown(other.to_string()),
2301 }
2302 }
2303}
2304
2305impl Serialize for RefusalFallbackScope {
2306 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2307 serializer.serialize_str(self.as_str())
2308 }
2309}
2310
2311impl<'de> Deserialize<'de> for RefusalFallbackScope {
2312 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2313 let s = String::deserialize(deserializer)?;
2314 Ok(Self::from(s.as_str()))
2315 }
2316}
2317
2318#[derive(Debug, Clone, Serialize, Deserialize)]
2319pub struct ModelRefusalNoFallbackMessage {
2320 pub original_model: String,
2321 pub request_id: Option<String>,
2322 #[serde(default, skip_serializing_if = "Option::is_none")]
2323 pub api_refusal_category: Option<String>,
2324 #[serde(default, skip_serializing_if = "Option::is_none")]
2325 pub api_refusal_explanation: Option<String>,
2326 pub content: Value,
2327 #[serde(default, skip_serializing_if = "Option::is_none")]
2328 pub uuid: Option<String>,
2329 #[serde(default, skip_serializing_if = "Option::is_none")]
2330 pub session_id: Option<String>,
2331}
2332
2333#[derive(Debug, Clone, Serialize, Deserialize)]
2334pub struct LocalCommandOutputMessage {
2335 pub content: String,
2336 #[serde(default, skip_serializing_if = "Option::is_none")]
2337 pub uuid: Option<String>,
2338 #[serde(default, skip_serializing_if = "Option::is_none")]
2339 pub session_id: Option<String>,
2340}
2341
2342#[derive(Debug, Clone, Serialize, Deserialize)]
2343pub struct HookStartedMessage {
2344 pub hook_id: String,
2345 pub hook_name: String,
2346 pub hook_event: String,
2347 #[serde(default, skip_serializing_if = "Option::is_none")]
2348 pub uuid: Option<String>,
2349 #[serde(default, skip_serializing_if = "Option::is_none")]
2350 pub session_id: Option<String>,
2351}
2352
2353#[derive(Debug, Clone, Serialize, Deserialize)]
2354pub struct HookProgressMessage {
2355 pub hook_id: String,
2356 pub hook_name: String,
2357 pub hook_event: String,
2358 #[serde(default, skip_serializing_if = "Option::is_none")]
2359 pub stdout: Option<String>,
2360 #[serde(default, skip_serializing_if = "Option::is_none")]
2361 pub stderr: Option<String>,
2362 #[serde(default, skip_serializing_if = "Option::is_none")]
2363 pub output: Option<String>,
2364 #[serde(default, skip_serializing_if = "Option::is_none")]
2365 pub uuid: Option<String>,
2366 #[serde(default, skip_serializing_if = "Option::is_none")]
2367 pub session_id: Option<String>,
2368}
2369
2370#[derive(Debug, Clone, Serialize, Deserialize)]
2371pub struct HookResponseMessage {
2372 pub hook_id: String,
2373 pub hook_name: String,
2374 pub hook_event: String,
2375 #[serde(default, skip_serializing_if = "Option::is_none")]
2376 pub stdout: Option<String>,
2377 #[serde(default, skip_serializing_if = "Option::is_none")]
2378 pub stderr: Option<String>,
2379 #[serde(default, skip_serializing_if = "Option::is_none")]
2380 pub output: Option<String>,
2381 #[serde(default, skip_serializing_if = "Option::is_none")]
2382 pub exit_code: Option<i32>,
2383 pub outcome: String,
2384 #[serde(default, skip_serializing_if = "Option::is_none")]
2385 pub uuid: Option<String>,
2386 #[serde(default, skip_serializing_if = "Option::is_none")]
2387 pub session_id: Option<String>,
2388}
2389
2390#[derive(Debug, Clone, Serialize, Deserialize)]
2391pub struct PluginInstallMessage {
2392 pub status: String,
2393 #[serde(default, skip_serializing_if = "Option::is_none")]
2394 pub name: Option<String>,
2395 #[serde(default, skip_serializing_if = "Option::is_none")]
2396 pub error: Option<String>,
2397 #[serde(default, skip_serializing_if = "Option::is_none")]
2398 pub uuid: Option<String>,
2399 #[serde(default, skip_serializing_if = "Option::is_none")]
2400 pub session_id: Option<String>,
2401}
2402
2403#[derive(Debug, Clone, Serialize, Deserialize)]
2404pub struct BackgroundTasksChangedMessage {
2405 pub tasks: Vec<BackgroundTaskInfo>,
2406 #[serde(default, skip_serializing_if = "Option::is_none")]
2407 pub uuid: Option<String>,
2408 #[serde(default, skip_serializing_if = "Option::is_none")]
2409 pub session_id: Option<String>,
2410}
2411
2412#[derive(Debug, Clone, Serialize, Deserialize)]
2413pub struct BackgroundTaskInfo {
2414 pub task_id: String,
2415 #[serde(default, skip_serializing_if = "Option::is_none")]
2418 pub run_id: Option<String>,
2419 pub task_type: String,
2420 #[serde(default, skip_serializing_if = "Option::is_none")]
2424 pub subagent_type: Option<String>,
2425 pub description: String,
2426 #[serde(default, skip_serializing_if = "Option::is_none")]
2429 pub parent_task_id: Option<String>,
2430 #[serde(default, skip_serializing_if = "Option::is_none")]
2433 pub ambient: Option<bool>,
2434 #[serde(default, skip_serializing_if = "Option::is_none")]
2437 pub awaited: Option<bool>,
2438}
2439
2440#[derive(Debug, Clone, Serialize, Deserialize)]
2441pub struct SessionStateChangedMessage {
2442 pub state: String,
2443 #[serde(default, skip_serializing_if = "Option::is_none")]
2444 pub uuid: Option<String>,
2445 #[serde(default, skip_serializing_if = "Option::is_none")]
2446 pub session_id: Option<String>,
2447}
2448
2449#[derive(Debug, Clone, Serialize, Deserialize)]
2450pub struct WorkerShuttingDownMessage {
2451 pub reason: String,
2452 #[serde(default, skip_serializing_if = "Option::is_none")]
2453 pub uuid: Option<String>,
2454 #[serde(default, skip_serializing_if = "Option::is_none")]
2455 pub session_id: Option<String>,
2456}
2457
2458#[derive(Debug, Clone, Serialize, Deserialize)]
2459pub struct CommandsChangedMessage {
2460 pub commands: Vec<CommandInfo>,
2461 #[serde(default, skip_serializing_if = "Option::is_none")]
2462 pub uuid: Option<String>,
2463 #[serde(default, skip_serializing_if = "Option::is_none")]
2464 pub session_id: Option<String>,
2465}
2466
2467#[derive(Debug, Clone, Serialize, Deserialize)]
2468pub struct CommandInfo {
2469 pub name: String,
2470 pub description: String,
2471 #[serde(rename = "argumentHint")]
2472 pub argument_hint: String,
2473 #[serde(default, skip_serializing_if = "Option::is_none")]
2474 pub aliases: Option<Vec<String>>,
2475 #[serde(default, skip_serializing_if = "Option::is_none")]
2481 pub builtin: Option<bool>,
2482}
2483
2484#[derive(Debug, Clone, Serialize, Deserialize)]
2485pub struct NotificationMessage {
2486 pub key: String,
2487 pub text: String,
2488 pub priority: String,
2489 #[serde(default, skip_serializing_if = "Option::is_none")]
2490 pub color: Option<String>,
2491 #[serde(default, skip_serializing_if = "Option::is_none")]
2492 pub timeout_ms: Option<u64>,
2493 #[serde(default, skip_serializing_if = "Option::is_none")]
2494 pub uuid: Option<String>,
2495 #[serde(default, skip_serializing_if = "Option::is_none")]
2496 pub session_id: Option<String>,
2497}
2498
2499#[derive(Debug, Clone, Serialize, Deserialize)]
2500pub struct FilesPersistedMessage {
2501 pub files: Vec<PersistedFile>,
2502 pub failed: Vec<FailedPersistedFile>,
2503 pub processed_at: String,
2504 #[serde(default, skip_serializing_if = "Option::is_none")]
2505 pub uuid: Option<String>,
2506 #[serde(default, skip_serializing_if = "Option::is_none")]
2507 pub session_id: Option<String>,
2508}
2509
2510#[derive(Debug, Clone, Serialize, Deserialize)]
2511pub struct PersistedFile {
2512 pub filename: String,
2513 pub file_id: String,
2514}
2515
2516#[derive(Debug, Clone, Serialize, Deserialize)]
2517pub struct FailedPersistedFile {
2518 pub filename: String,
2519 pub error: String,
2520}
2521
2522#[derive(Debug, Clone, Serialize, Deserialize)]
2523pub struct MemoryRecallMessage {
2524 pub mode: String,
2525 pub memories: Vec<MemoryRecallItem>,
2526 #[serde(default, skip_serializing_if = "Option::is_none")]
2527 pub uuid: Option<String>,
2528 #[serde(default, skip_serializing_if = "Option::is_none")]
2529 pub session_id: Option<String>,
2530}
2531
2532#[derive(Debug, Clone, Serialize, Deserialize)]
2533pub struct MemoryRecallItem {
2534 pub path: String,
2535 pub scope: String,
2536 #[serde(default, skip_serializing_if = "Option::is_none")]
2537 pub content: Option<String>,
2538}
2539
2540#[derive(Debug, Clone, Serialize, Deserialize)]
2541pub struct ElicitationCompleteMessage {
2542 pub mcp_server_name: String,
2543 pub elicitation_id: String,
2544 #[serde(default, skip_serializing_if = "Option::is_none")]
2545 pub uuid: Option<String>,
2546 #[serde(default, skip_serializing_if = "Option::is_none")]
2547 pub session_id: Option<String>,
2548}
2549
2550#[derive(Debug, Clone, Serialize, Deserialize)]
2551pub struct PermissionDeniedMessage {
2552 pub tool_name: String,
2553 pub tool_use_id: String,
2554 #[serde(default, skip_serializing_if = "Option::is_none")]
2555 pub agent_id: Option<String>,
2556 #[serde(default, skip_serializing_if = "Option::is_none")]
2557 pub decision_reason_type: Option<String>,
2558 #[serde(default, skip_serializing_if = "Option::is_none")]
2562 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2563 #[serde(default, skip_serializing_if = "Option::is_none")]
2564 pub decision_reason: Option<String>,
2565 pub message: String,
2566 #[serde(default, skip_serializing_if = "Option::is_none")]
2567 pub uuid: Option<String>,
2568 #[serde(default, skip_serializing_if = "Option::is_none")]
2569 pub session_id: Option<String>,
2570}
2571
2572#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2575pub enum PermissionDeniedReasonCode {
2576 ClassifierTranscriptTooLong,
2578 OutsideReadsBlocked,
2580 MemoryPaused,
2582 Unknown(String),
2584}
2585
2586impl PermissionDeniedReasonCode {
2587 pub fn as_str(&self) -> &str {
2588 match self {
2589 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2590 Self::OutsideReadsBlocked => "outside_reads_blocked",
2591 Self::MemoryPaused => "memory_paused",
2592 Self::Unknown(s) => s.as_str(),
2593 }
2594 }
2595}
2596
2597impl fmt::Display for PermissionDeniedReasonCode {
2598 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2599 f.write_str(self.as_str())
2600 }
2601}
2602
2603impl From<&str> for PermissionDeniedReasonCode {
2604 fn from(s: &str) -> Self {
2605 match s {
2606 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2607 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2608 "memory_paused" => Self::MemoryPaused,
2609 other => Self::Unknown(other.to_string()),
2610 }
2611 }
2612}
2613
2614impl Serialize for PermissionDeniedReasonCode {
2615 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2616 serializer.serialize_str(self.as_str())
2617 }
2618}
2619
2620impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2621 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2622 let s = String::deserialize(deserializer)?;
2623 Ok(Self::from(s.as_str()))
2624 }
2625}
2626
2627#[derive(Debug, Clone, Serialize, Deserialize)]
2628pub struct MirrorErrorMessage {
2629 pub error: String,
2630 pub key: MirrorErrorKey,
2631 #[serde(default, skip_serializing_if = "Option::is_none")]
2632 pub uuid: Option<String>,
2633 #[serde(default, skip_serializing_if = "Option::is_none")]
2634 pub session_id: Option<String>,
2635}
2636
2637#[derive(Debug, Clone, Serialize, Deserialize)]
2638pub struct MirrorErrorKey {
2639 #[serde(rename = "projectKey")]
2640 pub project_key: String,
2641 #[serde(rename = "sessionId")]
2642 pub session_id: String,
2643 #[serde(default, skip_serializing_if = "Option::is_none")]
2644 pub subpath: Option<String>,
2645}
2646
2647#[derive(Debug, Clone, Serialize, Deserialize)]
2648pub struct InformationalMessage {
2649 pub content: String,
2650 pub level: String,
2651 #[serde(default, skip_serializing_if = "Option::is_none")]
2652 pub tool_use_id: Option<String>,
2653 #[serde(default, skip_serializing_if = "Option::is_none")]
2654 pub prevent_continuation: Option<bool>,
2655 #[serde(default, skip_serializing_if = "Option::is_none")]
2658 pub tag: Option<String>,
2659 #[serde(default, skip_serializing_if = "Option::is_none")]
2665 pub prompt_submit_outcome: Option<PromptSubmitOutcome>,
2666 #[serde(default, skip_serializing_if = "Option::is_none")]
2667 pub uuid: Option<String>,
2668 #[serde(default, skip_serializing_if = "Option::is_none")]
2669 pub session_id: Option<String>,
2670}
2671
2672#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2675pub struct PromptSubmitOutcome {
2676 #[serde(default, skip_serializing_if = "Option::is_none")]
2680 pub kind: Option<PromptSubmitOutcomeKind>,
2681 #[serde(default, skip_serializing_if = "Option::is_none")]
2688 pub no_verdict_kind: Option<String>,
2689 pub prompt_kept: bool,
2692 #[serde(default, skip_serializing_if = "Option::is_none")]
2696 pub reason: Option<String>,
2697}
2698
2699#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2701pub enum PromptSubmitOutcomeKind {
2702 BlockedByHook,
2704 StoppedByHook,
2706 DroppedByHook,
2708 NotPassedOn,
2710 NoVerdict,
2713 Other(String),
2715}
2716
2717impl PromptSubmitOutcomeKind {
2718 pub fn as_str(&self) -> &str {
2719 match self {
2720 Self::BlockedByHook => "blocked_by_hook",
2721 Self::StoppedByHook => "stopped_by_hook",
2722 Self::DroppedByHook => "dropped_by_hook",
2723 Self::NotPassedOn => "not_passed_on",
2724 Self::NoVerdict => "no_verdict",
2725 Self::Other(s) => s.as_str(),
2726 }
2727 }
2728}
2729
2730impl fmt::Display for PromptSubmitOutcomeKind {
2731 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2732 f.write_str(self.as_str())
2733 }
2734}
2735
2736impl From<&str> for PromptSubmitOutcomeKind {
2737 fn from(s: &str) -> Self {
2738 match s {
2739 "blocked_by_hook" => Self::BlockedByHook,
2740 "stopped_by_hook" => Self::StoppedByHook,
2741 "dropped_by_hook" => Self::DroppedByHook,
2742 "not_passed_on" => Self::NotPassedOn,
2743 "no_verdict" => Self::NoVerdict,
2744 other => Self::Other(other.to_string()),
2745 }
2746 }
2747}
2748
2749impl Serialize for PromptSubmitOutcomeKind {
2750 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2751 serializer.serialize_str(self.as_str())
2752 }
2753}
2754
2755impl<'de> Deserialize<'de> for PromptSubmitOutcomeKind {
2756 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2757 let s = String::deserialize(deserializer)?;
2758 Ok(Self::from(s.as_str()))
2759 }
2760}
2761
2762#[derive(Debug, Clone, Serialize, Deserialize)]
2764pub struct PluginInfo {
2765 pub name: String,
2767 pub path: String,
2769 #[serde(skip_serializing_if = "Option::is_none")]
2771 pub source: Option<String>,
2772 #[serde(default, skip_serializing_if = "Option::is_none")]
2774 pub version: Option<String>,
2775}
2776
2777#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2779pub struct PluginDiagnostic {
2780 pub plugin: String,
2781 #[serde(rename = "type")]
2782 pub diagnostic_type: String,
2783 pub message: String,
2784}
2785
2786#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2788pub struct MemoryPaths {
2789 #[serde(default, skip_serializing_if = "Option::is_none")]
2790 pub auto: Option<String>,
2791 #[serde(default, skip_serializing_if = "Option::is_none")]
2792 pub team: Option<String>,
2793 #[serde(flatten)]
2794 pub extra: serde_json::Map<String, Value>,
2795}
2796
2797#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2801pub struct McpServerError {
2802 pub name: String,
2803 #[serde(rename = "type")]
2805 pub error_type: String,
2806 pub message: String,
2807}
2808
2809#[derive(Debug, Clone, Serialize, Deserialize)]
2811pub struct InitMessage {
2812 pub session_id: String,
2814 #[serde(skip_serializing_if = "Option::is_none")]
2816 pub cwd: Option<String>,
2817 #[serde(skip_serializing_if = "Option::is_none")]
2819 pub model: Option<String>,
2820 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2822 pub tools: Vec<String>,
2823 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2825 pub mcp_servers: Vec<Value>,
2826 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2828 pub slash_commands: Vec<String>,
2829 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2832 pub terminal_slash_commands: Vec<String>,
2833 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2835 pub agents: Vec<String>,
2836 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2838 pub plugins: Vec<PluginInfo>,
2839 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2841 pub skills: Vec<Value>,
2842 #[serde(skip_serializing_if = "Option::is_none")]
2844 pub claude_code_version: Option<String>,
2845 #[serde(skip_serializing_if = "Option::is_none")]
2848 pub messaging_socket_path: Option<String>,
2849 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2851 pub api_key_source: Option<ApiKeySource>,
2852 #[serde(skip_serializing_if = "Option::is_none")]
2854 pub output_style: Option<OutputStyle>,
2855 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2857 pub permission_mode: Option<InitPermissionMode>,
2858
2859 #[serde(skip_serializing_if = "Option::is_none")]
2861 pub uuid: Option<String>,
2862
2863 #[serde(skip_serializing_if = "Option::is_none")]
2865 pub memory_paths: Option<MemoryPaths>,
2866
2867 #[serde(default, skip_serializing_if = "Option::is_none")]
2872 pub scratchpad_path: Option<String>,
2873
2874 #[serde(skip_serializing_if = "Option::is_none")]
2876 pub fast_mode_state: Option<String>,
2877
2878 #[serde(default, skip_serializing_if = "Option::is_none")]
2880 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2881
2882 #[serde(default, skip_serializing_if = "Option::is_none")]
2885 pub mcp_server_errors: Option<Vec<McpServerError>>,
2886
2887 #[serde(default, skip_serializing_if = "Option::is_none")]
2889 pub analytics_disabled: Option<bool>,
2890
2891 #[serde(default, skip_serializing_if = "Option::is_none")]
2893 pub product_feedback_disabled: Option<bool>,
2894
2895 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2897 pub betas: Vec<String>,
2898
2899 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2901 pub capabilities: Vec<String>,
2902
2903 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2905 pub plugin_errors: Vec<PluginDiagnostic>,
2906
2907 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2909 pub plugin_warnings: Vec<PluginDiagnostic>,
2910
2911 #[serde(default, skip_serializing_if = "Option::is_none")]
2916 pub effort: Option<String>,
2917
2918 #[serde(default, skip_serializing_if = "Option::is_none")]
2923 pub cloud_session: Option<Value>,
2924
2925 #[serde(default, skip_serializing_if = "Option::is_none")]
2929 pub footer_indicator: Option<FooterIndicator>,
2930
2931 #[serde(default, skip_serializing_if = "Option::is_none")]
2935 pub worker_epoch: Option<u64>,
2936
2937 #[serde(default, skip_serializing_if = "Option::is_none")]
2941 pub powershell_path: Option<Option<String>>,
2942
2943 #[serde(default, skip_serializing_if = "Option::is_none")]
2947 pub startup_timing: Option<serde_json::Map<String, Value>>,
2948
2949 #[serde(default, skip_serializing_if = "Option::is_none")]
2959 pub per_turn_effort_active: Option<bool>,
2960
2961 #[serde(default, skip_serializing_if = "Option::is_none")]
2968 pub view_mode: Option<ViewMode>,
2969
2970 #[serde(default, skip_serializing_if = "Option::is_none")]
2977 pub original_cwd: Option<String>,
2978
2979 #[serde(default, skip_serializing_if = "Option::is_none")]
2988 pub additional_directories: Option<Vec<String>>,
2989}
2990
2991#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2995pub struct FooterIndicator {
2996 pub text: String,
2999}
3000
3001#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3004pub enum ViewMode {
3005 Focus,
3008 Default,
3010 Unknown(String),
3012}
3013
3014impl ViewMode {
3015 pub fn as_str(&self) -> &str {
3016 match self {
3017 Self::Focus => "focus",
3018 Self::Default => "default",
3019 Self::Unknown(s) => s.as_str(),
3020 }
3021 }
3022}
3023
3024impl fmt::Display for ViewMode {
3025 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3026 f.write_str(self.as_str())
3027 }
3028}
3029
3030impl From<&str> for ViewMode {
3031 fn from(s: &str) -> Self {
3032 match s {
3033 "focus" => Self::Focus,
3034 "default" => Self::Default,
3035 other => Self::Unknown(other.to_string()),
3036 }
3037 }
3038}
3039
3040impl Serialize for ViewMode {
3041 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3042 serializer.serialize_str(self.as_str())
3043 }
3044}
3045
3046impl<'de> Deserialize<'de> for ViewMode {
3047 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3048 let s = String::deserialize(deserializer)?;
3049 Ok(Self::from(s.as_str()))
3050 }
3051}
3052
3053#[derive(Debug, Clone, Serialize, Deserialize)]
3055pub struct StatusMessage {
3056 pub session_id: String,
3058 pub status: Option<StatusMessageStatus>,
3060 #[serde(skip_serializing_if = "Option::is_none")]
3062 pub uuid: Option<String>,
3063 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3065 pub permission_mode: Option<InitPermissionMode>,
3066 #[serde(skip_serializing_if = "Option::is_none")]
3067 pub compact_result: Option<String>,
3068 #[serde(skip_serializing_if = "Option::is_none")]
3069 pub compact_error: Option<String>,
3070 #[serde(default, skip_serializing_if = "Option::is_none")]
3077 pub compact_output_chars: Option<u64>,
3078}
3079
3080#[derive(Debug, Clone, Serialize, Deserialize)]
3082pub struct CompactBoundaryMessage {
3083 pub session_id: String,
3085 pub compact_metadata: CompactMetadata,
3087 #[serde(
3091 default,
3092 skip_serializing_if = "Option::is_none",
3093 alias = "content",
3094 alias = "text"
3095 )]
3096 pub summary: Option<String>,
3097 #[serde(
3101 default,
3102 skip_serializing_if = "Option::is_none",
3103 alias = "message_count"
3104 )]
3105 pub leaf_message_count: Option<u32>,
3106 #[serde(default, skip_serializing_if = "Option::is_none")]
3108 pub duration_ms: Option<u64>,
3109 #[serde(skip_serializing_if = "Option::is_none")]
3111 pub uuid: Option<String>,
3112 #[serde(skip_serializing_if = "Option::is_none")]
3114 pub logical_parent_uuid: Option<Option<String>>,
3115 #[serde(default, skip_serializing_if = "Option::is_none")]
3119 pub historical: Option<bool>,
3120}
3121
3122#[derive(Debug, Clone, Serialize, Deserialize)]
3124pub struct CompactMetadata {
3125 pub pre_tokens: u64,
3127 pub trigger: CompactionTrigger,
3129 #[serde(default, skip_serializing_if = "Option::is_none")]
3130 pub post_tokens: Option<u64>,
3131 #[serde(default, skip_serializing_if = "Option::is_none")]
3132 pub cumulative_dropped_tokens: Option<u64>,
3133 #[serde(default, skip_serializing_if = "Option::is_none")]
3134 pub duration_ms: Option<u64>,
3135 #[serde(default, skip_serializing_if = "Option::is_none")]
3136 pub user_context: Option<String>,
3137 #[serde(default, skip_serializing_if = "Option::is_none")]
3138 pub messages_summarized: Option<u64>,
3139 #[serde(default, skip_serializing_if = "Option::is_none")]
3140 pub precomputed: Option<bool>,
3141 #[serde(default, skip_serializing_if = "Option::is_none")]
3142 pub pre_compact_discovered_tools: Option<Vec<String>>,
3143 #[serde(default, skip_serializing_if = "Option::is_none")]
3148 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
3149 #[serde(default, skip_serializing_if = "Option::is_none")]
3150 pub preserved_segment: Option<PreservedSegment>,
3151 #[serde(default, skip_serializing_if = "Option::is_none")]
3152 pub preserved_messages: Option<PreservedMessages>,
3153}
3154
3155#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3158pub struct ArtifactReadVersion {
3159 pub slug: String,
3161 pub ver: String,
3163}
3164
3165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3166pub struct PreservedSegment {
3167 pub head_uuid: String,
3168 pub anchor_uuid: String,
3169 pub tail_uuid: String,
3170}
3171
3172#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3173pub struct PreservedMessages {
3174 pub anchor_uuid: String,
3175 pub uuids: Vec<String>,
3176 #[serde(default, skip_serializing_if = "Option::is_none")]
3177 pub all_uuids: Option<Vec<String>>,
3178}
3179
3180#[derive(Debug, Clone, Serialize, Deserialize)]
3186pub struct TaskUsage {
3187 pub duration_ms: u64,
3189 pub tool_uses: u64,
3191 pub total_tokens: u64,
3193}
3194
3195#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3197pub enum TaskType {
3198 LocalAgent,
3200 LocalBash,
3202 LocalWorkflow,
3204 Unknown(String),
3206}
3207
3208impl TaskType {
3209 pub fn as_str(&self) -> &str {
3210 match self {
3211 Self::LocalAgent => "local_agent",
3212 Self::LocalBash => "local_bash",
3213 Self::LocalWorkflow => "local_workflow",
3214 Self::Unknown(s) => s.as_str(),
3215 }
3216 }
3217}
3218
3219impl fmt::Display for TaskType {
3220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3221 f.write_str(self.as_str())
3222 }
3223}
3224
3225impl From<&str> for TaskType {
3226 fn from(s: &str) -> Self {
3227 match s {
3228 "local_agent" => Self::LocalAgent,
3229 "local_bash" => Self::LocalBash,
3230 "local_workflow" => Self::LocalWorkflow,
3231 other => Self::Unknown(other.to_string()),
3232 }
3233 }
3234}
3235
3236impl Serialize for TaskType {
3237 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3238 serializer.serialize_str(self.as_str())
3239 }
3240}
3241
3242impl<'de> Deserialize<'de> for TaskType {
3243 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3244 let s = String::deserialize(deserializer)?;
3245 Ok(Self::from(s.as_str()))
3246 }
3247}
3248
3249#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3251pub enum TaskStatus {
3252 Pending,
3253 Running,
3254 Completed,
3255 Failed,
3256 Killed,
3257 Paused,
3258 Stopped,
3259 Unknown(String),
3260}
3261
3262impl TaskStatus {
3263 pub fn as_str(&self) -> &str {
3264 match self {
3265 Self::Pending => "pending",
3266 Self::Running => "running",
3267 Self::Completed => "completed",
3268 Self::Failed => "failed",
3269 Self::Killed => "killed",
3270 Self::Paused => "paused",
3271 Self::Stopped => "stopped",
3272 Self::Unknown(s) => s.as_str(),
3273 }
3274 }
3275}
3276
3277impl fmt::Display for TaskStatus {
3278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3279 f.write_str(self.as_str())
3280 }
3281}
3282
3283impl From<&str> for TaskStatus {
3284 fn from(s: &str) -> Self {
3285 match s {
3286 "pending" => Self::Pending,
3287 "running" => Self::Running,
3288 "completed" => Self::Completed,
3289 "failed" => Self::Failed,
3290 "killed" => Self::Killed,
3291 "paused" => Self::Paused,
3292 "stopped" => Self::Stopped,
3293 other => Self::Unknown(other.to_string()),
3294 }
3295 }
3296}
3297
3298impl Serialize for TaskStatus {
3299 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3300 serializer.serialize_str(self.as_str())
3301 }
3302}
3303
3304impl<'de> Deserialize<'de> for TaskStatus {
3305 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3306 let s = String::deserialize(deserializer)?;
3307 Ok(Self::from(s.as_str()))
3308 }
3309}
3310
3311#[derive(Debug, Clone, Serialize, Deserialize)]
3313pub struct TaskStartedMessage {
3314 pub session_id: String,
3315 pub task_id: String,
3316 #[serde(default, skip_serializing_if = "Option::is_none")]
3322 pub run_id: Option<String>,
3323 #[serde(default, skip_serializing_if = "Option::is_none")]
3329 pub parent_task_id: Option<String>,
3330 #[serde(default, skip_serializing_if = "Option::is_none")]
3331 pub task_type: Option<TaskType>,
3332 #[serde(default, skip_serializing_if = "Option::is_none")]
3333 pub tool_use_id: Option<String>,
3334 pub description: String,
3335 #[serde(default, skip_serializing_if = "Option::is_none")]
3338 pub subagent_type: Option<String>,
3339 #[serde(default, skip_serializing_if = "Option::is_none")]
3345 pub is_backgrounded: Option<bool>,
3346 #[serde(default, skip_serializing_if = "Option::is_none")]
3354 pub awaited: Option<bool>,
3355 #[serde(default, skip_serializing_if = "Option::is_none")]
3359 pub spawn_depth: Option<u32>,
3360 #[serde(default, skip_serializing_if = "Option::is_none")]
3362 pub prompt: Option<String>,
3363 #[serde(default, skip_serializing_if = "Option::is_none")]
3364 pub workflow_name: Option<String>,
3365 #[serde(default, skip_serializing_if = "Option::is_none")]
3366 pub skip_transcript: Option<bool>,
3367 #[serde(default, skip_serializing_if = "Option::is_none")]
3372 pub ambient: Option<bool>,
3373 pub uuid: String,
3374}
3375
3376#[derive(Debug, Clone, Serialize, Deserialize)]
3380pub struct TaskUpdatedMessage {
3381 pub session_id: String,
3382 pub task_id: String,
3383 #[serde(default, skip_serializing_if = "Option::is_none")]
3386 pub run_id: Option<String>,
3387 pub patch: TaskPatch,
3388 pub uuid: String,
3389}
3390
3391#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3394pub struct TaskPatch {
3395 #[serde(default, skip_serializing_if = "Option::is_none")]
3396 pub status: Option<TaskStatus>,
3397 #[serde(default, skip_serializing_if = "Option::is_none")]
3400 pub end_time: Option<u64>,
3401 #[serde(default, skip_serializing_if = "Option::is_none")]
3402 pub description: Option<String>,
3403 #[serde(default, skip_serializing_if = "Option::is_none")]
3404 pub total_paused_ms: Option<u64>,
3405 #[serde(default, skip_serializing_if = "Option::is_none")]
3406 pub error: Option<String>,
3407 #[serde(default, skip_serializing_if = "Option::is_none")]
3408 pub is_backgrounded: Option<bool>,
3409}
3410
3411#[derive(Debug, Clone, Serialize, Deserialize)]
3414pub struct ThinkingTokensMessage {
3415 pub session_id: String,
3416 pub estimated_tokens: u64,
3418 pub estimated_tokens_delta: u64,
3420 #[serde(default, skip_serializing_if = "Option::is_none")]
3427 pub user_message_uuid: Option<String>,
3428 pub uuid: String,
3429}
3430
3431#[derive(Debug, Clone, Serialize, Deserialize)]
3434pub struct TaskProgressMessage {
3435 pub session_id: String,
3436 pub task_id: String,
3437 #[serde(default, skip_serializing_if = "Option::is_none")]
3440 pub run_id: Option<String>,
3441 #[serde(default, skip_serializing_if = "Option::is_none")]
3442 pub tool_use_id: Option<String>,
3443 pub description: String,
3444 #[serde(default, skip_serializing_if = "Option::is_none")]
3445 pub last_tool_name: Option<String>,
3446 pub usage: TaskUsage,
3447 #[serde(default, skip_serializing_if = "Option::is_none")]
3449 pub subagent_type: Option<String>,
3450 #[serde(default, skip_serializing_if = "Option::is_none")]
3451 pub summary: Option<String>,
3452 pub uuid: String,
3453}
3454
3455#[derive(Debug, Clone, Serialize, Deserialize)]
3458pub struct TaskNotificationMessage {
3459 pub session_id: String,
3460 pub task_id: String,
3461 #[serde(default, skip_serializing_if = "Option::is_none")]
3464 pub run_id: Option<String>,
3465 pub status: TaskStatus,
3466 #[serde(default, skip_serializing_if = "Option::is_none")]
3469 pub reason: Option<TaskEndReason>,
3470 pub summary: String,
3471 pub output_file: Option<String>,
3472 #[serde(skip_serializing_if = "Option::is_none")]
3473 pub tool_use_id: Option<String>,
3474 #[serde(skip_serializing_if = "Option::is_none")]
3475 pub usage: Option<TaskUsage>,
3476 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3482 pub resource_links: Vec<ResourceLink>,
3483 #[serde(default, skip_serializing_if = "Option::is_none")]
3490 pub handback: Option<TaskHandback>,
3491 #[serde(default, skip_serializing_if = "Option::is_none")]
3495 pub handback_report: Option<HandbackReport>,
3496 #[serde(default, skip_serializing_if = "Option::is_none")]
3497 pub skip_transcript: Option<bool>,
3498 #[serde(default, skip_serializing_if = "Option::is_none")]
3501 pub ambient: Option<bool>,
3502 #[serde(skip_serializing_if = "Option::is_none")]
3503 pub uuid: Option<String>,
3504}
3505
3506#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3509pub enum TaskHandback {
3510 Send,
3512 Flagged,
3514 Withheld,
3516 Unknown(String),
3518}
3519
3520impl TaskHandback {
3521 pub fn as_str(&self) -> &str {
3522 match self {
3523 Self::Send => "send",
3524 Self::Flagged => "flagged",
3525 Self::Withheld => "withheld",
3526 Self::Unknown(s) => s.as_str(),
3527 }
3528 }
3529}
3530
3531impl fmt::Display for TaskHandback {
3532 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3533 f.write_str(self.as_str())
3534 }
3535}
3536
3537impl From<&str> for TaskHandback {
3538 fn from(s: &str) -> Self {
3539 match s {
3540 "send" => Self::Send,
3541 "flagged" => Self::Flagged,
3542 "withheld" => Self::Withheld,
3543 other => Self::Unknown(other.to_string()),
3544 }
3545 }
3546}
3547
3548impl Serialize for TaskHandback {
3549 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3550 serializer.serialize_str(self.as_str())
3551 }
3552}
3553
3554impl<'de> Deserialize<'de> for TaskHandback {
3555 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3556 let s = String::deserialize(deserializer)?;
3557 Ok(Self::from(s.as_str()))
3558 }
3559}
3560
3561#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3564pub struct HandbackReport {
3565 pub text: String,
3568 #[serde(default, skip_serializing_if = "Option::is_none")]
3571 pub warning: Option<String>,
3572}
3573
3574#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3576pub enum AssistantErrorKind {
3577 AuthenticationFailed,
3578 OauthOrgNotAllowed,
3579 AccountOnHold,
3580 BillingError,
3581 RateLimit,
3582 Overloaded,
3583 InvalidRequest,
3584 ModelNotFound,
3585 ServerError,
3586 UnknownError,
3587 MaxOutputTokens,
3588 Unknown(String),
3589}
3590
3591impl AssistantErrorKind {
3592 pub fn as_str(&self) -> &str {
3593 match self {
3594 Self::AuthenticationFailed => "authentication_failed",
3595 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3596 Self::AccountOnHold => "account_on_hold",
3597 Self::BillingError => "billing_error",
3598 Self::RateLimit => "rate_limit",
3599 Self::Overloaded => "overloaded",
3600 Self::InvalidRequest => "invalid_request",
3601 Self::ModelNotFound => "model_not_found",
3602 Self::ServerError => "server_error",
3603 Self::UnknownError => "unknown",
3604 Self::MaxOutputTokens => "max_output_tokens",
3605 Self::Unknown(s) => s.as_str(),
3606 }
3607 }
3608}
3609
3610impl fmt::Display for AssistantErrorKind {
3611 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3612 f.write_str(self.as_str())
3613 }
3614}
3615
3616impl From<&str> for AssistantErrorKind {
3617 fn from(s: &str) -> Self {
3618 match s {
3619 "authentication_failed" => Self::AuthenticationFailed,
3620 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3621 "account_on_hold" => Self::AccountOnHold,
3622 "billing_error" => Self::BillingError,
3623 "rate_limit" => Self::RateLimit,
3624 "overloaded" => Self::Overloaded,
3625 "invalid_request" => Self::InvalidRequest,
3626 "model_not_found" => Self::ModelNotFound,
3627 "server_error" => Self::ServerError,
3628 "unknown" => Self::UnknownError,
3629 "max_output_tokens" => Self::MaxOutputTokens,
3630 other => Self::Unknown(other.to_string()),
3631 }
3632 }
3633}
3634
3635impl Serialize for AssistantErrorKind {
3636 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3637 serializer.serialize_str(self.as_str())
3638 }
3639}
3640
3641impl<'de> Deserialize<'de> for AssistantErrorKind {
3642 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3643 let s = String::deserialize(deserializer)?;
3644 Ok(Self::from(s.as_str()))
3645 }
3646}
3647
3648#[derive(Debug, Clone, Serialize, Deserialize)]
3655pub struct CodeChangePublishedMessage {
3656 pub provider: String,
3660 pub url: String,
3662 pub repo: String,
3665 pub identifier: String,
3667 #[serde(default, skip_serializing_if = "Option::is_none")]
3676 pub action: Option<String>,
3677 #[serde(default, skip_serializing_if = "Option::is_none")]
3685 pub branch: Option<String>,
3686 pub uuid: String,
3687 pub session_id: String,
3688}
3689
3690#[derive(Debug, Clone, Serialize, Deserialize)]
3695pub struct VcsStateChangedMessage {
3696 pub kind: VcsMutationKind,
3699 pub cwd: String,
3702 #[serde(default, skip_serializing_if = "Option::is_none")]
3707 pub branch: Option<String>,
3708 pub uuid: String,
3709 pub session_id: String,
3710}
3711
3712#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3714pub enum VcsMutationKind {
3715 Commit,
3716 Push,
3717 Merge,
3718 Rebase,
3719 Unknown(String),
3721}
3722
3723impl VcsMutationKind {
3724 pub fn as_str(&self) -> &str {
3725 match self {
3726 Self::Commit => "commit",
3727 Self::Push => "push",
3728 Self::Merge => "merge",
3729 Self::Rebase => "rebase",
3730 Self::Unknown(s) => s.as_str(),
3731 }
3732 }
3733}
3734
3735impl fmt::Display for VcsMutationKind {
3736 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3737 f.write_str(self.as_str())
3738 }
3739}
3740
3741impl From<&str> for VcsMutationKind {
3742 fn from(s: &str) -> Self {
3743 match s {
3744 "commit" => Self::Commit,
3745 "push" => Self::Push,
3746 "merge" => Self::Merge,
3747 "rebase" => Self::Rebase,
3748 other => Self::Unknown(other.to_string()),
3749 }
3750 }
3751}
3752
3753impl Serialize for VcsMutationKind {
3754 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3755 serializer.serialize_str(self.as_str())
3756 }
3757}
3758
3759impl<'de> Deserialize<'de> for VcsMutationKind {
3760 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3761 let s = String::deserialize(deserializer)?;
3762 Ok(Self::from(s.as_str()))
3763 }
3764}
3765
3766#[derive(Debug, Clone, Serialize, Deserialize)]
3768pub struct FeedbackDraftQueuedMessage {
3769 pub draft_id: String,
3770 pub draft_type: String,
3771 pub title: String,
3772 pub details_preview: String,
3773 #[serde(default, skip_serializing_if = "Option::is_none")]
3774 pub uuid: Option<String>,
3775 #[serde(default, skip_serializing_if = "Option::is_none")]
3776 pub session_id: Option<String>,
3777 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3778 pub extra: serde_json::Map<String, Value>,
3779}
3780
3781#[derive(Debug, Clone, Serialize, Deserialize)]
3789pub struct CloudSessionDeltaMessage {
3790 pub seq: u64,
3794 pub changed: Vec<String>,
3798 pub cloud_session: Value,
3802 pub uuid: String,
3803 pub session_id: String,
3804 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3805 pub extra: serde_json::Map<String, Value>,
3806}
3807
3808#[derive(Debug, Clone, Serialize, Deserialize)]
3824pub struct DevIntentMessage {
3825 pub kind: DevIntentKind,
3827 #[serde(default, skip_serializing_if = "Option::is_none")]
3833 pub trigger: Option<DevIntentTrigger>,
3834 pub uuid: String,
3835 pub session_id: String,
3836 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3837 pub extra: serde_json::Map<String, Value>,
3838}
3839
3840#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3844pub enum DevIntentKind {
3845 IosApp,
3851 AndroidApp,
3858 Unknown(String),
3860}
3861
3862impl DevIntentKind {
3863 pub fn as_str(&self) -> &str {
3864 match self {
3865 Self::IosApp => "ios_app",
3866 Self::AndroidApp => "android_app",
3867 Self::Unknown(s) => s.as_str(),
3868 }
3869 }
3870}
3871
3872impl fmt::Display for DevIntentKind {
3873 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3874 f.write_str(self.as_str())
3875 }
3876}
3877
3878impl From<&str> for DevIntentKind {
3879 fn from(s: &str) -> Self {
3880 match s {
3881 "ios_app" => Self::IosApp,
3882 "android_app" => Self::AndroidApp,
3883 other => Self::Unknown(other.to_string()),
3884 }
3885 }
3886}
3887
3888impl Serialize for DevIntentKind {
3889 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3890 serializer.serialize_str(self.as_str())
3891 }
3892}
3893
3894impl<'de> Deserialize<'de> for DevIntentKind {
3895 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3896 let s = String::deserialize(deserializer)?;
3897 Ok(Self::from(s.as_str()))
3898 }
3899}
3900
3901#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3905pub enum DevIntentTrigger {
3906 SwiftEdit,
3908 UikitImport,
3910 XcodeProject,
3913 IosCommand,
3916 KotlinEdit,
3918 JavaEdit,
3920 AndroidImport,
3922 AndroidManifest,
3924 GradlePlugin,
3926 AndroidCommand,
3929 ProjectScan,
3932 Unknown(String),
3934}
3935
3936impl DevIntentTrigger {
3937 pub fn as_str(&self) -> &str {
3938 match self {
3939 Self::SwiftEdit => "swift_edit",
3940 Self::UikitImport => "uikit_import",
3941 Self::XcodeProject => "xcode_project",
3942 Self::IosCommand => "ios_command",
3943 Self::KotlinEdit => "kotlin_edit",
3944 Self::JavaEdit => "java_edit",
3945 Self::AndroidImport => "android_import",
3946 Self::AndroidManifest => "android_manifest",
3947 Self::GradlePlugin => "gradle_plugin",
3948 Self::AndroidCommand => "android_command",
3949 Self::ProjectScan => "project_scan",
3950 Self::Unknown(s) => s.as_str(),
3951 }
3952 }
3953}
3954
3955impl fmt::Display for DevIntentTrigger {
3956 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3957 f.write_str(self.as_str())
3958 }
3959}
3960
3961impl From<&str> for DevIntentTrigger {
3962 fn from(s: &str) -> Self {
3963 match s {
3964 "swift_edit" => Self::SwiftEdit,
3965 "uikit_import" => Self::UikitImport,
3966 "xcode_project" => Self::XcodeProject,
3967 "ios_command" => Self::IosCommand,
3968 "kotlin_edit" => Self::KotlinEdit,
3969 "java_edit" => Self::JavaEdit,
3970 "android_import" => Self::AndroidImport,
3971 "android_manifest" => Self::AndroidManifest,
3972 "gradle_plugin" => Self::GradlePlugin,
3973 "android_command" => Self::AndroidCommand,
3974 "project_scan" => Self::ProjectScan,
3975 other => Self::Unknown(other.to_string()),
3976 }
3977 }
3978}
3979
3980impl Serialize for DevIntentTrigger {
3981 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3982 serializer.serialize_str(self.as_str())
3983 }
3984}
3985
3986impl<'de> Deserialize<'de> for DevIntentTrigger {
3987 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3988 let s = String::deserialize(deserializer)?;
3989 Ok(Self::from(s.as_str()))
3990 }
3991}
3992
3993#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3997pub enum TaskEndReason {
3998 WorkerRestart,
4001 Unknown(String),
4003}
4004
4005impl TaskEndReason {
4006 pub fn as_str(&self) -> &str {
4007 match self {
4008 Self::WorkerRestart => "worker_restart",
4009 Self::Unknown(s) => s.as_str(),
4010 }
4011 }
4012}
4013
4014impl fmt::Display for TaskEndReason {
4015 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4016 f.write_str(self.as_str())
4017 }
4018}
4019
4020impl From<&str> for TaskEndReason {
4021 fn from(s: &str) -> Self {
4022 match s {
4023 "worker_restart" => Self::WorkerRestart,
4024 other => Self::Unknown(other.to_string()),
4025 }
4026 }
4027}
4028
4029impl Serialize for TaskEndReason {
4030 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4031 serializer.serialize_str(self.as_str())
4032 }
4033}
4034
4035impl<'de> Deserialize<'de> for TaskEndReason {
4036 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4037 let s = String::deserialize(deserializer)?;
4038 Ok(Self::from(s.as_str()))
4039 }
4040}
4041
4042#[derive(Debug, Clone, Serialize, Deserialize)]
4051pub struct TurnHandoffAvailableMessage {
4052 pub v: u64,
4054 pub tools: Vec<String>,
4056 pub worker_epoch: u64,
4058 #[serde(default, skip_serializing_if = "Option::is_none")]
4062 pub relay_marker: Option<bool>,
4063 #[serde(default, skip_serializing_if = "Option::is_none")]
4071 pub staged_files: Option<bool>,
4072 #[serde(default, skip_serializing_if = "Option::is_none")]
4079 pub no_query_first: Option<bool>,
4080 #[serde(default, skip_serializing_if = "Option::is_none")]
4088 pub home_files: Option<bool>,
4089 #[serde(default, skip_serializing_if = "Option::is_none")]
4093 pub file_names: Option<bool>,
4094 #[serde(default, skip_serializing_if = "Option::is_none")]
4104 pub carried_writes: Option<bool>,
4105 #[serde(default, skip_serializing_if = "Option::is_none")]
4119 pub hydrates_carried_lines: Option<bool>,
4120 #[serde(default, skip_serializing_if = "Option::is_none")]
4126 pub shared_message_id: Option<bool>,
4127 pub uuid: String,
4128 pub session_id: String,
4129 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4130 pub extra: serde_json::Map<String, Value>,
4131}
4132
4133#[derive(Debug, Clone, Serialize, Deserialize)]
4145pub struct TurnPreemptedMessage {
4146 pub reason: String,
4150 pub preempted_by_uuid: String,
4154 #[serde(default)]
4157 pub preempted_message_uuids: Vec<String>,
4158 pub uuid: String,
4159 pub session_id: String,
4160 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4161 pub extra: serde_json::Map<String, Value>,
4162}
4163
4164#[derive(Debug, Clone, Serialize, Deserialize)]
4172pub struct PeerMessageHoldMessage {
4173 pub state: PeerMessageHoldState,
4179 #[serde(default, skip_serializing_if = "Option::is_none")]
4186 pub message_uuid: Option<String>,
4187 pub lane: PeerMessageLane,
4189 pub from: String,
4194 #[serde(default, skip_serializing_if = "Option::is_none")]
4197 pub from_name: Option<String>,
4198 #[serde(default, skip_serializing_if = "Option::is_none")]
4200 pub cause: Option<PeerMessageHoldCause>,
4201 #[serde(default, skip_serializing_if = "Option::is_none")]
4203 pub outcome: Option<PeerMessageHoldOutcome>,
4204 pub uuid: String,
4205 pub session_id: String,
4206 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4207 pub extra: serde_json::Map<String, Value>,
4208}
4209
4210#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4212pub enum PeerMessageHoldState {
4213 Held,
4215 Released,
4217 Dropped,
4219 Unknown(String),
4221}
4222
4223impl PeerMessageHoldState {
4224 pub fn as_str(&self) -> &str {
4225 match self {
4226 Self::Held => "held",
4227 Self::Released => "released",
4228 Self::Dropped => "dropped",
4229 Self::Unknown(s) => s.as_str(),
4230 }
4231 }
4232}
4233
4234impl fmt::Display for PeerMessageHoldState {
4235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4236 f.write_str(self.as_str())
4237 }
4238}
4239
4240impl From<&str> for PeerMessageHoldState {
4241 fn from(s: &str) -> Self {
4242 match s {
4243 "held" => Self::Held,
4244 "released" => Self::Released,
4245 "dropped" => Self::Dropped,
4246 other => Self::Unknown(other.to_string()),
4247 }
4248 }
4249}
4250
4251impl Serialize for PeerMessageHoldState {
4252 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4253 serializer.serialize_str(self.as_str())
4254 }
4255}
4256
4257impl<'de> Deserialize<'de> for PeerMessageHoldState {
4258 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4259 let s = String::deserialize(deserializer)?;
4260 Ok(Self::from(s.as_str()))
4261 }
4262}
4263
4264#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4266pub enum PeerMessageLane {
4267 Bridge,
4269 Stdin,
4271 Socket,
4273 Unknown(String),
4275}
4276
4277impl PeerMessageLane {
4278 pub fn as_str(&self) -> &str {
4279 match self {
4280 Self::Bridge => "bridge",
4281 Self::Stdin => "stdin",
4282 Self::Socket => "socket",
4283 Self::Unknown(s) => s.as_str(),
4284 }
4285 }
4286}
4287
4288impl fmt::Display for PeerMessageLane {
4289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4290 f.write_str(self.as_str())
4291 }
4292}
4293
4294impl From<&str> for PeerMessageLane {
4295 fn from(s: &str) -> Self {
4296 match s {
4297 "bridge" => Self::Bridge,
4298 "stdin" => Self::Stdin,
4299 "socket" => Self::Socket,
4300 other => Self::Unknown(other.to_string()),
4301 }
4302 }
4303}
4304
4305impl Serialize for PeerMessageLane {
4306 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4307 serializer.serialize_str(self.as_str())
4308 }
4309}
4310
4311impl<'de> Deserialize<'de> for PeerMessageLane {
4312 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4313 let s = String::deserialize(deserializer)?;
4314 Ok(Self::from(s.as_str()))
4315 }
4316}
4317
4318#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4325pub enum PeerMessageHoldCause {
4326 ExplicitSetting,
4327 ManagedSetting,
4328 RepoSetting,
4329 InvalidSetting,
4330 BypassDefault,
4331 ModeUnknown,
4332 ModeMismatch,
4333 NoModeAsserted,
4334 Unknown(String),
4336}
4337
4338impl PeerMessageHoldCause {
4339 pub fn as_str(&self) -> &str {
4340 match self {
4341 Self::ExplicitSetting => "explicit-setting",
4342 Self::ManagedSetting => "managed-setting",
4343 Self::RepoSetting => "repo-setting",
4344 Self::InvalidSetting => "invalid-setting",
4345 Self::BypassDefault => "bypass-default",
4346 Self::ModeUnknown => "mode-unknown",
4347 Self::ModeMismatch => "mode-mismatch",
4348 Self::NoModeAsserted => "no-mode-asserted",
4349 Self::Unknown(s) => s.as_str(),
4350 }
4351 }
4352}
4353
4354impl fmt::Display for PeerMessageHoldCause {
4355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4356 f.write_str(self.as_str())
4357 }
4358}
4359
4360impl From<&str> for PeerMessageHoldCause {
4361 fn from(s: &str) -> Self {
4362 match s {
4363 "explicit-setting" => Self::ExplicitSetting,
4364 "managed-setting" => Self::ManagedSetting,
4365 "repo-setting" => Self::RepoSetting,
4366 "invalid-setting" => Self::InvalidSetting,
4367 "bypass-default" => Self::BypassDefault,
4368 "mode-unknown" => Self::ModeUnknown,
4369 "mode-mismatch" => Self::ModeMismatch,
4370 "no-mode-asserted" => Self::NoModeAsserted,
4371 other => Self::Unknown(other.to_string()),
4372 }
4373 }
4374}
4375
4376impl Serialize for PeerMessageHoldCause {
4377 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4378 serializer.serialize_str(self.as_str())
4379 }
4380}
4381
4382impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4383 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4384 let s = String::deserialize(deserializer)?;
4385 Ok(Self::from(s.as_str()))
4386 }
4387}
4388
4389#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4392pub enum PeerMessageHoldOutcome {
4393 Expired,
4397 Refused,
4399 Dropped,
4402 Discarded,
4404 Unknown(String),
4406}
4407
4408impl PeerMessageHoldOutcome {
4409 pub fn as_str(&self) -> &str {
4410 match self {
4411 Self::Expired => "expired",
4412 Self::Refused => "refused",
4413 Self::Dropped => "dropped",
4414 Self::Discarded => "discarded",
4415 Self::Unknown(s) => s.as_str(),
4416 }
4417 }
4418}
4419
4420impl fmt::Display for PeerMessageHoldOutcome {
4421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4422 f.write_str(self.as_str())
4423 }
4424}
4425
4426impl From<&str> for PeerMessageHoldOutcome {
4427 fn from(s: &str) -> Self {
4428 match s {
4429 "expired" => Self::Expired,
4430 "refused" => Self::Refused,
4431 "dropped" => Self::Dropped,
4432 "discarded" => Self::Discarded,
4433 other => Self::Unknown(other.to_string()),
4434 }
4435 }
4436}
4437
4438impl Serialize for PeerMessageHoldOutcome {
4439 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4440 serializer.serialize_str(self.as_str())
4441 }
4442}
4443
4444impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4445 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4446 let s = String::deserialize(deserializer)?;
4447 Ok(Self::from(s.as_str()))
4448 }
4449}
4450
4451#[derive(Debug, Clone, Serialize, Deserialize)]
4462pub struct PerTurnEffortChangedMessage {
4463 pub per_turn_effort_active: bool,
4465 pub uuid: String,
4466 pub session_id: String,
4467}
4468
4469#[derive(Debug, Clone, Serialize, Deserialize)]
4476pub struct InstructionSizeWarningMessage {
4477 pub total_chars: u64,
4480 pub total_limit_chars: u64,
4483 pub file_count: u64,
4486 #[serde(default, skip_serializing_if = "Option::is_none")]
4490 pub largest_chars: Option<u64>,
4491 pub uuid: String,
4492 pub session_id: String,
4493}
4494
4495#[derive(Debug, Clone, Serialize, Deserialize)]
4502pub struct FileAttachmentsMissingMessage {
4503 pub message_uuid: String,
4506 pub sent_count: u64,
4508 pub missing: Vec<MissingFileAttachment>,
4510 pub uuid: String,
4511 pub session_id: String,
4512}
4513
4514#[derive(Debug, Clone, Serialize, Deserialize)]
4524pub struct PermissionCheckStatusMessage {
4525 pub tool_use_id: String,
4526 #[serde(default, skip_serializing_if = "Option::is_none")]
4529 pub agent_id: Option<String>,
4530 pub status: PermissionCheckStatus,
4531 pub uuid: String,
4532 pub session_id: String,
4533}
4534
4535#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4537pub enum PermissionCheckStatus {
4538 Checking,
4541 Done,
4543 Unknown(String),
4545}
4546
4547impl PermissionCheckStatus {
4548 pub fn as_str(&self) -> &str {
4549 match self {
4550 Self::Checking => "checking",
4551 Self::Done => "done",
4552 Self::Unknown(s) => s.as_str(),
4553 }
4554 }
4555}
4556
4557impl fmt::Display for PermissionCheckStatus {
4558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4559 f.write_str(self.as_str())
4560 }
4561}
4562
4563impl From<&str> for PermissionCheckStatus {
4564 fn from(s: &str) -> Self {
4565 match s {
4566 "checking" => Self::Checking,
4567 "done" => Self::Done,
4568 other => Self::Unknown(other.to_string()),
4569 }
4570 }
4571}
4572
4573impl Serialize for PermissionCheckStatus {
4574 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4575 serializer.serialize_str(self.as_str())
4576 }
4577}
4578
4579impl<'de> Deserialize<'de> for PermissionCheckStatus {
4580 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4581 let s = String::deserialize(deserializer)?;
4582 Ok(Self::from(s.as_str()))
4583 }
4584}
4585
4586#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4588pub struct MissingFileAttachment {
4589 pub file_uuid: String,
4591 pub reason: String,
4596}
4597
4598#[derive(Debug, Clone, Serialize, Deserialize)]
4608pub struct SessionTitleChangedMessage {
4609 pub title: String,
4614 pub uuid: String,
4615 pub session_id: String,
4616}
4617
4618#[derive(Debug, Clone, Serialize, Deserialize)]
4622pub struct UiLogMessage {
4623 pub plugin: String,
4625 pub text: String,
4629 pub uuid: String,
4630 pub session_id: String,
4631}
4632
4633#[derive(Debug, Clone, Serialize, Deserialize)]
4638pub struct UiToastMessage {
4639 pub plugin: String,
4641 pub text: String,
4645 pub timeout_ms: u64,
4648 pub uuid: String,
4649 pub session_id: String,
4650}
4651
4652#[derive(Debug, Clone, Serialize, Deserialize)]
4656pub struct UiStatusMessage {
4657 pub plugin: String,
4659 pub text: Option<String>,
4664 pub uuid: String,
4665 pub session_id: String,
4666}
4667
4668#[derive(Debug, Clone, Serialize, Deserialize)]
4673pub struct UiInvalidateMessage {
4674 pub event: String,
4677 #[serde(default, skip_serializing_if = "Option::is_none")]
4682 pub instances: Option<Vec<UiInvalidatedInstance>>,
4683 #[serde(default, skip_serializing_if = "Option::is_none")]
4691 pub prompts_screened: Option<bool>,
4692 pub uuid: String,
4693 pub session_id: String,
4694}
4695
4696#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4698pub struct UiInvalidatedInstance {
4699 pub surface: String,
4701 pub component: String,
4703 pub instance_id: String,
4706}
4707
4708#[derive(Debug, Clone, Serialize, Deserialize)]
4715pub struct UiPanesMessage {
4716 pub panes: Vec<UiPane>,
4719 pub shown_id: Option<String>,
4722 pub focused_id: Option<String>,
4725 pub focus_requested_id: Option<String>,
4729 pub uuid: String,
4730 pub session_id: String,
4731}
4732
4733#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4735pub struct UiPane {
4736 pub id: String,
4739 pub title: String,
4742 pub plugin: String,
4744 #[serde(default, skip_serializing_if = "Option::is_none")]
4747 pub close_on_escape: Option<bool>,
4748 #[serde(default, skip_serializing_if = "Option::is_none")]
4751 pub hold_toasts: Option<bool>,
4752 #[serde(default, skip_serializing_if = "Option::is_none")]
4755 pub rows: Option<u64>,
4756 #[serde(default, skip_serializing_if = "Option::is_none")]
4759 pub columns: Option<u64>,
4760}
4761
4762#[derive(Debug, Clone, Serialize, Deserialize)]
4766pub struct UiScrollMessage {
4767 pub client_id: String,
4769 pub component: UiSiteComponent,
4771 pub instance_id: String,
4773 pub offset: u64,
4776 #[serde(default, skip_serializing_if = "Option::is_none")]
4779 pub follow_end: Option<bool>,
4780 pub uuid: String,
4781 pub session_id: String,
4782}
4783
4784#[derive(Debug, Clone, Serialize, Deserialize)]
4789pub struct UiFocusMessage {
4790 pub client_id: String,
4792 pub component: UiSiteComponent,
4794 pub instance_id: String,
4796 pub plugin: String,
4798 pub key: String,
4800 pub uuid: String,
4801 pub session_id: String,
4802}
4803
4804#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4806pub enum UiSiteComponent {
4807 Pane,
4809 AbovePrompt,
4811 Unknown(String),
4813}
4814
4815impl UiSiteComponent {
4816 pub fn as_str(&self) -> &str {
4817 match self {
4818 Self::Pane => "Pane",
4819 Self::AbovePrompt => "AbovePrompt",
4820 Self::Unknown(s) => s.as_str(),
4821 }
4822 }
4823}
4824
4825impl fmt::Display for UiSiteComponent {
4826 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4827 f.write_str(self.as_str())
4828 }
4829}
4830
4831impl From<&str> for UiSiteComponent {
4832 fn from(s: &str) -> Self {
4833 match s {
4834 "Pane" => Self::Pane,
4835 "AbovePrompt" => Self::AbovePrompt,
4836 other => Self::Unknown(other.to_string()),
4837 }
4838 }
4839}
4840
4841impl Serialize for UiSiteComponent {
4842 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4843 serializer.serialize_str(self.as_str())
4844 }
4845}
4846
4847impl<'de> Deserialize<'de> for UiSiteComponent {
4848 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4849 let s = String::deserialize(deserializer)?;
4850 Ok(Self::from(s.as_str()))
4851 }
4852}
4853
4854#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4857pub struct LocalCommandRun {
4858 pub command: String,
4860 pub args: String,
4863 #[serde(default, skip_serializing_if = "Option::is_none")]
4868 pub plugin: Option<String>,
4869}
4870
4871#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4875pub struct LocalCommandOutcome {
4876 pub kind: LocalCommandOutcomeKind,
4877 #[serde(default, skip_serializing_if = "Option::is_none")]
4880 pub suggestion: Option<String>,
4881}
4882
4883#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4888pub enum LocalCommandOutcomeKind {
4889 UnavailableHeadless,
4891 Unknown,
4894 Failed,
4896 RestartRequired,
4898 Other(String),
4900}
4901
4902impl LocalCommandOutcomeKind {
4903 pub fn as_str(&self) -> &str {
4904 match self {
4905 Self::UnavailableHeadless => "unavailable_headless",
4906 Self::Unknown => "unknown",
4907 Self::Failed => "failed",
4908 Self::RestartRequired => "restart_required",
4909 Self::Other(s) => s.as_str(),
4910 }
4911 }
4912}
4913
4914impl fmt::Display for LocalCommandOutcomeKind {
4915 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4916 f.write_str(self.as_str())
4917 }
4918}
4919
4920impl From<&str> for LocalCommandOutcomeKind {
4921 fn from(s: &str) -> Self {
4922 match s {
4923 "unavailable_headless" => Self::UnavailableHeadless,
4924 "unknown" => Self::Unknown,
4925 "failed" => Self::Failed,
4926 "restart_required" => Self::RestartRequired,
4927 other => Self::Other(other.to_string()),
4928 }
4929 }
4930}
4931
4932impl Serialize for LocalCommandOutcomeKind {
4933 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4934 serializer.serialize_str(self.as_str())
4935 }
4936}
4937
4938impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4939 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4940 let s = String::deserialize(deserializer)?;
4941 Ok(Self::from(s.as_str()))
4942 }
4943}
4944
4945#[derive(Debug, Clone, Serialize, Deserialize)]
4951pub struct UsageReport {
4952 pub session: super::control::UsageSession,
4954 pub rate_limits: Option<UsageReportRateLimits>,
4958 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4959 pub extra: serde_json::Map<String, Value>,
4960}
4961
4962#[derive(Debug, Clone, Serialize, Deserialize)]
4964pub struct UsageReportRateLimits {
4965 pub limits: Option<Vec<UsageReportLimit>>,
4973 #[serde(default, skip_serializing_if = "Option::is_none")]
4976 pub extra_usage: Option<UsageReportExtraUsage>,
4977 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4978 pub extra: serde_json::Map<String, Value>,
4979}
4980
4981#[derive(Debug, Clone, Serialize, Deserialize)]
4983pub struct UsageReportLimit {
4984 pub kind: String,
4987 pub group: String,
4990 pub percent: f64,
4992 pub resets_at: Option<String>,
4994 #[serde(default, skip_serializing_if = "Option::is_none")]
4997 pub scope: Option<UsageReportScope>,
4998 #[serde(default, skip_serializing_if = "Option::is_none")]
5002 pub severity: Option<String>,
5003 #[serde(default, skip_serializing_if = "Option::is_none")]
5005 pub is_active: Option<bool>,
5006 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5007 pub extra: serde_json::Map<String, Value>,
5008}
5009
5010#[derive(Debug, Clone, Serialize, Deserialize)]
5012pub struct UsageReportScope {
5013 #[serde(default, skip_serializing_if = "Option::is_none")]
5014 pub model: Option<UsageReportScopeLabel>,
5015 #[serde(default, skip_serializing_if = "Option::is_none")]
5016 pub surface: Option<UsageReportScopeLabel>,
5017 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5018 pub extra: serde_json::Map<String, Value>,
5019}
5020
5021#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5023pub struct UsageReportScopeLabel {
5024 pub display_name: String,
5025}
5026
5027#[derive(Debug, Clone, Serialize, Deserialize)]
5031pub struct UsageReportExtraUsage {
5032 pub is_enabled: bool,
5034 pub monthly_limit: Option<f64>,
5035 pub used_credits: Option<f64>,
5036 pub utilization: Option<f64>,
5037 #[serde(default, skip_serializing_if = "Option::is_none")]
5038 pub currency: Option<String>,
5039 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5040 pub extra: serde_json::Map<String, Value>,
5041}
5042
5043#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5046pub struct BatchToolUse {
5047 pub id: String,
5048 pub name: String,
5049}
5050
5051#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5056pub struct ContextUsage {
5057 pub model: String,
5059 pub total_tokens: u64,
5062 pub raw_max_tokens: u64,
5065 pub percentage: u64,
5067 #[serde(default, skip_serializing_if = "Option::is_none")]
5069 pub over_limit: Option<ContextOverLimit>,
5070 #[serde(default)]
5072 pub categories: Vec<ContextCategory>,
5073 #[serde(default)]
5075 pub mcp_tools: Vec<ContextMcpTool>,
5076 #[serde(default)]
5078 pub memory_files: Vec<ContextMemoryFile>,
5079 #[serde(default)]
5081 pub agents: Vec<ContextAgent>,
5082 #[serde(default, skip_serializing_if = "Option::is_none")]
5084 pub skills: Option<Vec<ContextSkill>>,
5085}
5086
5087#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5089pub struct ContextOverLimit {
5090 pub tokens_over: u64,
5091 pub kind: String,
5094}
5095
5096#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5098pub struct ContextCategory {
5099 pub name: String,
5102 pub tokens: u64,
5103 pub kind: String,
5107}
5108
5109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5111pub struct ContextMcpTool {
5112 pub name: String,
5114 pub server_name: String,
5115 pub tokens: u64,
5116}
5117
5118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5120pub struct ContextMemoryFile {
5121 pub path: String,
5122 #[serde(rename = "type")]
5124 pub file_type: String,
5125 pub tokens: u64,
5126}
5127
5128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5130pub struct ContextAgent {
5131 pub agent_type: String,
5132 pub source: String,
5134 pub tokens: u64,
5135}
5136
5137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5139pub struct ContextSkill {
5140 pub name: String,
5141 pub source: String,
5143 #[serde(default, skip_serializing_if = "Option::is_none")]
5144 pub plugin_name: Option<String>,
5145 pub tokens: u64,
5146}
5147
5148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5150pub struct ToolUseMeta {
5151 pub id: String,
5152 pub display_name: String,
5153 #[serde(default, skip_serializing_if = "Option::is_none")]
5154 pub server_display_name: Option<String>,
5155 #[serde(default, skip_serializing_if = "Option::is_none")]
5156 pub icon_url: Option<String>,
5157 #[serde(default, skip_serializing_if = "Option::is_none")]
5162 pub server_id: Option<String>,
5163 #[serde(default, skip_serializing_if = "Option::is_none")]
5171 pub server_id_stamp: Option<String>,
5172 #[serde(default, skip_serializing_if = "Option::is_none")]
5175 pub tool_name: Option<String>,
5176}
5177
5178#[derive(Debug, Clone, Serialize, Deserialize)]
5180pub struct AssistantMessage {
5181 pub message: AssistantMessageContent,
5182 #[serde(alias = "sessionId")]
5183 pub session_id: String,
5184 #[serde(skip_serializing_if = "Option::is_none")]
5185 pub uuid: Option<String>,
5186 #[serde(skip_serializing_if = "Option::is_none")]
5187 pub parent_tool_use_id: Option<String>,
5188 #[serde(skip_serializing_if = "Option::is_none")]
5190 pub request_id: Option<String>,
5191 #[serde(default, skip_serializing_if = "Option::is_none")]
5196 pub refused_message_id: Option<String>,
5197 #[serde(default, skip_serializing_if = "Option::is_none")]
5206 pub user_message_uuid: Option<String>,
5207 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5213 pub user_message_uuids: Vec<String>,
5214 #[serde(default, skip_serializing_if = "Option::is_none")]
5218 pub agent_id: Option<String>,
5219 #[serde(skip_serializing_if = "Option::is_none")]
5222 pub subagent_type: Option<String>,
5223 #[serde(skip_serializing_if = "Option::is_none")]
5225 pub task_description: Option<String>,
5226 #[serde(skip_serializing_if = "Option::is_none")]
5227 pub error: Option<AssistantErrorKind>,
5228 #[serde(default, skip_serializing_if = "Option::is_none")]
5232 pub aborted: Option<bool>,
5233 #[serde(default, skip_serializing_if = "Option::is_none")]
5238 pub resumed_from_incomplete_thinking: Option<bool>,
5239 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5240 pub supersedes: Vec<String>,
5241 #[serde(skip_serializing_if = "Option::is_none")]
5242 pub timestamp: Option<String>,
5243 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5244 pub tool_use_meta: Vec<ToolUseMeta>,
5245 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5250 pub batch_tool_uses: Vec<BatchToolUse>,
5251 #[serde(default, skip_serializing_if = "Option::is_none")]
5257 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
5258 #[serde(default, skip_serializing_if = "Option::is_none")]
5264 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
5265 #[serde(default, skip_serializing_if = "Option::is_none")]
5269 pub historical: Option<bool>,
5270 #[serde(default, skip_serializing_if = "Option::is_none")]
5278 pub resume_reason: Option<String>,
5279 #[serde(default, skip_serializing_if = "Option::is_none")]
5285 pub local_command_source: Option<String>,
5286 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5295 pub narration_block_indexes: Vec<usize>,
5296 #[serde(default, skip_serializing_if = "Option::is_none")]
5300 pub narration_hint: Option<String>,
5301 #[serde(default, skip_serializing_if = "Option::is_none")]
5310 pub thinking_duration_ms: Option<u64>,
5311 #[serde(default, skip_serializing_if = "Option::is_none")]
5322 pub thinking_display: Option<String>,
5323 #[serde(default, skip_serializing_if = "Option::is_none")]
5327 pub context_usage: Option<ContextUsage>,
5328 #[serde(default, skip_serializing_if = "Option::is_none")]
5336 pub usage_report: Option<Box<UsageReport>>,
5337 #[serde(default, skip_serializing_if = "Option::is_none")]
5344 pub local_command_run: Option<LocalCommandRun>,
5345 #[serde(default, skip_serializing_if = "Option::is_none")]
5351 pub local_command_outcome: Option<LocalCommandOutcome>,
5352 #[serde(default, skip_serializing_if = "Option::is_none")]
5353 pub is_meta: Option<bool>,
5354 #[serde(default, skip_serializing_if = "Option::is_none")]
5355 pub is_virtual: Option<bool>,
5356 #[serde(default, skip_serializing_if = "Option::is_none")]
5357 pub is_api_error_message: Option<bool>,
5358 #[serde(skip_serializing_if = "Option::is_none")]
5359 pub api_error_status: Option<u16>,
5360 #[serde(skip_serializing_if = "Option::is_none")]
5383 pub api_error: Option<String>,
5384 #[serde(default, skip_serializing_if = "Option::is_none")]
5391 pub api_error_code: Option<String>,
5392 #[serde(default, skip_serializing_if = "Option::is_none")]
5403 pub api_error_origin: Option<String>,
5404 #[serde(default, skip_serializing_if = "Option::is_none")]
5407 pub api_error_params: Option<ApiErrorParams>,
5408 #[serde(skip_serializing_if = "Option::is_none")]
5409 pub error_details: Option<String>,
5410 #[serde(skip_serializing_if = "Option::is_none")]
5411 pub advisor_model: Option<String>,
5412 #[serde(skip_serializing_if = "Option::is_none")]
5413 pub attribution_agent: Option<String>,
5414 #[serde(skip_serializing_if = "Option::is_none")]
5415 pub attribution_skill: Option<String>,
5416 #[serde(skip_serializing_if = "Option::is_none")]
5417 pub attribution_plugin: Option<String>,
5418 #[serde(skip_serializing_if = "Option::is_none")]
5419 pub attribution_mcp_server: Option<String>,
5420 #[serde(skip_serializing_if = "Option::is_none")]
5421 pub attribution_mcp_tool: Option<String>,
5422}
5423
5424#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5428pub struct ApiErrorParams {
5429 #[serde(default, skip_serializing_if = "Option::is_none")]
5431 pub effort: Option<String>,
5432 #[serde(default, skip_serializing_if = "Option::is_none")]
5435 pub provider: Option<ApiErrorProvider>,
5436 #[serde(default, skip_serializing_if = "Option::is_none")]
5439 pub remedy: Option<ApiErrorRemedy>,
5440 #[serde(default, skip_serializing_if = "Option::is_none")]
5443 pub media: Option<ApiErrorMedia>,
5444 #[serde(default, skip_serializing_if = "Option::is_none")]
5446 pub media_reason: Option<ApiErrorMediaReason>,
5447 #[serde(default, skip_serializing_if = "Option::is_none")]
5455 pub rate_limit_info: Option<RateLimitInfo>,
5456}
5457
5458#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5460pub enum ApiErrorMedia {
5461 Image,
5462 Document,
5463 Unknown(String),
5465}
5466
5467impl ApiErrorMedia {
5468 pub fn as_str(&self) -> &str {
5469 match self {
5470 Self::Image => "image",
5471 Self::Document => "document",
5472 Self::Unknown(s) => s.as_str(),
5473 }
5474 }
5475}
5476
5477impl fmt::Display for ApiErrorMedia {
5478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5479 f.write_str(self.as_str())
5480 }
5481}
5482
5483impl From<&str> for ApiErrorMedia {
5484 fn from(s: &str) -> Self {
5485 match s {
5486 "image" => Self::Image,
5487 "document" => Self::Document,
5488 other => Self::Unknown(other.to_string()),
5489 }
5490 }
5491}
5492
5493impl Serialize for ApiErrorMedia {
5494 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5495 serializer.serialize_str(self.as_str())
5496 }
5497}
5498
5499impl<'de> Deserialize<'de> for ApiErrorMedia {
5500 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5501 let s = String::deserialize(deserializer)?;
5502 Ok(Self::from(s.as_str()))
5503 }
5504}
5505
5506#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5509pub enum ApiErrorMediaReason {
5510 Unprocessable,
5513 UnsupportedByModel,
5515 MediaBudget,
5518 Unknown(String),
5520}
5521
5522impl ApiErrorMediaReason {
5523 pub fn as_str(&self) -> &str {
5524 match self {
5525 Self::Unprocessable => "unprocessable",
5526 Self::UnsupportedByModel => "unsupported_by_model",
5527 Self::MediaBudget => "media_budget",
5528 Self::Unknown(s) => s.as_str(),
5529 }
5530 }
5531}
5532
5533impl fmt::Display for ApiErrorMediaReason {
5534 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5535 f.write_str(self.as_str())
5536 }
5537}
5538
5539impl From<&str> for ApiErrorMediaReason {
5540 fn from(s: &str) -> Self {
5541 match s {
5542 "unprocessable" => Self::Unprocessable,
5543 "unsupported_by_model" => Self::UnsupportedByModel,
5544 "media_budget" => Self::MediaBudget,
5545 other => Self::Unknown(other.to_string()),
5546 }
5547 }
5548}
5549
5550impl Serialize for ApiErrorMediaReason {
5551 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5552 serializer.serialize_str(self.as_str())
5553 }
5554}
5555
5556impl<'de> Deserialize<'de> for ApiErrorMediaReason {
5557 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5558 let s = String::deserialize(deserializer)?;
5559 Ok(Self::from(s.as_str()))
5560 }
5561}
5562
5563#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5565pub enum ApiErrorProvider {
5566 Bedrock,
5567 AnthropicAws,
5568 Mantle,
5569 AnthropicGoogleCloud,
5570 Vertex,
5571 Foundry,
5572 Gateway,
5573 Unknown(String),
5575}
5576
5577impl ApiErrorProvider {
5578 pub fn as_str(&self) -> &str {
5579 match self {
5580 Self::Bedrock => "bedrock",
5581 Self::AnthropicAws => "anthropicAws",
5582 Self::Mantle => "mantle",
5583 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
5584 Self::Vertex => "vertex",
5585 Self::Foundry => "foundry",
5586 Self::Gateway => "gateway",
5587 Self::Unknown(s) => s.as_str(),
5588 }
5589 }
5590}
5591
5592impl fmt::Display for ApiErrorProvider {
5593 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5594 f.write_str(self.as_str())
5595 }
5596}
5597
5598impl From<&str> for ApiErrorProvider {
5599 fn from(s: &str) -> Self {
5600 match s {
5601 "bedrock" => Self::Bedrock,
5602 "anthropicAws" => Self::AnthropicAws,
5603 "mantle" => Self::Mantle,
5604 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
5605 "vertex" => Self::Vertex,
5606 "foundry" => Self::Foundry,
5607 "gateway" => Self::Gateway,
5608 other => Self::Unknown(other.to_string()),
5609 }
5610 }
5611}
5612
5613impl Serialize for ApiErrorProvider {
5614 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5615 serializer.serialize_str(self.as_str())
5616 }
5617}
5618
5619impl<'de> Deserialize<'de> for ApiErrorProvider {
5620 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5621 let s = String::deserialize(deserializer)?;
5622 Ok(Self::from(s.as_str()))
5623 }
5624}
5625
5626#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5628pub enum ApiErrorRemedy {
5629 RefreshCommand,
5632 RefreshCredentials,
5634 Adc,
5637 GatewayToken,
5640 HostManaged,
5642 ModelAccess,
5645 Unknown(String),
5647}
5648
5649impl ApiErrorRemedy {
5650 pub fn as_str(&self) -> &str {
5651 match self {
5652 Self::RefreshCommand => "refresh_command",
5653 Self::RefreshCredentials => "refresh_credentials",
5654 Self::Adc => "adc",
5655 Self::GatewayToken => "gateway_token",
5656 Self::HostManaged => "host_managed",
5657 Self::ModelAccess => "model_access",
5658 Self::Unknown(s) => s.as_str(),
5659 }
5660 }
5661}
5662
5663impl fmt::Display for ApiErrorRemedy {
5664 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5665 f.write_str(self.as_str())
5666 }
5667}
5668
5669impl From<&str> for ApiErrorRemedy {
5670 fn from(s: &str) -> Self {
5671 match s {
5672 "refresh_command" => Self::RefreshCommand,
5673 "refresh_credentials" => Self::RefreshCredentials,
5674 "adc" => Self::Adc,
5675 "gateway_token" => Self::GatewayToken,
5676 "host_managed" => Self::HostManaged,
5677 "model_access" => Self::ModelAccess,
5678 other => Self::Unknown(other.to_string()),
5679 }
5680 }
5681}
5682
5683impl Serialize for ApiErrorRemedy {
5684 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5685 serializer.serialize_str(self.as_str())
5686 }
5687}
5688
5689impl<'de> Deserialize<'de> for ApiErrorRemedy {
5690 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5691 let s = String::deserialize(deserializer)?;
5692 Ok(Self::from(s.as_str()))
5693 }
5694}
5695
5696#[derive(Debug, Clone, Serialize, Deserialize)]
5698pub struct AssistantMessageContent {
5699 pub id: String,
5700 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
5702 pub message_type: Option<String>,
5703 pub role: MessageRole,
5704 pub model: String,
5705 pub content: Vec<ContentBlock>,
5706 #[serde(skip_serializing_if = "Option::is_none")]
5707 pub stop_reason: Option<StopReason>,
5708 #[serde(skip_serializing_if = "Option::is_none")]
5709 pub stop_sequence: Option<String>,
5710 #[serde(skip_serializing_if = "Option::is_none")]
5711 pub usage: Option<AssistantUsage>,
5712 #[serde(skip_serializing_if = "Option::is_none")]
5714 pub stop_details: Option<Value>,
5715 #[serde(skip_serializing_if = "Option::is_none")]
5717 pub context_management: Option<Value>,
5718}
5719
5720#[derive(Debug, Clone, Serialize, Deserialize)]
5722pub struct AssistantUsage {
5723 #[serde(default)]
5725 pub input_tokens: u32,
5726
5727 #[serde(default)]
5729 pub output_tokens: u32,
5730
5731 #[serde(default)]
5733 pub cache_creation_input_tokens: u32,
5734
5735 #[serde(default)]
5737 pub cache_read_input_tokens: u32,
5738
5739 #[serde(skip_serializing_if = "Option::is_none")]
5741 pub service_tier: Option<String>,
5742
5743 #[serde(skip_serializing_if = "Option::is_none")]
5745 pub cache_creation: Option<CacheCreationDetails>,
5746
5747 #[serde(skip_serializing_if = "Option::is_none")]
5749 pub inference_geo: Option<String>,
5750
5751 #[serde(default, skip_serializing_if = "Option::is_none")]
5754 pub speed: Option<String>,
5755
5756 #[serde(default, skip_serializing_if = "Option::is_none")]
5759 pub fallback_credit: Option<Value>,
5760}
5761
5762#[derive(Debug, Clone, Serialize, Deserialize)]
5764pub struct CacheCreationDetails {
5765 #[serde(default)]
5767 pub ephemeral_1h_input_tokens: u32,
5768
5769 #[serde(default)]
5771 pub ephemeral_5m_input_tokens: u32,
5772}
5773
5774#[cfg(test)]
5775mod tests {
5776 use crate::io::ClaudeOutput;
5777
5778 #[test]
5779 fn test_subagent_usage_rollup_accumulates_task_results() {
5780 use super::SubagentUsageRollup;
5781
5782 let mut rollup = SubagentUsageRollup::default();
5783
5784 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}}"#;
5785 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5786 assert!(rollup.observe(&output));
5787 assert_eq!(rollup.subagent_tokens, 10201);
5788 assert_eq!(rollup.agent_count, 1);
5789 assert_eq!(rollup.tool_uses, 3);
5790 assert_eq!(rollup.duration_ms, 1853);
5791
5792 assert!(!rollup.observe(&output));
5794 assert_eq!(rollup.agent_count, 1);
5795 assert_eq!(rollup.subagent_tokens, 10201);
5796
5797 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}}"#;
5799 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5800 assert!(rollup.observe(&output));
5801 assert_eq!(rollup.agent_count, 2);
5802 assert_eq!(rollup.subagent_tokens, 10701);
5803 }
5804
5805 #[test]
5806 fn test_subagent_usage_rollup_ignores_non_task_results() {
5807 use super::SubagentUsageRollup;
5808
5809 let mut rollup = SubagentUsageRollup::default();
5810
5811 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}}"#;
5814 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5815 assert!(!rollup.observe(&output));
5816
5817 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5819 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5820 assert!(!rollup.observe(&output));
5821
5822 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5824 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5825 assert!(!rollup.observe(&output));
5826
5827 assert_eq!(rollup, SubagentUsageRollup::default());
5828 }
5829
5830 #[test]
5831 fn test_subagent_usage_rollup_over_captured_session() {
5832 use super::SubagentUsageRollup;
5833
5834 let mut rollup = SubagentUsageRollup::default();
5835 let fixture =
5836 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5837 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5838 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5839 rollup.observe(&output);
5840 }
5841 }
5842 assert_eq!(rollup.agent_count, 1);
5843 assert_eq!(rollup.subagent_tokens, 10201);
5844 }
5845
5846 #[test]
5847 fn test_system_message_init() {
5848 let json = r#"{
5849 "type": "system",
5850 "subtype": "init",
5851 "session_id": "test-session-123",
5852 "cwd": "/home/user/project",
5853 "model": "claude-sonnet-4",
5854 "tools": ["Bash", "Read", "Write"],
5855 "mcp_servers": [],
5856 "slash_commands": ["compact", "cost", "review"],
5857 "agents": ["Bash", "Explore", "Plan"],
5858 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5859 "skills": [],
5860 "claude_code_version": "2.1.15",
5861 "apiKeySource": "none",
5862 "output_style": "default",
5863 "permissionMode": "default"
5864 }"#;
5865
5866 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5867 if let ClaudeOutput::System(sys) = output {
5868 assert!(sys.is_init());
5869 assert!(!sys.is_status());
5870 assert!(!sys.is_compact_boundary());
5871
5872 let init = sys.as_init().expect("Should parse as init");
5873 assert_eq!(init.session_id, "test-session-123");
5874 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5875 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5876 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5877 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5878 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5879 assert_eq!(init.plugins.len(), 1);
5880 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5881 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5882 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5883 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5884 assert_eq!(
5885 init.permission_mode,
5886 Some(super::InitPermissionMode::Default)
5887 );
5888 } else {
5889 panic!("Expected System message");
5890 }
5891 }
5892
5893 #[test]
5894 fn test_system_message_init_from_real_capture() {
5895 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5896 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5897 if let ClaudeOutput::System(sys) = output {
5898 let init = sys.as_init().expect("Should parse real init capture");
5899 assert_eq!(init.slash_commands.len(), 8);
5900 assert!(init.slash_commands.contains(&"compact".to_string()));
5901 assert!(init.slash_commands.contains(&"review".to_string()));
5902 assert_eq!(init.agents.len(), 5);
5903 assert!(init.agents.contains(&"Bash".to_string()));
5904 assert!(init.agents.contains(&"Explore".to_string()));
5905 assert_eq!(init.plugins.len(), 1);
5906 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5907 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5908 } else {
5909 panic!("Expected System message");
5910 }
5911 }
5912
5913 #[test]
5914 fn test_system_message_status() {
5915 let json = r#"{
5916 "type": "system",
5917 "subtype": "status",
5918 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5919 "status": "compacting",
5920 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5921 }"#;
5922
5923 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5924 if let ClaudeOutput::System(sys) = output {
5925 assert!(sys.is_status());
5926 assert!(!sys.is_init());
5927
5928 let status = sys.as_status().expect("Should parse as status");
5929 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5930 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5931 assert_eq!(
5932 status.uuid,
5933 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5934 );
5935 } else {
5936 panic!("Expected System message");
5937 }
5938 }
5939
5940 #[test]
5941 fn test_system_message_status_null() {
5942 let json = r#"{
5943 "type": "system",
5944 "subtype": "status",
5945 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5946 "status": null,
5947 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5948 }"#;
5949
5950 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5951 if let ClaudeOutput::System(sys) = output {
5952 let status = sys.as_status().expect("Should parse as status");
5953 assert_eq!(status.status, None);
5954 } else {
5955 panic!("Expected System message");
5956 }
5957 }
5958
5959 #[test]
5960 fn test_system_message_task_started() {
5961 let json = r#"{
5962 "type": "system",
5963 "subtype": "task_started",
5964 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5965 "task_id": "b6daf3f",
5966 "task_type": "local_bash",
5967 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5968 "description": "Wait for CI on PR #12",
5969 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5970 }"#;
5971
5972 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5973 if let ClaudeOutput::System(sys) = output {
5974 assert!(sys.is_task_started());
5975 assert!(!sys.is_task_progress());
5976 assert!(!sys.is_task_notification());
5977
5978 let task = sys.as_task_started().expect("Should parse as task_started");
5979 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5980 assert_eq!(task.task_id, "b6daf3f");
5981 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5982 assert_eq!(
5983 task.tool_use_id.as_deref(),
5984 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5985 );
5986 assert_eq!(task.description, "Wait for CI on PR #12");
5987 } else {
5988 panic!("Expected System message");
5989 }
5990 }
5991
5992 #[test]
5993 fn test_system_message_task_started_agent() {
5994 let json = r#"{
5995 "type": "system",
5996 "subtype": "task_started",
5997 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5998 "task_id": "a4a7e0906e5fc64cc",
5999 "task_type": "local_agent",
6000 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6001 "description": "Explore Scene/ArrayScene duplication",
6002 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6003 }"#;
6004
6005 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6006 if let ClaudeOutput::System(sys) = output {
6007 let task = sys.as_task_started().expect("Should parse as task_started");
6008 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
6009 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
6010 } else {
6011 panic!("Expected System message");
6012 }
6013 }
6014
6015 #[test]
6016 fn test_system_message_task_progress() {
6017 let json = r#"{
6018 "type": "system",
6019 "subtype": "task_progress",
6020 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6021 "task_id": "a4a7e0906e5fc64cc",
6022 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6023 "description": "Reading src/jplephem/chebyshev.rs",
6024 "last_tool_name": "Read",
6025 "usage": {
6026 "duration_ms": 13996,
6027 "tool_uses": 9,
6028 "total_tokens": 38779
6029 },
6030 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6031 }"#;
6032
6033 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6034 if let ClaudeOutput::System(sys) = output {
6035 assert!(sys.is_task_progress());
6036 assert!(!sys.is_task_started());
6037
6038 let progress = sys
6039 .as_task_progress()
6040 .expect("Should parse as task_progress");
6041 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
6042 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
6043 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
6044 assert_eq!(progress.usage.duration_ms, 13996);
6045 assert_eq!(progress.usage.tool_uses, 9);
6046 assert_eq!(progress.usage.total_tokens, 38779);
6047 } else {
6048 panic!("Expected System message");
6049 }
6050 }
6051
6052 #[test]
6053 fn test_system_message_task_notification_completed() {
6054 let json = r#"{
6055 "type": "system",
6056 "subtype": "task_notification",
6057 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6058 "task_id": "a0ba761e9dc9c316f",
6059 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
6060 "status": "completed",
6061 "summary": "Agent \"Write Hipparcos data source doc\" completed",
6062 "output_file": "",
6063 "usage": {
6064 "duration_ms": 172300,
6065 "tool_uses": 11,
6066 "total_tokens": 42005
6067 },
6068 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
6069 }"#;
6070
6071 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6072 if let ClaudeOutput::System(sys) = output {
6073 assert!(sys.is_task_notification());
6074
6075 let notif = sys
6076 .as_task_notification()
6077 .expect("Should parse as task_notification");
6078 assert_eq!(notif.status, super::TaskStatus::Completed);
6079 assert_eq!(
6080 notif.summary,
6081 "Agent \"Write Hipparcos data source doc\" completed"
6082 );
6083 assert_eq!(notif.output_file, Some("".to_string()));
6084 assert_eq!(
6085 notif.tool_use_id,
6086 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
6087 );
6088 let usage = notif.usage.expect("Should have usage");
6089 assert_eq!(usage.duration_ms, 172300);
6090 assert_eq!(usage.tool_uses, 11);
6091 assert_eq!(usage.total_tokens, 42005);
6092 } else {
6093 panic!("Expected System message");
6094 }
6095 }
6096
6097 #[test]
6098 fn test_system_message_task_notification_failed_no_usage() {
6099 let json = r#"{
6100 "type": "system",
6101 "subtype": "task_notification",
6102 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
6103 "task_id": "b98f6a3",
6104 "status": "failed",
6105 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
6106 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
6107 }"#;
6108
6109 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6110 if let ClaudeOutput::System(sys) = output {
6111 let notif = sys
6112 .as_task_notification()
6113 .expect("Should parse as task_notification");
6114 assert_eq!(notif.status, super::TaskStatus::Failed);
6115 assert!(notif.tool_use_id.is_none());
6116 assert!(notif.usage.is_none());
6117 assert_eq!(
6118 notif.output_file,
6119 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
6120 );
6121 } else {
6122 panic!("Expected System message");
6123 }
6124 }
6125
6126 #[test]
6132 fn test_task_messages_roundtrip_through_value() {
6133 let cases = [
6134 r#"{"type":"system","subtype":"task_started","session_id":"s1",
6135 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
6136 "description":"Sleep 3s","uuid":"u1"}"#,
6137 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
6138 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
6139 "last_tool_name":"Bash",
6140 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6141 "uuid":"u2"}"#,
6142 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
6143 "task_id":"t1","tool_use_id":"tu1","status":"completed",
6144 "summary":"done","output_file":"",
6145 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6146 "uuid":"u3"}"#,
6147 ];
6148
6149 for json in cases {
6150 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6151 let value = serde_json::to_value(&output).unwrap();
6152 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
6153
6154 let ClaudeOutput::System(sys) = reparsed else {
6155 panic!("Expected System variant after round-trip");
6156 };
6157
6158 match sys.subtype {
6159 super::SystemSubtype::TaskStarted => {
6160 assert!(
6161 sys.as_task_started().is_some(),
6162 "as_task_started failed after round-trip"
6163 );
6164 }
6165 super::SystemSubtype::TaskProgress => {
6166 assert!(
6167 sys.as_task_progress().is_some(),
6168 "as_task_progress failed after round-trip"
6169 );
6170 }
6171 super::SystemSubtype::TaskNotification => {
6172 assert!(
6173 sys.as_task_notification().is_some(),
6174 "as_task_notification failed after round-trip"
6175 );
6176 }
6177 other => panic!("unexpected subtype after round-trip: {other:?}"),
6178 }
6179 }
6180 }
6181
6182 #[test]
6183 fn test_system_message_compact_boundary() {
6184 let json = r#"{
6185 "type": "system",
6186 "subtype": "compact_boundary",
6187 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6188 "compact_metadata": {
6189 "pre_tokens": 155285,
6190 "trigger": "auto"
6191 },
6192 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
6193 }"#;
6194
6195 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6196 if let ClaudeOutput::System(sys) = output {
6197 assert!(sys.is_compact_boundary());
6198 assert!(!sys.is_init());
6199 assert!(!sys.is_status());
6200
6201 let compact = sys
6202 .as_compact_boundary()
6203 .expect("Should parse as compact_boundary");
6204 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6205 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
6206 assert_eq!(
6207 compact.compact_metadata.trigger,
6208 super::CompactionTrigger::Auto
6209 );
6210 assert!(compact.summary.is_none());
6212 assert!(compact.leaf_message_count.is_none());
6213 assert!(compact.duration_ms.is_none());
6214 } else {
6215 panic!("Expected System message");
6216 }
6217 }
6218
6219 #[test]
6220 fn test_compact_boundary_with_summary_stats() {
6221 let json = r#"{
6223 "type": "system",
6224 "subtype": "compact_boundary",
6225 "session_id": "s1",
6226 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
6227 "summary": "Summarized the earlier exploration.",
6228 "leaf_message_count": 42,
6229 "duration_ms": 1234,
6230 "uuid": "u1"
6231 }"#;
6232 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6233 let ClaudeOutput::System(sys) = output else {
6234 panic!("Expected System message");
6235 };
6236 let compact = sys.as_compact_boundary().expect("compact_boundary");
6237 assert_eq!(
6238 compact.summary.as_deref(),
6239 Some("Summarized the earlier exploration.")
6240 );
6241 assert_eq!(compact.leaf_message_count, Some(42));
6242 assert_eq!(compact.duration_ms, Some(1234));
6243
6244 let json_alt = r#"{
6247 "type": "system",
6248 "subtype": "compact_boundary",
6249 "session_id": "s2",
6250 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
6251 "content": "alt-key summary",
6252 "message_count": 7
6253 }"#;
6254 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
6255 let ClaudeOutput::System(sys) = output else {
6256 panic!("Expected System message");
6257 };
6258 let compact = sys.as_compact_boundary().expect("compact_boundary");
6259 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
6260 assert_eq!(compact.leaf_message_count, Some(7));
6261 }
6262
6263 #[test]
6264 fn test_init_message_with_new_fields() {
6265 let json = r#"{
6266 "type": "system",
6267 "subtype": "init",
6268 "session_id": "test-session",
6269 "cwd": "/home/user",
6270 "model": "claude-opus-4-7",
6271 "tools": ["Bash"],
6272 "mcp_servers": [],
6273 "permissionMode": "default",
6274 "apiKeySource": "none",
6275 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
6276 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
6277 "fast_mode_state": "off",
6278 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
6279 "claude_code_version": "2.1.117"
6280 }"#;
6281
6282 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6283 if let ClaudeOutput::System(sys) = output {
6284 let init = sys.as_init().expect("Should parse as init");
6285 assert_eq!(
6286 init.uuid.as_deref(),
6287 Some("44841a0d-182d-493a-86b5-79800d3d9665")
6288 );
6289 assert!(init.memory_paths.is_some());
6290 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
6291 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
6292 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
6293 } else {
6294 panic!("Expected System message");
6295 }
6296 }
6297
6298 #[test]
6299 fn test_assistant_message_with_new_fields() {
6300 let json = r#"{
6301 "type": "assistant",
6302 "message": {
6303 "id": "msg_1",
6304 "type": "message",
6305 "role": "assistant",
6306 "model": "claude-opus-4-7",
6307 "content": [{"type": "text", "text": "Hello"}],
6308 "stop_reason": "end_turn",
6309 "stop_details": null,
6310 "context_management": null,
6311 "usage": {
6312 "input_tokens": 100,
6313 "output_tokens": 10,
6314 "cache_creation_input_tokens": 50,
6315 "cache_read_input_tokens": 0,
6316 "service_tier": "standard",
6317 "inference_geo": "not_available"
6318 }
6319 },
6320 "session_id": "abc",
6321 "uuid": "msg-uuid-123"
6322 }"#;
6323
6324 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6325 if let ClaudeOutput::Assistant(asst) = output {
6326 assert_eq!(asst.message.stop_details, None);
6327 assert_eq!(asst.message.context_management, None);
6328 let usage = asst.message.usage.unwrap();
6329 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
6330 } else {
6331 panic!("Expected Assistant message");
6332 }
6333 }
6334
6335 #[test]
6336 fn test_user_message_with_new_fields() {
6337 let json = r#"{
6338 "type": "user",
6339 "message": {
6340 "role": "user",
6341 "content": [{"type": "text", "text": "Hello"}]
6342 },
6343 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6344 "parent_tool_use_id": "toolu_123",
6345 "uuid": "user-msg-456"
6346 }"#;
6347
6348 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6349 if let ClaudeOutput::User(user) = output {
6350 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
6351 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
6352 } else {
6353 panic!("Expected User message");
6354 }
6355 }
6356
6357 #[test]
6363 fn test_user_message_preserves_tool_use_result_and_timestamp() {
6364 let json = r#"{
6365 "type":"user",
6366 "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"}]},
6367 "parent_tool_use_id":null,
6368 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6369 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
6370 "timestamp":"2026-05-12T23:12:04.121Z",
6371 "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"}}
6372 }"#;
6373
6374 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6375 let user = match output {
6376 ClaudeOutput::User(u) => u,
6377 other => panic!("Expected User message, got {:?}", other.message_type()),
6378 };
6379
6380 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
6381 let raw = user
6382 .tool_use_result
6383 .as_ref()
6384 .expect("tool_use_result must be captured");
6385 assert_eq!(raw["answers"]["Color"], "Blue");
6386 assert_eq!(raw["questions"][0]["header"], "Color");
6387
6388 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
6392 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
6393 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
6394 assert_eq!(
6395 reser["tool_use_result"]["questions"][0]["question"],
6396 "Which color do you prefer?"
6397 );
6398
6399 let typed: crate::AskUserQuestionInput = user
6402 .tool_use_result_as::<crate::AskUserQuestionInput>()
6403 .expect("tool_use_result present")
6404 .expect("AskUserQuestionInput parses");
6405 assert_eq!(typed.questions.len(), 1);
6406 assert_eq!(typed.questions[0].header, "Color");
6407 let answers = typed.answers.expect("answers populated");
6408 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
6409 }
6410
6411 #[test]
6414 fn test_user_message_without_tool_use_result_omits_field() {
6415 let json = r#"{
6416 "type":"user",
6417 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
6418 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
6419 }"#;
6420
6421 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6422 let user = match output {
6423 ClaudeOutput::User(u) => u,
6424 _ => panic!("Expected User message"),
6425 };
6426 assert!(user.tool_use_result.is_none());
6427 assert!(user.timestamp.is_none());
6428
6429 let reser = serde_json::to_value(&user).unwrap();
6430 assert!(reser.get("tool_use_result").is_none());
6431 assert!(reser.get("timestamp").is_none());
6432 }
6433
6434 #[test]
6438 fn test_subagent_result_exposes_token_accounting() {
6439 let json = r#"{
6440 "type":"user",
6441 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
6442 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
6443 "tool_use_result":{
6444 "status":"completed",
6445 "prompt":"Count the .rs files.",
6446 "agentId":"ac4f0276e9d4b6232",
6447 "agentType":"Explore",
6448 "content":[{"type":"text","text":"21"}],
6449 "resolvedModel":"claude-haiku-4-5-20251001",
6450 "totalDurationMs":6869,
6451 "totalTokens":7834,
6452 "totalToolUseCount":1,
6453 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
6454 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
6455 }
6456 }"#;
6457
6458 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6459 let user = match output {
6460 ClaudeOutput::User(u) => u,
6461 _ => panic!("Expected User message"),
6462 };
6463
6464 let result = user.subagent_result().expect("subagent result parses");
6465 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
6466 assert_eq!(
6467 result.resolved_model.as_deref(),
6468 Some("claude-haiku-4-5-20251001")
6469 );
6470 assert_eq!(result.total_tokens, Some(7834));
6471 assert_eq!(result.total_duration_ms, Some(6869));
6472 assert_eq!(result.total_tool_use_count, Some(1));
6473
6474 let usage = result.usage.expect("nested usage present");
6475 assert_eq!(usage.input_tokens, 6);
6476 assert_eq!(usage.cache_read_input_tokens, 7699);
6477
6478 let stats = result.tool_stats.expect("toolStats present");
6479 assert_eq!(stats.bash_count, 1);
6480 }
6481
6482 #[test]
6486 fn test_subagent_result_absent_for_non_task_result() {
6487 let json = r#"{
6488 "type":"user",
6489 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
6490 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6491 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
6492 }"#;
6493
6494 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6495 let user = match output {
6496 ClaudeOutput::User(u) => u,
6497 _ => panic!("Expected User message"),
6498 };
6499
6500 let result = user.subagent_result().expect("lenient parse");
6501 assert_eq!(result.total_tokens, None);
6502 assert_eq!(result.agent_type, None);
6503 }
6504
6505 #[test]
6506 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
6507 use serde_json::Value;
6508
6509 let raw: Value = serde_json::from_str(
6510 r#"{
6511 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
6512 "fast_mode_state":"off",
6513 "fast_mode_disabled_reason":"not_first_party",
6514 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
6515 }"#,
6516 )
6517 .unwrap();
6518 crate::io::assert_fully_wrapped(&raw);
6519
6520 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6521 let ClaudeOutput::System(sys) = output else {
6522 panic!("expected System");
6523 };
6524 let init = sys.as_init().expect("parses as init");
6525 assert_eq!(
6526 init.fast_mode_disabled_reason,
6527 Some(crate::FastModeDisabledReason::NotFirstParty)
6528 );
6529 let errs = init.mcp_server_errors.unwrap();
6530 assert_eq!(errs.len(), 1);
6531 assert_eq!(errs[0].name, "broken");
6532 assert_eq!(errs[0].error_type, "invalid_config");
6533 }
6534
6535 #[test]
6536 fn test_code_change_published_fully_wrapped() {
6537 use super::{KnownSystemEvent, SystemSubtype};
6538 use serde_json::Value;
6539
6540 let raw: Value = serde_json::from_str(
6541 r#"{
6542 "type":"system","subtype":"code_change_published",
6543 "provider":"github","url":"https://github.com/owner/repo/pull/42",
6544 "repo":"owner/repo","identifier":"42",
6545 "uuid":"u1","session_id":"s1"
6546 }"#,
6547 )
6548 .unwrap();
6549 crate::io::assert_fully_wrapped(&raw);
6550
6551 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6552 let ClaudeOutput::System(sys) = output else {
6553 panic!("expected System");
6554 };
6555 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
6556 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
6557 panic!("expected CodeChangePublished event");
6558 };
6559 assert_eq!(msg.provider, "github");
6560 assert_eq!(msg.repo, "owner/repo");
6561 assert_eq!(msg.identifier, "42");
6562
6563 assert!(sys.is_code_change_published());
6564 assert!(!sys.is_vcs_state_changed());
6565 let direct = sys.as_code_change_published().expect("direct accessor");
6566 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
6567 assert!(sys.as_vcs_state_changed().is_none());
6568 }
6569
6570 #[test]
6571 fn test_feedback_draft_queued_fully_wrapped() {
6572 use super::{KnownSystemEvent, SystemSubtype};
6573 use serde_json::Value;
6574
6575 let raw: Value = serde_json::from_str(
6576 r#"{
6577 "type":"system","subtype":"feedback_draft_queued",
6578 "draft_id":"draft-1","draft_type":"bug_report",
6579 "title":"Tool output was truncated",
6580 "details_preview":"The last command omitted its final lines",
6581 "uuid":"u1","session_id":"s1","future_field":"preserved"
6582 }"#,
6583 )
6584 .unwrap();
6585 crate::io::assert_fully_wrapped(&raw);
6586
6587 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6588 let ClaudeOutput::System(sys) = output else {
6589 panic!("expected System");
6590 };
6591 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
6592 assert!(sys.is_feedback_draft_queued());
6593 assert!(!sys.is_vcs_state_changed());
6594
6595 let direct = sys
6596 .as_feedback_draft_queued()
6597 .expect("direct typed accessor");
6598 assert_eq!(direct.draft_id, "draft-1");
6599 assert_eq!(direct.draft_type, "bug_report");
6600 assert_eq!(direct.extra["future_field"], "preserved");
6601
6602 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
6603 panic!("expected FeedbackDraftQueued event");
6604 };
6605 assert_eq!(known.title, "Tool output was truncated");
6606 assert_eq!(
6607 sys.typed_value().expect("typed value")["future_field"],
6608 "preserved"
6609 );
6610 }
6611
6612 #[test]
6613 fn test_cloud_session_delta_fully_wrapped() {
6614 use super::{KnownSystemEvent, SystemSubtype};
6615 use serde_json::Value;
6616
6617 let raw: Value = serde_json::from_str(
6618 r#"{
6619 "type":"system","subtype":"cloud_session_delta",
6620 "seq":3,"changed":["serving","connection"],
6621 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
6622 "serving":{"state":"on"},"connection":{"state":"live"}},
6623 "uuid":"u1","session_id":"s1","future_field":"preserved"
6624 }"#,
6625 )
6626 .unwrap();
6627 crate::io::assert_fully_wrapped(&raw);
6628
6629 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6630 let ClaudeOutput::System(sys) = output else {
6631 panic!("expected System");
6632 };
6633 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
6634 assert!(sys.is_cloud_session_delta());
6635 assert!(!sys.is_feedback_draft_queued());
6636
6637 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
6638 assert_eq!(direct.seq, 3);
6639 assert_eq!(direct.changed, vec!["serving", "connection"]);
6640 assert_eq!(direct.cloud_session["id"], "session_abc");
6641 assert_eq!(direct.extra["future_field"], "preserved");
6642
6643 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
6644 panic!("expected CloudSessionDelta event");
6645 };
6646 assert_eq!(known.session_id, "s1");
6647 assert_eq!(
6648 sys.typed_value().expect("typed value")["future_field"],
6649 "preserved"
6650 );
6651 }
6652
6653 #[test]
6654 fn test_vcs_state_changed_fully_wrapped() {
6655 use super::{KnownSystemEvent, VcsMutationKind};
6656 use serde_json::Value;
6657
6658 for kind in ["commit", "push", "merge", "rebase"] {
6659 let raw: Value = serde_json::from_str(&format!(
6660 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
6661 kind
6662 ))
6663 .unwrap();
6664 crate::io::assert_fully_wrapped(&raw);
6665
6666 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6667 let ClaudeOutput::System(sys) = output else {
6668 panic!("expected System");
6669 };
6670 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6671 panic!("expected VcsStateChanged event");
6672 };
6673 assert_eq!(msg.kind.as_str(), kind);
6674 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
6675 }
6676
6677 let raw: Value = serde_json::from_str(
6679 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
6680 )
6681 .unwrap();
6682 crate::io::assert_fully_wrapped(&raw);
6683 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6684 let ClaudeOutput::System(sys) = output else {
6685 panic!("expected System");
6686 };
6687 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6688 panic!("expected VcsStateChanged event");
6689 };
6690 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
6691
6692 assert!(sys.is_vcs_state_changed());
6693 let direct = sys.as_vcs_state_changed().expect("direct accessor");
6694 assert_eq!(direct.cwd, "/repo");
6695 assert!(sys.as_code_change_published().is_none());
6696 }
6697
6698 #[test]
6699 fn test_assistant_aborted_and_resume_flags_roundtrip() {
6700 let json = r#"{
6701 "type":"assistant",
6702 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
6703 "session_id":"s1",
6704 "aborted":true,
6705 "resumed_from_incomplete_thinking":true
6706 }"#;
6707 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6708 let ClaudeOutput::Assistant(msg) = &output else {
6709 panic!("expected Assistant");
6710 };
6711 assert_eq!(msg.aborted, Some(true));
6712 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
6713 let reserialized = serde_json::to_string(&output).unwrap();
6714 assert!(reserialized.contains("\"aborted\":true"));
6715 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
6716
6717 let json = r#"{
6719 "type":"assistant",
6720 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
6721 "session_id":"s2"
6722 }"#;
6723 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6724 let reserialized = serde_json::to_string(&output).unwrap();
6725 assert!(!reserialized.contains("aborted"));
6726 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
6727 }
6728
6729 #[test]
6730 fn test_user_tool_result_meta_roundtrip() {
6731 let json = r#"{
6732 "type":"user",
6733 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6734 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6735 "tool_result_meta":[
6736 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6737 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6738 ]
6739 }"#;
6740 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6741 let ClaudeOutput::User(user) = &output else {
6742 panic!("expected User");
6743 };
6744 let meta = user.tool_result_meta.as_ref().unwrap();
6745 assert_eq!(meta.len(), 2);
6746 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6747 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6748 assert_eq!(meta[1].user_feedback, None);
6749
6750 let reserialized = serde_json::to_string(&output).unwrap();
6751 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6752 assert!(!reserialized.contains("\"user_feedback\":null"));
6753 }
6754
6755 #[test]
6759 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6760 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6761 let with_scope = serde_json::json!({
6762 "trigger": "refusal",
6763 "direction": "retry",
6764 "scope": "local",
6765 "original_model": "claude-fable-5",
6766 "fallback_model": "claude-opus-5",
6767 "request_id": null,
6768 "content": "Refused; retried on fallback model.",
6769 "uuid": "u1",
6770 "session_id": "s1"
6771 });
6772 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6773 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6774 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6775
6776 let mut without = with_scope.clone();
6779 without.as_object_mut().unwrap().remove("scope");
6780 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6781 assert_eq!(msg.scope, None);
6782 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6783
6784 assert_eq!(
6786 RefusalFallbackScope::from("workspace").as_str(),
6787 "workspace"
6788 );
6789 }
6790}