1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::Value;
3use std::fmt;
4use uuid::Uuid;
5
6use super::claude_output::ClaudeOutput;
7use super::content_blocks::{deserialize_content_blocks, ContentBlock};
8use super::rate_limit::RateLimitInfo;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum SystemSubtype {
17 Init,
18 Status,
19 CompactBoundary,
20 ThinkingTokens,
21 TaskStarted,
22 TaskProgress,
23 TaskUpdated,
24 TaskNotification,
25 ApiRetry,
26 ControlRequestProgress,
27 ModelRefusalFallback,
28 ModelRefusalNoFallback,
29 LocalCommandOutput,
30 HookStarted,
31 HookProgress,
32 HookResponse,
33 PluginInstall,
34 BackgroundTasksChanged,
35 SessionStateChanged,
36 WorkerShuttingDown,
37 CommandsChanged,
38 Notification,
39 FilesPersisted,
40 MemoryRecall,
41 ElicitationComplete,
42 PermissionDenied,
43 MirrorError,
44 Informational,
45 CodeChangePublished,
46 VcsStateChanged,
47 FeedbackDraftQueued,
48 CloudSessionDelta,
49 DevIntent,
50 TurnHandoffAvailable,
51 TurnPreempted,
52 PeerMessageHold,
53 PerTurnEffortChanged,
54 SessionTitleChanged,
55 InstructionSizeWarning,
56 FileAttachmentsMissing,
57 PermissionCheckStatus,
58 UiFocus,
59 UiInvalidate,
60 UiLog,
61 UiPanes,
62 UiScroll,
63 UiStatus,
64 UiToast,
65 Unknown(String),
67}
68
69impl SystemSubtype {
70 pub fn as_str(&self) -> &str {
71 match self {
72 Self::Init => "init",
73 Self::Status => "status",
74 Self::CompactBoundary => "compact_boundary",
75 Self::ThinkingTokens => "thinking_tokens",
76 Self::TaskStarted => "task_started",
77 Self::TaskProgress => "task_progress",
78 Self::TaskUpdated => "task_updated",
79 Self::TaskNotification => "task_notification",
80 Self::ApiRetry => "api_retry",
81 Self::ControlRequestProgress => "control_request_progress",
82 Self::ModelRefusalFallback => "model_refusal_fallback",
83 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
84 Self::LocalCommandOutput => "local_command_output",
85 Self::HookStarted => "hook_started",
86 Self::HookProgress => "hook_progress",
87 Self::HookResponse => "hook_response",
88 Self::PluginInstall => "plugin_install",
89 Self::BackgroundTasksChanged => "background_tasks_changed",
90 Self::SessionStateChanged => "session_state_changed",
91 Self::WorkerShuttingDown => "worker_shutting_down",
92 Self::CommandsChanged => "commands_changed",
93 Self::Notification => "notification",
94 Self::FilesPersisted => "files_persisted",
95 Self::MemoryRecall => "memory_recall",
96 Self::ElicitationComplete => "elicitation_complete",
97 Self::PermissionDenied => "permission_denied",
98 Self::MirrorError => "mirror_error",
99 Self::Informational => "informational",
100 Self::CodeChangePublished => "code_change_published",
101 Self::VcsStateChanged => "vcs_state_changed",
102 Self::FeedbackDraftQueued => "feedback_draft_queued",
103 Self::CloudSessionDelta => "cloud_session_delta",
104 Self::DevIntent => "dev_intent",
105 Self::TurnHandoffAvailable => "turn_handoff_available",
106 Self::TurnPreempted => "turn_preempted",
107 Self::PeerMessageHold => "peer_message_hold",
108 Self::PerTurnEffortChanged => "per_turn_effort_changed",
109 Self::SessionTitleChanged => "session_title_changed",
110 Self::InstructionSizeWarning => "instruction_size_warning",
111 Self::FileAttachmentsMissing => "file_attachments_missing",
112 Self::PermissionCheckStatus => "permission_check_status",
113 Self::UiFocus => "ui_focus",
114 Self::UiInvalidate => "ui_invalidate",
115 Self::UiLog => "ui_log",
116 Self::UiPanes => "ui_panes",
117 Self::UiScroll => "ui_scroll",
118 Self::UiStatus => "ui_status",
119 Self::UiToast => "ui_toast",
120 Self::Unknown(s) => s.as_str(),
121 }
122 }
123}
124
125impl fmt::Display for SystemSubtype {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.write_str(self.as_str())
128 }
129}
130
131impl From<&str> for SystemSubtype {
132 fn from(s: &str) -> Self {
133 match s {
134 "init" => Self::Init,
135 "status" => Self::Status,
136 "compact_boundary" => Self::CompactBoundary,
137 "thinking_tokens" => Self::ThinkingTokens,
138 "task_started" => Self::TaskStarted,
139 "task_progress" => Self::TaskProgress,
140 "task_updated" => Self::TaskUpdated,
141 "task_notification" => Self::TaskNotification,
142 "api_retry" => Self::ApiRetry,
143 "control_request_progress" => Self::ControlRequestProgress,
144 "model_refusal_fallback" => Self::ModelRefusalFallback,
145 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
146 "local_command_output" => Self::LocalCommandOutput,
147 "hook_started" => Self::HookStarted,
148 "hook_progress" => Self::HookProgress,
149 "hook_response" => Self::HookResponse,
150 "plugin_install" => Self::PluginInstall,
151 "background_tasks_changed" => Self::BackgroundTasksChanged,
152 "session_state_changed" => Self::SessionStateChanged,
153 "worker_shutting_down" => Self::WorkerShuttingDown,
154 "commands_changed" => Self::CommandsChanged,
155 "notification" => Self::Notification,
156 "files_persisted" => Self::FilesPersisted,
157 "memory_recall" => Self::MemoryRecall,
158 "elicitation_complete" => Self::ElicitationComplete,
159 "permission_denied" => Self::PermissionDenied,
160 "mirror_error" => Self::MirrorError,
161 "informational" => Self::Informational,
162 "code_change_published" => Self::CodeChangePublished,
163 "vcs_state_changed" => Self::VcsStateChanged,
164 "feedback_draft_queued" => Self::FeedbackDraftQueued,
165 "cloud_session_delta" => Self::CloudSessionDelta,
166 "dev_intent" => Self::DevIntent,
167 "turn_handoff_available" => Self::TurnHandoffAvailable,
168 "turn_preempted" => Self::TurnPreempted,
169 "peer_message_hold" => Self::PeerMessageHold,
170 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
171 "session_title_changed" => Self::SessionTitleChanged,
172 "instruction_size_warning" => Self::InstructionSizeWarning,
173 "file_attachments_missing" => Self::FileAttachmentsMissing,
174 "permission_check_status" => Self::PermissionCheckStatus,
175 "ui_focus" => Self::UiFocus,
176 "ui_invalidate" => Self::UiInvalidate,
177 "ui_log" => Self::UiLog,
178 "ui_panes" => Self::UiPanes,
179 "ui_scroll" => Self::UiScroll,
180 "ui_status" => Self::UiStatus,
181 "ui_toast" => Self::UiToast,
182 other => Self::Unknown(other.to_string()),
183 }
184 }
185}
186
187impl Serialize for SystemSubtype {
188 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
189 serializer.serialize_str(self.as_str())
190 }
191}
192
193impl<'de> Deserialize<'de> for SystemSubtype {
194 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
195 let s = String::deserialize(deserializer)?;
196 Ok(Self::from(s.as_str()))
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Hash)]
205pub enum MessageRole {
206 User,
207 Assistant,
208 Unknown(String),
210}
211
212impl MessageRole {
213 pub fn as_str(&self) -> &str {
214 match self {
215 Self::User => "user",
216 Self::Assistant => "assistant",
217 Self::Unknown(s) => s.as_str(),
218 }
219 }
220}
221
222impl fmt::Display for MessageRole {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 f.write_str(self.as_str())
225 }
226}
227
228impl From<&str> for MessageRole {
229 fn from(s: &str) -> Self {
230 match s {
231 "user" => Self::User,
232 "assistant" => Self::Assistant,
233 other => Self::Unknown(other.to_string()),
234 }
235 }
236}
237
238impl Serialize for MessageRole {
239 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
240 serializer.serialize_str(self.as_str())
241 }
242}
243
244impl<'de> Deserialize<'de> for MessageRole {
245 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
246 let s = String::deserialize(deserializer)?;
247 Ok(Self::from(s.as_str()))
248 }
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Hash)]
253pub enum CompactionTrigger {
254 Auto,
256 Manual,
258 Unknown(String),
260}
261
262impl CompactionTrigger {
263 pub fn as_str(&self) -> &str {
264 match self {
265 Self::Auto => "auto",
266 Self::Manual => "manual",
267 Self::Unknown(s) => s.as_str(),
268 }
269 }
270}
271
272impl fmt::Display for CompactionTrigger {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 f.write_str(self.as_str())
275 }
276}
277
278impl From<&str> for CompactionTrigger {
279 fn from(s: &str) -> Self {
280 match s {
281 "auto" => Self::Auto,
282 "manual" => Self::Manual,
283 other => Self::Unknown(other.to_string()),
284 }
285 }
286}
287
288impl Serialize for CompactionTrigger {
289 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
290 serializer.serialize_str(self.as_str())
291 }
292}
293
294impl<'de> Deserialize<'de> for CompactionTrigger {
295 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
296 let s = String::deserialize(deserializer)?;
297 Ok(Self::from(s.as_str()))
298 }
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Hash)]
303pub enum StopReason {
304 EndTurn,
306 MaxTokens,
308 ToolUse,
310 Unknown(String),
312}
313
314impl StopReason {
315 pub fn as_str(&self) -> &str {
316 match self {
317 Self::EndTurn => "end_turn",
318 Self::MaxTokens => "max_tokens",
319 Self::ToolUse => "tool_use",
320 Self::Unknown(s) => s.as_str(),
321 }
322 }
323}
324
325impl fmt::Display for StopReason {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 f.write_str(self.as_str())
328 }
329}
330
331impl From<&str> for StopReason {
332 fn from(s: &str) -> Self {
333 match s {
334 "end_turn" => Self::EndTurn,
335 "max_tokens" => Self::MaxTokens,
336 "tool_use" => Self::ToolUse,
337 other => Self::Unknown(other.to_string()),
338 }
339 }
340}
341
342impl Serialize for StopReason {
343 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
344 serializer.serialize_str(self.as_str())
345 }
346}
347
348impl<'de> Deserialize<'de> for StopReason {
349 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
350 let s = String::deserialize(deserializer)?;
351 Ok(Self::from(s.as_str()))
352 }
353}
354
355#[derive(Debug, Clone, PartialEq, Eq, Hash)]
357pub enum ApiKeySource {
358 None,
360 User,
361 Project,
362 Org,
363 Temporary,
364 Oauth,
365 Unknown(String),
367}
368
369impl ApiKeySource {
370 pub fn as_str(&self) -> &str {
371 match self {
372 Self::None => "none",
373 Self::User => "user",
374 Self::Project => "project",
375 Self::Org => "org",
376 Self::Temporary => "temporary",
377 Self::Oauth => "oauth",
378 Self::Unknown(s) => s.as_str(),
379 }
380 }
381}
382
383impl fmt::Display for ApiKeySource {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 f.write_str(self.as_str())
386 }
387}
388
389impl From<&str> for ApiKeySource {
390 fn from(s: &str) -> Self {
391 match s {
392 "none" => Self::None,
393 "user" => Self::User,
394 "project" => Self::Project,
395 "org" => Self::Org,
396 "temporary" => Self::Temporary,
397 "oauth" => Self::Oauth,
398 other => Self::Unknown(other.to_string()),
399 }
400 }
401}
402
403impl Serialize for ApiKeySource {
404 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
405 serializer.serialize_str(self.as_str())
406 }
407}
408
409impl<'de> Deserialize<'de> for ApiKeySource {
410 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
411 let s = String::deserialize(deserializer)?;
412 Ok(Self::from(s.as_str()))
413 }
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Hash)]
418pub enum OutputStyle {
419 Default,
421 Unknown(String),
423}
424
425impl OutputStyle {
426 pub fn as_str(&self) -> &str {
427 match self {
428 Self::Default => "default",
429 Self::Unknown(s) => s.as_str(),
430 }
431 }
432}
433
434impl fmt::Display for OutputStyle {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 f.write_str(self.as_str())
437 }
438}
439
440impl From<&str> for OutputStyle {
441 fn from(s: &str) -> Self {
442 match s {
443 "default" => Self::Default,
444 other => Self::Unknown(other.to_string()),
445 }
446 }
447}
448
449impl Serialize for OutputStyle {
450 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
451 serializer.serialize_str(self.as_str())
452 }
453}
454
455impl<'de> Deserialize<'de> for OutputStyle {
456 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
457 let s = String::deserialize(deserializer)?;
458 Ok(Self::from(s.as_str()))
459 }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Hash)]
464pub enum InitPermissionMode {
465 Default,
467 AcceptEdits,
468 BypassPermissions,
469 Plan,
470 DontAsk,
471 Auto,
472 Unknown(String),
474}
475
476impl InitPermissionMode {
477 pub fn as_str(&self) -> &str {
478 match self {
479 Self::Default => "default",
480 Self::AcceptEdits => "acceptEdits",
481 Self::BypassPermissions => "bypassPermissions",
482 Self::Plan => "plan",
483 Self::DontAsk => "dontAsk",
484 Self::Auto => "auto",
485 Self::Unknown(s) => s.as_str(),
486 }
487 }
488}
489
490impl fmt::Display for InitPermissionMode {
491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492 f.write_str(self.as_str())
493 }
494}
495
496impl From<&str> for InitPermissionMode {
497 fn from(s: &str) -> Self {
498 match s {
499 "default" => Self::Default,
500 "acceptEdits" => Self::AcceptEdits,
501 "bypassPermissions" => Self::BypassPermissions,
502 "plan" => Self::Plan,
503 "dontAsk" => Self::DontAsk,
504 "auto" => Self::Auto,
505 other => Self::Unknown(other.to_string()),
506 }
507 }
508}
509
510impl Serialize for InitPermissionMode {
511 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
512 serializer.serialize_str(self.as_str())
513 }
514}
515
516impl<'de> Deserialize<'de> for InitPermissionMode {
517 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
518 let s = String::deserialize(deserializer)?;
519 Ok(Self::from(s.as_str()))
520 }
521}
522
523#[derive(Debug, Clone, PartialEq, Eq, Hash)]
525pub enum StatusMessageStatus {
526 Compacting,
528 Requesting,
530 Unknown(String),
532}
533
534impl StatusMessageStatus {
535 pub fn as_str(&self) -> &str {
536 match self {
537 Self::Compacting => "compacting",
538 Self::Requesting => "requesting",
539 Self::Unknown(s) => s.as_str(),
540 }
541 }
542}
543
544impl fmt::Display for StatusMessageStatus {
545 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546 f.write_str(self.as_str())
547 }
548}
549
550impl From<&str> for StatusMessageStatus {
551 fn from(s: &str) -> Self {
552 match s {
553 "compacting" => Self::Compacting,
554 "requesting" => Self::Requesting,
555 other => Self::Unknown(other.to_string()),
556 }
557 }
558}
559
560impl Serialize for StatusMessageStatus {
561 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
562 serializer.serialize_str(self.as_str())
563 }
564}
565
566impl<'de> Deserialize<'de> for StatusMessageStatus {
567 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
568 let s = String::deserialize(deserializer)?;
569 Ok(Self::from(s.as_str()))
570 }
571}
572
573pub(crate) fn serialize_optional_uuid<S>(
575 uuid: &Option<Uuid>,
576 serializer: S,
577) -> Result<S::Ok, S::Error>
578where
579 S: Serializer,
580{
581 match uuid {
582 Some(id) => serializer.serialize_str(&id.to_string()),
583 None => serializer.serialize_none(),
584 }
585}
586
587pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
589where
590 D: Deserializer<'de>,
591{
592 let opt_str: Option<String> = Option::deserialize(deserializer)?;
593 match opt_str {
594 Some(s) => Uuid::parse_str(&s)
595 .map(Some)
596 .map_err(serde::de::Error::custom),
597 None => Ok(None),
598 }
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
604pub struct MessageOrigin {
605 pub kind: String,
606 #[serde(flatten)]
607 pub extra: serde_json::Map<String, Value>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
612pub struct SummarizeMetadata {
613 pub messages_summarized: u64,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 pub user_context: Option<String>,
616 #[serde(default, skip_serializing_if = "Option::is_none")]
617 pub direction: Option<String>,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
622pub struct McpMeta {
623 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
624 pub meta: Option<Value>,
625 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub structured_content: Option<Value>,
627 #[serde(default, skip_serializing_if = "Vec::is_empty")]
632 pub resource_links: Vec<ResourceLink>,
633}
634
635#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
639pub struct ResourceLink {
640 pub uri: String,
641 pub name: String,
642 #[serde(default, skip_serializing_if = "Option::is_none")]
643 pub title: Option<String>,
644 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub description: Option<String>,
646 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
647 pub mime_type: Option<String>,
648 #[serde(default, skip_serializing_if = "Option::is_none")]
649 pub size: Option<u64>,
650 #[serde(default, skip_serializing_if = "Option::is_none")]
651 pub annotations: Option<Value>,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
656pub struct ToolResultMeta {
657 pub id: String,
659 #[serde(default, skip_serializing_if = "Option::is_none")]
666 pub non_execution_kind: Option<String>,
667 #[serde(default, skip_serializing_if = "Option::is_none")]
669 pub user_feedback: Option<String>,
670 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub remedy: Option<ToolResultRemedy>,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
683 pub permission_decision: Option<ToolPermissionDecision>,
684}
685
686#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
690pub struct ToolPermissionDecision {
691 pub decision: PermissionDecisionOutcome,
693 pub source: String,
701 #[serde(default, skip_serializing_if = "Option::is_none")]
707 pub reason_type: Option<PermissionDecisionReasonType>,
708}
709
710#[derive(Debug, Clone, PartialEq, Eq, Hash)]
712pub enum PermissionDecisionOutcome {
713 Accept,
715 Reject,
717 Cancelled,
719 Unknown(String),
721}
722
723impl PermissionDecisionOutcome {
724 pub fn as_str(&self) -> &str {
725 match self {
726 Self::Accept => "accept",
727 Self::Reject => "reject",
728 Self::Cancelled => "cancelled",
729 Self::Unknown(s) => s.as_str(),
730 }
731 }
732}
733
734impl fmt::Display for PermissionDecisionOutcome {
735 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736 f.write_str(self.as_str())
737 }
738}
739
740impl From<&str> for PermissionDecisionOutcome {
741 fn from(s: &str) -> Self {
742 match s {
743 "accept" => Self::Accept,
744 "reject" => Self::Reject,
745 "cancelled" => Self::Cancelled,
746 other => Self::Unknown(other.to_string()),
747 }
748 }
749}
750
751impl Serialize for PermissionDecisionOutcome {
752 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
753 serializer.serialize_str(self.as_str())
754 }
755}
756
757impl<'de> Deserialize<'de> for PermissionDecisionOutcome {
758 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
759 let s = String::deserialize(deserializer)?;
760 Ok(Self::from(s.as_str()))
761 }
762}
763
764#[derive(Debug, Clone, PartialEq, Eq, Hash)]
767pub enum PermissionDecisionReasonType {
768 Rule,
770 Mode,
772 SubcommandResults,
774 PermissionPromptTool,
776 Hook,
778 AsyncAgent,
780 SandboxOverride,
782 WorkingDir,
784 SafetyCheck,
786 Classifier,
788 Other,
790 Unknown(String),
792}
793
794impl PermissionDecisionReasonType {
795 pub fn as_str(&self) -> &str {
796 match self {
797 Self::Rule => "rule",
798 Self::Mode => "mode",
799 Self::SubcommandResults => "subcommandResults",
800 Self::PermissionPromptTool => "permissionPromptTool",
801 Self::Hook => "hook",
802 Self::AsyncAgent => "asyncAgent",
803 Self::SandboxOverride => "sandboxOverride",
804 Self::WorkingDir => "workingDir",
805 Self::SafetyCheck => "safetyCheck",
806 Self::Classifier => "classifier",
807 Self::Other => "other",
808 Self::Unknown(s) => s.as_str(),
809 }
810 }
811}
812
813impl fmt::Display for PermissionDecisionReasonType {
814 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815 f.write_str(self.as_str())
816 }
817}
818
819impl From<&str> for PermissionDecisionReasonType {
820 fn from(s: &str) -> Self {
821 match s {
822 "rule" => Self::Rule,
823 "mode" => Self::Mode,
824 "subcommandResults" => Self::SubcommandResults,
825 "permissionPromptTool" => Self::PermissionPromptTool,
826 "hook" => Self::Hook,
827 "asyncAgent" => Self::AsyncAgent,
828 "sandboxOverride" => Self::SandboxOverride,
829 "workingDir" => Self::WorkingDir,
830 "safetyCheck" => Self::SafetyCheck,
831 "classifier" => Self::Classifier,
832 "other" => Self::Other,
833 other => Self::Unknown(other.to_string()),
834 }
835 }
836}
837
838impl Serialize for PermissionDecisionReasonType {
839 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
840 serializer.serialize_str(self.as_str())
841 }
842}
843
844impl<'de> Deserialize<'de> for PermissionDecisionReasonType {
845 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
846 let s = String::deserialize(deserializer)?;
847 Ok(Self::from(s.as_str()))
848 }
849}
850
851#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
856pub struct ToolResultRemedy {
857 pub kind: ToolResultRemedyKind,
860 #[serde(default, skip_serializing_if = "Option::is_none")]
864 pub servers: Option<Vec<String>>,
865 #[serde(default, skip_serializing_if = "Option::is_none")]
868 pub provider: Option<RemedyLoginProvider>,
869 #[serde(default, skip_serializing_if = "Option::is_none")]
873 pub path: Option<String>,
874 #[serde(default, skip_serializing_if = "Option::is_none")]
879 pub managed: Option<bool>,
880 #[serde(default, skip_serializing_if = "Option::is_none")]
882 pub feature: Option<RemedyFeature>,
883 #[serde(default, skip_serializing_if = "Option::is_none")]
885 pub cause: Option<RemedyFeatureCause>,
886 #[serde(default, skip_serializing_if = "Option::is_none")]
888 pub policy_kind: Option<RemedyPolicyKind>,
889}
890
891#[derive(Debug, Clone, PartialEq, Eq, Hash)]
893pub enum ToolResultRemedyKind {
894 McpNeedsAuth,
896 McpDisabled,
898 McpRequiredMissing,
900 AuthExpired,
902 AuthOverridden,
904 AuthMissingScope,
906 DesignNeedsAuthorization,
908 SandboxViolation,
910 OutsideReadsBlocked,
912 MemoryPaused,
914 FeatureDisabled,
916 PolicyDenied,
919 SpawnArgLimit,
921 StagedForReview,
923 Unknown(String),
925}
926
927impl ToolResultRemedyKind {
928 pub fn as_str(&self) -> &str {
929 match self {
930 Self::McpNeedsAuth => "mcp_needs_auth",
931 Self::McpDisabled => "mcp_disabled",
932 Self::McpRequiredMissing => "mcp_required_missing",
933 Self::AuthExpired => "auth_expired",
934 Self::AuthOverridden => "auth_overridden",
935 Self::AuthMissingScope => "auth_missing_scope",
936 Self::DesignNeedsAuthorization => "design_needs_authorization",
937 Self::SandboxViolation => "sandbox_violation",
938 Self::OutsideReadsBlocked => "outside_reads_blocked",
939 Self::MemoryPaused => "memory_paused",
940 Self::FeatureDisabled => "feature_disabled",
941 Self::PolicyDenied => "policy_denied",
942 Self::SpawnArgLimit => "spawn_arg_limit",
943 Self::StagedForReview => "staged_for_review",
944 Self::Unknown(s) => s.as_str(),
945 }
946 }
947}
948
949impl fmt::Display for ToolResultRemedyKind {
950 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
951 f.write_str(self.as_str())
952 }
953}
954
955impl From<&str> for ToolResultRemedyKind {
956 fn from(s: &str) -> Self {
957 match s {
958 "mcp_needs_auth" => Self::McpNeedsAuth,
959 "mcp_disabled" => Self::McpDisabled,
960 "mcp_required_missing" => Self::McpRequiredMissing,
961 "auth_expired" => Self::AuthExpired,
962 "auth_overridden" => Self::AuthOverridden,
963 "auth_missing_scope" => Self::AuthMissingScope,
964 "design_needs_authorization" => Self::DesignNeedsAuthorization,
965 "sandbox_violation" => Self::SandboxViolation,
966 "outside_reads_blocked" => Self::OutsideReadsBlocked,
967 "memory_paused" => Self::MemoryPaused,
968 "feature_disabled" => Self::FeatureDisabled,
969 "policy_denied" => Self::PolicyDenied,
970 "spawn_arg_limit" => Self::SpawnArgLimit,
971 "staged_for_review" => Self::StagedForReview,
972 other => Self::Unknown(other.to_string()),
973 }
974 }
975}
976
977impl Serialize for ToolResultRemedyKind {
978 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
979 serializer.serialize_str(self.as_str())
980 }
981}
982
983impl<'de> Deserialize<'de> for ToolResultRemedyKind {
984 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
985 let s = String::deserialize(deserializer)?;
986 Ok(Self::from(s.as_str()))
987 }
988}
989
990#[derive(Debug, Clone, PartialEq, Eq, Hash)]
992pub enum RemedyLoginProvider {
993 ClaudeAi,
995 ClaudeDesign,
997 Unknown(String),
999}
1000
1001impl RemedyLoginProvider {
1002 pub fn as_str(&self) -> &str {
1003 match self {
1004 Self::ClaudeAi => "claude_ai",
1005 Self::ClaudeDesign => "claude_design",
1006 Self::Unknown(s) => s.as_str(),
1007 }
1008 }
1009}
1010
1011impl fmt::Display for RemedyLoginProvider {
1012 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1013 f.write_str(self.as_str())
1014 }
1015}
1016
1017impl From<&str> for RemedyLoginProvider {
1018 fn from(s: &str) -> Self {
1019 match s {
1020 "claude_ai" => Self::ClaudeAi,
1021 "claude_design" => Self::ClaudeDesign,
1022 other => Self::Unknown(other.to_string()),
1023 }
1024 }
1025}
1026
1027impl Serialize for RemedyLoginProvider {
1028 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1029 serializer.serialize_str(self.as_str())
1030 }
1031}
1032
1033impl<'de> Deserialize<'de> for RemedyLoginProvider {
1034 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1035 let s = String::deserialize(deserializer)?;
1036 Ok(Self::from(s.as_str()))
1037 }
1038}
1039
1040#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1043pub enum RemedyFeature {
1044 Workflows,
1045 Artifacts,
1046 Unknown(String),
1048}
1049
1050impl RemedyFeature {
1051 pub fn as_str(&self) -> &str {
1052 match self {
1053 Self::Workflows => "workflows",
1054 Self::Artifacts => "artifacts",
1055 Self::Unknown(s) => s.as_str(),
1056 }
1057 }
1058}
1059
1060impl fmt::Display for RemedyFeature {
1061 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1062 f.write_str(self.as_str())
1063 }
1064}
1065
1066impl From<&str> for RemedyFeature {
1067 fn from(s: &str) -> Self {
1068 match s {
1069 "workflows" => Self::Workflows,
1070 "artifacts" => Self::Artifacts,
1071 other => Self::Unknown(other.to_string()),
1072 }
1073 }
1074}
1075
1076impl Serialize for RemedyFeature {
1077 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1078 serializer.serialize_str(self.as_str())
1079 }
1080}
1081
1082impl<'de> Deserialize<'de> for RemedyFeature {
1083 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1084 let s = String::deserialize(deserializer)?;
1085 Ok(Self::from(s.as_str()))
1086 }
1087}
1088
1089#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1092pub enum RemedyFeatureCause {
1093 ManagedSettings,
1096 OrgPolicy,
1098 Unavailable,
1100 UserSetting,
1103 Unknown(String),
1105}
1106
1107impl RemedyFeatureCause {
1108 pub fn as_str(&self) -> &str {
1109 match self {
1110 Self::ManagedSettings => "managed_settings",
1111 Self::OrgPolicy => "org_policy",
1112 Self::Unavailable => "unavailable",
1113 Self::UserSetting => "user_setting",
1114 Self::Unknown(s) => s.as_str(),
1115 }
1116 }
1117}
1118
1119impl fmt::Display for RemedyFeatureCause {
1120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1121 f.write_str(self.as_str())
1122 }
1123}
1124
1125impl From<&str> for RemedyFeatureCause {
1126 fn from(s: &str) -> Self {
1127 match s {
1128 "managed_settings" => Self::ManagedSettings,
1129 "org_policy" => Self::OrgPolicy,
1130 "unavailable" => Self::Unavailable,
1131 "user_setting" => Self::UserSetting,
1132 other => Self::Unknown(other.to_string()),
1133 }
1134 }
1135}
1136
1137impl Serialize for RemedyFeatureCause {
1138 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1139 serializer.serialize_str(self.as_str())
1140 }
1141}
1142
1143impl<'de> Deserialize<'de> for RemedyFeatureCause {
1144 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1145 let s = String::deserialize(deserializer)?;
1146 Ok(Self::from(s.as_str()))
1147 }
1148}
1149
1150#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1153pub enum RemedyPolicyKind {
1154 OrgDenied,
1156 Latched,
1159 Unknown(String),
1161}
1162
1163impl RemedyPolicyKind {
1164 pub fn as_str(&self) -> &str {
1165 match self {
1166 Self::OrgDenied => "org_denied",
1167 Self::Latched => "latched",
1168 Self::Unknown(s) => s.as_str(),
1169 }
1170 }
1171}
1172
1173impl fmt::Display for RemedyPolicyKind {
1174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1175 f.write_str(self.as_str())
1176 }
1177}
1178
1179impl From<&str> for RemedyPolicyKind {
1180 fn from(s: &str) -> Self {
1181 match s {
1182 "org_denied" => Self::OrgDenied,
1183 "latched" => Self::Latched,
1184 other => Self::Unknown(other.to_string()),
1185 }
1186 }
1187}
1188
1189impl Serialize for RemedyPolicyKind {
1190 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1191 serializer.serialize_str(self.as_str())
1192 }
1193}
1194
1195impl<'de> Deserialize<'de> for RemedyPolicyKind {
1196 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1197 let s = String::deserialize(deserializer)?;
1198 Ok(Self::from(s.as_str()))
1199 }
1200}
1201
1202#[derive(Debug, Clone, Serialize, Deserialize)]
1204pub struct UserMessage {
1205 pub message: MessageContent,
1206 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1207 #[serde(
1208 serialize_with = "serialize_optional_uuid",
1209 deserialize_with = "deserialize_optional_uuid"
1210 )]
1211 pub session_id: Option<Uuid>,
1212 #[serde(skip_serializing_if = "Option::is_none")]
1214 pub parent_tool_use_id: Option<String>,
1215 #[serde(skip_serializing_if = "Option::is_none")]
1217 pub uuid: Option<String>,
1218 #[serde(skip_serializing_if = "Option::is_none")]
1220 pub timestamp: Option<String>,
1221 #[serde(skip_serializing_if = "Option::is_none")]
1228 pub tool_use_result: Option<serde_json::Value>,
1229 #[serde(default, skip_serializing_if = "Option::is_none")]
1234 pub agent_id: Option<String>,
1235 #[serde(skip_serializing_if = "Option::is_none")]
1238 pub subagent_type: Option<String>,
1239 #[serde(skip_serializing_if = "Option::is_none")]
1241 pub task_description: Option<String>,
1242 #[serde(skip_serializing_if = "Option::is_none")]
1243 pub origin: Option<MessageOrigin>,
1244 #[serde(skip_serializing_if = "Option::is_none")]
1245 pub priority: Option<String>,
1246 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1247 pub is_synthetic: Option<bool>,
1248 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1249 pub should_query: Option<bool>,
1250 #[serde(default, skip_serializing_if = "Option::is_none")]
1251 pub is_meta: Option<bool>,
1252 #[serde(default, skip_serializing_if = "Option::is_none")]
1253 pub is_visible_in_transcript_only: Option<bool>,
1254 #[serde(default, skip_serializing_if = "Option::is_none")]
1255 pub is_virtual: Option<bool>,
1256 #[serde(default, skip_serializing_if = "Option::is_none")]
1257 pub is_compact_summary: Option<bool>,
1258 #[serde(skip_serializing_if = "Option::is_none")]
1259 pub summarize_metadata: Option<SummarizeMetadata>,
1260 #[serde(skip_serializing_if = "Option::is_none")]
1261 pub mcp_meta: Option<McpMeta>,
1262 #[serde(skip_serializing_if = "Option::is_none")]
1265 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1266 #[serde(skip_serializing_if = "Option::is_none")]
1267 pub source_tool_use_id: Option<String>,
1268 #[serde(skip_serializing_if = "Option::is_none")]
1269 pub source_tool_assistant_uuid: Option<String>,
1270 #[serde(skip_serializing_if = "Option::is_none")]
1271 pub image_paste_ids: Option<Vec<u64>>,
1272 #[serde(skip_serializing_if = "Option::is_none")]
1273 pub client_platform: Option<String>,
1274 #[serde(skip_serializing_if = "Option::is_none")]
1275 pub inbound_origin: Option<String>,
1276 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1277 pub is_replay: Option<bool>,
1278 #[serde(skip_serializing_if = "Option::is_none")]
1279 pub file_attachments: Option<Vec<Value>>,
1280 #[serde(default, skip_serializing_if = "Option::is_none")]
1282 pub seeded_summon: Option<bool>,
1283 #[serde(default, skip_serializing_if = "Option::is_none")]
1286 pub client_composed: Option<bool>,
1287 #[serde(default, skip_serializing_if = "Option::is_none")]
1292 pub initiator: Option<String>,
1293 #[serde(default, skip_serializing_if = "Option::is_none")]
1299 pub pasted_content: Option<Vec<Value>>,
1300 #[serde(default, skip_serializing_if = "Option::is_none")]
1304 pub historical: Option<bool>,
1305 #[serde(default, skip_serializing_if = "Option::is_none")]
1311 pub server_received_wall_ms: Option<f64>,
1312 #[serde(default, skip_serializing_if = "Option::is_none")]
1316 pub refused_message_id: Option<String>,
1317}
1318
1319impl UserMessage {
1320 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1328 &self,
1329 ) -> Option<Result<T, serde_json::Error>> {
1330 self.tool_use_result
1331 .as_ref()
1332 .map(|v| serde_json::from_value(v.clone()))
1333 }
1334
1335 pub fn subagent_result(&self) -> Option<SubagentResult> {
1346 self.tool_use_result
1347 .as_ref()
1348 .and_then(|v| serde_json::from_value(v.clone()).ok())
1349 }
1350}
1351
1352#[derive(Debug, Clone, Serialize, Deserialize)]
1363pub struct SubagentResult {
1364 #[serde(skip_serializing_if = "Option::is_none")]
1366 pub status: Option<String>,
1367 #[serde(skip_serializing_if = "Option::is_none")]
1369 pub prompt: Option<String>,
1370 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1372 pub agent_id: Option<String>,
1373 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1375 pub agent_type: Option<String>,
1376 #[serde(
1378 default,
1379 deserialize_with = "deserialize_content_blocks",
1380 skip_serializing_if = "Vec::is_empty"
1381 )]
1382 pub content: Vec<ContentBlock>,
1383 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1385 pub resolved_model: Option<String>,
1386 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1388 pub total_duration_ms: Option<u64>,
1389 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1391 pub total_tokens: Option<u64>,
1392 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1394 pub total_tool_use_count: Option<u64>,
1395 #[serde(skip_serializing_if = "Option::is_none")]
1397 pub usage: Option<super::result::UsageInfo>,
1398 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1400 pub tool_stats: Option<SubagentToolStats>,
1401}
1402
1403#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1408#[serde(rename_all = "camelCase")]
1409pub struct SubagentToolStats {
1410 #[serde(default)]
1411 pub read_count: u64,
1412 #[serde(default)]
1413 pub search_count: u64,
1414 #[serde(default)]
1415 pub bash_count: u64,
1416 #[serde(default)]
1417 pub edit_file_count: u64,
1418 #[serde(default)]
1419 pub lines_added: u64,
1420 #[serde(default)]
1421 pub lines_removed: u64,
1422 #[serde(default)]
1423 pub other_tool_count: u64,
1424 #[serde(flatten)]
1425 pub extra: serde_json::Map<String, Value>,
1426}
1427
1428#[derive(Debug, Clone, Default, PartialEq, Eq)]
1455pub struct SubagentUsageRollup {
1456 pub subagent_tokens: u64,
1459 pub agent_count: u32,
1461 pub tool_uses: u64,
1463 pub duration_ms: u64,
1465 seen_agent_ids: std::collections::BTreeSet<String>,
1466}
1467
1468impl SubagentUsageRollup {
1469 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1475 match output {
1476 ClaudeOutput::User(user) => self.observe_user(user),
1477 _ => false,
1478 }
1479 }
1480
1481 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1488 let Some(result) = user.subagent_result() else {
1489 return false;
1490 };
1491 if result.total_tokens.is_none() && result.agent_id.is_none() {
1492 return false;
1493 }
1494 if let Some(agent_id) = &result.agent_id {
1495 if !self.seen_agent_ids.insert(agent_id.clone()) {
1496 return false;
1497 }
1498 }
1499 self.agent_count += 1;
1500 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1501 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1502 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1503 true
1504 }
1505}
1506
1507#[derive(Debug, Clone, Serialize, Deserialize)]
1509pub struct MessageContent {
1510 pub role: MessageRole,
1511 #[serde(deserialize_with = "deserialize_content_blocks")]
1512 pub content: Vec<ContentBlock>,
1513}
1514
1515#[derive(Debug, Clone, Serialize, Deserialize)]
1517pub struct SystemMessage {
1518 pub subtype: SystemSubtype,
1519 #[serde(flatten)]
1520 pub data: Value, }
1522
1523impl SystemMessage {
1524 pub fn is_init(&self) -> bool {
1526 self.subtype == SystemSubtype::Init
1527 }
1528
1529 pub fn is_status(&self) -> bool {
1531 self.subtype == SystemSubtype::Status
1532 }
1533
1534 pub fn is_compact_boundary(&self) -> bool {
1536 self.subtype == SystemSubtype::CompactBoundary
1537 }
1538
1539 pub fn as_init(&self) -> Option<InitMessage> {
1541 if self.subtype != SystemSubtype::Init {
1542 return None;
1543 }
1544 serde_json::from_value(self.data.clone()).ok()
1545 }
1546
1547 pub fn as_status(&self) -> Option<StatusMessage> {
1549 if self.subtype != SystemSubtype::Status {
1550 return None;
1551 }
1552 serde_json::from_value(self.data.clone()).ok()
1553 }
1554
1555 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1557 if self.subtype != SystemSubtype::CompactBoundary {
1558 return None;
1559 }
1560 serde_json::from_value(self.data.clone()).ok()
1561 }
1562
1563 pub fn is_task_started(&self) -> bool {
1565 self.subtype == SystemSubtype::TaskStarted
1566 }
1567
1568 pub fn is_task_progress(&self) -> bool {
1570 self.subtype == SystemSubtype::TaskProgress
1571 }
1572
1573 pub fn is_task_notification(&self) -> bool {
1575 self.subtype == SystemSubtype::TaskNotification
1576 }
1577
1578 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1580 if self.subtype != SystemSubtype::TaskStarted {
1581 return None;
1582 }
1583 serde_json::from_value(self.data.clone()).ok()
1584 }
1585
1586 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1588 if self.subtype != SystemSubtype::TaskProgress {
1589 return None;
1590 }
1591 serde_json::from_value(self.data.clone()).ok()
1592 }
1593
1594 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1596 if self.subtype != SystemSubtype::TaskNotification {
1597 return None;
1598 }
1599 serde_json::from_value(self.data.clone()).ok()
1600 }
1601
1602 pub fn is_task_updated(&self) -> bool {
1604 self.subtype == SystemSubtype::TaskUpdated
1605 }
1606
1607 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1609 if self.subtype != SystemSubtype::TaskUpdated {
1610 return None;
1611 }
1612 serde_json::from_value(self.data.clone()).ok()
1613 }
1614
1615 pub fn is_thinking_tokens(&self) -> bool {
1617 self.subtype == SystemSubtype::ThinkingTokens
1618 }
1619
1620 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1622 if self.subtype != SystemSubtype::ThinkingTokens {
1623 return None;
1624 }
1625 serde_json::from_value(self.data.clone()).ok()
1626 }
1627
1628 pub fn is_code_change_published(&self) -> bool {
1630 self.subtype == SystemSubtype::CodeChangePublished
1631 }
1632
1633 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1635 if self.subtype != SystemSubtype::CodeChangePublished {
1636 return None;
1637 }
1638 serde_json::from_value(self.data.clone()).ok()
1639 }
1640
1641 pub fn is_vcs_state_changed(&self) -> bool {
1643 self.subtype == SystemSubtype::VcsStateChanged
1644 }
1645
1646 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1648 if self.subtype != SystemSubtype::VcsStateChanged {
1649 return None;
1650 }
1651 serde_json::from_value(self.data.clone()).ok()
1652 }
1653
1654 pub fn is_feedback_draft_queued(&self) -> bool {
1656 self.subtype == SystemSubtype::FeedbackDraftQueued
1657 }
1658
1659 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1661 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1662 return None;
1663 }
1664 serde_json::from_value(self.data.clone()).ok()
1665 }
1666
1667 pub fn is_cloud_session_delta(&self) -> bool {
1669 self.subtype == SystemSubtype::CloudSessionDelta
1670 }
1671
1672 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1674 if self.subtype != SystemSubtype::CloudSessionDelta {
1675 return None;
1676 }
1677 serde_json::from_value(self.data.clone()).ok()
1678 }
1679
1680 pub fn is_dev_intent(&self) -> bool {
1682 self.subtype == SystemSubtype::DevIntent
1683 }
1684
1685 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1687 if self.subtype != SystemSubtype::DevIntent {
1688 return None;
1689 }
1690 serde_json::from_value(self.data.clone()).ok()
1691 }
1692
1693 pub fn is_turn_handoff_available(&self) -> bool {
1695 self.subtype == SystemSubtype::TurnHandoffAvailable
1696 }
1697
1698 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1700 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1701 return None;
1702 }
1703 serde_json::from_value(self.data.clone()).ok()
1704 }
1705
1706 pub fn is_turn_preempted(&self) -> bool {
1708 self.subtype == SystemSubtype::TurnPreempted
1709 }
1710
1711 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1713 if self.subtype != SystemSubtype::TurnPreempted {
1714 return None;
1715 }
1716 serde_json::from_value(self.data.clone()).ok()
1717 }
1718
1719 pub fn is_peer_message_hold(&self) -> bool {
1721 self.subtype == SystemSubtype::PeerMessageHold
1722 }
1723
1724 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1726 if self.subtype != SystemSubtype::PeerMessageHold {
1727 return None;
1728 }
1729 serde_json::from_value(self.data.clone()).ok()
1730 }
1731
1732 pub fn is_per_turn_effort_changed(&self) -> bool {
1734 self.subtype == SystemSubtype::PerTurnEffortChanged
1735 }
1736
1737 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1739 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1740 return None;
1741 }
1742 serde_json::from_value(self.data.clone()).ok()
1743 }
1744
1745 pub fn is_session_title_changed(&self) -> bool {
1747 self.subtype == SystemSubtype::SessionTitleChanged
1748 }
1749
1750 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1752 if self.subtype != SystemSubtype::SessionTitleChanged {
1753 return None;
1754 }
1755 serde_json::from_value(self.data.clone()).ok()
1756 }
1757
1758 pub fn is_instruction_size_warning(&self) -> bool {
1760 self.subtype == SystemSubtype::InstructionSizeWarning
1761 }
1762
1763 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1765 if self.subtype != SystemSubtype::InstructionSizeWarning {
1766 return None;
1767 }
1768 serde_json::from_value(self.data.clone()).ok()
1769 }
1770
1771 pub fn is_permission_check_status(&self) -> bool {
1773 self.subtype == SystemSubtype::PermissionCheckStatus
1774 }
1775
1776 pub fn as_permission_check_status(&self) -> Option<PermissionCheckStatusMessage> {
1778 if self.subtype != SystemSubtype::PermissionCheckStatus {
1779 return None;
1780 }
1781 serde_json::from_value(self.data.clone()).ok()
1782 }
1783
1784 pub fn is_file_attachments_missing(&self) -> bool {
1786 self.subtype == SystemSubtype::FileAttachmentsMissing
1787 }
1788
1789 pub fn as_file_attachments_missing(&self) -> Option<FileAttachmentsMissingMessage> {
1791 if self.subtype != SystemSubtype::FileAttachmentsMissing {
1792 return None;
1793 }
1794 serde_json::from_value(self.data.clone()).ok()
1795 }
1796
1797 pub fn is_ui_focus(&self) -> bool {
1799 self.subtype == SystemSubtype::UiFocus
1800 }
1801
1802 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1804 if self.subtype != SystemSubtype::UiFocus {
1805 return None;
1806 }
1807 serde_json::from_value(self.data.clone()).ok()
1808 }
1809
1810 pub fn is_ui_invalidate(&self) -> bool {
1812 self.subtype == SystemSubtype::UiInvalidate
1813 }
1814
1815 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1817 if self.subtype != SystemSubtype::UiInvalidate {
1818 return None;
1819 }
1820 serde_json::from_value(self.data.clone()).ok()
1821 }
1822
1823 pub fn is_ui_log(&self) -> bool {
1825 self.subtype == SystemSubtype::UiLog
1826 }
1827
1828 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1830 if self.subtype != SystemSubtype::UiLog {
1831 return None;
1832 }
1833 serde_json::from_value(self.data.clone()).ok()
1834 }
1835
1836 pub fn is_ui_panes(&self) -> bool {
1838 self.subtype == SystemSubtype::UiPanes
1839 }
1840
1841 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1843 if self.subtype != SystemSubtype::UiPanes {
1844 return None;
1845 }
1846 serde_json::from_value(self.data.clone()).ok()
1847 }
1848
1849 pub fn is_ui_scroll(&self) -> bool {
1851 self.subtype == SystemSubtype::UiScroll
1852 }
1853
1854 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1856 if self.subtype != SystemSubtype::UiScroll {
1857 return None;
1858 }
1859 serde_json::from_value(self.data.clone()).ok()
1860 }
1861
1862 pub fn is_ui_status(&self) -> bool {
1864 self.subtype == SystemSubtype::UiStatus
1865 }
1866
1867 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1869 if self.subtype != SystemSubtype::UiStatus {
1870 return None;
1871 }
1872 serde_json::from_value(self.data.clone()).ok()
1873 }
1874
1875 pub fn is_ui_toast(&self) -> bool {
1877 self.subtype == SystemSubtype::UiToast
1878 }
1879
1880 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
1882 if self.subtype != SystemSubtype::UiToast {
1883 return None;
1884 }
1885 serde_json::from_value(self.data.clone()).ok()
1886 }
1887
1888 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1890 macro_rules! parse {
1891 ($variant:ident, $ty:ty) => {
1892 serde_json::from_value::<$ty>(self.data.clone())
1893 .ok()
1894 .map(KnownSystemEvent::$variant)
1895 };
1896 }
1897
1898 match self.subtype {
1899 SystemSubtype::Init => parse!(Init, InitMessage),
1900 SystemSubtype::Status => parse!(Status, StatusMessage),
1901 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1902 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1903 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1904 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1905 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1906 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1907 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1908 SystemSubtype::ControlRequestProgress => {
1909 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1910 }
1911 SystemSubtype::ModelRefusalFallback => {
1912 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1913 }
1914 SystemSubtype::ModelRefusalNoFallback => {
1915 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1916 }
1917 SystemSubtype::LocalCommandOutput => {
1918 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1919 }
1920 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1921 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1922 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1923 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1924 SystemSubtype::BackgroundTasksChanged => {
1925 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1926 }
1927 SystemSubtype::SessionStateChanged => {
1928 parse!(SessionStateChanged, SessionStateChangedMessage)
1929 }
1930 SystemSubtype::WorkerShuttingDown => {
1931 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1932 }
1933 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1934 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1935 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1936 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1937 SystemSubtype::ElicitationComplete => {
1938 parse!(ElicitationComplete, ElicitationCompleteMessage)
1939 }
1940 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1941 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1942 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1943 SystemSubtype::CodeChangePublished => {
1944 parse!(CodeChangePublished, CodeChangePublishedMessage)
1945 }
1946 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1947 SystemSubtype::FeedbackDraftQueued => {
1948 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1949 }
1950 SystemSubtype::CloudSessionDelta => {
1951 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1952 }
1953 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1954 SystemSubtype::TurnHandoffAvailable => {
1955 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1956 }
1957 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1958 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1959 SystemSubtype::PerTurnEffortChanged => {
1960 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1961 }
1962 SystemSubtype::SessionTitleChanged => {
1963 parse!(SessionTitleChanged, SessionTitleChangedMessage)
1964 }
1965 SystemSubtype::InstructionSizeWarning => {
1966 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
1967 }
1968 SystemSubtype::FileAttachmentsMissing => {
1969 parse!(FileAttachmentsMissing, FileAttachmentsMissingMessage)
1970 }
1971 SystemSubtype::PermissionCheckStatus => {
1972 parse!(PermissionCheckStatus, PermissionCheckStatusMessage)
1973 }
1974 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
1975 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
1976 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
1977 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
1978 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
1979 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
1980 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
1981 SystemSubtype::Unknown(_) => None,
1982 }
1983 }
1984
1985 pub fn typed_value(&self) -> Option<Value> {
1994 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1995 parsed.and_then(|v| serde_json::to_value(v).ok())
1996 }
1997 match self.subtype {
1998 SystemSubtype::Init => reserialize(self.as_init()),
1999 SystemSubtype::Status => reserialize(self.as_status()),
2000 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
2001 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
2002 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
2003 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
2004 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
2005 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
2006 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
2007 SystemSubtype::ControlRequestProgress => {
2008 reserialize(parse_system::<ControlRequestProgressMessage>(self))
2009 }
2010 SystemSubtype::ModelRefusalFallback => {
2011 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
2012 }
2013 SystemSubtype::ModelRefusalNoFallback => {
2014 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
2015 }
2016 SystemSubtype::LocalCommandOutput => {
2017 reserialize(parse_system::<LocalCommandOutputMessage>(self))
2018 }
2019 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
2020 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
2021 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
2022 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
2023 SystemSubtype::BackgroundTasksChanged => {
2024 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
2025 }
2026 SystemSubtype::SessionStateChanged => {
2027 reserialize(parse_system::<SessionStateChangedMessage>(self))
2028 }
2029 SystemSubtype::WorkerShuttingDown => {
2030 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
2031 }
2032 SystemSubtype::CommandsChanged => {
2033 reserialize(parse_system::<CommandsChangedMessage>(self))
2034 }
2035 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
2036 SystemSubtype::FilesPersisted => {
2037 reserialize(parse_system::<FilesPersistedMessage>(self))
2038 }
2039 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
2040 SystemSubtype::ElicitationComplete => {
2041 reserialize(parse_system::<ElicitationCompleteMessage>(self))
2042 }
2043 SystemSubtype::PermissionDenied => {
2044 reserialize(parse_system::<PermissionDeniedMessage>(self))
2045 }
2046 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
2047 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
2048 SystemSubtype::CodeChangePublished => {
2049 reserialize(parse_system::<CodeChangePublishedMessage>(self))
2050 }
2051 SystemSubtype::VcsStateChanged => {
2052 reserialize(parse_system::<VcsStateChangedMessage>(self))
2053 }
2054 SystemSubtype::FeedbackDraftQueued => {
2055 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
2056 }
2057 SystemSubtype::CloudSessionDelta => {
2058 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
2059 }
2060 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
2061 SystemSubtype::TurnHandoffAvailable => {
2062 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
2063 }
2064 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
2065 SystemSubtype::PeerMessageHold => {
2066 reserialize(parse_system::<PeerMessageHoldMessage>(self))
2067 }
2068 SystemSubtype::PerTurnEffortChanged => {
2069 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
2070 }
2071 SystemSubtype::SessionTitleChanged => {
2072 reserialize(parse_system::<SessionTitleChangedMessage>(self))
2073 }
2074 SystemSubtype::InstructionSizeWarning => {
2075 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
2076 }
2077 SystemSubtype::FileAttachmentsMissing => {
2078 reserialize(parse_system::<FileAttachmentsMissingMessage>(self))
2079 }
2080 SystemSubtype::PermissionCheckStatus => {
2081 reserialize(parse_system::<PermissionCheckStatusMessage>(self))
2082 }
2083 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
2084 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
2085 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
2086 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
2087 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
2088 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
2089 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
2090 SystemSubtype::Unknown(_) => None,
2091 }
2092 }
2093}
2094
2095fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
2096 serde_json::from_value(message.data.clone()).ok()
2097}
2098
2099#[allow(clippy::large_enum_variant)]
2105#[derive(Debug, Clone, Serialize, Deserialize)]
2106pub enum KnownSystemEvent {
2107 Init(InitMessage),
2108 Status(StatusMessage),
2109 CompactBoundary(CompactBoundaryMessage),
2110 ThinkingTokens(ThinkingTokensMessage),
2111 TaskStarted(TaskStartedMessage),
2112 TaskProgress(TaskProgressMessage),
2113 TaskUpdated(TaskUpdatedMessage),
2114 TaskNotification(TaskNotificationMessage),
2115 ApiRetry(ApiRetryMessage),
2116 ControlRequestProgress(ControlRequestProgressMessage),
2117 ModelRefusalFallback(ModelRefusalFallbackMessage),
2118 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
2119 LocalCommandOutput(LocalCommandOutputMessage),
2120 HookStarted(HookStartedMessage),
2121 HookProgress(HookProgressMessage),
2122 HookResponse(HookResponseMessage),
2123 PluginInstall(PluginInstallMessage),
2124 BackgroundTasksChanged(BackgroundTasksChangedMessage),
2125 SessionStateChanged(SessionStateChangedMessage),
2126 WorkerShuttingDown(WorkerShuttingDownMessage),
2127 CommandsChanged(CommandsChangedMessage),
2128 Notification(NotificationMessage),
2129 FilesPersisted(FilesPersistedMessage),
2130 MemoryRecall(MemoryRecallMessage),
2131 ElicitationComplete(ElicitationCompleteMessage),
2132 PermissionDenied(PermissionDeniedMessage),
2133 MirrorError(MirrorErrorMessage),
2134 Informational(InformationalMessage),
2135 CodeChangePublished(CodeChangePublishedMessage),
2136 VcsStateChanged(VcsStateChangedMessage),
2137 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
2138 CloudSessionDelta(CloudSessionDeltaMessage),
2139 DevIntent(DevIntentMessage),
2140 TurnHandoffAvailable(TurnHandoffAvailableMessage),
2141 TurnPreempted(TurnPreemptedMessage),
2142 PeerMessageHold(PeerMessageHoldMessage),
2143 PerTurnEffortChanged(PerTurnEffortChangedMessage),
2144 SessionTitleChanged(SessionTitleChangedMessage),
2145 InstructionSizeWarning(InstructionSizeWarningMessage),
2146 FileAttachmentsMissing(FileAttachmentsMissingMessage),
2147 PermissionCheckStatus(PermissionCheckStatusMessage),
2148 UiFocus(UiFocusMessage),
2149 UiInvalidate(UiInvalidateMessage),
2150 UiLog(UiLogMessage),
2151 UiPanes(UiPanesMessage),
2152 UiScroll(UiScrollMessage),
2153 UiStatus(UiStatusMessage),
2154 UiToast(UiToastMessage),
2155}
2156
2157#[derive(Debug, Clone, Serialize, Deserialize)]
2158pub struct ApiRetryMessage {
2159 pub attempt: u64,
2160 pub max_retries: u64,
2161 pub retry_delay_ms: u64,
2162 pub error_status: Option<u16>,
2163 pub error: String,
2164 #[serde(default, skip_serializing_if = "Option::is_none")]
2169 pub no_response: Option<ApiRetryNoResponse>,
2170 #[serde(default, skip_serializing_if = "Option::is_none")]
2171 pub uuid: Option<String>,
2172 #[serde(default, skip_serializing_if = "Option::is_none")]
2173 pub session_id: Option<String>,
2174}
2175
2176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2179pub struct ApiRetryNoResponse {
2180 pub waited_ms: u64,
2182 pub retry_wait_ms: u64,
2184}
2185
2186#[derive(Debug, Clone, Serialize, Deserialize)]
2187pub struct ControlRequestProgressMessage {
2188 pub request_id: String,
2189 pub status: String,
2190 #[serde(default, skip_serializing_if = "Option::is_none")]
2191 pub attempt: Option<u64>,
2192 #[serde(default, skip_serializing_if = "Option::is_none")]
2193 pub max_retries: Option<u64>,
2194 #[serde(default, skip_serializing_if = "Option::is_none")]
2195 pub retry_delay_ms: Option<u64>,
2196 #[serde(default, skip_serializing_if = "Option::is_none")]
2197 pub error_status: Option<u16>,
2198 #[serde(default, skip_serializing_if = "Option::is_none")]
2199 pub error: Option<String>,
2200 #[serde(default, skip_serializing_if = "Option::is_none")]
2201 pub uuid: Option<String>,
2202 #[serde(default, skip_serializing_if = "Option::is_none")]
2203 pub session_id: Option<String>,
2204}
2205
2206#[derive(Debug, Clone, Serialize, Deserialize)]
2207pub struct ModelRefusalFallbackMessage {
2208 pub trigger: String,
2209 pub direction: String,
2210 #[serde(default, skip_serializing_if = "Option::is_none")]
2216 pub scope: Option<RefusalFallbackScope>,
2217 pub original_model: String,
2218 pub fallback_model: String,
2219 pub request_id: Option<String>,
2220 #[serde(default, skip_serializing_if = "Option::is_none")]
2221 pub api_refusal_category: Option<String>,
2222 #[serde(default, skip_serializing_if = "Option::is_none")]
2227 pub saw_cyber_refusal: Option<bool>,
2228 #[serde(default, skip_serializing_if = "Option::is_none")]
2229 pub api_refusal_explanation: Option<String>,
2230 #[serde(default, skip_serializing_if = "Option::is_none")]
2231 pub retracted_message_uuids: Option<Vec<String>>,
2232 #[serde(default, skip_serializing_if = "Option::is_none")]
2233 pub refused_user_message_uuid: Option<String>,
2234 pub content: Value,
2235 #[serde(default, skip_serializing_if = "Option::is_none")]
2236 pub uuid: Option<String>,
2237 #[serde(default, skip_serializing_if = "Option::is_none")]
2238 pub session_id: Option<String>,
2239}
2240
2241#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2245pub enum RefusalFallbackScope {
2246 Session,
2248 Local,
2251 Unknown(String),
2253}
2254
2255impl RefusalFallbackScope {
2256 pub fn as_str(&self) -> &str {
2257 match self {
2258 Self::Session => "session",
2259 Self::Local => "local",
2260 Self::Unknown(s) => s.as_str(),
2261 }
2262 }
2263}
2264
2265impl fmt::Display for RefusalFallbackScope {
2266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2267 f.write_str(self.as_str())
2268 }
2269}
2270
2271impl From<&str> for RefusalFallbackScope {
2272 fn from(s: &str) -> Self {
2273 match s {
2274 "session" => Self::Session,
2275 "local" => Self::Local,
2276 other => Self::Unknown(other.to_string()),
2277 }
2278 }
2279}
2280
2281impl Serialize for RefusalFallbackScope {
2282 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2283 serializer.serialize_str(self.as_str())
2284 }
2285}
2286
2287impl<'de> Deserialize<'de> for RefusalFallbackScope {
2288 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2289 let s = String::deserialize(deserializer)?;
2290 Ok(Self::from(s.as_str()))
2291 }
2292}
2293
2294#[derive(Debug, Clone, Serialize, Deserialize)]
2295pub struct ModelRefusalNoFallbackMessage {
2296 pub original_model: String,
2297 pub request_id: Option<String>,
2298 #[serde(default, skip_serializing_if = "Option::is_none")]
2299 pub api_refusal_category: Option<String>,
2300 #[serde(default, skip_serializing_if = "Option::is_none")]
2301 pub api_refusal_explanation: Option<String>,
2302 pub content: Value,
2303 #[serde(default, skip_serializing_if = "Option::is_none")]
2304 pub uuid: Option<String>,
2305 #[serde(default, skip_serializing_if = "Option::is_none")]
2306 pub session_id: Option<String>,
2307}
2308
2309#[derive(Debug, Clone, Serialize, Deserialize)]
2310pub struct LocalCommandOutputMessage {
2311 pub content: String,
2312 #[serde(default, skip_serializing_if = "Option::is_none")]
2313 pub uuid: Option<String>,
2314 #[serde(default, skip_serializing_if = "Option::is_none")]
2315 pub session_id: Option<String>,
2316}
2317
2318#[derive(Debug, Clone, Serialize, Deserialize)]
2319pub struct HookStartedMessage {
2320 pub hook_id: String,
2321 pub hook_name: String,
2322 pub hook_event: String,
2323 #[serde(default, skip_serializing_if = "Option::is_none")]
2324 pub uuid: Option<String>,
2325 #[serde(default, skip_serializing_if = "Option::is_none")]
2326 pub session_id: Option<String>,
2327}
2328
2329#[derive(Debug, Clone, Serialize, Deserialize)]
2330pub struct HookProgressMessage {
2331 pub hook_id: String,
2332 pub hook_name: String,
2333 pub hook_event: String,
2334 #[serde(default, skip_serializing_if = "Option::is_none")]
2335 pub stdout: Option<String>,
2336 #[serde(default, skip_serializing_if = "Option::is_none")]
2337 pub stderr: Option<String>,
2338 #[serde(default, skip_serializing_if = "Option::is_none")]
2339 pub output: Option<String>,
2340 #[serde(default, skip_serializing_if = "Option::is_none")]
2341 pub uuid: Option<String>,
2342 #[serde(default, skip_serializing_if = "Option::is_none")]
2343 pub session_id: Option<String>,
2344}
2345
2346#[derive(Debug, Clone, Serialize, Deserialize)]
2347pub struct HookResponseMessage {
2348 pub hook_id: String,
2349 pub hook_name: String,
2350 pub hook_event: String,
2351 #[serde(default, skip_serializing_if = "Option::is_none")]
2352 pub stdout: Option<String>,
2353 #[serde(default, skip_serializing_if = "Option::is_none")]
2354 pub stderr: Option<String>,
2355 #[serde(default, skip_serializing_if = "Option::is_none")]
2356 pub output: Option<String>,
2357 #[serde(default, skip_serializing_if = "Option::is_none")]
2358 pub exit_code: Option<i32>,
2359 pub outcome: String,
2360 #[serde(default, skip_serializing_if = "Option::is_none")]
2361 pub uuid: Option<String>,
2362 #[serde(default, skip_serializing_if = "Option::is_none")]
2363 pub session_id: Option<String>,
2364}
2365
2366#[derive(Debug, Clone, Serialize, Deserialize)]
2367pub struct PluginInstallMessage {
2368 pub status: String,
2369 #[serde(default, skip_serializing_if = "Option::is_none")]
2370 pub name: Option<String>,
2371 #[serde(default, skip_serializing_if = "Option::is_none")]
2372 pub error: Option<String>,
2373 #[serde(default, skip_serializing_if = "Option::is_none")]
2374 pub uuid: Option<String>,
2375 #[serde(default, skip_serializing_if = "Option::is_none")]
2376 pub session_id: Option<String>,
2377}
2378
2379#[derive(Debug, Clone, Serialize, Deserialize)]
2380pub struct BackgroundTasksChangedMessage {
2381 pub tasks: Vec<BackgroundTaskInfo>,
2382 #[serde(default, skip_serializing_if = "Option::is_none")]
2383 pub uuid: Option<String>,
2384 #[serde(default, skip_serializing_if = "Option::is_none")]
2385 pub session_id: Option<String>,
2386}
2387
2388#[derive(Debug, Clone, Serialize, Deserialize)]
2389pub struct BackgroundTaskInfo {
2390 pub task_id: String,
2391 #[serde(default, skip_serializing_if = "Option::is_none")]
2394 pub run_id: Option<String>,
2395 pub task_type: String,
2396 #[serde(default, skip_serializing_if = "Option::is_none")]
2400 pub subagent_type: Option<String>,
2401 pub description: String,
2402 #[serde(default, skip_serializing_if = "Option::is_none")]
2405 pub parent_task_id: Option<String>,
2406 #[serde(default, skip_serializing_if = "Option::is_none")]
2409 pub ambient: Option<bool>,
2410 #[serde(default, skip_serializing_if = "Option::is_none")]
2413 pub awaited: Option<bool>,
2414}
2415
2416#[derive(Debug, Clone, Serialize, Deserialize)]
2417pub struct SessionStateChangedMessage {
2418 pub state: String,
2419 #[serde(default, skip_serializing_if = "Option::is_none")]
2420 pub uuid: Option<String>,
2421 #[serde(default, skip_serializing_if = "Option::is_none")]
2422 pub session_id: Option<String>,
2423}
2424
2425#[derive(Debug, Clone, Serialize, Deserialize)]
2426pub struct WorkerShuttingDownMessage {
2427 pub reason: String,
2428 #[serde(default, skip_serializing_if = "Option::is_none")]
2429 pub uuid: Option<String>,
2430 #[serde(default, skip_serializing_if = "Option::is_none")]
2431 pub session_id: Option<String>,
2432}
2433
2434#[derive(Debug, Clone, Serialize, Deserialize)]
2435pub struct CommandsChangedMessage {
2436 pub commands: Vec<CommandInfo>,
2437 #[serde(default, skip_serializing_if = "Option::is_none")]
2438 pub uuid: Option<String>,
2439 #[serde(default, skip_serializing_if = "Option::is_none")]
2440 pub session_id: Option<String>,
2441}
2442
2443#[derive(Debug, Clone, Serialize, Deserialize)]
2444pub struct CommandInfo {
2445 pub name: String,
2446 pub description: String,
2447 #[serde(rename = "argumentHint")]
2448 pub argument_hint: String,
2449 #[serde(default, skip_serializing_if = "Option::is_none")]
2450 pub aliases: Option<Vec<String>>,
2451 #[serde(default, skip_serializing_if = "Option::is_none")]
2457 pub builtin: Option<bool>,
2458}
2459
2460#[derive(Debug, Clone, Serialize, Deserialize)]
2461pub struct NotificationMessage {
2462 pub key: String,
2463 pub text: String,
2464 pub priority: String,
2465 #[serde(default, skip_serializing_if = "Option::is_none")]
2466 pub color: Option<String>,
2467 #[serde(default, skip_serializing_if = "Option::is_none")]
2468 pub timeout_ms: Option<u64>,
2469 #[serde(default, skip_serializing_if = "Option::is_none")]
2470 pub uuid: Option<String>,
2471 #[serde(default, skip_serializing_if = "Option::is_none")]
2472 pub session_id: Option<String>,
2473}
2474
2475#[derive(Debug, Clone, Serialize, Deserialize)]
2476pub struct FilesPersistedMessage {
2477 pub files: Vec<PersistedFile>,
2478 pub failed: Vec<FailedPersistedFile>,
2479 pub processed_at: String,
2480 #[serde(default, skip_serializing_if = "Option::is_none")]
2481 pub uuid: Option<String>,
2482 #[serde(default, skip_serializing_if = "Option::is_none")]
2483 pub session_id: Option<String>,
2484}
2485
2486#[derive(Debug, Clone, Serialize, Deserialize)]
2487pub struct PersistedFile {
2488 pub filename: String,
2489 pub file_id: String,
2490}
2491
2492#[derive(Debug, Clone, Serialize, Deserialize)]
2493pub struct FailedPersistedFile {
2494 pub filename: String,
2495 pub error: String,
2496}
2497
2498#[derive(Debug, Clone, Serialize, Deserialize)]
2499pub struct MemoryRecallMessage {
2500 pub mode: String,
2501 pub memories: Vec<MemoryRecallItem>,
2502 #[serde(default, skip_serializing_if = "Option::is_none")]
2503 pub uuid: Option<String>,
2504 #[serde(default, skip_serializing_if = "Option::is_none")]
2505 pub session_id: Option<String>,
2506}
2507
2508#[derive(Debug, Clone, Serialize, Deserialize)]
2509pub struct MemoryRecallItem {
2510 pub path: String,
2511 pub scope: String,
2512 #[serde(default, skip_serializing_if = "Option::is_none")]
2513 pub content: Option<String>,
2514}
2515
2516#[derive(Debug, Clone, Serialize, Deserialize)]
2517pub struct ElicitationCompleteMessage {
2518 pub mcp_server_name: String,
2519 pub elicitation_id: String,
2520 #[serde(default, skip_serializing_if = "Option::is_none")]
2521 pub uuid: Option<String>,
2522 #[serde(default, skip_serializing_if = "Option::is_none")]
2523 pub session_id: Option<String>,
2524}
2525
2526#[derive(Debug, Clone, Serialize, Deserialize)]
2527pub struct PermissionDeniedMessage {
2528 pub tool_name: String,
2529 pub tool_use_id: String,
2530 #[serde(default, skip_serializing_if = "Option::is_none")]
2531 pub agent_id: Option<String>,
2532 #[serde(default, skip_serializing_if = "Option::is_none")]
2533 pub decision_reason_type: Option<String>,
2534 #[serde(default, skip_serializing_if = "Option::is_none")]
2538 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2539 #[serde(default, skip_serializing_if = "Option::is_none")]
2540 pub decision_reason: Option<String>,
2541 pub message: String,
2542 #[serde(default, skip_serializing_if = "Option::is_none")]
2543 pub uuid: Option<String>,
2544 #[serde(default, skip_serializing_if = "Option::is_none")]
2545 pub session_id: Option<String>,
2546}
2547
2548#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2551pub enum PermissionDeniedReasonCode {
2552 ClassifierTranscriptTooLong,
2554 OutsideReadsBlocked,
2556 MemoryPaused,
2558 Unknown(String),
2560}
2561
2562impl PermissionDeniedReasonCode {
2563 pub fn as_str(&self) -> &str {
2564 match self {
2565 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2566 Self::OutsideReadsBlocked => "outside_reads_blocked",
2567 Self::MemoryPaused => "memory_paused",
2568 Self::Unknown(s) => s.as_str(),
2569 }
2570 }
2571}
2572
2573impl fmt::Display for PermissionDeniedReasonCode {
2574 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2575 f.write_str(self.as_str())
2576 }
2577}
2578
2579impl From<&str> for PermissionDeniedReasonCode {
2580 fn from(s: &str) -> Self {
2581 match s {
2582 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2583 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2584 "memory_paused" => Self::MemoryPaused,
2585 other => Self::Unknown(other.to_string()),
2586 }
2587 }
2588}
2589
2590impl Serialize for PermissionDeniedReasonCode {
2591 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2592 serializer.serialize_str(self.as_str())
2593 }
2594}
2595
2596impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2597 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2598 let s = String::deserialize(deserializer)?;
2599 Ok(Self::from(s.as_str()))
2600 }
2601}
2602
2603#[derive(Debug, Clone, Serialize, Deserialize)]
2604pub struct MirrorErrorMessage {
2605 pub error: String,
2606 pub key: MirrorErrorKey,
2607 #[serde(default, skip_serializing_if = "Option::is_none")]
2608 pub uuid: Option<String>,
2609 #[serde(default, skip_serializing_if = "Option::is_none")]
2610 pub session_id: Option<String>,
2611}
2612
2613#[derive(Debug, Clone, Serialize, Deserialize)]
2614pub struct MirrorErrorKey {
2615 #[serde(rename = "projectKey")]
2616 pub project_key: String,
2617 #[serde(rename = "sessionId")]
2618 pub session_id: String,
2619 #[serde(default, skip_serializing_if = "Option::is_none")]
2620 pub subpath: Option<String>,
2621}
2622
2623#[derive(Debug, Clone, Serialize, Deserialize)]
2624pub struct InformationalMessage {
2625 pub content: String,
2626 pub level: String,
2627 #[serde(default, skip_serializing_if = "Option::is_none")]
2628 pub tool_use_id: Option<String>,
2629 #[serde(default, skip_serializing_if = "Option::is_none")]
2630 pub prevent_continuation: Option<bool>,
2631 #[serde(default, skip_serializing_if = "Option::is_none")]
2634 pub tag: Option<String>,
2635 #[serde(default, skip_serializing_if = "Option::is_none")]
2636 pub uuid: Option<String>,
2637 #[serde(default, skip_serializing_if = "Option::is_none")]
2638 pub session_id: Option<String>,
2639}
2640
2641#[derive(Debug, Clone, Serialize, Deserialize)]
2643pub struct PluginInfo {
2644 pub name: String,
2646 pub path: String,
2648 #[serde(skip_serializing_if = "Option::is_none")]
2650 pub source: Option<String>,
2651 #[serde(default, skip_serializing_if = "Option::is_none")]
2653 pub version: Option<String>,
2654}
2655
2656#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2658pub struct PluginDiagnostic {
2659 pub plugin: String,
2660 #[serde(rename = "type")]
2661 pub diagnostic_type: String,
2662 pub message: String,
2663}
2664
2665#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2667pub struct MemoryPaths {
2668 #[serde(default, skip_serializing_if = "Option::is_none")]
2669 pub auto: Option<String>,
2670 #[serde(default, skip_serializing_if = "Option::is_none")]
2671 pub team: Option<String>,
2672 #[serde(flatten)]
2673 pub extra: serde_json::Map<String, Value>,
2674}
2675
2676#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2680pub struct McpServerError {
2681 pub name: String,
2682 #[serde(rename = "type")]
2684 pub error_type: String,
2685 pub message: String,
2686}
2687
2688#[derive(Debug, Clone, Serialize, Deserialize)]
2690pub struct InitMessage {
2691 pub session_id: String,
2693 #[serde(skip_serializing_if = "Option::is_none")]
2695 pub cwd: Option<String>,
2696 #[serde(skip_serializing_if = "Option::is_none")]
2698 pub model: Option<String>,
2699 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2701 pub tools: Vec<String>,
2702 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2704 pub mcp_servers: Vec<Value>,
2705 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2707 pub slash_commands: Vec<String>,
2708 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2711 pub terminal_slash_commands: Vec<String>,
2712 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2714 pub agents: Vec<String>,
2715 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2717 pub plugins: Vec<PluginInfo>,
2718 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2720 pub skills: Vec<Value>,
2721 #[serde(skip_serializing_if = "Option::is_none")]
2723 pub claude_code_version: Option<String>,
2724 #[serde(skip_serializing_if = "Option::is_none")]
2727 pub messaging_socket_path: Option<String>,
2728 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2730 pub api_key_source: Option<ApiKeySource>,
2731 #[serde(skip_serializing_if = "Option::is_none")]
2733 pub output_style: Option<OutputStyle>,
2734 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2736 pub permission_mode: Option<InitPermissionMode>,
2737
2738 #[serde(skip_serializing_if = "Option::is_none")]
2740 pub uuid: Option<String>,
2741
2742 #[serde(skip_serializing_if = "Option::is_none")]
2744 pub memory_paths: Option<MemoryPaths>,
2745
2746 #[serde(default, skip_serializing_if = "Option::is_none")]
2751 pub scratchpad_path: Option<String>,
2752
2753 #[serde(skip_serializing_if = "Option::is_none")]
2755 pub fast_mode_state: Option<String>,
2756
2757 #[serde(default, skip_serializing_if = "Option::is_none")]
2759 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2760
2761 #[serde(default, skip_serializing_if = "Option::is_none")]
2764 pub mcp_server_errors: Option<Vec<McpServerError>>,
2765
2766 #[serde(default, skip_serializing_if = "Option::is_none")]
2768 pub analytics_disabled: Option<bool>,
2769
2770 #[serde(default, skip_serializing_if = "Option::is_none")]
2772 pub product_feedback_disabled: Option<bool>,
2773
2774 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2776 pub betas: Vec<String>,
2777
2778 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2780 pub capabilities: Vec<String>,
2781
2782 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2784 pub plugin_errors: Vec<PluginDiagnostic>,
2785
2786 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2788 pub plugin_warnings: Vec<PluginDiagnostic>,
2789
2790 #[serde(default, skip_serializing_if = "Option::is_none")]
2795 pub effort: Option<String>,
2796
2797 #[serde(default, skip_serializing_if = "Option::is_none")]
2802 pub cloud_session: Option<Value>,
2803
2804 #[serde(default, skip_serializing_if = "Option::is_none")]
2808 pub footer_indicator: Option<FooterIndicator>,
2809
2810 #[serde(default, skip_serializing_if = "Option::is_none")]
2814 pub worker_epoch: Option<u64>,
2815
2816 #[serde(default, skip_serializing_if = "Option::is_none")]
2820 pub powershell_path: Option<Option<String>>,
2821
2822 #[serde(default, skip_serializing_if = "Option::is_none")]
2826 pub startup_timing: Option<serde_json::Map<String, Value>>,
2827
2828 #[serde(default, skip_serializing_if = "Option::is_none")]
2838 pub per_turn_effort_active: Option<bool>,
2839
2840 #[serde(default, skip_serializing_if = "Option::is_none")]
2847 pub view_mode: Option<ViewMode>,
2848}
2849
2850#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2854pub struct FooterIndicator {
2855 pub text: String,
2858}
2859
2860#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2863pub enum ViewMode {
2864 Focus,
2867 Default,
2869 Unknown(String),
2871}
2872
2873impl ViewMode {
2874 pub fn as_str(&self) -> &str {
2875 match self {
2876 Self::Focus => "focus",
2877 Self::Default => "default",
2878 Self::Unknown(s) => s.as_str(),
2879 }
2880 }
2881}
2882
2883impl fmt::Display for ViewMode {
2884 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2885 f.write_str(self.as_str())
2886 }
2887}
2888
2889impl From<&str> for ViewMode {
2890 fn from(s: &str) -> Self {
2891 match s {
2892 "focus" => Self::Focus,
2893 "default" => Self::Default,
2894 other => Self::Unknown(other.to_string()),
2895 }
2896 }
2897}
2898
2899impl Serialize for ViewMode {
2900 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2901 serializer.serialize_str(self.as_str())
2902 }
2903}
2904
2905impl<'de> Deserialize<'de> for ViewMode {
2906 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2907 let s = String::deserialize(deserializer)?;
2908 Ok(Self::from(s.as_str()))
2909 }
2910}
2911
2912#[derive(Debug, Clone, Serialize, Deserialize)]
2914pub struct StatusMessage {
2915 pub session_id: String,
2917 pub status: Option<StatusMessageStatus>,
2919 #[serde(skip_serializing_if = "Option::is_none")]
2921 pub uuid: Option<String>,
2922 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2924 pub permission_mode: Option<InitPermissionMode>,
2925 #[serde(skip_serializing_if = "Option::is_none")]
2926 pub compact_result: Option<String>,
2927 #[serde(skip_serializing_if = "Option::is_none")]
2928 pub compact_error: Option<String>,
2929}
2930
2931#[derive(Debug, Clone, Serialize, Deserialize)]
2933pub struct CompactBoundaryMessage {
2934 pub session_id: String,
2936 pub compact_metadata: CompactMetadata,
2938 #[serde(
2942 default,
2943 skip_serializing_if = "Option::is_none",
2944 alias = "content",
2945 alias = "text"
2946 )]
2947 pub summary: Option<String>,
2948 #[serde(
2952 default,
2953 skip_serializing_if = "Option::is_none",
2954 alias = "message_count"
2955 )]
2956 pub leaf_message_count: Option<u32>,
2957 #[serde(default, skip_serializing_if = "Option::is_none")]
2959 pub duration_ms: Option<u64>,
2960 #[serde(skip_serializing_if = "Option::is_none")]
2962 pub uuid: Option<String>,
2963 #[serde(skip_serializing_if = "Option::is_none")]
2965 pub logical_parent_uuid: Option<Option<String>>,
2966 #[serde(default, skip_serializing_if = "Option::is_none")]
2970 pub historical: Option<bool>,
2971}
2972
2973#[derive(Debug, Clone, Serialize, Deserialize)]
2975pub struct CompactMetadata {
2976 pub pre_tokens: u64,
2978 pub trigger: CompactionTrigger,
2980 #[serde(default, skip_serializing_if = "Option::is_none")]
2981 pub post_tokens: Option<u64>,
2982 #[serde(default, skip_serializing_if = "Option::is_none")]
2983 pub cumulative_dropped_tokens: Option<u64>,
2984 #[serde(default, skip_serializing_if = "Option::is_none")]
2985 pub duration_ms: Option<u64>,
2986 #[serde(default, skip_serializing_if = "Option::is_none")]
2987 pub user_context: Option<String>,
2988 #[serde(default, skip_serializing_if = "Option::is_none")]
2989 pub messages_summarized: Option<u64>,
2990 #[serde(default, skip_serializing_if = "Option::is_none")]
2991 pub precomputed: Option<bool>,
2992 #[serde(default, skip_serializing_if = "Option::is_none")]
2993 pub pre_compact_discovered_tools: Option<Vec<String>>,
2994 #[serde(default, skip_serializing_if = "Option::is_none")]
2999 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
3000 #[serde(default, skip_serializing_if = "Option::is_none")]
3001 pub preserved_segment: Option<PreservedSegment>,
3002 #[serde(default, skip_serializing_if = "Option::is_none")]
3003 pub preserved_messages: Option<PreservedMessages>,
3004}
3005
3006#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3009pub struct ArtifactReadVersion {
3010 pub slug: String,
3012 pub ver: String,
3014}
3015
3016#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3017pub struct PreservedSegment {
3018 pub head_uuid: String,
3019 pub anchor_uuid: String,
3020 pub tail_uuid: String,
3021}
3022
3023#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3024pub struct PreservedMessages {
3025 pub anchor_uuid: String,
3026 pub uuids: Vec<String>,
3027 #[serde(default, skip_serializing_if = "Option::is_none")]
3028 pub all_uuids: Option<Vec<String>>,
3029}
3030
3031#[derive(Debug, Clone, Serialize, Deserialize)]
3037pub struct TaskUsage {
3038 pub duration_ms: u64,
3040 pub tool_uses: u64,
3042 pub total_tokens: u64,
3044}
3045
3046#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3048pub enum TaskType {
3049 LocalAgent,
3051 LocalBash,
3053 LocalWorkflow,
3055 Unknown(String),
3057}
3058
3059impl TaskType {
3060 pub fn as_str(&self) -> &str {
3061 match self {
3062 Self::LocalAgent => "local_agent",
3063 Self::LocalBash => "local_bash",
3064 Self::LocalWorkflow => "local_workflow",
3065 Self::Unknown(s) => s.as_str(),
3066 }
3067 }
3068}
3069
3070impl fmt::Display for TaskType {
3071 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3072 f.write_str(self.as_str())
3073 }
3074}
3075
3076impl From<&str> for TaskType {
3077 fn from(s: &str) -> Self {
3078 match s {
3079 "local_agent" => Self::LocalAgent,
3080 "local_bash" => Self::LocalBash,
3081 "local_workflow" => Self::LocalWorkflow,
3082 other => Self::Unknown(other.to_string()),
3083 }
3084 }
3085}
3086
3087impl Serialize for TaskType {
3088 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3089 serializer.serialize_str(self.as_str())
3090 }
3091}
3092
3093impl<'de> Deserialize<'de> for TaskType {
3094 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3095 let s = String::deserialize(deserializer)?;
3096 Ok(Self::from(s.as_str()))
3097 }
3098}
3099
3100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3102pub enum TaskStatus {
3103 Pending,
3104 Running,
3105 Completed,
3106 Failed,
3107 Killed,
3108 Paused,
3109 Stopped,
3110 Unknown(String),
3111}
3112
3113impl TaskStatus {
3114 pub fn as_str(&self) -> &str {
3115 match self {
3116 Self::Pending => "pending",
3117 Self::Running => "running",
3118 Self::Completed => "completed",
3119 Self::Failed => "failed",
3120 Self::Killed => "killed",
3121 Self::Paused => "paused",
3122 Self::Stopped => "stopped",
3123 Self::Unknown(s) => s.as_str(),
3124 }
3125 }
3126}
3127
3128impl fmt::Display for TaskStatus {
3129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3130 f.write_str(self.as_str())
3131 }
3132}
3133
3134impl From<&str> for TaskStatus {
3135 fn from(s: &str) -> Self {
3136 match s {
3137 "pending" => Self::Pending,
3138 "running" => Self::Running,
3139 "completed" => Self::Completed,
3140 "failed" => Self::Failed,
3141 "killed" => Self::Killed,
3142 "paused" => Self::Paused,
3143 "stopped" => Self::Stopped,
3144 other => Self::Unknown(other.to_string()),
3145 }
3146 }
3147}
3148
3149impl Serialize for TaskStatus {
3150 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3151 serializer.serialize_str(self.as_str())
3152 }
3153}
3154
3155impl<'de> Deserialize<'de> for TaskStatus {
3156 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3157 let s = String::deserialize(deserializer)?;
3158 Ok(Self::from(s.as_str()))
3159 }
3160}
3161
3162#[derive(Debug, Clone, Serialize, Deserialize)]
3164pub struct TaskStartedMessage {
3165 pub session_id: String,
3166 pub task_id: String,
3167 #[serde(default, skip_serializing_if = "Option::is_none")]
3173 pub run_id: Option<String>,
3174 #[serde(default, skip_serializing_if = "Option::is_none")]
3180 pub parent_task_id: Option<String>,
3181 #[serde(default, skip_serializing_if = "Option::is_none")]
3182 pub task_type: Option<TaskType>,
3183 #[serde(default, skip_serializing_if = "Option::is_none")]
3184 pub tool_use_id: Option<String>,
3185 pub description: String,
3186 #[serde(default, skip_serializing_if = "Option::is_none")]
3189 pub subagent_type: Option<String>,
3190 #[serde(default, skip_serializing_if = "Option::is_none")]
3196 pub is_backgrounded: Option<bool>,
3197 #[serde(default, skip_serializing_if = "Option::is_none")]
3205 pub awaited: Option<bool>,
3206 #[serde(default, skip_serializing_if = "Option::is_none")]
3210 pub spawn_depth: Option<u32>,
3211 #[serde(default, skip_serializing_if = "Option::is_none")]
3213 pub prompt: Option<String>,
3214 #[serde(default, skip_serializing_if = "Option::is_none")]
3215 pub workflow_name: Option<String>,
3216 #[serde(default, skip_serializing_if = "Option::is_none")]
3217 pub skip_transcript: Option<bool>,
3218 #[serde(default, skip_serializing_if = "Option::is_none")]
3223 pub ambient: Option<bool>,
3224 pub uuid: String,
3225}
3226
3227#[derive(Debug, Clone, Serialize, Deserialize)]
3231pub struct TaskUpdatedMessage {
3232 pub session_id: String,
3233 pub task_id: String,
3234 #[serde(default, skip_serializing_if = "Option::is_none")]
3237 pub run_id: Option<String>,
3238 pub patch: TaskPatch,
3239 pub uuid: String,
3240}
3241
3242#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3245pub struct TaskPatch {
3246 #[serde(default, skip_serializing_if = "Option::is_none")]
3247 pub status: Option<TaskStatus>,
3248 #[serde(default, skip_serializing_if = "Option::is_none")]
3251 pub end_time: Option<u64>,
3252 #[serde(default, skip_serializing_if = "Option::is_none")]
3253 pub description: Option<String>,
3254 #[serde(default, skip_serializing_if = "Option::is_none")]
3255 pub total_paused_ms: Option<u64>,
3256 #[serde(default, skip_serializing_if = "Option::is_none")]
3257 pub error: Option<String>,
3258 #[serde(default, skip_serializing_if = "Option::is_none")]
3259 pub is_backgrounded: Option<bool>,
3260}
3261
3262#[derive(Debug, Clone, Serialize, Deserialize)]
3265pub struct ThinkingTokensMessage {
3266 pub session_id: String,
3267 pub estimated_tokens: u64,
3269 pub estimated_tokens_delta: u64,
3271 #[serde(default, skip_serializing_if = "Option::is_none")]
3278 pub user_message_uuid: Option<String>,
3279 pub uuid: String,
3280}
3281
3282#[derive(Debug, Clone, Serialize, Deserialize)]
3285pub struct TaskProgressMessage {
3286 pub session_id: String,
3287 pub task_id: String,
3288 #[serde(default, skip_serializing_if = "Option::is_none")]
3291 pub run_id: Option<String>,
3292 #[serde(default, skip_serializing_if = "Option::is_none")]
3293 pub tool_use_id: Option<String>,
3294 pub description: String,
3295 #[serde(default, skip_serializing_if = "Option::is_none")]
3296 pub last_tool_name: Option<String>,
3297 pub usage: TaskUsage,
3298 #[serde(default, skip_serializing_if = "Option::is_none")]
3300 pub subagent_type: Option<String>,
3301 #[serde(default, skip_serializing_if = "Option::is_none")]
3302 pub summary: Option<String>,
3303 pub uuid: String,
3304}
3305
3306#[derive(Debug, Clone, Serialize, Deserialize)]
3309pub struct TaskNotificationMessage {
3310 pub session_id: String,
3311 pub task_id: String,
3312 #[serde(default, skip_serializing_if = "Option::is_none")]
3315 pub run_id: Option<String>,
3316 pub status: TaskStatus,
3317 #[serde(default, skip_serializing_if = "Option::is_none")]
3320 pub reason: Option<TaskEndReason>,
3321 pub summary: String,
3322 pub output_file: Option<String>,
3323 #[serde(skip_serializing_if = "Option::is_none")]
3324 pub tool_use_id: Option<String>,
3325 #[serde(skip_serializing_if = "Option::is_none")]
3326 pub usage: Option<TaskUsage>,
3327 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3333 pub resource_links: Vec<ResourceLink>,
3334 #[serde(default, skip_serializing_if = "Option::is_none")]
3341 pub handback: Option<TaskHandback>,
3342 #[serde(default, skip_serializing_if = "Option::is_none")]
3346 pub handback_report: Option<HandbackReport>,
3347 #[serde(default, skip_serializing_if = "Option::is_none")]
3348 pub skip_transcript: Option<bool>,
3349 #[serde(default, skip_serializing_if = "Option::is_none")]
3352 pub ambient: Option<bool>,
3353 #[serde(skip_serializing_if = "Option::is_none")]
3354 pub uuid: Option<String>,
3355}
3356
3357#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3360pub enum TaskHandback {
3361 Send,
3363 Flagged,
3365 Withheld,
3367 Unknown(String),
3369}
3370
3371impl TaskHandback {
3372 pub fn as_str(&self) -> &str {
3373 match self {
3374 Self::Send => "send",
3375 Self::Flagged => "flagged",
3376 Self::Withheld => "withheld",
3377 Self::Unknown(s) => s.as_str(),
3378 }
3379 }
3380}
3381
3382impl fmt::Display for TaskHandback {
3383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3384 f.write_str(self.as_str())
3385 }
3386}
3387
3388impl From<&str> for TaskHandback {
3389 fn from(s: &str) -> Self {
3390 match s {
3391 "send" => Self::Send,
3392 "flagged" => Self::Flagged,
3393 "withheld" => Self::Withheld,
3394 other => Self::Unknown(other.to_string()),
3395 }
3396 }
3397}
3398
3399impl Serialize for TaskHandback {
3400 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3401 serializer.serialize_str(self.as_str())
3402 }
3403}
3404
3405impl<'de> Deserialize<'de> for TaskHandback {
3406 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3407 let s = String::deserialize(deserializer)?;
3408 Ok(Self::from(s.as_str()))
3409 }
3410}
3411
3412#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3415pub struct HandbackReport {
3416 pub text: String,
3419 #[serde(default, skip_serializing_if = "Option::is_none")]
3422 pub warning: Option<String>,
3423}
3424
3425#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3427pub enum AssistantErrorKind {
3428 AuthenticationFailed,
3429 OauthOrgNotAllowed,
3430 AccountOnHold,
3431 BillingError,
3432 RateLimit,
3433 Overloaded,
3434 InvalidRequest,
3435 ModelNotFound,
3436 ServerError,
3437 UnknownError,
3438 MaxOutputTokens,
3439 Unknown(String),
3440}
3441
3442impl AssistantErrorKind {
3443 pub fn as_str(&self) -> &str {
3444 match self {
3445 Self::AuthenticationFailed => "authentication_failed",
3446 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3447 Self::AccountOnHold => "account_on_hold",
3448 Self::BillingError => "billing_error",
3449 Self::RateLimit => "rate_limit",
3450 Self::Overloaded => "overloaded",
3451 Self::InvalidRequest => "invalid_request",
3452 Self::ModelNotFound => "model_not_found",
3453 Self::ServerError => "server_error",
3454 Self::UnknownError => "unknown",
3455 Self::MaxOutputTokens => "max_output_tokens",
3456 Self::Unknown(s) => s.as_str(),
3457 }
3458 }
3459}
3460
3461impl fmt::Display for AssistantErrorKind {
3462 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3463 f.write_str(self.as_str())
3464 }
3465}
3466
3467impl From<&str> for AssistantErrorKind {
3468 fn from(s: &str) -> Self {
3469 match s {
3470 "authentication_failed" => Self::AuthenticationFailed,
3471 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3472 "account_on_hold" => Self::AccountOnHold,
3473 "billing_error" => Self::BillingError,
3474 "rate_limit" => Self::RateLimit,
3475 "overloaded" => Self::Overloaded,
3476 "invalid_request" => Self::InvalidRequest,
3477 "model_not_found" => Self::ModelNotFound,
3478 "server_error" => Self::ServerError,
3479 "unknown" => Self::UnknownError,
3480 "max_output_tokens" => Self::MaxOutputTokens,
3481 other => Self::Unknown(other.to_string()),
3482 }
3483 }
3484}
3485
3486impl Serialize for AssistantErrorKind {
3487 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3488 serializer.serialize_str(self.as_str())
3489 }
3490}
3491
3492impl<'de> Deserialize<'de> for AssistantErrorKind {
3493 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3494 let s = String::deserialize(deserializer)?;
3495 Ok(Self::from(s.as_str()))
3496 }
3497}
3498
3499#[derive(Debug, Clone, Serialize, Deserialize)]
3506pub struct CodeChangePublishedMessage {
3507 pub provider: String,
3511 pub url: String,
3513 pub repo: String,
3516 pub identifier: String,
3518 #[serde(default, skip_serializing_if = "Option::is_none")]
3527 pub action: Option<String>,
3528 #[serde(default, skip_serializing_if = "Option::is_none")]
3536 pub branch: Option<String>,
3537 pub uuid: String,
3538 pub session_id: String,
3539}
3540
3541#[derive(Debug, Clone, Serialize, Deserialize)]
3546pub struct VcsStateChangedMessage {
3547 pub kind: VcsMutationKind,
3550 pub cwd: String,
3553 #[serde(default, skip_serializing_if = "Option::is_none")]
3558 pub branch: Option<String>,
3559 pub uuid: String,
3560 pub session_id: String,
3561}
3562
3563#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3565pub enum VcsMutationKind {
3566 Commit,
3567 Push,
3568 Merge,
3569 Rebase,
3570 Unknown(String),
3572}
3573
3574impl VcsMutationKind {
3575 pub fn as_str(&self) -> &str {
3576 match self {
3577 Self::Commit => "commit",
3578 Self::Push => "push",
3579 Self::Merge => "merge",
3580 Self::Rebase => "rebase",
3581 Self::Unknown(s) => s.as_str(),
3582 }
3583 }
3584}
3585
3586impl fmt::Display for VcsMutationKind {
3587 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3588 f.write_str(self.as_str())
3589 }
3590}
3591
3592impl From<&str> for VcsMutationKind {
3593 fn from(s: &str) -> Self {
3594 match s {
3595 "commit" => Self::Commit,
3596 "push" => Self::Push,
3597 "merge" => Self::Merge,
3598 "rebase" => Self::Rebase,
3599 other => Self::Unknown(other.to_string()),
3600 }
3601 }
3602}
3603
3604impl Serialize for VcsMutationKind {
3605 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3606 serializer.serialize_str(self.as_str())
3607 }
3608}
3609
3610impl<'de> Deserialize<'de> for VcsMutationKind {
3611 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3612 let s = String::deserialize(deserializer)?;
3613 Ok(Self::from(s.as_str()))
3614 }
3615}
3616
3617#[derive(Debug, Clone, Serialize, Deserialize)]
3619pub struct FeedbackDraftQueuedMessage {
3620 pub draft_id: String,
3621 pub draft_type: String,
3622 pub title: String,
3623 pub details_preview: String,
3624 #[serde(default, skip_serializing_if = "Option::is_none")]
3625 pub uuid: Option<String>,
3626 #[serde(default, skip_serializing_if = "Option::is_none")]
3627 pub session_id: Option<String>,
3628 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3629 pub extra: serde_json::Map<String, Value>,
3630}
3631
3632#[derive(Debug, Clone, Serialize, Deserialize)]
3640pub struct CloudSessionDeltaMessage {
3641 pub seq: u64,
3645 pub changed: Vec<String>,
3649 pub cloud_session: Value,
3653 pub uuid: String,
3654 pub session_id: String,
3655 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3656 pub extra: serde_json::Map<String, Value>,
3657}
3658
3659#[derive(Debug, Clone, Serialize, Deserialize)]
3675pub struct DevIntentMessage {
3676 pub kind: DevIntentKind,
3678 #[serde(default, skip_serializing_if = "Option::is_none")]
3684 pub trigger: Option<DevIntentTrigger>,
3685 pub uuid: String,
3686 pub session_id: String,
3687 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3688 pub extra: serde_json::Map<String, Value>,
3689}
3690
3691#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3695pub enum DevIntentKind {
3696 IosApp,
3702 AndroidApp,
3709 Unknown(String),
3711}
3712
3713impl DevIntentKind {
3714 pub fn as_str(&self) -> &str {
3715 match self {
3716 Self::IosApp => "ios_app",
3717 Self::AndroidApp => "android_app",
3718 Self::Unknown(s) => s.as_str(),
3719 }
3720 }
3721}
3722
3723impl fmt::Display for DevIntentKind {
3724 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3725 f.write_str(self.as_str())
3726 }
3727}
3728
3729impl From<&str> for DevIntentKind {
3730 fn from(s: &str) -> Self {
3731 match s {
3732 "ios_app" => Self::IosApp,
3733 "android_app" => Self::AndroidApp,
3734 other => Self::Unknown(other.to_string()),
3735 }
3736 }
3737}
3738
3739impl Serialize for DevIntentKind {
3740 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3741 serializer.serialize_str(self.as_str())
3742 }
3743}
3744
3745impl<'de> Deserialize<'de> for DevIntentKind {
3746 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3747 let s = String::deserialize(deserializer)?;
3748 Ok(Self::from(s.as_str()))
3749 }
3750}
3751
3752#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3756pub enum DevIntentTrigger {
3757 SwiftEdit,
3759 UikitImport,
3761 XcodeProject,
3764 IosCommand,
3767 KotlinEdit,
3769 JavaEdit,
3771 AndroidImport,
3773 AndroidManifest,
3775 GradlePlugin,
3777 AndroidCommand,
3780 ProjectScan,
3783 Unknown(String),
3785}
3786
3787impl DevIntentTrigger {
3788 pub fn as_str(&self) -> &str {
3789 match self {
3790 Self::SwiftEdit => "swift_edit",
3791 Self::UikitImport => "uikit_import",
3792 Self::XcodeProject => "xcode_project",
3793 Self::IosCommand => "ios_command",
3794 Self::KotlinEdit => "kotlin_edit",
3795 Self::JavaEdit => "java_edit",
3796 Self::AndroidImport => "android_import",
3797 Self::AndroidManifest => "android_manifest",
3798 Self::GradlePlugin => "gradle_plugin",
3799 Self::AndroidCommand => "android_command",
3800 Self::ProjectScan => "project_scan",
3801 Self::Unknown(s) => s.as_str(),
3802 }
3803 }
3804}
3805
3806impl fmt::Display for DevIntentTrigger {
3807 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3808 f.write_str(self.as_str())
3809 }
3810}
3811
3812impl From<&str> for DevIntentTrigger {
3813 fn from(s: &str) -> Self {
3814 match s {
3815 "swift_edit" => Self::SwiftEdit,
3816 "uikit_import" => Self::UikitImport,
3817 "xcode_project" => Self::XcodeProject,
3818 "ios_command" => Self::IosCommand,
3819 "kotlin_edit" => Self::KotlinEdit,
3820 "java_edit" => Self::JavaEdit,
3821 "android_import" => Self::AndroidImport,
3822 "android_manifest" => Self::AndroidManifest,
3823 "gradle_plugin" => Self::GradlePlugin,
3824 "android_command" => Self::AndroidCommand,
3825 "project_scan" => Self::ProjectScan,
3826 other => Self::Unknown(other.to_string()),
3827 }
3828 }
3829}
3830
3831impl Serialize for DevIntentTrigger {
3832 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3833 serializer.serialize_str(self.as_str())
3834 }
3835}
3836
3837impl<'de> Deserialize<'de> for DevIntentTrigger {
3838 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3839 let s = String::deserialize(deserializer)?;
3840 Ok(Self::from(s.as_str()))
3841 }
3842}
3843
3844#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3848pub enum TaskEndReason {
3849 WorkerRestart,
3852 Unknown(String),
3854}
3855
3856impl TaskEndReason {
3857 pub fn as_str(&self) -> &str {
3858 match self {
3859 Self::WorkerRestart => "worker_restart",
3860 Self::Unknown(s) => s.as_str(),
3861 }
3862 }
3863}
3864
3865impl fmt::Display for TaskEndReason {
3866 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3867 f.write_str(self.as_str())
3868 }
3869}
3870
3871impl From<&str> for TaskEndReason {
3872 fn from(s: &str) -> Self {
3873 match s {
3874 "worker_restart" => Self::WorkerRestart,
3875 other => Self::Unknown(other.to_string()),
3876 }
3877 }
3878}
3879
3880impl Serialize for TaskEndReason {
3881 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3882 serializer.serialize_str(self.as_str())
3883 }
3884}
3885
3886impl<'de> Deserialize<'de> for TaskEndReason {
3887 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3888 let s = String::deserialize(deserializer)?;
3889 Ok(Self::from(s.as_str()))
3890 }
3891}
3892
3893#[derive(Debug, Clone, Serialize, Deserialize)]
3902pub struct TurnHandoffAvailableMessage {
3903 pub v: u64,
3905 pub tools: Vec<String>,
3907 pub worker_epoch: u64,
3909 #[serde(default, skip_serializing_if = "Option::is_none")]
3913 pub relay_marker: Option<bool>,
3914 #[serde(default, skip_serializing_if = "Option::is_none")]
3922 pub staged_files: Option<bool>,
3923 #[serde(default, skip_serializing_if = "Option::is_none")]
3930 pub no_query_first: Option<bool>,
3931 #[serde(default, skip_serializing_if = "Option::is_none")]
3938 pub home_files: Option<bool>,
3939 #[serde(default, skip_serializing_if = "Option::is_none")]
3943 pub file_names: Option<bool>,
3944 #[serde(default, skip_serializing_if = "Option::is_none")]
3954 pub carried_writes: Option<bool>,
3955 pub uuid: String,
3956 pub session_id: String,
3957 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3958 pub extra: serde_json::Map<String, Value>,
3959}
3960
3961#[derive(Debug, Clone, Serialize, Deserialize)]
3973pub struct TurnPreemptedMessage {
3974 pub reason: String,
3978 pub preempted_by_uuid: String,
3982 #[serde(default)]
3985 pub preempted_message_uuids: Vec<String>,
3986 pub uuid: String,
3987 pub session_id: String,
3988 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3989 pub extra: serde_json::Map<String, Value>,
3990}
3991
3992#[derive(Debug, Clone, Serialize, Deserialize)]
4000pub struct PeerMessageHoldMessage {
4001 pub state: PeerMessageHoldState,
4007 #[serde(default, skip_serializing_if = "Option::is_none")]
4014 pub message_uuid: Option<String>,
4015 pub lane: PeerMessageLane,
4017 pub from: String,
4022 #[serde(default, skip_serializing_if = "Option::is_none")]
4025 pub from_name: Option<String>,
4026 #[serde(default, skip_serializing_if = "Option::is_none")]
4028 pub cause: Option<PeerMessageHoldCause>,
4029 #[serde(default, skip_serializing_if = "Option::is_none")]
4031 pub outcome: Option<PeerMessageHoldOutcome>,
4032 pub uuid: String,
4033 pub session_id: String,
4034 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4035 pub extra: serde_json::Map<String, Value>,
4036}
4037
4038#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4040pub enum PeerMessageHoldState {
4041 Held,
4043 Released,
4045 Dropped,
4047 Unknown(String),
4049}
4050
4051impl PeerMessageHoldState {
4052 pub fn as_str(&self) -> &str {
4053 match self {
4054 Self::Held => "held",
4055 Self::Released => "released",
4056 Self::Dropped => "dropped",
4057 Self::Unknown(s) => s.as_str(),
4058 }
4059 }
4060}
4061
4062impl fmt::Display for PeerMessageHoldState {
4063 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4064 f.write_str(self.as_str())
4065 }
4066}
4067
4068impl From<&str> for PeerMessageHoldState {
4069 fn from(s: &str) -> Self {
4070 match s {
4071 "held" => Self::Held,
4072 "released" => Self::Released,
4073 "dropped" => Self::Dropped,
4074 other => Self::Unknown(other.to_string()),
4075 }
4076 }
4077}
4078
4079impl Serialize for PeerMessageHoldState {
4080 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4081 serializer.serialize_str(self.as_str())
4082 }
4083}
4084
4085impl<'de> Deserialize<'de> for PeerMessageHoldState {
4086 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4087 let s = String::deserialize(deserializer)?;
4088 Ok(Self::from(s.as_str()))
4089 }
4090}
4091
4092#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4094pub enum PeerMessageLane {
4095 Bridge,
4097 Stdin,
4099 Socket,
4101 Unknown(String),
4103}
4104
4105impl PeerMessageLane {
4106 pub fn as_str(&self) -> &str {
4107 match self {
4108 Self::Bridge => "bridge",
4109 Self::Stdin => "stdin",
4110 Self::Socket => "socket",
4111 Self::Unknown(s) => s.as_str(),
4112 }
4113 }
4114}
4115
4116impl fmt::Display for PeerMessageLane {
4117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4118 f.write_str(self.as_str())
4119 }
4120}
4121
4122impl From<&str> for PeerMessageLane {
4123 fn from(s: &str) -> Self {
4124 match s {
4125 "bridge" => Self::Bridge,
4126 "stdin" => Self::Stdin,
4127 "socket" => Self::Socket,
4128 other => Self::Unknown(other.to_string()),
4129 }
4130 }
4131}
4132
4133impl Serialize for PeerMessageLane {
4134 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4135 serializer.serialize_str(self.as_str())
4136 }
4137}
4138
4139impl<'de> Deserialize<'de> for PeerMessageLane {
4140 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4141 let s = String::deserialize(deserializer)?;
4142 Ok(Self::from(s.as_str()))
4143 }
4144}
4145
4146#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4153pub enum PeerMessageHoldCause {
4154 ExplicitSetting,
4155 ManagedSetting,
4156 RepoSetting,
4157 InvalidSetting,
4158 BypassDefault,
4159 ModeUnknown,
4160 ModeMismatch,
4161 NoModeAsserted,
4162 Unknown(String),
4164}
4165
4166impl PeerMessageHoldCause {
4167 pub fn as_str(&self) -> &str {
4168 match self {
4169 Self::ExplicitSetting => "explicit-setting",
4170 Self::ManagedSetting => "managed-setting",
4171 Self::RepoSetting => "repo-setting",
4172 Self::InvalidSetting => "invalid-setting",
4173 Self::BypassDefault => "bypass-default",
4174 Self::ModeUnknown => "mode-unknown",
4175 Self::ModeMismatch => "mode-mismatch",
4176 Self::NoModeAsserted => "no-mode-asserted",
4177 Self::Unknown(s) => s.as_str(),
4178 }
4179 }
4180}
4181
4182impl fmt::Display for PeerMessageHoldCause {
4183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4184 f.write_str(self.as_str())
4185 }
4186}
4187
4188impl From<&str> for PeerMessageHoldCause {
4189 fn from(s: &str) -> Self {
4190 match s {
4191 "explicit-setting" => Self::ExplicitSetting,
4192 "managed-setting" => Self::ManagedSetting,
4193 "repo-setting" => Self::RepoSetting,
4194 "invalid-setting" => Self::InvalidSetting,
4195 "bypass-default" => Self::BypassDefault,
4196 "mode-unknown" => Self::ModeUnknown,
4197 "mode-mismatch" => Self::ModeMismatch,
4198 "no-mode-asserted" => Self::NoModeAsserted,
4199 other => Self::Unknown(other.to_string()),
4200 }
4201 }
4202}
4203
4204impl Serialize for PeerMessageHoldCause {
4205 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4206 serializer.serialize_str(self.as_str())
4207 }
4208}
4209
4210impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4211 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4212 let s = String::deserialize(deserializer)?;
4213 Ok(Self::from(s.as_str()))
4214 }
4215}
4216
4217#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4220pub enum PeerMessageHoldOutcome {
4221 Expired,
4225 Refused,
4227 Dropped,
4230 Discarded,
4232 Unknown(String),
4234}
4235
4236impl PeerMessageHoldOutcome {
4237 pub fn as_str(&self) -> &str {
4238 match self {
4239 Self::Expired => "expired",
4240 Self::Refused => "refused",
4241 Self::Dropped => "dropped",
4242 Self::Discarded => "discarded",
4243 Self::Unknown(s) => s.as_str(),
4244 }
4245 }
4246}
4247
4248impl fmt::Display for PeerMessageHoldOutcome {
4249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4250 f.write_str(self.as_str())
4251 }
4252}
4253
4254impl From<&str> for PeerMessageHoldOutcome {
4255 fn from(s: &str) -> Self {
4256 match s {
4257 "expired" => Self::Expired,
4258 "refused" => Self::Refused,
4259 "dropped" => Self::Dropped,
4260 "discarded" => Self::Discarded,
4261 other => Self::Unknown(other.to_string()),
4262 }
4263 }
4264}
4265
4266impl Serialize for PeerMessageHoldOutcome {
4267 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4268 serializer.serialize_str(self.as_str())
4269 }
4270}
4271
4272impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4273 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4274 let s = String::deserialize(deserializer)?;
4275 Ok(Self::from(s.as_str()))
4276 }
4277}
4278
4279#[derive(Debug, Clone, Serialize, Deserialize)]
4290pub struct PerTurnEffortChangedMessage {
4291 pub per_turn_effort_active: bool,
4293 pub uuid: String,
4294 pub session_id: String,
4295}
4296
4297#[derive(Debug, Clone, Serialize, Deserialize)]
4304pub struct InstructionSizeWarningMessage {
4305 pub total_chars: u64,
4308 pub total_limit_chars: u64,
4311 pub file_count: u64,
4314 #[serde(default, skip_serializing_if = "Option::is_none")]
4318 pub largest_chars: Option<u64>,
4319 pub uuid: String,
4320 pub session_id: String,
4321}
4322
4323#[derive(Debug, Clone, Serialize, Deserialize)]
4330pub struct FileAttachmentsMissingMessage {
4331 pub message_uuid: String,
4334 pub sent_count: u64,
4336 pub missing: Vec<MissingFileAttachment>,
4338 pub uuid: String,
4339 pub session_id: String,
4340}
4341
4342#[derive(Debug, Clone, Serialize, Deserialize)]
4352pub struct PermissionCheckStatusMessage {
4353 pub tool_use_id: String,
4354 #[serde(default, skip_serializing_if = "Option::is_none")]
4357 pub agent_id: Option<String>,
4358 pub status: PermissionCheckStatus,
4359 pub uuid: String,
4360 pub session_id: String,
4361}
4362
4363#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4365pub enum PermissionCheckStatus {
4366 Checking,
4369 Done,
4371 Unknown(String),
4373}
4374
4375impl PermissionCheckStatus {
4376 pub fn as_str(&self) -> &str {
4377 match self {
4378 Self::Checking => "checking",
4379 Self::Done => "done",
4380 Self::Unknown(s) => s.as_str(),
4381 }
4382 }
4383}
4384
4385impl fmt::Display for PermissionCheckStatus {
4386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4387 f.write_str(self.as_str())
4388 }
4389}
4390
4391impl From<&str> for PermissionCheckStatus {
4392 fn from(s: &str) -> Self {
4393 match s {
4394 "checking" => Self::Checking,
4395 "done" => Self::Done,
4396 other => Self::Unknown(other.to_string()),
4397 }
4398 }
4399}
4400
4401impl Serialize for PermissionCheckStatus {
4402 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4403 serializer.serialize_str(self.as_str())
4404 }
4405}
4406
4407impl<'de> Deserialize<'de> for PermissionCheckStatus {
4408 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4409 let s = String::deserialize(deserializer)?;
4410 Ok(Self::from(s.as_str()))
4411 }
4412}
4413
4414#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4416pub struct MissingFileAttachment {
4417 pub file_uuid: String,
4419 pub reason: String,
4424}
4425
4426#[derive(Debug, Clone, Serialize, Deserialize)]
4436pub struct SessionTitleChangedMessage {
4437 pub title: String,
4442 pub uuid: String,
4443 pub session_id: String,
4444}
4445
4446#[derive(Debug, Clone, Serialize, Deserialize)]
4450pub struct UiLogMessage {
4451 pub plugin: String,
4453 pub text: String,
4457 pub uuid: String,
4458 pub session_id: String,
4459}
4460
4461#[derive(Debug, Clone, Serialize, Deserialize)]
4466pub struct UiToastMessage {
4467 pub plugin: String,
4469 pub text: String,
4473 pub timeout_ms: u64,
4476 pub uuid: String,
4477 pub session_id: String,
4478}
4479
4480#[derive(Debug, Clone, Serialize, Deserialize)]
4484pub struct UiStatusMessage {
4485 pub plugin: String,
4487 pub text: Option<String>,
4492 pub uuid: String,
4493 pub session_id: String,
4494}
4495
4496#[derive(Debug, Clone, Serialize, Deserialize)]
4501pub struct UiInvalidateMessage {
4502 pub event: String,
4505 #[serde(default, skip_serializing_if = "Option::is_none")]
4510 pub instances: Option<Vec<UiInvalidatedInstance>>,
4511 pub uuid: String,
4512 pub session_id: String,
4513}
4514
4515#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4517pub struct UiInvalidatedInstance {
4518 pub surface: String,
4520 pub component: String,
4522 pub instance_id: String,
4525}
4526
4527#[derive(Debug, Clone, Serialize, Deserialize)]
4534pub struct UiPanesMessage {
4535 pub panes: Vec<UiPane>,
4538 pub shown_id: Option<String>,
4541 pub focused_id: Option<String>,
4544 pub focus_requested_id: Option<String>,
4548 pub uuid: String,
4549 pub session_id: String,
4550}
4551
4552#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4554pub struct UiPane {
4555 pub id: String,
4558 pub title: String,
4561 pub plugin: String,
4563 #[serde(default, skip_serializing_if = "Option::is_none")]
4566 pub close_on_escape: Option<bool>,
4567 #[serde(default, skip_serializing_if = "Option::is_none")]
4570 pub hold_toasts: Option<bool>,
4571 #[serde(default, skip_serializing_if = "Option::is_none")]
4574 pub rows: Option<u64>,
4575 #[serde(default, skip_serializing_if = "Option::is_none")]
4578 pub columns: Option<u64>,
4579}
4580
4581#[derive(Debug, Clone, Serialize, Deserialize)]
4585pub struct UiScrollMessage {
4586 pub client_id: String,
4588 pub component: UiSiteComponent,
4590 pub instance_id: String,
4592 pub offset: u64,
4595 #[serde(default, skip_serializing_if = "Option::is_none")]
4598 pub follow_end: Option<bool>,
4599 pub uuid: String,
4600 pub session_id: String,
4601}
4602
4603#[derive(Debug, Clone, Serialize, Deserialize)]
4608pub struct UiFocusMessage {
4609 pub client_id: String,
4611 pub component: UiSiteComponent,
4613 pub instance_id: String,
4615 pub plugin: String,
4617 pub key: String,
4619 pub uuid: String,
4620 pub session_id: String,
4621}
4622
4623#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4625pub enum UiSiteComponent {
4626 Pane,
4628 AbovePrompt,
4630 Unknown(String),
4632}
4633
4634impl UiSiteComponent {
4635 pub fn as_str(&self) -> &str {
4636 match self {
4637 Self::Pane => "Pane",
4638 Self::AbovePrompt => "AbovePrompt",
4639 Self::Unknown(s) => s.as_str(),
4640 }
4641 }
4642}
4643
4644impl fmt::Display for UiSiteComponent {
4645 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4646 f.write_str(self.as_str())
4647 }
4648}
4649
4650impl From<&str> for UiSiteComponent {
4651 fn from(s: &str) -> Self {
4652 match s {
4653 "Pane" => Self::Pane,
4654 "AbovePrompt" => Self::AbovePrompt,
4655 other => Self::Unknown(other.to_string()),
4656 }
4657 }
4658}
4659
4660impl Serialize for UiSiteComponent {
4661 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4662 serializer.serialize_str(self.as_str())
4663 }
4664}
4665
4666impl<'de> Deserialize<'de> for UiSiteComponent {
4667 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4668 let s = String::deserialize(deserializer)?;
4669 Ok(Self::from(s.as_str()))
4670 }
4671}
4672
4673#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4676pub struct LocalCommandRun {
4677 pub command: String,
4679 pub args: String,
4682}
4683
4684#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4688pub struct LocalCommandOutcome {
4689 pub kind: LocalCommandOutcomeKind,
4690 #[serde(default, skip_serializing_if = "Option::is_none")]
4693 pub suggestion: Option<String>,
4694}
4695
4696#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4701pub enum LocalCommandOutcomeKind {
4702 UnavailableHeadless,
4704 Unknown,
4707 Failed,
4709 RestartRequired,
4711 Other(String),
4713}
4714
4715impl LocalCommandOutcomeKind {
4716 pub fn as_str(&self) -> &str {
4717 match self {
4718 Self::UnavailableHeadless => "unavailable_headless",
4719 Self::Unknown => "unknown",
4720 Self::Failed => "failed",
4721 Self::RestartRequired => "restart_required",
4722 Self::Other(s) => s.as_str(),
4723 }
4724 }
4725}
4726
4727impl fmt::Display for LocalCommandOutcomeKind {
4728 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4729 f.write_str(self.as_str())
4730 }
4731}
4732
4733impl From<&str> for LocalCommandOutcomeKind {
4734 fn from(s: &str) -> Self {
4735 match s {
4736 "unavailable_headless" => Self::UnavailableHeadless,
4737 "unknown" => Self::Unknown,
4738 "failed" => Self::Failed,
4739 "restart_required" => Self::RestartRequired,
4740 other => Self::Other(other.to_string()),
4741 }
4742 }
4743}
4744
4745impl Serialize for LocalCommandOutcomeKind {
4746 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4747 serializer.serialize_str(self.as_str())
4748 }
4749}
4750
4751impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4752 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4753 let s = String::deserialize(deserializer)?;
4754 Ok(Self::from(s.as_str()))
4755 }
4756}
4757
4758#[derive(Debug, Clone, Serialize, Deserialize)]
4764pub struct UsageReport {
4765 pub session: super::control::UsageSession,
4767 pub rate_limits: Option<UsageReportRateLimits>,
4771 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4772 pub extra: serde_json::Map<String, Value>,
4773}
4774
4775#[derive(Debug, Clone, Serialize, Deserialize)]
4777pub struct UsageReportRateLimits {
4778 pub limits: Option<Vec<UsageReportLimit>>,
4786 #[serde(default, skip_serializing_if = "Option::is_none")]
4789 pub extra_usage: Option<UsageReportExtraUsage>,
4790 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4791 pub extra: serde_json::Map<String, Value>,
4792}
4793
4794#[derive(Debug, Clone, Serialize, Deserialize)]
4796pub struct UsageReportLimit {
4797 pub kind: String,
4800 pub group: String,
4803 pub percent: f64,
4805 pub resets_at: Option<String>,
4807 #[serde(default, skip_serializing_if = "Option::is_none")]
4810 pub scope: Option<UsageReportScope>,
4811 #[serde(default, skip_serializing_if = "Option::is_none")]
4815 pub severity: Option<String>,
4816 #[serde(default, skip_serializing_if = "Option::is_none")]
4818 pub is_active: Option<bool>,
4819 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4820 pub extra: serde_json::Map<String, Value>,
4821}
4822
4823#[derive(Debug, Clone, Serialize, Deserialize)]
4825pub struct UsageReportScope {
4826 #[serde(default, skip_serializing_if = "Option::is_none")]
4827 pub model: Option<UsageReportScopeLabel>,
4828 #[serde(default, skip_serializing_if = "Option::is_none")]
4829 pub surface: Option<UsageReportScopeLabel>,
4830 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4831 pub extra: serde_json::Map<String, Value>,
4832}
4833
4834#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4836pub struct UsageReportScopeLabel {
4837 pub display_name: String,
4838}
4839
4840#[derive(Debug, Clone, Serialize, Deserialize)]
4844pub struct UsageReportExtraUsage {
4845 pub is_enabled: bool,
4847 pub monthly_limit: Option<f64>,
4848 pub used_credits: Option<f64>,
4849 pub utilization: Option<f64>,
4850 #[serde(default, skip_serializing_if = "Option::is_none")]
4851 pub currency: Option<String>,
4852 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4853 pub extra: serde_json::Map<String, Value>,
4854}
4855
4856#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4859pub struct BatchToolUse {
4860 pub id: String,
4861 pub name: String,
4862}
4863
4864#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4869pub struct ContextUsage {
4870 pub model: String,
4872 pub total_tokens: u64,
4875 pub raw_max_tokens: u64,
4878 pub percentage: u64,
4880 #[serde(default, skip_serializing_if = "Option::is_none")]
4882 pub over_limit: Option<ContextOverLimit>,
4883 #[serde(default)]
4885 pub categories: Vec<ContextCategory>,
4886 #[serde(default)]
4888 pub mcp_tools: Vec<ContextMcpTool>,
4889 #[serde(default)]
4891 pub memory_files: Vec<ContextMemoryFile>,
4892 #[serde(default)]
4894 pub agents: Vec<ContextAgent>,
4895 #[serde(default, skip_serializing_if = "Option::is_none")]
4897 pub skills: Option<Vec<ContextSkill>>,
4898}
4899
4900#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4902pub struct ContextOverLimit {
4903 pub tokens_over: u64,
4904 pub kind: String,
4907}
4908
4909#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4911pub struct ContextCategory {
4912 pub name: String,
4915 pub tokens: u64,
4916 pub kind: String,
4920}
4921
4922#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4924pub struct ContextMcpTool {
4925 pub name: String,
4927 pub server_name: String,
4928 pub tokens: u64,
4929}
4930
4931#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4933pub struct ContextMemoryFile {
4934 pub path: String,
4935 #[serde(rename = "type")]
4937 pub file_type: String,
4938 pub tokens: u64,
4939}
4940
4941#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4943pub struct ContextAgent {
4944 pub agent_type: String,
4945 pub source: String,
4947 pub tokens: u64,
4948}
4949
4950#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4952pub struct ContextSkill {
4953 pub name: String,
4954 pub source: String,
4956 #[serde(default, skip_serializing_if = "Option::is_none")]
4957 pub plugin_name: Option<String>,
4958 pub tokens: u64,
4959}
4960
4961#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4963pub struct ToolUseMeta {
4964 pub id: String,
4965 pub display_name: String,
4966 #[serde(default, skip_serializing_if = "Option::is_none")]
4967 pub server_display_name: Option<String>,
4968 #[serde(default, skip_serializing_if = "Option::is_none")]
4969 pub icon_url: Option<String>,
4970}
4971
4972#[derive(Debug, Clone, Serialize, Deserialize)]
4974pub struct AssistantMessage {
4975 pub message: AssistantMessageContent,
4976 #[serde(alias = "sessionId")]
4977 pub session_id: String,
4978 #[serde(skip_serializing_if = "Option::is_none")]
4979 pub uuid: Option<String>,
4980 #[serde(skip_serializing_if = "Option::is_none")]
4981 pub parent_tool_use_id: Option<String>,
4982 #[serde(skip_serializing_if = "Option::is_none")]
4984 pub request_id: Option<String>,
4985 #[serde(default, skip_serializing_if = "Option::is_none")]
4994 pub user_message_uuid: Option<String>,
4995 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5001 pub user_message_uuids: Vec<String>,
5002 #[serde(default, skip_serializing_if = "Option::is_none")]
5006 pub agent_id: Option<String>,
5007 #[serde(skip_serializing_if = "Option::is_none")]
5010 pub subagent_type: Option<String>,
5011 #[serde(skip_serializing_if = "Option::is_none")]
5013 pub task_description: Option<String>,
5014 #[serde(skip_serializing_if = "Option::is_none")]
5015 pub error: Option<AssistantErrorKind>,
5016 #[serde(default, skip_serializing_if = "Option::is_none")]
5020 pub aborted: Option<bool>,
5021 #[serde(default, skip_serializing_if = "Option::is_none")]
5026 pub resumed_from_incomplete_thinking: Option<bool>,
5027 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5028 pub supersedes: Vec<String>,
5029 #[serde(skip_serializing_if = "Option::is_none")]
5030 pub timestamp: Option<String>,
5031 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5032 pub tool_use_meta: Vec<ToolUseMeta>,
5033 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5038 pub batch_tool_uses: Vec<BatchToolUse>,
5039 #[serde(default, skip_serializing_if = "Option::is_none")]
5045 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
5046 #[serde(default, skip_serializing_if = "Option::is_none")]
5052 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
5053 #[serde(default, skip_serializing_if = "Option::is_none")]
5057 pub historical: Option<bool>,
5058 #[serde(default, skip_serializing_if = "Option::is_none")]
5066 pub resume_reason: Option<String>,
5067 #[serde(default, skip_serializing_if = "Option::is_none")]
5073 pub local_command_source: Option<String>,
5074 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5083 pub narration_block_indexes: Vec<usize>,
5084 #[serde(default, skip_serializing_if = "Option::is_none")]
5088 pub narration_hint: Option<String>,
5089 #[serde(default, skip_serializing_if = "Option::is_none")]
5098 pub thinking_duration_ms: Option<u64>,
5099 #[serde(default, skip_serializing_if = "Option::is_none")]
5103 pub context_usage: Option<ContextUsage>,
5104 #[serde(default, skip_serializing_if = "Option::is_none")]
5112 pub usage_report: Option<Box<UsageReport>>,
5113 #[serde(default, skip_serializing_if = "Option::is_none")]
5120 pub local_command_run: Option<LocalCommandRun>,
5121 #[serde(default, skip_serializing_if = "Option::is_none")]
5127 pub local_command_outcome: Option<LocalCommandOutcome>,
5128 #[serde(default, skip_serializing_if = "Option::is_none")]
5129 pub is_meta: Option<bool>,
5130 #[serde(default, skip_serializing_if = "Option::is_none")]
5131 pub is_virtual: Option<bool>,
5132 #[serde(default, skip_serializing_if = "Option::is_none")]
5133 pub is_api_error_message: Option<bool>,
5134 #[serde(skip_serializing_if = "Option::is_none")]
5135 pub api_error_status: Option<u16>,
5136 #[serde(skip_serializing_if = "Option::is_none")]
5156 pub api_error: Option<String>,
5157 #[serde(default, skip_serializing_if = "Option::is_none")]
5164 pub api_error_code: Option<String>,
5165 #[serde(default, skip_serializing_if = "Option::is_none")]
5168 pub api_error_params: Option<ApiErrorParams>,
5169 #[serde(skip_serializing_if = "Option::is_none")]
5170 pub error_details: Option<String>,
5171 #[serde(skip_serializing_if = "Option::is_none")]
5172 pub advisor_model: Option<String>,
5173 #[serde(skip_serializing_if = "Option::is_none")]
5174 pub attribution_agent: Option<String>,
5175 #[serde(skip_serializing_if = "Option::is_none")]
5176 pub attribution_skill: Option<String>,
5177 #[serde(skip_serializing_if = "Option::is_none")]
5178 pub attribution_plugin: Option<String>,
5179 #[serde(skip_serializing_if = "Option::is_none")]
5180 pub attribution_mcp_server: Option<String>,
5181 #[serde(skip_serializing_if = "Option::is_none")]
5182 pub attribution_mcp_tool: Option<String>,
5183}
5184
5185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5189pub struct ApiErrorParams {
5190 #[serde(default, skip_serializing_if = "Option::is_none")]
5192 pub effort: Option<String>,
5193 #[serde(default, skip_serializing_if = "Option::is_none")]
5196 pub provider: Option<ApiErrorProvider>,
5197 #[serde(default, skip_serializing_if = "Option::is_none")]
5200 pub remedy: Option<ApiErrorRemedy>,
5201 #[serde(default, skip_serializing_if = "Option::is_none")]
5204 pub media: Option<ApiErrorMedia>,
5205 #[serde(default, skip_serializing_if = "Option::is_none")]
5207 pub media_reason: Option<ApiErrorMediaReason>,
5208 #[serde(default, skip_serializing_if = "Option::is_none")]
5216 pub rate_limit_info: Option<RateLimitInfo>,
5217}
5218
5219#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5221pub enum ApiErrorMedia {
5222 Image,
5223 Document,
5224 Unknown(String),
5226}
5227
5228impl ApiErrorMedia {
5229 pub fn as_str(&self) -> &str {
5230 match self {
5231 Self::Image => "image",
5232 Self::Document => "document",
5233 Self::Unknown(s) => s.as_str(),
5234 }
5235 }
5236}
5237
5238impl fmt::Display for ApiErrorMedia {
5239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5240 f.write_str(self.as_str())
5241 }
5242}
5243
5244impl From<&str> for ApiErrorMedia {
5245 fn from(s: &str) -> Self {
5246 match s {
5247 "image" => Self::Image,
5248 "document" => Self::Document,
5249 other => Self::Unknown(other.to_string()),
5250 }
5251 }
5252}
5253
5254impl Serialize for ApiErrorMedia {
5255 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5256 serializer.serialize_str(self.as_str())
5257 }
5258}
5259
5260impl<'de> Deserialize<'de> for ApiErrorMedia {
5261 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5262 let s = String::deserialize(deserializer)?;
5263 Ok(Self::from(s.as_str()))
5264 }
5265}
5266
5267#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5270pub enum ApiErrorMediaReason {
5271 Unprocessable,
5274 UnsupportedByModel,
5276 MediaBudget,
5279 Unknown(String),
5281}
5282
5283impl ApiErrorMediaReason {
5284 pub fn as_str(&self) -> &str {
5285 match self {
5286 Self::Unprocessable => "unprocessable",
5287 Self::UnsupportedByModel => "unsupported_by_model",
5288 Self::MediaBudget => "media_budget",
5289 Self::Unknown(s) => s.as_str(),
5290 }
5291 }
5292}
5293
5294impl fmt::Display for ApiErrorMediaReason {
5295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5296 f.write_str(self.as_str())
5297 }
5298}
5299
5300impl From<&str> for ApiErrorMediaReason {
5301 fn from(s: &str) -> Self {
5302 match s {
5303 "unprocessable" => Self::Unprocessable,
5304 "unsupported_by_model" => Self::UnsupportedByModel,
5305 "media_budget" => Self::MediaBudget,
5306 other => Self::Unknown(other.to_string()),
5307 }
5308 }
5309}
5310
5311impl Serialize for ApiErrorMediaReason {
5312 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5313 serializer.serialize_str(self.as_str())
5314 }
5315}
5316
5317impl<'de> Deserialize<'de> for ApiErrorMediaReason {
5318 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5319 let s = String::deserialize(deserializer)?;
5320 Ok(Self::from(s.as_str()))
5321 }
5322}
5323
5324#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5326pub enum ApiErrorProvider {
5327 Bedrock,
5328 AnthropicAws,
5329 Mantle,
5330 AnthropicGoogleCloud,
5331 Vertex,
5332 Foundry,
5333 Gateway,
5334 Unknown(String),
5336}
5337
5338impl ApiErrorProvider {
5339 pub fn as_str(&self) -> &str {
5340 match self {
5341 Self::Bedrock => "bedrock",
5342 Self::AnthropicAws => "anthropicAws",
5343 Self::Mantle => "mantle",
5344 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
5345 Self::Vertex => "vertex",
5346 Self::Foundry => "foundry",
5347 Self::Gateway => "gateway",
5348 Self::Unknown(s) => s.as_str(),
5349 }
5350 }
5351}
5352
5353impl fmt::Display for ApiErrorProvider {
5354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5355 f.write_str(self.as_str())
5356 }
5357}
5358
5359impl From<&str> for ApiErrorProvider {
5360 fn from(s: &str) -> Self {
5361 match s {
5362 "bedrock" => Self::Bedrock,
5363 "anthropicAws" => Self::AnthropicAws,
5364 "mantle" => Self::Mantle,
5365 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
5366 "vertex" => Self::Vertex,
5367 "foundry" => Self::Foundry,
5368 "gateway" => Self::Gateway,
5369 other => Self::Unknown(other.to_string()),
5370 }
5371 }
5372}
5373
5374impl Serialize for ApiErrorProvider {
5375 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5376 serializer.serialize_str(self.as_str())
5377 }
5378}
5379
5380impl<'de> Deserialize<'de> for ApiErrorProvider {
5381 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5382 let s = String::deserialize(deserializer)?;
5383 Ok(Self::from(s.as_str()))
5384 }
5385}
5386
5387#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5389pub enum ApiErrorRemedy {
5390 RefreshCommand,
5393 RefreshCredentials,
5395 Adc,
5398 GatewayToken,
5401 HostManaged,
5403 ModelAccess,
5406 Unknown(String),
5408}
5409
5410impl ApiErrorRemedy {
5411 pub fn as_str(&self) -> &str {
5412 match self {
5413 Self::RefreshCommand => "refresh_command",
5414 Self::RefreshCredentials => "refresh_credentials",
5415 Self::Adc => "adc",
5416 Self::GatewayToken => "gateway_token",
5417 Self::HostManaged => "host_managed",
5418 Self::ModelAccess => "model_access",
5419 Self::Unknown(s) => s.as_str(),
5420 }
5421 }
5422}
5423
5424impl fmt::Display for ApiErrorRemedy {
5425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5426 f.write_str(self.as_str())
5427 }
5428}
5429
5430impl From<&str> for ApiErrorRemedy {
5431 fn from(s: &str) -> Self {
5432 match s {
5433 "refresh_command" => Self::RefreshCommand,
5434 "refresh_credentials" => Self::RefreshCredentials,
5435 "adc" => Self::Adc,
5436 "gateway_token" => Self::GatewayToken,
5437 "host_managed" => Self::HostManaged,
5438 "model_access" => Self::ModelAccess,
5439 other => Self::Unknown(other.to_string()),
5440 }
5441 }
5442}
5443
5444impl Serialize for ApiErrorRemedy {
5445 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5446 serializer.serialize_str(self.as_str())
5447 }
5448}
5449
5450impl<'de> Deserialize<'de> for ApiErrorRemedy {
5451 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5452 let s = String::deserialize(deserializer)?;
5453 Ok(Self::from(s.as_str()))
5454 }
5455}
5456
5457#[derive(Debug, Clone, Serialize, Deserialize)]
5459pub struct AssistantMessageContent {
5460 pub id: String,
5461 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
5463 pub message_type: Option<String>,
5464 pub role: MessageRole,
5465 pub model: String,
5466 pub content: Vec<ContentBlock>,
5467 #[serde(skip_serializing_if = "Option::is_none")]
5468 pub stop_reason: Option<StopReason>,
5469 #[serde(skip_serializing_if = "Option::is_none")]
5470 pub stop_sequence: Option<String>,
5471 #[serde(skip_serializing_if = "Option::is_none")]
5472 pub usage: Option<AssistantUsage>,
5473 #[serde(skip_serializing_if = "Option::is_none")]
5475 pub stop_details: Option<Value>,
5476 #[serde(skip_serializing_if = "Option::is_none")]
5478 pub context_management: Option<Value>,
5479}
5480
5481#[derive(Debug, Clone, Serialize, Deserialize)]
5483pub struct AssistantUsage {
5484 #[serde(default)]
5486 pub input_tokens: u32,
5487
5488 #[serde(default)]
5490 pub output_tokens: u32,
5491
5492 #[serde(default)]
5494 pub cache_creation_input_tokens: u32,
5495
5496 #[serde(default)]
5498 pub cache_read_input_tokens: u32,
5499
5500 #[serde(skip_serializing_if = "Option::is_none")]
5502 pub service_tier: Option<String>,
5503
5504 #[serde(skip_serializing_if = "Option::is_none")]
5506 pub cache_creation: Option<CacheCreationDetails>,
5507
5508 #[serde(skip_serializing_if = "Option::is_none")]
5510 pub inference_geo: Option<String>,
5511
5512 #[serde(default, skip_serializing_if = "Option::is_none")]
5515 pub fallback_credit: Option<Value>,
5516}
5517
5518#[derive(Debug, Clone, Serialize, Deserialize)]
5520pub struct CacheCreationDetails {
5521 #[serde(default)]
5523 pub ephemeral_1h_input_tokens: u32,
5524
5525 #[serde(default)]
5527 pub ephemeral_5m_input_tokens: u32,
5528}
5529
5530#[cfg(test)]
5531mod tests {
5532 use crate::io::ClaudeOutput;
5533
5534 #[test]
5535 fn test_subagent_usage_rollup_accumulates_task_results() {
5536 use super::SubagentUsageRollup;
5537
5538 let mut rollup = SubagentUsageRollup::default();
5539
5540 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}}"#;
5541 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5542 assert!(rollup.observe(&output));
5543 assert_eq!(rollup.subagent_tokens, 10201);
5544 assert_eq!(rollup.agent_count, 1);
5545 assert_eq!(rollup.tool_uses, 3);
5546 assert_eq!(rollup.duration_ms, 1853);
5547
5548 assert!(!rollup.observe(&output));
5550 assert_eq!(rollup.agent_count, 1);
5551 assert_eq!(rollup.subagent_tokens, 10201);
5552
5553 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}}"#;
5555 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5556 assert!(rollup.observe(&output));
5557 assert_eq!(rollup.agent_count, 2);
5558 assert_eq!(rollup.subagent_tokens, 10701);
5559 }
5560
5561 #[test]
5562 fn test_subagent_usage_rollup_ignores_non_task_results() {
5563 use super::SubagentUsageRollup;
5564
5565 let mut rollup = SubagentUsageRollup::default();
5566
5567 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}}"#;
5570 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5571 assert!(!rollup.observe(&output));
5572
5573 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5575 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5576 assert!(!rollup.observe(&output));
5577
5578 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5580 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5581 assert!(!rollup.observe(&output));
5582
5583 assert_eq!(rollup, SubagentUsageRollup::default());
5584 }
5585
5586 #[test]
5587 fn test_subagent_usage_rollup_over_captured_session() {
5588 use super::SubagentUsageRollup;
5589
5590 let mut rollup = SubagentUsageRollup::default();
5591 let fixture =
5592 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5593 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5594 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5595 rollup.observe(&output);
5596 }
5597 }
5598 assert_eq!(rollup.agent_count, 1);
5599 assert_eq!(rollup.subagent_tokens, 10201);
5600 }
5601
5602 #[test]
5603 fn test_system_message_init() {
5604 let json = r#"{
5605 "type": "system",
5606 "subtype": "init",
5607 "session_id": "test-session-123",
5608 "cwd": "/home/user/project",
5609 "model": "claude-sonnet-4",
5610 "tools": ["Bash", "Read", "Write"],
5611 "mcp_servers": [],
5612 "slash_commands": ["compact", "cost", "review"],
5613 "agents": ["Bash", "Explore", "Plan"],
5614 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5615 "skills": [],
5616 "claude_code_version": "2.1.15",
5617 "apiKeySource": "none",
5618 "output_style": "default",
5619 "permissionMode": "default"
5620 }"#;
5621
5622 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5623 if let ClaudeOutput::System(sys) = output {
5624 assert!(sys.is_init());
5625 assert!(!sys.is_status());
5626 assert!(!sys.is_compact_boundary());
5627
5628 let init = sys.as_init().expect("Should parse as init");
5629 assert_eq!(init.session_id, "test-session-123");
5630 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5631 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5632 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5633 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5634 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5635 assert_eq!(init.plugins.len(), 1);
5636 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5637 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5638 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5639 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5640 assert_eq!(
5641 init.permission_mode,
5642 Some(super::InitPermissionMode::Default)
5643 );
5644 } else {
5645 panic!("Expected System message");
5646 }
5647 }
5648
5649 #[test]
5650 fn test_system_message_init_from_real_capture() {
5651 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5652 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5653 if let ClaudeOutput::System(sys) = output {
5654 let init = sys.as_init().expect("Should parse real init capture");
5655 assert_eq!(init.slash_commands.len(), 8);
5656 assert!(init.slash_commands.contains(&"compact".to_string()));
5657 assert!(init.slash_commands.contains(&"review".to_string()));
5658 assert_eq!(init.agents.len(), 5);
5659 assert!(init.agents.contains(&"Bash".to_string()));
5660 assert!(init.agents.contains(&"Explore".to_string()));
5661 assert_eq!(init.plugins.len(), 1);
5662 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5663 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5664 } else {
5665 panic!("Expected System message");
5666 }
5667 }
5668
5669 #[test]
5670 fn test_system_message_status() {
5671 let json = r#"{
5672 "type": "system",
5673 "subtype": "status",
5674 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5675 "status": "compacting",
5676 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5677 }"#;
5678
5679 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5680 if let ClaudeOutput::System(sys) = output {
5681 assert!(sys.is_status());
5682 assert!(!sys.is_init());
5683
5684 let status = sys.as_status().expect("Should parse as status");
5685 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5686 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5687 assert_eq!(
5688 status.uuid,
5689 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5690 );
5691 } else {
5692 panic!("Expected System message");
5693 }
5694 }
5695
5696 #[test]
5697 fn test_system_message_status_null() {
5698 let json = r#"{
5699 "type": "system",
5700 "subtype": "status",
5701 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5702 "status": null,
5703 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5704 }"#;
5705
5706 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5707 if let ClaudeOutput::System(sys) = output {
5708 let status = sys.as_status().expect("Should parse as status");
5709 assert_eq!(status.status, None);
5710 } else {
5711 panic!("Expected System message");
5712 }
5713 }
5714
5715 #[test]
5716 fn test_system_message_task_started() {
5717 let json = r#"{
5718 "type": "system",
5719 "subtype": "task_started",
5720 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5721 "task_id": "b6daf3f",
5722 "task_type": "local_bash",
5723 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5724 "description": "Wait for CI on PR #12",
5725 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5726 }"#;
5727
5728 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5729 if let ClaudeOutput::System(sys) = output {
5730 assert!(sys.is_task_started());
5731 assert!(!sys.is_task_progress());
5732 assert!(!sys.is_task_notification());
5733
5734 let task = sys.as_task_started().expect("Should parse as task_started");
5735 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5736 assert_eq!(task.task_id, "b6daf3f");
5737 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5738 assert_eq!(
5739 task.tool_use_id.as_deref(),
5740 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5741 );
5742 assert_eq!(task.description, "Wait for CI on PR #12");
5743 } else {
5744 panic!("Expected System message");
5745 }
5746 }
5747
5748 #[test]
5749 fn test_system_message_task_started_agent() {
5750 let json = r#"{
5751 "type": "system",
5752 "subtype": "task_started",
5753 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5754 "task_id": "a4a7e0906e5fc64cc",
5755 "task_type": "local_agent",
5756 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5757 "description": "Explore Scene/ArrayScene duplication",
5758 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5759 }"#;
5760
5761 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5762 if let ClaudeOutput::System(sys) = output {
5763 let task = sys.as_task_started().expect("Should parse as task_started");
5764 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5765 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5766 } else {
5767 panic!("Expected System message");
5768 }
5769 }
5770
5771 #[test]
5772 fn test_system_message_task_progress() {
5773 let json = r#"{
5774 "type": "system",
5775 "subtype": "task_progress",
5776 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5777 "task_id": "a4a7e0906e5fc64cc",
5778 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5779 "description": "Reading src/jplephem/chebyshev.rs",
5780 "last_tool_name": "Read",
5781 "usage": {
5782 "duration_ms": 13996,
5783 "tool_uses": 9,
5784 "total_tokens": 38779
5785 },
5786 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5787 }"#;
5788
5789 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5790 if let ClaudeOutput::System(sys) = output {
5791 assert!(sys.is_task_progress());
5792 assert!(!sys.is_task_started());
5793
5794 let progress = sys
5795 .as_task_progress()
5796 .expect("Should parse as task_progress");
5797 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5798 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5799 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5800 assert_eq!(progress.usage.duration_ms, 13996);
5801 assert_eq!(progress.usage.tool_uses, 9);
5802 assert_eq!(progress.usage.total_tokens, 38779);
5803 } else {
5804 panic!("Expected System message");
5805 }
5806 }
5807
5808 #[test]
5809 fn test_system_message_task_notification_completed() {
5810 let json = r#"{
5811 "type": "system",
5812 "subtype": "task_notification",
5813 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5814 "task_id": "a0ba761e9dc9c316f",
5815 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
5816 "status": "completed",
5817 "summary": "Agent \"Write Hipparcos data source doc\" completed",
5818 "output_file": "",
5819 "usage": {
5820 "duration_ms": 172300,
5821 "tool_uses": 11,
5822 "total_tokens": 42005
5823 },
5824 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
5825 }"#;
5826
5827 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5828 if let ClaudeOutput::System(sys) = output {
5829 assert!(sys.is_task_notification());
5830
5831 let notif = sys
5832 .as_task_notification()
5833 .expect("Should parse as task_notification");
5834 assert_eq!(notif.status, super::TaskStatus::Completed);
5835 assert_eq!(
5836 notif.summary,
5837 "Agent \"Write Hipparcos data source doc\" completed"
5838 );
5839 assert_eq!(notif.output_file, Some("".to_string()));
5840 assert_eq!(
5841 notif.tool_use_id,
5842 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
5843 );
5844 let usage = notif.usage.expect("Should have usage");
5845 assert_eq!(usage.duration_ms, 172300);
5846 assert_eq!(usage.tool_uses, 11);
5847 assert_eq!(usage.total_tokens, 42005);
5848 } else {
5849 panic!("Expected System message");
5850 }
5851 }
5852
5853 #[test]
5854 fn test_system_message_task_notification_failed_no_usage() {
5855 let json = r#"{
5856 "type": "system",
5857 "subtype": "task_notification",
5858 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
5859 "task_id": "b98f6a3",
5860 "status": "failed",
5861 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
5862 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
5863 }"#;
5864
5865 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5866 if let ClaudeOutput::System(sys) = output {
5867 let notif = sys
5868 .as_task_notification()
5869 .expect("Should parse as task_notification");
5870 assert_eq!(notif.status, super::TaskStatus::Failed);
5871 assert!(notif.tool_use_id.is_none());
5872 assert!(notif.usage.is_none());
5873 assert_eq!(
5874 notif.output_file,
5875 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
5876 );
5877 } else {
5878 panic!("Expected System message");
5879 }
5880 }
5881
5882 #[test]
5888 fn test_task_messages_roundtrip_through_value() {
5889 let cases = [
5890 r#"{"type":"system","subtype":"task_started","session_id":"s1",
5891 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
5892 "description":"Sleep 3s","uuid":"u1"}"#,
5893 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
5894 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
5895 "last_tool_name":"Bash",
5896 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5897 "uuid":"u2"}"#,
5898 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
5899 "task_id":"t1","tool_use_id":"tu1","status":"completed",
5900 "summary":"done","output_file":"",
5901 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5902 "uuid":"u3"}"#,
5903 ];
5904
5905 for json in cases {
5906 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5907 let value = serde_json::to_value(&output).unwrap();
5908 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
5909
5910 let ClaudeOutput::System(sys) = reparsed else {
5911 panic!("Expected System variant after round-trip");
5912 };
5913
5914 match sys.subtype {
5915 super::SystemSubtype::TaskStarted => {
5916 assert!(
5917 sys.as_task_started().is_some(),
5918 "as_task_started failed after round-trip"
5919 );
5920 }
5921 super::SystemSubtype::TaskProgress => {
5922 assert!(
5923 sys.as_task_progress().is_some(),
5924 "as_task_progress failed after round-trip"
5925 );
5926 }
5927 super::SystemSubtype::TaskNotification => {
5928 assert!(
5929 sys.as_task_notification().is_some(),
5930 "as_task_notification failed after round-trip"
5931 );
5932 }
5933 other => panic!("unexpected subtype after round-trip: {other:?}"),
5934 }
5935 }
5936 }
5937
5938 #[test]
5939 fn test_system_message_compact_boundary() {
5940 let json = r#"{
5941 "type": "system",
5942 "subtype": "compact_boundary",
5943 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5944 "compact_metadata": {
5945 "pre_tokens": 155285,
5946 "trigger": "auto"
5947 },
5948 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
5949 }"#;
5950
5951 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5952 if let ClaudeOutput::System(sys) = output {
5953 assert!(sys.is_compact_boundary());
5954 assert!(!sys.is_init());
5955 assert!(!sys.is_status());
5956
5957 let compact = sys
5958 .as_compact_boundary()
5959 .expect("Should parse as compact_boundary");
5960 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5961 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
5962 assert_eq!(
5963 compact.compact_metadata.trigger,
5964 super::CompactionTrigger::Auto
5965 );
5966 assert!(compact.summary.is_none());
5968 assert!(compact.leaf_message_count.is_none());
5969 assert!(compact.duration_ms.is_none());
5970 } else {
5971 panic!("Expected System message");
5972 }
5973 }
5974
5975 #[test]
5976 fn test_compact_boundary_with_summary_stats() {
5977 let json = r#"{
5979 "type": "system",
5980 "subtype": "compact_boundary",
5981 "session_id": "s1",
5982 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
5983 "summary": "Summarized the earlier exploration.",
5984 "leaf_message_count": 42,
5985 "duration_ms": 1234,
5986 "uuid": "u1"
5987 }"#;
5988 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5989 let ClaudeOutput::System(sys) = output else {
5990 panic!("Expected System message");
5991 };
5992 let compact = sys.as_compact_boundary().expect("compact_boundary");
5993 assert_eq!(
5994 compact.summary.as_deref(),
5995 Some("Summarized the earlier exploration.")
5996 );
5997 assert_eq!(compact.leaf_message_count, Some(42));
5998 assert_eq!(compact.duration_ms, Some(1234));
5999
6000 let json_alt = r#"{
6003 "type": "system",
6004 "subtype": "compact_boundary",
6005 "session_id": "s2",
6006 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
6007 "content": "alt-key summary",
6008 "message_count": 7
6009 }"#;
6010 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
6011 let ClaudeOutput::System(sys) = output else {
6012 panic!("Expected System message");
6013 };
6014 let compact = sys.as_compact_boundary().expect("compact_boundary");
6015 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
6016 assert_eq!(compact.leaf_message_count, Some(7));
6017 }
6018
6019 #[test]
6020 fn test_init_message_with_new_fields() {
6021 let json = r#"{
6022 "type": "system",
6023 "subtype": "init",
6024 "session_id": "test-session",
6025 "cwd": "/home/user",
6026 "model": "claude-opus-4-7",
6027 "tools": ["Bash"],
6028 "mcp_servers": [],
6029 "permissionMode": "default",
6030 "apiKeySource": "none",
6031 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
6032 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
6033 "fast_mode_state": "off",
6034 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
6035 "claude_code_version": "2.1.117"
6036 }"#;
6037
6038 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6039 if let ClaudeOutput::System(sys) = output {
6040 let init = sys.as_init().expect("Should parse as init");
6041 assert_eq!(
6042 init.uuid.as_deref(),
6043 Some("44841a0d-182d-493a-86b5-79800d3d9665")
6044 );
6045 assert!(init.memory_paths.is_some());
6046 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
6047 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
6048 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
6049 } else {
6050 panic!("Expected System message");
6051 }
6052 }
6053
6054 #[test]
6055 fn test_assistant_message_with_new_fields() {
6056 let json = r#"{
6057 "type": "assistant",
6058 "message": {
6059 "id": "msg_1",
6060 "type": "message",
6061 "role": "assistant",
6062 "model": "claude-opus-4-7",
6063 "content": [{"type": "text", "text": "Hello"}],
6064 "stop_reason": "end_turn",
6065 "stop_details": null,
6066 "context_management": null,
6067 "usage": {
6068 "input_tokens": 100,
6069 "output_tokens": 10,
6070 "cache_creation_input_tokens": 50,
6071 "cache_read_input_tokens": 0,
6072 "service_tier": "standard",
6073 "inference_geo": "not_available"
6074 }
6075 },
6076 "session_id": "abc",
6077 "uuid": "msg-uuid-123"
6078 }"#;
6079
6080 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6081 if let ClaudeOutput::Assistant(asst) = output {
6082 assert_eq!(asst.message.stop_details, None);
6083 assert_eq!(asst.message.context_management, None);
6084 let usage = asst.message.usage.unwrap();
6085 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
6086 } else {
6087 panic!("Expected Assistant message");
6088 }
6089 }
6090
6091 #[test]
6092 fn test_user_message_with_new_fields() {
6093 let json = r#"{
6094 "type": "user",
6095 "message": {
6096 "role": "user",
6097 "content": [{"type": "text", "text": "Hello"}]
6098 },
6099 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6100 "parent_tool_use_id": "toolu_123",
6101 "uuid": "user-msg-456"
6102 }"#;
6103
6104 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6105 if let ClaudeOutput::User(user) = output {
6106 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
6107 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
6108 } else {
6109 panic!("Expected User message");
6110 }
6111 }
6112
6113 #[test]
6119 fn test_user_message_preserves_tool_use_result_and_timestamp() {
6120 let json = r#"{
6121 "type":"user",
6122 "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"}]},
6123 "parent_tool_use_id":null,
6124 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6125 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
6126 "timestamp":"2026-05-12T23:12:04.121Z",
6127 "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"}}
6128 }"#;
6129
6130 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6131 let user = match output {
6132 ClaudeOutput::User(u) => u,
6133 other => panic!("Expected User message, got {:?}", other.message_type()),
6134 };
6135
6136 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
6137 let raw = user
6138 .tool_use_result
6139 .as_ref()
6140 .expect("tool_use_result must be captured");
6141 assert_eq!(raw["answers"]["Color"], "Blue");
6142 assert_eq!(raw["questions"][0]["header"], "Color");
6143
6144 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
6148 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
6149 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
6150 assert_eq!(
6151 reser["tool_use_result"]["questions"][0]["question"],
6152 "Which color do you prefer?"
6153 );
6154
6155 let typed: crate::AskUserQuestionInput = user
6158 .tool_use_result_as::<crate::AskUserQuestionInput>()
6159 .expect("tool_use_result present")
6160 .expect("AskUserQuestionInput parses");
6161 assert_eq!(typed.questions.len(), 1);
6162 assert_eq!(typed.questions[0].header, "Color");
6163 let answers = typed.answers.expect("answers populated");
6164 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
6165 }
6166
6167 #[test]
6170 fn test_user_message_without_tool_use_result_omits_field() {
6171 let json = r#"{
6172 "type":"user",
6173 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
6174 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
6175 }"#;
6176
6177 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6178 let user = match output {
6179 ClaudeOutput::User(u) => u,
6180 _ => panic!("Expected User message"),
6181 };
6182 assert!(user.tool_use_result.is_none());
6183 assert!(user.timestamp.is_none());
6184
6185 let reser = serde_json::to_value(&user).unwrap();
6186 assert!(reser.get("tool_use_result").is_none());
6187 assert!(reser.get("timestamp").is_none());
6188 }
6189
6190 #[test]
6194 fn test_subagent_result_exposes_token_accounting() {
6195 let json = r#"{
6196 "type":"user",
6197 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
6198 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
6199 "tool_use_result":{
6200 "status":"completed",
6201 "prompt":"Count the .rs files.",
6202 "agentId":"ac4f0276e9d4b6232",
6203 "agentType":"Explore",
6204 "content":[{"type":"text","text":"21"}],
6205 "resolvedModel":"claude-haiku-4-5-20251001",
6206 "totalDurationMs":6869,
6207 "totalTokens":7834,
6208 "totalToolUseCount":1,
6209 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
6210 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
6211 }
6212 }"#;
6213
6214 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6215 let user = match output {
6216 ClaudeOutput::User(u) => u,
6217 _ => panic!("Expected User message"),
6218 };
6219
6220 let result = user.subagent_result().expect("subagent result parses");
6221 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
6222 assert_eq!(
6223 result.resolved_model.as_deref(),
6224 Some("claude-haiku-4-5-20251001")
6225 );
6226 assert_eq!(result.total_tokens, Some(7834));
6227 assert_eq!(result.total_duration_ms, Some(6869));
6228 assert_eq!(result.total_tool_use_count, Some(1));
6229
6230 let usage = result.usage.expect("nested usage present");
6231 assert_eq!(usage.input_tokens, 6);
6232 assert_eq!(usage.cache_read_input_tokens, 7699);
6233
6234 let stats = result.tool_stats.expect("toolStats present");
6235 assert_eq!(stats.bash_count, 1);
6236 }
6237
6238 #[test]
6242 fn test_subagent_result_absent_for_non_task_result() {
6243 let json = r#"{
6244 "type":"user",
6245 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
6246 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6247 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
6248 }"#;
6249
6250 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6251 let user = match output {
6252 ClaudeOutput::User(u) => u,
6253 _ => panic!("Expected User message"),
6254 };
6255
6256 let result = user.subagent_result().expect("lenient parse");
6257 assert_eq!(result.total_tokens, None);
6258 assert_eq!(result.agent_type, None);
6259 }
6260
6261 #[test]
6262 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
6263 use serde_json::Value;
6264
6265 let raw: Value = serde_json::from_str(
6266 r#"{
6267 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
6268 "fast_mode_state":"off",
6269 "fast_mode_disabled_reason":"not_first_party",
6270 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
6271 }"#,
6272 )
6273 .unwrap();
6274 crate::io::assert_fully_wrapped(&raw);
6275
6276 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6277 let ClaudeOutput::System(sys) = output else {
6278 panic!("expected System");
6279 };
6280 let init = sys.as_init().expect("parses as init");
6281 assert_eq!(
6282 init.fast_mode_disabled_reason,
6283 Some(crate::FastModeDisabledReason::NotFirstParty)
6284 );
6285 let errs = init.mcp_server_errors.unwrap();
6286 assert_eq!(errs.len(), 1);
6287 assert_eq!(errs[0].name, "broken");
6288 assert_eq!(errs[0].error_type, "invalid_config");
6289 }
6290
6291 #[test]
6292 fn test_code_change_published_fully_wrapped() {
6293 use super::{KnownSystemEvent, SystemSubtype};
6294 use serde_json::Value;
6295
6296 let raw: Value = serde_json::from_str(
6297 r#"{
6298 "type":"system","subtype":"code_change_published",
6299 "provider":"github","url":"https://github.com/owner/repo/pull/42",
6300 "repo":"owner/repo","identifier":"42",
6301 "uuid":"u1","session_id":"s1"
6302 }"#,
6303 )
6304 .unwrap();
6305 crate::io::assert_fully_wrapped(&raw);
6306
6307 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6308 let ClaudeOutput::System(sys) = output else {
6309 panic!("expected System");
6310 };
6311 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
6312 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
6313 panic!("expected CodeChangePublished event");
6314 };
6315 assert_eq!(msg.provider, "github");
6316 assert_eq!(msg.repo, "owner/repo");
6317 assert_eq!(msg.identifier, "42");
6318
6319 assert!(sys.is_code_change_published());
6320 assert!(!sys.is_vcs_state_changed());
6321 let direct = sys.as_code_change_published().expect("direct accessor");
6322 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
6323 assert!(sys.as_vcs_state_changed().is_none());
6324 }
6325
6326 #[test]
6327 fn test_feedback_draft_queued_fully_wrapped() {
6328 use super::{KnownSystemEvent, SystemSubtype};
6329 use serde_json::Value;
6330
6331 let raw: Value = serde_json::from_str(
6332 r#"{
6333 "type":"system","subtype":"feedback_draft_queued",
6334 "draft_id":"draft-1","draft_type":"bug_report",
6335 "title":"Tool output was truncated",
6336 "details_preview":"The last command omitted its final lines",
6337 "uuid":"u1","session_id":"s1","future_field":"preserved"
6338 }"#,
6339 )
6340 .unwrap();
6341 crate::io::assert_fully_wrapped(&raw);
6342
6343 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6344 let ClaudeOutput::System(sys) = output else {
6345 panic!("expected System");
6346 };
6347 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
6348 assert!(sys.is_feedback_draft_queued());
6349 assert!(!sys.is_vcs_state_changed());
6350
6351 let direct = sys
6352 .as_feedback_draft_queued()
6353 .expect("direct typed accessor");
6354 assert_eq!(direct.draft_id, "draft-1");
6355 assert_eq!(direct.draft_type, "bug_report");
6356 assert_eq!(direct.extra["future_field"], "preserved");
6357
6358 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
6359 panic!("expected FeedbackDraftQueued event");
6360 };
6361 assert_eq!(known.title, "Tool output was truncated");
6362 assert_eq!(
6363 sys.typed_value().expect("typed value")["future_field"],
6364 "preserved"
6365 );
6366 }
6367
6368 #[test]
6369 fn test_cloud_session_delta_fully_wrapped() {
6370 use super::{KnownSystemEvent, SystemSubtype};
6371 use serde_json::Value;
6372
6373 let raw: Value = serde_json::from_str(
6374 r#"{
6375 "type":"system","subtype":"cloud_session_delta",
6376 "seq":3,"changed":["serving","connection"],
6377 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
6378 "serving":{"state":"on"},"connection":{"state":"live"}},
6379 "uuid":"u1","session_id":"s1","future_field":"preserved"
6380 }"#,
6381 )
6382 .unwrap();
6383 crate::io::assert_fully_wrapped(&raw);
6384
6385 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6386 let ClaudeOutput::System(sys) = output else {
6387 panic!("expected System");
6388 };
6389 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
6390 assert!(sys.is_cloud_session_delta());
6391 assert!(!sys.is_feedback_draft_queued());
6392
6393 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
6394 assert_eq!(direct.seq, 3);
6395 assert_eq!(direct.changed, vec!["serving", "connection"]);
6396 assert_eq!(direct.cloud_session["id"], "session_abc");
6397 assert_eq!(direct.extra["future_field"], "preserved");
6398
6399 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
6400 panic!("expected CloudSessionDelta event");
6401 };
6402 assert_eq!(known.session_id, "s1");
6403 assert_eq!(
6404 sys.typed_value().expect("typed value")["future_field"],
6405 "preserved"
6406 );
6407 }
6408
6409 #[test]
6410 fn test_vcs_state_changed_fully_wrapped() {
6411 use super::{KnownSystemEvent, VcsMutationKind};
6412 use serde_json::Value;
6413
6414 for kind in ["commit", "push", "merge", "rebase"] {
6415 let raw: Value = serde_json::from_str(&format!(
6416 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
6417 kind
6418 ))
6419 .unwrap();
6420 crate::io::assert_fully_wrapped(&raw);
6421
6422 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6423 let ClaudeOutput::System(sys) = output else {
6424 panic!("expected System");
6425 };
6426 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6427 panic!("expected VcsStateChanged event");
6428 };
6429 assert_eq!(msg.kind.as_str(), kind);
6430 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
6431 }
6432
6433 let raw: Value = serde_json::from_str(
6435 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
6436 )
6437 .unwrap();
6438 crate::io::assert_fully_wrapped(&raw);
6439 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6440 let ClaudeOutput::System(sys) = output else {
6441 panic!("expected System");
6442 };
6443 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6444 panic!("expected VcsStateChanged event");
6445 };
6446 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
6447
6448 assert!(sys.is_vcs_state_changed());
6449 let direct = sys.as_vcs_state_changed().expect("direct accessor");
6450 assert_eq!(direct.cwd, "/repo");
6451 assert!(sys.as_code_change_published().is_none());
6452 }
6453
6454 #[test]
6455 fn test_assistant_aborted_and_resume_flags_roundtrip() {
6456 let json = r#"{
6457 "type":"assistant",
6458 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
6459 "session_id":"s1",
6460 "aborted":true,
6461 "resumed_from_incomplete_thinking":true
6462 }"#;
6463 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6464 let ClaudeOutput::Assistant(msg) = &output else {
6465 panic!("expected Assistant");
6466 };
6467 assert_eq!(msg.aborted, Some(true));
6468 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
6469 let reserialized = serde_json::to_string(&output).unwrap();
6470 assert!(reserialized.contains("\"aborted\":true"));
6471 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
6472
6473 let json = r#"{
6475 "type":"assistant",
6476 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
6477 "session_id":"s2"
6478 }"#;
6479 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6480 let reserialized = serde_json::to_string(&output).unwrap();
6481 assert!(!reserialized.contains("aborted"));
6482 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
6483 }
6484
6485 #[test]
6486 fn test_user_tool_result_meta_roundtrip() {
6487 let json = r#"{
6488 "type":"user",
6489 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6490 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6491 "tool_result_meta":[
6492 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6493 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6494 ]
6495 }"#;
6496 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6497 let ClaudeOutput::User(user) = &output else {
6498 panic!("expected User");
6499 };
6500 let meta = user.tool_result_meta.as_ref().unwrap();
6501 assert_eq!(meta.len(), 2);
6502 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6503 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6504 assert_eq!(meta[1].user_feedback, None);
6505
6506 let reserialized = serde_json::to_string(&output).unwrap();
6507 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6508 assert!(!reserialized.contains("\"user_feedback\":null"));
6509 }
6510
6511 #[test]
6515 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6516 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6517 let with_scope = serde_json::json!({
6518 "trigger": "refusal",
6519 "direction": "retry",
6520 "scope": "local",
6521 "original_model": "claude-fable-5",
6522 "fallback_model": "claude-opus-5",
6523 "request_id": null,
6524 "content": "Refused; retried on fallback model.",
6525 "uuid": "u1",
6526 "session_id": "s1"
6527 });
6528 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6529 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6530 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6531
6532 let mut without = with_scope.clone();
6535 without.as_object_mut().unwrap().remove("scope");
6536 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6537 assert_eq!(msg.scope, None);
6538 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6539
6540 assert_eq!(
6542 RefusalFallbackScope::from("workspace").as_str(),
6543 "workspace"
6544 );
6545 }
6546}