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};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum SystemSubtype {
16 Init,
17 Status,
18 CompactBoundary,
19 ThinkingTokens,
20 TaskStarted,
21 TaskProgress,
22 TaskUpdated,
23 TaskNotification,
24 ApiRetry,
25 ControlRequestProgress,
26 ModelRefusalFallback,
27 ModelRefusalNoFallback,
28 LocalCommandOutput,
29 HookStarted,
30 HookProgress,
31 HookResponse,
32 PluginInstall,
33 BackgroundTasksChanged,
34 SessionStateChanged,
35 WorkerShuttingDown,
36 CommandsChanged,
37 Notification,
38 FilesPersisted,
39 MemoryRecall,
40 ElicitationComplete,
41 PermissionDenied,
42 MirrorError,
43 Informational,
44 CodeChangePublished,
45 VcsStateChanged,
46 FeedbackDraftQueued,
47 CloudSessionDelta,
48 DevIntent,
49 TurnHandoffAvailable,
50 TurnPreempted,
51 PeerMessageHold,
52 PerTurnEffortChanged,
53 SessionTitleChanged,
54 InstructionSizeWarning,
55 FileAttachmentsMissing,
56 UiFocus,
57 UiInvalidate,
58 UiLog,
59 UiPanes,
60 UiScroll,
61 UiStatus,
62 UiToast,
63 Unknown(String),
65}
66
67impl SystemSubtype {
68 pub fn as_str(&self) -> &str {
69 match self {
70 Self::Init => "init",
71 Self::Status => "status",
72 Self::CompactBoundary => "compact_boundary",
73 Self::ThinkingTokens => "thinking_tokens",
74 Self::TaskStarted => "task_started",
75 Self::TaskProgress => "task_progress",
76 Self::TaskUpdated => "task_updated",
77 Self::TaskNotification => "task_notification",
78 Self::ApiRetry => "api_retry",
79 Self::ControlRequestProgress => "control_request_progress",
80 Self::ModelRefusalFallback => "model_refusal_fallback",
81 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
82 Self::LocalCommandOutput => "local_command_output",
83 Self::HookStarted => "hook_started",
84 Self::HookProgress => "hook_progress",
85 Self::HookResponse => "hook_response",
86 Self::PluginInstall => "plugin_install",
87 Self::BackgroundTasksChanged => "background_tasks_changed",
88 Self::SessionStateChanged => "session_state_changed",
89 Self::WorkerShuttingDown => "worker_shutting_down",
90 Self::CommandsChanged => "commands_changed",
91 Self::Notification => "notification",
92 Self::FilesPersisted => "files_persisted",
93 Self::MemoryRecall => "memory_recall",
94 Self::ElicitationComplete => "elicitation_complete",
95 Self::PermissionDenied => "permission_denied",
96 Self::MirrorError => "mirror_error",
97 Self::Informational => "informational",
98 Self::CodeChangePublished => "code_change_published",
99 Self::VcsStateChanged => "vcs_state_changed",
100 Self::FeedbackDraftQueued => "feedback_draft_queued",
101 Self::CloudSessionDelta => "cloud_session_delta",
102 Self::DevIntent => "dev_intent",
103 Self::TurnHandoffAvailable => "turn_handoff_available",
104 Self::TurnPreempted => "turn_preempted",
105 Self::PeerMessageHold => "peer_message_hold",
106 Self::PerTurnEffortChanged => "per_turn_effort_changed",
107 Self::SessionTitleChanged => "session_title_changed",
108 Self::InstructionSizeWarning => "instruction_size_warning",
109 Self::FileAttachmentsMissing => "file_attachments_missing",
110 Self::UiFocus => "ui_focus",
111 Self::UiInvalidate => "ui_invalidate",
112 Self::UiLog => "ui_log",
113 Self::UiPanes => "ui_panes",
114 Self::UiScroll => "ui_scroll",
115 Self::UiStatus => "ui_status",
116 Self::UiToast => "ui_toast",
117 Self::Unknown(s) => s.as_str(),
118 }
119 }
120}
121
122impl fmt::Display for SystemSubtype {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.write_str(self.as_str())
125 }
126}
127
128impl From<&str> for SystemSubtype {
129 fn from(s: &str) -> Self {
130 match s {
131 "init" => Self::Init,
132 "status" => Self::Status,
133 "compact_boundary" => Self::CompactBoundary,
134 "thinking_tokens" => Self::ThinkingTokens,
135 "task_started" => Self::TaskStarted,
136 "task_progress" => Self::TaskProgress,
137 "task_updated" => Self::TaskUpdated,
138 "task_notification" => Self::TaskNotification,
139 "api_retry" => Self::ApiRetry,
140 "control_request_progress" => Self::ControlRequestProgress,
141 "model_refusal_fallback" => Self::ModelRefusalFallback,
142 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
143 "local_command_output" => Self::LocalCommandOutput,
144 "hook_started" => Self::HookStarted,
145 "hook_progress" => Self::HookProgress,
146 "hook_response" => Self::HookResponse,
147 "plugin_install" => Self::PluginInstall,
148 "background_tasks_changed" => Self::BackgroundTasksChanged,
149 "session_state_changed" => Self::SessionStateChanged,
150 "worker_shutting_down" => Self::WorkerShuttingDown,
151 "commands_changed" => Self::CommandsChanged,
152 "notification" => Self::Notification,
153 "files_persisted" => Self::FilesPersisted,
154 "memory_recall" => Self::MemoryRecall,
155 "elicitation_complete" => Self::ElicitationComplete,
156 "permission_denied" => Self::PermissionDenied,
157 "mirror_error" => Self::MirrorError,
158 "informational" => Self::Informational,
159 "code_change_published" => Self::CodeChangePublished,
160 "vcs_state_changed" => Self::VcsStateChanged,
161 "feedback_draft_queued" => Self::FeedbackDraftQueued,
162 "cloud_session_delta" => Self::CloudSessionDelta,
163 "dev_intent" => Self::DevIntent,
164 "turn_handoff_available" => Self::TurnHandoffAvailable,
165 "turn_preempted" => Self::TurnPreempted,
166 "peer_message_hold" => Self::PeerMessageHold,
167 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
168 "session_title_changed" => Self::SessionTitleChanged,
169 "instruction_size_warning" => Self::InstructionSizeWarning,
170 "file_attachments_missing" => Self::FileAttachmentsMissing,
171 "ui_focus" => Self::UiFocus,
172 "ui_invalidate" => Self::UiInvalidate,
173 "ui_log" => Self::UiLog,
174 "ui_panes" => Self::UiPanes,
175 "ui_scroll" => Self::UiScroll,
176 "ui_status" => Self::UiStatus,
177 "ui_toast" => Self::UiToast,
178 other => Self::Unknown(other.to_string()),
179 }
180 }
181}
182
183impl Serialize for SystemSubtype {
184 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
185 serializer.serialize_str(self.as_str())
186 }
187}
188
189impl<'de> Deserialize<'de> for SystemSubtype {
190 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
191 let s = String::deserialize(deserializer)?;
192 Ok(Self::from(s.as_str()))
193 }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, Hash)]
201pub enum MessageRole {
202 User,
203 Assistant,
204 Unknown(String),
206}
207
208impl MessageRole {
209 pub fn as_str(&self) -> &str {
210 match self {
211 Self::User => "user",
212 Self::Assistant => "assistant",
213 Self::Unknown(s) => s.as_str(),
214 }
215 }
216}
217
218impl fmt::Display for MessageRole {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 f.write_str(self.as_str())
221 }
222}
223
224impl From<&str> for MessageRole {
225 fn from(s: &str) -> Self {
226 match s {
227 "user" => Self::User,
228 "assistant" => Self::Assistant,
229 other => Self::Unknown(other.to_string()),
230 }
231 }
232}
233
234impl Serialize for MessageRole {
235 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
236 serializer.serialize_str(self.as_str())
237 }
238}
239
240impl<'de> Deserialize<'de> for MessageRole {
241 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
242 let s = String::deserialize(deserializer)?;
243 Ok(Self::from(s.as_str()))
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Hash)]
249pub enum CompactionTrigger {
250 Auto,
252 Manual,
254 Unknown(String),
256}
257
258impl CompactionTrigger {
259 pub fn as_str(&self) -> &str {
260 match self {
261 Self::Auto => "auto",
262 Self::Manual => "manual",
263 Self::Unknown(s) => s.as_str(),
264 }
265 }
266}
267
268impl fmt::Display for CompactionTrigger {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 f.write_str(self.as_str())
271 }
272}
273
274impl From<&str> for CompactionTrigger {
275 fn from(s: &str) -> Self {
276 match s {
277 "auto" => Self::Auto,
278 "manual" => Self::Manual,
279 other => Self::Unknown(other.to_string()),
280 }
281 }
282}
283
284impl Serialize for CompactionTrigger {
285 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
286 serializer.serialize_str(self.as_str())
287 }
288}
289
290impl<'de> Deserialize<'de> for CompactionTrigger {
291 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
292 let s = String::deserialize(deserializer)?;
293 Ok(Self::from(s.as_str()))
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Eq, Hash)]
299pub enum StopReason {
300 EndTurn,
302 MaxTokens,
304 ToolUse,
306 Unknown(String),
308}
309
310impl StopReason {
311 pub fn as_str(&self) -> &str {
312 match self {
313 Self::EndTurn => "end_turn",
314 Self::MaxTokens => "max_tokens",
315 Self::ToolUse => "tool_use",
316 Self::Unknown(s) => s.as_str(),
317 }
318 }
319}
320
321impl fmt::Display for StopReason {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 f.write_str(self.as_str())
324 }
325}
326
327impl From<&str> for StopReason {
328 fn from(s: &str) -> Self {
329 match s {
330 "end_turn" => Self::EndTurn,
331 "max_tokens" => Self::MaxTokens,
332 "tool_use" => Self::ToolUse,
333 other => Self::Unknown(other.to_string()),
334 }
335 }
336}
337
338impl Serialize for StopReason {
339 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
340 serializer.serialize_str(self.as_str())
341 }
342}
343
344impl<'de> Deserialize<'de> for StopReason {
345 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
346 let s = String::deserialize(deserializer)?;
347 Ok(Self::from(s.as_str()))
348 }
349}
350
351#[derive(Debug, Clone, PartialEq, Eq, Hash)]
353pub enum ApiKeySource {
354 None,
356 User,
357 Project,
358 Org,
359 Temporary,
360 Oauth,
361 Unknown(String),
363}
364
365impl ApiKeySource {
366 pub fn as_str(&self) -> &str {
367 match self {
368 Self::None => "none",
369 Self::User => "user",
370 Self::Project => "project",
371 Self::Org => "org",
372 Self::Temporary => "temporary",
373 Self::Oauth => "oauth",
374 Self::Unknown(s) => s.as_str(),
375 }
376 }
377}
378
379impl fmt::Display for ApiKeySource {
380 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381 f.write_str(self.as_str())
382 }
383}
384
385impl From<&str> for ApiKeySource {
386 fn from(s: &str) -> Self {
387 match s {
388 "none" => Self::None,
389 "user" => Self::User,
390 "project" => Self::Project,
391 "org" => Self::Org,
392 "temporary" => Self::Temporary,
393 "oauth" => Self::Oauth,
394 other => Self::Unknown(other.to_string()),
395 }
396 }
397}
398
399impl Serialize for ApiKeySource {
400 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
401 serializer.serialize_str(self.as_str())
402 }
403}
404
405impl<'de> Deserialize<'de> for ApiKeySource {
406 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
407 let s = String::deserialize(deserializer)?;
408 Ok(Self::from(s.as_str()))
409 }
410}
411
412#[derive(Debug, Clone, PartialEq, Eq, Hash)]
414pub enum OutputStyle {
415 Default,
417 Unknown(String),
419}
420
421impl OutputStyle {
422 pub fn as_str(&self) -> &str {
423 match self {
424 Self::Default => "default",
425 Self::Unknown(s) => s.as_str(),
426 }
427 }
428}
429
430impl fmt::Display for OutputStyle {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 f.write_str(self.as_str())
433 }
434}
435
436impl From<&str> for OutputStyle {
437 fn from(s: &str) -> Self {
438 match s {
439 "default" => Self::Default,
440 other => Self::Unknown(other.to_string()),
441 }
442 }
443}
444
445impl Serialize for OutputStyle {
446 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
447 serializer.serialize_str(self.as_str())
448 }
449}
450
451impl<'de> Deserialize<'de> for OutputStyle {
452 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
453 let s = String::deserialize(deserializer)?;
454 Ok(Self::from(s.as_str()))
455 }
456}
457
458#[derive(Debug, Clone, PartialEq, Eq, Hash)]
460pub enum InitPermissionMode {
461 Default,
463 AcceptEdits,
464 BypassPermissions,
465 Plan,
466 DontAsk,
467 Auto,
468 Unknown(String),
470}
471
472impl InitPermissionMode {
473 pub fn as_str(&self) -> &str {
474 match self {
475 Self::Default => "default",
476 Self::AcceptEdits => "acceptEdits",
477 Self::BypassPermissions => "bypassPermissions",
478 Self::Plan => "plan",
479 Self::DontAsk => "dontAsk",
480 Self::Auto => "auto",
481 Self::Unknown(s) => s.as_str(),
482 }
483 }
484}
485
486impl fmt::Display for InitPermissionMode {
487 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488 f.write_str(self.as_str())
489 }
490}
491
492impl From<&str> for InitPermissionMode {
493 fn from(s: &str) -> Self {
494 match s {
495 "default" => Self::Default,
496 "acceptEdits" => Self::AcceptEdits,
497 "bypassPermissions" => Self::BypassPermissions,
498 "plan" => Self::Plan,
499 "dontAsk" => Self::DontAsk,
500 "auto" => Self::Auto,
501 other => Self::Unknown(other.to_string()),
502 }
503 }
504}
505
506impl Serialize for InitPermissionMode {
507 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
508 serializer.serialize_str(self.as_str())
509 }
510}
511
512impl<'de> Deserialize<'de> for InitPermissionMode {
513 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
514 let s = String::deserialize(deserializer)?;
515 Ok(Self::from(s.as_str()))
516 }
517}
518
519#[derive(Debug, Clone, PartialEq, Eq, Hash)]
521pub enum StatusMessageStatus {
522 Compacting,
524 Requesting,
526 Unknown(String),
528}
529
530impl StatusMessageStatus {
531 pub fn as_str(&self) -> &str {
532 match self {
533 Self::Compacting => "compacting",
534 Self::Requesting => "requesting",
535 Self::Unknown(s) => s.as_str(),
536 }
537 }
538}
539
540impl fmt::Display for StatusMessageStatus {
541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542 f.write_str(self.as_str())
543 }
544}
545
546impl From<&str> for StatusMessageStatus {
547 fn from(s: &str) -> Self {
548 match s {
549 "compacting" => Self::Compacting,
550 "requesting" => Self::Requesting,
551 other => Self::Unknown(other.to_string()),
552 }
553 }
554}
555
556impl Serialize for StatusMessageStatus {
557 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
558 serializer.serialize_str(self.as_str())
559 }
560}
561
562impl<'de> Deserialize<'de> for StatusMessageStatus {
563 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
564 let s = String::deserialize(deserializer)?;
565 Ok(Self::from(s.as_str()))
566 }
567}
568
569pub(crate) fn serialize_optional_uuid<S>(
571 uuid: &Option<Uuid>,
572 serializer: S,
573) -> Result<S::Ok, S::Error>
574where
575 S: Serializer,
576{
577 match uuid {
578 Some(id) => serializer.serialize_str(&id.to_string()),
579 None => serializer.serialize_none(),
580 }
581}
582
583pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
585where
586 D: Deserializer<'de>,
587{
588 let opt_str: Option<String> = Option::deserialize(deserializer)?;
589 match opt_str {
590 Some(s) => Uuid::parse_str(&s)
591 .map(Some)
592 .map_err(serde::de::Error::custom),
593 None => Ok(None),
594 }
595}
596
597#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
600pub struct MessageOrigin {
601 pub kind: String,
602 #[serde(flatten)]
603 pub extra: serde_json::Map<String, Value>,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
608pub struct SummarizeMetadata {
609 pub messages_summarized: u64,
610 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub user_context: Option<String>,
612 #[serde(default, skip_serializing_if = "Option::is_none")]
613 pub direction: Option<String>,
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
618pub struct McpMeta {
619 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
620 pub meta: Option<Value>,
621 #[serde(default, skip_serializing_if = "Option::is_none")]
622 pub structured_content: Option<Value>,
623 #[serde(default, skip_serializing_if = "Vec::is_empty")]
628 pub resource_links: Vec<ResourceLink>,
629}
630
631#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
635pub struct ResourceLink {
636 pub uri: String,
637 pub name: String,
638 #[serde(default, skip_serializing_if = "Option::is_none")]
639 pub title: Option<String>,
640 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub description: Option<String>,
642 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
643 pub mime_type: Option<String>,
644 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub size: Option<u64>,
646 #[serde(default, skip_serializing_if = "Option::is_none")]
647 pub annotations: Option<Value>,
648}
649
650#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
652pub struct ToolResultMeta {
653 pub id: String,
655 #[serde(default, skip_serializing_if = "Option::is_none")]
662 pub non_execution_kind: Option<String>,
663 #[serde(default, skip_serializing_if = "Option::is_none")]
665 pub user_feedback: Option<String>,
666 #[serde(default, skip_serializing_if = "Option::is_none")]
671 pub remedy: Option<ToolResultRemedy>,
672}
673
674#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
679pub struct ToolResultRemedy {
680 pub kind: ToolResultRemedyKind,
683 #[serde(default, skip_serializing_if = "Option::is_none")]
687 pub servers: Option<Vec<String>>,
688 #[serde(default, skip_serializing_if = "Option::is_none")]
691 pub provider: Option<RemedyLoginProvider>,
692 #[serde(default, skip_serializing_if = "Option::is_none")]
696 pub path: Option<String>,
697 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub managed: Option<bool>,
703 #[serde(default, skip_serializing_if = "Option::is_none")]
705 pub feature: Option<RemedyFeature>,
706 #[serde(default, skip_serializing_if = "Option::is_none")]
708 pub cause: Option<RemedyFeatureCause>,
709 #[serde(default, skip_serializing_if = "Option::is_none")]
711 pub policy_kind: Option<RemedyPolicyKind>,
712}
713
714#[derive(Debug, Clone, PartialEq, Eq, Hash)]
716pub enum ToolResultRemedyKind {
717 McpNeedsAuth,
719 McpDisabled,
721 McpRequiredMissing,
723 AuthExpired,
725 AuthOverridden,
727 AuthMissingScope,
729 DesignNeedsAuthorization,
731 SandboxViolation,
733 OutsideReadsBlocked,
735 MemoryPaused,
737 FeatureDisabled,
739 PolicyDenied,
742 SpawnArgLimit,
744 StagedForReview,
746 Unknown(String),
748}
749
750impl ToolResultRemedyKind {
751 pub fn as_str(&self) -> &str {
752 match self {
753 Self::McpNeedsAuth => "mcp_needs_auth",
754 Self::McpDisabled => "mcp_disabled",
755 Self::McpRequiredMissing => "mcp_required_missing",
756 Self::AuthExpired => "auth_expired",
757 Self::AuthOverridden => "auth_overridden",
758 Self::AuthMissingScope => "auth_missing_scope",
759 Self::DesignNeedsAuthorization => "design_needs_authorization",
760 Self::SandboxViolation => "sandbox_violation",
761 Self::OutsideReadsBlocked => "outside_reads_blocked",
762 Self::MemoryPaused => "memory_paused",
763 Self::FeatureDisabled => "feature_disabled",
764 Self::PolicyDenied => "policy_denied",
765 Self::SpawnArgLimit => "spawn_arg_limit",
766 Self::StagedForReview => "staged_for_review",
767 Self::Unknown(s) => s.as_str(),
768 }
769 }
770}
771
772impl fmt::Display for ToolResultRemedyKind {
773 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774 f.write_str(self.as_str())
775 }
776}
777
778impl From<&str> for ToolResultRemedyKind {
779 fn from(s: &str) -> Self {
780 match s {
781 "mcp_needs_auth" => Self::McpNeedsAuth,
782 "mcp_disabled" => Self::McpDisabled,
783 "mcp_required_missing" => Self::McpRequiredMissing,
784 "auth_expired" => Self::AuthExpired,
785 "auth_overridden" => Self::AuthOverridden,
786 "auth_missing_scope" => Self::AuthMissingScope,
787 "design_needs_authorization" => Self::DesignNeedsAuthorization,
788 "sandbox_violation" => Self::SandboxViolation,
789 "outside_reads_blocked" => Self::OutsideReadsBlocked,
790 "memory_paused" => Self::MemoryPaused,
791 "feature_disabled" => Self::FeatureDisabled,
792 "policy_denied" => Self::PolicyDenied,
793 "spawn_arg_limit" => Self::SpawnArgLimit,
794 "staged_for_review" => Self::StagedForReview,
795 other => Self::Unknown(other.to_string()),
796 }
797 }
798}
799
800impl Serialize for ToolResultRemedyKind {
801 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
802 serializer.serialize_str(self.as_str())
803 }
804}
805
806impl<'de> Deserialize<'de> for ToolResultRemedyKind {
807 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
808 let s = String::deserialize(deserializer)?;
809 Ok(Self::from(s.as_str()))
810 }
811}
812
813#[derive(Debug, Clone, PartialEq, Eq, Hash)]
815pub enum RemedyLoginProvider {
816 ClaudeAi,
818 ClaudeDesign,
820 Unknown(String),
822}
823
824impl RemedyLoginProvider {
825 pub fn as_str(&self) -> &str {
826 match self {
827 Self::ClaudeAi => "claude_ai",
828 Self::ClaudeDesign => "claude_design",
829 Self::Unknown(s) => s.as_str(),
830 }
831 }
832}
833
834impl fmt::Display for RemedyLoginProvider {
835 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836 f.write_str(self.as_str())
837 }
838}
839
840impl From<&str> for RemedyLoginProvider {
841 fn from(s: &str) -> Self {
842 match s {
843 "claude_ai" => Self::ClaudeAi,
844 "claude_design" => Self::ClaudeDesign,
845 other => Self::Unknown(other.to_string()),
846 }
847 }
848}
849
850impl Serialize for RemedyLoginProvider {
851 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
852 serializer.serialize_str(self.as_str())
853 }
854}
855
856impl<'de> Deserialize<'de> for RemedyLoginProvider {
857 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
858 let s = String::deserialize(deserializer)?;
859 Ok(Self::from(s.as_str()))
860 }
861}
862
863#[derive(Debug, Clone, PartialEq, Eq, Hash)]
866pub enum RemedyFeature {
867 Workflows,
868 Artifacts,
869 Unknown(String),
871}
872
873impl RemedyFeature {
874 pub fn as_str(&self) -> &str {
875 match self {
876 Self::Workflows => "workflows",
877 Self::Artifacts => "artifacts",
878 Self::Unknown(s) => s.as_str(),
879 }
880 }
881}
882
883impl fmt::Display for RemedyFeature {
884 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
885 f.write_str(self.as_str())
886 }
887}
888
889impl From<&str> for RemedyFeature {
890 fn from(s: &str) -> Self {
891 match s {
892 "workflows" => Self::Workflows,
893 "artifacts" => Self::Artifacts,
894 other => Self::Unknown(other.to_string()),
895 }
896 }
897}
898
899impl Serialize for RemedyFeature {
900 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
901 serializer.serialize_str(self.as_str())
902 }
903}
904
905impl<'de> Deserialize<'de> for RemedyFeature {
906 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
907 let s = String::deserialize(deserializer)?;
908 Ok(Self::from(s.as_str()))
909 }
910}
911
912#[derive(Debug, Clone, PartialEq, Eq, Hash)]
915pub enum RemedyFeatureCause {
916 ManagedSettings,
919 OrgPolicy,
921 Unavailable,
923 UserSetting,
926 Unknown(String),
928}
929
930impl RemedyFeatureCause {
931 pub fn as_str(&self) -> &str {
932 match self {
933 Self::ManagedSettings => "managed_settings",
934 Self::OrgPolicy => "org_policy",
935 Self::Unavailable => "unavailable",
936 Self::UserSetting => "user_setting",
937 Self::Unknown(s) => s.as_str(),
938 }
939 }
940}
941
942impl fmt::Display for RemedyFeatureCause {
943 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
944 f.write_str(self.as_str())
945 }
946}
947
948impl From<&str> for RemedyFeatureCause {
949 fn from(s: &str) -> Self {
950 match s {
951 "managed_settings" => Self::ManagedSettings,
952 "org_policy" => Self::OrgPolicy,
953 "unavailable" => Self::Unavailable,
954 "user_setting" => Self::UserSetting,
955 other => Self::Unknown(other.to_string()),
956 }
957 }
958}
959
960impl Serialize for RemedyFeatureCause {
961 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
962 serializer.serialize_str(self.as_str())
963 }
964}
965
966impl<'de> Deserialize<'de> for RemedyFeatureCause {
967 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
968 let s = String::deserialize(deserializer)?;
969 Ok(Self::from(s.as_str()))
970 }
971}
972
973#[derive(Debug, Clone, PartialEq, Eq, Hash)]
976pub enum RemedyPolicyKind {
977 OrgDenied,
979 Latched,
982 Unknown(String),
984}
985
986impl RemedyPolicyKind {
987 pub fn as_str(&self) -> &str {
988 match self {
989 Self::OrgDenied => "org_denied",
990 Self::Latched => "latched",
991 Self::Unknown(s) => s.as_str(),
992 }
993 }
994}
995
996impl fmt::Display for RemedyPolicyKind {
997 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
998 f.write_str(self.as_str())
999 }
1000}
1001
1002impl From<&str> for RemedyPolicyKind {
1003 fn from(s: &str) -> Self {
1004 match s {
1005 "org_denied" => Self::OrgDenied,
1006 "latched" => Self::Latched,
1007 other => Self::Unknown(other.to_string()),
1008 }
1009 }
1010}
1011
1012impl Serialize for RemedyPolicyKind {
1013 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1014 serializer.serialize_str(self.as_str())
1015 }
1016}
1017
1018impl<'de> Deserialize<'de> for RemedyPolicyKind {
1019 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1020 let s = String::deserialize(deserializer)?;
1021 Ok(Self::from(s.as_str()))
1022 }
1023}
1024
1025#[derive(Debug, Clone, Serialize, Deserialize)]
1027pub struct UserMessage {
1028 pub message: MessageContent,
1029 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1030 #[serde(
1031 serialize_with = "serialize_optional_uuid",
1032 deserialize_with = "deserialize_optional_uuid"
1033 )]
1034 pub session_id: Option<Uuid>,
1035 #[serde(skip_serializing_if = "Option::is_none")]
1037 pub parent_tool_use_id: Option<String>,
1038 #[serde(skip_serializing_if = "Option::is_none")]
1040 pub uuid: Option<String>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1043 pub timestamp: Option<String>,
1044 #[serde(skip_serializing_if = "Option::is_none")]
1051 pub tool_use_result: Option<serde_json::Value>,
1052 #[serde(skip_serializing_if = "Option::is_none")]
1055 pub subagent_type: Option<String>,
1056 #[serde(skip_serializing_if = "Option::is_none")]
1058 pub task_description: Option<String>,
1059 #[serde(skip_serializing_if = "Option::is_none")]
1060 pub origin: Option<MessageOrigin>,
1061 #[serde(skip_serializing_if = "Option::is_none")]
1062 pub priority: Option<String>,
1063 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1064 pub is_synthetic: Option<bool>,
1065 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1066 pub should_query: Option<bool>,
1067 #[serde(default, skip_serializing_if = "Option::is_none")]
1068 pub is_meta: Option<bool>,
1069 #[serde(default, skip_serializing_if = "Option::is_none")]
1070 pub is_visible_in_transcript_only: Option<bool>,
1071 #[serde(default, skip_serializing_if = "Option::is_none")]
1072 pub is_virtual: Option<bool>,
1073 #[serde(default, skip_serializing_if = "Option::is_none")]
1074 pub is_compact_summary: Option<bool>,
1075 #[serde(skip_serializing_if = "Option::is_none")]
1076 pub summarize_metadata: Option<SummarizeMetadata>,
1077 #[serde(skip_serializing_if = "Option::is_none")]
1078 pub mcp_meta: Option<McpMeta>,
1079 #[serde(skip_serializing_if = "Option::is_none")]
1082 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1083 #[serde(skip_serializing_if = "Option::is_none")]
1084 pub source_tool_use_id: Option<String>,
1085 #[serde(skip_serializing_if = "Option::is_none")]
1086 pub source_tool_assistant_uuid: Option<String>,
1087 #[serde(skip_serializing_if = "Option::is_none")]
1088 pub image_paste_ids: Option<Vec<u64>>,
1089 #[serde(skip_serializing_if = "Option::is_none")]
1090 pub client_platform: Option<String>,
1091 #[serde(skip_serializing_if = "Option::is_none")]
1092 pub inbound_origin: Option<String>,
1093 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1094 pub is_replay: Option<bool>,
1095 #[serde(skip_serializing_if = "Option::is_none")]
1096 pub file_attachments: Option<Vec<Value>>,
1097 #[serde(default, skip_serializing_if = "Option::is_none")]
1099 pub seeded_summon: Option<bool>,
1100 #[serde(default, skip_serializing_if = "Option::is_none")]
1103 pub client_composed: Option<bool>,
1104 #[serde(default, skip_serializing_if = "Option::is_none")]
1109 pub initiator: Option<String>,
1110 #[serde(default, skip_serializing_if = "Option::is_none")]
1116 pub pasted_content: Option<Vec<Value>>,
1117 #[serde(default, skip_serializing_if = "Option::is_none")]
1121 pub historical: Option<bool>,
1122 #[serde(default, skip_serializing_if = "Option::is_none")]
1128 pub server_received_wall_ms: Option<f64>,
1129 #[serde(default, skip_serializing_if = "Option::is_none")]
1133 pub refused_message_id: Option<String>,
1134}
1135
1136impl UserMessage {
1137 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1145 &self,
1146 ) -> Option<Result<T, serde_json::Error>> {
1147 self.tool_use_result
1148 .as_ref()
1149 .map(|v| serde_json::from_value(v.clone()))
1150 }
1151
1152 pub fn subagent_result(&self) -> Option<SubagentResult> {
1163 self.tool_use_result
1164 .as_ref()
1165 .and_then(|v| serde_json::from_value(v.clone()).ok())
1166 }
1167}
1168
1169#[derive(Debug, Clone, Serialize, Deserialize)]
1180pub struct SubagentResult {
1181 #[serde(skip_serializing_if = "Option::is_none")]
1183 pub status: Option<String>,
1184 #[serde(skip_serializing_if = "Option::is_none")]
1186 pub prompt: Option<String>,
1187 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1189 pub agent_id: Option<String>,
1190 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1192 pub agent_type: Option<String>,
1193 #[serde(
1195 default,
1196 deserialize_with = "deserialize_content_blocks",
1197 skip_serializing_if = "Vec::is_empty"
1198 )]
1199 pub content: Vec<ContentBlock>,
1200 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1202 pub resolved_model: Option<String>,
1203 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1205 pub total_duration_ms: Option<u64>,
1206 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1208 pub total_tokens: Option<u64>,
1209 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1211 pub total_tool_use_count: Option<u64>,
1212 #[serde(skip_serializing_if = "Option::is_none")]
1214 pub usage: Option<super::result::UsageInfo>,
1215 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1217 pub tool_stats: Option<SubagentToolStats>,
1218}
1219
1220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1225#[serde(rename_all = "camelCase")]
1226pub struct SubagentToolStats {
1227 #[serde(default)]
1228 pub read_count: u64,
1229 #[serde(default)]
1230 pub search_count: u64,
1231 #[serde(default)]
1232 pub bash_count: u64,
1233 #[serde(default)]
1234 pub edit_file_count: u64,
1235 #[serde(default)]
1236 pub lines_added: u64,
1237 #[serde(default)]
1238 pub lines_removed: u64,
1239 #[serde(default)]
1240 pub other_tool_count: u64,
1241 #[serde(flatten)]
1242 pub extra: serde_json::Map<String, Value>,
1243}
1244
1245#[derive(Debug, Clone, Default, PartialEq, Eq)]
1272pub struct SubagentUsageRollup {
1273 pub subagent_tokens: u64,
1276 pub agent_count: u32,
1278 pub tool_uses: u64,
1280 pub duration_ms: u64,
1282 seen_agent_ids: std::collections::BTreeSet<String>,
1283}
1284
1285impl SubagentUsageRollup {
1286 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1292 match output {
1293 ClaudeOutput::User(user) => self.observe_user(user),
1294 _ => false,
1295 }
1296 }
1297
1298 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1305 let Some(result) = user.subagent_result() else {
1306 return false;
1307 };
1308 if result.total_tokens.is_none() && result.agent_id.is_none() {
1309 return false;
1310 }
1311 if let Some(agent_id) = &result.agent_id {
1312 if !self.seen_agent_ids.insert(agent_id.clone()) {
1313 return false;
1314 }
1315 }
1316 self.agent_count += 1;
1317 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1318 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1319 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1320 true
1321 }
1322}
1323
1324#[derive(Debug, Clone, Serialize, Deserialize)]
1326pub struct MessageContent {
1327 pub role: MessageRole,
1328 #[serde(deserialize_with = "deserialize_content_blocks")]
1329 pub content: Vec<ContentBlock>,
1330}
1331
1332#[derive(Debug, Clone, Serialize, Deserialize)]
1334pub struct SystemMessage {
1335 pub subtype: SystemSubtype,
1336 #[serde(flatten)]
1337 pub data: Value, }
1339
1340impl SystemMessage {
1341 pub fn is_init(&self) -> bool {
1343 self.subtype == SystemSubtype::Init
1344 }
1345
1346 pub fn is_status(&self) -> bool {
1348 self.subtype == SystemSubtype::Status
1349 }
1350
1351 pub fn is_compact_boundary(&self) -> bool {
1353 self.subtype == SystemSubtype::CompactBoundary
1354 }
1355
1356 pub fn as_init(&self) -> Option<InitMessage> {
1358 if self.subtype != SystemSubtype::Init {
1359 return None;
1360 }
1361 serde_json::from_value(self.data.clone()).ok()
1362 }
1363
1364 pub fn as_status(&self) -> Option<StatusMessage> {
1366 if self.subtype != SystemSubtype::Status {
1367 return None;
1368 }
1369 serde_json::from_value(self.data.clone()).ok()
1370 }
1371
1372 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1374 if self.subtype != SystemSubtype::CompactBoundary {
1375 return None;
1376 }
1377 serde_json::from_value(self.data.clone()).ok()
1378 }
1379
1380 pub fn is_task_started(&self) -> bool {
1382 self.subtype == SystemSubtype::TaskStarted
1383 }
1384
1385 pub fn is_task_progress(&self) -> bool {
1387 self.subtype == SystemSubtype::TaskProgress
1388 }
1389
1390 pub fn is_task_notification(&self) -> bool {
1392 self.subtype == SystemSubtype::TaskNotification
1393 }
1394
1395 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1397 if self.subtype != SystemSubtype::TaskStarted {
1398 return None;
1399 }
1400 serde_json::from_value(self.data.clone()).ok()
1401 }
1402
1403 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1405 if self.subtype != SystemSubtype::TaskProgress {
1406 return None;
1407 }
1408 serde_json::from_value(self.data.clone()).ok()
1409 }
1410
1411 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1413 if self.subtype != SystemSubtype::TaskNotification {
1414 return None;
1415 }
1416 serde_json::from_value(self.data.clone()).ok()
1417 }
1418
1419 pub fn is_task_updated(&self) -> bool {
1421 self.subtype == SystemSubtype::TaskUpdated
1422 }
1423
1424 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1426 if self.subtype != SystemSubtype::TaskUpdated {
1427 return None;
1428 }
1429 serde_json::from_value(self.data.clone()).ok()
1430 }
1431
1432 pub fn is_thinking_tokens(&self) -> bool {
1434 self.subtype == SystemSubtype::ThinkingTokens
1435 }
1436
1437 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1439 if self.subtype != SystemSubtype::ThinkingTokens {
1440 return None;
1441 }
1442 serde_json::from_value(self.data.clone()).ok()
1443 }
1444
1445 pub fn is_code_change_published(&self) -> bool {
1447 self.subtype == SystemSubtype::CodeChangePublished
1448 }
1449
1450 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1452 if self.subtype != SystemSubtype::CodeChangePublished {
1453 return None;
1454 }
1455 serde_json::from_value(self.data.clone()).ok()
1456 }
1457
1458 pub fn is_vcs_state_changed(&self) -> bool {
1460 self.subtype == SystemSubtype::VcsStateChanged
1461 }
1462
1463 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1465 if self.subtype != SystemSubtype::VcsStateChanged {
1466 return None;
1467 }
1468 serde_json::from_value(self.data.clone()).ok()
1469 }
1470
1471 pub fn is_feedback_draft_queued(&self) -> bool {
1473 self.subtype == SystemSubtype::FeedbackDraftQueued
1474 }
1475
1476 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1478 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1479 return None;
1480 }
1481 serde_json::from_value(self.data.clone()).ok()
1482 }
1483
1484 pub fn is_cloud_session_delta(&self) -> bool {
1486 self.subtype == SystemSubtype::CloudSessionDelta
1487 }
1488
1489 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1491 if self.subtype != SystemSubtype::CloudSessionDelta {
1492 return None;
1493 }
1494 serde_json::from_value(self.data.clone()).ok()
1495 }
1496
1497 pub fn is_dev_intent(&self) -> bool {
1499 self.subtype == SystemSubtype::DevIntent
1500 }
1501
1502 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1504 if self.subtype != SystemSubtype::DevIntent {
1505 return None;
1506 }
1507 serde_json::from_value(self.data.clone()).ok()
1508 }
1509
1510 pub fn is_turn_handoff_available(&self) -> bool {
1512 self.subtype == SystemSubtype::TurnHandoffAvailable
1513 }
1514
1515 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1517 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1518 return None;
1519 }
1520 serde_json::from_value(self.data.clone()).ok()
1521 }
1522
1523 pub fn is_turn_preempted(&self) -> bool {
1525 self.subtype == SystemSubtype::TurnPreempted
1526 }
1527
1528 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1530 if self.subtype != SystemSubtype::TurnPreempted {
1531 return None;
1532 }
1533 serde_json::from_value(self.data.clone()).ok()
1534 }
1535
1536 pub fn is_peer_message_hold(&self) -> bool {
1538 self.subtype == SystemSubtype::PeerMessageHold
1539 }
1540
1541 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1543 if self.subtype != SystemSubtype::PeerMessageHold {
1544 return None;
1545 }
1546 serde_json::from_value(self.data.clone()).ok()
1547 }
1548
1549 pub fn is_per_turn_effort_changed(&self) -> bool {
1551 self.subtype == SystemSubtype::PerTurnEffortChanged
1552 }
1553
1554 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1556 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1557 return None;
1558 }
1559 serde_json::from_value(self.data.clone()).ok()
1560 }
1561
1562 pub fn is_session_title_changed(&self) -> bool {
1564 self.subtype == SystemSubtype::SessionTitleChanged
1565 }
1566
1567 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1569 if self.subtype != SystemSubtype::SessionTitleChanged {
1570 return None;
1571 }
1572 serde_json::from_value(self.data.clone()).ok()
1573 }
1574
1575 pub fn is_instruction_size_warning(&self) -> bool {
1577 self.subtype == SystemSubtype::InstructionSizeWarning
1578 }
1579
1580 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1582 if self.subtype != SystemSubtype::InstructionSizeWarning {
1583 return None;
1584 }
1585 serde_json::from_value(self.data.clone()).ok()
1586 }
1587
1588 pub fn is_file_attachments_missing(&self) -> bool {
1590 self.subtype == SystemSubtype::FileAttachmentsMissing
1591 }
1592
1593 pub fn as_file_attachments_missing(&self) -> Option<FileAttachmentsMissingMessage> {
1595 if self.subtype != SystemSubtype::FileAttachmentsMissing {
1596 return None;
1597 }
1598 serde_json::from_value(self.data.clone()).ok()
1599 }
1600
1601 pub fn is_ui_focus(&self) -> bool {
1603 self.subtype == SystemSubtype::UiFocus
1604 }
1605
1606 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1608 if self.subtype != SystemSubtype::UiFocus {
1609 return None;
1610 }
1611 serde_json::from_value(self.data.clone()).ok()
1612 }
1613
1614 pub fn is_ui_invalidate(&self) -> bool {
1616 self.subtype == SystemSubtype::UiInvalidate
1617 }
1618
1619 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1621 if self.subtype != SystemSubtype::UiInvalidate {
1622 return None;
1623 }
1624 serde_json::from_value(self.data.clone()).ok()
1625 }
1626
1627 pub fn is_ui_log(&self) -> bool {
1629 self.subtype == SystemSubtype::UiLog
1630 }
1631
1632 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1634 if self.subtype != SystemSubtype::UiLog {
1635 return None;
1636 }
1637 serde_json::from_value(self.data.clone()).ok()
1638 }
1639
1640 pub fn is_ui_panes(&self) -> bool {
1642 self.subtype == SystemSubtype::UiPanes
1643 }
1644
1645 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1647 if self.subtype != SystemSubtype::UiPanes {
1648 return None;
1649 }
1650 serde_json::from_value(self.data.clone()).ok()
1651 }
1652
1653 pub fn is_ui_scroll(&self) -> bool {
1655 self.subtype == SystemSubtype::UiScroll
1656 }
1657
1658 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1660 if self.subtype != SystemSubtype::UiScroll {
1661 return None;
1662 }
1663 serde_json::from_value(self.data.clone()).ok()
1664 }
1665
1666 pub fn is_ui_status(&self) -> bool {
1668 self.subtype == SystemSubtype::UiStatus
1669 }
1670
1671 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1673 if self.subtype != SystemSubtype::UiStatus {
1674 return None;
1675 }
1676 serde_json::from_value(self.data.clone()).ok()
1677 }
1678
1679 pub fn is_ui_toast(&self) -> bool {
1681 self.subtype == SystemSubtype::UiToast
1682 }
1683
1684 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
1686 if self.subtype != SystemSubtype::UiToast {
1687 return None;
1688 }
1689 serde_json::from_value(self.data.clone()).ok()
1690 }
1691
1692 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1694 macro_rules! parse {
1695 ($variant:ident, $ty:ty) => {
1696 serde_json::from_value::<$ty>(self.data.clone())
1697 .ok()
1698 .map(KnownSystemEvent::$variant)
1699 };
1700 }
1701
1702 match self.subtype {
1703 SystemSubtype::Init => parse!(Init, InitMessage),
1704 SystemSubtype::Status => parse!(Status, StatusMessage),
1705 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1706 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1707 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1708 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1709 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1710 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1711 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1712 SystemSubtype::ControlRequestProgress => {
1713 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1714 }
1715 SystemSubtype::ModelRefusalFallback => {
1716 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1717 }
1718 SystemSubtype::ModelRefusalNoFallback => {
1719 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1720 }
1721 SystemSubtype::LocalCommandOutput => {
1722 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1723 }
1724 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1725 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1726 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1727 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1728 SystemSubtype::BackgroundTasksChanged => {
1729 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1730 }
1731 SystemSubtype::SessionStateChanged => {
1732 parse!(SessionStateChanged, SessionStateChangedMessage)
1733 }
1734 SystemSubtype::WorkerShuttingDown => {
1735 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1736 }
1737 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1738 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1739 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1740 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1741 SystemSubtype::ElicitationComplete => {
1742 parse!(ElicitationComplete, ElicitationCompleteMessage)
1743 }
1744 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1745 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1746 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1747 SystemSubtype::CodeChangePublished => {
1748 parse!(CodeChangePublished, CodeChangePublishedMessage)
1749 }
1750 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1751 SystemSubtype::FeedbackDraftQueued => {
1752 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1753 }
1754 SystemSubtype::CloudSessionDelta => {
1755 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1756 }
1757 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1758 SystemSubtype::TurnHandoffAvailable => {
1759 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1760 }
1761 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1762 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1763 SystemSubtype::PerTurnEffortChanged => {
1764 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1765 }
1766 SystemSubtype::SessionTitleChanged => {
1767 parse!(SessionTitleChanged, SessionTitleChangedMessage)
1768 }
1769 SystemSubtype::InstructionSizeWarning => {
1770 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
1771 }
1772 SystemSubtype::FileAttachmentsMissing => {
1773 parse!(FileAttachmentsMissing, FileAttachmentsMissingMessage)
1774 }
1775 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
1776 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
1777 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
1778 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
1779 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
1780 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
1781 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
1782 SystemSubtype::Unknown(_) => None,
1783 }
1784 }
1785
1786 pub fn typed_value(&self) -> Option<Value> {
1795 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1796 parsed.and_then(|v| serde_json::to_value(v).ok())
1797 }
1798 match self.subtype {
1799 SystemSubtype::Init => reserialize(self.as_init()),
1800 SystemSubtype::Status => reserialize(self.as_status()),
1801 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1802 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1803 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1804 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1805 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1806 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1807 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1808 SystemSubtype::ControlRequestProgress => {
1809 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1810 }
1811 SystemSubtype::ModelRefusalFallback => {
1812 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1813 }
1814 SystemSubtype::ModelRefusalNoFallback => {
1815 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1816 }
1817 SystemSubtype::LocalCommandOutput => {
1818 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1819 }
1820 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1821 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1822 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1823 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1824 SystemSubtype::BackgroundTasksChanged => {
1825 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1826 }
1827 SystemSubtype::SessionStateChanged => {
1828 reserialize(parse_system::<SessionStateChangedMessage>(self))
1829 }
1830 SystemSubtype::WorkerShuttingDown => {
1831 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1832 }
1833 SystemSubtype::CommandsChanged => {
1834 reserialize(parse_system::<CommandsChangedMessage>(self))
1835 }
1836 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1837 SystemSubtype::FilesPersisted => {
1838 reserialize(parse_system::<FilesPersistedMessage>(self))
1839 }
1840 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1841 SystemSubtype::ElicitationComplete => {
1842 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1843 }
1844 SystemSubtype::PermissionDenied => {
1845 reserialize(parse_system::<PermissionDeniedMessage>(self))
1846 }
1847 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1848 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1849 SystemSubtype::CodeChangePublished => {
1850 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1851 }
1852 SystemSubtype::VcsStateChanged => {
1853 reserialize(parse_system::<VcsStateChangedMessage>(self))
1854 }
1855 SystemSubtype::FeedbackDraftQueued => {
1856 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1857 }
1858 SystemSubtype::CloudSessionDelta => {
1859 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1860 }
1861 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1862 SystemSubtype::TurnHandoffAvailable => {
1863 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1864 }
1865 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1866 SystemSubtype::PeerMessageHold => {
1867 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1868 }
1869 SystemSubtype::PerTurnEffortChanged => {
1870 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
1871 }
1872 SystemSubtype::SessionTitleChanged => {
1873 reserialize(parse_system::<SessionTitleChangedMessage>(self))
1874 }
1875 SystemSubtype::InstructionSizeWarning => {
1876 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
1877 }
1878 SystemSubtype::FileAttachmentsMissing => {
1879 reserialize(parse_system::<FileAttachmentsMissingMessage>(self))
1880 }
1881 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
1882 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
1883 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
1884 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
1885 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
1886 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
1887 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
1888 SystemSubtype::Unknown(_) => None,
1889 }
1890 }
1891}
1892
1893fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1894 serde_json::from_value(message.data.clone()).ok()
1895}
1896
1897#[allow(clippy::large_enum_variant)]
1903#[derive(Debug, Clone, Serialize, Deserialize)]
1904pub enum KnownSystemEvent {
1905 Init(InitMessage),
1906 Status(StatusMessage),
1907 CompactBoundary(CompactBoundaryMessage),
1908 ThinkingTokens(ThinkingTokensMessage),
1909 TaskStarted(TaskStartedMessage),
1910 TaskProgress(TaskProgressMessage),
1911 TaskUpdated(TaskUpdatedMessage),
1912 TaskNotification(TaskNotificationMessage),
1913 ApiRetry(ApiRetryMessage),
1914 ControlRequestProgress(ControlRequestProgressMessage),
1915 ModelRefusalFallback(ModelRefusalFallbackMessage),
1916 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1917 LocalCommandOutput(LocalCommandOutputMessage),
1918 HookStarted(HookStartedMessage),
1919 HookProgress(HookProgressMessage),
1920 HookResponse(HookResponseMessage),
1921 PluginInstall(PluginInstallMessage),
1922 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1923 SessionStateChanged(SessionStateChangedMessage),
1924 WorkerShuttingDown(WorkerShuttingDownMessage),
1925 CommandsChanged(CommandsChangedMessage),
1926 Notification(NotificationMessage),
1927 FilesPersisted(FilesPersistedMessage),
1928 MemoryRecall(MemoryRecallMessage),
1929 ElicitationComplete(ElicitationCompleteMessage),
1930 PermissionDenied(PermissionDeniedMessage),
1931 MirrorError(MirrorErrorMessage),
1932 Informational(InformationalMessage),
1933 CodeChangePublished(CodeChangePublishedMessage),
1934 VcsStateChanged(VcsStateChangedMessage),
1935 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1936 CloudSessionDelta(CloudSessionDeltaMessage),
1937 DevIntent(DevIntentMessage),
1938 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1939 TurnPreempted(TurnPreemptedMessage),
1940 PeerMessageHold(PeerMessageHoldMessage),
1941 PerTurnEffortChanged(PerTurnEffortChangedMessage),
1942 SessionTitleChanged(SessionTitleChangedMessage),
1943 InstructionSizeWarning(InstructionSizeWarningMessage),
1944 FileAttachmentsMissing(FileAttachmentsMissingMessage),
1945 UiFocus(UiFocusMessage),
1946 UiInvalidate(UiInvalidateMessage),
1947 UiLog(UiLogMessage),
1948 UiPanes(UiPanesMessage),
1949 UiScroll(UiScrollMessage),
1950 UiStatus(UiStatusMessage),
1951 UiToast(UiToastMessage),
1952}
1953
1954#[derive(Debug, Clone, Serialize, Deserialize)]
1955pub struct ApiRetryMessage {
1956 pub attempt: u64,
1957 pub max_retries: u64,
1958 pub retry_delay_ms: u64,
1959 pub error_status: Option<u16>,
1960 pub error: String,
1961 #[serde(default, skip_serializing_if = "Option::is_none")]
1966 pub no_response: Option<ApiRetryNoResponse>,
1967 #[serde(default, skip_serializing_if = "Option::is_none")]
1968 pub uuid: Option<String>,
1969 #[serde(default, skip_serializing_if = "Option::is_none")]
1970 pub session_id: Option<String>,
1971}
1972
1973#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1976pub struct ApiRetryNoResponse {
1977 pub waited_ms: u64,
1979 pub retry_wait_ms: u64,
1981}
1982
1983#[derive(Debug, Clone, Serialize, Deserialize)]
1984pub struct ControlRequestProgressMessage {
1985 pub request_id: String,
1986 pub status: String,
1987 #[serde(default, skip_serializing_if = "Option::is_none")]
1988 pub attempt: Option<u64>,
1989 #[serde(default, skip_serializing_if = "Option::is_none")]
1990 pub max_retries: Option<u64>,
1991 #[serde(default, skip_serializing_if = "Option::is_none")]
1992 pub retry_delay_ms: Option<u64>,
1993 #[serde(default, skip_serializing_if = "Option::is_none")]
1994 pub error_status: Option<u16>,
1995 #[serde(default, skip_serializing_if = "Option::is_none")]
1996 pub error: Option<String>,
1997 #[serde(default, skip_serializing_if = "Option::is_none")]
1998 pub uuid: Option<String>,
1999 #[serde(default, skip_serializing_if = "Option::is_none")]
2000 pub session_id: Option<String>,
2001}
2002
2003#[derive(Debug, Clone, Serialize, Deserialize)]
2004pub struct ModelRefusalFallbackMessage {
2005 pub trigger: String,
2006 pub direction: String,
2007 #[serde(default, skip_serializing_if = "Option::is_none")]
2013 pub scope: Option<RefusalFallbackScope>,
2014 pub original_model: String,
2015 pub fallback_model: String,
2016 pub request_id: Option<String>,
2017 #[serde(default, skip_serializing_if = "Option::is_none")]
2018 pub api_refusal_category: Option<String>,
2019 #[serde(default, skip_serializing_if = "Option::is_none")]
2024 pub saw_cyber_refusal: Option<bool>,
2025 #[serde(default, skip_serializing_if = "Option::is_none")]
2026 pub api_refusal_explanation: Option<String>,
2027 #[serde(default, skip_serializing_if = "Option::is_none")]
2028 pub retracted_message_uuids: Option<Vec<String>>,
2029 #[serde(default, skip_serializing_if = "Option::is_none")]
2030 pub refused_user_message_uuid: Option<String>,
2031 pub content: Value,
2032 #[serde(default, skip_serializing_if = "Option::is_none")]
2033 pub uuid: Option<String>,
2034 #[serde(default, skip_serializing_if = "Option::is_none")]
2035 pub session_id: Option<String>,
2036}
2037
2038#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2042pub enum RefusalFallbackScope {
2043 Session,
2045 Local,
2048 Unknown(String),
2050}
2051
2052impl RefusalFallbackScope {
2053 pub fn as_str(&self) -> &str {
2054 match self {
2055 Self::Session => "session",
2056 Self::Local => "local",
2057 Self::Unknown(s) => s.as_str(),
2058 }
2059 }
2060}
2061
2062impl fmt::Display for RefusalFallbackScope {
2063 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2064 f.write_str(self.as_str())
2065 }
2066}
2067
2068impl From<&str> for RefusalFallbackScope {
2069 fn from(s: &str) -> Self {
2070 match s {
2071 "session" => Self::Session,
2072 "local" => Self::Local,
2073 other => Self::Unknown(other.to_string()),
2074 }
2075 }
2076}
2077
2078impl Serialize for RefusalFallbackScope {
2079 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2080 serializer.serialize_str(self.as_str())
2081 }
2082}
2083
2084impl<'de> Deserialize<'de> for RefusalFallbackScope {
2085 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2086 let s = String::deserialize(deserializer)?;
2087 Ok(Self::from(s.as_str()))
2088 }
2089}
2090
2091#[derive(Debug, Clone, Serialize, Deserialize)]
2092pub struct ModelRefusalNoFallbackMessage {
2093 pub original_model: String,
2094 pub request_id: Option<String>,
2095 #[serde(default, skip_serializing_if = "Option::is_none")]
2096 pub api_refusal_category: Option<String>,
2097 #[serde(default, skip_serializing_if = "Option::is_none")]
2098 pub api_refusal_explanation: Option<String>,
2099 pub content: Value,
2100 #[serde(default, skip_serializing_if = "Option::is_none")]
2101 pub uuid: Option<String>,
2102 #[serde(default, skip_serializing_if = "Option::is_none")]
2103 pub session_id: Option<String>,
2104}
2105
2106#[derive(Debug, Clone, Serialize, Deserialize)]
2107pub struct LocalCommandOutputMessage {
2108 pub content: String,
2109 #[serde(default, skip_serializing_if = "Option::is_none")]
2110 pub uuid: Option<String>,
2111 #[serde(default, skip_serializing_if = "Option::is_none")]
2112 pub session_id: Option<String>,
2113}
2114
2115#[derive(Debug, Clone, Serialize, Deserialize)]
2116pub struct HookStartedMessage {
2117 pub hook_id: String,
2118 pub hook_name: String,
2119 pub hook_event: String,
2120 #[serde(default, skip_serializing_if = "Option::is_none")]
2121 pub uuid: Option<String>,
2122 #[serde(default, skip_serializing_if = "Option::is_none")]
2123 pub session_id: Option<String>,
2124}
2125
2126#[derive(Debug, Clone, Serialize, Deserialize)]
2127pub struct HookProgressMessage {
2128 pub hook_id: String,
2129 pub hook_name: String,
2130 pub hook_event: String,
2131 #[serde(default, skip_serializing_if = "Option::is_none")]
2132 pub stdout: Option<String>,
2133 #[serde(default, skip_serializing_if = "Option::is_none")]
2134 pub stderr: Option<String>,
2135 #[serde(default, skip_serializing_if = "Option::is_none")]
2136 pub output: Option<String>,
2137 #[serde(default, skip_serializing_if = "Option::is_none")]
2138 pub uuid: Option<String>,
2139 #[serde(default, skip_serializing_if = "Option::is_none")]
2140 pub session_id: Option<String>,
2141}
2142
2143#[derive(Debug, Clone, Serialize, Deserialize)]
2144pub struct HookResponseMessage {
2145 pub hook_id: String,
2146 pub hook_name: String,
2147 pub hook_event: String,
2148 #[serde(default, skip_serializing_if = "Option::is_none")]
2149 pub stdout: Option<String>,
2150 #[serde(default, skip_serializing_if = "Option::is_none")]
2151 pub stderr: Option<String>,
2152 #[serde(default, skip_serializing_if = "Option::is_none")]
2153 pub output: Option<String>,
2154 #[serde(default, skip_serializing_if = "Option::is_none")]
2155 pub exit_code: Option<i32>,
2156 pub outcome: String,
2157 #[serde(default, skip_serializing_if = "Option::is_none")]
2158 pub uuid: Option<String>,
2159 #[serde(default, skip_serializing_if = "Option::is_none")]
2160 pub session_id: Option<String>,
2161}
2162
2163#[derive(Debug, Clone, Serialize, Deserialize)]
2164pub struct PluginInstallMessage {
2165 pub status: String,
2166 #[serde(default, skip_serializing_if = "Option::is_none")]
2167 pub name: Option<String>,
2168 #[serde(default, skip_serializing_if = "Option::is_none")]
2169 pub error: Option<String>,
2170 #[serde(default, skip_serializing_if = "Option::is_none")]
2171 pub uuid: Option<String>,
2172 #[serde(default, skip_serializing_if = "Option::is_none")]
2173 pub session_id: Option<String>,
2174}
2175
2176#[derive(Debug, Clone, Serialize, Deserialize)]
2177pub struct BackgroundTasksChangedMessage {
2178 pub tasks: Vec<BackgroundTaskInfo>,
2179 #[serde(default, skip_serializing_if = "Option::is_none")]
2180 pub uuid: Option<String>,
2181 #[serde(default, skip_serializing_if = "Option::is_none")]
2182 pub session_id: Option<String>,
2183}
2184
2185#[derive(Debug, Clone, Serialize, Deserialize)]
2186pub struct BackgroundTaskInfo {
2187 pub task_id: String,
2188 pub task_type: String,
2189 pub description: String,
2190 #[serde(default, skip_serializing_if = "Option::is_none")]
2193 pub ambient: Option<bool>,
2194}
2195
2196#[derive(Debug, Clone, Serialize, Deserialize)]
2197pub struct SessionStateChangedMessage {
2198 pub state: String,
2199 #[serde(default, skip_serializing_if = "Option::is_none")]
2200 pub uuid: Option<String>,
2201 #[serde(default, skip_serializing_if = "Option::is_none")]
2202 pub session_id: Option<String>,
2203}
2204
2205#[derive(Debug, Clone, Serialize, Deserialize)]
2206pub struct WorkerShuttingDownMessage {
2207 pub reason: String,
2208 #[serde(default, skip_serializing_if = "Option::is_none")]
2209 pub uuid: Option<String>,
2210 #[serde(default, skip_serializing_if = "Option::is_none")]
2211 pub session_id: Option<String>,
2212}
2213
2214#[derive(Debug, Clone, Serialize, Deserialize)]
2215pub struct CommandsChangedMessage {
2216 pub commands: Vec<CommandInfo>,
2217 #[serde(default, skip_serializing_if = "Option::is_none")]
2218 pub uuid: Option<String>,
2219 #[serde(default, skip_serializing_if = "Option::is_none")]
2220 pub session_id: Option<String>,
2221}
2222
2223#[derive(Debug, Clone, Serialize, Deserialize)]
2224pub struct CommandInfo {
2225 pub name: String,
2226 pub description: String,
2227 #[serde(rename = "argumentHint")]
2228 pub argument_hint: String,
2229 #[serde(default, skip_serializing_if = "Option::is_none")]
2230 pub aliases: Option<Vec<String>>,
2231 #[serde(default, skip_serializing_if = "Option::is_none")]
2237 pub builtin: Option<bool>,
2238}
2239
2240#[derive(Debug, Clone, Serialize, Deserialize)]
2241pub struct NotificationMessage {
2242 pub key: String,
2243 pub text: String,
2244 pub priority: String,
2245 #[serde(default, skip_serializing_if = "Option::is_none")]
2246 pub color: Option<String>,
2247 #[serde(default, skip_serializing_if = "Option::is_none")]
2248 pub timeout_ms: Option<u64>,
2249 #[serde(default, skip_serializing_if = "Option::is_none")]
2250 pub uuid: Option<String>,
2251 #[serde(default, skip_serializing_if = "Option::is_none")]
2252 pub session_id: Option<String>,
2253}
2254
2255#[derive(Debug, Clone, Serialize, Deserialize)]
2256pub struct FilesPersistedMessage {
2257 pub files: Vec<PersistedFile>,
2258 pub failed: Vec<FailedPersistedFile>,
2259 pub processed_at: String,
2260 #[serde(default, skip_serializing_if = "Option::is_none")]
2261 pub uuid: Option<String>,
2262 #[serde(default, skip_serializing_if = "Option::is_none")]
2263 pub session_id: Option<String>,
2264}
2265
2266#[derive(Debug, Clone, Serialize, Deserialize)]
2267pub struct PersistedFile {
2268 pub filename: String,
2269 pub file_id: String,
2270}
2271
2272#[derive(Debug, Clone, Serialize, Deserialize)]
2273pub struct FailedPersistedFile {
2274 pub filename: String,
2275 pub error: String,
2276}
2277
2278#[derive(Debug, Clone, Serialize, Deserialize)]
2279pub struct MemoryRecallMessage {
2280 pub mode: String,
2281 pub memories: Vec<MemoryRecallItem>,
2282 #[serde(default, skip_serializing_if = "Option::is_none")]
2283 pub uuid: Option<String>,
2284 #[serde(default, skip_serializing_if = "Option::is_none")]
2285 pub session_id: Option<String>,
2286}
2287
2288#[derive(Debug, Clone, Serialize, Deserialize)]
2289pub struct MemoryRecallItem {
2290 pub path: String,
2291 pub scope: String,
2292 #[serde(default, skip_serializing_if = "Option::is_none")]
2293 pub content: Option<String>,
2294}
2295
2296#[derive(Debug, Clone, Serialize, Deserialize)]
2297pub struct ElicitationCompleteMessage {
2298 pub mcp_server_name: String,
2299 pub elicitation_id: String,
2300 #[serde(default, skip_serializing_if = "Option::is_none")]
2301 pub uuid: Option<String>,
2302 #[serde(default, skip_serializing_if = "Option::is_none")]
2303 pub session_id: Option<String>,
2304}
2305
2306#[derive(Debug, Clone, Serialize, Deserialize)]
2307pub struct PermissionDeniedMessage {
2308 pub tool_name: String,
2309 pub tool_use_id: String,
2310 #[serde(default, skip_serializing_if = "Option::is_none")]
2311 pub agent_id: Option<String>,
2312 #[serde(default, skip_serializing_if = "Option::is_none")]
2313 pub decision_reason_type: Option<String>,
2314 #[serde(default, skip_serializing_if = "Option::is_none")]
2318 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2319 #[serde(default, skip_serializing_if = "Option::is_none")]
2320 pub decision_reason: Option<String>,
2321 pub message: String,
2322 #[serde(default, skip_serializing_if = "Option::is_none")]
2323 pub uuid: Option<String>,
2324 #[serde(default, skip_serializing_if = "Option::is_none")]
2325 pub session_id: Option<String>,
2326}
2327
2328#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2331pub enum PermissionDeniedReasonCode {
2332 ClassifierTranscriptTooLong,
2334 OutsideReadsBlocked,
2336 MemoryPaused,
2338 Unknown(String),
2340}
2341
2342impl PermissionDeniedReasonCode {
2343 pub fn as_str(&self) -> &str {
2344 match self {
2345 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2346 Self::OutsideReadsBlocked => "outside_reads_blocked",
2347 Self::MemoryPaused => "memory_paused",
2348 Self::Unknown(s) => s.as_str(),
2349 }
2350 }
2351}
2352
2353impl fmt::Display for PermissionDeniedReasonCode {
2354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2355 f.write_str(self.as_str())
2356 }
2357}
2358
2359impl From<&str> for PermissionDeniedReasonCode {
2360 fn from(s: &str) -> Self {
2361 match s {
2362 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2363 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2364 "memory_paused" => Self::MemoryPaused,
2365 other => Self::Unknown(other.to_string()),
2366 }
2367 }
2368}
2369
2370impl Serialize for PermissionDeniedReasonCode {
2371 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2372 serializer.serialize_str(self.as_str())
2373 }
2374}
2375
2376impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2377 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2378 let s = String::deserialize(deserializer)?;
2379 Ok(Self::from(s.as_str()))
2380 }
2381}
2382
2383#[derive(Debug, Clone, Serialize, Deserialize)]
2384pub struct MirrorErrorMessage {
2385 pub error: String,
2386 pub key: MirrorErrorKey,
2387 #[serde(default, skip_serializing_if = "Option::is_none")]
2388 pub uuid: Option<String>,
2389 #[serde(default, skip_serializing_if = "Option::is_none")]
2390 pub session_id: Option<String>,
2391}
2392
2393#[derive(Debug, Clone, Serialize, Deserialize)]
2394pub struct MirrorErrorKey {
2395 #[serde(rename = "projectKey")]
2396 pub project_key: String,
2397 #[serde(rename = "sessionId")]
2398 pub session_id: String,
2399 #[serde(default, skip_serializing_if = "Option::is_none")]
2400 pub subpath: Option<String>,
2401}
2402
2403#[derive(Debug, Clone, Serialize, Deserialize)]
2404pub struct InformationalMessage {
2405 pub content: String,
2406 pub level: String,
2407 #[serde(default, skip_serializing_if = "Option::is_none")]
2408 pub tool_use_id: Option<String>,
2409 #[serde(default, skip_serializing_if = "Option::is_none")]
2410 pub prevent_continuation: Option<bool>,
2411 #[serde(default, skip_serializing_if = "Option::is_none")]
2414 pub tag: Option<String>,
2415 #[serde(default, skip_serializing_if = "Option::is_none")]
2416 pub uuid: Option<String>,
2417 #[serde(default, skip_serializing_if = "Option::is_none")]
2418 pub session_id: Option<String>,
2419}
2420
2421#[derive(Debug, Clone, Serialize, Deserialize)]
2423pub struct PluginInfo {
2424 pub name: String,
2426 pub path: String,
2428 #[serde(skip_serializing_if = "Option::is_none")]
2430 pub source: Option<String>,
2431 #[serde(default, skip_serializing_if = "Option::is_none")]
2433 pub version: Option<String>,
2434}
2435
2436#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2438pub struct PluginDiagnostic {
2439 pub plugin: String,
2440 #[serde(rename = "type")]
2441 pub diagnostic_type: String,
2442 pub message: String,
2443}
2444
2445#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2447pub struct MemoryPaths {
2448 #[serde(default, skip_serializing_if = "Option::is_none")]
2449 pub auto: Option<String>,
2450 #[serde(default, skip_serializing_if = "Option::is_none")]
2451 pub team: Option<String>,
2452 #[serde(flatten)]
2453 pub extra: serde_json::Map<String, Value>,
2454}
2455
2456#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2460pub struct McpServerError {
2461 pub name: String,
2462 #[serde(rename = "type")]
2464 pub error_type: String,
2465 pub message: String,
2466}
2467
2468#[derive(Debug, Clone, Serialize, Deserialize)]
2470pub struct InitMessage {
2471 pub session_id: String,
2473 #[serde(skip_serializing_if = "Option::is_none")]
2475 pub cwd: Option<String>,
2476 #[serde(skip_serializing_if = "Option::is_none")]
2478 pub model: Option<String>,
2479 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2481 pub tools: Vec<String>,
2482 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2484 pub mcp_servers: Vec<Value>,
2485 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2487 pub slash_commands: Vec<String>,
2488 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2491 pub terminal_slash_commands: Vec<String>,
2492 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2494 pub agents: Vec<String>,
2495 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2497 pub plugins: Vec<PluginInfo>,
2498 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2500 pub skills: Vec<Value>,
2501 #[serde(skip_serializing_if = "Option::is_none")]
2503 pub claude_code_version: Option<String>,
2504 #[serde(skip_serializing_if = "Option::is_none")]
2507 pub messaging_socket_path: Option<String>,
2508 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2510 pub api_key_source: Option<ApiKeySource>,
2511 #[serde(skip_serializing_if = "Option::is_none")]
2513 pub output_style: Option<OutputStyle>,
2514 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2516 pub permission_mode: Option<InitPermissionMode>,
2517
2518 #[serde(skip_serializing_if = "Option::is_none")]
2520 pub uuid: Option<String>,
2521
2522 #[serde(skip_serializing_if = "Option::is_none")]
2524 pub memory_paths: Option<MemoryPaths>,
2525
2526 #[serde(default, skip_serializing_if = "Option::is_none")]
2531 pub scratchpad_path: Option<String>,
2532
2533 #[serde(skip_serializing_if = "Option::is_none")]
2535 pub fast_mode_state: Option<String>,
2536
2537 #[serde(default, skip_serializing_if = "Option::is_none")]
2539 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2540
2541 #[serde(default, skip_serializing_if = "Option::is_none")]
2544 pub mcp_server_errors: Option<Vec<McpServerError>>,
2545
2546 #[serde(default, skip_serializing_if = "Option::is_none")]
2548 pub analytics_disabled: Option<bool>,
2549
2550 #[serde(default, skip_serializing_if = "Option::is_none")]
2552 pub product_feedback_disabled: Option<bool>,
2553
2554 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2556 pub betas: Vec<String>,
2557
2558 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2560 pub capabilities: Vec<String>,
2561
2562 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2564 pub plugin_errors: Vec<PluginDiagnostic>,
2565
2566 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2568 pub plugin_warnings: Vec<PluginDiagnostic>,
2569
2570 #[serde(default, skip_serializing_if = "Option::is_none")]
2575 pub effort: Option<String>,
2576
2577 #[serde(default, skip_serializing_if = "Option::is_none")]
2582 pub cloud_session: Option<Value>,
2583
2584 #[serde(default, skip_serializing_if = "Option::is_none")]
2588 pub footer_indicator: Option<FooterIndicator>,
2589
2590 #[serde(default, skip_serializing_if = "Option::is_none")]
2594 pub worker_epoch: Option<u64>,
2595
2596 #[serde(default, skip_serializing_if = "Option::is_none")]
2600 pub powershell_path: Option<Option<String>>,
2601
2602 #[serde(default, skip_serializing_if = "Option::is_none")]
2606 pub startup_timing: Option<serde_json::Map<String, Value>>,
2607
2608 #[serde(default, skip_serializing_if = "Option::is_none")]
2618 pub per_turn_effort_active: Option<bool>,
2619
2620 #[serde(default, skip_serializing_if = "Option::is_none")]
2627 pub view_mode: Option<ViewMode>,
2628}
2629
2630#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2634pub struct FooterIndicator {
2635 pub text: String,
2638}
2639
2640#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2643pub enum ViewMode {
2644 Focus,
2647 Default,
2649 Unknown(String),
2651}
2652
2653impl ViewMode {
2654 pub fn as_str(&self) -> &str {
2655 match self {
2656 Self::Focus => "focus",
2657 Self::Default => "default",
2658 Self::Unknown(s) => s.as_str(),
2659 }
2660 }
2661}
2662
2663impl fmt::Display for ViewMode {
2664 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2665 f.write_str(self.as_str())
2666 }
2667}
2668
2669impl From<&str> for ViewMode {
2670 fn from(s: &str) -> Self {
2671 match s {
2672 "focus" => Self::Focus,
2673 "default" => Self::Default,
2674 other => Self::Unknown(other.to_string()),
2675 }
2676 }
2677}
2678
2679impl Serialize for ViewMode {
2680 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2681 serializer.serialize_str(self.as_str())
2682 }
2683}
2684
2685impl<'de> Deserialize<'de> for ViewMode {
2686 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2687 let s = String::deserialize(deserializer)?;
2688 Ok(Self::from(s.as_str()))
2689 }
2690}
2691
2692#[derive(Debug, Clone, Serialize, Deserialize)]
2694pub struct StatusMessage {
2695 pub session_id: String,
2697 pub status: Option<StatusMessageStatus>,
2699 #[serde(skip_serializing_if = "Option::is_none")]
2701 pub uuid: Option<String>,
2702 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2704 pub permission_mode: Option<InitPermissionMode>,
2705 #[serde(skip_serializing_if = "Option::is_none")]
2706 pub compact_result: Option<String>,
2707 #[serde(skip_serializing_if = "Option::is_none")]
2708 pub compact_error: Option<String>,
2709}
2710
2711#[derive(Debug, Clone, Serialize, Deserialize)]
2713pub struct CompactBoundaryMessage {
2714 pub session_id: String,
2716 pub compact_metadata: CompactMetadata,
2718 #[serde(
2722 default,
2723 skip_serializing_if = "Option::is_none",
2724 alias = "content",
2725 alias = "text"
2726 )]
2727 pub summary: Option<String>,
2728 #[serde(
2732 default,
2733 skip_serializing_if = "Option::is_none",
2734 alias = "message_count"
2735 )]
2736 pub leaf_message_count: Option<u32>,
2737 #[serde(default, skip_serializing_if = "Option::is_none")]
2739 pub duration_ms: Option<u64>,
2740 #[serde(skip_serializing_if = "Option::is_none")]
2742 pub uuid: Option<String>,
2743 #[serde(skip_serializing_if = "Option::is_none")]
2745 pub logical_parent_uuid: Option<Option<String>>,
2746 #[serde(default, skip_serializing_if = "Option::is_none")]
2750 pub historical: Option<bool>,
2751}
2752
2753#[derive(Debug, Clone, Serialize, Deserialize)]
2755pub struct CompactMetadata {
2756 pub pre_tokens: u64,
2758 pub trigger: CompactionTrigger,
2760 #[serde(default, skip_serializing_if = "Option::is_none")]
2761 pub post_tokens: Option<u64>,
2762 #[serde(default, skip_serializing_if = "Option::is_none")]
2763 pub cumulative_dropped_tokens: Option<u64>,
2764 #[serde(default, skip_serializing_if = "Option::is_none")]
2765 pub duration_ms: Option<u64>,
2766 #[serde(default, skip_serializing_if = "Option::is_none")]
2767 pub user_context: Option<String>,
2768 #[serde(default, skip_serializing_if = "Option::is_none")]
2769 pub messages_summarized: Option<u64>,
2770 #[serde(default, skip_serializing_if = "Option::is_none")]
2771 pub precomputed: Option<bool>,
2772 #[serde(default, skip_serializing_if = "Option::is_none")]
2773 pub pre_compact_discovered_tools: Option<Vec<String>>,
2774 #[serde(default, skip_serializing_if = "Option::is_none")]
2779 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
2780 #[serde(default, skip_serializing_if = "Option::is_none")]
2781 pub preserved_segment: Option<PreservedSegment>,
2782 #[serde(default, skip_serializing_if = "Option::is_none")]
2783 pub preserved_messages: Option<PreservedMessages>,
2784}
2785
2786#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2789pub struct ArtifactReadVersion {
2790 pub slug: String,
2792 pub ver: String,
2794}
2795
2796#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2797pub struct PreservedSegment {
2798 pub head_uuid: String,
2799 pub anchor_uuid: String,
2800 pub tail_uuid: String,
2801}
2802
2803#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2804pub struct PreservedMessages {
2805 pub anchor_uuid: String,
2806 pub uuids: Vec<String>,
2807 #[serde(default, skip_serializing_if = "Option::is_none")]
2808 pub all_uuids: Option<Vec<String>>,
2809}
2810
2811#[derive(Debug, Clone, Serialize, Deserialize)]
2817pub struct TaskUsage {
2818 pub duration_ms: u64,
2820 pub tool_uses: u64,
2822 pub total_tokens: u64,
2824}
2825
2826#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2828pub enum TaskType {
2829 LocalAgent,
2831 LocalBash,
2833 LocalWorkflow,
2835 Unknown(String),
2837}
2838
2839impl TaskType {
2840 pub fn as_str(&self) -> &str {
2841 match self {
2842 Self::LocalAgent => "local_agent",
2843 Self::LocalBash => "local_bash",
2844 Self::LocalWorkflow => "local_workflow",
2845 Self::Unknown(s) => s.as_str(),
2846 }
2847 }
2848}
2849
2850impl fmt::Display for TaskType {
2851 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2852 f.write_str(self.as_str())
2853 }
2854}
2855
2856impl From<&str> for TaskType {
2857 fn from(s: &str) -> Self {
2858 match s {
2859 "local_agent" => Self::LocalAgent,
2860 "local_bash" => Self::LocalBash,
2861 "local_workflow" => Self::LocalWorkflow,
2862 other => Self::Unknown(other.to_string()),
2863 }
2864 }
2865}
2866
2867impl Serialize for TaskType {
2868 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2869 serializer.serialize_str(self.as_str())
2870 }
2871}
2872
2873impl<'de> Deserialize<'de> for TaskType {
2874 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2875 let s = String::deserialize(deserializer)?;
2876 Ok(Self::from(s.as_str()))
2877 }
2878}
2879
2880#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2882pub enum TaskStatus {
2883 Pending,
2884 Running,
2885 Completed,
2886 Failed,
2887 Killed,
2888 Paused,
2889 Stopped,
2890 Unknown(String),
2891}
2892
2893impl TaskStatus {
2894 pub fn as_str(&self) -> &str {
2895 match self {
2896 Self::Pending => "pending",
2897 Self::Running => "running",
2898 Self::Completed => "completed",
2899 Self::Failed => "failed",
2900 Self::Killed => "killed",
2901 Self::Paused => "paused",
2902 Self::Stopped => "stopped",
2903 Self::Unknown(s) => s.as_str(),
2904 }
2905 }
2906}
2907
2908impl fmt::Display for TaskStatus {
2909 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2910 f.write_str(self.as_str())
2911 }
2912}
2913
2914impl From<&str> for TaskStatus {
2915 fn from(s: &str) -> Self {
2916 match s {
2917 "pending" => Self::Pending,
2918 "running" => Self::Running,
2919 "completed" => Self::Completed,
2920 "failed" => Self::Failed,
2921 "killed" => Self::Killed,
2922 "paused" => Self::Paused,
2923 "stopped" => Self::Stopped,
2924 other => Self::Unknown(other.to_string()),
2925 }
2926 }
2927}
2928
2929impl Serialize for TaskStatus {
2930 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2931 serializer.serialize_str(self.as_str())
2932 }
2933}
2934
2935impl<'de> Deserialize<'de> for TaskStatus {
2936 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2937 let s = String::deserialize(deserializer)?;
2938 Ok(Self::from(s.as_str()))
2939 }
2940}
2941
2942#[derive(Debug, Clone, Serialize, Deserialize)]
2944pub struct TaskStartedMessage {
2945 pub session_id: String,
2946 pub task_id: String,
2947 #[serde(default, skip_serializing_if = "Option::is_none")]
2948 pub task_type: Option<TaskType>,
2949 #[serde(default, skip_serializing_if = "Option::is_none")]
2950 pub tool_use_id: Option<String>,
2951 pub description: String,
2952 #[serde(default, skip_serializing_if = "Option::is_none")]
2955 pub subagent_type: Option<String>,
2956 #[serde(default, skip_serializing_if = "Option::is_none")]
2962 pub is_backgrounded: Option<bool>,
2963 #[serde(default, skip_serializing_if = "Option::is_none")]
2967 pub spawn_depth: Option<u32>,
2968 #[serde(default, skip_serializing_if = "Option::is_none")]
2970 pub prompt: Option<String>,
2971 #[serde(default, skip_serializing_if = "Option::is_none")]
2972 pub workflow_name: Option<String>,
2973 #[serde(default, skip_serializing_if = "Option::is_none")]
2974 pub skip_transcript: Option<bool>,
2975 #[serde(default, skip_serializing_if = "Option::is_none")]
2980 pub ambient: Option<bool>,
2981 pub uuid: String,
2982}
2983
2984#[derive(Debug, Clone, Serialize, Deserialize)]
2988pub struct TaskUpdatedMessage {
2989 pub session_id: String,
2990 pub task_id: String,
2991 pub patch: TaskPatch,
2992 pub uuid: String,
2993}
2994
2995#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2998pub struct TaskPatch {
2999 #[serde(default, skip_serializing_if = "Option::is_none")]
3000 pub status: Option<TaskStatus>,
3001 #[serde(default, skip_serializing_if = "Option::is_none")]
3004 pub end_time: Option<u64>,
3005 #[serde(default, skip_serializing_if = "Option::is_none")]
3006 pub description: Option<String>,
3007 #[serde(default, skip_serializing_if = "Option::is_none")]
3008 pub total_paused_ms: Option<u64>,
3009 #[serde(default, skip_serializing_if = "Option::is_none")]
3010 pub error: Option<String>,
3011 #[serde(default, skip_serializing_if = "Option::is_none")]
3012 pub is_backgrounded: Option<bool>,
3013}
3014
3015#[derive(Debug, Clone, Serialize, Deserialize)]
3018pub struct ThinkingTokensMessage {
3019 pub session_id: String,
3020 pub estimated_tokens: u64,
3022 pub estimated_tokens_delta: u64,
3024 #[serde(default, skip_serializing_if = "Option::is_none")]
3031 pub user_message_uuid: Option<String>,
3032 pub uuid: String,
3033}
3034
3035#[derive(Debug, Clone, Serialize, Deserialize)]
3038pub struct TaskProgressMessage {
3039 pub session_id: String,
3040 pub task_id: String,
3041 #[serde(default, skip_serializing_if = "Option::is_none")]
3042 pub tool_use_id: Option<String>,
3043 pub description: String,
3044 #[serde(default, skip_serializing_if = "Option::is_none")]
3045 pub last_tool_name: Option<String>,
3046 pub usage: TaskUsage,
3047 #[serde(default, skip_serializing_if = "Option::is_none")]
3049 pub subagent_type: Option<String>,
3050 #[serde(default, skip_serializing_if = "Option::is_none")]
3051 pub summary: Option<String>,
3052 pub uuid: String,
3053}
3054
3055#[derive(Debug, Clone, Serialize, Deserialize)]
3058pub struct TaskNotificationMessage {
3059 pub session_id: String,
3060 pub task_id: String,
3061 pub status: TaskStatus,
3062 #[serde(default, skip_serializing_if = "Option::is_none")]
3065 pub reason: Option<TaskEndReason>,
3066 pub summary: String,
3067 pub output_file: Option<String>,
3068 #[serde(skip_serializing_if = "Option::is_none")]
3069 pub tool_use_id: Option<String>,
3070 #[serde(skip_serializing_if = "Option::is_none")]
3071 pub usage: Option<TaskUsage>,
3072 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3078 pub resource_links: Vec<ResourceLink>,
3079 #[serde(default, skip_serializing_if = "Option::is_none")]
3080 pub skip_transcript: Option<bool>,
3081 #[serde(default, skip_serializing_if = "Option::is_none")]
3084 pub ambient: Option<bool>,
3085 #[serde(skip_serializing_if = "Option::is_none")]
3086 pub uuid: Option<String>,
3087}
3088
3089#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3091pub enum AssistantErrorKind {
3092 AuthenticationFailed,
3093 OauthOrgNotAllowed,
3094 AccountOnHold,
3095 BillingError,
3096 RateLimit,
3097 Overloaded,
3098 InvalidRequest,
3099 ModelNotFound,
3100 ServerError,
3101 UnknownError,
3102 MaxOutputTokens,
3103 Unknown(String),
3104}
3105
3106impl AssistantErrorKind {
3107 pub fn as_str(&self) -> &str {
3108 match self {
3109 Self::AuthenticationFailed => "authentication_failed",
3110 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3111 Self::AccountOnHold => "account_on_hold",
3112 Self::BillingError => "billing_error",
3113 Self::RateLimit => "rate_limit",
3114 Self::Overloaded => "overloaded",
3115 Self::InvalidRequest => "invalid_request",
3116 Self::ModelNotFound => "model_not_found",
3117 Self::ServerError => "server_error",
3118 Self::UnknownError => "unknown",
3119 Self::MaxOutputTokens => "max_output_tokens",
3120 Self::Unknown(s) => s.as_str(),
3121 }
3122 }
3123}
3124
3125impl fmt::Display for AssistantErrorKind {
3126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3127 f.write_str(self.as_str())
3128 }
3129}
3130
3131impl From<&str> for AssistantErrorKind {
3132 fn from(s: &str) -> Self {
3133 match s {
3134 "authentication_failed" => Self::AuthenticationFailed,
3135 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3136 "account_on_hold" => Self::AccountOnHold,
3137 "billing_error" => Self::BillingError,
3138 "rate_limit" => Self::RateLimit,
3139 "overloaded" => Self::Overloaded,
3140 "invalid_request" => Self::InvalidRequest,
3141 "model_not_found" => Self::ModelNotFound,
3142 "server_error" => Self::ServerError,
3143 "unknown" => Self::UnknownError,
3144 "max_output_tokens" => Self::MaxOutputTokens,
3145 other => Self::Unknown(other.to_string()),
3146 }
3147 }
3148}
3149
3150impl Serialize for AssistantErrorKind {
3151 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3152 serializer.serialize_str(self.as_str())
3153 }
3154}
3155
3156impl<'de> Deserialize<'de> for AssistantErrorKind {
3157 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3158 let s = String::deserialize(deserializer)?;
3159 Ok(Self::from(s.as_str()))
3160 }
3161}
3162
3163#[derive(Debug, Clone, Serialize, Deserialize)]
3170pub struct CodeChangePublishedMessage {
3171 pub provider: String,
3175 pub url: String,
3177 pub repo: String,
3180 pub identifier: String,
3182 #[serde(default, skip_serializing_if = "Option::is_none")]
3191 pub action: Option<String>,
3192 #[serde(default, skip_serializing_if = "Option::is_none")]
3200 pub branch: Option<String>,
3201 pub uuid: String,
3202 pub session_id: String,
3203}
3204
3205#[derive(Debug, Clone, Serialize, Deserialize)]
3210pub struct VcsStateChangedMessage {
3211 pub kind: VcsMutationKind,
3214 pub cwd: String,
3217 #[serde(default, skip_serializing_if = "Option::is_none")]
3222 pub branch: Option<String>,
3223 pub uuid: String,
3224 pub session_id: String,
3225}
3226
3227#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3229pub enum VcsMutationKind {
3230 Commit,
3231 Push,
3232 Merge,
3233 Rebase,
3234 Unknown(String),
3236}
3237
3238impl VcsMutationKind {
3239 pub fn as_str(&self) -> &str {
3240 match self {
3241 Self::Commit => "commit",
3242 Self::Push => "push",
3243 Self::Merge => "merge",
3244 Self::Rebase => "rebase",
3245 Self::Unknown(s) => s.as_str(),
3246 }
3247 }
3248}
3249
3250impl fmt::Display for VcsMutationKind {
3251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3252 f.write_str(self.as_str())
3253 }
3254}
3255
3256impl From<&str> for VcsMutationKind {
3257 fn from(s: &str) -> Self {
3258 match s {
3259 "commit" => Self::Commit,
3260 "push" => Self::Push,
3261 "merge" => Self::Merge,
3262 "rebase" => Self::Rebase,
3263 other => Self::Unknown(other.to_string()),
3264 }
3265 }
3266}
3267
3268impl Serialize for VcsMutationKind {
3269 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3270 serializer.serialize_str(self.as_str())
3271 }
3272}
3273
3274impl<'de> Deserialize<'de> for VcsMutationKind {
3275 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3276 let s = String::deserialize(deserializer)?;
3277 Ok(Self::from(s.as_str()))
3278 }
3279}
3280
3281#[derive(Debug, Clone, Serialize, Deserialize)]
3283pub struct FeedbackDraftQueuedMessage {
3284 pub draft_id: String,
3285 pub draft_type: String,
3286 pub title: String,
3287 pub details_preview: String,
3288 #[serde(default, skip_serializing_if = "Option::is_none")]
3289 pub uuid: Option<String>,
3290 #[serde(default, skip_serializing_if = "Option::is_none")]
3291 pub session_id: Option<String>,
3292 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3293 pub extra: serde_json::Map<String, Value>,
3294}
3295
3296#[derive(Debug, Clone, Serialize, Deserialize)]
3304pub struct CloudSessionDeltaMessage {
3305 pub seq: u64,
3309 pub changed: Vec<String>,
3313 pub cloud_session: Value,
3317 pub uuid: String,
3318 pub session_id: String,
3319 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3320 pub extra: serde_json::Map<String, Value>,
3321}
3322
3323#[derive(Debug, Clone, Serialize, Deserialize)]
3339pub struct DevIntentMessage {
3340 pub kind: DevIntentKind,
3342 #[serde(default, skip_serializing_if = "Option::is_none")]
3348 pub trigger: Option<DevIntentTrigger>,
3349 pub uuid: String,
3350 pub session_id: String,
3351 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3352 pub extra: serde_json::Map<String, Value>,
3353}
3354
3355#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3359pub enum DevIntentKind {
3360 IosApp,
3366 AndroidApp,
3373 Unknown(String),
3375}
3376
3377impl DevIntentKind {
3378 pub fn as_str(&self) -> &str {
3379 match self {
3380 Self::IosApp => "ios_app",
3381 Self::AndroidApp => "android_app",
3382 Self::Unknown(s) => s.as_str(),
3383 }
3384 }
3385}
3386
3387impl fmt::Display for DevIntentKind {
3388 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3389 f.write_str(self.as_str())
3390 }
3391}
3392
3393impl From<&str> for DevIntentKind {
3394 fn from(s: &str) -> Self {
3395 match s {
3396 "ios_app" => Self::IosApp,
3397 "android_app" => Self::AndroidApp,
3398 other => Self::Unknown(other.to_string()),
3399 }
3400 }
3401}
3402
3403impl Serialize for DevIntentKind {
3404 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3405 serializer.serialize_str(self.as_str())
3406 }
3407}
3408
3409impl<'de> Deserialize<'de> for DevIntentKind {
3410 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3411 let s = String::deserialize(deserializer)?;
3412 Ok(Self::from(s.as_str()))
3413 }
3414}
3415
3416#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3420pub enum DevIntentTrigger {
3421 SwiftEdit,
3423 UikitImport,
3425 XcodeProject,
3428 IosCommand,
3431 KotlinEdit,
3433 JavaEdit,
3435 AndroidImport,
3437 AndroidManifest,
3439 GradlePlugin,
3441 AndroidCommand,
3444 ProjectScan,
3447 Unknown(String),
3449}
3450
3451impl DevIntentTrigger {
3452 pub fn as_str(&self) -> &str {
3453 match self {
3454 Self::SwiftEdit => "swift_edit",
3455 Self::UikitImport => "uikit_import",
3456 Self::XcodeProject => "xcode_project",
3457 Self::IosCommand => "ios_command",
3458 Self::KotlinEdit => "kotlin_edit",
3459 Self::JavaEdit => "java_edit",
3460 Self::AndroidImport => "android_import",
3461 Self::AndroidManifest => "android_manifest",
3462 Self::GradlePlugin => "gradle_plugin",
3463 Self::AndroidCommand => "android_command",
3464 Self::ProjectScan => "project_scan",
3465 Self::Unknown(s) => s.as_str(),
3466 }
3467 }
3468}
3469
3470impl fmt::Display for DevIntentTrigger {
3471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3472 f.write_str(self.as_str())
3473 }
3474}
3475
3476impl From<&str> for DevIntentTrigger {
3477 fn from(s: &str) -> Self {
3478 match s {
3479 "swift_edit" => Self::SwiftEdit,
3480 "uikit_import" => Self::UikitImport,
3481 "xcode_project" => Self::XcodeProject,
3482 "ios_command" => Self::IosCommand,
3483 "kotlin_edit" => Self::KotlinEdit,
3484 "java_edit" => Self::JavaEdit,
3485 "android_import" => Self::AndroidImport,
3486 "android_manifest" => Self::AndroidManifest,
3487 "gradle_plugin" => Self::GradlePlugin,
3488 "android_command" => Self::AndroidCommand,
3489 "project_scan" => Self::ProjectScan,
3490 other => Self::Unknown(other.to_string()),
3491 }
3492 }
3493}
3494
3495impl Serialize for DevIntentTrigger {
3496 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3497 serializer.serialize_str(self.as_str())
3498 }
3499}
3500
3501impl<'de> Deserialize<'de> for DevIntentTrigger {
3502 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3503 let s = String::deserialize(deserializer)?;
3504 Ok(Self::from(s.as_str()))
3505 }
3506}
3507
3508#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3512pub enum TaskEndReason {
3513 WorkerRestart,
3516 Unknown(String),
3518}
3519
3520impl TaskEndReason {
3521 pub fn as_str(&self) -> &str {
3522 match self {
3523 Self::WorkerRestart => "worker_restart",
3524 Self::Unknown(s) => s.as_str(),
3525 }
3526 }
3527}
3528
3529impl fmt::Display for TaskEndReason {
3530 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3531 f.write_str(self.as_str())
3532 }
3533}
3534
3535impl From<&str> for TaskEndReason {
3536 fn from(s: &str) -> Self {
3537 match s {
3538 "worker_restart" => Self::WorkerRestart,
3539 other => Self::Unknown(other.to_string()),
3540 }
3541 }
3542}
3543
3544impl Serialize for TaskEndReason {
3545 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3546 serializer.serialize_str(self.as_str())
3547 }
3548}
3549
3550impl<'de> Deserialize<'de> for TaskEndReason {
3551 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3552 let s = String::deserialize(deserializer)?;
3553 Ok(Self::from(s.as_str()))
3554 }
3555}
3556
3557#[derive(Debug, Clone, Serialize, Deserialize)]
3566pub struct TurnHandoffAvailableMessage {
3567 pub v: u64,
3569 pub tools: Vec<String>,
3571 pub worker_epoch: u64,
3573 #[serde(default, skip_serializing_if = "Option::is_none")]
3577 pub relay_marker: Option<bool>,
3578 #[serde(default, skip_serializing_if = "Option::is_none")]
3586 pub staged_files: Option<bool>,
3587 #[serde(default, skip_serializing_if = "Option::is_none")]
3594 pub no_query_first: Option<bool>,
3595 #[serde(default, skip_serializing_if = "Option::is_none")]
3602 pub home_files: Option<bool>,
3603 #[serde(default, skip_serializing_if = "Option::is_none")]
3607 pub file_names: Option<bool>,
3608 #[serde(default, skip_serializing_if = "Option::is_none")]
3618 pub carried_writes: Option<bool>,
3619 pub uuid: String,
3620 pub session_id: String,
3621 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3622 pub extra: serde_json::Map<String, Value>,
3623}
3624
3625#[derive(Debug, Clone, Serialize, Deserialize)]
3637pub struct TurnPreemptedMessage {
3638 pub reason: String,
3642 pub preempted_by_uuid: String,
3646 #[serde(default)]
3649 pub preempted_message_uuids: Vec<String>,
3650 pub uuid: String,
3651 pub session_id: String,
3652 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3653 pub extra: serde_json::Map<String, Value>,
3654}
3655
3656#[derive(Debug, Clone, Serialize, Deserialize)]
3664pub struct PeerMessageHoldMessage {
3665 pub state: PeerMessageHoldState,
3671 #[serde(default, skip_serializing_if = "Option::is_none")]
3678 pub message_uuid: Option<String>,
3679 pub lane: PeerMessageLane,
3681 pub from: String,
3686 #[serde(default, skip_serializing_if = "Option::is_none")]
3689 pub from_name: Option<String>,
3690 #[serde(default, skip_serializing_if = "Option::is_none")]
3692 pub cause: Option<PeerMessageHoldCause>,
3693 #[serde(default, skip_serializing_if = "Option::is_none")]
3695 pub outcome: Option<PeerMessageHoldOutcome>,
3696 pub uuid: String,
3697 pub session_id: String,
3698 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3699 pub extra: serde_json::Map<String, Value>,
3700}
3701
3702#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3704pub enum PeerMessageHoldState {
3705 Held,
3707 Released,
3709 Dropped,
3711 Unknown(String),
3713}
3714
3715impl PeerMessageHoldState {
3716 pub fn as_str(&self) -> &str {
3717 match self {
3718 Self::Held => "held",
3719 Self::Released => "released",
3720 Self::Dropped => "dropped",
3721 Self::Unknown(s) => s.as_str(),
3722 }
3723 }
3724}
3725
3726impl fmt::Display for PeerMessageHoldState {
3727 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3728 f.write_str(self.as_str())
3729 }
3730}
3731
3732impl From<&str> for PeerMessageHoldState {
3733 fn from(s: &str) -> Self {
3734 match s {
3735 "held" => Self::Held,
3736 "released" => Self::Released,
3737 "dropped" => Self::Dropped,
3738 other => Self::Unknown(other.to_string()),
3739 }
3740 }
3741}
3742
3743impl Serialize for PeerMessageHoldState {
3744 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3745 serializer.serialize_str(self.as_str())
3746 }
3747}
3748
3749impl<'de> Deserialize<'de> for PeerMessageHoldState {
3750 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3751 let s = String::deserialize(deserializer)?;
3752 Ok(Self::from(s.as_str()))
3753 }
3754}
3755
3756#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3758pub enum PeerMessageLane {
3759 Bridge,
3761 Stdin,
3763 Socket,
3765 Unknown(String),
3767}
3768
3769impl PeerMessageLane {
3770 pub fn as_str(&self) -> &str {
3771 match self {
3772 Self::Bridge => "bridge",
3773 Self::Stdin => "stdin",
3774 Self::Socket => "socket",
3775 Self::Unknown(s) => s.as_str(),
3776 }
3777 }
3778}
3779
3780impl fmt::Display for PeerMessageLane {
3781 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3782 f.write_str(self.as_str())
3783 }
3784}
3785
3786impl From<&str> for PeerMessageLane {
3787 fn from(s: &str) -> Self {
3788 match s {
3789 "bridge" => Self::Bridge,
3790 "stdin" => Self::Stdin,
3791 "socket" => Self::Socket,
3792 other => Self::Unknown(other.to_string()),
3793 }
3794 }
3795}
3796
3797impl Serialize for PeerMessageLane {
3798 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3799 serializer.serialize_str(self.as_str())
3800 }
3801}
3802
3803impl<'de> Deserialize<'de> for PeerMessageLane {
3804 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3805 let s = String::deserialize(deserializer)?;
3806 Ok(Self::from(s.as_str()))
3807 }
3808}
3809
3810#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3817pub enum PeerMessageHoldCause {
3818 ExplicitSetting,
3819 ManagedSetting,
3820 RepoSetting,
3821 InvalidSetting,
3822 BypassDefault,
3823 ModeUnknown,
3824 ModeMismatch,
3825 NoModeAsserted,
3826 Unknown(String),
3828}
3829
3830impl PeerMessageHoldCause {
3831 pub fn as_str(&self) -> &str {
3832 match self {
3833 Self::ExplicitSetting => "explicit-setting",
3834 Self::ManagedSetting => "managed-setting",
3835 Self::RepoSetting => "repo-setting",
3836 Self::InvalidSetting => "invalid-setting",
3837 Self::BypassDefault => "bypass-default",
3838 Self::ModeUnknown => "mode-unknown",
3839 Self::ModeMismatch => "mode-mismatch",
3840 Self::NoModeAsserted => "no-mode-asserted",
3841 Self::Unknown(s) => s.as_str(),
3842 }
3843 }
3844}
3845
3846impl fmt::Display for PeerMessageHoldCause {
3847 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3848 f.write_str(self.as_str())
3849 }
3850}
3851
3852impl From<&str> for PeerMessageHoldCause {
3853 fn from(s: &str) -> Self {
3854 match s {
3855 "explicit-setting" => Self::ExplicitSetting,
3856 "managed-setting" => Self::ManagedSetting,
3857 "repo-setting" => Self::RepoSetting,
3858 "invalid-setting" => Self::InvalidSetting,
3859 "bypass-default" => Self::BypassDefault,
3860 "mode-unknown" => Self::ModeUnknown,
3861 "mode-mismatch" => Self::ModeMismatch,
3862 "no-mode-asserted" => Self::NoModeAsserted,
3863 other => Self::Unknown(other.to_string()),
3864 }
3865 }
3866}
3867
3868impl Serialize for PeerMessageHoldCause {
3869 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3870 serializer.serialize_str(self.as_str())
3871 }
3872}
3873
3874impl<'de> Deserialize<'de> for PeerMessageHoldCause {
3875 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3876 let s = String::deserialize(deserializer)?;
3877 Ok(Self::from(s.as_str()))
3878 }
3879}
3880
3881#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3884pub enum PeerMessageHoldOutcome {
3885 Expired,
3889 Refused,
3891 Dropped,
3894 Discarded,
3896 Unknown(String),
3898}
3899
3900impl PeerMessageHoldOutcome {
3901 pub fn as_str(&self) -> &str {
3902 match self {
3903 Self::Expired => "expired",
3904 Self::Refused => "refused",
3905 Self::Dropped => "dropped",
3906 Self::Discarded => "discarded",
3907 Self::Unknown(s) => s.as_str(),
3908 }
3909 }
3910}
3911
3912impl fmt::Display for PeerMessageHoldOutcome {
3913 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3914 f.write_str(self.as_str())
3915 }
3916}
3917
3918impl From<&str> for PeerMessageHoldOutcome {
3919 fn from(s: &str) -> Self {
3920 match s {
3921 "expired" => Self::Expired,
3922 "refused" => Self::Refused,
3923 "dropped" => Self::Dropped,
3924 "discarded" => Self::Discarded,
3925 other => Self::Unknown(other.to_string()),
3926 }
3927 }
3928}
3929
3930impl Serialize for PeerMessageHoldOutcome {
3931 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3932 serializer.serialize_str(self.as_str())
3933 }
3934}
3935
3936impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
3937 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3938 let s = String::deserialize(deserializer)?;
3939 Ok(Self::from(s.as_str()))
3940 }
3941}
3942
3943#[derive(Debug, Clone, Serialize, Deserialize)]
3954pub struct PerTurnEffortChangedMessage {
3955 pub per_turn_effort_active: bool,
3957 pub uuid: String,
3958 pub session_id: String,
3959}
3960
3961#[derive(Debug, Clone, Serialize, Deserialize)]
3968pub struct InstructionSizeWarningMessage {
3969 pub total_chars: u64,
3972 pub total_limit_chars: u64,
3975 pub file_count: u64,
3978 #[serde(default, skip_serializing_if = "Option::is_none")]
3982 pub largest_chars: Option<u64>,
3983 pub uuid: String,
3984 pub session_id: String,
3985}
3986
3987#[derive(Debug, Clone, Serialize, Deserialize)]
3994pub struct FileAttachmentsMissingMessage {
3995 pub message_uuid: String,
3998 pub sent_count: u64,
4000 pub missing: Vec<MissingFileAttachment>,
4002 pub uuid: String,
4003 pub session_id: String,
4004}
4005
4006#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4008pub struct MissingFileAttachment {
4009 pub file_uuid: String,
4011 pub reason: String,
4015}
4016
4017#[derive(Debug, Clone, Serialize, Deserialize)]
4027pub struct SessionTitleChangedMessage {
4028 pub title: String,
4033 pub uuid: String,
4034 pub session_id: String,
4035}
4036
4037#[derive(Debug, Clone, Serialize, Deserialize)]
4041pub struct UiLogMessage {
4042 pub plugin: String,
4044 pub text: String,
4048 pub uuid: String,
4049 pub session_id: String,
4050}
4051
4052#[derive(Debug, Clone, Serialize, Deserialize)]
4057pub struct UiToastMessage {
4058 pub plugin: String,
4060 pub text: String,
4064 pub timeout_ms: u64,
4067 pub uuid: String,
4068 pub session_id: String,
4069}
4070
4071#[derive(Debug, Clone, Serialize, Deserialize)]
4075pub struct UiStatusMessage {
4076 pub plugin: String,
4078 pub text: Option<String>,
4083 pub uuid: String,
4084 pub session_id: String,
4085}
4086
4087#[derive(Debug, Clone, Serialize, Deserialize)]
4092pub struct UiInvalidateMessage {
4093 pub event: String,
4096 #[serde(default, skip_serializing_if = "Option::is_none")]
4101 pub instances: Option<Vec<UiInvalidatedInstance>>,
4102 pub uuid: String,
4103 pub session_id: String,
4104}
4105
4106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4108pub struct UiInvalidatedInstance {
4109 pub surface: String,
4111 pub component: String,
4113 pub instance_id: String,
4116}
4117
4118#[derive(Debug, Clone, Serialize, Deserialize)]
4125pub struct UiPanesMessage {
4126 pub panes: Vec<UiPane>,
4129 pub shown_id: Option<String>,
4132 pub focused_id: Option<String>,
4135 pub focus_requested_id: Option<String>,
4139 pub uuid: String,
4140 pub session_id: String,
4141}
4142
4143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4145pub struct UiPane {
4146 pub id: String,
4149 pub title: String,
4152 pub plugin: String,
4154 #[serde(default, skip_serializing_if = "Option::is_none")]
4157 pub close_on_escape: Option<bool>,
4158 #[serde(default, skip_serializing_if = "Option::is_none")]
4161 pub hold_toasts: Option<bool>,
4162 #[serde(default, skip_serializing_if = "Option::is_none")]
4165 pub rows: Option<u64>,
4166 #[serde(default, skip_serializing_if = "Option::is_none")]
4169 pub columns: Option<u64>,
4170}
4171
4172#[derive(Debug, Clone, Serialize, Deserialize)]
4176pub struct UiScrollMessage {
4177 pub client_id: String,
4179 pub component: UiSiteComponent,
4181 pub instance_id: String,
4183 pub offset: u64,
4186 #[serde(default, skip_serializing_if = "Option::is_none")]
4189 pub follow_end: Option<bool>,
4190 pub uuid: String,
4191 pub session_id: String,
4192}
4193
4194#[derive(Debug, Clone, Serialize, Deserialize)]
4199pub struct UiFocusMessage {
4200 pub client_id: String,
4202 pub component: UiSiteComponent,
4204 pub instance_id: String,
4206 pub plugin: String,
4208 pub key: String,
4210 pub uuid: String,
4211 pub session_id: String,
4212}
4213
4214#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4216pub enum UiSiteComponent {
4217 Pane,
4219 AbovePrompt,
4221 Unknown(String),
4223}
4224
4225impl UiSiteComponent {
4226 pub fn as_str(&self) -> &str {
4227 match self {
4228 Self::Pane => "Pane",
4229 Self::AbovePrompt => "AbovePrompt",
4230 Self::Unknown(s) => s.as_str(),
4231 }
4232 }
4233}
4234
4235impl fmt::Display for UiSiteComponent {
4236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4237 f.write_str(self.as_str())
4238 }
4239}
4240
4241impl From<&str> for UiSiteComponent {
4242 fn from(s: &str) -> Self {
4243 match s {
4244 "Pane" => Self::Pane,
4245 "AbovePrompt" => Self::AbovePrompt,
4246 other => Self::Unknown(other.to_string()),
4247 }
4248 }
4249}
4250
4251impl Serialize for UiSiteComponent {
4252 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4253 serializer.serialize_str(self.as_str())
4254 }
4255}
4256
4257impl<'de> Deserialize<'de> for UiSiteComponent {
4258 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4259 let s = String::deserialize(deserializer)?;
4260 Ok(Self::from(s.as_str()))
4261 }
4262}
4263
4264#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4267pub struct LocalCommandRun {
4268 pub command: String,
4270 pub args: String,
4273}
4274
4275#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4279pub struct LocalCommandOutcome {
4280 pub kind: LocalCommandOutcomeKind,
4281 #[serde(default, skip_serializing_if = "Option::is_none")]
4284 pub suggestion: Option<String>,
4285}
4286
4287#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4292pub enum LocalCommandOutcomeKind {
4293 UnavailableHeadless,
4295 Unknown,
4298 Failed,
4300 RestartRequired,
4302 Other(String),
4304}
4305
4306impl LocalCommandOutcomeKind {
4307 pub fn as_str(&self) -> &str {
4308 match self {
4309 Self::UnavailableHeadless => "unavailable_headless",
4310 Self::Unknown => "unknown",
4311 Self::Failed => "failed",
4312 Self::RestartRequired => "restart_required",
4313 Self::Other(s) => s.as_str(),
4314 }
4315 }
4316}
4317
4318impl fmt::Display for LocalCommandOutcomeKind {
4319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4320 f.write_str(self.as_str())
4321 }
4322}
4323
4324impl From<&str> for LocalCommandOutcomeKind {
4325 fn from(s: &str) -> Self {
4326 match s {
4327 "unavailable_headless" => Self::UnavailableHeadless,
4328 "unknown" => Self::Unknown,
4329 "failed" => Self::Failed,
4330 "restart_required" => Self::RestartRequired,
4331 other => Self::Other(other.to_string()),
4332 }
4333 }
4334}
4335
4336impl Serialize for LocalCommandOutcomeKind {
4337 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4338 serializer.serialize_str(self.as_str())
4339 }
4340}
4341
4342impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4343 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4344 let s = String::deserialize(deserializer)?;
4345 Ok(Self::from(s.as_str()))
4346 }
4347}
4348
4349#[derive(Debug, Clone, Serialize, Deserialize)]
4355pub struct UsageReport {
4356 pub session: super::control::UsageSession,
4358 pub rate_limits: Option<UsageReportRateLimits>,
4362 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4363 pub extra: serde_json::Map<String, Value>,
4364}
4365
4366#[derive(Debug, Clone, Serialize, Deserialize)]
4368pub struct UsageReportRateLimits {
4369 pub limits: Option<Vec<UsageReportLimit>>,
4377 #[serde(default, skip_serializing_if = "Option::is_none")]
4380 pub extra_usage: Option<UsageReportExtraUsage>,
4381 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4382 pub extra: serde_json::Map<String, Value>,
4383}
4384
4385#[derive(Debug, Clone, Serialize, Deserialize)]
4387pub struct UsageReportLimit {
4388 pub kind: String,
4391 pub group: String,
4394 pub percent: f64,
4396 pub resets_at: Option<String>,
4398 #[serde(default, skip_serializing_if = "Option::is_none")]
4401 pub scope: Option<UsageReportScope>,
4402 #[serde(default, skip_serializing_if = "Option::is_none")]
4406 pub severity: Option<String>,
4407 #[serde(default, skip_serializing_if = "Option::is_none")]
4409 pub is_active: Option<bool>,
4410 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4411 pub extra: serde_json::Map<String, Value>,
4412}
4413
4414#[derive(Debug, Clone, Serialize, Deserialize)]
4416pub struct UsageReportScope {
4417 #[serde(default, skip_serializing_if = "Option::is_none")]
4418 pub model: Option<UsageReportScopeLabel>,
4419 #[serde(default, skip_serializing_if = "Option::is_none")]
4420 pub surface: Option<UsageReportScopeLabel>,
4421 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4422 pub extra: serde_json::Map<String, Value>,
4423}
4424
4425#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4427pub struct UsageReportScopeLabel {
4428 pub display_name: String,
4429}
4430
4431#[derive(Debug, Clone, Serialize, Deserialize)]
4435pub struct UsageReportExtraUsage {
4436 pub is_enabled: bool,
4438 pub monthly_limit: Option<f64>,
4439 pub used_credits: Option<f64>,
4440 pub utilization: Option<f64>,
4441 #[serde(default, skip_serializing_if = "Option::is_none")]
4442 pub currency: Option<String>,
4443 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4444 pub extra: serde_json::Map<String, Value>,
4445}
4446
4447#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4450pub struct BatchToolUse {
4451 pub id: String,
4452 pub name: String,
4453}
4454
4455#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4460pub struct ContextUsage {
4461 pub model: String,
4463 pub total_tokens: u64,
4466 pub raw_max_tokens: u64,
4469 pub percentage: u64,
4471 #[serde(default, skip_serializing_if = "Option::is_none")]
4473 pub over_limit: Option<ContextOverLimit>,
4474 #[serde(default)]
4476 pub categories: Vec<ContextCategory>,
4477 #[serde(default)]
4479 pub mcp_tools: Vec<ContextMcpTool>,
4480 #[serde(default)]
4482 pub memory_files: Vec<ContextMemoryFile>,
4483 #[serde(default)]
4485 pub agents: Vec<ContextAgent>,
4486 #[serde(default, skip_serializing_if = "Option::is_none")]
4488 pub skills: Option<Vec<ContextSkill>>,
4489}
4490
4491#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4493pub struct ContextOverLimit {
4494 pub tokens_over: u64,
4495 pub kind: String,
4498}
4499
4500#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4502pub struct ContextCategory {
4503 pub name: String,
4506 pub tokens: u64,
4507 pub kind: String,
4511}
4512
4513#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4515pub struct ContextMcpTool {
4516 pub name: String,
4518 pub server_name: String,
4519 pub tokens: u64,
4520}
4521
4522#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4524pub struct ContextMemoryFile {
4525 pub path: String,
4526 #[serde(rename = "type")]
4528 pub file_type: String,
4529 pub tokens: u64,
4530}
4531
4532#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4534pub struct ContextAgent {
4535 pub agent_type: String,
4536 pub source: String,
4538 pub tokens: u64,
4539}
4540
4541#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4543pub struct ContextSkill {
4544 pub name: String,
4545 pub source: String,
4547 #[serde(default, skip_serializing_if = "Option::is_none")]
4548 pub plugin_name: Option<String>,
4549 pub tokens: u64,
4550}
4551
4552#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4554pub struct ToolUseMeta {
4555 pub id: String,
4556 pub display_name: String,
4557 #[serde(default, skip_serializing_if = "Option::is_none")]
4558 pub server_display_name: Option<String>,
4559 #[serde(default, skip_serializing_if = "Option::is_none")]
4560 pub icon_url: Option<String>,
4561}
4562
4563#[derive(Debug, Clone, Serialize, Deserialize)]
4565pub struct AssistantMessage {
4566 pub message: AssistantMessageContent,
4567 #[serde(alias = "sessionId")]
4568 pub session_id: String,
4569 #[serde(skip_serializing_if = "Option::is_none")]
4570 pub uuid: Option<String>,
4571 #[serde(skip_serializing_if = "Option::is_none")]
4572 pub parent_tool_use_id: Option<String>,
4573 #[serde(skip_serializing_if = "Option::is_none")]
4575 pub request_id: Option<String>,
4576 #[serde(default, skip_serializing_if = "Option::is_none")]
4585 pub user_message_uuid: Option<String>,
4586 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4592 pub user_message_uuids: Vec<String>,
4593 #[serde(skip_serializing_if = "Option::is_none")]
4596 pub subagent_type: Option<String>,
4597 #[serde(skip_serializing_if = "Option::is_none")]
4599 pub task_description: Option<String>,
4600 #[serde(skip_serializing_if = "Option::is_none")]
4601 pub error: Option<AssistantErrorKind>,
4602 #[serde(default, skip_serializing_if = "Option::is_none")]
4606 pub aborted: Option<bool>,
4607 #[serde(default, skip_serializing_if = "Option::is_none")]
4612 pub resumed_from_incomplete_thinking: Option<bool>,
4613 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4614 pub supersedes: Vec<String>,
4615 #[serde(skip_serializing_if = "Option::is_none")]
4616 pub timestamp: Option<String>,
4617 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4618 pub tool_use_meta: Vec<ToolUseMeta>,
4619 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4624 pub batch_tool_uses: Vec<BatchToolUse>,
4625 #[serde(default, skip_serializing_if = "Option::is_none")]
4631 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
4632 #[serde(default, skip_serializing_if = "Option::is_none")]
4638 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
4639 #[serde(default, skip_serializing_if = "Option::is_none")]
4643 pub historical: Option<bool>,
4644 #[serde(default, skip_serializing_if = "Option::is_none")]
4652 pub resume_reason: Option<String>,
4653 #[serde(default, skip_serializing_if = "Option::is_none")]
4659 pub local_command_source: Option<String>,
4660 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4669 pub narration_block_indexes: Vec<usize>,
4670 #[serde(default, skip_serializing_if = "Option::is_none")]
4674 pub narration_hint: Option<String>,
4675 #[serde(default, skip_serializing_if = "Option::is_none")]
4684 pub thinking_duration_ms: Option<u64>,
4685 #[serde(default, skip_serializing_if = "Option::is_none")]
4689 pub context_usage: Option<ContextUsage>,
4690 #[serde(default, skip_serializing_if = "Option::is_none")]
4698 pub usage_report: Option<Box<UsageReport>>,
4699 #[serde(default, skip_serializing_if = "Option::is_none")]
4706 pub local_command_run: Option<LocalCommandRun>,
4707 #[serde(default, skip_serializing_if = "Option::is_none")]
4713 pub local_command_outcome: Option<LocalCommandOutcome>,
4714 #[serde(default, skip_serializing_if = "Option::is_none")]
4715 pub is_meta: Option<bool>,
4716 #[serde(default, skip_serializing_if = "Option::is_none")]
4717 pub is_virtual: Option<bool>,
4718 #[serde(default, skip_serializing_if = "Option::is_none")]
4719 pub is_api_error_message: Option<bool>,
4720 #[serde(skip_serializing_if = "Option::is_none")]
4721 pub api_error_status: Option<u16>,
4722 #[serde(skip_serializing_if = "Option::is_none")]
4739 pub api_error: Option<String>,
4740 #[serde(default, skip_serializing_if = "Option::is_none")]
4747 pub api_error_code: Option<String>,
4748 #[serde(default, skip_serializing_if = "Option::is_none")]
4751 pub api_error_params: Option<ApiErrorParams>,
4752 #[serde(skip_serializing_if = "Option::is_none")]
4753 pub error_details: Option<String>,
4754 #[serde(skip_serializing_if = "Option::is_none")]
4755 pub advisor_model: Option<String>,
4756 #[serde(skip_serializing_if = "Option::is_none")]
4757 pub attribution_agent: Option<String>,
4758 #[serde(skip_serializing_if = "Option::is_none")]
4759 pub attribution_skill: Option<String>,
4760 #[serde(skip_serializing_if = "Option::is_none")]
4761 pub attribution_plugin: Option<String>,
4762 #[serde(skip_serializing_if = "Option::is_none")]
4763 pub attribution_mcp_server: Option<String>,
4764 #[serde(skip_serializing_if = "Option::is_none")]
4765 pub attribution_mcp_tool: Option<String>,
4766}
4767
4768#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
4772pub struct ApiErrorParams {
4773 #[serde(default, skip_serializing_if = "Option::is_none")]
4775 pub effort: Option<String>,
4776 #[serde(default, skip_serializing_if = "Option::is_none")]
4779 pub provider: Option<ApiErrorProvider>,
4780 #[serde(default, skip_serializing_if = "Option::is_none")]
4783 pub remedy: Option<ApiErrorRemedy>,
4784 #[serde(default, skip_serializing_if = "Option::is_none")]
4787 pub media: Option<ApiErrorMedia>,
4788 #[serde(default, skip_serializing_if = "Option::is_none")]
4790 pub media_reason: Option<ApiErrorMediaReason>,
4791}
4792
4793#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4795pub enum ApiErrorMedia {
4796 Image,
4797 Document,
4798 Unknown(String),
4800}
4801
4802impl ApiErrorMedia {
4803 pub fn as_str(&self) -> &str {
4804 match self {
4805 Self::Image => "image",
4806 Self::Document => "document",
4807 Self::Unknown(s) => s.as_str(),
4808 }
4809 }
4810}
4811
4812impl fmt::Display for ApiErrorMedia {
4813 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4814 f.write_str(self.as_str())
4815 }
4816}
4817
4818impl From<&str> for ApiErrorMedia {
4819 fn from(s: &str) -> Self {
4820 match s {
4821 "image" => Self::Image,
4822 "document" => Self::Document,
4823 other => Self::Unknown(other.to_string()),
4824 }
4825 }
4826}
4827
4828impl Serialize for ApiErrorMedia {
4829 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4830 serializer.serialize_str(self.as_str())
4831 }
4832}
4833
4834impl<'de> Deserialize<'de> for ApiErrorMedia {
4835 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4836 let s = String::deserialize(deserializer)?;
4837 Ok(Self::from(s.as_str()))
4838 }
4839}
4840
4841#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4844pub enum ApiErrorMediaReason {
4845 Unprocessable,
4848 UnsupportedByModel,
4850 MediaBudget,
4853 Unknown(String),
4855}
4856
4857impl ApiErrorMediaReason {
4858 pub fn as_str(&self) -> &str {
4859 match self {
4860 Self::Unprocessable => "unprocessable",
4861 Self::UnsupportedByModel => "unsupported_by_model",
4862 Self::MediaBudget => "media_budget",
4863 Self::Unknown(s) => s.as_str(),
4864 }
4865 }
4866}
4867
4868impl fmt::Display for ApiErrorMediaReason {
4869 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4870 f.write_str(self.as_str())
4871 }
4872}
4873
4874impl From<&str> for ApiErrorMediaReason {
4875 fn from(s: &str) -> Self {
4876 match s {
4877 "unprocessable" => Self::Unprocessable,
4878 "unsupported_by_model" => Self::UnsupportedByModel,
4879 "media_budget" => Self::MediaBudget,
4880 other => Self::Unknown(other.to_string()),
4881 }
4882 }
4883}
4884
4885impl Serialize for ApiErrorMediaReason {
4886 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4887 serializer.serialize_str(self.as_str())
4888 }
4889}
4890
4891impl<'de> Deserialize<'de> for ApiErrorMediaReason {
4892 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4893 let s = String::deserialize(deserializer)?;
4894 Ok(Self::from(s.as_str()))
4895 }
4896}
4897
4898#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4900pub enum ApiErrorProvider {
4901 Bedrock,
4902 AnthropicAws,
4903 Mantle,
4904 AnthropicGoogleCloud,
4905 Vertex,
4906 Foundry,
4907 Gateway,
4908 Unknown(String),
4910}
4911
4912impl ApiErrorProvider {
4913 pub fn as_str(&self) -> &str {
4914 match self {
4915 Self::Bedrock => "bedrock",
4916 Self::AnthropicAws => "anthropicAws",
4917 Self::Mantle => "mantle",
4918 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
4919 Self::Vertex => "vertex",
4920 Self::Foundry => "foundry",
4921 Self::Gateway => "gateway",
4922 Self::Unknown(s) => s.as_str(),
4923 }
4924 }
4925}
4926
4927impl fmt::Display for ApiErrorProvider {
4928 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4929 f.write_str(self.as_str())
4930 }
4931}
4932
4933impl From<&str> for ApiErrorProvider {
4934 fn from(s: &str) -> Self {
4935 match s {
4936 "bedrock" => Self::Bedrock,
4937 "anthropicAws" => Self::AnthropicAws,
4938 "mantle" => Self::Mantle,
4939 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4940 "vertex" => Self::Vertex,
4941 "foundry" => Self::Foundry,
4942 "gateway" => Self::Gateway,
4943 other => Self::Unknown(other.to_string()),
4944 }
4945 }
4946}
4947
4948impl Serialize for ApiErrorProvider {
4949 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4950 serializer.serialize_str(self.as_str())
4951 }
4952}
4953
4954impl<'de> Deserialize<'de> for ApiErrorProvider {
4955 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4956 let s = String::deserialize(deserializer)?;
4957 Ok(Self::from(s.as_str()))
4958 }
4959}
4960
4961#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4963pub enum ApiErrorRemedy {
4964 RefreshCommand,
4967 RefreshCredentials,
4969 Adc,
4972 GatewayToken,
4975 HostManaged,
4977 ModelAccess,
4980 Unknown(String),
4982}
4983
4984impl ApiErrorRemedy {
4985 pub fn as_str(&self) -> &str {
4986 match self {
4987 Self::RefreshCommand => "refresh_command",
4988 Self::RefreshCredentials => "refresh_credentials",
4989 Self::Adc => "adc",
4990 Self::GatewayToken => "gateway_token",
4991 Self::HostManaged => "host_managed",
4992 Self::ModelAccess => "model_access",
4993 Self::Unknown(s) => s.as_str(),
4994 }
4995 }
4996}
4997
4998impl fmt::Display for ApiErrorRemedy {
4999 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5000 f.write_str(self.as_str())
5001 }
5002}
5003
5004impl From<&str> for ApiErrorRemedy {
5005 fn from(s: &str) -> Self {
5006 match s {
5007 "refresh_command" => Self::RefreshCommand,
5008 "refresh_credentials" => Self::RefreshCredentials,
5009 "adc" => Self::Adc,
5010 "gateway_token" => Self::GatewayToken,
5011 "host_managed" => Self::HostManaged,
5012 "model_access" => Self::ModelAccess,
5013 other => Self::Unknown(other.to_string()),
5014 }
5015 }
5016}
5017
5018impl Serialize for ApiErrorRemedy {
5019 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5020 serializer.serialize_str(self.as_str())
5021 }
5022}
5023
5024impl<'de> Deserialize<'de> for ApiErrorRemedy {
5025 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5026 let s = String::deserialize(deserializer)?;
5027 Ok(Self::from(s.as_str()))
5028 }
5029}
5030
5031#[derive(Debug, Clone, Serialize, Deserialize)]
5033pub struct AssistantMessageContent {
5034 pub id: String,
5035 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
5037 pub message_type: Option<String>,
5038 pub role: MessageRole,
5039 pub model: String,
5040 pub content: Vec<ContentBlock>,
5041 #[serde(skip_serializing_if = "Option::is_none")]
5042 pub stop_reason: Option<StopReason>,
5043 #[serde(skip_serializing_if = "Option::is_none")]
5044 pub stop_sequence: Option<String>,
5045 #[serde(skip_serializing_if = "Option::is_none")]
5046 pub usage: Option<AssistantUsage>,
5047 #[serde(skip_serializing_if = "Option::is_none")]
5049 pub stop_details: Option<Value>,
5050 #[serde(skip_serializing_if = "Option::is_none")]
5052 pub context_management: Option<Value>,
5053}
5054
5055#[derive(Debug, Clone, Serialize, Deserialize)]
5057pub struct AssistantUsage {
5058 #[serde(default)]
5060 pub input_tokens: u32,
5061
5062 #[serde(default)]
5064 pub output_tokens: u32,
5065
5066 #[serde(default)]
5068 pub cache_creation_input_tokens: u32,
5069
5070 #[serde(default)]
5072 pub cache_read_input_tokens: u32,
5073
5074 #[serde(skip_serializing_if = "Option::is_none")]
5076 pub service_tier: Option<String>,
5077
5078 #[serde(skip_serializing_if = "Option::is_none")]
5080 pub cache_creation: Option<CacheCreationDetails>,
5081
5082 #[serde(skip_serializing_if = "Option::is_none")]
5084 pub inference_geo: Option<String>,
5085
5086 #[serde(default, skip_serializing_if = "Option::is_none")]
5089 pub fallback_credit: Option<Value>,
5090}
5091
5092#[derive(Debug, Clone, Serialize, Deserialize)]
5094pub struct CacheCreationDetails {
5095 #[serde(default)]
5097 pub ephemeral_1h_input_tokens: u32,
5098
5099 #[serde(default)]
5101 pub ephemeral_5m_input_tokens: u32,
5102}
5103
5104#[cfg(test)]
5105mod tests {
5106 use crate::io::ClaudeOutput;
5107
5108 #[test]
5109 fn test_subagent_usage_rollup_accumulates_task_results() {
5110 use super::SubagentUsageRollup;
5111
5112 let mut rollup = SubagentUsageRollup::default();
5113
5114 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}}"#;
5115 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5116 assert!(rollup.observe(&output));
5117 assert_eq!(rollup.subagent_tokens, 10201);
5118 assert_eq!(rollup.agent_count, 1);
5119 assert_eq!(rollup.tool_uses, 3);
5120 assert_eq!(rollup.duration_ms, 1853);
5121
5122 assert!(!rollup.observe(&output));
5124 assert_eq!(rollup.agent_count, 1);
5125 assert_eq!(rollup.subagent_tokens, 10201);
5126
5127 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}}"#;
5129 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5130 assert!(rollup.observe(&output));
5131 assert_eq!(rollup.agent_count, 2);
5132 assert_eq!(rollup.subagent_tokens, 10701);
5133 }
5134
5135 #[test]
5136 fn test_subagent_usage_rollup_ignores_non_task_results() {
5137 use super::SubagentUsageRollup;
5138
5139 let mut rollup = SubagentUsageRollup::default();
5140
5141 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}}"#;
5144 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5145 assert!(!rollup.observe(&output));
5146
5147 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5149 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5150 assert!(!rollup.observe(&output));
5151
5152 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5154 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5155 assert!(!rollup.observe(&output));
5156
5157 assert_eq!(rollup, SubagentUsageRollup::default());
5158 }
5159
5160 #[test]
5161 fn test_subagent_usage_rollup_over_captured_session() {
5162 use super::SubagentUsageRollup;
5163
5164 let mut rollup = SubagentUsageRollup::default();
5165 let fixture =
5166 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5167 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5168 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5169 rollup.observe(&output);
5170 }
5171 }
5172 assert_eq!(rollup.agent_count, 1);
5173 assert_eq!(rollup.subagent_tokens, 10201);
5174 }
5175
5176 #[test]
5177 fn test_system_message_init() {
5178 let json = r#"{
5179 "type": "system",
5180 "subtype": "init",
5181 "session_id": "test-session-123",
5182 "cwd": "/home/user/project",
5183 "model": "claude-sonnet-4",
5184 "tools": ["Bash", "Read", "Write"],
5185 "mcp_servers": [],
5186 "slash_commands": ["compact", "cost", "review"],
5187 "agents": ["Bash", "Explore", "Plan"],
5188 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5189 "skills": [],
5190 "claude_code_version": "2.1.15",
5191 "apiKeySource": "none",
5192 "output_style": "default",
5193 "permissionMode": "default"
5194 }"#;
5195
5196 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5197 if let ClaudeOutput::System(sys) = output {
5198 assert!(sys.is_init());
5199 assert!(!sys.is_status());
5200 assert!(!sys.is_compact_boundary());
5201
5202 let init = sys.as_init().expect("Should parse as init");
5203 assert_eq!(init.session_id, "test-session-123");
5204 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5205 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5206 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5207 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5208 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5209 assert_eq!(init.plugins.len(), 1);
5210 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5211 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5212 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5213 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5214 assert_eq!(
5215 init.permission_mode,
5216 Some(super::InitPermissionMode::Default)
5217 );
5218 } else {
5219 panic!("Expected System message");
5220 }
5221 }
5222
5223 #[test]
5224 fn test_system_message_init_from_real_capture() {
5225 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5226 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5227 if let ClaudeOutput::System(sys) = output {
5228 let init = sys.as_init().expect("Should parse real init capture");
5229 assert_eq!(init.slash_commands.len(), 8);
5230 assert!(init.slash_commands.contains(&"compact".to_string()));
5231 assert!(init.slash_commands.contains(&"review".to_string()));
5232 assert_eq!(init.agents.len(), 5);
5233 assert!(init.agents.contains(&"Bash".to_string()));
5234 assert!(init.agents.contains(&"Explore".to_string()));
5235 assert_eq!(init.plugins.len(), 1);
5236 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5237 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5238 } else {
5239 panic!("Expected System message");
5240 }
5241 }
5242
5243 #[test]
5244 fn test_system_message_status() {
5245 let json = r#"{
5246 "type": "system",
5247 "subtype": "status",
5248 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5249 "status": "compacting",
5250 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5251 }"#;
5252
5253 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5254 if let ClaudeOutput::System(sys) = output {
5255 assert!(sys.is_status());
5256 assert!(!sys.is_init());
5257
5258 let status = sys.as_status().expect("Should parse as status");
5259 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5260 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5261 assert_eq!(
5262 status.uuid,
5263 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5264 );
5265 } else {
5266 panic!("Expected System message");
5267 }
5268 }
5269
5270 #[test]
5271 fn test_system_message_status_null() {
5272 let json = r#"{
5273 "type": "system",
5274 "subtype": "status",
5275 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5276 "status": null,
5277 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5278 }"#;
5279
5280 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5281 if let ClaudeOutput::System(sys) = output {
5282 let status = sys.as_status().expect("Should parse as status");
5283 assert_eq!(status.status, None);
5284 } else {
5285 panic!("Expected System message");
5286 }
5287 }
5288
5289 #[test]
5290 fn test_system_message_task_started() {
5291 let json = r#"{
5292 "type": "system",
5293 "subtype": "task_started",
5294 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5295 "task_id": "b6daf3f",
5296 "task_type": "local_bash",
5297 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5298 "description": "Wait for CI on PR #12",
5299 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5300 }"#;
5301
5302 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5303 if let ClaudeOutput::System(sys) = output {
5304 assert!(sys.is_task_started());
5305 assert!(!sys.is_task_progress());
5306 assert!(!sys.is_task_notification());
5307
5308 let task = sys.as_task_started().expect("Should parse as task_started");
5309 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5310 assert_eq!(task.task_id, "b6daf3f");
5311 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5312 assert_eq!(
5313 task.tool_use_id.as_deref(),
5314 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5315 );
5316 assert_eq!(task.description, "Wait for CI on PR #12");
5317 } else {
5318 panic!("Expected System message");
5319 }
5320 }
5321
5322 #[test]
5323 fn test_system_message_task_started_agent() {
5324 let json = r#"{
5325 "type": "system",
5326 "subtype": "task_started",
5327 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5328 "task_id": "a4a7e0906e5fc64cc",
5329 "task_type": "local_agent",
5330 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5331 "description": "Explore Scene/ArrayScene duplication",
5332 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5333 }"#;
5334
5335 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5336 if let ClaudeOutput::System(sys) = output {
5337 let task = sys.as_task_started().expect("Should parse as task_started");
5338 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5339 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5340 } else {
5341 panic!("Expected System message");
5342 }
5343 }
5344
5345 #[test]
5346 fn test_system_message_task_progress() {
5347 let json = r#"{
5348 "type": "system",
5349 "subtype": "task_progress",
5350 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5351 "task_id": "a4a7e0906e5fc64cc",
5352 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5353 "description": "Reading src/jplephem/chebyshev.rs",
5354 "last_tool_name": "Read",
5355 "usage": {
5356 "duration_ms": 13996,
5357 "tool_uses": 9,
5358 "total_tokens": 38779
5359 },
5360 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5361 }"#;
5362
5363 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5364 if let ClaudeOutput::System(sys) = output {
5365 assert!(sys.is_task_progress());
5366 assert!(!sys.is_task_started());
5367
5368 let progress = sys
5369 .as_task_progress()
5370 .expect("Should parse as task_progress");
5371 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5372 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5373 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5374 assert_eq!(progress.usage.duration_ms, 13996);
5375 assert_eq!(progress.usage.tool_uses, 9);
5376 assert_eq!(progress.usage.total_tokens, 38779);
5377 } else {
5378 panic!("Expected System message");
5379 }
5380 }
5381
5382 #[test]
5383 fn test_system_message_task_notification_completed() {
5384 let json = r#"{
5385 "type": "system",
5386 "subtype": "task_notification",
5387 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5388 "task_id": "a0ba761e9dc9c316f",
5389 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
5390 "status": "completed",
5391 "summary": "Agent \"Write Hipparcos data source doc\" completed",
5392 "output_file": "",
5393 "usage": {
5394 "duration_ms": 172300,
5395 "tool_uses": 11,
5396 "total_tokens": 42005
5397 },
5398 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
5399 }"#;
5400
5401 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5402 if let ClaudeOutput::System(sys) = output {
5403 assert!(sys.is_task_notification());
5404
5405 let notif = sys
5406 .as_task_notification()
5407 .expect("Should parse as task_notification");
5408 assert_eq!(notif.status, super::TaskStatus::Completed);
5409 assert_eq!(
5410 notif.summary,
5411 "Agent \"Write Hipparcos data source doc\" completed"
5412 );
5413 assert_eq!(notif.output_file, Some("".to_string()));
5414 assert_eq!(
5415 notif.tool_use_id,
5416 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
5417 );
5418 let usage = notif.usage.expect("Should have usage");
5419 assert_eq!(usage.duration_ms, 172300);
5420 assert_eq!(usage.tool_uses, 11);
5421 assert_eq!(usage.total_tokens, 42005);
5422 } else {
5423 panic!("Expected System message");
5424 }
5425 }
5426
5427 #[test]
5428 fn test_system_message_task_notification_failed_no_usage() {
5429 let json = r#"{
5430 "type": "system",
5431 "subtype": "task_notification",
5432 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
5433 "task_id": "b98f6a3",
5434 "status": "failed",
5435 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
5436 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
5437 }"#;
5438
5439 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5440 if let ClaudeOutput::System(sys) = output {
5441 let notif = sys
5442 .as_task_notification()
5443 .expect("Should parse as task_notification");
5444 assert_eq!(notif.status, super::TaskStatus::Failed);
5445 assert!(notif.tool_use_id.is_none());
5446 assert!(notif.usage.is_none());
5447 assert_eq!(
5448 notif.output_file,
5449 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
5450 );
5451 } else {
5452 panic!("Expected System message");
5453 }
5454 }
5455
5456 #[test]
5462 fn test_task_messages_roundtrip_through_value() {
5463 let cases = [
5464 r#"{"type":"system","subtype":"task_started","session_id":"s1",
5465 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
5466 "description":"Sleep 3s","uuid":"u1"}"#,
5467 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
5468 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
5469 "last_tool_name":"Bash",
5470 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5471 "uuid":"u2"}"#,
5472 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
5473 "task_id":"t1","tool_use_id":"tu1","status":"completed",
5474 "summary":"done","output_file":"",
5475 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5476 "uuid":"u3"}"#,
5477 ];
5478
5479 for json in cases {
5480 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5481 let value = serde_json::to_value(&output).unwrap();
5482 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
5483
5484 let ClaudeOutput::System(sys) = reparsed else {
5485 panic!("Expected System variant after round-trip");
5486 };
5487
5488 match sys.subtype {
5489 super::SystemSubtype::TaskStarted => {
5490 assert!(
5491 sys.as_task_started().is_some(),
5492 "as_task_started failed after round-trip"
5493 );
5494 }
5495 super::SystemSubtype::TaskProgress => {
5496 assert!(
5497 sys.as_task_progress().is_some(),
5498 "as_task_progress failed after round-trip"
5499 );
5500 }
5501 super::SystemSubtype::TaskNotification => {
5502 assert!(
5503 sys.as_task_notification().is_some(),
5504 "as_task_notification failed after round-trip"
5505 );
5506 }
5507 other => panic!("unexpected subtype after round-trip: {other:?}"),
5508 }
5509 }
5510 }
5511
5512 #[test]
5513 fn test_system_message_compact_boundary() {
5514 let json = r#"{
5515 "type": "system",
5516 "subtype": "compact_boundary",
5517 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5518 "compact_metadata": {
5519 "pre_tokens": 155285,
5520 "trigger": "auto"
5521 },
5522 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
5523 }"#;
5524
5525 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5526 if let ClaudeOutput::System(sys) = output {
5527 assert!(sys.is_compact_boundary());
5528 assert!(!sys.is_init());
5529 assert!(!sys.is_status());
5530
5531 let compact = sys
5532 .as_compact_boundary()
5533 .expect("Should parse as compact_boundary");
5534 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5535 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
5536 assert_eq!(
5537 compact.compact_metadata.trigger,
5538 super::CompactionTrigger::Auto
5539 );
5540 assert!(compact.summary.is_none());
5542 assert!(compact.leaf_message_count.is_none());
5543 assert!(compact.duration_ms.is_none());
5544 } else {
5545 panic!("Expected System message");
5546 }
5547 }
5548
5549 #[test]
5550 fn test_compact_boundary_with_summary_stats() {
5551 let json = r#"{
5553 "type": "system",
5554 "subtype": "compact_boundary",
5555 "session_id": "s1",
5556 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
5557 "summary": "Summarized the earlier exploration.",
5558 "leaf_message_count": 42,
5559 "duration_ms": 1234,
5560 "uuid": "u1"
5561 }"#;
5562 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5563 let ClaudeOutput::System(sys) = output else {
5564 panic!("Expected System message");
5565 };
5566 let compact = sys.as_compact_boundary().expect("compact_boundary");
5567 assert_eq!(
5568 compact.summary.as_deref(),
5569 Some("Summarized the earlier exploration.")
5570 );
5571 assert_eq!(compact.leaf_message_count, Some(42));
5572 assert_eq!(compact.duration_ms, Some(1234));
5573
5574 let json_alt = r#"{
5577 "type": "system",
5578 "subtype": "compact_boundary",
5579 "session_id": "s2",
5580 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
5581 "content": "alt-key summary",
5582 "message_count": 7
5583 }"#;
5584 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
5585 let ClaudeOutput::System(sys) = output else {
5586 panic!("Expected System message");
5587 };
5588 let compact = sys.as_compact_boundary().expect("compact_boundary");
5589 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
5590 assert_eq!(compact.leaf_message_count, Some(7));
5591 }
5592
5593 #[test]
5594 fn test_init_message_with_new_fields() {
5595 let json = r#"{
5596 "type": "system",
5597 "subtype": "init",
5598 "session_id": "test-session",
5599 "cwd": "/home/user",
5600 "model": "claude-opus-4-7",
5601 "tools": ["Bash"],
5602 "mcp_servers": [],
5603 "permissionMode": "default",
5604 "apiKeySource": "none",
5605 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
5606 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
5607 "fast_mode_state": "off",
5608 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
5609 "claude_code_version": "2.1.117"
5610 }"#;
5611
5612 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5613 if let ClaudeOutput::System(sys) = output {
5614 let init = sys.as_init().expect("Should parse as init");
5615 assert_eq!(
5616 init.uuid.as_deref(),
5617 Some("44841a0d-182d-493a-86b5-79800d3d9665")
5618 );
5619 assert!(init.memory_paths.is_some());
5620 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
5621 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
5622 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
5623 } else {
5624 panic!("Expected System message");
5625 }
5626 }
5627
5628 #[test]
5629 fn test_assistant_message_with_new_fields() {
5630 let json = r#"{
5631 "type": "assistant",
5632 "message": {
5633 "id": "msg_1",
5634 "type": "message",
5635 "role": "assistant",
5636 "model": "claude-opus-4-7",
5637 "content": [{"type": "text", "text": "Hello"}],
5638 "stop_reason": "end_turn",
5639 "stop_details": null,
5640 "context_management": null,
5641 "usage": {
5642 "input_tokens": 100,
5643 "output_tokens": 10,
5644 "cache_creation_input_tokens": 50,
5645 "cache_read_input_tokens": 0,
5646 "service_tier": "standard",
5647 "inference_geo": "not_available"
5648 }
5649 },
5650 "session_id": "abc",
5651 "uuid": "msg-uuid-123"
5652 }"#;
5653
5654 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5655 if let ClaudeOutput::Assistant(asst) = output {
5656 assert_eq!(asst.message.stop_details, None);
5657 assert_eq!(asst.message.context_management, None);
5658 let usage = asst.message.usage.unwrap();
5659 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
5660 } else {
5661 panic!("Expected Assistant message");
5662 }
5663 }
5664
5665 #[test]
5666 fn test_user_message_with_new_fields() {
5667 let json = r#"{
5668 "type": "user",
5669 "message": {
5670 "role": "user",
5671 "content": [{"type": "text", "text": "Hello"}]
5672 },
5673 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5674 "parent_tool_use_id": "toolu_123",
5675 "uuid": "user-msg-456"
5676 }"#;
5677
5678 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5679 if let ClaudeOutput::User(user) = output {
5680 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
5681 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
5682 } else {
5683 panic!("Expected User message");
5684 }
5685 }
5686
5687 #[test]
5693 fn test_user_message_preserves_tool_use_result_and_timestamp() {
5694 let json = r#"{
5695 "type":"user",
5696 "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"}]},
5697 "parent_tool_use_id":null,
5698 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5699 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
5700 "timestamp":"2026-05-12T23:12:04.121Z",
5701 "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"}}
5702 }"#;
5703
5704 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5705 let user = match output {
5706 ClaudeOutput::User(u) => u,
5707 other => panic!("Expected User message, got {:?}", other.message_type()),
5708 };
5709
5710 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
5711 let raw = user
5712 .tool_use_result
5713 .as_ref()
5714 .expect("tool_use_result must be captured");
5715 assert_eq!(raw["answers"]["Color"], "Blue");
5716 assert_eq!(raw["questions"][0]["header"], "Color");
5717
5718 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
5722 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
5723 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
5724 assert_eq!(
5725 reser["tool_use_result"]["questions"][0]["question"],
5726 "Which color do you prefer?"
5727 );
5728
5729 let typed: crate::AskUserQuestionInput = user
5732 .tool_use_result_as::<crate::AskUserQuestionInput>()
5733 .expect("tool_use_result present")
5734 .expect("AskUserQuestionInput parses");
5735 assert_eq!(typed.questions.len(), 1);
5736 assert_eq!(typed.questions[0].header, "Color");
5737 let answers = typed.answers.expect("answers populated");
5738 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
5739 }
5740
5741 #[test]
5744 fn test_user_message_without_tool_use_result_omits_field() {
5745 let json = r#"{
5746 "type":"user",
5747 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
5748 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
5749 }"#;
5750
5751 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5752 let user = match output {
5753 ClaudeOutput::User(u) => u,
5754 _ => panic!("Expected User message"),
5755 };
5756 assert!(user.tool_use_result.is_none());
5757 assert!(user.timestamp.is_none());
5758
5759 let reser = serde_json::to_value(&user).unwrap();
5760 assert!(reser.get("tool_use_result").is_none());
5761 assert!(reser.get("timestamp").is_none());
5762 }
5763
5764 #[test]
5768 fn test_subagent_result_exposes_token_accounting() {
5769 let json = r#"{
5770 "type":"user",
5771 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
5772 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
5773 "tool_use_result":{
5774 "status":"completed",
5775 "prompt":"Count the .rs files.",
5776 "agentId":"ac4f0276e9d4b6232",
5777 "agentType":"Explore",
5778 "content":[{"type":"text","text":"21"}],
5779 "resolvedModel":"claude-haiku-4-5-20251001",
5780 "totalDurationMs":6869,
5781 "totalTokens":7834,
5782 "totalToolUseCount":1,
5783 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
5784 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
5785 }
5786 }"#;
5787
5788 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5789 let user = match output {
5790 ClaudeOutput::User(u) => u,
5791 _ => panic!("Expected User message"),
5792 };
5793
5794 let result = user.subagent_result().expect("subagent result parses");
5795 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
5796 assert_eq!(
5797 result.resolved_model.as_deref(),
5798 Some("claude-haiku-4-5-20251001")
5799 );
5800 assert_eq!(result.total_tokens, Some(7834));
5801 assert_eq!(result.total_duration_ms, Some(6869));
5802 assert_eq!(result.total_tool_use_count, Some(1));
5803
5804 let usage = result.usage.expect("nested usage present");
5805 assert_eq!(usage.input_tokens, 6);
5806 assert_eq!(usage.cache_read_input_tokens, 7699);
5807
5808 let stats = result.tool_stats.expect("toolStats present");
5809 assert_eq!(stats.bash_count, 1);
5810 }
5811
5812 #[test]
5816 fn test_subagent_result_absent_for_non_task_result() {
5817 let json = r#"{
5818 "type":"user",
5819 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
5820 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5821 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
5822 }"#;
5823
5824 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5825 let user = match output {
5826 ClaudeOutput::User(u) => u,
5827 _ => panic!("Expected User message"),
5828 };
5829
5830 let result = user.subagent_result().expect("lenient parse");
5831 assert_eq!(result.total_tokens, None);
5832 assert_eq!(result.agent_type, None);
5833 }
5834
5835 #[test]
5836 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
5837 use serde_json::Value;
5838
5839 let raw: Value = serde_json::from_str(
5840 r#"{
5841 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
5842 "fast_mode_state":"off",
5843 "fast_mode_disabled_reason":"not_first_party",
5844 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
5845 }"#,
5846 )
5847 .unwrap();
5848 crate::io::assert_fully_wrapped(&raw);
5849
5850 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5851 let ClaudeOutput::System(sys) = output else {
5852 panic!("expected System");
5853 };
5854 let init = sys.as_init().expect("parses as init");
5855 assert_eq!(
5856 init.fast_mode_disabled_reason,
5857 Some(crate::FastModeDisabledReason::NotFirstParty)
5858 );
5859 let errs = init.mcp_server_errors.unwrap();
5860 assert_eq!(errs.len(), 1);
5861 assert_eq!(errs[0].name, "broken");
5862 assert_eq!(errs[0].error_type, "invalid_config");
5863 }
5864
5865 #[test]
5866 fn test_code_change_published_fully_wrapped() {
5867 use super::{KnownSystemEvent, SystemSubtype};
5868 use serde_json::Value;
5869
5870 let raw: Value = serde_json::from_str(
5871 r#"{
5872 "type":"system","subtype":"code_change_published",
5873 "provider":"github","url":"https://github.com/owner/repo/pull/42",
5874 "repo":"owner/repo","identifier":"42",
5875 "uuid":"u1","session_id":"s1"
5876 }"#,
5877 )
5878 .unwrap();
5879 crate::io::assert_fully_wrapped(&raw);
5880
5881 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5882 let ClaudeOutput::System(sys) = output else {
5883 panic!("expected System");
5884 };
5885 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
5886 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
5887 panic!("expected CodeChangePublished event");
5888 };
5889 assert_eq!(msg.provider, "github");
5890 assert_eq!(msg.repo, "owner/repo");
5891 assert_eq!(msg.identifier, "42");
5892
5893 assert!(sys.is_code_change_published());
5894 assert!(!sys.is_vcs_state_changed());
5895 let direct = sys.as_code_change_published().expect("direct accessor");
5896 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
5897 assert!(sys.as_vcs_state_changed().is_none());
5898 }
5899
5900 #[test]
5901 fn test_feedback_draft_queued_fully_wrapped() {
5902 use super::{KnownSystemEvent, SystemSubtype};
5903 use serde_json::Value;
5904
5905 let raw: Value = serde_json::from_str(
5906 r#"{
5907 "type":"system","subtype":"feedback_draft_queued",
5908 "draft_id":"draft-1","draft_type":"bug_report",
5909 "title":"Tool output was truncated",
5910 "details_preview":"The last command omitted its final lines",
5911 "uuid":"u1","session_id":"s1","future_field":"preserved"
5912 }"#,
5913 )
5914 .unwrap();
5915 crate::io::assert_fully_wrapped(&raw);
5916
5917 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5918 let ClaudeOutput::System(sys) = output else {
5919 panic!("expected System");
5920 };
5921 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
5922 assert!(sys.is_feedback_draft_queued());
5923 assert!(!sys.is_vcs_state_changed());
5924
5925 let direct = sys
5926 .as_feedback_draft_queued()
5927 .expect("direct typed accessor");
5928 assert_eq!(direct.draft_id, "draft-1");
5929 assert_eq!(direct.draft_type, "bug_report");
5930 assert_eq!(direct.extra["future_field"], "preserved");
5931
5932 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
5933 panic!("expected FeedbackDraftQueued event");
5934 };
5935 assert_eq!(known.title, "Tool output was truncated");
5936 assert_eq!(
5937 sys.typed_value().expect("typed value")["future_field"],
5938 "preserved"
5939 );
5940 }
5941
5942 #[test]
5943 fn test_cloud_session_delta_fully_wrapped() {
5944 use super::{KnownSystemEvent, SystemSubtype};
5945 use serde_json::Value;
5946
5947 let raw: Value = serde_json::from_str(
5948 r#"{
5949 "type":"system","subtype":"cloud_session_delta",
5950 "seq":3,"changed":["serving","connection"],
5951 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5952 "serving":{"state":"on"},"connection":{"state":"live"}},
5953 "uuid":"u1","session_id":"s1","future_field":"preserved"
5954 }"#,
5955 )
5956 .unwrap();
5957 crate::io::assert_fully_wrapped(&raw);
5958
5959 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5960 let ClaudeOutput::System(sys) = output else {
5961 panic!("expected System");
5962 };
5963 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5964 assert!(sys.is_cloud_session_delta());
5965 assert!(!sys.is_feedback_draft_queued());
5966
5967 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5968 assert_eq!(direct.seq, 3);
5969 assert_eq!(direct.changed, vec!["serving", "connection"]);
5970 assert_eq!(direct.cloud_session["id"], "session_abc");
5971 assert_eq!(direct.extra["future_field"], "preserved");
5972
5973 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5974 panic!("expected CloudSessionDelta event");
5975 };
5976 assert_eq!(known.session_id, "s1");
5977 assert_eq!(
5978 sys.typed_value().expect("typed value")["future_field"],
5979 "preserved"
5980 );
5981 }
5982
5983 #[test]
5984 fn test_vcs_state_changed_fully_wrapped() {
5985 use super::{KnownSystemEvent, VcsMutationKind};
5986 use serde_json::Value;
5987
5988 for kind in ["commit", "push", "merge", "rebase"] {
5989 let raw: Value = serde_json::from_str(&format!(
5990 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5991 kind
5992 ))
5993 .unwrap();
5994 crate::io::assert_fully_wrapped(&raw);
5995
5996 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5997 let ClaudeOutput::System(sys) = output else {
5998 panic!("expected System");
5999 };
6000 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6001 panic!("expected VcsStateChanged event");
6002 };
6003 assert_eq!(msg.kind.as_str(), kind);
6004 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
6005 }
6006
6007 let raw: Value = serde_json::from_str(
6009 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
6010 )
6011 .unwrap();
6012 crate::io::assert_fully_wrapped(&raw);
6013 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6014 let ClaudeOutput::System(sys) = output else {
6015 panic!("expected System");
6016 };
6017 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6018 panic!("expected VcsStateChanged event");
6019 };
6020 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
6021
6022 assert!(sys.is_vcs_state_changed());
6023 let direct = sys.as_vcs_state_changed().expect("direct accessor");
6024 assert_eq!(direct.cwd, "/repo");
6025 assert!(sys.as_code_change_published().is_none());
6026 }
6027
6028 #[test]
6029 fn test_assistant_aborted_and_resume_flags_roundtrip() {
6030 let json = r#"{
6031 "type":"assistant",
6032 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
6033 "session_id":"s1",
6034 "aborted":true,
6035 "resumed_from_incomplete_thinking":true
6036 }"#;
6037 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6038 let ClaudeOutput::Assistant(msg) = &output else {
6039 panic!("expected Assistant");
6040 };
6041 assert_eq!(msg.aborted, Some(true));
6042 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
6043 let reserialized = serde_json::to_string(&output).unwrap();
6044 assert!(reserialized.contains("\"aborted\":true"));
6045 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
6046
6047 let json = r#"{
6049 "type":"assistant",
6050 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
6051 "session_id":"s2"
6052 }"#;
6053 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6054 let reserialized = serde_json::to_string(&output).unwrap();
6055 assert!(!reserialized.contains("aborted"));
6056 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
6057 }
6058
6059 #[test]
6060 fn test_user_tool_result_meta_roundtrip() {
6061 let json = r#"{
6062 "type":"user",
6063 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6064 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6065 "tool_result_meta":[
6066 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6067 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6068 ]
6069 }"#;
6070 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6071 let ClaudeOutput::User(user) = &output else {
6072 panic!("expected User");
6073 };
6074 let meta = user.tool_result_meta.as_ref().unwrap();
6075 assert_eq!(meta.len(), 2);
6076 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6077 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6078 assert_eq!(meta[1].user_feedback, None);
6079
6080 let reserialized = serde_json::to_string(&output).unwrap();
6081 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6082 assert!(!reserialized.contains("\"user_feedback\":null"));
6083 }
6084
6085 #[test]
6089 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6090 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6091 let with_scope = serde_json::json!({
6092 "trigger": "refusal",
6093 "direction": "retry",
6094 "scope": "local",
6095 "original_model": "claude-fable-5",
6096 "fallback_model": "claude-opus-5",
6097 "request_id": null,
6098 "content": "Refused; retried on fallback model.",
6099 "uuid": "u1",
6100 "session_id": "s1"
6101 });
6102 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6103 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6104 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6105
6106 let mut without = with_scope.clone();
6109 without.as_object_mut().unwrap().remove("scope");
6110 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6111 assert_eq!(msg.scope, None);
6112 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6113
6114 assert_eq!(
6116 RefusalFallbackScope::from("workspace").as_str(),
6117 "workspace"
6118 );
6119 }
6120}