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 SessionWritesLost,
56 InstructionSizeWarning,
57 FileAttachmentsMissing,
58 PermissionCheckStatus,
59 InheritedAsksSettled,
60 UiFocus,
61 UiInvalidate,
62 UiLog,
63 UiPanes,
64 UiPlaced,
65 UiScroll,
66 UiStatus,
67 UiToast,
68 Unknown(String),
70}
71
72impl SystemSubtype {
73 pub fn as_str(&self) -> &str {
74 match self {
75 Self::Init => "init",
76 Self::Status => "status",
77 Self::CompactBoundary => "compact_boundary",
78 Self::ThinkingTokens => "thinking_tokens",
79 Self::TaskStarted => "task_started",
80 Self::TaskProgress => "task_progress",
81 Self::TaskUpdated => "task_updated",
82 Self::TaskNotification => "task_notification",
83 Self::ApiRetry => "api_retry",
84 Self::ControlRequestProgress => "control_request_progress",
85 Self::ModelRefusalFallback => "model_refusal_fallback",
86 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
87 Self::LocalCommandOutput => "local_command_output",
88 Self::HookStarted => "hook_started",
89 Self::HookProgress => "hook_progress",
90 Self::HookResponse => "hook_response",
91 Self::PluginInstall => "plugin_install",
92 Self::BackgroundTasksChanged => "background_tasks_changed",
93 Self::SessionStateChanged => "session_state_changed",
94 Self::WorkerShuttingDown => "worker_shutting_down",
95 Self::CommandsChanged => "commands_changed",
96 Self::Notification => "notification",
97 Self::FilesPersisted => "files_persisted",
98 Self::MemoryRecall => "memory_recall",
99 Self::ElicitationComplete => "elicitation_complete",
100 Self::PermissionDenied => "permission_denied",
101 Self::MirrorError => "mirror_error",
102 Self::Informational => "informational",
103 Self::CodeChangePublished => "code_change_published",
104 Self::VcsStateChanged => "vcs_state_changed",
105 Self::FeedbackDraftQueued => "feedback_draft_queued",
106 Self::CloudSessionDelta => "cloud_session_delta",
107 Self::DevIntent => "dev_intent",
108 Self::TurnHandoffAvailable => "turn_handoff_available",
109 Self::TurnPreempted => "turn_preempted",
110 Self::PeerMessageHold => "peer_message_hold",
111 Self::PerTurnEffortChanged => "per_turn_effort_changed",
112 Self::SessionTitleChanged => "session_title_changed",
113 Self::SessionWritesLost => "session_writes_lost",
114 Self::InstructionSizeWarning => "instruction_size_warning",
115 Self::FileAttachmentsMissing => "file_attachments_missing",
116 Self::PermissionCheckStatus => "permission_check_status",
117 Self::InheritedAsksSettled => "inherited_asks_settled",
118 Self::UiFocus => "ui_focus",
119 Self::UiInvalidate => "ui_invalidate",
120 Self::UiLog => "ui_log",
121 Self::UiPanes => "ui_panes",
122 Self::UiPlaced => "ui_placed",
123 Self::UiScroll => "ui_scroll",
124 Self::UiStatus => "ui_status",
125 Self::UiToast => "ui_toast",
126 Self::Unknown(s) => s.as_str(),
127 }
128 }
129}
130
131impl fmt::Display for SystemSubtype {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 f.write_str(self.as_str())
134 }
135}
136
137impl From<&str> for SystemSubtype {
138 fn from(s: &str) -> Self {
139 match s {
140 "init" => Self::Init,
141 "status" => Self::Status,
142 "compact_boundary" => Self::CompactBoundary,
143 "thinking_tokens" => Self::ThinkingTokens,
144 "task_started" => Self::TaskStarted,
145 "task_progress" => Self::TaskProgress,
146 "task_updated" => Self::TaskUpdated,
147 "task_notification" => Self::TaskNotification,
148 "api_retry" => Self::ApiRetry,
149 "control_request_progress" => Self::ControlRequestProgress,
150 "model_refusal_fallback" => Self::ModelRefusalFallback,
151 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
152 "local_command_output" => Self::LocalCommandOutput,
153 "hook_started" => Self::HookStarted,
154 "hook_progress" => Self::HookProgress,
155 "hook_response" => Self::HookResponse,
156 "plugin_install" => Self::PluginInstall,
157 "background_tasks_changed" => Self::BackgroundTasksChanged,
158 "session_state_changed" => Self::SessionStateChanged,
159 "worker_shutting_down" => Self::WorkerShuttingDown,
160 "commands_changed" => Self::CommandsChanged,
161 "notification" => Self::Notification,
162 "files_persisted" => Self::FilesPersisted,
163 "memory_recall" => Self::MemoryRecall,
164 "elicitation_complete" => Self::ElicitationComplete,
165 "permission_denied" => Self::PermissionDenied,
166 "mirror_error" => Self::MirrorError,
167 "informational" => Self::Informational,
168 "code_change_published" => Self::CodeChangePublished,
169 "vcs_state_changed" => Self::VcsStateChanged,
170 "feedback_draft_queued" => Self::FeedbackDraftQueued,
171 "cloud_session_delta" => Self::CloudSessionDelta,
172 "dev_intent" => Self::DevIntent,
173 "turn_handoff_available" => Self::TurnHandoffAvailable,
174 "turn_preempted" => Self::TurnPreempted,
175 "peer_message_hold" => Self::PeerMessageHold,
176 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
177 "session_title_changed" => Self::SessionTitleChanged,
178 "session_writes_lost" => Self::SessionWritesLost,
179 "instruction_size_warning" => Self::InstructionSizeWarning,
180 "file_attachments_missing" => Self::FileAttachmentsMissing,
181 "permission_check_status" => Self::PermissionCheckStatus,
182 "inherited_asks_settled" => Self::InheritedAsksSettled,
183 "ui_focus" => Self::UiFocus,
184 "ui_invalidate" => Self::UiInvalidate,
185 "ui_log" => Self::UiLog,
186 "ui_panes" => Self::UiPanes,
187 "ui_placed" => Self::UiPlaced,
188 "ui_scroll" => Self::UiScroll,
189 "ui_status" => Self::UiStatus,
190 "ui_toast" => Self::UiToast,
191 other => Self::Unknown(other.to_string()),
192 }
193 }
194}
195
196impl Serialize for SystemSubtype {
197 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
198 serializer.serialize_str(self.as_str())
199 }
200}
201
202impl<'de> Deserialize<'de> for SystemSubtype {
203 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
204 let s = String::deserialize(deserializer)?;
205 Ok(Self::from(s.as_str()))
206 }
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Hash)]
214pub enum MessageRole {
215 User,
216 Assistant,
217 Unknown(String),
219}
220
221impl MessageRole {
222 pub fn as_str(&self) -> &str {
223 match self {
224 Self::User => "user",
225 Self::Assistant => "assistant",
226 Self::Unknown(s) => s.as_str(),
227 }
228 }
229}
230
231impl fmt::Display for MessageRole {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 f.write_str(self.as_str())
234 }
235}
236
237impl From<&str> for MessageRole {
238 fn from(s: &str) -> Self {
239 match s {
240 "user" => Self::User,
241 "assistant" => Self::Assistant,
242 other => Self::Unknown(other.to_string()),
243 }
244 }
245}
246
247impl Serialize for MessageRole {
248 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
249 serializer.serialize_str(self.as_str())
250 }
251}
252
253impl<'de> Deserialize<'de> for MessageRole {
254 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
255 let s = String::deserialize(deserializer)?;
256 Ok(Self::from(s.as_str()))
257 }
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Hash)]
262pub enum CompactionTrigger {
263 Auto,
265 Manual,
267 Unknown(String),
269}
270
271impl CompactionTrigger {
272 pub fn as_str(&self) -> &str {
273 match self {
274 Self::Auto => "auto",
275 Self::Manual => "manual",
276 Self::Unknown(s) => s.as_str(),
277 }
278 }
279}
280
281impl fmt::Display for CompactionTrigger {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 f.write_str(self.as_str())
284 }
285}
286
287impl From<&str> for CompactionTrigger {
288 fn from(s: &str) -> Self {
289 match s {
290 "auto" => Self::Auto,
291 "manual" => Self::Manual,
292 other => Self::Unknown(other.to_string()),
293 }
294 }
295}
296
297impl Serialize for CompactionTrigger {
298 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
299 serializer.serialize_str(self.as_str())
300 }
301}
302
303impl<'de> Deserialize<'de> for CompactionTrigger {
304 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
305 let s = String::deserialize(deserializer)?;
306 Ok(Self::from(s.as_str()))
307 }
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, Hash)]
312pub enum StopReason {
313 EndTurn,
315 MaxTokens,
317 ToolUse,
319 Unknown(String),
321}
322
323impl StopReason {
324 pub fn as_str(&self) -> &str {
325 match self {
326 Self::EndTurn => "end_turn",
327 Self::MaxTokens => "max_tokens",
328 Self::ToolUse => "tool_use",
329 Self::Unknown(s) => s.as_str(),
330 }
331 }
332}
333
334impl fmt::Display for StopReason {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 f.write_str(self.as_str())
337 }
338}
339
340impl From<&str> for StopReason {
341 fn from(s: &str) -> Self {
342 match s {
343 "end_turn" => Self::EndTurn,
344 "max_tokens" => Self::MaxTokens,
345 "tool_use" => Self::ToolUse,
346 other => Self::Unknown(other.to_string()),
347 }
348 }
349}
350
351impl Serialize for StopReason {
352 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
353 serializer.serialize_str(self.as_str())
354 }
355}
356
357impl<'de> Deserialize<'de> for StopReason {
358 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
359 let s = String::deserialize(deserializer)?;
360 Ok(Self::from(s.as_str()))
361 }
362}
363
364#[derive(Debug, Clone, PartialEq, Eq, Hash)]
366pub enum ApiKeySource {
367 None,
369 User,
370 Project,
371 Org,
372 Temporary,
373 Oauth,
374 Unknown(String),
376}
377
378impl ApiKeySource {
379 pub fn as_str(&self) -> &str {
380 match self {
381 Self::None => "none",
382 Self::User => "user",
383 Self::Project => "project",
384 Self::Org => "org",
385 Self::Temporary => "temporary",
386 Self::Oauth => "oauth",
387 Self::Unknown(s) => s.as_str(),
388 }
389 }
390}
391
392impl fmt::Display for ApiKeySource {
393 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394 f.write_str(self.as_str())
395 }
396}
397
398impl From<&str> for ApiKeySource {
399 fn from(s: &str) -> Self {
400 match s {
401 "none" => Self::None,
402 "user" => Self::User,
403 "project" => Self::Project,
404 "org" => Self::Org,
405 "temporary" => Self::Temporary,
406 "oauth" => Self::Oauth,
407 other => Self::Unknown(other.to_string()),
408 }
409 }
410}
411
412impl Serialize for ApiKeySource {
413 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
414 serializer.serialize_str(self.as_str())
415 }
416}
417
418impl<'de> Deserialize<'de> for ApiKeySource {
419 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
420 let s = String::deserialize(deserializer)?;
421 Ok(Self::from(s.as_str()))
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Hash)]
427pub enum OutputStyle {
428 Default,
430 Unknown(String),
432}
433
434impl OutputStyle {
435 pub fn as_str(&self) -> &str {
436 match self {
437 Self::Default => "default",
438 Self::Unknown(s) => s.as_str(),
439 }
440 }
441}
442
443impl fmt::Display for OutputStyle {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445 f.write_str(self.as_str())
446 }
447}
448
449impl From<&str> for OutputStyle {
450 fn from(s: &str) -> Self {
451 match s {
452 "default" => Self::Default,
453 other => Self::Unknown(other.to_string()),
454 }
455 }
456}
457
458impl Serialize for OutputStyle {
459 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
460 serializer.serialize_str(self.as_str())
461 }
462}
463
464impl<'de> Deserialize<'de> for OutputStyle {
465 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
466 let s = String::deserialize(deserializer)?;
467 Ok(Self::from(s.as_str()))
468 }
469}
470
471#[derive(Debug, Clone, PartialEq, Eq, Hash)]
473pub enum InitPermissionMode {
474 Default,
476 AcceptEdits,
477 BypassPermissions,
478 Plan,
479 DontAsk,
480 Auto,
481 Unknown(String),
483}
484
485impl InitPermissionMode {
486 pub fn as_str(&self) -> &str {
487 match self {
488 Self::Default => "default",
489 Self::AcceptEdits => "acceptEdits",
490 Self::BypassPermissions => "bypassPermissions",
491 Self::Plan => "plan",
492 Self::DontAsk => "dontAsk",
493 Self::Auto => "auto",
494 Self::Unknown(s) => s.as_str(),
495 }
496 }
497}
498
499impl fmt::Display for InitPermissionMode {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 f.write_str(self.as_str())
502 }
503}
504
505impl From<&str> for InitPermissionMode {
506 fn from(s: &str) -> Self {
507 match s {
508 "default" => Self::Default,
509 "acceptEdits" => Self::AcceptEdits,
510 "bypassPermissions" => Self::BypassPermissions,
511 "plan" => Self::Plan,
512 "dontAsk" => Self::DontAsk,
513 "auto" => Self::Auto,
514 other => Self::Unknown(other.to_string()),
515 }
516 }
517}
518
519impl Serialize for InitPermissionMode {
520 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
521 serializer.serialize_str(self.as_str())
522 }
523}
524
525impl<'de> Deserialize<'de> for InitPermissionMode {
526 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
527 let s = String::deserialize(deserializer)?;
528 Ok(Self::from(s.as_str()))
529 }
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Hash)]
534pub enum StatusMessageStatus {
535 Compacting,
537 Requesting,
539 Unknown(String),
541}
542
543impl StatusMessageStatus {
544 pub fn as_str(&self) -> &str {
545 match self {
546 Self::Compacting => "compacting",
547 Self::Requesting => "requesting",
548 Self::Unknown(s) => s.as_str(),
549 }
550 }
551}
552
553impl fmt::Display for StatusMessageStatus {
554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555 f.write_str(self.as_str())
556 }
557}
558
559impl From<&str> for StatusMessageStatus {
560 fn from(s: &str) -> Self {
561 match s {
562 "compacting" => Self::Compacting,
563 "requesting" => Self::Requesting,
564 other => Self::Unknown(other.to_string()),
565 }
566 }
567}
568
569impl Serialize for StatusMessageStatus {
570 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
571 serializer.serialize_str(self.as_str())
572 }
573}
574
575impl<'de> Deserialize<'de> for StatusMessageStatus {
576 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
577 let s = String::deserialize(deserializer)?;
578 Ok(Self::from(s.as_str()))
579 }
580}
581
582pub(crate) fn serialize_optional_uuid<S>(
584 uuid: &Option<Uuid>,
585 serializer: S,
586) -> Result<S::Ok, S::Error>
587where
588 S: Serializer,
589{
590 match uuid {
591 Some(id) => serializer.serialize_str(&id.to_string()),
592 None => serializer.serialize_none(),
593 }
594}
595
596pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
598where
599 D: Deserializer<'de>,
600{
601 let opt_str: Option<String> = Option::deserialize(deserializer)?;
602 match opt_str {
603 Some(s) => Uuid::parse_str(&s)
604 .map(Some)
605 .map_err(serde::de::Error::custom),
606 None => Ok(None),
607 }
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
613pub struct MessageOrigin {
614 pub kind: String,
615 #[serde(flatten)]
616 pub extra: serde_json::Map<String, Value>,
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
621pub struct SummarizeMetadata {
622 pub messages_summarized: u64,
623 #[serde(default, skip_serializing_if = "Option::is_none")]
624 pub user_context: Option<String>,
625 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub direction: Option<String>,
627}
628
629#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
631pub struct McpMeta {
632 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
633 pub meta: Option<Value>,
634 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub structured_content: Option<Value>,
636 #[serde(default, skip_serializing_if = "Vec::is_empty")]
641 pub resource_links: Vec<ResourceLink>,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
648pub struct ResourceLink {
649 pub uri: String,
650 pub name: String,
651 #[serde(default, skip_serializing_if = "Option::is_none")]
652 pub title: Option<String>,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub description: Option<String>,
655 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
656 pub mime_type: Option<String>,
657 #[serde(default, skip_serializing_if = "Option::is_none")]
658 pub size: Option<u64>,
659 #[serde(default, skip_serializing_if = "Option::is_none")]
660 pub annotations: Option<Value>,
661}
662
663#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
665pub struct ToolResultMeta {
666 pub id: String,
668 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub non_execution_kind: Option<String>,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub user_feedback: Option<String>,
679 #[serde(default, skip_serializing_if = "Option::is_none")]
684 pub remedy: Option<ToolResultRemedy>,
685 #[serde(default, skip_serializing_if = "Option::is_none")]
692 pub permission_decision: Option<ToolPermissionDecision>,
693 #[serde(default, skip_serializing_if = "Option::is_none")]
700 pub built_in_tool: Option<String>,
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
707pub struct ToolPermissionDecision {
708 pub decision: PermissionDecisionOutcome,
710 pub source: String,
718 #[serde(default, skip_serializing_if = "Option::is_none")]
724 pub reason_type: Option<PermissionDecisionReasonType>,
725 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub auto_mode_rule: Option<AutoModeRule>,
732}
733
734#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
737pub struct AutoModeRule {
738 pub list: AutoModeRuleList,
740 pub name: String,
742}
743
744#[derive(Debug, Clone, PartialEq, Eq, Hash)]
746pub enum AutoModeRuleList {
747 Allow,
749 SoftDeny,
751 Unknown(String),
753}
754
755impl AutoModeRuleList {
756 pub fn as_str(&self) -> &str {
757 match self {
758 Self::Allow => "allow",
759 Self::SoftDeny => "soft_deny",
760 Self::Unknown(s) => s.as_str(),
761 }
762 }
763}
764
765impl fmt::Display for AutoModeRuleList {
766 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767 f.write_str(self.as_str())
768 }
769}
770
771impl From<&str> for AutoModeRuleList {
772 fn from(s: &str) -> Self {
773 match s {
774 "allow" => Self::Allow,
775 "soft_deny" => Self::SoftDeny,
776 other => Self::Unknown(other.to_string()),
777 }
778 }
779}
780
781impl Serialize for AutoModeRuleList {
782 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
783 serializer.serialize_str(self.as_str())
784 }
785}
786
787impl<'de> Deserialize<'de> for AutoModeRuleList {
788 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
789 let s = String::deserialize(deserializer)?;
790 Ok(Self::from(s.as_str()))
791 }
792}
793
794#[derive(Debug, Clone, PartialEq, Eq, Hash)]
796pub enum PermissionDecisionOutcome {
797 Accept,
799 Reject,
801 Cancelled,
803 Unknown(String),
805}
806
807impl PermissionDecisionOutcome {
808 pub fn as_str(&self) -> &str {
809 match self {
810 Self::Accept => "accept",
811 Self::Reject => "reject",
812 Self::Cancelled => "cancelled",
813 Self::Unknown(s) => s.as_str(),
814 }
815 }
816}
817
818impl fmt::Display for PermissionDecisionOutcome {
819 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
820 f.write_str(self.as_str())
821 }
822}
823
824impl From<&str> for PermissionDecisionOutcome {
825 fn from(s: &str) -> Self {
826 match s {
827 "accept" => Self::Accept,
828 "reject" => Self::Reject,
829 "cancelled" => Self::Cancelled,
830 other => Self::Unknown(other.to_string()),
831 }
832 }
833}
834
835impl Serialize for PermissionDecisionOutcome {
836 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
837 serializer.serialize_str(self.as_str())
838 }
839}
840
841impl<'de> Deserialize<'de> for PermissionDecisionOutcome {
842 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
843 let s = String::deserialize(deserializer)?;
844 Ok(Self::from(s.as_str()))
845 }
846}
847
848#[derive(Debug, Clone, PartialEq, Eq, Hash)]
851pub enum PermissionDecisionReasonType {
852 Rule,
854 Mode,
856 SubcommandResults,
858 PermissionPromptTool,
860 Hook,
862 AsyncAgent,
864 SandboxOverride,
866 WorkingDir,
868 SafetyCheck,
870 Classifier,
872 Other,
874 Unknown(String),
876}
877
878impl PermissionDecisionReasonType {
879 pub fn as_str(&self) -> &str {
880 match self {
881 Self::Rule => "rule",
882 Self::Mode => "mode",
883 Self::SubcommandResults => "subcommandResults",
884 Self::PermissionPromptTool => "permissionPromptTool",
885 Self::Hook => "hook",
886 Self::AsyncAgent => "asyncAgent",
887 Self::SandboxOverride => "sandboxOverride",
888 Self::WorkingDir => "workingDir",
889 Self::SafetyCheck => "safetyCheck",
890 Self::Classifier => "classifier",
891 Self::Other => "other",
892 Self::Unknown(s) => s.as_str(),
893 }
894 }
895}
896
897impl fmt::Display for PermissionDecisionReasonType {
898 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
899 f.write_str(self.as_str())
900 }
901}
902
903impl From<&str> for PermissionDecisionReasonType {
904 fn from(s: &str) -> Self {
905 match s {
906 "rule" => Self::Rule,
907 "mode" => Self::Mode,
908 "subcommandResults" => Self::SubcommandResults,
909 "permissionPromptTool" => Self::PermissionPromptTool,
910 "hook" => Self::Hook,
911 "asyncAgent" => Self::AsyncAgent,
912 "sandboxOverride" => Self::SandboxOverride,
913 "workingDir" => Self::WorkingDir,
914 "safetyCheck" => Self::SafetyCheck,
915 "classifier" => Self::Classifier,
916 "other" => Self::Other,
917 other => Self::Unknown(other.to_string()),
918 }
919 }
920}
921
922impl Serialize for PermissionDecisionReasonType {
923 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
924 serializer.serialize_str(self.as_str())
925 }
926}
927
928impl<'de> Deserialize<'de> for PermissionDecisionReasonType {
929 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
930 let s = String::deserialize(deserializer)?;
931 Ok(Self::from(s.as_str()))
932 }
933}
934
935#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
940pub struct ToolResultRemedy {
941 pub kind: ToolResultRemedyKind,
944 #[serde(default, skip_serializing_if = "Option::is_none")]
948 pub servers: Option<Vec<String>>,
949 #[serde(default, skip_serializing_if = "Option::is_none")]
952 pub provider: Option<RemedyLoginProvider>,
953 #[serde(default, skip_serializing_if = "Option::is_none")]
957 pub path: Option<String>,
958 #[serde(default, skip_serializing_if = "Option::is_none")]
963 pub managed: Option<bool>,
964 #[serde(default, skip_serializing_if = "Option::is_none")]
966 pub feature: Option<RemedyFeature>,
967 #[serde(default, skip_serializing_if = "Option::is_none")]
969 pub cause: Option<RemedyFeatureCause>,
970 #[serde(default, skip_serializing_if = "Option::is_none")]
972 pub policy_kind: Option<RemedyPolicyKind>,
973}
974
975#[derive(Debug, Clone, PartialEq, Eq, Hash)]
977pub enum ToolResultRemedyKind {
978 McpNeedsAuth,
980 McpDisabled,
982 McpRequiredMissing,
984 AuthExpired,
986 AuthOverridden,
988 AuthMissingScope,
990 DesignNeedsAuthorization,
992 SandboxViolation,
994 OutsideReadsBlocked,
996 MemoryPaused,
998 FeatureDisabled,
1000 PolicyDenied,
1003 SpawnArgLimit,
1005 StagedForReview,
1007 Unknown(String),
1009}
1010
1011impl ToolResultRemedyKind {
1012 pub fn as_str(&self) -> &str {
1013 match self {
1014 Self::McpNeedsAuth => "mcp_needs_auth",
1015 Self::McpDisabled => "mcp_disabled",
1016 Self::McpRequiredMissing => "mcp_required_missing",
1017 Self::AuthExpired => "auth_expired",
1018 Self::AuthOverridden => "auth_overridden",
1019 Self::AuthMissingScope => "auth_missing_scope",
1020 Self::DesignNeedsAuthorization => "design_needs_authorization",
1021 Self::SandboxViolation => "sandbox_violation",
1022 Self::OutsideReadsBlocked => "outside_reads_blocked",
1023 Self::MemoryPaused => "memory_paused",
1024 Self::FeatureDisabled => "feature_disabled",
1025 Self::PolicyDenied => "policy_denied",
1026 Self::SpawnArgLimit => "spawn_arg_limit",
1027 Self::StagedForReview => "staged_for_review",
1028 Self::Unknown(s) => s.as_str(),
1029 }
1030 }
1031}
1032
1033impl fmt::Display for ToolResultRemedyKind {
1034 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1035 f.write_str(self.as_str())
1036 }
1037}
1038
1039impl From<&str> for ToolResultRemedyKind {
1040 fn from(s: &str) -> Self {
1041 match s {
1042 "mcp_needs_auth" => Self::McpNeedsAuth,
1043 "mcp_disabled" => Self::McpDisabled,
1044 "mcp_required_missing" => Self::McpRequiredMissing,
1045 "auth_expired" => Self::AuthExpired,
1046 "auth_overridden" => Self::AuthOverridden,
1047 "auth_missing_scope" => Self::AuthMissingScope,
1048 "design_needs_authorization" => Self::DesignNeedsAuthorization,
1049 "sandbox_violation" => Self::SandboxViolation,
1050 "outside_reads_blocked" => Self::OutsideReadsBlocked,
1051 "memory_paused" => Self::MemoryPaused,
1052 "feature_disabled" => Self::FeatureDisabled,
1053 "policy_denied" => Self::PolicyDenied,
1054 "spawn_arg_limit" => Self::SpawnArgLimit,
1055 "staged_for_review" => Self::StagedForReview,
1056 other => Self::Unknown(other.to_string()),
1057 }
1058 }
1059}
1060
1061impl Serialize for ToolResultRemedyKind {
1062 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1063 serializer.serialize_str(self.as_str())
1064 }
1065}
1066
1067impl<'de> Deserialize<'de> for ToolResultRemedyKind {
1068 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1069 let s = String::deserialize(deserializer)?;
1070 Ok(Self::from(s.as_str()))
1071 }
1072}
1073
1074#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1076pub enum RemedyLoginProvider {
1077 ClaudeAi,
1079 ClaudeDesign,
1081 Unknown(String),
1083}
1084
1085impl RemedyLoginProvider {
1086 pub fn as_str(&self) -> &str {
1087 match self {
1088 Self::ClaudeAi => "claude_ai",
1089 Self::ClaudeDesign => "claude_design",
1090 Self::Unknown(s) => s.as_str(),
1091 }
1092 }
1093}
1094
1095impl fmt::Display for RemedyLoginProvider {
1096 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1097 f.write_str(self.as_str())
1098 }
1099}
1100
1101impl From<&str> for RemedyLoginProvider {
1102 fn from(s: &str) -> Self {
1103 match s {
1104 "claude_ai" => Self::ClaudeAi,
1105 "claude_design" => Self::ClaudeDesign,
1106 other => Self::Unknown(other.to_string()),
1107 }
1108 }
1109}
1110
1111impl Serialize for RemedyLoginProvider {
1112 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1113 serializer.serialize_str(self.as_str())
1114 }
1115}
1116
1117impl<'de> Deserialize<'de> for RemedyLoginProvider {
1118 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1119 let s = String::deserialize(deserializer)?;
1120 Ok(Self::from(s.as_str()))
1121 }
1122}
1123
1124#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1127pub enum RemedyFeature {
1128 Workflows,
1129 Artifacts,
1130 Unknown(String),
1132}
1133
1134impl RemedyFeature {
1135 pub fn as_str(&self) -> &str {
1136 match self {
1137 Self::Workflows => "workflows",
1138 Self::Artifacts => "artifacts",
1139 Self::Unknown(s) => s.as_str(),
1140 }
1141 }
1142}
1143
1144impl fmt::Display for RemedyFeature {
1145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1146 f.write_str(self.as_str())
1147 }
1148}
1149
1150impl From<&str> for RemedyFeature {
1151 fn from(s: &str) -> Self {
1152 match s {
1153 "workflows" => Self::Workflows,
1154 "artifacts" => Self::Artifacts,
1155 other => Self::Unknown(other.to_string()),
1156 }
1157 }
1158}
1159
1160impl Serialize for RemedyFeature {
1161 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1162 serializer.serialize_str(self.as_str())
1163 }
1164}
1165
1166impl<'de> Deserialize<'de> for RemedyFeature {
1167 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1168 let s = String::deserialize(deserializer)?;
1169 Ok(Self::from(s.as_str()))
1170 }
1171}
1172
1173#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1176pub enum RemedyFeatureCause {
1177 ManagedSettings,
1180 OrgPolicy,
1182 Unavailable,
1184 UserSetting,
1187 UnsupportedRuntime,
1191 Unknown(String),
1193}
1194
1195impl RemedyFeatureCause {
1196 pub fn as_str(&self) -> &str {
1197 match self {
1198 Self::ManagedSettings => "managed_settings",
1199 Self::OrgPolicy => "org_policy",
1200 Self::Unavailable => "unavailable",
1201 Self::UserSetting => "user_setting",
1202 Self::UnsupportedRuntime => "unsupported_runtime",
1203 Self::Unknown(s) => s.as_str(),
1204 }
1205 }
1206}
1207
1208impl fmt::Display for RemedyFeatureCause {
1209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1210 f.write_str(self.as_str())
1211 }
1212}
1213
1214impl From<&str> for RemedyFeatureCause {
1215 fn from(s: &str) -> Self {
1216 match s {
1217 "managed_settings" => Self::ManagedSettings,
1218 "org_policy" => Self::OrgPolicy,
1219 "unavailable" => Self::Unavailable,
1220 "user_setting" => Self::UserSetting,
1221 "unsupported_runtime" => Self::UnsupportedRuntime,
1222 other => Self::Unknown(other.to_string()),
1223 }
1224 }
1225}
1226
1227impl Serialize for RemedyFeatureCause {
1228 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1229 serializer.serialize_str(self.as_str())
1230 }
1231}
1232
1233impl<'de> Deserialize<'de> for RemedyFeatureCause {
1234 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1235 let s = String::deserialize(deserializer)?;
1236 Ok(Self::from(s.as_str()))
1237 }
1238}
1239
1240#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1243pub enum RemedyPolicyKind {
1244 OrgDenied,
1246 Latched,
1249 Unknown(String),
1251}
1252
1253impl RemedyPolicyKind {
1254 pub fn as_str(&self) -> &str {
1255 match self {
1256 Self::OrgDenied => "org_denied",
1257 Self::Latched => "latched",
1258 Self::Unknown(s) => s.as_str(),
1259 }
1260 }
1261}
1262
1263impl fmt::Display for RemedyPolicyKind {
1264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1265 f.write_str(self.as_str())
1266 }
1267}
1268
1269impl From<&str> for RemedyPolicyKind {
1270 fn from(s: &str) -> Self {
1271 match s {
1272 "org_denied" => Self::OrgDenied,
1273 "latched" => Self::Latched,
1274 other => Self::Unknown(other.to_string()),
1275 }
1276 }
1277}
1278
1279impl Serialize for RemedyPolicyKind {
1280 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1281 serializer.serialize_str(self.as_str())
1282 }
1283}
1284
1285impl<'de> Deserialize<'de> for RemedyPolicyKind {
1286 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1287 let s = String::deserialize(deserializer)?;
1288 Ok(Self::from(s.as_str()))
1289 }
1290}
1291
1292#[derive(Debug, Clone, Serialize, Deserialize)]
1294pub struct UserMessage {
1295 pub message: MessageContent,
1296 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1297 #[serde(
1298 serialize_with = "serialize_optional_uuid",
1299 deserialize_with = "deserialize_optional_uuid"
1300 )]
1301 pub session_id: Option<Uuid>,
1302 #[serde(skip_serializing_if = "Option::is_none")]
1304 pub parent_tool_use_id: Option<String>,
1305 #[serde(skip_serializing_if = "Option::is_none")]
1307 pub uuid: Option<String>,
1308 #[serde(skip_serializing_if = "Option::is_none")]
1310 pub timestamp: Option<String>,
1311 #[serde(skip_serializing_if = "Option::is_none")]
1318 pub tool_use_result: Option<serde_json::Value>,
1319 #[serde(default, skip_serializing_if = "Option::is_none")]
1324 pub agent_id: Option<String>,
1325 #[serde(skip_serializing_if = "Option::is_none")]
1328 pub subagent_type: Option<String>,
1329 #[serde(skip_serializing_if = "Option::is_none")]
1331 pub task_description: Option<String>,
1332 #[serde(skip_serializing_if = "Option::is_none")]
1333 pub origin: Option<MessageOrigin>,
1334 #[serde(skip_serializing_if = "Option::is_none")]
1335 pub priority: Option<String>,
1336 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1337 pub is_synthetic: Option<bool>,
1338 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1339 pub should_query: Option<bool>,
1340 #[serde(default, skip_serializing_if = "Option::is_none")]
1341 pub is_meta: Option<bool>,
1342 #[serde(default, skip_serializing_if = "Option::is_none")]
1343 pub is_visible_in_transcript_only: Option<bool>,
1344 #[serde(default, skip_serializing_if = "Option::is_none")]
1345 pub is_virtual: Option<bool>,
1346 #[serde(default, skip_serializing_if = "Option::is_none")]
1347 pub is_compact_summary: Option<bool>,
1348 #[serde(skip_serializing_if = "Option::is_none")]
1349 pub summarize_metadata: Option<SummarizeMetadata>,
1350 #[serde(skip_serializing_if = "Option::is_none")]
1351 pub mcp_meta: Option<McpMeta>,
1352 #[serde(skip_serializing_if = "Option::is_none")]
1355 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1356 #[serde(skip_serializing_if = "Option::is_none")]
1357 pub source_tool_use_id: Option<String>,
1358 #[serde(skip_serializing_if = "Option::is_none")]
1359 pub source_tool_assistant_uuid: Option<String>,
1360 #[serde(skip_serializing_if = "Option::is_none")]
1361 pub image_paste_ids: Option<Vec<u64>>,
1362 #[serde(skip_serializing_if = "Option::is_none")]
1363 pub client_platform: Option<String>,
1364 #[serde(skip_serializing_if = "Option::is_none")]
1365 pub inbound_origin: Option<String>,
1366 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1367 pub is_replay: Option<bool>,
1368 #[serde(skip_serializing_if = "Option::is_none")]
1369 pub file_attachments: Option<Vec<Value>>,
1370 #[serde(default, skip_serializing_if = "Option::is_none")]
1372 pub seeded_summon: Option<bool>,
1373 #[serde(default, skip_serializing_if = "Option::is_none")]
1376 pub client_composed: Option<bool>,
1377 #[serde(default, skip_serializing_if = "Option::is_none")]
1386 pub text_not_typed: Option<bool>,
1387 #[serde(default, skip_serializing_if = "Option::is_none")]
1394 pub initiator: Option<String>,
1395 #[serde(default, skip_serializing_if = "Option::is_none")]
1401 pub pasted_content: Option<Vec<Value>>,
1402 #[serde(default, skip_serializing_if = "Option::is_none")]
1406 pub historical: Option<bool>,
1407 #[serde(default, skip_serializing_if = "Option::is_none")]
1413 pub server_received_wall_ms: Option<f64>,
1414 #[serde(default, skip_serializing_if = "Option::is_none")]
1418 pub refused_message_id: Option<String>,
1419 #[serde(default, skip_serializing_if = "Option::is_none")]
1426 pub requeue_if_interrupted_within_ms: Option<u64>,
1427 #[serde(default, skip_serializing_if = "Option::is_none")]
1436 pub ultrathink_typed: Option<bool>,
1437 #[serde(default, skip_serializing_if = "Option::is_none")]
1441 pub tool_states: Option<Vec<Value>>,
1442 #[serde(default, skip_serializing_if = "Option::is_none")]
1448 pub plugin_prompt: Option<PluginPrompt>,
1449}
1450
1451#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1454pub struct PluginPrompt {
1455 pub name: String,
1458 pub text: String,
1464}
1465
1466impl UserMessage {
1467 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1475 &self,
1476 ) -> Option<Result<T, serde_json::Error>> {
1477 self.tool_use_result
1478 .as_ref()
1479 .map(|v| serde_json::from_value(v.clone()))
1480 }
1481
1482 pub fn subagent_result(&self) -> Option<SubagentResult> {
1493 self.tool_use_result
1494 .as_ref()
1495 .and_then(|v| serde_json::from_value(v.clone()).ok())
1496 }
1497}
1498
1499#[derive(Debug, Clone, Serialize, Deserialize)]
1510pub struct SubagentResult {
1511 #[serde(skip_serializing_if = "Option::is_none")]
1513 pub status: Option<String>,
1514 #[serde(skip_serializing_if = "Option::is_none")]
1516 pub prompt: Option<String>,
1517 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1519 pub agent_id: Option<String>,
1520 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1522 pub agent_type: Option<String>,
1523 #[serde(
1525 default,
1526 deserialize_with = "deserialize_content_blocks",
1527 skip_serializing_if = "Vec::is_empty"
1528 )]
1529 pub content: Vec<ContentBlock>,
1530 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1532 pub resolved_model: Option<String>,
1533 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1535 pub total_duration_ms: Option<u64>,
1536 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1538 pub total_tokens: Option<u64>,
1539 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1541 pub total_tool_use_count: Option<u64>,
1542 #[serde(skip_serializing_if = "Option::is_none")]
1544 pub usage: Option<super::result::UsageInfo>,
1545 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1547 pub tool_stats: Option<SubagentToolStats>,
1548}
1549
1550#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1555#[serde(rename_all = "camelCase")]
1556pub struct SubagentToolStats {
1557 #[serde(default)]
1558 pub read_count: u64,
1559 #[serde(default)]
1560 pub search_count: u64,
1561 #[serde(default)]
1562 pub bash_count: u64,
1563 #[serde(default)]
1564 pub edit_file_count: u64,
1565 #[serde(default)]
1566 pub lines_added: u64,
1567 #[serde(default)]
1568 pub lines_removed: u64,
1569 #[serde(default)]
1570 pub other_tool_count: u64,
1571 #[serde(flatten)]
1572 pub extra: serde_json::Map<String, Value>,
1573}
1574
1575#[derive(Debug, Clone, Default, PartialEq, Eq)]
1602pub struct SubagentUsageRollup {
1603 pub subagent_tokens: u64,
1606 pub agent_count: u32,
1608 pub tool_uses: u64,
1610 pub duration_ms: u64,
1612 seen_agent_ids: std::collections::BTreeSet<String>,
1613}
1614
1615impl SubagentUsageRollup {
1616 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1622 match output {
1623 ClaudeOutput::User(user) => self.observe_user(user),
1624 _ => false,
1625 }
1626 }
1627
1628 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1635 let Some(result) = user.subagent_result() else {
1636 return false;
1637 };
1638 if result.total_tokens.is_none() && result.agent_id.is_none() {
1639 return false;
1640 }
1641 if let Some(agent_id) = &result.agent_id {
1642 if !self.seen_agent_ids.insert(agent_id.clone()) {
1643 return false;
1644 }
1645 }
1646 self.agent_count += 1;
1647 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1648 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1649 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1650 true
1651 }
1652}
1653
1654#[derive(Debug, Clone, Serialize, Deserialize)]
1656pub struct MessageContent {
1657 pub role: MessageRole,
1658 #[serde(deserialize_with = "deserialize_content_blocks")]
1659 pub content: Vec<ContentBlock>,
1660}
1661
1662#[derive(Debug, Clone, Serialize, Deserialize)]
1664pub struct SystemMessage {
1665 pub subtype: SystemSubtype,
1666 #[serde(flatten)]
1667 pub data: Value, }
1669
1670impl SystemMessage {
1671 pub fn is_init(&self) -> bool {
1673 self.subtype == SystemSubtype::Init
1674 }
1675
1676 pub fn is_status(&self) -> bool {
1678 self.subtype == SystemSubtype::Status
1679 }
1680
1681 pub fn is_compact_boundary(&self) -> bool {
1683 self.subtype == SystemSubtype::CompactBoundary
1684 }
1685
1686 pub fn as_init(&self) -> Option<InitMessage> {
1688 if self.subtype != SystemSubtype::Init {
1689 return None;
1690 }
1691 serde_json::from_value(self.data.clone()).ok()
1692 }
1693
1694 pub fn as_status(&self) -> Option<StatusMessage> {
1696 if self.subtype != SystemSubtype::Status {
1697 return None;
1698 }
1699 serde_json::from_value(self.data.clone()).ok()
1700 }
1701
1702 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1704 if self.subtype != SystemSubtype::CompactBoundary {
1705 return None;
1706 }
1707 serde_json::from_value(self.data.clone()).ok()
1708 }
1709
1710 pub fn is_task_started(&self) -> bool {
1712 self.subtype == SystemSubtype::TaskStarted
1713 }
1714
1715 pub fn is_task_progress(&self) -> bool {
1717 self.subtype == SystemSubtype::TaskProgress
1718 }
1719
1720 pub fn is_task_notification(&self) -> bool {
1722 self.subtype == SystemSubtype::TaskNotification
1723 }
1724
1725 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1727 if self.subtype != SystemSubtype::TaskStarted {
1728 return None;
1729 }
1730 serde_json::from_value(self.data.clone()).ok()
1731 }
1732
1733 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1735 if self.subtype != SystemSubtype::TaskProgress {
1736 return None;
1737 }
1738 serde_json::from_value(self.data.clone()).ok()
1739 }
1740
1741 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1743 if self.subtype != SystemSubtype::TaskNotification {
1744 return None;
1745 }
1746 serde_json::from_value(self.data.clone()).ok()
1747 }
1748
1749 pub fn is_task_updated(&self) -> bool {
1751 self.subtype == SystemSubtype::TaskUpdated
1752 }
1753
1754 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1756 if self.subtype != SystemSubtype::TaskUpdated {
1757 return None;
1758 }
1759 serde_json::from_value(self.data.clone()).ok()
1760 }
1761
1762 pub fn is_thinking_tokens(&self) -> bool {
1764 self.subtype == SystemSubtype::ThinkingTokens
1765 }
1766
1767 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1769 if self.subtype != SystemSubtype::ThinkingTokens {
1770 return None;
1771 }
1772 serde_json::from_value(self.data.clone()).ok()
1773 }
1774
1775 pub fn is_code_change_published(&self) -> bool {
1777 self.subtype == SystemSubtype::CodeChangePublished
1778 }
1779
1780 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1782 if self.subtype != SystemSubtype::CodeChangePublished {
1783 return None;
1784 }
1785 serde_json::from_value(self.data.clone()).ok()
1786 }
1787
1788 pub fn is_vcs_state_changed(&self) -> bool {
1790 self.subtype == SystemSubtype::VcsStateChanged
1791 }
1792
1793 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1795 if self.subtype != SystemSubtype::VcsStateChanged {
1796 return None;
1797 }
1798 serde_json::from_value(self.data.clone()).ok()
1799 }
1800
1801 pub fn is_feedback_draft_queued(&self) -> bool {
1803 self.subtype == SystemSubtype::FeedbackDraftQueued
1804 }
1805
1806 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1808 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1809 return None;
1810 }
1811 serde_json::from_value(self.data.clone()).ok()
1812 }
1813
1814 pub fn is_cloud_session_delta(&self) -> bool {
1816 self.subtype == SystemSubtype::CloudSessionDelta
1817 }
1818
1819 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1821 if self.subtype != SystemSubtype::CloudSessionDelta {
1822 return None;
1823 }
1824 serde_json::from_value(self.data.clone()).ok()
1825 }
1826
1827 pub fn is_dev_intent(&self) -> bool {
1829 self.subtype == SystemSubtype::DevIntent
1830 }
1831
1832 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1834 if self.subtype != SystemSubtype::DevIntent {
1835 return None;
1836 }
1837 serde_json::from_value(self.data.clone()).ok()
1838 }
1839
1840 pub fn is_turn_handoff_available(&self) -> bool {
1842 self.subtype == SystemSubtype::TurnHandoffAvailable
1843 }
1844
1845 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1847 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1848 return None;
1849 }
1850 serde_json::from_value(self.data.clone()).ok()
1851 }
1852
1853 pub fn is_turn_preempted(&self) -> bool {
1855 self.subtype == SystemSubtype::TurnPreempted
1856 }
1857
1858 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1860 if self.subtype != SystemSubtype::TurnPreempted {
1861 return None;
1862 }
1863 serde_json::from_value(self.data.clone()).ok()
1864 }
1865
1866 pub fn is_peer_message_hold(&self) -> bool {
1868 self.subtype == SystemSubtype::PeerMessageHold
1869 }
1870
1871 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1873 if self.subtype != SystemSubtype::PeerMessageHold {
1874 return None;
1875 }
1876 serde_json::from_value(self.data.clone()).ok()
1877 }
1878
1879 pub fn is_per_turn_effort_changed(&self) -> bool {
1881 self.subtype == SystemSubtype::PerTurnEffortChanged
1882 }
1883
1884 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1886 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1887 return None;
1888 }
1889 serde_json::from_value(self.data.clone()).ok()
1890 }
1891
1892 pub fn is_session_title_changed(&self) -> bool {
1894 self.subtype == SystemSubtype::SessionTitleChanged
1895 }
1896
1897 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1899 if self.subtype != SystemSubtype::SessionTitleChanged {
1900 return None;
1901 }
1902 serde_json::from_value(self.data.clone()).ok()
1903 }
1904
1905 pub fn is_session_writes_lost(&self) -> bool {
1907 self.subtype == SystemSubtype::SessionWritesLost
1908 }
1909
1910 pub fn as_session_writes_lost(&self) -> Option<SessionWritesLostMessage> {
1912 if self.subtype != SystemSubtype::SessionWritesLost {
1913 return None;
1914 }
1915 serde_json::from_value(self.data.clone()).ok()
1916 }
1917
1918 pub fn is_instruction_size_warning(&self) -> bool {
1920 self.subtype == SystemSubtype::InstructionSizeWarning
1921 }
1922
1923 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1925 if self.subtype != SystemSubtype::InstructionSizeWarning {
1926 return None;
1927 }
1928 serde_json::from_value(self.data.clone()).ok()
1929 }
1930
1931 pub fn is_permission_check_status(&self) -> bool {
1933 self.subtype == SystemSubtype::PermissionCheckStatus
1934 }
1935
1936 pub fn as_permission_check_status(&self) -> Option<PermissionCheckStatusMessage> {
1938 if self.subtype != SystemSubtype::PermissionCheckStatus {
1939 return None;
1940 }
1941 serde_json::from_value(self.data.clone()).ok()
1942 }
1943
1944 pub fn is_inherited_asks_settled(&self) -> bool {
1946 self.subtype == SystemSubtype::InheritedAsksSettled
1947 }
1948
1949 pub fn as_inherited_asks_settled(&self) -> Option<InheritedAsksSettledMessage> {
1951 if self.subtype != SystemSubtype::InheritedAsksSettled {
1952 return None;
1953 }
1954 serde_json::from_value(self.data.clone()).ok()
1955 }
1956
1957 pub fn is_file_attachments_missing(&self) -> bool {
1959 self.subtype == SystemSubtype::FileAttachmentsMissing
1960 }
1961
1962 pub fn as_file_attachments_missing(&self) -> Option<FileAttachmentsMissingMessage> {
1964 if self.subtype != SystemSubtype::FileAttachmentsMissing {
1965 return None;
1966 }
1967 serde_json::from_value(self.data.clone()).ok()
1968 }
1969
1970 pub fn is_ui_focus(&self) -> bool {
1972 self.subtype == SystemSubtype::UiFocus
1973 }
1974
1975 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1977 if self.subtype != SystemSubtype::UiFocus {
1978 return None;
1979 }
1980 serde_json::from_value(self.data.clone()).ok()
1981 }
1982
1983 pub fn is_ui_invalidate(&self) -> bool {
1985 self.subtype == SystemSubtype::UiInvalidate
1986 }
1987
1988 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1990 if self.subtype != SystemSubtype::UiInvalidate {
1991 return None;
1992 }
1993 serde_json::from_value(self.data.clone()).ok()
1994 }
1995
1996 pub fn is_ui_log(&self) -> bool {
1998 self.subtype == SystemSubtype::UiLog
1999 }
2000
2001 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
2003 if self.subtype != SystemSubtype::UiLog {
2004 return None;
2005 }
2006 serde_json::from_value(self.data.clone()).ok()
2007 }
2008
2009 pub fn is_ui_panes(&self) -> bool {
2011 self.subtype == SystemSubtype::UiPanes
2012 }
2013
2014 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
2016 if self.subtype != SystemSubtype::UiPanes {
2017 return None;
2018 }
2019 serde_json::from_value(self.data.clone()).ok()
2020 }
2021
2022 pub fn is_ui_placed(&self) -> bool {
2024 self.subtype == SystemSubtype::UiPlaced
2025 }
2026
2027 pub fn as_ui_placed(&self) -> Option<UiPlacedMessage> {
2029 if self.subtype != SystemSubtype::UiPlaced {
2030 return None;
2031 }
2032 serde_json::from_value(self.data.clone()).ok()
2033 }
2034
2035 pub fn is_ui_scroll(&self) -> bool {
2037 self.subtype == SystemSubtype::UiScroll
2038 }
2039
2040 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
2042 if self.subtype != SystemSubtype::UiScroll {
2043 return None;
2044 }
2045 serde_json::from_value(self.data.clone()).ok()
2046 }
2047
2048 pub fn is_ui_status(&self) -> bool {
2050 self.subtype == SystemSubtype::UiStatus
2051 }
2052
2053 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
2055 if self.subtype != SystemSubtype::UiStatus {
2056 return None;
2057 }
2058 serde_json::from_value(self.data.clone()).ok()
2059 }
2060
2061 pub fn is_ui_toast(&self) -> bool {
2063 self.subtype == SystemSubtype::UiToast
2064 }
2065
2066 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
2068 if self.subtype != SystemSubtype::UiToast {
2069 return None;
2070 }
2071 serde_json::from_value(self.data.clone()).ok()
2072 }
2073
2074 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
2076 macro_rules! parse {
2077 ($variant:ident, $ty:ty) => {
2078 serde_json::from_value::<$ty>(self.data.clone())
2079 .ok()
2080 .map(KnownSystemEvent::$variant)
2081 };
2082 }
2083
2084 match self.subtype {
2085 SystemSubtype::Init => parse!(Init, InitMessage),
2086 SystemSubtype::Status => parse!(Status, StatusMessage),
2087 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
2088 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
2089 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
2090 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
2091 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
2092 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
2093 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
2094 SystemSubtype::ControlRequestProgress => {
2095 parse!(ControlRequestProgress, ControlRequestProgressMessage)
2096 }
2097 SystemSubtype::ModelRefusalFallback => {
2098 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
2099 }
2100 SystemSubtype::ModelRefusalNoFallback => {
2101 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
2102 }
2103 SystemSubtype::LocalCommandOutput => {
2104 parse!(LocalCommandOutput, LocalCommandOutputMessage)
2105 }
2106 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
2107 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
2108 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
2109 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
2110 SystemSubtype::BackgroundTasksChanged => {
2111 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
2112 }
2113 SystemSubtype::SessionStateChanged => {
2114 parse!(SessionStateChanged, SessionStateChangedMessage)
2115 }
2116 SystemSubtype::WorkerShuttingDown => {
2117 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
2118 }
2119 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
2120 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
2121 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
2122 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
2123 SystemSubtype::ElicitationComplete => {
2124 parse!(ElicitationComplete, ElicitationCompleteMessage)
2125 }
2126 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
2127 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
2128 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
2129 SystemSubtype::CodeChangePublished => {
2130 parse!(CodeChangePublished, CodeChangePublishedMessage)
2131 }
2132 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
2133 SystemSubtype::FeedbackDraftQueued => {
2134 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
2135 }
2136 SystemSubtype::CloudSessionDelta => {
2137 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
2138 }
2139 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
2140 SystemSubtype::TurnHandoffAvailable => {
2141 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
2142 }
2143 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
2144 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
2145 SystemSubtype::PerTurnEffortChanged => {
2146 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
2147 }
2148 SystemSubtype::SessionTitleChanged => {
2149 parse!(SessionTitleChanged, SessionTitleChangedMessage)
2150 }
2151 SystemSubtype::SessionWritesLost => {
2152 parse!(SessionWritesLost, SessionWritesLostMessage)
2153 }
2154 SystemSubtype::InstructionSizeWarning => {
2155 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
2156 }
2157 SystemSubtype::FileAttachmentsMissing => {
2158 parse!(FileAttachmentsMissing, FileAttachmentsMissingMessage)
2159 }
2160 SystemSubtype::PermissionCheckStatus => {
2161 parse!(PermissionCheckStatus, PermissionCheckStatusMessage)
2162 }
2163 SystemSubtype::InheritedAsksSettled => {
2164 parse!(InheritedAsksSettled, InheritedAsksSettledMessage)
2165 }
2166 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
2167 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
2168 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
2169 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
2170 SystemSubtype::UiPlaced => parse!(UiPlaced, UiPlacedMessage),
2171 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
2172 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
2173 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
2174 SystemSubtype::Unknown(_) => None,
2175 }
2176 }
2177
2178 pub fn typed_value(&self) -> Option<Value> {
2187 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
2188 parsed.and_then(|v| serde_json::to_value(v).ok())
2189 }
2190 match self.subtype {
2191 SystemSubtype::Init => reserialize(self.as_init()),
2192 SystemSubtype::Status => reserialize(self.as_status()),
2193 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
2194 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
2195 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
2196 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
2197 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
2198 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
2199 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
2200 SystemSubtype::ControlRequestProgress => {
2201 reserialize(parse_system::<ControlRequestProgressMessage>(self))
2202 }
2203 SystemSubtype::ModelRefusalFallback => {
2204 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
2205 }
2206 SystemSubtype::ModelRefusalNoFallback => {
2207 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
2208 }
2209 SystemSubtype::LocalCommandOutput => {
2210 reserialize(parse_system::<LocalCommandOutputMessage>(self))
2211 }
2212 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
2213 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
2214 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
2215 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
2216 SystemSubtype::BackgroundTasksChanged => {
2217 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
2218 }
2219 SystemSubtype::SessionStateChanged => {
2220 reserialize(parse_system::<SessionStateChangedMessage>(self))
2221 }
2222 SystemSubtype::WorkerShuttingDown => {
2223 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
2224 }
2225 SystemSubtype::CommandsChanged => {
2226 reserialize(parse_system::<CommandsChangedMessage>(self))
2227 }
2228 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
2229 SystemSubtype::FilesPersisted => {
2230 reserialize(parse_system::<FilesPersistedMessage>(self))
2231 }
2232 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
2233 SystemSubtype::ElicitationComplete => {
2234 reserialize(parse_system::<ElicitationCompleteMessage>(self))
2235 }
2236 SystemSubtype::PermissionDenied => {
2237 reserialize(parse_system::<PermissionDeniedMessage>(self))
2238 }
2239 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
2240 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
2241 SystemSubtype::CodeChangePublished => {
2242 reserialize(parse_system::<CodeChangePublishedMessage>(self))
2243 }
2244 SystemSubtype::VcsStateChanged => {
2245 reserialize(parse_system::<VcsStateChangedMessage>(self))
2246 }
2247 SystemSubtype::FeedbackDraftQueued => {
2248 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
2249 }
2250 SystemSubtype::CloudSessionDelta => {
2251 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
2252 }
2253 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
2254 SystemSubtype::TurnHandoffAvailable => {
2255 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
2256 }
2257 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
2258 SystemSubtype::PeerMessageHold => {
2259 reserialize(parse_system::<PeerMessageHoldMessage>(self))
2260 }
2261 SystemSubtype::PerTurnEffortChanged => {
2262 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
2263 }
2264 SystemSubtype::SessionTitleChanged => {
2265 reserialize(parse_system::<SessionTitleChangedMessage>(self))
2266 }
2267 SystemSubtype::SessionWritesLost => {
2268 reserialize(parse_system::<SessionWritesLostMessage>(self))
2269 }
2270 SystemSubtype::InstructionSizeWarning => {
2271 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
2272 }
2273 SystemSubtype::FileAttachmentsMissing => {
2274 reserialize(parse_system::<FileAttachmentsMissingMessage>(self))
2275 }
2276 SystemSubtype::PermissionCheckStatus => {
2277 reserialize(parse_system::<PermissionCheckStatusMessage>(self))
2278 }
2279 SystemSubtype::InheritedAsksSettled => {
2280 reserialize(parse_system::<InheritedAsksSettledMessage>(self))
2281 }
2282 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
2283 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
2284 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
2285 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
2286 SystemSubtype::UiPlaced => reserialize(parse_system::<UiPlacedMessage>(self)),
2287 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
2288 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
2289 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
2290 SystemSubtype::Unknown(_) => None,
2291 }
2292 }
2293}
2294
2295fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
2296 serde_json::from_value(message.data.clone()).ok()
2297}
2298
2299#[allow(clippy::large_enum_variant)]
2305#[derive(Debug, Clone, Serialize, Deserialize)]
2306pub enum KnownSystemEvent {
2307 Init(InitMessage),
2308 Status(StatusMessage),
2309 CompactBoundary(CompactBoundaryMessage),
2310 ThinkingTokens(ThinkingTokensMessage),
2311 TaskStarted(TaskStartedMessage),
2312 TaskProgress(TaskProgressMessage),
2313 TaskUpdated(TaskUpdatedMessage),
2314 TaskNotification(TaskNotificationMessage),
2315 ApiRetry(ApiRetryMessage),
2316 ControlRequestProgress(ControlRequestProgressMessage),
2317 ModelRefusalFallback(ModelRefusalFallbackMessage),
2318 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
2319 LocalCommandOutput(LocalCommandOutputMessage),
2320 HookStarted(HookStartedMessage),
2321 HookProgress(HookProgressMessage),
2322 HookResponse(HookResponseMessage),
2323 PluginInstall(PluginInstallMessage),
2324 BackgroundTasksChanged(BackgroundTasksChangedMessage),
2325 SessionStateChanged(SessionStateChangedMessage),
2326 WorkerShuttingDown(WorkerShuttingDownMessage),
2327 CommandsChanged(CommandsChangedMessage),
2328 Notification(NotificationMessage),
2329 FilesPersisted(FilesPersistedMessage),
2330 MemoryRecall(MemoryRecallMessage),
2331 ElicitationComplete(ElicitationCompleteMessage),
2332 PermissionDenied(PermissionDeniedMessage),
2333 MirrorError(MirrorErrorMessage),
2334 Informational(InformationalMessage),
2335 CodeChangePublished(CodeChangePublishedMessage),
2336 VcsStateChanged(VcsStateChangedMessage),
2337 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
2338 CloudSessionDelta(CloudSessionDeltaMessage),
2339 DevIntent(DevIntentMessage),
2340 TurnHandoffAvailable(TurnHandoffAvailableMessage),
2341 TurnPreempted(TurnPreemptedMessage),
2342 PeerMessageHold(PeerMessageHoldMessage),
2343 PerTurnEffortChanged(PerTurnEffortChangedMessage),
2344 SessionTitleChanged(SessionTitleChangedMessage),
2345 SessionWritesLost(SessionWritesLostMessage),
2346 InstructionSizeWarning(InstructionSizeWarningMessage),
2347 FileAttachmentsMissing(FileAttachmentsMissingMessage),
2348 PermissionCheckStatus(PermissionCheckStatusMessage),
2349 InheritedAsksSettled(InheritedAsksSettledMessage),
2350 UiFocus(UiFocusMessage),
2351 UiInvalidate(UiInvalidateMessage),
2352 UiLog(UiLogMessage),
2353 UiPanes(UiPanesMessage),
2354 UiPlaced(UiPlacedMessage),
2355 UiScroll(UiScrollMessage),
2356 UiStatus(UiStatusMessage),
2357 UiToast(UiToastMessage),
2358}
2359
2360#[derive(Debug, Clone, Serialize, Deserialize)]
2361pub struct ApiRetryMessage {
2362 pub attempt: u64,
2363 pub max_retries: u64,
2364 pub retry_delay_ms: u64,
2365 pub error_status: Option<u16>,
2366 pub error: String,
2367 #[serde(default, skip_serializing_if = "Option::is_none")]
2372 pub no_response: Option<ApiRetryNoResponse>,
2373 #[serde(default, skip_serializing_if = "Option::is_none")]
2374 pub uuid: Option<String>,
2375 #[serde(default, skip_serializing_if = "Option::is_none")]
2376 pub session_id: Option<String>,
2377}
2378
2379#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2382pub struct ApiRetryNoResponse {
2383 pub waited_ms: u64,
2385 pub retry_wait_ms: u64,
2387}
2388
2389#[derive(Debug, Clone, Serialize, Deserialize)]
2390pub struct ControlRequestProgressMessage {
2391 pub request_id: String,
2392 pub status: String,
2393 #[serde(default, skip_serializing_if = "Option::is_none")]
2394 pub attempt: Option<u64>,
2395 #[serde(default, skip_serializing_if = "Option::is_none")]
2396 pub max_retries: Option<u64>,
2397 #[serde(default, skip_serializing_if = "Option::is_none")]
2398 pub retry_delay_ms: Option<u64>,
2399 #[serde(default, skip_serializing_if = "Option::is_none")]
2400 pub error_status: Option<u16>,
2401 #[serde(default, skip_serializing_if = "Option::is_none")]
2402 pub error: Option<String>,
2403 #[serde(default, skip_serializing_if = "Option::is_none")]
2404 pub uuid: Option<String>,
2405 #[serde(default, skip_serializing_if = "Option::is_none")]
2406 pub session_id: Option<String>,
2407}
2408
2409#[derive(Debug, Clone, Serialize, Deserialize)]
2410pub struct ModelRefusalFallbackMessage {
2411 pub trigger: String,
2412 pub direction: String,
2413 #[serde(default, skip_serializing_if = "Option::is_none")]
2419 pub scope: Option<RefusalFallbackScope>,
2420 pub original_model: String,
2421 pub fallback_model: String,
2422 pub request_id: Option<String>,
2423 #[serde(default, skip_serializing_if = "Option::is_none")]
2424 pub api_refusal_category: Option<String>,
2425 #[serde(default, skip_serializing_if = "Option::is_none")]
2430 pub saw_cyber_refusal: Option<bool>,
2431 #[serde(default, skip_serializing_if = "Option::is_none")]
2432 pub api_refusal_explanation: Option<String>,
2433 #[serde(default, skip_serializing_if = "Option::is_none")]
2441 pub api_refusal_cyber_verification: Option<CyberVerificationStatus>,
2442 #[serde(default, skip_serializing_if = "Option::is_none")]
2443 pub retracted_message_uuids: Option<Vec<String>>,
2444 #[serde(default, skip_serializing_if = "Option::is_none")]
2445 pub refused_user_message_uuid: Option<String>,
2446 pub content: Value,
2447 #[serde(default, skip_serializing_if = "Option::is_none")]
2448 pub uuid: Option<String>,
2449 #[serde(default, skip_serializing_if = "Option::is_none")]
2450 pub session_id: Option<String>,
2451}
2452
2453#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2457pub struct CyberVerificationStatus {
2458 #[serde(rename = "type")]
2459 pub verification_type: String,
2460 #[serde(default, skip_serializing_if = "Option::is_none")]
2461 pub tier: Option<String>,
2462 #[serde(default, skip_serializing_if = "Option::is_none")]
2463 pub reason: Option<String>,
2464}
2465
2466#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2470pub enum RefusalFallbackScope {
2471 Session,
2473 Local,
2476 Unknown(String),
2478}
2479
2480impl RefusalFallbackScope {
2481 pub fn as_str(&self) -> &str {
2482 match self {
2483 Self::Session => "session",
2484 Self::Local => "local",
2485 Self::Unknown(s) => s.as_str(),
2486 }
2487 }
2488}
2489
2490impl fmt::Display for RefusalFallbackScope {
2491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2492 f.write_str(self.as_str())
2493 }
2494}
2495
2496impl From<&str> for RefusalFallbackScope {
2497 fn from(s: &str) -> Self {
2498 match s {
2499 "session" => Self::Session,
2500 "local" => Self::Local,
2501 other => Self::Unknown(other.to_string()),
2502 }
2503 }
2504}
2505
2506impl Serialize for RefusalFallbackScope {
2507 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2508 serializer.serialize_str(self.as_str())
2509 }
2510}
2511
2512impl<'de> Deserialize<'de> for RefusalFallbackScope {
2513 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2514 let s = String::deserialize(deserializer)?;
2515 Ok(Self::from(s.as_str()))
2516 }
2517}
2518
2519#[derive(Debug, Clone, Serialize, Deserialize)]
2520pub struct ModelRefusalNoFallbackMessage {
2521 pub original_model: String,
2522 pub request_id: Option<String>,
2523 #[serde(default, skip_serializing_if = "Option::is_none")]
2524 pub api_refusal_category: Option<String>,
2525 #[serde(default, skip_serializing_if = "Option::is_none")]
2526 pub api_refusal_explanation: Option<String>,
2527 pub content: Value,
2528 #[serde(default, skip_serializing_if = "Option::is_none")]
2529 pub uuid: Option<String>,
2530 #[serde(default, skip_serializing_if = "Option::is_none")]
2531 pub session_id: Option<String>,
2532}
2533
2534#[derive(Debug, Clone, Serialize, Deserialize)]
2535pub struct LocalCommandOutputMessage {
2536 pub content: String,
2537 #[serde(default, skip_serializing_if = "Option::is_none")]
2538 pub uuid: Option<String>,
2539 #[serde(default, skip_serializing_if = "Option::is_none")]
2540 pub session_id: Option<String>,
2541}
2542
2543#[derive(Debug, Clone, Serialize, Deserialize)]
2544pub struct HookStartedMessage {
2545 pub hook_id: String,
2546 pub hook_name: String,
2547 pub hook_event: String,
2548 #[serde(default, skip_serializing_if = "Option::is_none")]
2549 pub uuid: Option<String>,
2550 #[serde(default, skip_serializing_if = "Option::is_none")]
2551 pub session_id: Option<String>,
2552}
2553
2554#[derive(Debug, Clone, Serialize, Deserialize)]
2555pub struct HookProgressMessage {
2556 pub hook_id: String,
2557 pub hook_name: String,
2558 pub hook_event: String,
2559 #[serde(default, skip_serializing_if = "Option::is_none")]
2560 pub stdout: Option<String>,
2561 #[serde(default, skip_serializing_if = "Option::is_none")]
2562 pub stderr: Option<String>,
2563 #[serde(default, skip_serializing_if = "Option::is_none")]
2564 pub output: Option<String>,
2565 #[serde(default, skip_serializing_if = "Option::is_none")]
2566 pub uuid: Option<String>,
2567 #[serde(default, skip_serializing_if = "Option::is_none")]
2568 pub session_id: Option<String>,
2569}
2570
2571#[derive(Debug, Clone, Serialize, Deserialize)]
2572pub struct HookResponseMessage {
2573 pub hook_id: String,
2574 pub hook_name: String,
2575 pub hook_event: String,
2576 #[serde(default, skip_serializing_if = "Option::is_none")]
2577 pub stdout: Option<String>,
2578 #[serde(default, skip_serializing_if = "Option::is_none")]
2579 pub stderr: Option<String>,
2580 #[serde(default, skip_serializing_if = "Option::is_none")]
2581 pub output: Option<String>,
2582 #[serde(default, skip_serializing_if = "Option::is_none")]
2583 pub exit_code: Option<i32>,
2584 pub outcome: String,
2585 #[serde(default, skip_serializing_if = "Option::is_none")]
2586 pub uuid: Option<String>,
2587 #[serde(default, skip_serializing_if = "Option::is_none")]
2588 pub session_id: Option<String>,
2589}
2590
2591#[derive(Debug, Clone, Serialize, Deserialize)]
2592pub struct PluginInstallMessage {
2593 pub status: String,
2594 #[serde(default, skip_serializing_if = "Option::is_none")]
2595 pub name: Option<String>,
2596 #[serde(default, skip_serializing_if = "Option::is_none")]
2597 pub error: Option<String>,
2598 #[serde(default, skip_serializing_if = "Option::is_none")]
2599 pub uuid: Option<String>,
2600 #[serde(default, skip_serializing_if = "Option::is_none")]
2601 pub session_id: Option<String>,
2602}
2603
2604#[derive(Debug, Clone, Serialize, Deserialize)]
2605pub struct BackgroundTasksChangedMessage {
2606 pub tasks: Vec<BackgroundTaskInfo>,
2607 #[serde(default, skip_serializing_if = "Option::is_none")]
2608 pub uuid: Option<String>,
2609 #[serde(default, skip_serializing_if = "Option::is_none")]
2610 pub session_id: Option<String>,
2611}
2612
2613#[derive(Debug, Clone, Serialize, Deserialize)]
2614pub struct BackgroundTaskInfo {
2615 pub task_id: String,
2616 #[serde(default, skip_serializing_if = "Option::is_none")]
2619 pub run_id: Option<String>,
2620 pub task_type: String,
2621 #[serde(default, skip_serializing_if = "Option::is_none")]
2625 pub subagent_type: Option<String>,
2626 pub description: String,
2627 #[serde(default, skip_serializing_if = "Option::is_none")]
2630 pub parent_task_id: Option<String>,
2631 #[serde(default, skip_serializing_if = "Option::is_none")]
2634 pub ambient: Option<bool>,
2635 #[serde(default, skip_serializing_if = "Option::is_none")]
2638 pub awaited: Option<bool>,
2639}
2640
2641#[derive(Debug, Clone, Serialize, Deserialize)]
2642pub struct SessionStateChangedMessage {
2643 pub state: String,
2644 #[serde(default, skip_serializing_if = "Option::is_none")]
2645 pub uuid: Option<String>,
2646 #[serde(default, skip_serializing_if = "Option::is_none")]
2647 pub session_id: Option<String>,
2648}
2649
2650#[derive(Debug, Clone, Serialize, Deserialize)]
2651pub struct WorkerShuttingDownMessage {
2652 pub reason: String,
2653 #[serde(default, skip_serializing_if = "Option::is_none")]
2654 pub uuid: Option<String>,
2655 #[serde(default, skip_serializing_if = "Option::is_none")]
2656 pub session_id: Option<String>,
2657}
2658
2659#[derive(Debug, Clone, Serialize, Deserialize)]
2660pub struct CommandsChangedMessage {
2661 pub commands: Vec<CommandInfo>,
2662 #[serde(default, skip_serializing_if = "Option::is_none")]
2663 pub uuid: Option<String>,
2664 #[serde(default, skip_serializing_if = "Option::is_none")]
2665 pub session_id: Option<String>,
2666}
2667
2668#[derive(Debug, Clone, Serialize, Deserialize)]
2669pub struct CommandInfo {
2670 pub name: String,
2671 pub description: String,
2672 #[serde(rename = "argumentHint")]
2673 pub argument_hint: String,
2674 #[serde(default, skip_serializing_if = "Option::is_none")]
2675 pub aliases: Option<Vec<String>>,
2676 #[serde(default, skip_serializing_if = "Option::is_none")]
2682 pub builtin: Option<bool>,
2683}
2684
2685#[derive(Debug, Clone, Serialize, Deserialize)]
2686pub struct NotificationMessage {
2687 pub key: String,
2688 pub text: String,
2689 pub priority: String,
2690 #[serde(default, skip_serializing_if = "Option::is_none")]
2691 pub color: Option<String>,
2692 #[serde(default, skip_serializing_if = "Option::is_none")]
2693 pub timeout_ms: Option<u64>,
2694 #[serde(default, skip_serializing_if = "Option::is_none")]
2695 pub uuid: Option<String>,
2696 #[serde(default, skip_serializing_if = "Option::is_none")]
2697 pub session_id: Option<String>,
2698}
2699
2700#[derive(Debug, Clone, Serialize, Deserialize)]
2701pub struct FilesPersistedMessage {
2702 pub files: Vec<PersistedFile>,
2703 pub failed: Vec<FailedPersistedFile>,
2704 pub processed_at: String,
2705 #[serde(default, skip_serializing_if = "Option::is_none")]
2706 pub uuid: Option<String>,
2707 #[serde(default, skip_serializing_if = "Option::is_none")]
2708 pub session_id: Option<String>,
2709}
2710
2711#[derive(Debug, Clone, Serialize, Deserialize)]
2712pub struct PersistedFile {
2713 pub filename: String,
2714 pub file_id: String,
2715}
2716
2717#[derive(Debug, Clone, Serialize, Deserialize)]
2718pub struct FailedPersistedFile {
2719 pub filename: String,
2720 pub error: String,
2721}
2722
2723#[derive(Debug, Clone, Serialize, Deserialize)]
2724pub struct MemoryRecallMessage {
2725 pub mode: String,
2726 pub memories: Vec<MemoryRecallItem>,
2727 #[serde(default, skip_serializing_if = "Option::is_none")]
2728 pub uuid: Option<String>,
2729 #[serde(default, skip_serializing_if = "Option::is_none")]
2730 pub session_id: Option<String>,
2731}
2732
2733#[derive(Debug, Clone, Serialize, Deserialize)]
2734pub struct MemoryRecallItem {
2735 pub path: String,
2736 pub scope: String,
2737 #[serde(default, skip_serializing_if = "Option::is_none")]
2738 pub content: Option<String>,
2739}
2740
2741#[derive(Debug, Clone, Serialize, Deserialize)]
2742pub struct ElicitationCompleteMessage {
2743 pub mcp_server_name: String,
2744 pub elicitation_id: String,
2745 #[serde(default, skip_serializing_if = "Option::is_none")]
2746 pub uuid: Option<String>,
2747 #[serde(default, skip_serializing_if = "Option::is_none")]
2748 pub session_id: Option<String>,
2749}
2750
2751#[derive(Debug, Clone, Serialize, Deserialize)]
2752pub struct PermissionDeniedMessage {
2753 pub tool_name: String,
2754 pub tool_use_id: String,
2755 #[serde(default, skip_serializing_if = "Option::is_none")]
2756 pub agent_id: Option<String>,
2757 #[serde(default, skip_serializing_if = "Option::is_none")]
2758 pub decision_reason_type: Option<String>,
2759 #[serde(default, skip_serializing_if = "Option::is_none")]
2763 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2764 #[serde(default, skip_serializing_if = "Option::is_none")]
2765 pub decision_reason: Option<String>,
2766 pub message: String,
2767 #[serde(default, skip_serializing_if = "Option::is_none")]
2768 pub uuid: Option<String>,
2769 #[serde(default, skip_serializing_if = "Option::is_none")]
2770 pub session_id: Option<String>,
2771}
2772
2773#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2776pub enum PermissionDeniedReasonCode {
2777 ClassifierTranscriptTooLong,
2779 OutsideReadsBlocked,
2781 MemoryPaused,
2783 AutoModeNoCheckInSession,
2788 AutoModeStopNoCheckInSession,
2792 AutoModeStopNoResultForCalls,
2795 AutoModeStopNoVerdict,
2799 Unknown(String),
2801}
2802
2803impl PermissionDeniedReasonCode {
2804 pub fn as_str(&self) -> &str {
2805 match self {
2806 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2807 Self::OutsideReadsBlocked => "outside_reads_blocked",
2808 Self::MemoryPaused => "memory_paused",
2809 Self::AutoModeNoCheckInSession => "auto_mode_no_check_in_session",
2810 Self::AutoModeStopNoCheckInSession => "auto_mode_stop_no_check_in_session",
2811 Self::AutoModeStopNoResultForCalls => "auto_mode_stop_no_result_for_calls",
2812 Self::AutoModeStopNoVerdict => "auto_mode_stop_no_verdict",
2813 Self::Unknown(s) => s.as_str(),
2814 }
2815 }
2816}
2817
2818impl fmt::Display for PermissionDeniedReasonCode {
2819 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2820 f.write_str(self.as_str())
2821 }
2822}
2823
2824impl From<&str> for PermissionDeniedReasonCode {
2825 fn from(s: &str) -> Self {
2826 match s {
2827 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2828 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2829 "memory_paused" => Self::MemoryPaused,
2830 "auto_mode_no_check_in_session" => Self::AutoModeNoCheckInSession,
2831 "auto_mode_stop_no_check_in_session" => Self::AutoModeStopNoCheckInSession,
2832 "auto_mode_stop_no_result_for_calls" => Self::AutoModeStopNoResultForCalls,
2833 "auto_mode_stop_no_verdict" => Self::AutoModeStopNoVerdict,
2834 other => Self::Unknown(other.to_string()),
2835 }
2836 }
2837}
2838
2839impl Serialize for PermissionDeniedReasonCode {
2840 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2841 serializer.serialize_str(self.as_str())
2842 }
2843}
2844
2845impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2846 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2847 let s = String::deserialize(deserializer)?;
2848 Ok(Self::from(s.as_str()))
2849 }
2850}
2851
2852#[derive(Debug, Clone, Serialize, Deserialize)]
2853pub struct MirrorErrorMessage {
2854 pub error: String,
2855 pub key: MirrorErrorKey,
2856 #[serde(default, skip_serializing_if = "Option::is_none")]
2857 pub uuid: Option<String>,
2858 #[serde(default, skip_serializing_if = "Option::is_none")]
2859 pub session_id: Option<String>,
2860}
2861
2862#[derive(Debug, Clone, Serialize, Deserialize)]
2863pub struct MirrorErrorKey {
2864 #[serde(rename = "projectKey")]
2865 pub project_key: String,
2866 #[serde(rename = "sessionId")]
2867 pub session_id: String,
2868 #[serde(default, skip_serializing_if = "Option::is_none")]
2869 pub subpath: Option<String>,
2870}
2871
2872#[derive(Debug, Clone, Serialize, Deserialize)]
2873pub struct InformationalMessage {
2874 pub content: String,
2875 pub level: String,
2876 #[serde(default, skip_serializing_if = "Option::is_none")]
2877 pub tool_use_id: Option<String>,
2878 #[serde(default, skip_serializing_if = "Option::is_none")]
2879 pub prevent_continuation: Option<bool>,
2880 #[serde(default, skip_serializing_if = "Option::is_none")]
2883 pub tag: Option<String>,
2884 #[serde(default, skip_serializing_if = "Option::is_none")]
2890 pub prompt_submit_outcome: Option<PromptSubmitOutcome>,
2891 #[serde(default, skip_serializing_if = "Option::is_none")]
2895 pub model_restriction: Option<ModelRestriction>,
2896 #[serde(default, skip_serializing_if = "Option::is_none")]
2897 pub uuid: Option<String>,
2898 #[serde(default, skip_serializing_if = "Option::is_none")]
2899 pub session_id: Option<String>,
2900}
2901
2902#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2908pub struct ModelRestriction {
2909 pub requested_model: String,
2912 pub effective_model: String,
2915 #[serde(default, skip_serializing_if = "Option::is_none")]
2920 pub at: Option<String>,
2921}
2922
2923#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2926pub struct PromptSubmitOutcome {
2927 #[serde(default, skip_serializing_if = "Option::is_none")]
2931 pub kind: Option<PromptSubmitOutcomeKind>,
2932 #[serde(default, skip_serializing_if = "Option::is_none")]
2939 pub no_verdict_kind: Option<String>,
2940 pub prompt_kept: bool,
2943 #[serde(default, skip_serializing_if = "Option::is_none")]
2947 pub reason: Option<String>,
2948}
2949
2950#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2952pub enum PromptSubmitOutcomeKind {
2953 BlockedByHook,
2955 StoppedByHook,
2957 DroppedByHook,
2959 NotPassedOn,
2961 NoVerdict,
2964 Other(String),
2966}
2967
2968impl PromptSubmitOutcomeKind {
2969 pub fn as_str(&self) -> &str {
2970 match self {
2971 Self::BlockedByHook => "blocked_by_hook",
2972 Self::StoppedByHook => "stopped_by_hook",
2973 Self::DroppedByHook => "dropped_by_hook",
2974 Self::NotPassedOn => "not_passed_on",
2975 Self::NoVerdict => "no_verdict",
2976 Self::Other(s) => s.as_str(),
2977 }
2978 }
2979}
2980
2981impl fmt::Display for PromptSubmitOutcomeKind {
2982 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2983 f.write_str(self.as_str())
2984 }
2985}
2986
2987impl From<&str> for PromptSubmitOutcomeKind {
2988 fn from(s: &str) -> Self {
2989 match s {
2990 "blocked_by_hook" => Self::BlockedByHook,
2991 "stopped_by_hook" => Self::StoppedByHook,
2992 "dropped_by_hook" => Self::DroppedByHook,
2993 "not_passed_on" => Self::NotPassedOn,
2994 "no_verdict" => Self::NoVerdict,
2995 other => Self::Other(other.to_string()),
2996 }
2997 }
2998}
2999
3000impl Serialize for PromptSubmitOutcomeKind {
3001 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3002 serializer.serialize_str(self.as_str())
3003 }
3004}
3005
3006impl<'de> Deserialize<'de> for PromptSubmitOutcomeKind {
3007 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3008 let s = String::deserialize(deserializer)?;
3009 Ok(Self::from(s.as_str()))
3010 }
3011}
3012
3013#[derive(Debug, Clone, Serialize, Deserialize)]
3015pub struct PluginInfo {
3016 pub name: String,
3018 pub path: String,
3020 #[serde(skip_serializing_if = "Option::is_none")]
3022 pub source: Option<String>,
3023 #[serde(default, skip_serializing_if = "Option::is_none")]
3025 pub version: Option<String>,
3026}
3027
3028#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3030pub struct PluginDiagnostic {
3031 pub plugin: String,
3032 #[serde(rename = "type")]
3033 pub diagnostic_type: String,
3034 pub message: String,
3035}
3036
3037#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3039pub struct MemoryPaths {
3040 #[serde(default, skip_serializing_if = "Option::is_none")]
3041 pub auto: Option<String>,
3042 #[serde(default, skip_serializing_if = "Option::is_none")]
3043 pub team: Option<String>,
3044 #[serde(flatten)]
3045 pub extra: serde_json::Map<String, Value>,
3046}
3047
3048#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3052pub struct McpServerError {
3053 pub name: String,
3054 #[serde(rename = "type")]
3056 pub error_type: String,
3057 pub message: String,
3058}
3059
3060#[derive(Debug, Clone, Serialize, Deserialize)]
3062pub struct InitMessage {
3063 pub session_id: String,
3065 #[serde(skip_serializing_if = "Option::is_none")]
3067 pub cwd: Option<String>,
3068 #[serde(skip_serializing_if = "Option::is_none")]
3070 pub model: Option<String>,
3071 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3073 pub tools: Vec<String>,
3074 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3076 pub mcp_servers: Vec<Value>,
3077 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3079 pub slash_commands: Vec<String>,
3080 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3083 pub terminal_slash_commands: Vec<String>,
3084 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3086 pub agents: Vec<String>,
3087 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3089 pub plugins: Vec<PluginInfo>,
3090 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3092 pub skills: Vec<Value>,
3093 #[serde(skip_serializing_if = "Option::is_none")]
3095 pub claude_code_version: Option<String>,
3096 #[serde(skip_serializing_if = "Option::is_none")]
3099 pub messaging_socket_path: Option<String>,
3100 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
3102 pub api_key_source: Option<ApiKeySource>,
3103 #[serde(skip_serializing_if = "Option::is_none")]
3105 pub output_style: Option<OutputStyle>,
3106 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3108 pub permission_mode: Option<InitPermissionMode>,
3109
3110 #[serde(skip_serializing_if = "Option::is_none")]
3112 pub uuid: Option<String>,
3113
3114 #[serde(skip_serializing_if = "Option::is_none")]
3116 pub memory_paths: Option<MemoryPaths>,
3117
3118 #[serde(default, skip_serializing_if = "Option::is_none")]
3123 pub scratchpad_path: Option<String>,
3124
3125 #[serde(skip_serializing_if = "Option::is_none")]
3127 pub fast_mode_state: Option<String>,
3128
3129 #[serde(default, skip_serializing_if = "Option::is_none")]
3131 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
3132
3133 #[serde(default, skip_serializing_if = "Option::is_none")]
3136 pub mcp_server_errors: Option<Vec<McpServerError>>,
3137
3138 #[serde(default, skip_serializing_if = "Option::is_none")]
3140 pub analytics_disabled: Option<bool>,
3141
3142 #[serde(default, skip_serializing_if = "Option::is_none")]
3144 pub product_feedback_disabled: Option<bool>,
3145
3146 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3148 pub betas: Vec<String>,
3149
3150 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3152 pub capabilities: Vec<String>,
3153
3154 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3156 pub plugin_errors: Vec<PluginDiagnostic>,
3157
3158 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3160 pub plugin_warnings: Vec<PluginDiagnostic>,
3161
3162 #[serde(default, skip_serializing_if = "Option::is_none")]
3167 pub effort: Option<String>,
3168
3169 #[serde(default, skip_serializing_if = "Option::is_none")]
3174 pub cloud_session: Option<Value>,
3175
3176 #[serde(default, skip_serializing_if = "Option::is_none")]
3180 pub footer_indicator: Option<FooterIndicator>,
3181
3182 #[serde(default, skip_serializing_if = "Option::is_none")]
3186 pub worker_epoch: Option<u64>,
3187
3188 #[serde(default, skip_serializing_if = "Option::is_none")]
3192 pub powershell_path: Option<Option<String>>,
3193
3194 #[serde(default, skip_serializing_if = "Option::is_none")]
3198 pub startup_timing: Option<serde_json::Map<String, Value>>,
3199
3200 #[serde(default, skip_serializing_if = "Option::is_none")]
3210 pub per_turn_effort_active: Option<bool>,
3211
3212 #[serde(default, skip_serializing_if = "Option::is_none")]
3220 pub ultrathink_available: Option<bool>,
3221
3222 #[serde(default, skip_serializing_if = "Option::is_none")]
3232 pub ultrathink_typed_raises: Option<bool>,
3233
3234 #[serde(default, skip_serializing_if = "Option::is_none")]
3239 pub client_thinking: Option<ClientThinking>,
3240
3241 #[serde(default, skip_serializing_if = "Option::is_none")]
3246 pub flag_restate_noop: Option<bool>,
3247
3248 #[serde(default, skip_serializing_if = "Option::is_none")]
3256 pub synced_skills_path: Option<String>,
3257
3258 #[serde(default, skip_serializing_if = "Option::is_none")]
3265 pub view_mode: Option<ViewMode>,
3266
3267 #[serde(default, skip_serializing_if = "Option::is_none")]
3274 pub original_cwd: Option<String>,
3275
3276 #[serde(default, skip_serializing_if = "Option::is_none")]
3285 pub additional_directories: Option<Vec<String>>,
3286}
3287
3288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3291pub struct ClientThinking {
3292 #[serde(default)]
3295 pub max_thinking_tokens: Option<u64>,
3296 #[serde(default)]
3300 pub thinking_display: Option<String>,
3301}
3302
3303#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3307pub struct FooterIndicator {
3308 pub text: String,
3311}
3312
3313#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3316pub enum ViewMode {
3317 Focus,
3320 Default,
3322 Unknown(String),
3324}
3325
3326impl ViewMode {
3327 pub fn as_str(&self) -> &str {
3328 match self {
3329 Self::Focus => "focus",
3330 Self::Default => "default",
3331 Self::Unknown(s) => s.as_str(),
3332 }
3333 }
3334}
3335
3336impl fmt::Display for ViewMode {
3337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3338 f.write_str(self.as_str())
3339 }
3340}
3341
3342impl From<&str> for ViewMode {
3343 fn from(s: &str) -> Self {
3344 match s {
3345 "focus" => Self::Focus,
3346 "default" => Self::Default,
3347 other => Self::Unknown(other.to_string()),
3348 }
3349 }
3350}
3351
3352impl Serialize for ViewMode {
3353 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3354 serializer.serialize_str(self.as_str())
3355 }
3356}
3357
3358impl<'de> Deserialize<'de> for ViewMode {
3359 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3360 let s = String::deserialize(deserializer)?;
3361 Ok(Self::from(s.as_str()))
3362 }
3363}
3364
3365#[derive(Debug, Clone, Serialize, Deserialize)]
3367pub struct StatusMessage {
3368 pub session_id: String,
3370 pub status: Option<StatusMessageStatus>,
3372 #[serde(skip_serializing_if = "Option::is_none")]
3374 pub uuid: Option<String>,
3375 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3377 pub permission_mode: Option<InitPermissionMode>,
3378 #[serde(skip_serializing_if = "Option::is_none")]
3379 pub compact_result: Option<String>,
3380 #[serde(skip_serializing_if = "Option::is_none")]
3381 pub compact_error: Option<String>,
3382 #[serde(default, skip_serializing_if = "Option::is_none")]
3389 pub compact_output_chars: Option<u64>,
3390 #[serde(default, skip_serializing_if = "Option::is_none")]
3397 pub resume_held_for_plan: Option<bool>,
3398}
3399
3400#[derive(Debug, Clone, Serialize, Deserialize)]
3402pub struct CompactBoundaryMessage {
3403 pub session_id: String,
3405 pub compact_metadata: CompactMetadata,
3407 #[serde(
3411 default,
3412 skip_serializing_if = "Option::is_none",
3413 alias = "content",
3414 alias = "text"
3415 )]
3416 pub summary: Option<String>,
3417 #[serde(
3421 default,
3422 skip_serializing_if = "Option::is_none",
3423 alias = "message_count"
3424 )]
3425 pub leaf_message_count: Option<u32>,
3426 #[serde(default, skip_serializing_if = "Option::is_none")]
3428 pub duration_ms: Option<u64>,
3429 #[serde(skip_serializing_if = "Option::is_none")]
3431 pub uuid: Option<String>,
3432 #[serde(skip_serializing_if = "Option::is_none")]
3434 pub logical_parent_uuid: Option<Option<String>>,
3435 #[serde(default, skip_serializing_if = "Option::is_none")]
3439 pub historical: Option<bool>,
3440}
3441
3442#[derive(Debug, Clone, Serialize, Deserialize)]
3444pub struct CompactMetadata {
3445 pub pre_tokens: u64,
3447 pub trigger: CompactionTrigger,
3449 #[serde(default, skip_serializing_if = "Option::is_none")]
3450 pub post_tokens: Option<u64>,
3451 #[serde(default, skip_serializing_if = "Option::is_none")]
3452 pub cumulative_dropped_tokens: Option<u64>,
3453 #[serde(default, skip_serializing_if = "Option::is_none")]
3454 pub duration_ms: Option<u64>,
3455 #[serde(default, skip_serializing_if = "Option::is_none")]
3456 pub user_context: Option<String>,
3457 #[serde(default, skip_serializing_if = "Option::is_none")]
3458 pub messages_summarized: Option<u64>,
3459 #[serde(default, skip_serializing_if = "Option::is_none")]
3460 pub precomputed: Option<bool>,
3461 #[serde(default, skip_serializing_if = "Option::is_none")]
3462 pub pre_compact_discovered_tools: Option<Vec<String>>,
3463 #[serde(default, skip_serializing_if = "Option::is_none")]
3468 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
3469 #[serde(default, skip_serializing_if = "Option::is_none")]
3470 pub preserved_segment: Option<PreservedSegment>,
3471 #[serde(default, skip_serializing_if = "Option::is_none")]
3472 pub preserved_messages: Option<PreservedMessages>,
3473}
3474
3475#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3478pub struct ArtifactReadVersion {
3479 pub slug: String,
3481 pub ver: String,
3483}
3484
3485#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3486pub struct PreservedSegment {
3487 pub head_uuid: String,
3488 pub anchor_uuid: String,
3489 pub tail_uuid: String,
3490}
3491
3492#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3493pub struct PreservedMessages {
3494 pub anchor_uuid: String,
3495 pub uuids: Vec<String>,
3496 #[serde(default, skip_serializing_if = "Option::is_none")]
3497 pub all_uuids: Option<Vec<String>>,
3498}
3499
3500#[derive(Debug, Clone, Serialize, Deserialize)]
3506pub struct TaskUsage {
3507 pub duration_ms: u64,
3509 pub tool_uses: u64,
3511 pub total_tokens: u64,
3513}
3514
3515#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3517pub enum TaskType {
3518 LocalAgent,
3520 LocalBash,
3522 LocalWorkflow,
3524 Unknown(String),
3526}
3527
3528impl TaskType {
3529 pub fn as_str(&self) -> &str {
3530 match self {
3531 Self::LocalAgent => "local_agent",
3532 Self::LocalBash => "local_bash",
3533 Self::LocalWorkflow => "local_workflow",
3534 Self::Unknown(s) => s.as_str(),
3535 }
3536 }
3537}
3538
3539impl fmt::Display for TaskType {
3540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3541 f.write_str(self.as_str())
3542 }
3543}
3544
3545impl From<&str> for TaskType {
3546 fn from(s: &str) -> Self {
3547 match s {
3548 "local_agent" => Self::LocalAgent,
3549 "local_bash" => Self::LocalBash,
3550 "local_workflow" => Self::LocalWorkflow,
3551 other => Self::Unknown(other.to_string()),
3552 }
3553 }
3554}
3555
3556impl Serialize for TaskType {
3557 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3558 serializer.serialize_str(self.as_str())
3559 }
3560}
3561
3562impl<'de> Deserialize<'de> for TaskType {
3563 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3564 let s = String::deserialize(deserializer)?;
3565 Ok(Self::from(s.as_str()))
3566 }
3567}
3568
3569#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3571pub enum TaskStatus {
3572 Pending,
3573 Running,
3574 Completed,
3575 Failed,
3576 Killed,
3577 Paused,
3578 Stopped,
3579 Unknown(String),
3580}
3581
3582impl TaskStatus {
3583 pub fn as_str(&self) -> &str {
3584 match self {
3585 Self::Pending => "pending",
3586 Self::Running => "running",
3587 Self::Completed => "completed",
3588 Self::Failed => "failed",
3589 Self::Killed => "killed",
3590 Self::Paused => "paused",
3591 Self::Stopped => "stopped",
3592 Self::Unknown(s) => s.as_str(),
3593 }
3594 }
3595}
3596
3597impl fmt::Display for TaskStatus {
3598 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3599 f.write_str(self.as_str())
3600 }
3601}
3602
3603impl From<&str> for TaskStatus {
3604 fn from(s: &str) -> Self {
3605 match s {
3606 "pending" => Self::Pending,
3607 "running" => Self::Running,
3608 "completed" => Self::Completed,
3609 "failed" => Self::Failed,
3610 "killed" => Self::Killed,
3611 "paused" => Self::Paused,
3612 "stopped" => Self::Stopped,
3613 other => Self::Unknown(other.to_string()),
3614 }
3615 }
3616}
3617
3618impl Serialize for TaskStatus {
3619 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3620 serializer.serialize_str(self.as_str())
3621 }
3622}
3623
3624impl<'de> Deserialize<'de> for TaskStatus {
3625 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3626 let s = String::deserialize(deserializer)?;
3627 Ok(Self::from(s.as_str()))
3628 }
3629}
3630
3631#[derive(Debug, Clone, Serialize, Deserialize)]
3633pub struct TaskStartedMessage {
3634 pub session_id: String,
3635 pub task_id: String,
3636 #[serde(default, skip_serializing_if = "Option::is_none")]
3642 pub run_id: Option<String>,
3643 #[serde(default, skip_serializing_if = "Option::is_none")]
3649 pub parent_task_id: Option<String>,
3650 #[serde(default, skip_serializing_if = "Option::is_none")]
3651 pub task_type: Option<TaskType>,
3652 #[serde(default, skip_serializing_if = "Option::is_none")]
3653 pub tool_use_id: Option<String>,
3654 pub description: String,
3655 #[serde(default, skip_serializing_if = "Option::is_none")]
3658 pub subagent_type: Option<String>,
3659 #[serde(default, skip_serializing_if = "Option::is_none")]
3665 pub is_backgrounded: Option<bool>,
3666 #[serde(default, skip_serializing_if = "Option::is_none")]
3674 pub awaited: Option<bool>,
3675 #[serde(default, skip_serializing_if = "Option::is_none")]
3679 pub spawn_depth: Option<u32>,
3680 #[serde(default, skip_serializing_if = "Option::is_none")]
3682 pub prompt: Option<String>,
3683 #[serde(default, skip_serializing_if = "Option::is_none")]
3684 pub workflow_name: Option<String>,
3685 #[serde(default, skip_serializing_if = "Option::is_none")]
3686 pub skip_transcript: Option<bool>,
3687 #[serde(default, skip_serializing_if = "Option::is_none")]
3692 pub ambient: Option<bool>,
3693 pub uuid: String,
3694}
3695
3696#[derive(Debug, Clone, Serialize, Deserialize)]
3700pub struct TaskUpdatedMessage {
3701 pub session_id: String,
3702 pub task_id: String,
3703 #[serde(default, skip_serializing_if = "Option::is_none")]
3706 pub run_id: Option<String>,
3707 pub patch: TaskPatch,
3708 pub uuid: String,
3709}
3710
3711#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3714pub struct TaskPatch {
3715 #[serde(default, skip_serializing_if = "Option::is_none")]
3716 pub status: Option<TaskStatus>,
3717 #[serde(default, skip_serializing_if = "Option::is_none")]
3720 pub end_time: Option<u64>,
3721 #[serde(default, skip_serializing_if = "Option::is_none")]
3722 pub description: Option<String>,
3723 #[serde(default, skip_serializing_if = "Option::is_none")]
3724 pub total_paused_ms: Option<u64>,
3725 #[serde(default, skip_serializing_if = "Option::is_none")]
3726 pub error: Option<String>,
3727 #[serde(default, skip_serializing_if = "Option::is_none")]
3728 pub is_backgrounded: Option<bool>,
3729}
3730
3731#[derive(Debug, Clone, Serialize, Deserialize)]
3734pub struct ThinkingTokensMessage {
3735 pub session_id: String,
3736 pub estimated_tokens: u64,
3738 pub estimated_tokens_delta: u64,
3740 #[serde(default, skip_serializing_if = "Option::is_none")]
3747 pub user_message_uuid: Option<String>,
3748 pub uuid: String,
3749}
3750
3751#[derive(Debug, Clone, Serialize, Deserialize)]
3754pub struct TaskProgressMessage {
3755 pub session_id: String,
3756 pub task_id: String,
3757 #[serde(default, skip_serializing_if = "Option::is_none")]
3760 pub run_id: Option<String>,
3761 #[serde(default, skip_serializing_if = "Option::is_none")]
3762 pub tool_use_id: Option<String>,
3763 pub description: String,
3764 #[serde(default, skip_serializing_if = "Option::is_none")]
3765 pub last_tool_name: Option<String>,
3766 pub usage: TaskUsage,
3767 #[serde(default, skip_serializing_if = "Option::is_none")]
3769 pub subagent_type: Option<String>,
3770 #[serde(default, skip_serializing_if = "Option::is_none")]
3771 pub summary: Option<String>,
3772 pub uuid: String,
3773}
3774
3775#[derive(Debug, Clone, Serialize, Deserialize)]
3778pub struct TaskNotificationMessage {
3779 pub session_id: String,
3780 pub task_id: String,
3781 #[serde(default, skip_serializing_if = "Option::is_none")]
3784 pub run_id: Option<String>,
3785 pub status: TaskStatus,
3786 #[serde(default, skip_serializing_if = "Option::is_none")]
3789 pub reason: Option<TaskEndReason>,
3790 pub summary: String,
3791 pub output_file: Option<String>,
3792 #[serde(skip_serializing_if = "Option::is_none")]
3793 pub tool_use_id: Option<String>,
3794 #[serde(skip_serializing_if = "Option::is_none")]
3795 pub usage: Option<TaskUsage>,
3796 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3802 pub resource_links: Vec<ResourceLink>,
3803 #[serde(default, skip_serializing_if = "Option::is_none")]
3810 pub handback: Option<TaskHandback>,
3811 #[serde(default, skip_serializing_if = "Option::is_none")]
3815 pub handback_report: Option<HandbackReport>,
3816 #[serde(default, skip_serializing_if = "Option::is_none")]
3817 pub skip_transcript: Option<bool>,
3818 #[serde(default, skip_serializing_if = "Option::is_none")]
3821 pub ambient: Option<bool>,
3822 #[serde(skip_serializing_if = "Option::is_none")]
3823 pub uuid: Option<String>,
3824}
3825
3826#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3829pub enum TaskHandback {
3830 Send,
3832 Flagged,
3834 Withheld,
3836 Unknown(String),
3838}
3839
3840impl TaskHandback {
3841 pub fn as_str(&self) -> &str {
3842 match self {
3843 Self::Send => "send",
3844 Self::Flagged => "flagged",
3845 Self::Withheld => "withheld",
3846 Self::Unknown(s) => s.as_str(),
3847 }
3848 }
3849}
3850
3851impl fmt::Display for TaskHandback {
3852 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3853 f.write_str(self.as_str())
3854 }
3855}
3856
3857impl From<&str> for TaskHandback {
3858 fn from(s: &str) -> Self {
3859 match s {
3860 "send" => Self::Send,
3861 "flagged" => Self::Flagged,
3862 "withheld" => Self::Withheld,
3863 other => Self::Unknown(other.to_string()),
3864 }
3865 }
3866}
3867
3868impl Serialize for TaskHandback {
3869 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3870 serializer.serialize_str(self.as_str())
3871 }
3872}
3873
3874impl<'de> Deserialize<'de> for TaskHandback {
3875 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3876 let s = String::deserialize(deserializer)?;
3877 Ok(Self::from(s.as_str()))
3878 }
3879}
3880
3881#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3884pub struct HandbackReport {
3885 pub text: String,
3888 #[serde(default, skip_serializing_if = "Option::is_none")]
3891 pub warning: Option<String>,
3892}
3893
3894#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3896pub enum AssistantErrorKind {
3897 AuthenticationFailed,
3898 OauthOrgNotAllowed,
3899 AccountOnHold,
3900 BillingError,
3901 RateLimit,
3902 Overloaded,
3903 InvalidRequest,
3904 ModelNotFound,
3905 ServerError,
3906 UnknownError,
3907 MaxOutputTokens,
3908 Unknown(String),
3909}
3910
3911impl AssistantErrorKind {
3912 pub fn as_str(&self) -> &str {
3913 match self {
3914 Self::AuthenticationFailed => "authentication_failed",
3915 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3916 Self::AccountOnHold => "account_on_hold",
3917 Self::BillingError => "billing_error",
3918 Self::RateLimit => "rate_limit",
3919 Self::Overloaded => "overloaded",
3920 Self::InvalidRequest => "invalid_request",
3921 Self::ModelNotFound => "model_not_found",
3922 Self::ServerError => "server_error",
3923 Self::UnknownError => "unknown",
3924 Self::MaxOutputTokens => "max_output_tokens",
3925 Self::Unknown(s) => s.as_str(),
3926 }
3927 }
3928}
3929
3930impl fmt::Display for AssistantErrorKind {
3931 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3932 f.write_str(self.as_str())
3933 }
3934}
3935
3936impl From<&str> for AssistantErrorKind {
3937 fn from(s: &str) -> Self {
3938 match s {
3939 "authentication_failed" => Self::AuthenticationFailed,
3940 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3941 "account_on_hold" => Self::AccountOnHold,
3942 "billing_error" => Self::BillingError,
3943 "rate_limit" => Self::RateLimit,
3944 "overloaded" => Self::Overloaded,
3945 "invalid_request" => Self::InvalidRequest,
3946 "model_not_found" => Self::ModelNotFound,
3947 "server_error" => Self::ServerError,
3948 "unknown" => Self::UnknownError,
3949 "max_output_tokens" => Self::MaxOutputTokens,
3950 other => Self::Unknown(other.to_string()),
3951 }
3952 }
3953}
3954
3955impl Serialize for AssistantErrorKind {
3956 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3957 serializer.serialize_str(self.as_str())
3958 }
3959}
3960
3961impl<'de> Deserialize<'de> for AssistantErrorKind {
3962 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3963 let s = String::deserialize(deserializer)?;
3964 Ok(Self::from(s.as_str()))
3965 }
3966}
3967
3968#[derive(Debug, Clone, Serialize, Deserialize)]
3975pub struct CodeChangePublishedMessage {
3976 pub provider: String,
3980 pub url: String,
3982 pub repo: String,
3985 pub identifier: String,
3987 #[serde(default, skip_serializing_if = "Option::is_none")]
3996 pub action: Option<String>,
3997 #[serde(default, skip_serializing_if = "Option::is_none")]
4005 pub branch: Option<String>,
4006 pub uuid: String,
4007 pub session_id: String,
4008}
4009
4010#[derive(Debug, Clone, Serialize, Deserialize)]
4015pub struct VcsStateChangedMessage {
4016 pub kind: VcsMutationKind,
4019 pub cwd: String,
4022 #[serde(default, skip_serializing_if = "Option::is_none")]
4027 pub branch: Option<String>,
4028 pub uuid: String,
4029 pub session_id: String,
4030}
4031
4032#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4034pub enum VcsMutationKind {
4035 Commit,
4036 Push,
4037 Merge,
4038 Rebase,
4039 Unknown(String),
4041}
4042
4043impl VcsMutationKind {
4044 pub fn as_str(&self) -> &str {
4045 match self {
4046 Self::Commit => "commit",
4047 Self::Push => "push",
4048 Self::Merge => "merge",
4049 Self::Rebase => "rebase",
4050 Self::Unknown(s) => s.as_str(),
4051 }
4052 }
4053}
4054
4055impl fmt::Display for VcsMutationKind {
4056 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4057 f.write_str(self.as_str())
4058 }
4059}
4060
4061impl From<&str> for VcsMutationKind {
4062 fn from(s: &str) -> Self {
4063 match s {
4064 "commit" => Self::Commit,
4065 "push" => Self::Push,
4066 "merge" => Self::Merge,
4067 "rebase" => Self::Rebase,
4068 other => Self::Unknown(other.to_string()),
4069 }
4070 }
4071}
4072
4073impl Serialize for VcsMutationKind {
4074 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4075 serializer.serialize_str(self.as_str())
4076 }
4077}
4078
4079impl<'de> Deserialize<'de> for VcsMutationKind {
4080 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4081 let s = String::deserialize(deserializer)?;
4082 Ok(Self::from(s.as_str()))
4083 }
4084}
4085
4086#[derive(Debug, Clone, Serialize, Deserialize)]
4088pub struct FeedbackDraftQueuedMessage {
4089 pub draft_id: String,
4090 pub draft_type: String,
4091 pub title: String,
4092 pub details_preview: String,
4093 #[serde(default, skip_serializing_if = "Option::is_none")]
4094 pub uuid: Option<String>,
4095 #[serde(default, skip_serializing_if = "Option::is_none")]
4096 pub session_id: Option<String>,
4097 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4098 pub extra: serde_json::Map<String, Value>,
4099}
4100
4101#[derive(Debug, Clone, Serialize, Deserialize)]
4109pub struct CloudSessionDeltaMessage {
4110 pub seq: u64,
4114 pub changed: Vec<String>,
4118 pub cloud_session: Value,
4122 pub uuid: String,
4123 pub session_id: String,
4124 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4125 pub extra: serde_json::Map<String, Value>,
4126}
4127
4128#[derive(Debug, Clone, Serialize, Deserialize)]
4144pub struct DevIntentMessage {
4145 pub kind: DevIntentKind,
4147 #[serde(default, skip_serializing_if = "Option::is_none")]
4153 pub trigger: Option<DevIntentTrigger>,
4154 pub uuid: String,
4155 pub session_id: String,
4156 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4157 pub extra: serde_json::Map<String, Value>,
4158}
4159
4160#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4164pub enum DevIntentKind {
4165 IosApp,
4171 AndroidApp,
4178 Unknown(String),
4180}
4181
4182impl DevIntentKind {
4183 pub fn as_str(&self) -> &str {
4184 match self {
4185 Self::IosApp => "ios_app",
4186 Self::AndroidApp => "android_app",
4187 Self::Unknown(s) => s.as_str(),
4188 }
4189 }
4190}
4191
4192impl fmt::Display for DevIntentKind {
4193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4194 f.write_str(self.as_str())
4195 }
4196}
4197
4198impl From<&str> for DevIntentKind {
4199 fn from(s: &str) -> Self {
4200 match s {
4201 "ios_app" => Self::IosApp,
4202 "android_app" => Self::AndroidApp,
4203 other => Self::Unknown(other.to_string()),
4204 }
4205 }
4206}
4207
4208impl Serialize for DevIntentKind {
4209 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4210 serializer.serialize_str(self.as_str())
4211 }
4212}
4213
4214impl<'de> Deserialize<'de> for DevIntentKind {
4215 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4216 let s = String::deserialize(deserializer)?;
4217 Ok(Self::from(s.as_str()))
4218 }
4219}
4220
4221#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4225pub enum DevIntentTrigger {
4226 SwiftEdit,
4228 UikitImport,
4230 XcodeProject,
4233 IosCommand,
4236 KotlinEdit,
4238 JavaEdit,
4240 AndroidImport,
4242 AndroidManifest,
4244 GradlePlugin,
4246 AndroidCommand,
4249 ProjectScan,
4252 Unknown(String),
4254}
4255
4256impl DevIntentTrigger {
4257 pub fn as_str(&self) -> &str {
4258 match self {
4259 Self::SwiftEdit => "swift_edit",
4260 Self::UikitImport => "uikit_import",
4261 Self::XcodeProject => "xcode_project",
4262 Self::IosCommand => "ios_command",
4263 Self::KotlinEdit => "kotlin_edit",
4264 Self::JavaEdit => "java_edit",
4265 Self::AndroidImport => "android_import",
4266 Self::AndroidManifest => "android_manifest",
4267 Self::GradlePlugin => "gradle_plugin",
4268 Self::AndroidCommand => "android_command",
4269 Self::ProjectScan => "project_scan",
4270 Self::Unknown(s) => s.as_str(),
4271 }
4272 }
4273}
4274
4275impl fmt::Display for DevIntentTrigger {
4276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4277 f.write_str(self.as_str())
4278 }
4279}
4280
4281impl From<&str> for DevIntentTrigger {
4282 fn from(s: &str) -> Self {
4283 match s {
4284 "swift_edit" => Self::SwiftEdit,
4285 "uikit_import" => Self::UikitImport,
4286 "xcode_project" => Self::XcodeProject,
4287 "ios_command" => Self::IosCommand,
4288 "kotlin_edit" => Self::KotlinEdit,
4289 "java_edit" => Self::JavaEdit,
4290 "android_import" => Self::AndroidImport,
4291 "android_manifest" => Self::AndroidManifest,
4292 "gradle_plugin" => Self::GradlePlugin,
4293 "android_command" => Self::AndroidCommand,
4294 "project_scan" => Self::ProjectScan,
4295 other => Self::Unknown(other.to_string()),
4296 }
4297 }
4298}
4299
4300impl Serialize for DevIntentTrigger {
4301 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4302 serializer.serialize_str(self.as_str())
4303 }
4304}
4305
4306impl<'de> Deserialize<'de> for DevIntentTrigger {
4307 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4308 let s = String::deserialize(deserializer)?;
4309 Ok(Self::from(s.as_str()))
4310 }
4311}
4312
4313#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4317pub enum TaskEndReason {
4318 WorkerRestart,
4321 Unknown(String),
4323}
4324
4325impl TaskEndReason {
4326 pub fn as_str(&self) -> &str {
4327 match self {
4328 Self::WorkerRestart => "worker_restart",
4329 Self::Unknown(s) => s.as_str(),
4330 }
4331 }
4332}
4333
4334impl fmt::Display for TaskEndReason {
4335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4336 f.write_str(self.as_str())
4337 }
4338}
4339
4340impl From<&str> for TaskEndReason {
4341 fn from(s: &str) -> Self {
4342 match s {
4343 "worker_restart" => Self::WorkerRestart,
4344 other => Self::Unknown(other.to_string()),
4345 }
4346 }
4347}
4348
4349impl Serialize for TaskEndReason {
4350 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4351 serializer.serialize_str(self.as_str())
4352 }
4353}
4354
4355impl<'de> Deserialize<'de> for TaskEndReason {
4356 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4357 let s = String::deserialize(deserializer)?;
4358 Ok(Self::from(s.as_str()))
4359 }
4360}
4361
4362#[derive(Debug, Clone, Serialize, Deserialize)]
4371pub struct TurnHandoffAvailableMessage {
4372 pub v: u64,
4374 pub tools: Vec<String>,
4376 pub worker_epoch: u64,
4378 #[serde(default, skip_serializing_if = "Option::is_none")]
4382 pub relay_marker: Option<bool>,
4383 #[serde(default, skip_serializing_if = "Option::is_none")]
4391 pub staged_files: Option<bool>,
4392 #[serde(default, skip_serializing_if = "Option::is_none")]
4399 pub no_query_first: Option<bool>,
4400 #[serde(default, skip_serializing_if = "Option::is_none")]
4408 pub home_files: Option<bool>,
4409 #[serde(default, skip_serializing_if = "Option::is_none")]
4413 pub file_names: Option<bool>,
4414 #[serde(default, skip_serializing_if = "Option::is_none")]
4424 pub carried_writes: Option<bool>,
4425 #[serde(default, skip_serializing_if = "Option::is_none")]
4439 pub hydrates_carried_lines: Option<bool>,
4440 #[serde(default, skip_serializing_if = "Option::is_none")]
4446 pub shared_message_id: Option<bool>,
4447 #[serde(default, skip_serializing_if = "Option::is_none")]
4452 pub requires_rewind_request_id: Option<bool>,
4453 pub uuid: String,
4454 pub session_id: String,
4455 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4456 pub extra: serde_json::Map<String, Value>,
4457}
4458
4459#[derive(Debug, Clone, Serialize, Deserialize)]
4471pub struct TurnPreemptedMessage {
4472 pub reason: String,
4476 pub preempted_by_uuid: String,
4480 #[serde(default)]
4483 pub preempted_message_uuids: Vec<String>,
4484 pub uuid: String,
4485 pub session_id: String,
4486 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4487 pub extra: serde_json::Map<String, Value>,
4488}
4489
4490#[derive(Debug, Clone, Serialize, Deserialize)]
4498pub struct PeerMessageHoldMessage {
4499 pub state: PeerMessageHoldState,
4505 #[serde(default, skip_serializing_if = "Option::is_none")]
4512 pub message_uuid: Option<String>,
4513 pub lane: PeerMessageLane,
4515 pub from: String,
4520 #[serde(default, skip_serializing_if = "Option::is_none")]
4523 pub from_name: Option<String>,
4524 #[serde(default, skip_serializing_if = "Option::is_none")]
4526 pub cause: Option<PeerMessageHoldCause>,
4527 #[serde(default, skip_serializing_if = "Option::is_none")]
4529 pub outcome: Option<PeerMessageHoldOutcome>,
4530 pub uuid: String,
4531 pub session_id: String,
4532 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4533 pub extra: serde_json::Map<String, Value>,
4534}
4535
4536#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4538pub enum PeerMessageHoldState {
4539 Held,
4541 Released,
4543 Dropped,
4545 Unknown(String),
4547}
4548
4549impl PeerMessageHoldState {
4550 pub fn as_str(&self) -> &str {
4551 match self {
4552 Self::Held => "held",
4553 Self::Released => "released",
4554 Self::Dropped => "dropped",
4555 Self::Unknown(s) => s.as_str(),
4556 }
4557 }
4558}
4559
4560impl fmt::Display for PeerMessageHoldState {
4561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4562 f.write_str(self.as_str())
4563 }
4564}
4565
4566impl From<&str> for PeerMessageHoldState {
4567 fn from(s: &str) -> Self {
4568 match s {
4569 "held" => Self::Held,
4570 "released" => Self::Released,
4571 "dropped" => Self::Dropped,
4572 other => Self::Unknown(other.to_string()),
4573 }
4574 }
4575}
4576
4577impl Serialize for PeerMessageHoldState {
4578 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4579 serializer.serialize_str(self.as_str())
4580 }
4581}
4582
4583impl<'de> Deserialize<'de> for PeerMessageHoldState {
4584 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4585 let s = String::deserialize(deserializer)?;
4586 Ok(Self::from(s.as_str()))
4587 }
4588}
4589
4590#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4592pub enum PeerMessageLane {
4593 Bridge,
4595 Stdin,
4597 Socket,
4599 Unknown(String),
4601}
4602
4603impl PeerMessageLane {
4604 pub fn as_str(&self) -> &str {
4605 match self {
4606 Self::Bridge => "bridge",
4607 Self::Stdin => "stdin",
4608 Self::Socket => "socket",
4609 Self::Unknown(s) => s.as_str(),
4610 }
4611 }
4612}
4613
4614impl fmt::Display for PeerMessageLane {
4615 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4616 f.write_str(self.as_str())
4617 }
4618}
4619
4620impl From<&str> for PeerMessageLane {
4621 fn from(s: &str) -> Self {
4622 match s {
4623 "bridge" => Self::Bridge,
4624 "stdin" => Self::Stdin,
4625 "socket" => Self::Socket,
4626 other => Self::Unknown(other.to_string()),
4627 }
4628 }
4629}
4630
4631impl Serialize for PeerMessageLane {
4632 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4633 serializer.serialize_str(self.as_str())
4634 }
4635}
4636
4637impl<'de> Deserialize<'de> for PeerMessageLane {
4638 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4639 let s = String::deserialize(deserializer)?;
4640 Ok(Self::from(s.as_str()))
4641 }
4642}
4643
4644#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4651pub enum PeerMessageHoldCause {
4652 ExplicitSetting,
4653 ManagedSetting,
4654 RepoSetting,
4655 InvalidSetting,
4656 BypassDefault,
4657 ModeUnknown,
4658 ModeMismatch,
4659 NoModeAsserted,
4660 Unknown(String),
4662}
4663
4664impl PeerMessageHoldCause {
4665 pub fn as_str(&self) -> &str {
4666 match self {
4667 Self::ExplicitSetting => "explicit-setting",
4668 Self::ManagedSetting => "managed-setting",
4669 Self::RepoSetting => "repo-setting",
4670 Self::InvalidSetting => "invalid-setting",
4671 Self::BypassDefault => "bypass-default",
4672 Self::ModeUnknown => "mode-unknown",
4673 Self::ModeMismatch => "mode-mismatch",
4674 Self::NoModeAsserted => "no-mode-asserted",
4675 Self::Unknown(s) => s.as_str(),
4676 }
4677 }
4678}
4679
4680impl fmt::Display for PeerMessageHoldCause {
4681 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4682 f.write_str(self.as_str())
4683 }
4684}
4685
4686impl From<&str> for PeerMessageHoldCause {
4687 fn from(s: &str) -> Self {
4688 match s {
4689 "explicit-setting" => Self::ExplicitSetting,
4690 "managed-setting" => Self::ManagedSetting,
4691 "repo-setting" => Self::RepoSetting,
4692 "invalid-setting" => Self::InvalidSetting,
4693 "bypass-default" => Self::BypassDefault,
4694 "mode-unknown" => Self::ModeUnknown,
4695 "mode-mismatch" => Self::ModeMismatch,
4696 "no-mode-asserted" => Self::NoModeAsserted,
4697 other => Self::Unknown(other.to_string()),
4698 }
4699 }
4700}
4701
4702impl Serialize for PeerMessageHoldCause {
4703 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4704 serializer.serialize_str(self.as_str())
4705 }
4706}
4707
4708impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4709 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4710 let s = String::deserialize(deserializer)?;
4711 Ok(Self::from(s.as_str()))
4712 }
4713}
4714
4715#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4718pub enum PeerMessageHoldOutcome {
4719 Expired,
4723 Refused,
4725 Dropped,
4728 Discarded,
4730 Unknown(String),
4732}
4733
4734impl PeerMessageHoldOutcome {
4735 pub fn as_str(&self) -> &str {
4736 match self {
4737 Self::Expired => "expired",
4738 Self::Refused => "refused",
4739 Self::Dropped => "dropped",
4740 Self::Discarded => "discarded",
4741 Self::Unknown(s) => s.as_str(),
4742 }
4743 }
4744}
4745
4746impl fmt::Display for PeerMessageHoldOutcome {
4747 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4748 f.write_str(self.as_str())
4749 }
4750}
4751
4752impl From<&str> for PeerMessageHoldOutcome {
4753 fn from(s: &str) -> Self {
4754 match s {
4755 "expired" => Self::Expired,
4756 "refused" => Self::Refused,
4757 "dropped" => Self::Dropped,
4758 "discarded" => Self::Discarded,
4759 other => Self::Unknown(other.to_string()),
4760 }
4761 }
4762}
4763
4764impl Serialize for PeerMessageHoldOutcome {
4765 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4766 serializer.serialize_str(self.as_str())
4767 }
4768}
4769
4770impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4771 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4772 let s = String::deserialize(deserializer)?;
4773 Ok(Self::from(s.as_str()))
4774 }
4775}
4776
4777#[derive(Debug, Clone, Serialize, Deserialize)]
4788pub struct PerTurnEffortChangedMessage {
4789 pub per_turn_effort_active: bool,
4791 pub uuid: String,
4792 pub session_id: String,
4793}
4794
4795#[derive(Debug, Clone, Serialize, Deserialize)]
4802pub struct InstructionSizeWarningMessage {
4803 pub total_chars: u64,
4806 pub total_limit_chars: u64,
4809 pub file_count: u64,
4812 #[serde(default, skip_serializing_if = "Option::is_none")]
4816 pub largest_chars: Option<u64>,
4817 pub uuid: String,
4818 pub session_id: String,
4819}
4820
4821#[derive(Debug, Clone, Serialize, Deserialize)]
4828pub struct FileAttachmentsMissingMessage {
4829 pub message_uuid: String,
4832 pub sent_count: u64,
4834 pub missing: Vec<MissingFileAttachment>,
4836 pub uuid: String,
4837 pub session_id: String,
4838}
4839
4840#[derive(Debug, Clone, Serialize, Deserialize)]
4850pub struct PermissionCheckStatusMessage {
4851 pub tool_use_id: String,
4852 #[serde(default, skip_serializing_if = "Option::is_none")]
4855 pub agent_id: Option<String>,
4856 pub status: PermissionCheckStatus,
4857 pub uuid: String,
4858 pub session_id: String,
4859}
4860
4861#[derive(Debug, Clone, Serialize, Deserialize)]
4866pub struct InheritedAsksSettledMessage {
4867 pub kept_request_ids: Vec<String>,
4870 pub uuid: String,
4871 pub session_id: String,
4872}
4873
4874#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4876pub enum PermissionCheckStatus {
4877 Checking,
4880 Done,
4882 Unknown(String),
4884}
4885
4886impl PermissionCheckStatus {
4887 pub fn as_str(&self) -> &str {
4888 match self {
4889 Self::Checking => "checking",
4890 Self::Done => "done",
4891 Self::Unknown(s) => s.as_str(),
4892 }
4893 }
4894}
4895
4896impl fmt::Display for PermissionCheckStatus {
4897 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4898 f.write_str(self.as_str())
4899 }
4900}
4901
4902impl From<&str> for PermissionCheckStatus {
4903 fn from(s: &str) -> Self {
4904 match s {
4905 "checking" => Self::Checking,
4906 "done" => Self::Done,
4907 other => Self::Unknown(other.to_string()),
4908 }
4909 }
4910}
4911
4912impl Serialize for PermissionCheckStatus {
4913 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4914 serializer.serialize_str(self.as_str())
4915 }
4916}
4917
4918impl<'de> Deserialize<'de> for PermissionCheckStatus {
4919 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4920 let s = String::deserialize(deserializer)?;
4921 Ok(Self::from(s.as_str()))
4922 }
4923}
4924
4925#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4927pub struct MissingFileAttachment {
4928 pub file_uuid: String,
4930 pub reason: String,
4935}
4936
4937#[derive(Debug, Clone, Serialize, Deserialize)]
4947pub struct SessionTitleChangedMessage {
4948 pub title: String,
4953 pub uuid: String,
4954 pub session_id: String,
4955}
4956
4957#[derive(Debug, Clone, Serialize, Deserialize)]
4967pub struct SessionWritesLostMessage {
4968 pub error_code: SessionWriteErrorCode,
4971 pub lost: Vec<LostSessionWrite>,
4973 #[serde(default, skip_serializing_if = "Option::is_none")]
4979 pub transcript_session_ids: Option<Vec<String>>,
4980 pub uuid: String,
4981 pub session_id: String,
4985}
4986
4987#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4989pub enum SessionWriteErrorCode {
4990 Enospc,
4992 Edquot,
4994 Unknown(String),
4996}
4997
4998impl SessionWriteErrorCode {
4999 pub fn as_str(&self) -> &str {
5000 match self {
5001 Self::Enospc => "ENOSPC",
5002 Self::Edquot => "EDQUOT",
5003 Self::Unknown(s) => s.as_str(),
5004 }
5005 }
5006}
5007
5008impl fmt::Display for SessionWriteErrorCode {
5009 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5010 f.write_str(self.as_str())
5011 }
5012}
5013
5014impl From<&str> for SessionWriteErrorCode {
5015 fn from(s: &str) -> Self {
5016 match s {
5017 "ENOSPC" => Self::Enospc,
5018 "EDQUOT" => Self::Edquot,
5019 other => Self::Unknown(other.to_string()),
5020 }
5021 }
5022}
5023
5024impl Serialize for SessionWriteErrorCode {
5025 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5026 serializer.serialize_str(self.as_str())
5027 }
5028}
5029
5030impl<'de> Deserialize<'de> for SessionWriteErrorCode {
5031 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5032 let s = String::deserialize(deserializer)?;
5033 Ok(Self::from(s.as_str()))
5034 }
5035}
5036
5037#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5039pub enum LostSessionWrite {
5040 Transcript,
5043 GlobalConfig,
5045 Unknown(String),
5047}
5048
5049impl LostSessionWrite {
5050 pub fn as_str(&self) -> &str {
5051 match self {
5052 Self::Transcript => "transcript",
5053 Self::GlobalConfig => "global_config",
5054 Self::Unknown(s) => s.as_str(),
5055 }
5056 }
5057}
5058
5059impl fmt::Display for LostSessionWrite {
5060 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5061 f.write_str(self.as_str())
5062 }
5063}
5064
5065impl From<&str> for LostSessionWrite {
5066 fn from(s: &str) -> Self {
5067 match s {
5068 "transcript" => Self::Transcript,
5069 "global_config" => Self::GlobalConfig,
5070 other => Self::Unknown(other.to_string()),
5071 }
5072 }
5073}
5074
5075impl Serialize for LostSessionWrite {
5076 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5077 serializer.serialize_str(self.as_str())
5078 }
5079}
5080
5081impl<'de> Deserialize<'de> for LostSessionWrite {
5082 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5083 let s = String::deserialize(deserializer)?;
5084 Ok(Self::from(s.as_str()))
5085 }
5086}
5087
5088#[derive(Debug, Clone, Serialize, Deserialize)]
5092pub struct UiLogMessage {
5093 pub plugin: String,
5095 pub text: String,
5099 pub uuid: String,
5100 pub session_id: String,
5101}
5102
5103#[derive(Debug, Clone, Serialize, Deserialize)]
5108pub struct UiToastMessage {
5109 pub plugin: String,
5111 pub text: String,
5115 pub timeout_ms: u64,
5118 pub uuid: String,
5119 pub session_id: String,
5120}
5121
5122#[derive(Debug, Clone, Serialize, Deserialize)]
5126pub struct UiStatusMessage {
5127 pub plugin: String,
5129 pub text: Option<String>,
5134 pub uuid: String,
5135 pub session_id: String,
5136}
5137
5138#[derive(Debug, Clone, Serialize, Deserialize)]
5144pub struct UiInvalidateMessage {
5145 pub event: String,
5148 #[serde(default, skip_serializing_if = "Option::is_none")]
5156 pub instances: Option<Vec<UiInvalidatedInstance>>,
5157 #[serde(default, skip_serializing_if = "Option::is_none")]
5165 pub prompts_screened: Option<bool>,
5166 pub uuid: String,
5167 pub session_id: String,
5168}
5169
5170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5172pub struct UiInvalidatedInstance {
5173 pub surface: String,
5175 pub component: String,
5177 pub instance_id: String,
5180}
5181
5182#[derive(Debug, Clone, Serialize, Deserialize)]
5189pub struct UiPanesMessage {
5190 pub panes: Vec<UiPane>,
5193 pub shown_id: Option<String>,
5196 pub focused_id: Option<String>,
5199 pub focus_requested_id: Option<String>,
5203 pub uuid: String,
5204 pub session_id: String,
5205}
5206
5207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5209pub struct UiPane {
5210 pub id: String,
5213 pub title: String,
5216 pub plugin: String,
5218 #[serde(default, skip_serializing_if = "Option::is_none")]
5221 pub close_on_escape: Option<bool>,
5222 #[serde(default, skip_serializing_if = "Option::is_none")]
5225 pub hold_toasts: Option<bool>,
5226 #[serde(default, skip_serializing_if = "Option::is_none")]
5231 pub background_color: Option<String>,
5232 #[serde(default, skip_serializing_if = "Option::is_none")]
5235 pub rows: Option<u64>,
5236 #[serde(default, skip_serializing_if = "Option::is_none")]
5239 pub columns: Option<u64>,
5240}
5241
5242#[derive(Debug, Clone, Serialize, Deserialize)]
5250pub struct UiPlacedMessage {
5251 pub rows: Vec<UiPlacedRow>,
5253 pub uuid: String,
5254 pub session_id: String,
5255}
5256
5257#[derive(Debug, Clone, Serialize, Deserialize)]
5259pub struct UiPlacedRow {
5260 pub plugin: String,
5262 pub id: String,
5264 pub request_id: String,
5267 #[serde(default, skip_serializing_if = "Option::is_none")]
5270 pub after_uuid: Option<String>,
5271}
5272
5273#[derive(Debug, Clone, Serialize, Deserialize)]
5277pub struct UiScrollMessage {
5278 pub client_id: String,
5280 pub component: UiSiteComponent,
5282 pub instance_id: String,
5284 pub offset: u64,
5287 #[serde(default, skip_serializing_if = "Option::is_none")]
5290 pub follow_end: Option<bool>,
5291 pub uuid: String,
5292 pub session_id: String,
5293}
5294
5295#[derive(Debug, Clone, Serialize, Deserialize)]
5300pub struct UiFocusMessage {
5301 pub client_id: String,
5303 pub component: UiSiteComponent,
5305 pub instance_id: String,
5307 pub plugin: String,
5309 pub key: String,
5311 pub uuid: String,
5312 pub session_id: String,
5313}
5314
5315#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5317pub enum UiSiteComponent {
5318 Pane,
5320 AbovePrompt,
5322 Unknown(String),
5324}
5325
5326impl UiSiteComponent {
5327 pub fn as_str(&self) -> &str {
5328 match self {
5329 Self::Pane => "Pane",
5330 Self::AbovePrompt => "AbovePrompt",
5331 Self::Unknown(s) => s.as_str(),
5332 }
5333 }
5334}
5335
5336impl fmt::Display for UiSiteComponent {
5337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5338 f.write_str(self.as_str())
5339 }
5340}
5341
5342impl From<&str> for UiSiteComponent {
5343 fn from(s: &str) -> Self {
5344 match s {
5345 "Pane" => Self::Pane,
5346 "AbovePrompt" => Self::AbovePrompt,
5347 other => Self::Unknown(other.to_string()),
5348 }
5349 }
5350}
5351
5352impl Serialize for UiSiteComponent {
5353 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5354 serializer.serialize_str(self.as_str())
5355 }
5356}
5357
5358impl<'de> Deserialize<'de> for UiSiteComponent {
5359 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5360 let s = String::deserialize(deserializer)?;
5361 Ok(Self::from(s.as_str()))
5362 }
5363}
5364
5365#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5368pub struct LocalCommandRun {
5369 pub command: String,
5371 pub args: String,
5374 #[serde(default, skip_serializing_if = "Option::is_none")]
5379 pub plugin: Option<String>,
5380}
5381
5382#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5386pub struct LocalCommandOutcome {
5387 pub kind: LocalCommandOutcomeKind,
5388 #[serde(default, skip_serializing_if = "Option::is_none")]
5391 pub suggestion: Option<String>,
5392}
5393
5394#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5399pub enum LocalCommandOutcomeKind {
5400 UnavailableHeadless,
5402 Unknown,
5405 Failed,
5407 RestartRequired,
5409 Other(String),
5411}
5412
5413impl LocalCommandOutcomeKind {
5414 pub fn as_str(&self) -> &str {
5415 match self {
5416 Self::UnavailableHeadless => "unavailable_headless",
5417 Self::Unknown => "unknown",
5418 Self::Failed => "failed",
5419 Self::RestartRequired => "restart_required",
5420 Self::Other(s) => s.as_str(),
5421 }
5422 }
5423}
5424
5425impl fmt::Display for LocalCommandOutcomeKind {
5426 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5427 f.write_str(self.as_str())
5428 }
5429}
5430
5431impl From<&str> for LocalCommandOutcomeKind {
5432 fn from(s: &str) -> Self {
5433 match s {
5434 "unavailable_headless" => Self::UnavailableHeadless,
5435 "unknown" => Self::Unknown,
5436 "failed" => Self::Failed,
5437 "restart_required" => Self::RestartRequired,
5438 other => Self::Other(other.to_string()),
5439 }
5440 }
5441}
5442
5443impl Serialize for LocalCommandOutcomeKind {
5444 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5445 serializer.serialize_str(self.as_str())
5446 }
5447}
5448
5449impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
5450 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5451 let s = String::deserialize(deserializer)?;
5452 Ok(Self::from(s.as_str()))
5453 }
5454}
5455
5456#[derive(Debug, Clone, Serialize, Deserialize)]
5462pub struct UsageReport {
5463 pub session: super::control::UsageSession,
5465 pub rate_limits: Option<UsageReportRateLimits>,
5469 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5470 pub extra: serde_json::Map<String, Value>,
5471}
5472
5473#[derive(Debug, Clone, Serialize, Deserialize)]
5475pub struct UsageReportRateLimits {
5476 pub limits: Option<Vec<UsageReportLimit>>,
5484 #[serde(default, skip_serializing_if = "Option::is_none")]
5487 pub extra_usage: Option<UsageReportExtraUsage>,
5488 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5489 pub extra: serde_json::Map<String, Value>,
5490}
5491
5492#[derive(Debug, Clone, Serialize, Deserialize)]
5494pub struct UsageReportLimit {
5495 pub kind: String,
5498 pub group: String,
5501 pub percent: f64,
5503 pub resets_at: Option<String>,
5505 #[serde(default, skip_serializing_if = "Option::is_none")]
5508 pub scope: Option<UsageReportScope>,
5509 #[serde(default, skip_serializing_if = "Option::is_none")]
5513 pub severity: Option<String>,
5514 #[serde(default, skip_serializing_if = "Option::is_none")]
5516 pub is_active: Option<bool>,
5517 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5518 pub extra: serde_json::Map<String, Value>,
5519}
5520
5521#[derive(Debug, Clone, Serialize, Deserialize)]
5523pub struct UsageReportScope {
5524 #[serde(default, skip_serializing_if = "Option::is_none")]
5525 pub model: Option<UsageReportScopeLabel>,
5526 #[serde(default, skip_serializing_if = "Option::is_none")]
5527 pub surface: Option<UsageReportScopeLabel>,
5528 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5529 pub extra: serde_json::Map<String, Value>,
5530}
5531
5532#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5534pub struct UsageReportScopeLabel {
5535 pub display_name: String,
5536}
5537
5538#[derive(Debug, Clone, Serialize, Deserialize)]
5542pub struct UsageReportExtraUsage {
5543 pub is_enabled: bool,
5545 pub monthly_limit: Option<f64>,
5546 pub used_credits: Option<f64>,
5547 pub utilization: Option<f64>,
5548 #[serde(default, skip_serializing_if = "Option::is_none")]
5549 pub currency: Option<String>,
5550 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5551 pub extra: serde_json::Map<String, Value>,
5552}
5553
5554#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5557pub struct BatchToolUse {
5558 pub id: String,
5559 pub name: String,
5560}
5561
5562#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5567pub struct ContextUsage {
5568 pub model: String,
5570 pub total_tokens: u64,
5573 pub raw_max_tokens: u64,
5576 pub percentage: u64,
5578 #[serde(default, skip_serializing_if = "Option::is_none")]
5580 pub over_limit: Option<ContextOverLimit>,
5581 #[serde(default)]
5583 pub categories: Vec<ContextCategory>,
5584 #[serde(default)]
5586 pub mcp_tools: Vec<ContextMcpTool>,
5587 #[serde(default)]
5589 pub memory_files: Vec<ContextMemoryFile>,
5590 #[serde(default)]
5592 pub agents: Vec<ContextAgent>,
5593 #[serde(default, skip_serializing_if = "Option::is_none")]
5595 pub skills: Option<Vec<ContextSkill>>,
5596}
5597
5598#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5600pub struct ContextOverLimit {
5601 pub tokens_over: u64,
5602 pub kind: String,
5605}
5606
5607#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5609pub struct ContextCategory {
5610 pub name: String,
5613 pub tokens: u64,
5614 pub kind: String,
5618}
5619
5620#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5622pub struct ContextMcpTool {
5623 pub name: String,
5625 pub server_name: String,
5626 pub tokens: u64,
5627}
5628
5629#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5631pub struct ContextMemoryFile {
5632 pub path: String,
5633 #[serde(rename = "type")]
5635 pub file_type: String,
5636 pub tokens: u64,
5637}
5638
5639#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5641pub struct ContextAgent {
5642 pub agent_type: String,
5643 pub source: String,
5645 pub tokens: u64,
5646}
5647
5648#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5650pub struct ContextSkill {
5651 pub name: String,
5652 pub source: String,
5654 #[serde(default, skip_serializing_if = "Option::is_none")]
5655 pub plugin_name: Option<String>,
5656 pub tokens: u64,
5657}
5658
5659#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5661pub struct ToolUseMeta {
5662 pub id: String,
5663 pub display_name: String,
5664 #[serde(default, skip_serializing_if = "Option::is_none")]
5665 pub server_display_name: Option<String>,
5666 #[serde(default, skip_serializing_if = "Option::is_none")]
5667 pub icon_url: Option<String>,
5668 #[serde(default, skip_serializing_if = "Option::is_none")]
5673 pub server_id: Option<String>,
5674 #[serde(default, skip_serializing_if = "Option::is_none")]
5682 pub server_id_stamp: Option<String>,
5683 #[serde(default, skip_serializing_if = "Option::is_none")]
5686 pub tool_name: Option<String>,
5687}
5688
5689#[derive(Debug, Clone, Serialize, Deserialize)]
5691pub struct AssistantMessage {
5692 pub message: AssistantMessageContent,
5693 #[serde(alias = "sessionId")]
5694 pub session_id: String,
5695 #[serde(skip_serializing_if = "Option::is_none")]
5696 pub uuid: Option<String>,
5697 #[serde(skip_serializing_if = "Option::is_none")]
5698 pub parent_tool_use_id: Option<String>,
5699 #[serde(skip_serializing_if = "Option::is_none")]
5701 pub request_id: Option<String>,
5702 #[serde(default, skip_serializing_if = "Option::is_none")]
5707 pub refused_message_id: Option<String>,
5708 #[serde(default, skip_serializing_if = "Option::is_none")]
5717 pub user_message_uuid: Option<String>,
5718 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5724 pub user_message_uuids: Vec<String>,
5725 #[serde(default, skip_serializing_if = "Option::is_none")]
5729 pub agent_id: Option<String>,
5730 #[serde(skip_serializing_if = "Option::is_none")]
5733 pub subagent_type: Option<String>,
5734 #[serde(skip_serializing_if = "Option::is_none")]
5736 pub task_description: Option<String>,
5737 #[serde(skip_serializing_if = "Option::is_none")]
5738 pub error: Option<AssistantErrorKind>,
5739 #[serde(default, skip_serializing_if = "Option::is_none")]
5743 pub aborted: Option<bool>,
5744 #[serde(default, skip_serializing_if = "Option::is_none")]
5749 pub resumed_from_incomplete_thinking: Option<bool>,
5750 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5751 pub supersedes: Vec<String>,
5752 #[serde(skip_serializing_if = "Option::is_none")]
5753 pub timestamp: Option<String>,
5754 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5755 pub tool_use_meta: Vec<ToolUseMeta>,
5756 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5761 pub batch_tool_uses: Vec<BatchToolUse>,
5762 #[serde(default, skip_serializing_if = "Option::is_none")]
5768 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
5769 #[serde(default, skip_serializing_if = "Option::is_none")]
5775 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
5776 #[serde(default, skip_serializing_if = "Option::is_none")]
5780 pub historical: Option<bool>,
5781 #[serde(default, skip_serializing_if = "Option::is_none")]
5789 pub resume_reason: Option<String>,
5790 #[serde(default, skip_serializing_if = "Option::is_none")]
5796 pub local_command_source: Option<String>,
5797 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5806 pub narration_block_indexes: Vec<usize>,
5807 #[serde(default, skip_serializing_if = "Option::is_none")]
5811 pub narration_hint: Option<String>,
5812 #[serde(default, skip_serializing_if = "Option::is_none")]
5821 pub thinking_duration_ms: Option<u64>,
5822 #[serde(default, skip_serializing_if = "Option::is_none")]
5833 pub thinking_display: Option<String>,
5834 #[serde(default, skip_serializing_if = "Option::is_none")]
5838 pub context_usage: Option<ContextUsage>,
5839 #[serde(default, skip_serializing_if = "Option::is_none")]
5847 pub usage_report: Option<Box<UsageReport>>,
5848 #[serde(default, skip_serializing_if = "Option::is_none")]
5855 pub local_command_run: Option<LocalCommandRun>,
5856 #[serde(default, skip_serializing_if = "Option::is_none")]
5862 pub local_command_outcome: Option<LocalCommandOutcome>,
5863 #[serde(default, skip_serializing_if = "Option::is_none")]
5864 pub is_meta: Option<bool>,
5865 #[serde(default, skip_serializing_if = "Option::is_none")]
5866 pub is_virtual: Option<bool>,
5867 #[serde(default, skip_serializing_if = "Option::is_none")]
5868 pub is_api_error_message: Option<bool>,
5869 #[serde(skip_serializing_if = "Option::is_none")]
5870 pub api_error_status: Option<u16>,
5871 #[serde(skip_serializing_if = "Option::is_none")]
5919 pub api_error: Option<String>,
5920 #[serde(default, skip_serializing_if = "Option::is_none")]
5927 pub api_error_code: Option<String>,
5928 #[serde(default, skip_serializing_if = "Option::is_none")]
5939 pub api_error_origin: Option<String>,
5940 #[serde(default, skip_serializing_if = "Option::is_none")]
5943 pub api_error_params: Option<ApiErrorParams>,
5944 #[serde(skip_serializing_if = "Option::is_none")]
5945 pub error_details: Option<String>,
5946 #[serde(skip_serializing_if = "Option::is_none")]
5947 pub advisor_model: Option<String>,
5948 #[serde(skip_serializing_if = "Option::is_none")]
5949 pub attribution_agent: Option<String>,
5950 #[serde(skip_serializing_if = "Option::is_none")]
5951 pub attribution_skill: Option<String>,
5952 #[serde(skip_serializing_if = "Option::is_none")]
5953 pub attribution_plugin: Option<String>,
5954 #[serde(skip_serializing_if = "Option::is_none")]
5955 pub attribution_mcp_server: Option<String>,
5956 #[serde(skip_serializing_if = "Option::is_none")]
5957 pub attribution_mcp_tool: Option<String>,
5958}
5959
5960#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5964pub struct ApiErrorParams {
5965 #[serde(default, skip_serializing_if = "Option::is_none")]
5967 pub effort: Option<String>,
5968 #[serde(default, skip_serializing_if = "Option::is_none")]
5971 pub provider: Option<ApiErrorProvider>,
5972 #[serde(default, skip_serializing_if = "Option::is_none")]
5975 pub remedy: Option<ApiErrorRemedy>,
5976 #[serde(default, skip_serializing_if = "Option::is_none")]
5979 pub media: Option<ApiErrorMedia>,
5980 #[serde(default, skip_serializing_if = "Option::is_none")]
5982 pub media_reason: Option<ApiErrorMediaReason>,
5983 #[serde(default, skip_serializing_if = "Option::is_none")]
6005 pub rate_limit_info: Option<RateLimitInfo>,
6006}
6007
6008#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6010pub enum ApiErrorMedia {
6011 Image,
6012 Document,
6013 Unknown(String),
6015}
6016
6017impl ApiErrorMedia {
6018 pub fn as_str(&self) -> &str {
6019 match self {
6020 Self::Image => "image",
6021 Self::Document => "document",
6022 Self::Unknown(s) => s.as_str(),
6023 }
6024 }
6025}
6026
6027impl fmt::Display for ApiErrorMedia {
6028 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6029 f.write_str(self.as_str())
6030 }
6031}
6032
6033impl From<&str> for ApiErrorMedia {
6034 fn from(s: &str) -> Self {
6035 match s {
6036 "image" => Self::Image,
6037 "document" => Self::Document,
6038 other => Self::Unknown(other.to_string()),
6039 }
6040 }
6041}
6042
6043impl Serialize for ApiErrorMedia {
6044 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6045 serializer.serialize_str(self.as_str())
6046 }
6047}
6048
6049impl<'de> Deserialize<'de> for ApiErrorMedia {
6050 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6051 let s = String::deserialize(deserializer)?;
6052 Ok(Self::from(s.as_str()))
6053 }
6054}
6055
6056#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6059pub enum ApiErrorMediaReason {
6060 Unprocessable,
6063 UnsupportedByModel,
6065 MediaBudget,
6068 Unknown(String),
6070}
6071
6072impl ApiErrorMediaReason {
6073 pub fn as_str(&self) -> &str {
6074 match self {
6075 Self::Unprocessable => "unprocessable",
6076 Self::UnsupportedByModel => "unsupported_by_model",
6077 Self::MediaBudget => "media_budget",
6078 Self::Unknown(s) => s.as_str(),
6079 }
6080 }
6081}
6082
6083impl fmt::Display for ApiErrorMediaReason {
6084 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6085 f.write_str(self.as_str())
6086 }
6087}
6088
6089impl From<&str> for ApiErrorMediaReason {
6090 fn from(s: &str) -> Self {
6091 match s {
6092 "unprocessable" => Self::Unprocessable,
6093 "unsupported_by_model" => Self::UnsupportedByModel,
6094 "media_budget" => Self::MediaBudget,
6095 other => Self::Unknown(other.to_string()),
6096 }
6097 }
6098}
6099
6100impl Serialize for ApiErrorMediaReason {
6101 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6102 serializer.serialize_str(self.as_str())
6103 }
6104}
6105
6106impl<'de> Deserialize<'de> for ApiErrorMediaReason {
6107 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6108 let s = String::deserialize(deserializer)?;
6109 Ok(Self::from(s.as_str()))
6110 }
6111}
6112
6113#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6115pub enum ApiErrorProvider {
6116 Bedrock,
6117 AnthropicAws,
6118 Mantle,
6119 AnthropicGoogleCloud,
6120 Vertex,
6121 Foundry,
6122 Gateway,
6123 Unknown(String),
6125}
6126
6127impl ApiErrorProvider {
6128 pub fn as_str(&self) -> &str {
6129 match self {
6130 Self::Bedrock => "bedrock",
6131 Self::AnthropicAws => "anthropicAws",
6132 Self::Mantle => "mantle",
6133 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
6134 Self::Vertex => "vertex",
6135 Self::Foundry => "foundry",
6136 Self::Gateway => "gateway",
6137 Self::Unknown(s) => s.as_str(),
6138 }
6139 }
6140}
6141
6142impl fmt::Display for ApiErrorProvider {
6143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6144 f.write_str(self.as_str())
6145 }
6146}
6147
6148impl From<&str> for ApiErrorProvider {
6149 fn from(s: &str) -> Self {
6150 match s {
6151 "bedrock" => Self::Bedrock,
6152 "anthropicAws" => Self::AnthropicAws,
6153 "mantle" => Self::Mantle,
6154 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
6155 "vertex" => Self::Vertex,
6156 "foundry" => Self::Foundry,
6157 "gateway" => Self::Gateway,
6158 other => Self::Unknown(other.to_string()),
6159 }
6160 }
6161}
6162
6163impl Serialize for ApiErrorProvider {
6164 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6165 serializer.serialize_str(self.as_str())
6166 }
6167}
6168
6169impl<'de> Deserialize<'de> for ApiErrorProvider {
6170 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6171 let s = String::deserialize(deserializer)?;
6172 Ok(Self::from(s.as_str()))
6173 }
6174}
6175
6176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6178pub enum ApiErrorRemedy {
6179 RefreshCommand,
6182 RefreshCredentials,
6184 Adc,
6187 GatewayToken,
6190 HostManaged,
6192 ModelAccess,
6195 Unknown(String),
6197}
6198
6199impl ApiErrorRemedy {
6200 pub fn as_str(&self) -> &str {
6201 match self {
6202 Self::RefreshCommand => "refresh_command",
6203 Self::RefreshCredentials => "refresh_credentials",
6204 Self::Adc => "adc",
6205 Self::GatewayToken => "gateway_token",
6206 Self::HostManaged => "host_managed",
6207 Self::ModelAccess => "model_access",
6208 Self::Unknown(s) => s.as_str(),
6209 }
6210 }
6211}
6212
6213impl fmt::Display for ApiErrorRemedy {
6214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6215 f.write_str(self.as_str())
6216 }
6217}
6218
6219impl From<&str> for ApiErrorRemedy {
6220 fn from(s: &str) -> Self {
6221 match s {
6222 "refresh_command" => Self::RefreshCommand,
6223 "refresh_credentials" => Self::RefreshCredentials,
6224 "adc" => Self::Adc,
6225 "gateway_token" => Self::GatewayToken,
6226 "host_managed" => Self::HostManaged,
6227 "model_access" => Self::ModelAccess,
6228 other => Self::Unknown(other.to_string()),
6229 }
6230 }
6231}
6232
6233impl Serialize for ApiErrorRemedy {
6234 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6235 serializer.serialize_str(self.as_str())
6236 }
6237}
6238
6239impl<'de> Deserialize<'de> for ApiErrorRemedy {
6240 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6241 let s = String::deserialize(deserializer)?;
6242 Ok(Self::from(s.as_str()))
6243 }
6244}
6245
6246#[derive(Debug, Clone, Serialize, Deserialize)]
6248pub struct AssistantMessageContent {
6249 pub id: String,
6250 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
6252 pub message_type: Option<String>,
6253 pub role: MessageRole,
6254 pub model: String,
6255 pub content: Vec<ContentBlock>,
6256 #[serde(skip_serializing_if = "Option::is_none")]
6257 pub stop_reason: Option<StopReason>,
6258 #[serde(skip_serializing_if = "Option::is_none")]
6259 pub stop_sequence: Option<String>,
6260 #[serde(skip_serializing_if = "Option::is_none")]
6261 pub usage: Option<AssistantUsage>,
6262 #[serde(skip_serializing_if = "Option::is_none")]
6264 pub stop_details: Option<Value>,
6265 #[serde(skip_serializing_if = "Option::is_none")]
6267 pub context_management: Option<Value>,
6268}
6269
6270#[derive(Debug, Clone, Serialize, Deserialize)]
6272pub struct AssistantUsage {
6273 #[serde(default)]
6275 pub input_tokens: u32,
6276
6277 #[serde(default)]
6279 pub output_tokens: u32,
6280
6281 #[serde(default)]
6283 pub cache_creation_input_tokens: u32,
6284
6285 #[serde(default)]
6287 pub cache_read_input_tokens: u32,
6288
6289 #[serde(skip_serializing_if = "Option::is_none")]
6291 pub service_tier: Option<String>,
6292
6293 #[serde(skip_serializing_if = "Option::is_none")]
6295 pub cache_creation: Option<CacheCreationDetails>,
6296
6297 #[serde(skip_serializing_if = "Option::is_none")]
6299 pub inference_geo: Option<String>,
6300
6301 #[serde(default, skip_serializing_if = "Option::is_none")]
6304 pub speed: Option<String>,
6305
6306 #[serde(default, skip_serializing_if = "Option::is_none")]
6309 pub fallback_credit: Option<Value>,
6310}
6311
6312#[derive(Debug, Clone, Serialize, Deserialize)]
6314pub struct CacheCreationDetails {
6315 #[serde(default)]
6317 pub ephemeral_1h_input_tokens: u32,
6318
6319 #[serde(default)]
6321 pub ephemeral_5m_input_tokens: u32,
6322}
6323
6324#[cfg(test)]
6325mod tests {
6326 use crate::io::ClaudeOutput;
6327
6328 #[test]
6329 fn test_subagent_usage_rollup_accumulates_task_results() {
6330 use super::SubagentUsageRollup;
6331
6332 let mut rollup = SubagentUsageRollup::default();
6333
6334 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}}"#;
6335 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
6336 assert!(rollup.observe(&output));
6337 assert_eq!(rollup.subagent_tokens, 10201);
6338 assert_eq!(rollup.agent_count, 1);
6339 assert_eq!(rollup.tool_uses, 3);
6340 assert_eq!(rollup.duration_ms, 1853);
6341
6342 assert!(!rollup.observe(&output));
6344 assert_eq!(rollup.agent_count, 1);
6345 assert_eq!(rollup.subagent_tokens, 10201);
6346
6347 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}}"#;
6349 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
6350 assert!(rollup.observe(&output));
6351 assert_eq!(rollup.agent_count, 2);
6352 assert_eq!(rollup.subagent_tokens, 10701);
6353 }
6354
6355 #[test]
6356 fn test_subagent_usage_rollup_ignores_non_task_results() {
6357 use super::SubagentUsageRollup;
6358
6359 let mut rollup = SubagentUsageRollup::default();
6360
6361 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}}"#;
6364 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
6365 assert!(!rollup.observe(&output));
6366
6367 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
6369 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
6370 assert!(!rollup.observe(&output));
6371
6372 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
6374 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
6375 assert!(!rollup.observe(&output));
6376
6377 assert_eq!(rollup, SubagentUsageRollup::default());
6378 }
6379
6380 #[test]
6381 fn test_subagent_usage_rollup_over_captured_session() {
6382 use super::SubagentUsageRollup;
6383
6384 let mut rollup = SubagentUsageRollup::default();
6385 let fixture =
6386 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
6387 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
6388 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
6389 rollup.observe(&output);
6390 }
6391 }
6392 assert_eq!(rollup.agent_count, 1);
6393 assert_eq!(rollup.subagent_tokens, 10201);
6394 }
6395
6396 #[test]
6397 fn test_system_message_init() {
6398 let json = r#"{
6399 "type": "system",
6400 "subtype": "init",
6401 "session_id": "test-session-123",
6402 "cwd": "/home/user/project",
6403 "model": "claude-sonnet-4",
6404 "tools": ["Bash", "Read", "Write"],
6405 "mcp_servers": [],
6406 "slash_commands": ["compact", "cost", "review"],
6407 "agents": ["Bash", "Explore", "Plan"],
6408 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
6409 "skills": [],
6410 "claude_code_version": "2.1.15",
6411 "apiKeySource": "none",
6412 "output_style": "default",
6413 "permissionMode": "default"
6414 }"#;
6415
6416 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6417 if let ClaudeOutput::System(sys) = output {
6418 assert!(sys.is_init());
6419 assert!(!sys.is_status());
6420 assert!(!sys.is_compact_boundary());
6421
6422 let init = sys.as_init().expect("Should parse as init");
6423 assert_eq!(init.session_id, "test-session-123");
6424 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
6425 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
6426 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
6427 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
6428 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
6429 assert_eq!(init.plugins.len(), 1);
6430 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
6431 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
6432 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
6433 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
6434 assert_eq!(
6435 init.permission_mode,
6436 Some(super::InitPermissionMode::Default)
6437 );
6438 } else {
6439 panic!("Expected System message");
6440 }
6441 }
6442
6443 #[test]
6444 fn test_system_message_init_from_real_capture() {
6445 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
6446 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6447 if let ClaudeOutput::System(sys) = output {
6448 let init = sys.as_init().expect("Should parse real init capture");
6449 assert_eq!(init.slash_commands.len(), 8);
6450 assert!(init.slash_commands.contains(&"compact".to_string()));
6451 assert!(init.slash_commands.contains(&"review".to_string()));
6452 assert_eq!(init.agents.len(), 5);
6453 assert!(init.agents.contains(&"Bash".to_string()));
6454 assert!(init.agents.contains(&"Explore".to_string()));
6455 assert_eq!(init.plugins.len(), 1);
6456 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
6457 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
6458 } else {
6459 panic!("Expected System message");
6460 }
6461 }
6462
6463 #[test]
6464 fn test_system_message_status() {
6465 let json = r#"{
6466 "type": "system",
6467 "subtype": "status",
6468 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6469 "status": "compacting",
6470 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
6471 }"#;
6472
6473 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6474 if let ClaudeOutput::System(sys) = output {
6475 assert!(sys.is_status());
6476 assert!(!sys.is_init());
6477
6478 let status = sys.as_status().expect("Should parse as status");
6479 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6480 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
6481 assert_eq!(
6482 status.uuid,
6483 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
6484 );
6485 } else {
6486 panic!("Expected System message");
6487 }
6488 }
6489
6490 #[test]
6491 fn test_system_message_status_null() {
6492 let json = r#"{
6493 "type": "system",
6494 "subtype": "status",
6495 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6496 "status": null,
6497 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
6498 }"#;
6499
6500 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6501 if let ClaudeOutput::System(sys) = output {
6502 let status = sys.as_status().expect("Should parse as status");
6503 assert_eq!(status.status, None);
6504 } else {
6505 panic!("Expected System message");
6506 }
6507 }
6508
6509 #[test]
6510 fn test_system_message_task_started() {
6511 let json = r#"{
6512 "type": "system",
6513 "subtype": "task_started",
6514 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6515 "task_id": "b6daf3f",
6516 "task_type": "local_bash",
6517 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
6518 "description": "Wait for CI on PR #12",
6519 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
6520 }"#;
6521
6522 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6523 if let ClaudeOutput::System(sys) = output {
6524 assert!(sys.is_task_started());
6525 assert!(!sys.is_task_progress());
6526 assert!(!sys.is_task_notification());
6527
6528 let task = sys.as_task_started().expect("Should parse as task_started");
6529 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
6530 assert_eq!(task.task_id, "b6daf3f");
6531 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
6532 assert_eq!(
6533 task.tool_use_id.as_deref(),
6534 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
6535 );
6536 assert_eq!(task.description, "Wait for CI on PR #12");
6537 } else {
6538 panic!("Expected System message");
6539 }
6540 }
6541
6542 #[test]
6543 fn test_system_message_task_started_agent() {
6544 let json = r#"{
6545 "type": "system",
6546 "subtype": "task_started",
6547 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6548 "task_id": "a4a7e0906e5fc64cc",
6549 "task_type": "local_agent",
6550 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6551 "description": "Explore Scene/ArrayScene duplication",
6552 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6553 }"#;
6554
6555 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6556 if let ClaudeOutput::System(sys) = output {
6557 let task = sys.as_task_started().expect("Should parse as task_started");
6558 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
6559 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
6560 } else {
6561 panic!("Expected System message");
6562 }
6563 }
6564
6565 #[test]
6566 fn test_system_message_task_progress() {
6567 let json = r#"{
6568 "type": "system",
6569 "subtype": "task_progress",
6570 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6571 "task_id": "a4a7e0906e5fc64cc",
6572 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6573 "description": "Reading src/jplephem/chebyshev.rs",
6574 "last_tool_name": "Read",
6575 "usage": {
6576 "duration_ms": 13996,
6577 "tool_uses": 9,
6578 "total_tokens": 38779
6579 },
6580 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6581 }"#;
6582
6583 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6584 if let ClaudeOutput::System(sys) = output {
6585 assert!(sys.is_task_progress());
6586 assert!(!sys.is_task_started());
6587
6588 let progress = sys
6589 .as_task_progress()
6590 .expect("Should parse as task_progress");
6591 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
6592 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
6593 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
6594 assert_eq!(progress.usage.duration_ms, 13996);
6595 assert_eq!(progress.usage.tool_uses, 9);
6596 assert_eq!(progress.usage.total_tokens, 38779);
6597 } else {
6598 panic!("Expected System message");
6599 }
6600 }
6601
6602 #[test]
6603 fn test_system_message_task_notification_completed() {
6604 let json = r#"{
6605 "type": "system",
6606 "subtype": "task_notification",
6607 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6608 "task_id": "a0ba761e9dc9c316f",
6609 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
6610 "status": "completed",
6611 "summary": "Agent \"Write Hipparcos data source doc\" completed",
6612 "output_file": "",
6613 "usage": {
6614 "duration_ms": 172300,
6615 "tool_uses": 11,
6616 "total_tokens": 42005
6617 },
6618 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
6619 }"#;
6620
6621 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6622 if let ClaudeOutput::System(sys) = output {
6623 assert!(sys.is_task_notification());
6624
6625 let notif = sys
6626 .as_task_notification()
6627 .expect("Should parse as task_notification");
6628 assert_eq!(notif.status, super::TaskStatus::Completed);
6629 assert_eq!(
6630 notif.summary,
6631 "Agent \"Write Hipparcos data source doc\" completed"
6632 );
6633 assert_eq!(notif.output_file, Some("".to_string()));
6634 assert_eq!(
6635 notif.tool_use_id,
6636 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
6637 );
6638 let usage = notif.usage.expect("Should have usage");
6639 assert_eq!(usage.duration_ms, 172300);
6640 assert_eq!(usage.tool_uses, 11);
6641 assert_eq!(usage.total_tokens, 42005);
6642 } else {
6643 panic!("Expected System message");
6644 }
6645 }
6646
6647 #[test]
6648 fn test_system_message_task_notification_failed_no_usage() {
6649 let json = r#"{
6650 "type": "system",
6651 "subtype": "task_notification",
6652 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
6653 "task_id": "b98f6a3",
6654 "status": "failed",
6655 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
6656 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
6657 }"#;
6658
6659 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6660 if let ClaudeOutput::System(sys) = output {
6661 let notif = sys
6662 .as_task_notification()
6663 .expect("Should parse as task_notification");
6664 assert_eq!(notif.status, super::TaskStatus::Failed);
6665 assert!(notif.tool_use_id.is_none());
6666 assert!(notif.usage.is_none());
6667 assert_eq!(
6668 notif.output_file,
6669 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
6670 );
6671 } else {
6672 panic!("Expected System message");
6673 }
6674 }
6675
6676 #[test]
6682 fn test_task_messages_roundtrip_through_value() {
6683 let cases = [
6684 r#"{"type":"system","subtype":"task_started","session_id":"s1",
6685 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
6686 "description":"Sleep 3s","uuid":"u1"}"#,
6687 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
6688 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
6689 "last_tool_name":"Bash",
6690 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6691 "uuid":"u2"}"#,
6692 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
6693 "task_id":"t1","tool_use_id":"tu1","status":"completed",
6694 "summary":"done","output_file":"",
6695 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6696 "uuid":"u3"}"#,
6697 ];
6698
6699 for json in cases {
6700 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6701 let value = serde_json::to_value(&output).unwrap();
6702 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
6703
6704 let ClaudeOutput::System(sys) = reparsed else {
6705 panic!("Expected System variant after round-trip");
6706 };
6707
6708 match sys.subtype {
6709 super::SystemSubtype::TaskStarted => {
6710 assert!(
6711 sys.as_task_started().is_some(),
6712 "as_task_started failed after round-trip"
6713 );
6714 }
6715 super::SystemSubtype::TaskProgress => {
6716 assert!(
6717 sys.as_task_progress().is_some(),
6718 "as_task_progress failed after round-trip"
6719 );
6720 }
6721 super::SystemSubtype::TaskNotification => {
6722 assert!(
6723 sys.as_task_notification().is_some(),
6724 "as_task_notification failed after round-trip"
6725 );
6726 }
6727 other => panic!("unexpected subtype after round-trip: {other:?}"),
6728 }
6729 }
6730 }
6731
6732 #[test]
6733 fn test_system_message_compact_boundary() {
6734 let json = r#"{
6735 "type": "system",
6736 "subtype": "compact_boundary",
6737 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6738 "compact_metadata": {
6739 "pre_tokens": 155285,
6740 "trigger": "auto"
6741 },
6742 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
6743 }"#;
6744
6745 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6746 if let ClaudeOutput::System(sys) = output {
6747 assert!(sys.is_compact_boundary());
6748 assert!(!sys.is_init());
6749 assert!(!sys.is_status());
6750
6751 let compact = sys
6752 .as_compact_boundary()
6753 .expect("Should parse as compact_boundary");
6754 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6755 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
6756 assert_eq!(
6757 compact.compact_metadata.trigger,
6758 super::CompactionTrigger::Auto
6759 );
6760 assert!(compact.summary.is_none());
6762 assert!(compact.leaf_message_count.is_none());
6763 assert!(compact.duration_ms.is_none());
6764 } else {
6765 panic!("Expected System message");
6766 }
6767 }
6768
6769 #[test]
6770 fn test_compact_boundary_with_summary_stats() {
6771 let json = r#"{
6773 "type": "system",
6774 "subtype": "compact_boundary",
6775 "session_id": "s1",
6776 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
6777 "summary": "Summarized the earlier exploration.",
6778 "leaf_message_count": 42,
6779 "duration_ms": 1234,
6780 "uuid": "u1"
6781 }"#;
6782 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6783 let ClaudeOutput::System(sys) = output else {
6784 panic!("Expected System message");
6785 };
6786 let compact = sys.as_compact_boundary().expect("compact_boundary");
6787 assert_eq!(
6788 compact.summary.as_deref(),
6789 Some("Summarized the earlier exploration.")
6790 );
6791 assert_eq!(compact.leaf_message_count, Some(42));
6792 assert_eq!(compact.duration_ms, Some(1234));
6793
6794 let json_alt = r#"{
6797 "type": "system",
6798 "subtype": "compact_boundary",
6799 "session_id": "s2",
6800 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
6801 "content": "alt-key summary",
6802 "message_count": 7
6803 }"#;
6804 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
6805 let ClaudeOutput::System(sys) = output else {
6806 panic!("Expected System message");
6807 };
6808 let compact = sys.as_compact_boundary().expect("compact_boundary");
6809 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
6810 assert_eq!(compact.leaf_message_count, Some(7));
6811 }
6812
6813 #[test]
6814 fn test_init_message_with_new_fields() {
6815 let json = r#"{
6816 "type": "system",
6817 "subtype": "init",
6818 "session_id": "test-session",
6819 "cwd": "/home/user",
6820 "model": "claude-opus-4-7",
6821 "tools": ["Bash"],
6822 "mcp_servers": [],
6823 "permissionMode": "default",
6824 "apiKeySource": "none",
6825 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
6826 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
6827 "fast_mode_state": "off",
6828 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
6829 "claude_code_version": "2.1.117"
6830 }"#;
6831
6832 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6833 if let ClaudeOutput::System(sys) = output {
6834 let init = sys.as_init().expect("Should parse as init");
6835 assert_eq!(
6836 init.uuid.as_deref(),
6837 Some("44841a0d-182d-493a-86b5-79800d3d9665")
6838 );
6839 assert!(init.memory_paths.is_some());
6840 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
6841 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
6842 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
6843 } else {
6844 panic!("Expected System message");
6845 }
6846 }
6847
6848 #[test]
6849 fn test_assistant_message_with_new_fields() {
6850 let json = r#"{
6851 "type": "assistant",
6852 "message": {
6853 "id": "msg_1",
6854 "type": "message",
6855 "role": "assistant",
6856 "model": "claude-opus-4-7",
6857 "content": [{"type": "text", "text": "Hello"}],
6858 "stop_reason": "end_turn",
6859 "stop_details": null,
6860 "context_management": null,
6861 "usage": {
6862 "input_tokens": 100,
6863 "output_tokens": 10,
6864 "cache_creation_input_tokens": 50,
6865 "cache_read_input_tokens": 0,
6866 "service_tier": "standard",
6867 "inference_geo": "not_available"
6868 }
6869 },
6870 "session_id": "abc",
6871 "uuid": "msg-uuid-123"
6872 }"#;
6873
6874 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6875 if let ClaudeOutput::Assistant(asst) = output {
6876 assert_eq!(asst.message.stop_details, None);
6877 assert_eq!(asst.message.context_management, None);
6878 let usage = asst.message.usage.unwrap();
6879 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
6880 } else {
6881 panic!("Expected Assistant message");
6882 }
6883 }
6884
6885 #[test]
6886 fn test_user_message_with_new_fields() {
6887 let json = r#"{
6888 "type": "user",
6889 "message": {
6890 "role": "user",
6891 "content": [{"type": "text", "text": "Hello"}]
6892 },
6893 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6894 "parent_tool_use_id": "toolu_123",
6895 "uuid": "user-msg-456"
6896 }"#;
6897
6898 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6899 if let ClaudeOutput::User(user) = output {
6900 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
6901 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
6902 } else {
6903 panic!("Expected User message");
6904 }
6905 }
6906
6907 #[test]
6913 fn test_user_message_preserves_tool_use_result_and_timestamp() {
6914 let json = r#"{
6915 "type":"user",
6916 "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"}]},
6917 "parent_tool_use_id":null,
6918 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6919 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
6920 "timestamp":"2026-05-12T23:12:04.121Z",
6921 "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"}}
6922 }"#;
6923
6924 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6925 let user = match output {
6926 ClaudeOutput::User(u) => u,
6927 other => panic!("Expected User message, got {:?}", other.message_type()),
6928 };
6929
6930 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
6931 let raw = user
6932 .tool_use_result
6933 .as_ref()
6934 .expect("tool_use_result must be captured");
6935 assert_eq!(raw["answers"]["Color"], "Blue");
6936 assert_eq!(raw["questions"][0]["header"], "Color");
6937
6938 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
6942 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
6943 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
6944 assert_eq!(
6945 reser["tool_use_result"]["questions"][0]["question"],
6946 "Which color do you prefer?"
6947 );
6948
6949 let typed: crate::AskUserQuestionInput = user
6952 .tool_use_result_as::<crate::AskUserQuestionInput>()
6953 .expect("tool_use_result present")
6954 .expect("AskUserQuestionInput parses");
6955 assert_eq!(typed.questions.len(), 1);
6956 assert_eq!(typed.questions[0].header, "Color");
6957 let answers = typed.answers.expect("answers populated");
6958 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
6959 }
6960
6961 #[test]
6964 fn test_user_message_without_tool_use_result_omits_field() {
6965 let json = r#"{
6966 "type":"user",
6967 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
6968 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
6969 }"#;
6970
6971 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6972 let user = match output {
6973 ClaudeOutput::User(u) => u,
6974 _ => panic!("Expected User message"),
6975 };
6976 assert!(user.tool_use_result.is_none());
6977 assert!(user.timestamp.is_none());
6978
6979 let reser = serde_json::to_value(&user).unwrap();
6980 assert!(reser.get("tool_use_result").is_none());
6981 assert!(reser.get("timestamp").is_none());
6982 }
6983
6984 #[test]
6988 fn test_subagent_result_exposes_token_accounting() {
6989 let json = r#"{
6990 "type":"user",
6991 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
6992 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
6993 "tool_use_result":{
6994 "status":"completed",
6995 "prompt":"Count the .rs files.",
6996 "agentId":"ac4f0276e9d4b6232",
6997 "agentType":"Explore",
6998 "content":[{"type":"text","text":"21"}],
6999 "resolvedModel":"claude-haiku-4-5-20251001",
7000 "totalDurationMs":6869,
7001 "totalTokens":7834,
7002 "totalToolUseCount":1,
7003 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
7004 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
7005 }
7006 }"#;
7007
7008 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7009 let user = match output {
7010 ClaudeOutput::User(u) => u,
7011 _ => panic!("Expected User message"),
7012 };
7013
7014 let result = user.subagent_result().expect("subagent result parses");
7015 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
7016 assert_eq!(
7017 result.resolved_model.as_deref(),
7018 Some("claude-haiku-4-5-20251001")
7019 );
7020 assert_eq!(result.total_tokens, Some(7834));
7021 assert_eq!(result.total_duration_ms, Some(6869));
7022 assert_eq!(result.total_tool_use_count, Some(1));
7023
7024 let usage = result.usage.expect("nested usage present");
7025 assert_eq!(usage.input_tokens, 6);
7026 assert_eq!(usage.cache_read_input_tokens, 7699);
7027
7028 let stats = result.tool_stats.expect("toolStats present");
7029 assert_eq!(stats.bash_count, 1);
7030 }
7031
7032 #[test]
7036 fn test_subagent_result_absent_for_non_task_result() {
7037 let json = r#"{
7038 "type":"user",
7039 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
7040 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
7041 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
7042 }"#;
7043
7044 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7045 let user = match output {
7046 ClaudeOutput::User(u) => u,
7047 _ => panic!("Expected User message"),
7048 };
7049
7050 let result = user.subagent_result().expect("lenient parse");
7051 assert_eq!(result.total_tokens, None);
7052 assert_eq!(result.agent_type, None);
7053 }
7054
7055 #[test]
7056 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
7057 use serde_json::Value;
7058
7059 let raw: Value = serde_json::from_str(
7060 r#"{
7061 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
7062 "fast_mode_state":"off",
7063 "fast_mode_disabled_reason":"not_first_party",
7064 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
7065 }"#,
7066 )
7067 .unwrap();
7068 crate::io::assert_fully_wrapped(&raw);
7069
7070 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7071 let ClaudeOutput::System(sys) = output else {
7072 panic!("expected System");
7073 };
7074 let init = sys.as_init().expect("parses as init");
7075 assert_eq!(
7076 init.fast_mode_disabled_reason,
7077 Some(crate::FastModeDisabledReason::NotFirstParty)
7078 );
7079 let errs = init.mcp_server_errors.unwrap();
7080 assert_eq!(errs.len(), 1);
7081 assert_eq!(errs[0].name, "broken");
7082 assert_eq!(errs[0].error_type, "invalid_config");
7083 }
7084
7085 #[test]
7086 fn test_code_change_published_fully_wrapped() {
7087 use super::{KnownSystemEvent, SystemSubtype};
7088 use serde_json::Value;
7089
7090 let raw: Value = serde_json::from_str(
7091 r#"{
7092 "type":"system","subtype":"code_change_published",
7093 "provider":"github","url":"https://github.com/owner/repo/pull/42",
7094 "repo":"owner/repo","identifier":"42",
7095 "uuid":"u1","session_id":"s1"
7096 }"#,
7097 )
7098 .unwrap();
7099 crate::io::assert_fully_wrapped(&raw);
7100
7101 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7102 let ClaudeOutput::System(sys) = output else {
7103 panic!("expected System");
7104 };
7105 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
7106 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
7107 panic!("expected CodeChangePublished event");
7108 };
7109 assert_eq!(msg.provider, "github");
7110 assert_eq!(msg.repo, "owner/repo");
7111 assert_eq!(msg.identifier, "42");
7112
7113 assert!(sys.is_code_change_published());
7114 assert!(!sys.is_vcs_state_changed());
7115 let direct = sys.as_code_change_published().expect("direct accessor");
7116 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
7117 assert!(sys.as_vcs_state_changed().is_none());
7118 }
7119
7120 #[test]
7121 fn test_feedback_draft_queued_fully_wrapped() {
7122 use super::{KnownSystemEvent, SystemSubtype};
7123 use serde_json::Value;
7124
7125 let raw: Value = serde_json::from_str(
7126 r#"{
7127 "type":"system","subtype":"feedback_draft_queued",
7128 "draft_id":"draft-1","draft_type":"bug_report",
7129 "title":"Tool output was truncated",
7130 "details_preview":"The last command omitted its final lines",
7131 "uuid":"u1","session_id":"s1","future_field":"preserved"
7132 }"#,
7133 )
7134 .unwrap();
7135 crate::io::assert_fully_wrapped(&raw);
7136
7137 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7138 let ClaudeOutput::System(sys) = output else {
7139 panic!("expected System");
7140 };
7141 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
7142 assert!(sys.is_feedback_draft_queued());
7143 assert!(!sys.is_vcs_state_changed());
7144
7145 let direct = sys
7146 .as_feedback_draft_queued()
7147 .expect("direct typed accessor");
7148 assert_eq!(direct.draft_id, "draft-1");
7149 assert_eq!(direct.draft_type, "bug_report");
7150 assert_eq!(direct.extra["future_field"], "preserved");
7151
7152 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
7153 panic!("expected FeedbackDraftQueued event");
7154 };
7155 assert_eq!(known.title, "Tool output was truncated");
7156 assert_eq!(
7157 sys.typed_value().expect("typed value")["future_field"],
7158 "preserved"
7159 );
7160 }
7161
7162 #[test]
7163 fn test_cloud_session_delta_fully_wrapped() {
7164 use super::{KnownSystemEvent, SystemSubtype};
7165 use serde_json::Value;
7166
7167 let raw: Value = serde_json::from_str(
7168 r#"{
7169 "type":"system","subtype":"cloud_session_delta",
7170 "seq":3,"changed":["serving","connection"],
7171 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
7172 "serving":{"state":"on"},"connection":{"state":"live"}},
7173 "uuid":"u1","session_id":"s1","future_field":"preserved"
7174 }"#,
7175 )
7176 .unwrap();
7177 crate::io::assert_fully_wrapped(&raw);
7178
7179 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7180 let ClaudeOutput::System(sys) = output else {
7181 panic!("expected System");
7182 };
7183 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
7184 assert!(sys.is_cloud_session_delta());
7185 assert!(!sys.is_feedback_draft_queued());
7186
7187 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
7188 assert_eq!(direct.seq, 3);
7189 assert_eq!(direct.changed, vec!["serving", "connection"]);
7190 assert_eq!(direct.cloud_session["id"], "session_abc");
7191 assert_eq!(direct.extra["future_field"], "preserved");
7192
7193 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
7194 panic!("expected CloudSessionDelta event");
7195 };
7196 assert_eq!(known.session_id, "s1");
7197 assert_eq!(
7198 sys.typed_value().expect("typed value")["future_field"],
7199 "preserved"
7200 );
7201 }
7202
7203 #[test]
7204 fn test_vcs_state_changed_fully_wrapped() {
7205 use super::{KnownSystemEvent, VcsMutationKind};
7206 use serde_json::Value;
7207
7208 for kind in ["commit", "push", "merge", "rebase"] {
7209 let raw: Value = serde_json::from_str(&format!(
7210 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
7211 kind
7212 ))
7213 .unwrap();
7214 crate::io::assert_fully_wrapped(&raw);
7215
7216 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7217 let ClaudeOutput::System(sys) = output else {
7218 panic!("expected System");
7219 };
7220 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
7221 panic!("expected VcsStateChanged event");
7222 };
7223 assert_eq!(msg.kind.as_str(), kind);
7224 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
7225 }
7226
7227 let raw: Value = serde_json::from_str(
7229 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
7230 )
7231 .unwrap();
7232 crate::io::assert_fully_wrapped(&raw);
7233 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7234 let ClaudeOutput::System(sys) = output else {
7235 panic!("expected System");
7236 };
7237 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
7238 panic!("expected VcsStateChanged event");
7239 };
7240 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
7241
7242 assert!(sys.is_vcs_state_changed());
7243 let direct = sys.as_vcs_state_changed().expect("direct accessor");
7244 assert_eq!(direct.cwd, "/repo");
7245 assert!(sys.as_code_change_published().is_none());
7246 }
7247
7248 #[test]
7249 fn test_assistant_aborted_and_resume_flags_roundtrip() {
7250 let json = r#"{
7251 "type":"assistant",
7252 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
7253 "session_id":"s1",
7254 "aborted":true,
7255 "resumed_from_incomplete_thinking":true
7256 }"#;
7257 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7258 let ClaudeOutput::Assistant(msg) = &output else {
7259 panic!("expected Assistant");
7260 };
7261 assert_eq!(msg.aborted, Some(true));
7262 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
7263 let reserialized = serde_json::to_string(&output).unwrap();
7264 assert!(reserialized.contains("\"aborted\":true"));
7265 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
7266
7267 let json = r#"{
7269 "type":"assistant",
7270 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
7271 "session_id":"s2"
7272 }"#;
7273 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7274 let reserialized = serde_json::to_string(&output).unwrap();
7275 assert!(!reserialized.contains("aborted"));
7276 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
7277 }
7278
7279 #[test]
7280 fn test_user_tool_result_meta_roundtrip() {
7281 let json = r#"{
7282 "type":"user",
7283 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
7284 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
7285 "tool_result_meta":[
7286 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
7287 {"id":"toolu_2","non_execution_kind":"permission-rule"}
7288 ]
7289 }"#;
7290 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7291 let ClaudeOutput::User(user) = &output else {
7292 panic!("expected User");
7293 };
7294 let meta = user.tool_result_meta.as_ref().unwrap();
7295 assert_eq!(meta.len(), 2);
7296 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
7297 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
7298 assert_eq!(meta[1].user_feedback, None);
7299
7300 let reserialized = serde_json::to_string(&output).unwrap();
7301 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
7302 assert!(!reserialized.contains("\"user_feedback\":null"));
7303 }
7304
7305 #[test]
7309 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
7310 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
7311 let with_scope = serde_json::json!({
7312 "trigger": "refusal",
7313 "direction": "retry",
7314 "scope": "local",
7315 "original_model": "claude-fable-5",
7316 "fallback_model": "claude-opus-5",
7317 "request_id": null,
7318 "content": "Refused; retried on fallback model.",
7319 "uuid": "u1",
7320 "session_id": "s1"
7321 });
7322 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
7323 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
7324 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
7325
7326 let mut without = with_scope.clone();
7329 without.as_object_mut().unwrap().remove("scope");
7330 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
7331 assert_eq!(msg.scope, None);
7332 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
7333
7334 assert_eq!(
7336 RefusalFallbackScope::from("workspace").as_str(),
7337 "workspace"
7338 );
7339 }
7340}