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 UiNotify,
64 UiNotifyCancel,
65 UiPanes,
66 UiPlaced,
67 UiScroll,
68 UiStatus,
69 UiToast,
70 Unknown(String),
72}
73
74impl SystemSubtype {
75 pub fn as_str(&self) -> &str {
76 match self {
77 Self::Init => "init",
78 Self::Status => "status",
79 Self::CompactBoundary => "compact_boundary",
80 Self::ThinkingTokens => "thinking_tokens",
81 Self::TaskStarted => "task_started",
82 Self::TaskProgress => "task_progress",
83 Self::TaskUpdated => "task_updated",
84 Self::TaskNotification => "task_notification",
85 Self::ApiRetry => "api_retry",
86 Self::ControlRequestProgress => "control_request_progress",
87 Self::ModelRefusalFallback => "model_refusal_fallback",
88 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
89 Self::LocalCommandOutput => "local_command_output",
90 Self::HookStarted => "hook_started",
91 Self::HookProgress => "hook_progress",
92 Self::HookResponse => "hook_response",
93 Self::PluginInstall => "plugin_install",
94 Self::BackgroundTasksChanged => "background_tasks_changed",
95 Self::SessionStateChanged => "session_state_changed",
96 Self::WorkerShuttingDown => "worker_shutting_down",
97 Self::CommandsChanged => "commands_changed",
98 Self::Notification => "notification",
99 Self::FilesPersisted => "files_persisted",
100 Self::MemoryRecall => "memory_recall",
101 Self::ElicitationComplete => "elicitation_complete",
102 Self::PermissionDenied => "permission_denied",
103 Self::MirrorError => "mirror_error",
104 Self::Informational => "informational",
105 Self::CodeChangePublished => "code_change_published",
106 Self::VcsStateChanged => "vcs_state_changed",
107 Self::FeedbackDraftQueued => "feedback_draft_queued",
108 Self::CloudSessionDelta => "cloud_session_delta",
109 Self::DevIntent => "dev_intent",
110 Self::TurnHandoffAvailable => "turn_handoff_available",
111 Self::TurnPreempted => "turn_preempted",
112 Self::PeerMessageHold => "peer_message_hold",
113 Self::PerTurnEffortChanged => "per_turn_effort_changed",
114 Self::SessionTitleChanged => "session_title_changed",
115 Self::SessionWritesLost => "session_writes_lost",
116 Self::InstructionSizeWarning => "instruction_size_warning",
117 Self::FileAttachmentsMissing => "file_attachments_missing",
118 Self::PermissionCheckStatus => "permission_check_status",
119 Self::InheritedAsksSettled => "inherited_asks_settled",
120 Self::UiFocus => "ui_focus",
121 Self::UiInvalidate => "ui_invalidate",
122 Self::UiLog => "ui_log",
123 Self::UiNotify => "ui_notify",
124 Self::UiNotifyCancel => "ui_notify_cancel",
125 Self::UiPanes => "ui_panes",
126 Self::UiPlaced => "ui_placed",
127 Self::UiScroll => "ui_scroll",
128 Self::UiStatus => "ui_status",
129 Self::UiToast => "ui_toast",
130 Self::Unknown(s) => s.as_str(),
131 }
132 }
133}
134
135impl fmt::Display for SystemSubtype {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 f.write_str(self.as_str())
138 }
139}
140
141impl From<&str> for SystemSubtype {
142 fn from(s: &str) -> Self {
143 match s {
144 "init" => Self::Init,
145 "status" => Self::Status,
146 "compact_boundary" => Self::CompactBoundary,
147 "thinking_tokens" => Self::ThinkingTokens,
148 "task_started" => Self::TaskStarted,
149 "task_progress" => Self::TaskProgress,
150 "task_updated" => Self::TaskUpdated,
151 "task_notification" => Self::TaskNotification,
152 "api_retry" => Self::ApiRetry,
153 "control_request_progress" => Self::ControlRequestProgress,
154 "model_refusal_fallback" => Self::ModelRefusalFallback,
155 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
156 "local_command_output" => Self::LocalCommandOutput,
157 "hook_started" => Self::HookStarted,
158 "hook_progress" => Self::HookProgress,
159 "hook_response" => Self::HookResponse,
160 "plugin_install" => Self::PluginInstall,
161 "background_tasks_changed" => Self::BackgroundTasksChanged,
162 "session_state_changed" => Self::SessionStateChanged,
163 "worker_shutting_down" => Self::WorkerShuttingDown,
164 "commands_changed" => Self::CommandsChanged,
165 "notification" => Self::Notification,
166 "files_persisted" => Self::FilesPersisted,
167 "memory_recall" => Self::MemoryRecall,
168 "elicitation_complete" => Self::ElicitationComplete,
169 "permission_denied" => Self::PermissionDenied,
170 "mirror_error" => Self::MirrorError,
171 "informational" => Self::Informational,
172 "code_change_published" => Self::CodeChangePublished,
173 "vcs_state_changed" => Self::VcsStateChanged,
174 "feedback_draft_queued" => Self::FeedbackDraftQueued,
175 "cloud_session_delta" => Self::CloudSessionDelta,
176 "dev_intent" => Self::DevIntent,
177 "turn_handoff_available" => Self::TurnHandoffAvailable,
178 "turn_preempted" => Self::TurnPreempted,
179 "peer_message_hold" => Self::PeerMessageHold,
180 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
181 "session_title_changed" => Self::SessionTitleChanged,
182 "session_writes_lost" => Self::SessionWritesLost,
183 "instruction_size_warning" => Self::InstructionSizeWarning,
184 "file_attachments_missing" => Self::FileAttachmentsMissing,
185 "permission_check_status" => Self::PermissionCheckStatus,
186 "inherited_asks_settled" => Self::InheritedAsksSettled,
187 "ui_focus" => Self::UiFocus,
188 "ui_invalidate" => Self::UiInvalidate,
189 "ui_log" => Self::UiLog,
190 "ui_notify" => Self::UiNotify,
191 "ui_notify_cancel" => Self::UiNotifyCancel,
192 "ui_panes" => Self::UiPanes,
193 "ui_placed" => Self::UiPlaced,
194 "ui_scroll" => Self::UiScroll,
195 "ui_status" => Self::UiStatus,
196 "ui_toast" => Self::UiToast,
197 other => Self::Unknown(other.to_string()),
198 }
199 }
200}
201
202impl Serialize for SystemSubtype {
203 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
204 serializer.serialize_str(self.as_str())
205 }
206}
207
208impl<'de> Deserialize<'de> for SystemSubtype {
209 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
210 let s = String::deserialize(deserializer)?;
211 Ok(Self::from(s.as_str()))
212 }
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Hash)]
220pub enum MessageRole {
221 User,
222 Assistant,
223 Unknown(String),
225}
226
227impl MessageRole {
228 pub fn as_str(&self) -> &str {
229 match self {
230 Self::User => "user",
231 Self::Assistant => "assistant",
232 Self::Unknown(s) => s.as_str(),
233 }
234 }
235}
236
237impl fmt::Display for MessageRole {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 f.write_str(self.as_str())
240 }
241}
242
243impl From<&str> for MessageRole {
244 fn from(s: &str) -> Self {
245 match s {
246 "user" => Self::User,
247 "assistant" => Self::Assistant,
248 other => Self::Unknown(other.to_string()),
249 }
250 }
251}
252
253impl Serialize for MessageRole {
254 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
255 serializer.serialize_str(self.as_str())
256 }
257}
258
259impl<'de> Deserialize<'de> for MessageRole {
260 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
261 let s = String::deserialize(deserializer)?;
262 Ok(Self::from(s.as_str()))
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, Hash)]
268pub enum CompactionTrigger {
269 Auto,
271 Manual,
273 Unknown(String),
275}
276
277impl CompactionTrigger {
278 pub fn as_str(&self) -> &str {
279 match self {
280 Self::Auto => "auto",
281 Self::Manual => "manual",
282 Self::Unknown(s) => s.as_str(),
283 }
284 }
285}
286
287impl fmt::Display for CompactionTrigger {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 f.write_str(self.as_str())
290 }
291}
292
293impl From<&str> for CompactionTrigger {
294 fn from(s: &str) -> Self {
295 match s {
296 "auto" => Self::Auto,
297 "manual" => Self::Manual,
298 other => Self::Unknown(other.to_string()),
299 }
300 }
301}
302
303impl Serialize for CompactionTrigger {
304 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
305 serializer.serialize_str(self.as_str())
306 }
307}
308
309impl<'de> Deserialize<'de> for CompactionTrigger {
310 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
311 let s = String::deserialize(deserializer)?;
312 Ok(Self::from(s.as_str()))
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Hash)]
318pub enum StopReason {
319 EndTurn,
321 MaxTokens,
323 ToolUse,
325 Unknown(String),
327}
328
329impl StopReason {
330 pub fn as_str(&self) -> &str {
331 match self {
332 Self::EndTurn => "end_turn",
333 Self::MaxTokens => "max_tokens",
334 Self::ToolUse => "tool_use",
335 Self::Unknown(s) => s.as_str(),
336 }
337 }
338}
339
340impl fmt::Display for StopReason {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 f.write_str(self.as_str())
343 }
344}
345
346impl From<&str> for StopReason {
347 fn from(s: &str) -> Self {
348 match s {
349 "end_turn" => Self::EndTurn,
350 "max_tokens" => Self::MaxTokens,
351 "tool_use" => Self::ToolUse,
352 other => Self::Unknown(other.to_string()),
353 }
354 }
355}
356
357impl Serialize for StopReason {
358 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
359 serializer.serialize_str(self.as_str())
360 }
361}
362
363impl<'de> Deserialize<'de> for StopReason {
364 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
365 let s = String::deserialize(deserializer)?;
366 Ok(Self::from(s.as_str()))
367 }
368}
369
370#[derive(Debug, Clone, PartialEq, Eq, Hash)]
372pub enum ApiKeySource {
373 None,
375 User,
376 Project,
377 Org,
378 Temporary,
379 Oauth,
380 Unknown(String),
382}
383
384impl ApiKeySource {
385 pub fn as_str(&self) -> &str {
386 match self {
387 Self::None => "none",
388 Self::User => "user",
389 Self::Project => "project",
390 Self::Org => "org",
391 Self::Temporary => "temporary",
392 Self::Oauth => "oauth",
393 Self::Unknown(s) => s.as_str(),
394 }
395 }
396}
397
398impl fmt::Display for ApiKeySource {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 f.write_str(self.as_str())
401 }
402}
403
404impl From<&str> for ApiKeySource {
405 fn from(s: &str) -> Self {
406 match s {
407 "none" => Self::None,
408 "user" => Self::User,
409 "project" => Self::Project,
410 "org" => Self::Org,
411 "temporary" => Self::Temporary,
412 "oauth" => Self::Oauth,
413 other => Self::Unknown(other.to_string()),
414 }
415 }
416}
417
418impl Serialize for ApiKeySource {
419 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
420 serializer.serialize_str(self.as_str())
421 }
422}
423
424impl<'de> Deserialize<'de> for ApiKeySource {
425 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
426 let s = String::deserialize(deserializer)?;
427 Ok(Self::from(s.as_str()))
428 }
429}
430
431#[derive(Debug, Clone, PartialEq, Eq, Hash)]
433pub enum OutputStyle {
434 Default,
436 Unknown(String),
438}
439
440impl OutputStyle {
441 pub fn as_str(&self) -> &str {
442 match self {
443 Self::Default => "default",
444 Self::Unknown(s) => s.as_str(),
445 }
446 }
447}
448
449impl fmt::Display for OutputStyle {
450 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451 f.write_str(self.as_str())
452 }
453}
454
455impl From<&str> for OutputStyle {
456 fn from(s: &str) -> Self {
457 match s {
458 "default" => Self::Default,
459 other => Self::Unknown(other.to_string()),
460 }
461 }
462}
463
464impl Serialize for OutputStyle {
465 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
466 serializer.serialize_str(self.as_str())
467 }
468}
469
470impl<'de> Deserialize<'de> for OutputStyle {
471 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
472 let s = String::deserialize(deserializer)?;
473 Ok(Self::from(s.as_str()))
474 }
475}
476
477#[derive(Debug, Clone, PartialEq, Eq, Hash)]
479pub enum InitPermissionMode {
480 Default,
482 AcceptEdits,
483 BypassPermissions,
484 Plan,
485 DontAsk,
486 Auto,
487 Unknown(String),
489}
490
491impl InitPermissionMode {
492 pub fn as_str(&self) -> &str {
493 match self {
494 Self::Default => "default",
495 Self::AcceptEdits => "acceptEdits",
496 Self::BypassPermissions => "bypassPermissions",
497 Self::Plan => "plan",
498 Self::DontAsk => "dontAsk",
499 Self::Auto => "auto",
500 Self::Unknown(s) => s.as_str(),
501 }
502 }
503}
504
505impl fmt::Display for InitPermissionMode {
506 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507 f.write_str(self.as_str())
508 }
509}
510
511impl From<&str> for InitPermissionMode {
512 fn from(s: &str) -> Self {
513 match s {
514 "default" => Self::Default,
515 "acceptEdits" => Self::AcceptEdits,
516 "bypassPermissions" => Self::BypassPermissions,
517 "plan" => Self::Plan,
518 "dontAsk" => Self::DontAsk,
519 "auto" => Self::Auto,
520 other => Self::Unknown(other.to_string()),
521 }
522 }
523}
524
525impl Serialize for InitPermissionMode {
526 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
527 serializer.serialize_str(self.as_str())
528 }
529}
530
531impl<'de> Deserialize<'de> for InitPermissionMode {
532 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
533 let s = String::deserialize(deserializer)?;
534 Ok(Self::from(s.as_str()))
535 }
536}
537
538#[derive(Debug, Clone, PartialEq, Eq, Hash)]
540pub enum StatusMessageStatus {
541 Compacting,
543 Requesting,
545 Unknown(String),
547}
548
549impl StatusMessageStatus {
550 pub fn as_str(&self) -> &str {
551 match self {
552 Self::Compacting => "compacting",
553 Self::Requesting => "requesting",
554 Self::Unknown(s) => s.as_str(),
555 }
556 }
557}
558
559impl fmt::Display for StatusMessageStatus {
560 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561 f.write_str(self.as_str())
562 }
563}
564
565impl From<&str> for StatusMessageStatus {
566 fn from(s: &str) -> Self {
567 match s {
568 "compacting" => Self::Compacting,
569 "requesting" => Self::Requesting,
570 other => Self::Unknown(other.to_string()),
571 }
572 }
573}
574
575impl Serialize for StatusMessageStatus {
576 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
577 serializer.serialize_str(self.as_str())
578 }
579}
580
581impl<'de> Deserialize<'de> for StatusMessageStatus {
582 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
583 let s = String::deserialize(deserializer)?;
584 Ok(Self::from(s.as_str()))
585 }
586}
587
588pub(crate) fn serialize_optional_uuid<S>(
590 uuid: &Option<Uuid>,
591 serializer: S,
592) -> Result<S::Ok, S::Error>
593where
594 S: Serializer,
595{
596 match uuid {
597 Some(id) => serializer.serialize_str(&id.to_string()),
598 None => serializer.serialize_none(),
599 }
600}
601
602pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
604where
605 D: Deserializer<'de>,
606{
607 let opt_str: Option<String> = Option::deserialize(deserializer)?;
608 match opt_str {
609 Some(s) => Uuid::parse_str(&s)
610 .map(Some)
611 .map_err(serde::de::Error::custom),
612 None => Ok(None),
613 }
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
619pub struct MessageOrigin {
620 pub kind: String,
621 #[serde(flatten)]
622 pub extra: serde_json::Map<String, Value>,
623}
624
625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
627pub struct SummarizeMetadata {
628 pub messages_summarized: u64,
629 #[serde(default, skip_serializing_if = "Option::is_none")]
630 pub user_context: Option<String>,
631 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub direction: Option<String>,
633}
634
635#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
637pub struct McpMeta {
638 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
639 pub meta: Option<Value>,
640 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub structured_content: Option<Value>,
642 #[serde(default, skip_serializing_if = "Vec::is_empty")]
647 pub resource_links: Vec<ResourceLink>,
648}
649
650#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
654pub struct ResourceLink {
655 pub uri: String,
656 pub name: String,
657 #[serde(default, skip_serializing_if = "Option::is_none")]
658 pub title: Option<String>,
659 #[serde(default, skip_serializing_if = "Option::is_none")]
660 pub description: Option<String>,
661 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
662 pub mime_type: Option<String>,
663 #[serde(default, skip_serializing_if = "Option::is_none")]
664 pub size: Option<u64>,
665 #[serde(default, skip_serializing_if = "Option::is_none")]
666 pub annotations: Option<Value>,
667}
668
669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
671pub struct ToolResultMeta {
672 pub id: String,
674 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub non_execution_kind: Option<String>,
682 #[serde(default, skip_serializing_if = "Option::is_none")]
684 pub user_feedback: Option<String>,
685 #[serde(default, skip_serializing_if = "Option::is_none")]
690 pub remedy: Option<ToolResultRemedy>,
691 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub permission_decision: Option<ToolPermissionDecision>,
699 #[serde(default, skip_serializing_if = "Option::is_none")]
706 pub built_in_tool: Option<String>,
707}
708
709#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
713pub struct ToolPermissionDecision {
714 pub decision: PermissionDecisionOutcome,
716 pub source: String,
724 #[serde(default, skip_serializing_if = "Option::is_none")]
730 pub reason_type: Option<PermissionDecisionReasonType>,
731 #[serde(default, skip_serializing_if = "Option::is_none")]
737 pub auto_mode_rule: Option<AutoModeRule>,
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
743pub struct AutoModeRule {
744 pub list: AutoModeRuleList,
746 pub name: String,
748}
749
750#[derive(Debug, Clone, PartialEq, Eq, Hash)]
752pub enum AutoModeRuleList {
753 Allow,
755 SoftDeny,
757 Unknown(String),
759}
760
761impl AutoModeRuleList {
762 pub fn as_str(&self) -> &str {
763 match self {
764 Self::Allow => "allow",
765 Self::SoftDeny => "soft_deny",
766 Self::Unknown(s) => s.as_str(),
767 }
768 }
769}
770
771impl fmt::Display for AutoModeRuleList {
772 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
773 f.write_str(self.as_str())
774 }
775}
776
777impl From<&str> for AutoModeRuleList {
778 fn from(s: &str) -> Self {
779 match s {
780 "allow" => Self::Allow,
781 "soft_deny" => Self::SoftDeny,
782 other => Self::Unknown(other.to_string()),
783 }
784 }
785}
786
787impl Serialize for AutoModeRuleList {
788 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
789 serializer.serialize_str(self.as_str())
790 }
791}
792
793impl<'de> Deserialize<'de> for AutoModeRuleList {
794 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
795 let s = String::deserialize(deserializer)?;
796 Ok(Self::from(s.as_str()))
797 }
798}
799
800#[derive(Debug, Clone, PartialEq, Eq, Hash)]
802pub enum PermissionDecisionOutcome {
803 Accept,
805 Reject,
807 Cancelled,
809 Unknown(String),
811}
812
813impl PermissionDecisionOutcome {
814 pub fn as_str(&self) -> &str {
815 match self {
816 Self::Accept => "accept",
817 Self::Reject => "reject",
818 Self::Cancelled => "cancelled",
819 Self::Unknown(s) => s.as_str(),
820 }
821 }
822}
823
824impl fmt::Display for PermissionDecisionOutcome {
825 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
826 f.write_str(self.as_str())
827 }
828}
829
830impl From<&str> for PermissionDecisionOutcome {
831 fn from(s: &str) -> Self {
832 match s {
833 "accept" => Self::Accept,
834 "reject" => Self::Reject,
835 "cancelled" => Self::Cancelled,
836 other => Self::Unknown(other.to_string()),
837 }
838 }
839}
840
841impl Serialize for PermissionDecisionOutcome {
842 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
843 serializer.serialize_str(self.as_str())
844 }
845}
846
847impl<'de> Deserialize<'de> for PermissionDecisionOutcome {
848 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
849 let s = String::deserialize(deserializer)?;
850 Ok(Self::from(s.as_str()))
851 }
852}
853
854#[derive(Debug, Clone, PartialEq, Eq, Hash)]
857pub enum PermissionDecisionReasonType {
858 Rule,
860 Mode,
862 SubcommandResults,
864 PermissionPromptTool,
866 Hook,
868 AsyncAgent,
870 SandboxOverride,
872 WorkingDir,
874 SafetyCheck,
876 Classifier,
878 Other,
880 Unknown(String),
882}
883
884impl PermissionDecisionReasonType {
885 pub fn as_str(&self) -> &str {
886 match self {
887 Self::Rule => "rule",
888 Self::Mode => "mode",
889 Self::SubcommandResults => "subcommandResults",
890 Self::PermissionPromptTool => "permissionPromptTool",
891 Self::Hook => "hook",
892 Self::AsyncAgent => "asyncAgent",
893 Self::SandboxOverride => "sandboxOverride",
894 Self::WorkingDir => "workingDir",
895 Self::SafetyCheck => "safetyCheck",
896 Self::Classifier => "classifier",
897 Self::Other => "other",
898 Self::Unknown(s) => s.as_str(),
899 }
900 }
901}
902
903impl fmt::Display for PermissionDecisionReasonType {
904 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
905 f.write_str(self.as_str())
906 }
907}
908
909impl From<&str> for PermissionDecisionReasonType {
910 fn from(s: &str) -> Self {
911 match s {
912 "rule" => Self::Rule,
913 "mode" => Self::Mode,
914 "subcommandResults" => Self::SubcommandResults,
915 "permissionPromptTool" => Self::PermissionPromptTool,
916 "hook" => Self::Hook,
917 "asyncAgent" => Self::AsyncAgent,
918 "sandboxOverride" => Self::SandboxOverride,
919 "workingDir" => Self::WorkingDir,
920 "safetyCheck" => Self::SafetyCheck,
921 "classifier" => Self::Classifier,
922 "other" => Self::Other,
923 other => Self::Unknown(other.to_string()),
924 }
925 }
926}
927
928impl Serialize for PermissionDecisionReasonType {
929 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
930 serializer.serialize_str(self.as_str())
931 }
932}
933
934impl<'de> Deserialize<'de> for PermissionDecisionReasonType {
935 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
936 let s = String::deserialize(deserializer)?;
937 Ok(Self::from(s.as_str()))
938 }
939}
940
941#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
946pub struct ToolResultRemedy {
947 pub kind: ToolResultRemedyKind,
950 #[serde(default, skip_serializing_if = "Option::is_none")]
954 pub servers: Option<Vec<String>>,
955 #[serde(default, skip_serializing_if = "Option::is_none")]
958 pub provider: Option<RemedyLoginProvider>,
959 #[serde(default, skip_serializing_if = "Option::is_none")]
963 pub path: Option<String>,
964 #[serde(default, skip_serializing_if = "Option::is_none")]
969 pub managed: Option<bool>,
970 #[serde(default, skip_serializing_if = "Option::is_none")]
972 pub feature: Option<RemedyFeature>,
973 #[serde(default, skip_serializing_if = "Option::is_none")]
975 pub cause: Option<RemedyFeatureCause>,
976 #[serde(default, skip_serializing_if = "Option::is_none")]
978 pub policy_kind: Option<RemedyPolicyKind>,
979}
980
981#[derive(Debug, Clone, PartialEq, Eq, Hash)]
983pub enum ToolResultRemedyKind {
984 McpNeedsAuth,
986 McpDisabled,
988 McpRequiredMissing,
990 AuthExpired,
992 AuthOverridden,
994 AuthMissingScope,
996 DesignNeedsAuthorization,
998 SandboxViolation,
1000 OutsideReadsBlocked,
1002 MemoryPaused,
1004 FeatureDisabled,
1006 PolicyDenied,
1009 SpawnArgLimit,
1011 StagedForReview,
1013 Unknown(String),
1015}
1016
1017impl ToolResultRemedyKind {
1018 pub fn as_str(&self) -> &str {
1019 match self {
1020 Self::McpNeedsAuth => "mcp_needs_auth",
1021 Self::McpDisabled => "mcp_disabled",
1022 Self::McpRequiredMissing => "mcp_required_missing",
1023 Self::AuthExpired => "auth_expired",
1024 Self::AuthOverridden => "auth_overridden",
1025 Self::AuthMissingScope => "auth_missing_scope",
1026 Self::DesignNeedsAuthorization => "design_needs_authorization",
1027 Self::SandboxViolation => "sandbox_violation",
1028 Self::OutsideReadsBlocked => "outside_reads_blocked",
1029 Self::MemoryPaused => "memory_paused",
1030 Self::FeatureDisabled => "feature_disabled",
1031 Self::PolicyDenied => "policy_denied",
1032 Self::SpawnArgLimit => "spawn_arg_limit",
1033 Self::StagedForReview => "staged_for_review",
1034 Self::Unknown(s) => s.as_str(),
1035 }
1036 }
1037}
1038
1039impl fmt::Display for ToolResultRemedyKind {
1040 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1041 f.write_str(self.as_str())
1042 }
1043}
1044
1045impl From<&str> for ToolResultRemedyKind {
1046 fn from(s: &str) -> Self {
1047 match s {
1048 "mcp_needs_auth" => Self::McpNeedsAuth,
1049 "mcp_disabled" => Self::McpDisabled,
1050 "mcp_required_missing" => Self::McpRequiredMissing,
1051 "auth_expired" => Self::AuthExpired,
1052 "auth_overridden" => Self::AuthOverridden,
1053 "auth_missing_scope" => Self::AuthMissingScope,
1054 "design_needs_authorization" => Self::DesignNeedsAuthorization,
1055 "sandbox_violation" => Self::SandboxViolation,
1056 "outside_reads_blocked" => Self::OutsideReadsBlocked,
1057 "memory_paused" => Self::MemoryPaused,
1058 "feature_disabled" => Self::FeatureDisabled,
1059 "policy_denied" => Self::PolicyDenied,
1060 "spawn_arg_limit" => Self::SpawnArgLimit,
1061 "staged_for_review" => Self::StagedForReview,
1062 other => Self::Unknown(other.to_string()),
1063 }
1064 }
1065}
1066
1067impl Serialize for ToolResultRemedyKind {
1068 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1069 serializer.serialize_str(self.as_str())
1070 }
1071}
1072
1073impl<'de> Deserialize<'de> for ToolResultRemedyKind {
1074 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1075 let s = String::deserialize(deserializer)?;
1076 Ok(Self::from(s.as_str()))
1077 }
1078}
1079
1080#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1082pub enum RemedyLoginProvider {
1083 ClaudeAi,
1085 ClaudeDesign,
1087 Unknown(String),
1089}
1090
1091impl RemedyLoginProvider {
1092 pub fn as_str(&self) -> &str {
1093 match self {
1094 Self::ClaudeAi => "claude_ai",
1095 Self::ClaudeDesign => "claude_design",
1096 Self::Unknown(s) => s.as_str(),
1097 }
1098 }
1099}
1100
1101impl fmt::Display for RemedyLoginProvider {
1102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1103 f.write_str(self.as_str())
1104 }
1105}
1106
1107impl From<&str> for RemedyLoginProvider {
1108 fn from(s: &str) -> Self {
1109 match s {
1110 "claude_ai" => Self::ClaudeAi,
1111 "claude_design" => Self::ClaudeDesign,
1112 other => Self::Unknown(other.to_string()),
1113 }
1114 }
1115}
1116
1117impl Serialize for RemedyLoginProvider {
1118 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1119 serializer.serialize_str(self.as_str())
1120 }
1121}
1122
1123impl<'de> Deserialize<'de> for RemedyLoginProvider {
1124 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1125 let s = String::deserialize(deserializer)?;
1126 Ok(Self::from(s.as_str()))
1127 }
1128}
1129
1130#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1133pub enum RemedyFeature {
1134 Workflows,
1135 Artifacts,
1136 Unknown(String),
1138}
1139
1140impl RemedyFeature {
1141 pub fn as_str(&self) -> &str {
1142 match self {
1143 Self::Workflows => "workflows",
1144 Self::Artifacts => "artifacts",
1145 Self::Unknown(s) => s.as_str(),
1146 }
1147 }
1148}
1149
1150impl fmt::Display for RemedyFeature {
1151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1152 f.write_str(self.as_str())
1153 }
1154}
1155
1156impl From<&str> for RemedyFeature {
1157 fn from(s: &str) -> Self {
1158 match s {
1159 "workflows" => Self::Workflows,
1160 "artifacts" => Self::Artifacts,
1161 other => Self::Unknown(other.to_string()),
1162 }
1163 }
1164}
1165
1166impl Serialize for RemedyFeature {
1167 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1168 serializer.serialize_str(self.as_str())
1169 }
1170}
1171
1172impl<'de> Deserialize<'de> for RemedyFeature {
1173 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1174 let s = String::deserialize(deserializer)?;
1175 Ok(Self::from(s.as_str()))
1176 }
1177}
1178
1179#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1182pub enum RemedyFeatureCause {
1183 ManagedSettings,
1186 OrgPolicy,
1188 Unavailable,
1190 UserSetting,
1193 UnsupportedRuntime,
1197 Unknown(String),
1199}
1200
1201impl RemedyFeatureCause {
1202 pub fn as_str(&self) -> &str {
1203 match self {
1204 Self::ManagedSettings => "managed_settings",
1205 Self::OrgPolicy => "org_policy",
1206 Self::Unavailable => "unavailable",
1207 Self::UserSetting => "user_setting",
1208 Self::UnsupportedRuntime => "unsupported_runtime",
1209 Self::Unknown(s) => s.as_str(),
1210 }
1211 }
1212}
1213
1214impl fmt::Display for RemedyFeatureCause {
1215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1216 f.write_str(self.as_str())
1217 }
1218}
1219
1220impl From<&str> for RemedyFeatureCause {
1221 fn from(s: &str) -> Self {
1222 match s {
1223 "managed_settings" => Self::ManagedSettings,
1224 "org_policy" => Self::OrgPolicy,
1225 "unavailable" => Self::Unavailable,
1226 "user_setting" => Self::UserSetting,
1227 "unsupported_runtime" => Self::UnsupportedRuntime,
1228 other => Self::Unknown(other.to_string()),
1229 }
1230 }
1231}
1232
1233impl Serialize for RemedyFeatureCause {
1234 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1235 serializer.serialize_str(self.as_str())
1236 }
1237}
1238
1239impl<'de> Deserialize<'de> for RemedyFeatureCause {
1240 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1241 let s = String::deserialize(deserializer)?;
1242 Ok(Self::from(s.as_str()))
1243 }
1244}
1245
1246#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1249pub enum RemedyPolicyKind {
1250 OrgDenied,
1252 Latched,
1255 Unknown(String),
1257}
1258
1259impl RemedyPolicyKind {
1260 pub fn as_str(&self) -> &str {
1261 match self {
1262 Self::OrgDenied => "org_denied",
1263 Self::Latched => "latched",
1264 Self::Unknown(s) => s.as_str(),
1265 }
1266 }
1267}
1268
1269impl fmt::Display for RemedyPolicyKind {
1270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1271 f.write_str(self.as_str())
1272 }
1273}
1274
1275impl From<&str> for RemedyPolicyKind {
1276 fn from(s: &str) -> Self {
1277 match s {
1278 "org_denied" => Self::OrgDenied,
1279 "latched" => Self::Latched,
1280 other => Self::Unknown(other.to_string()),
1281 }
1282 }
1283}
1284
1285impl Serialize for RemedyPolicyKind {
1286 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1287 serializer.serialize_str(self.as_str())
1288 }
1289}
1290
1291impl<'de> Deserialize<'de> for RemedyPolicyKind {
1292 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1293 let s = String::deserialize(deserializer)?;
1294 Ok(Self::from(s.as_str()))
1295 }
1296}
1297
1298#[derive(Debug, Clone, Serialize, Deserialize)]
1300pub struct UserMessage {
1301 pub message: MessageContent,
1302 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1303 #[serde(
1304 serialize_with = "serialize_optional_uuid",
1305 deserialize_with = "deserialize_optional_uuid"
1306 )]
1307 pub session_id: Option<Uuid>,
1308 #[serde(skip_serializing_if = "Option::is_none")]
1310 pub parent_tool_use_id: Option<String>,
1311 #[serde(skip_serializing_if = "Option::is_none")]
1313 pub uuid: Option<String>,
1314 #[serde(skip_serializing_if = "Option::is_none")]
1316 pub timestamp: Option<String>,
1317 #[serde(skip_serializing_if = "Option::is_none")]
1324 pub tool_use_result: Option<serde_json::Value>,
1325 #[serde(default, skip_serializing_if = "Option::is_none")]
1330 pub agent_id: Option<String>,
1331 #[serde(skip_serializing_if = "Option::is_none")]
1334 pub subagent_type: Option<String>,
1335 #[serde(skip_serializing_if = "Option::is_none")]
1337 pub task_description: Option<String>,
1338 #[serde(skip_serializing_if = "Option::is_none")]
1339 pub origin: Option<MessageOrigin>,
1340 #[serde(skip_serializing_if = "Option::is_none")]
1341 pub priority: Option<String>,
1342 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1343 pub is_synthetic: Option<bool>,
1344 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1345 pub should_query: Option<bool>,
1346 #[serde(default, skip_serializing_if = "Option::is_none")]
1347 pub is_meta: Option<bool>,
1348 #[serde(default, skip_serializing_if = "Option::is_none")]
1349 pub is_visible_in_transcript_only: Option<bool>,
1350 #[serde(default, skip_serializing_if = "Option::is_none")]
1351 pub is_virtual: Option<bool>,
1352 #[serde(default, skip_serializing_if = "Option::is_none")]
1353 pub is_compact_summary: Option<bool>,
1354 #[serde(skip_serializing_if = "Option::is_none")]
1355 pub summarize_metadata: Option<SummarizeMetadata>,
1356 #[serde(skip_serializing_if = "Option::is_none")]
1357 pub mcp_meta: Option<McpMeta>,
1358 #[serde(skip_serializing_if = "Option::is_none")]
1361 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1362 #[serde(skip_serializing_if = "Option::is_none")]
1363 pub source_tool_use_id: Option<String>,
1364 #[serde(skip_serializing_if = "Option::is_none")]
1365 pub source_tool_assistant_uuid: Option<String>,
1366 #[serde(skip_serializing_if = "Option::is_none")]
1367 pub image_paste_ids: Option<Vec<u64>>,
1368 #[serde(skip_serializing_if = "Option::is_none")]
1369 pub client_platform: Option<String>,
1370 #[serde(skip_serializing_if = "Option::is_none")]
1371 pub inbound_origin: Option<String>,
1372 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1373 pub is_replay: Option<bool>,
1374 #[serde(skip_serializing_if = "Option::is_none")]
1375 pub file_attachments: Option<Vec<Value>>,
1376 #[serde(default, skip_serializing_if = "Option::is_none")]
1378 pub seeded_summon: Option<bool>,
1379 #[serde(default, skip_serializing_if = "Option::is_none")]
1382 pub client_composed: Option<bool>,
1383 #[serde(default, skip_serializing_if = "Option::is_none")]
1392 pub text_not_typed: Option<bool>,
1393 #[serde(default, skip_serializing_if = "Option::is_none")]
1400 pub initiator: Option<String>,
1401 #[serde(default, skip_serializing_if = "Option::is_none")]
1407 pub pasted_content: Option<Vec<Value>>,
1408 #[serde(default, skip_serializing_if = "Option::is_none")]
1412 pub historical: Option<bool>,
1413 #[serde(default, skip_serializing_if = "Option::is_none")]
1419 pub server_received_wall_ms: Option<f64>,
1420 #[serde(default, skip_serializing_if = "Option::is_none")]
1424 pub refused_message_id: Option<String>,
1425 #[serde(default, skip_serializing_if = "Option::is_none")]
1432 pub requeue_if_interrupted_within_ms: Option<u64>,
1433 #[serde(default, skip_serializing_if = "Option::is_none")]
1442 pub ultrathink_typed: Option<bool>,
1443 #[serde(default, skip_serializing_if = "Option::is_none")]
1447 pub tool_states: Option<Vec<Value>>,
1448 #[serde(default, skip_serializing_if = "Option::is_none")]
1454 pub plugin_prompt: Option<PluginPrompt>,
1455}
1456
1457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1460pub struct PluginPrompt {
1461 pub name: String,
1464 pub text: String,
1470}
1471
1472impl UserMessage {
1473 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1481 &self,
1482 ) -> Option<Result<T, serde_json::Error>> {
1483 self.tool_use_result
1484 .as_ref()
1485 .map(|v| serde_json::from_value(v.clone()))
1486 }
1487
1488 pub fn subagent_result(&self) -> Option<SubagentResult> {
1499 self.tool_use_result
1500 .as_ref()
1501 .and_then(|v| serde_json::from_value(v.clone()).ok())
1502 }
1503}
1504
1505#[derive(Debug, Clone, Serialize, Deserialize)]
1516pub struct SubagentResult {
1517 #[serde(skip_serializing_if = "Option::is_none")]
1519 pub status: Option<String>,
1520 #[serde(skip_serializing_if = "Option::is_none")]
1522 pub prompt: Option<String>,
1523 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1525 pub agent_id: Option<String>,
1526 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1528 pub agent_type: Option<String>,
1529 #[serde(
1531 default,
1532 deserialize_with = "deserialize_content_blocks",
1533 skip_serializing_if = "Vec::is_empty"
1534 )]
1535 pub content: Vec<ContentBlock>,
1536 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1538 pub resolved_model: Option<String>,
1539 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1541 pub total_duration_ms: Option<u64>,
1542 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1544 pub total_tokens: Option<u64>,
1545 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1547 pub total_tool_use_count: Option<u64>,
1548 #[serde(skip_serializing_if = "Option::is_none")]
1550 pub usage: Option<super::result::UsageInfo>,
1551 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1553 pub tool_stats: Option<SubagentToolStats>,
1554}
1555
1556#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1561#[serde(rename_all = "camelCase")]
1562pub struct SubagentToolStats {
1563 #[serde(default)]
1564 pub read_count: u64,
1565 #[serde(default)]
1566 pub search_count: u64,
1567 #[serde(default)]
1568 pub bash_count: u64,
1569 #[serde(default)]
1570 pub edit_file_count: u64,
1571 #[serde(default)]
1572 pub lines_added: u64,
1573 #[serde(default)]
1574 pub lines_removed: u64,
1575 #[serde(default)]
1576 pub other_tool_count: u64,
1577 #[serde(flatten)]
1578 pub extra: serde_json::Map<String, Value>,
1579}
1580
1581#[derive(Debug, Clone, Default, PartialEq, Eq)]
1608pub struct SubagentUsageRollup {
1609 pub subagent_tokens: u64,
1612 pub agent_count: u32,
1614 pub tool_uses: u64,
1616 pub duration_ms: u64,
1618 seen_agent_ids: std::collections::BTreeSet<String>,
1619}
1620
1621impl SubagentUsageRollup {
1622 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1628 match output {
1629 ClaudeOutput::User(user) => self.observe_user(user),
1630 _ => false,
1631 }
1632 }
1633
1634 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1641 let Some(result) = user.subagent_result() else {
1642 return false;
1643 };
1644 if result.total_tokens.is_none() && result.agent_id.is_none() {
1645 return false;
1646 }
1647 if let Some(agent_id) = &result.agent_id {
1648 if !self.seen_agent_ids.insert(agent_id.clone()) {
1649 return false;
1650 }
1651 }
1652 self.agent_count += 1;
1653 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1654 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1655 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1656 true
1657 }
1658}
1659
1660#[derive(Debug, Clone, Serialize, Deserialize)]
1662pub struct MessageContent {
1663 pub role: MessageRole,
1664 #[serde(deserialize_with = "deserialize_content_blocks")]
1665 pub content: Vec<ContentBlock>,
1666}
1667
1668#[derive(Debug, Clone, Serialize, Deserialize)]
1670pub struct SystemMessage {
1671 pub subtype: SystemSubtype,
1672 #[serde(flatten)]
1673 pub data: Value, }
1675
1676impl SystemMessage {
1677 pub fn is_init(&self) -> bool {
1679 self.subtype == SystemSubtype::Init
1680 }
1681
1682 pub fn is_status(&self) -> bool {
1684 self.subtype == SystemSubtype::Status
1685 }
1686
1687 pub fn is_compact_boundary(&self) -> bool {
1689 self.subtype == SystemSubtype::CompactBoundary
1690 }
1691
1692 pub fn as_init(&self) -> Option<InitMessage> {
1694 if self.subtype != SystemSubtype::Init {
1695 return None;
1696 }
1697 serde_json::from_value(self.data.clone()).ok()
1698 }
1699
1700 pub fn as_status(&self) -> Option<StatusMessage> {
1702 if self.subtype != SystemSubtype::Status {
1703 return None;
1704 }
1705 serde_json::from_value(self.data.clone()).ok()
1706 }
1707
1708 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1710 if self.subtype != SystemSubtype::CompactBoundary {
1711 return None;
1712 }
1713 serde_json::from_value(self.data.clone()).ok()
1714 }
1715
1716 pub fn is_task_started(&self) -> bool {
1718 self.subtype == SystemSubtype::TaskStarted
1719 }
1720
1721 pub fn is_task_progress(&self) -> bool {
1723 self.subtype == SystemSubtype::TaskProgress
1724 }
1725
1726 pub fn is_task_notification(&self) -> bool {
1728 self.subtype == SystemSubtype::TaskNotification
1729 }
1730
1731 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1733 if self.subtype != SystemSubtype::TaskStarted {
1734 return None;
1735 }
1736 serde_json::from_value(self.data.clone()).ok()
1737 }
1738
1739 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1741 if self.subtype != SystemSubtype::TaskProgress {
1742 return None;
1743 }
1744 serde_json::from_value(self.data.clone()).ok()
1745 }
1746
1747 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1749 if self.subtype != SystemSubtype::TaskNotification {
1750 return None;
1751 }
1752 serde_json::from_value(self.data.clone()).ok()
1753 }
1754
1755 pub fn is_task_updated(&self) -> bool {
1757 self.subtype == SystemSubtype::TaskUpdated
1758 }
1759
1760 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1762 if self.subtype != SystemSubtype::TaskUpdated {
1763 return None;
1764 }
1765 serde_json::from_value(self.data.clone()).ok()
1766 }
1767
1768 pub fn is_thinking_tokens(&self) -> bool {
1770 self.subtype == SystemSubtype::ThinkingTokens
1771 }
1772
1773 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1775 if self.subtype != SystemSubtype::ThinkingTokens {
1776 return None;
1777 }
1778 serde_json::from_value(self.data.clone()).ok()
1779 }
1780
1781 pub fn is_code_change_published(&self) -> bool {
1783 self.subtype == SystemSubtype::CodeChangePublished
1784 }
1785
1786 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1788 if self.subtype != SystemSubtype::CodeChangePublished {
1789 return None;
1790 }
1791 serde_json::from_value(self.data.clone()).ok()
1792 }
1793
1794 pub fn is_vcs_state_changed(&self) -> bool {
1796 self.subtype == SystemSubtype::VcsStateChanged
1797 }
1798
1799 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1801 if self.subtype != SystemSubtype::VcsStateChanged {
1802 return None;
1803 }
1804 serde_json::from_value(self.data.clone()).ok()
1805 }
1806
1807 pub fn is_feedback_draft_queued(&self) -> bool {
1809 self.subtype == SystemSubtype::FeedbackDraftQueued
1810 }
1811
1812 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1814 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1815 return None;
1816 }
1817 serde_json::from_value(self.data.clone()).ok()
1818 }
1819
1820 pub fn is_cloud_session_delta(&self) -> bool {
1822 self.subtype == SystemSubtype::CloudSessionDelta
1823 }
1824
1825 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1827 if self.subtype != SystemSubtype::CloudSessionDelta {
1828 return None;
1829 }
1830 serde_json::from_value(self.data.clone()).ok()
1831 }
1832
1833 pub fn is_dev_intent(&self) -> bool {
1835 self.subtype == SystemSubtype::DevIntent
1836 }
1837
1838 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1840 if self.subtype != SystemSubtype::DevIntent {
1841 return None;
1842 }
1843 serde_json::from_value(self.data.clone()).ok()
1844 }
1845
1846 pub fn is_turn_handoff_available(&self) -> bool {
1848 self.subtype == SystemSubtype::TurnHandoffAvailable
1849 }
1850
1851 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1853 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1854 return None;
1855 }
1856 serde_json::from_value(self.data.clone()).ok()
1857 }
1858
1859 pub fn is_turn_preempted(&self) -> bool {
1861 self.subtype == SystemSubtype::TurnPreempted
1862 }
1863
1864 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1866 if self.subtype != SystemSubtype::TurnPreempted {
1867 return None;
1868 }
1869 serde_json::from_value(self.data.clone()).ok()
1870 }
1871
1872 pub fn is_peer_message_hold(&self) -> bool {
1874 self.subtype == SystemSubtype::PeerMessageHold
1875 }
1876
1877 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1879 if self.subtype != SystemSubtype::PeerMessageHold {
1880 return None;
1881 }
1882 serde_json::from_value(self.data.clone()).ok()
1883 }
1884
1885 pub fn is_per_turn_effort_changed(&self) -> bool {
1887 self.subtype == SystemSubtype::PerTurnEffortChanged
1888 }
1889
1890 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1892 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1893 return None;
1894 }
1895 serde_json::from_value(self.data.clone()).ok()
1896 }
1897
1898 pub fn is_session_title_changed(&self) -> bool {
1900 self.subtype == SystemSubtype::SessionTitleChanged
1901 }
1902
1903 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1905 if self.subtype != SystemSubtype::SessionTitleChanged {
1906 return None;
1907 }
1908 serde_json::from_value(self.data.clone()).ok()
1909 }
1910
1911 pub fn is_session_writes_lost(&self) -> bool {
1913 self.subtype == SystemSubtype::SessionWritesLost
1914 }
1915
1916 pub fn as_session_writes_lost(&self) -> Option<SessionWritesLostMessage> {
1918 if self.subtype != SystemSubtype::SessionWritesLost {
1919 return None;
1920 }
1921 serde_json::from_value(self.data.clone()).ok()
1922 }
1923
1924 pub fn is_instruction_size_warning(&self) -> bool {
1926 self.subtype == SystemSubtype::InstructionSizeWarning
1927 }
1928
1929 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1931 if self.subtype != SystemSubtype::InstructionSizeWarning {
1932 return None;
1933 }
1934 serde_json::from_value(self.data.clone()).ok()
1935 }
1936
1937 pub fn is_permission_check_status(&self) -> bool {
1939 self.subtype == SystemSubtype::PermissionCheckStatus
1940 }
1941
1942 pub fn as_permission_check_status(&self) -> Option<PermissionCheckStatusMessage> {
1944 if self.subtype != SystemSubtype::PermissionCheckStatus {
1945 return None;
1946 }
1947 serde_json::from_value(self.data.clone()).ok()
1948 }
1949
1950 pub fn is_inherited_asks_settled(&self) -> bool {
1952 self.subtype == SystemSubtype::InheritedAsksSettled
1953 }
1954
1955 pub fn as_inherited_asks_settled(&self) -> Option<InheritedAsksSettledMessage> {
1957 if self.subtype != SystemSubtype::InheritedAsksSettled {
1958 return None;
1959 }
1960 serde_json::from_value(self.data.clone()).ok()
1961 }
1962
1963 pub fn is_file_attachments_missing(&self) -> bool {
1965 self.subtype == SystemSubtype::FileAttachmentsMissing
1966 }
1967
1968 pub fn as_file_attachments_missing(&self) -> Option<FileAttachmentsMissingMessage> {
1970 if self.subtype != SystemSubtype::FileAttachmentsMissing {
1971 return None;
1972 }
1973 serde_json::from_value(self.data.clone()).ok()
1974 }
1975
1976 pub fn is_ui_focus(&self) -> bool {
1978 self.subtype == SystemSubtype::UiFocus
1979 }
1980
1981 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1983 if self.subtype != SystemSubtype::UiFocus {
1984 return None;
1985 }
1986 serde_json::from_value(self.data.clone()).ok()
1987 }
1988
1989 pub fn is_ui_invalidate(&self) -> bool {
1991 self.subtype == SystemSubtype::UiInvalidate
1992 }
1993
1994 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1996 if self.subtype != SystemSubtype::UiInvalidate {
1997 return None;
1998 }
1999 serde_json::from_value(self.data.clone()).ok()
2000 }
2001
2002 pub fn is_ui_log(&self) -> bool {
2004 self.subtype == SystemSubtype::UiLog
2005 }
2006
2007 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
2009 if self.subtype != SystemSubtype::UiLog {
2010 return None;
2011 }
2012 serde_json::from_value(self.data.clone()).ok()
2013 }
2014
2015 pub fn is_ui_notify(&self) -> bool {
2017 self.subtype == SystemSubtype::UiNotify
2018 }
2019
2020 pub fn as_ui_notify(&self) -> Option<UiNotifyMessage> {
2022 if self.subtype != SystemSubtype::UiNotify {
2023 return None;
2024 }
2025 serde_json::from_value(self.data.clone()).ok()
2026 }
2027
2028 pub fn is_ui_notify_cancel(&self) -> bool {
2030 self.subtype == SystemSubtype::UiNotifyCancel
2031 }
2032
2033 pub fn as_ui_notify_cancel(&self) -> Option<UiNotifyCancelMessage> {
2035 if self.subtype != SystemSubtype::UiNotifyCancel {
2036 return None;
2037 }
2038 serde_json::from_value(self.data.clone()).ok()
2039 }
2040
2041 pub fn is_ui_panes(&self) -> bool {
2043 self.subtype == SystemSubtype::UiPanes
2044 }
2045
2046 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
2048 if self.subtype != SystemSubtype::UiPanes {
2049 return None;
2050 }
2051 serde_json::from_value(self.data.clone()).ok()
2052 }
2053
2054 pub fn is_ui_placed(&self) -> bool {
2056 self.subtype == SystemSubtype::UiPlaced
2057 }
2058
2059 pub fn as_ui_placed(&self) -> Option<UiPlacedMessage> {
2061 if self.subtype != SystemSubtype::UiPlaced {
2062 return None;
2063 }
2064 serde_json::from_value(self.data.clone()).ok()
2065 }
2066
2067 pub fn is_ui_scroll(&self) -> bool {
2069 self.subtype == SystemSubtype::UiScroll
2070 }
2071
2072 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
2074 if self.subtype != SystemSubtype::UiScroll {
2075 return None;
2076 }
2077 serde_json::from_value(self.data.clone()).ok()
2078 }
2079
2080 pub fn is_ui_status(&self) -> bool {
2082 self.subtype == SystemSubtype::UiStatus
2083 }
2084
2085 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
2087 if self.subtype != SystemSubtype::UiStatus {
2088 return None;
2089 }
2090 serde_json::from_value(self.data.clone()).ok()
2091 }
2092
2093 pub fn is_ui_toast(&self) -> bool {
2095 self.subtype == SystemSubtype::UiToast
2096 }
2097
2098 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
2100 if self.subtype != SystemSubtype::UiToast {
2101 return None;
2102 }
2103 serde_json::from_value(self.data.clone()).ok()
2104 }
2105
2106 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
2108 macro_rules! parse {
2109 ($variant:ident, $ty:ty) => {
2110 serde_json::from_value::<$ty>(self.data.clone())
2111 .ok()
2112 .map(KnownSystemEvent::$variant)
2113 };
2114 }
2115
2116 match self.subtype {
2117 SystemSubtype::Init => parse!(Init, InitMessage),
2118 SystemSubtype::Status => parse!(Status, StatusMessage),
2119 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
2120 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
2121 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
2122 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
2123 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
2124 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
2125 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
2126 SystemSubtype::ControlRequestProgress => {
2127 parse!(ControlRequestProgress, ControlRequestProgressMessage)
2128 }
2129 SystemSubtype::ModelRefusalFallback => {
2130 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
2131 }
2132 SystemSubtype::ModelRefusalNoFallback => {
2133 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
2134 }
2135 SystemSubtype::LocalCommandOutput => {
2136 parse!(LocalCommandOutput, LocalCommandOutputMessage)
2137 }
2138 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
2139 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
2140 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
2141 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
2142 SystemSubtype::BackgroundTasksChanged => {
2143 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
2144 }
2145 SystemSubtype::SessionStateChanged => {
2146 parse!(SessionStateChanged, SessionStateChangedMessage)
2147 }
2148 SystemSubtype::WorkerShuttingDown => {
2149 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
2150 }
2151 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
2152 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
2153 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
2154 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
2155 SystemSubtype::ElicitationComplete => {
2156 parse!(ElicitationComplete, ElicitationCompleteMessage)
2157 }
2158 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
2159 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
2160 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
2161 SystemSubtype::CodeChangePublished => {
2162 parse!(CodeChangePublished, CodeChangePublishedMessage)
2163 }
2164 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
2165 SystemSubtype::FeedbackDraftQueued => {
2166 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
2167 }
2168 SystemSubtype::CloudSessionDelta => {
2169 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
2170 }
2171 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
2172 SystemSubtype::TurnHandoffAvailable => {
2173 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
2174 }
2175 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
2176 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
2177 SystemSubtype::PerTurnEffortChanged => {
2178 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
2179 }
2180 SystemSubtype::SessionTitleChanged => {
2181 parse!(SessionTitleChanged, SessionTitleChangedMessage)
2182 }
2183 SystemSubtype::SessionWritesLost => {
2184 parse!(SessionWritesLost, SessionWritesLostMessage)
2185 }
2186 SystemSubtype::InstructionSizeWarning => {
2187 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
2188 }
2189 SystemSubtype::FileAttachmentsMissing => {
2190 parse!(FileAttachmentsMissing, FileAttachmentsMissingMessage)
2191 }
2192 SystemSubtype::PermissionCheckStatus => {
2193 parse!(PermissionCheckStatus, PermissionCheckStatusMessage)
2194 }
2195 SystemSubtype::InheritedAsksSettled => {
2196 parse!(InheritedAsksSettled, InheritedAsksSettledMessage)
2197 }
2198 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
2199 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
2200 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
2201 SystemSubtype::UiNotify => parse!(UiNotify, UiNotifyMessage),
2202 SystemSubtype::UiNotifyCancel => parse!(UiNotifyCancel, UiNotifyCancelMessage),
2203 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
2204 SystemSubtype::UiPlaced => parse!(UiPlaced, UiPlacedMessage),
2205 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
2206 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
2207 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
2208 SystemSubtype::Unknown(_) => None,
2209 }
2210 }
2211
2212 pub fn typed_value(&self) -> Option<Value> {
2221 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
2222 parsed.and_then(|v| serde_json::to_value(v).ok())
2223 }
2224 match self.subtype {
2225 SystemSubtype::Init => reserialize(self.as_init()),
2226 SystemSubtype::Status => reserialize(self.as_status()),
2227 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
2228 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
2229 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
2230 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
2231 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
2232 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
2233 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
2234 SystemSubtype::ControlRequestProgress => {
2235 reserialize(parse_system::<ControlRequestProgressMessage>(self))
2236 }
2237 SystemSubtype::ModelRefusalFallback => {
2238 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
2239 }
2240 SystemSubtype::ModelRefusalNoFallback => {
2241 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
2242 }
2243 SystemSubtype::LocalCommandOutput => {
2244 reserialize(parse_system::<LocalCommandOutputMessage>(self))
2245 }
2246 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
2247 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
2248 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
2249 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
2250 SystemSubtype::BackgroundTasksChanged => {
2251 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
2252 }
2253 SystemSubtype::SessionStateChanged => {
2254 reserialize(parse_system::<SessionStateChangedMessage>(self))
2255 }
2256 SystemSubtype::WorkerShuttingDown => {
2257 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
2258 }
2259 SystemSubtype::CommandsChanged => {
2260 reserialize(parse_system::<CommandsChangedMessage>(self))
2261 }
2262 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
2263 SystemSubtype::FilesPersisted => {
2264 reserialize(parse_system::<FilesPersistedMessage>(self))
2265 }
2266 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
2267 SystemSubtype::ElicitationComplete => {
2268 reserialize(parse_system::<ElicitationCompleteMessage>(self))
2269 }
2270 SystemSubtype::PermissionDenied => {
2271 reserialize(parse_system::<PermissionDeniedMessage>(self))
2272 }
2273 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
2274 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
2275 SystemSubtype::CodeChangePublished => {
2276 reserialize(parse_system::<CodeChangePublishedMessage>(self))
2277 }
2278 SystemSubtype::VcsStateChanged => {
2279 reserialize(parse_system::<VcsStateChangedMessage>(self))
2280 }
2281 SystemSubtype::FeedbackDraftQueued => {
2282 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
2283 }
2284 SystemSubtype::CloudSessionDelta => {
2285 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
2286 }
2287 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
2288 SystemSubtype::TurnHandoffAvailable => {
2289 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
2290 }
2291 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
2292 SystemSubtype::PeerMessageHold => {
2293 reserialize(parse_system::<PeerMessageHoldMessage>(self))
2294 }
2295 SystemSubtype::PerTurnEffortChanged => {
2296 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
2297 }
2298 SystemSubtype::SessionTitleChanged => {
2299 reserialize(parse_system::<SessionTitleChangedMessage>(self))
2300 }
2301 SystemSubtype::SessionWritesLost => {
2302 reserialize(parse_system::<SessionWritesLostMessage>(self))
2303 }
2304 SystemSubtype::InstructionSizeWarning => {
2305 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
2306 }
2307 SystemSubtype::FileAttachmentsMissing => {
2308 reserialize(parse_system::<FileAttachmentsMissingMessage>(self))
2309 }
2310 SystemSubtype::PermissionCheckStatus => {
2311 reserialize(parse_system::<PermissionCheckStatusMessage>(self))
2312 }
2313 SystemSubtype::InheritedAsksSettled => {
2314 reserialize(parse_system::<InheritedAsksSettledMessage>(self))
2315 }
2316 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
2317 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
2318 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
2319 SystemSubtype::UiNotify => reserialize(parse_system::<UiNotifyMessage>(self)),
2320 SystemSubtype::UiNotifyCancel => {
2321 reserialize(parse_system::<UiNotifyCancelMessage>(self))
2322 }
2323 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
2324 SystemSubtype::UiPlaced => reserialize(parse_system::<UiPlacedMessage>(self)),
2325 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
2326 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
2327 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
2328 SystemSubtype::Unknown(_) => None,
2329 }
2330 }
2331}
2332
2333fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
2334 serde_json::from_value(message.data.clone()).ok()
2335}
2336
2337#[allow(clippy::large_enum_variant)]
2343#[derive(Debug, Clone, Serialize, Deserialize)]
2344pub enum KnownSystemEvent {
2345 Init(InitMessage),
2346 Status(StatusMessage),
2347 CompactBoundary(CompactBoundaryMessage),
2348 ThinkingTokens(ThinkingTokensMessage),
2349 TaskStarted(TaskStartedMessage),
2350 TaskProgress(TaskProgressMessage),
2351 TaskUpdated(TaskUpdatedMessage),
2352 TaskNotification(TaskNotificationMessage),
2353 ApiRetry(ApiRetryMessage),
2354 ControlRequestProgress(ControlRequestProgressMessage),
2355 ModelRefusalFallback(ModelRefusalFallbackMessage),
2356 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
2357 LocalCommandOutput(LocalCommandOutputMessage),
2358 HookStarted(HookStartedMessage),
2359 HookProgress(HookProgressMessage),
2360 HookResponse(HookResponseMessage),
2361 PluginInstall(PluginInstallMessage),
2362 BackgroundTasksChanged(BackgroundTasksChangedMessage),
2363 SessionStateChanged(SessionStateChangedMessage),
2364 WorkerShuttingDown(WorkerShuttingDownMessage),
2365 CommandsChanged(CommandsChangedMessage),
2366 Notification(NotificationMessage),
2367 FilesPersisted(FilesPersistedMessage),
2368 MemoryRecall(MemoryRecallMessage),
2369 ElicitationComplete(ElicitationCompleteMessage),
2370 PermissionDenied(PermissionDeniedMessage),
2371 MirrorError(MirrorErrorMessage),
2372 Informational(InformationalMessage),
2373 CodeChangePublished(CodeChangePublishedMessage),
2374 VcsStateChanged(VcsStateChangedMessage),
2375 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
2376 CloudSessionDelta(CloudSessionDeltaMessage),
2377 DevIntent(DevIntentMessage),
2378 TurnHandoffAvailable(TurnHandoffAvailableMessage),
2379 TurnPreempted(TurnPreemptedMessage),
2380 PeerMessageHold(PeerMessageHoldMessage),
2381 PerTurnEffortChanged(PerTurnEffortChangedMessage),
2382 SessionTitleChanged(SessionTitleChangedMessage),
2383 SessionWritesLost(SessionWritesLostMessage),
2384 InstructionSizeWarning(InstructionSizeWarningMessage),
2385 FileAttachmentsMissing(FileAttachmentsMissingMessage),
2386 PermissionCheckStatus(PermissionCheckStatusMessage),
2387 InheritedAsksSettled(InheritedAsksSettledMessage),
2388 UiFocus(UiFocusMessage),
2389 UiInvalidate(UiInvalidateMessage),
2390 UiLog(UiLogMessage),
2391 UiNotify(UiNotifyMessage),
2392 UiNotifyCancel(UiNotifyCancelMessage),
2393 UiPanes(UiPanesMessage),
2394 UiPlaced(UiPlacedMessage),
2395 UiScroll(UiScrollMessage),
2396 UiStatus(UiStatusMessage),
2397 UiToast(UiToastMessage),
2398}
2399
2400#[derive(Debug, Clone, Serialize, Deserialize)]
2401pub struct ApiRetryMessage {
2402 pub attempt: u64,
2403 pub max_retries: u64,
2404 pub retry_delay_ms: u64,
2405 pub error_status: Option<u16>,
2406 pub error: String,
2407 #[serde(default, skip_serializing_if = "Option::is_none")]
2412 pub no_response: Option<ApiRetryNoResponse>,
2413 #[serde(default, skip_serializing_if = "Option::is_none")]
2414 pub uuid: Option<String>,
2415 #[serde(default, skip_serializing_if = "Option::is_none")]
2416 pub session_id: Option<String>,
2417}
2418
2419#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2422pub struct ApiRetryNoResponse {
2423 pub waited_ms: u64,
2425 pub retry_wait_ms: u64,
2427}
2428
2429#[derive(Debug, Clone, Serialize, Deserialize)]
2430pub struct ControlRequestProgressMessage {
2431 pub request_id: String,
2432 pub status: String,
2433 #[serde(default, skip_serializing_if = "Option::is_none")]
2434 pub attempt: Option<u64>,
2435 #[serde(default, skip_serializing_if = "Option::is_none")]
2436 pub max_retries: Option<u64>,
2437 #[serde(default, skip_serializing_if = "Option::is_none")]
2438 pub retry_delay_ms: Option<u64>,
2439 #[serde(default, skip_serializing_if = "Option::is_none")]
2440 pub error_status: Option<u16>,
2441 #[serde(default, skip_serializing_if = "Option::is_none")]
2442 pub error: Option<String>,
2443 #[serde(default, skip_serializing_if = "Option::is_none")]
2444 pub uuid: Option<String>,
2445 #[serde(default, skip_serializing_if = "Option::is_none")]
2446 pub session_id: Option<String>,
2447}
2448
2449#[derive(Debug, Clone, Serialize, Deserialize)]
2450pub struct ModelRefusalFallbackMessage {
2451 pub trigger: String,
2452 pub direction: String,
2453 #[serde(default, skip_serializing_if = "Option::is_none")]
2459 pub scope: Option<RefusalFallbackScope>,
2460 pub original_model: String,
2461 pub fallback_model: String,
2462 pub request_id: Option<String>,
2463 #[serde(default, skip_serializing_if = "Option::is_none")]
2464 pub api_refusal_category: Option<String>,
2465 #[serde(default, skip_serializing_if = "Option::is_none")]
2470 pub saw_cyber_refusal: Option<bool>,
2471 #[serde(default, skip_serializing_if = "Option::is_none")]
2472 pub api_refusal_explanation: Option<String>,
2473 #[serde(default, skip_serializing_if = "Option::is_none")]
2481 pub api_refusal_cyber_verification: Option<CyberVerificationStatus>,
2482 #[serde(default, skip_serializing_if = "Option::is_none")]
2483 pub retracted_message_uuids: Option<Vec<String>>,
2484 #[serde(default, skip_serializing_if = "Option::is_none")]
2485 pub refused_user_message_uuid: Option<String>,
2486 pub content: Value,
2487 #[serde(default, skip_serializing_if = "Option::is_none")]
2488 pub uuid: Option<String>,
2489 #[serde(default, skip_serializing_if = "Option::is_none")]
2490 pub session_id: Option<String>,
2491}
2492
2493#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2497pub struct CyberVerificationStatus {
2498 #[serde(rename = "type")]
2499 pub verification_type: String,
2500 #[serde(default, skip_serializing_if = "Option::is_none")]
2501 pub tier: Option<String>,
2502 #[serde(default, skip_serializing_if = "Option::is_none")]
2503 pub reason: Option<String>,
2504}
2505
2506#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2510pub enum RefusalFallbackScope {
2511 Session,
2513 Local,
2516 Unknown(String),
2518}
2519
2520impl RefusalFallbackScope {
2521 pub fn as_str(&self) -> &str {
2522 match self {
2523 Self::Session => "session",
2524 Self::Local => "local",
2525 Self::Unknown(s) => s.as_str(),
2526 }
2527 }
2528}
2529
2530impl fmt::Display for RefusalFallbackScope {
2531 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2532 f.write_str(self.as_str())
2533 }
2534}
2535
2536impl From<&str> for RefusalFallbackScope {
2537 fn from(s: &str) -> Self {
2538 match s {
2539 "session" => Self::Session,
2540 "local" => Self::Local,
2541 other => Self::Unknown(other.to_string()),
2542 }
2543 }
2544}
2545
2546impl Serialize for RefusalFallbackScope {
2547 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2548 serializer.serialize_str(self.as_str())
2549 }
2550}
2551
2552impl<'de> Deserialize<'de> for RefusalFallbackScope {
2553 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2554 let s = String::deserialize(deserializer)?;
2555 Ok(Self::from(s.as_str()))
2556 }
2557}
2558
2559#[derive(Debug, Clone, Serialize, Deserialize)]
2560pub struct ModelRefusalNoFallbackMessage {
2561 pub original_model: String,
2562 pub request_id: Option<String>,
2563 #[serde(default, skip_serializing_if = "Option::is_none")]
2564 pub api_refusal_category: Option<String>,
2565 #[serde(default, skip_serializing_if = "Option::is_none")]
2566 pub api_refusal_explanation: Option<String>,
2567 pub content: Value,
2568 #[serde(default, skip_serializing_if = "Option::is_none")]
2569 pub uuid: Option<String>,
2570 #[serde(default, skip_serializing_if = "Option::is_none")]
2571 pub session_id: Option<String>,
2572}
2573
2574#[derive(Debug, Clone, Serialize, Deserialize)]
2575pub struct LocalCommandOutputMessage {
2576 pub content: String,
2577 #[serde(default, skip_serializing_if = "Option::is_none")]
2578 pub uuid: Option<String>,
2579 #[serde(default, skip_serializing_if = "Option::is_none")]
2580 pub session_id: Option<String>,
2581}
2582
2583#[derive(Debug, Clone, Serialize, Deserialize)]
2584pub struct HookStartedMessage {
2585 pub hook_id: String,
2586 pub hook_name: String,
2587 pub hook_event: String,
2588 #[serde(default, skip_serializing_if = "Option::is_none")]
2589 pub uuid: Option<String>,
2590 #[serde(default, skip_serializing_if = "Option::is_none")]
2591 pub session_id: Option<String>,
2592}
2593
2594#[derive(Debug, Clone, Serialize, Deserialize)]
2595pub struct HookProgressMessage {
2596 pub hook_id: String,
2597 pub hook_name: String,
2598 pub hook_event: String,
2599 #[serde(default, skip_serializing_if = "Option::is_none")]
2600 pub stdout: Option<String>,
2601 #[serde(default, skip_serializing_if = "Option::is_none")]
2602 pub stderr: Option<String>,
2603 #[serde(default, skip_serializing_if = "Option::is_none")]
2604 pub output: Option<String>,
2605 #[serde(default, skip_serializing_if = "Option::is_none")]
2606 pub uuid: Option<String>,
2607 #[serde(default, skip_serializing_if = "Option::is_none")]
2608 pub session_id: Option<String>,
2609}
2610
2611#[derive(Debug, Clone, Serialize, Deserialize)]
2612pub struct HookResponseMessage {
2613 pub hook_id: String,
2614 pub hook_name: String,
2615 pub hook_event: String,
2616 #[serde(default, skip_serializing_if = "Option::is_none")]
2617 pub stdout: Option<String>,
2618 #[serde(default, skip_serializing_if = "Option::is_none")]
2619 pub stderr: Option<String>,
2620 #[serde(default, skip_serializing_if = "Option::is_none")]
2621 pub output: Option<String>,
2622 #[serde(default, skip_serializing_if = "Option::is_none")]
2623 pub exit_code: Option<i32>,
2624 pub outcome: String,
2625 #[serde(default, skip_serializing_if = "Option::is_none")]
2626 pub uuid: Option<String>,
2627 #[serde(default, skip_serializing_if = "Option::is_none")]
2628 pub session_id: Option<String>,
2629}
2630
2631#[derive(Debug, Clone, Serialize, Deserialize)]
2632pub struct PluginInstallMessage {
2633 pub status: String,
2634 #[serde(default, skip_serializing_if = "Option::is_none")]
2635 pub name: Option<String>,
2636 #[serde(default, skip_serializing_if = "Option::is_none")]
2637 pub error: Option<String>,
2638 #[serde(default, skip_serializing_if = "Option::is_none")]
2639 pub uuid: Option<String>,
2640 #[serde(default, skip_serializing_if = "Option::is_none")]
2641 pub session_id: Option<String>,
2642}
2643
2644#[derive(Debug, Clone, Serialize, Deserialize)]
2645pub struct BackgroundTasksChangedMessage {
2646 pub tasks: Vec<BackgroundTaskInfo>,
2647 #[serde(default, skip_serializing_if = "Option::is_none")]
2648 pub uuid: Option<String>,
2649 #[serde(default, skip_serializing_if = "Option::is_none")]
2650 pub session_id: Option<String>,
2651}
2652
2653#[derive(Debug, Clone, Serialize, Deserialize)]
2654pub struct BackgroundTaskInfo {
2655 pub task_id: String,
2656 #[serde(default, skip_serializing_if = "Option::is_none")]
2659 pub run_id: Option<String>,
2660 pub task_type: String,
2661 #[serde(default, skip_serializing_if = "Option::is_none")]
2665 pub subagent_type: Option<String>,
2666 pub description: String,
2667 #[serde(default, skip_serializing_if = "Option::is_none")]
2670 pub parent_task_id: Option<String>,
2671 #[serde(default, skip_serializing_if = "Option::is_none")]
2674 pub ambient: Option<bool>,
2675 #[serde(default, skip_serializing_if = "Option::is_none")]
2678 pub awaited: Option<bool>,
2679}
2680
2681#[derive(Debug, Clone, Serialize, Deserialize)]
2682pub struct SessionStateChangedMessage {
2683 pub state: String,
2684 #[serde(default, skip_serializing_if = "Option::is_none")]
2685 pub uuid: Option<String>,
2686 #[serde(default, skip_serializing_if = "Option::is_none")]
2687 pub session_id: Option<String>,
2688}
2689
2690#[derive(Debug, Clone, Serialize, Deserialize)]
2691pub struct WorkerShuttingDownMessage {
2692 pub reason: String,
2693 #[serde(default, skip_serializing_if = "Option::is_none")]
2694 pub uuid: Option<String>,
2695 #[serde(default, skip_serializing_if = "Option::is_none")]
2696 pub session_id: Option<String>,
2697}
2698
2699#[derive(Debug, Clone, Serialize, Deserialize)]
2700pub struct CommandsChangedMessage {
2701 pub commands: Vec<CommandInfo>,
2702 #[serde(default, skip_serializing_if = "Option::is_none")]
2703 pub uuid: Option<String>,
2704 #[serde(default, skip_serializing_if = "Option::is_none")]
2705 pub session_id: Option<String>,
2706}
2707
2708#[derive(Debug, Clone, Serialize, Deserialize)]
2709pub struct CommandInfo {
2710 pub name: String,
2711 pub description: String,
2712 #[serde(rename = "argumentHint")]
2713 pub argument_hint: String,
2714 #[serde(default, skip_serializing_if = "Option::is_none")]
2715 pub aliases: Option<Vec<String>>,
2716 #[serde(default, skip_serializing_if = "Option::is_none")]
2722 pub builtin: Option<bool>,
2723}
2724
2725#[derive(Debug, Clone, Serialize, Deserialize)]
2726pub struct NotificationMessage {
2727 pub key: String,
2728 pub text: String,
2729 pub priority: String,
2730 #[serde(default, skip_serializing_if = "Option::is_none")]
2731 pub color: Option<String>,
2732 #[serde(default, skip_serializing_if = "Option::is_none")]
2733 pub timeout_ms: Option<u64>,
2734 #[serde(default, skip_serializing_if = "Option::is_none")]
2735 pub uuid: Option<String>,
2736 #[serde(default, skip_serializing_if = "Option::is_none")]
2737 pub session_id: Option<String>,
2738}
2739
2740#[derive(Debug, Clone, Serialize, Deserialize)]
2741pub struct FilesPersistedMessage {
2742 pub files: Vec<PersistedFile>,
2743 pub failed: Vec<FailedPersistedFile>,
2744 pub processed_at: 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 PersistedFile {
2753 pub filename: String,
2754 pub file_id: String,
2755}
2756
2757#[derive(Debug, Clone, Serialize, Deserialize)]
2758pub struct FailedPersistedFile {
2759 pub filename: String,
2760 pub error: String,
2761}
2762
2763#[derive(Debug, Clone, Serialize, Deserialize)]
2764pub struct MemoryRecallMessage {
2765 pub mode: String,
2766 pub memories: Vec<MemoryRecallItem>,
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, Serialize, Deserialize)]
2774pub struct MemoryRecallItem {
2775 pub path: String,
2776 pub scope: String,
2777 #[serde(default, skip_serializing_if = "Option::is_none")]
2778 pub content: Option<String>,
2779}
2780
2781#[derive(Debug, Clone, Serialize, Deserialize)]
2782pub struct ElicitationCompleteMessage {
2783 pub mcp_server_name: String,
2784 pub elicitation_id: String,
2785 #[serde(default, skip_serializing_if = "Option::is_none")]
2786 pub uuid: Option<String>,
2787 #[serde(default, skip_serializing_if = "Option::is_none")]
2788 pub session_id: Option<String>,
2789}
2790
2791#[derive(Debug, Clone, Serialize, Deserialize)]
2792pub struct PermissionDeniedMessage {
2793 pub tool_name: String,
2794 pub tool_use_id: String,
2795 #[serde(default, skip_serializing_if = "Option::is_none")]
2796 pub agent_id: Option<String>,
2797 #[serde(default, skip_serializing_if = "Option::is_none")]
2798 pub decision_reason_type: Option<String>,
2799 #[serde(default, skip_serializing_if = "Option::is_none")]
2803 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2804 #[serde(default, skip_serializing_if = "Option::is_none")]
2805 pub decision_reason: Option<String>,
2806 pub message: String,
2807 #[serde(default, skip_serializing_if = "Option::is_none")]
2808 pub uuid: Option<String>,
2809 #[serde(default, skip_serializing_if = "Option::is_none")]
2810 pub session_id: Option<String>,
2811}
2812
2813#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2816pub enum PermissionDeniedReasonCode {
2817 ClassifierTranscriptTooLong,
2819 OutsideReadsBlocked,
2821 MemoryPaused,
2823 AutoModeNoCheckInSession,
2828 AutoModeStopNoCheckInSession,
2832 AutoModeStopNoResultForCalls,
2835 AutoModeStopNoVerdict,
2839 Unknown(String),
2841}
2842
2843impl PermissionDeniedReasonCode {
2844 pub fn as_str(&self) -> &str {
2845 match self {
2846 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2847 Self::OutsideReadsBlocked => "outside_reads_blocked",
2848 Self::MemoryPaused => "memory_paused",
2849 Self::AutoModeNoCheckInSession => "auto_mode_no_check_in_session",
2850 Self::AutoModeStopNoCheckInSession => "auto_mode_stop_no_check_in_session",
2851 Self::AutoModeStopNoResultForCalls => "auto_mode_stop_no_result_for_calls",
2852 Self::AutoModeStopNoVerdict => "auto_mode_stop_no_verdict",
2853 Self::Unknown(s) => s.as_str(),
2854 }
2855 }
2856}
2857
2858impl fmt::Display for PermissionDeniedReasonCode {
2859 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2860 f.write_str(self.as_str())
2861 }
2862}
2863
2864impl From<&str> for PermissionDeniedReasonCode {
2865 fn from(s: &str) -> Self {
2866 match s {
2867 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2868 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2869 "memory_paused" => Self::MemoryPaused,
2870 "auto_mode_no_check_in_session" => Self::AutoModeNoCheckInSession,
2871 "auto_mode_stop_no_check_in_session" => Self::AutoModeStopNoCheckInSession,
2872 "auto_mode_stop_no_result_for_calls" => Self::AutoModeStopNoResultForCalls,
2873 "auto_mode_stop_no_verdict" => Self::AutoModeStopNoVerdict,
2874 other => Self::Unknown(other.to_string()),
2875 }
2876 }
2877}
2878
2879impl Serialize for PermissionDeniedReasonCode {
2880 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2881 serializer.serialize_str(self.as_str())
2882 }
2883}
2884
2885impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2886 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2887 let s = String::deserialize(deserializer)?;
2888 Ok(Self::from(s.as_str()))
2889 }
2890}
2891
2892#[derive(Debug, Clone, Serialize, Deserialize)]
2893pub struct MirrorErrorMessage {
2894 pub error: String,
2895 pub key: MirrorErrorKey,
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)]
2903pub struct MirrorErrorKey {
2904 #[serde(rename = "projectKey")]
2905 pub project_key: String,
2906 #[serde(rename = "sessionId")]
2907 pub session_id: String,
2908 #[serde(default, skip_serializing_if = "Option::is_none")]
2909 pub subpath: Option<String>,
2910}
2911
2912#[derive(Debug, Clone, Serialize, Deserialize)]
2913pub struct InformationalMessage {
2914 pub content: String,
2915 pub level: String,
2916 #[serde(default, skip_serializing_if = "Option::is_none")]
2917 pub tool_use_id: Option<String>,
2918 #[serde(default, skip_serializing_if = "Option::is_none")]
2919 pub prevent_continuation: Option<bool>,
2920 #[serde(default, skip_serializing_if = "Option::is_none")]
2923 pub tag: Option<String>,
2924 #[serde(default, skip_serializing_if = "Option::is_none")]
2930 pub prompt_submit_outcome: Option<PromptSubmitOutcome>,
2931 #[serde(default, skip_serializing_if = "Option::is_none")]
2935 pub model_restriction: Option<ModelRestriction>,
2936 #[serde(default, skip_serializing_if = "Option::is_none")]
2937 pub uuid: Option<String>,
2938 #[serde(default, skip_serializing_if = "Option::is_none")]
2939 pub session_id: Option<String>,
2940}
2941
2942#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2948pub struct ModelRestriction {
2949 pub requested_model: String,
2952 pub effective_model: String,
2955 #[serde(default, skip_serializing_if = "Option::is_none")]
2960 pub at: Option<String>,
2961}
2962
2963#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2966pub struct PromptSubmitOutcome {
2967 #[serde(default, skip_serializing_if = "Option::is_none")]
2971 pub kind: Option<PromptSubmitOutcomeKind>,
2972 #[serde(default, skip_serializing_if = "Option::is_none")]
2979 pub no_verdict_kind: Option<String>,
2980 pub prompt_kept: bool,
2983 #[serde(default, skip_serializing_if = "Option::is_none")]
2987 pub reason: Option<String>,
2988}
2989
2990#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2992pub enum PromptSubmitOutcomeKind {
2993 BlockedByHook,
2995 StoppedByHook,
2997 DroppedByHook,
2999 NotPassedOn,
3001 NoVerdict,
3004 Other(String),
3006}
3007
3008impl PromptSubmitOutcomeKind {
3009 pub fn as_str(&self) -> &str {
3010 match self {
3011 Self::BlockedByHook => "blocked_by_hook",
3012 Self::StoppedByHook => "stopped_by_hook",
3013 Self::DroppedByHook => "dropped_by_hook",
3014 Self::NotPassedOn => "not_passed_on",
3015 Self::NoVerdict => "no_verdict",
3016 Self::Other(s) => s.as_str(),
3017 }
3018 }
3019}
3020
3021impl fmt::Display for PromptSubmitOutcomeKind {
3022 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3023 f.write_str(self.as_str())
3024 }
3025}
3026
3027impl From<&str> for PromptSubmitOutcomeKind {
3028 fn from(s: &str) -> Self {
3029 match s {
3030 "blocked_by_hook" => Self::BlockedByHook,
3031 "stopped_by_hook" => Self::StoppedByHook,
3032 "dropped_by_hook" => Self::DroppedByHook,
3033 "not_passed_on" => Self::NotPassedOn,
3034 "no_verdict" => Self::NoVerdict,
3035 other => Self::Other(other.to_string()),
3036 }
3037 }
3038}
3039
3040impl Serialize for PromptSubmitOutcomeKind {
3041 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3042 serializer.serialize_str(self.as_str())
3043 }
3044}
3045
3046impl<'de> Deserialize<'de> for PromptSubmitOutcomeKind {
3047 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3048 let s = String::deserialize(deserializer)?;
3049 Ok(Self::from(s.as_str()))
3050 }
3051}
3052
3053#[derive(Debug, Clone, Serialize, Deserialize)]
3055pub struct PluginInfo {
3056 pub name: String,
3058 pub path: String,
3060 #[serde(skip_serializing_if = "Option::is_none")]
3062 pub source: Option<String>,
3063 #[serde(default, skip_serializing_if = "Option::is_none")]
3065 pub version: Option<String>,
3066}
3067
3068#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3070pub struct PluginDiagnostic {
3071 pub plugin: String,
3072 #[serde(rename = "type")]
3073 pub diagnostic_type: String,
3074 pub message: String,
3075}
3076
3077#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3079pub struct MemoryPaths {
3080 #[serde(default, skip_serializing_if = "Option::is_none")]
3081 pub auto: Option<String>,
3082 #[serde(default, skip_serializing_if = "Option::is_none")]
3083 pub team: Option<String>,
3084 #[serde(flatten)]
3085 pub extra: serde_json::Map<String, Value>,
3086}
3087
3088#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3092pub struct McpServerError {
3093 pub name: String,
3094 #[serde(rename = "type")]
3096 pub error_type: String,
3097 pub message: String,
3098}
3099
3100#[derive(Debug, Clone, Serialize, Deserialize)]
3102pub struct InitMessage {
3103 pub session_id: String,
3105 #[serde(skip_serializing_if = "Option::is_none")]
3107 pub cwd: Option<String>,
3108 #[serde(skip_serializing_if = "Option::is_none")]
3110 pub model: Option<String>,
3111 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3113 pub tools: Vec<String>,
3114 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3116 pub mcp_servers: Vec<Value>,
3117 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3119 pub slash_commands: Vec<String>,
3120 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3123 pub terminal_slash_commands: Vec<String>,
3124 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3126 pub agents: Vec<String>,
3127 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3129 pub plugins: Vec<PluginInfo>,
3130 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3132 pub skills: Vec<Value>,
3133 #[serde(skip_serializing_if = "Option::is_none")]
3135 pub claude_code_version: Option<String>,
3136 #[serde(skip_serializing_if = "Option::is_none")]
3139 pub messaging_socket_path: Option<String>,
3140 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
3142 pub api_key_source: Option<ApiKeySource>,
3143 #[serde(skip_serializing_if = "Option::is_none")]
3145 pub output_style: Option<OutputStyle>,
3146 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3148 pub permission_mode: Option<InitPermissionMode>,
3149
3150 #[serde(skip_serializing_if = "Option::is_none")]
3152 pub uuid: Option<String>,
3153
3154 #[serde(skip_serializing_if = "Option::is_none")]
3156 pub memory_paths: Option<MemoryPaths>,
3157
3158 #[serde(default, skip_serializing_if = "Option::is_none")]
3163 pub scratchpad_path: Option<String>,
3164
3165 #[serde(skip_serializing_if = "Option::is_none")]
3167 pub fast_mode_state: Option<String>,
3168
3169 #[serde(default, skip_serializing_if = "Option::is_none")]
3171 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
3172
3173 #[serde(default, skip_serializing_if = "Option::is_none")]
3176 pub mcp_server_errors: Option<Vec<McpServerError>>,
3177
3178 #[serde(default, skip_serializing_if = "Option::is_none")]
3180 pub analytics_disabled: Option<bool>,
3181
3182 #[serde(default, skip_serializing_if = "Option::is_none")]
3184 pub product_feedback_disabled: Option<bool>,
3185
3186 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3188 pub betas: Vec<String>,
3189
3190 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3192 pub capabilities: Vec<String>,
3193
3194 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3196 pub plugin_errors: Vec<PluginDiagnostic>,
3197
3198 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3200 pub plugin_warnings: Vec<PluginDiagnostic>,
3201
3202 #[serde(default, skip_serializing_if = "Option::is_none")]
3207 pub effort: Option<String>,
3208
3209 #[serde(default, skip_serializing_if = "Option::is_none")]
3214 pub cloud_session: Option<Value>,
3215
3216 #[serde(default, skip_serializing_if = "Option::is_none")]
3220 pub footer_indicator: Option<FooterIndicator>,
3221
3222 #[serde(default, skip_serializing_if = "Option::is_none")]
3226 pub worker_epoch: Option<u64>,
3227
3228 #[serde(default, skip_serializing_if = "Option::is_none")]
3232 pub powershell_path: Option<Option<String>>,
3233
3234 #[serde(default, skip_serializing_if = "Option::is_none")]
3238 pub startup_timing: Option<serde_json::Map<String, Value>>,
3239
3240 #[serde(default, skip_serializing_if = "Option::is_none")]
3250 pub per_turn_effort_active: Option<bool>,
3251
3252 #[serde(default, skip_serializing_if = "Option::is_none")]
3260 pub ultrathink_available: Option<bool>,
3261
3262 #[serde(default, skip_serializing_if = "Option::is_none")]
3272 pub ultrathink_typed_raises: Option<bool>,
3273
3274 #[serde(default, skip_serializing_if = "Option::is_none")]
3279 pub client_thinking: Option<ClientThinking>,
3280
3281 #[serde(default, skip_serializing_if = "Option::is_none")]
3286 pub flag_restate_noop: Option<bool>,
3287
3288 #[serde(default, skip_serializing_if = "Option::is_none")]
3296 pub synced_skills_path: Option<String>,
3297
3298 #[serde(default, skip_serializing_if = "Option::is_none")]
3305 pub view_mode: Option<ViewMode>,
3306
3307 #[serde(default, skip_serializing_if = "Option::is_none")]
3314 pub original_cwd: Option<String>,
3315
3316 #[serde(default, skip_serializing_if = "Option::is_none")]
3325 pub additional_directories: Option<Vec<String>>,
3326}
3327
3328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3331pub struct ClientThinking {
3332 #[serde(default)]
3335 pub max_thinking_tokens: Option<u64>,
3336 #[serde(default)]
3340 pub thinking_display: Option<String>,
3341}
3342
3343#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3347pub struct FooterIndicator {
3348 pub text: String,
3351}
3352
3353#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3356pub enum ViewMode {
3357 Focus,
3360 Default,
3362 Unknown(String),
3364}
3365
3366impl ViewMode {
3367 pub fn as_str(&self) -> &str {
3368 match self {
3369 Self::Focus => "focus",
3370 Self::Default => "default",
3371 Self::Unknown(s) => s.as_str(),
3372 }
3373 }
3374}
3375
3376impl fmt::Display for ViewMode {
3377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3378 f.write_str(self.as_str())
3379 }
3380}
3381
3382impl From<&str> for ViewMode {
3383 fn from(s: &str) -> Self {
3384 match s {
3385 "focus" => Self::Focus,
3386 "default" => Self::Default,
3387 other => Self::Unknown(other.to_string()),
3388 }
3389 }
3390}
3391
3392impl Serialize for ViewMode {
3393 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3394 serializer.serialize_str(self.as_str())
3395 }
3396}
3397
3398impl<'de> Deserialize<'de> for ViewMode {
3399 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3400 let s = String::deserialize(deserializer)?;
3401 Ok(Self::from(s.as_str()))
3402 }
3403}
3404
3405#[derive(Debug, Clone, Serialize, Deserialize)]
3407pub struct StatusMessage {
3408 pub session_id: String,
3410 pub status: Option<StatusMessageStatus>,
3412 #[serde(skip_serializing_if = "Option::is_none")]
3414 pub uuid: Option<String>,
3415 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3417 pub permission_mode: Option<InitPermissionMode>,
3418 #[serde(skip_serializing_if = "Option::is_none")]
3419 pub compact_result: Option<String>,
3420 #[serde(skip_serializing_if = "Option::is_none")]
3421 pub compact_error: Option<String>,
3422 #[serde(default, skip_serializing_if = "Option::is_none")]
3429 pub compact_output_chars: Option<u64>,
3430 #[serde(default, skip_serializing_if = "Option::is_none")]
3437 pub resume_held_for_plan: Option<bool>,
3438}
3439
3440#[derive(Debug, Clone, Serialize, Deserialize)]
3442pub struct CompactBoundaryMessage {
3443 pub session_id: String,
3445 pub compact_metadata: CompactMetadata,
3447 #[serde(
3451 default,
3452 skip_serializing_if = "Option::is_none",
3453 alias = "content",
3454 alias = "text"
3455 )]
3456 pub summary: Option<String>,
3457 #[serde(
3461 default,
3462 skip_serializing_if = "Option::is_none",
3463 alias = "message_count"
3464 )]
3465 pub leaf_message_count: Option<u32>,
3466 #[serde(default, skip_serializing_if = "Option::is_none")]
3468 pub duration_ms: Option<u64>,
3469 #[serde(skip_serializing_if = "Option::is_none")]
3471 pub uuid: Option<String>,
3472 #[serde(skip_serializing_if = "Option::is_none")]
3474 pub logical_parent_uuid: Option<Option<String>>,
3475 #[serde(default, skip_serializing_if = "Option::is_none")]
3479 pub historical: Option<bool>,
3480}
3481
3482#[derive(Debug, Clone, Serialize, Deserialize)]
3484pub struct CompactMetadata {
3485 pub pre_tokens: u64,
3487 pub trigger: CompactionTrigger,
3489 #[serde(default, skip_serializing_if = "Option::is_none")]
3490 pub post_tokens: Option<u64>,
3491 #[serde(default, skip_serializing_if = "Option::is_none")]
3492 pub cumulative_dropped_tokens: Option<u64>,
3493 #[serde(default, skip_serializing_if = "Option::is_none")]
3494 pub duration_ms: Option<u64>,
3495 #[serde(default, skip_serializing_if = "Option::is_none")]
3496 pub user_context: Option<String>,
3497 #[serde(default, skip_serializing_if = "Option::is_none")]
3498 pub messages_summarized: Option<u64>,
3499 #[serde(default, skip_serializing_if = "Option::is_none")]
3500 pub precomputed: Option<bool>,
3501 #[serde(default, skip_serializing_if = "Option::is_none")]
3502 pub pre_compact_discovered_tools: Option<Vec<String>>,
3503 #[serde(default, skip_serializing_if = "Option::is_none")]
3508 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
3509 #[serde(default, skip_serializing_if = "Option::is_none")]
3510 pub preserved_segment: Option<PreservedSegment>,
3511 #[serde(default, skip_serializing_if = "Option::is_none")]
3512 pub preserved_messages: Option<PreservedMessages>,
3513}
3514
3515#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3518pub struct ArtifactReadVersion {
3519 pub slug: String,
3521 pub ver: String,
3523}
3524
3525#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3526pub struct PreservedSegment {
3527 pub head_uuid: String,
3528 pub anchor_uuid: String,
3529 pub tail_uuid: String,
3530}
3531
3532#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3533pub struct PreservedMessages {
3534 pub anchor_uuid: String,
3535 pub uuids: Vec<String>,
3536 #[serde(default, skip_serializing_if = "Option::is_none")]
3537 pub all_uuids: Option<Vec<String>>,
3538}
3539
3540#[derive(Debug, Clone, Serialize, Deserialize)]
3546pub struct TaskUsage {
3547 pub duration_ms: u64,
3549 pub tool_uses: u64,
3551 pub total_tokens: u64,
3553}
3554
3555#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3557pub enum TaskType {
3558 LocalAgent,
3560 LocalBash,
3562 LocalWorkflow,
3564 Unknown(String),
3566}
3567
3568impl TaskType {
3569 pub fn as_str(&self) -> &str {
3570 match self {
3571 Self::LocalAgent => "local_agent",
3572 Self::LocalBash => "local_bash",
3573 Self::LocalWorkflow => "local_workflow",
3574 Self::Unknown(s) => s.as_str(),
3575 }
3576 }
3577}
3578
3579impl fmt::Display for TaskType {
3580 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3581 f.write_str(self.as_str())
3582 }
3583}
3584
3585impl From<&str> for TaskType {
3586 fn from(s: &str) -> Self {
3587 match s {
3588 "local_agent" => Self::LocalAgent,
3589 "local_bash" => Self::LocalBash,
3590 "local_workflow" => Self::LocalWorkflow,
3591 other => Self::Unknown(other.to_string()),
3592 }
3593 }
3594}
3595
3596impl Serialize for TaskType {
3597 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3598 serializer.serialize_str(self.as_str())
3599 }
3600}
3601
3602impl<'de> Deserialize<'de> for TaskType {
3603 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3604 let s = String::deserialize(deserializer)?;
3605 Ok(Self::from(s.as_str()))
3606 }
3607}
3608
3609#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3611pub enum TaskStatus {
3612 Pending,
3613 Running,
3614 Completed,
3615 Failed,
3616 Killed,
3617 Paused,
3618 Stopped,
3619 Unknown(String),
3620}
3621
3622impl TaskStatus {
3623 pub fn as_str(&self) -> &str {
3624 match self {
3625 Self::Pending => "pending",
3626 Self::Running => "running",
3627 Self::Completed => "completed",
3628 Self::Failed => "failed",
3629 Self::Killed => "killed",
3630 Self::Paused => "paused",
3631 Self::Stopped => "stopped",
3632 Self::Unknown(s) => s.as_str(),
3633 }
3634 }
3635}
3636
3637impl fmt::Display for TaskStatus {
3638 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3639 f.write_str(self.as_str())
3640 }
3641}
3642
3643impl From<&str> for TaskStatus {
3644 fn from(s: &str) -> Self {
3645 match s {
3646 "pending" => Self::Pending,
3647 "running" => Self::Running,
3648 "completed" => Self::Completed,
3649 "failed" => Self::Failed,
3650 "killed" => Self::Killed,
3651 "paused" => Self::Paused,
3652 "stopped" => Self::Stopped,
3653 other => Self::Unknown(other.to_string()),
3654 }
3655 }
3656}
3657
3658impl Serialize for TaskStatus {
3659 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3660 serializer.serialize_str(self.as_str())
3661 }
3662}
3663
3664impl<'de> Deserialize<'de> for TaskStatus {
3665 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3666 let s = String::deserialize(deserializer)?;
3667 Ok(Self::from(s.as_str()))
3668 }
3669}
3670
3671#[derive(Debug, Clone, Serialize, Deserialize)]
3673pub struct TaskStartedMessage {
3674 pub session_id: String,
3675 pub task_id: String,
3676 #[serde(default, skip_serializing_if = "Option::is_none")]
3682 pub run_id: Option<String>,
3683 #[serde(default, skip_serializing_if = "Option::is_none")]
3689 pub parent_task_id: Option<String>,
3690 #[serde(default, skip_serializing_if = "Option::is_none")]
3691 pub task_type: Option<TaskType>,
3692 #[serde(default, skip_serializing_if = "Option::is_none")]
3693 pub tool_use_id: Option<String>,
3694 pub description: String,
3695 #[serde(default, skip_serializing_if = "Option::is_none")]
3698 pub subagent_type: Option<String>,
3699 #[serde(default, skip_serializing_if = "Option::is_none")]
3705 pub is_backgrounded: Option<bool>,
3706 #[serde(default, skip_serializing_if = "Option::is_none")]
3714 pub awaited: Option<bool>,
3715 #[serde(default, skip_serializing_if = "Option::is_none")]
3719 pub spawn_depth: Option<u32>,
3720 #[serde(default, skip_serializing_if = "Option::is_none")]
3722 pub prompt: Option<String>,
3723 #[serde(default, skip_serializing_if = "Option::is_none")]
3724 pub workflow_name: Option<String>,
3725 #[serde(default, skip_serializing_if = "Option::is_none")]
3726 pub skip_transcript: Option<bool>,
3727 #[serde(default, skip_serializing_if = "Option::is_none")]
3732 pub ambient: Option<bool>,
3733 pub uuid: String,
3734}
3735
3736#[derive(Debug, Clone, Serialize, Deserialize)]
3740pub struct TaskUpdatedMessage {
3741 pub session_id: String,
3742 pub task_id: String,
3743 #[serde(default, skip_serializing_if = "Option::is_none")]
3746 pub run_id: Option<String>,
3747 pub patch: TaskPatch,
3748 pub uuid: String,
3749}
3750
3751#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3754pub struct TaskPatch {
3755 #[serde(default, skip_serializing_if = "Option::is_none")]
3756 pub status: Option<TaskStatus>,
3757 #[serde(default, skip_serializing_if = "Option::is_none")]
3760 pub end_time: Option<u64>,
3761 #[serde(default, skip_serializing_if = "Option::is_none")]
3762 pub description: Option<String>,
3763 #[serde(default, skip_serializing_if = "Option::is_none")]
3764 pub total_paused_ms: Option<u64>,
3765 #[serde(default, skip_serializing_if = "Option::is_none")]
3766 pub error: Option<String>,
3767 #[serde(default, skip_serializing_if = "Option::is_none")]
3768 pub is_backgrounded: Option<bool>,
3769}
3770
3771#[derive(Debug, Clone, Serialize, Deserialize)]
3774pub struct ThinkingTokensMessage {
3775 pub session_id: String,
3776 pub estimated_tokens: u64,
3778 pub estimated_tokens_delta: u64,
3780 #[serde(default, skip_serializing_if = "Option::is_none")]
3787 pub user_message_uuid: Option<String>,
3788 pub uuid: String,
3789}
3790
3791#[derive(Debug, Clone, Serialize, Deserialize)]
3794pub struct TaskProgressMessage {
3795 pub session_id: String,
3796 pub task_id: String,
3797 #[serde(default, skip_serializing_if = "Option::is_none")]
3800 pub run_id: Option<String>,
3801 #[serde(default, skip_serializing_if = "Option::is_none")]
3802 pub tool_use_id: Option<String>,
3803 pub description: String,
3804 #[serde(default, skip_serializing_if = "Option::is_none")]
3805 pub last_tool_name: Option<String>,
3806 pub usage: TaskUsage,
3807 #[serde(default, skip_serializing_if = "Option::is_none")]
3809 pub subagent_type: Option<String>,
3810 #[serde(default, skip_serializing_if = "Option::is_none")]
3811 pub summary: Option<String>,
3812 pub uuid: String,
3813}
3814
3815#[derive(Debug, Clone, Serialize, Deserialize)]
3818pub struct TaskNotificationMessage {
3819 pub session_id: String,
3820 pub task_id: String,
3821 #[serde(default, skip_serializing_if = "Option::is_none")]
3824 pub run_id: Option<String>,
3825 pub status: TaskStatus,
3826 #[serde(default, skip_serializing_if = "Option::is_none")]
3829 pub reason: Option<TaskEndReason>,
3830 pub summary: String,
3831 pub output_file: Option<String>,
3832 #[serde(skip_serializing_if = "Option::is_none")]
3833 pub tool_use_id: Option<String>,
3834 #[serde(skip_serializing_if = "Option::is_none")]
3835 pub usage: Option<TaskUsage>,
3836 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3842 pub resource_links: Vec<ResourceLink>,
3843 #[serde(default, skip_serializing_if = "Option::is_none")]
3850 pub handback: Option<TaskHandback>,
3851 #[serde(default, skip_serializing_if = "Option::is_none")]
3855 pub handback_report: Option<HandbackReport>,
3856 #[serde(default, skip_serializing_if = "Option::is_none")]
3857 pub skip_transcript: Option<bool>,
3858 #[serde(default, skip_serializing_if = "Option::is_none")]
3861 pub ambient: Option<bool>,
3862 #[serde(skip_serializing_if = "Option::is_none")]
3863 pub uuid: Option<String>,
3864}
3865
3866#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3869pub enum TaskHandback {
3870 Send,
3872 Flagged,
3874 Withheld,
3876 Unknown(String),
3878}
3879
3880impl TaskHandback {
3881 pub fn as_str(&self) -> &str {
3882 match self {
3883 Self::Send => "send",
3884 Self::Flagged => "flagged",
3885 Self::Withheld => "withheld",
3886 Self::Unknown(s) => s.as_str(),
3887 }
3888 }
3889}
3890
3891impl fmt::Display for TaskHandback {
3892 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3893 f.write_str(self.as_str())
3894 }
3895}
3896
3897impl From<&str> for TaskHandback {
3898 fn from(s: &str) -> Self {
3899 match s {
3900 "send" => Self::Send,
3901 "flagged" => Self::Flagged,
3902 "withheld" => Self::Withheld,
3903 other => Self::Unknown(other.to_string()),
3904 }
3905 }
3906}
3907
3908impl Serialize for TaskHandback {
3909 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3910 serializer.serialize_str(self.as_str())
3911 }
3912}
3913
3914impl<'de> Deserialize<'de> for TaskHandback {
3915 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3916 let s = String::deserialize(deserializer)?;
3917 Ok(Self::from(s.as_str()))
3918 }
3919}
3920
3921#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3924pub struct HandbackReport {
3925 pub text: String,
3928 #[serde(default, skip_serializing_if = "Option::is_none")]
3931 pub warning: Option<String>,
3932}
3933
3934#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3936pub enum AssistantErrorKind {
3937 AuthenticationFailed,
3938 OauthOrgNotAllowed,
3939 AccountOnHold,
3940 BillingError,
3941 RateLimit,
3942 Overloaded,
3943 InvalidRequest,
3944 ModelNotFound,
3945 ServerError,
3946 UnknownError,
3947 MaxOutputTokens,
3948 Unknown(String),
3949}
3950
3951impl AssistantErrorKind {
3952 pub fn as_str(&self) -> &str {
3953 match self {
3954 Self::AuthenticationFailed => "authentication_failed",
3955 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3956 Self::AccountOnHold => "account_on_hold",
3957 Self::BillingError => "billing_error",
3958 Self::RateLimit => "rate_limit",
3959 Self::Overloaded => "overloaded",
3960 Self::InvalidRequest => "invalid_request",
3961 Self::ModelNotFound => "model_not_found",
3962 Self::ServerError => "server_error",
3963 Self::UnknownError => "unknown",
3964 Self::MaxOutputTokens => "max_output_tokens",
3965 Self::Unknown(s) => s.as_str(),
3966 }
3967 }
3968}
3969
3970impl fmt::Display for AssistantErrorKind {
3971 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3972 f.write_str(self.as_str())
3973 }
3974}
3975
3976impl From<&str> for AssistantErrorKind {
3977 fn from(s: &str) -> Self {
3978 match s {
3979 "authentication_failed" => Self::AuthenticationFailed,
3980 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3981 "account_on_hold" => Self::AccountOnHold,
3982 "billing_error" => Self::BillingError,
3983 "rate_limit" => Self::RateLimit,
3984 "overloaded" => Self::Overloaded,
3985 "invalid_request" => Self::InvalidRequest,
3986 "model_not_found" => Self::ModelNotFound,
3987 "server_error" => Self::ServerError,
3988 "unknown" => Self::UnknownError,
3989 "max_output_tokens" => Self::MaxOutputTokens,
3990 other => Self::Unknown(other.to_string()),
3991 }
3992 }
3993}
3994
3995impl Serialize for AssistantErrorKind {
3996 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3997 serializer.serialize_str(self.as_str())
3998 }
3999}
4000
4001impl<'de> Deserialize<'de> for AssistantErrorKind {
4002 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4003 let s = String::deserialize(deserializer)?;
4004 Ok(Self::from(s.as_str()))
4005 }
4006}
4007
4008#[derive(Debug, Clone, Serialize, Deserialize)]
4015pub struct CodeChangePublishedMessage {
4016 pub provider: String,
4020 pub url: String,
4022 pub repo: String,
4025 pub identifier: String,
4027 #[serde(default, skip_serializing_if = "Option::is_none")]
4036 pub action: Option<String>,
4037 #[serde(default, skip_serializing_if = "Option::is_none")]
4045 pub branch: Option<String>,
4046 pub uuid: String,
4047 pub session_id: String,
4048}
4049
4050#[derive(Debug, Clone, Serialize, Deserialize)]
4055pub struct VcsStateChangedMessage {
4056 pub kind: VcsMutationKind,
4059 pub cwd: String,
4062 #[serde(default, skip_serializing_if = "Option::is_none")]
4067 pub branch: Option<String>,
4068 pub uuid: String,
4069 pub session_id: String,
4070}
4071
4072#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4074pub enum VcsMutationKind {
4075 Commit,
4076 Push,
4077 Merge,
4078 Rebase,
4079 Unknown(String),
4081}
4082
4083impl VcsMutationKind {
4084 pub fn as_str(&self) -> &str {
4085 match self {
4086 Self::Commit => "commit",
4087 Self::Push => "push",
4088 Self::Merge => "merge",
4089 Self::Rebase => "rebase",
4090 Self::Unknown(s) => s.as_str(),
4091 }
4092 }
4093}
4094
4095impl fmt::Display for VcsMutationKind {
4096 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4097 f.write_str(self.as_str())
4098 }
4099}
4100
4101impl From<&str> for VcsMutationKind {
4102 fn from(s: &str) -> Self {
4103 match s {
4104 "commit" => Self::Commit,
4105 "push" => Self::Push,
4106 "merge" => Self::Merge,
4107 "rebase" => Self::Rebase,
4108 other => Self::Unknown(other.to_string()),
4109 }
4110 }
4111}
4112
4113impl Serialize for VcsMutationKind {
4114 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4115 serializer.serialize_str(self.as_str())
4116 }
4117}
4118
4119impl<'de> Deserialize<'de> for VcsMutationKind {
4120 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4121 let s = String::deserialize(deserializer)?;
4122 Ok(Self::from(s.as_str()))
4123 }
4124}
4125
4126#[derive(Debug, Clone, Serialize, Deserialize)]
4128pub struct FeedbackDraftQueuedMessage {
4129 pub draft_id: String,
4130 pub draft_type: String,
4131 pub title: String,
4132 pub details_preview: String,
4133 #[serde(default, skip_serializing_if = "Option::is_none")]
4134 pub uuid: Option<String>,
4135 #[serde(default, skip_serializing_if = "Option::is_none")]
4136 pub session_id: Option<String>,
4137 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4138 pub extra: serde_json::Map<String, Value>,
4139}
4140
4141#[derive(Debug, Clone, Serialize, Deserialize)]
4149pub struct CloudSessionDeltaMessage {
4150 pub seq: u64,
4154 pub changed: Vec<String>,
4158 pub cloud_session: Value,
4162 pub uuid: String,
4163 pub session_id: String,
4164 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4165 pub extra: serde_json::Map<String, Value>,
4166}
4167
4168#[derive(Debug, Clone, Serialize, Deserialize)]
4184pub struct DevIntentMessage {
4185 pub kind: DevIntentKind,
4187 #[serde(default, skip_serializing_if = "Option::is_none")]
4193 pub trigger: Option<DevIntentTrigger>,
4194 pub uuid: String,
4195 pub session_id: String,
4196 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4197 pub extra: serde_json::Map<String, Value>,
4198}
4199
4200#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4204pub enum DevIntentKind {
4205 IosApp,
4211 AndroidApp,
4218 Unknown(String),
4220}
4221
4222impl DevIntentKind {
4223 pub fn as_str(&self) -> &str {
4224 match self {
4225 Self::IosApp => "ios_app",
4226 Self::AndroidApp => "android_app",
4227 Self::Unknown(s) => s.as_str(),
4228 }
4229 }
4230}
4231
4232impl fmt::Display for DevIntentKind {
4233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4234 f.write_str(self.as_str())
4235 }
4236}
4237
4238impl From<&str> for DevIntentKind {
4239 fn from(s: &str) -> Self {
4240 match s {
4241 "ios_app" => Self::IosApp,
4242 "android_app" => Self::AndroidApp,
4243 other => Self::Unknown(other.to_string()),
4244 }
4245 }
4246}
4247
4248impl Serialize for DevIntentKind {
4249 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4250 serializer.serialize_str(self.as_str())
4251 }
4252}
4253
4254impl<'de> Deserialize<'de> for DevIntentKind {
4255 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4256 let s = String::deserialize(deserializer)?;
4257 Ok(Self::from(s.as_str()))
4258 }
4259}
4260
4261#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4265pub enum DevIntentTrigger {
4266 SwiftEdit,
4268 UikitImport,
4270 XcodeProject,
4273 IosCommand,
4276 KotlinEdit,
4278 JavaEdit,
4280 AndroidImport,
4282 AndroidManifest,
4284 GradlePlugin,
4286 AndroidCommand,
4289 ProjectScan,
4292 Unknown(String),
4294}
4295
4296impl DevIntentTrigger {
4297 pub fn as_str(&self) -> &str {
4298 match self {
4299 Self::SwiftEdit => "swift_edit",
4300 Self::UikitImport => "uikit_import",
4301 Self::XcodeProject => "xcode_project",
4302 Self::IosCommand => "ios_command",
4303 Self::KotlinEdit => "kotlin_edit",
4304 Self::JavaEdit => "java_edit",
4305 Self::AndroidImport => "android_import",
4306 Self::AndroidManifest => "android_manifest",
4307 Self::GradlePlugin => "gradle_plugin",
4308 Self::AndroidCommand => "android_command",
4309 Self::ProjectScan => "project_scan",
4310 Self::Unknown(s) => s.as_str(),
4311 }
4312 }
4313}
4314
4315impl fmt::Display for DevIntentTrigger {
4316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4317 f.write_str(self.as_str())
4318 }
4319}
4320
4321impl From<&str> for DevIntentTrigger {
4322 fn from(s: &str) -> Self {
4323 match s {
4324 "swift_edit" => Self::SwiftEdit,
4325 "uikit_import" => Self::UikitImport,
4326 "xcode_project" => Self::XcodeProject,
4327 "ios_command" => Self::IosCommand,
4328 "kotlin_edit" => Self::KotlinEdit,
4329 "java_edit" => Self::JavaEdit,
4330 "android_import" => Self::AndroidImport,
4331 "android_manifest" => Self::AndroidManifest,
4332 "gradle_plugin" => Self::GradlePlugin,
4333 "android_command" => Self::AndroidCommand,
4334 "project_scan" => Self::ProjectScan,
4335 other => Self::Unknown(other.to_string()),
4336 }
4337 }
4338}
4339
4340impl Serialize for DevIntentTrigger {
4341 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4342 serializer.serialize_str(self.as_str())
4343 }
4344}
4345
4346impl<'de> Deserialize<'de> for DevIntentTrigger {
4347 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4348 let s = String::deserialize(deserializer)?;
4349 Ok(Self::from(s.as_str()))
4350 }
4351}
4352
4353#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4357pub enum TaskEndReason {
4358 WorkerRestart,
4361 Unknown(String),
4363}
4364
4365impl TaskEndReason {
4366 pub fn as_str(&self) -> &str {
4367 match self {
4368 Self::WorkerRestart => "worker_restart",
4369 Self::Unknown(s) => s.as_str(),
4370 }
4371 }
4372}
4373
4374impl fmt::Display for TaskEndReason {
4375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4376 f.write_str(self.as_str())
4377 }
4378}
4379
4380impl From<&str> for TaskEndReason {
4381 fn from(s: &str) -> Self {
4382 match s {
4383 "worker_restart" => Self::WorkerRestart,
4384 other => Self::Unknown(other.to_string()),
4385 }
4386 }
4387}
4388
4389impl Serialize for TaskEndReason {
4390 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4391 serializer.serialize_str(self.as_str())
4392 }
4393}
4394
4395impl<'de> Deserialize<'de> for TaskEndReason {
4396 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4397 let s = String::deserialize(deserializer)?;
4398 Ok(Self::from(s.as_str()))
4399 }
4400}
4401
4402#[derive(Debug, Clone, Serialize, Deserialize)]
4411pub struct TurnHandoffAvailableMessage {
4412 pub v: u64,
4414 pub tools: Vec<String>,
4416 pub worker_epoch: u64,
4418 #[serde(default, skip_serializing_if = "Option::is_none")]
4422 pub relay_marker: Option<bool>,
4423 #[serde(default, skip_serializing_if = "Option::is_none")]
4431 pub staged_files: Option<bool>,
4432 #[serde(default, skip_serializing_if = "Option::is_none")]
4439 pub no_query_first: Option<bool>,
4440 #[serde(default, skip_serializing_if = "Option::is_none")]
4448 pub home_files: Option<bool>,
4449 #[serde(default, skip_serializing_if = "Option::is_none")]
4453 pub file_names: Option<bool>,
4454 #[serde(default, skip_serializing_if = "Option::is_none")]
4464 pub carried_writes: Option<bool>,
4465 #[serde(default, skip_serializing_if = "Option::is_none")]
4479 pub hydrates_carried_lines: Option<bool>,
4480 #[serde(default, skip_serializing_if = "Option::is_none")]
4486 pub shared_message_id: Option<bool>,
4487 #[serde(default, skip_serializing_if = "Option::is_none")]
4492 pub requires_rewind_request_id: Option<bool>,
4493 pub uuid: String,
4494 pub session_id: String,
4495 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4496 pub extra: serde_json::Map<String, Value>,
4497}
4498
4499#[derive(Debug, Clone, Serialize, Deserialize)]
4511pub struct TurnPreemptedMessage {
4512 pub reason: String,
4516 pub preempted_by_uuid: String,
4520 #[serde(default)]
4523 pub preempted_message_uuids: Vec<String>,
4524 pub uuid: String,
4525 pub session_id: String,
4526 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4527 pub extra: serde_json::Map<String, Value>,
4528}
4529
4530#[derive(Debug, Clone, Serialize, Deserialize)]
4538pub struct PeerMessageHoldMessage {
4539 pub state: PeerMessageHoldState,
4545 #[serde(default, skip_serializing_if = "Option::is_none")]
4552 pub message_uuid: Option<String>,
4553 pub lane: PeerMessageLane,
4555 pub from: String,
4560 #[serde(default, skip_serializing_if = "Option::is_none")]
4563 pub from_name: Option<String>,
4564 #[serde(default, skip_serializing_if = "Option::is_none")]
4566 pub cause: Option<PeerMessageHoldCause>,
4567 #[serde(default, skip_serializing_if = "Option::is_none")]
4569 pub outcome: Option<PeerMessageHoldOutcome>,
4570 pub uuid: String,
4571 pub session_id: String,
4572 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4573 pub extra: serde_json::Map<String, Value>,
4574}
4575
4576#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4578pub enum PeerMessageHoldState {
4579 Held,
4581 Released,
4583 Dropped,
4585 Unknown(String),
4587}
4588
4589impl PeerMessageHoldState {
4590 pub fn as_str(&self) -> &str {
4591 match self {
4592 Self::Held => "held",
4593 Self::Released => "released",
4594 Self::Dropped => "dropped",
4595 Self::Unknown(s) => s.as_str(),
4596 }
4597 }
4598}
4599
4600impl fmt::Display for PeerMessageHoldState {
4601 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4602 f.write_str(self.as_str())
4603 }
4604}
4605
4606impl From<&str> for PeerMessageHoldState {
4607 fn from(s: &str) -> Self {
4608 match s {
4609 "held" => Self::Held,
4610 "released" => Self::Released,
4611 "dropped" => Self::Dropped,
4612 other => Self::Unknown(other.to_string()),
4613 }
4614 }
4615}
4616
4617impl Serialize for PeerMessageHoldState {
4618 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4619 serializer.serialize_str(self.as_str())
4620 }
4621}
4622
4623impl<'de> Deserialize<'de> for PeerMessageHoldState {
4624 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4625 let s = String::deserialize(deserializer)?;
4626 Ok(Self::from(s.as_str()))
4627 }
4628}
4629
4630#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4632pub enum PeerMessageLane {
4633 Bridge,
4635 Stdin,
4637 Socket,
4639 Unknown(String),
4641}
4642
4643impl PeerMessageLane {
4644 pub fn as_str(&self) -> &str {
4645 match self {
4646 Self::Bridge => "bridge",
4647 Self::Stdin => "stdin",
4648 Self::Socket => "socket",
4649 Self::Unknown(s) => s.as_str(),
4650 }
4651 }
4652}
4653
4654impl fmt::Display for PeerMessageLane {
4655 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4656 f.write_str(self.as_str())
4657 }
4658}
4659
4660impl From<&str> for PeerMessageLane {
4661 fn from(s: &str) -> Self {
4662 match s {
4663 "bridge" => Self::Bridge,
4664 "stdin" => Self::Stdin,
4665 "socket" => Self::Socket,
4666 other => Self::Unknown(other.to_string()),
4667 }
4668 }
4669}
4670
4671impl Serialize for PeerMessageLane {
4672 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4673 serializer.serialize_str(self.as_str())
4674 }
4675}
4676
4677impl<'de> Deserialize<'de> for PeerMessageLane {
4678 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4679 let s = String::deserialize(deserializer)?;
4680 Ok(Self::from(s.as_str()))
4681 }
4682}
4683
4684#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4691pub enum PeerMessageHoldCause {
4692 ExplicitSetting,
4693 ManagedSetting,
4694 RepoSetting,
4695 InvalidSetting,
4696 BypassDefault,
4697 ModeUnknown,
4698 ModeMismatch,
4699 NoModeAsserted,
4700 Unknown(String),
4702}
4703
4704impl PeerMessageHoldCause {
4705 pub fn as_str(&self) -> &str {
4706 match self {
4707 Self::ExplicitSetting => "explicit-setting",
4708 Self::ManagedSetting => "managed-setting",
4709 Self::RepoSetting => "repo-setting",
4710 Self::InvalidSetting => "invalid-setting",
4711 Self::BypassDefault => "bypass-default",
4712 Self::ModeUnknown => "mode-unknown",
4713 Self::ModeMismatch => "mode-mismatch",
4714 Self::NoModeAsserted => "no-mode-asserted",
4715 Self::Unknown(s) => s.as_str(),
4716 }
4717 }
4718}
4719
4720impl fmt::Display for PeerMessageHoldCause {
4721 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4722 f.write_str(self.as_str())
4723 }
4724}
4725
4726impl From<&str> for PeerMessageHoldCause {
4727 fn from(s: &str) -> Self {
4728 match s {
4729 "explicit-setting" => Self::ExplicitSetting,
4730 "managed-setting" => Self::ManagedSetting,
4731 "repo-setting" => Self::RepoSetting,
4732 "invalid-setting" => Self::InvalidSetting,
4733 "bypass-default" => Self::BypassDefault,
4734 "mode-unknown" => Self::ModeUnknown,
4735 "mode-mismatch" => Self::ModeMismatch,
4736 "no-mode-asserted" => Self::NoModeAsserted,
4737 other => Self::Unknown(other.to_string()),
4738 }
4739 }
4740}
4741
4742impl Serialize for PeerMessageHoldCause {
4743 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4744 serializer.serialize_str(self.as_str())
4745 }
4746}
4747
4748impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4749 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4750 let s = String::deserialize(deserializer)?;
4751 Ok(Self::from(s.as_str()))
4752 }
4753}
4754
4755#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4758pub enum PeerMessageHoldOutcome {
4759 Expired,
4763 Refused,
4765 Dropped,
4768 Discarded,
4770 Unknown(String),
4772}
4773
4774impl PeerMessageHoldOutcome {
4775 pub fn as_str(&self) -> &str {
4776 match self {
4777 Self::Expired => "expired",
4778 Self::Refused => "refused",
4779 Self::Dropped => "dropped",
4780 Self::Discarded => "discarded",
4781 Self::Unknown(s) => s.as_str(),
4782 }
4783 }
4784}
4785
4786impl fmt::Display for PeerMessageHoldOutcome {
4787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4788 f.write_str(self.as_str())
4789 }
4790}
4791
4792impl From<&str> for PeerMessageHoldOutcome {
4793 fn from(s: &str) -> Self {
4794 match s {
4795 "expired" => Self::Expired,
4796 "refused" => Self::Refused,
4797 "dropped" => Self::Dropped,
4798 "discarded" => Self::Discarded,
4799 other => Self::Unknown(other.to_string()),
4800 }
4801 }
4802}
4803
4804impl Serialize for PeerMessageHoldOutcome {
4805 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4806 serializer.serialize_str(self.as_str())
4807 }
4808}
4809
4810impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4811 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4812 let s = String::deserialize(deserializer)?;
4813 Ok(Self::from(s.as_str()))
4814 }
4815}
4816
4817#[derive(Debug, Clone, Serialize, Deserialize)]
4828pub struct PerTurnEffortChangedMessage {
4829 pub per_turn_effort_active: bool,
4831 pub uuid: String,
4832 pub session_id: String,
4833}
4834
4835#[derive(Debug, Clone, Serialize, Deserialize)]
4842pub struct InstructionSizeWarningMessage {
4843 pub total_chars: u64,
4846 pub total_limit_chars: u64,
4849 pub file_count: u64,
4852 #[serde(default, skip_serializing_if = "Option::is_none")]
4856 pub largest_chars: Option<u64>,
4857 pub uuid: String,
4858 pub session_id: String,
4859}
4860
4861#[derive(Debug, Clone, Serialize, Deserialize)]
4868pub struct FileAttachmentsMissingMessage {
4869 pub message_uuid: String,
4872 pub sent_count: u64,
4874 pub missing: Vec<MissingFileAttachment>,
4876 pub uuid: String,
4877 pub session_id: String,
4878}
4879
4880#[derive(Debug, Clone, Serialize, Deserialize)]
4890pub struct PermissionCheckStatusMessage {
4891 pub tool_use_id: String,
4892 #[serde(default, skip_serializing_if = "Option::is_none")]
4895 pub agent_id: Option<String>,
4896 pub status: PermissionCheckStatus,
4897 pub uuid: String,
4898 pub session_id: String,
4899}
4900
4901#[derive(Debug, Clone, Serialize, Deserialize)]
4906pub struct InheritedAsksSettledMessage {
4907 pub kept_request_ids: Vec<String>,
4910 pub uuid: String,
4911 pub session_id: String,
4912}
4913
4914#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4916pub enum PermissionCheckStatus {
4917 Checking,
4920 Done,
4922 Unknown(String),
4924}
4925
4926impl PermissionCheckStatus {
4927 pub fn as_str(&self) -> &str {
4928 match self {
4929 Self::Checking => "checking",
4930 Self::Done => "done",
4931 Self::Unknown(s) => s.as_str(),
4932 }
4933 }
4934}
4935
4936impl fmt::Display for PermissionCheckStatus {
4937 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4938 f.write_str(self.as_str())
4939 }
4940}
4941
4942impl From<&str> for PermissionCheckStatus {
4943 fn from(s: &str) -> Self {
4944 match s {
4945 "checking" => Self::Checking,
4946 "done" => Self::Done,
4947 other => Self::Unknown(other.to_string()),
4948 }
4949 }
4950}
4951
4952impl Serialize for PermissionCheckStatus {
4953 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4954 serializer.serialize_str(self.as_str())
4955 }
4956}
4957
4958impl<'de> Deserialize<'de> for PermissionCheckStatus {
4959 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4960 let s = String::deserialize(deserializer)?;
4961 Ok(Self::from(s.as_str()))
4962 }
4963}
4964
4965#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4967pub struct MissingFileAttachment {
4968 pub file_uuid: String,
4970 pub reason: String,
4975}
4976
4977#[derive(Debug, Clone, Serialize, Deserialize)]
4987pub struct SessionTitleChangedMessage {
4988 pub title: String,
4993 pub uuid: String,
4994 pub session_id: String,
4995}
4996
4997#[derive(Debug, Clone, Serialize, Deserialize)]
5007pub struct SessionWritesLostMessage {
5008 pub error_code: SessionWriteErrorCode,
5011 pub lost: Vec<LostSessionWrite>,
5013 #[serde(default, skip_serializing_if = "Option::is_none")]
5019 pub transcript_session_ids: Option<Vec<String>>,
5020 pub uuid: String,
5021 pub session_id: String,
5025}
5026
5027#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5029pub enum SessionWriteErrorCode {
5030 Enospc,
5032 Edquot,
5034 Unknown(String),
5036}
5037
5038impl SessionWriteErrorCode {
5039 pub fn as_str(&self) -> &str {
5040 match self {
5041 Self::Enospc => "ENOSPC",
5042 Self::Edquot => "EDQUOT",
5043 Self::Unknown(s) => s.as_str(),
5044 }
5045 }
5046}
5047
5048impl fmt::Display for SessionWriteErrorCode {
5049 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5050 f.write_str(self.as_str())
5051 }
5052}
5053
5054impl From<&str> for SessionWriteErrorCode {
5055 fn from(s: &str) -> Self {
5056 match s {
5057 "ENOSPC" => Self::Enospc,
5058 "EDQUOT" => Self::Edquot,
5059 other => Self::Unknown(other.to_string()),
5060 }
5061 }
5062}
5063
5064impl Serialize for SessionWriteErrorCode {
5065 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5066 serializer.serialize_str(self.as_str())
5067 }
5068}
5069
5070impl<'de> Deserialize<'de> for SessionWriteErrorCode {
5071 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5072 let s = String::deserialize(deserializer)?;
5073 Ok(Self::from(s.as_str()))
5074 }
5075}
5076
5077#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5079pub enum LostSessionWrite {
5080 Transcript,
5083 GlobalConfig,
5085 Unknown(String),
5087}
5088
5089impl LostSessionWrite {
5090 pub fn as_str(&self) -> &str {
5091 match self {
5092 Self::Transcript => "transcript",
5093 Self::GlobalConfig => "global_config",
5094 Self::Unknown(s) => s.as_str(),
5095 }
5096 }
5097}
5098
5099impl fmt::Display for LostSessionWrite {
5100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5101 f.write_str(self.as_str())
5102 }
5103}
5104
5105impl From<&str> for LostSessionWrite {
5106 fn from(s: &str) -> Self {
5107 match s {
5108 "transcript" => Self::Transcript,
5109 "global_config" => Self::GlobalConfig,
5110 other => Self::Unknown(other.to_string()),
5111 }
5112 }
5113}
5114
5115impl Serialize for LostSessionWrite {
5116 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5117 serializer.serialize_str(self.as_str())
5118 }
5119}
5120
5121impl<'de> Deserialize<'de> for LostSessionWrite {
5122 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5123 let s = String::deserialize(deserializer)?;
5124 Ok(Self::from(s.as_str()))
5125 }
5126}
5127
5128#[derive(Debug, Clone, Serialize, Deserialize)]
5132pub struct UiLogMessage {
5133 pub plugin: String,
5135 pub text: String,
5139 pub uuid: String,
5140 pub session_id: String,
5141}
5142
5143#[derive(Debug, Clone, Serialize, Deserialize)]
5148pub struct UiNotifyMessage {
5149 pub plugin: String,
5154 pub source: String,
5158 pub title: String,
5162 pub text: String,
5166 #[serde(default, skip_serializing_if = "Option::is_none")]
5170 pub key: Option<String>,
5171 #[serde(default, skip_serializing_if = "Option::is_none")]
5175 pub delay_ms: Option<u64>,
5176 #[serde(default, skip_serializing_if = "Option::is_none")]
5179 pub at: Option<u64>,
5180 #[serde(default, skip_serializing_if = "Option::is_none")]
5183 pub until_back: Option<bool>,
5184 #[serde(default)]
5189 pub rewritten: bool,
5190 pub uuid: String,
5191 pub session_id: String,
5192}
5193
5194#[derive(Debug, Clone, Serialize, Deserialize)]
5199pub struct UiNotifyCancelMessage {
5200 pub plugin: String,
5203 pub source: String,
5206 #[serde(default, skip_serializing_if = "Option::is_none")]
5211 pub key: Option<String>,
5212 pub uuid: String,
5213 pub session_id: String,
5214}
5215
5216#[derive(Debug, Clone, Serialize, Deserialize)]
5221pub struct UiToastMessage {
5222 pub plugin: String,
5224 pub text: String,
5228 pub timeout_ms: u64,
5231 pub uuid: String,
5232 pub session_id: String,
5233}
5234
5235#[derive(Debug, Clone, Serialize, Deserialize)]
5239pub struct UiStatusMessage {
5240 pub plugin: String,
5242 pub text: Option<String>,
5247 pub uuid: String,
5248 pub session_id: String,
5249}
5250
5251#[derive(Debug, Clone, Serialize, Deserialize)]
5257pub struct UiInvalidateMessage {
5258 pub event: String,
5261 #[serde(default, skip_serializing_if = "Option::is_none")]
5271 pub instances: Option<Vec<UiInvalidatedInstance>>,
5272 #[serde(default, skip_serializing_if = "Option::is_none")]
5280 pub prompts_screened: Option<bool>,
5281 pub uuid: String,
5282 pub session_id: String,
5283}
5284
5285#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5287pub struct UiInvalidatedInstance {
5288 pub surface: String,
5290 pub component: String,
5292 pub instance_id: String,
5295}
5296
5297#[derive(Debug, Clone, Serialize, Deserialize)]
5304pub struct UiPanesMessage {
5305 pub panes: Vec<UiPane>,
5308 pub shown_id: Option<String>,
5311 pub focused_id: Option<String>,
5314 pub focus_requested_id: Option<String>,
5318 pub uuid: String,
5319 pub session_id: String,
5320}
5321
5322#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5324pub struct UiPane {
5325 pub id: String,
5328 pub title: String,
5331 pub plugin: String,
5333 #[serde(default, skip_serializing_if = "Option::is_none")]
5336 pub close_on_escape: Option<bool>,
5337 #[serde(default, skip_serializing_if = "Option::is_none")]
5340 pub hold_toasts: Option<bool>,
5341 #[serde(default, skip_serializing_if = "Option::is_none")]
5346 pub background_color: Option<String>,
5347 #[serde(default, skip_serializing_if = "Option::is_none")]
5350 pub rows: Option<u64>,
5351 #[serde(default, skip_serializing_if = "Option::is_none")]
5354 pub columns: Option<u64>,
5355}
5356
5357#[derive(Debug, Clone, Serialize, Deserialize)]
5365pub struct UiPlacedMessage {
5366 pub rows: Vec<UiPlacedRow>,
5368 pub uuid: String,
5369 pub session_id: String,
5370}
5371
5372#[derive(Debug, Clone, Serialize, Deserialize)]
5374pub struct UiPlacedRow {
5375 pub plugin: String,
5377 pub id: String,
5379 pub request_id: String,
5382 #[serde(default, skip_serializing_if = "Option::is_none")]
5385 pub after_uuid: Option<String>,
5386}
5387
5388#[derive(Debug, Clone, Serialize, Deserialize)]
5392pub struct UiScrollMessage {
5393 pub client_id: String,
5395 pub component: UiSiteComponent,
5397 pub instance_id: String,
5399 pub offset: u64,
5402 #[serde(default, skip_serializing_if = "Option::is_none")]
5405 pub follow_end: Option<bool>,
5406 pub uuid: String,
5407 pub session_id: String,
5408}
5409
5410#[derive(Debug, Clone, Serialize, Deserialize)]
5415pub struct UiFocusMessage {
5416 pub client_id: String,
5418 pub component: UiSiteComponent,
5420 pub instance_id: String,
5422 pub plugin: String,
5424 pub key: String,
5426 pub uuid: String,
5427 pub session_id: String,
5428}
5429
5430#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5432pub enum UiSiteComponent {
5433 Pane,
5435 AbovePrompt,
5437 Unknown(String),
5439}
5440
5441impl UiSiteComponent {
5442 pub fn as_str(&self) -> &str {
5443 match self {
5444 Self::Pane => "Pane",
5445 Self::AbovePrompt => "AbovePrompt",
5446 Self::Unknown(s) => s.as_str(),
5447 }
5448 }
5449}
5450
5451impl fmt::Display for UiSiteComponent {
5452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5453 f.write_str(self.as_str())
5454 }
5455}
5456
5457impl From<&str> for UiSiteComponent {
5458 fn from(s: &str) -> Self {
5459 match s {
5460 "Pane" => Self::Pane,
5461 "AbovePrompt" => Self::AbovePrompt,
5462 other => Self::Unknown(other.to_string()),
5463 }
5464 }
5465}
5466
5467impl Serialize for UiSiteComponent {
5468 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5469 serializer.serialize_str(self.as_str())
5470 }
5471}
5472
5473impl<'de> Deserialize<'de> for UiSiteComponent {
5474 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5475 let s = String::deserialize(deserializer)?;
5476 Ok(Self::from(s.as_str()))
5477 }
5478}
5479
5480#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5483pub struct LocalCommandRun {
5484 pub command: String,
5486 pub args: String,
5489 #[serde(default, skip_serializing_if = "Option::is_none")]
5494 pub plugin: Option<String>,
5495}
5496
5497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5501pub struct LocalCommandOutcome {
5502 pub kind: LocalCommandOutcomeKind,
5503 #[serde(default, skip_serializing_if = "Option::is_none")]
5506 pub suggestion: Option<String>,
5507}
5508
5509#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5514pub enum LocalCommandOutcomeKind {
5515 UnavailableHeadless,
5517 Unknown,
5520 Failed,
5522 RestartRequired,
5524 Other(String),
5526}
5527
5528impl LocalCommandOutcomeKind {
5529 pub fn as_str(&self) -> &str {
5530 match self {
5531 Self::UnavailableHeadless => "unavailable_headless",
5532 Self::Unknown => "unknown",
5533 Self::Failed => "failed",
5534 Self::RestartRequired => "restart_required",
5535 Self::Other(s) => s.as_str(),
5536 }
5537 }
5538}
5539
5540impl fmt::Display for LocalCommandOutcomeKind {
5541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5542 f.write_str(self.as_str())
5543 }
5544}
5545
5546impl From<&str> for LocalCommandOutcomeKind {
5547 fn from(s: &str) -> Self {
5548 match s {
5549 "unavailable_headless" => Self::UnavailableHeadless,
5550 "unknown" => Self::Unknown,
5551 "failed" => Self::Failed,
5552 "restart_required" => Self::RestartRequired,
5553 other => Self::Other(other.to_string()),
5554 }
5555 }
5556}
5557
5558impl Serialize for LocalCommandOutcomeKind {
5559 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5560 serializer.serialize_str(self.as_str())
5561 }
5562}
5563
5564impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
5565 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5566 let s = String::deserialize(deserializer)?;
5567 Ok(Self::from(s.as_str()))
5568 }
5569}
5570
5571#[derive(Debug, Clone, Serialize, Deserialize)]
5577pub struct UsageReport {
5578 pub session: super::control::UsageSession,
5580 pub rate_limits: Option<UsageReportRateLimits>,
5584 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5585 pub extra: serde_json::Map<String, Value>,
5586}
5587
5588#[derive(Debug, Clone, Serialize, Deserialize)]
5590pub struct UsageReportRateLimits {
5591 pub limits: Option<Vec<UsageReportLimit>>,
5599 #[serde(default, skip_serializing_if = "Option::is_none")]
5602 pub extra_usage: Option<UsageReportExtraUsage>,
5603 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5604 pub extra: serde_json::Map<String, Value>,
5605}
5606
5607#[derive(Debug, Clone, Serialize, Deserialize)]
5609pub struct UsageReportLimit {
5610 pub kind: String,
5613 pub group: String,
5616 pub percent: f64,
5618 pub resets_at: Option<String>,
5620 #[serde(default, skip_serializing_if = "Option::is_none")]
5623 pub scope: Option<UsageReportScope>,
5624 #[serde(default, skip_serializing_if = "Option::is_none")]
5628 pub severity: Option<String>,
5629 #[serde(default, skip_serializing_if = "Option::is_none")]
5631 pub is_active: Option<bool>,
5632 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5633 pub extra: serde_json::Map<String, Value>,
5634}
5635
5636#[derive(Debug, Clone, Serialize, Deserialize)]
5638pub struct UsageReportScope {
5639 #[serde(default, skip_serializing_if = "Option::is_none")]
5640 pub model: Option<UsageReportScopeLabel>,
5641 #[serde(default, skip_serializing_if = "Option::is_none")]
5642 pub surface: Option<UsageReportScopeLabel>,
5643 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5644 pub extra: serde_json::Map<String, Value>,
5645}
5646
5647#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5649pub struct UsageReportScopeLabel {
5650 pub display_name: String,
5651}
5652
5653#[derive(Debug, Clone, Serialize, Deserialize)]
5657pub struct UsageReportExtraUsage {
5658 pub is_enabled: bool,
5660 pub monthly_limit: Option<f64>,
5661 pub used_credits: Option<f64>,
5662 pub utilization: Option<f64>,
5663 #[serde(default, skip_serializing_if = "Option::is_none")]
5664 pub currency: Option<String>,
5665 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5666 pub extra: serde_json::Map<String, Value>,
5667}
5668
5669#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5672pub struct BatchToolUse {
5673 pub id: String,
5674 pub name: String,
5675}
5676
5677#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5682pub struct ContextUsage {
5683 pub model: String,
5685 pub total_tokens: u64,
5688 pub raw_max_tokens: u64,
5691 pub percentage: u64,
5693 #[serde(default, skip_serializing_if = "Option::is_none")]
5695 pub over_limit: Option<ContextOverLimit>,
5696 #[serde(default)]
5698 pub categories: Vec<ContextCategory>,
5699 #[serde(default)]
5701 pub mcp_tools: Vec<ContextMcpTool>,
5702 #[serde(default)]
5704 pub memory_files: Vec<ContextMemoryFile>,
5705 #[serde(default)]
5707 pub agents: Vec<ContextAgent>,
5708 #[serde(default, skip_serializing_if = "Option::is_none")]
5710 pub skills: Option<Vec<ContextSkill>>,
5711}
5712
5713#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5715pub struct ContextOverLimit {
5716 pub tokens_over: u64,
5717 pub kind: String,
5720}
5721
5722#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5724pub struct ContextCategory {
5725 pub name: String,
5728 pub tokens: u64,
5729 pub kind: String,
5733}
5734
5735#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5737pub struct ContextMcpTool {
5738 pub name: String,
5740 pub server_name: String,
5741 pub tokens: u64,
5742}
5743
5744#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5746pub struct ContextMemoryFile {
5747 pub path: String,
5748 #[serde(rename = "type")]
5750 pub file_type: String,
5751 pub tokens: u64,
5752}
5753
5754#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5756pub struct ContextAgent {
5757 pub agent_type: String,
5758 pub source: String,
5760 pub tokens: u64,
5761}
5762
5763#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5765pub struct ContextSkill {
5766 pub name: String,
5767 pub source: String,
5769 #[serde(default, skip_serializing_if = "Option::is_none")]
5770 pub plugin_name: Option<String>,
5771 pub tokens: u64,
5772}
5773
5774#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5776pub struct ToolUseMeta {
5777 pub id: String,
5778 pub display_name: String,
5779 #[serde(default, skip_serializing_if = "Option::is_none")]
5780 pub server_display_name: Option<String>,
5781 #[serde(default, skip_serializing_if = "Option::is_none")]
5782 pub icon_url: Option<String>,
5783 #[serde(default, skip_serializing_if = "Option::is_none")]
5788 pub server_id: Option<String>,
5789 #[serde(default, skip_serializing_if = "Option::is_none")]
5797 pub server_id_stamp: Option<String>,
5798 #[serde(default, skip_serializing_if = "Option::is_none")]
5801 pub tool_name: Option<String>,
5802 #[serde(default, skip_serializing_if = "Option::is_none")]
5811 pub artifact_publish: Option<bool>,
5812}
5813
5814#[derive(Debug, Clone, Serialize, Deserialize)]
5816pub struct AssistantMessage {
5817 pub message: AssistantMessageContent,
5818 #[serde(alias = "sessionId")]
5819 pub session_id: String,
5820 #[serde(skip_serializing_if = "Option::is_none")]
5821 pub uuid: Option<String>,
5822 #[serde(skip_serializing_if = "Option::is_none")]
5823 pub parent_tool_use_id: Option<String>,
5824 #[serde(skip_serializing_if = "Option::is_none")]
5826 pub request_id: Option<String>,
5827 #[serde(default, skip_serializing_if = "Option::is_none")]
5832 pub refused_message_id: Option<String>,
5833 #[serde(default, skip_serializing_if = "Option::is_none")]
5842 pub user_message_uuid: Option<String>,
5843 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5849 pub user_message_uuids: Vec<String>,
5850 #[serde(default, skip_serializing_if = "Option::is_none")]
5854 pub agent_id: Option<String>,
5855 #[serde(skip_serializing_if = "Option::is_none")]
5858 pub subagent_type: Option<String>,
5859 #[serde(skip_serializing_if = "Option::is_none")]
5861 pub task_description: Option<String>,
5862 #[serde(skip_serializing_if = "Option::is_none")]
5863 pub error: Option<AssistantErrorKind>,
5864 #[serde(default, skip_serializing_if = "Option::is_none")]
5868 pub aborted: Option<bool>,
5869 #[serde(default, skip_serializing_if = "Option::is_none")]
5874 pub resumed_from_incomplete_thinking: Option<bool>,
5875 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5876 pub supersedes: Vec<String>,
5877 #[serde(skip_serializing_if = "Option::is_none")]
5878 pub timestamp: Option<String>,
5879 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5880 pub tool_use_meta: Vec<ToolUseMeta>,
5881 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5886 pub batch_tool_uses: Vec<BatchToolUse>,
5887 #[serde(default, skip_serializing_if = "Option::is_none")]
5893 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
5894 #[serde(default, skip_serializing_if = "Option::is_none")]
5900 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
5901 #[serde(default, skip_serializing_if = "Option::is_none")]
5905 pub historical: Option<bool>,
5906 #[serde(default, skip_serializing_if = "Option::is_none")]
5914 pub resume_reason: Option<String>,
5915 #[serde(default, skip_serializing_if = "Option::is_none")]
5921 pub local_command_source: Option<String>,
5922 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5931 pub narration_block_indexes: Vec<usize>,
5932 #[serde(default, skip_serializing_if = "Option::is_none")]
5936 pub narration_hint: Option<String>,
5937 #[serde(default, skip_serializing_if = "Option::is_none")]
5946 pub thinking_duration_ms: Option<u64>,
5947 #[serde(default, skip_serializing_if = "Option::is_none")]
5958 pub thinking_display: Option<String>,
5959 #[serde(default, skip_serializing_if = "Option::is_none")]
5963 pub context_usage: Option<ContextUsage>,
5964 #[serde(default, skip_serializing_if = "Option::is_none")]
5972 pub usage_report: Option<Box<UsageReport>>,
5973 #[serde(default, skip_serializing_if = "Option::is_none")]
5980 pub local_command_run: Option<LocalCommandRun>,
5981 #[serde(default, skip_serializing_if = "Option::is_none")]
5987 pub local_command_outcome: Option<LocalCommandOutcome>,
5988 #[serde(default, skip_serializing_if = "Option::is_none")]
5989 pub is_meta: Option<bool>,
5990 #[serde(default, skip_serializing_if = "Option::is_none")]
5991 pub is_virtual: Option<bool>,
5992 #[serde(default, skip_serializing_if = "Option::is_none")]
5993 pub is_api_error_message: Option<bool>,
5994 #[serde(skip_serializing_if = "Option::is_none")]
5995 pub api_error_status: Option<u16>,
5996 #[serde(skip_serializing_if = "Option::is_none")]
6045 pub api_error: Option<String>,
6046 #[serde(default, skip_serializing_if = "Option::is_none")]
6053 pub api_error_code: Option<String>,
6054 #[serde(default, skip_serializing_if = "Option::is_none")]
6065 pub api_error_origin: Option<String>,
6066 #[serde(default, skip_serializing_if = "Option::is_none")]
6069 pub api_error_params: Option<ApiErrorParams>,
6070 #[serde(skip_serializing_if = "Option::is_none")]
6071 pub error_details: Option<String>,
6072 #[serde(skip_serializing_if = "Option::is_none")]
6073 pub advisor_model: Option<String>,
6074 #[serde(skip_serializing_if = "Option::is_none")]
6075 pub attribution_agent: Option<String>,
6076 #[serde(skip_serializing_if = "Option::is_none")]
6077 pub attribution_skill: Option<String>,
6078 #[serde(skip_serializing_if = "Option::is_none")]
6079 pub attribution_plugin: Option<String>,
6080 #[serde(skip_serializing_if = "Option::is_none")]
6081 pub attribution_mcp_server: Option<String>,
6082 #[serde(skip_serializing_if = "Option::is_none")]
6083 pub attribution_mcp_tool: Option<String>,
6084}
6085
6086#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
6090pub struct ApiErrorParams {
6091 #[serde(default, skip_serializing_if = "Option::is_none")]
6093 pub effort: Option<String>,
6094 #[serde(default, skip_serializing_if = "Option::is_none")]
6097 pub provider: Option<ApiErrorProvider>,
6098 #[serde(default, skip_serializing_if = "Option::is_none")]
6101 pub remedy: Option<ApiErrorRemedy>,
6102 #[serde(default, skip_serializing_if = "Option::is_none")]
6105 pub media: Option<ApiErrorMedia>,
6106 #[serde(default, skip_serializing_if = "Option::is_none")]
6108 pub media_reason: Option<ApiErrorMediaReason>,
6109 #[serde(default, skip_serializing_if = "Option::is_none")]
6131 pub rate_limit_info: Option<RateLimitInfo>,
6132 #[serde(default, skip_serializing_if = "Option::is_none")]
6137 pub cut: Option<ApiErrorCut>,
6138}
6139
6140#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6142pub enum ApiErrorCut {
6143 OutputLimit,
6145 ContextWindow,
6147 Unknown(String),
6149}
6150
6151impl ApiErrorCut {
6152 pub fn as_str(&self) -> &str {
6153 match self {
6154 Self::OutputLimit => "output_limit",
6155 Self::ContextWindow => "context_window",
6156 Self::Unknown(s) => s.as_str(),
6157 }
6158 }
6159}
6160
6161impl fmt::Display for ApiErrorCut {
6162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6163 f.write_str(self.as_str())
6164 }
6165}
6166
6167impl From<&str> for ApiErrorCut {
6168 fn from(s: &str) -> Self {
6169 match s {
6170 "output_limit" => Self::OutputLimit,
6171 "context_window" => Self::ContextWindow,
6172 other => Self::Unknown(other.to_string()),
6173 }
6174 }
6175}
6176
6177impl Serialize for ApiErrorCut {
6178 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6179 serializer.serialize_str(self.as_str())
6180 }
6181}
6182
6183impl<'de> Deserialize<'de> for ApiErrorCut {
6184 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6185 let s = String::deserialize(deserializer)?;
6186 Ok(Self::from(s.as_str()))
6187 }
6188}
6189
6190#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6192pub enum ApiErrorMedia {
6193 Image,
6194 Document,
6195 Unknown(String),
6197}
6198
6199impl ApiErrorMedia {
6200 pub fn as_str(&self) -> &str {
6201 match self {
6202 Self::Image => "image",
6203 Self::Document => "document",
6204 Self::Unknown(s) => s.as_str(),
6205 }
6206 }
6207}
6208
6209impl fmt::Display for ApiErrorMedia {
6210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6211 f.write_str(self.as_str())
6212 }
6213}
6214
6215impl From<&str> for ApiErrorMedia {
6216 fn from(s: &str) -> Self {
6217 match s {
6218 "image" => Self::Image,
6219 "document" => Self::Document,
6220 other => Self::Unknown(other.to_string()),
6221 }
6222 }
6223}
6224
6225impl Serialize for ApiErrorMedia {
6226 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6227 serializer.serialize_str(self.as_str())
6228 }
6229}
6230
6231impl<'de> Deserialize<'de> for ApiErrorMedia {
6232 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6233 let s = String::deserialize(deserializer)?;
6234 Ok(Self::from(s.as_str()))
6235 }
6236}
6237
6238#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6241pub enum ApiErrorMediaReason {
6242 Unprocessable,
6245 UnsupportedByModel,
6247 MediaBudget,
6250 Unknown(String),
6252}
6253
6254impl ApiErrorMediaReason {
6255 pub fn as_str(&self) -> &str {
6256 match self {
6257 Self::Unprocessable => "unprocessable",
6258 Self::UnsupportedByModel => "unsupported_by_model",
6259 Self::MediaBudget => "media_budget",
6260 Self::Unknown(s) => s.as_str(),
6261 }
6262 }
6263}
6264
6265impl fmt::Display for ApiErrorMediaReason {
6266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6267 f.write_str(self.as_str())
6268 }
6269}
6270
6271impl From<&str> for ApiErrorMediaReason {
6272 fn from(s: &str) -> Self {
6273 match s {
6274 "unprocessable" => Self::Unprocessable,
6275 "unsupported_by_model" => Self::UnsupportedByModel,
6276 "media_budget" => Self::MediaBudget,
6277 other => Self::Unknown(other.to_string()),
6278 }
6279 }
6280}
6281
6282impl Serialize for ApiErrorMediaReason {
6283 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6284 serializer.serialize_str(self.as_str())
6285 }
6286}
6287
6288impl<'de> Deserialize<'de> for ApiErrorMediaReason {
6289 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6290 let s = String::deserialize(deserializer)?;
6291 Ok(Self::from(s.as_str()))
6292 }
6293}
6294
6295#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6297pub enum ApiErrorProvider {
6298 Bedrock,
6299 AnthropicAws,
6300 Mantle,
6301 AnthropicGoogleCloud,
6302 Vertex,
6303 Foundry,
6304 Gateway,
6305 Unknown(String),
6307}
6308
6309impl ApiErrorProvider {
6310 pub fn as_str(&self) -> &str {
6311 match self {
6312 Self::Bedrock => "bedrock",
6313 Self::AnthropicAws => "anthropicAws",
6314 Self::Mantle => "mantle",
6315 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
6316 Self::Vertex => "vertex",
6317 Self::Foundry => "foundry",
6318 Self::Gateway => "gateway",
6319 Self::Unknown(s) => s.as_str(),
6320 }
6321 }
6322}
6323
6324impl fmt::Display for ApiErrorProvider {
6325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6326 f.write_str(self.as_str())
6327 }
6328}
6329
6330impl From<&str> for ApiErrorProvider {
6331 fn from(s: &str) -> Self {
6332 match s {
6333 "bedrock" => Self::Bedrock,
6334 "anthropicAws" => Self::AnthropicAws,
6335 "mantle" => Self::Mantle,
6336 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
6337 "vertex" => Self::Vertex,
6338 "foundry" => Self::Foundry,
6339 "gateway" => Self::Gateway,
6340 other => Self::Unknown(other.to_string()),
6341 }
6342 }
6343}
6344
6345impl Serialize for ApiErrorProvider {
6346 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6347 serializer.serialize_str(self.as_str())
6348 }
6349}
6350
6351impl<'de> Deserialize<'de> for ApiErrorProvider {
6352 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6353 let s = String::deserialize(deserializer)?;
6354 Ok(Self::from(s.as_str()))
6355 }
6356}
6357
6358#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6360pub enum ApiErrorRemedy {
6361 RefreshCommand,
6364 RefreshCredentials,
6366 Adc,
6369 GatewayToken,
6372 HostManaged,
6374 ModelAccess,
6377 Unknown(String),
6379}
6380
6381impl ApiErrorRemedy {
6382 pub fn as_str(&self) -> &str {
6383 match self {
6384 Self::RefreshCommand => "refresh_command",
6385 Self::RefreshCredentials => "refresh_credentials",
6386 Self::Adc => "adc",
6387 Self::GatewayToken => "gateway_token",
6388 Self::HostManaged => "host_managed",
6389 Self::ModelAccess => "model_access",
6390 Self::Unknown(s) => s.as_str(),
6391 }
6392 }
6393}
6394
6395impl fmt::Display for ApiErrorRemedy {
6396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6397 f.write_str(self.as_str())
6398 }
6399}
6400
6401impl From<&str> for ApiErrorRemedy {
6402 fn from(s: &str) -> Self {
6403 match s {
6404 "refresh_command" => Self::RefreshCommand,
6405 "refresh_credentials" => Self::RefreshCredentials,
6406 "adc" => Self::Adc,
6407 "gateway_token" => Self::GatewayToken,
6408 "host_managed" => Self::HostManaged,
6409 "model_access" => Self::ModelAccess,
6410 other => Self::Unknown(other.to_string()),
6411 }
6412 }
6413}
6414
6415impl Serialize for ApiErrorRemedy {
6416 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6417 serializer.serialize_str(self.as_str())
6418 }
6419}
6420
6421impl<'de> Deserialize<'de> for ApiErrorRemedy {
6422 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6423 let s = String::deserialize(deserializer)?;
6424 Ok(Self::from(s.as_str()))
6425 }
6426}
6427
6428#[derive(Debug, Clone, Serialize, Deserialize)]
6430pub struct AssistantMessageContent {
6431 pub id: String,
6432 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
6434 pub message_type: Option<String>,
6435 pub role: MessageRole,
6436 pub model: String,
6437 pub content: Vec<ContentBlock>,
6438 #[serde(skip_serializing_if = "Option::is_none")]
6439 pub stop_reason: Option<StopReason>,
6440 #[serde(skip_serializing_if = "Option::is_none")]
6441 pub stop_sequence: Option<String>,
6442 #[serde(skip_serializing_if = "Option::is_none")]
6443 pub usage: Option<AssistantUsage>,
6444 #[serde(skip_serializing_if = "Option::is_none")]
6446 pub stop_details: Option<Value>,
6447 #[serde(skip_serializing_if = "Option::is_none")]
6449 pub context_management: Option<Value>,
6450}
6451
6452#[derive(Debug, Clone, Serialize, Deserialize)]
6454pub struct AssistantUsage {
6455 #[serde(default)]
6457 pub input_tokens: u32,
6458
6459 #[serde(default)]
6461 pub output_tokens: u32,
6462
6463 #[serde(default)]
6465 pub cache_creation_input_tokens: u32,
6466
6467 #[serde(default)]
6469 pub cache_read_input_tokens: u32,
6470
6471 #[serde(skip_serializing_if = "Option::is_none")]
6473 pub service_tier: Option<String>,
6474
6475 #[serde(skip_serializing_if = "Option::is_none")]
6477 pub cache_creation: Option<CacheCreationDetails>,
6478
6479 #[serde(skip_serializing_if = "Option::is_none")]
6481 pub inference_geo: Option<String>,
6482
6483 #[serde(default, skip_serializing_if = "Option::is_none")]
6486 pub speed: Option<String>,
6487
6488 #[serde(default, skip_serializing_if = "Option::is_none")]
6491 pub fallback_credit: Option<Value>,
6492}
6493
6494#[derive(Debug, Clone, Serialize, Deserialize)]
6496pub struct CacheCreationDetails {
6497 #[serde(default)]
6499 pub ephemeral_1h_input_tokens: u32,
6500
6501 #[serde(default)]
6503 pub ephemeral_5m_input_tokens: u32,
6504}
6505
6506#[cfg(test)]
6507mod tests {
6508 use crate::io::ClaudeOutput;
6509
6510 #[test]
6511 fn test_subagent_usage_rollup_accumulates_task_results() {
6512 use super::SubagentUsageRollup;
6513
6514 let mut rollup = SubagentUsageRollup::default();
6515
6516 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}}"#;
6517 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
6518 assert!(rollup.observe(&output));
6519 assert_eq!(rollup.subagent_tokens, 10201);
6520 assert_eq!(rollup.agent_count, 1);
6521 assert_eq!(rollup.tool_uses, 3);
6522 assert_eq!(rollup.duration_ms, 1853);
6523
6524 assert!(!rollup.observe(&output));
6526 assert_eq!(rollup.agent_count, 1);
6527 assert_eq!(rollup.subagent_tokens, 10201);
6528
6529 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}}"#;
6531 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
6532 assert!(rollup.observe(&output));
6533 assert_eq!(rollup.agent_count, 2);
6534 assert_eq!(rollup.subagent_tokens, 10701);
6535 }
6536
6537 #[test]
6538 fn test_subagent_usage_rollup_ignores_non_task_results() {
6539 use super::SubagentUsageRollup;
6540
6541 let mut rollup = SubagentUsageRollup::default();
6542
6543 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}}"#;
6546 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
6547 assert!(!rollup.observe(&output));
6548
6549 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
6551 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
6552 assert!(!rollup.observe(&output));
6553
6554 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
6556 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
6557 assert!(!rollup.observe(&output));
6558
6559 assert_eq!(rollup, SubagentUsageRollup::default());
6560 }
6561
6562 #[test]
6563 fn test_subagent_usage_rollup_over_captured_session() {
6564 use super::SubagentUsageRollup;
6565
6566 let mut rollup = SubagentUsageRollup::default();
6567 let fixture =
6568 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
6569 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
6570 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
6571 rollup.observe(&output);
6572 }
6573 }
6574 assert_eq!(rollup.agent_count, 1);
6575 assert_eq!(rollup.subagent_tokens, 10201);
6576 }
6577
6578 #[test]
6579 fn test_system_message_init() {
6580 let json = r#"{
6581 "type": "system",
6582 "subtype": "init",
6583 "session_id": "test-session-123",
6584 "cwd": "/home/user/project",
6585 "model": "claude-sonnet-4",
6586 "tools": ["Bash", "Read", "Write"],
6587 "mcp_servers": [],
6588 "slash_commands": ["compact", "cost", "review"],
6589 "agents": ["Bash", "Explore", "Plan"],
6590 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
6591 "skills": [],
6592 "claude_code_version": "2.1.15",
6593 "apiKeySource": "none",
6594 "output_style": "default",
6595 "permissionMode": "default"
6596 }"#;
6597
6598 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6599 if let ClaudeOutput::System(sys) = output {
6600 assert!(sys.is_init());
6601 assert!(!sys.is_status());
6602 assert!(!sys.is_compact_boundary());
6603
6604 let init = sys.as_init().expect("Should parse as init");
6605 assert_eq!(init.session_id, "test-session-123");
6606 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
6607 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
6608 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
6609 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
6610 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
6611 assert_eq!(init.plugins.len(), 1);
6612 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
6613 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
6614 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
6615 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
6616 assert_eq!(
6617 init.permission_mode,
6618 Some(super::InitPermissionMode::Default)
6619 );
6620 } else {
6621 panic!("Expected System message");
6622 }
6623 }
6624
6625 #[test]
6626 fn test_system_message_init_from_real_capture() {
6627 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
6628 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6629 if let ClaudeOutput::System(sys) = output {
6630 let init = sys.as_init().expect("Should parse real init capture");
6631 assert_eq!(init.slash_commands.len(), 8);
6632 assert!(init.slash_commands.contains(&"compact".to_string()));
6633 assert!(init.slash_commands.contains(&"review".to_string()));
6634 assert_eq!(init.agents.len(), 5);
6635 assert!(init.agents.contains(&"Bash".to_string()));
6636 assert!(init.agents.contains(&"Explore".to_string()));
6637 assert_eq!(init.plugins.len(), 1);
6638 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
6639 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
6640 } else {
6641 panic!("Expected System message");
6642 }
6643 }
6644
6645 #[test]
6646 fn test_system_message_status() {
6647 let json = r#"{
6648 "type": "system",
6649 "subtype": "status",
6650 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6651 "status": "compacting",
6652 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
6653 }"#;
6654
6655 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6656 if let ClaudeOutput::System(sys) = output {
6657 assert!(sys.is_status());
6658 assert!(!sys.is_init());
6659
6660 let status = sys.as_status().expect("Should parse as status");
6661 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6662 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
6663 assert_eq!(
6664 status.uuid,
6665 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
6666 );
6667 } else {
6668 panic!("Expected System message");
6669 }
6670 }
6671
6672 #[test]
6673 fn test_system_message_status_null() {
6674 let json = r#"{
6675 "type": "system",
6676 "subtype": "status",
6677 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6678 "status": null,
6679 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
6680 }"#;
6681
6682 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6683 if let ClaudeOutput::System(sys) = output {
6684 let status = sys.as_status().expect("Should parse as status");
6685 assert_eq!(status.status, None);
6686 } else {
6687 panic!("Expected System message");
6688 }
6689 }
6690
6691 #[test]
6692 fn test_system_message_task_started() {
6693 let json = r#"{
6694 "type": "system",
6695 "subtype": "task_started",
6696 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6697 "task_id": "b6daf3f",
6698 "task_type": "local_bash",
6699 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
6700 "description": "Wait for CI on PR #12",
6701 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
6702 }"#;
6703
6704 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6705 if let ClaudeOutput::System(sys) = output {
6706 assert!(sys.is_task_started());
6707 assert!(!sys.is_task_progress());
6708 assert!(!sys.is_task_notification());
6709
6710 let task = sys.as_task_started().expect("Should parse as task_started");
6711 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
6712 assert_eq!(task.task_id, "b6daf3f");
6713 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
6714 assert_eq!(
6715 task.tool_use_id.as_deref(),
6716 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
6717 );
6718 assert_eq!(task.description, "Wait for CI on PR #12");
6719 } else {
6720 panic!("Expected System message");
6721 }
6722 }
6723
6724 #[test]
6725 fn test_system_message_task_started_agent() {
6726 let json = r#"{
6727 "type": "system",
6728 "subtype": "task_started",
6729 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6730 "task_id": "a4a7e0906e5fc64cc",
6731 "task_type": "local_agent",
6732 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6733 "description": "Explore Scene/ArrayScene duplication",
6734 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6735 }"#;
6736
6737 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6738 if let ClaudeOutput::System(sys) = output {
6739 let task = sys.as_task_started().expect("Should parse as task_started");
6740 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
6741 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
6742 } else {
6743 panic!("Expected System message");
6744 }
6745 }
6746
6747 #[test]
6748 fn test_system_message_task_progress() {
6749 let json = r#"{
6750 "type": "system",
6751 "subtype": "task_progress",
6752 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6753 "task_id": "a4a7e0906e5fc64cc",
6754 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6755 "description": "Reading src/jplephem/chebyshev.rs",
6756 "last_tool_name": "Read",
6757 "usage": {
6758 "duration_ms": 13996,
6759 "tool_uses": 9,
6760 "total_tokens": 38779
6761 },
6762 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6763 }"#;
6764
6765 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6766 if let ClaudeOutput::System(sys) = output {
6767 assert!(sys.is_task_progress());
6768 assert!(!sys.is_task_started());
6769
6770 let progress = sys
6771 .as_task_progress()
6772 .expect("Should parse as task_progress");
6773 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
6774 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
6775 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
6776 assert_eq!(progress.usage.duration_ms, 13996);
6777 assert_eq!(progress.usage.tool_uses, 9);
6778 assert_eq!(progress.usage.total_tokens, 38779);
6779 } else {
6780 panic!("Expected System message");
6781 }
6782 }
6783
6784 #[test]
6785 fn test_system_message_task_notification_completed() {
6786 let json = r#"{
6787 "type": "system",
6788 "subtype": "task_notification",
6789 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6790 "task_id": "a0ba761e9dc9c316f",
6791 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
6792 "status": "completed",
6793 "summary": "Agent \"Write Hipparcos data source doc\" completed",
6794 "output_file": "",
6795 "usage": {
6796 "duration_ms": 172300,
6797 "tool_uses": 11,
6798 "total_tokens": 42005
6799 },
6800 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
6801 }"#;
6802
6803 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6804 if let ClaudeOutput::System(sys) = output {
6805 assert!(sys.is_task_notification());
6806
6807 let notif = sys
6808 .as_task_notification()
6809 .expect("Should parse as task_notification");
6810 assert_eq!(notif.status, super::TaskStatus::Completed);
6811 assert_eq!(
6812 notif.summary,
6813 "Agent \"Write Hipparcos data source doc\" completed"
6814 );
6815 assert_eq!(notif.output_file, Some("".to_string()));
6816 assert_eq!(
6817 notif.tool_use_id,
6818 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
6819 );
6820 let usage = notif.usage.expect("Should have usage");
6821 assert_eq!(usage.duration_ms, 172300);
6822 assert_eq!(usage.tool_uses, 11);
6823 assert_eq!(usage.total_tokens, 42005);
6824 } else {
6825 panic!("Expected System message");
6826 }
6827 }
6828
6829 #[test]
6830 fn test_system_message_task_notification_failed_no_usage() {
6831 let json = r#"{
6832 "type": "system",
6833 "subtype": "task_notification",
6834 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
6835 "task_id": "b98f6a3",
6836 "status": "failed",
6837 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
6838 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
6839 }"#;
6840
6841 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6842 if let ClaudeOutput::System(sys) = output {
6843 let notif = sys
6844 .as_task_notification()
6845 .expect("Should parse as task_notification");
6846 assert_eq!(notif.status, super::TaskStatus::Failed);
6847 assert!(notif.tool_use_id.is_none());
6848 assert!(notif.usage.is_none());
6849 assert_eq!(
6850 notif.output_file,
6851 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
6852 );
6853 } else {
6854 panic!("Expected System message");
6855 }
6856 }
6857
6858 #[test]
6864 fn test_task_messages_roundtrip_through_value() {
6865 let cases = [
6866 r#"{"type":"system","subtype":"task_started","session_id":"s1",
6867 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
6868 "description":"Sleep 3s","uuid":"u1"}"#,
6869 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
6870 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
6871 "last_tool_name":"Bash",
6872 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6873 "uuid":"u2"}"#,
6874 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
6875 "task_id":"t1","tool_use_id":"tu1","status":"completed",
6876 "summary":"done","output_file":"",
6877 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6878 "uuid":"u3"}"#,
6879 ];
6880
6881 for json in cases {
6882 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6883 let value = serde_json::to_value(&output).unwrap();
6884 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
6885
6886 let ClaudeOutput::System(sys) = reparsed else {
6887 panic!("Expected System variant after round-trip");
6888 };
6889
6890 match sys.subtype {
6891 super::SystemSubtype::TaskStarted => {
6892 assert!(
6893 sys.as_task_started().is_some(),
6894 "as_task_started failed after round-trip"
6895 );
6896 }
6897 super::SystemSubtype::TaskProgress => {
6898 assert!(
6899 sys.as_task_progress().is_some(),
6900 "as_task_progress failed after round-trip"
6901 );
6902 }
6903 super::SystemSubtype::TaskNotification => {
6904 assert!(
6905 sys.as_task_notification().is_some(),
6906 "as_task_notification failed after round-trip"
6907 );
6908 }
6909 other => panic!("unexpected subtype after round-trip: {other:?}"),
6910 }
6911 }
6912 }
6913
6914 #[test]
6915 fn test_system_message_compact_boundary() {
6916 let json = r#"{
6917 "type": "system",
6918 "subtype": "compact_boundary",
6919 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6920 "compact_metadata": {
6921 "pre_tokens": 155285,
6922 "trigger": "auto"
6923 },
6924 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
6925 }"#;
6926
6927 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6928 if let ClaudeOutput::System(sys) = output {
6929 assert!(sys.is_compact_boundary());
6930 assert!(!sys.is_init());
6931 assert!(!sys.is_status());
6932
6933 let compact = sys
6934 .as_compact_boundary()
6935 .expect("Should parse as compact_boundary");
6936 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6937 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
6938 assert_eq!(
6939 compact.compact_metadata.trigger,
6940 super::CompactionTrigger::Auto
6941 );
6942 assert!(compact.summary.is_none());
6944 assert!(compact.leaf_message_count.is_none());
6945 assert!(compact.duration_ms.is_none());
6946 } else {
6947 panic!("Expected System message");
6948 }
6949 }
6950
6951 #[test]
6952 fn test_compact_boundary_with_summary_stats() {
6953 let json = r#"{
6955 "type": "system",
6956 "subtype": "compact_boundary",
6957 "session_id": "s1",
6958 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
6959 "summary": "Summarized the earlier exploration.",
6960 "leaf_message_count": 42,
6961 "duration_ms": 1234,
6962 "uuid": "u1"
6963 }"#;
6964 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6965 let ClaudeOutput::System(sys) = output else {
6966 panic!("Expected System message");
6967 };
6968 let compact = sys.as_compact_boundary().expect("compact_boundary");
6969 assert_eq!(
6970 compact.summary.as_deref(),
6971 Some("Summarized the earlier exploration.")
6972 );
6973 assert_eq!(compact.leaf_message_count, Some(42));
6974 assert_eq!(compact.duration_ms, Some(1234));
6975
6976 let json_alt = r#"{
6979 "type": "system",
6980 "subtype": "compact_boundary",
6981 "session_id": "s2",
6982 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
6983 "content": "alt-key summary",
6984 "message_count": 7
6985 }"#;
6986 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
6987 let ClaudeOutput::System(sys) = output else {
6988 panic!("Expected System message");
6989 };
6990 let compact = sys.as_compact_boundary().expect("compact_boundary");
6991 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
6992 assert_eq!(compact.leaf_message_count, Some(7));
6993 }
6994
6995 #[test]
6996 fn test_init_message_with_new_fields() {
6997 let json = r#"{
6998 "type": "system",
6999 "subtype": "init",
7000 "session_id": "test-session",
7001 "cwd": "/home/user",
7002 "model": "claude-opus-4-7",
7003 "tools": ["Bash"],
7004 "mcp_servers": [],
7005 "permissionMode": "default",
7006 "apiKeySource": "none",
7007 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
7008 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
7009 "fast_mode_state": "off",
7010 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
7011 "claude_code_version": "2.1.117"
7012 }"#;
7013
7014 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7015 if let ClaudeOutput::System(sys) = output {
7016 let init = sys.as_init().expect("Should parse as init");
7017 assert_eq!(
7018 init.uuid.as_deref(),
7019 Some("44841a0d-182d-493a-86b5-79800d3d9665")
7020 );
7021 assert!(init.memory_paths.is_some());
7022 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
7023 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
7024 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
7025 } else {
7026 panic!("Expected System message");
7027 }
7028 }
7029
7030 #[test]
7031 fn test_assistant_message_with_new_fields() {
7032 let json = r#"{
7033 "type": "assistant",
7034 "message": {
7035 "id": "msg_1",
7036 "type": "message",
7037 "role": "assistant",
7038 "model": "claude-opus-4-7",
7039 "content": [{"type": "text", "text": "Hello"}],
7040 "stop_reason": "end_turn",
7041 "stop_details": null,
7042 "context_management": null,
7043 "usage": {
7044 "input_tokens": 100,
7045 "output_tokens": 10,
7046 "cache_creation_input_tokens": 50,
7047 "cache_read_input_tokens": 0,
7048 "service_tier": "standard",
7049 "inference_geo": "not_available"
7050 }
7051 },
7052 "session_id": "abc",
7053 "uuid": "msg-uuid-123"
7054 }"#;
7055
7056 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7057 if let ClaudeOutput::Assistant(asst) = output {
7058 assert_eq!(asst.message.stop_details, None);
7059 assert_eq!(asst.message.context_management, None);
7060 let usage = asst.message.usage.unwrap();
7061 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
7062 } else {
7063 panic!("Expected Assistant message");
7064 }
7065 }
7066
7067 #[test]
7068 fn test_user_message_with_new_fields() {
7069 let json = r#"{
7070 "type": "user",
7071 "message": {
7072 "role": "user",
7073 "content": [{"type": "text", "text": "Hello"}]
7074 },
7075 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
7076 "parent_tool_use_id": "toolu_123",
7077 "uuid": "user-msg-456"
7078 }"#;
7079
7080 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7081 if let ClaudeOutput::User(user) = output {
7082 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
7083 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
7084 } else {
7085 panic!("Expected User message");
7086 }
7087 }
7088
7089 #[test]
7095 fn test_user_message_preserves_tool_use_result_and_timestamp() {
7096 let json = r#"{
7097 "type":"user",
7098 "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"}]},
7099 "parent_tool_use_id":null,
7100 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
7101 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
7102 "timestamp":"2026-05-12T23:12:04.121Z",
7103 "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"}}
7104 }"#;
7105
7106 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7107 let user = match output {
7108 ClaudeOutput::User(u) => u,
7109 other => panic!("Expected User message, got {:?}", other.message_type()),
7110 };
7111
7112 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
7113 let raw = user
7114 .tool_use_result
7115 .as_ref()
7116 .expect("tool_use_result must be captured");
7117 assert_eq!(raw["answers"]["Color"], "Blue");
7118 assert_eq!(raw["questions"][0]["header"], "Color");
7119
7120 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
7124 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
7125 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
7126 assert_eq!(
7127 reser["tool_use_result"]["questions"][0]["question"],
7128 "Which color do you prefer?"
7129 );
7130
7131 let typed: crate::AskUserQuestionInput = user
7134 .tool_use_result_as::<crate::AskUserQuestionInput>()
7135 .expect("tool_use_result present")
7136 .expect("AskUserQuestionInput parses");
7137 assert_eq!(typed.questions.len(), 1);
7138 assert_eq!(typed.questions[0].header, "Color");
7139 let answers = typed.answers.expect("answers populated");
7140 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
7141 }
7142
7143 #[test]
7146 fn test_user_message_without_tool_use_result_omits_field() {
7147 let json = r#"{
7148 "type":"user",
7149 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
7150 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
7151 }"#;
7152
7153 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7154 let user = match output {
7155 ClaudeOutput::User(u) => u,
7156 _ => panic!("Expected User message"),
7157 };
7158 assert!(user.tool_use_result.is_none());
7159 assert!(user.timestamp.is_none());
7160
7161 let reser = serde_json::to_value(&user).unwrap();
7162 assert!(reser.get("tool_use_result").is_none());
7163 assert!(reser.get("timestamp").is_none());
7164 }
7165
7166 #[test]
7170 fn test_subagent_result_exposes_token_accounting() {
7171 let json = r#"{
7172 "type":"user",
7173 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
7174 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
7175 "tool_use_result":{
7176 "status":"completed",
7177 "prompt":"Count the .rs files.",
7178 "agentId":"ac4f0276e9d4b6232",
7179 "agentType":"Explore",
7180 "content":[{"type":"text","text":"21"}],
7181 "resolvedModel":"claude-haiku-4-5-20251001",
7182 "totalDurationMs":6869,
7183 "totalTokens":7834,
7184 "totalToolUseCount":1,
7185 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
7186 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
7187 }
7188 }"#;
7189
7190 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7191 let user = match output {
7192 ClaudeOutput::User(u) => u,
7193 _ => panic!("Expected User message"),
7194 };
7195
7196 let result = user.subagent_result().expect("subagent result parses");
7197 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
7198 assert_eq!(
7199 result.resolved_model.as_deref(),
7200 Some("claude-haiku-4-5-20251001")
7201 );
7202 assert_eq!(result.total_tokens, Some(7834));
7203 assert_eq!(result.total_duration_ms, Some(6869));
7204 assert_eq!(result.total_tool_use_count, Some(1));
7205
7206 let usage = result.usage.expect("nested usage present");
7207 assert_eq!(usage.input_tokens, 6);
7208 assert_eq!(usage.cache_read_input_tokens, 7699);
7209
7210 let stats = result.tool_stats.expect("toolStats present");
7211 assert_eq!(stats.bash_count, 1);
7212 }
7213
7214 #[test]
7218 fn test_subagent_result_absent_for_non_task_result() {
7219 let json = r#"{
7220 "type":"user",
7221 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
7222 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
7223 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
7224 }"#;
7225
7226 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7227 let user = match output {
7228 ClaudeOutput::User(u) => u,
7229 _ => panic!("Expected User message"),
7230 };
7231
7232 let result = user.subagent_result().expect("lenient parse");
7233 assert_eq!(result.total_tokens, None);
7234 assert_eq!(result.agent_type, None);
7235 }
7236
7237 #[test]
7238 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
7239 use serde_json::Value;
7240
7241 let raw: Value = serde_json::from_str(
7242 r#"{
7243 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
7244 "fast_mode_state":"off",
7245 "fast_mode_disabled_reason":"not_first_party",
7246 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
7247 }"#,
7248 )
7249 .unwrap();
7250 crate::io::assert_fully_wrapped(&raw);
7251
7252 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7253 let ClaudeOutput::System(sys) = output else {
7254 panic!("expected System");
7255 };
7256 let init = sys.as_init().expect("parses as init");
7257 assert_eq!(
7258 init.fast_mode_disabled_reason,
7259 Some(crate::FastModeDisabledReason::NotFirstParty)
7260 );
7261 let errs = init.mcp_server_errors.unwrap();
7262 assert_eq!(errs.len(), 1);
7263 assert_eq!(errs[0].name, "broken");
7264 assert_eq!(errs[0].error_type, "invalid_config");
7265 }
7266
7267 #[test]
7268 fn test_code_change_published_fully_wrapped() {
7269 use super::{KnownSystemEvent, SystemSubtype};
7270 use serde_json::Value;
7271
7272 let raw: Value = serde_json::from_str(
7273 r#"{
7274 "type":"system","subtype":"code_change_published",
7275 "provider":"github","url":"https://github.com/owner/repo/pull/42",
7276 "repo":"owner/repo","identifier":"42",
7277 "uuid":"u1","session_id":"s1"
7278 }"#,
7279 )
7280 .unwrap();
7281 crate::io::assert_fully_wrapped(&raw);
7282
7283 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7284 let ClaudeOutput::System(sys) = output else {
7285 panic!("expected System");
7286 };
7287 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
7288 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
7289 panic!("expected CodeChangePublished event");
7290 };
7291 assert_eq!(msg.provider, "github");
7292 assert_eq!(msg.repo, "owner/repo");
7293 assert_eq!(msg.identifier, "42");
7294
7295 assert!(sys.is_code_change_published());
7296 assert!(!sys.is_vcs_state_changed());
7297 let direct = sys.as_code_change_published().expect("direct accessor");
7298 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
7299 assert!(sys.as_vcs_state_changed().is_none());
7300 }
7301
7302 #[test]
7303 fn test_feedback_draft_queued_fully_wrapped() {
7304 use super::{KnownSystemEvent, SystemSubtype};
7305 use serde_json::Value;
7306
7307 let raw: Value = serde_json::from_str(
7308 r#"{
7309 "type":"system","subtype":"feedback_draft_queued",
7310 "draft_id":"draft-1","draft_type":"bug_report",
7311 "title":"Tool output was truncated",
7312 "details_preview":"The last command omitted its final lines",
7313 "uuid":"u1","session_id":"s1","future_field":"preserved"
7314 }"#,
7315 )
7316 .unwrap();
7317 crate::io::assert_fully_wrapped(&raw);
7318
7319 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7320 let ClaudeOutput::System(sys) = output else {
7321 panic!("expected System");
7322 };
7323 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
7324 assert!(sys.is_feedback_draft_queued());
7325 assert!(!sys.is_vcs_state_changed());
7326
7327 let direct = sys
7328 .as_feedback_draft_queued()
7329 .expect("direct typed accessor");
7330 assert_eq!(direct.draft_id, "draft-1");
7331 assert_eq!(direct.draft_type, "bug_report");
7332 assert_eq!(direct.extra["future_field"], "preserved");
7333
7334 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
7335 panic!("expected FeedbackDraftQueued event");
7336 };
7337 assert_eq!(known.title, "Tool output was truncated");
7338 assert_eq!(
7339 sys.typed_value().expect("typed value")["future_field"],
7340 "preserved"
7341 );
7342 }
7343
7344 #[test]
7345 fn test_cloud_session_delta_fully_wrapped() {
7346 use super::{KnownSystemEvent, SystemSubtype};
7347 use serde_json::Value;
7348
7349 let raw: Value = serde_json::from_str(
7350 r#"{
7351 "type":"system","subtype":"cloud_session_delta",
7352 "seq":3,"changed":["serving","connection"],
7353 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
7354 "serving":{"state":"on"},"connection":{"state":"live"}},
7355 "uuid":"u1","session_id":"s1","future_field":"preserved"
7356 }"#,
7357 )
7358 .unwrap();
7359 crate::io::assert_fully_wrapped(&raw);
7360
7361 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7362 let ClaudeOutput::System(sys) = output else {
7363 panic!("expected System");
7364 };
7365 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
7366 assert!(sys.is_cloud_session_delta());
7367 assert!(!sys.is_feedback_draft_queued());
7368
7369 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
7370 assert_eq!(direct.seq, 3);
7371 assert_eq!(direct.changed, vec!["serving", "connection"]);
7372 assert_eq!(direct.cloud_session["id"], "session_abc");
7373 assert_eq!(direct.extra["future_field"], "preserved");
7374
7375 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
7376 panic!("expected CloudSessionDelta event");
7377 };
7378 assert_eq!(known.session_id, "s1");
7379 assert_eq!(
7380 sys.typed_value().expect("typed value")["future_field"],
7381 "preserved"
7382 );
7383 }
7384
7385 #[test]
7386 fn test_vcs_state_changed_fully_wrapped() {
7387 use super::{KnownSystemEvent, VcsMutationKind};
7388 use serde_json::Value;
7389
7390 for kind in ["commit", "push", "merge", "rebase"] {
7391 let raw: Value = serde_json::from_str(&format!(
7392 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
7393 kind
7394 ))
7395 .unwrap();
7396 crate::io::assert_fully_wrapped(&raw);
7397
7398 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7399 let ClaudeOutput::System(sys) = output else {
7400 panic!("expected System");
7401 };
7402 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
7403 panic!("expected VcsStateChanged event");
7404 };
7405 assert_eq!(msg.kind.as_str(), kind);
7406 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
7407 }
7408
7409 let raw: Value = serde_json::from_str(
7411 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
7412 )
7413 .unwrap();
7414 crate::io::assert_fully_wrapped(&raw);
7415 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7416 let ClaudeOutput::System(sys) = output else {
7417 panic!("expected System");
7418 };
7419 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
7420 panic!("expected VcsStateChanged event");
7421 };
7422 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
7423
7424 assert!(sys.is_vcs_state_changed());
7425 let direct = sys.as_vcs_state_changed().expect("direct accessor");
7426 assert_eq!(direct.cwd, "/repo");
7427 assert!(sys.as_code_change_published().is_none());
7428 }
7429
7430 #[test]
7431 fn test_assistant_aborted_and_resume_flags_roundtrip() {
7432 let json = r#"{
7433 "type":"assistant",
7434 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
7435 "session_id":"s1",
7436 "aborted":true,
7437 "resumed_from_incomplete_thinking":true
7438 }"#;
7439 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7440 let ClaudeOutput::Assistant(msg) = &output else {
7441 panic!("expected Assistant");
7442 };
7443 assert_eq!(msg.aborted, Some(true));
7444 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
7445 let reserialized = serde_json::to_string(&output).unwrap();
7446 assert!(reserialized.contains("\"aborted\":true"));
7447 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
7448
7449 let json = r#"{
7451 "type":"assistant",
7452 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
7453 "session_id":"s2"
7454 }"#;
7455 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7456 let reserialized = serde_json::to_string(&output).unwrap();
7457 assert!(!reserialized.contains("aborted"));
7458 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
7459 }
7460
7461 #[test]
7462 fn test_user_tool_result_meta_roundtrip() {
7463 let json = r#"{
7464 "type":"user",
7465 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
7466 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
7467 "tool_result_meta":[
7468 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
7469 {"id":"toolu_2","non_execution_kind":"permission-rule"}
7470 ]
7471 }"#;
7472 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7473 let ClaudeOutput::User(user) = &output else {
7474 panic!("expected User");
7475 };
7476 let meta = user.tool_result_meta.as_ref().unwrap();
7477 assert_eq!(meta.len(), 2);
7478 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
7479 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
7480 assert_eq!(meta[1].user_feedback, None);
7481
7482 let reserialized = serde_json::to_string(&output).unwrap();
7483 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
7484 assert!(!reserialized.contains("\"user_feedback\":null"));
7485 }
7486
7487 #[test]
7491 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
7492 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
7493 let with_scope = serde_json::json!({
7494 "trigger": "refusal",
7495 "direction": "retry",
7496 "scope": "local",
7497 "original_model": "claude-fable-5",
7498 "fallback_model": "claude-opus-5",
7499 "request_id": null,
7500 "content": "Refused; retried on fallback model.",
7501 "uuid": "u1",
7502 "session_id": "s1"
7503 });
7504 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
7505 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
7506 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
7507
7508 let mut without = with_scope.clone();
7511 without.as_object_mut().unwrap().remove("scope");
7512 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
7513 assert_eq!(msg.scope, None);
7514 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
7515
7516 assert_eq!(
7518 RefusalFallbackScope::from("workspace").as_str(),
7519 "workspace"
7520 );
7521 }
7522}