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 SessionWritesLost,
56 InstructionSizeWarning,
57 FileAttachmentsMissing,
58 PermissionCheckStatus,
59 InheritedAsksSettled,
60 UiFocus,
61 UiInvalidate,
62 UiLog,
63 UiPanes,
64 UiPlaced,
65 UiScroll,
66 UiStatus,
67 UiToast,
68 Unknown(String),
70}
71
72impl SystemSubtype {
73 pub fn as_str(&self) -> &str {
74 match self {
75 Self::Init => "init",
76 Self::Status => "status",
77 Self::CompactBoundary => "compact_boundary",
78 Self::ThinkingTokens => "thinking_tokens",
79 Self::TaskStarted => "task_started",
80 Self::TaskProgress => "task_progress",
81 Self::TaskUpdated => "task_updated",
82 Self::TaskNotification => "task_notification",
83 Self::ApiRetry => "api_retry",
84 Self::ControlRequestProgress => "control_request_progress",
85 Self::ModelRefusalFallback => "model_refusal_fallback",
86 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
87 Self::LocalCommandOutput => "local_command_output",
88 Self::HookStarted => "hook_started",
89 Self::HookProgress => "hook_progress",
90 Self::HookResponse => "hook_response",
91 Self::PluginInstall => "plugin_install",
92 Self::BackgroundTasksChanged => "background_tasks_changed",
93 Self::SessionStateChanged => "session_state_changed",
94 Self::WorkerShuttingDown => "worker_shutting_down",
95 Self::CommandsChanged => "commands_changed",
96 Self::Notification => "notification",
97 Self::FilesPersisted => "files_persisted",
98 Self::MemoryRecall => "memory_recall",
99 Self::ElicitationComplete => "elicitation_complete",
100 Self::PermissionDenied => "permission_denied",
101 Self::MirrorError => "mirror_error",
102 Self::Informational => "informational",
103 Self::CodeChangePublished => "code_change_published",
104 Self::VcsStateChanged => "vcs_state_changed",
105 Self::FeedbackDraftQueued => "feedback_draft_queued",
106 Self::CloudSessionDelta => "cloud_session_delta",
107 Self::DevIntent => "dev_intent",
108 Self::TurnHandoffAvailable => "turn_handoff_available",
109 Self::TurnPreempted => "turn_preempted",
110 Self::PeerMessageHold => "peer_message_hold",
111 Self::PerTurnEffortChanged => "per_turn_effort_changed",
112 Self::SessionTitleChanged => "session_title_changed",
113 Self::SessionWritesLost => "session_writes_lost",
114 Self::InstructionSizeWarning => "instruction_size_warning",
115 Self::FileAttachmentsMissing => "file_attachments_missing",
116 Self::PermissionCheckStatus => "permission_check_status",
117 Self::InheritedAsksSettled => "inherited_asks_settled",
118 Self::UiFocus => "ui_focus",
119 Self::UiInvalidate => "ui_invalidate",
120 Self::UiLog => "ui_log",
121 Self::UiPanes => "ui_panes",
122 Self::UiPlaced => "ui_placed",
123 Self::UiScroll => "ui_scroll",
124 Self::UiStatus => "ui_status",
125 Self::UiToast => "ui_toast",
126 Self::Unknown(s) => s.as_str(),
127 }
128 }
129}
130
131impl fmt::Display for SystemSubtype {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 f.write_str(self.as_str())
134 }
135}
136
137impl From<&str> for SystemSubtype {
138 fn from(s: &str) -> Self {
139 match s {
140 "init" => Self::Init,
141 "status" => Self::Status,
142 "compact_boundary" => Self::CompactBoundary,
143 "thinking_tokens" => Self::ThinkingTokens,
144 "task_started" => Self::TaskStarted,
145 "task_progress" => Self::TaskProgress,
146 "task_updated" => Self::TaskUpdated,
147 "task_notification" => Self::TaskNotification,
148 "api_retry" => Self::ApiRetry,
149 "control_request_progress" => Self::ControlRequestProgress,
150 "model_refusal_fallback" => Self::ModelRefusalFallback,
151 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
152 "local_command_output" => Self::LocalCommandOutput,
153 "hook_started" => Self::HookStarted,
154 "hook_progress" => Self::HookProgress,
155 "hook_response" => Self::HookResponse,
156 "plugin_install" => Self::PluginInstall,
157 "background_tasks_changed" => Self::BackgroundTasksChanged,
158 "session_state_changed" => Self::SessionStateChanged,
159 "worker_shutting_down" => Self::WorkerShuttingDown,
160 "commands_changed" => Self::CommandsChanged,
161 "notification" => Self::Notification,
162 "files_persisted" => Self::FilesPersisted,
163 "memory_recall" => Self::MemoryRecall,
164 "elicitation_complete" => Self::ElicitationComplete,
165 "permission_denied" => Self::PermissionDenied,
166 "mirror_error" => Self::MirrorError,
167 "informational" => Self::Informational,
168 "code_change_published" => Self::CodeChangePublished,
169 "vcs_state_changed" => Self::VcsStateChanged,
170 "feedback_draft_queued" => Self::FeedbackDraftQueued,
171 "cloud_session_delta" => Self::CloudSessionDelta,
172 "dev_intent" => Self::DevIntent,
173 "turn_handoff_available" => Self::TurnHandoffAvailable,
174 "turn_preempted" => Self::TurnPreempted,
175 "peer_message_hold" => Self::PeerMessageHold,
176 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
177 "session_title_changed" => Self::SessionTitleChanged,
178 "session_writes_lost" => Self::SessionWritesLost,
179 "instruction_size_warning" => Self::InstructionSizeWarning,
180 "file_attachments_missing" => Self::FileAttachmentsMissing,
181 "permission_check_status" => Self::PermissionCheckStatus,
182 "inherited_asks_settled" => Self::InheritedAsksSettled,
183 "ui_focus" => Self::UiFocus,
184 "ui_invalidate" => Self::UiInvalidate,
185 "ui_log" => Self::UiLog,
186 "ui_panes" => Self::UiPanes,
187 "ui_placed" => Self::UiPlaced,
188 "ui_scroll" => Self::UiScroll,
189 "ui_status" => Self::UiStatus,
190 "ui_toast" => Self::UiToast,
191 other => Self::Unknown(other.to_string()),
192 }
193 }
194}
195
196impl Serialize for SystemSubtype {
197 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
198 serializer.serialize_str(self.as_str())
199 }
200}
201
202impl<'de> Deserialize<'de> for SystemSubtype {
203 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
204 let s = String::deserialize(deserializer)?;
205 Ok(Self::from(s.as_str()))
206 }
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Hash)]
214pub enum MessageRole {
215 User,
216 Assistant,
217 Unknown(String),
219}
220
221impl MessageRole {
222 pub fn as_str(&self) -> &str {
223 match self {
224 Self::User => "user",
225 Self::Assistant => "assistant",
226 Self::Unknown(s) => s.as_str(),
227 }
228 }
229}
230
231impl fmt::Display for MessageRole {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 f.write_str(self.as_str())
234 }
235}
236
237impl From<&str> for MessageRole {
238 fn from(s: &str) -> Self {
239 match s {
240 "user" => Self::User,
241 "assistant" => Self::Assistant,
242 other => Self::Unknown(other.to_string()),
243 }
244 }
245}
246
247impl Serialize for MessageRole {
248 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
249 serializer.serialize_str(self.as_str())
250 }
251}
252
253impl<'de> Deserialize<'de> for MessageRole {
254 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
255 let s = String::deserialize(deserializer)?;
256 Ok(Self::from(s.as_str()))
257 }
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Hash)]
262pub enum CompactionTrigger {
263 Auto,
265 Manual,
267 Unknown(String),
269}
270
271impl CompactionTrigger {
272 pub fn as_str(&self) -> &str {
273 match self {
274 Self::Auto => "auto",
275 Self::Manual => "manual",
276 Self::Unknown(s) => s.as_str(),
277 }
278 }
279}
280
281impl fmt::Display for CompactionTrigger {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 f.write_str(self.as_str())
284 }
285}
286
287impl From<&str> for CompactionTrigger {
288 fn from(s: &str) -> Self {
289 match s {
290 "auto" => Self::Auto,
291 "manual" => Self::Manual,
292 other => Self::Unknown(other.to_string()),
293 }
294 }
295}
296
297impl Serialize for CompactionTrigger {
298 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
299 serializer.serialize_str(self.as_str())
300 }
301}
302
303impl<'de> Deserialize<'de> for CompactionTrigger {
304 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
305 let s = String::deserialize(deserializer)?;
306 Ok(Self::from(s.as_str()))
307 }
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, Hash)]
312pub enum StopReason {
313 EndTurn,
315 MaxTokens,
317 ToolUse,
319 Unknown(String),
321}
322
323impl StopReason {
324 pub fn as_str(&self) -> &str {
325 match self {
326 Self::EndTurn => "end_turn",
327 Self::MaxTokens => "max_tokens",
328 Self::ToolUse => "tool_use",
329 Self::Unknown(s) => s.as_str(),
330 }
331 }
332}
333
334impl fmt::Display for StopReason {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 f.write_str(self.as_str())
337 }
338}
339
340impl From<&str> for StopReason {
341 fn from(s: &str) -> Self {
342 match s {
343 "end_turn" => Self::EndTurn,
344 "max_tokens" => Self::MaxTokens,
345 "tool_use" => Self::ToolUse,
346 other => Self::Unknown(other.to_string()),
347 }
348 }
349}
350
351impl Serialize for StopReason {
352 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
353 serializer.serialize_str(self.as_str())
354 }
355}
356
357impl<'de> Deserialize<'de> for StopReason {
358 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
359 let s = String::deserialize(deserializer)?;
360 Ok(Self::from(s.as_str()))
361 }
362}
363
364#[derive(Debug, Clone, PartialEq, Eq, Hash)]
366pub enum ApiKeySource {
367 None,
369 User,
370 Project,
371 Org,
372 Temporary,
373 Oauth,
374 Unknown(String),
376}
377
378impl ApiKeySource {
379 pub fn as_str(&self) -> &str {
380 match self {
381 Self::None => "none",
382 Self::User => "user",
383 Self::Project => "project",
384 Self::Org => "org",
385 Self::Temporary => "temporary",
386 Self::Oauth => "oauth",
387 Self::Unknown(s) => s.as_str(),
388 }
389 }
390}
391
392impl fmt::Display for ApiKeySource {
393 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394 f.write_str(self.as_str())
395 }
396}
397
398impl From<&str> for ApiKeySource {
399 fn from(s: &str) -> Self {
400 match s {
401 "none" => Self::None,
402 "user" => Self::User,
403 "project" => Self::Project,
404 "org" => Self::Org,
405 "temporary" => Self::Temporary,
406 "oauth" => Self::Oauth,
407 other => Self::Unknown(other.to_string()),
408 }
409 }
410}
411
412impl Serialize for ApiKeySource {
413 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
414 serializer.serialize_str(self.as_str())
415 }
416}
417
418impl<'de> Deserialize<'de> for ApiKeySource {
419 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
420 let s = String::deserialize(deserializer)?;
421 Ok(Self::from(s.as_str()))
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Hash)]
427pub enum OutputStyle {
428 Default,
430 Unknown(String),
432}
433
434impl OutputStyle {
435 pub fn as_str(&self) -> &str {
436 match self {
437 Self::Default => "default",
438 Self::Unknown(s) => s.as_str(),
439 }
440 }
441}
442
443impl fmt::Display for OutputStyle {
444 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445 f.write_str(self.as_str())
446 }
447}
448
449impl From<&str> for OutputStyle {
450 fn from(s: &str) -> Self {
451 match s {
452 "default" => Self::Default,
453 other => Self::Unknown(other.to_string()),
454 }
455 }
456}
457
458impl Serialize for OutputStyle {
459 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
460 serializer.serialize_str(self.as_str())
461 }
462}
463
464impl<'de> Deserialize<'de> for OutputStyle {
465 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
466 let s = String::deserialize(deserializer)?;
467 Ok(Self::from(s.as_str()))
468 }
469}
470
471#[derive(Debug, Clone, PartialEq, Eq, Hash)]
473pub enum InitPermissionMode {
474 Default,
476 AcceptEdits,
477 BypassPermissions,
478 Plan,
479 DontAsk,
480 Auto,
481 Unknown(String),
483}
484
485impl InitPermissionMode {
486 pub fn as_str(&self) -> &str {
487 match self {
488 Self::Default => "default",
489 Self::AcceptEdits => "acceptEdits",
490 Self::BypassPermissions => "bypassPermissions",
491 Self::Plan => "plan",
492 Self::DontAsk => "dontAsk",
493 Self::Auto => "auto",
494 Self::Unknown(s) => s.as_str(),
495 }
496 }
497}
498
499impl fmt::Display for InitPermissionMode {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 f.write_str(self.as_str())
502 }
503}
504
505impl From<&str> for InitPermissionMode {
506 fn from(s: &str) -> Self {
507 match s {
508 "default" => Self::Default,
509 "acceptEdits" => Self::AcceptEdits,
510 "bypassPermissions" => Self::BypassPermissions,
511 "plan" => Self::Plan,
512 "dontAsk" => Self::DontAsk,
513 "auto" => Self::Auto,
514 other => Self::Unknown(other.to_string()),
515 }
516 }
517}
518
519impl Serialize for InitPermissionMode {
520 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
521 serializer.serialize_str(self.as_str())
522 }
523}
524
525impl<'de> Deserialize<'de> for InitPermissionMode {
526 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
527 let s = String::deserialize(deserializer)?;
528 Ok(Self::from(s.as_str()))
529 }
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Hash)]
534pub enum StatusMessageStatus {
535 Compacting,
537 Requesting,
539 Unknown(String),
541}
542
543impl StatusMessageStatus {
544 pub fn as_str(&self) -> &str {
545 match self {
546 Self::Compacting => "compacting",
547 Self::Requesting => "requesting",
548 Self::Unknown(s) => s.as_str(),
549 }
550 }
551}
552
553impl fmt::Display for StatusMessageStatus {
554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555 f.write_str(self.as_str())
556 }
557}
558
559impl From<&str> for StatusMessageStatus {
560 fn from(s: &str) -> Self {
561 match s {
562 "compacting" => Self::Compacting,
563 "requesting" => Self::Requesting,
564 other => Self::Unknown(other.to_string()),
565 }
566 }
567}
568
569impl Serialize for StatusMessageStatus {
570 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
571 serializer.serialize_str(self.as_str())
572 }
573}
574
575impl<'de> Deserialize<'de> for StatusMessageStatus {
576 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
577 let s = String::deserialize(deserializer)?;
578 Ok(Self::from(s.as_str()))
579 }
580}
581
582pub(crate) fn serialize_optional_uuid<S>(
584 uuid: &Option<Uuid>,
585 serializer: S,
586) -> Result<S::Ok, S::Error>
587where
588 S: Serializer,
589{
590 match uuid {
591 Some(id) => serializer.serialize_str(&id.to_string()),
592 None => serializer.serialize_none(),
593 }
594}
595
596pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
598where
599 D: Deserializer<'de>,
600{
601 let opt_str: Option<String> = Option::deserialize(deserializer)?;
602 match opt_str {
603 Some(s) => Uuid::parse_str(&s)
604 .map(Some)
605 .map_err(serde::de::Error::custom),
606 None => Ok(None),
607 }
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
613pub struct MessageOrigin {
614 pub kind: String,
615 #[serde(flatten)]
616 pub extra: serde_json::Map<String, Value>,
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
621pub struct SummarizeMetadata {
622 pub messages_summarized: u64,
623 #[serde(default, skip_serializing_if = "Option::is_none")]
624 pub user_context: Option<String>,
625 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub direction: Option<String>,
627}
628
629#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
631pub struct McpMeta {
632 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
633 pub meta: Option<Value>,
634 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub structured_content: Option<Value>,
636 #[serde(default, skip_serializing_if = "Vec::is_empty")]
641 pub resource_links: Vec<ResourceLink>,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
648pub struct ResourceLink {
649 pub uri: String,
650 pub name: String,
651 #[serde(default, skip_serializing_if = "Option::is_none")]
652 pub title: Option<String>,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub description: Option<String>,
655 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
656 pub mime_type: Option<String>,
657 #[serde(default, skip_serializing_if = "Option::is_none")]
658 pub size: Option<u64>,
659 #[serde(default, skip_serializing_if = "Option::is_none")]
660 pub annotations: Option<Value>,
661}
662
663#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
665pub struct ToolResultMeta {
666 pub id: String,
668 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub non_execution_kind: Option<String>,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub user_feedback: Option<String>,
679 #[serde(default, skip_serializing_if = "Option::is_none")]
684 pub remedy: Option<ToolResultRemedy>,
685 #[serde(default, skip_serializing_if = "Option::is_none")]
692 pub permission_decision: Option<ToolPermissionDecision>,
693 #[serde(default, skip_serializing_if = "Option::is_none")]
700 pub built_in_tool: Option<String>,
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
707pub struct ToolPermissionDecision {
708 pub decision: PermissionDecisionOutcome,
710 pub source: String,
718 #[serde(default, skip_serializing_if = "Option::is_none")]
724 pub reason_type: Option<PermissionDecisionReasonType>,
725}
726
727#[derive(Debug, Clone, PartialEq, Eq, Hash)]
729pub enum PermissionDecisionOutcome {
730 Accept,
732 Reject,
734 Cancelled,
736 Unknown(String),
738}
739
740impl PermissionDecisionOutcome {
741 pub fn as_str(&self) -> &str {
742 match self {
743 Self::Accept => "accept",
744 Self::Reject => "reject",
745 Self::Cancelled => "cancelled",
746 Self::Unknown(s) => s.as_str(),
747 }
748 }
749}
750
751impl fmt::Display for PermissionDecisionOutcome {
752 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753 f.write_str(self.as_str())
754 }
755}
756
757impl From<&str> for PermissionDecisionOutcome {
758 fn from(s: &str) -> Self {
759 match s {
760 "accept" => Self::Accept,
761 "reject" => Self::Reject,
762 "cancelled" => Self::Cancelled,
763 other => Self::Unknown(other.to_string()),
764 }
765 }
766}
767
768impl Serialize for PermissionDecisionOutcome {
769 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
770 serializer.serialize_str(self.as_str())
771 }
772}
773
774impl<'de> Deserialize<'de> for PermissionDecisionOutcome {
775 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
776 let s = String::deserialize(deserializer)?;
777 Ok(Self::from(s.as_str()))
778 }
779}
780
781#[derive(Debug, Clone, PartialEq, Eq, Hash)]
784pub enum PermissionDecisionReasonType {
785 Rule,
787 Mode,
789 SubcommandResults,
791 PermissionPromptTool,
793 Hook,
795 AsyncAgent,
797 SandboxOverride,
799 WorkingDir,
801 SafetyCheck,
803 Classifier,
805 Other,
807 Unknown(String),
809}
810
811impl PermissionDecisionReasonType {
812 pub fn as_str(&self) -> &str {
813 match self {
814 Self::Rule => "rule",
815 Self::Mode => "mode",
816 Self::SubcommandResults => "subcommandResults",
817 Self::PermissionPromptTool => "permissionPromptTool",
818 Self::Hook => "hook",
819 Self::AsyncAgent => "asyncAgent",
820 Self::SandboxOverride => "sandboxOverride",
821 Self::WorkingDir => "workingDir",
822 Self::SafetyCheck => "safetyCheck",
823 Self::Classifier => "classifier",
824 Self::Other => "other",
825 Self::Unknown(s) => s.as_str(),
826 }
827 }
828}
829
830impl fmt::Display for PermissionDecisionReasonType {
831 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
832 f.write_str(self.as_str())
833 }
834}
835
836impl From<&str> for PermissionDecisionReasonType {
837 fn from(s: &str) -> Self {
838 match s {
839 "rule" => Self::Rule,
840 "mode" => Self::Mode,
841 "subcommandResults" => Self::SubcommandResults,
842 "permissionPromptTool" => Self::PermissionPromptTool,
843 "hook" => Self::Hook,
844 "asyncAgent" => Self::AsyncAgent,
845 "sandboxOverride" => Self::SandboxOverride,
846 "workingDir" => Self::WorkingDir,
847 "safetyCheck" => Self::SafetyCheck,
848 "classifier" => Self::Classifier,
849 "other" => Self::Other,
850 other => Self::Unknown(other.to_string()),
851 }
852 }
853}
854
855impl Serialize for PermissionDecisionReasonType {
856 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
857 serializer.serialize_str(self.as_str())
858 }
859}
860
861impl<'de> Deserialize<'de> for PermissionDecisionReasonType {
862 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
863 let s = String::deserialize(deserializer)?;
864 Ok(Self::from(s.as_str()))
865 }
866}
867
868#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
873pub struct ToolResultRemedy {
874 pub kind: ToolResultRemedyKind,
877 #[serde(default, skip_serializing_if = "Option::is_none")]
881 pub servers: Option<Vec<String>>,
882 #[serde(default, skip_serializing_if = "Option::is_none")]
885 pub provider: Option<RemedyLoginProvider>,
886 #[serde(default, skip_serializing_if = "Option::is_none")]
890 pub path: Option<String>,
891 #[serde(default, skip_serializing_if = "Option::is_none")]
896 pub managed: Option<bool>,
897 #[serde(default, skip_serializing_if = "Option::is_none")]
899 pub feature: Option<RemedyFeature>,
900 #[serde(default, skip_serializing_if = "Option::is_none")]
902 pub cause: Option<RemedyFeatureCause>,
903 #[serde(default, skip_serializing_if = "Option::is_none")]
905 pub policy_kind: Option<RemedyPolicyKind>,
906}
907
908#[derive(Debug, Clone, PartialEq, Eq, Hash)]
910pub enum ToolResultRemedyKind {
911 McpNeedsAuth,
913 McpDisabled,
915 McpRequiredMissing,
917 AuthExpired,
919 AuthOverridden,
921 AuthMissingScope,
923 DesignNeedsAuthorization,
925 SandboxViolation,
927 OutsideReadsBlocked,
929 MemoryPaused,
931 FeatureDisabled,
933 PolicyDenied,
936 SpawnArgLimit,
938 StagedForReview,
940 Unknown(String),
942}
943
944impl ToolResultRemedyKind {
945 pub fn as_str(&self) -> &str {
946 match self {
947 Self::McpNeedsAuth => "mcp_needs_auth",
948 Self::McpDisabled => "mcp_disabled",
949 Self::McpRequiredMissing => "mcp_required_missing",
950 Self::AuthExpired => "auth_expired",
951 Self::AuthOverridden => "auth_overridden",
952 Self::AuthMissingScope => "auth_missing_scope",
953 Self::DesignNeedsAuthorization => "design_needs_authorization",
954 Self::SandboxViolation => "sandbox_violation",
955 Self::OutsideReadsBlocked => "outside_reads_blocked",
956 Self::MemoryPaused => "memory_paused",
957 Self::FeatureDisabled => "feature_disabled",
958 Self::PolicyDenied => "policy_denied",
959 Self::SpawnArgLimit => "spawn_arg_limit",
960 Self::StagedForReview => "staged_for_review",
961 Self::Unknown(s) => s.as_str(),
962 }
963 }
964}
965
966impl fmt::Display for ToolResultRemedyKind {
967 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
968 f.write_str(self.as_str())
969 }
970}
971
972impl From<&str> for ToolResultRemedyKind {
973 fn from(s: &str) -> Self {
974 match s {
975 "mcp_needs_auth" => Self::McpNeedsAuth,
976 "mcp_disabled" => Self::McpDisabled,
977 "mcp_required_missing" => Self::McpRequiredMissing,
978 "auth_expired" => Self::AuthExpired,
979 "auth_overridden" => Self::AuthOverridden,
980 "auth_missing_scope" => Self::AuthMissingScope,
981 "design_needs_authorization" => Self::DesignNeedsAuthorization,
982 "sandbox_violation" => Self::SandboxViolation,
983 "outside_reads_blocked" => Self::OutsideReadsBlocked,
984 "memory_paused" => Self::MemoryPaused,
985 "feature_disabled" => Self::FeatureDisabled,
986 "policy_denied" => Self::PolicyDenied,
987 "spawn_arg_limit" => Self::SpawnArgLimit,
988 "staged_for_review" => Self::StagedForReview,
989 other => Self::Unknown(other.to_string()),
990 }
991 }
992}
993
994impl Serialize for ToolResultRemedyKind {
995 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
996 serializer.serialize_str(self.as_str())
997 }
998}
999
1000impl<'de> Deserialize<'de> for ToolResultRemedyKind {
1001 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1002 let s = String::deserialize(deserializer)?;
1003 Ok(Self::from(s.as_str()))
1004 }
1005}
1006
1007#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1009pub enum RemedyLoginProvider {
1010 ClaudeAi,
1012 ClaudeDesign,
1014 Unknown(String),
1016}
1017
1018impl RemedyLoginProvider {
1019 pub fn as_str(&self) -> &str {
1020 match self {
1021 Self::ClaudeAi => "claude_ai",
1022 Self::ClaudeDesign => "claude_design",
1023 Self::Unknown(s) => s.as_str(),
1024 }
1025 }
1026}
1027
1028impl fmt::Display for RemedyLoginProvider {
1029 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1030 f.write_str(self.as_str())
1031 }
1032}
1033
1034impl From<&str> for RemedyLoginProvider {
1035 fn from(s: &str) -> Self {
1036 match s {
1037 "claude_ai" => Self::ClaudeAi,
1038 "claude_design" => Self::ClaudeDesign,
1039 other => Self::Unknown(other.to_string()),
1040 }
1041 }
1042}
1043
1044impl Serialize for RemedyLoginProvider {
1045 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1046 serializer.serialize_str(self.as_str())
1047 }
1048}
1049
1050impl<'de> Deserialize<'de> for RemedyLoginProvider {
1051 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1052 let s = String::deserialize(deserializer)?;
1053 Ok(Self::from(s.as_str()))
1054 }
1055}
1056
1057#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1060pub enum RemedyFeature {
1061 Workflows,
1062 Artifacts,
1063 Unknown(String),
1065}
1066
1067impl RemedyFeature {
1068 pub fn as_str(&self) -> &str {
1069 match self {
1070 Self::Workflows => "workflows",
1071 Self::Artifacts => "artifacts",
1072 Self::Unknown(s) => s.as_str(),
1073 }
1074 }
1075}
1076
1077impl fmt::Display for RemedyFeature {
1078 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1079 f.write_str(self.as_str())
1080 }
1081}
1082
1083impl From<&str> for RemedyFeature {
1084 fn from(s: &str) -> Self {
1085 match s {
1086 "workflows" => Self::Workflows,
1087 "artifacts" => Self::Artifacts,
1088 other => Self::Unknown(other.to_string()),
1089 }
1090 }
1091}
1092
1093impl Serialize for RemedyFeature {
1094 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1095 serializer.serialize_str(self.as_str())
1096 }
1097}
1098
1099impl<'de> Deserialize<'de> for RemedyFeature {
1100 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1101 let s = String::deserialize(deserializer)?;
1102 Ok(Self::from(s.as_str()))
1103 }
1104}
1105
1106#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1109pub enum RemedyFeatureCause {
1110 ManagedSettings,
1113 OrgPolicy,
1115 Unavailable,
1117 UserSetting,
1120 UnsupportedRuntime,
1124 Unknown(String),
1126}
1127
1128impl RemedyFeatureCause {
1129 pub fn as_str(&self) -> &str {
1130 match self {
1131 Self::ManagedSettings => "managed_settings",
1132 Self::OrgPolicy => "org_policy",
1133 Self::Unavailable => "unavailable",
1134 Self::UserSetting => "user_setting",
1135 Self::UnsupportedRuntime => "unsupported_runtime",
1136 Self::Unknown(s) => s.as_str(),
1137 }
1138 }
1139}
1140
1141impl fmt::Display for RemedyFeatureCause {
1142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143 f.write_str(self.as_str())
1144 }
1145}
1146
1147impl From<&str> for RemedyFeatureCause {
1148 fn from(s: &str) -> Self {
1149 match s {
1150 "managed_settings" => Self::ManagedSettings,
1151 "org_policy" => Self::OrgPolicy,
1152 "unavailable" => Self::Unavailable,
1153 "user_setting" => Self::UserSetting,
1154 "unsupported_runtime" => Self::UnsupportedRuntime,
1155 other => Self::Unknown(other.to_string()),
1156 }
1157 }
1158}
1159
1160impl Serialize for RemedyFeatureCause {
1161 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1162 serializer.serialize_str(self.as_str())
1163 }
1164}
1165
1166impl<'de> Deserialize<'de> for RemedyFeatureCause {
1167 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1168 let s = String::deserialize(deserializer)?;
1169 Ok(Self::from(s.as_str()))
1170 }
1171}
1172
1173#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1176pub enum RemedyPolicyKind {
1177 OrgDenied,
1179 Latched,
1182 Unknown(String),
1184}
1185
1186impl RemedyPolicyKind {
1187 pub fn as_str(&self) -> &str {
1188 match self {
1189 Self::OrgDenied => "org_denied",
1190 Self::Latched => "latched",
1191 Self::Unknown(s) => s.as_str(),
1192 }
1193 }
1194}
1195
1196impl fmt::Display for RemedyPolicyKind {
1197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1198 f.write_str(self.as_str())
1199 }
1200}
1201
1202impl From<&str> for RemedyPolicyKind {
1203 fn from(s: &str) -> Self {
1204 match s {
1205 "org_denied" => Self::OrgDenied,
1206 "latched" => Self::Latched,
1207 other => Self::Unknown(other.to_string()),
1208 }
1209 }
1210}
1211
1212impl Serialize for RemedyPolicyKind {
1213 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1214 serializer.serialize_str(self.as_str())
1215 }
1216}
1217
1218impl<'de> Deserialize<'de> for RemedyPolicyKind {
1219 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1220 let s = String::deserialize(deserializer)?;
1221 Ok(Self::from(s.as_str()))
1222 }
1223}
1224
1225#[derive(Debug, Clone, Serialize, Deserialize)]
1227pub struct UserMessage {
1228 pub message: MessageContent,
1229 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1230 #[serde(
1231 serialize_with = "serialize_optional_uuid",
1232 deserialize_with = "deserialize_optional_uuid"
1233 )]
1234 pub session_id: Option<Uuid>,
1235 #[serde(skip_serializing_if = "Option::is_none")]
1237 pub parent_tool_use_id: Option<String>,
1238 #[serde(skip_serializing_if = "Option::is_none")]
1240 pub uuid: Option<String>,
1241 #[serde(skip_serializing_if = "Option::is_none")]
1243 pub timestamp: Option<String>,
1244 #[serde(skip_serializing_if = "Option::is_none")]
1251 pub tool_use_result: Option<serde_json::Value>,
1252 #[serde(default, skip_serializing_if = "Option::is_none")]
1257 pub agent_id: Option<String>,
1258 #[serde(skip_serializing_if = "Option::is_none")]
1261 pub subagent_type: Option<String>,
1262 #[serde(skip_serializing_if = "Option::is_none")]
1264 pub task_description: Option<String>,
1265 #[serde(skip_serializing_if = "Option::is_none")]
1266 pub origin: Option<MessageOrigin>,
1267 #[serde(skip_serializing_if = "Option::is_none")]
1268 pub priority: Option<String>,
1269 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1270 pub is_synthetic: Option<bool>,
1271 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1272 pub should_query: Option<bool>,
1273 #[serde(default, skip_serializing_if = "Option::is_none")]
1274 pub is_meta: Option<bool>,
1275 #[serde(default, skip_serializing_if = "Option::is_none")]
1276 pub is_visible_in_transcript_only: Option<bool>,
1277 #[serde(default, skip_serializing_if = "Option::is_none")]
1278 pub is_virtual: Option<bool>,
1279 #[serde(default, skip_serializing_if = "Option::is_none")]
1280 pub is_compact_summary: Option<bool>,
1281 #[serde(skip_serializing_if = "Option::is_none")]
1282 pub summarize_metadata: Option<SummarizeMetadata>,
1283 #[serde(skip_serializing_if = "Option::is_none")]
1284 pub mcp_meta: Option<McpMeta>,
1285 #[serde(skip_serializing_if = "Option::is_none")]
1288 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1289 #[serde(skip_serializing_if = "Option::is_none")]
1290 pub source_tool_use_id: Option<String>,
1291 #[serde(skip_serializing_if = "Option::is_none")]
1292 pub source_tool_assistant_uuid: Option<String>,
1293 #[serde(skip_serializing_if = "Option::is_none")]
1294 pub image_paste_ids: Option<Vec<u64>>,
1295 #[serde(skip_serializing_if = "Option::is_none")]
1296 pub client_platform: Option<String>,
1297 #[serde(skip_serializing_if = "Option::is_none")]
1298 pub inbound_origin: Option<String>,
1299 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1300 pub is_replay: Option<bool>,
1301 #[serde(skip_serializing_if = "Option::is_none")]
1302 pub file_attachments: Option<Vec<Value>>,
1303 #[serde(default, skip_serializing_if = "Option::is_none")]
1305 pub seeded_summon: Option<bool>,
1306 #[serde(default, skip_serializing_if = "Option::is_none")]
1309 pub client_composed: Option<bool>,
1310 #[serde(default, skip_serializing_if = "Option::is_none")]
1319 pub text_not_typed: Option<bool>,
1320 #[serde(default, skip_serializing_if = "Option::is_none")]
1327 pub initiator: Option<String>,
1328 #[serde(default, skip_serializing_if = "Option::is_none")]
1334 pub pasted_content: Option<Vec<Value>>,
1335 #[serde(default, skip_serializing_if = "Option::is_none")]
1339 pub historical: Option<bool>,
1340 #[serde(default, skip_serializing_if = "Option::is_none")]
1346 pub server_received_wall_ms: Option<f64>,
1347 #[serde(default, skip_serializing_if = "Option::is_none")]
1351 pub refused_message_id: Option<String>,
1352 #[serde(default, skip_serializing_if = "Option::is_none")]
1359 pub requeue_if_interrupted_within_ms: Option<u64>,
1360 #[serde(default, skip_serializing_if = "Option::is_none")]
1369 pub ultrathink_typed: Option<bool>,
1370 #[serde(default, skip_serializing_if = "Option::is_none")]
1374 pub tool_states: Option<Vec<Value>>,
1375 #[serde(default, skip_serializing_if = "Option::is_none")]
1381 pub plugin_prompt: Option<PluginPrompt>,
1382}
1383
1384#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1387pub struct PluginPrompt {
1388 pub name: String,
1391 pub text: String,
1397}
1398
1399impl UserMessage {
1400 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1408 &self,
1409 ) -> Option<Result<T, serde_json::Error>> {
1410 self.tool_use_result
1411 .as_ref()
1412 .map(|v| serde_json::from_value(v.clone()))
1413 }
1414
1415 pub fn subagent_result(&self) -> Option<SubagentResult> {
1426 self.tool_use_result
1427 .as_ref()
1428 .and_then(|v| serde_json::from_value(v.clone()).ok())
1429 }
1430}
1431
1432#[derive(Debug, Clone, Serialize, Deserialize)]
1443pub struct SubagentResult {
1444 #[serde(skip_serializing_if = "Option::is_none")]
1446 pub status: Option<String>,
1447 #[serde(skip_serializing_if = "Option::is_none")]
1449 pub prompt: Option<String>,
1450 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1452 pub agent_id: Option<String>,
1453 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1455 pub agent_type: Option<String>,
1456 #[serde(
1458 default,
1459 deserialize_with = "deserialize_content_blocks",
1460 skip_serializing_if = "Vec::is_empty"
1461 )]
1462 pub content: Vec<ContentBlock>,
1463 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1465 pub resolved_model: Option<String>,
1466 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1468 pub total_duration_ms: Option<u64>,
1469 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1471 pub total_tokens: Option<u64>,
1472 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1474 pub total_tool_use_count: Option<u64>,
1475 #[serde(skip_serializing_if = "Option::is_none")]
1477 pub usage: Option<super::result::UsageInfo>,
1478 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1480 pub tool_stats: Option<SubagentToolStats>,
1481}
1482
1483#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1488#[serde(rename_all = "camelCase")]
1489pub struct SubagentToolStats {
1490 #[serde(default)]
1491 pub read_count: u64,
1492 #[serde(default)]
1493 pub search_count: u64,
1494 #[serde(default)]
1495 pub bash_count: u64,
1496 #[serde(default)]
1497 pub edit_file_count: u64,
1498 #[serde(default)]
1499 pub lines_added: u64,
1500 #[serde(default)]
1501 pub lines_removed: u64,
1502 #[serde(default)]
1503 pub other_tool_count: u64,
1504 #[serde(flatten)]
1505 pub extra: serde_json::Map<String, Value>,
1506}
1507
1508#[derive(Debug, Clone, Default, PartialEq, Eq)]
1535pub struct SubagentUsageRollup {
1536 pub subagent_tokens: u64,
1539 pub agent_count: u32,
1541 pub tool_uses: u64,
1543 pub duration_ms: u64,
1545 seen_agent_ids: std::collections::BTreeSet<String>,
1546}
1547
1548impl SubagentUsageRollup {
1549 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1555 match output {
1556 ClaudeOutput::User(user) => self.observe_user(user),
1557 _ => false,
1558 }
1559 }
1560
1561 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1568 let Some(result) = user.subagent_result() else {
1569 return false;
1570 };
1571 if result.total_tokens.is_none() && result.agent_id.is_none() {
1572 return false;
1573 }
1574 if let Some(agent_id) = &result.agent_id {
1575 if !self.seen_agent_ids.insert(agent_id.clone()) {
1576 return false;
1577 }
1578 }
1579 self.agent_count += 1;
1580 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1581 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1582 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1583 true
1584 }
1585}
1586
1587#[derive(Debug, Clone, Serialize, Deserialize)]
1589pub struct MessageContent {
1590 pub role: MessageRole,
1591 #[serde(deserialize_with = "deserialize_content_blocks")]
1592 pub content: Vec<ContentBlock>,
1593}
1594
1595#[derive(Debug, Clone, Serialize, Deserialize)]
1597pub struct SystemMessage {
1598 pub subtype: SystemSubtype,
1599 #[serde(flatten)]
1600 pub data: Value, }
1602
1603impl SystemMessage {
1604 pub fn is_init(&self) -> bool {
1606 self.subtype == SystemSubtype::Init
1607 }
1608
1609 pub fn is_status(&self) -> bool {
1611 self.subtype == SystemSubtype::Status
1612 }
1613
1614 pub fn is_compact_boundary(&self) -> bool {
1616 self.subtype == SystemSubtype::CompactBoundary
1617 }
1618
1619 pub fn as_init(&self) -> Option<InitMessage> {
1621 if self.subtype != SystemSubtype::Init {
1622 return None;
1623 }
1624 serde_json::from_value(self.data.clone()).ok()
1625 }
1626
1627 pub fn as_status(&self) -> Option<StatusMessage> {
1629 if self.subtype != SystemSubtype::Status {
1630 return None;
1631 }
1632 serde_json::from_value(self.data.clone()).ok()
1633 }
1634
1635 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1637 if self.subtype != SystemSubtype::CompactBoundary {
1638 return None;
1639 }
1640 serde_json::from_value(self.data.clone()).ok()
1641 }
1642
1643 pub fn is_task_started(&self) -> bool {
1645 self.subtype == SystemSubtype::TaskStarted
1646 }
1647
1648 pub fn is_task_progress(&self) -> bool {
1650 self.subtype == SystemSubtype::TaskProgress
1651 }
1652
1653 pub fn is_task_notification(&self) -> bool {
1655 self.subtype == SystemSubtype::TaskNotification
1656 }
1657
1658 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1660 if self.subtype != SystemSubtype::TaskStarted {
1661 return None;
1662 }
1663 serde_json::from_value(self.data.clone()).ok()
1664 }
1665
1666 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1668 if self.subtype != SystemSubtype::TaskProgress {
1669 return None;
1670 }
1671 serde_json::from_value(self.data.clone()).ok()
1672 }
1673
1674 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1676 if self.subtype != SystemSubtype::TaskNotification {
1677 return None;
1678 }
1679 serde_json::from_value(self.data.clone()).ok()
1680 }
1681
1682 pub fn is_task_updated(&self) -> bool {
1684 self.subtype == SystemSubtype::TaskUpdated
1685 }
1686
1687 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1689 if self.subtype != SystemSubtype::TaskUpdated {
1690 return None;
1691 }
1692 serde_json::from_value(self.data.clone()).ok()
1693 }
1694
1695 pub fn is_thinking_tokens(&self) -> bool {
1697 self.subtype == SystemSubtype::ThinkingTokens
1698 }
1699
1700 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1702 if self.subtype != SystemSubtype::ThinkingTokens {
1703 return None;
1704 }
1705 serde_json::from_value(self.data.clone()).ok()
1706 }
1707
1708 pub fn is_code_change_published(&self) -> bool {
1710 self.subtype == SystemSubtype::CodeChangePublished
1711 }
1712
1713 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1715 if self.subtype != SystemSubtype::CodeChangePublished {
1716 return None;
1717 }
1718 serde_json::from_value(self.data.clone()).ok()
1719 }
1720
1721 pub fn is_vcs_state_changed(&self) -> bool {
1723 self.subtype == SystemSubtype::VcsStateChanged
1724 }
1725
1726 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1728 if self.subtype != SystemSubtype::VcsStateChanged {
1729 return None;
1730 }
1731 serde_json::from_value(self.data.clone()).ok()
1732 }
1733
1734 pub fn is_feedback_draft_queued(&self) -> bool {
1736 self.subtype == SystemSubtype::FeedbackDraftQueued
1737 }
1738
1739 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1741 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1742 return None;
1743 }
1744 serde_json::from_value(self.data.clone()).ok()
1745 }
1746
1747 pub fn is_cloud_session_delta(&self) -> bool {
1749 self.subtype == SystemSubtype::CloudSessionDelta
1750 }
1751
1752 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1754 if self.subtype != SystemSubtype::CloudSessionDelta {
1755 return None;
1756 }
1757 serde_json::from_value(self.data.clone()).ok()
1758 }
1759
1760 pub fn is_dev_intent(&self) -> bool {
1762 self.subtype == SystemSubtype::DevIntent
1763 }
1764
1765 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1767 if self.subtype != SystemSubtype::DevIntent {
1768 return None;
1769 }
1770 serde_json::from_value(self.data.clone()).ok()
1771 }
1772
1773 pub fn is_turn_handoff_available(&self) -> bool {
1775 self.subtype == SystemSubtype::TurnHandoffAvailable
1776 }
1777
1778 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1780 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1781 return None;
1782 }
1783 serde_json::from_value(self.data.clone()).ok()
1784 }
1785
1786 pub fn is_turn_preempted(&self) -> bool {
1788 self.subtype == SystemSubtype::TurnPreempted
1789 }
1790
1791 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1793 if self.subtype != SystemSubtype::TurnPreempted {
1794 return None;
1795 }
1796 serde_json::from_value(self.data.clone()).ok()
1797 }
1798
1799 pub fn is_peer_message_hold(&self) -> bool {
1801 self.subtype == SystemSubtype::PeerMessageHold
1802 }
1803
1804 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1806 if self.subtype != SystemSubtype::PeerMessageHold {
1807 return None;
1808 }
1809 serde_json::from_value(self.data.clone()).ok()
1810 }
1811
1812 pub fn is_per_turn_effort_changed(&self) -> bool {
1814 self.subtype == SystemSubtype::PerTurnEffortChanged
1815 }
1816
1817 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1819 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1820 return None;
1821 }
1822 serde_json::from_value(self.data.clone()).ok()
1823 }
1824
1825 pub fn is_session_title_changed(&self) -> bool {
1827 self.subtype == SystemSubtype::SessionTitleChanged
1828 }
1829
1830 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1832 if self.subtype != SystemSubtype::SessionTitleChanged {
1833 return None;
1834 }
1835 serde_json::from_value(self.data.clone()).ok()
1836 }
1837
1838 pub fn is_session_writes_lost(&self) -> bool {
1840 self.subtype == SystemSubtype::SessionWritesLost
1841 }
1842
1843 pub fn as_session_writes_lost(&self) -> Option<SessionWritesLostMessage> {
1845 if self.subtype != SystemSubtype::SessionWritesLost {
1846 return None;
1847 }
1848 serde_json::from_value(self.data.clone()).ok()
1849 }
1850
1851 pub fn is_instruction_size_warning(&self) -> bool {
1853 self.subtype == SystemSubtype::InstructionSizeWarning
1854 }
1855
1856 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1858 if self.subtype != SystemSubtype::InstructionSizeWarning {
1859 return None;
1860 }
1861 serde_json::from_value(self.data.clone()).ok()
1862 }
1863
1864 pub fn is_permission_check_status(&self) -> bool {
1866 self.subtype == SystemSubtype::PermissionCheckStatus
1867 }
1868
1869 pub fn as_permission_check_status(&self) -> Option<PermissionCheckStatusMessage> {
1871 if self.subtype != SystemSubtype::PermissionCheckStatus {
1872 return None;
1873 }
1874 serde_json::from_value(self.data.clone()).ok()
1875 }
1876
1877 pub fn is_inherited_asks_settled(&self) -> bool {
1879 self.subtype == SystemSubtype::InheritedAsksSettled
1880 }
1881
1882 pub fn as_inherited_asks_settled(&self) -> Option<InheritedAsksSettledMessage> {
1884 if self.subtype != SystemSubtype::InheritedAsksSettled {
1885 return None;
1886 }
1887 serde_json::from_value(self.data.clone()).ok()
1888 }
1889
1890 pub fn is_file_attachments_missing(&self) -> bool {
1892 self.subtype == SystemSubtype::FileAttachmentsMissing
1893 }
1894
1895 pub fn as_file_attachments_missing(&self) -> Option<FileAttachmentsMissingMessage> {
1897 if self.subtype != SystemSubtype::FileAttachmentsMissing {
1898 return None;
1899 }
1900 serde_json::from_value(self.data.clone()).ok()
1901 }
1902
1903 pub fn is_ui_focus(&self) -> bool {
1905 self.subtype == SystemSubtype::UiFocus
1906 }
1907
1908 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1910 if self.subtype != SystemSubtype::UiFocus {
1911 return None;
1912 }
1913 serde_json::from_value(self.data.clone()).ok()
1914 }
1915
1916 pub fn is_ui_invalidate(&self) -> bool {
1918 self.subtype == SystemSubtype::UiInvalidate
1919 }
1920
1921 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1923 if self.subtype != SystemSubtype::UiInvalidate {
1924 return None;
1925 }
1926 serde_json::from_value(self.data.clone()).ok()
1927 }
1928
1929 pub fn is_ui_log(&self) -> bool {
1931 self.subtype == SystemSubtype::UiLog
1932 }
1933
1934 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1936 if self.subtype != SystemSubtype::UiLog {
1937 return None;
1938 }
1939 serde_json::from_value(self.data.clone()).ok()
1940 }
1941
1942 pub fn is_ui_panes(&self) -> bool {
1944 self.subtype == SystemSubtype::UiPanes
1945 }
1946
1947 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1949 if self.subtype != SystemSubtype::UiPanes {
1950 return None;
1951 }
1952 serde_json::from_value(self.data.clone()).ok()
1953 }
1954
1955 pub fn is_ui_placed(&self) -> bool {
1957 self.subtype == SystemSubtype::UiPlaced
1958 }
1959
1960 pub fn as_ui_placed(&self) -> Option<UiPlacedMessage> {
1962 if self.subtype != SystemSubtype::UiPlaced {
1963 return None;
1964 }
1965 serde_json::from_value(self.data.clone()).ok()
1966 }
1967
1968 pub fn is_ui_scroll(&self) -> bool {
1970 self.subtype == SystemSubtype::UiScroll
1971 }
1972
1973 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1975 if self.subtype != SystemSubtype::UiScroll {
1976 return None;
1977 }
1978 serde_json::from_value(self.data.clone()).ok()
1979 }
1980
1981 pub fn is_ui_status(&self) -> bool {
1983 self.subtype == SystemSubtype::UiStatus
1984 }
1985
1986 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1988 if self.subtype != SystemSubtype::UiStatus {
1989 return None;
1990 }
1991 serde_json::from_value(self.data.clone()).ok()
1992 }
1993
1994 pub fn is_ui_toast(&self) -> bool {
1996 self.subtype == SystemSubtype::UiToast
1997 }
1998
1999 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
2001 if self.subtype != SystemSubtype::UiToast {
2002 return None;
2003 }
2004 serde_json::from_value(self.data.clone()).ok()
2005 }
2006
2007 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
2009 macro_rules! parse {
2010 ($variant:ident, $ty:ty) => {
2011 serde_json::from_value::<$ty>(self.data.clone())
2012 .ok()
2013 .map(KnownSystemEvent::$variant)
2014 };
2015 }
2016
2017 match self.subtype {
2018 SystemSubtype::Init => parse!(Init, InitMessage),
2019 SystemSubtype::Status => parse!(Status, StatusMessage),
2020 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
2021 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
2022 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
2023 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
2024 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
2025 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
2026 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
2027 SystemSubtype::ControlRequestProgress => {
2028 parse!(ControlRequestProgress, ControlRequestProgressMessage)
2029 }
2030 SystemSubtype::ModelRefusalFallback => {
2031 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
2032 }
2033 SystemSubtype::ModelRefusalNoFallback => {
2034 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
2035 }
2036 SystemSubtype::LocalCommandOutput => {
2037 parse!(LocalCommandOutput, LocalCommandOutputMessage)
2038 }
2039 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
2040 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
2041 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
2042 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
2043 SystemSubtype::BackgroundTasksChanged => {
2044 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
2045 }
2046 SystemSubtype::SessionStateChanged => {
2047 parse!(SessionStateChanged, SessionStateChangedMessage)
2048 }
2049 SystemSubtype::WorkerShuttingDown => {
2050 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
2051 }
2052 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
2053 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
2054 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
2055 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
2056 SystemSubtype::ElicitationComplete => {
2057 parse!(ElicitationComplete, ElicitationCompleteMessage)
2058 }
2059 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
2060 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
2061 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
2062 SystemSubtype::CodeChangePublished => {
2063 parse!(CodeChangePublished, CodeChangePublishedMessage)
2064 }
2065 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
2066 SystemSubtype::FeedbackDraftQueued => {
2067 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
2068 }
2069 SystemSubtype::CloudSessionDelta => {
2070 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
2071 }
2072 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
2073 SystemSubtype::TurnHandoffAvailable => {
2074 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
2075 }
2076 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
2077 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
2078 SystemSubtype::PerTurnEffortChanged => {
2079 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
2080 }
2081 SystemSubtype::SessionTitleChanged => {
2082 parse!(SessionTitleChanged, SessionTitleChangedMessage)
2083 }
2084 SystemSubtype::SessionWritesLost => {
2085 parse!(SessionWritesLost, SessionWritesLostMessage)
2086 }
2087 SystemSubtype::InstructionSizeWarning => {
2088 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
2089 }
2090 SystemSubtype::FileAttachmentsMissing => {
2091 parse!(FileAttachmentsMissing, FileAttachmentsMissingMessage)
2092 }
2093 SystemSubtype::PermissionCheckStatus => {
2094 parse!(PermissionCheckStatus, PermissionCheckStatusMessage)
2095 }
2096 SystemSubtype::InheritedAsksSettled => {
2097 parse!(InheritedAsksSettled, InheritedAsksSettledMessage)
2098 }
2099 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
2100 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
2101 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
2102 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
2103 SystemSubtype::UiPlaced => parse!(UiPlaced, UiPlacedMessage),
2104 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
2105 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
2106 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
2107 SystemSubtype::Unknown(_) => None,
2108 }
2109 }
2110
2111 pub fn typed_value(&self) -> Option<Value> {
2120 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
2121 parsed.and_then(|v| serde_json::to_value(v).ok())
2122 }
2123 match self.subtype {
2124 SystemSubtype::Init => reserialize(self.as_init()),
2125 SystemSubtype::Status => reserialize(self.as_status()),
2126 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
2127 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
2128 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
2129 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
2130 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
2131 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
2132 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
2133 SystemSubtype::ControlRequestProgress => {
2134 reserialize(parse_system::<ControlRequestProgressMessage>(self))
2135 }
2136 SystemSubtype::ModelRefusalFallback => {
2137 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
2138 }
2139 SystemSubtype::ModelRefusalNoFallback => {
2140 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
2141 }
2142 SystemSubtype::LocalCommandOutput => {
2143 reserialize(parse_system::<LocalCommandOutputMessage>(self))
2144 }
2145 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
2146 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
2147 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
2148 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
2149 SystemSubtype::BackgroundTasksChanged => {
2150 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
2151 }
2152 SystemSubtype::SessionStateChanged => {
2153 reserialize(parse_system::<SessionStateChangedMessage>(self))
2154 }
2155 SystemSubtype::WorkerShuttingDown => {
2156 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
2157 }
2158 SystemSubtype::CommandsChanged => {
2159 reserialize(parse_system::<CommandsChangedMessage>(self))
2160 }
2161 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
2162 SystemSubtype::FilesPersisted => {
2163 reserialize(parse_system::<FilesPersistedMessage>(self))
2164 }
2165 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
2166 SystemSubtype::ElicitationComplete => {
2167 reserialize(parse_system::<ElicitationCompleteMessage>(self))
2168 }
2169 SystemSubtype::PermissionDenied => {
2170 reserialize(parse_system::<PermissionDeniedMessage>(self))
2171 }
2172 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
2173 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
2174 SystemSubtype::CodeChangePublished => {
2175 reserialize(parse_system::<CodeChangePublishedMessage>(self))
2176 }
2177 SystemSubtype::VcsStateChanged => {
2178 reserialize(parse_system::<VcsStateChangedMessage>(self))
2179 }
2180 SystemSubtype::FeedbackDraftQueued => {
2181 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
2182 }
2183 SystemSubtype::CloudSessionDelta => {
2184 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
2185 }
2186 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
2187 SystemSubtype::TurnHandoffAvailable => {
2188 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
2189 }
2190 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
2191 SystemSubtype::PeerMessageHold => {
2192 reserialize(parse_system::<PeerMessageHoldMessage>(self))
2193 }
2194 SystemSubtype::PerTurnEffortChanged => {
2195 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
2196 }
2197 SystemSubtype::SessionTitleChanged => {
2198 reserialize(parse_system::<SessionTitleChangedMessage>(self))
2199 }
2200 SystemSubtype::SessionWritesLost => {
2201 reserialize(parse_system::<SessionWritesLostMessage>(self))
2202 }
2203 SystemSubtype::InstructionSizeWarning => {
2204 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
2205 }
2206 SystemSubtype::FileAttachmentsMissing => {
2207 reserialize(parse_system::<FileAttachmentsMissingMessage>(self))
2208 }
2209 SystemSubtype::PermissionCheckStatus => {
2210 reserialize(parse_system::<PermissionCheckStatusMessage>(self))
2211 }
2212 SystemSubtype::InheritedAsksSettled => {
2213 reserialize(parse_system::<InheritedAsksSettledMessage>(self))
2214 }
2215 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
2216 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
2217 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
2218 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
2219 SystemSubtype::UiPlaced => reserialize(parse_system::<UiPlacedMessage>(self)),
2220 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
2221 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
2222 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
2223 SystemSubtype::Unknown(_) => None,
2224 }
2225 }
2226}
2227
2228fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
2229 serde_json::from_value(message.data.clone()).ok()
2230}
2231
2232#[allow(clippy::large_enum_variant)]
2238#[derive(Debug, Clone, Serialize, Deserialize)]
2239pub enum KnownSystemEvent {
2240 Init(InitMessage),
2241 Status(StatusMessage),
2242 CompactBoundary(CompactBoundaryMessage),
2243 ThinkingTokens(ThinkingTokensMessage),
2244 TaskStarted(TaskStartedMessage),
2245 TaskProgress(TaskProgressMessage),
2246 TaskUpdated(TaskUpdatedMessage),
2247 TaskNotification(TaskNotificationMessage),
2248 ApiRetry(ApiRetryMessage),
2249 ControlRequestProgress(ControlRequestProgressMessage),
2250 ModelRefusalFallback(ModelRefusalFallbackMessage),
2251 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
2252 LocalCommandOutput(LocalCommandOutputMessage),
2253 HookStarted(HookStartedMessage),
2254 HookProgress(HookProgressMessage),
2255 HookResponse(HookResponseMessage),
2256 PluginInstall(PluginInstallMessage),
2257 BackgroundTasksChanged(BackgroundTasksChangedMessage),
2258 SessionStateChanged(SessionStateChangedMessage),
2259 WorkerShuttingDown(WorkerShuttingDownMessage),
2260 CommandsChanged(CommandsChangedMessage),
2261 Notification(NotificationMessage),
2262 FilesPersisted(FilesPersistedMessage),
2263 MemoryRecall(MemoryRecallMessage),
2264 ElicitationComplete(ElicitationCompleteMessage),
2265 PermissionDenied(PermissionDeniedMessage),
2266 MirrorError(MirrorErrorMessage),
2267 Informational(InformationalMessage),
2268 CodeChangePublished(CodeChangePublishedMessage),
2269 VcsStateChanged(VcsStateChangedMessage),
2270 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
2271 CloudSessionDelta(CloudSessionDeltaMessage),
2272 DevIntent(DevIntentMessage),
2273 TurnHandoffAvailable(TurnHandoffAvailableMessage),
2274 TurnPreempted(TurnPreemptedMessage),
2275 PeerMessageHold(PeerMessageHoldMessage),
2276 PerTurnEffortChanged(PerTurnEffortChangedMessage),
2277 SessionTitleChanged(SessionTitleChangedMessage),
2278 SessionWritesLost(SessionWritesLostMessage),
2279 InstructionSizeWarning(InstructionSizeWarningMessage),
2280 FileAttachmentsMissing(FileAttachmentsMissingMessage),
2281 PermissionCheckStatus(PermissionCheckStatusMessage),
2282 InheritedAsksSettled(InheritedAsksSettledMessage),
2283 UiFocus(UiFocusMessage),
2284 UiInvalidate(UiInvalidateMessage),
2285 UiLog(UiLogMessage),
2286 UiPanes(UiPanesMessage),
2287 UiPlaced(UiPlacedMessage),
2288 UiScroll(UiScrollMessage),
2289 UiStatus(UiStatusMessage),
2290 UiToast(UiToastMessage),
2291}
2292
2293#[derive(Debug, Clone, Serialize, Deserialize)]
2294pub struct ApiRetryMessage {
2295 pub attempt: u64,
2296 pub max_retries: u64,
2297 pub retry_delay_ms: u64,
2298 pub error_status: Option<u16>,
2299 pub error: String,
2300 #[serde(default, skip_serializing_if = "Option::is_none")]
2305 pub no_response: Option<ApiRetryNoResponse>,
2306 #[serde(default, skip_serializing_if = "Option::is_none")]
2307 pub uuid: Option<String>,
2308 #[serde(default, skip_serializing_if = "Option::is_none")]
2309 pub session_id: Option<String>,
2310}
2311
2312#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2315pub struct ApiRetryNoResponse {
2316 pub waited_ms: u64,
2318 pub retry_wait_ms: u64,
2320}
2321
2322#[derive(Debug, Clone, Serialize, Deserialize)]
2323pub struct ControlRequestProgressMessage {
2324 pub request_id: String,
2325 pub status: String,
2326 #[serde(default, skip_serializing_if = "Option::is_none")]
2327 pub attempt: Option<u64>,
2328 #[serde(default, skip_serializing_if = "Option::is_none")]
2329 pub max_retries: Option<u64>,
2330 #[serde(default, skip_serializing_if = "Option::is_none")]
2331 pub retry_delay_ms: Option<u64>,
2332 #[serde(default, skip_serializing_if = "Option::is_none")]
2333 pub error_status: Option<u16>,
2334 #[serde(default, skip_serializing_if = "Option::is_none")]
2335 pub error: Option<String>,
2336 #[serde(default, skip_serializing_if = "Option::is_none")]
2337 pub uuid: Option<String>,
2338 #[serde(default, skip_serializing_if = "Option::is_none")]
2339 pub session_id: Option<String>,
2340}
2341
2342#[derive(Debug, Clone, Serialize, Deserialize)]
2343pub struct ModelRefusalFallbackMessage {
2344 pub trigger: String,
2345 pub direction: String,
2346 #[serde(default, skip_serializing_if = "Option::is_none")]
2352 pub scope: Option<RefusalFallbackScope>,
2353 pub original_model: String,
2354 pub fallback_model: String,
2355 pub request_id: Option<String>,
2356 #[serde(default, skip_serializing_if = "Option::is_none")]
2357 pub api_refusal_category: Option<String>,
2358 #[serde(default, skip_serializing_if = "Option::is_none")]
2363 pub saw_cyber_refusal: Option<bool>,
2364 #[serde(default, skip_serializing_if = "Option::is_none")]
2365 pub api_refusal_explanation: Option<String>,
2366 #[serde(default, skip_serializing_if = "Option::is_none")]
2374 pub api_refusal_cyber_verification: Option<CyberVerificationStatus>,
2375 #[serde(default, skip_serializing_if = "Option::is_none")]
2376 pub retracted_message_uuids: Option<Vec<String>>,
2377 #[serde(default, skip_serializing_if = "Option::is_none")]
2378 pub refused_user_message_uuid: Option<String>,
2379 pub content: Value,
2380 #[serde(default, skip_serializing_if = "Option::is_none")]
2381 pub uuid: Option<String>,
2382 #[serde(default, skip_serializing_if = "Option::is_none")]
2383 pub session_id: Option<String>,
2384}
2385
2386#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2390pub struct CyberVerificationStatus {
2391 #[serde(rename = "type")]
2392 pub verification_type: String,
2393 #[serde(default, skip_serializing_if = "Option::is_none")]
2394 pub tier: Option<String>,
2395 #[serde(default, skip_serializing_if = "Option::is_none")]
2396 pub reason: Option<String>,
2397}
2398
2399#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2403pub enum RefusalFallbackScope {
2404 Session,
2406 Local,
2409 Unknown(String),
2411}
2412
2413impl RefusalFallbackScope {
2414 pub fn as_str(&self) -> &str {
2415 match self {
2416 Self::Session => "session",
2417 Self::Local => "local",
2418 Self::Unknown(s) => s.as_str(),
2419 }
2420 }
2421}
2422
2423impl fmt::Display for RefusalFallbackScope {
2424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2425 f.write_str(self.as_str())
2426 }
2427}
2428
2429impl From<&str> for RefusalFallbackScope {
2430 fn from(s: &str) -> Self {
2431 match s {
2432 "session" => Self::Session,
2433 "local" => Self::Local,
2434 other => Self::Unknown(other.to_string()),
2435 }
2436 }
2437}
2438
2439impl Serialize for RefusalFallbackScope {
2440 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2441 serializer.serialize_str(self.as_str())
2442 }
2443}
2444
2445impl<'de> Deserialize<'de> for RefusalFallbackScope {
2446 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2447 let s = String::deserialize(deserializer)?;
2448 Ok(Self::from(s.as_str()))
2449 }
2450}
2451
2452#[derive(Debug, Clone, Serialize, Deserialize)]
2453pub struct ModelRefusalNoFallbackMessage {
2454 pub original_model: String,
2455 pub request_id: Option<String>,
2456 #[serde(default, skip_serializing_if = "Option::is_none")]
2457 pub api_refusal_category: Option<String>,
2458 #[serde(default, skip_serializing_if = "Option::is_none")]
2459 pub api_refusal_explanation: Option<String>,
2460 pub content: Value,
2461 #[serde(default, skip_serializing_if = "Option::is_none")]
2462 pub uuid: Option<String>,
2463 #[serde(default, skip_serializing_if = "Option::is_none")]
2464 pub session_id: Option<String>,
2465}
2466
2467#[derive(Debug, Clone, Serialize, Deserialize)]
2468pub struct LocalCommandOutputMessage {
2469 pub content: String,
2470 #[serde(default, skip_serializing_if = "Option::is_none")]
2471 pub uuid: Option<String>,
2472 #[serde(default, skip_serializing_if = "Option::is_none")]
2473 pub session_id: Option<String>,
2474}
2475
2476#[derive(Debug, Clone, Serialize, Deserialize)]
2477pub struct HookStartedMessage {
2478 pub hook_id: String,
2479 pub hook_name: String,
2480 pub hook_event: String,
2481 #[serde(default, skip_serializing_if = "Option::is_none")]
2482 pub uuid: Option<String>,
2483 #[serde(default, skip_serializing_if = "Option::is_none")]
2484 pub session_id: Option<String>,
2485}
2486
2487#[derive(Debug, Clone, Serialize, Deserialize)]
2488pub struct HookProgressMessage {
2489 pub hook_id: String,
2490 pub hook_name: String,
2491 pub hook_event: String,
2492 #[serde(default, skip_serializing_if = "Option::is_none")]
2493 pub stdout: Option<String>,
2494 #[serde(default, skip_serializing_if = "Option::is_none")]
2495 pub stderr: Option<String>,
2496 #[serde(default, skip_serializing_if = "Option::is_none")]
2497 pub output: Option<String>,
2498 #[serde(default, skip_serializing_if = "Option::is_none")]
2499 pub uuid: Option<String>,
2500 #[serde(default, skip_serializing_if = "Option::is_none")]
2501 pub session_id: Option<String>,
2502}
2503
2504#[derive(Debug, Clone, Serialize, Deserialize)]
2505pub struct HookResponseMessage {
2506 pub hook_id: String,
2507 pub hook_name: String,
2508 pub hook_event: String,
2509 #[serde(default, skip_serializing_if = "Option::is_none")]
2510 pub stdout: Option<String>,
2511 #[serde(default, skip_serializing_if = "Option::is_none")]
2512 pub stderr: Option<String>,
2513 #[serde(default, skip_serializing_if = "Option::is_none")]
2514 pub output: Option<String>,
2515 #[serde(default, skip_serializing_if = "Option::is_none")]
2516 pub exit_code: Option<i32>,
2517 pub outcome: String,
2518 #[serde(default, skip_serializing_if = "Option::is_none")]
2519 pub uuid: Option<String>,
2520 #[serde(default, skip_serializing_if = "Option::is_none")]
2521 pub session_id: Option<String>,
2522}
2523
2524#[derive(Debug, Clone, Serialize, Deserialize)]
2525pub struct PluginInstallMessage {
2526 pub status: String,
2527 #[serde(default, skip_serializing_if = "Option::is_none")]
2528 pub name: Option<String>,
2529 #[serde(default, skip_serializing_if = "Option::is_none")]
2530 pub error: Option<String>,
2531 #[serde(default, skip_serializing_if = "Option::is_none")]
2532 pub uuid: Option<String>,
2533 #[serde(default, skip_serializing_if = "Option::is_none")]
2534 pub session_id: Option<String>,
2535}
2536
2537#[derive(Debug, Clone, Serialize, Deserialize)]
2538pub struct BackgroundTasksChangedMessage {
2539 pub tasks: Vec<BackgroundTaskInfo>,
2540 #[serde(default, skip_serializing_if = "Option::is_none")]
2541 pub uuid: Option<String>,
2542 #[serde(default, skip_serializing_if = "Option::is_none")]
2543 pub session_id: Option<String>,
2544}
2545
2546#[derive(Debug, Clone, Serialize, Deserialize)]
2547pub struct BackgroundTaskInfo {
2548 pub task_id: String,
2549 #[serde(default, skip_serializing_if = "Option::is_none")]
2552 pub run_id: Option<String>,
2553 pub task_type: String,
2554 #[serde(default, skip_serializing_if = "Option::is_none")]
2558 pub subagent_type: Option<String>,
2559 pub description: String,
2560 #[serde(default, skip_serializing_if = "Option::is_none")]
2563 pub parent_task_id: Option<String>,
2564 #[serde(default, skip_serializing_if = "Option::is_none")]
2567 pub ambient: Option<bool>,
2568 #[serde(default, skip_serializing_if = "Option::is_none")]
2571 pub awaited: Option<bool>,
2572}
2573
2574#[derive(Debug, Clone, Serialize, Deserialize)]
2575pub struct SessionStateChangedMessage {
2576 pub state: String,
2577 #[serde(default, skip_serializing_if = "Option::is_none")]
2578 pub uuid: Option<String>,
2579 #[serde(default, skip_serializing_if = "Option::is_none")]
2580 pub session_id: Option<String>,
2581}
2582
2583#[derive(Debug, Clone, Serialize, Deserialize)]
2584pub struct WorkerShuttingDownMessage {
2585 pub reason: String,
2586 #[serde(default, skip_serializing_if = "Option::is_none")]
2587 pub uuid: Option<String>,
2588 #[serde(default, skip_serializing_if = "Option::is_none")]
2589 pub session_id: Option<String>,
2590}
2591
2592#[derive(Debug, Clone, Serialize, Deserialize)]
2593pub struct CommandsChangedMessage {
2594 pub commands: Vec<CommandInfo>,
2595 #[serde(default, skip_serializing_if = "Option::is_none")]
2596 pub uuid: Option<String>,
2597 #[serde(default, skip_serializing_if = "Option::is_none")]
2598 pub session_id: Option<String>,
2599}
2600
2601#[derive(Debug, Clone, Serialize, Deserialize)]
2602pub struct CommandInfo {
2603 pub name: String,
2604 pub description: String,
2605 #[serde(rename = "argumentHint")]
2606 pub argument_hint: String,
2607 #[serde(default, skip_serializing_if = "Option::is_none")]
2608 pub aliases: Option<Vec<String>>,
2609 #[serde(default, skip_serializing_if = "Option::is_none")]
2615 pub builtin: Option<bool>,
2616}
2617
2618#[derive(Debug, Clone, Serialize, Deserialize)]
2619pub struct NotificationMessage {
2620 pub key: String,
2621 pub text: String,
2622 pub priority: String,
2623 #[serde(default, skip_serializing_if = "Option::is_none")]
2624 pub color: Option<String>,
2625 #[serde(default, skip_serializing_if = "Option::is_none")]
2626 pub timeout_ms: Option<u64>,
2627 #[serde(default, skip_serializing_if = "Option::is_none")]
2628 pub uuid: Option<String>,
2629 #[serde(default, skip_serializing_if = "Option::is_none")]
2630 pub session_id: Option<String>,
2631}
2632
2633#[derive(Debug, Clone, Serialize, Deserialize)]
2634pub struct FilesPersistedMessage {
2635 pub files: Vec<PersistedFile>,
2636 pub failed: Vec<FailedPersistedFile>,
2637 pub processed_at: String,
2638 #[serde(default, skip_serializing_if = "Option::is_none")]
2639 pub uuid: Option<String>,
2640 #[serde(default, skip_serializing_if = "Option::is_none")]
2641 pub session_id: Option<String>,
2642}
2643
2644#[derive(Debug, Clone, Serialize, Deserialize)]
2645pub struct PersistedFile {
2646 pub filename: String,
2647 pub file_id: String,
2648}
2649
2650#[derive(Debug, Clone, Serialize, Deserialize)]
2651pub struct FailedPersistedFile {
2652 pub filename: String,
2653 pub error: String,
2654}
2655
2656#[derive(Debug, Clone, Serialize, Deserialize)]
2657pub struct MemoryRecallMessage {
2658 pub mode: String,
2659 pub memories: Vec<MemoryRecallItem>,
2660 #[serde(default, skip_serializing_if = "Option::is_none")]
2661 pub uuid: Option<String>,
2662 #[serde(default, skip_serializing_if = "Option::is_none")]
2663 pub session_id: Option<String>,
2664}
2665
2666#[derive(Debug, Clone, Serialize, Deserialize)]
2667pub struct MemoryRecallItem {
2668 pub path: String,
2669 pub scope: String,
2670 #[serde(default, skip_serializing_if = "Option::is_none")]
2671 pub content: Option<String>,
2672}
2673
2674#[derive(Debug, Clone, Serialize, Deserialize)]
2675pub struct ElicitationCompleteMessage {
2676 pub mcp_server_name: String,
2677 pub elicitation_id: String,
2678 #[serde(default, skip_serializing_if = "Option::is_none")]
2679 pub uuid: Option<String>,
2680 #[serde(default, skip_serializing_if = "Option::is_none")]
2681 pub session_id: Option<String>,
2682}
2683
2684#[derive(Debug, Clone, Serialize, Deserialize)]
2685pub struct PermissionDeniedMessage {
2686 pub tool_name: String,
2687 pub tool_use_id: String,
2688 #[serde(default, skip_serializing_if = "Option::is_none")]
2689 pub agent_id: Option<String>,
2690 #[serde(default, skip_serializing_if = "Option::is_none")]
2691 pub decision_reason_type: Option<String>,
2692 #[serde(default, skip_serializing_if = "Option::is_none")]
2696 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2697 #[serde(default, skip_serializing_if = "Option::is_none")]
2698 pub decision_reason: Option<String>,
2699 pub message: String,
2700 #[serde(default, skip_serializing_if = "Option::is_none")]
2701 pub uuid: Option<String>,
2702 #[serde(default, skip_serializing_if = "Option::is_none")]
2703 pub session_id: Option<String>,
2704}
2705
2706#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2709pub enum PermissionDeniedReasonCode {
2710 ClassifierTranscriptTooLong,
2712 OutsideReadsBlocked,
2714 MemoryPaused,
2716 Unknown(String),
2718}
2719
2720impl PermissionDeniedReasonCode {
2721 pub fn as_str(&self) -> &str {
2722 match self {
2723 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2724 Self::OutsideReadsBlocked => "outside_reads_blocked",
2725 Self::MemoryPaused => "memory_paused",
2726 Self::Unknown(s) => s.as_str(),
2727 }
2728 }
2729}
2730
2731impl fmt::Display for PermissionDeniedReasonCode {
2732 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2733 f.write_str(self.as_str())
2734 }
2735}
2736
2737impl From<&str> for PermissionDeniedReasonCode {
2738 fn from(s: &str) -> Self {
2739 match s {
2740 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2741 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2742 "memory_paused" => Self::MemoryPaused,
2743 other => Self::Unknown(other.to_string()),
2744 }
2745 }
2746}
2747
2748impl Serialize for PermissionDeniedReasonCode {
2749 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2750 serializer.serialize_str(self.as_str())
2751 }
2752}
2753
2754impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2755 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2756 let s = String::deserialize(deserializer)?;
2757 Ok(Self::from(s.as_str()))
2758 }
2759}
2760
2761#[derive(Debug, Clone, Serialize, Deserialize)]
2762pub struct MirrorErrorMessage {
2763 pub error: String,
2764 pub key: MirrorErrorKey,
2765 #[serde(default, skip_serializing_if = "Option::is_none")]
2766 pub uuid: Option<String>,
2767 #[serde(default, skip_serializing_if = "Option::is_none")]
2768 pub session_id: Option<String>,
2769}
2770
2771#[derive(Debug, Clone, Serialize, Deserialize)]
2772pub struct MirrorErrorKey {
2773 #[serde(rename = "projectKey")]
2774 pub project_key: String,
2775 #[serde(rename = "sessionId")]
2776 pub session_id: String,
2777 #[serde(default, skip_serializing_if = "Option::is_none")]
2778 pub subpath: Option<String>,
2779}
2780
2781#[derive(Debug, Clone, Serialize, Deserialize)]
2782pub struct InformationalMessage {
2783 pub content: String,
2784 pub level: String,
2785 #[serde(default, skip_serializing_if = "Option::is_none")]
2786 pub tool_use_id: Option<String>,
2787 #[serde(default, skip_serializing_if = "Option::is_none")]
2788 pub prevent_continuation: Option<bool>,
2789 #[serde(default, skip_serializing_if = "Option::is_none")]
2792 pub tag: Option<String>,
2793 #[serde(default, skip_serializing_if = "Option::is_none")]
2799 pub prompt_submit_outcome: Option<PromptSubmitOutcome>,
2800 #[serde(default, skip_serializing_if = "Option::is_none")]
2804 pub model_restriction: Option<ModelRestriction>,
2805 #[serde(default, skip_serializing_if = "Option::is_none")]
2806 pub uuid: Option<String>,
2807 #[serde(default, skip_serializing_if = "Option::is_none")]
2808 pub session_id: Option<String>,
2809}
2810
2811#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2817pub struct ModelRestriction {
2818 pub requested_model: String,
2821 pub effective_model: String,
2824 #[serde(default, skip_serializing_if = "Option::is_none")]
2829 pub at: Option<String>,
2830}
2831
2832#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2835pub struct PromptSubmitOutcome {
2836 #[serde(default, skip_serializing_if = "Option::is_none")]
2840 pub kind: Option<PromptSubmitOutcomeKind>,
2841 #[serde(default, skip_serializing_if = "Option::is_none")]
2848 pub no_verdict_kind: Option<String>,
2849 pub prompt_kept: bool,
2852 #[serde(default, skip_serializing_if = "Option::is_none")]
2856 pub reason: Option<String>,
2857}
2858
2859#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2861pub enum PromptSubmitOutcomeKind {
2862 BlockedByHook,
2864 StoppedByHook,
2866 DroppedByHook,
2868 NotPassedOn,
2870 NoVerdict,
2873 Other(String),
2875}
2876
2877impl PromptSubmitOutcomeKind {
2878 pub fn as_str(&self) -> &str {
2879 match self {
2880 Self::BlockedByHook => "blocked_by_hook",
2881 Self::StoppedByHook => "stopped_by_hook",
2882 Self::DroppedByHook => "dropped_by_hook",
2883 Self::NotPassedOn => "not_passed_on",
2884 Self::NoVerdict => "no_verdict",
2885 Self::Other(s) => s.as_str(),
2886 }
2887 }
2888}
2889
2890impl fmt::Display for PromptSubmitOutcomeKind {
2891 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2892 f.write_str(self.as_str())
2893 }
2894}
2895
2896impl From<&str> for PromptSubmitOutcomeKind {
2897 fn from(s: &str) -> Self {
2898 match s {
2899 "blocked_by_hook" => Self::BlockedByHook,
2900 "stopped_by_hook" => Self::StoppedByHook,
2901 "dropped_by_hook" => Self::DroppedByHook,
2902 "not_passed_on" => Self::NotPassedOn,
2903 "no_verdict" => Self::NoVerdict,
2904 other => Self::Other(other.to_string()),
2905 }
2906 }
2907}
2908
2909impl Serialize for PromptSubmitOutcomeKind {
2910 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2911 serializer.serialize_str(self.as_str())
2912 }
2913}
2914
2915impl<'de> Deserialize<'de> for PromptSubmitOutcomeKind {
2916 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2917 let s = String::deserialize(deserializer)?;
2918 Ok(Self::from(s.as_str()))
2919 }
2920}
2921
2922#[derive(Debug, Clone, Serialize, Deserialize)]
2924pub struct PluginInfo {
2925 pub name: String,
2927 pub path: String,
2929 #[serde(skip_serializing_if = "Option::is_none")]
2931 pub source: Option<String>,
2932 #[serde(default, skip_serializing_if = "Option::is_none")]
2934 pub version: Option<String>,
2935}
2936
2937#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2939pub struct PluginDiagnostic {
2940 pub plugin: String,
2941 #[serde(rename = "type")]
2942 pub diagnostic_type: String,
2943 pub message: String,
2944}
2945
2946#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2948pub struct MemoryPaths {
2949 #[serde(default, skip_serializing_if = "Option::is_none")]
2950 pub auto: Option<String>,
2951 #[serde(default, skip_serializing_if = "Option::is_none")]
2952 pub team: Option<String>,
2953 #[serde(flatten)]
2954 pub extra: serde_json::Map<String, Value>,
2955}
2956
2957#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2961pub struct McpServerError {
2962 pub name: String,
2963 #[serde(rename = "type")]
2965 pub error_type: String,
2966 pub message: String,
2967}
2968
2969#[derive(Debug, Clone, Serialize, Deserialize)]
2971pub struct InitMessage {
2972 pub session_id: String,
2974 #[serde(skip_serializing_if = "Option::is_none")]
2976 pub cwd: Option<String>,
2977 #[serde(skip_serializing_if = "Option::is_none")]
2979 pub model: Option<String>,
2980 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2982 pub tools: Vec<String>,
2983 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2985 pub mcp_servers: Vec<Value>,
2986 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2988 pub slash_commands: Vec<String>,
2989 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2992 pub terminal_slash_commands: Vec<String>,
2993 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2995 pub agents: Vec<String>,
2996 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2998 pub plugins: Vec<PluginInfo>,
2999 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3001 pub skills: Vec<Value>,
3002 #[serde(skip_serializing_if = "Option::is_none")]
3004 pub claude_code_version: Option<String>,
3005 #[serde(skip_serializing_if = "Option::is_none")]
3008 pub messaging_socket_path: Option<String>,
3009 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
3011 pub api_key_source: Option<ApiKeySource>,
3012 #[serde(skip_serializing_if = "Option::is_none")]
3014 pub output_style: Option<OutputStyle>,
3015 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3017 pub permission_mode: Option<InitPermissionMode>,
3018
3019 #[serde(skip_serializing_if = "Option::is_none")]
3021 pub uuid: Option<String>,
3022
3023 #[serde(skip_serializing_if = "Option::is_none")]
3025 pub memory_paths: Option<MemoryPaths>,
3026
3027 #[serde(default, skip_serializing_if = "Option::is_none")]
3032 pub scratchpad_path: Option<String>,
3033
3034 #[serde(skip_serializing_if = "Option::is_none")]
3036 pub fast_mode_state: Option<String>,
3037
3038 #[serde(default, skip_serializing_if = "Option::is_none")]
3040 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
3041
3042 #[serde(default, skip_serializing_if = "Option::is_none")]
3045 pub mcp_server_errors: Option<Vec<McpServerError>>,
3046
3047 #[serde(default, skip_serializing_if = "Option::is_none")]
3049 pub analytics_disabled: Option<bool>,
3050
3051 #[serde(default, skip_serializing_if = "Option::is_none")]
3053 pub product_feedback_disabled: Option<bool>,
3054
3055 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3057 pub betas: Vec<String>,
3058
3059 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3061 pub capabilities: Vec<String>,
3062
3063 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3065 pub plugin_errors: Vec<PluginDiagnostic>,
3066
3067 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3069 pub plugin_warnings: Vec<PluginDiagnostic>,
3070
3071 #[serde(default, skip_serializing_if = "Option::is_none")]
3076 pub effort: Option<String>,
3077
3078 #[serde(default, skip_serializing_if = "Option::is_none")]
3083 pub cloud_session: Option<Value>,
3084
3085 #[serde(default, skip_serializing_if = "Option::is_none")]
3089 pub footer_indicator: Option<FooterIndicator>,
3090
3091 #[serde(default, skip_serializing_if = "Option::is_none")]
3095 pub worker_epoch: Option<u64>,
3096
3097 #[serde(default, skip_serializing_if = "Option::is_none")]
3101 pub powershell_path: Option<Option<String>>,
3102
3103 #[serde(default, skip_serializing_if = "Option::is_none")]
3107 pub startup_timing: Option<serde_json::Map<String, Value>>,
3108
3109 #[serde(default, skip_serializing_if = "Option::is_none")]
3119 pub per_turn_effort_active: Option<bool>,
3120
3121 #[serde(default, skip_serializing_if = "Option::is_none")]
3129 pub ultrathink_available: Option<bool>,
3130
3131 #[serde(default, skip_serializing_if = "Option::is_none")]
3141 pub ultrathink_typed_raises: Option<bool>,
3142
3143 #[serde(default, skip_serializing_if = "Option::is_none")]
3148 pub client_thinking: Option<ClientThinking>,
3149
3150 #[serde(default, skip_serializing_if = "Option::is_none")]
3155 pub flag_restate_noop: Option<bool>,
3156
3157 #[serde(default, skip_serializing_if = "Option::is_none")]
3165 pub synced_skills_path: Option<String>,
3166
3167 #[serde(default, skip_serializing_if = "Option::is_none")]
3174 pub view_mode: Option<ViewMode>,
3175
3176 #[serde(default, skip_serializing_if = "Option::is_none")]
3183 pub original_cwd: Option<String>,
3184
3185 #[serde(default, skip_serializing_if = "Option::is_none")]
3194 pub additional_directories: Option<Vec<String>>,
3195}
3196
3197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3200pub struct ClientThinking {
3201 #[serde(default)]
3204 pub max_thinking_tokens: Option<u64>,
3205 #[serde(default)]
3209 pub thinking_display: Option<String>,
3210}
3211
3212#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3216pub struct FooterIndicator {
3217 pub text: String,
3220}
3221
3222#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3225pub enum ViewMode {
3226 Focus,
3229 Default,
3231 Unknown(String),
3233}
3234
3235impl ViewMode {
3236 pub fn as_str(&self) -> &str {
3237 match self {
3238 Self::Focus => "focus",
3239 Self::Default => "default",
3240 Self::Unknown(s) => s.as_str(),
3241 }
3242 }
3243}
3244
3245impl fmt::Display for ViewMode {
3246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3247 f.write_str(self.as_str())
3248 }
3249}
3250
3251impl From<&str> for ViewMode {
3252 fn from(s: &str) -> Self {
3253 match s {
3254 "focus" => Self::Focus,
3255 "default" => Self::Default,
3256 other => Self::Unknown(other.to_string()),
3257 }
3258 }
3259}
3260
3261impl Serialize for ViewMode {
3262 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3263 serializer.serialize_str(self.as_str())
3264 }
3265}
3266
3267impl<'de> Deserialize<'de> for ViewMode {
3268 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3269 let s = String::deserialize(deserializer)?;
3270 Ok(Self::from(s.as_str()))
3271 }
3272}
3273
3274#[derive(Debug, Clone, Serialize, Deserialize)]
3276pub struct StatusMessage {
3277 pub session_id: String,
3279 pub status: Option<StatusMessageStatus>,
3281 #[serde(skip_serializing_if = "Option::is_none")]
3283 pub uuid: Option<String>,
3284 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
3286 pub permission_mode: Option<InitPermissionMode>,
3287 #[serde(skip_serializing_if = "Option::is_none")]
3288 pub compact_result: Option<String>,
3289 #[serde(skip_serializing_if = "Option::is_none")]
3290 pub compact_error: Option<String>,
3291 #[serde(default, skip_serializing_if = "Option::is_none")]
3298 pub compact_output_chars: Option<u64>,
3299}
3300
3301#[derive(Debug, Clone, Serialize, Deserialize)]
3303pub struct CompactBoundaryMessage {
3304 pub session_id: String,
3306 pub compact_metadata: CompactMetadata,
3308 #[serde(
3312 default,
3313 skip_serializing_if = "Option::is_none",
3314 alias = "content",
3315 alias = "text"
3316 )]
3317 pub summary: Option<String>,
3318 #[serde(
3322 default,
3323 skip_serializing_if = "Option::is_none",
3324 alias = "message_count"
3325 )]
3326 pub leaf_message_count: Option<u32>,
3327 #[serde(default, skip_serializing_if = "Option::is_none")]
3329 pub duration_ms: Option<u64>,
3330 #[serde(skip_serializing_if = "Option::is_none")]
3332 pub uuid: Option<String>,
3333 #[serde(skip_serializing_if = "Option::is_none")]
3335 pub logical_parent_uuid: Option<Option<String>>,
3336 #[serde(default, skip_serializing_if = "Option::is_none")]
3340 pub historical: Option<bool>,
3341}
3342
3343#[derive(Debug, Clone, Serialize, Deserialize)]
3345pub struct CompactMetadata {
3346 pub pre_tokens: u64,
3348 pub trigger: CompactionTrigger,
3350 #[serde(default, skip_serializing_if = "Option::is_none")]
3351 pub post_tokens: Option<u64>,
3352 #[serde(default, skip_serializing_if = "Option::is_none")]
3353 pub cumulative_dropped_tokens: Option<u64>,
3354 #[serde(default, skip_serializing_if = "Option::is_none")]
3355 pub duration_ms: Option<u64>,
3356 #[serde(default, skip_serializing_if = "Option::is_none")]
3357 pub user_context: Option<String>,
3358 #[serde(default, skip_serializing_if = "Option::is_none")]
3359 pub messages_summarized: Option<u64>,
3360 #[serde(default, skip_serializing_if = "Option::is_none")]
3361 pub precomputed: Option<bool>,
3362 #[serde(default, skip_serializing_if = "Option::is_none")]
3363 pub pre_compact_discovered_tools: Option<Vec<String>>,
3364 #[serde(default, skip_serializing_if = "Option::is_none")]
3369 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
3370 #[serde(default, skip_serializing_if = "Option::is_none")]
3371 pub preserved_segment: Option<PreservedSegment>,
3372 #[serde(default, skip_serializing_if = "Option::is_none")]
3373 pub preserved_messages: Option<PreservedMessages>,
3374}
3375
3376#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3379pub struct ArtifactReadVersion {
3380 pub slug: String,
3382 pub ver: String,
3384}
3385
3386#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3387pub struct PreservedSegment {
3388 pub head_uuid: String,
3389 pub anchor_uuid: String,
3390 pub tail_uuid: String,
3391}
3392
3393#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3394pub struct PreservedMessages {
3395 pub anchor_uuid: String,
3396 pub uuids: Vec<String>,
3397 #[serde(default, skip_serializing_if = "Option::is_none")]
3398 pub all_uuids: Option<Vec<String>>,
3399}
3400
3401#[derive(Debug, Clone, Serialize, Deserialize)]
3407pub struct TaskUsage {
3408 pub duration_ms: u64,
3410 pub tool_uses: u64,
3412 pub total_tokens: u64,
3414}
3415
3416#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3418pub enum TaskType {
3419 LocalAgent,
3421 LocalBash,
3423 LocalWorkflow,
3425 Unknown(String),
3427}
3428
3429impl TaskType {
3430 pub fn as_str(&self) -> &str {
3431 match self {
3432 Self::LocalAgent => "local_agent",
3433 Self::LocalBash => "local_bash",
3434 Self::LocalWorkflow => "local_workflow",
3435 Self::Unknown(s) => s.as_str(),
3436 }
3437 }
3438}
3439
3440impl fmt::Display for TaskType {
3441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3442 f.write_str(self.as_str())
3443 }
3444}
3445
3446impl From<&str> for TaskType {
3447 fn from(s: &str) -> Self {
3448 match s {
3449 "local_agent" => Self::LocalAgent,
3450 "local_bash" => Self::LocalBash,
3451 "local_workflow" => Self::LocalWorkflow,
3452 other => Self::Unknown(other.to_string()),
3453 }
3454 }
3455}
3456
3457impl Serialize for TaskType {
3458 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3459 serializer.serialize_str(self.as_str())
3460 }
3461}
3462
3463impl<'de> Deserialize<'de> for TaskType {
3464 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3465 let s = String::deserialize(deserializer)?;
3466 Ok(Self::from(s.as_str()))
3467 }
3468}
3469
3470#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3472pub enum TaskStatus {
3473 Pending,
3474 Running,
3475 Completed,
3476 Failed,
3477 Killed,
3478 Paused,
3479 Stopped,
3480 Unknown(String),
3481}
3482
3483impl TaskStatus {
3484 pub fn as_str(&self) -> &str {
3485 match self {
3486 Self::Pending => "pending",
3487 Self::Running => "running",
3488 Self::Completed => "completed",
3489 Self::Failed => "failed",
3490 Self::Killed => "killed",
3491 Self::Paused => "paused",
3492 Self::Stopped => "stopped",
3493 Self::Unknown(s) => s.as_str(),
3494 }
3495 }
3496}
3497
3498impl fmt::Display for TaskStatus {
3499 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3500 f.write_str(self.as_str())
3501 }
3502}
3503
3504impl From<&str> for TaskStatus {
3505 fn from(s: &str) -> Self {
3506 match s {
3507 "pending" => Self::Pending,
3508 "running" => Self::Running,
3509 "completed" => Self::Completed,
3510 "failed" => Self::Failed,
3511 "killed" => Self::Killed,
3512 "paused" => Self::Paused,
3513 "stopped" => Self::Stopped,
3514 other => Self::Unknown(other.to_string()),
3515 }
3516 }
3517}
3518
3519impl Serialize for TaskStatus {
3520 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3521 serializer.serialize_str(self.as_str())
3522 }
3523}
3524
3525impl<'de> Deserialize<'de> for TaskStatus {
3526 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3527 let s = String::deserialize(deserializer)?;
3528 Ok(Self::from(s.as_str()))
3529 }
3530}
3531
3532#[derive(Debug, Clone, Serialize, Deserialize)]
3534pub struct TaskStartedMessage {
3535 pub session_id: String,
3536 pub task_id: String,
3537 #[serde(default, skip_serializing_if = "Option::is_none")]
3543 pub run_id: Option<String>,
3544 #[serde(default, skip_serializing_if = "Option::is_none")]
3550 pub parent_task_id: Option<String>,
3551 #[serde(default, skip_serializing_if = "Option::is_none")]
3552 pub task_type: Option<TaskType>,
3553 #[serde(default, skip_serializing_if = "Option::is_none")]
3554 pub tool_use_id: Option<String>,
3555 pub description: String,
3556 #[serde(default, skip_serializing_if = "Option::is_none")]
3559 pub subagent_type: Option<String>,
3560 #[serde(default, skip_serializing_if = "Option::is_none")]
3566 pub is_backgrounded: Option<bool>,
3567 #[serde(default, skip_serializing_if = "Option::is_none")]
3575 pub awaited: Option<bool>,
3576 #[serde(default, skip_serializing_if = "Option::is_none")]
3580 pub spawn_depth: Option<u32>,
3581 #[serde(default, skip_serializing_if = "Option::is_none")]
3583 pub prompt: Option<String>,
3584 #[serde(default, skip_serializing_if = "Option::is_none")]
3585 pub workflow_name: Option<String>,
3586 #[serde(default, skip_serializing_if = "Option::is_none")]
3587 pub skip_transcript: Option<bool>,
3588 #[serde(default, skip_serializing_if = "Option::is_none")]
3593 pub ambient: Option<bool>,
3594 pub uuid: String,
3595}
3596
3597#[derive(Debug, Clone, Serialize, Deserialize)]
3601pub struct TaskUpdatedMessage {
3602 pub session_id: String,
3603 pub task_id: String,
3604 #[serde(default, skip_serializing_if = "Option::is_none")]
3607 pub run_id: Option<String>,
3608 pub patch: TaskPatch,
3609 pub uuid: String,
3610}
3611
3612#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3615pub struct TaskPatch {
3616 #[serde(default, skip_serializing_if = "Option::is_none")]
3617 pub status: Option<TaskStatus>,
3618 #[serde(default, skip_serializing_if = "Option::is_none")]
3621 pub end_time: Option<u64>,
3622 #[serde(default, skip_serializing_if = "Option::is_none")]
3623 pub description: Option<String>,
3624 #[serde(default, skip_serializing_if = "Option::is_none")]
3625 pub total_paused_ms: Option<u64>,
3626 #[serde(default, skip_serializing_if = "Option::is_none")]
3627 pub error: Option<String>,
3628 #[serde(default, skip_serializing_if = "Option::is_none")]
3629 pub is_backgrounded: Option<bool>,
3630}
3631
3632#[derive(Debug, Clone, Serialize, Deserialize)]
3635pub struct ThinkingTokensMessage {
3636 pub session_id: String,
3637 pub estimated_tokens: u64,
3639 pub estimated_tokens_delta: u64,
3641 #[serde(default, skip_serializing_if = "Option::is_none")]
3648 pub user_message_uuid: Option<String>,
3649 pub uuid: String,
3650}
3651
3652#[derive(Debug, Clone, Serialize, Deserialize)]
3655pub struct TaskProgressMessage {
3656 pub session_id: String,
3657 pub task_id: String,
3658 #[serde(default, skip_serializing_if = "Option::is_none")]
3661 pub run_id: Option<String>,
3662 #[serde(default, skip_serializing_if = "Option::is_none")]
3663 pub tool_use_id: Option<String>,
3664 pub description: String,
3665 #[serde(default, skip_serializing_if = "Option::is_none")]
3666 pub last_tool_name: Option<String>,
3667 pub usage: TaskUsage,
3668 #[serde(default, skip_serializing_if = "Option::is_none")]
3670 pub subagent_type: Option<String>,
3671 #[serde(default, skip_serializing_if = "Option::is_none")]
3672 pub summary: Option<String>,
3673 pub uuid: String,
3674}
3675
3676#[derive(Debug, Clone, Serialize, Deserialize)]
3679pub struct TaskNotificationMessage {
3680 pub session_id: String,
3681 pub task_id: String,
3682 #[serde(default, skip_serializing_if = "Option::is_none")]
3685 pub run_id: Option<String>,
3686 pub status: TaskStatus,
3687 #[serde(default, skip_serializing_if = "Option::is_none")]
3690 pub reason: Option<TaskEndReason>,
3691 pub summary: String,
3692 pub output_file: Option<String>,
3693 #[serde(skip_serializing_if = "Option::is_none")]
3694 pub tool_use_id: Option<String>,
3695 #[serde(skip_serializing_if = "Option::is_none")]
3696 pub usage: Option<TaskUsage>,
3697 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3703 pub resource_links: Vec<ResourceLink>,
3704 #[serde(default, skip_serializing_if = "Option::is_none")]
3711 pub handback: Option<TaskHandback>,
3712 #[serde(default, skip_serializing_if = "Option::is_none")]
3716 pub handback_report: Option<HandbackReport>,
3717 #[serde(default, skip_serializing_if = "Option::is_none")]
3718 pub skip_transcript: Option<bool>,
3719 #[serde(default, skip_serializing_if = "Option::is_none")]
3722 pub ambient: Option<bool>,
3723 #[serde(skip_serializing_if = "Option::is_none")]
3724 pub uuid: Option<String>,
3725}
3726
3727#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3730pub enum TaskHandback {
3731 Send,
3733 Flagged,
3735 Withheld,
3737 Unknown(String),
3739}
3740
3741impl TaskHandback {
3742 pub fn as_str(&self) -> &str {
3743 match self {
3744 Self::Send => "send",
3745 Self::Flagged => "flagged",
3746 Self::Withheld => "withheld",
3747 Self::Unknown(s) => s.as_str(),
3748 }
3749 }
3750}
3751
3752impl fmt::Display for TaskHandback {
3753 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3754 f.write_str(self.as_str())
3755 }
3756}
3757
3758impl From<&str> for TaskHandback {
3759 fn from(s: &str) -> Self {
3760 match s {
3761 "send" => Self::Send,
3762 "flagged" => Self::Flagged,
3763 "withheld" => Self::Withheld,
3764 other => Self::Unknown(other.to_string()),
3765 }
3766 }
3767}
3768
3769impl Serialize for TaskHandback {
3770 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3771 serializer.serialize_str(self.as_str())
3772 }
3773}
3774
3775impl<'de> Deserialize<'de> for TaskHandback {
3776 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3777 let s = String::deserialize(deserializer)?;
3778 Ok(Self::from(s.as_str()))
3779 }
3780}
3781
3782#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3785pub struct HandbackReport {
3786 pub text: String,
3789 #[serde(default, skip_serializing_if = "Option::is_none")]
3792 pub warning: Option<String>,
3793}
3794
3795#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3797pub enum AssistantErrorKind {
3798 AuthenticationFailed,
3799 OauthOrgNotAllowed,
3800 AccountOnHold,
3801 BillingError,
3802 RateLimit,
3803 Overloaded,
3804 InvalidRequest,
3805 ModelNotFound,
3806 ServerError,
3807 UnknownError,
3808 MaxOutputTokens,
3809 Unknown(String),
3810}
3811
3812impl AssistantErrorKind {
3813 pub fn as_str(&self) -> &str {
3814 match self {
3815 Self::AuthenticationFailed => "authentication_failed",
3816 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3817 Self::AccountOnHold => "account_on_hold",
3818 Self::BillingError => "billing_error",
3819 Self::RateLimit => "rate_limit",
3820 Self::Overloaded => "overloaded",
3821 Self::InvalidRequest => "invalid_request",
3822 Self::ModelNotFound => "model_not_found",
3823 Self::ServerError => "server_error",
3824 Self::UnknownError => "unknown",
3825 Self::MaxOutputTokens => "max_output_tokens",
3826 Self::Unknown(s) => s.as_str(),
3827 }
3828 }
3829}
3830
3831impl fmt::Display for AssistantErrorKind {
3832 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3833 f.write_str(self.as_str())
3834 }
3835}
3836
3837impl From<&str> for AssistantErrorKind {
3838 fn from(s: &str) -> Self {
3839 match s {
3840 "authentication_failed" => Self::AuthenticationFailed,
3841 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3842 "account_on_hold" => Self::AccountOnHold,
3843 "billing_error" => Self::BillingError,
3844 "rate_limit" => Self::RateLimit,
3845 "overloaded" => Self::Overloaded,
3846 "invalid_request" => Self::InvalidRequest,
3847 "model_not_found" => Self::ModelNotFound,
3848 "server_error" => Self::ServerError,
3849 "unknown" => Self::UnknownError,
3850 "max_output_tokens" => Self::MaxOutputTokens,
3851 other => Self::Unknown(other.to_string()),
3852 }
3853 }
3854}
3855
3856impl Serialize for AssistantErrorKind {
3857 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3858 serializer.serialize_str(self.as_str())
3859 }
3860}
3861
3862impl<'de> Deserialize<'de> for AssistantErrorKind {
3863 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3864 let s = String::deserialize(deserializer)?;
3865 Ok(Self::from(s.as_str()))
3866 }
3867}
3868
3869#[derive(Debug, Clone, Serialize, Deserialize)]
3876pub struct CodeChangePublishedMessage {
3877 pub provider: String,
3881 pub url: String,
3883 pub repo: String,
3886 pub identifier: String,
3888 #[serde(default, skip_serializing_if = "Option::is_none")]
3897 pub action: Option<String>,
3898 #[serde(default, skip_serializing_if = "Option::is_none")]
3906 pub branch: Option<String>,
3907 pub uuid: String,
3908 pub session_id: String,
3909}
3910
3911#[derive(Debug, Clone, Serialize, Deserialize)]
3916pub struct VcsStateChangedMessage {
3917 pub kind: VcsMutationKind,
3920 pub cwd: String,
3923 #[serde(default, skip_serializing_if = "Option::is_none")]
3928 pub branch: Option<String>,
3929 pub uuid: String,
3930 pub session_id: String,
3931}
3932
3933#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3935pub enum VcsMutationKind {
3936 Commit,
3937 Push,
3938 Merge,
3939 Rebase,
3940 Unknown(String),
3942}
3943
3944impl VcsMutationKind {
3945 pub fn as_str(&self) -> &str {
3946 match self {
3947 Self::Commit => "commit",
3948 Self::Push => "push",
3949 Self::Merge => "merge",
3950 Self::Rebase => "rebase",
3951 Self::Unknown(s) => s.as_str(),
3952 }
3953 }
3954}
3955
3956impl fmt::Display for VcsMutationKind {
3957 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3958 f.write_str(self.as_str())
3959 }
3960}
3961
3962impl From<&str> for VcsMutationKind {
3963 fn from(s: &str) -> Self {
3964 match s {
3965 "commit" => Self::Commit,
3966 "push" => Self::Push,
3967 "merge" => Self::Merge,
3968 "rebase" => Self::Rebase,
3969 other => Self::Unknown(other.to_string()),
3970 }
3971 }
3972}
3973
3974impl Serialize for VcsMutationKind {
3975 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3976 serializer.serialize_str(self.as_str())
3977 }
3978}
3979
3980impl<'de> Deserialize<'de> for VcsMutationKind {
3981 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3982 let s = String::deserialize(deserializer)?;
3983 Ok(Self::from(s.as_str()))
3984 }
3985}
3986
3987#[derive(Debug, Clone, Serialize, Deserialize)]
3989pub struct FeedbackDraftQueuedMessage {
3990 pub draft_id: String,
3991 pub draft_type: String,
3992 pub title: String,
3993 pub details_preview: String,
3994 #[serde(default, skip_serializing_if = "Option::is_none")]
3995 pub uuid: Option<String>,
3996 #[serde(default, skip_serializing_if = "Option::is_none")]
3997 pub session_id: Option<String>,
3998 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3999 pub extra: serde_json::Map<String, Value>,
4000}
4001
4002#[derive(Debug, Clone, Serialize, Deserialize)]
4010pub struct CloudSessionDeltaMessage {
4011 pub seq: u64,
4015 pub changed: Vec<String>,
4019 pub cloud_session: Value,
4023 pub uuid: String,
4024 pub session_id: String,
4025 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4026 pub extra: serde_json::Map<String, Value>,
4027}
4028
4029#[derive(Debug, Clone, Serialize, Deserialize)]
4045pub struct DevIntentMessage {
4046 pub kind: DevIntentKind,
4048 #[serde(default, skip_serializing_if = "Option::is_none")]
4054 pub trigger: Option<DevIntentTrigger>,
4055 pub uuid: String,
4056 pub session_id: String,
4057 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4058 pub extra: serde_json::Map<String, Value>,
4059}
4060
4061#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4065pub enum DevIntentKind {
4066 IosApp,
4072 AndroidApp,
4079 Unknown(String),
4081}
4082
4083impl DevIntentKind {
4084 pub fn as_str(&self) -> &str {
4085 match self {
4086 Self::IosApp => "ios_app",
4087 Self::AndroidApp => "android_app",
4088 Self::Unknown(s) => s.as_str(),
4089 }
4090 }
4091}
4092
4093impl fmt::Display for DevIntentKind {
4094 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4095 f.write_str(self.as_str())
4096 }
4097}
4098
4099impl From<&str> for DevIntentKind {
4100 fn from(s: &str) -> Self {
4101 match s {
4102 "ios_app" => Self::IosApp,
4103 "android_app" => Self::AndroidApp,
4104 other => Self::Unknown(other.to_string()),
4105 }
4106 }
4107}
4108
4109impl Serialize for DevIntentKind {
4110 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4111 serializer.serialize_str(self.as_str())
4112 }
4113}
4114
4115impl<'de> Deserialize<'de> for DevIntentKind {
4116 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4117 let s = String::deserialize(deserializer)?;
4118 Ok(Self::from(s.as_str()))
4119 }
4120}
4121
4122#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4126pub enum DevIntentTrigger {
4127 SwiftEdit,
4129 UikitImport,
4131 XcodeProject,
4134 IosCommand,
4137 KotlinEdit,
4139 JavaEdit,
4141 AndroidImport,
4143 AndroidManifest,
4145 GradlePlugin,
4147 AndroidCommand,
4150 ProjectScan,
4153 Unknown(String),
4155}
4156
4157impl DevIntentTrigger {
4158 pub fn as_str(&self) -> &str {
4159 match self {
4160 Self::SwiftEdit => "swift_edit",
4161 Self::UikitImport => "uikit_import",
4162 Self::XcodeProject => "xcode_project",
4163 Self::IosCommand => "ios_command",
4164 Self::KotlinEdit => "kotlin_edit",
4165 Self::JavaEdit => "java_edit",
4166 Self::AndroidImport => "android_import",
4167 Self::AndroidManifest => "android_manifest",
4168 Self::GradlePlugin => "gradle_plugin",
4169 Self::AndroidCommand => "android_command",
4170 Self::ProjectScan => "project_scan",
4171 Self::Unknown(s) => s.as_str(),
4172 }
4173 }
4174}
4175
4176impl fmt::Display for DevIntentTrigger {
4177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4178 f.write_str(self.as_str())
4179 }
4180}
4181
4182impl From<&str> for DevIntentTrigger {
4183 fn from(s: &str) -> Self {
4184 match s {
4185 "swift_edit" => Self::SwiftEdit,
4186 "uikit_import" => Self::UikitImport,
4187 "xcode_project" => Self::XcodeProject,
4188 "ios_command" => Self::IosCommand,
4189 "kotlin_edit" => Self::KotlinEdit,
4190 "java_edit" => Self::JavaEdit,
4191 "android_import" => Self::AndroidImport,
4192 "android_manifest" => Self::AndroidManifest,
4193 "gradle_plugin" => Self::GradlePlugin,
4194 "android_command" => Self::AndroidCommand,
4195 "project_scan" => Self::ProjectScan,
4196 other => Self::Unknown(other.to_string()),
4197 }
4198 }
4199}
4200
4201impl Serialize for DevIntentTrigger {
4202 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4203 serializer.serialize_str(self.as_str())
4204 }
4205}
4206
4207impl<'de> Deserialize<'de> for DevIntentTrigger {
4208 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4209 let s = String::deserialize(deserializer)?;
4210 Ok(Self::from(s.as_str()))
4211 }
4212}
4213
4214#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4218pub enum TaskEndReason {
4219 WorkerRestart,
4222 Unknown(String),
4224}
4225
4226impl TaskEndReason {
4227 pub fn as_str(&self) -> &str {
4228 match self {
4229 Self::WorkerRestart => "worker_restart",
4230 Self::Unknown(s) => s.as_str(),
4231 }
4232 }
4233}
4234
4235impl fmt::Display for TaskEndReason {
4236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4237 f.write_str(self.as_str())
4238 }
4239}
4240
4241impl From<&str> for TaskEndReason {
4242 fn from(s: &str) -> Self {
4243 match s {
4244 "worker_restart" => Self::WorkerRestart,
4245 other => Self::Unknown(other.to_string()),
4246 }
4247 }
4248}
4249
4250impl Serialize for TaskEndReason {
4251 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4252 serializer.serialize_str(self.as_str())
4253 }
4254}
4255
4256impl<'de> Deserialize<'de> for TaskEndReason {
4257 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4258 let s = String::deserialize(deserializer)?;
4259 Ok(Self::from(s.as_str()))
4260 }
4261}
4262
4263#[derive(Debug, Clone, Serialize, Deserialize)]
4272pub struct TurnHandoffAvailableMessage {
4273 pub v: u64,
4275 pub tools: Vec<String>,
4277 pub worker_epoch: u64,
4279 #[serde(default, skip_serializing_if = "Option::is_none")]
4283 pub relay_marker: Option<bool>,
4284 #[serde(default, skip_serializing_if = "Option::is_none")]
4292 pub staged_files: Option<bool>,
4293 #[serde(default, skip_serializing_if = "Option::is_none")]
4300 pub no_query_first: Option<bool>,
4301 #[serde(default, skip_serializing_if = "Option::is_none")]
4309 pub home_files: Option<bool>,
4310 #[serde(default, skip_serializing_if = "Option::is_none")]
4314 pub file_names: Option<bool>,
4315 #[serde(default, skip_serializing_if = "Option::is_none")]
4325 pub carried_writes: Option<bool>,
4326 #[serde(default, skip_serializing_if = "Option::is_none")]
4340 pub hydrates_carried_lines: Option<bool>,
4341 #[serde(default, skip_serializing_if = "Option::is_none")]
4347 pub shared_message_id: Option<bool>,
4348 #[serde(default, skip_serializing_if = "Option::is_none")]
4353 pub requires_rewind_request_id: Option<bool>,
4354 pub uuid: String,
4355 pub session_id: String,
4356 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4357 pub extra: serde_json::Map<String, Value>,
4358}
4359
4360#[derive(Debug, Clone, Serialize, Deserialize)]
4372pub struct TurnPreemptedMessage {
4373 pub reason: String,
4377 pub preempted_by_uuid: String,
4381 #[serde(default)]
4384 pub preempted_message_uuids: Vec<String>,
4385 pub uuid: String,
4386 pub session_id: String,
4387 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4388 pub extra: serde_json::Map<String, Value>,
4389}
4390
4391#[derive(Debug, Clone, Serialize, Deserialize)]
4399pub struct PeerMessageHoldMessage {
4400 pub state: PeerMessageHoldState,
4406 #[serde(default, skip_serializing_if = "Option::is_none")]
4413 pub message_uuid: Option<String>,
4414 pub lane: PeerMessageLane,
4416 pub from: String,
4421 #[serde(default, skip_serializing_if = "Option::is_none")]
4424 pub from_name: Option<String>,
4425 #[serde(default, skip_serializing_if = "Option::is_none")]
4427 pub cause: Option<PeerMessageHoldCause>,
4428 #[serde(default, skip_serializing_if = "Option::is_none")]
4430 pub outcome: Option<PeerMessageHoldOutcome>,
4431 pub uuid: String,
4432 pub session_id: String,
4433 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4434 pub extra: serde_json::Map<String, Value>,
4435}
4436
4437#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4439pub enum PeerMessageHoldState {
4440 Held,
4442 Released,
4444 Dropped,
4446 Unknown(String),
4448}
4449
4450impl PeerMessageHoldState {
4451 pub fn as_str(&self) -> &str {
4452 match self {
4453 Self::Held => "held",
4454 Self::Released => "released",
4455 Self::Dropped => "dropped",
4456 Self::Unknown(s) => s.as_str(),
4457 }
4458 }
4459}
4460
4461impl fmt::Display for PeerMessageHoldState {
4462 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4463 f.write_str(self.as_str())
4464 }
4465}
4466
4467impl From<&str> for PeerMessageHoldState {
4468 fn from(s: &str) -> Self {
4469 match s {
4470 "held" => Self::Held,
4471 "released" => Self::Released,
4472 "dropped" => Self::Dropped,
4473 other => Self::Unknown(other.to_string()),
4474 }
4475 }
4476}
4477
4478impl Serialize for PeerMessageHoldState {
4479 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4480 serializer.serialize_str(self.as_str())
4481 }
4482}
4483
4484impl<'de> Deserialize<'de> for PeerMessageHoldState {
4485 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4486 let s = String::deserialize(deserializer)?;
4487 Ok(Self::from(s.as_str()))
4488 }
4489}
4490
4491#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4493pub enum PeerMessageLane {
4494 Bridge,
4496 Stdin,
4498 Socket,
4500 Unknown(String),
4502}
4503
4504impl PeerMessageLane {
4505 pub fn as_str(&self) -> &str {
4506 match self {
4507 Self::Bridge => "bridge",
4508 Self::Stdin => "stdin",
4509 Self::Socket => "socket",
4510 Self::Unknown(s) => s.as_str(),
4511 }
4512 }
4513}
4514
4515impl fmt::Display for PeerMessageLane {
4516 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4517 f.write_str(self.as_str())
4518 }
4519}
4520
4521impl From<&str> for PeerMessageLane {
4522 fn from(s: &str) -> Self {
4523 match s {
4524 "bridge" => Self::Bridge,
4525 "stdin" => Self::Stdin,
4526 "socket" => Self::Socket,
4527 other => Self::Unknown(other.to_string()),
4528 }
4529 }
4530}
4531
4532impl Serialize for PeerMessageLane {
4533 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4534 serializer.serialize_str(self.as_str())
4535 }
4536}
4537
4538impl<'de> Deserialize<'de> for PeerMessageLane {
4539 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4540 let s = String::deserialize(deserializer)?;
4541 Ok(Self::from(s.as_str()))
4542 }
4543}
4544
4545#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4552pub enum PeerMessageHoldCause {
4553 ExplicitSetting,
4554 ManagedSetting,
4555 RepoSetting,
4556 InvalidSetting,
4557 BypassDefault,
4558 ModeUnknown,
4559 ModeMismatch,
4560 NoModeAsserted,
4561 Unknown(String),
4563}
4564
4565impl PeerMessageHoldCause {
4566 pub fn as_str(&self) -> &str {
4567 match self {
4568 Self::ExplicitSetting => "explicit-setting",
4569 Self::ManagedSetting => "managed-setting",
4570 Self::RepoSetting => "repo-setting",
4571 Self::InvalidSetting => "invalid-setting",
4572 Self::BypassDefault => "bypass-default",
4573 Self::ModeUnknown => "mode-unknown",
4574 Self::ModeMismatch => "mode-mismatch",
4575 Self::NoModeAsserted => "no-mode-asserted",
4576 Self::Unknown(s) => s.as_str(),
4577 }
4578 }
4579}
4580
4581impl fmt::Display for PeerMessageHoldCause {
4582 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4583 f.write_str(self.as_str())
4584 }
4585}
4586
4587impl From<&str> for PeerMessageHoldCause {
4588 fn from(s: &str) -> Self {
4589 match s {
4590 "explicit-setting" => Self::ExplicitSetting,
4591 "managed-setting" => Self::ManagedSetting,
4592 "repo-setting" => Self::RepoSetting,
4593 "invalid-setting" => Self::InvalidSetting,
4594 "bypass-default" => Self::BypassDefault,
4595 "mode-unknown" => Self::ModeUnknown,
4596 "mode-mismatch" => Self::ModeMismatch,
4597 "no-mode-asserted" => Self::NoModeAsserted,
4598 other => Self::Unknown(other.to_string()),
4599 }
4600 }
4601}
4602
4603impl Serialize for PeerMessageHoldCause {
4604 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4605 serializer.serialize_str(self.as_str())
4606 }
4607}
4608
4609impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4610 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4611 let s = String::deserialize(deserializer)?;
4612 Ok(Self::from(s.as_str()))
4613 }
4614}
4615
4616#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4619pub enum PeerMessageHoldOutcome {
4620 Expired,
4624 Refused,
4626 Dropped,
4629 Discarded,
4631 Unknown(String),
4633}
4634
4635impl PeerMessageHoldOutcome {
4636 pub fn as_str(&self) -> &str {
4637 match self {
4638 Self::Expired => "expired",
4639 Self::Refused => "refused",
4640 Self::Dropped => "dropped",
4641 Self::Discarded => "discarded",
4642 Self::Unknown(s) => s.as_str(),
4643 }
4644 }
4645}
4646
4647impl fmt::Display for PeerMessageHoldOutcome {
4648 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4649 f.write_str(self.as_str())
4650 }
4651}
4652
4653impl From<&str> for PeerMessageHoldOutcome {
4654 fn from(s: &str) -> Self {
4655 match s {
4656 "expired" => Self::Expired,
4657 "refused" => Self::Refused,
4658 "dropped" => Self::Dropped,
4659 "discarded" => Self::Discarded,
4660 other => Self::Unknown(other.to_string()),
4661 }
4662 }
4663}
4664
4665impl Serialize for PeerMessageHoldOutcome {
4666 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4667 serializer.serialize_str(self.as_str())
4668 }
4669}
4670
4671impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4672 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4673 let s = String::deserialize(deserializer)?;
4674 Ok(Self::from(s.as_str()))
4675 }
4676}
4677
4678#[derive(Debug, Clone, Serialize, Deserialize)]
4689pub struct PerTurnEffortChangedMessage {
4690 pub per_turn_effort_active: bool,
4692 pub uuid: String,
4693 pub session_id: String,
4694}
4695
4696#[derive(Debug, Clone, Serialize, Deserialize)]
4703pub struct InstructionSizeWarningMessage {
4704 pub total_chars: u64,
4707 pub total_limit_chars: u64,
4710 pub file_count: u64,
4713 #[serde(default, skip_serializing_if = "Option::is_none")]
4717 pub largest_chars: Option<u64>,
4718 pub uuid: String,
4719 pub session_id: String,
4720}
4721
4722#[derive(Debug, Clone, Serialize, Deserialize)]
4729pub struct FileAttachmentsMissingMessage {
4730 pub message_uuid: String,
4733 pub sent_count: u64,
4735 pub missing: Vec<MissingFileAttachment>,
4737 pub uuid: String,
4738 pub session_id: String,
4739}
4740
4741#[derive(Debug, Clone, Serialize, Deserialize)]
4751pub struct PermissionCheckStatusMessage {
4752 pub tool_use_id: String,
4753 #[serde(default, skip_serializing_if = "Option::is_none")]
4756 pub agent_id: Option<String>,
4757 pub status: PermissionCheckStatus,
4758 pub uuid: String,
4759 pub session_id: String,
4760}
4761
4762#[derive(Debug, Clone, Serialize, Deserialize)]
4767pub struct InheritedAsksSettledMessage {
4768 pub kept_request_ids: Vec<String>,
4771 pub uuid: String,
4772 pub session_id: String,
4773}
4774
4775#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4777pub enum PermissionCheckStatus {
4778 Checking,
4781 Done,
4783 Unknown(String),
4785}
4786
4787impl PermissionCheckStatus {
4788 pub fn as_str(&self) -> &str {
4789 match self {
4790 Self::Checking => "checking",
4791 Self::Done => "done",
4792 Self::Unknown(s) => s.as_str(),
4793 }
4794 }
4795}
4796
4797impl fmt::Display for PermissionCheckStatus {
4798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4799 f.write_str(self.as_str())
4800 }
4801}
4802
4803impl From<&str> for PermissionCheckStatus {
4804 fn from(s: &str) -> Self {
4805 match s {
4806 "checking" => Self::Checking,
4807 "done" => Self::Done,
4808 other => Self::Unknown(other.to_string()),
4809 }
4810 }
4811}
4812
4813impl Serialize for PermissionCheckStatus {
4814 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4815 serializer.serialize_str(self.as_str())
4816 }
4817}
4818
4819impl<'de> Deserialize<'de> for PermissionCheckStatus {
4820 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4821 let s = String::deserialize(deserializer)?;
4822 Ok(Self::from(s.as_str()))
4823 }
4824}
4825
4826#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4828pub struct MissingFileAttachment {
4829 pub file_uuid: String,
4831 pub reason: String,
4836}
4837
4838#[derive(Debug, Clone, Serialize, Deserialize)]
4848pub struct SessionTitleChangedMessage {
4849 pub title: String,
4854 pub uuid: String,
4855 pub session_id: String,
4856}
4857
4858#[derive(Debug, Clone, Serialize, Deserialize)]
4866pub struct SessionWritesLostMessage {
4867 pub error_code: SessionWriteErrorCode,
4870 pub lost: Vec<LostSessionWrite>,
4872 #[serde(default, skip_serializing_if = "Option::is_none")]
4878 pub transcript_session_ids: Option<Vec<String>>,
4879 pub uuid: String,
4880 pub session_id: String,
4884}
4885
4886#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4888pub enum SessionWriteErrorCode {
4889 Enospc,
4891 Edquot,
4893 Unknown(String),
4895}
4896
4897impl SessionWriteErrorCode {
4898 pub fn as_str(&self) -> &str {
4899 match self {
4900 Self::Enospc => "ENOSPC",
4901 Self::Edquot => "EDQUOT",
4902 Self::Unknown(s) => s.as_str(),
4903 }
4904 }
4905}
4906
4907impl fmt::Display for SessionWriteErrorCode {
4908 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4909 f.write_str(self.as_str())
4910 }
4911}
4912
4913impl From<&str> for SessionWriteErrorCode {
4914 fn from(s: &str) -> Self {
4915 match s {
4916 "ENOSPC" => Self::Enospc,
4917 "EDQUOT" => Self::Edquot,
4918 other => Self::Unknown(other.to_string()),
4919 }
4920 }
4921}
4922
4923impl Serialize for SessionWriteErrorCode {
4924 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4925 serializer.serialize_str(self.as_str())
4926 }
4927}
4928
4929impl<'de> Deserialize<'de> for SessionWriteErrorCode {
4930 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4931 let s = String::deserialize(deserializer)?;
4932 Ok(Self::from(s.as_str()))
4933 }
4934}
4935
4936#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4938pub enum LostSessionWrite {
4939 Transcript,
4942 GlobalConfig,
4944 Unknown(String),
4946}
4947
4948impl LostSessionWrite {
4949 pub fn as_str(&self) -> &str {
4950 match self {
4951 Self::Transcript => "transcript",
4952 Self::GlobalConfig => "global_config",
4953 Self::Unknown(s) => s.as_str(),
4954 }
4955 }
4956}
4957
4958impl fmt::Display for LostSessionWrite {
4959 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4960 f.write_str(self.as_str())
4961 }
4962}
4963
4964impl From<&str> for LostSessionWrite {
4965 fn from(s: &str) -> Self {
4966 match s {
4967 "transcript" => Self::Transcript,
4968 "global_config" => Self::GlobalConfig,
4969 other => Self::Unknown(other.to_string()),
4970 }
4971 }
4972}
4973
4974impl Serialize for LostSessionWrite {
4975 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4976 serializer.serialize_str(self.as_str())
4977 }
4978}
4979
4980impl<'de> Deserialize<'de> for LostSessionWrite {
4981 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4982 let s = String::deserialize(deserializer)?;
4983 Ok(Self::from(s.as_str()))
4984 }
4985}
4986
4987#[derive(Debug, Clone, Serialize, Deserialize)]
4991pub struct UiLogMessage {
4992 pub plugin: String,
4994 pub text: String,
4998 pub uuid: String,
4999 pub session_id: String,
5000}
5001
5002#[derive(Debug, Clone, Serialize, Deserialize)]
5007pub struct UiToastMessage {
5008 pub plugin: String,
5010 pub text: String,
5014 pub timeout_ms: u64,
5017 pub uuid: String,
5018 pub session_id: String,
5019}
5020
5021#[derive(Debug, Clone, Serialize, Deserialize)]
5025pub struct UiStatusMessage {
5026 pub plugin: String,
5028 pub text: Option<String>,
5033 pub uuid: String,
5034 pub session_id: String,
5035}
5036
5037#[derive(Debug, Clone, Serialize, Deserialize)]
5042pub struct UiInvalidateMessage {
5043 pub event: String,
5046 #[serde(default, skip_serializing_if = "Option::is_none")]
5051 pub instances: Option<Vec<UiInvalidatedInstance>>,
5052 #[serde(default, skip_serializing_if = "Option::is_none")]
5060 pub prompts_screened: Option<bool>,
5061 pub uuid: String,
5062 pub session_id: String,
5063}
5064
5065#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5067pub struct UiInvalidatedInstance {
5068 pub surface: String,
5070 pub component: String,
5072 pub instance_id: String,
5075}
5076
5077#[derive(Debug, Clone, Serialize, Deserialize)]
5084pub struct UiPanesMessage {
5085 pub panes: Vec<UiPane>,
5088 pub shown_id: Option<String>,
5091 pub focused_id: Option<String>,
5094 pub focus_requested_id: Option<String>,
5098 pub uuid: String,
5099 pub session_id: String,
5100}
5101
5102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5104pub struct UiPane {
5105 pub id: String,
5108 pub title: String,
5111 pub plugin: String,
5113 #[serde(default, skip_serializing_if = "Option::is_none")]
5116 pub close_on_escape: Option<bool>,
5117 #[serde(default, skip_serializing_if = "Option::is_none")]
5120 pub hold_toasts: Option<bool>,
5121 #[serde(default, skip_serializing_if = "Option::is_none")]
5126 pub background_color: Option<String>,
5127 #[serde(default, skip_serializing_if = "Option::is_none")]
5130 pub rows: Option<u64>,
5131 #[serde(default, skip_serializing_if = "Option::is_none")]
5134 pub columns: Option<u64>,
5135}
5136
5137#[derive(Debug, Clone, Serialize, Deserialize)]
5145pub struct UiPlacedMessage {
5146 pub rows: Vec<UiPlacedRow>,
5148 pub uuid: String,
5149 pub session_id: String,
5150}
5151
5152#[derive(Debug, Clone, Serialize, Deserialize)]
5154pub struct UiPlacedRow {
5155 pub plugin: String,
5157 pub id: String,
5159 pub request_id: String,
5162 #[serde(default, skip_serializing_if = "Option::is_none")]
5165 pub after_uuid: Option<String>,
5166}
5167
5168#[derive(Debug, Clone, Serialize, Deserialize)]
5172pub struct UiScrollMessage {
5173 pub client_id: String,
5175 pub component: UiSiteComponent,
5177 pub instance_id: String,
5179 pub offset: u64,
5182 #[serde(default, skip_serializing_if = "Option::is_none")]
5185 pub follow_end: Option<bool>,
5186 pub uuid: String,
5187 pub session_id: String,
5188}
5189
5190#[derive(Debug, Clone, Serialize, Deserialize)]
5195pub struct UiFocusMessage {
5196 pub client_id: String,
5198 pub component: UiSiteComponent,
5200 pub instance_id: String,
5202 pub plugin: String,
5204 pub key: String,
5206 pub uuid: String,
5207 pub session_id: String,
5208}
5209
5210#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5212pub enum UiSiteComponent {
5213 Pane,
5215 AbovePrompt,
5217 Unknown(String),
5219}
5220
5221impl UiSiteComponent {
5222 pub fn as_str(&self) -> &str {
5223 match self {
5224 Self::Pane => "Pane",
5225 Self::AbovePrompt => "AbovePrompt",
5226 Self::Unknown(s) => s.as_str(),
5227 }
5228 }
5229}
5230
5231impl fmt::Display for UiSiteComponent {
5232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5233 f.write_str(self.as_str())
5234 }
5235}
5236
5237impl From<&str> for UiSiteComponent {
5238 fn from(s: &str) -> Self {
5239 match s {
5240 "Pane" => Self::Pane,
5241 "AbovePrompt" => Self::AbovePrompt,
5242 other => Self::Unknown(other.to_string()),
5243 }
5244 }
5245}
5246
5247impl Serialize for UiSiteComponent {
5248 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5249 serializer.serialize_str(self.as_str())
5250 }
5251}
5252
5253impl<'de> Deserialize<'de> for UiSiteComponent {
5254 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5255 let s = String::deserialize(deserializer)?;
5256 Ok(Self::from(s.as_str()))
5257 }
5258}
5259
5260#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5263pub struct LocalCommandRun {
5264 pub command: String,
5266 pub args: String,
5269 #[serde(default, skip_serializing_if = "Option::is_none")]
5274 pub plugin: Option<String>,
5275}
5276
5277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
5281pub struct LocalCommandOutcome {
5282 pub kind: LocalCommandOutcomeKind,
5283 #[serde(default, skip_serializing_if = "Option::is_none")]
5286 pub suggestion: Option<String>,
5287}
5288
5289#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5294pub enum LocalCommandOutcomeKind {
5295 UnavailableHeadless,
5297 Unknown,
5300 Failed,
5302 RestartRequired,
5304 Other(String),
5306}
5307
5308impl LocalCommandOutcomeKind {
5309 pub fn as_str(&self) -> &str {
5310 match self {
5311 Self::UnavailableHeadless => "unavailable_headless",
5312 Self::Unknown => "unknown",
5313 Self::Failed => "failed",
5314 Self::RestartRequired => "restart_required",
5315 Self::Other(s) => s.as_str(),
5316 }
5317 }
5318}
5319
5320impl fmt::Display for LocalCommandOutcomeKind {
5321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5322 f.write_str(self.as_str())
5323 }
5324}
5325
5326impl From<&str> for LocalCommandOutcomeKind {
5327 fn from(s: &str) -> Self {
5328 match s {
5329 "unavailable_headless" => Self::UnavailableHeadless,
5330 "unknown" => Self::Unknown,
5331 "failed" => Self::Failed,
5332 "restart_required" => Self::RestartRequired,
5333 other => Self::Other(other.to_string()),
5334 }
5335 }
5336}
5337
5338impl Serialize for LocalCommandOutcomeKind {
5339 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5340 serializer.serialize_str(self.as_str())
5341 }
5342}
5343
5344impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
5345 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5346 let s = String::deserialize(deserializer)?;
5347 Ok(Self::from(s.as_str()))
5348 }
5349}
5350
5351#[derive(Debug, Clone, Serialize, Deserialize)]
5357pub struct UsageReport {
5358 pub session: super::control::UsageSession,
5360 pub rate_limits: Option<UsageReportRateLimits>,
5364 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5365 pub extra: serde_json::Map<String, Value>,
5366}
5367
5368#[derive(Debug, Clone, Serialize, Deserialize)]
5370pub struct UsageReportRateLimits {
5371 pub limits: Option<Vec<UsageReportLimit>>,
5379 #[serde(default, skip_serializing_if = "Option::is_none")]
5382 pub extra_usage: Option<UsageReportExtraUsage>,
5383 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5384 pub extra: serde_json::Map<String, Value>,
5385}
5386
5387#[derive(Debug, Clone, Serialize, Deserialize)]
5389pub struct UsageReportLimit {
5390 pub kind: String,
5393 pub group: String,
5396 pub percent: f64,
5398 pub resets_at: Option<String>,
5400 #[serde(default, skip_serializing_if = "Option::is_none")]
5403 pub scope: Option<UsageReportScope>,
5404 #[serde(default, skip_serializing_if = "Option::is_none")]
5408 pub severity: Option<String>,
5409 #[serde(default, skip_serializing_if = "Option::is_none")]
5411 pub is_active: Option<bool>,
5412 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5413 pub extra: serde_json::Map<String, Value>,
5414}
5415
5416#[derive(Debug, Clone, Serialize, Deserialize)]
5418pub struct UsageReportScope {
5419 #[serde(default, skip_serializing_if = "Option::is_none")]
5420 pub model: Option<UsageReportScopeLabel>,
5421 #[serde(default, skip_serializing_if = "Option::is_none")]
5422 pub surface: Option<UsageReportScopeLabel>,
5423 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5424 pub extra: serde_json::Map<String, Value>,
5425}
5426
5427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5429pub struct UsageReportScopeLabel {
5430 pub display_name: String,
5431}
5432
5433#[derive(Debug, Clone, Serialize, Deserialize)]
5437pub struct UsageReportExtraUsage {
5438 pub is_enabled: bool,
5440 pub monthly_limit: Option<f64>,
5441 pub used_credits: Option<f64>,
5442 pub utilization: Option<f64>,
5443 #[serde(default, skip_serializing_if = "Option::is_none")]
5444 pub currency: Option<String>,
5445 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
5446 pub extra: serde_json::Map<String, Value>,
5447}
5448
5449#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5452pub struct BatchToolUse {
5453 pub id: String,
5454 pub name: String,
5455}
5456
5457#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5462pub struct ContextUsage {
5463 pub model: String,
5465 pub total_tokens: u64,
5468 pub raw_max_tokens: u64,
5471 pub percentage: u64,
5473 #[serde(default, skip_serializing_if = "Option::is_none")]
5475 pub over_limit: Option<ContextOverLimit>,
5476 #[serde(default)]
5478 pub categories: Vec<ContextCategory>,
5479 #[serde(default)]
5481 pub mcp_tools: Vec<ContextMcpTool>,
5482 #[serde(default)]
5484 pub memory_files: Vec<ContextMemoryFile>,
5485 #[serde(default)]
5487 pub agents: Vec<ContextAgent>,
5488 #[serde(default, skip_serializing_if = "Option::is_none")]
5490 pub skills: Option<Vec<ContextSkill>>,
5491}
5492
5493#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5495pub struct ContextOverLimit {
5496 pub tokens_over: u64,
5497 pub kind: String,
5500}
5501
5502#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5504pub struct ContextCategory {
5505 pub name: String,
5508 pub tokens: u64,
5509 pub kind: String,
5513}
5514
5515#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5517pub struct ContextMcpTool {
5518 pub name: String,
5520 pub server_name: String,
5521 pub tokens: u64,
5522}
5523
5524#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5526pub struct ContextMemoryFile {
5527 pub path: String,
5528 #[serde(rename = "type")]
5530 pub file_type: String,
5531 pub tokens: u64,
5532}
5533
5534#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5536pub struct ContextAgent {
5537 pub agent_type: String,
5538 pub source: String,
5540 pub tokens: u64,
5541}
5542
5543#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5545pub struct ContextSkill {
5546 pub name: String,
5547 pub source: String,
5549 #[serde(default, skip_serializing_if = "Option::is_none")]
5550 pub plugin_name: Option<String>,
5551 pub tokens: u64,
5552}
5553
5554#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5556pub struct ToolUseMeta {
5557 pub id: String,
5558 pub display_name: String,
5559 #[serde(default, skip_serializing_if = "Option::is_none")]
5560 pub server_display_name: Option<String>,
5561 #[serde(default, skip_serializing_if = "Option::is_none")]
5562 pub icon_url: Option<String>,
5563 #[serde(default, skip_serializing_if = "Option::is_none")]
5568 pub server_id: Option<String>,
5569 #[serde(default, skip_serializing_if = "Option::is_none")]
5577 pub server_id_stamp: Option<String>,
5578 #[serde(default, skip_serializing_if = "Option::is_none")]
5581 pub tool_name: Option<String>,
5582}
5583
5584#[derive(Debug, Clone, Serialize, Deserialize)]
5586pub struct AssistantMessage {
5587 pub message: AssistantMessageContent,
5588 #[serde(alias = "sessionId")]
5589 pub session_id: String,
5590 #[serde(skip_serializing_if = "Option::is_none")]
5591 pub uuid: Option<String>,
5592 #[serde(skip_serializing_if = "Option::is_none")]
5593 pub parent_tool_use_id: Option<String>,
5594 #[serde(skip_serializing_if = "Option::is_none")]
5596 pub request_id: Option<String>,
5597 #[serde(default, skip_serializing_if = "Option::is_none")]
5602 pub refused_message_id: Option<String>,
5603 #[serde(default, skip_serializing_if = "Option::is_none")]
5612 pub user_message_uuid: Option<String>,
5613 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5619 pub user_message_uuids: Vec<String>,
5620 #[serde(default, skip_serializing_if = "Option::is_none")]
5624 pub agent_id: Option<String>,
5625 #[serde(skip_serializing_if = "Option::is_none")]
5628 pub subagent_type: Option<String>,
5629 #[serde(skip_serializing_if = "Option::is_none")]
5631 pub task_description: Option<String>,
5632 #[serde(skip_serializing_if = "Option::is_none")]
5633 pub error: Option<AssistantErrorKind>,
5634 #[serde(default, skip_serializing_if = "Option::is_none")]
5638 pub aborted: Option<bool>,
5639 #[serde(default, skip_serializing_if = "Option::is_none")]
5644 pub resumed_from_incomplete_thinking: Option<bool>,
5645 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5646 pub supersedes: Vec<String>,
5647 #[serde(skip_serializing_if = "Option::is_none")]
5648 pub timestamp: Option<String>,
5649 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5650 pub tool_use_meta: Vec<ToolUseMeta>,
5651 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5656 pub batch_tool_uses: Vec<BatchToolUse>,
5657 #[serde(default, skip_serializing_if = "Option::is_none")]
5663 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
5664 #[serde(default, skip_serializing_if = "Option::is_none")]
5670 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
5671 #[serde(default, skip_serializing_if = "Option::is_none")]
5675 pub historical: Option<bool>,
5676 #[serde(default, skip_serializing_if = "Option::is_none")]
5684 pub resume_reason: Option<String>,
5685 #[serde(default, skip_serializing_if = "Option::is_none")]
5691 pub local_command_source: Option<String>,
5692 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5701 pub narration_block_indexes: Vec<usize>,
5702 #[serde(default, skip_serializing_if = "Option::is_none")]
5706 pub narration_hint: Option<String>,
5707 #[serde(default, skip_serializing_if = "Option::is_none")]
5716 pub thinking_duration_ms: Option<u64>,
5717 #[serde(default, skip_serializing_if = "Option::is_none")]
5728 pub thinking_display: Option<String>,
5729 #[serde(default, skip_serializing_if = "Option::is_none")]
5733 pub context_usage: Option<ContextUsage>,
5734 #[serde(default, skip_serializing_if = "Option::is_none")]
5742 pub usage_report: Option<Box<UsageReport>>,
5743 #[serde(default, skip_serializing_if = "Option::is_none")]
5750 pub local_command_run: Option<LocalCommandRun>,
5751 #[serde(default, skip_serializing_if = "Option::is_none")]
5757 pub local_command_outcome: Option<LocalCommandOutcome>,
5758 #[serde(default, skip_serializing_if = "Option::is_none")]
5759 pub is_meta: Option<bool>,
5760 #[serde(default, skip_serializing_if = "Option::is_none")]
5761 pub is_virtual: Option<bool>,
5762 #[serde(default, skip_serializing_if = "Option::is_none")]
5763 pub is_api_error_message: Option<bool>,
5764 #[serde(skip_serializing_if = "Option::is_none")]
5765 pub api_error_status: Option<u16>,
5766 #[serde(skip_serializing_if = "Option::is_none")]
5814 pub api_error: Option<String>,
5815 #[serde(default, skip_serializing_if = "Option::is_none")]
5822 pub api_error_code: Option<String>,
5823 #[serde(default, skip_serializing_if = "Option::is_none")]
5834 pub api_error_origin: Option<String>,
5835 #[serde(default, skip_serializing_if = "Option::is_none")]
5838 pub api_error_params: Option<ApiErrorParams>,
5839 #[serde(skip_serializing_if = "Option::is_none")]
5840 pub error_details: Option<String>,
5841 #[serde(skip_serializing_if = "Option::is_none")]
5842 pub advisor_model: Option<String>,
5843 #[serde(skip_serializing_if = "Option::is_none")]
5844 pub attribution_agent: Option<String>,
5845 #[serde(skip_serializing_if = "Option::is_none")]
5846 pub attribution_skill: Option<String>,
5847 #[serde(skip_serializing_if = "Option::is_none")]
5848 pub attribution_plugin: Option<String>,
5849 #[serde(skip_serializing_if = "Option::is_none")]
5850 pub attribution_mcp_server: Option<String>,
5851 #[serde(skip_serializing_if = "Option::is_none")]
5852 pub attribution_mcp_tool: Option<String>,
5853}
5854
5855#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
5859pub struct ApiErrorParams {
5860 #[serde(default, skip_serializing_if = "Option::is_none")]
5862 pub effort: Option<String>,
5863 #[serde(default, skip_serializing_if = "Option::is_none")]
5866 pub provider: Option<ApiErrorProvider>,
5867 #[serde(default, skip_serializing_if = "Option::is_none")]
5870 pub remedy: Option<ApiErrorRemedy>,
5871 #[serde(default, skip_serializing_if = "Option::is_none")]
5874 pub media: Option<ApiErrorMedia>,
5875 #[serde(default, skip_serializing_if = "Option::is_none")]
5877 pub media_reason: Option<ApiErrorMediaReason>,
5878 #[serde(default, skip_serializing_if = "Option::is_none")]
5897 pub rate_limit_info: Option<RateLimitInfo>,
5898}
5899
5900#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5902pub enum ApiErrorMedia {
5903 Image,
5904 Document,
5905 Unknown(String),
5907}
5908
5909impl ApiErrorMedia {
5910 pub fn as_str(&self) -> &str {
5911 match self {
5912 Self::Image => "image",
5913 Self::Document => "document",
5914 Self::Unknown(s) => s.as_str(),
5915 }
5916 }
5917}
5918
5919impl fmt::Display for ApiErrorMedia {
5920 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5921 f.write_str(self.as_str())
5922 }
5923}
5924
5925impl From<&str> for ApiErrorMedia {
5926 fn from(s: &str) -> Self {
5927 match s {
5928 "image" => Self::Image,
5929 "document" => Self::Document,
5930 other => Self::Unknown(other.to_string()),
5931 }
5932 }
5933}
5934
5935impl Serialize for ApiErrorMedia {
5936 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5937 serializer.serialize_str(self.as_str())
5938 }
5939}
5940
5941impl<'de> Deserialize<'de> for ApiErrorMedia {
5942 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5943 let s = String::deserialize(deserializer)?;
5944 Ok(Self::from(s.as_str()))
5945 }
5946}
5947
5948#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5951pub enum ApiErrorMediaReason {
5952 Unprocessable,
5955 UnsupportedByModel,
5957 MediaBudget,
5960 Unknown(String),
5962}
5963
5964impl ApiErrorMediaReason {
5965 pub fn as_str(&self) -> &str {
5966 match self {
5967 Self::Unprocessable => "unprocessable",
5968 Self::UnsupportedByModel => "unsupported_by_model",
5969 Self::MediaBudget => "media_budget",
5970 Self::Unknown(s) => s.as_str(),
5971 }
5972 }
5973}
5974
5975impl fmt::Display for ApiErrorMediaReason {
5976 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5977 f.write_str(self.as_str())
5978 }
5979}
5980
5981impl From<&str> for ApiErrorMediaReason {
5982 fn from(s: &str) -> Self {
5983 match s {
5984 "unprocessable" => Self::Unprocessable,
5985 "unsupported_by_model" => Self::UnsupportedByModel,
5986 "media_budget" => Self::MediaBudget,
5987 other => Self::Unknown(other.to_string()),
5988 }
5989 }
5990}
5991
5992impl Serialize for ApiErrorMediaReason {
5993 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5994 serializer.serialize_str(self.as_str())
5995 }
5996}
5997
5998impl<'de> Deserialize<'de> for ApiErrorMediaReason {
5999 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6000 let s = String::deserialize(deserializer)?;
6001 Ok(Self::from(s.as_str()))
6002 }
6003}
6004
6005#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6007pub enum ApiErrorProvider {
6008 Bedrock,
6009 AnthropicAws,
6010 Mantle,
6011 AnthropicGoogleCloud,
6012 Vertex,
6013 Foundry,
6014 Gateway,
6015 Unknown(String),
6017}
6018
6019impl ApiErrorProvider {
6020 pub fn as_str(&self) -> &str {
6021 match self {
6022 Self::Bedrock => "bedrock",
6023 Self::AnthropicAws => "anthropicAws",
6024 Self::Mantle => "mantle",
6025 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
6026 Self::Vertex => "vertex",
6027 Self::Foundry => "foundry",
6028 Self::Gateway => "gateway",
6029 Self::Unknown(s) => s.as_str(),
6030 }
6031 }
6032}
6033
6034impl fmt::Display for ApiErrorProvider {
6035 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6036 f.write_str(self.as_str())
6037 }
6038}
6039
6040impl From<&str> for ApiErrorProvider {
6041 fn from(s: &str) -> Self {
6042 match s {
6043 "bedrock" => Self::Bedrock,
6044 "anthropicAws" => Self::AnthropicAws,
6045 "mantle" => Self::Mantle,
6046 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
6047 "vertex" => Self::Vertex,
6048 "foundry" => Self::Foundry,
6049 "gateway" => Self::Gateway,
6050 other => Self::Unknown(other.to_string()),
6051 }
6052 }
6053}
6054
6055impl Serialize for ApiErrorProvider {
6056 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6057 serializer.serialize_str(self.as_str())
6058 }
6059}
6060
6061impl<'de> Deserialize<'de> for ApiErrorProvider {
6062 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6063 let s = String::deserialize(deserializer)?;
6064 Ok(Self::from(s.as_str()))
6065 }
6066}
6067
6068#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6070pub enum ApiErrorRemedy {
6071 RefreshCommand,
6074 RefreshCredentials,
6076 Adc,
6079 GatewayToken,
6082 HostManaged,
6084 ModelAccess,
6087 Unknown(String),
6089}
6090
6091impl ApiErrorRemedy {
6092 pub fn as_str(&self) -> &str {
6093 match self {
6094 Self::RefreshCommand => "refresh_command",
6095 Self::RefreshCredentials => "refresh_credentials",
6096 Self::Adc => "adc",
6097 Self::GatewayToken => "gateway_token",
6098 Self::HostManaged => "host_managed",
6099 Self::ModelAccess => "model_access",
6100 Self::Unknown(s) => s.as_str(),
6101 }
6102 }
6103}
6104
6105impl fmt::Display for ApiErrorRemedy {
6106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6107 f.write_str(self.as_str())
6108 }
6109}
6110
6111impl From<&str> for ApiErrorRemedy {
6112 fn from(s: &str) -> Self {
6113 match s {
6114 "refresh_command" => Self::RefreshCommand,
6115 "refresh_credentials" => Self::RefreshCredentials,
6116 "adc" => Self::Adc,
6117 "gateway_token" => Self::GatewayToken,
6118 "host_managed" => Self::HostManaged,
6119 "model_access" => Self::ModelAccess,
6120 other => Self::Unknown(other.to_string()),
6121 }
6122 }
6123}
6124
6125impl Serialize for ApiErrorRemedy {
6126 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
6127 serializer.serialize_str(self.as_str())
6128 }
6129}
6130
6131impl<'de> Deserialize<'de> for ApiErrorRemedy {
6132 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
6133 let s = String::deserialize(deserializer)?;
6134 Ok(Self::from(s.as_str()))
6135 }
6136}
6137
6138#[derive(Debug, Clone, Serialize, Deserialize)]
6140pub struct AssistantMessageContent {
6141 pub id: String,
6142 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
6144 pub message_type: Option<String>,
6145 pub role: MessageRole,
6146 pub model: String,
6147 pub content: Vec<ContentBlock>,
6148 #[serde(skip_serializing_if = "Option::is_none")]
6149 pub stop_reason: Option<StopReason>,
6150 #[serde(skip_serializing_if = "Option::is_none")]
6151 pub stop_sequence: Option<String>,
6152 #[serde(skip_serializing_if = "Option::is_none")]
6153 pub usage: Option<AssistantUsage>,
6154 #[serde(skip_serializing_if = "Option::is_none")]
6156 pub stop_details: Option<Value>,
6157 #[serde(skip_serializing_if = "Option::is_none")]
6159 pub context_management: Option<Value>,
6160}
6161
6162#[derive(Debug, Clone, Serialize, Deserialize)]
6164pub struct AssistantUsage {
6165 #[serde(default)]
6167 pub input_tokens: u32,
6168
6169 #[serde(default)]
6171 pub output_tokens: u32,
6172
6173 #[serde(default)]
6175 pub cache_creation_input_tokens: u32,
6176
6177 #[serde(default)]
6179 pub cache_read_input_tokens: u32,
6180
6181 #[serde(skip_serializing_if = "Option::is_none")]
6183 pub service_tier: Option<String>,
6184
6185 #[serde(skip_serializing_if = "Option::is_none")]
6187 pub cache_creation: Option<CacheCreationDetails>,
6188
6189 #[serde(skip_serializing_if = "Option::is_none")]
6191 pub inference_geo: Option<String>,
6192
6193 #[serde(default, skip_serializing_if = "Option::is_none")]
6196 pub speed: Option<String>,
6197
6198 #[serde(default, skip_serializing_if = "Option::is_none")]
6201 pub fallback_credit: Option<Value>,
6202}
6203
6204#[derive(Debug, Clone, Serialize, Deserialize)]
6206pub struct CacheCreationDetails {
6207 #[serde(default)]
6209 pub ephemeral_1h_input_tokens: u32,
6210
6211 #[serde(default)]
6213 pub ephemeral_5m_input_tokens: u32,
6214}
6215
6216#[cfg(test)]
6217mod tests {
6218 use crate::io::ClaudeOutput;
6219
6220 #[test]
6221 fn test_subagent_usage_rollup_accumulates_task_results() {
6222 use super::SubagentUsageRollup;
6223
6224 let mut rollup = SubagentUsageRollup::default();
6225
6226 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}}"#;
6227 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
6228 assert!(rollup.observe(&output));
6229 assert_eq!(rollup.subagent_tokens, 10201);
6230 assert_eq!(rollup.agent_count, 1);
6231 assert_eq!(rollup.tool_uses, 3);
6232 assert_eq!(rollup.duration_ms, 1853);
6233
6234 assert!(!rollup.observe(&output));
6236 assert_eq!(rollup.agent_count, 1);
6237 assert_eq!(rollup.subagent_tokens, 10201);
6238
6239 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}}"#;
6241 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
6242 assert!(rollup.observe(&output));
6243 assert_eq!(rollup.agent_count, 2);
6244 assert_eq!(rollup.subagent_tokens, 10701);
6245 }
6246
6247 #[test]
6248 fn test_subagent_usage_rollup_ignores_non_task_results() {
6249 use super::SubagentUsageRollup;
6250
6251 let mut rollup = SubagentUsageRollup::default();
6252
6253 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}}"#;
6256 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
6257 assert!(!rollup.observe(&output));
6258
6259 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
6261 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
6262 assert!(!rollup.observe(&output));
6263
6264 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
6266 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
6267 assert!(!rollup.observe(&output));
6268
6269 assert_eq!(rollup, SubagentUsageRollup::default());
6270 }
6271
6272 #[test]
6273 fn test_subagent_usage_rollup_over_captured_session() {
6274 use super::SubagentUsageRollup;
6275
6276 let mut rollup = SubagentUsageRollup::default();
6277 let fixture =
6278 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
6279 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
6280 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
6281 rollup.observe(&output);
6282 }
6283 }
6284 assert_eq!(rollup.agent_count, 1);
6285 assert_eq!(rollup.subagent_tokens, 10201);
6286 }
6287
6288 #[test]
6289 fn test_system_message_init() {
6290 let json = r#"{
6291 "type": "system",
6292 "subtype": "init",
6293 "session_id": "test-session-123",
6294 "cwd": "/home/user/project",
6295 "model": "claude-sonnet-4",
6296 "tools": ["Bash", "Read", "Write"],
6297 "mcp_servers": [],
6298 "slash_commands": ["compact", "cost", "review"],
6299 "agents": ["Bash", "Explore", "Plan"],
6300 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
6301 "skills": [],
6302 "claude_code_version": "2.1.15",
6303 "apiKeySource": "none",
6304 "output_style": "default",
6305 "permissionMode": "default"
6306 }"#;
6307
6308 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6309 if let ClaudeOutput::System(sys) = output {
6310 assert!(sys.is_init());
6311 assert!(!sys.is_status());
6312 assert!(!sys.is_compact_boundary());
6313
6314 let init = sys.as_init().expect("Should parse as init");
6315 assert_eq!(init.session_id, "test-session-123");
6316 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
6317 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
6318 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
6319 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
6320 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
6321 assert_eq!(init.plugins.len(), 1);
6322 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
6323 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
6324 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
6325 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
6326 assert_eq!(
6327 init.permission_mode,
6328 Some(super::InitPermissionMode::Default)
6329 );
6330 } else {
6331 panic!("Expected System message");
6332 }
6333 }
6334
6335 #[test]
6336 fn test_system_message_init_from_real_capture() {
6337 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
6338 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6339 if let ClaudeOutput::System(sys) = output {
6340 let init = sys.as_init().expect("Should parse real init capture");
6341 assert_eq!(init.slash_commands.len(), 8);
6342 assert!(init.slash_commands.contains(&"compact".to_string()));
6343 assert!(init.slash_commands.contains(&"review".to_string()));
6344 assert_eq!(init.agents.len(), 5);
6345 assert!(init.agents.contains(&"Bash".to_string()));
6346 assert!(init.agents.contains(&"Explore".to_string()));
6347 assert_eq!(init.plugins.len(), 1);
6348 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
6349 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
6350 } else {
6351 panic!("Expected System message");
6352 }
6353 }
6354
6355 #[test]
6356 fn test_system_message_status() {
6357 let json = r#"{
6358 "type": "system",
6359 "subtype": "status",
6360 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6361 "status": "compacting",
6362 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
6363 }"#;
6364
6365 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6366 if let ClaudeOutput::System(sys) = output {
6367 assert!(sys.is_status());
6368 assert!(!sys.is_init());
6369
6370 let status = sys.as_status().expect("Should parse as status");
6371 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6372 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
6373 assert_eq!(
6374 status.uuid,
6375 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
6376 );
6377 } else {
6378 panic!("Expected System message");
6379 }
6380 }
6381
6382 #[test]
6383 fn test_system_message_status_null() {
6384 let json = r#"{
6385 "type": "system",
6386 "subtype": "status",
6387 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6388 "status": null,
6389 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
6390 }"#;
6391
6392 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6393 if let ClaudeOutput::System(sys) = output {
6394 let status = sys.as_status().expect("Should parse as status");
6395 assert_eq!(status.status, None);
6396 } else {
6397 panic!("Expected System message");
6398 }
6399 }
6400
6401 #[test]
6402 fn test_system_message_task_started() {
6403 let json = r#"{
6404 "type": "system",
6405 "subtype": "task_started",
6406 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6407 "task_id": "b6daf3f",
6408 "task_type": "local_bash",
6409 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
6410 "description": "Wait for CI on PR #12",
6411 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
6412 }"#;
6413
6414 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6415 if let ClaudeOutput::System(sys) = output {
6416 assert!(sys.is_task_started());
6417 assert!(!sys.is_task_progress());
6418 assert!(!sys.is_task_notification());
6419
6420 let task = sys.as_task_started().expect("Should parse as task_started");
6421 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
6422 assert_eq!(task.task_id, "b6daf3f");
6423 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
6424 assert_eq!(
6425 task.tool_use_id.as_deref(),
6426 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
6427 );
6428 assert_eq!(task.description, "Wait for CI on PR #12");
6429 } else {
6430 panic!("Expected System message");
6431 }
6432 }
6433
6434 #[test]
6435 fn test_system_message_task_started_agent() {
6436 let json = r#"{
6437 "type": "system",
6438 "subtype": "task_started",
6439 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6440 "task_id": "a4a7e0906e5fc64cc",
6441 "task_type": "local_agent",
6442 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6443 "description": "Explore Scene/ArrayScene duplication",
6444 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6445 }"#;
6446
6447 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6448 if let ClaudeOutput::System(sys) = output {
6449 let task = sys.as_task_started().expect("Should parse as task_started");
6450 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
6451 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
6452 } else {
6453 panic!("Expected System message");
6454 }
6455 }
6456
6457 #[test]
6458 fn test_system_message_task_progress() {
6459 let json = r#"{
6460 "type": "system",
6461 "subtype": "task_progress",
6462 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6463 "task_id": "a4a7e0906e5fc64cc",
6464 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
6465 "description": "Reading src/jplephem/chebyshev.rs",
6466 "last_tool_name": "Read",
6467 "usage": {
6468 "duration_ms": 13996,
6469 "tool_uses": 9,
6470 "total_tokens": 38779
6471 },
6472 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
6473 }"#;
6474
6475 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6476 if let ClaudeOutput::System(sys) = output {
6477 assert!(sys.is_task_progress());
6478 assert!(!sys.is_task_started());
6479
6480 let progress = sys
6481 .as_task_progress()
6482 .expect("Should parse as task_progress");
6483 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
6484 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
6485 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
6486 assert_eq!(progress.usage.duration_ms, 13996);
6487 assert_eq!(progress.usage.tool_uses, 9);
6488 assert_eq!(progress.usage.total_tokens, 38779);
6489 } else {
6490 panic!("Expected System message");
6491 }
6492 }
6493
6494 #[test]
6495 fn test_system_message_task_notification_completed() {
6496 let json = r#"{
6497 "type": "system",
6498 "subtype": "task_notification",
6499 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
6500 "task_id": "a0ba761e9dc9c316f",
6501 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
6502 "status": "completed",
6503 "summary": "Agent \"Write Hipparcos data source doc\" completed",
6504 "output_file": "",
6505 "usage": {
6506 "duration_ms": 172300,
6507 "tool_uses": 11,
6508 "total_tokens": 42005
6509 },
6510 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
6511 }"#;
6512
6513 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6514 if let ClaudeOutput::System(sys) = output {
6515 assert!(sys.is_task_notification());
6516
6517 let notif = sys
6518 .as_task_notification()
6519 .expect("Should parse as task_notification");
6520 assert_eq!(notif.status, super::TaskStatus::Completed);
6521 assert_eq!(
6522 notif.summary,
6523 "Agent \"Write Hipparcos data source doc\" completed"
6524 );
6525 assert_eq!(notif.output_file, Some("".to_string()));
6526 assert_eq!(
6527 notif.tool_use_id,
6528 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
6529 );
6530 let usage = notif.usage.expect("Should have usage");
6531 assert_eq!(usage.duration_ms, 172300);
6532 assert_eq!(usage.tool_uses, 11);
6533 assert_eq!(usage.total_tokens, 42005);
6534 } else {
6535 panic!("Expected System message");
6536 }
6537 }
6538
6539 #[test]
6540 fn test_system_message_task_notification_failed_no_usage() {
6541 let json = r#"{
6542 "type": "system",
6543 "subtype": "task_notification",
6544 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
6545 "task_id": "b98f6a3",
6546 "status": "failed",
6547 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
6548 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
6549 }"#;
6550
6551 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6552 if let ClaudeOutput::System(sys) = output {
6553 let notif = sys
6554 .as_task_notification()
6555 .expect("Should parse as task_notification");
6556 assert_eq!(notif.status, super::TaskStatus::Failed);
6557 assert!(notif.tool_use_id.is_none());
6558 assert!(notif.usage.is_none());
6559 assert_eq!(
6560 notif.output_file,
6561 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
6562 );
6563 } else {
6564 panic!("Expected System message");
6565 }
6566 }
6567
6568 #[test]
6574 fn test_task_messages_roundtrip_through_value() {
6575 let cases = [
6576 r#"{"type":"system","subtype":"task_started","session_id":"s1",
6577 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
6578 "description":"Sleep 3s","uuid":"u1"}"#,
6579 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
6580 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
6581 "last_tool_name":"Bash",
6582 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6583 "uuid":"u2"}"#,
6584 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
6585 "task_id":"t1","tool_use_id":"tu1","status":"completed",
6586 "summary":"done","output_file":"",
6587 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
6588 "uuid":"u3"}"#,
6589 ];
6590
6591 for json in cases {
6592 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6593 let value = serde_json::to_value(&output).unwrap();
6594 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
6595
6596 let ClaudeOutput::System(sys) = reparsed else {
6597 panic!("Expected System variant after round-trip");
6598 };
6599
6600 match sys.subtype {
6601 super::SystemSubtype::TaskStarted => {
6602 assert!(
6603 sys.as_task_started().is_some(),
6604 "as_task_started failed after round-trip"
6605 );
6606 }
6607 super::SystemSubtype::TaskProgress => {
6608 assert!(
6609 sys.as_task_progress().is_some(),
6610 "as_task_progress failed after round-trip"
6611 );
6612 }
6613 super::SystemSubtype::TaskNotification => {
6614 assert!(
6615 sys.as_task_notification().is_some(),
6616 "as_task_notification failed after round-trip"
6617 );
6618 }
6619 other => panic!("unexpected subtype after round-trip: {other:?}"),
6620 }
6621 }
6622 }
6623
6624 #[test]
6625 fn test_system_message_compact_boundary() {
6626 let json = r#"{
6627 "type": "system",
6628 "subtype": "compact_boundary",
6629 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
6630 "compact_metadata": {
6631 "pre_tokens": 155285,
6632 "trigger": "auto"
6633 },
6634 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
6635 }"#;
6636
6637 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6638 if let ClaudeOutput::System(sys) = output {
6639 assert!(sys.is_compact_boundary());
6640 assert!(!sys.is_init());
6641 assert!(!sys.is_status());
6642
6643 let compact = sys
6644 .as_compact_boundary()
6645 .expect("Should parse as compact_boundary");
6646 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
6647 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
6648 assert_eq!(
6649 compact.compact_metadata.trigger,
6650 super::CompactionTrigger::Auto
6651 );
6652 assert!(compact.summary.is_none());
6654 assert!(compact.leaf_message_count.is_none());
6655 assert!(compact.duration_ms.is_none());
6656 } else {
6657 panic!("Expected System message");
6658 }
6659 }
6660
6661 #[test]
6662 fn test_compact_boundary_with_summary_stats() {
6663 let json = r#"{
6665 "type": "system",
6666 "subtype": "compact_boundary",
6667 "session_id": "s1",
6668 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
6669 "summary": "Summarized the earlier exploration.",
6670 "leaf_message_count": 42,
6671 "duration_ms": 1234,
6672 "uuid": "u1"
6673 }"#;
6674 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6675 let ClaudeOutput::System(sys) = output else {
6676 panic!("Expected System message");
6677 };
6678 let compact = sys.as_compact_boundary().expect("compact_boundary");
6679 assert_eq!(
6680 compact.summary.as_deref(),
6681 Some("Summarized the earlier exploration.")
6682 );
6683 assert_eq!(compact.leaf_message_count, Some(42));
6684 assert_eq!(compact.duration_ms, Some(1234));
6685
6686 let json_alt = r#"{
6689 "type": "system",
6690 "subtype": "compact_boundary",
6691 "session_id": "s2",
6692 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
6693 "content": "alt-key summary",
6694 "message_count": 7
6695 }"#;
6696 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
6697 let ClaudeOutput::System(sys) = output else {
6698 panic!("Expected System message");
6699 };
6700 let compact = sys.as_compact_boundary().expect("compact_boundary");
6701 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
6702 assert_eq!(compact.leaf_message_count, Some(7));
6703 }
6704
6705 #[test]
6706 fn test_init_message_with_new_fields() {
6707 let json = r#"{
6708 "type": "system",
6709 "subtype": "init",
6710 "session_id": "test-session",
6711 "cwd": "/home/user",
6712 "model": "claude-opus-4-7",
6713 "tools": ["Bash"],
6714 "mcp_servers": [],
6715 "permissionMode": "default",
6716 "apiKeySource": "none",
6717 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
6718 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
6719 "fast_mode_state": "off",
6720 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
6721 "claude_code_version": "2.1.117"
6722 }"#;
6723
6724 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6725 if let ClaudeOutput::System(sys) = output {
6726 let init = sys.as_init().expect("Should parse as init");
6727 assert_eq!(
6728 init.uuid.as_deref(),
6729 Some("44841a0d-182d-493a-86b5-79800d3d9665")
6730 );
6731 assert!(init.memory_paths.is_some());
6732 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
6733 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
6734 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
6735 } else {
6736 panic!("Expected System message");
6737 }
6738 }
6739
6740 #[test]
6741 fn test_assistant_message_with_new_fields() {
6742 let json = r#"{
6743 "type": "assistant",
6744 "message": {
6745 "id": "msg_1",
6746 "type": "message",
6747 "role": "assistant",
6748 "model": "claude-opus-4-7",
6749 "content": [{"type": "text", "text": "Hello"}],
6750 "stop_reason": "end_turn",
6751 "stop_details": null,
6752 "context_management": null,
6753 "usage": {
6754 "input_tokens": 100,
6755 "output_tokens": 10,
6756 "cache_creation_input_tokens": 50,
6757 "cache_read_input_tokens": 0,
6758 "service_tier": "standard",
6759 "inference_geo": "not_available"
6760 }
6761 },
6762 "session_id": "abc",
6763 "uuid": "msg-uuid-123"
6764 }"#;
6765
6766 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6767 if let ClaudeOutput::Assistant(asst) = output {
6768 assert_eq!(asst.message.stop_details, None);
6769 assert_eq!(asst.message.context_management, None);
6770 let usage = asst.message.usage.unwrap();
6771 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
6772 } else {
6773 panic!("Expected Assistant message");
6774 }
6775 }
6776
6777 #[test]
6778 fn test_user_message_with_new_fields() {
6779 let json = r#"{
6780 "type": "user",
6781 "message": {
6782 "role": "user",
6783 "content": [{"type": "text", "text": "Hello"}]
6784 },
6785 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
6786 "parent_tool_use_id": "toolu_123",
6787 "uuid": "user-msg-456"
6788 }"#;
6789
6790 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6791 if let ClaudeOutput::User(user) = output {
6792 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
6793 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
6794 } else {
6795 panic!("Expected User message");
6796 }
6797 }
6798
6799 #[test]
6805 fn test_user_message_preserves_tool_use_result_and_timestamp() {
6806 let json = r#"{
6807 "type":"user",
6808 "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"}]},
6809 "parent_tool_use_id":null,
6810 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6811 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
6812 "timestamp":"2026-05-12T23:12:04.121Z",
6813 "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"}}
6814 }"#;
6815
6816 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6817 let user = match output {
6818 ClaudeOutput::User(u) => u,
6819 other => panic!("Expected User message, got {:?}", other.message_type()),
6820 };
6821
6822 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
6823 let raw = user
6824 .tool_use_result
6825 .as_ref()
6826 .expect("tool_use_result must be captured");
6827 assert_eq!(raw["answers"]["Color"], "Blue");
6828 assert_eq!(raw["questions"][0]["header"], "Color");
6829
6830 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
6834 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
6835 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
6836 assert_eq!(
6837 reser["tool_use_result"]["questions"][0]["question"],
6838 "Which color do you prefer?"
6839 );
6840
6841 let typed: crate::AskUserQuestionInput = user
6844 .tool_use_result_as::<crate::AskUserQuestionInput>()
6845 .expect("tool_use_result present")
6846 .expect("AskUserQuestionInput parses");
6847 assert_eq!(typed.questions.len(), 1);
6848 assert_eq!(typed.questions[0].header, "Color");
6849 let answers = typed.answers.expect("answers populated");
6850 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
6851 }
6852
6853 #[test]
6856 fn test_user_message_without_tool_use_result_omits_field() {
6857 let json = r#"{
6858 "type":"user",
6859 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
6860 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
6861 }"#;
6862
6863 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6864 let user = match output {
6865 ClaudeOutput::User(u) => u,
6866 _ => panic!("Expected User message"),
6867 };
6868 assert!(user.tool_use_result.is_none());
6869 assert!(user.timestamp.is_none());
6870
6871 let reser = serde_json::to_value(&user).unwrap();
6872 assert!(reser.get("tool_use_result").is_none());
6873 assert!(reser.get("timestamp").is_none());
6874 }
6875
6876 #[test]
6880 fn test_subagent_result_exposes_token_accounting() {
6881 let json = r#"{
6882 "type":"user",
6883 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
6884 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
6885 "tool_use_result":{
6886 "status":"completed",
6887 "prompt":"Count the .rs files.",
6888 "agentId":"ac4f0276e9d4b6232",
6889 "agentType":"Explore",
6890 "content":[{"type":"text","text":"21"}],
6891 "resolvedModel":"claude-haiku-4-5-20251001",
6892 "totalDurationMs":6869,
6893 "totalTokens":7834,
6894 "totalToolUseCount":1,
6895 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
6896 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
6897 }
6898 }"#;
6899
6900 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6901 let user = match output {
6902 ClaudeOutput::User(u) => u,
6903 _ => panic!("Expected User message"),
6904 };
6905
6906 let result = user.subagent_result().expect("subagent result parses");
6907 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
6908 assert_eq!(
6909 result.resolved_model.as_deref(),
6910 Some("claude-haiku-4-5-20251001")
6911 );
6912 assert_eq!(result.total_tokens, Some(7834));
6913 assert_eq!(result.total_duration_ms, Some(6869));
6914 assert_eq!(result.total_tool_use_count, Some(1));
6915
6916 let usage = result.usage.expect("nested usage present");
6917 assert_eq!(usage.input_tokens, 6);
6918 assert_eq!(usage.cache_read_input_tokens, 7699);
6919
6920 let stats = result.tool_stats.expect("toolStats present");
6921 assert_eq!(stats.bash_count, 1);
6922 }
6923
6924 #[test]
6928 fn test_subagent_result_absent_for_non_task_result() {
6929 let json = r#"{
6930 "type":"user",
6931 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
6932 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6933 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
6934 }"#;
6935
6936 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6937 let user = match output {
6938 ClaudeOutput::User(u) => u,
6939 _ => panic!("Expected User message"),
6940 };
6941
6942 let result = user.subagent_result().expect("lenient parse");
6943 assert_eq!(result.total_tokens, None);
6944 assert_eq!(result.agent_type, None);
6945 }
6946
6947 #[test]
6948 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
6949 use serde_json::Value;
6950
6951 let raw: Value = serde_json::from_str(
6952 r#"{
6953 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
6954 "fast_mode_state":"off",
6955 "fast_mode_disabled_reason":"not_first_party",
6956 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
6957 }"#,
6958 )
6959 .unwrap();
6960 crate::io::assert_fully_wrapped(&raw);
6961
6962 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6963 let ClaudeOutput::System(sys) = output else {
6964 panic!("expected System");
6965 };
6966 let init = sys.as_init().expect("parses as init");
6967 assert_eq!(
6968 init.fast_mode_disabled_reason,
6969 Some(crate::FastModeDisabledReason::NotFirstParty)
6970 );
6971 let errs = init.mcp_server_errors.unwrap();
6972 assert_eq!(errs.len(), 1);
6973 assert_eq!(errs[0].name, "broken");
6974 assert_eq!(errs[0].error_type, "invalid_config");
6975 }
6976
6977 #[test]
6978 fn test_code_change_published_fully_wrapped() {
6979 use super::{KnownSystemEvent, SystemSubtype};
6980 use serde_json::Value;
6981
6982 let raw: Value = serde_json::from_str(
6983 r#"{
6984 "type":"system","subtype":"code_change_published",
6985 "provider":"github","url":"https://github.com/owner/repo/pull/42",
6986 "repo":"owner/repo","identifier":"42",
6987 "uuid":"u1","session_id":"s1"
6988 }"#,
6989 )
6990 .unwrap();
6991 crate::io::assert_fully_wrapped(&raw);
6992
6993 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6994 let ClaudeOutput::System(sys) = output else {
6995 panic!("expected System");
6996 };
6997 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
6998 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
6999 panic!("expected CodeChangePublished event");
7000 };
7001 assert_eq!(msg.provider, "github");
7002 assert_eq!(msg.repo, "owner/repo");
7003 assert_eq!(msg.identifier, "42");
7004
7005 assert!(sys.is_code_change_published());
7006 assert!(!sys.is_vcs_state_changed());
7007 let direct = sys.as_code_change_published().expect("direct accessor");
7008 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
7009 assert!(sys.as_vcs_state_changed().is_none());
7010 }
7011
7012 #[test]
7013 fn test_feedback_draft_queued_fully_wrapped() {
7014 use super::{KnownSystemEvent, SystemSubtype};
7015 use serde_json::Value;
7016
7017 let raw: Value = serde_json::from_str(
7018 r#"{
7019 "type":"system","subtype":"feedback_draft_queued",
7020 "draft_id":"draft-1","draft_type":"bug_report",
7021 "title":"Tool output was truncated",
7022 "details_preview":"The last command omitted its final lines",
7023 "uuid":"u1","session_id":"s1","future_field":"preserved"
7024 }"#,
7025 )
7026 .unwrap();
7027 crate::io::assert_fully_wrapped(&raw);
7028
7029 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7030 let ClaudeOutput::System(sys) = output else {
7031 panic!("expected System");
7032 };
7033 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
7034 assert!(sys.is_feedback_draft_queued());
7035 assert!(!sys.is_vcs_state_changed());
7036
7037 let direct = sys
7038 .as_feedback_draft_queued()
7039 .expect("direct typed accessor");
7040 assert_eq!(direct.draft_id, "draft-1");
7041 assert_eq!(direct.draft_type, "bug_report");
7042 assert_eq!(direct.extra["future_field"], "preserved");
7043
7044 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
7045 panic!("expected FeedbackDraftQueued event");
7046 };
7047 assert_eq!(known.title, "Tool output was truncated");
7048 assert_eq!(
7049 sys.typed_value().expect("typed value")["future_field"],
7050 "preserved"
7051 );
7052 }
7053
7054 #[test]
7055 fn test_cloud_session_delta_fully_wrapped() {
7056 use super::{KnownSystemEvent, SystemSubtype};
7057 use serde_json::Value;
7058
7059 let raw: Value = serde_json::from_str(
7060 r#"{
7061 "type":"system","subtype":"cloud_session_delta",
7062 "seq":3,"changed":["serving","connection"],
7063 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
7064 "serving":{"state":"on"},"connection":{"state":"live"}},
7065 "uuid":"u1","session_id":"s1","future_field":"preserved"
7066 }"#,
7067 )
7068 .unwrap();
7069 crate::io::assert_fully_wrapped(&raw);
7070
7071 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7072 let ClaudeOutput::System(sys) = output else {
7073 panic!("expected System");
7074 };
7075 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
7076 assert!(sys.is_cloud_session_delta());
7077 assert!(!sys.is_feedback_draft_queued());
7078
7079 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
7080 assert_eq!(direct.seq, 3);
7081 assert_eq!(direct.changed, vec!["serving", "connection"]);
7082 assert_eq!(direct.cloud_session["id"], "session_abc");
7083 assert_eq!(direct.extra["future_field"], "preserved");
7084
7085 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
7086 panic!("expected CloudSessionDelta event");
7087 };
7088 assert_eq!(known.session_id, "s1");
7089 assert_eq!(
7090 sys.typed_value().expect("typed value")["future_field"],
7091 "preserved"
7092 );
7093 }
7094
7095 #[test]
7096 fn test_vcs_state_changed_fully_wrapped() {
7097 use super::{KnownSystemEvent, VcsMutationKind};
7098 use serde_json::Value;
7099
7100 for kind in ["commit", "push", "merge", "rebase"] {
7101 let raw: Value = serde_json::from_str(&format!(
7102 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
7103 kind
7104 ))
7105 .unwrap();
7106 crate::io::assert_fully_wrapped(&raw);
7107
7108 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7109 let ClaudeOutput::System(sys) = output else {
7110 panic!("expected System");
7111 };
7112 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
7113 panic!("expected VcsStateChanged event");
7114 };
7115 assert_eq!(msg.kind.as_str(), kind);
7116 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
7117 }
7118
7119 let raw: Value = serde_json::from_str(
7121 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
7122 )
7123 .unwrap();
7124 crate::io::assert_fully_wrapped(&raw);
7125 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
7126 let ClaudeOutput::System(sys) = output else {
7127 panic!("expected System");
7128 };
7129 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
7130 panic!("expected VcsStateChanged event");
7131 };
7132 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
7133
7134 assert!(sys.is_vcs_state_changed());
7135 let direct = sys.as_vcs_state_changed().expect("direct accessor");
7136 assert_eq!(direct.cwd, "/repo");
7137 assert!(sys.as_code_change_published().is_none());
7138 }
7139
7140 #[test]
7141 fn test_assistant_aborted_and_resume_flags_roundtrip() {
7142 let json = r#"{
7143 "type":"assistant",
7144 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
7145 "session_id":"s1",
7146 "aborted":true,
7147 "resumed_from_incomplete_thinking":true
7148 }"#;
7149 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7150 let ClaudeOutput::Assistant(msg) = &output else {
7151 panic!("expected Assistant");
7152 };
7153 assert_eq!(msg.aborted, Some(true));
7154 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
7155 let reserialized = serde_json::to_string(&output).unwrap();
7156 assert!(reserialized.contains("\"aborted\":true"));
7157 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
7158
7159 let json = r#"{
7161 "type":"assistant",
7162 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
7163 "session_id":"s2"
7164 }"#;
7165 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7166 let reserialized = serde_json::to_string(&output).unwrap();
7167 assert!(!reserialized.contains("aborted"));
7168 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
7169 }
7170
7171 #[test]
7172 fn test_user_tool_result_meta_roundtrip() {
7173 let json = r#"{
7174 "type":"user",
7175 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
7176 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
7177 "tool_result_meta":[
7178 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
7179 {"id":"toolu_2","non_execution_kind":"permission-rule"}
7180 ]
7181 }"#;
7182 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
7183 let ClaudeOutput::User(user) = &output else {
7184 panic!("expected User");
7185 };
7186 let meta = user.tool_result_meta.as_ref().unwrap();
7187 assert_eq!(meta.len(), 2);
7188 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
7189 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
7190 assert_eq!(meta[1].user_feedback, None);
7191
7192 let reserialized = serde_json::to_string(&output).unwrap();
7193 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
7194 assert!(!reserialized.contains("\"user_feedback\":null"));
7195 }
7196
7197 #[test]
7201 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
7202 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
7203 let with_scope = serde_json::json!({
7204 "trigger": "refusal",
7205 "direction": "retry",
7206 "scope": "local",
7207 "original_model": "claude-fable-5",
7208 "fallback_model": "claude-opus-5",
7209 "request_id": null,
7210 "content": "Refused; retried on fallback model.",
7211 "uuid": "u1",
7212 "session_id": "s1"
7213 });
7214 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
7215 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
7216 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
7217
7218 let mut without = with_scope.clone();
7221 without.as_object_mut().unwrap().remove("scope");
7222 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
7223 assert_eq!(msg.scope, None);
7224 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
7225
7226 assert_eq!(
7228 RefusalFallbackScope::from("workspace").as_str(),
7229 "workspace"
7230 );
7231 }
7232}