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")]
1296 pub text_not_typed: Option<bool>,
1297 #[serde(default, skip_serializing_if = "Option::is_none")]
1302 pub initiator: Option<String>,
1303 #[serde(default, skip_serializing_if = "Option::is_none")]
1309 pub pasted_content: Option<Vec<Value>>,
1310 #[serde(default, skip_serializing_if = "Option::is_none")]
1314 pub historical: Option<bool>,
1315 #[serde(default, skip_serializing_if = "Option::is_none")]
1321 pub server_received_wall_ms: Option<f64>,
1322 #[serde(default, skip_serializing_if = "Option::is_none")]
1326 pub refused_message_id: Option<String>,
1327}
1328
1329impl UserMessage {
1330 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1338 &self,
1339 ) -> Option<Result<T, serde_json::Error>> {
1340 self.tool_use_result
1341 .as_ref()
1342 .map(|v| serde_json::from_value(v.clone()))
1343 }
1344
1345 pub fn subagent_result(&self) -> Option<SubagentResult> {
1356 self.tool_use_result
1357 .as_ref()
1358 .and_then(|v| serde_json::from_value(v.clone()).ok())
1359 }
1360}
1361
1362#[derive(Debug, Clone, Serialize, Deserialize)]
1373pub struct SubagentResult {
1374 #[serde(skip_serializing_if = "Option::is_none")]
1376 pub status: Option<String>,
1377 #[serde(skip_serializing_if = "Option::is_none")]
1379 pub prompt: Option<String>,
1380 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1382 pub agent_id: Option<String>,
1383 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1385 pub agent_type: Option<String>,
1386 #[serde(
1388 default,
1389 deserialize_with = "deserialize_content_blocks",
1390 skip_serializing_if = "Vec::is_empty"
1391 )]
1392 pub content: Vec<ContentBlock>,
1393 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1395 pub resolved_model: Option<String>,
1396 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1398 pub total_duration_ms: Option<u64>,
1399 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1401 pub total_tokens: Option<u64>,
1402 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1404 pub total_tool_use_count: Option<u64>,
1405 #[serde(skip_serializing_if = "Option::is_none")]
1407 pub usage: Option<super::result::UsageInfo>,
1408 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1410 pub tool_stats: Option<SubagentToolStats>,
1411}
1412
1413#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1418#[serde(rename_all = "camelCase")]
1419pub struct SubagentToolStats {
1420 #[serde(default)]
1421 pub read_count: u64,
1422 #[serde(default)]
1423 pub search_count: u64,
1424 #[serde(default)]
1425 pub bash_count: u64,
1426 #[serde(default)]
1427 pub edit_file_count: u64,
1428 #[serde(default)]
1429 pub lines_added: u64,
1430 #[serde(default)]
1431 pub lines_removed: u64,
1432 #[serde(default)]
1433 pub other_tool_count: u64,
1434 #[serde(flatten)]
1435 pub extra: serde_json::Map<String, Value>,
1436}
1437
1438#[derive(Debug, Clone, Default, PartialEq, Eq)]
1465pub struct SubagentUsageRollup {
1466 pub subagent_tokens: u64,
1469 pub agent_count: u32,
1471 pub tool_uses: u64,
1473 pub duration_ms: u64,
1475 seen_agent_ids: std::collections::BTreeSet<String>,
1476}
1477
1478impl SubagentUsageRollup {
1479 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1485 match output {
1486 ClaudeOutput::User(user) => self.observe_user(user),
1487 _ => false,
1488 }
1489 }
1490
1491 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1498 let Some(result) = user.subagent_result() else {
1499 return false;
1500 };
1501 if result.total_tokens.is_none() && result.agent_id.is_none() {
1502 return false;
1503 }
1504 if let Some(agent_id) = &result.agent_id {
1505 if !self.seen_agent_ids.insert(agent_id.clone()) {
1506 return false;
1507 }
1508 }
1509 self.agent_count += 1;
1510 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1511 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1512 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1513 true
1514 }
1515}
1516
1517#[derive(Debug, Clone, Serialize, Deserialize)]
1519pub struct MessageContent {
1520 pub role: MessageRole,
1521 #[serde(deserialize_with = "deserialize_content_blocks")]
1522 pub content: Vec<ContentBlock>,
1523}
1524
1525#[derive(Debug, Clone, Serialize, Deserialize)]
1527pub struct SystemMessage {
1528 pub subtype: SystemSubtype,
1529 #[serde(flatten)]
1530 pub data: Value, }
1532
1533impl SystemMessage {
1534 pub fn is_init(&self) -> bool {
1536 self.subtype == SystemSubtype::Init
1537 }
1538
1539 pub fn is_status(&self) -> bool {
1541 self.subtype == SystemSubtype::Status
1542 }
1543
1544 pub fn is_compact_boundary(&self) -> bool {
1546 self.subtype == SystemSubtype::CompactBoundary
1547 }
1548
1549 pub fn as_init(&self) -> Option<InitMessage> {
1551 if self.subtype != SystemSubtype::Init {
1552 return None;
1553 }
1554 serde_json::from_value(self.data.clone()).ok()
1555 }
1556
1557 pub fn as_status(&self) -> Option<StatusMessage> {
1559 if self.subtype != SystemSubtype::Status {
1560 return None;
1561 }
1562 serde_json::from_value(self.data.clone()).ok()
1563 }
1564
1565 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1567 if self.subtype != SystemSubtype::CompactBoundary {
1568 return None;
1569 }
1570 serde_json::from_value(self.data.clone()).ok()
1571 }
1572
1573 pub fn is_task_started(&self) -> bool {
1575 self.subtype == SystemSubtype::TaskStarted
1576 }
1577
1578 pub fn is_task_progress(&self) -> bool {
1580 self.subtype == SystemSubtype::TaskProgress
1581 }
1582
1583 pub fn is_task_notification(&self) -> bool {
1585 self.subtype == SystemSubtype::TaskNotification
1586 }
1587
1588 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1590 if self.subtype != SystemSubtype::TaskStarted {
1591 return None;
1592 }
1593 serde_json::from_value(self.data.clone()).ok()
1594 }
1595
1596 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1598 if self.subtype != SystemSubtype::TaskProgress {
1599 return None;
1600 }
1601 serde_json::from_value(self.data.clone()).ok()
1602 }
1603
1604 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1606 if self.subtype != SystemSubtype::TaskNotification {
1607 return None;
1608 }
1609 serde_json::from_value(self.data.clone()).ok()
1610 }
1611
1612 pub fn is_task_updated(&self) -> bool {
1614 self.subtype == SystemSubtype::TaskUpdated
1615 }
1616
1617 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1619 if self.subtype != SystemSubtype::TaskUpdated {
1620 return None;
1621 }
1622 serde_json::from_value(self.data.clone()).ok()
1623 }
1624
1625 pub fn is_thinking_tokens(&self) -> bool {
1627 self.subtype == SystemSubtype::ThinkingTokens
1628 }
1629
1630 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1632 if self.subtype != SystemSubtype::ThinkingTokens {
1633 return None;
1634 }
1635 serde_json::from_value(self.data.clone()).ok()
1636 }
1637
1638 pub fn is_code_change_published(&self) -> bool {
1640 self.subtype == SystemSubtype::CodeChangePublished
1641 }
1642
1643 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1645 if self.subtype != SystemSubtype::CodeChangePublished {
1646 return None;
1647 }
1648 serde_json::from_value(self.data.clone()).ok()
1649 }
1650
1651 pub fn is_vcs_state_changed(&self) -> bool {
1653 self.subtype == SystemSubtype::VcsStateChanged
1654 }
1655
1656 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1658 if self.subtype != SystemSubtype::VcsStateChanged {
1659 return None;
1660 }
1661 serde_json::from_value(self.data.clone()).ok()
1662 }
1663
1664 pub fn is_feedback_draft_queued(&self) -> bool {
1666 self.subtype == SystemSubtype::FeedbackDraftQueued
1667 }
1668
1669 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1671 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1672 return None;
1673 }
1674 serde_json::from_value(self.data.clone()).ok()
1675 }
1676
1677 pub fn is_cloud_session_delta(&self) -> bool {
1679 self.subtype == SystemSubtype::CloudSessionDelta
1680 }
1681
1682 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1684 if self.subtype != SystemSubtype::CloudSessionDelta {
1685 return None;
1686 }
1687 serde_json::from_value(self.data.clone()).ok()
1688 }
1689
1690 pub fn is_dev_intent(&self) -> bool {
1692 self.subtype == SystemSubtype::DevIntent
1693 }
1694
1695 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1697 if self.subtype != SystemSubtype::DevIntent {
1698 return None;
1699 }
1700 serde_json::from_value(self.data.clone()).ok()
1701 }
1702
1703 pub fn is_turn_handoff_available(&self) -> bool {
1705 self.subtype == SystemSubtype::TurnHandoffAvailable
1706 }
1707
1708 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1710 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1711 return None;
1712 }
1713 serde_json::from_value(self.data.clone()).ok()
1714 }
1715
1716 pub fn is_turn_preempted(&self) -> bool {
1718 self.subtype == SystemSubtype::TurnPreempted
1719 }
1720
1721 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1723 if self.subtype != SystemSubtype::TurnPreempted {
1724 return None;
1725 }
1726 serde_json::from_value(self.data.clone()).ok()
1727 }
1728
1729 pub fn is_peer_message_hold(&self) -> bool {
1731 self.subtype == SystemSubtype::PeerMessageHold
1732 }
1733
1734 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1736 if self.subtype != SystemSubtype::PeerMessageHold {
1737 return None;
1738 }
1739 serde_json::from_value(self.data.clone()).ok()
1740 }
1741
1742 pub fn is_per_turn_effort_changed(&self) -> bool {
1744 self.subtype == SystemSubtype::PerTurnEffortChanged
1745 }
1746
1747 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1749 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1750 return None;
1751 }
1752 serde_json::from_value(self.data.clone()).ok()
1753 }
1754
1755 pub fn is_session_title_changed(&self) -> bool {
1757 self.subtype == SystemSubtype::SessionTitleChanged
1758 }
1759
1760 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1762 if self.subtype != SystemSubtype::SessionTitleChanged {
1763 return None;
1764 }
1765 serde_json::from_value(self.data.clone()).ok()
1766 }
1767
1768 pub fn is_instruction_size_warning(&self) -> bool {
1770 self.subtype == SystemSubtype::InstructionSizeWarning
1771 }
1772
1773 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1775 if self.subtype != SystemSubtype::InstructionSizeWarning {
1776 return None;
1777 }
1778 serde_json::from_value(self.data.clone()).ok()
1779 }
1780
1781 pub fn is_permission_check_status(&self) -> bool {
1783 self.subtype == SystemSubtype::PermissionCheckStatus
1784 }
1785
1786 pub fn as_permission_check_status(&self) -> Option<PermissionCheckStatusMessage> {
1788 if self.subtype != SystemSubtype::PermissionCheckStatus {
1789 return None;
1790 }
1791 serde_json::from_value(self.data.clone()).ok()
1792 }
1793
1794 pub fn is_file_attachments_missing(&self) -> bool {
1796 self.subtype == SystemSubtype::FileAttachmentsMissing
1797 }
1798
1799 pub fn as_file_attachments_missing(&self) -> Option<FileAttachmentsMissingMessage> {
1801 if self.subtype != SystemSubtype::FileAttachmentsMissing {
1802 return None;
1803 }
1804 serde_json::from_value(self.data.clone()).ok()
1805 }
1806
1807 pub fn is_ui_focus(&self) -> bool {
1809 self.subtype == SystemSubtype::UiFocus
1810 }
1811
1812 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1814 if self.subtype != SystemSubtype::UiFocus {
1815 return None;
1816 }
1817 serde_json::from_value(self.data.clone()).ok()
1818 }
1819
1820 pub fn is_ui_invalidate(&self) -> bool {
1822 self.subtype == SystemSubtype::UiInvalidate
1823 }
1824
1825 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1827 if self.subtype != SystemSubtype::UiInvalidate {
1828 return None;
1829 }
1830 serde_json::from_value(self.data.clone()).ok()
1831 }
1832
1833 pub fn is_ui_log(&self) -> bool {
1835 self.subtype == SystemSubtype::UiLog
1836 }
1837
1838 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1840 if self.subtype != SystemSubtype::UiLog {
1841 return None;
1842 }
1843 serde_json::from_value(self.data.clone()).ok()
1844 }
1845
1846 pub fn is_ui_panes(&self) -> bool {
1848 self.subtype == SystemSubtype::UiPanes
1849 }
1850
1851 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1853 if self.subtype != SystemSubtype::UiPanes {
1854 return None;
1855 }
1856 serde_json::from_value(self.data.clone()).ok()
1857 }
1858
1859 pub fn is_ui_scroll(&self) -> bool {
1861 self.subtype == SystemSubtype::UiScroll
1862 }
1863
1864 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1866 if self.subtype != SystemSubtype::UiScroll {
1867 return None;
1868 }
1869 serde_json::from_value(self.data.clone()).ok()
1870 }
1871
1872 pub fn is_ui_status(&self) -> bool {
1874 self.subtype == SystemSubtype::UiStatus
1875 }
1876
1877 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1879 if self.subtype != SystemSubtype::UiStatus {
1880 return None;
1881 }
1882 serde_json::from_value(self.data.clone()).ok()
1883 }
1884
1885 pub fn is_ui_toast(&self) -> bool {
1887 self.subtype == SystemSubtype::UiToast
1888 }
1889
1890 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
1892 if self.subtype != SystemSubtype::UiToast {
1893 return None;
1894 }
1895 serde_json::from_value(self.data.clone()).ok()
1896 }
1897
1898 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1900 macro_rules! parse {
1901 ($variant:ident, $ty:ty) => {
1902 serde_json::from_value::<$ty>(self.data.clone())
1903 .ok()
1904 .map(KnownSystemEvent::$variant)
1905 };
1906 }
1907
1908 match self.subtype {
1909 SystemSubtype::Init => parse!(Init, InitMessage),
1910 SystemSubtype::Status => parse!(Status, StatusMessage),
1911 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1912 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1913 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1914 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1915 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1916 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1917 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1918 SystemSubtype::ControlRequestProgress => {
1919 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1920 }
1921 SystemSubtype::ModelRefusalFallback => {
1922 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1923 }
1924 SystemSubtype::ModelRefusalNoFallback => {
1925 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1926 }
1927 SystemSubtype::LocalCommandOutput => {
1928 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1929 }
1930 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1931 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1932 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1933 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1934 SystemSubtype::BackgroundTasksChanged => {
1935 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1936 }
1937 SystemSubtype::SessionStateChanged => {
1938 parse!(SessionStateChanged, SessionStateChangedMessage)
1939 }
1940 SystemSubtype::WorkerShuttingDown => {
1941 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1942 }
1943 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1944 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1945 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1946 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1947 SystemSubtype::ElicitationComplete => {
1948 parse!(ElicitationComplete, ElicitationCompleteMessage)
1949 }
1950 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1951 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1952 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1953 SystemSubtype::CodeChangePublished => {
1954 parse!(CodeChangePublished, CodeChangePublishedMessage)
1955 }
1956 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1957 SystemSubtype::FeedbackDraftQueued => {
1958 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1959 }
1960 SystemSubtype::CloudSessionDelta => {
1961 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1962 }
1963 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1964 SystemSubtype::TurnHandoffAvailable => {
1965 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1966 }
1967 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1968 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1969 SystemSubtype::PerTurnEffortChanged => {
1970 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1971 }
1972 SystemSubtype::SessionTitleChanged => {
1973 parse!(SessionTitleChanged, SessionTitleChangedMessage)
1974 }
1975 SystemSubtype::InstructionSizeWarning => {
1976 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
1977 }
1978 SystemSubtype::FileAttachmentsMissing => {
1979 parse!(FileAttachmentsMissing, FileAttachmentsMissingMessage)
1980 }
1981 SystemSubtype::PermissionCheckStatus => {
1982 parse!(PermissionCheckStatus, PermissionCheckStatusMessage)
1983 }
1984 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
1985 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
1986 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
1987 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
1988 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
1989 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
1990 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
1991 SystemSubtype::Unknown(_) => None,
1992 }
1993 }
1994
1995 pub fn typed_value(&self) -> Option<Value> {
2004 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
2005 parsed.and_then(|v| serde_json::to_value(v).ok())
2006 }
2007 match self.subtype {
2008 SystemSubtype::Init => reserialize(self.as_init()),
2009 SystemSubtype::Status => reserialize(self.as_status()),
2010 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
2011 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
2012 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
2013 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
2014 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
2015 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
2016 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
2017 SystemSubtype::ControlRequestProgress => {
2018 reserialize(parse_system::<ControlRequestProgressMessage>(self))
2019 }
2020 SystemSubtype::ModelRefusalFallback => {
2021 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
2022 }
2023 SystemSubtype::ModelRefusalNoFallback => {
2024 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
2025 }
2026 SystemSubtype::LocalCommandOutput => {
2027 reserialize(parse_system::<LocalCommandOutputMessage>(self))
2028 }
2029 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
2030 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
2031 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
2032 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
2033 SystemSubtype::BackgroundTasksChanged => {
2034 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
2035 }
2036 SystemSubtype::SessionStateChanged => {
2037 reserialize(parse_system::<SessionStateChangedMessage>(self))
2038 }
2039 SystemSubtype::WorkerShuttingDown => {
2040 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
2041 }
2042 SystemSubtype::CommandsChanged => {
2043 reserialize(parse_system::<CommandsChangedMessage>(self))
2044 }
2045 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
2046 SystemSubtype::FilesPersisted => {
2047 reserialize(parse_system::<FilesPersistedMessage>(self))
2048 }
2049 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
2050 SystemSubtype::ElicitationComplete => {
2051 reserialize(parse_system::<ElicitationCompleteMessage>(self))
2052 }
2053 SystemSubtype::PermissionDenied => {
2054 reserialize(parse_system::<PermissionDeniedMessage>(self))
2055 }
2056 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
2057 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
2058 SystemSubtype::CodeChangePublished => {
2059 reserialize(parse_system::<CodeChangePublishedMessage>(self))
2060 }
2061 SystemSubtype::VcsStateChanged => {
2062 reserialize(parse_system::<VcsStateChangedMessage>(self))
2063 }
2064 SystemSubtype::FeedbackDraftQueued => {
2065 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
2066 }
2067 SystemSubtype::CloudSessionDelta => {
2068 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
2069 }
2070 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
2071 SystemSubtype::TurnHandoffAvailable => {
2072 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
2073 }
2074 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
2075 SystemSubtype::PeerMessageHold => {
2076 reserialize(parse_system::<PeerMessageHoldMessage>(self))
2077 }
2078 SystemSubtype::PerTurnEffortChanged => {
2079 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
2080 }
2081 SystemSubtype::SessionTitleChanged => {
2082 reserialize(parse_system::<SessionTitleChangedMessage>(self))
2083 }
2084 SystemSubtype::InstructionSizeWarning => {
2085 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
2086 }
2087 SystemSubtype::FileAttachmentsMissing => {
2088 reserialize(parse_system::<FileAttachmentsMissingMessage>(self))
2089 }
2090 SystemSubtype::PermissionCheckStatus => {
2091 reserialize(parse_system::<PermissionCheckStatusMessage>(self))
2092 }
2093 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
2094 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
2095 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
2096 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
2097 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
2098 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
2099 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
2100 SystemSubtype::Unknown(_) => None,
2101 }
2102 }
2103}
2104
2105fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
2106 serde_json::from_value(message.data.clone()).ok()
2107}
2108
2109#[allow(clippy::large_enum_variant)]
2115#[derive(Debug, Clone, Serialize, Deserialize)]
2116pub enum KnownSystemEvent {
2117 Init(InitMessage),
2118 Status(StatusMessage),
2119 CompactBoundary(CompactBoundaryMessage),
2120 ThinkingTokens(ThinkingTokensMessage),
2121 TaskStarted(TaskStartedMessage),
2122 TaskProgress(TaskProgressMessage),
2123 TaskUpdated(TaskUpdatedMessage),
2124 TaskNotification(TaskNotificationMessage),
2125 ApiRetry(ApiRetryMessage),
2126 ControlRequestProgress(ControlRequestProgressMessage),
2127 ModelRefusalFallback(ModelRefusalFallbackMessage),
2128 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
2129 LocalCommandOutput(LocalCommandOutputMessage),
2130 HookStarted(HookStartedMessage),
2131 HookProgress(HookProgressMessage),
2132 HookResponse(HookResponseMessage),
2133 PluginInstall(PluginInstallMessage),
2134 BackgroundTasksChanged(BackgroundTasksChangedMessage),
2135 SessionStateChanged(SessionStateChangedMessage),
2136 WorkerShuttingDown(WorkerShuttingDownMessage),
2137 CommandsChanged(CommandsChangedMessage),
2138 Notification(NotificationMessage),
2139 FilesPersisted(FilesPersistedMessage),
2140 MemoryRecall(MemoryRecallMessage),
2141 ElicitationComplete(ElicitationCompleteMessage),
2142 PermissionDenied(PermissionDeniedMessage),
2143 MirrorError(MirrorErrorMessage),
2144 Informational(InformationalMessage),
2145 CodeChangePublished(CodeChangePublishedMessage),
2146 VcsStateChanged(VcsStateChangedMessage),
2147 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
2148 CloudSessionDelta(CloudSessionDeltaMessage),
2149 DevIntent(DevIntentMessage),
2150 TurnHandoffAvailable(TurnHandoffAvailableMessage),
2151 TurnPreempted(TurnPreemptedMessage),
2152 PeerMessageHold(PeerMessageHoldMessage),
2153 PerTurnEffortChanged(PerTurnEffortChangedMessage),
2154 SessionTitleChanged(SessionTitleChangedMessage),
2155 InstructionSizeWarning(InstructionSizeWarningMessage),
2156 FileAttachmentsMissing(FileAttachmentsMissingMessage),
2157 PermissionCheckStatus(PermissionCheckStatusMessage),
2158 UiFocus(UiFocusMessage),
2159 UiInvalidate(UiInvalidateMessage),
2160 UiLog(UiLogMessage),
2161 UiPanes(UiPanesMessage),
2162 UiScroll(UiScrollMessage),
2163 UiStatus(UiStatusMessage),
2164 UiToast(UiToastMessage),
2165}
2166
2167#[derive(Debug, Clone, Serialize, Deserialize)]
2168pub struct ApiRetryMessage {
2169 pub attempt: u64,
2170 pub max_retries: u64,
2171 pub retry_delay_ms: u64,
2172 pub error_status: Option<u16>,
2173 pub error: String,
2174 #[serde(default, skip_serializing_if = "Option::is_none")]
2179 pub no_response: Option<ApiRetryNoResponse>,
2180 #[serde(default, skip_serializing_if = "Option::is_none")]
2181 pub uuid: Option<String>,
2182 #[serde(default, skip_serializing_if = "Option::is_none")]
2183 pub session_id: Option<String>,
2184}
2185
2186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2189pub struct ApiRetryNoResponse {
2190 pub waited_ms: u64,
2192 pub retry_wait_ms: u64,
2194}
2195
2196#[derive(Debug, Clone, Serialize, Deserialize)]
2197pub struct ControlRequestProgressMessage {
2198 pub request_id: String,
2199 pub status: String,
2200 #[serde(default, skip_serializing_if = "Option::is_none")]
2201 pub attempt: Option<u64>,
2202 #[serde(default, skip_serializing_if = "Option::is_none")]
2203 pub max_retries: Option<u64>,
2204 #[serde(default, skip_serializing_if = "Option::is_none")]
2205 pub retry_delay_ms: Option<u64>,
2206 #[serde(default, skip_serializing_if = "Option::is_none")]
2207 pub error_status: Option<u16>,
2208 #[serde(default, skip_serializing_if = "Option::is_none")]
2209 pub error: Option<String>,
2210 #[serde(default, skip_serializing_if = "Option::is_none")]
2211 pub uuid: Option<String>,
2212 #[serde(default, skip_serializing_if = "Option::is_none")]
2213 pub session_id: Option<String>,
2214}
2215
2216#[derive(Debug, Clone, Serialize, Deserialize)]
2217pub struct ModelRefusalFallbackMessage {
2218 pub trigger: String,
2219 pub direction: String,
2220 #[serde(default, skip_serializing_if = "Option::is_none")]
2226 pub scope: Option<RefusalFallbackScope>,
2227 pub original_model: String,
2228 pub fallback_model: String,
2229 pub request_id: Option<String>,
2230 #[serde(default, skip_serializing_if = "Option::is_none")]
2231 pub api_refusal_category: Option<String>,
2232 #[serde(default, skip_serializing_if = "Option::is_none")]
2237 pub saw_cyber_refusal: Option<bool>,
2238 #[serde(default, skip_serializing_if = "Option::is_none")]
2239 pub api_refusal_explanation: Option<String>,
2240 #[serde(default, skip_serializing_if = "Option::is_none")]
2241 pub retracted_message_uuids: Option<Vec<String>>,
2242 #[serde(default, skip_serializing_if = "Option::is_none")]
2243 pub refused_user_message_uuid: Option<String>,
2244 pub content: Value,
2245 #[serde(default, skip_serializing_if = "Option::is_none")]
2246 pub uuid: Option<String>,
2247 #[serde(default, skip_serializing_if = "Option::is_none")]
2248 pub session_id: Option<String>,
2249}
2250
2251#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2255pub enum RefusalFallbackScope {
2256 Session,
2258 Local,
2261 Unknown(String),
2263}
2264
2265impl RefusalFallbackScope {
2266 pub fn as_str(&self) -> &str {
2267 match self {
2268 Self::Session => "session",
2269 Self::Local => "local",
2270 Self::Unknown(s) => s.as_str(),
2271 }
2272 }
2273}
2274
2275impl fmt::Display for RefusalFallbackScope {
2276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2277 f.write_str(self.as_str())
2278 }
2279}
2280
2281impl From<&str> for RefusalFallbackScope {
2282 fn from(s: &str) -> Self {
2283 match s {
2284 "session" => Self::Session,
2285 "local" => Self::Local,
2286 other => Self::Unknown(other.to_string()),
2287 }
2288 }
2289}
2290
2291impl Serialize for RefusalFallbackScope {
2292 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2293 serializer.serialize_str(self.as_str())
2294 }
2295}
2296
2297impl<'de> Deserialize<'de> for RefusalFallbackScope {
2298 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2299 let s = String::deserialize(deserializer)?;
2300 Ok(Self::from(s.as_str()))
2301 }
2302}
2303
2304#[derive(Debug, Clone, Serialize, Deserialize)]
2305pub struct ModelRefusalNoFallbackMessage {
2306 pub original_model: String,
2307 pub request_id: Option<String>,
2308 #[serde(default, skip_serializing_if = "Option::is_none")]
2309 pub api_refusal_category: Option<String>,
2310 #[serde(default, skip_serializing_if = "Option::is_none")]
2311 pub api_refusal_explanation: Option<String>,
2312 pub content: Value,
2313 #[serde(default, skip_serializing_if = "Option::is_none")]
2314 pub uuid: Option<String>,
2315 #[serde(default, skip_serializing_if = "Option::is_none")]
2316 pub session_id: Option<String>,
2317}
2318
2319#[derive(Debug, Clone, Serialize, Deserialize)]
2320pub struct LocalCommandOutputMessage {
2321 pub content: String,
2322 #[serde(default, skip_serializing_if = "Option::is_none")]
2323 pub uuid: Option<String>,
2324 #[serde(default, skip_serializing_if = "Option::is_none")]
2325 pub session_id: Option<String>,
2326}
2327
2328#[derive(Debug, Clone, Serialize, Deserialize)]
2329pub struct HookStartedMessage {
2330 pub hook_id: String,
2331 pub hook_name: String,
2332 pub hook_event: String,
2333 #[serde(default, skip_serializing_if = "Option::is_none")]
2334 pub uuid: Option<String>,
2335 #[serde(default, skip_serializing_if = "Option::is_none")]
2336 pub session_id: Option<String>,
2337}
2338
2339#[derive(Debug, Clone, Serialize, Deserialize)]
2340pub struct HookProgressMessage {
2341 pub hook_id: String,
2342 pub hook_name: String,
2343 pub hook_event: String,
2344 #[serde(default, skip_serializing_if = "Option::is_none")]
2345 pub stdout: Option<String>,
2346 #[serde(default, skip_serializing_if = "Option::is_none")]
2347 pub stderr: Option<String>,
2348 #[serde(default, skip_serializing_if = "Option::is_none")]
2349 pub output: Option<String>,
2350 #[serde(default, skip_serializing_if = "Option::is_none")]
2351 pub uuid: Option<String>,
2352 #[serde(default, skip_serializing_if = "Option::is_none")]
2353 pub session_id: Option<String>,
2354}
2355
2356#[derive(Debug, Clone, Serialize, Deserialize)]
2357pub struct HookResponseMessage {
2358 pub hook_id: String,
2359 pub hook_name: String,
2360 pub hook_event: String,
2361 #[serde(default, skip_serializing_if = "Option::is_none")]
2362 pub stdout: Option<String>,
2363 #[serde(default, skip_serializing_if = "Option::is_none")]
2364 pub stderr: Option<String>,
2365 #[serde(default, skip_serializing_if = "Option::is_none")]
2366 pub output: Option<String>,
2367 #[serde(default, skip_serializing_if = "Option::is_none")]
2368 pub exit_code: Option<i32>,
2369 pub outcome: String,
2370 #[serde(default, skip_serializing_if = "Option::is_none")]
2371 pub uuid: Option<String>,
2372 #[serde(default, skip_serializing_if = "Option::is_none")]
2373 pub session_id: Option<String>,
2374}
2375
2376#[derive(Debug, Clone, Serialize, Deserialize)]
2377pub struct PluginInstallMessage {
2378 pub status: String,
2379 #[serde(default, skip_serializing_if = "Option::is_none")]
2380 pub name: Option<String>,
2381 #[serde(default, skip_serializing_if = "Option::is_none")]
2382 pub error: Option<String>,
2383 #[serde(default, skip_serializing_if = "Option::is_none")]
2384 pub uuid: Option<String>,
2385 #[serde(default, skip_serializing_if = "Option::is_none")]
2386 pub session_id: Option<String>,
2387}
2388
2389#[derive(Debug, Clone, Serialize, Deserialize)]
2390pub struct BackgroundTasksChangedMessage {
2391 pub tasks: Vec<BackgroundTaskInfo>,
2392 #[serde(default, skip_serializing_if = "Option::is_none")]
2393 pub uuid: Option<String>,
2394 #[serde(default, skip_serializing_if = "Option::is_none")]
2395 pub session_id: Option<String>,
2396}
2397
2398#[derive(Debug, Clone, Serialize, Deserialize)]
2399pub struct BackgroundTaskInfo {
2400 pub task_id: String,
2401 #[serde(default, skip_serializing_if = "Option::is_none")]
2404 pub run_id: Option<String>,
2405 pub task_type: String,
2406 #[serde(default, skip_serializing_if = "Option::is_none")]
2410 pub subagent_type: Option<String>,
2411 pub description: String,
2412 #[serde(default, skip_serializing_if = "Option::is_none")]
2415 pub parent_task_id: Option<String>,
2416 #[serde(default, skip_serializing_if = "Option::is_none")]
2419 pub ambient: Option<bool>,
2420 #[serde(default, skip_serializing_if = "Option::is_none")]
2423 pub awaited: Option<bool>,
2424}
2425
2426#[derive(Debug, Clone, Serialize, Deserialize)]
2427pub struct SessionStateChangedMessage {
2428 pub state: String,
2429 #[serde(default, skip_serializing_if = "Option::is_none")]
2430 pub uuid: Option<String>,
2431 #[serde(default, skip_serializing_if = "Option::is_none")]
2432 pub session_id: Option<String>,
2433}
2434
2435#[derive(Debug, Clone, Serialize, Deserialize)]
2436pub struct WorkerShuttingDownMessage {
2437 pub reason: String,
2438 #[serde(default, skip_serializing_if = "Option::is_none")]
2439 pub uuid: Option<String>,
2440 #[serde(default, skip_serializing_if = "Option::is_none")]
2441 pub session_id: Option<String>,
2442}
2443
2444#[derive(Debug, Clone, Serialize, Deserialize)]
2445pub struct CommandsChangedMessage {
2446 pub commands: Vec<CommandInfo>,
2447 #[serde(default, skip_serializing_if = "Option::is_none")]
2448 pub uuid: Option<String>,
2449 #[serde(default, skip_serializing_if = "Option::is_none")]
2450 pub session_id: Option<String>,
2451}
2452
2453#[derive(Debug, Clone, Serialize, Deserialize)]
2454pub struct CommandInfo {
2455 pub name: String,
2456 pub description: String,
2457 #[serde(rename = "argumentHint")]
2458 pub argument_hint: String,
2459 #[serde(default, skip_serializing_if = "Option::is_none")]
2460 pub aliases: Option<Vec<String>>,
2461 #[serde(default, skip_serializing_if = "Option::is_none")]
2467 pub builtin: Option<bool>,
2468}
2469
2470#[derive(Debug, Clone, Serialize, Deserialize)]
2471pub struct NotificationMessage {
2472 pub key: String,
2473 pub text: String,
2474 pub priority: String,
2475 #[serde(default, skip_serializing_if = "Option::is_none")]
2476 pub color: Option<String>,
2477 #[serde(default, skip_serializing_if = "Option::is_none")]
2478 pub timeout_ms: Option<u64>,
2479 #[serde(default, skip_serializing_if = "Option::is_none")]
2480 pub uuid: Option<String>,
2481 #[serde(default, skip_serializing_if = "Option::is_none")]
2482 pub session_id: Option<String>,
2483}
2484
2485#[derive(Debug, Clone, Serialize, Deserialize)]
2486pub struct FilesPersistedMessage {
2487 pub files: Vec<PersistedFile>,
2488 pub failed: Vec<FailedPersistedFile>,
2489 pub processed_at: String,
2490 #[serde(default, skip_serializing_if = "Option::is_none")]
2491 pub uuid: Option<String>,
2492 #[serde(default, skip_serializing_if = "Option::is_none")]
2493 pub session_id: Option<String>,
2494}
2495
2496#[derive(Debug, Clone, Serialize, Deserialize)]
2497pub struct PersistedFile {
2498 pub filename: String,
2499 pub file_id: String,
2500}
2501
2502#[derive(Debug, Clone, Serialize, Deserialize)]
2503pub struct FailedPersistedFile {
2504 pub filename: String,
2505 pub error: String,
2506}
2507
2508#[derive(Debug, Clone, Serialize, Deserialize)]
2509pub struct MemoryRecallMessage {
2510 pub mode: String,
2511 pub memories: Vec<MemoryRecallItem>,
2512 #[serde(default, skip_serializing_if = "Option::is_none")]
2513 pub uuid: Option<String>,
2514 #[serde(default, skip_serializing_if = "Option::is_none")]
2515 pub session_id: Option<String>,
2516}
2517
2518#[derive(Debug, Clone, Serialize, Deserialize)]
2519pub struct MemoryRecallItem {
2520 pub path: String,
2521 pub scope: String,
2522 #[serde(default, skip_serializing_if = "Option::is_none")]
2523 pub content: Option<String>,
2524}
2525
2526#[derive(Debug, Clone, Serialize, Deserialize)]
2527pub struct ElicitationCompleteMessage {
2528 pub mcp_server_name: String,
2529 pub elicitation_id: String,
2530 #[serde(default, skip_serializing_if = "Option::is_none")]
2531 pub uuid: Option<String>,
2532 #[serde(default, skip_serializing_if = "Option::is_none")]
2533 pub session_id: Option<String>,
2534}
2535
2536#[derive(Debug, Clone, Serialize, Deserialize)]
2537pub struct PermissionDeniedMessage {
2538 pub tool_name: String,
2539 pub tool_use_id: String,
2540 #[serde(default, skip_serializing_if = "Option::is_none")]
2541 pub agent_id: Option<String>,
2542 #[serde(default, skip_serializing_if = "Option::is_none")]
2543 pub decision_reason_type: Option<String>,
2544 #[serde(default, skip_serializing_if = "Option::is_none")]
2548 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2549 #[serde(default, skip_serializing_if = "Option::is_none")]
2550 pub decision_reason: Option<String>,
2551 pub message: String,
2552 #[serde(default, skip_serializing_if = "Option::is_none")]
2553 pub uuid: Option<String>,
2554 #[serde(default, skip_serializing_if = "Option::is_none")]
2555 pub session_id: Option<String>,
2556}
2557
2558#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2561pub enum PermissionDeniedReasonCode {
2562 ClassifierTranscriptTooLong,
2564 OutsideReadsBlocked,
2566 MemoryPaused,
2568 Unknown(String),
2570}
2571
2572impl PermissionDeniedReasonCode {
2573 pub fn as_str(&self) -> &str {
2574 match self {
2575 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2576 Self::OutsideReadsBlocked => "outside_reads_blocked",
2577 Self::MemoryPaused => "memory_paused",
2578 Self::Unknown(s) => s.as_str(),
2579 }
2580 }
2581}
2582
2583impl fmt::Display for PermissionDeniedReasonCode {
2584 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2585 f.write_str(self.as_str())
2586 }
2587}
2588
2589impl From<&str> for PermissionDeniedReasonCode {
2590 fn from(s: &str) -> Self {
2591 match s {
2592 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2593 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2594 "memory_paused" => Self::MemoryPaused,
2595 other => Self::Unknown(other.to_string()),
2596 }
2597 }
2598}
2599
2600impl Serialize for PermissionDeniedReasonCode {
2601 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2602 serializer.serialize_str(self.as_str())
2603 }
2604}
2605
2606impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2607 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2608 let s = String::deserialize(deserializer)?;
2609 Ok(Self::from(s.as_str()))
2610 }
2611}
2612
2613#[derive(Debug, Clone, Serialize, Deserialize)]
2614pub struct MirrorErrorMessage {
2615 pub error: String,
2616 pub key: MirrorErrorKey,
2617 #[serde(default, skip_serializing_if = "Option::is_none")]
2618 pub uuid: Option<String>,
2619 #[serde(default, skip_serializing_if = "Option::is_none")]
2620 pub session_id: Option<String>,
2621}
2622
2623#[derive(Debug, Clone, Serialize, Deserialize)]
2624pub struct MirrorErrorKey {
2625 #[serde(rename = "projectKey")]
2626 pub project_key: String,
2627 #[serde(rename = "sessionId")]
2628 pub session_id: String,
2629 #[serde(default, skip_serializing_if = "Option::is_none")]
2630 pub subpath: Option<String>,
2631}
2632
2633#[derive(Debug, Clone, Serialize, Deserialize)]
2634pub struct InformationalMessage {
2635 pub content: String,
2636 pub level: String,
2637 #[serde(default, skip_serializing_if = "Option::is_none")]
2638 pub tool_use_id: Option<String>,
2639 #[serde(default, skip_serializing_if = "Option::is_none")]
2640 pub prevent_continuation: Option<bool>,
2641 #[serde(default, skip_serializing_if = "Option::is_none")]
2644 pub tag: Option<String>,
2645 #[serde(default, skip_serializing_if = "Option::is_none")]
2651 pub prompt_submit_outcome: Option<PromptSubmitOutcome>,
2652 #[serde(default, skip_serializing_if = "Option::is_none")]
2653 pub uuid: Option<String>,
2654 #[serde(default, skip_serializing_if = "Option::is_none")]
2655 pub session_id: Option<String>,
2656}
2657
2658#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2661pub struct PromptSubmitOutcome {
2662 #[serde(default, skip_serializing_if = "Option::is_none")]
2666 pub kind: Option<PromptSubmitOutcomeKind>,
2667 #[serde(default, skip_serializing_if = "Option::is_none")]
2674 pub no_verdict_kind: Option<String>,
2675 pub prompt_kept: bool,
2678 #[serde(default, skip_serializing_if = "Option::is_none")]
2682 pub reason: Option<String>,
2683}
2684
2685#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2687pub enum PromptSubmitOutcomeKind {
2688 BlockedByHook,
2690 StoppedByHook,
2692 DroppedByHook,
2694 NotPassedOn,
2696 NoVerdict,
2699 Other(String),
2701}
2702
2703impl PromptSubmitOutcomeKind {
2704 pub fn as_str(&self) -> &str {
2705 match self {
2706 Self::BlockedByHook => "blocked_by_hook",
2707 Self::StoppedByHook => "stopped_by_hook",
2708 Self::DroppedByHook => "dropped_by_hook",
2709 Self::NotPassedOn => "not_passed_on",
2710 Self::NoVerdict => "no_verdict",
2711 Self::Other(s) => s.as_str(),
2712 }
2713 }
2714}
2715
2716impl fmt::Display for PromptSubmitOutcomeKind {
2717 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2718 f.write_str(self.as_str())
2719 }
2720}
2721
2722impl From<&str> for PromptSubmitOutcomeKind {
2723 fn from(s: &str) -> Self {
2724 match s {
2725 "blocked_by_hook" => Self::BlockedByHook,
2726 "stopped_by_hook" => Self::StoppedByHook,
2727 "dropped_by_hook" => Self::DroppedByHook,
2728 "not_passed_on" => Self::NotPassedOn,
2729 "no_verdict" => Self::NoVerdict,
2730 other => Self::Other(other.to_string()),
2731 }
2732 }
2733}
2734
2735impl Serialize for PromptSubmitOutcomeKind {
2736 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2737 serializer.serialize_str(self.as_str())
2738 }
2739}
2740
2741impl<'de> Deserialize<'de> for PromptSubmitOutcomeKind {
2742 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2743 let s = String::deserialize(deserializer)?;
2744 Ok(Self::from(s.as_str()))
2745 }
2746}
2747
2748#[derive(Debug, Clone, Serialize, Deserialize)]
2750pub struct PluginInfo {
2751 pub name: String,
2753 pub path: String,
2755 #[serde(skip_serializing_if = "Option::is_none")]
2757 pub source: Option<String>,
2758 #[serde(default, skip_serializing_if = "Option::is_none")]
2760 pub version: Option<String>,
2761}
2762
2763#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2765pub struct PluginDiagnostic {
2766 pub plugin: String,
2767 #[serde(rename = "type")]
2768 pub diagnostic_type: String,
2769 pub message: String,
2770}
2771
2772#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2774pub struct MemoryPaths {
2775 #[serde(default, skip_serializing_if = "Option::is_none")]
2776 pub auto: Option<String>,
2777 #[serde(default, skip_serializing_if = "Option::is_none")]
2778 pub team: Option<String>,
2779 #[serde(flatten)]
2780 pub extra: serde_json::Map<String, Value>,
2781}
2782
2783#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2787pub struct McpServerError {
2788 pub name: String,
2789 #[serde(rename = "type")]
2791 pub error_type: String,
2792 pub message: String,
2793}
2794
2795#[derive(Debug, Clone, Serialize, Deserialize)]
2797pub struct InitMessage {
2798 pub session_id: String,
2800 #[serde(skip_serializing_if = "Option::is_none")]
2802 pub cwd: Option<String>,
2803 #[serde(skip_serializing_if = "Option::is_none")]
2805 pub model: Option<String>,
2806 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2808 pub tools: Vec<String>,
2809 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2811 pub mcp_servers: Vec<Value>,
2812 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2814 pub slash_commands: Vec<String>,
2815 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2818 pub terminal_slash_commands: Vec<String>,
2819 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2821 pub agents: Vec<String>,
2822 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2824 pub plugins: Vec<PluginInfo>,
2825 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2827 pub skills: Vec<Value>,
2828 #[serde(skip_serializing_if = "Option::is_none")]
2830 pub claude_code_version: Option<String>,
2831 #[serde(skip_serializing_if = "Option::is_none")]
2834 pub messaging_socket_path: Option<String>,
2835 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2837 pub api_key_source: Option<ApiKeySource>,
2838 #[serde(skip_serializing_if = "Option::is_none")]
2840 pub output_style: Option<OutputStyle>,
2841 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2843 pub permission_mode: Option<InitPermissionMode>,
2844
2845 #[serde(skip_serializing_if = "Option::is_none")]
2847 pub uuid: Option<String>,
2848
2849 #[serde(skip_serializing_if = "Option::is_none")]
2851 pub memory_paths: Option<MemoryPaths>,
2852
2853 #[serde(default, skip_serializing_if = "Option::is_none")]
2858 pub scratchpad_path: Option<String>,
2859
2860 #[serde(skip_serializing_if = "Option::is_none")]
2862 pub fast_mode_state: Option<String>,
2863
2864 #[serde(default, skip_serializing_if = "Option::is_none")]
2866 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2867
2868 #[serde(default, skip_serializing_if = "Option::is_none")]
2871 pub mcp_server_errors: Option<Vec<McpServerError>>,
2872
2873 #[serde(default, skip_serializing_if = "Option::is_none")]
2875 pub analytics_disabled: Option<bool>,
2876
2877 #[serde(default, skip_serializing_if = "Option::is_none")]
2879 pub product_feedback_disabled: Option<bool>,
2880
2881 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2883 pub betas: Vec<String>,
2884
2885 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2887 pub capabilities: Vec<String>,
2888
2889 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2891 pub plugin_errors: Vec<PluginDiagnostic>,
2892
2893 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2895 pub plugin_warnings: Vec<PluginDiagnostic>,
2896
2897 #[serde(default, skip_serializing_if = "Option::is_none")]
2902 pub effort: Option<String>,
2903
2904 #[serde(default, skip_serializing_if = "Option::is_none")]
2909 pub cloud_session: Option<Value>,
2910
2911 #[serde(default, skip_serializing_if = "Option::is_none")]
2915 pub footer_indicator: Option<FooterIndicator>,
2916
2917 #[serde(default, skip_serializing_if = "Option::is_none")]
2921 pub worker_epoch: Option<u64>,
2922
2923 #[serde(default, skip_serializing_if = "Option::is_none")]
2927 pub powershell_path: Option<Option<String>>,
2928
2929 #[serde(default, skip_serializing_if = "Option::is_none")]
2933 pub startup_timing: Option<serde_json::Map<String, Value>>,
2934
2935 #[serde(default, skip_serializing_if = "Option::is_none")]
2945 pub per_turn_effort_active: Option<bool>,
2946
2947 #[serde(default, skip_serializing_if = "Option::is_none")]
2954 pub view_mode: Option<ViewMode>,
2955
2956 #[serde(default, skip_serializing_if = "Option::is_none")]
2963 pub original_cwd: Option<String>,
2964
2965 #[serde(default, skip_serializing_if = "Option::is_none")]
2974 pub additional_directories: Option<Vec<String>>,
2975}
2976
2977#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2981pub struct FooterIndicator {
2982 pub text: String,
2985}
2986
2987#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2990pub enum ViewMode {
2991 Focus,
2994 Default,
2996 Unknown(String),
2998}
2999
3000impl ViewMode {
3001 pub fn as_str(&self) -> &str {
3002 match self {
3003 Self::Focus => "focus",
3004 Self::Default => "default",
3005 Self::Unknown(s) => s.as_str(),
3006 }
3007 }
3008}
3009
3010impl fmt::Display for ViewMode {
3011 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3012 f.write_str(self.as_str())
3013 }
3014}
3015
3016impl From<&str> for ViewMode {
3017 fn from(s: &str) -> Self {
3018 match s {
3019 "focus" => Self::Focus,
3020 "default" => Self::Default,
3021 other => Self::Unknown(other.to_string()),
3022 }
3023 }
3024}
3025
3026impl Serialize for ViewMode {
3027 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3028 serializer.serialize_str(self.as_str())
3029 }
3030}
3031
3032impl<'de> Deserialize<'de> for ViewMode {
3033 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3034 let s = String::deserialize(deserializer)?;
3035 Ok(Self::from(s.as_str()))
3036 }
3037}
3038
3039#[derive(Debug, Clone, Serialize, Deserialize)]
3041pub struct StatusMessage {
3042 pub session_id: String,
3044 pub status: Option<StatusMessageStatus>,
3046 #[serde(skip_serializing_if = "Option::is_none")]
3048 pub uuid: Option<String>,
3049 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3051 pub permission_mode: Option<InitPermissionMode>,
3052 #[serde(skip_serializing_if = "Option::is_none")]
3053 pub compact_result: Option<String>,
3054 #[serde(skip_serializing_if = "Option::is_none")]
3055 pub compact_error: Option<String>,
3056 #[serde(default, skip_serializing_if = "Option::is_none")]
3063 pub compact_output_chars: Option<u64>,
3064}
3065
3066#[derive(Debug, Clone, Serialize, Deserialize)]
3068pub struct CompactBoundaryMessage {
3069 pub session_id: String,
3071 pub compact_metadata: CompactMetadata,
3073 #[serde(
3077 default,
3078 skip_serializing_if = "Option::is_none",
3079 alias = "content",
3080 alias = "text"
3081 )]
3082 pub summary: Option<String>,
3083 #[serde(
3087 default,
3088 skip_serializing_if = "Option::is_none",
3089 alias = "message_count"
3090 )]
3091 pub leaf_message_count: Option<u32>,
3092 #[serde(default, skip_serializing_if = "Option::is_none")]
3094 pub duration_ms: Option<u64>,
3095 #[serde(skip_serializing_if = "Option::is_none")]
3097 pub uuid: Option<String>,
3098 #[serde(skip_serializing_if = "Option::is_none")]
3100 pub logical_parent_uuid: Option<Option<String>>,
3101 #[serde(default, skip_serializing_if = "Option::is_none")]
3105 pub historical: Option<bool>,
3106}
3107
3108#[derive(Debug, Clone, Serialize, Deserialize)]
3110pub struct CompactMetadata {
3111 pub pre_tokens: u64,
3113 pub trigger: CompactionTrigger,
3115 #[serde(default, skip_serializing_if = "Option::is_none")]
3116 pub post_tokens: Option<u64>,
3117 #[serde(default, skip_serializing_if = "Option::is_none")]
3118 pub cumulative_dropped_tokens: Option<u64>,
3119 #[serde(default, skip_serializing_if = "Option::is_none")]
3120 pub duration_ms: Option<u64>,
3121 #[serde(default, skip_serializing_if = "Option::is_none")]
3122 pub user_context: Option<String>,
3123 #[serde(default, skip_serializing_if = "Option::is_none")]
3124 pub messages_summarized: Option<u64>,
3125 #[serde(default, skip_serializing_if = "Option::is_none")]
3126 pub precomputed: Option<bool>,
3127 #[serde(default, skip_serializing_if = "Option::is_none")]
3128 pub pre_compact_discovered_tools: Option<Vec<String>>,
3129 #[serde(default, skip_serializing_if = "Option::is_none")]
3134 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
3135 #[serde(default, skip_serializing_if = "Option::is_none")]
3136 pub preserved_segment: Option<PreservedSegment>,
3137 #[serde(default, skip_serializing_if = "Option::is_none")]
3138 pub preserved_messages: Option<PreservedMessages>,
3139}
3140
3141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3144pub struct ArtifactReadVersion {
3145 pub slug: String,
3147 pub ver: String,
3149}
3150
3151#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3152pub struct PreservedSegment {
3153 pub head_uuid: String,
3154 pub anchor_uuid: String,
3155 pub tail_uuid: String,
3156}
3157
3158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3159pub struct PreservedMessages {
3160 pub anchor_uuid: String,
3161 pub uuids: Vec<String>,
3162 #[serde(default, skip_serializing_if = "Option::is_none")]
3163 pub all_uuids: Option<Vec<String>>,
3164}
3165
3166#[derive(Debug, Clone, Serialize, Deserialize)]
3172pub struct TaskUsage {
3173 pub duration_ms: u64,
3175 pub tool_uses: u64,
3177 pub total_tokens: u64,
3179}
3180
3181#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3183pub enum TaskType {
3184 LocalAgent,
3186 LocalBash,
3188 LocalWorkflow,
3190 Unknown(String),
3192}
3193
3194impl TaskType {
3195 pub fn as_str(&self) -> &str {
3196 match self {
3197 Self::LocalAgent => "local_agent",
3198 Self::LocalBash => "local_bash",
3199 Self::LocalWorkflow => "local_workflow",
3200 Self::Unknown(s) => s.as_str(),
3201 }
3202 }
3203}
3204
3205impl fmt::Display for TaskType {
3206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3207 f.write_str(self.as_str())
3208 }
3209}
3210
3211impl From<&str> for TaskType {
3212 fn from(s: &str) -> Self {
3213 match s {
3214 "local_agent" => Self::LocalAgent,
3215 "local_bash" => Self::LocalBash,
3216 "local_workflow" => Self::LocalWorkflow,
3217 other => Self::Unknown(other.to_string()),
3218 }
3219 }
3220}
3221
3222impl Serialize for TaskType {
3223 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3224 serializer.serialize_str(self.as_str())
3225 }
3226}
3227
3228impl<'de> Deserialize<'de> for TaskType {
3229 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3230 let s = String::deserialize(deserializer)?;
3231 Ok(Self::from(s.as_str()))
3232 }
3233}
3234
3235#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3237pub enum TaskStatus {
3238 Pending,
3239 Running,
3240 Completed,
3241 Failed,
3242 Killed,
3243 Paused,
3244 Stopped,
3245 Unknown(String),
3246}
3247
3248impl TaskStatus {
3249 pub fn as_str(&self) -> &str {
3250 match self {
3251 Self::Pending => "pending",
3252 Self::Running => "running",
3253 Self::Completed => "completed",
3254 Self::Failed => "failed",
3255 Self::Killed => "killed",
3256 Self::Paused => "paused",
3257 Self::Stopped => "stopped",
3258 Self::Unknown(s) => s.as_str(),
3259 }
3260 }
3261}
3262
3263impl fmt::Display for TaskStatus {
3264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3265 f.write_str(self.as_str())
3266 }
3267}
3268
3269impl From<&str> for TaskStatus {
3270 fn from(s: &str) -> Self {
3271 match s {
3272 "pending" => Self::Pending,
3273 "running" => Self::Running,
3274 "completed" => Self::Completed,
3275 "failed" => Self::Failed,
3276 "killed" => Self::Killed,
3277 "paused" => Self::Paused,
3278 "stopped" => Self::Stopped,
3279 other => Self::Unknown(other.to_string()),
3280 }
3281 }
3282}
3283
3284impl Serialize for TaskStatus {
3285 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3286 serializer.serialize_str(self.as_str())
3287 }
3288}
3289
3290impl<'de> Deserialize<'de> for TaskStatus {
3291 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3292 let s = String::deserialize(deserializer)?;
3293 Ok(Self::from(s.as_str()))
3294 }
3295}
3296
3297#[derive(Debug, Clone, Serialize, Deserialize)]
3299pub struct TaskStartedMessage {
3300 pub session_id: String,
3301 pub task_id: String,
3302 #[serde(default, skip_serializing_if = "Option::is_none")]
3308 pub run_id: Option<String>,
3309 #[serde(default, skip_serializing_if = "Option::is_none")]
3315 pub parent_task_id: Option<String>,
3316 #[serde(default, skip_serializing_if = "Option::is_none")]
3317 pub task_type: Option<TaskType>,
3318 #[serde(default, skip_serializing_if = "Option::is_none")]
3319 pub tool_use_id: Option<String>,
3320 pub description: String,
3321 #[serde(default, skip_serializing_if = "Option::is_none")]
3324 pub subagent_type: Option<String>,
3325 #[serde(default, skip_serializing_if = "Option::is_none")]
3331 pub is_backgrounded: Option<bool>,
3332 #[serde(default, skip_serializing_if = "Option::is_none")]
3340 pub awaited: Option<bool>,
3341 #[serde(default, skip_serializing_if = "Option::is_none")]
3345 pub spawn_depth: Option<u32>,
3346 #[serde(default, skip_serializing_if = "Option::is_none")]
3348 pub prompt: Option<String>,
3349 #[serde(default, skip_serializing_if = "Option::is_none")]
3350 pub workflow_name: Option<String>,
3351 #[serde(default, skip_serializing_if = "Option::is_none")]
3352 pub skip_transcript: Option<bool>,
3353 #[serde(default, skip_serializing_if = "Option::is_none")]
3358 pub ambient: Option<bool>,
3359 pub uuid: String,
3360}
3361
3362#[derive(Debug, Clone, Serialize, Deserialize)]
3366pub struct TaskUpdatedMessage {
3367 pub session_id: String,
3368 pub task_id: String,
3369 #[serde(default, skip_serializing_if = "Option::is_none")]
3372 pub run_id: Option<String>,
3373 pub patch: TaskPatch,
3374 pub uuid: String,
3375}
3376
3377#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3380pub struct TaskPatch {
3381 #[serde(default, skip_serializing_if = "Option::is_none")]
3382 pub status: Option<TaskStatus>,
3383 #[serde(default, skip_serializing_if = "Option::is_none")]
3386 pub end_time: Option<u64>,
3387 #[serde(default, skip_serializing_if = "Option::is_none")]
3388 pub description: Option<String>,
3389 #[serde(default, skip_serializing_if = "Option::is_none")]
3390 pub total_paused_ms: Option<u64>,
3391 #[serde(default, skip_serializing_if = "Option::is_none")]
3392 pub error: Option<String>,
3393 #[serde(default, skip_serializing_if = "Option::is_none")]
3394 pub is_backgrounded: Option<bool>,
3395}
3396
3397#[derive(Debug, Clone, Serialize, Deserialize)]
3400pub struct ThinkingTokensMessage {
3401 pub session_id: String,
3402 pub estimated_tokens: u64,
3404 pub estimated_tokens_delta: u64,
3406 #[serde(default, skip_serializing_if = "Option::is_none")]
3413 pub user_message_uuid: Option<String>,
3414 pub uuid: String,
3415}
3416
3417#[derive(Debug, Clone, Serialize, Deserialize)]
3420pub struct TaskProgressMessage {
3421 pub session_id: String,
3422 pub task_id: String,
3423 #[serde(default, skip_serializing_if = "Option::is_none")]
3426 pub run_id: Option<String>,
3427 #[serde(default, skip_serializing_if = "Option::is_none")]
3428 pub tool_use_id: Option<String>,
3429 pub description: String,
3430 #[serde(default, skip_serializing_if = "Option::is_none")]
3431 pub last_tool_name: Option<String>,
3432 pub usage: TaskUsage,
3433 #[serde(default, skip_serializing_if = "Option::is_none")]
3435 pub subagent_type: Option<String>,
3436 #[serde(default, skip_serializing_if = "Option::is_none")]
3437 pub summary: Option<String>,
3438 pub uuid: String,
3439}
3440
3441#[derive(Debug, Clone, Serialize, Deserialize)]
3444pub struct TaskNotificationMessage {
3445 pub session_id: String,
3446 pub task_id: String,
3447 #[serde(default, skip_serializing_if = "Option::is_none")]
3450 pub run_id: Option<String>,
3451 pub status: TaskStatus,
3452 #[serde(default, skip_serializing_if = "Option::is_none")]
3455 pub reason: Option<TaskEndReason>,
3456 pub summary: String,
3457 pub output_file: Option<String>,
3458 #[serde(skip_serializing_if = "Option::is_none")]
3459 pub tool_use_id: Option<String>,
3460 #[serde(skip_serializing_if = "Option::is_none")]
3461 pub usage: Option<TaskUsage>,
3462 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3468 pub resource_links: Vec<ResourceLink>,
3469 #[serde(default, skip_serializing_if = "Option::is_none")]
3476 pub handback: Option<TaskHandback>,
3477 #[serde(default, skip_serializing_if = "Option::is_none")]
3481 pub handback_report: Option<HandbackReport>,
3482 #[serde(default, skip_serializing_if = "Option::is_none")]
3483 pub skip_transcript: Option<bool>,
3484 #[serde(default, skip_serializing_if = "Option::is_none")]
3487 pub ambient: Option<bool>,
3488 #[serde(skip_serializing_if = "Option::is_none")]
3489 pub uuid: Option<String>,
3490}
3491
3492#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3495pub enum TaskHandback {
3496 Send,
3498 Flagged,
3500 Withheld,
3502 Unknown(String),
3504}
3505
3506impl TaskHandback {
3507 pub fn as_str(&self) -> &str {
3508 match self {
3509 Self::Send => "send",
3510 Self::Flagged => "flagged",
3511 Self::Withheld => "withheld",
3512 Self::Unknown(s) => s.as_str(),
3513 }
3514 }
3515}
3516
3517impl fmt::Display for TaskHandback {
3518 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3519 f.write_str(self.as_str())
3520 }
3521}
3522
3523impl From<&str> for TaskHandback {
3524 fn from(s: &str) -> Self {
3525 match s {
3526 "send" => Self::Send,
3527 "flagged" => Self::Flagged,
3528 "withheld" => Self::Withheld,
3529 other => Self::Unknown(other.to_string()),
3530 }
3531 }
3532}
3533
3534impl Serialize for TaskHandback {
3535 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3536 serializer.serialize_str(self.as_str())
3537 }
3538}
3539
3540impl<'de> Deserialize<'de> for TaskHandback {
3541 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3542 let s = String::deserialize(deserializer)?;
3543 Ok(Self::from(s.as_str()))
3544 }
3545}
3546
3547#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3550pub struct HandbackReport {
3551 pub text: String,
3554 #[serde(default, skip_serializing_if = "Option::is_none")]
3557 pub warning: Option<String>,
3558}
3559
3560#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3562pub enum AssistantErrorKind {
3563 AuthenticationFailed,
3564 OauthOrgNotAllowed,
3565 AccountOnHold,
3566 BillingError,
3567 RateLimit,
3568 Overloaded,
3569 InvalidRequest,
3570 ModelNotFound,
3571 ServerError,
3572 UnknownError,
3573 MaxOutputTokens,
3574 Unknown(String),
3575}
3576
3577impl AssistantErrorKind {
3578 pub fn as_str(&self) -> &str {
3579 match self {
3580 Self::AuthenticationFailed => "authentication_failed",
3581 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3582 Self::AccountOnHold => "account_on_hold",
3583 Self::BillingError => "billing_error",
3584 Self::RateLimit => "rate_limit",
3585 Self::Overloaded => "overloaded",
3586 Self::InvalidRequest => "invalid_request",
3587 Self::ModelNotFound => "model_not_found",
3588 Self::ServerError => "server_error",
3589 Self::UnknownError => "unknown",
3590 Self::MaxOutputTokens => "max_output_tokens",
3591 Self::Unknown(s) => s.as_str(),
3592 }
3593 }
3594}
3595
3596impl fmt::Display for AssistantErrorKind {
3597 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3598 f.write_str(self.as_str())
3599 }
3600}
3601
3602impl From<&str> for AssistantErrorKind {
3603 fn from(s: &str) -> Self {
3604 match s {
3605 "authentication_failed" => Self::AuthenticationFailed,
3606 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3607 "account_on_hold" => Self::AccountOnHold,
3608 "billing_error" => Self::BillingError,
3609 "rate_limit" => Self::RateLimit,
3610 "overloaded" => Self::Overloaded,
3611 "invalid_request" => Self::InvalidRequest,
3612 "model_not_found" => Self::ModelNotFound,
3613 "server_error" => Self::ServerError,
3614 "unknown" => Self::UnknownError,
3615 "max_output_tokens" => Self::MaxOutputTokens,
3616 other => Self::Unknown(other.to_string()),
3617 }
3618 }
3619}
3620
3621impl Serialize for AssistantErrorKind {
3622 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3623 serializer.serialize_str(self.as_str())
3624 }
3625}
3626
3627impl<'de> Deserialize<'de> for AssistantErrorKind {
3628 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3629 let s = String::deserialize(deserializer)?;
3630 Ok(Self::from(s.as_str()))
3631 }
3632}
3633
3634#[derive(Debug, Clone, Serialize, Deserialize)]
3641pub struct CodeChangePublishedMessage {
3642 pub provider: String,
3646 pub url: String,
3648 pub repo: String,
3651 pub identifier: String,
3653 #[serde(default, skip_serializing_if = "Option::is_none")]
3662 pub action: Option<String>,
3663 #[serde(default, skip_serializing_if = "Option::is_none")]
3671 pub branch: Option<String>,
3672 pub uuid: String,
3673 pub session_id: String,
3674}
3675
3676#[derive(Debug, Clone, Serialize, Deserialize)]
3681pub struct VcsStateChangedMessage {
3682 pub kind: VcsMutationKind,
3685 pub cwd: String,
3688 #[serde(default, skip_serializing_if = "Option::is_none")]
3693 pub branch: Option<String>,
3694 pub uuid: String,
3695 pub session_id: String,
3696}
3697
3698#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3700pub enum VcsMutationKind {
3701 Commit,
3702 Push,
3703 Merge,
3704 Rebase,
3705 Unknown(String),
3707}
3708
3709impl VcsMutationKind {
3710 pub fn as_str(&self) -> &str {
3711 match self {
3712 Self::Commit => "commit",
3713 Self::Push => "push",
3714 Self::Merge => "merge",
3715 Self::Rebase => "rebase",
3716 Self::Unknown(s) => s.as_str(),
3717 }
3718 }
3719}
3720
3721impl fmt::Display for VcsMutationKind {
3722 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3723 f.write_str(self.as_str())
3724 }
3725}
3726
3727impl From<&str> for VcsMutationKind {
3728 fn from(s: &str) -> Self {
3729 match s {
3730 "commit" => Self::Commit,
3731 "push" => Self::Push,
3732 "merge" => Self::Merge,
3733 "rebase" => Self::Rebase,
3734 other => Self::Unknown(other.to_string()),
3735 }
3736 }
3737}
3738
3739impl Serialize for VcsMutationKind {
3740 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3741 serializer.serialize_str(self.as_str())
3742 }
3743}
3744
3745impl<'de> Deserialize<'de> for VcsMutationKind {
3746 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3747 let s = String::deserialize(deserializer)?;
3748 Ok(Self::from(s.as_str()))
3749 }
3750}
3751
3752#[derive(Debug, Clone, Serialize, Deserialize)]
3754pub struct FeedbackDraftQueuedMessage {
3755 pub draft_id: String,
3756 pub draft_type: String,
3757 pub title: String,
3758 pub details_preview: String,
3759 #[serde(default, skip_serializing_if = "Option::is_none")]
3760 pub uuid: Option<String>,
3761 #[serde(default, skip_serializing_if = "Option::is_none")]
3762 pub session_id: Option<String>,
3763 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3764 pub extra: serde_json::Map<String, Value>,
3765}
3766
3767#[derive(Debug, Clone, Serialize, Deserialize)]
3775pub struct CloudSessionDeltaMessage {
3776 pub seq: u64,
3780 pub changed: Vec<String>,
3784 pub cloud_session: Value,
3788 pub uuid: String,
3789 pub session_id: String,
3790 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3791 pub extra: serde_json::Map<String, Value>,
3792}
3793
3794#[derive(Debug, Clone, Serialize, Deserialize)]
3810pub struct DevIntentMessage {
3811 pub kind: DevIntentKind,
3813 #[serde(default, skip_serializing_if = "Option::is_none")]
3819 pub trigger: Option<DevIntentTrigger>,
3820 pub uuid: String,
3821 pub session_id: String,
3822 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3823 pub extra: serde_json::Map<String, Value>,
3824}
3825
3826#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3830pub enum DevIntentKind {
3831 IosApp,
3837 AndroidApp,
3844 Unknown(String),
3846}
3847
3848impl DevIntentKind {
3849 pub fn as_str(&self) -> &str {
3850 match self {
3851 Self::IosApp => "ios_app",
3852 Self::AndroidApp => "android_app",
3853 Self::Unknown(s) => s.as_str(),
3854 }
3855 }
3856}
3857
3858impl fmt::Display for DevIntentKind {
3859 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3860 f.write_str(self.as_str())
3861 }
3862}
3863
3864impl From<&str> for DevIntentKind {
3865 fn from(s: &str) -> Self {
3866 match s {
3867 "ios_app" => Self::IosApp,
3868 "android_app" => Self::AndroidApp,
3869 other => Self::Unknown(other.to_string()),
3870 }
3871 }
3872}
3873
3874impl Serialize for DevIntentKind {
3875 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3876 serializer.serialize_str(self.as_str())
3877 }
3878}
3879
3880impl<'de> Deserialize<'de> for DevIntentKind {
3881 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3882 let s = String::deserialize(deserializer)?;
3883 Ok(Self::from(s.as_str()))
3884 }
3885}
3886
3887#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3891pub enum DevIntentTrigger {
3892 SwiftEdit,
3894 UikitImport,
3896 XcodeProject,
3899 IosCommand,
3902 KotlinEdit,
3904 JavaEdit,
3906 AndroidImport,
3908 AndroidManifest,
3910 GradlePlugin,
3912 AndroidCommand,
3915 ProjectScan,
3918 Unknown(String),
3920}
3921
3922impl DevIntentTrigger {
3923 pub fn as_str(&self) -> &str {
3924 match self {
3925 Self::SwiftEdit => "swift_edit",
3926 Self::UikitImport => "uikit_import",
3927 Self::XcodeProject => "xcode_project",
3928 Self::IosCommand => "ios_command",
3929 Self::KotlinEdit => "kotlin_edit",
3930 Self::JavaEdit => "java_edit",
3931 Self::AndroidImport => "android_import",
3932 Self::AndroidManifest => "android_manifest",
3933 Self::GradlePlugin => "gradle_plugin",
3934 Self::AndroidCommand => "android_command",
3935 Self::ProjectScan => "project_scan",
3936 Self::Unknown(s) => s.as_str(),
3937 }
3938 }
3939}
3940
3941impl fmt::Display for DevIntentTrigger {
3942 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3943 f.write_str(self.as_str())
3944 }
3945}
3946
3947impl From<&str> for DevIntentTrigger {
3948 fn from(s: &str) -> Self {
3949 match s {
3950 "swift_edit" => Self::SwiftEdit,
3951 "uikit_import" => Self::UikitImport,
3952 "xcode_project" => Self::XcodeProject,
3953 "ios_command" => Self::IosCommand,
3954 "kotlin_edit" => Self::KotlinEdit,
3955 "java_edit" => Self::JavaEdit,
3956 "android_import" => Self::AndroidImport,
3957 "android_manifest" => Self::AndroidManifest,
3958 "gradle_plugin" => Self::GradlePlugin,
3959 "android_command" => Self::AndroidCommand,
3960 "project_scan" => Self::ProjectScan,
3961 other => Self::Unknown(other.to_string()),
3962 }
3963 }
3964}
3965
3966impl Serialize for DevIntentTrigger {
3967 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3968 serializer.serialize_str(self.as_str())
3969 }
3970}
3971
3972impl<'de> Deserialize<'de> for DevIntentTrigger {
3973 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3974 let s = String::deserialize(deserializer)?;
3975 Ok(Self::from(s.as_str()))
3976 }
3977}
3978
3979#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3983pub enum TaskEndReason {
3984 WorkerRestart,
3987 Unknown(String),
3989}
3990
3991impl TaskEndReason {
3992 pub fn as_str(&self) -> &str {
3993 match self {
3994 Self::WorkerRestart => "worker_restart",
3995 Self::Unknown(s) => s.as_str(),
3996 }
3997 }
3998}
3999
4000impl fmt::Display for TaskEndReason {
4001 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4002 f.write_str(self.as_str())
4003 }
4004}
4005
4006impl From<&str> for TaskEndReason {
4007 fn from(s: &str) -> Self {
4008 match s {
4009 "worker_restart" => Self::WorkerRestart,
4010 other => Self::Unknown(other.to_string()),
4011 }
4012 }
4013}
4014
4015impl Serialize for TaskEndReason {
4016 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4017 serializer.serialize_str(self.as_str())
4018 }
4019}
4020
4021impl<'de> Deserialize<'de> for TaskEndReason {
4022 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4023 let s = String::deserialize(deserializer)?;
4024 Ok(Self::from(s.as_str()))
4025 }
4026}
4027
4028#[derive(Debug, Clone, Serialize, Deserialize)]
4037pub struct TurnHandoffAvailableMessage {
4038 pub v: u64,
4040 pub tools: Vec<String>,
4042 pub worker_epoch: u64,
4044 #[serde(default, skip_serializing_if = "Option::is_none")]
4048 pub relay_marker: Option<bool>,
4049 #[serde(default, skip_serializing_if = "Option::is_none")]
4057 pub staged_files: Option<bool>,
4058 #[serde(default, skip_serializing_if = "Option::is_none")]
4065 pub no_query_first: Option<bool>,
4066 #[serde(default, skip_serializing_if = "Option::is_none")]
4074 pub home_files: Option<bool>,
4075 #[serde(default, skip_serializing_if = "Option::is_none")]
4079 pub file_names: Option<bool>,
4080 #[serde(default, skip_serializing_if = "Option::is_none")]
4090 pub carried_writes: Option<bool>,
4091 #[serde(default, skip_serializing_if = "Option::is_none")]
4105 pub hydrates_carried_lines: Option<bool>,
4106 #[serde(default, skip_serializing_if = "Option::is_none")]
4112 pub shared_message_id: Option<bool>,
4113 pub uuid: String,
4114 pub session_id: String,
4115 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4116 pub extra: serde_json::Map<String, Value>,
4117}
4118
4119#[derive(Debug, Clone, Serialize, Deserialize)]
4131pub struct TurnPreemptedMessage {
4132 pub reason: String,
4136 pub preempted_by_uuid: String,
4140 #[serde(default)]
4143 pub preempted_message_uuids: Vec<String>,
4144 pub uuid: String,
4145 pub session_id: String,
4146 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4147 pub extra: serde_json::Map<String, Value>,
4148}
4149
4150#[derive(Debug, Clone, Serialize, Deserialize)]
4158pub struct PeerMessageHoldMessage {
4159 pub state: PeerMessageHoldState,
4165 #[serde(default, skip_serializing_if = "Option::is_none")]
4172 pub message_uuid: Option<String>,
4173 pub lane: PeerMessageLane,
4175 pub from: String,
4180 #[serde(default, skip_serializing_if = "Option::is_none")]
4183 pub from_name: Option<String>,
4184 #[serde(default, skip_serializing_if = "Option::is_none")]
4186 pub cause: Option<PeerMessageHoldCause>,
4187 #[serde(default, skip_serializing_if = "Option::is_none")]
4189 pub outcome: Option<PeerMessageHoldOutcome>,
4190 pub uuid: String,
4191 pub session_id: String,
4192 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4193 pub extra: serde_json::Map<String, Value>,
4194}
4195
4196#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4198pub enum PeerMessageHoldState {
4199 Held,
4201 Released,
4203 Dropped,
4205 Unknown(String),
4207}
4208
4209impl PeerMessageHoldState {
4210 pub fn as_str(&self) -> &str {
4211 match self {
4212 Self::Held => "held",
4213 Self::Released => "released",
4214 Self::Dropped => "dropped",
4215 Self::Unknown(s) => s.as_str(),
4216 }
4217 }
4218}
4219
4220impl fmt::Display for PeerMessageHoldState {
4221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4222 f.write_str(self.as_str())
4223 }
4224}
4225
4226impl From<&str> for PeerMessageHoldState {
4227 fn from(s: &str) -> Self {
4228 match s {
4229 "held" => Self::Held,
4230 "released" => Self::Released,
4231 "dropped" => Self::Dropped,
4232 other => Self::Unknown(other.to_string()),
4233 }
4234 }
4235}
4236
4237impl Serialize for PeerMessageHoldState {
4238 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4239 serializer.serialize_str(self.as_str())
4240 }
4241}
4242
4243impl<'de> Deserialize<'de> for PeerMessageHoldState {
4244 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4245 let s = String::deserialize(deserializer)?;
4246 Ok(Self::from(s.as_str()))
4247 }
4248}
4249
4250#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4252pub enum PeerMessageLane {
4253 Bridge,
4255 Stdin,
4257 Socket,
4259 Unknown(String),
4261}
4262
4263impl PeerMessageLane {
4264 pub fn as_str(&self) -> &str {
4265 match self {
4266 Self::Bridge => "bridge",
4267 Self::Stdin => "stdin",
4268 Self::Socket => "socket",
4269 Self::Unknown(s) => s.as_str(),
4270 }
4271 }
4272}
4273
4274impl fmt::Display for PeerMessageLane {
4275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4276 f.write_str(self.as_str())
4277 }
4278}
4279
4280impl From<&str> for PeerMessageLane {
4281 fn from(s: &str) -> Self {
4282 match s {
4283 "bridge" => Self::Bridge,
4284 "stdin" => Self::Stdin,
4285 "socket" => Self::Socket,
4286 other => Self::Unknown(other.to_string()),
4287 }
4288 }
4289}
4290
4291impl Serialize for PeerMessageLane {
4292 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4293 serializer.serialize_str(self.as_str())
4294 }
4295}
4296
4297impl<'de> Deserialize<'de> for PeerMessageLane {
4298 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4299 let s = String::deserialize(deserializer)?;
4300 Ok(Self::from(s.as_str()))
4301 }
4302}
4303
4304#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4311pub enum PeerMessageHoldCause {
4312 ExplicitSetting,
4313 ManagedSetting,
4314 RepoSetting,
4315 InvalidSetting,
4316 BypassDefault,
4317 ModeUnknown,
4318 ModeMismatch,
4319 NoModeAsserted,
4320 Unknown(String),
4322}
4323
4324impl PeerMessageHoldCause {
4325 pub fn as_str(&self) -> &str {
4326 match self {
4327 Self::ExplicitSetting => "explicit-setting",
4328 Self::ManagedSetting => "managed-setting",
4329 Self::RepoSetting => "repo-setting",
4330 Self::InvalidSetting => "invalid-setting",
4331 Self::BypassDefault => "bypass-default",
4332 Self::ModeUnknown => "mode-unknown",
4333 Self::ModeMismatch => "mode-mismatch",
4334 Self::NoModeAsserted => "no-mode-asserted",
4335 Self::Unknown(s) => s.as_str(),
4336 }
4337 }
4338}
4339
4340impl fmt::Display for PeerMessageHoldCause {
4341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4342 f.write_str(self.as_str())
4343 }
4344}
4345
4346impl From<&str> for PeerMessageHoldCause {
4347 fn from(s: &str) -> Self {
4348 match s {
4349 "explicit-setting" => Self::ExplicitSetting,
4350 "managed-setting" => Self::ManagedSetting,
4351 "repo-setting" => Self::RepoSetting,
4352 "invalid-setting" => Self::InvalidSetting,
4353 "bypass-default" => Self::BypassDefault,
4354 "mode-unknown" => Self::ModeUnknown,
4355 "mode-mismatch" => Self::ModeMismatch,
4356 "no-mode-asserted" => Self::NoModeAsserted,
4357 other => Self::Unknown(other.to_string()),
4358 }
4359 }
4360}
4361
4362impl Serialize for PeerMessageHoldCause {
4363 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4364 serializer.serialize_str(self.as_str())
4365 }
4366}
4367
4368impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4369 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4370 let s = String::deserialize(deserializer)?;
4371 Ok(Self::from(s.as_str()))
4372 }
4373}
4374
4375#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4378pub enum PeerMessageHoldOutcome {
4379 Expired,
4383 Refused,
4385 Dropped,
4388 Discarded,
4390 Unknown(String),
4392}
4393
4394impl PeerMessageHoldOutcome {
4395 pub fn as_str(&self) -> &str {
4396 match self {
4397 Self::Expired => "expired",
4398 Self::Refused => "refused",
4399 Self::Dropped => "dropped",
4400 Self::Discarded => "discarded",
4401 Self::Unknown(s) => s.as_str(),
4402 }
4403 }
4404}
4405
4406impl fmt::Display for PeerMessageHoldOutcome {
4407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4408 f.write_str(self.as_str())
4409 }
4410}
4411
4412impl From<&str> for PeerMessageHoldOutcome {
4413 fn from(s: &str) -> Self {
4414 match s {
4415 "expired" => Self::Expired,
4416 "refused" => Self::Refused,
4417 "dropped" => Self::Dropped,
4418 "discarded" => Self::Discarded,
4419 other => Self::Unknown(other.to_string()),
4420 }
4421 }
4422}
4423
4424impl Serialize for PeerMessageHoldOutcome {
4425 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4426 serializer.serialize_str(self.as_str())
4427 }
4428}
4429
4430impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4431 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4432 let s = String::deserialize(deserializer)?;
4433 Ok(Self::from(s.as_str()))
4434 }
4435}
4436
4437#[derive(Debug, Clone, Serialize, Deserialize)]
4448pub struct PerTurnEffortChangedMessage {
4449 pub per_turn_effort_active: bool,
4451 pub uuid: String,
4452 pub session_id: String,
4453}
4454
4455#[derive(Debug, Clone, Serialize, Deserialize)]
4462pub struct InstructionSizeWarningMessage {
4463 pub total_chars: u64,
4466 pub total_limit_chars: u64,
4469 pub file_count: u64,
4472 #[serde(default, skip_serializing_if = "Option::is_none")]
4476 pub largest_chars: Option<u64>,
4477 pub uuid: String,
4478 pub session_id: String,
4479}
4480
4481#[derive(Debug, Clone, Serialize, Deserialize)]
4488pub struct FileAttachmentsMissingMessage {
4489 pub message_uuid: String,
4492 pub sent_count: u64,
4494 pub missing: Vec<MissingFileAttachment>,
4496 pub uuid: String,
4497 pub session_id: String,
4498}
4499
4500#[derive(Debug, Clone, Serialize, Deserialize)]
4510pub struct PermissionCheckStatusMessage {
4511 pub tool_use_id: String,
4512 #[serde(default, skip_serializing_if = "Option::is_none")]
4515 pub agent_id: Option<String>,
4516 pub status: PermissionCheckStatus,
4517 pub uuid: String,
4518 pub session_id: String,
4519}
4520
4521#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4523pub enum PermissionCheckStatus {
4524 Checking,
4527 Done,
4529 Unknown(String),
4531}
4532
4533impl PermissionCheckStatus {
4534 pub fn as_str(&self) -> &str {
4535 match self {
4536 Self::Checking => "checking",
4537 Self::Done => "done",
4538 Self::Unknown(s) => s.as_str(),
4539 }
4540 }
4541}
4542
4543impl fmt::Display for PermissionCheckStatus {
4544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4545 f.write_str(self.as_str())
4546 }
4547}
4548
4549impl From<&str> for PermissionCheckStatus {
4550 fn from(s: &str) -> Self {
4551 match s {
4552 "checking" => Self::Checking,
4553 "done" => Self::Done,
4554 other => Self::Unknown(other.to_string()),
4555 }
4556 }
4557}
4558
4559impl Serialize for PermissionCheckStatus {
4560 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4561 serializer.serialize_str(self.as_str())
4562 }
4563}
4564
4565impl<'de> Deserialize<'de> for PermissionCheckStatus {
4566 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4567 let s = String::deserialize(deserializer)?;
4568 Ok(Self::from(s.as_str()))
4569 }
4570}
4571
4572#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4574pub struct MissingFileAttachment {
4575 pub file_uuid: String,
4577 pub reason: String,
4582}
4583
4584#[derive(Debug, Clone, Serialize, Deserialize)]
4594pub struct SessionTitleChangedMessage {
4595 pub title: String,
4600 pub uuid: String,
4601 pub session_id: String,
4602}
4603
4604#[derive(Debug, Clone, Serialize, Deserialize)]
4608pub struct UiLogMessage {
4609 pub plugin: String,
4611 pub text: String,
4615 pub uuid: String,
4616 pub session_id: String,
4617}
4618
4619#[derive(Debug, Clone, Serialize, Deserialize)]
4624pub struct UiToastMessage {
4625 pub plugin: String,
4627 pub text: String,
4631 pub timeout_ms: u64,
4634 pub uuid: String,
4635 pub session_id: String,
4636}
4637
4638#[derive(Debug, Clone, Serialize, Deserialize)]
4642pub struct UiStatusMessage {
4643 pub plugin: String,
4645 pub text: Option<String>,
4650 pub uuid: String,
4651 pub session_id: String,
4652}
4653
4654#[derive(Debug, Clone, Serialize, Deserialize)]
4659pub struct UiInvalidateMessage {
4660 pub event: String,
4663 #[serde(default, skip_serializing_if = "Option::is_none")]
4668 pub instances: Option<Vec<UiInvalidatedInstance>>,
4669 #[serde(default, skip_serializing_if = "Option::is_none")]
4677 pub prompts_screened: Option<bool>,
4678 pub uuid: String,
4679 pub session_id: String,
4680}
4681
4682#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4684pub struct UiInvalidatedInstance {
4685 pub surface: String,
4687 pub component: String,
4689 pub instance_id: String,
4692}
4693
4694#[derive(Debug, Clone, Serialize, Deserialize)]
4701pub struct UiPanesMessage {
4702 pub panes: Vec<UiPane>,
4705 pub shown_id: Option<String>,
4708 pub focused_id: Option<String>,
4711 pub focus_requested_id: Option<String>,
4715 pub uuid: String,
4716 pub session_id: String,
4717}
4718
4719#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4721pub struct UiPane {
4722 pub id: String,
4725 pub title: String,
4728 pub plugin: String,
4730 #[serde(default, skip_serializing_if = "Option::is_none")]
4733 pub close_on_escape: Option<bool>,
4734 #[serde(default, skip_serializing_if = "Option::is_none")]
4737 pub hold_toasts: Option<bool>,
4738 #[serde(default, skip_serializing_if = "Option::is_none")]
4741 pub rows: Option<u64>,
4742 #[serde(default, skip_serializing_if = "Option::is_none")]
4745 pub columns: Option<u64>,
4746}
4747
4748#[derive(Debug, Clone, Serialize, Deserialize)]
4752pub struct UiScrollMessage {
4753 pub client_id: String,
4755 pub component: UiSiteComponent,
4757 pub instance_id: String,
4759 pub offset: u64,
4762 #[serde(default, skip_serializing_if = "Option::is_none")]
4765 pub follow_end: Option<bool>,
4766 pub uuid: String,
4767 pub session_id: String,
4768}
4769
4770#[derive(Debug, Clone, Serialize, Deserialize)]
4775pub struct UiFocusMessage {
4776 pub client_id: String,
4778 pub component: UiSiteComponent,
4780 pub instance_id: String,
4782 pub plugin: String,
4784 pub key: String,
4786 pub uuid: String,
4787 pub session_id: String,
4788}
4789
4790#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4792pub enum UiSiteComponent {
4793 Pane,
4795 AbovePrompt,
4797 Unknown(String),
4799}
4800
4801impl UiSiteComponent {
4802 pub fn as_str(&self) -> &str {
4803 match self {
4804 Self::Pane => "Pane",
4805 Self::AbovePrompt => "AbovePrompt",
4806 Self::Unknown(s) => s.as_str(),
4807 }
4808 }
4809}
4810
4811impl fmt::Display for UiSiteComponent {
4812 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4813 f.write_str(self.as_str())
4814 }
4815}
4816
4817impl From<&str> for UiSiteComponent {
4818 fn from(s: &str) -> Self {
4819 match s {
4820 "Pane" => Self::Pane,
4821 "AbovePrompt" => Self::AbovePrompt,
4822 other => Self::Unknown(other.to_string()),
4823 }
4824 }
4825}
4826
4827impl Serialize for UiSiteComponent {
4828 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4829 serializer.serialize_str(self.as_str())
4830 }
4831}
4832
4833impl<'de> Deserialize<'de> for UiSiteComponent {
4834 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4835 let s = String::deserialize(deserializer)?;
4836 Ok(Self::from(s.as_str()))
4837 }
4838}
4839
4840#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4843pub struct LocalCommandRun {
4844 pub command: String,
4846 pub args: String,
4849 #[serde(default, skip_serializing_if = "Option::is_none")]
4854 pub plugin: Option<String>,
4855}
4856
4857#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4861pub struct LocalCommandOutcome {
4862 pub kind: LocalCommandOutcomeKind,
4863 #[serde(default, skip_serializing_if = "Option::is_none")]
4866 pub suggestion: Option<String>,
4867}
4868
4869#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4874pub enum LocalCommandOutcomeKind {
4875 UnavailableHeadless,
4877 Unknown,
4880 Failed,
4882 RestartRequired,
4884 Other(String),
4886}
4887
4888impl LocalCommandOutcomeKind {
4889 pub fn as_str(&self) -> &str {
4890 match self {
4891 Self::UnavailableHeadless => "unavailable_headless",
4892 Self::Unknown => "unknown",
4893 Self::Failed => "failed",
4894 Self::RestartRequired => "restart_required",
4895 Self::Other(s) => s.as_str(),
4896 }
4897 }
4898}
4899
4900impl fmt::Display for LocalCommandOutcomeKind {
4901 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4902 f.write_str(self.as_str())
4903 }
4904}
4905
4906impl From<&str> for LocalCommandOutcomeKind {
4907 fn from(s: &str) -> Self {
4908 match s {
4909 "unavailable_headless" => Self::UnavailableHeadless,
4910 "unknown" => Self::Unknown,
4911 "failed" => Self::Failed,
4912 "restart_required" => Self::RestartRequired,
4913 other => Self::Other(other.to_string()),
4914 }
4915 }
4916}
4917
4918impl Serialize for LocalCommandOutcomeKind {
4919 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4920 serializer.serialize_str(self.as_str())
4921 }
4922}
4923
4924impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4925 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4926 let s = String::deserialize(deserializer)?;
4927 Ok(Self::from(s.as_str()))
4928 }
4929}
4930
4931#[derive(Debug, Clone, Serialize, Deserialize)]
4937pub struct UsageReport {
4938 pub session: super::control::UsageSession,
4940 pub rate_limits: Option<UsageReportRateLimits>,
4944 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4945 pub extra: serde_json::Map<String, Value>,
4946}
4947
4948#[derive(Debug, Clone, Serialize, Deserialize)]
4950pub struct UsageReportRateLimits {
4951 pub limits: Option<Vec<UsageReportLimit>>,
4959 #[serde(default, skip_serializing_if = "Option::is_none")]
4962 pub extra_usage: Option<UsageReportExtraUsage>,
4963 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4964 pub extra: serde_json::Map<String, Value>,
4965}
4966
4967#[derive(Debug, Clone, Serialize, Deserialize)]
4969pub struct UsageReportLimit {
4970 pub kind: String,
4973 pub group: String,
4976 pub percent: f64,
4978 pub resets_at: Option<String>,
4980 #[serde(default, skip_serializing_if = "Option::is_none")]
4983 pub scope: Option<UsageReportScope>,
4984 #[serde(default, skip_serializing_if = "Option::is_none")]
4988 pub severity: Option<String>,
4989 #[serde(default, skip_serializing_if = "Option::is_none")]
4991 pub is_active: Option<bool>,
4992 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4993 pub extra: serde_json::Map<String, Value>,
4994}
4995
4996#[derive(Debug, Clone, Serialize, Deserialize)]
4998pub struct UsageReportScope {
4999 #[serde(default, skip_serializing_if = "Option::is_none")]
5000 pub model: Option<UsageReportScopeLabel>,
5001 #[serde(default, skip_serializing_if = "Option::is_none")]
5002 pub surface: Option<UsageReportScopeLabel>,
5003 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5004 pub extra: serde_json::Map<String, Value>,
5005}
5006
5007#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5009pub struct UsageReportScopeLabel {
5010 pub display_name: String,
5011}
5012
5013#[derive(Debug, Clone, Serialize, Deserialize)]
5017pub struct UsageReportExtraUsage {
5018 pub is_enabled: bool,
5020 pub monthly_limit: Option<f64>,
5021 pub used_credits: Option<f64>,
5022 pub utilization: Option<f64>,
5023 #[serde(default, skip_serializing_if = "Option::is_none")]
5024 pub currency: Option<String>,
5025 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5026 pub extra: serde_json::Map<String, Value>,
5027}
5028
5029#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5032pub struct BatchToolUse {
5033 pub id: String,
5034 pub name: String,
5035}
5036
5037#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5042pub struct ContextUsage {
5043 pub model: String,
5045 pub total_tokens: u64,
5048 pub raw_max_tokens: u64,
5051 pub percentage: u64,
5053 #[serde(default, skip_serializing_if = "Option::is_none")]
5055 pub over_limit: Option<ContextOverLimit>,
5056 #[serde(default)]
5058 pub categories: Vec<ContextCategory>,
5059 #[serde(default)]
5061 pub mcp_tools: Vec<ContextMcpTool>,
5062 #[serde(default)]
5064 pub memory_files: Vec<ContextMemoryFile>,
5065 #[serde(default)]
5067 pub agents: Vec<ContextAgent>,
5068 #[serde(default, skip_serializing_if = "Option::is_none")]
5070 pub skills: Option<Vec<ContextSkill>>,
5071}
5072
5073#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5075pub struct ContextOverLimit {
5076 pub tokens_over: u64,
5077 pub kind: String,
5080}
5081
5082#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5084pub struct ContextCategory {
5085 pub name: String,
5088 pub tokens: u64,
5089 pub kind: String,
5093}
5094
5095#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5097pub struct ContextMcpTool {
5098 pub name: String,
5100 pub server_name: String,
5101 pub tokens: u64,
5102}
5103
5104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5106pub struct ContextMemoryFile {
5107 pub path: String,
5108 #[serde(rename = "type")]
5110 pub file_type: String,
5111 pub tokens: u64,
5112}
5113
5114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5116pub struct ContextAgent {
5117 pub agent_type: String,
5118 pub source: String,
5120 pub tokens: u64,
5121}
5122
5123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5125pub struct ContextSkill {
5126 pub name: String,
5127 pub source: String,
5129 #[serde(default, skip_serializing_if = "Option::is_none")]
5130 pub plugin_name: Option<String>,
5131 pub tokens: u64,
5132}
5133
5134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5136pub struct ToolUseMeta {
5137 pub id: String,
5138 pub display_name: String,
5139 #[serde(default, skip_serializing_if = "Option::is_none")]
5140 pub server_display_name: Option<String>,
5141 #[serde(default, skip_serializing_if = "Option::is_none")]
5142 pub icon_url: Option<String>,
5143 #[serde(default, skip_serializing_if = "Option::is_none")]
5148 pub server_id: Option<String>,
5149 #[serde(default, skip_serializing_if = "Option::is_none")]
5157 pub server_id_stamp: Option<String>,
5158 #[serde(default, skip_serializing_if = "Option::is_none")]
5161 pub tool_name: Option<String>,
5162}
5163
5164#[derive(Debug, Clone, Serialize, Deserialize)]
5166pub struct AssistantMessage {
5167 pub message: AssistantMessageContent,
5168 #[serde(alias = "sessionId")]
5169 pub session_id: String,
5170 #[serde(skip_serializing_if = "Option::is_none")]
5171 pub uuid: Option<String>,
5172 #[serde(skip_serializing_if = "Option::is_none")]
5173 pub parent_tool_use_id: Option<String>,
5174 #[serde(skip_serializing_if = "Option::is_none")]
5176 pub request_id: Option<String>,
5177 #[serde(default, skip_serializing_if = "Option::is_none")]
5182 pub refused_message_id: Option<String>,
5183 #[serde(default, skip_serializing_if = "Option::is_none")]
5192 pub user_message_uuid: Option<String>,
5193 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5199 pub user_message_uuids: Vec<String>,
5200 #[serde(default, skip_serializing_if = "Option::is_none")]
5204 pub agent_id: Option<String>,
5205 #[serde(skip_serializing_if = "Option::is_none")]
5208 pub subagent_type: Option<String>,
5209 #[serde(skip_serializing_if = "Option::is_none")]
5211 pub task_description: Option<String>,
5212 #[serde(skip_serializing_if = "Option::is_none")]
5213 pub error: Option<AssistantErrorKind>,
5214 #[serde(default, skip_serializing_if = "Option::is_none")]
5218 pub aborted: Option<bool>,
5219 #[serde(default, skip_serializing_if = "Option::is_none")]
5224 pub resumed_from_incomplete_thinking: Option<bool>,
5225 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5226 pub supersedes: Vec<String>,
5227 #[serde(skip_serializing_if = "Option::is_none")]
5228 pub timestamp: Option<String>,
5229 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5230 pub tool_use_meta: Vec<ToolUseMeta>,
5231 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5236 pub batch_tool_uses: Vec<BatchToolUse>,
5237 #[serde(default, skip_serializing_if = "Option::is_none")]
5243 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
5244 #[serde(default, skip_serializing_if = "Option::is_none")]
5250 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
5251 #[serde(default, skip_serializing_if = "Option::is_none")]
5255 pub historical: Option<bool>,
5256 #[serde(default, skip_serializing_if = "Option::is_none")]
5264 pub resume_reason: Option<String>,
5265 #[serde(default, skip_serializing_if = "Option::is_none")]
5271 pub local_command_source: Option<String>,
5272 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5281 pub narration_block_indexes: Vec<usize>,
5282 #[serde(default, skip_serializing_if = "Option::is_none")]
5286 pub narration_hint: Option<String>,
5287 #[serde(default, skip_serializing_if = "Option::is_none")]
5296 pub thinking_duration_ms: Option<u64>,
5297 #[serde(default, skip_serializing_if = "Option::is_none")]
5308 pub thinking_display: Option<String>,
5309 #[serde(default, skip_serializing_if = "Option::is_none")]
5313 pub context_usage: Option<ContextUsage>,
5314 #[serde(default, skip_serializing_if = "Option::is_none")]
5322 pub usage_report: Option<Box<UsageReport>>,
5323 #[serde(default, skip_serializing_if = "Option::is_none")]
5330 pub local_command_run: Option<LocalCommandRun>,
5331 #[serde(default, skip_serializing_if = "Option::is_none")]
5337 pub local_command_outcome: Option<LocalCommandOutcome>,
5338 #[serde(default, skip_serializing_if = "Option::is_none")]
5339 pub is_meta: Option<bool>,
5340 #[serde(default, skip_serializing_if = "Option::is_none")]
5341 pub is_virtual: Option<bool>,
5342 #[serde(default, skip_serializing_if = "Option::is_none")]
5343 pub is_api_error_message: Option<bool>,
5344 #[serde(skip_serializing_if = "Option::is_none")]
5345 pub api_error_status: Option<u16>,
5346 #[serde(skip_serializing_if = "Option::is_none")]
5366 pub api_error: Option<String>,
5367 #[serde(default, skip_serializing_if = "Option::is_none")]
5374 pub api_error_code: Option<String>,
5375 #[serde(default, skip_serializing_if = "Option::is_none")]
5386 pub api_error_origin: Option<String>,
5387 #[serde(default, skip_serializing_if = "Option::is_none")]
5390 pub api_error_params: Option<ApiErrorParams>,
5391 #[serde(skip_serializing_if = "Option::is_none")]
5392 pub error_details: Option<String>,
5393 #[serde(skip_serializing_if = "Option::is_none")]
5394 pub advisor_model: Option<String>,
5395 #[serde(skip_serializing_if = "Option::is_none")]
5396 pub attribution_agent: Option<String>,
5397 #[serde(skip_serializing_if = "Option::is_none")]
5398 pub attribution_skill: Option<String>,
5399 #[serde(skip_serializing_if = "Option::is_none")]
5400 pub attribution_plugin: Option<String>,
5401 #[serde(skip_serializing_if = "Option::is_none")]
5402 pub attribution_mcp_server: Option<String>,
5403 #[serde(skip_serializing_if = "Option::is_none")]
5404 pub attribution_mcp_tool: Option<String>,
5405}
5406
5407#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5411pub struct ApiErrorParams {
5412 #[serde(default, skip_serializing_if = "Option::is_none")]
5414 pub effort: Option<String>,
5415 #[serde(default, skip_serializing_if = "Option::is_none")]
5418 pub provider: Option<ApiErrorProvider>,
5419 #[serde(default, skip_serializing_if = "Option::is_none")]
5422 pub remedy: Option<ApiErrorRemedy>,
5423 #[serde(default, skip_serializing_if = "Option::is_none")]
5426 pub media: Option<ApiErrorMedia>,
5427 #[serde(default, skip_serializing_if = "Option::is_none")]
5429 pub media_reason: Option<ApiErrorMediaReason>,
5430 #[serde(default, skip_serializing_if = "Option::is_none")]
5438 pub rate_limit_info: Option<RateLimitInfo>,
5439}
5440
5441#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5443pub enum ApiErrorMedia {
5444 Image,
5445 Document,
5446 Unknown(String),
5448}
5449
5450impl ApiErrorMedia {
5451 pub fn as_str(&self) -> &str {
5452 match self {
5453 Self::Image => "image",
5454 Self::Document => "document",
5455 Self::Unknown(s) => s.as_str(),
5456 }
5457 }
5458}
5459
5460impl fmt::Display for ApiErrorMedia {
5461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5462 f.write_str(self.as_str())
5463 }
5464}
5465
5466impl From<&str> for ApiErrorMedia {
5467 fn from(s: &str) -> Self {
5468 match s {
5469 "image" => Self::Image,
5470 "document" => Self::Document,
5471 other => Self::Unknown(other.to_string()),
5472 }
5473 }
5474}
5475
5476impl Serialize for ApiErrorMedia {
5477 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5478 serializer.serialize_str(self.as_str())
5479 }
5480}
5481
5482impl<'de> Deserialize<'de> for ApiErrorMedia {
5483 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5484 let s = String::deserialize(deserializer)?;
5485 Ok(Self::from(s.as_str()))
5486 }
5487}
5488
5489#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5492pub enum ApiErrorMediaReason {
5493 Unprocessable,
5496 UnsupportedByModel,
5498 MediaBudget,
5501 Unknown(String),
5503}
5504
5505impl ApiErrorMediaReason {
5506 pub fn as_str(&self) -> &str {
5507 match self {
5508 Self::Unprocessable => "unprocessable",
5509 Self::UnsupportedByModel => "unsupported_by_model",
5510 Self::MediaBudget => "media_budget",
5511 Self::Unknown(s) => s.as_str(),
5512 }
5513 }
5514}
5515
5516impl fmt::Display for ApiErrorMediaReason {
5517 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5518 f.write_str(self.as_str())
5519 }
5520}
5521
5522impl From<&str> for ApiErrorMediaReason {
5523 fn from(s: &str) -> Self {
5524 match s {
5525 "unprocessable" => Self::Unprocessable,
5526 "unsupported_by_model" => Self::UnsupportedByModel,
5527 "media_budget" => Self::MediaBudget,
5528 other => Self::Unknown(other.to_string()),
5529 }
5530 }
5531}
5532
5533impl Serialize for ApiErrorMediaReason {
5534 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5535 serializer.serialize_str(self.as_str())
5536 }
5537}
5538
5539impl<'de> Deserialize<'de> for ApiErrorMediaReason {
5540 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5541 let s = String::deserialize(deserializer)?;
5542 Ok(Self::from(s.as_str()))
5543 }
5544}
5545
5546#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5548pub enum ApiErrorProvider {
5549 Bedrock,
5550 AnthropicAws,
5551 Mantle,
5552 AnthropicGoogleCloud,
5553 Vertex,
5554 Foundry,
5555 Gateway,
5556 Unknown(String),
5558}
5559
5560impl ApiErrorProvider {
5561 pub fn as_str(&self) -> &str {
5562 match self {
5563 Self::Bedrock => "bedrock",
5564 Self::AnthropicAws => "anthropicAws",
5565 Self::Mantle => "mantle",
5566 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
5567 Self::Vertex => "vertex",
5568 Self::Foundry => "foundry",
5569 Self::Gateway => "gateway",
5570 Self::Unknown(s) => s.as_str(),
5571 }
5572 }
5573}
5574
5575impl fmt::Display for ApiErrorProvider {
5576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5577 f.write_str(self.as_str())
5578 }
5579}
5580
5581impl From<&str> for ApiErrorProvider {
5582 fn from(s: &str) -> Self {
5583 match s {
5584 "bedrock" => Self::Bedrock,
5585 "anthropicAws" => Self::AnthropicAws,
5586 "mantle" => Self::Mantle,
5587 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
5588 "vertex" => Self::Vertex,
5589 "foundry" => Self::Foundry,
5590 "gateway" => Self::Gateway,
5591 other => Self::Unknown(other.to_string()),
5592 }
5593 }
5594}
5595
5596impl Serialize for ApiErrorProvider {
5597 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5598 serializer.serialize_str(self.as_str())
5599 }
5600}
5601
5602impl<'de> Deserialize<'de> for ApiErrorProvider {
5603 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5604 let s = String::deserialize(deserializer)?;
5605 Ok(Self::from(s.as_str()))
5606 }
5607}
5608
5609#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5611pub enum ApiErrorRemedy {
5612 RefreshCommand,
5615 RefreshCredentials,
5617 Adc,
5620 GatewayToken,
5623 HostManaged,
5625 ModelAccess,
5628 Unknown(String),
5630}
5631
5632impl ApiErrorRemedy {
5633 pub fn as_str(&self) -> &str {
5634 match self {
5635 Self::RefreshCommand => "refresh_command",
5636 Self::RefreshCredentials => "refresh_credentials",
5637 Self::Adc => "adc",
5638 Self::GatewayToken => "gateway_token",
5639 Self::HostManaged => "host_managed",
5640 Self::ModelAccess => "model_access",
5641 Self::Unknown(s) => s.as_str(),
5642 }
5643 }
5644}
5645
5646impl fmt::Display for ApiErrorRemedy {
5647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5648 f.write_str(self.as_str())
5649 }
5650}
5651
5652impl From<&str> for ApiErrorRemedy {
5653 fn from(s: &str) -> Self {
5654 match s {
5655 "refresh_command" => Self::RefreshCommand,
5656 "refresh_credentials" => Self::RefreshCredentials,
5657 "adc" => Self::Adc,
5658 "gateway_token" => Self::GatewayToken,
5659 "host_managed" => Self::HostManaged,
5660 "model_access" => Self::ModelAccess,
5661 other => Self::Unknown(other.to_string()),
5662 }
5663 }
5664}
5665
5666impl Serialize for ApiErrorRemedy {
5667 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5668 serializer.serialize_str(self.as_str())
5669 }
5670}
5671
5672impl<'de> Deserialize<'de> for ApiErrorRemedy {
5673 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5674 let s = String::deserialize(deserializer)?;
5675 Ok(Self::from(s.as_str()))
5676 }
5677}
5678
5679#[derive(Debug, Clone, Serialize, Deserialize)]
5681pub struct AssistantMessageContent {
5682 pub id: String,
5683 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
5685 pub message_type: Option<String>,
5686 pub role: MessageRole,
5687 pub model: String,
5688 pub content: Vec<ContentBlock>,
5689 #[serde(skip_serializing_if = "Option::is_none")]
5690 pub stop_reason: Option<StopReason>,
5691 #[serde(skip_serializing_if = "Option::is_none")]
5692 pub stop_sequence: Option<String>,
5693 #[serde(skip_serializing_if = "Option::is_none")]
5694 pub usage: Option<AssistantUsage>,
5695 #[serde(skip_serializing_if = "Option::is_none")]
5697 pub stop_details: Option<Value>,
5698 #[serde(skip_serializing_if = "Option::is_none")]
5700 pub context_management: Option<Value>,
5701}
5702
5703#[derive(Debug, Clone, Serialize, Deserialize)]
5705pub struct AssistantUsage {
5706 #[serde(default)]
5708 pub input_tokens: u32,
5709
5710 #[serde(default)]
5712 pub output_tokens: u32,
5713
5714 #[serde(default)]
5716 pub cache_creation_input_tokens: u32,
5717
5718 #[serde(default)]
5720 pub cache_read_input_tokens: u32,
5721
5722 #[serde(skip_serializing_if = "Option::is_none")]
5724 pub service_tier: Option<String>,
5725
5726 #[serde(skip_serializing_if = "Option::is_none")]
5728 pub cache_creation: Option<CacheCreationDetails>,
5729
5730 #[serde(skip_serializing_if = "Option::is_none")]
5732 pub inference_geo: Option<String>,
5733
5734 #[serde(default, skip_serializing_if = "Option::is_none")]
5737 pub speed: Option<String>,
5738
5739 #[serde(default, skip_serializing_if = "Option::is_none")]
5742 pub fallback_credit: Option<Value>,
5743}
5744
5745#[derive(Debug, Clone, Serialize, Deserialize)]
5747pub struct CacheCreationDetails {
5748 #[serde(default)]
5750 pub ephemeral_1h_input_tokens: u32,
5751
5752 #[serde(default)]
5754 pub ephemeral_5m_input_tokens: u32,
5755}
5756
5757#[cfg(test)]
5758mod tests {
5759 use crate::io::ClaudeOutput;
5760
5761 #[test]
5762 fn test_subagent_usage_rollup_accumulates_task_results() {
5763 use super::SubagentUsageRollup;
5764
5765 let mut rollup = SubagentUsageRollup::default();
5766
5767 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}}"#;
5768 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5769 assert!(rollup.observe(&output));
5770 assert_eq!(rollup.subagent_tokens, 10201);
5771 assert_eq!(rollup.agent_count, 1);
5772 assert_eq!(rollup.tool_uses, 3);
5773 assert_eq!(rollup.duration_ms, 1853);
5774
5775 assert!(!rollup.observe(&output));
5777 assert_eq!(rollup.agent_count, 1);
5778 assert_eq!(rollup.subagent_tokens, 10201);
5779
5780 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}}"#;
5782 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5783 assert!(rollup.observe(&output));
5784 assert_eq!(rollup.agent_count, 2);
5785 assert_eq!(rollup.subagent_tokens, 10701);
5786 }
5787
5788 #[test]
5789 fn test_subagent_usage_rollup_ignores_non_task_results() {
5790 use super::SubagentUsageRollup;
5791
5792 let mut rollup = SubagentUsageRollup::default();
5793
5794 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}}"#;
5797 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5798 assert!(!rollup.observe(&output));
5799
5800 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5802 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5803 assert!(!rollup.observe(&output));
5804
5805 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5807 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5808 assert!(!rollup.observe(&output));
5809
5810 assert_eq!(rollup, SubagentUsageRollup::default());
5811 }
5812
5813 #[test]
5814 fn test_subagent_usage_rollup_over_captured_session() {
5815 use super::SubagentUsageRollup;
5816
5817 let mut rollup = SubagentUsageRollup::default();
5818 let fixture =
5819 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5820 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5821 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5822 rollup.observe(&output);
5823 }
5824 }
5825 assert_eq!(rollup.agent_count, 1);
5826 assert_eq!(rollup.subagent_tokens, 10201);
5827 }
5828
5829 #[test]
5830 fn test_system_message_init() {
5831 let json = r#"{
5832 "type": "system",
5833 "subtype": "init",
5834 "session_id": "test-session-123",
5835 "cwd": "/home/user/project",
5836 "model": "claude-sonnet-4",
5837 "tools": ["Bash", "Read", "Write"],
5838 "mcp_servers": [],
5839 "slash_commands": ["compact", "cost", "review"],
5840 "agents": ["Bash", "Explore", "Plan"],
5841 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5842 "skills": [],
5843 "claude_code_version": "2.1.15",
5844 "apiKeySource": "none",
5845 "output_style": "default",
5846 "permissionMode": "default"
5847 }"#;
5848
5849 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5850 if let ClaudeOutput::System(sys) = output {
5851 assert!(sys.is_init());
5852 assert!(!sys.is_status());
5853 assert!(!sys.is_compact_boundary());
5854
5855 let init = sys.as_init().expect("Should parse as init");
5856 assert_eq!(init.session_id, "test-session-123");
5857 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5858 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5859 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5860 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5861 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5862 assert_eq!(init.plugins.len(), 1);
5863 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5864 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5865 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5866 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5867 assert_eq!(
5868 init.permission_mode,
5869 Some(super::InitPermissionMode::Default)
5870 );
5871 } else {
5872 panic!("Expected System message");
5873 }
5874 }
5875
5876 #[test]
5877 fn test_system_message_init_from_real_capture() {
5878 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5879 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5880 if let ClaudeOutput::System(sys) = output {
5881 let init = sys.as_init().expect("Should parse real init capture");
5882 assert_eq!(init.slash_commands.len(), 8);
5883 assert!(init.slash_commands.contains(&"compact".to_string()));
5884 assert!(init.slash_commands.contains(&"review".to_string()));
5885 assert_eq!(init.agents.len(), 5);
5886 assert!(init.agents.contains(&"Bash".to_string()));
5887 assert!(init.agents.contains(&"Explore".to_string()));
5888 assert_eq!(init.plugins.len(), 1);
5889 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5890 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5891 } else {
5892 panic!("Expected System message");
5893 }
5894 }
5895
5896 #[test]
5897 fn test_system_message_status() {
5898 let json = r#"{
5899 "type": "system",
5900 "subtype": "status",
5901 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5902 "status": "compacting",
5903 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5904 }"#;
5905
5906 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5907 if let ClaudeOutput::System(sys) = output {
5908 assert!(sys.is_status());
5909 assert!(!sys.is_init());
5910
5911 let status = sys.as_status().expect("Should parse as status");
5912 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5913 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5914 assert_eq!(
5915 status.uuid,
5916 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5917 );
5918 } else {
5919 panic!("Expected System message");
5920 }
5921 }
5922
5923 #[test]
5924 fn test_system_message_status_null() {
5925 let json = r#"{
5926 "type": "system",
5927 "subtype": "status",
5928 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5929 "status": null,
5930 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5931 }"#;
5932
5933 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5934 if let ClaudeOutput::System(sys) = output {
5935 let status = sys.as_status().expect("Should parse as status");
5936 assert_eq!(status.status, None);
5937 } else {
5938 panic!("Expected System message");
5939 }
5940 }
5941
5942 #[test]
5943 fn test_system_message_task_started() {
5944 let json = r#"{
5945 "type": "system",
5946 "subtype": "task_started",
5947 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5948 "task_id": "b6daf3f",
5949 "task_type": "local_bash",
5950 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5951 "description": "Wait for CI on PR #12",
5952 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5953 }"#;
5954
5955 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5956 if let ClaudeOutput::System(sys) = output {
5957 assert!(sys.is_task_started());
5958 assert!(!sys.is_task_progress());
5959 assert!(!sys.is_task_notification());
5960
5961 let task = sys.as_task_started().expect("Should parse as task_started");
5962 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5963 assert_eq!(task.task_id, "b6daf3f");
5964 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5965 assert_eq!(
5966 task.tool_use_id.as_deref(),
5967 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5968 );
5969 assert_eq!(task.description, "Wait for CI on PR #12");
5970 } else {
5971 panic!("Expected System message");
5972 }
5973 }
5974
5975 #[test]
5976 fn test_system_message_task_started_agent() {
5977 let json = r#"{
5978 "type": "system",
5979 "subtype": "task_started",
5980 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5981 "task_id": "a4a7e0906e5fc64cc",
5982 "task_type": "local_agent",
5983 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5984 "description": "Explore Scene/ArrayScene duplication",
5985 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5986 }"#;
5987
5988 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5989 if let ClaudeOutput::System(sys) = output {
5990 let task = sys.as_task_started().expect("Should parse as task_started");
5991 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5992 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5993 } else {
5994 panic!("Expected System message");
5995 }
5996 }
5997
5998 #[test]
5999 fn test_system_message_task_progress() {
6000 let json = r#"{
6001 "type": "system",
6002 "subtype": "task_progress",
6003 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6004 "task_id": "a4a7e0906e5fc64cc",
6005 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6006 "description": "Reading src/jplephem/chebyshev.rs",
6007 "last_tool_name": "Read",
6008 "usage": {
6009 "duration_ms": 13996,
6010 "tool_uses": 9,
6011 "total_tokens": 38779
6012 },
6013 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6014 }"#;
6015
6016 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6017 if let ClaudeOutput::System(sys) = output {
6018 assert!(sys.is_task_progress());
6019 assert!(!sys.is_task_started());
6020
6021 let progress = sys
6022 .as_task_progress()
6023 .expect("Should parse as task_progress");
6024 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
6025 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
6026 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
6027 assert_eq!(progress.usage.duration_ms, 13996);
6028 assert_eq!(progress.usage.tool_uses, 9);
6029 assert_eq!(progress.usage.total_tokens, 38779);
6030 } else {
6031 panic!("Expected System message");
6032 }
6033 }
6034
6035 #[test]
6036 fn test_system_message_task_notification_completed() {
6037 let json = r#"{
6038 "type": "system",
6039 "subtype": "task_notification",
6040 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6041 "task_id": "a0ba761e9dc9c316f",
6042 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
6043 "status": "completed",
6044 "summary": "Agent \"Write Hipparcos data source doc\" completed",
6045 "output_file": "",
6046 "usage": {
6047 "duration_ms": 172300,
6048 "tool_uses": 11,
6049 "total_tokens": 42005
6050 },
6051 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
6052 }"#;
6053
6054 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6055 if let ClaudeOutput::System(sys) = output {
6056 assert!(sys.is_task_notification());
6057
6058 let notif = sys
6059 .as_task_notification()
6060 .expect("Should parse as task_notification");
6061 assert_eq!(notif.status, super::TaskStatus::Completed);
6062 assert_eq!(
6063 notif.summary,
6064 "Agent \"Write Hipparcos data source doc\" completed"
6065 );
6066 assert_eq!(notif.output_file, Some("".to_string()));
6067 assert_eq!(
6068 notif.tool_use_id,
6069 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
6070 );
6071 let usage = notif.usage.expect("Should have usage");
6072 assert_eq!(usage.duration_ms, 172300);
6073 assert_eq!(usage.tool_uses, 11);
6074 assert_eq!(usage.total_tokens, 42005);
6075 } else {
6076 panic!("Expected System message");
6077 }
6078 }
6079
6080 #[test]
6081 fn test_system_message_task_notification_failed_no_usage() {
6082 let json = r#"{
6083 "type": "system",
6084 "subtype": "task_notification",
6085 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
6086 "task_id": "b98f6a3",
6087 "status": "failed",
6088 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
6089 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
6090 }"#;
6091
6092 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6093 if let ClaudeOutput::System(sys) = output {
6094 let notif = sys
6095 .as_task_notification()
6096 .expect("Should parse as task_notification");
6097 assert_eq!(notif.status, super::TaskStatus::Failed);
6098 assert!(notif.tool_use_id.is_none());
6099 assert!(notif.usage.is_none());
6100 assert_eq!(
6101 notif.output_file,
6102 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
6103 );
6104 } else {
6105 panic!("Expected System message");
6106 }
6107 }
6108
6109 #[test]
6115 fn test_task_messages_roundtrip_through_value() {
6116 let cases = [
6117 r#"{"type":"system","subtype":"task_started","session_id":"s1",
6118 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
6119 "description":"Sleep 3s","uuid":"u1"}"#,
6120 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
6121 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
6122 "last_tool_name":"Bash",
6123 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6124 "uuid":"u2"}"#,
6125 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
6126 "task_id":"t1","tool_use_id":"tu1","status":"completed",
6127 "summary":"done","output_file":"",
6128 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6129 "uuid":"u3"}"#,
6130 ];
6131
6132 for json in cases {
6133 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6134 let value = serde_json::to_value(&output).unwrap();
6135 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
6136
6137 let ClaudeOutput::System(sys) = reparsed else {
6138 panic!("Expected System variant after round-trip");
6139 };
6140
6141 match sys.subtype {
6142 super::SystemSubtype::TaskStarted => {
6143 assert!(
6144 sys.as_task_started().is_some(),
6145 "as_task_started failed after round-trip"
6146 );
6147 }
6148 super::SystemSubtype::TaskProgress => {
6149 assert!(
6150 sys.as_task_progress().is_some(),
6151 "as_task_progress failed after round-trip"
6152 );
6153 }
6154 super::SystemSubtype::TaskNotification => {
6155 assert!(
6156 sys.as_task_notification().is_some(),
6157 "as_task_notification failed after round-trip"
6158 );
6159 }
6160 other => panic!("unexpected subtype after round-trip: {other:?}"),
6161 }
6162 }
6163 }
6164
6165 #[test]
6166 fn test_system_message_compact_boundary() {
6167 let json = r#"{
6168 "type": "system",
6169 "subtype": "compact_boundary",
6170 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6171 "compact_metadata": {
6172 "pre_tokens": 155285,
6173 "trigger": "auto"
6174 },
6175 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
6176 }"#;
6177
6178 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6179 if let ClaudeOutput::System(sys) = output {
6180 assert!(sys.is_compact_boundary());
6181 assert!(!sys.is_init());
6182 assert!(!sys.is_status());
6183
6184 let compact = sys
6185 .as_compact_boundary()
6186 .expect("Should parse as compact_boundary");
6187 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6188 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
6189 assert_eq!(
6190 compact.compact_metadata.trigger,
6191 super::CompactionTrigger::Auto
6192 );
6193 assert!(compact.summary.is_none());
6195 assert!(compact.leaf_message_count.is_none());
6196 assert!(compact.duration_ms.is_none());
6197 } else {
6198 panic!("Expected System message");
6199 }
6200 }
6201
6202 #[test]
6203 fn test_compact_boundary_with_summary_stats() {
6204 let json = r#"{
6206 "type": "system",
6207 "subtype": "compact_boundary",
6208 "session_id": "s1",
6209 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
6210 "summary": "Summarized the earlier exploration.",
6211 "leaf_message_count": 42,
6212 "duration_ms": 1234,
6213 "uuid": "u1"
6214 }"#;
6215 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6216 let ClaudeOutput::System(sys) = output else {
6217 panic!("Expected System message");
6218 };
6219 let compact = sys.as_compact_boundary().expect("compact_boundary");
6220 assert_eq!(
6221 compact.summary.as_deref(),
6222 Some("Summarized the earlier exploration.")
6223 );
6224 assert_eq!(compact.leaf_message_count, Some(42));
6225 assert_eq!(compact.duration_ms, Some(1234));
6226
6227 let json_alt = r#"{
6230 "type": "system",
6231 "subtype": "compact_boundary",
6232 "session_id": "s2",
6233 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
6234 "content": "alt-key summary",
6235 "message_count": 7
6236 }"#;
6237 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
6238 let ClaudeOutput::System(sys) = output else {
6239 panic!("Expected System message");
6240 };
6241 let compact = sys.as_compact_boundary().expect("compact_boundary");
6242 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
6243 assert_eq!(compact.leaf_message_count, Some(7));
6244 }
6245
6246 #[test]
6247 fn test_init_message_with_new_fields() {
6248 let json = r#"{
6249 "type": "system",
6250 "subtype": "init",
6251 "session_id": "test-session",
6252 "cwd": "/home/user",
6253 "model": "claude-opus-4-7",
6254 "tools": ["Bash"],
6255 "mcp_servers": [],
6256 "permissionMode": "default",
6257 "apiKeySource": "none",
6258 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
6259 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
6260 "fast_mode_state": "off",
6261 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
6262 "claude_code_version": "2.1.117"
6263 }"#;
6264
6265 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6266 if let ClaudeOutput::System(sys) = output {
6267 let init = sys.as_init().expect("Should parse as init");
6268 assert_eq!(
6269 init.uuid.as_deref(),
6270 Some("44841a0d-182d-493a-86b5-79800d3d9665")
6271 );
6272 assert!(init.memory_paths.is_some());
6273 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
6274 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
6275 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
6276 } else {
6277 panic!("Expected System message");
6278 }
6279 }
6280
6281 #[test]
6282 fn test_assistant_message_with_new_fields() {
6283 let json = r#"{
6284 "type": "assistant",
6285 "message": {
6286 "id": "msg_1",
6287 "type": "message",
6288 "role": "assistant",
6289 "model": "claude-opus-4-7",
6290 "content": [{"type": "text", "text": "Hello"}],
6291 "stop_reason": "end_turn",
6292 "stop_details": null,
6293 "context_management": null,
6294 "usage": {
6295 "input_tokens": 100,
6296 "output_tokens": 10,
6297 "cache_creation_input_tokens": 50,
6298 "cache_read_input_tokens": 0,
6299 "service_tier": "standard",
6300 "inference_geo": "not_available"
6301 }
6302 },
6303 "session_id": "abc",
6304 "uuid": "msg-uuid-123"
6305 }"#;
6306
6307 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6308 if let ClaudeOutput::Assistant(asst) = output {
6309 assert_eq!(asst.message.stop_details, None);
6310 assert_eq!(asst.message.context_management, None);
6311 let usage = asst.message.usage.unwrap();
6312 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
6313 } else {
6314 panic!("Expected Assistant message");
6315 }
6316 }
6317
6318 #[test]
6319 fn test_user_message_with_new_fields() {
6320 let json = r#"{
6321 "type": "user",
6322 "message": {
6323 "role": "user",
6324 "content": [{"type": "text", "text": "Hello"}]
6325 },
6326 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6327 "parent_tool_use_id": "toolu_123",
6328 "uuid": "user-msg-456"
6329 }"#;
6330
6331 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6332 if let ClaudeOutput::User(user) = output {
6333 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
6334 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
6335 } else {
6336 panic!("Expected User message");
6337 }
6338 }
6339
6340 #[test]
6346 fn test_user_message_preserves_tool_use_result_and_timestamp() {
6347 let json = r#"{
6348 "type":"user",
6349 "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"}]},
6350 "parent_tool_use_id":null,
6351 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6352 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
6353 "timestamp":"2026-05-12T23:12:04.121Z",
6354 "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"}}
6355 }"#;
6356
6357 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6358 let user = match output {
6359 ClaudeOutput::User(u) => u,
6360 other => panic!("Expected User message, got {:?}", other.message_type()),
6361 };
6362
6363 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
6364 let raw = user
6365 .tool_use_result
6366 .as_ref()
6367 .expect("tool_use_result must be captured");
6368 assert_eq!(raw["answers"]["Color"], "Blue");
6369 assert_eq!(raw["questions"][0]["header"], "Color");
6370
6371 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
6375 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
6376 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
6377 assert_eq!(
6378 reser["tool_use_result"]["questions"][0]["question"],
6379 "Which color do you prefer?"
6380 );
6381
6382 let typed: crate::AskUserQuestionInput = user
6385 .tool_use_result_as::<crate::AskUserQuestionInput>()
6386 .expect("tool_use_result present")
6387 .expect("AskUserQuestionInput parses");
6388 assert_eq!(typed.questions.len(), 1);
6389 assert_eq!(typed.questions[0].header, "Color");
6390 let answers = typed.answers.expect("answers populated");
6391 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
6392 }
6393
6394 #[test]
6397 fn test_user_message_without_tool_use_result_omits_field() {
6398 let json = r#"{
6399 "type":"user",
6400 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
6401 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
6402 }"#;
6403
6404 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6405 let user = match output {
6406 ClaudeOutput::User(u) => u,
6407 _ => panic!("Expected User message"),
6408 };
6409 assert!(user.tool_use_result.is_none());
6410 assert!(user.timestamp.is_none());
6411
6412 let reser = serde_json::to_value(&user).unwrap();
6413 assert!(reser.get("tool_use_result").is_none());
6414 assert!(reser.get("timestamp").is_none());
6415 }
6416
6417 #[test]
6421 fn test_subagent_result_exposes_token_accounting() {
6422 let json = r#"{
6423 "type":"user",
6424 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
6425 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
6426 "tool_use_result":{
6427 "status":"completed",
6428 "prompt":"Count the .rs files.",
6429 "agentId":"ac4f0276e9d4b6232",
6430 "agentType":"Explore",
6431 "content":[{"type":"text","text":"21"}],
6432 "resolvedModel":"claude-haiku-4-5-20251001",
6433 "totalDurationMs":6869,
6434 "totalTokens":7834,
6435 "totalToolUseCount":1,
6436 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
6437 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
6438 }
6439 }"#;
6440
6441 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6442 let user = match output {
6443 ClaudeOutput::User(u) => u,
6444 _ => panic!("Expected User message"),
6445 };
6446
6447 let result = user.subagent_result().expect("subagent result parses");
6448 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
6449 assert_eq!(
6450 result.resolved_model.as_deref(),
6451 Some("claude-haiku-4-5-20251001")
6452 );
6453 assert_eq!(result.total_tokens, Some(7834));
6454 assert_eq!(result.total_duration_ms, Some(6869));
6455 assert_eq!(result.total_tool_use_count, Some(1));
6456
6457 let usage = result.usage.expect("nested usage present");
6458 assert_eq!(usage.input_tokens, 6);
6459 assert_eq!(usage.cache_read_input_tokens, 7699);
6460
6461 let stats = result.tool_stats.expect("toolStats present");
6462 assert_eq!(stats.bash_count, 1);
6463 }
6464
6465 #[test]
6469 fn test_subagent_result_absent_for_non_task_result() {
6470 let json = r#"{
6471 "type":"user",
6472 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
6473 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6474 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
6475 }"#;
6476
6477 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6478 let user = match output {
6479 ClaudeOutput::User(u) => u,
6480 _ => panic!("Expected User message"),
6481 };
6482
6483 let result = user.subagent_result().expect("lenient parse");
6484 assert_eq!(result.total_tokens, None);
6485 assert_eq!(result.agent_type, None);
6486 }
6487
6488 #[test]
6489 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
6490 use serde_json::Value;
6491
6492 let raw: Value = serde_json::from_str(
6493 r#"{
6494 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
6495 "fast_mode_state":"off",
6496 "fast_mode_disabled_reason":"not_first_party",
6497 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
6498 }"#,
6499 )
6500 .unwrap();
6501 crate::io::assert_fully_wrapped(&raw);
6502
6503 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6504 let ClaudeOutput::System(sys) = output else {
6505 panic!("expected System");
6506 };
6507 let init = sys.as_init().expect("parses as init");
6508 assert_eq!(
6509 init.fast_mode_disabled_reason,
6510 Some(crate::FastModeDisabledReason::NotFirstParty)
6511 );
6512 let errs = init.mcp_server_errors.unwrap();
6513 assert_eq!(errs.len(), 1);
6514 assert_eq!(errs[0].name, "broken");
6515 assert_eq!(errs[0].error_type, "invalid_config");
6516 }
6517
6518 #[test]
6519 fn test_code_change_published_fully_wrapped() {
6520 use super::{KnownSystemEvent, SystemSubtype};
6521 use serde_json::Value;
6522
6523 let raw: Value = serde_json::from_str(
6524 r#"{
6525 "type":"system","subtype":"code_change_published",
6526 "provider":"github","url":"https://github.com/owner/repo/pull/42",
6527 "repo":"owner/repo","identifier":"42",
6528 "uuid":"u1","session_id":"s1"
6529 }"#,
6530 )
6531 .unwrap();
6532 crate::io::assert_fully_wrapped(&raw);
6533
6534 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6535 let ClaudeOutput::System(sys) = output else {
6536 panic!("expected System");
6537 };
6538 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
6539 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
6540 panic!("expected CodeChangePublished event");
6541 };
6542 assert_eq!(msg.provider, "github");
6543 assert_eq!(msg.repo, "owner/repo");
6544 assert_eq!(msg.identifier, "42");
6545
6546 assert!(sys.is_code_change_published());
6547 assert!(!sys.is_vcs_state_changed());
6548 let direct = sys.as_code_change_published().expect("direct accessor");
6549 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
6550 assert!(sys.as_vcs_state_changed().is_none());
6551 }
6552
6553 #[test]
6554 fn test_feedback_draft_queued_fully_wrapped() {
6555 use super::{KnownSystemEvent, SystemSubtype};
6556 use serde_json::Value;
6557
6558 let raw: Value = serde_json::from_str(
6559 r#"{
6560 "type":"system","subtype":"feedback_draft_queued",
6561 "draft_id":"draft-1","draft_type":"bug_report",
6562 "title":"Tool output was truncated",
6563 "details_preview":"The last command omitted its final lines",
6564 "uuid":"u1","session_id":"s1","future_field":"preserved"
6565 }"#,
6566 )
6567 .unwrap();
6568 crate::io::assert_fully_wrapped(&raw);
6569
6570 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6571 let ClaudeOutput::System(sys) = output else {
6572 panic!("expected System");
6573 };
6574 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
6575 assert!(sys.is_feedback_draft_queued());
6576 assert!(!sys.is_vcs_state_changed());
6577
6578 let direct = sys
6579 .as_feedback_draft_queued()
6580 .expect("direct typed accessor");
6581 assert_eq!(direct.draft_id, "draft-1");
6582 assert_eq!(direct.draft_type, "bug_report");
6583 assert_eq!(direct.extra["future_field"], "preserved");
6584
6585 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
6586 panic!("expected FeedbackDraftQueued event");
6587 };
6588 assert_eq!(known.title, "Tool output was truncated");
6589 assert_eq!(
6590 sys.typed_value().expect("typed value")["future_field"],
6591 "preserved"
6592 );
6593 }
6594
6595 #[test]
6596 fn test_cloud_session_delta_fully_wrapped() {
6597 use super::{KnownSystemEvent, SystemSubtype};
6598 use serde_json::Value;
6599
6600 let raw: Value = serde_json::from_str(
6601 r#"{
6602 "type":"system","subtype":"cloud_session_delta",
6603 "seq":3,"changed":["serving","connection"],
6604 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
6605 "serving":{"state":"on"},"connection":{"state":"live"}},
6606 "uuid":"u1","session_id":"s1","future_field":"preserved"
6607 }"#,
6608 )
6609 .unwrap();
6610 crate::io::assert_fully_wrapped(&raw);
6611
6612 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6613 let ClaudeOutput::System(sys) = output else {
6614 panic!("expected System");
6615 };
6616 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
6617 assert!(sys.is_cloud_session_delta());
6618 assert!(!sys.is_feedback_draft_queued());
6619
6620 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
6621 assert_eq!(direct.seq, 3);
6622 assert_eq!(direct.changed, vec!["serving", "connection"]);
6623 assert_eq!(direct.cloud_session["id"], "session_abc");
6624 assert_eq!(direct.extra["future_field"], "preserved");
6625
6626 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
6627 panic!("expected CloudSessionDelta event");
6628 };
6629 assert_eq!(known.session_id, "s1");
6630 assert_eq!(
6631 sys.typed_value().expect("typed value")["future_field"],
6632 "preserved"
6633 );
6634 }
6635
6636 #[test]
6637 fn test_vcs_state_changed_fully_wrapped() {
6638 use super::{KnownSystemEvent, VcsMutationKind};
6639 use serde_json::Value;
6640
6641 for kind in ["commit", "push", "merge", "rebase"] {
6642 let raw: Value = serde_json::from_str(&format!(
6643 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
6644 kind
6645 ))
6646 .unwrap();
6647 crate::io::assert_fully_wrapped(&raw);
6648
6649 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6650 let ClaudeOutput::System(sys) = output else {
6651 panic!("expected System");
6652 };
6653 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6654 panic!("expected VcsStateChanged event");
6655 };
6656 assert_eq!(msg.kind.as_str(), kind);
6657 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
6658 }
6659
6660 let raw: Value = serde_json::from_str(
6662 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
6663 )
6664 .unwrap();
6665 crate::io::assert_fully_wrapped(&raw);
6666 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6667 let ClaudeOutput::System(sys) = output else {
6668 panic!("expected System");
6669 };
6670 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6671 panic!("expected VcsStateChanged event");
6672 };
6673 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
6674
6675 assert!(sys.is_vcs_state_changed());
6676 let direct = sys.as_vcs_state_changed().expect("direct accessor");
6677 assert_eq!(direct.cwd, "/repo");
6678 assert!(sys.as_code_change_published().is_none());
6679 }
6680
6681 #[test]
6682 fn test_assistant_aborted_and_resume_flags_roundtrip() {
6683 let json = r#"{
6684 "type":"assistant",
6685 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
6686 "session_id":"s1",
6687 "aborted":true,
6688 "resumed_from_incomplete_thinking":true
6689 }"#;
6690 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6691 let ClaudeOutput::Assistant(msg) = &output else {
6692 panic!("expected Assistant");
6693 };
6694 assert_eq!(msg.aborted, Some(true));
6695 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
6696 let reserialized = serde_json::to_string(&output).unwrap();
6697 assert!(reserialized.contains("\"aborted\":true"));
6698 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
6699
6700 let json = r#"{
6702 "type":"assistant",
6703 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
6704 "session_id":"s2"
6705 }"#;
6706 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6707 let reserialized = serde_json::to_string(&output).unwrap();
6708 assert!(!reserialized.contains("aborted"));
6709 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
6710 }
6711
6712 #[test]
6713 fn test_user_tool_result_meta_roundtrip() {
6714 let json = r#"{
6715 "type":"user",
6716 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6717 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6718 "tool_result_meta":[
6719 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6720 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6721 ]
6722 }"#;
6723 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6724 let ClaudeOutput::User(user) = &output else {
6725 panic!("expected User");
6726 };
6727 let meta = user.tool_result_meta.as_ref().unwrap();
6728 assert_eq!(meta.len(), 2);
6729 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6730 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6731 assert_eq!(meta[1].user_feedback, None);
6732
6733 let reserialized = serde_json::to_string(&output).unwrap();
6734 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6735 assert!(!reserialized.contains("\"user_feedback\":null"));
6736 }
6737
6738 #[test]
6742 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6743 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6744 let with_scope = serde_json::json!({
6745 "trigger": "refusal",
6746 "direction": "retry",
6747 "scope": "local",
6748 "original_model": "claude-fable-5",
6749 "fallback_model": "claude-opus-5",
6750 "request_id": null,
6751 "content": "Refused; retried on fallback model.",
6752 "uuid": "u1",
6753 "session_id": "s1"
6754 });
6755 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6756 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6757 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6758
6759 let mut without = with_scope.clone();
6762 without.as_object_mut().unwrap().remove("scope");
6763 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6764 assert_eq!(msg.scope, None);
6765 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6766
6767 assert_eq!(
6769 RefusalFallbackScope::from("workspace").as_str(),
6770 "workspace"
6771 );
6772 }
6773}