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
2957#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2961pub struct FooterIndicator {
2962 pub text: String,
2965}
2966
2967#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2970pub enum ViewMode {
2971 Focus,
2974 Default,
2976 Unknown(String),
2978}
2979
2980impl ViewMode {
2981 pub fn as_str(&self) -> &str {
2982 match self {
2983 Self::Focus => "focus",
2984 Self::Default => "default",
2985 Self::Unknown(s) => s.as_str(),
2986 }
2987 }
2988}
2989
2990impl fmt::Display for ViewMode {
2991 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2992 f.write_str(self.as_str())
2993 }
2994}
2995
2996impl From<&str> for ViewMode {
2997 fn from(s: &str) -> Self {
2998 match s {
2999 "focus" => Self::Focus,
3000 "default" => Self::Default,
3001 other => Self::Unknown(other.to_string()),
3002 }
3003 }
3004}
3005
3006impl Serialize for ViewMode {
3007 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3008 serializer.serialize_str(self.as_str())
3009 }
3010}
3011
3012impl<'de> Deserialize<'de> for ViewMode {
3013 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3014 let s = String::deserialize(deserializer)?;
3015 Ok(Self::from(s.as_str()))
3016 }
3017}
3018
3019#[derive(Debug, Clone, Serialize, Deserialize)]
3021pub struct StatusMessage {
3022 pub session_id: String,
3024 pub status: Option<StatusMessageStatus>,
3026 #[serde(skip_serializing_if = "Option::is_none")]
3028 pub uuid: Option<String>,
3029 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3031 pub permission_mode: Option<InitPermissionMode>,
3032 #[serde(skip_serializing_if = "Option::is_none")]
3033 pub compact_result: Option<String>,
3034 #[serde(skip_serializing_if = "Option::is_none")]
3035 pub compact_error: Option<String>,
3036 #[serde(default, skip_serializing_if = "Option::is_none")]
3043 pub compact_output_chars: Option<u64>,
3044}
3045
3046#[derive(Debug, Clone, Serialize, Deserialize)]
3048pub struct CompactBoundaryMessage {
3049 pub session_id: String,
3051 pub compact_metadata: CompactMetadata,
3053 #[serde(
3057 default,
3058 skip_serializing_if = "Option::is_none",
3059 alias = "content",
3060 alias = "text"
3061 )]
3062 pub summary: Option<String>,
3063 #[serde(
3067 default,
3068 skip_serializing_if = "Option::is_none",
3069 alias = "message_count"
3070 )]
3071 pub leaf_message_count: Option<u32>,
3072 #[serde(default, skip_serializing_if = "Option::is_none")]
3074 pub duration_ms: Option<u64>,
3075 #[serde(skip_serializing_if = "Option::is_none")]
3077 pub uuid: Option<String>,
3078 #[serde(skip_serializing_if = "Option::is_none")]
3080 pub logical_parent_uuid: Option<Option<String>>,
3081 #[serde(default, skip_serializing_if = "Option::is_none")]
3085 pub historical: Option<bool>,
3086}
3087
3088#[derive(Debug, Clone, Serialize, Deserialize)]
3090pub struct CompactMetadata {
3091 pub pre_tokens: u64,
3093 pub trigger: CompactionTrigger,
3095 #[serde(default, skip_serializing_if = "Option::is_none")]
3096 pub post_tokens: Option<u64>,
3097 #[serde(default, skip_serializing_if = "Option::is_none")]
3098 pub cumulative_dropped_tokens: Option<u64>,
3099 #[serde(default, skip_serializing_if = "Option::is_none")]
3100 pub duration_ms: Option<u64>,
3101 #[serde(default, skip_serializing_if = "Option::is_none")]
3102 pub user_context: Option<String>,
3103 #[serde(default, skip_serializing_if = "Option::is_none")]
3104 pub messages_summarized: Option<u64>,
3105 #[serde(default, skip_serializing_if = "Option::is_none")]
3106 pub precomputed: Option<bool>,
3107 #[serde(default, skip_serializing_if = "Option::is_none")]
3108 pub pre_compact_discovered_tools: Option<Vec<String>>,
3109 #[serde(default, skip_serializing_if = "Option::is_none")]
3114 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
3115 #[serde(default, skip_serializing_if = "Option::is_none")]
3116 pub preserved_segment: Option<PreservedSegment>,
3117 #[serde(default, skip_serializing_if = "Option::is_none")]
3118 pub preserved_messages: Option<PreservedMessages>,
3119}
3120
3121#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3124pub struct ArtifactReadVersion {
3125 pub slug: String,
3127 pub ver: String,
3129}
3130
3131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3132pub struct PreservedSegment {
3133 pub head_uuid: String,
3134 pub anchor_uuid: String,
3135 pub tail_uuid: String,
3136}
3137
3138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3139pub struct PreservedMessages {
3140 pub anchor_uuid: String,
3141 pub uuids: Vec<String>,
3142 #[serde(default, skip_serializing_if = "Option::is_none")]
3143 pub all_uuids: Option<Vec<String>>,
3144}
3145
3146#[derive(Debug, Clone, Serialize, Deserialize)]
3152pub struct TaskUsage {
3153 pub duration_ms: u64,
3155 pub tool_uses: u64,
3157 pub total_tokens: u64,
3159}
3160
3161#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3163pub enum TaskType {
3164 LocalAgent,
3166 LocalBash,
3168 LocalWorkflow,
3170 Unknown(String),
3172}
3173
3174impl TaskType {
3175 pub fn as_str(&self) -> &str {
3176 match self {
3177 Self::LocalAgent => "local_agent",
3178 Self::LocalBash => "local_bash",
3179 Self::LocalWorkflow => "local_workflow",
3180 Self::Unknown(s) => s.as_str(),
3181 }
3182 }
3183}
3184
3185impl fmt::Display for TaskType {
3186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3187 f.write_str(self.as_str())
3188 }
3189}
3190
3191impl From<&str> for TaskType {
3192 fn from(s: &str) -> Self {
3193 match s {
3194 "local_agent" => Self::LocalAgent,
3195 "local_bash" => Self::LocalBash,
3196 "local_workflow" => Self::LocalWorkflow,
3197 other => Self::Unknown(other.to_string()),
3198 }
3199 }
3200}
3201
3202impl Serialize for TaskType {
3203 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3204 serializer.serialize_str(self.as_str())
3205 }
3206}
3207
3208impl<'de> Deserialize<'de> for TaskType {
3209 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3210 let s = String::deserialize(deserializer)?;
3211 Ok(Self::from(s.as_str()))
3212 }
3213}
3214
3215#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3217pub enum TaskStatus {
3218 Pending,
3219 Running,
3220 Completed,
3221 Failed,
3222 Killed,
3223 Paused,
3224 Stopped,
3225 Unknown(String),
3226}
3227
3228impl TaskStatus {
3229 pub fn as_str(&self) -> &str {
3230 match self {
3231 Self::Pending => "pending",
3232 Self::Running => "running",
3233 Self::Completed => "completed",
3234 Self::Failed => "failed",
3235 Self::Killed => "killed",
3236 Self::Paused => "paused",
3237 Self::Stopped => "stopped",
3238 Self::Unknown(s) => s.as_str(),
3239 }
3240 }
3241}
3242
3243impl fmt::Display for TaskStatus {
3244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3245 f.write_str(self.as_str())
3246 }
3247}
3248
3249impl From<&str> for TaskStatus {
3250 fn from(s: &str) -> Self {
3251 match s {
3252 "pending" => Self::Pending,
3253 "running" => Self::Running,
3254 "completed" => Self::Completed,
3255 "failed" => Self::Failed,
3256 "killed" => Self::Killed,
3257 "paused" => Self::Paused,
3258 "stopped" => Self::Stopped,
3259 other => Self::Unknown(other.to_string()),
3260 }
3261 }
3262}
3263
3264impl Serialize for TaskStatus {
3265 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3266 serializer.serialize_str(self.as_str())
3267 }
3268}
3269
3270impl<'de> Deserialize<'de> for TaskStatus {
3271 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3272 let s = String::deserialize(deserializer)?;
3273 Ok(Self::from(s.as_str()))
3274 }
3275}
3276
3277#[derive(Debug, Clone, Serialize, Deserialize)]
3279pub struct TaskStartedMessage {
3280 pub session_id: String,
3281 pub task_id: String,
3282 #[serde(default, skip_serializing_if = "Option::is_none")]
3288 pub run_id: Option<String>,
3289 #[serde(default, skip_serializing_if = "Option::is_none")]
3295 pub parent_task_id: Option<String>,
3296 #[serde(default, skip_serializing_if = "Option::is_none")]
3297 pub task_type: Option<TaskType>,
3298 #[serde(default, skip_serializing_if = "Option::is_none")]
3299 pub tool_use_id: Option<String>,
3300 pub description: String,
3301 #[serde(default, skip_serializing_if = "Option::is_none")]
3304 pub subagent_type: Option<String>,
3305 #[serde(default, skip_serializing_if = "Option::is_none")]
3311 pub is_backgrounded: Option<bool>,
3312 #[serde(default, skip_serializing_if = "Option::is_none")]
3320 pub awaited: Option<bool>,
3321 #[serde(default, skip_serializing_if = "Option::is_none")]
3325 pub spawn_depth: Option<u32>,
3326 #[serde(default, skip_serializing_if = "Option::is_none")]
3328 pub prompt: Option<String>,
3329 #[serde(default, skip_serializing_if = "Option::is_none")]
3330 pub workflow_name: Option<String>,
3331 #[serde(default, skip_serializing_if = "Option::is_none")]
3332 pub skip_transcript: Option<bool>,
3333 #[serde(default, skip_serializing_if = "Option::is_none")]
3338 pub ambient: Option<bool>,
3339 pub uuid: String,
3340}
3341
3342#[derive(Debug, Clone, Serialize, Deserialize)]
3346pub struct TaskUpdatedMessage {
3347 pub session_id: String,
3348 pub task_id: String,
3349 #[serde(default, skip_serializing_if = "Option::is_none")]
3352 pub run_id: Option<String>,
3353 pub patch: TaskPatch,
3354 pub uuid: String,
3355}
3356
3357#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3360pub struct TaskPatch {
3361 #[serde(default, skip_serializing_if = "Option::is_none")]
3362 pub status: Option<TaskStatus>,
3363 #[serde(default, skip_serializing_if = "Option::is_none")]
3366 pub end_time: Option<u64>,
3367 #[serde(default, skip_serializing_if = "Option::is_none")]
3368 pub description: Option<String>,
3369 #[serde(default, skip_serializing_if = "Option::is_none")]
3370 pub total_paused_ms: Option<u64>,
3371 #[serde(default, skip_serializing_if = "Option::is_none")]
3372 pub error: Option<String>,
3373 #[serde(default, skip_serializing_if = "Option::is_none")]
3374 pub is_backgrounded: Option<bool>,
3375}
3376
3377#[derive(Debug, Clone, Serialize, Deserialize)]
3380pub struct ThinkingTokensMessage {
3381 pub session_id: String,
3382 pub estimated_tokens: u64,
3384 pub estimated_tokens_delta: u64,
3386 #[serde(default, skip_serializing_if = "Option::is_none")]
3393 pub user_message_uuid: Option<String>,
3394 pub uuid: String,
3395}
3396
3397#[derive(Debug, Clone, Serialize, Deserialize)]
3400pub struct TaskProgressMessage {
3401 pub session_id: String,
3402 pub task_id: String,
3403 #[serde(default, skip_serializing_if = "Option::is_none")]
3406 pub run_id: Option<String>,
3407 #[serde(default, skip_serializing_if = "Option::is_none")]
3408 pub tool_use_id: Option<String>,
3409 pub description: String,
3410 #[serde(default, skip_serializing_if = "Option::is_none")]
3411 pub last_tool_name: Option<String>,
3412 pub usage: TaskUsage,
3413 #[serde(default, skip_serializing_if = "Option::is_none")]
3415 pub subagent_type: Option<String>,
3416 #[serde(default, skip_serializing_if = "Option::is_none")]
3417 pub summary: Option<String>,
3418 pub uuid: String,
3419}
3420
3421#[derive(Debug, Clone, Serialize, Deserialize)]
3424pub struct TaskNotificationMessage {
3425 pub session_id: String,
3426 pub task_id: String,
3427 #[serde(default, skip_serializing_if = "Option::is_none")]
3430 pub run_id: Option<String>,
3431 pub status: TaskStatus,
3432 #[serde(default, skip_serializing_if = "Option::is_none")]
3435 pub reason: Option<TaskEndReason>,
3436 pub summary: String,
3437 pub output_file: Option<String>,
3438 #[serde(skip_serializing_if = "Option::is_none")]
3439 pub tool_use_id: Option<String>,
3440 #[serde(skip_serializing_if = "Option::is_none")]
3441 pub usage: Option<TaskUsage>,
3442 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3448 pub resource_links: Vec<ResourceLink>,
3449 #[serde(default, skip_serializing_if = "Option::is_none")]
3456 pub handback: Option<TaskHandback>,
3457 #[serde(default, skip_serializing_if = "Option::is_none")]
3461 pub handback_report: Option<HandbackReport>,
3462 #[serde(default, skip_serializing_if = "Option::is_none")]
3463 pub skip_transcript: Option<bool>,
3464 #[serde(default, skip_serializing_if = "Option::is_none")]
3467 pub ambient: Option<bool>,
3468 #[serde(skip_serializing_if = "Option::is_none")]
3469 pub uuid: Option<String>,
3470}
3471
3472#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3475pub enum TaskHandback {
3476 Send,
3478 Flagged,
3480 Withheld,
3482 Unknown(String),
3484}
3485
3486impl TaskHandback {
3487 pub fn as_str(&self) -> &str {
3488 match self {
3489 Self::Send => "send",
3490 Self::Flagged => "flagged",
3491 Self::Withheld => "withheld",
3492 Self::Unknown(s) => s.as_str(),
3493 }
3494 }
3495}
3496
3497impl fmt::Display for TaskHandback {
3498 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3499 f.write_str(self.as_str())
3500 }
3501}
3502
3503impl From<&str> for TaskHandback {
3504 fn from(s: &str) -> Self {
3505 match s {
3506 "send" => Self::Send,
3507 "flagged" => Self::Flagged,
3508 "withheld" => Self::Withheld,
3509 other => Self::Unknown(other.to_string()),
3510 }
3511 }
3512}
3513
3514impl Serialize for TaskHandback {
3515 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3516 serializer.serialize_str(self.as_str())
3517 }
3518}
3519
3520impl<'de> Deserialize<'de> for TaskHandback {
3521 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3522 let s = String::deserialize(deserializer)?;
3523 Ok(Self::from(s.as_str()))
3524 }
3525}
3526
3527#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3530pub struct HandbackReport {
3531 pub text: String,
3534 #[serde(default, skip_serializing_if = "Option::is_none")]
3537 pub warning: Option<String>,
3538}
3539
3540#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3542pub enum AssistantErrorKind {
3543 AuthenticationFailed,
3544 OauthOrgNotAllowed,
3545 AccountOnHold,
3546 BillingError,
3547 RateLimit,
3548 Overloaded,
3549 InvalidRequest,
3550 ModelNotFound,
3551 ServerError,
3552 UnknownError,
3553 MaxOutputTokens,
3554 Unknown(String),
3555}
3556
3557impl AssistantErrorKind {
3558 pub fn as_str(&self) -> &str {
3559 match self {
3560 Self::AuthenticationFailed => "authentication_failed",
3561 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3562 Self::AccountOnHold => "account_on_hold",
3563 Self::BillingError => "billing_error",
3564 Self::RateLimit => "rate_limit",
3565 Self::Overloaded => "overloaded",
3566 Self::InvalidRequest => "invalid_request",
3567 Self::ModelNotFound => "model_not_found",
3568 Self::ServerError => "server_error",
3569 Self::UnknownError => "unknown",
3570 Self::MaxOutputTokens => "max_output_tokens",
3571 Self::Unknown(s) => s.as_str(),
3572 }
3573 }
3574}
3575
3576impl fmt::Display for AssistantErrorKind {
3577 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3578 f.write_str(self.as_str())
3579 }
3580}
3581
3582impl From<&str> for AssistantErrorKind {
3583 fn from(s: &str) -> Self {
3584 match s {
3585 "authentication_failed" => Self::AuthenticationFailed,
3586 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3587 "account_on_hold" => Self::AccountOnHold,
3588 "billing_error" => Self::BillingError,
3589 "rate_limit" => Self::RateLimit,
3590 "overloaded" => Self::Overloaded,
3591 "invalid_request" => Self::InvalidRequest,
3592 "model_not_found" => Self::ModelNotFound,
3593 "server_error" => Self::ServerError,
3594 "unknown" => Self::UnknownError,
3595 "max_output_tokens" => Self::MaxOutputTokens,
3596 other => Self::Unknown(other.to_string()),
3597 }
3598 }
3599}
3600
3601impl Serialize for AssistantErrorKind {
3602 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3603 serializer.serialize_str(self.as_str())
3604 }
3605}
3606
3607impl<'de> Deserialize<'de> for AssistantErrorKind {
3608 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3609 let s = String::deserialize(deserializer)?;
3610 Ok(Self::from(s.as_str()))
3611 }
3612}
3613
3614#[derive(Debug, Clone, Serialize, Deserialize)]
3621pub struct CodeChangePublishedMessage {
3622 pub provider: String,
3626 pub url: String,
3628 pub repo: String,
3631 pub identifier: String,
3633 #[serde(default, skip_serializing_if = "Option::is_none")]
3642 pub action: Option<String>,
3643 #[serde(default, skip_serializing_if = "Option::is_none")]
3651 pub branch: Option<String>,
3652 pub uuid: String,
3653 pub session_id: String,
3654}
3655
3656#[derive(Debug, Clone, Serialize, Deserialize)]
3661pub struct VcsStateChangedMessage {
3662 pub kind: VcsMutationKind,
3665 pub cwd: String,
3668 #[serde(default, skip_serializing_if = "Option::is_none")]
3673 pub branch: Option<String>,
3674 pub uuid: String,
3675 pub session_id: String,
3676}
3677
3678#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3680pub enum VcsMutationKind {
3681 Commit,
3682 Push,
3683 Merge,
3684 Rebase,
3685 Unknown(String),
3687}
3688
3689impl VcsMutationKind {
3690 pub fn as_str(&self) -> &str {
3691 match self {
3692 Self::Commit => "commit",
3693 Self::Push => "push",
3694 Self::Merge => "merge",
3695 Self::Rebase => "rebase",
3696 Self::Unknown(s) => s.as_str(),
3697 }
3698 }
3699}
3700
3701impl fmt::Display for VcsMutationKind {
3702 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3703 f.write_str(self.as_str())
3704 }
3705}
3706
3707impl From<&str> for VcsMutationKind {
3708 fn from(s: &str) -> Self {
3709 match s {
3710 "commit" => Self::Commit,
3711 "push" => Self::Push,
3712 "merge" => Self::Merge,
3713 "rebase" => Self::Rebase,
3714 other => Self::Unknown(other.to_string()),
3715 }
3716 }
3717}
3718
3719impl Serialize for VcsMutationKind {
3720 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3721 serializer.serialize_str(self.as_str())
3722 }
3723}
3724
3725impl<'de> Deserialize<'de> for VcsMutationKind {
3726 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3727 let s = String::deserialize(deserializer)?;
3728 Ok(Self::from(s.as_str()))
3729 }
3730}
3731
3732#[derive(Debug, Clone, Serialize, Deserialize)]
3734pub struct FeedbackDraftQueuedMessage {
3735 pub draft_id: String,
3736 pub draft_type: String,
3737 pub title: String,
3738 pub details_preview: String,
3739 #[serde(default, skip_serializing_if = "Option::is_none")]
3740 pub uuid: Option<String>,
3741 #[serde(default, skip_serializing_if = "Option::is_none")]
3742 pub session_id: Option<String>,
3743 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3744 pub extra: serde_json::Map<String, Value>,
3745}
3746
3747#[derive(Debug, Clone, Serialize, Deserialize)]
3755pub struct CloudSessionDeltaMessage {
3756 pub seq: u64,
3760 pub changed: Vec<String>,
3764 pub cloud_session: Value,
3768 pub uuid: String,
3769 pub session_id: String,
3770 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3771 pub extra: serde_json::Map<String, Value>,
3772}
3773
3774#[derive(Debug, Clone, Serialize, Deserialize)]
3790pub struct DevIntentMessage {
3791 pub kind: DevIntentKind,
3793 #[serde(default, skip_serializing_if = "Option::is_none")]
3799 pub trigger: Option<DevIntentTrigger>,
3800 pub uuid: String,
3801 pub session_id: String,
3802 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3803 pub extra: serde_json::Map<String, Value>,
3804}
3805
3806#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3810pub enum DevIntentKind {
3811 IosApp,
3817 AndroidApp,
3824 Unknown(String),
3826}
3827
3828impl DevIntentKind {
3829 pub fn as_str(&self) -> &str {
3830 match self {
3831 Self::IosApp => "ios_app",
3832 Self::AndroidApp => "android_app",
3833 Self::Unknown(s) => s.as_str(),
3834 }
3835 }
3836}
3837
3838impl fmt::Display for DevIntentKind {
3839 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3840 f.write_str(self.as_str())
3841 }
3842}
3843
3844impl From<&str> for DevIntentKind {
3845 fn from(s: &str) -> Self {
3846 match s {
3847 "ios_app" => Self::IosApp,
3848 "android_app" => Self::AndroidApp,
3849 other => Self::Unknown(other.to_string()),
3850 }
3851 }
3852}
3853
3854impl Serialize for DevIntentKind {
3855 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3856 serializer.serialize_str(self.as_str())
3857 }
3858}
3859
3860impl<'de> Deserialize<'de> for DevIntentKind {
3861 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3862 let s = String::deserialize(deserializer)?;
3863 Ok(Self::from(s.as_str()))
3864 }
3865}
3866
3867#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3871pub enum DevIntentTrigger {
3872 SwiftEdit,
3874 UikitImport,
3876 XcodeProject,
3879 IosCommand,
3882 KotlinEdit,
3884 JavaEdit,
3886 AndroidImport,
3888 AndroidManifest,
3890 GradlePlugin,
3892 AndroidCommand,
3895 ProjectScan,
3898 Unknown(String),
3900}
3901
3902impl DevIntentTrigger {
3903 pub fn as_str(&self) -> &str {
3904 match self {
3905 Self::SwiftEdit => "swift_edit",
3906 Self::UikitImport => "uikit_import",
3907 Self::XcodeProject => "xcode_project",
3908 Self::IosCommand => "ios_command",
3909 Self::KotlinEdit => "kotlin_edit",
3910 Self::JavaEdit => "java_edit",
3911 Self::AndroidImport => "android_import",
3912 Self::AndroidManifest => "android_manifest",
3913 Self::GradlePlugin => "gradle_plugin",
3914 Self::AndroidCommand => "android_command",
3915 Self::ProjectScan => "project_scan",
3916 Self::Unknown(s) => s.as_str(),
3917 }
3918 }
3919}
3920
3921impl fmt::Display for DevIntentTrigger {
3922 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3923 f.write_str(self.as_str())
3924 }
3925}
3926
3927impl From<&str> for DevIntentTrigger {
3928 fn from(s: &str) -> Self {
3929 match s {
3930 "swift_edit" => Self::SwiftEdit,
3931 "uikit_import" => Self::UikitImport,
3932 "xcode_project" => Self::XcodeProject,
3933 "ios_command" => Self::IosCommand,
3934 "kotlin_edit" => Self::KotlinEdit,
3935 "java_edit" => Self::JavaEdit,
3936 "android_import" => Self::AndroidImport,
3937 "android_manifest" => Self::AndroidManifest,
3938 "gradle_plugin" => Self::GradlePlugin,
3939 "android_command" => Self::AndroidCommand,
3940 "project_scan" => Self::ProjectScan,
3941 other => Self::Unknown(other.to_string()),
3942 }
3943 }
3944}
3945
3946impl Serialize for DevIntentTrigger {
3947 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3948 serializer.serialize_str(self.as_str())
3949 }
3950}
3951
3952impl<'de> Deserialize<'de> for DevIntentTrigger {
3953 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3954 let s = String::deserialize(deserializer)?;
3955 Ok(Self::from(s.as_str()))
3956 }
3957}
3958
3959#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3963pub enum TaskEndReason {
3964 WorkerRestart,
3967 Unknown(String),
3969}
3970
3971impl TaskEndReason {
3972 pub fn as_str(&self) -> &str {
3973 match self {
3974 Self::WorkerRestart => "worker_restart",
3975 Self::Unknown(s) => s.as_str(),
3976 }
3977 }
3978}
3979
3980impl fmt::Display for TaskEndReason {
3981 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3982 f.write_str(self.as_str())
3983 }
3984}
3985
3986impl From<&str> for TaskEndReason {
3987 fn from(s: &str) -> Self {
3988 match s {
3989 "worker_restart" => Self::WorkerRestart,
3990 other => Self::Unknown(other.to_string()),
3991 }
3992 }
3993}
3994
3995impl Serialize for TaskEndReason {
3996 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3997 serializer.serialize_str(self.as_str())
3998 }
3999}
4000
4001impl<'de> Deserialize<'de> for TaskEndReason {
4002 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4003 let s = String::deserialize(deserializer)?;
4004 Ok(Self::from(s.as_str()))
4005 }
4006}
4007
4008#[derive(Debug, Clone, Serialize, Deserialize)]
4017pub struct TurnHandoffAvailableMessage {
4018 pub v: u64,
4020 pub tools: Vec<String>,
4022 pub worker_epoch: u64,
4024 #[serde(default, skip_serializing_if = "Option::is_none")]
4028 pub relay_marker: Option<bool>,
4029 #[serde(default, skip_serializing_if = "Option::is_none")]
4037 pub staged_files: Option<bool>,
4038 #[serde(default, skip_serializing_if = "Option::is_none")]
4045 pub no_query_first: Option<bool>,
4046 #[serde(default, skip_serializing_if = "Option::is_none")]
4054 pub home_files: Option<bool>,
4055 #[serde(default, skip_serializing_if = "Option::is_none")]
4059 pub file_names: Option<bool>,
4060 #[serde(default, skip_serializing_if = "Option::is_none")]
4070 pub carried_writes: Option<bool>,
4071 #[serde(default, skip_serializing_if = "Option::is_none")]
4085 pub hydrates_carried_lines: Option<bool>,
4086 #[serde(default, skip_serializing_if = "Option::is_none")]
4092 pub shared_message_id: Option<bool>,
4093 pub uuid: String,
4094 pub session_id: String,
4095 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4096 pub extra: serde_json::Map<String, Value>,
4097}
4098
4099#[derive(Debug, Clone, Serialize, Deserialize)]
4111pub struct TurnPreemptedMessage {
4112 pub reason: String,
4116 pub preempted_by_uuid: String,
4120 #[serde(default)]
4123 pub preempted_message_uuids: Vec<String>,
4124 pub uuid: String,
4125 pub session_id: String,
4126 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4127 pub extra: serde_json::Map<String, Value>,
4128}
4129
4130#[derive(Debug, Clone, Serialize, Deserialize)]
4138pub struct PeerMessageHoldMessage {
4139 pub state: PeerMessageHoldState,
4145 #[serde(default, skip_serializing_if = "Option::is_none")]
4152 pub message_uuid: Option<String>,
4153 pub lane: PeerMessageLane,
4155 pub from: String,
4160 #[serde(default, skip_serializing_if = "Option::is_none")]
4163 pub from_name: Option<String>,
4164 #[serde(default, skip_serializing_if = "Option::is_none")]
4166 pub cause: Option<PeerMessageHoldCause>,
4167 #[serde(default, skip_serializing_if = "Option::is_none")]
4169 pub outcome: Option<PeerMessageHoldOutcome>,
4170 pub uuid: String,
4171 pub session_id: String,
4172 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4173 pub extra: serde_json::Map<String, Value>,
4174}
4175
4176#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4178pub enum PeerMessageHoldState {
4179 Held,
4181 Released,
4183 Dropped,
4185 Unknown(String),
4187}
4188
4189impl PeerMessageHoldState {
4190 pub fn as_str(&self) -> &str {
4191 match self {
4192 Self::Held => "held",
4193 Self::Released => "released",
4194 Self::Dropped => "dropped",
4195 Self::Unknown(s) => s.as_str(),
4196 }
4197 }
4198}
4199
4200impl fmt::Display for PeerMessageHoldState {
4201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4202 f.write_str(self.as_str())
4203 }
4204}
4205
4206impl From<&str> for PeerMessageHoldState {
4207 fn from(s: &str) -> Self {
4208 match s {
4209 "held" => Self::Held,
4210 "released" => Self::Released,
4211 "dropped" => Self::Dropped,
4212 other => Self::Unknown(other.to_string()),
4213 }
4214 }
4215}
4216
4217impl Serialize for PeerMessageHoldState {
4218 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4219 serializer.serialize_str(self.as_str())
4220 }
4221}
4222
4223impl<'de> Deserialize<'de> for PeerMessageHoldState {
4224 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4225 let s = String::deserialize(deserializer)?;
4226 Ok(Self::from(s.as_str()))
4227 }
4228}
4229
4230#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4232pub enum PeerMessageLane {
4233 Bridge,
4235 Stdin,
4237 Socket,
4239 Unknown(String),
4241}
4242
4243impl PeerMessageLane {
4244 pub fn as_str(&self) -> &str {
4245 match self {
4246 Self::Bridge => "bridge",
4247 Self::Stdin => "stdin",
4248 Self::Socket => "socket",
4249 Self::Unknown(s) => s.as_str(),
4250 }
4251 }
4252}
4253
4254impl fmt::Display for PeerMessageLane {
4255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4256 f.write_str(self.as_str())
4257 }
4258}
4259
4260impl From<&str> for PeerMessageLane {
4261 fn from(s: &str) -> Self {
4262 match s {
4263 "bridge" => Self::Bridge,
4264 "stdin" => Self::Stdin,
4265 "socket" => Self::Socket,
4266 other => Self::Unknown(other.to_string()),
4267 }
4268 }
4269}
4270
4271impl Serialize for PeerMessageLane {
4272 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4273 serializer.serialize_str(self.as_str())
4274 }
4275}
4276
4277impl<'de> Deserialize<'de> for PeerMessageLane {
4278 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4279 let s = String::deserialize(deserializer)?;
4280 Ok(Self::from(s.as_str()))
4281 }
4282}
4283
4284#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4291pub enum PeerMessageHoldCause {
4292 ExplicitSetting,
4293 ManagedSetting,
4294 RepoSetting,
4295 InvalidSetting,
4296 BypassDefault,
4297 ModeUnknown,
4298 ModeMismatch,
4299 NoModeAsserted,
4300 Unknown(String),
4302}
4303
4304impl PeerMessageHoldCause {
4305 pub fn as_str(&self) -> &str {
4306 match self {
4307 Self::ExplicitSetting => "explicit-setting",
4308 Self::ManagedSetting => "managed-setting",
4309 Self::RepoSetting => "repo-setting",
4310 Self::InvalidSetting => "invalid-setting",
4311 Self::BypassDefault => "bypass-default",
4312 Self::ModeUnknown => "mode-unknown",
4313 Self::ModeMismatch => "mode-mismatch",
4314 Self::NoModeAsserted => "no-mode-asserted",
4315 Self::Unknown(s) => s.as_str(),
4316 }
4317 }
4318}
4319
4320impl fmt::Display for PeerMessageHoldCause {
4321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4322 f.write_str(self.as_str())
4323 }
4324}
4325
4326impl From<&str> for PeerMessageHoldCause {
4327 fn from(s: &str) -> Self {
4328 match s {
4329 "explicit-setting" => Self::ExplicitSetting,
4330 "managed-setting" => Self::ManagedSetting,
4331 "repo-setting" => Self::RepoSetting,
4332 "invalid-setting" => Self::InvalidSetting,
4333 "bypass-default" => Self::BypassDefault,
4334 "mode-unknown" => Self::ModeUnknown,
4335 "mode-mismatch" => Self::ModeMismatch,
4336 "no-mode-asserted" => Self::NoModeAsserted,
4337 other => Self::Unknown(other.to_string()),
4338 }
4339 }
4340}
4341
4342impl Serialize for PeerMessageHoldCause {
4343 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4344 serializer.serialize_str(self.as_str())
4345 }
4346}
4347
4348impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4349 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4350 let s = String::deserialize(deserializer)?;
4351 Ok(Self::from(s.as_str()))
4352 }
4353}
4354
4355#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4358pub enum PeerMessageHoldOutcome {
4359 Expired,
4363 Refused,
4365 Dropped,
4368 Discarded,
4370 Unknown(String),
4372}
4373
4374impl PeerMessageHoldOutcome {
4375 pub fn as_str(&self) -> &str {
4376 match self {
4377 Self::Expired => "expired",
4378 Self::Refused => "refused",
4379 Self::Dropped => "dropped",
4380 Self::Discarded => "discarded",
4381 Self::Unknown(s) => s.as_str(),
4382 }
4383 }
4384}
4385
4386impl fmt::Display for PeerMessageHoldOutcome {
4387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4388 f.write_str(self.as_str())
4389 }
4390}
4391
4392impl From<&str> for PeerMessageHoldOutcome {
4393 fn from(s: &str) -> Self {
4394 match s {
4395 "expired" => Self::Expired,
4396 "refused" => Self::Refused,
4397 "dropped" => Self::Dropped,
4398 "discarded" => Self::Discarded,
4399 other => Self::Unknown(other.to_string()),
4400 }
4401 }
4402}
4403
4404impl Serialize for PeerMessageHoldOutcome {
4405 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4406 serializer.serialize_str(self.as_str())
4407 }
4408}
4409
4410impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4411 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4412 let s = String::deserialize(deserializer)?;
4413 Ok(Self::from(s.as_str()))
4414 }
4415}
4416
4417#[derive(Debug, Clone, Serialize, Deserialize)]
4428pub struct PerTurnEffortChangedMessage {
4429 pub per_turn_effort_active: bool,
4431 pub uuid: String,
4432 pub session_id: String,
4433}
4434
4435#[derive(Debug, Clone, Serialize, Deserialize)]
4442pub struct InstructionSizeWarningMessage {
4443 pub total_chars: u64,
4446 pub total_limit_chars: u64,
4449 pub file_count: u64,
4452 #[serde(default, skip_serializing_if = "Option::is_none")]
4456 pub largest_chars: Option<u64>,
4457 pub uuid: String,
4458 pub session_id: String,
4459}
4460
4461#[derive(Debug, Clone, Serialize, Deserialize)]
4468pub struct FileAttachmentsMissingMessage {
4469 pub message_uuid: String,
4472 pub sent_count: u64,
4474 pub missing: Vec<MissingFileAttachment>,
4476 pub uuid: String,
4477 pub session_id: String,
4478}
4479
4480#[derive(Debug, Clone, Serialize, Deserialize)]
4490pub struct PermissionCheckStatusMessage {
4491 pub tool_use_id: String,
4492 #[serde(default, skip_serializing_if = "Option::is_none")]
4495 pub agent_id: Option<String>,
4496 pub status: PermissionCheckStatus,
4497 pub uuid: String,
4498 pub session_id: String,
4499}
4500
4501#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4503pub enum PermissionCheckStatus {
4504 Checking,
4507 Done,
4509 Unknown(String),
4511}
4512
4513impl PermissionCheckStatus {
4514 pub fn as_str(&self) -> &str {
4515 match self {
4516 Self::Checking => "checking",
4517 Self::Done => "done",
4518 Self::Unknown(s) => s.as_str(),
4519 }
4520 }
4521}
4522
4523impl fmt::Display for PermissionCheckStatus {
4524 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4525 f.write_str(self.as_str())
4526 }
4527}
4528
4529impl From<&str> for PermissionCheckStatus {
4530 fn from(s: &str) -> Self {
4531 match s {
4532 "checking" => Self::Checking,
4533 "done" => Self::Done,
4534 other => Self::Unknown(other.to_string()),
4535 }
4536 }
4537}
4538
4539impl Serialize for PermissionCheckStatus {
4540 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4541 serializer.serialize_str(self.as_str())
4542 }
4543}
4544
4545impl<'de> Deserialize<'de> for PermissionCheckStatus {
4546 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4547 let s = String::deserialize(deserializer)?;
4548 Ok(Self::from(s.as_str()))
4549 }
4550}
4551
4552#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4554pub struct MissingFileAttachment {
4555 pub file_uuid: String,
4557 pub reason: String,
4562}
4563
4564#[derive(Debug, Clone, Serialize, Deserialize)]
4574pub struct SessionTitleChangedMessage {
4575 pub title: String,
4580 pub uuid: String,
4581 pub session_id: String,
4582}
4583
4584#[derive(Debug, Clone, Serialize, Deserialize)]
4588pub struct UiLogMessage {
4589 pub plugin: String,
4591 pub text: String,
4595 pub uuid: String,
4596 pub session_id: String,
4597}
4598
4599#[derive(Debug, Clone, Serialize, Deserialize)]
4604pub struct UiToastMessage {
4605 pub plugin: String,
4607 pub text: String,
4611 pub timeout_ms: u64,
4614 pub uuid: String,
4615 pub session_id: String,
4616}
4617
4618#[derive(Debug, Clone, Serialize, Deserialize)]
4622pub struct UiStatusMessage {
4623 pub plugin: String,
4625 pub text: Option<String>,
4630 pub uuid: String,
4631 pub session_id: String,
4632}
4633
4634#[derive(Debug, Clone, Serialize, Deserialize)]
4639pub struct UiInvalidateMessage {
4640 pub event: String,
4643 #[serde(default, skip_serializing_if = "Option::is_none")]
4648 pub instances: Option<Vec<UiInvalidatedInstance>>,
4649 #[serde(default, skip_serializing_if = "Option::is_none")]
4657 pub prompts_screened: Option<bool>,
4658 pub uuid: String,
4659 pub session_id: String,
4660}
4661
4662#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4664pub struct UiInvalidatedInstance {
4665 pub surface: String,
4667 pub component: String,
4669 pub instance_id: String,
4672}
4673
4674#[derive(Debug, Clone, Serialize, Deserialize)]
4681pub struct UiPanesMessage {
4682 pub panes: Vec<UiPane>,
4685 pub shown_id: Option<String>,
4688 pub focused_id: Option<String>,
4691 pub focus_requested_id: Option<String>,
4695 pub uuid: String,
4696 pub session_id: String,
4697}
4698
4699#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4701pub struct UiPane {
4702 pub id: String,
4705 pub title: String,
4708 pub plugin: String,
4710 #[serde(default, skip_serializing_if = "Option::is_none")]
4713 pub close_on_escape: Option<bool>,
4714 #[serde(default, skip_serializing_if = "Option::is_none")]
4717 pub hold_toasts: Option<bool>,
4718 #[serde(default, skip_serializing_if = "Option::is_none")]
4721 pub rows: Option<u64>,
4722 #[serde(default, skip_serializing_if = "Option::is_none")]
4725 pub columns: Option<u64>,
4726}
4727
4728#[derive(Debug, Clone, Serialize, Deserialize)]
4732pub struct UiScrollMessage {
4733 pub client_id: String,
4735 pub component: UiSiteComponent,
4737 pub instance_id: String,
4739 pub offset: u64,
4742 #[serde(default, skip_serializing_if = "Option::is_none")]
4745 pub follow_end: Option<bool>,
4746 pub uuid: String,
4747 pub session_id: String,
4748}
4749
4750#[derive(Debug, Clone, Serialize, Deserialize)]
4755pub struct UiFocusMessage {
4756 pub client_id: String,
4758 pub component: UiSiteComponent,
4760 pub instance_id: String,
4762 pub plugin: String,
4764 pub key: String,
4766 pub uuid: String,
4767 pub session_id: String,
4768}
4769
4770#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4772pub enum UiSiteComponent {
4773 Pane,
4775 AbovePrompt,
4777 Unknown(String),
4779}
4780
4781impl UiSiteComponent {
4782 pub fn as_str(&self) -> &str {
4783 match self {
4784 Self::Pane => "Pane",
4785 Self::AbovePrompt => "AbovePrompt",
4786 Self::Unknown(s) => s.as_str(),
4787 }
4788 }
4789}
4790
4791impl fmt::Display for UiSiteComponent {
4792 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4793 f.write_str(self.as_str())
4794 }
4795}
4796
4797impl From<&str> for UiSiteComponent {
4798 fn from(s: &str) -> Self {
4799 match s {
4800 "Pane" => Self::Pane,
4801 "AbovePrompt" => Self::AbovePrompt,
4802 other => Self::Unknown(other.to_string()),
4803 }
4804 }
4805}
4806
4807impl Serialize for UiSiteComponent {
4808 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4809 serializer.serialize_str(self.as_str())
4810 }
4811}
4812
4813impl<'de> Deserialize<'de> for UiSiteComponent {
4814 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4815 let s = String::deserialize(deserializer)?;
4816 Ok(Self::from(s.as_str()))
4817 }
4818}
4819
4820#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4823pub struct LocalCommandRun {
4824 pub command: String,
4826 pub args: String,
4829 #[serde(default, skip_serializing_if = "Option::is_none")]
4834 pub plugin: Option<String>,
4835}
4836
4837#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4841pub struct LocalCommandOutcome {
4842 pub kind: LocalCommandOutcomeKind,
4843 #[serde(default, skip_serializing_if = "Option::is_none")]
4846 pub suggestion: Option<String>,
4847}
4848
4849#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4854pub enum LocalCommandOutcomeKind {
4855 UnavailableHeadless,
4857 Unknown,
4860 Failed,
4862 RestartRequired,
4864 Other(String),
4866}
4867
4868impl LocalCommandOutcomeKind {
4869 pub fn as_str(&self) -> &str {
4870 match self {
4871 Self::UnavailableHeadless => "unavailable_headless",
4872 Self::Unknown => "unknown",
4873 Self::Failed => "failed",
4874 Self::RestartRequired => "restart_required",
4875 Self::Other(s) => s.as_str(),
4876 }
4877 }
4878}
4879
4880impl fmt::Display for LocalCommandOutcomeKind {
4881 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4882 f.write_str(self.as_str())
4883 }
4884}
4885
4886impl From<&str> for LocalCommandOutcomeKind {
4887 fn from(s: &str) -> Self {
4888 match s {
4889 "unavailable_headless" => Self::UnavailableHeadless,
4890 "unknown" => Self::Unknown,
4891 "failed" => Self::Failed,
4892 "restart_required" => Self::RestartRequired,
4893 other => Self::Other(other.to_string()),
4894 }
4895 }
4896}
4897
4898impl Serialize for LocalCommandOutcomeKind {
4899 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4900 serializer.serialize_str(self.as_str())
4901 }
4902}
4903
4904impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4905 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4906 let s = String::deserialize(deserializer)?;
4907 Ok(Self::from(s.as_str()))
4908 }
4909}
4910
4911#[derive(Debug, Clone, Serialize, Deserialize)]
4917pub struct UsageReport {
4918 pub session: super::control::UsageSession,
4920 pub rate_limits: Option<UsageReportRateLimits>,
4924 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4925 pub extra: serde_json::Map<String, Value>,
4926}
4927
4928#[derive(Debug, Clone, Serialize, Deserialize)]
4930pub struct UsageReportRateLimits {
4931 pub limits: Option<Vec<UsageReportLimit>>,
4939 #[serde(default, skip_serializing_if = "Option::is_none")]
4942 pub extra_usage: Option<UsageReportExtraUsage>,
4943 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4944 pub extra: serde_json::Map<String, Value>,
4945}
4946
4947#[derive(Debug, Clone, Serialize, Deserialize)]
4949pub struct UsageReportLimit {
4950 pub kind: String,
4953 pub group: String,
4956 pub percent: f64,
4958 pub resets_at: Option<String>,
4960 #[serde(default, skip_serializing_if = "Option::is_none")]
4963 pub scope: Option<UsageReportScope>,
4964 #[serde(default, skip_serializing_if = "Option::is_none")]
4968 pub severity: Option<String>,
4969 #[serde(default, skip_serializing_if = "Option::is_none")]
4971 pub is_active: Option<bool>,
4972 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4973 pub extra: serde_json::Map<String, Value>,
4974}
4975
4976#[derive(Debug, Clone, Serialize, Deserialize)]
4978pub struct UsageReportScope {
4979 #[serde(default, skip_serializing_if = "Option::is_none")]
4980 pub model: Option<UsageReportScopeLabel>,
4981 #[serde(default, skip_serializing_if = "Option::is_none")]
4982 pub surface: Option<UsageReportScopeLabel>,
4983 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4984 pub extra: serde_json::Map<String, Value>,
4985}
4986
4987#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4989pub struct UsageReportScopeLabel {
4990 pub display_name: String,
4991}
4992
4993#[derive(Debug, Clone, Serialize, Deserialize)]
4997pub struct UsageReportExtraUsage {
4998 pub is_enabled: bool,
5000 pub monthly_limit: Option<f64>,
5001 pub used_credits: Option<f64>,
5002 pub utilization: Option<f64>,
5003 #[serde(default, skip_serializing_if = "Option::is_none")]
5004 pub currency: Option<String>,
5005 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5006 pub extra: serde_json::Map<String, Value>,
5007}
5008
5009#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5012pub struct BatchToolUse {
5013 pub id: String,
5014 pub name: String,
5015}
5016
5017#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5022pub struct ContextUsage {
5023 pub model: String,
5025 pub total_tokens: u64,
5028 pub raw_max_tokens: u64,
5031 pub percentage: u64,
5033 #[serde(default, skip_serializing_if = "Option::is_none")]
5035 pub over_limit: Option<ContextOverLimit>,
5036 #[serde(default)]
5038 pub categories: Vec<ContextCategory>,
5039 #[serde(default)]
5041 pub mcp_tools: Vec<ContextMcpTool>,
5042 #[serde(default)]
5044 pub memory_files: Vec<ContextMemoryFile>,
5045 #[serde(default)]
5047 pub agents: Vec<ContextAgent>,
5048 #[serde(default, skip_serializing_if = "Option::is_none")]
5050 pub skills: Option<Vec<ContextSkill>>,
5051}
5052
5053#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5055pub struct ContextOverLimit {
5056 pub tokens_over: u64,
5057 pub kind: String,
5060}
5061
5062#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5064pub struct ContextCategory {
5065 pub name: String,
5068 pub tokens: u64,
5069 pub kind: String,
5073}
5074
5075#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5077pub struct ContextMcpTool {
5078 pub name: String,
5080 pub server_name: String,
5081 pub tokens: u64,
5082}
5083
5084#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5086pub struct ContextMemoryFile {
5087 pub path: String,
5088 #[serde(rename = "type")]
5090 pub file_type: String,
5091 pub tokens: u64,
5092}
5093
5094#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5096pub struct ContextAgent {
5097 pub agent_type: String,
5098 pub source: String,
5100 pub tokens: u64,
5101}
5102
5103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5105pub struct ContextSkill {
5106 pub name: String,
5107 pub source: String,
5109 #[serde(default, skip_serializing_if = "Option::is_none")]
5110 pub plugin_name: Option<String>,
5111 pub tokens: u64,
5112}
5113
5114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5116pub struct ToolUseMeta {
5117 pub id: String,
5118 pub display_name: String,
5119 #[serde(default, skip_serializing_if = "Option::is_none")]
5120 pub server_display_name: Option<String>,
5121 #[serde(default, skip_serializing_if = "Option::is_none")]
5122 pub icon_url: Option<String>,
5123 #[serde(default, skip_serializing_if = "Option::is_none")]
5128 pub server_id: Option<String>,
5129 #[serde(default, skip_serializing_if = "Option::is_none")]
5137 pub server_id_stamp: Option<String>,
5138 #[serde(default, skip_serializing_if = "Option::is_none")]
5141 pub tool_name: Option<String>,
5142}
5143
5144#[derive(Debug, Clone, Serialize, Deserialize)]
5146pub struct AssistantMessage {
5147 pub message: AssistantMessageContent,
5148 #[serde(alias = "sessionId")]
5149 pub session_id: String,
5150 #[serde(skip_serializing_if = "Option::is_none")]
5151 pub uuid: Option<String>,
5152 #[serde(skip_serializing_if = "Option::is_none")]
5153 pub parent_tool_use_id: Option<String>,
5154 #[serde(skip_serializing_if = "Option::is_none")]
5156 pub request_id: Option<String>,
5157 #[serde(default, skip_serializing_if = "Option::is_none")]
5166 pub user_message_uuid: Option<String>,
5167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5173 pub user_message_uuids: Vec<String>,
5174 #[serde(default, skip_serializing_if = "Option::is_none")]
5178 pub agent_id: Option<String>,
5179 #[serde(skip_serializing_if = "Option::is_none")]
5182 pub subagent_type: Option<String>,
5183 #[serde(skip_serializing_if = "Option::is_none")]
5185 pub task_description: Option<String>,
5186 #[serde(skip_serializing_if = "Option::is_none")]
5187 pub error: Option<AssistantErrorKind>,
5188 #[serde(default, skip_serializing_if = "Option::is_none")]
5192 pub aborted: Option<bool>,
5193 #[serde(default, skip_serializing_if = "Option::is_none")]
5198 pub resumed_from_incomplete_thinking: Option<bool>,
5199 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5200 pub supersedes: Vec<String>,
5201 #[serde(skip_serializing_if = "Option::is_none")]
5202 pub timestamp: Option<String>,
5203 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5204 pub tool_use_meta: Vec<ToolUseMeta>,
5205 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5210 pub batch_tool_uses: Vec<BatchToolUse>,
5211 #[serde(default, skip_serializing_if = "Option::is_none")]
5217 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
5218 #[serde(default, skip_serializing_if = "Option::is_none")]
5224 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
5225 #[serde(default, skip_serializing_if = "Option::is_none")]
5229 pub historical: Option<bool>,
5230 #[serde(default, skip_serializing_if = "Option::is_none")]
5238 pub resume_reason: Option<String>,
5239 #[serde(default, skip_serializing_if = "Option::is_none")]
5245 pub local_command_source: Option<String>,
5246 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5255 pub narration_block_indexes: Vec<usize>,
5256 #[serde(default, skip_serializing_if = "Option::is_none")]
5260 pub narration_hint: Option<String>,
5261 #[serde(default, skip_serializing_if = "Option::is_none")]
5270 pub thinking_duration_ms: Option<u64>,
5271 #[serde(default, skip_serializing_if = "Option::is_none")]
5282 pub thinking_display: Option<String>,
5283 #[serde(default, skip_serializing_if = "Option::is_none")]
5287 pub context_usage: Option<ContextUsage>,
5288 #[serde(default, skip_serializing_if = "Option::is_none")]
5296 pub usage_report: Option<Box<UsageReport>>,
5297 #[serde(default, skip_serializing_if = "Option::is_none")]
5304 pub local_command_run: Option<LocalCommandRun>,
5305 #[serde(default, skip_serializing_if = "Option::is_none")]
5311 pub local_command_outcome: Option<LocalCommandOutcome>,
5312 #[serde(default, skip_serializing_if = "Option::is_none")]
5313 pub is_meta: Option<bool>,
5314 #[serde(default, skip_serializing_if = "Option::is_none")]
5315 pub is_virtual: Option<bool>,
5316 #[serde(default, skip_serializing_if = "Option::is_none")]
5317 pub is_api_error_message: Option<bool>,
5318 #[serde(skip_serializing_if = "Option::is_none")]
5319 pub api_error_status: Option<u16>,
5320 #[serde(skip_serializing_if = "Option::is_none")]
5340 pub api_error: Option<String>,
5341 #[serde(default, skip_serializing_if = "Option::is_none")]
5348 pub api_error_code: Option<String>,
5349 #[serde(default, skip_serializing_if = "Option::is_none")]
5360 pub api_error_origin: Option<String>,
5361 #[serde(default, skip_serializing_if = "Option::is_none")]
5364 pub api_error_params: Option<ApiErrorParams>,
5365 #[serde(skip_serializing_if = "Option::is_none")]
5366 pub error_details: Option<String>,
5367 #[serde(skip_serializing_if = "Option::is_none")]
5368 pub advisor_model: Option<String>,
5369 #[serde(skip_serializing_if = "Option::is_none")]
5370 pub attribution_agent: Option<String>,
5371 #[serde(skip_serializing_if = "Option::is_none")]
5372 pub attribution_skill: Option<String>,
5373 #[serde(skip_serializing_if = "Option::is_none")]
5374 pub attribution_plugin: Option<String>,
5375 #[serde(skip_serializing_if = "Option::is_none")]
5376 pub attribution_mcp_server: Option<String>,
5377 #[serde(skip_serializing_if = "Option::is_none")]
5378 pub attribution_mcp_tool: Option<String>,
5379}
5380
5381#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5385pub struct ApiErrorParams {
5386 #[serde(default, skip_serializing_if = "Option::is_none")]
5388 pub effort: Option<String>,
5389 #[serde(default, skip_serializing_if = "Option::is_none")]
5392 pub provider: Option<ApiErrorProvider>,
5393 #[serde(default, skip_serializing_if = "Option::is_none")]
5396 pub remedy: Option<ApiErrorRemedy>,
5397 #[serde(default, skip_serializing_if = "Option::is_none")]
5400 pub media: Option<ApiErrorMedia>,
5401 #[serde(default, skip_serializing_if = "Option::is_none")]
5403 pub media_reason: Option<ApiErrorMediaReason>,
5404 #[serde(default, skip_serializing_if = "Option::is_none")]
5412 pub rate_limit_info: Option<RateLimitInfo>,
5413}
5414
5415#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5417pub enum ApiErrorMedia {
5418 Image,
5419 Document,
5420 Unknown(String),
5422}
5423
5424impl ApiErrorMedia {
5425 pub fn as_str(&self) -> &str {
5426 match self {
5427 Self::Image => "image",
5428 Self::Document => "document",
5429 Self::Unknown(s) => s.as_str(),
5430 }
5431 }
5432}
5433
5434impl fmt::Display for ApiErrorMedia {
5435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5436 f.write_str(self.as_str())
5437 }
5438}
5439
5440impl From<&str> for ApiErrorMedia {
5441 fn from(s: &str) -> Self {
5442 match s {
5443 "image" => Self::Image,
5444 "document" => Self::Document,
5445 other => Self::Unknown(other.to_string()),
5446 }
5447 }
5448}
5449
5450impl Serialize for ApiErrorMedia {
5451 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5452 serializer.serialize_str(self.as_str())
5453 }
5454}
5455
5456impl<'de> Deserialize<'de> for ApiErrorMedia {
5457 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5458 let s = String::deserialize(deserializer)?;
5459 Ok(Self::from(s.as_str()))
5460 }
5461}
5462
5463#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5466pub enum ApiErrorMediaReason {
5467 Unprocessable,
5470 UnsupportedByModel,
5472 MediaBudget,
5475 Unknown(String),
5477}
5478
5479impl ApiErrorMediaReason {
5480 pub fn as_str(&self) -> &str {
5481 match self {
5482 Self::Unprocessable => "unprocessable",
5483 Self::UnsupportedByModel => "unsupported_by_model",
5484 Self::MediaBudget => "media_budget",
5485 Self::Unknown(s) => s.as_str(),
5486 }
5487 }
5488}
5489
5490impl fmt::Display for ApiErrorMediaReason {
5491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5492 f.write_str(self.as_str())
5493 }
5494}
5495
5496impl From<&str> for ApiErrorMediaReason {
5497 fn from(s: &str) -> Self {
5498 match s {
5499 "unprocessable" => Self::Unprocessable,
5500 "unsupported_by_model" => Self::UnsupportedByModel,
5501 "media_budget" => Self::MediaBudget,
5502 other => Self::Unknown(other.to_string()),
5503 }
5504 }
5505}
5506
5507impl Serialize for ApiErrorMediaReason {
5508 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5509 serializer.serialize_str(self.as_str())
5510 }
5511}
5512
5513impl<'de> Deserialize<'de> for ApiErrorMediaReason {
5514 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5515 let s = String::deserialize(deserializer)?;
5516 Ok(Self::from(s.as_str()))
5517 }
5518}
5519
5520#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5522pub enum ApiErrorProvider {
5523 Bedrock,
5524 AnthropicAws,
5525 Mantle,
5526 AnthropicGoogleCloud,
5527 Vertex,
5528 Foundry,
5529 Gateway,
5530 Unknown(String),
5532}
5533
5534impl ApiErrorProvider {
5535 pub fn as_str(&self) -> &str {
5536 match self {
5537 Self::Bedrock => "bedrock",
5538 Self::AnthropicAws => "anthropicAws",
5539 Self::Mantle => "mantle",
5540 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
5541 Self::Vertex => "vertex",
5542 Self::Foundry => "foundry",
5543 Self::Gateway => "gateway",
5544 Self::Unknown(s) => s.as_str(),
5545 }
5546 }
5547}
5548
5549impl fmt::Display for ApiErrorProvider {
5550 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5551 f.write_str(self.as_str())
5552 }
5553}
5554
5555impl From<&str> for ApiErrorProvider {
5556 fn from(s: &str) -> Self {
5557 match s {
5558 "bedrock" => Self::Bedrock,
5559 "anthropicAws" => Self::AnthropicAws,
5560 "mantle" => Self::Mantle,
5561 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
5562 "vertex" => Self::Vertex,
5563 "foundry" => Self::Foundry,
5564 "gateway" => Self::Gateway,
5565 other => Self::Unknown(other.to_string()),
5566 }
5567 }
5568}
5569
5570impl Serialize for ApiErrorProvider {
5571 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5572 serializer.serialize_str(self.as_str())
5573 }
5574}
5575
5576impl<'de> Deserialize<'de> for ApiErrorProvider {
5577 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5578 let s = String::deserialize(deserializer)?;
5579 Ok(Self::from(s.as_str()))
5580 }
5581}
5582
5583#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5585pub enum ApiErrorRemedy {
5586 RefreshCommand,
5589 RefreshCredentials,
5591 Adc,
5594 GatewayToken,
5597 HostManaged,
5599 ModelAccess,
5602 Unknown(String),
5604}
5605
5606impl ApiErrorRemedy {
5607 pub fn as_str(&self) -> &str {
5608 match self {
5609 Self::RefreshCommand => "refresh_command",
5610 Self::RefreshCredentials => "refresh_credentials",
5611 Self::Adc => "adc",
5612 Self::GatewayToken => "gateway_token",
5613 Self::HostManaged => "host_managed",
5614 Self::ModelAccess => "model_access",
5615 Self::Unknown(s) => s.as_str(),
5616 }
5617 }
5618}
5619
5620impl fmt::Display for ApiErrorRemedy {
5621 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5622 f.write_str(self.as_str())
5623 }
5624}
5625
5626impl From<&str> for ApiErrorRemedy {
5627 fn from(s: &str) -> Self {
5628 match s {
5629 "refresh_command" => Self::RefreshCommand,
5630 "refresh_credentials" => Self::RefreshCredentials,
5631 "adc" => Self::Adc,
5632 "gateway_token" => Self::GatewayToken,
5633 "host_managed" => Self::HostManaged,
5634 "model_access" => Self::ModelAccess,
5635 other => Self::Unknown(other.to_string()),
5636 }
5637 }
5638}
5639
5640impl Serialize for ApiErrorRemedy {
5641 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5642 serializer.serialize_str(self.as_str())
5643 }
5644}
5645
5646impl<'de> Deserialize<'de> for ApiErrorRemedy {
5647 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5648 let s = String::deserialize(deserializer)?;
5649 Ok(Self::from(s.as_str()))
5650 }
5651}
5652
5653#[derive(Debug, Clone, Serialize, Deserialize)]
5655pub struct AssistantMessageContent {
5656 pub id: String,
5657 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
5659 pub message_type: Option<String>,
5660 pub role: MessageRole,
5661 pub model: String,
5662 pub content: Vec<ContentBlock>,
5663 #[serde(skip_serializing_if = "Option::is_none")]
5664 pub stop_reason: Option<StopReason>,
5665 #[serde(skip_serializing_if = "Option::is_none")]
5666 pub stop_sequence: Option<String>,
5667 #[serde(skip_serializing_if = "Option::is_none")]
5668 pub usage: Option<AssistantUsage>,
5669 #[serde(skip_serializing_if = "Option::is_none")]
5671 pub stop_details: Option<Value>,
5672 #[serde(skip_serializing_if = "Option::is_none")]
5674 pub context_management: Option<Value>,
5675}
5676
5677#[derive(Debug, Clone, Serialize, Deserialize)]
5679pub struct AssistantUsage {
5680 #[serde(default)]
5682 pub input_tokens: u32,
5683
5684 #[serde(default)]
5686 pub output_tokens: u32,
5687
5688 #[serde(default)]
5690 pub cache_creation_input_tokens: u32,
5691
5692 #[serde(default)]
5694 pub cache_read_input_tokens: u32,
5695
5696 #[serde(skip_serializing_if = "Option::is_none")]
5698 pub service_tier: Option<String>,
5699
5700 #[serde(skip_serializing_if = "Option::is_none")]
5702 pub cache_creation: Option<CacheCreationDetails>,
5703
5704 #[serde(skip_serializing_if = "Option::is_none")]
5706 pub inference_geo: Option<String>,
5707
5708 #[serde(default, skip_serializing_if = "Option::is_none")]
5711 pub fallback_credit: Option<Value>,
5712}
5713
5714#[derive(Debug, Clone, Serialize, Deserialize)]
5716pub struct CacheCreationDetails {
5717 #[serde(default)]
5719 pub ephemeral_1h_input_tokens: u32,
5720
5721 #[serde(default)]
5723 pub ephemeral_5m_input_tokens: u32,
5724}
5725
5726#[cfg(test)]
5727mod tests {
5728 use crate::io::ClaudeOutput;
5729
5730 #[test]
5731 fn test_subagent_usage_rollup_accumulates_task_results() {
5732 use super::SubagentUsageRollup;
5733
5734 let mut rollup = SubagentUsageRollup::default();
5735
5736 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}}"#;
5737 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5738 assert!(rollup.observe(&output));
5739 assert_eq!(rollup.subagent_tokens, 10201);
5740 assert_eq!(rollup.agent_count, 1);
5741 assert_eq!(rollup.tool_uses, 3);
5742 assert_eq!(rollup.duration_ms, 1853);
5743
5744 assert!(!rollup.observe(&output));
5746 assert_eq!(rollup.agent_count, 1);
5747 assert_eq!(rollup.subagent_tokens, 10201);
5748
5749 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}}"#;
5751 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5752 assert!(rollup.observe(&output));
5753 assert_eq!(rollup.agent_count, 2);
5754 assert_eq!(rollup.subagent_tokens, 10701);
5755 }
5756
5757 #[test]
5758 fn test_subagent_usage_rollup_ignores_non_task_results() {
5759 use super::SubagentUsageRollup;
5760
5761 let mut rollup = SubagentUsageRollup::default();
5762
5763 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}}"#;
5766 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5767 assert!(!rollup.observe(&output));
5768
5769 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5771 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5772 assert!(!rollup.observe(&output));
5773
5774 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5776 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5777 assert!(!rollup.observe(&output));
5778
5779 assert_eq!(rollup, SubagentUsageRollup::default());
5780 }
5781
5782 #[test]
5783 fn test_subagent_usage_rollup_over_captured_session() {
5784 use super::SubagentUsageRollup;
5785
5786 let mut rollup = SubagentUsageRollup::default();
5787 let fixture =
5788 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5789 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5790 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5791 rollup.observe(&output);
5792 }
5793 }
5794 assert_eq!(rollup.agent_count, 1);
5795 assert_eq!(rollup.subagent_tokens, 10201);
5796 }
5797
5798 #[test]
5799 fn test_system_message_init() {
5800 let json = r#"{
5801 "type": "system",
5802 "subtype": "init",
5803 "session_id": "test-session-123",
5804 "cwd": "/home/user/project",
5805 "model": "claude-sonnet-4",
5806 "tools": ["Bash", "Read", "Write"],
5807 "mcp_servers": [],
5808 "slash_commands": ["compact", "cost", "review"],
5809 "agents": ["Bash", "Explore", "Plan"],
5810 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5811 "skills": [],
5812 "claude_code_version": "2.1.15",
5813 "apiKeySource": "none",
5814 "output_style": "default",
5815 "permissionMode": "default"
5816 }"#;
5817
5818 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5819 if let ClaudeOutput::System(sys) = output {
5820 assert!(sys.is_init());
5821 assert!(!sys.is_status());
5822 assert!(!sys.is_compact_boundary());
5823
5824 let init = sys.as_init().expect("Should parse as init");
5825 assert_eq!(init.session_id, "test-session-123");
5826 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5827 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5828 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5829 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5830 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5831 assert_eq!(init.plugins.len(), 1);
5832 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5833 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5834 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5835 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5836 assert_eq!(
5837 init.permission_mode,
5838 Some(super::InitPermissionMode::Default)
5839 );
5840 } else {
5841 panic!("Expected System message");
5842 }
5843 }
5844
5845 #[test]
5846 fn test_system_message_init_from_real_capture() {
5847 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5848 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5849 if let ClaudeOutput::System(sys) = output {
5850 let init = sys.as_init().expect("Should parse real init capture");
5851 assert_eq!(init.slash_commands.len(), 8);
5852 assert!(init.slash_commands.contains(&"compact".to_string()));
5853 assert!(init.slash_commands.contains(&"review".to_string()));
5854 assert_eq!(init.agents.len(), 5);
5855 assert!(init.agents.contains(&"Bash".to_string()));
5856 assert!(init.agents.contains(&"Explore".to_string()));
5857 assert_eq!(init.plugins.len(), 1);
5858 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5859 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5860 } else {
5861 panic!("Expected System message");
5862 }
5863 }
5864
5865 #[test]
5866 fn test_system_message_status() {
5867 let json = r#"{
5868 "type": "system",
5869 "subtype": "status",
5870 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5871 "status": "compacting",
5872 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5873 }"#;
5874
5875 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5876 if let ClaudeOutput::System(sys) = output {
5877 assert!(sys.is_status());
5878 assert!(!sys.is_init());
5879
5880 let status = sys.as_status().expect("Should parse as status");
5881 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5882 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5883 assert_eq!(
5884 status.uuid,
5885 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5886 );
5887 } else {
5888 panic!("Expected System message");
5889 }
5890 }
5891
5892 #[test]
5893 fn test_system_message_status_null() {
5894 let json = r#"{
5895 "type": "system",
5896 "subtype": "status",
5897 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5898 "status": null,
5899 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5900 }"#;
5901
5902 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5903 if let ClaudeOutput::System(sys) = output {
5904 let status = sys.as_status().expect("Should parse as status");
5905 assert_eq!(status.status, None);
5906 } else {
5907 panic!("Expected System message");
5908 }
5909 }
5910
5911 #[test]
5912 fn test_system_message_task_started() {
5913 let json = r#"{
5914 "type": "system",
5915 "subtype": "task_started",
5916 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5917 "task_id": "b6daf3f",
5918 "task_type": "local_bash",
5919 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5920 "description": "Wait for CI on PR #12",
5921 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5922 }"#;
5923
5924 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5925 if let ClaudeOutput::System(sys) = output {
5926 assert!(sys.is_task_started());
5927 assert!(!sys.is_task_progress());
5928 assert!(!sys.is_task_notification());
5929
5930 let task = sys.as_task_started().expect("Should parse as task_started");
5931 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5932 assert_eq!(task.task_id, "b6daf3f");
5933 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5934 assert_eq!(
5935 task.tool_use_id.as_deref(),
5936 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5937 );
5938 assert_eq!(task.description, "Wait for CI on PR #12");
5939 } else {
5940 panic!("Expected System message");
5941 }
5942 }
5943
5944 #[test]
5945 fn test_system_message_task_started_agent() {
5946 let json = r#"{
5947 "type": "system",
5948 "subtype": "task_started",
5949 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5950 "task_id": "a4a7e0906e5fc64cc",
5951 "task_type": "local_agent",
5952 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5953 "description": "Explore Scene/ArrayScene duplication",
5954 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5955 }"#;
5956
5957 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5958 if let ClaudeOutput::System(sys) = output {
5959 let task = sys.as_task_started().expect("Should parse as task_started");
5960 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5961 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5962 } else {
5963 panic!("Expected System message");
5964 }
5965 }
5966
5967 #[test]
5968 fn test_system_message_task_progress() {
5969 let json = r#"{
5970 "type": "system",
5971 "subtype": "task_progress",
5972 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5973 "task_id": "a4a7e0906e5fc64cc",
5974 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5975 "description": "Reading src/jplephem/chebyshev.rs",
5976 "last_tool_name": "Read",
5977 "usage": {
5978 "duration_ms": 13996,
5979 "tool_uses": 9,
5980 "total_tokens": 38779
5981 },
5982 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5983 }"#;
5984
5985 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5986 if let ClaudeOutput::System(sys) = output {
5987 assert!(sys.is_task_progress());
5988 assert!(!sys.is_task_started());
5989
5990 let progress = sys
5991 .as_task_progress()
5992 .expect("Should parse as task_progress");
5993 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5994 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5995 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5996 assert_eq!(progress.usage.duration_ms, 13996);
5997 assert_eq!(progress.usage.tool_uses, 9);
5998 assert_eq!(progress.usage.total_tokens, 38779);
5999 } else {
6000 panic!("Expected System message");
6001 }
6002 }
6003
6004 #[test]
6005 fn test_system_message_task_notification_completed() {
6006 let json = r#"{
6007 "type": "system",
6008 "subtype": "task_notification",
6009 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6010 "task_id": "a0ba761e9dc9c316f",
6011 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
6012 "status": "completed",
6013 "summary": "Agent \"Write Hipparcos data source doc\" completed",
6014 "output_file": "",
6015 "usage": {
6016 "duration_ms": 172300,
6017 "tool_uses": 11,
6018 "total_tokens": 42005
6019 },
6020 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
6021 }"#;
6022
6023 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6024 if let ClaudeOutput::System(sys) = output {
6025 assert!(sys.is_task_notification());
6026
6027 let notif = sys
6028 .as_task_notification()
6029 .expect("Should parse as task_notification");
6030 assert_eq!(notif.status, super::TaskStatus::Completed);
6031 assert_eq!(
6032 notif.summary,
6033 "Agent \"Write Hipparcos data source doc\" completed"
6034 );
6035 assert_eq!(notif.output_file, Some("".to_string()));
6036 assert_eq!(
6037 notif.tool_use_id,
6038 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
6039 );
6040 let usage = notif.usage.expect("Should have usage");
6041 assert_eq!(usage.duration_ms, 172300);
6042 assert_eq!(usage.tool_uses, 11);
6043 assert_eq!(usage.total_tokens, 42005);
6044 } else {
6045 panic!("Expected System message");
6046 }
6047 }
6048
6049 #[test]
6050 fn test_system_message_task_notification_failed_no_usage() {
6051 let json = r#"{
6052 "type": "system",
6053 "subtype": "task_notification",
6054 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
6055 "task_id": "b98f6a3",
6056 "status": "failed",
6057 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
6058 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
6059 }"#;
6060
6061 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6062 if let ClaudeOutput::System(sys) = output {
6063 let notif = sys
6064 .as_task_notification()
6065 .expect("Should parse as task_notification");
6066 assert_eq!(notif.status, super::TaskStatus::Failed);
6067 assert!(notif.tool_use_id.is_none());
6068 assert!(notif.usage.is_none());
6069 assert_eq!(
6070 notif.output_file,
6071 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
6072 );
6073 } else {
6074 panic!("Expected System message");
6075 }
6076 }
6077
6078 #[test]
6084 fn test_task_messages_roundtrip_through_value() {
6085 let cases = [
6086 r#"{"type":"system","subtype":"task_started","session_id":"s1",
6087 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
6088 "description":"Sleep 3s","uuid":"u1"}"#,
6089 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
6090 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
6091 "last_tool_name":"Bash",
6092 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6093 "uuid":"u2"}"#,
6094 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
6095 "task_id":"t1","tool_use_id":"tu1","status":"completed",
6096 "summary":"done","output_file":"",
6097 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6098 "uuid":"u3"}"#,
6099 ];
6100
6101 for json in cases {
6102 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6103 let value = serde_json::to_value(&output).unwrap();
6104 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
6105
6106 let ClaudeOutput::System(sys) = reparsed else {
6107 panic!("Expected System variant after round-trip");
6108 };
6109
6110 match sys.subtype {
6111 super::SystemSubtype::TaskStarted => {
6112 assert!(
6113 sys.as_task_started().is_some(),
6114 "as_task_started failed after round-trip"
6115 );
6116 }
6117 super::SystemSubtype::TaskProgress => {
6118 assert!(
6119 sys.as_task_progress().is_some(),
6120 "as_task_progress failed after round-trip"
6121 );
6122 }
6123 super::SystemSubtype::TaskNotification => {
6124 assert!(
6125 sys.as_task_notification().is_some(),
6126 "as_task_notification failed after round-trip"
6127 );
6128 }
6129 other => panic!("unexpected subtype after round-trip: {other:?}"),
6130 }
6131 }
6132 }
6133
6134 #[test]
6135 fn test_system_message_compact_boundary() {
6136 let json = r#"{
6137 "type": "system",
6138 "subtype": "compact_boundary",
6139 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6140 "compact_metadata": {
6141 "pre_tokens": 155285,
6142 "trigger": "auto"
6143 },
6144 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
6145 }"#;
6146
6147 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6148 if let ClaudeOutput::System(sys) = output {
6149 assert!(sys.is_compact_boundary());
6150 assert!(!sys.is_init());
6151 assert!(!sys.is_status());
6152
6153 let compact = sys
6154 .as_compact_boundary()
6155 .expect("Should parse as compact_boundary");
6156 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6157 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
6158 assert_eq!(
6159 compact.compact_metadata.trigger,
6160 super::CompactionTrigger::Auto
6161 );
6162 assert!(compact.summary.is_none());
6164 assert!(compact.leaf_message_count.is_none());
6165 assert!(compact.duration_ms.is_none());
6166 } else {
6167 panic!("Expected System message");
6168 }
6169 }
6170
6171 #[test]
6172 fn test_compact_boundary_with_summary_stats() {
6173 let json = r#"{
6175 "type": "system",
6176 "subtype": "compact_boundary",
6177 "session_id": "s1",
6178 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
6179 "summary": "Summarized the earlier exploration.",
6180 "leaf_message_count": 42,
6181 "duration_ms": 1234,
6182 "uuid": "u1"
6183 }"#;
6184 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6185 let ClaudeOutput::System(sys) = output else {
6186 panic!("Expected System message");
6187 };
6188 let compact = sys.as_compact_boundary().expect("compact_boundary");
6189 assert_eq!(
6190 compact.summary.as_deref(),
6191 Some("Summarized the earlier exploration.")
6192 );
6193 assert_eq!(compact.leaf_message_count, Some(42));
6194 assert_eq!(compact.duration_ms, Some(1234));
6195
6196 let json_alt = r#"{
6199 "type": "system",
6200 "subtype": "compact_boundary",
6201 "session_id": "s2",
6202 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
6203 "content": "alt-key summary",
6204 "message_count": 7
6205 }"#;
6206 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
6207 let ClaudeOutput::System(sys) = output else {
6208 panic!("Expected System message");
6209 };
6210 let compact = sys.as_compact_boundary().expect("compact_boundary");
6211 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
6212 assert_eq!(compact.leaf_message_count, Some(7));
6213 }
6214
6215 #[test]
6216 fn test_init_message_with_new_fields() {
6217 let json = r#"{
6218 "type": "system",
6219 "subtype": "init",
6220 "session_id": "test-session",
6221 "cwd": "/home/user",
6222 "model": "claude-opus-4-7",
6223 "tools": ["Bash"],
6224 "mcp_servers": [],
6225 "permissionMode": "default",
6226 "apiKeySource": "none",
6227 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
6228 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
6229 "fast_mode_state": "off",
6230 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
6231 "claude_code_version": "2.1.117"
6232 }"#;
6233
6234 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6235 if let ClaudeOutput::System(sys) = output {
6236 let init = sys.as_init().expect("Should parse as init");
6237 assert_eq!(
6238 init.uuid.as_deref(),
6239 Some("44841a0d-182d-493a-86b5-79800d3d9665")
6240 );
6241 assert!(init.memory_paths.is_some());
6242 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
6243 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
6244 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
6245 } else {
6246 panic!("Expected System message");
6247 }
6248 }
6249
6250 #[test]
6251 fn test_assistant_message_with_new_fields() {
6252 let json = r#"{
6253 "type": "assistant",
6254 "message": {
6255 "id": "msg_1",
6256 "type": "message",
6257 "role": "assistant",
6258 "model": "claude-opus-4-7",
6259 "content": [{"type": "text", "text": "Hello"}],
6260 "stop_reason": "end_turn",
6261 "stop_details": null,
6262 "context_management": null,
6263 "usage": {
6264 "input_tokens": 100,
6265 "output_tokens": 10,
6266 "cache_creation_input_tokens": 50,
6267 "cache_read_input_tokens": 0,
6268 "service_tier": "standard",
6269 "inference_geo": "not_available"
6270 }
6271 },
6272 "session_id": "abc",
6273 "uuid": "msg-uuid-123"
6274 }"#;
6275
6276 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6277 if let ClaudeOutput::Assistant(asst) = output {
6278 assert_eq!(asst.message.stop_details, None);
6279 assert_eq!(asst.message.context_management, None);
6280 let usage = asst.message.usage.unwrap();
6281 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
6282 } else {
6283 panic!("Expected Assistant message");
6284 }
6285 }
6286
6287 #[test]
6288 fn test_user_message_with_new_fields() {
6289 let json = r#"{
6290 "type": "user",
6291 "message": {
6292 "role": "user",
6293 "content": [{"type": "text", "text": "Hello"}]
6294 },
6295 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6296 "parent_tool_use_id": "toolu_123",
6297 "uuid": "user-msg-456"
6298 }"#;
6299
6300 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6301 if let ClaudeOutput::User(user) = output {
6302 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
6303 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
6304 } else {
6305 panic!("Expected User message");
6306 }
6307 }
6308
6309 #[test]
6315 fn test_user_message_preserves_tool_use_result_and_timestamp() {
6316 let json = r#"{
6317 "type":"user",
6318 "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"}]},
6319 "parent_tool_use_id":null,
6320 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6321 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
6322 "timestamp":"2026-05-12T23:12:04.121Z",
6323 "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"}}
6324 }"#;
6325
6326 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6327 let user = match output {
6328 ClaudeOutput::User(u) => u,
6329 other => panic!("Expected User message, got {:?}", other.message_type()),
6330 };
6331
6332 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
6333 let raw = user
6334 .tool_use_result
6335 .as_ref()
6336 .expect("tool_use_result must be captured");
6337 assert_eq!(raw["answers"]["Color"], "Blue");
6338 assert_eq!(raw["questions"][0]["header"], "Color");
6339
6340 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
6344 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
6345 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
6346 assert_eq!(
6347 reser["tool_use_result"]["questions"][0]["question"],
6348 "Which color do you prefer?"
6349 );
6350
6351 let typed: crate::AskUserQuestionInput = user
6354 .tool_use_result_as::<crate::AskUserQuestionInput>()
6355 .expect("tool_use_result present")
6356 .expect("AskUserQuestionInput parses");
6357 assert_eq!(typed.questions.len(), 1);
6358 assert_eq!(typed.questions[0].header, "Color");
6359 let answers = typed.answers.expect("answers populated");
6360 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
6361 }
6362
6363 #[test]
6366 fn test_user_message_without_tool_use_result_omits_field() {
6367 let json = r#"{
6368 "type":"user",
6369 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
6370 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
6371 }"#;
6372
6373 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6374 let user = match output {
6375 ClaudeOutput::User(u) => u,
6376 _ => panic!("Expected User message"),
6377 };
6378 assert!(user.tool_use_result.is_none());
6379 assert!(user.timestamp.is_none());
6380
6381 let reser = serde_json::to_value(&user).unwrap();
6382 assert!(reser.get("tool_use_result").is_none());
6383 assert!(reser.get("timestamp").is_none());
6384 }
6385
6386 #[test]
6390 fn test_subagent_result_exposes_token_accounting() {
6391 let json = r#"{
6392 "type":"user",
6393 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
6394 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
6395 "tool_use_result":{
6396 "status":"completed",
6397 "prompt":"Count the .rs files.",
6398 "agentId":"ac4f0276e9d4b6232",
6399 "agentType":"Explore",
6400 "content":[{"type":"text","text":"21"}],
6401 "resolvedModel":"claude-haiku-4-5-20251001",
6402 "totalDurationMs":6869,
6403 "totalTokens":7834,
6404 "totalToolUseCount":1,
6405 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
6406 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
6407 }
6408 }"#;
6409
6410 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6411 let user = match output {
6412 ClaudeOutput::User(u) => u,
6413 _ => panic!("Expected User message"),
6414 };
6415
6416 let result = user.subagent_result().expect("subagent result parses");
6417 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
6418 assert_eq!(
6419 result.resolved_model.as_deref(),
6420 Some("claude-haiku-4-5-20251001")
6421 );
6422 assert_eq!(result.total_tokens, Some(7834));
6423 assert_eq!(result.total_duration_ms, Some(6869));
6424 assert_eq!(result.total_tool_use_count, Some(1));
6425
6426 let usage = result.usage.expect("nested usage present");
6427 assert_eq!(usage.input_tokens, 6);
6428 assert_eq!(usage.cache_read_input_tokens, 7699);
6429
6430 let stats = result.tool_stats.expect("toolStats present");
6431 assert_eq!(stats.bash_count, 1);
6432 }
6433
6434 #[test]
6438 fn test_subagent_result_absent_for_non_task_result() {
6439 let json = r#"{
6440 "type":"user",
6441 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
6442 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6443 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
6444 }"#;
6445
6446 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6447 let user = match output {
6448 ClaudeOutput::User(u) => u,
6449 _ => panic!("Expected User message"),
6450 };
6451
6452 let result = user.subagent_result().expect("lenient parse");
6453 assert_eq!(result.total_tokens, None);
6454 assert_eq!(result.agent_type, None);
6455 }
6456
6457 #[test]
6458 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
6459 use serde_json::Value;
6460
6461 let raw: Value = serde_json::from_str(
6462 r#"{
6463 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
6464 "fast_mode_state":"off",
6465 "fast_mode_disabled_reason":"not_first_party",
6466 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
6467 }"#,
6468 )
6469 .unwrap();
6470 crate::io::assert_fully_wrapped(&raw);
6471
6472 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6473 let ClaudeOutput::System(sys) = output else {
6474 panic!("expected System");
6475 };
6476 let init = sys.as_init().expect("parses as init");
6477 assert_eq!(
6478 init.fast_mode_disabled_reason,
6479 Some(crate::FastModeDisabledReason::NotFirstParty)
6480 );
6481 let errs = init.mcp_server_errors.unwrap();
6482 assert_eq!(errs.len(), 1);
6483 assert_eq!(errs[0].name, "broken");
6484 assert_eq!(errs[0].error_type, "invalid_config");
6485 }
6486
6487 #[test]
6488 fn test_code_change_published_fully_wrapped() {
6489 use super::{KnownSystemEvent, SystemSubtype};
6490 use serde_json::Value;
6491
6492 let raw: Value = serde_json::from_str(
6493 r#"{
6494 "type":"system","subtype":"code_change_published",
6495 "provider":"github","url":"https://github.com/owner/repo/pull/42",
6496 "repo":"owner/repo","identifier":"42",
6497 "uuid":"u1","session_id":"s1"
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 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
6508 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
6509 panic!("expected CodeChangePublished event");
6510 };
6511 assert_eq!(msg.provider, "github");
6512 assert_eq!(msg.repo, "owner/repo");
6513 assert_eq!(msg.identifier, "42");
6514
6515 assert!(sys.is_code_change_published());
6516 assert!(!sys.is_vcs_state_changed());
6517 let direct = sys.as_code_change_published().expect("direct accessor");
6518 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
6519 assert!(sys.as_vcs_state_changed().is_none());
6520 }
6521
6522 #[test]
6523 fn test_feedback_draft_queued_fully_wrapped() {
6524 use super::{KnownSystemEvent, SystemSubtype};
6525 use serde_json::Value;
6526
6527 let raw: Value = serde_json::from_str(
6528 r#"{
6529 "type":"system","subtype":"feedback_draft_queued",
6530 "draft_id":"draft-1","draft_type":"bug_report",
6531 "title":"Tool output was truncated",
6532 "details_preview":"The last command omitted its final lines",
6533 "uuid":"u1","session_id":"s1","future_field":"preserved"
6534 }"#,
6535 )
6536 .unwrap();
6537 crate::io::assert_fully_wrapped(&raw);
6538
6539 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6540 let ClaudeOutput::System(sys) = output else {
6541 panic!("expected System");
6542 };
6543 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
6544 assert!(sys.is_feedback_draft_queued());
6545 assert!(!sys.is_vcs_state_changed());
6546
6547 let direct = sys
6548 .as_feedback_draft_queued()
6549 .expect("direct typed accessor");
6550 assert_eq!(direct.draft_id, "draft-1");
6551 assert_eq!(direct.draft_type, "bug_report");
6552 assert_eq!(direct.extra["future_field"], "preserved");
6553
6554 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
6555 panic!("expected FeedbackDraftQueued event");
6556 };
6557 assert_eq!(known.title, "Tool output was truncated");
6558 assert_eq!(
6559 sys.typed_value().expect("typed value")["future_field"],
6560 "preserved"
6561 );
6562 }
6563
6564 #[test]
6565 fn test_cloud_session_delta_fully_wrapped() {
6566 use super::{KnownSystemEvent, SystemSubtype};
6567 use serde_json::Value;
6568
6569 let raw: Value = serde_json::from_str(
6570 r#"{
6571 "type":"system","subtype":"cloud_session_delta",
6572 "seq":3,"changed":["serving","connection"],
6573 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
6574 "serving":{"state":"on"},"connection":{"state":"live"}},
6575 "uuid":"u1","session_id":"s1","future_field":"preserved"
6576 }"#,
6577 )
6578 .unwrap();
6579 crate::io::assert_fully_wrapped(&raw);
6580
6581 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6582 let ClaudeOutput::System(sys) = output else {
6583 panic!("expected System");
6584 };
6585 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
6586 assert!(sys.is_cloud_session_delta());
6587 assert!(!sys.is_feedback_draft_queued());
6588
6589 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
6590 assert_eq!(direct.seq, 3);
6591 assert_eq!(direct.changed, vec!["serving", "connection"]);
6592 assert_eq!(direct.cloud_session["id"], "session_abc");
6593 assert_eq!(direct.extra["future_field"], "preserved");
6594
6595 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
6596 panic!("expected CloudSessionDelta event");
6597 };
6598 assert_eq!(known.session_id, "s1");
6599 assert_eq!(
6600 sys.typed_value().expect("typed value")["future_field"],
6601 "preserved"
6602 );
6603 }
6604
6605 #[test]
6606 fn test_vcs_state_changed_fully_wrapped() {
6607 use super::{KnownSystemEvent, VcsMutationKind};
6608 use serde_json::Value;
6609
6610 for kind in ["commit", "push", "merge", "rebase"] {
6611 let raw: Value = serde_json::from_str(&format!(
6612 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
6613 kind
6614 ))
6615 .unwrap();
6616 crate::io::assert_fully_wrapped(&raw);
6617
6618 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6619 let ClaudeOutput::System(sys) = output else {
6620 panic!("expected System");
6621 };
6622 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6623 panic!("expected VcsStateChanged event");
6624 };
6625 assert_eq!(msg.kind.as_str(), kind);
6626 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
6627 }
6628
6629 let raw: Value = serde_json::from_str(
6631 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
6632 )
6633 .unwrap();
6634 crate::io::assert_fully_wrapped(&raw);
6635 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6636 let ClaudeOutput::System(sys) = output else {
6637 panic!("expected System");
6638 };
6639 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6640 panic!("expected VcsStateChanged event");
6641 };
6642 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
6643
6644 assert!(sys.is_vcs_state_changed());
6645 let direct = sys.as_vcs_state_changed().expect("direct accessor");
6646 assert_eq!(direct.cwd, "/repo");
6647 assert!(sys.as_code_change_published().is_none());
6648 }
6649
6650 #[test]
6651 fn test_assistant_aborted_and_resume_flags_roundtrip() {
6652 let json = r#"{
6653 "type":"assistant",
6654 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
6655 "session_id":"s1",
6656 "aborted":true,
6657 "resumed_from_incomplete_thinking":true
6658 }"#;
6659 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6660 let ClaudeOutput::Assistant(msg) = &output else {
6661 panic!("expected Assistant");
6662 };
6663 assert_eq!(msg.aborted, Some(true));
6664 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
6665 let reserialized = serde_json::to_string(&output).unwrap();
6666 assert!(reserialized.contains("\"aborted\":true"));
6667 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
6668
6669 let json = r#"{
6671 "type":"assistant",
6672 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
6673 "session_id":"s2"
6674 }"#;
6675 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6676 let reserialized = serde_json::to_string(&output).unwrap();
6677 assert!(!reserialized.contains("aborted"));
6678 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
6679 }
6680
6681 #[test]
6682 fn test_user_tool_result_meta_roundtrip() {
6683 let json = r#"{
6684 "type":"user",
6685 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6686 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6687 "tool_result_meta":[
6688 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6689 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6690 ]
6691 }"#;
6692 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6693 let ClaudeOutput::User(user) = &output else {
6694 panic!("expected User");
6695 };
6696 let meta = user.tool_result_meta.as_ref().unwrap();
6697 assert_eq!(meta.len(), 2);
6698 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6699 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6700 assert_eq!(meta[1].user_feedback, None);
6701
6702 let reserialized = serde_json::to_string(&output).unwrap();
6703 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6704 assert!(!reserialized.contains("\"user_feedback\":null"));
6705 }
6706
6707 #[test]
6711 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6712 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6713 let with_scope = serde_json::json!({
6714 "trigger": "refusal",
6715 "direction": "retry",
6716 "scope": "local",
6717 "original_model": "claude-fable-5",
6718 "fallback_model": "claude-opus-5",
6719 "request_id": null,
6720 "content": "Refused; retried on fallback model.",
6721 "uuid": "u1",
6722 "session_id": "s1"
6723 });
6724 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6725 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6726 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6727
6728 let mut without = with_scope.clone();
6731 without.as_object_mut().unwrap().remove("scope");
6732 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6733 assert_eq!(msg.scope, None);
6734 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6735
6736 assert_eq!(
6738 RefusalFallbackScope::from("workspace").as_str(),
6739 "workspace"
6740 );
6741 }
6742}