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