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 #[serde(default, skip_serializing_if = "Option::is_none")]
2936 pub compact_output_chars: Option<u64>,
2937}
2938
2939#[derive(Debug, Clone, Serialize, Deserialize)]
2941pub struct CompactBoundaryMessage {
2942 pub session_id: String,
2944 pub compact_metadata: CompactMetadata,
2946 #[serde(
2950 default,
2951 skip_serializing_if = "Option::is_none",
2952 alias = "content",
2953 alias = "text"
2954 )]
2955 pub summary: Option<String>,
2956 #[serde(
2960 default,
2961 skip_serializing_if = "Option::is_none",
2962 alias = "message_count"
2963 )]
2964 pub leaf_message_count: Option<u32>,
2965 #[serde(default, skip_serializing_if = "Option::is_none")]
2967 pub duration_ms: Option<u64>,
2968 #[serde(skip_serializing_if = "Option::is_none")]
2970 pub uuid: Option<String>,
2971 #[serde(skip_serializing_if = "Option::is_none")]
2973 pub logical_parent_uuid: Option<Option<String>>,
2974 #[serde(default, skip_serializing_if = "Option::is_none")]
2978 pub historical: Option<bool>,
2979}
2980
2981#[derive(Debug, Clone, Serialize, Deserialize)]
2983pub struct CompactMetadata {
2984 pub pre_tokens: u64,
2986 pub trigger: CompactionTrigger,
2988 #[serde(default, skip_serializing_if = "Option::is_none")]
2989 pub post_tokens: Option<u64>,
2990 #[serde(default, skip_serializing_if = "Option::is_none")]
2991 pub cumulative_dropped_tokens: Option<u64>,
2992 #[serde(default, skip_serializing_if = "Option::is_none")]
2993 pub duration_ms: Option<u64>,
2994 #[serde(default, skip_serializing_if = "Option::is_none")]
2995 pub user_context: Option<String>,
2996 #[serde(default, skip_serializing_if = "Option::is_none")]
2997 pub messages_summarized: Option<u64>,
2998 #[serde(default, skip_serializing_if = "Option::is_none")]
2999 pub precomputed: Option<bool>,
3000 #[serde(default, skip_serializing_if = "Option::is_none")]
3001 pub pre_compact_discovered_tools: Option<Vec<String>>,
3002 #[serde(default, skip_serializing_if = "Option::is_none")]
3007 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
3008 #[serde(default, skip_serializing_if = "Option::is_none")]
3009 pub preserved_segment: Option<PreservedSegment>,
3010 #[serde(default, skip_serializing_if = "Option::is_none")]
3011 pub preserved_messages: Option<PreservedMessages>,
3012}
3013
3014#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3017pub struct ArtifactReadVersion {
3018 pub slug: String,
3020 pub ver: String,
3022}
3023
3024#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3025pub struct PreservedSegment {
3026 pub head_uuid: String,
3027 pub anchor_uuid: String,
3028 pub tail_uuid: String,
3029}
3030
3031#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3032pub struct PreservedMessages {
3033 pub anchor_uuid: String,
3034 pub uuids: Vec<String>,
3035 #[serde(default, skip_serializing_if = "Option::is_none")]
3036 pub all_uuids: Option<Vec<String>>,
3037}
3038
3039#[derive(Debug, Clone, Serialize, Deserialize)]
3045pub struct TaskUsage {
3046 pub duration_ms: u64,
3048 pub tool_uses: u64,
3050 pub total_tokens: u64,
3052}
3053
3054#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3056pub enum TaskType {
3057 LocalAgent,
3059 LocalBash,
3061 LocalWorkflow,
3063 Unknown(String),
3065}
3066
3067impl TaskType {
3068 pub fn as_str(&self) -> &str {
3069 match self {
3070 Self::LocalAgent => "local_agent",
3071 Self::LocalBash => "local_bash",
3072 Self::LocalWorkflow => "local_workflow",
3073 Self::Unknown(s) => s.as_str(),
3074 }
3075 }
3076}
3077
3078impl fmt::Display for TaskType {
3079 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3080 f.write_str(self.as_str())
3081 }
3082}
3083
3084impl From<&str> for TaskType {
3085 fn from(s: &str) -> Self {
3086 match s {
3087 "local_agent" => Self::LocalAgent,
3088 "local_bash" => Self::LocalBash,
3089 "local_workflow" => Self::LocalWorkflow,
3090 other => Self::Unknown(other.to_string()),
3091 }
3092 }
3093}
3094
3095impl Serialize for TaskType {
3096 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3097 serializer.serialize_str(self.as_str())
3098 }
3099}
3100
3101impl<'de> Deserialize<'de> for TaskType {
3102 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3103 let s = String::deserialize(deserializer)?;
3104 Ok(Self::from(s.as_str()))
3105 }
3106}
3107
3108#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3110pub enum TaskStatus {
3111 Pending,
3112 Running,
3113 Completed,
3114 Failed,
3115 Killed,
3116 Paused,
3117 Stopped,
3118 Unknown(String),
3119}
3120
3121impl TaskStatus {
3122 pub fn as_str(&self) -> &str {
3123 match self {
3124 Self::Pending => "pending",
3125 Self::Running => "running",
3126 Self::Completed => "completed",
3127 Self::Failed => "failed",
3128 Self::Killed => "killed",
3129 Self::Paused => "paused",
3130 Self::Stopped => "stopped",
3131 Self::Unknown(s) => s.as_str(),
3132 }
3133 }
3134}
3135
3136impl fmt::Display for TaskStatus {
3137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3138 f.write_str(self.as_str())
3139 }
3140}
3141
3142impl From<&str> for TaskStatus {
3143 fn from(s: &str) -> Self {
3144 match s {
3145 "pending" => Self::Pending,
3146 "running" => Self::Running,
3147 "completed" => Self::Completed,
3148 "failed" => Self::Failed,
3149 "killed" => Self::Killed,
3150 "paused" => Self::Paused,
3151 "stopped" => Self::Stopped,
3152 other => Self::Unknown(other.to_string()),
3153 }
3154 }
3155}
3156
3157impl Serialize for TaskStatus {
3158 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3159 serializer.serialize_str(self.as_str())
3160 }
3161}
3162
3163impl<'de> Deserialize<'de> for TaskStatus {
3164 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3165 let s = String::deserialize(deserializer)?;
3166 Ok(Self::from(s.as_str()))
3167 }
3168}
3169
3170#[derive(Debug, Clone, Serialize, Deserialize)]
3172pub struct TaskStartedMessage {
3173 pub session_id: String,
3174 pub task_id: String,
3175 #[serde(default, skip_serializing_if = "Option::is_none")]
3181 pub run_id: Option<String>,
3182 #[serde(default, skip_serializing_if = "Option::is_none")]
3188 pub parent_task_id: Option<String>,
3189 #[serde(default, skip_serializing_if = "Option::is_none")]
3190 pub task_type: Option<TaskType>,
3191 #[serde(default, skip_serializing_if = "Option::is_none")]
3192 pub tool_use_id: Option<String>,
3193 pub description: String,
3194 #[serde(default, skip_serializing_if = "Option::is_none")]
3197 pub subagent_type: Option<String>,
3198 #[serde(default, skip_serializing_if = "Option::is_none")]
3204 pub is_backgrounded: Option<bool>,
3205 #[serde(default, skip_serializing_if = "Option::is_none")]
3213 pub awaited: Option<bool>,
3214 #[serde(default, skip_serializing_if = "Option::is_none")]
3218 pub spawn_depth: Option<u32>,
3219 #[serde(default, skip_serializing_if = "Option::is_none")]
3221 pub prompt: Option<String>,
3222 #[serde(default, skip_serializing_if = "Option::is_none")]
3223 pub workflow_name: Option<String>,
3224 #[serde(default, skip_serializing_if = "Option::is_none")]
3225 pub skip_transcript: Option<bool>,
3226 #[serde(default, skip_serializing_if = "Option::is_none")]
3231 pub ambient: Option<bool>,
3232 pub uuid: String,
3233}
3234
3235#[derive(Debug, Clone, Serialize, Deserialize)]
3239pub struct TaskUpdatedMessage {
3240 pub session_id: String,
3241 pub task_id: String,
3242 #[serde(default, skip_serializing_if = "Option::is_none")]
3245 pub run_id: Option<String>,
3246 pub patch: TaskPatch,
3247 pub uuid: String,
3248}
3249
3250#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3253pub struct TaskPatch {
3254 #[serde(default, skip_serializing_if = "Option::is_none")]
3255 pub status: Option<TaskStatus>,
3256 #[serde(default, skip_serializing_if = "Option::is_none")]
3259 pub end_time: Option<u64>,
3260 #[serde(default, skip_serializing_if = "Option::is_none")]
3261 pub description: Option<String>,
3262 #[serde(default, skip_serializing_if = "Option::is_none")]
3263 pub total_paused_ms: Option<u64>,
3264 #[serde(default, skip_serializing_if = "Option::is_none")]
3265 pub error: Option<String>,
3266 #[serde(default, skip_serializing_if = "Option::is_none")]
3267 pub is_backgrounded: Option<bool>,
3268}
3269
3270#[derive(Debug, Clone, Serialize, Deserialize)]
3273pub struct ThinkingTokensMessage {
3274 pub session_id: String,
3275 pub estimated_tokens: u64,
3277 pub estimated_tokens_delta: u64,
3279 #[serde(default, skip_serializing_if = "Option::is_none")]
3286 pub user_message_uuid: Option<String>,
3287 pub uuid: String,
3288}
3289
3290#[derive(Debug, Clone, Serialize, Deserialize)]
3293pub struct TaskProgressMessage {
3294 pub session_id: String,
3295 pub task_id: String,
3296 #[serde(default, skip_serializing_if = "Option::is_none")]
3299 pub run_id: Option<String>,
3300 #[serde(default, skip_serializing_if = "Option::is_none")]
3301 pub tool_use_id: Option<String>,
3302 pub description: String,
3303 #[serde(default, skip_serializing_if = "Option::is_none")]
3304 pub last_tool_name: Option<String>,
3305 pub usage: TaskUsage,
3306 #[serde(default, skip_serializing_if = "Option::is_none")]
3308 pub subagent_type: Option<String>,
3309 #[serde(default, skip_serializing_if = "Option::is_none")]
3310 pub summary: Option<String>,
3311 pub uuid: String,
3312}
3313
3314#[derive(Debug, Clone, Serialize, Deserialize)]
3317pub struct TaskNotificationMessage {
3318 pub session_id: String,
3319 pub task_id: String,
3320 #[serde(default, skip_serializing_if = "Option::is_none")]
3323 pub run_id: Option<String>,
3324 pub status: TaskStatus,
3325 #[serde(default, skip_serializing_if = "Option::is_none")]
3328 pub reason: Option<TaskEndReason>,
3329 pub summary: String,
3330 pub output_file: Option<String>,
3331 #[serde(skip_serializing_if = "Option::is_none")]
3332 pub tool_use_id: Option<String>,
3333 #[serde(skip_serializing_if = "Option::is_none")]
3334 pub usage: Option<TaskUsage>,
3335 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3341 pub resource_links: Vec<ResourceLink>,
3342 #[serde(default, skip_serializing_if = "Option::is_none")]
3349 pub handback: Option<TaskHandback>,
3350 #[serde(default, skip_serializing_if = "Option::is_none")]
3354 pub handback_report: Option<HandbackReport>,
3355 #[serde(default, skip_serializing_if = "Option::is_none")]
3356 pub skip_transcript: Option<bool>,
3357 #[serde(default, skip_serializing_if = "Option::is_none")]
3360 pub ambient: Option<bool>,
3361 #[serde(skip_serializing_if = "Option::is_none")]
3362 pub uuid: Option<String>,
3363}
3364
3365#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3368pub enum TaskHandback {
3369 Send,
3371 Flagged,
3373 Withheld,
3375 Unknown(String),
3377}
3378
3379impl TaskHandback {
3380 pub fn as_str(&self) -> &str {
3381 match self {
3382 Self::Send => "send",
3383 Self::Flagged => "flagged",
3384 Self::Withheld => "withheld",
3385 Self::Unknown(s) => s.as_str(),
3386 }
3387 }
3388}
3389
3390impl fmt::Display for TaskHandback {
3391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3392 f.write_str(self.as_str())
3393 }
3394}
3395
3396impl From<&str> for TaskHandback {
3397 fn from(s: &str) -> Self {
3398 match s {
3399 "send" => Self::Send,
3400 "flagged" => Self::Flagged,
3401 "withheld" => Self::Withheld,
3402 other => Self::Unknown(other.to_string()),
3403 }
3404 }
3405}
3406
3407impl Serialize for TaskHandback {
3408 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3409 serializer.serialize_str(self.as_str())
3410 }
3411}
3412
3413impl<'de> Deserialize<'de> for TaskHandback {
3414 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3415 let s = String::deserialize(deserializer)?;
3416 Ok(Self::from(s.as_str()))
3417 }
3418}
3419
3420#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3423pub struct HandbackReport {
3424 pub text: String,
3427 #[serde(default, skip_serializing_if = "Option::is_none")]
3430 pub warning: Option<String>,
3431}
3432
3433#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3435pub enum AssistantErrorKind {
3436 AuthenticationFailed,
3437 OauthOrgNotAllowed,
3438 AccountOnHold,
3439 BillingError,
3440 RateLimit,
3441 Overloaded,
3442 InvalidRequest,
3443 ModelNotFound,
3444 ServerError,
3445 UnknownError,
3446 MaxOutputTokens,
3447 Unknown(String),
3448}
3449
3450impl AssistantErrorKind {
3451 pub fn as_str(&self) -> &str {
3452 match self {
3453 Self::AuthenticationFailed => "authentication_failed",
3454 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3455 Self::AccountOnHold => "account_on_hold",
3456 Self::BillingError => "billing_error",
3457 Self::RateLimit => "rate_limit",
3458 Self::Overloaded => "overloaded",
3459 Self::InvalidRequest => "invalid_request",
3460 Self::ModelNotFound => "model_not_found",
3461 Self::ServerError => "server_error",
3462 Self::UnknownError => "unknown",
3463 Self::MaxOutputTokens => "max_output_tokens",
3464 Self::Unknown(s) => s.as_str(),
3465 }
3466 }
3467}
3468
3469impl fmt::Display for AssistantErrorKind {
3470 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3471 f.write_str(self.as_str())
3472 }
3473}
3474
3475impl From<&str> for AssistantErrorKind {
3476 fn from(s: &str) -> Self {
3477 match s {
3478 "authentication_failed" => Self::AuthenticationFailed,
3479 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3480 "account_on_hold" => Self::AccountOnHold,
3481 "billing_error" => Self::BillingError,
3482 "rate_limit" => Self::RateLimit,
3483 "overloaded" => Self::Overloaded,
3484 "invalid_request" => Self::InvalidRequest,
3485 "model_not_found" => Self::ModelNotFound,
3486 "server_error" => Self::ServerError,
3487 "unknown" => Self::UnknownError,
3488 "max_output_tokens" => Self::MaxOutputTokens,
3489 other => Self::Unknown(other.to_string()),
3490 }
3491 }
3492}
3493
3494impl Serialize for AssistantErrorKind {
3495 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3496 serializer.serialize_str(self.as_str())
3497 }
3498}
3499
3500impl<'de> Deserialize<'de> for AssistantErrorKind {
3501 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3502 let s = String::deserialize(deserializer)?;
3503 Ok(Self::from(s.as_str()))
3504 }
3505}
3506
3507#[derive(Debug, Clone, Serialize, Deserialize)]
3514pub struct CodeChangePublishedMessage {
3515 pub provider: String,
3519 pub url: String,
3521 pub repo: String,
3524 pub identifier: String,
3526 #[serde(default, skip_serializing_if = "Option::is_none")]
3535 pub action: Option<String>,
3536 #[serde(default, skip_serializing_if = "Option::is_none")]
3544 pub branch: Option<String>,
3545 pub uuid: String,
3546 pub session_id: String,
3547}
3548
3549#[derive(Debug, Clone, Serialize, Deserialize)]
3554pub struct VcsStateChangedMessage {
3555 pub kind: VcsMutationKind,
3558 pub cwd: String,
3561 #[serde(default, skip_serializing_if = "Option::is_none")]
3566 pub branch: Option<String>,
3567 pub uuid: String,
3568 pub session_id: String,
3569}
3570
3571#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3573pub enum VcsMutationKind {
3574 Commit,
3575 Push,
3576 Merge,
3577 Rebase,
3578 Unknown(String),
3580}
3581
3582impl VcsMutationKind {
3583 pub fn as_str(&self) -> &str {
3584 match self {
3585 Self::Commit => "commit",
3586 Self::Push => "push",
3587 Self::Merge => "merge",
3588 Self::Rebase => "rebase",
3589 Self::Unknown(s) => s.as_str(),
3590 }
3591 }
3592}
3593
3594impl fmt::Display for VcsMutationKind {
3595 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3596 f.write_str(self.as_str())
3597 }
3598}
3599
3600impl From<&str> for VcsMutationKind {
3601 fn from(s: &str) -> Self {
3602 match s {
3603 "commit" => Self::Commit,
3604 "push" => Self::Push,
3605 "merge" => Self::Merge,
3606 "rebase" => Self::Rebase,
3607 other => Self::Unknown(other.to_string()),
3608 }
3609 }
3610}
3611
3612impl Serialize for VcsMutationKind {
3613 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3614 serializer.serialize_str(self.as_str())
3615 }
3616}
3617
3618impl<'de> Deserialize<'de> for VcsMutationKind {
3619 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3620 let s = String::deserialize(deserializer)?;
3621 Ok(Self::from(s.as_str()))
3622 }
3623}
3624
3625#[derive(Debug, Clone, Serialize, Deserialize)]
3627pub struct FeedbackDraftQueuedMessage {
3628 pub draft_id: String,
3629 pub draft_type: String,
3630 pub title: String,
3631 pub details_preview: String,
3632 #[serde(default, skip_serializing_if = "Option::is_none")]
3633 pub uuid: Option<String>,
3634 #[serde(default, skip_serializing_if = "Option::is_none")]
3635 pub session_id: Option<String>,
3636 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3637 pub extra: serde_json::Map<String, Value>,
3638}
3639
3640#[derive(Debug, Clone, Serialize, Deserialize)]
3648pub struct CloudSessionDeltaMessage {
3649 pub seq: u64,
3653 pub changed: Vec<String>,
3657 pub cloud_session: Value,
3661 pub uuid: String,
3662 pub session_id: String,
3663 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3664 pub extra: serde_json::Map<String, Value>,
3665}
3666
3667#[derive(Debug, Clone, Serialize, Deserialize)]
3683pub struct DevIntentMessage {
3684 pub kind: DevIntentKind,
3686 #[serde(default, skip_serializing_if = "Option::is_none")]
3692 pub trigger: Option<DevIntentTrigger>,
3693 pub uuid: String,
3694 pub session_id: String,
3695 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3696 pub extra: serde_json::Map<String, Value>,
3697}
3698
3699#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3703pub enum DevIntentKind {
3704 IosApp,
3710 AndroidApp,
3717 Unknown(String),
3719}
3720
3721impl DevIntentKind {
3722 pub fn as_str(&self) -> &str {
3723 match self {
3724 Self::IosApp => "ios_app",
3725 Self::AndroidApp => "android_app",
3726 Self::Unknown(s) => s.as_str(),
3727 }
3728 }
3729}
3730
3731impl fmt::Display for DevIntentKind {
3732 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3733 f.write_str(self.as_str())
3734 }
3735}
3736
3737impl From<&str> for DevIntentKind {
3738 fn from(s: &str) -> Self {
3739 match s {
3740 "ios_app" => Self::IosApp,
3741 "android_app" => Self::AndroidApp,
3742 other => Self::Unknown(other.to_string()),
3743 }
3744 }
3745}
3746
3747impl Serialize for DevIntentKind {
3748 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3749 serializer.serialize_str(self.as_str())
3750 }
3751}
3752
3753impl<'de> Deserialize<'de> for DevIntentKind {
3754 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3755 let s = String::deserialize(deserializer)?;
3756 Ok(Self::from(s.as_str()))
3757 }
3758}
3759
3760#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3764pub enum DevIntentTrigger {
3765 SwiftEdit,
3767 UikitImport,
3769 XcodeProject,
3772 IosCommand,
3775 KotlinEdit,
3777 JavaEdit,
3779 AndroidImport,
3781 AndroidManifest,
3783 GradlePlugin,
3785 AndroidCommand,
3788 ProjectScan,
3791 Unknown(String),
3793}
3794
3795impl DevIntentTrigger {
3796 pub fn as_str(&self) -> &str {
3797 match self {
3798 Self::SwiftEdit => "swift_edit",
3799 Self::UikitImport => "uikit_import",
3800 Self::XcodeProject => "xcode_project",
3801 Self::IosCommand => "ios_command",
3802 Self::KotlinEdit => "kotlin_edit",
3803 Self::JavaEdit => "java_edit",
3804 Self::AndroidImport => "android_import",
3805 Self::AndroidManifest => "android_manifest",
3806 Self::GradlePlugin => "gradle_plugin",
3807 Self::AndroidCommand => "android_command",
3808 Self::ProjectScan => "project_scan",
3809 Self::Unknown(s) => s.as_str(),
3810 }
3811 }
3812}
3813
3814impl fmt::Display for DevIntentTrigger {
3815 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3816 f.write_str(self.as_str())
3817 }
3818}
3819
3820impl From<&str> for DevIntentTrigger {
3821 fn from(s: &str) -> Self {
3822 match s {
3823 "swift_edit" => Self::SwiftEdit,
3824 "uikit_import" => Self::UikitImport,
3825 "xcode_project" => Self::XcodeProject,
3826 "ios_command" => Self::IosCommand,
3827 "kotlin_edit" => Self::KotlinEdit,
3828 "java_edit" => Self::JavaEdit,
3829 "android_import" => Self::AndroidImport,
3830 "android_manifest" => Self::AndroidManifest,
3831 "gradle_plugin" => Self::GradlePlugin,
3832 "android_command" => Self::AndroidCommand,
3833 "project_scan" => Self::ProjectScan,
3834 other => Self::Unknown(other.to_string()),
3835 }
3836 }
3837}
3838
3839impl Serialize for DevIntentTrigger {
3840 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3841 serializer.serialize_str(self.as_str())
3842 }
3843}
3844
3845impl<'de> Deserialize<'de> for DevIntentTrigger {
3846 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3847 let s = String::deserialize(deserializer)?;
3848 Ok(Self::from(s.as_str()))
3849 }
3850}
3851
3852#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3856pub enum TaskEndReason {
3857 WorkerRestart,
3860 Unknown(String),
3862}
3863
3864impl TaskEndReason {
3865 pub fn as_str(&self) -> &str {
3866 match self {
3867 Self::WorkerRestart => "worker_restart",
3868 Self::Unknown(s) => s.as_str(),
3869 }
3870 }
3871}
3872
3873impl fmt::Display for TaskEndReason {
3874 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3875 f.write_str(self.as_str())
3876 }
3877}
3878
3879impl From<&str> for TaskEndReason {
3880 fn from(s: &str) -> Self {
3881 match s {
3882 "worker_restart" => Self::WorkerRestart,
3883 other => Self::Unknown(other.to_string()),
3884 }
3885 }
3886}
3887
3888impl Serialize for TaskEndReason {
3889 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3890 serializer.serialize_str(self.as_str())
3891 }
3892}
3893
3894impl<'de> Deserialize<'de> for TaskEndReason {
3895 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3896 let s = String::deserialize(deserializer)?;
3897 Ok(Self::from(s.as_str()))
3898 }
3899}
3900
3901#[derive(Debug, Clone, Serialize, Deserialize)]
3910pub struct TurnHandoffAvailableMessage {
3911 pub v: u64,
3913 pub tools: Vec<String>,
3915 pub worker_epoch: u64,
3917 #[serde(default, skip_serializing_if = "Option::is_none")]
3921 pub relay_marker: Option<bool>,
3922 #[serde(default, skip_serializing_if = "Option::is_none")]
3930 pub staged_files: Option<bool>,
3931 #[serde(default, skip_serializing_if = "Option::is_none")]
3938 pub no_query_first: Option<bool>,
3939 #[serde(default, skip_serializing_if = "Option::is_none")]
3946 pub home_files: Option<bool>,
3947 #[serde(default, skip_serializing_if = "Option::is_none")]
3951 pub file_names: Option<bool>,
3952 #[serde(default, skip_serializing_if = "Option::is_none")]
3962 pub carried_writes: Option<bool>,
3963 pub uuid: String,
3964 pub session_id: String,
3965 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3966 pub extra: serde_json::Map<String, Value>,
3967}
3968
3969#[derive(Debug, Clone, Serialize, Deserialize)]
3981pub struct TurnPreemptedMessage {
3982 pub reason: String,
3986 pub preempted_by_uuid: String,
3990 #[serde(default)]
3993 pub preempted_message_uuids: Vec<String>,
3994 pub uuid: String,
3995 pub session_id: String,
3996 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3997 pub extra: serde_json::Map<String, Value>,
3998}
3999
4000#[derive(Debug, Clone, Serialize, Deserialize)]
4008pub struct PeerMessageHoldMessage {
4009 pub state: PeerMessageHoldState,
4015 #[serde(default, skip_serializing_if = "Option::is_none")]
4022 pub message_uuid: Option<String>,
4023 pub lane: PeerMessageLane,
4025 pub from: String,
4030 #[serde(default, skip_serializing_if = "Option::is_none")]
4033 pub from_name: Option<String>,
4034 #[serde(default, skip_serializing_if = "Option::is_none")]
4036 pub cause: Option<PeerMessageHoldCause>,
4037 #[serde(default, skip_serializing_if = "Option::is_none")]
4039 pub outcome: Option<PeerMessageHoldOutcome>,
4040 pub uuid: String,
4041 pub session_id: String,
4042 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4043 pub extra: serde_json::Map<String, Value>,
4044}
4045
4046#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4048pub enum PeerMessageHoldState {
4049 Held,
4051 Released,
4053 Dropped,
4055 Unknown(String),
4057}
4058
4059impl PeerMessageHoldState {
4060 pub fn as_str(&self) -> &str {
4061 match self {
4062 Self::Held => "held",
4063 Self::Released => "released",
4064 Self::Dropped => "dropped",
4065 Self::Unknown(s) => s.as_str(),
4066 }
4067 }
4068}
4069
4070impl fmt::Display for PeerMessageHoldState {
4071 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4072 f.write_str(self.as_str())
4073 }
4074}
4075
4076impl From<&str> for PeerMessageHoldState {
4077 fn from(s: &str) -> Self {
4078 match s {
4079 "held" => Self::Held,
4080 "released" => Self::Released,
4081 "dropped" => Self::Dropped,
4082 other => Self::Unknown(other.to_string()),
4083 }
4084 }
4085}
4086
4087impl Serialize for PeerMessageHoldState {
4088 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4089 serializer.serialize_str(self.as_str())
4090 }
4091}
4092
4093impl<'de> Deserialize<'de> for PeerMessageHoldState {
4094 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4095 let s = String::deserialize(deserializer)?;
4096 Ok(Self::from(s.as_str()))
4097 }
4098}
4099
4100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4102pub enum PeerMessageLane {
4103 Bridge,
4105 Stdin,
4107 Socket,
4109 Unknown(String),
4111}
4112
4113impl PeerMessageLane {
4114 pub fn as_str(&self) -> &str {
4115 match self {
4116 Self::Bridge => "bridge",
4117 Self::Stdin => "stdin",
4118 Self::Socket => "socket",
4119 Self::Unknown(s) => s.as_str(),
4120 }
4121 }
4122}
4123
4124impl fmt::Display for PeerMessageLane {
4125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4126 f.write_str(self.as_str())
4127 }
4128}
4129
4130impl From<&str> for PeerMessageLane {
4131 fn from(s: &str) -> Self {
4132 match s {
4133 "bridge" => Self::Bridge,
4134 "stdin" => Self::Stdin,
4135 "socket" => Self::Socket,
4136 other => Self::Unknown(other.to_string()),
4137 }
4138 }
4139}
4140
4141impl Serialize for PeerMessageLane {
4142 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4143 serializer.serialize_str(self.as_str())
4144 }
4145}
4146
4147impl<'de> Deserialize<'de> for PeerMessageLane {
4148 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4149 let s = String::deserialize(deserializer)?;
4150 Ok(Self::from(s.as_str()))
4151 }
4152}
4153
4154#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4161pub enum PeerMessageHoldCause {
4162 ExplicitSetting,
4163 ManagedSetting,
4164 RepoSetting,
4165 InvalidSetting,
4166 BypassDefault,
4167 ModeUnknown,
4168 ModeMismatch,
4169 NoModeAsserted,
4170 Unknown(String),
4172}
4173
4174impl PeerMessageHoldCause {
4175 pub fn as_str(&self) -> &str {
4176 match self {
4177 Self::ExplicitSetting => "explicit-setting",
4178 Self::ManagedSetting => "managed-setting",
4179 Self::RepoSetting => "repo-setting",
4180 Self::InvalidSetting => "invalid-setting",
4181 Self::BypassDefault => "bypass-default",
4182 Self::ModeUnknown => "mode-unknown",
4183 Self::ModeMismatch => "mode-mismatch",
4184 Self::NoModeAsserted => "no-mode-asserted",
4185 Self::Unknown(s) => s.as_str(),
4186 }
4187 }
4188}
4189
4190impl fmt::Display for PeerMessageHoldCause {
4191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4192 f.write_str(self.as_str())
4193 }
4194}
4195
4196impl From<&str> for PeerMessageHoldCause {
4197 fn from(s: &str) -> Self {
4198 match s {
4199 "explicit-setting" => Self::ExplicitSetting,
4200 "managed-setting" => Self::ManagedSetting,
4201 "repo-setting" => Self::RepoSetting,
4202 "invalid-setting" => Self::InvalidSetting,
4203 "bypass-default" => Self::BypassDefault,
4204 "mode-unknown" => Self::ModeUnknown,
4205 "mode-mismatch" => Self::ModeMismatch,
4206 "no-mode-asserted" => Self::NoModeAsserted,
4207 other => Self::Unknown(other.to_string()),
4208 }
4209 }
4210}
4211
4212impl Serialize for PeerMessageHoldCause {
4213 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4214 serializer.serialize_str(self.as_str())
4215 }
4216}
4217
4218impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4219 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4220 let s = String::deserialize(deserializer)?;
4221 Ok(Self::from(s.as_str()))
4222 }
4223}
4224
4225#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4228pub enum PeerMessageHoldOutcome {
4229 Expired,
4233 Refused,
4235 Dropped,
4238 Discarded,
4240 Unknown(String),
4242}
4243
4244impl PeerMessageHoldOutcome {
4245 pub fn as_str(&self) -> &str {
4246 match self {
4247 Self::Expired => "expired",
4248 Self::Refused => "refused",
4249 Self::Dropped => "dropped",
4250 Self::Discarded => "discarded",
4251 Self::Unknown(s) => s.as_str(),
4252 }
4253 }
4254}
4255
4256impl fmt::Display for PeerMessageHoldOutcome {
4257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4258 f.write_str(self.as_str())
4259 }
4260}
4261
4262impl From<&str> for PeerMessageHoldOutcome {
4263 fn from(s: &str) -> Self {
4264 match s {
4265 "expired" => Self::Expired,
4266 "refused" => Self::Refused,
4267 "dropped" => Self::Dropped,
4268 "discarded" => Self::Discarded,
4269 other => Self::Unknown(other.to_string()),
4270 }
4271 }
4272}
4273
4274impl Serialize for PeerMessageHoldOutcome {
4275 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4276 serializer.serialize_str(self.as_str())
4277 }
4278}
4279
4280impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4281 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4282 let s = String::deserialize(deserializer)?;
4283 Ok(Self::from(s.as_str()))
4284 }
4285}
4286
4287#[derive(Debug, Clone, Serialize, Deserialize)]
4298pub struct PerTurnEffortChangedMessage {
4299 pub per_turn_effort_active: bool,
4301 pub uuid: String,
4302 pub session_id: String,
4303}
4304
4305#[derive(Debug, Clone, Serialize, Deserialize)]
4312pub struct InstructionSizeWarningMessage {
4313 pub total_chars: u64,
4316 pub total_limit_chars: u64,
4319 pub file_count: u64,
4322 #[serde(default, skip_serializing_if = "Option::is_none")]
4326 pub largest_chars: Option<u64>,
4327 pub uuid: String,
4328 pub session_id: String,
4329}
4330
4331#[derive(Debug, Clone, Serialize, Deserialize)]
4338pub struct FileAttachmentsMissingMessage {
4339 pub message_uuid: String,
4342 pub sent_count: u64,
4344 pub missing: Vec<MissingFileAttachment>,
4346 pub uuid: String,
4347 pub session_id: String,
4348}
4349
4350#[derive(Debug, Clone, Serialize, Deserialize)]
4360pub struct PermissionCheckStatusMessage {
4361 pub tool_use_id: String,
4362 #[serde(default, skip_serializing_if = "Option::is_none")]
4365 pub agent_id: Option<String>,
4366 pub status: PermissionCheckStatus,
4367 pub uuid: String,
4368 pub session_id: String,
4369}
4370
4371#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4373pub enum PermissionCheckStatus {
4374 Checking,
4377 Done,
4379 Unknown(String),
4381}
4382
4383impl PermissionCheckStatus {
4384 pub fn as_str(&self) -> &str {
4385 match self {
4386 Self::Checking => "checking",
4387 Self::Done => "done",
4388 Self::Unknown(s) => s.as_str(),
4389 }
4390 }
4391}
4392
4393impl fmt::Display for PermissionCheckStatus {
4394 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4395 f.write_str(self.as_str())
4396 }
4397}
4398
4399impl From<&str> for PermissionCheckStatus {
4400 fn from(s: &str) -> Self {
4401 match s {
4402 "checking" => Self::Checking,
4403 "done" => Self::Done,
4404 other => Self::Unknown(other.to_string()),
4405 }
4406 }
4407}
4408
4409impl Serialize for PermissionCheckStatus {
4410 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4411 serializer.serialize_str(self.as_str())
4412 }
4413}
4414
4415impl<'de> Deserialize<'de> for PermissionCheckStatus {
4416 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4417 let s = String::deserialize(deserializer)?;
4418 Ok(Self::from(s.as_str()))
4419 }
4420}
4421
4422#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4424pub struct MissingFileAttachment {
4425 pub file_uuid: String,
4427 pub reason: String,
4432}
4433
4434#[derive(Debug, Clone, Serialize, Deserialize)]
4444pub struct SessionTitleChangedMessage {
4445 pub title: String,
4450 pub uuid: String,
4451 pub session_id: String,
4452}
4453
4454#[derive(Debug, Clone, Serialize, Deserialize)]
4458pub struct UiLogMessage {
4459 pub plugin: String,
4461 pub text: String,
4465 pub uuid: String,
4466 pub session_id: String,
4467}
4468
4469#[derive(Debug, Clone, Serialize, Deserialize)]
4474pub struct UiToastMessage {
4475 pub plugin: String,
4477 pub text: String,
4481 pub timeout_ms: u64,
4484 pub uuid: String,
4485 pub session_id: String,
4486}
4487
4488#[derive(Debug, Clone, Serialize, Deserialize)]
4492pub struct UiStatusMessage {
4493 pub plugin: String,
4495 pub text: Option<String>,
4500 pub uuid: String,
4501 pub session_id: String,
4502}
4503
4504#[derive(Debug, Clone, Serialize, Deserialize)]
4509pub struct UiInvalidateMessage {
4510 pub event: String,
4513 #[serde(default, skip_serializing_if = "Option::is_none")]
4518 pub instances: Option<Vec<UiInvalidatedInstance>>,
4519 pub uuid: String,
4520 pub session_id: String,
4521}
4522
4523#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4525pub struct UiInvalidatedInstance {
4526 pub surface: String,
4528 pub component: String,
4530 pub instance_id: String,
4533}
4534
4535#[derive(Debug, Clone, Serialize, Deserialize)]
4542pub struct UiPanesMessage {
4543 pub panes: Vec<UiPane>,
4546 pub shown_id: Option<String>,
4549 pub focused_id: Option<String>,
4552 pub focus_requested_id: Option<String>,
4556 pub uuid: String,
4557 pub session_id: String,
4558}
4559
4560#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4562pub struct UiPane {
4563 pub id: String,
4566 pub title: String,
4569 pub plugin: String,
4571 #[serde(default, skip_serializing_if = "Option::is_none")]
4574 pub close_on_escape: Option<bool>,
4575 #[serde(default, skip_serializing_if = "Option::is_none")]
4578 pub hold_toasts: Option<bool>,
4579 #[serde(default, skip_serializing_if = "Option::is_none")]
4582 pub rows: Option<u64>,
4583 #[serde(default, skip_serializing_if = "Option::is_none")]
4586 pub columns: Option<u64>,
4587}
4588
4589#[derive(Debug, Clone, Serialize, Deserialize)]
4593pub struct UiScrollMessage {
4594 pub client_id: String,
4596 pub component: UiSiteComponent,
4598 pub instance_id: String,
4600 pub offset: u64,
4603 #[serde(default, skip_serializing_if = "Option::is_none")]
4606 pub follow_end: Option<bool>,
4607 pub uuid: String,
4608 pub session_id: String,
4609}
4610
4611#[derive(Debug, Clone, Serialize, Deserialize)]
4616pub struct UiFocusMessage {
4617 pub client_id: String,
4619 pub component: UiSiteComponent,
4621 pub instance_id: String,
4623 pub plugin: String,
4625 pub key: String,
4627 pub uuid: String,
4628 pub session_id: String,
4629}
4630
4631#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4633pub enum UiSiteComponent {
4634 Pane,
4636 AbovePrompt,
4638 Unknown(String),
4640}
4641
4642impl UiSiteComponent {
4643 pub fn as_str(&self) -> &str {
4644 match self {
4645 Self::Pane => "Pane",
4646 Self::AbovePrompt => "AbovePrompt",
4647 Self::Unknown(s) => s.as_str(),
4648 }
4649 }
4650}
4651
4652impl fmt::Display for UiSiteComponent {
4653 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4654 f.write_str(self.as_str())
4655 }
4656}
4657
4658impl From<&str> for UiSiteComponent {
4659 fn from(s: &str) -> Self {
4660 match s {
4661 "Pane" => Self::Pane,
4662 "AbovePrompt" => Self::AbovePrompt,
4663 other => Self::Unknown(other.to_string()),
4664 }
4665 }
4666}
4667
4668impl Serialize for UiSiteComponent {
4669 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4670 serializer.serialize_str(self.as_str())
4671 }
4672}
4673
4674impl<'de> Deserialize<'de> for UiSiteComponent {
4675 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4676 let s = String::deserialize(deserializer)?;
4677 Ok(Self::from(s.as_str()))
4678 }
4679}
4680
4681#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4684pub struct LocalCommandRun {
4685 pub command: String,
4687 pub args: String,
4690}
4691
4692#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4696pub struct LocalCommandOutcome {
4697 pub kind: LocalCommandOutcomeKind,
4698 #[serde(default, skip_serializing_if = "Option::is_none")]
4701 pub suggestion: Option<String>,
4702}
4703
4704#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4709pub enum LocalCommandOutcomeKind {
4710 UnavailableHeadless,
4712 Unknown,
4715 Failed,
4717 RestartRequired,
4719 Other(String),
4721}
4722
4723impl LocalCommandOutcomeKind {
4724 pub fn as_str(&self) -> &str {
4725 match self {
4726 Self::UnavailableHeadless => "unavailable_headless",
4727 Self::Unknown => "unknown",
4728 Self::Failed => "failed",
4729 Self::RestartRequired => "restart_required",
4730 Self::Other(s) => s.as_str(),
4731 }
4732 }
4733}
4734
4735impl fmt::Display for LocalCommandOutcomeKind {
4736 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4737 f.write_str(self.as_str())
4738 }
4739}
4740
4741impl From<&str> for LocalCommandOutcomeKind {
4742 fn from(s: &str) -> Self {
4743 match s {
4744 "unavailable_headless" => Self::UnavailableHeadless,
4745 "unknown" => Self::Unknown,
4746 "failed" => Self::Failed,
4747 "restart_required" => Self::RestartRequired,
4748 other => Self::Other(other.to_string()),
4749 }
4750 }
4751}
4752
4753impl Serialize for LocalCommandOutcomeKind {
4754 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4755 serializer.serialize_str(self.as_str())
4756 }
4757}
4758
4759impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4760 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4761 let s = String::deserialize(deserializer)?;
4762 Ok(Self::from(s.as_str()))
4763 }
4764}
4765
4766#[derive(Debug, Clone, Serialize, Deserialize)]
4772pub struct UsageReport {
4773 pub session: super::control::UsageSession,
4775 pub rate_limits: Option<UsageReportRateLimits>,
4779 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4780 pub extra: serde_json::Map<String, Value>,
4781}
4782
4783#[derive(Debug, Clone, Serialize, Deserialize)]
4785pub struct UsageReportRateLimits {
4786 pub limits: Option<Vec<UsageReportLimit>>,
4794 #[serde(default, skip_serializing_if = "Option::is_none")]
4797 pub extra_usage: Option<UsageReportExtraUsage>,
4798 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4799 pub extra: serde_json::Map<String, Value>,
4800}
4801
4802#[derive(Debug, Clone, Serialize, Deserialize)]
4804pub struct UsageReportLimit {
4805 pub kind: String,
4808 pub group: String,
4811 pub percent: f64,
4813 pub resets_at: Option<String>,
4815 #[serde(default, skip_serializing_if = "Option::is_none")]
4818 pub scope: Option<UsageReportScope>,
4819 #[serde(default, skip_serializing_if = "Option::is_none")]
4823 pub severity: Option<String>,
4824 #[serde(default, skip_serializing_if = "Option::is_none")]
4826 pub is_active: Option<bool>,
4827 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4828 pub extra: serde_json::Map<String, Value>,
4829}
4830
4831#[derive(Debug, Clone, Serialize, Deserialize)]
4833pub struct UsageReportScope {
4834 #[serde(default, skip_serializing_if = "Option::is_none")]
4835 pub model: Option<UsageReportScopeLabel>,
4836 #[serde(default, skip_serializing_if = "Option::is_none")]
4837 pub surface: Option<UsageReportScopeLabel>,
4838 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4839 pub extra: serde_json::Map<String, Value>,
4840}
4841
4842#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4844pub struct UsageReportScopeLabel {
4845 pub display_name: String,
4846}
4847
4848#[derive(Debug, Clone, Serialize, Deserialize)]
4852pub struct UsageReportExtraUsage {
4853 pub is_enabled: bool,
4855 pub monthly_limit: Option<f64>,
4856 pub used_credits: Option<f64>,
4857 pub utilization: Option<f64>,
4858 #[serde(default, skip_serializing_if = "Option::is_none")]
4859 pub currency: Option<String>,
4860 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4861 pub extra: serde_json::Map<String, Value>,
4862}
4863
4864#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4867pub struct BatchToolUse {
4868 pub id: String,
4869 pub name: String,
4870}
4871
4872#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4877pub struct ContextUsage {
4878 pub model: String,
4880 pub total_tokens: u64,
4883 pub raw_max_tokens: u64,
4886 pub percentage: u64,
4888 #[serde(default, skip_serializing_if = "Option::is_none")]
4890 pub over_limit: Option<ContextOverLimit>,
4891 #[serde(default)]
4893 pub categories: Vec<ContextCategory>,
4894 #[serde(default)]
4896 pub mcp_tools: Vec<ContextMcpTool>,
4897 #[serde(default)]
4899 pub memory_files: Vec<ContextMemoryFile>,
4900 #[serde(default)]
4902 pub agents: Vec<ContextAgent>,
4903 #[serde(default, skip_serializing_if = "Option::is_none")]
4905 pub skills: Option<Vec<ContextSkill>>,
4906}
4907
4908#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4910pub struct ContextOverLimit {
4911 pub tokens_over: u64,
4912 pub kind: String,
4915}
4916
4917#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4919pub struct ContextCategory {
4920 pub name: String,
4923 pub tokens: u64,
4924 pub kind: String,
4928}
4929
4930#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4932pub struct ContextMcpTool {
4933 pub name: String,
4935 pub server_name: String,
4936 pub tokens: u64,
4937}
4938
4939#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4941pub struct ContextMemoryFile {
4942 pub path: String,
4943 #[serde(rename = "type")]
4945 pub file_type: String,
4946 pub tokens: u64,
4947}
4948
4949#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4951pub struct ContextAgent {
4952 pub agent_type: String,
4953 pub source: String,
4955 pub tokens: u64,
4956}
4957
4958#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4960pub struct ContextSkill {
4961 pub name: String,
4962 pub source: String,
4964 #[serde(default, skip_serializing_if = "Option::is_none")]
4965 pub plugin_name: Option<String>,
4966 pub tokens: u64,
4967}
4968
4969#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4971pub struct ToolUseMeta {
4972 pub id: String,
4973 pub display_name: String,
4974 #[serde(default, skip_serializing_if = "Option::is_none")]
4975 pub server_display_name: Option<String>,
4976 #[serde(default, skip_serializing_if = "Option::is_none")]
4977 pub icon_url: Option<String>,
4978}
4979
4980#[derive(Debug, Clone, Serialize, Deserialize)]
4982pub struct AssistantMessage {
4983 pub message: AssistantMessageContent,
4984 #[serde(alias = "sessionId")]
4985 pub session_id: String,
4986 #[serde(skip_serializing_if = "Option::is_none")]
4987 pub uuid: Option<String>,
4988 #[serde(skip_serializing_if = "Option::is_none")]
4989 pub parent_tool_use_id: Option<String>,
4990 #[serde(skip_serializing_if = "Option::is_none")]
4992 pub request_id: Option<String>,
4993 #[serde(default, skip_serializing_if = "Option::is_none")]
5002 pub user_message_uuid: Option<String>,
5003 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5009 pub user_message_uuids: Vec<String>,
5010 #[serde(default, skip_serializing_if = "Option::is_none")]
5014 pub agent_id: Option<String>,
5015 #[serde(skip_serializing_if = "Option::is_none")]
5018 pub subagent_type: Option<String>,
5019 #[serde(skip_serializing_if = "Option::is_none")]
5021 pub task_description: Option<String>,
5022 #[serde(skip_serializing_if = "Option::is_none")]
5023 pub error: Option<AssistantErrorKind>,
5024 #[serde(default, skip_serializing_if = "Option::is_none")]
5028 pub aborted: Option<bool>,
5029 #[serde(default, skip_serializing_if = "Option::is_none")]
5034 pub resumed_from_incomplete_thinking: Option<bool>,
5035 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5036 pub supersedes: Vec<String>,
5037 #[serde(skip_serializing_if = "Option::is_none")]
5038 pub timestamp: Option<String>,
5039 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5040 pub tool_use_meta: Vec<ToolUseMeta>,
5041 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5046 pub batch_tool_uses: Vec<BatchToolUse>,
5047 #[serde(default, skip_serializing_if = "Option::is_none")]
5053 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
5054 #[serde(default, skip_serializing_if = "Option::is_none")]
5060 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
5061 #[serde(default, skip_serializing_if = "Option::is_none")]
5065 pub historical: Option<bool>,
5066 #[serde(default, skip_serializing_if = "Option::is_none")]
5074 pub resume_reason: Option<String>,
5075 #[serde(default, skip_serializing_if = "Option::is_none")]
5081 pub local_command_source: Option<String>,
5082 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5091 pub narration_block_indexes: Vec<usize>,
5092 #[serde(default, skip_serializing_if = "Option::is_none")]
5096 pub narration_hint: Option<String>,
5097 #[serde(default, skip_serializing_if = "Option::is_none")]
5106 pub thinking_duration_ms: Option<u64>,
5107 #[serde(default, skip_serializing_if = "Option::is_none")]
5111 pub context_usage: Option<ContextUsage>,
5112 #[serde(default, skip_serializing_if = "Option::is_none")]
5120 pub usage_report: Option<Box<UsageReport>>,
5121 #[serde(default, skip_serializing_if = "Option::is_none")]
5128 pub local_command_run: Option<LocalCommandRun>,
5129 #[serde(default, skip_serializing_if = "Option::is_none")]
5135 pub local_command_outcome: Option<LocalCommandOutcome>,
5136 #[serde(default, skip_serializing_if = "Option::is_none")]
5137 pub is_meta: Option<bool>,
5138 #[serde(default, skip_serializing_if = "Option::is_none")]
5139 pub is_virtual: Option<bool>,
5140 #[serde(default, skip_serializing_if = "Option::is_none")]
5141 pub is_api_error_message: Option<bool>,
5142 #[serde(skip_serializing_if = "Option::is_none")]
5143 pub api_error_status: Option<u16>,
5144 #[serde(skip_serializing_if = "Option::is_none")]
5164 pub api_error: Option<String>,
5165 #[serde(default, skip_serializing_if = "Option::is_none")]
5172 pub api_error_code: Option<String>,
5173 #[serde(default, skip_serializing_if = "Option::is_none")]
5176 pub api_error_params: Option<ApiErrorParams>,
5177 #[serde(skip_serializing_if = "Option::is_none")]
5178 pub error_details: Option<String>,
5179 #[serde(skip_serializing_if = "Option::is_none")]
5180 pub advisor_model: Option<String>,
5181 #[serde(skip_serializing_if = "Option::is_none")]
5182 pub attribution_agent: Option<String>,
5183 #[serde(skip_serializing_if = "Option::is_none")]
5184 pub attribution_skill: Option<String>,
5185 #[serde(skip_serializing_if = "Option::is_none")]
5186 pub attribution_plugin: Option<String>,
5187 #[serde(skip_serializing_if = "Option::is_none")]
5188 pub attribution_mcp_server: Option<String>,
5189 #[serde(skip_serializing_if = "Option::is_none")]
5190 pub attribution_mcp_tool: Option<String>,
5191}
5192
5193#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5197pub struct ApiErrorParams {
5198 #[serde(default, skip_serializing_if = "Option::is_none")]
5200 pub effort: Option<String>,
5201 #[serde(default, skip_serializing_if = "Option::is_none")]
5204 pub provider: Option<ApiErrorProvider>,
5205 #[serde(default, skip_serializing_if = "Option::is_none")]
5208 pub remedy: Option<ApiErrorRemedy>,
5209 #[serde(default, skip_serializing_if = "Option::is_none")]
5212 pub media: Option<ApiErrorMedia>,
5213 #[serde(default, skip_serializing_if = "Option::is_none")]
5215 pub media_reason: Option<ApiErrorMediaReason>,
5216 #[serde(default, skip_serializing_if = "Option::is_none")]
5224 pub rate_limit_info: Option<RateLimitInfo>,
5225}
5226
5227#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5229pub enum ApiErrorMedia {
5230 Image,
5231 Document,
5232 Unknown(String),
5234}
5235
5236impl ApiErrorMedia {
5237 pub fn as_str(&self) -> &str {
5238 match self {
5239 Self::Image => "image",
5240 Self::Document => "document",
5241 Self::Unknown(s) => s.as_str(),
5242 }
5243 }
5244}
5245
5246impl fmt::Display for ApiErrorMedia {
5247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5248 f.write_str(self.as_str())
5249 }
5250}
5251
5252impl From<&str> for ApiErrorMedia {
5253 fn from(s: &str) -> Self {
5254 match s {
5255 "image" => Self::Image,
5256 "document" => Self::Document,
5257 other => Self::Unknown(other.to_string()),
5258 }
5259 }
5260}
5261
5262impl Serialize for ApiErrorMedia {
5263 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5264 serializer.serialize_str(self.as_str())
5265 }
5266}
5267
5268impl<'de> Deserialize<'de> for ApiErrorMedia {
5269 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5270 let s = String::deserialize(deserializer)?;
5271 Ok(Self::from(s.as_str()))
5272 }
5273}
5274
5275#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5278pub enum ApiErrorMediaReason {
5279 Unprocessable,
5282 UnsupportedByModel,
5284 MediaBudget,
5287 Unknown(String),
5289}
5290
5291impl ApiErrorMediaReason {
5292 pub fn as_str(&self) -> &str {
5293 match self {
5294 Self::Unprocessable => "unprocessable",
5295 Self::UnsupportedByModel => "unsupported_by_model",
5296 Self::MediaBudget => "media_budget",
5297 Self::Unknown(s) => s.as_str(),
5298 }
5299 }
5300}
5301
5302impl fmt::Display for ApiErrorMediaReason {
5303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5304 f.write_str(self.as_str())
5305 }
5306}
5307
5308impl From<&str> for ApiErrorMediaReason {
5309 fn from(s: &str) -> Self {
5310 match s {
5311 "unprocessable" => Self::Unprocessable,
5312 "unsupported_by_model" => Self::UnsupportedByModel,
5313 "media_budget" => Self::MediaBudget,
5314 other => Self::Unknown(other.to_string()),
5315 }
5316 }
5317}
5318
5319impl Serialize for ApiErrorMediaReason {
5320 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5321 serializer.serialize_str(self.as_str())
5322 }
5323}
5324
5325impl<'de> Deserialize<'de> for ApiErrorMediaReason {
5326 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5327 let s = String::deserialize(deserializer)?;
5328 Ok(Self::from(s.as_str()))
5329 }
5330}
5331
5332#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5334pub enum ApiErrorProvider {
5335 Bedrock,
5336 AnthropicAws,
5337 Mantle,
5338 AnthropicGoogleCloud,
5339 Vertex,
5340 Foundry,
5341 Gateway,
5342 Unknown(String),
5344}
5345
5346impl ApiErrorProvider {
5347 pub fn as_str(&self) -> &str {
5348 match self {
5349 Self::Bedrock => "bedrock",
5350 Self::AnthropicAws => "anthropicAws",
5351 Self::Mantle => "mantle",
5352 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
5353 Self::Vertex => "vertex",
5354 Self::Foundry => "foundry",
5355 Self::Gateway => "gateway",
5356 Self::Unknown(s) => s.as_str(),
5357 }
5358 }
5359}
5360
5361impl fmt::Display for ApiErrorProvider {
5362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5363 f.write_str(self.as_str())
5364 }
5365}
5366
5367impl From<&str> for ApiErrorProvider {
5368 fn from(s: &str) -> Self {
5369 match s {
5370 "bedrock" => Self::Bedrock,
5371 "anthropicAws" => Self::AnthropicAws,
5372 "mantle" => Self::Mantle,
5373 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
5374 "vertex" => Self::Vertex,
5375 "foundry" => Self::Foundry,
5376 "gateway" => Self::Gateway,
5377 other => Self::Unknown(other.to_string()),
5378 }
5379 }
5380}
5381
5382impl Serialize for ApiErrorProvider {
5383 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5384 serializer.serialize_str(self.as_str())
5385 }
5386}
5387
5388impl<'de> Deserialize<'de> for ApiErrorProvider {
5389 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5390 let s = String::deserialize(deserializer)?;
5391 Ok(Self::from(s.as_str()))
5392 }
5393}
5394
5395#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5397pub enum ApiErrorRemedy {
5398 RefreshCommand,
5401 RefreshCredentials,
5403 Adc,
5406 GatewayToken,
5409 HostManaged,
5411 ModelAccess,
5414 Unknown(String),
5416}
5417
5418impl ApiErrorRemedy {
5419 pub fn as_str(&self) -> &str {
5420 match self {
5421 Self::RefreshCommand => "refresh_command",
5422 Self::RefreshCredentials => "refresh_credentials",
5423 Self::Adc => "adc",
5424 Self::GatewayToken => "gateway_token",
5425 Self::HostManaged => "host_managed",
5426 Self::ModelAccess => "model_access",
5427 Self::Unknown(s) => s.as_str(),
5428 }
5429 }
5430}
5431
5432impl fmt::Display for ApiErrorRemedy {
5433 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5434 f.write_str(self.as_str())
5435 }
5436}
5437
5438impl From<&str> for ApiErrorRemedy {
5439 fn from(s: &str) -> Self {
5440 match s {
5441 "refresh_command" => Self::RefreshCommand,
5442 "refresh_credentials" => Self::RefreshCredentials,
5443 "adc" => Self::Adc,
5444 "gateway_token" => Self::GatewayToken,
5445 "host_managed" => Self::HostManaged,
5446 "model_access" => Self::ModelAccess,
5447 other => Self::Unknown(other.to_string()),
5448 }
5449 }
5450}
5451
5452impl Serialize for ApiErrorRemedy {
5453 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5454 serializer.serialize_str(self.as_str())
5455 }
5456}
5457
5458impl<'de> Deserialize<'de> for ApiErrorRemedy {
5459 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5460 let s = String::deserialize(deserializer)?;
5461 Ok(Self::from(s.as_str()))
5462 }
5463}
5464
5465#[derive(Debug, Clone, Serialize, Deserialize)]
5467pub struct AssistantMessageContent {
5468 pub id: String,
5469 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
5471 pub message_type: Option<String>,
5472 pub role: MessageRole,
5473 pub model: String,
5474 pub content: Vec<ContentBlock>,
5475 #[serde(skip_serializing_if = "Option::is_none")]
5476 pub stop_reason: Option<StopReason>,
5477 #[serde(skip_serializing_if = "Option::is_none")]
5478 pub stop_sequence: Option<String>,
5479 #[serde(skip_serializing_if = "Option::is_none")]
5480 pub usage: Option<AssistantUsage>,
5481 #[serde(skip_serializing_if = "Option::is_none")]
5483 pub stop_details: Option<Value>,
5484 #[serde(skip_serializing_if = "Option::is_none")]
5486 pub context_management: Option<Value>,
5487}
5488
5489#[derive(Debug, Clone, Serialize, Deserialize)]
5491pub struct AssistantUsage {
5492 #[serde(default)]
5494 pub input_tokens: u32,
5495
5496 #[serde(default)]
5498 pub output_tokens: u32,
5499
5500 #[serde(default)]
5502 pub cache_creation_input_tokens: u32,
5503
5504 #[serde(default)]
5506 pub cache_read_input_tokens: u32,
5507
5508 #[serde(skip_serializing_if = "Option::is_none")]
5510 pub service_tier: Option<String>,
5511
5512 #[serde(skip_serializing_if = "Option::is_none")]
5514 pub cache_creation: Option<CacheCreationDetails>,
5515
5516 #[serde(skip_serializing_if = "Option::is_none")]
5518 pub inference_geo: Option<String>,
5519
5520 #[serde(default, skip_serializing_if = "Option::is_none")]
5523 pub fallback_credit: Option<Value>,
5524}
5525
5526#[derive(Debug, Clone, Serialize, Deserialize)]
5528pub struct CacheCreationDetails {
5529 #[serde(default)]
5531 pub ephemeral_1h_input_tokens: u32,
5532
5533 #[serde(default)]
5535 pub ephemeral_5m_input_tokens: u32,
5536}
5537
5538#[cfg(test)]
5539mod tests {
5540 use crate::io::ClaudeOutput;
5541
5542 #[test]
5543 fn test_subagent_usage_rollup_accumulates_task_results() {
5544 use super::SubagentUsageRollup;
5545
5546 let mut rollup = SubagentUsageRollup::default();
5547
5548 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}}"#;
5549 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5550 assert!(rollup.observe(&output));
5551 assert_eq!(rollup.subagent_tokens, 10201);
5552 assert_eq!(rollup.agent_count, 1);
5553 assert_eq!(rollup.tool_uses, 3);
5554 assert_eq!(rollup.duration_ms, 1853);
5555
5556 assert!(!rollup.observe(&output));
5558 assert_eq!(rollup.agent_count, 1);
5559 assert_eq!(rollup.subagent_tokens, 10201);
5560
5561 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}}"#;
5563 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5564 assert!(rollup.observe(&output));
5565 assert_eq!(rollup.agent_count, 2);
5566 assert_eq!(rollup.subagent_tokens, 10701);
5567 }
5568
5569 #[test]
5570 fn test_subagent_usage_rollup_ignores_non_task_results() {
5571 use super::SubagentUsageRollup;
5572
5573 let mut rollup = SubagentUsageRollup::default();
5574
5575 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}}"#;
5578 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5579 assert!(!rollup.observe(&output));
5580
5581 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5583 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5584 assert!(!rollup.observe(&output));
5585
5586 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5588 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5589 assert!(!rollup.observe(&output));
5590
5591 assert_eq!(rollup, SubagentUsageRollup::default());
5592 }
5593
5594 #[test]
5595 fn test_subagent_usage_rollup_over_captured_session() {
5596 use super::SubagentUsageRollup;
5597
5598 let mut rollup = SubagentUsageRollup::default();
5599 let fixture =
5600 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5601 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5602 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5603 rollup.observe(&output);
5604 }
5605 }
5606 assert_eq!(rollup.agent_count, 1);
5607 assert_eq!(rollup.subagent_tokens, 10201);
5608 }
5609
5610 #[test]
5611 fn test_system_message_init() {
5612 let json = r#"{
5613 "type": "system",
5614 "subtype": "init",
5615 "session_id": "test-session-123",
5616 "cwd": "/home/user/project",
5617 "model": "claude-sonnet-4",
5618 "tools": ["Bash", "Read", "Write"],
5619 "mcp_servers": [],
5620 "slash_commands": ["compact", "cost", "review"],
5621 "agents": ["Bash", "Explore", "Plan"],
5622 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5623 "skills": [],
5624 "claude_code_version": "2.1.15",
5625 "apiKeySource": "none",
5626 "output_style": "default",
5627 "permissionMode": "default"
5628 }"#;
5629
5630 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5631 if let ClaudeOutput::System(sys) = output {
5632 assert!(sys.is_init());
5633 assert!(!sys.is_status());
5634 assert!(!sys.is_compact_boundary());
5635
5636 let init = sys.as_init().expect("Should parse as init");
5637 assert_eq!(init.session_id, "test-session-123");
5638 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5639 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5640 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5641 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5642 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5643 assert_eq!(init.plugins.len(), 1);
5644 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5645 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5646 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5647 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5648 assert_eq!(
5649 init.permission_mode,
5650 Some(super::InitPermissionMode::Default)
5651 );
5652 } else {
5653 panic!("Expected System message");
5654 }
5655 }
5656
5657 #[test]
5658 fn test_system_message_init_from_real_capture() {
5659 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5660 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5661 if let ClaudeOutput::System(sys) = output {
5662 let init = sys.as_init().expect("Should parse real init capture");
5663 assert_eq!(init.slash_commands.len(), 8);
5664 assert!(init.slash_commands.contains(&"compact".to_string()));
5665 assert!(init.slash_commands.contains(&"review".to_string()));
5666 assert_eq!(init.agents.len(), 5);
5667 assert!(init.agents.contains(&"Bash".to_string()));
5668 assert!(init.agents.contains(&"Explore".to_string()));
5669 assert_eq!(init.plugins.len(), 1);
5670 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5671 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5672 } else {
5673 panic!("Expected System message");
5674 }
5675 }
5676
5677 #[test]
5678 fn test_system_message_status() {
5679 let json = r#"{
5680 "type": "system",
5681 "subtype": "status",
5682 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5683 "status": "compacting",
5684 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5685 }"#;
5686
5687 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5688 if let ClaudeOutput::System(sys) = output {
5689 assert!(sys.is_status());
5690 assert!(!sys.is_init());
5691
5692 let status = sys.as_status().expect("Should parse as status");
5693 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5694 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5695 assert_eq!(
5696 status.uuid,
5697 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5698 );
5699 } else {
5700 panic!("Expected System message");
5701 }
5702 }
5703
5704 #[test]
5705 fn test_system_message_status_null() {
5706 let json = r#"{
5707 "type": "system",
5708 "subtype": "status",
5709 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5710 "status": null,
5711 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5712 }"#;
5713
5714 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5715 if let ClaudeOutput::System(sys) = output {
5716 let status = sys.as_status().expect("Should parse as status");
5717 assert_eq!(status.status, None);
5718 } else {
5719 panic!("Expected System message");
5720 }
5721 }
5722
5723 #[test]
5724 fn test_system_message_task_started() {
5725 let json = r#"{
5726 "type": "system",
5727 "subtype": "task_started",
5728 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5729 "task_id": "b6daf3f",
5730 "task_type": "local_bash",
5731 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5732 "description": "Wait for CI on PR #12",
5733 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5734 }"#;
5735
5736 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5737 if let ClaudeOutput::System(sys) = output {
5738 assert!(sys.is_task_started());
5739 assert!(!sys.is_task_progress());
5740 assert!(!sys.is_task_notification());
5741
5742 let task = sys.as_task_started().expect("Should parse as task_started");
5743 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5744 assert_eq!(task.task_id, "b6daf3f");
5745 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5746 assert_eq!(
5747 task.tool_use_id.as_deref(),
5748 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5749 );
5750 assert_eq!(task.description, "Wait for CI on PR #12");
5751 } else {
5752 panic!("Expected System message");
5753 }
5754 }
5755
5756 #[test]
5757 fn test_system_message_task_started_agent() {
5758 let json = r#"{
5759 "type": "system",
5760 "subtype": "task_started",
5761 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5762 "task_id": "a4a7e0906e5fc64cc",
5763 "task_type": "local_agent",
5764 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5765 "description": "Explore Scene/ArrayScene duplication",
5766 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5767 }"#;
5768
5769 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5770 if let ClaudeOutput::System(sys) = output {
5771 let task = sys.as_task_started().expect("Should parse as task_started");
5772 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5773 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5774 } else {
5775 panic!("Expected System message");
5776 }
5777 }
5778
5779 #[test]
5780 fn test_system_message_task_progress() {
5781 let json = r#"{
5782 "type": "system",
5783 "subtype": "task_progress",
5784 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5785 "task_id": "a4a7e0906e5fc64cc",
5786 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5787 "description": "Reading src/jplephem/chebyshev.rs",
5788 "last_tool_name": "Read",
5789 "usage": {
5790 "duration_ms": 13996,
5791 "tool_uses": 9,
5792 "total_tokens": 38779
5793 },
5794 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5795 }"#;
5796
5797 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5798 if let ClaudeOutput::System(sys) = output {
5799 assert!(sys.is_task_progress());
5800 assert!(!sys.is_task_started());
5801
5802 let progress = sys
5803 .as_task_progress()
5804 .expect("Should parse as task_progress");
5805 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5806 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5807 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5808 assert_eq!(progress.usage.duration_ms, 13996);
5809 assert_eq!(progress.usage.tool_uses, 9);
5810 assert_eq!(progress.usage.total_tokens, 38779);
5811 } else {
5812 panic!("Expected System message");
5813 }
5814 }
5815
5816 #[test]
5817 fn test_system_message_task_notification_completed() {
5818 let json = r#"{
5819 "type": "system",
5820 "subtype": "task_notification",
5821 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5822 "task_id": "a0ba761e9dc9c316f",
5823 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
5824 "status": "completed",
5825 "summary": "Agent \"Write Hipparcos data source doc\" completed",
5826 "output_file": "",
5827 "usage": {
5828 "duration_ms": 172300,
5829 "tool_uses": 11,
5830 "total_tokens": 42005
5831 },
5832 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
5833 }"#;
5834
5835 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5836 if let ClaudeOutput::System(sys) = output {
5837 assert!(sys.is_task_notification());
5838
5839 let notif = sys
5840 .as_task_notification()
5841 .expect("Should parse as task_notification");
5842 assert_eq!(notif.status, super::TaskStatus::Completed);
5843 assert_eq!(
5844 notif.summary,
5845 "Agent \"Write Hipparcos data source doc\" completed"
5846 );
5847 assert_eq!(notif.output_file, Some("".to_string()));
5848 assert_eq!(
5849 notif.tool_use_id,
5850 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
5851 );
5852 let usage = notif.usage.expect("Should have usage");
5853 assert_eq!(usage.duration_ms, 172300);
5854 assert_eq!(usage.tool_uses, 11);
5855 assert_eq!(usage.total_tokens, 42005);
5856 } else {
5857 panic!("Expected System message");
5858 }
5859 }
5860
5861 #[test]
5862 fn test_system_message_task_notification_failed_no_usage() {
5863 let json = r#"{
5864 "type": "system",
5865 "subtype": "task_notification",
5866 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
5867 "task_id": "b98f6a3",
5868 "status": "failed",
5869 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
5870 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
5871 }"#;
5872
5873 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5874 if let ClaudeOutput::System(sys) = output {
5875 let notif = sys
5876 .as_task_notification()
5877 .expect("Should parse as task_notification");
5878 assert_eq!(notif.status, super::TaskStatus::Failed);
5879 assert!(notif.tool_use_id.is_none());
5880 assert!(notif.usage.is_none());
5881 assert_eq!(
5882 notif.output_file,
5883 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
5884 );
5885 } else {
5886 panic!("Expected System message");
5887 }
5888 }
5889
5890 #[test]
5896 fn test_task_messages_roundtrip_through_value() {
5897 let cases = [
5898 r#"{"type":"system","subtype":"task_started","session_id":"s1",
5899 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
5900 "description":"Sleep 3s","uuid":"u1"}"#,
5901 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
5902 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
5903 "last_tool_name":"Bash",
5904 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5905 "uuid":"u2"}"#,
5906 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
5907 "task_id":"t1","tool_use_id":"tu1","status":"completed",
5908 "summary":"done","output_file":"",
5909 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5910 "uuid":"u3"}"#,
5911 ];
5912
5913 for json in cases {
5914 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5915 let value = serde_json::to_value(&output).unwrap();
5916 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
5917
5918 let ClaudeOutput::System(sys) = reparsed else {
5919 panic!("Expected System variant after round-trip");
5920 };
5921
5922 match sys.subtype {
5923 super::SystemSubtype::TaskStarted => {
5924 assert!(
5925 sys.as_task_started().is_some(),
5926 "as_task_started failed after round-trip"
5927 );
5928 }
5929 super::SystemSubtype::TaskProgress => {
5930 assert!(
5931 sys.as_task_progress().is_some(),
5932 "as_task_progress failed after round-trip"
5933 );
5934 }
5935 super::SystemSubtype::TaskNotification => {
5936 assert!(
5937 sys.as_task_notification().is_some(),
5938 "as_task_notification failed after round-trip"
5939 );
5940 }
5941 other => panic!("unexpected subtype after round-trip: {other:?}"),
5942 }
5943 }
5944 }
5945
5946 #[test]
5947 fn test_system_message_compact_boundary() {
5948 let json = r#"{
5949 "type": "system",
5950 "subtype": "compact_boundary",
5951 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5952 "compact_metadata": {
5953 "pre_tokens": 155285,
5954 "trigger": "auto"
5955 },
5956 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
5957 }"#;
5958
5959 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5960 if let ClaudeOutput::System(sys) = output {
5961 assert!(sys.is_compact_boundary());
5962 assert!(!sys.is_init());
5963 assert!(!sys.is_status());
5964
5965 let compact = sys
5966 .as_compact_boundary()
5967 .expect("Should parse as compact_boundary");
5968 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5969 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
5970 assert_eq!(
5971 compact.compact_metadata.trigger,
5972 super::CompactionTrigger::Auto
5973 );
5974 assert!(compact.summary.is_none());
5976 assert!(compact.leaf_message_count.is_none());
5977 assert!(compact.duration_ms.is_none());
5978 } else {
5979 panic!("Expected System message");
5980 }
5981 }
5982
5983 #[test]
5984 fn test_compact_boundary_with_summary_stats() {
5985 let json = r#"{
5987 "type": "system",
5988 "subtype": "compact_boundary",
5989 "session_id": "s1",
5990 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
5991 "summary": "Summarized the earlier exploration.",
5992 "leaf_message_count": 42,
5993 "duration_ms": 1234,
5994 "uuid": "u1"
5995 }"#;
5996 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5997 let ClaudeOutput::System(sys) = output else {
5998 panic!("Expected System message");
5999 };
6000 let compact = sys.as_compact_boundary().expect("compact_boundary");
6001 assert_eq!(
6002 compact.summary.as_deref(),
6003 Some("Summarized the earlier exploration.")
6004 );
6005 assert_eq!(compact.leaf_message_count, Some(42));
6006 assert_eq!(compact.duration_ms, Some(1234));
6007
6008 let json_alt = r#"{
6011 "type": "system",
6012 "subtype": "compact_boundary",
6013 "session_id": "s2",
6014 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
6015 "content": "alt-key summary",
6016 "message_count": 7
6017 }"#;
6018 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
6019 let ClaudeOutput::System(sys) = output else {
6020 panic!("Expected System message");
6021 };
6022 let compact = sys.as_compact_boundary().expect("compact_boundary");
6023 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
6024 assert_eq!(compact.leaf_message_count, Some(7));
6025 }
6026
6027 #[test]
6028 fn test_init_message_with_new_fields() {
6029 let json = r#"{
6030 "type": "system",
6031 "subtype": "init",
6032 "session_id": "test-session",
6033 "cwd": "/home/user",
6034 "model": "claude-opus-4-7",
6035 "tools": ["Bash"],
6036 "mcp_servers": [],
6037 "permissionMode": "default",
6038 "apiKeySource": "none",
6039 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
6040 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
6041 "fast_mode_state": "off",
6042 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
6043 "claude_code_version": "2.1.117"
6044 }"#;
6045
6046 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6047 if let ClaudeOutput::System(sys) = output {
6048 let init = sys.as_init().expect("Should parse as init");
6049 assert_eq!(
6050 init.uuid.as_deref(),
6051 Some("44841a0d-182d-493a-86b5-79800d3d9665")
6052 );
6053 assert!(init.memory_paths.is_some());
6054 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
6055 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
6056 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
6057 } else {
6058 panic!("Expected System message");
6059 }
6060 }
6061
6062 #[test]
6063 fn test_assistant_message_with_new_fields() {
6064 let json = r#"{
6065 "type": "assistant",
6066 "message": {
6067 "id": "msg_1",
6068 "type": "message",
6069 "role": "assistant",
6070 "model": "claude-opus-4-7",
6071 "content": [{"type": "text", "text": "Hello"}],
6072 "stop_reason": "end_turn",
6073 "stop_details": null,
6074 "context_management": null,
6075 "usage": {
6076 "input_tokens": 100,
6077 "output_tokens": 10,
6078 "cache_creation_input_tokens": 50,
6079 "cache_read_input_tokens": 0,
6080 "service_tier": "standard",
6081 "inference_geo": "not_available"
6082 }
6083 },
6084 "session_id": "abc",
6085 "uuid": "msg-uuid-123"
6086 }"#;
6087
6088 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6089 if let ClaudeOutput::Assistant(asst) = output {
6090 assert_eq!(asst.message.stop_details, None);
6091 assert_eq!(asst.message.context_management, None);
6092 let usage = asst.message.usage.unwrap();
6093 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
6094 } else {
6095 panic!("Expected Assistant message");
6096 }
6097 }
6098
6099 #[test]
6100 fn test_user_message_with_new_fields() {
6101 let json = r#"{
6102 "type": "user",
6103 "message": {
6104 "role": "user",
6105 "content": [{"type": "text", "text": "Hello"}]
6106 },
6107 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6108 "parent_tool_use_id": "toolu_123",
6109 "uuid": "user-msg-456"
6110 }"#;
6111
6112 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6113 if let ClaudeOutput::User(user) = output {
6114 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
6115 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
6116 } else {
6117 panic!("Expected User message");
6118 }
6119 }
6120
6121 #[test]
6127 fn test_user_message_preserves_tool_use_result_and_timestamp() {
6128 let json = r#"{
6129 "type":"user",
6130 "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"}]},
6131 "parent_tool_use_id":null,
6132 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6133 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
6134 "timestamp":"2026-05-12T23:12:04.121Z",
6135 "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"}}
6136 }"#;
6137
6138 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6139 let user = match output {
6140 ClaudeOutput::User(u) => u,
6141 other => panic!("Expected User message, got {:?}", other.message_type()),
6142 };
6143
6144 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
6145 let raw = user
6146 .tool_use_result
6147 .as_ref()
6148 .expect("tool_use_result must be captured");
6149 assert_eq!(raw["answers"]["Color"], "Blue");
6150 assert_eq!(raw["questions"][0]["header"], "Color");
6151
6152 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
6156 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
6157 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
6158 assert_eq!(
6159 reser["tool_use_result"]["questions"][0]["question"],
6160 "Which color do you prefer?"
6161 );
6162
6163 let typed: crate::AskUserQuestionInput = user
6166 .tool_use_result_as::<crate::AskUserQuestionInput>()
6167 .expect("tool_use_result present")
6168 .expect("AskUserQuestionInput parses");
6169 assert_eq!(typed.questions.len(), 1);
6170 assert_eq!(typed.questions[0].header, "Color");
6171 let answers = typed.answers.expect("answers populated");
6172 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
6173 }
6174
6175 #[test]
6178 fn test_user_message_without_tool_use_result_omits_field() {
6179 let json = r#"{
6180 "type":"user",
6181 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
6182 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
6183 }"#;
6184
6185 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6186 let user = match output {
6187 ClaudeOutput::User(u) => u,
6188 _ => panic!("Expected User message"),
6189 };
6190 assert!(user.tool_use_result.is_none());
6191 assert!(user.timestamp.is_none());
6192
6193 let reser = serde_json::to_value(&user).unwrap();
6194 assert!(reser.get("tool_use_result").is_none());
6195 assert!(reser.get("timestamp").is_none());
6196 }
6197
6198 #[test]
6202 fn test_subagent_result_exposes_token_accounting() {
6203 let json = r#"{
6204 "type":"user",
6205 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
6206 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
6207 "tool_use_result":{
6208 "status":"completed",
6209 "prompt":"Count the .rs files.",
6210 "agentId":"ac4f0276e9d4b6232",
6211 "agentType":"Explore",
6212 "content":[{"type":"text","text":"21"}],
6213 "resolvedModel":"claude-haiku-4-5-20251001",
6214 "totalDurationMs":6869,
6215 "totalTokens":7834,
6216 "totalToolUseCount":1,
6217 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
6218 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
6219 }
6220 }"#;
6221
6222 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6223 let user = match output {
6224 ClaudeOutput::User(u) => u,
6225 _ => panic!("Expected User message"),
6226 };
6227
6228 let result = user.subagent_result().expect("subagent result parses");
6229 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
6230 assert_eq!(
6231 result.resolved_model.as_deref(),
6232 Some("claude-haiku-4-5-20251001")
6233 );
6234 assert_eq!(result.total_tokens, Some(7834));
6235 assert_eq!(result.total_duration_ms, Some(6869));
6236 assert_eq!(result.total_tool_use_count, Some(1));
6237
6238 let usage = result.usage.expect("nested usage present");
6239 assert_eq!(usage.input_tokens, 6);
6240 assert_eq!(usage.cache_read_input_tokens, 7699);
6241
6242 let stats = result.tool_stats.expect("toolStats present");
6243 assert_eq!(stats.bash_count, 1);
6244 }
6245
6246 #[test]
6250 fn test_subagent_result_absent_for_non_task_result() {
6251 let json = r#"{
6252 "type":"user",
6253 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
6254 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6255 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
6256 }"#;
6257
6258 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6259 let user = match output {
6260 ClaudeOutput::User(u) => u,
6261 _ => panic!("Expected User message"),
6262 };
6263
6264 let result = user.subagent_result().expect("lenient parse");
6265 assert_eq!(result.total_tokens, None);
6266 assert_eq!(result.agent_type, None);
6267 }
6268
6269 #[test]
6270 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
6271 use serde_json::Value;
6272
6273 let raw: Value = serde_json::from_str(
6274 r#"{
6275 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
6276 "fast_mode_state":"off",
6277 "fast_mode_disabled_reason":"not_first_party",
6278 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
6279 }"#,
6280 )
6281 .unwrap();
6282 crate::io::assert_fully_wrapped(&raw);
6283
6284 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6285 let ClaudeOutput::System(sys) = output else {
6286 panic!("expected System");
6287 };
6288 let init = sys.as_init().expect("parses as init");
6289 assert_eq!(
6290 init.fast_mode_disabled_reason,
6291 Some(crate::FastModeDisabledReason::NotFirstParty)
6292 );
6293 let errs = init.mcp_server_errors.unwrap();
6294 assert_eq!(errs.len(), 1);
6295 assert_eq!(errs[0].name, "broken");
6296 assert_eq!(errs[0].error_type, "invalid_config");
6297 }
6298
6299 #[test]
6300 fn test_code_change_published_fully_wrapped() {
6301 use super::{KnownSystemEvent, SystemSubtype};
6302 use serde_json::Value;
6303
6304 let raw: Value = serde_json::from_str(
6305 r#"{
6306 "type":"system","subtype":"code_change_published",
6307 "provider":"github","url":"https://github.com/owner/repo/pull/42",
6308 "repo":"owner/repo","identifier":"42",
6309 "uuid":"u1","session_id":"s1"
6310 }"#,
6311 )
6312 .unwrap();
6313 crate::io::assert_fully_wrapped(&raw);
6314
6315 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6316 let ClaudeOutput::System(sys) = output else {
6317 panic!("expected System");
6318 };
6319 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
6320 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
6321 panic!("expected CodeChangePublished event");
6322 };
6323 assert_eq!(msg.provider, "github");
6324 assert_eq!(msg.repo, "owner/repo");
6325 assert_eq!(msg.identifier, "42");
6326
6327 assert!(sys.is_code_change_published());
6328 assert!(!sys.is_vcs_state_changed());
6329 let direct = sys.as_code_change_published().expect("direct accessor");
6330 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
6331 assert!(sys.as_vcs_state_changed().is_none());
6332 }
6333
6334 #[test]
6335 fn test_feedback_draft_queued_fully_wrapped() {
6336 use super::{KnownSystemEvent, SystemSubtype};
6337 use serde_json::Value;
6338
6339 let raw: Value = serde_json::from_str(
6340 r#"{
6341 "type":"system","subtype":"feedback_draft_queued",
6342 "draft_id":"draft-1","draft_type":"bug_report",
6343 "title":"Tool output was truncated",
6344 "details_preview":"The last command omitted its final lines",
6345 "uuid":"u1","session_id":"s1","future_field":"preserved"
6346 }"#,
6347 )
6348 .unwrap();
6349 crate::io::assert_fully_wrapped(&raw);
6350
6351 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6352 let ClaudeOutput::System(sys) = output else {
6353 panic!("expected System");
6354 };
6355 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
6356 assert!(sys.is_feedback_draft_queued());
6357 assert!(!sys.is_vcs_state_changed());
6358
6359 let direct = sys
6360 .as_feedback_draft_queued()
6361 .expect("direct typed accessor");
6362 assert_eq!(direct.draft_id, "draft-1");
6363 assert_eq!(direct.draft_type, "bug_report");
6364 assert_eq!(direct.extra["future_field"], "preserved");
6365
6366 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
6367 panic!("expected FeedbackDraftQueued event");
6368 };
6369 assert_eq!(known.title, "Tool output was truncated");
6370 assert_eq!(
6371 sys.typed_value().expect("typed value")["future_field"],
6372 "preserved"
6373 );
6374 }
6375
6376 #[test]
6377 fn test_cloud_session_delta_fully_wrapped() {
6378 use super::{KnownSystemEvent, SystemSubtype};
6379 use serde_json::Value;
6380
6381 let raw: Value = serde_json::from_str(
6382 r#"{
6383 "type":"system","subtype":"cloud_session_delta",
6384 "seq":3,"changed":["serving","connection"],
6385 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
6386 "serving":{"state":"on"},"connection":{"state":"live"}},
6387 "uuid":"u1","session_id":"s1","future_field":"preserved"
6388 }"#,
6389 )
6390 .unwrap();
6391 crate::io::assert_fully_wrapped(&raw);
6392
6393 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6394 let ClaudeOutput::System(sys) = output else {
6395 panic!("expected System");
6396 };
6397 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
6398 assert!(sys.is_cloud_session_delta());
6399 assert!(!sys.is_feedback_draft_queued());
6400
6401 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
6402 assert_eq!(direct.seq, 3);
6403 assert_eq!(direct.changed, vec!["serving", "connection"]);
6404 assert_eq!(direct.cloud_session["id"], "session_abc");
6405 assert_eq!(direct.extra["future_field"], "preserved");
6406
6407 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
6408 panic!("expected CloudSessionDelta event");
6409 };
6410 assert_eq!(known.session_id, "s1");
6411 assert_eq!(
6412 sys.typed_value().expect("typed value")["future_field"],
6413 "preserved"
6414 );
6415 }
6416
6417 #[test]
6418 fn test_vcs_state_changed_fully_wrapped() {
6419 use super::{KnownSystemEvent, VcsMutationKind};
6420 use serde_json::Value;
6421
6422 for kind in ["commit", "push", "merge", "rebase"] {
6423 let raw: Value = serde_json::from_str(&format!(
6424 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
6425 kind
6426 ))
6427 .unwrap();
6428 crate::io::assert_fully_wrapped(&raw);
6429
6430 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6431 let ClaudeOutput::System(sys) = output else {
6432 panic!("expected System");
6433 };
6434 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6435 panic!("expected VcsStateChanged event");
6436 };
6437 assert_eq!(msg.kind.as_str(), kind);
6438 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
6439 }
6440
6441 let raw: Value = serde_json::from_str(
6443 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
6444 )
6445 .unwrap();
6446 crate::io::assert_fully_wrapped(&raw);
6447 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6448 let ClaudeOutput::System(sys) = output else {
6449 panic!("expected System");
6450 };
6451 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6452 panic!("expected VcsStateChanged event");
6453 };
6454 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
6455
6456 assert!(sys.is_vcs_state_changed());
6457 let direct = sys.as_vcs_state_changed().expect("direct accessor");
6458 assert_eq!(direct.cwd, "/repo");
6459 assert!(sys.as_code_change_published().is_none());
6460 }
6461
6462 #[test]
6463 fn test_assistant_aborted_and_resume_flags_roundtrip() {
6464 let json = r#"{
6465 "type":"assistant",
6466 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
6467 "session_id":"s1",
6468 "aborted":true,
6469 "resumed_from_incomplete_thinking":true
6470 }"#;
6471 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6472 let ClaudeOutput::Assistant(msg) = &output else {
6473 panic!("expected Assistant");
6474 };
6475 assert_eq!(msg.aborted, Some(true));
6476 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
6477 let reserialized = serde_json::to_string(&output).unwrap();
6478 assert!(reserialized.contains("\"aborted\":true"));
6479 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
6480
6481 let json = r#"{
6483 "type":"assistant",
6484 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
6485 "session_id":"s2"
6486 }"#;
6487 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6488 let reserialized = serde_json::to_string(&output).unwrap();
6489 assert!(!reserialized.contains("aborted"));
6490 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
6491 }
6492
6493 #[test]
6494 fn test_user_tool_result_meta_roundtrip() {
6495 let json = r#"{
6496 "type":"user",
6497 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6498 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6499 "tool_result_meta":[
6500 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6501 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6502 ]
6503 }"#;
6504 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6505 let ClaudeOutput::User(user) = &output else {
6506 panic!("expected User");
6507 };
6508 let meta = user.tool_result_meta.as_ref().unwrap();
6509 assert_eq!(meta.len(), 2);
6510 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6511 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6512 assert_eq!(meta[1].user_feedback, None);
6513
6514 let reserialized = serde_json::to_string(&output).unwrap();
6515 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6516 assert!(!reserialized.contains("\"user_feedback\":null"));
6517 }
6518
6519 #[test]
6523 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6524 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6525 let with_scope = serde_json::json!({
6526 "trigger": "refusal",
6527 "direction": "retry",
6528 "scope": "local",
6529 "original_model": "claude-fable-5",
6530 "fallback_model": "claude-opus-5",
6531 "request_id": null,
6532 "content": "Refused; retried on fallback model.",
6533 "uuid": "u1",
6534 "session_id": "s1"
6535 });
6536 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6537 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6538 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6539
6540 let mut without = with_scope.clone();
6543 without.as_object_mut().unwrap().remove("scope");
6544 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6545 assert_eq!(msg.scope, None);
6546 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6547
6548 assert_eq!(
6550 RefusalFallbackScope::from("workspace").as_str(),
6551 "workspace"
6552 );
6553 }
6554}