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 Unknown(String),
54}
55
56impl SystemSubtype {
57 pub fn as_str(&self) -> &str {
58 match self {
59 Self::Init => "init",
60 Self::Status => "status",
61 Self::CompactBoundary => "compact_boundary",
62 Self::ThinkingTokens => "thinking_tokens",
63 Self::TaskStarted => "task_started",
64 Self::TaskProgress => "task_progress",
65 Self::TaskUpdated => "task_updated",
66 Self::TaskNotification => "task_notification",
67 Self::ApiRetry => "api_retry",
68 Self::ControlRequestProgress => "control_request_progress",
69 Self::ModelRefusalFallback => "model_refusal_fallback",
70 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
71 Self::LocalCommandOutput => "local_command_output",
72 Self::HookStarted => "hook_started",
73 Self::HookProgress => "hook_progress",
74 Self::HookResponse => "hook_response",
75 Self::PluginInstall => "plugin_install",
76 Self::BackgroundTasksChanged => "background_tasks_changed",
77 Self::SessionStateChanged => "session_state_changed",
78 Self::WorkerShuttingDown => "worker_shutting_down",
79 Self::CommandsChanged => "commands_changed",
80 Self::Notification => "notification",
81 Self::FilesPersisted => "files_persisted",
82 Self::MemoryRecall => "memory_recall",
83 Self::ElicitationComplete => "elicitation_complete",
84 Self::PermissionDenied => "permission_denied",
85 Self::MirrorError => "mirror_error",
86 Self::Informational => "informational",
87 Self::CodeChangePublished => "code_change_published",
88 Self::VcsStateChanged => "vcs_state_changed",
89 Self::FeedbackDraftQueued => "feedback_draft_queued",
90 Self::CloudSessionDelta => "cloud_session_delta",
91 Self::DevIntent => "dev_intent",
92 Self::TurnHandoffAvailable => "turn_handoff_available",
93 Self::TurnPreempted => "turn_preempted",
94 Self::PeerMessageHold => "peer_message_hold",
95 Self::Unknown(s) => s.as_str(),
96 }
97 }
98}
99
100impl fmt::Display for SystemSubtype {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.write_str(self.as_str())
103 }
104}
105
106impl From<&str> for SystemSubtype {
107 fn from(s: &str) -> Self {
108 match s {
109 "init" => Self::Init,
110 "status" => Self::Status,
111 "compact_boundary" => Self::CompactBoundary,
112 "thinking_tokens" => Self::ThinkingTokens,
113 "task_started" => Self::TaskStarted,
114 "task_progress" => Self::TaskProgress,
115 "task_updated" => Self::TaskUpdated,
116 "task_notification" => Self::TaskNotification,
117 "api_retry" => Self::ApiRetry,
118 "control_request_progress" => Self::ControlRequestProgress,
119 "model_refusal_fallback" => Self::ModelRefusalFallback,
120 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
121 "local_command_output" => Self::LocalCommandOutput,
122 "hook_started" => Self::HookStarted,
123 "hook_progress" => Self::HookProgress,
124 "hook_response" => Self::HookResponse,
125 "plugin_install" => Self::PluginInstall,
126 "background_tasks_changed" => Self::BackgroundTasksChanged,
127 "session_state_changed" => Self::SessionStateChanged,
128 "worker_shutting_down" => Self::WorkerShuttingDown,
129 "commands_changed" => Self::CommandsChanged,
130 "notification" => Self::Notification,
131 "files_persisted" => Self::FilesPersisted,
132 "memory_recall" => Self::MemoryRecall,
133 "elicitation_complete" => Self::ElicitationComplete,
134 "permission_denied" => Self::PermissionDenied,
135 "mirror_error" => Self::MirrorError,
136 "informational" => Self::Informational,
137 "code_change_published" => Self::CodeChangePublished,
138 "vcs_state_changed" => Self::VcsStateChanged,
139 "feedback_draft_queued" => Self::FeedbackDraftQueued,
140 "cloud_session_delta" => Self::CloudSessionDelta,
141 "dev_intent" => Self::DevIntent,
142 "turn_handoff_available" => Self::TurnHandoffAvailable,
143 "turn_preempted" => Self::TurnPreempted,
144 "peer_message_hold" => Self::PeerMessageHold,
145 other => Self::Unknown(other.to_string()),
146 }
147 }
148}
149
150impl Serialize for SystemSubtype {
151 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
152 serializer.serialize_str(self.as_str())
153 }
154}
155
156impl<'de> Deserialize<'de> for SystemSubtype {
157 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
158 let s = String::deserialize(deserializer)?;
159 Ok(Self::from(s.as_str()))
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub enum MessageRole {
169 User,
170 Assistant,
171 Unknown(String),
173}
174
175impl MessageRole {
176 pub fn as_str(&self) -> &str {
177 match self {
178 Self::User => "user",
179 Self::Assistant => "assistant",
180 Self::Unknown(s) => s.as_str(),
181 }
182 }
183}
184
185impl fmt::Display for MessageRole {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 f.write_str(self.as_str())
188 }
189}
190
191impl From<&str> for MessageRole {
192 fn from(s: &str) -> Self {
193 match s {
194 "user" => Self::User,
195 "assistant" => Self::Assistant,
196 other => Self::Unknown(other.to_string()),
197 }
198 }
199}
200
201impl Serialize for MessageRole {
202 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
203 serializer.serialize_str(self.as_str())
204 }
205}
206
207impl<'de> Deserialize<'de> for MessageRole {
208 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
209 let s = String::deserialize(deserializer)?;
210 Ok(Self::from(s.as_str()))
211 }
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Hash)]
216pub enum CompactionTrigger {
217 Auto,
219 Manual,
221 Unknown(String),
223}
224
225impl CompactionTrigger {
226 pub fn as_str(&self) -> &str {
227 match self {
228 Self::Auto => "auto",
229 Self::Manual => "manual",
230 Self::Unknown(s) => s.as_str(),
231 }
232 }
233}
234
235impl fmt::Display for CompactionTrigger {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 f.write_str(self.as_str())
238 }
239}
240
241impl From<&str> for CompactionTrigger {
242 fn from(s: &str) -> Self {
243 match s {
244 "auto" => Self::Auto,
245 "manual" => Self::Manual,
246 other => Self::Unknown(other.to_string()),
247 }
248 }
249}
250
251impl Serialize for CompactionTrigger {
252 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
253 serializer.serialize_str(self.as_str())
254 }
255}
256
257impl<'de> Deserialize<'de> for CompactionTrigger {
258 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
259 let s = String::deserialize(deserializer)?;
260 Ok(Self::from(s.as_str()))
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Eq, Hash)]
266pub enum StopReason {
267 EndTurn,
269 MaxTokens,
271 ToolUse,
273 Unknown(String),
275}
276
277impl StopReason {
278 pub fn as_str(&self) -> &str {
279 match self {
280 Self::EndTurn => "end_turn",
281 Self::MaxTokens => "max_tokens",
282 Self::ToolUse => "tool_use",
283 Self::Unknown(s) => s.as_str(),
284 }
285 }
286}
287
288impl fmt::Display for StopReason {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 f.write_str(self.as_str())
291 }
292}
293
294impl From<&str> for StopReason {
295 fn from(s: &str) -> Self {
296 match s {
297 "end_turn" => Self::EndTurn,
298 "max_tokens" => Self::MaxTokens,
299 "tool_use" => Self::ToolUse,
300 other => Self::Unknown(other.to_string()),
301 }
302 }
303}
304
305impl Serialize for StopReason {
306 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
307 serializer.serialize_str(self.as_str())
308 }
309}
310
311impl<'de> Deserialize<'de> for StopReason {
312 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
313 let s = String::deserialize(deserializer)?;
314 Ok(Self::from(s.as_str()))
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Hash)]
320pub enum ApiKeySource {
321 None,
323 User,
324 Project,
325 Org,
326 Temporary,
327 Oauth,
328 Unknown(String),
330}
331
332impl ApiKeySource {
333 pub fn as_str(&self) -> &str {
334 match self {
335 Self::None => "none",
336 Self::User => "user",
337 Self::Project => "project",
338 Self::Org => "org",
339 Self::Temporary => "temporary",
340 Self::Oauth => "oauth",
341 Self::Unknown(s) => s.as_str(),
342 }
343 }
344}
345
346impl fmt::Display for ApiKeySource {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 f.write_str(self.as_str())
349 }
350}
351
352impl From<&str> for ApiKeySource {
353 fn from(s: &str) -> Self {
354 match s {
355 "none" => Self::None,
356 "user" => Self::User,
357 "project" => Self::Project,
358 "org" => Self::Org,
359 "temporary" => Self::Temporary,
360 "oauth" => Self::Oauth,
361 other => Self::Unknown(other.to_string()),
362 }
363 }
364}
365
366impl Serialize for ApiKeySource {
367 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
368 serializer.serialize_str(self.as_str())
369 }
370}
371
372impl<'de> Deserialize<'de> for ApiKeySource {
373 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
374 let s = String::deserialize(deserializer)?;
375 Ok(Self::from(s.as_str()))
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, Hash)]
381pub enum OutputStyle {
382 Default,
384 Unknown(String),
386}
387
388impl OutputStyle {
389 pub fn as_str(&self) -> &str {
390 match self {
391 Self::Default => "default",
392 Self::Unknown(s) => s.as_str(),
393 }
394 }
395}
396
397impl fmt::Display for OutputStyle {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 f.write_str(self.as_str())
400 }
401}
402
403impl From<&str> for OutputStyle {
404 fn from(s: &str) -> Self {
405 match s {
406 "default" => Self::Default,
407 other => Self::Unknown(other.to_string()),
408 }
409 }
410}
411
412impl Serialize for OutputStyle {
413 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
414 serializer.serialize_str(self.as_str())
415 }
416}
417
418impl<'de> Deserialize<'de> for OutputStyle {
419 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
420 let s = String::deserialize(deserializer)?;
421 Ok(Self::from(s.as_str()))
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Hash)]
427pub enum InitPermissionMode {
428 Default,
430 AcceptEdits,
431 BypassPermissions,
432 Plan,
433 DontAsk,
434 Auto,
435 Unknown(String),
437}
438
439impl InitPermissionMode {
440 pub fn as_str(&self) -> &str {
441 match self {
442 Self::Default => "default",
443 Self::AcceptEdits => "acceptEdits",
444 Self::BypassPermissions => "bypassPermissions",
445 Self::Plan => "plan",
446 Self::DontAsk => "dontAsk",
447 Self::Auto => "auto",
448 Self::Unknown(s) => s.as_str(),
449 }
450 }
451}
452
453impl fmt::Display for InitPermissionMode {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 f.write_str(self.as_str())
456 }
457}
458
459impl From<&str> for InitPermissionMode {
460 fn from(s: &str) -> Self {
461 match s {
462 "default" => Self::Default,
463 "acceptEdits" => Self::AcceptEdits,
464 "bypassPermissions" => Self::BypassPermissions,
465 "plan" => Self::Plan,
466 "dontAsk" => Self::DontAsk,
467 "auto" => Self::Auto,
468 other => Self::Unknown(other.to_string()),
469 }
470 }
471}
472
473impl Serialize for InitPermissionMode {
474 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
475 serializer.serialize_str(self.as_str())
476 }
477}
478
479impl<'de> Deserialize<'de> for InitPermissionMode {
480 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
481 let s = String::deserialize(deserializer)?;
482 Ok(Self::from(s.as_str()))
483 }
484}
485
486#[derive(Debug, Clone, PartialEq, Eq, Hash)]
488pub enum StatusMessageStatus {
489 Compacting,
491 Requesting,
493 Unknown(String),
495}
496
497impl StatusMessageStatus {
498 pub fn as_str(&self) -> &str {
499 match self {
500 Self::Compacting => "compacting",
501 Self::Requesting => "requesting",
502 Self::Unknown(s) => s.as_str(),
503 }
504 }
505}
506
507impl fmt::Display for StatusMessageStatus {
508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509 f.write_str(self.as_str())
510 }
511}
512
513impl From<&str> for StatusMessageStatus {
514 fn from(s: &str) -> Self {
515 match s {
516 "compacting" => Self::Compacting,
517 "requesting" => Self::Requesting,
518 other => Self::Unknown(other.to_string()),
519 }
520 }
521}
522
523impl Serialize for StatusMessageStatus {
524 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
525 serializer.serialize_str(self.as_str())
526 }
527}
528
529impl<'de> Deserialize<'de> for StatusMessageStatus {
530 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
531 let s = String::deserialize(deserializer)?;
532 Ok(Self::from(s.as_str()))
533 }
534}
535
536pub(crate) fn serialize_optional_uuid<S>(
538 uuid: &Option<Uuid>,
539 serializer: S,
540) -> Result<S::Ok, S::Error>
541where
542 S: Serializer,
543{
544 match uuid {
545 Some(id) => serializer.serialize_str(&id.to_string()),
546 None => serializer.serialize_none(),
547 }
548}
549
550pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
552where
553 D: Deserializer<'de>,
554{
555 let opt_str: Option<String> = Option::deserialize(deserializer)?;
556 match opt_str {
557 Some(s) => Uuid::parse_str(&s)
558 .map(Some)
559 .map_err(serde::de::Error::custom),
560 None => Ok(None),
561 }
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
567pub struct MessageOrigin {
568 pub kind: String,
569 #[serde(flatten)]
570 pub extra: serde_json::Map<String, Value>,
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
575pub struct SummarizeMetadata {
576 pub messages_summarized: u64,
577 #[serde(default, skip_serializing_if = "Option::is_none")]
578 pub user_context: Option<String>,
579 #[serde(default, skip_serializing_if = "Option::is_none")]
580 pub direction: Option<String>,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
585pub struct McpMeta {
586 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
587 pub meta: Option<Value>,
588 #[serde(default, skip_serializing_if = "Option::is_none")]
589 pub structured_content: Option<Value>,
590 #[serde(default, skip_serializing_if = "Vec::is_empty")]
595 pub resource_links: Vec<ResourceLink>,
596}
597
598#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
602pub struct ResourceLink {
603 pub uri: String,
604 pub name: String,
605 #[serde(default, skip_serializing_if = "Option::is_none")]
606 pub title: Option<String>,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
608 pub description: Option<String>,
609 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
610 pub mime_type: Option<String>,
611 #[serde(default, skip_serializing_if = "Option::is_none")]
612 pub size: Option<u64>,
613 #[serde(default, skip_serializing_if = "Option::is_none")]
614 pub annotations: Option<Value>,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
619pub struct ToolResultMeta {
620 pub id: String,
622 #[serde(default, skip_serializing_if = "Option::is_none")]
629 pub non_execution_kind: Option<String>,
630 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub user_feedback: Option<String>,
633 #[serde(default, skip_serializing_if = "Option::is_none")]
638 pub remedy: Option<ToolResultRemedy>,
639}
640
641#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
646pub struct ToolResultRemedy {
647 pub kind: ToolResultRemedyKind,
650 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub servers: Option<Vec<String>>,
655 #[serde(default, skip_serializing_if = "Option::is_none")]
658 pub provider: Option<RemedyLoginProvider>,
659 #[serde(default, skip_serializing_if = "Option::is_none")]
663 pub path: Option<String>,
664 #[serde(default, skip_serializing_if = "Option::is_none")]
669 pub managed: Option<bool>,
670 #[serde(default, skip_serializing_if = "Option::is_none")]
672 pub feature: Option<RemedyFeature>,
673 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub cause: Option<RemedyFeatureCause>,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub policy_kind: Option<RemedyPolicyKind>,
679}
680
681#[derive(Debug, Clone, PartialEq, Eq, Hash)]
683pub enum ToolResultRemedyKind {
684 McpNeedsAuth,
686 McpDisabled,
688 McpRequiredMissing,
690 AuthExpired,
692 AuthOverridden,
694 AuthMissingScope,
696 DesignNeedsAuthorization,
698 SandboxViolation,
700 OutsideReadsBlocked,
702 MemoryPaused,
704 FeatureDisabled,
706 PolicyDenied,
709 SpawnArgLimit,
711 StagedForReview,
713 Unknown(String),
715}
716
717impl ToolResultRemedyKind {
718 pub fn as_str(&self) -> &str {
719 match self {
720 Self::McpNeedsAuth => "mcp_needs_auth",
721 Self::McpDisabled => "mcp_disabled",
722 Self::McpRequiredMissing => "mcp_required_missing",
723 Self::AuthExpired => "auth_expired",
724 Self::AuthOverridden => "auth_overridden",
725 Self::AuthMissingScope => "auth_missing_scope",
726 Self::DesignNeedsAuthorization => "design_needs_authorization",
727 Self::SandboxViolation => "sandbox_violation",
728 Self::OutsideReadsBlocked => "outside_reads_blocked",
729 Self::MemoryPaused => "memory_paused",
730 Self::FeatureDisabled => "feature_disabled",
731 Self::PolicyDenied => "policy_denied",
732 Self::SpawnArgLimit => "spawn_arg_limit",
733 Self::StagedForReview => "staged_for_review",
734 Self::Unknown(s) => s.as_str(),
735 }
736 }
737}
738
739impl fmt::Display for ToolResultRemedyKind {
740 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741 f.write_str(self.as_str())
742 }
743}
744
745impl From<&str> for ToolResultRemedyKind {
746 fn from(s: &str) -> Self {
747 match s {
748 "mcp_needs_auth" => Self::McpNeedsAuth,
749 "mcp_disabled" => Self::McpDisabled,
750 "mcp_required_missing" => Self::McpRequiredMissing,
751 "auth_expired" => Self::AuthExpired,
752 "auth_overridden" => Self::AuthOverridden,
753 "auth_missing_scope" => Self::AuthMissingScope,
754 "design_needs_authorization" => Self::DesignNeedsAuthorization,
755 "sandbox_violation" => Self::SandboxViolation,
756 "outside_reads_blocked" => Self::OutsideReadsBlocked,
757 "memory_paused" => Self::MemoryPaused,
758 "feature_disabled" => Self::FeatureDisabled,
759 "policy_denied" => Self::PolicyDenied,
760 "spawn_arg_limit" => Self::SpawnArgLimit,
761 "staged_for_review" => Self::StagedForReview,
762 other => Self::Unknown(other.to_string()),
763 }
764 }
765}
766
767impl Serialize for ToolResultRemedyKind {
768 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
769 serializer.serialize_str(self.as_str())
770 }
771}
772
773impl<'de> Deserialize<'de> for ToolResultRemedyKind {
774 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
775 let s = String::deserialize(deserializer)?;
776 Ok(Self::from(s.as_str()))
777 }
778}
779
780#[derive(Debug, Clone, PartialEq, Eq, Hash)]
782pub enum RemedyLoginProvider {
783 ClaudeAi,
785 ClaudeDesign,
787 Unknown(String),
789}
790
791impl RemedyLoginProvider {
792 pub fn as_str(&self) -> &str {
793 match self {
794 Self::ClaudeAi => "claude_ai",
795 Self::ClaudeDesign => "claude_design",
796 Self::Unknown(s) => s.as_str(),
797 }
798 }
799}
800
801impl fmt::Display for RemedyLoginProvider {
802 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
803 f.write_str(self.as_str())
804 }
805}
806
807impl From<&str> for RemedyLoginProvider {
808 fn from(s: &str) -> Self {
809 match s {
810 "claude_ai" => Self::ClaudeAi,
811 "claude_design" => Self::ClaudeDesign,
812 other => Self::Unknown(other.to_string()),
813 }
814 }
815}
816
817impl Serialize for RemedyLoginProvider {
818 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
819 serializer.serialize_str(self.as_str())
820 }
821}
822
823impl<'de> Deserialize<'de> for RemedyLoginProvider {
824 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
825 let s = String::deserialize(deserializer)?;
826 Ok(Self::from(s.as_str()))
827 }
828}
829
830#[derive(Debug, Clone, PartialEq, Eq, Hash)]
833pub enum RemedyFeature {
834 Workflows,
835 Artifacts,
836 Unknown(String),
838}
839
840impl RemedyFeature {
841 pub fn as_str(&self) -> &str {
842 match self {
843 Self::Workflows => "workflows",
844 Self::Artifacts => "artifacts",
845 Self::Unknown(s) => s.as_str(),
846 }
847 }
848}
849
850impl fmt::Display for RemedyFeature {
851 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
852 f.write_str(self.as_str())
853 }
854}
855
856impl From<&str> for RemedyFeature {
857 fn from(s: &str) -> Self {
858 match s {
859 "workflows" => Self::Workflows,
860 "artifacts" => Self::Artifacts,
861 other => Self::Unknown(other.to_string()),
862 }
863 }
864}
865
866impl Serialize for RemedyFeature {
867 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
868 serializer.serialize_str(self.as_str())
869 }
870}
871
872impl<'de> Deserialize<'de> for RemedyFeature {
873 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
874 let s = String::deserialize(deserializer)?;
875 Ok(Self::from(s.as_str()))
876 }
877}
878
879#[derive(Debug, Clone, PartialEq, Eq, Hash)]
882pub enum RemedyFeatureCause {
883 ManagedSettings,
886 OrgPolicy,
888 Unavailable,
890 UserSetting,
893 Unknown(String),
895}
896
897impl RemedyFeatureCause {
898 pub fn as_str(&self) -> &str {
899 match self {
900 Self::ManagedSettings => "managed_settings",
901 Self::OrgPolicy => "org_policy",
902 Self::Unavailable => "unavailable",
903 Self::UserSetting => "user_setting",
904 Self::Unknown(s) => s.as_str(),
905 }
906 }
907}
908
909impl fmt::Display for RemedyFeatureCause {
910 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911 f.write_str(self.as_str())
912 }
913}
914
915impl From<&str> for RemedyFeatureCause {
916 fn from(s: &str) -> Self {
917 match s {
918 "managed_settings" => Self::ManagedSettings,
919 "org_policy" => Self::OrgPolicy,
920 "unavailable" => Self::Unavailable,
921 "user_setting" => Self::UserSetting,
922 other => Self::Unknown(other.to_string()),
923 }
924 }
925}
926
927impl Serialize for RemedyFeatureCause {
928 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
929 serializer.serialize_str(self.as_str())
930 }
931}
932
933impl<'de> Deserialize<'de> for RemedyFeatureCause {
934 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
935 let s = String::deserialize(deserializer)?;
936 Ok(Self::from(s.as_str()))
937 }
938}
939
940#[derive(Debug, Clone, PartialEq, Eq, Hash)]
943pub enum RemedyPolicyKind {
944 OrgDenied,
946 Latched,
949 Unknown(String),
951}
952
953impl RemedyPolicyKind {
954 pub fn as_str(&self) -> &str {
955 match self {
956 Self::OrgDenied => "org_denied",
957 Self::Latched => "latched",
958 Self::Unknown(s) => s.as_str(),
959 }
960 }
961}
962
963impl fmt::Display for RemedyPolicyKind {
964 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
965 f.write_str(self.as_str())
966 }
967}
968
969impl From<&str> for RemedyPolicyKind {
970 fn from(s: &str) -> Self {
971 match s {
972 "org_denied" => Self::OrgDenied,
973 "latched" => Self::Latched,
974 other => Self::Unknown(other.to_string()),
975 }
976 }
977}
978
979impl Serialize for RemedyPolicyKind {
980 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
981 serializer.serialize_str(self.as_str())
982 }
983}
984
985impl<'de> Deserialize<'de> for RemedyPolicyKind {
986 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
987 let s = String::deserialize(deserializer)?;
988 Ok(Self::from(s.as_str()))
989 }
990}
991
992#[derive(Debug, Clone, Serialize, Deserialize)]
994pub struct UserMessage {
995 pub message: MessageContent,
996 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
997 #[serde(
998 serialize_with = "serialize_optional_uuid",
999 deserialize_with = "deserialize_optional_uuid"
1000 )]
1001 pub session_id: Option<Uuid>,
1002 #[serde(skip_serializing_if = "Option::is_none")]
1004 pub parent_tool_use_id: Option<String>,
1005 #[serde(skip_serializing_if = "Option::is_none")]
1007 pub uuid: Option<String>,
1008 #[serde(skip_serializing_if = "Option::is_none")]
1010 pub timestamp: Option<String>,
1011 #[serde(skip_serializing_if = "Option::is_none")]
1018 pub tool_use_result: Option<serde_json::Value>,
1019 #[serde(skip_serializing_if = "Option::is_none")]
1022 pub subagent_type: Option<String>,
1023 #[serde(skip_serializing_if = "Option::is_none")]
1025 pub task_description: Option<String>,
1026 #[serde(skip_serializing_if = "Option::is_none")]
1027 pub origin: Option<MessageOrigin>,
1028 #[serde(skip_serializing_if = "Option::is_none")]
1029 pub priority: Option<String>,
1030 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1031 pub is_synthetic: Option<bool>,
1032 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1033 pub should_query: Option<bool>,
1034 #[serde(default, skip_serializing_if = "Option::is_none")]
1035 pub is_meta: Option<bool>,
1036 #[serde(default, skip_serializing_if = "Option::is_none")]
1037 pub is_visible_in_transcript_only: Option<bool>,
1038 #[serde(default, skip_serializing_if = "Option::is_none")]
1039 pub is_virtual: Option<bool>,
1040 #[serde(default, skip_serializing_if = "Option::is_none")]
1041 pub is_compact_summary: Option<bool>,
1042 #[serde(skip_serializing_if = "Option::is_none")]
1043 pub summarize_metadata: Option<SummarizeMetadata>,
1044 #[serde(skip_serializing_if = "Option::is_none")]
1045 pub mcp_meta: Option<McpMeta>,
1046 #[serde(skip_serializing_if = "Option::is_none")]
1049 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1050 #[serde(skip_serializing_if = "Option::is_none")]
1051 pub source_tool_use_id: Option<String>,
1052 #[serde(skip_serializing_if = "Option::is_none")]
1053 pub source_tool_assistant_uuid: Option<String>,
1054 #[serde(skip_serializing_if = "Option::is_none")]
1055 pub image_paste_ids: Option<Vec<u64>>,
1056 #[serde(skip_serializing_if = "Option::is_none")]
1057 pub client_platform: Option<String>,
1058 #[serde(skip_serializing_if = "Option::is_none")]
1059 pub inbound_origin: Option<String>,
1060 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1061 pub is_replay: Option<bool>,
1062 #[serde(skip_serializing_if = "Option::is_none")]
1063 pub file_attachments: Option<Vec<Value>>,
1064 #[serde(default, skip_serializing_if = "Option::is_none")]
1066 pub seeded_summon: Option<bool>,
1067 #[serde(default, skip_serializing_if = "Option::is_none")]
1070 pub client_composed: Option<bool>,
1071 #[serde(default, skip_serializing_if = "Option::is_none")]
1075 pub historical: Option<bool>,
1076}
1077
1078impl UserMessage {
1079 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1087 &self,
1088 ) -> Option<Result<T, serde_json::Error>> {
1089 self.tool_use_result
1090 .as_ref()
1091 .map(|v| serde_json::from_value(v.clone()))
1092 }
1093
1094 pub fn subagent_result(&self) -> Option<SubagentResult> {
1105 self.tool_use_result
1106 .as_ref()
1107 .and_then(|v| serde_json::from_value(v.clone()).ok())
1108 }
1109}
1110
1111#[derive(Debug, Clone, Serialize, Deserialize)]
1122pub struct SubagentResult {
1123 #[serde(skip_serializing_if = "Option::is_none")]
1125 pub status: Option<String>,
1126 #[serde(skip_serializing_if = "Option::is_none")]
1128 pub prompt: Option<String>,
1129 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1131 pub agent_id: Option<String>,
1132 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1134 pub agent_type: Option<String>,
1135 #[serde(
1137 default,
1138 deserialize_with = "deserialize_content_blocks",
1139 skip_serializing_if = "Vec::is_empty"
1140 )]
1141 pub content: Vec<ContentBlock>,
1142 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1144 pub resolved_model: Option<String>,
1145 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1147 pub total_duration_ms: Option<u64>,
1148 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1150 pub total_tokens: Option<u64>,
1151 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1153 pub total_tool_use_count: Option<u64>,
1154 #[serde(skip_serializing_if = "Option::is_none")]
1156 pub usage: Option<super::result::UsageInfo>,
1157 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1159 pub tool_stats: Option<SubagentToolStats>,
1160}
1161
1162#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1167#[serde(rename_all = "camelCase")]
1168pub struct SubagentToolStats {
1169 #[serde(default)]
1170 pub read_count: u64,
1171 #[serde(default)]
1172 pub search_count: u64,
1173 #[serde(default)]
1174 pub bash_count: u64,
1175 #[serde(default)]
1176 pub edit_file_count: u64,
1177 #[serde(default)]
1178 pub lines_added: u64,
1179 #[serde(default)]
1180 pub lines_removed: u64,
1181 #[serde(default)]
1182 pub other_tool_count: u64,
1183 #[serde(flatten)]
1184 pub extra: serde_json::Map<String, Value>,
1185}
1186
1187#[derive(Debug, Clone, Default, PartialEq, Eq)]
1214pub struct SubagentUsageRollup {
1215 pub subagent_tokens: u64,
1218 pub agent_count: u32,
1220 pub tool_uses: u64,
1222 pub duration_ms: u64,
1224 seen_agent_ids: std::collections::BTreeSet<String>,
1225}
1226
1227impl SubagentUsageRollup {
1228 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1234 match output {
1235 ClaudeOutput::User(user) => self.observe_user(user),
1236 _ => false,
1237 }
1238 }
1239
1240 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1247 let Some(result) = user.subagent_result() else {
1248 return false;
1249 };
1250 if result.total_tokens.is_none() && result.agent_id.is_none() {
1251 return false;
1252 }
1253 if let Some(agent_id) = &result.agent_id {
1254 if !self.seen_agent_ids.insert(agent_id.clone()) {
1255 return false;
1256 }
1257 }
1258 self.agent_count += 1;
1259 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1260 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1261 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1262 true
1263 }
1264}
1265
1266#[derive(Debug, Clone, Serialize, Deserialize)]
1268pub struct MessageContent {
1269 pub role: MessageRole,
1270 #[serde(deserialize_with = "deserialize_content_blocks")]
1271 pub content: Vec<ContentBlock>,
1272}
1273
1274#[derive(Debug, Clone, Serialize, Deserialize)]
1276pub struct SystemMessage {
1277 pub subtype: SystemSubtype,
1278 #[serde(flatten)]
1279 pub data: Value, }
1281
1282impl SystemMessage {
1283 pub fn is_init(&self) -> bool {
1285 self.subtype == SystemSubtype::Init
1286 }
1287
1288 pub fn is_status(&self) -> bool {
1290 self.subtype == SystemSubtype::Status
1291 }
1292
1293 pub fn is_compact_boundary(&self) -> bool {
1295 self.subtype == SystemSubtype::CompactBoundary
1296 }
1297
1298 pub fn as_init(&self) -> Option<InitMessage> {
1300 if self.subtype != SystemSubtype::Init {
1301 return None;
1302 }
1303 serde_json::from_value(self.data.clone()).ok()
1304 }
1305
1306 pub fn as_status(&self) -> Option<StatusMessage> {
1308 if self.subtype != SystemSubtype::Status {
1309 return None;
1310 }
1311 serde_json::from_value(self.data.clone()).ok()
1312 }
1313
1314 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1316 if self.subtype != SystemSubtype::CompactBoundary {
1317 return None;
1318 }
1319 serde_json::from_value(self.data.clone()).ok()
1320 }
1321
1322 pub fn is_task_started(&self) -> bool {
1324 self.subtype == SystemSubtype::TaskStarted
1325 }
1326
1327 pub fn is_task_progress(&self) -> bool {
1329 self.subtype == SystemSubtype::TaskProgress
1330 }
1331
1332 pub fn is_task_notification(&self) -> bool {
1334 self.subtype == SystemSubtype::TaskNotification
1335 }
1336
1337 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1339 if self.subtype != SystemSubtype::TaskStarted {
1340 return None;
1341 }
1342 serde_json::from_value(self.data.clone()).ok()
1343 }
1344
1345 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1347 if self.subtype != SystemSubtype::TaskProgress {
1348 return None;
1349 }
1350 serde_json::from_value(self.data.clone()).ok()
1351 }
1352
1353 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1355 if self.subtype != SystemSubtype::TaskNotification {
1356 return None;
1357 }
1358 serde_json::from_value(self.data.clone()).ok()
1359 }
1360
1361 pub fn is_task_updated(&self) -> bool {
1363 self.subtype == SystemSubtype::TaskUpdated
1364 }
1365
1366 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1368 if self.subtype != SystemSubtype::TaskUpdated {
1369 return None;
1370 }
1371 serde_json::from_value(self.data.clone()).ok()
1372 }
1373
1374 pub fn is_thinking_tokens(&self) -> bool {
1376 self.subtype == SystemSubtype::ThinkingTokens
1377 }
1378
1379 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1381 if self.subtype != SystemSubtype::ThinkingTokens {
1382 return None;
1383 }
1384 serde_json::from_value(self.data.clone()).ok()
1385 }
1386
1387 pub fn is_code_change_published(&self) -> bool {
1389 self.subtype == SystemSubtype::CodeChangePublished
1390 }
1391
1392 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1394 if self.subtype != SystemSubtype::CodeChangePublished {
1395 return None;
1396 }
1397 serde_json::from_value(self.data.clone()).ok()
1398 }
1399
1400 pub fn is_vcs_state_changed(&self) -> bool {
1402 self.subtype == SystemSubtype::VcsStateChanged
1403 }
1404
1405 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1407 if self.subtype != SystemSubtype::VcsStateChanged {
1408 return None;
1409 }
1410 serde_json::from_value(self.data.clone()).ok()
1411 }
1412
1413 pub fn is_feedback_draft_queued(&self) -> bool {
1415 self.subtype == SystemSubtype::FeedbackDraftQueued
1416 }
1417
1418 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1420 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1421 return None;
1422 }
1423 serde_json::from_value(self.data.clone()).ok()
1424 }
1425
1426 pub fn is_cloud_session_delta(&self) -> bool {
1428 self.subtype == SystemSubtype::CloudSessionDelta
1429 }
1430
1431 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1433 if self.subtype != SystemSubtype::CloudSessionDelta {
1434 return None;
1435 }
1436 serde_json::from_value(self.data.clone()).ok()
1437 }
1438
1439 pub fn is_dev_intent(&self) -> bool {
1441 self.subtype == SystemSubtype::DevIntent
1442 }
1443
1444 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1446 if self.subtype != SystemSubtype::DevIntent {
1447 return None;
1448 }
1449 serde_json::from_value(self.data.clone()).ok()
1450 }
1451
1452 pub fn is_turn_handoff_available(&self) -> bool {
1454 self.subtype == SystemSubtype::TurnHandoffAvailable
1455 }
1456
1457 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1459 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1460 return None;
1461 }
1462 serde_json::from_value(self.data.clone()).ok()
1463 }
1464
1465 pub fn is_turn_preempted(&self) -> bool {
1467 self.subtype == SystemSubtype::TurnPreempted
1468 }
1469
1470 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1472 if self.subtype != SystemSubtype::TurnPreempted {
1473 return None;
1474 }
1475 serde_json::from_value(self.data.clone()).ok()
1476 }
1477
1478 pub fn is_peer_message_hold(&self) -> bool {
1480 self.subtype == SystemSubtype::PeerMessageHold
1481 }
1482
1483 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1485 if self.subtype != SystemSubtype::PeerMessageHold {
1486 return None;
1487 }
1488 serde_json::from_value(self.data.clone()).ok()
1489 }
1490
1491 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1493 macro_rules! parse {
1494 ($variant:ident, $ty:ty) => {
1495 serde_json::from_value::<$ty>(self.data.clone())
1496 .ok()
1497 .map(KnownSystemEvent::$variant)
1498 };
1499 }
1500
1501 match self.subtype {
1502 SystemSubtype::Init => parse!(Init, InitMessage),
1503 SystemSubtype::Status => parse!(Status, StatusMessage),
1504 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1505 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1506 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1507 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1508 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1509 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1510 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1511 SystemSubtype::ControlRequestProgress => {
1512 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1513 }
1514 SystemSubtype::ModelRefusalFallback => {
1515 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1516 }
1517 SystemSubtype::ModelRefusalNoFallback => {
1518 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1519 }
1520 SystemSubtype::LocalCommandOutput => {
1521 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1522 }
1523 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1524 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1525 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1526 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1527 SystemSubtype::BackgroundTasksChanged => {
1528 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1529 }
1530 SystemSubtype::SessionStateChanged => {
1531 parse!(SessionStateChanged, SessionStateChangedMessage)
1532 }
1533 SystemSubtype::WorkerShuttingDown => {
1534 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1535 }
1536 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1537 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1538 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1539 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1540 SystemSubtype::ElicitationComplete => {
1541 parse!(ElicitationComplete, ElicitationCompleteMessage)
1542 }
1543 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1544 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1545 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1546 SystemSubtype::CodeChangePublished => {
1547 parse!(CodeChangePublished, CodeChangePublishedMessage)
1548 }
1549 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1550 SystemSubtype::FeedbackDraftQueued => {
1551 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1552 }
1553 SystemSubtype::CloudSessionDelta => {
1554 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1555 }
1556 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1557 SystemSubtype::TurnHandoffAvailable => {
1558 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1559 }
1560 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1561 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1562 SystemSubtype::Unknown(_) => None,
1563 }
1564 }
1565
1566 pub fn typed_value(&self) -> Option<Value> {
1575 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1576 parsed.and_then(|v| serde_json::to_value(v).ok())
1577 }
1578 match self.subtype {
1579 SystemSubtype::Init => reserialize(self.as_init()),
1580 SystemSubtype::Status => reserialize(self.as_status()),
1581 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1582 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1583 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1584 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1585 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1586 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1587 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1588 SystemSubtype::ControlRequestProgress => {
1589 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1590 }
1591 SystemSubtype::ModelRefusalFallback => {
1592 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1593 }
1594 SystemSubtype::ModelRefusalNoFallback => {
1595 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1596 }
1597 SystemSubtype::LocalCommandOutput => {
1598 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1599 }
1600 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1601 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1602 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1603 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1604 SystemSubtype::BackgroundTasksChanged => {
1605 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1606 }
1607 SystemSubtype::SessionStateChanged => {
1608 reserialize(parse_system::<SessionStateChangedMessage>(self))
1609 }
1610 SystemSubtype::WorkerShuttingDown => {
1611 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1612 }
1613 SystemSubtype::CommandsChanged => {
1614 reserialize(parse_system::<CommandsChangedMessage>(self))
1615 }
1616 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1617 SystemSubtype::FilesPersisted => {
1618 reserialize(parse_system::<FilesPersistedMessage>(self))
1619 }
1620 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1621 SystemSubtype::ElicitationComplete => {
1622 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1623 }
1624 SystemSubtype::PermissionDenied => {
1625 reserialize(parse_system::<PermissionDeniedMessage>(self))
1626 }
1627 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1628 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1629 SystemSubtype::CodeChangePublished => {
1630 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1631 }
1632 SystemSubtype::VcsStateChanged => {
1633 reserialize(parse_system::<VcsStateChangedMessage>(self))
1634 }
1635 SystemSubtype::FeedbackDraftQueued => {
1636 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1637 }
1638 SystemSubtype::CloudSessionDelta => {
1639 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1640 }
1641 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1642 SystemSubtype::TurnHandoffAvailable => {
1643 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1644 }
1645 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1646 SystemSubtype::PeerMessageHold => {
1647 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1648 }
1649 SystemSubtype::Unknown(_) => None,
1650 }
1651 }
1652}
1653
1654fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1655 serde_json::from_value(message.data.clone()).ok()
1656}
1657
1658#[allow(clippy::large_enum_variant)]
1664#[derive(Debug, Clone, Serialize, Deserialize)]
1665pub enum KnownSystemEvent {
1666 Init(InitMessage),
1667 Status(StatusMessage),
1668 CompactBoundary(CompactBoundaryMessage),
1669 ThinkingTokens(ThinkingTokensMessage),
1670 TaskStarted(TaskStartedMessage),
1671 TaskProgress(TaskProgressMessage),
1672 TaskUpdated(TaskUpdatedMessage),
1673 TaskNotification(TaskNotificationMessage),
1674 ApiRetry(ApiRetryMessage),
1675 ControlRequestProgress(ControlRequestProgressMessage),
1676 ModelRefusalFallback(ModelRefusalFallbackMessage),
1677 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1678 LocalCommandOutput(LocalCommandOutputMessage),
1679 HookStarted(HookStartedMessage),
1680 HookProgress(HookProgressMessage),
1681 HookResponse(HookResponseMessage),
1682 PluginInstall(PluginInstallMessage),
1683 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1684 SessionStateChanged(SessionStateChangedMessage),
1685 WorkerShuttingDown(WorkerShuttingDownMessage),
1686 CommandsChanged(CommandsChangedMessage),
1687 Notification(NotificationMessage),
1688 FilesPersisted(FilesPersistedMessage),
1689 MemoryRecall(MemoryRecallMessage),
1690 ElicitationComplete(ElicitationCompleteMessage),
1691 PermissionDenied(PermissionDeniedMessage),
1692 MirrorError(MirrorErrorMessage),
1693 Informational(InformationalMessage),
1694 CodeChangePublished(CodeChangePublishedMessage),
1695 VcsStateChanged(VcsStateChangedMessage),
1696 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1697 CloudSessionDelta(CloudSessionDeltaMessage),
1698 DevIntent(DevIntentMessage),
1699 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1700 TurnPreempted(TurnPreemptedMessage),
1701 PeerMessageHold(PeerMessageHoldMessage),
1702}
1703
1704#[derive(Debug, Clone, Serialize, Deserialize)]
1705pub struct ApiRetryMessage {
1706 pub attempt: u64,
1707 pub max_retries: u64,
1708 pub retry_delay_ms: u64,
1709 pub error_status: Option<u16>,
1710 pub error: String,
1711 #[serde(default, skip_serializing_if = "Option::is_none")]
1716 pub no_response: Option<ApiRetryNoResponse>,
1717 #[serde(default, skip_serializing_if = "Option::is_none")]
1718 pub uuid: Option<String>,
1719 #[serde(default, skip_serializing_if = "Option::is_none")]
1720 pub session_id: Option<String>,
1721}
1722
1723#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1726pub struct ApiRetryNoResponse {
1727 pub waited_ms: u64,
1729 pub retry_wait_ms: u64,
1731}
1732
1733#[derive(Debug, Clone, Serialize, Deserialize)]
1734pub struct ControlRequestProgressMessage {
1735 pub request_id: String,
1736 pub status: String,
1737 #[serde(default, skip_serializing_if = "Option::is_none")]
1738 pub attempt: Option<u64>,
1739 #[serde(default, skip_serializing_if = "Option::is_none")]
1740 pub max_retries: Option<u64>,
1741 #[serde(default, skip_serializing_if = "Option::is_none")]
1742 pub retry_delay_ms: Option<u64>,
1743 #[serde(default, skip_serializing_if = "Option::is_none")]
1744 pub error_status: Option<u16>,
1745 #[serde(default, skip_serializing_if = "Option::is_none")]
1746 pub error: Option<String>,
1747 #[serde(default, skip_serializing_if = "Option::is_none")]
1748 pub uuid: Option<String>,
1749 #[serde(default, skip_serializing_if = "Option::is_none")]
1750 pub session_id: Option<String>,
1751}
1752
1753#[derive(Debug, Clone, Serialize, Deserialize)]
1754pub struct ModelRefusalFallbackMessage {
1755 pub trigger: String,
1756 pub direction: String,
1757 #[serde(default, skip_serializing_if = "Option::is_none")]
1763 pub scope: Option<RefusalFallbackScope>,
1764 pub original_model: String,
1765 pub fallback_model: String,
1766 pub request_id: Option<String>,
1767 #[serde(default, skip_serializing_if = "Option::is_none")]
1768 pub api_refusal_category: Option<String>,
1769 #[serde(default, skip_serializing_if = "Option::is_none")]
1774 pub saw_cyber_refusal: Option<bool>,
1775 #[serde(default, skip_serializing_if = "Option::is_none")]
1776 pub api_refusal_explanation: Option<String>,
1777 #[serde(default, skip_serializing_if = "Option::is_none")]
1778 pub retracted_message_uuids: Option<Vec<String>>,
1779 #[serde(default, skip_serializing_if = "Option::is_none")]
1780 pub refused_user_message_uuid: Option<String>,
1781 pub content: Value,
1782 #[serde(default, skip_serializing_if = "Option::is_none")]
1783 pub uuid: Option<String>,
1784 #[serde(default, skip_serializing_if = "Option::is_none")]
1785 pub session_id: Option<String>,
1786}
1787
1788#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1792pub enum RefusalFallbackScope {
1793 Session,
1795 Local,
1798 Unknown(String),
1800}
1801
1802impl RefusalFallbackScope {
1803 pub fn as_str(&self) -> &str {
1804 match self {
1805 Self::Session => "session",
1806 Self::Local => "local",
1807 Self::Unknown(s) => s.as_str(),
1808 }
1809 }
1810}
1811
1812impl fmt::Display for RefusalFallbackScope {
1813 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1814 f.write_str(self.as_str())
1815 }
1816}
1817
1818impl From<&str> for RefusalFallbackScope {
1819 fn from(s: &str) -> Self {
1820 match s {
1821 "session" => Self::Session,
1822 "local" => Self::Local,
1823 other => Self::Unknown(other.to_string()),
1824 }
1825 }
1826}
1827
1828impl Serialize for RefusalFallbackScope {
1829 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1830 serializer.serialize_str(self.as_str())
1831 }
1832}
1833
1834impl<'de> Deserialize<'de> for RefusalFallbackScope {
1835 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1836 let s = String::deserialize(deserializer)?;
1837 Ok(Self::from(s.as_str()))
1838 }
1839}
1840
1841#[derive(Debug, Clone, Serialize, Deserialize)]
1842pub struct ModelRefusalNoFallbackMessage {
1843 pub original_model: String,
1844 pub request_id: Option<String>,
1845 #[serde(default, skip_serializing_if = "Option::is_none")]
1846 pub api_refusal_category: Option<String>,
1847 #[serde(default, skip_serializing_if = "Option::is_none")]
1848 pub api_refusal_explanation: Option<String>,
1849 pub content: Value,
1850 #[serde(default, skip_serializing_if = "Option::is_none")]
1851 pub uuid: Option<String>,
1852 #[serde(default, skip_serializing_if = "Option::is_none")]
1853 pub session_id: Option<String>,
1854}
1855
1856#[derive(Debug, Clone, Serialize, Deserialize)]
1857pub struct LocalCommandOutputMessage {
1858 pub content: String,
1859 #[serde(default, skip_serializing_if = "Option::is_none")]
1860 pub uuid: Option<String>,
1861 #[serde(default, skip_serializing_if = "Option::is_none")]
1862 pub session_id: Option<String>,
1863}
1864
1865#[derive(Debug, Clone, Serialize, Deserialize)]
1866pub struct HookStartedMessage {
1867 pub hook_id: String,
1868 pub hook_name: String,
1869 pub hook_event: String,
1870 #[serde(default, skip_serializing_if = "Option::is_none")]
1871 pub uuid: Option<String>,
1872 #[serde(default, skip_serializing_if = "Option::is_none")]
1873 pub session_id: Option<String>,
1874}
1875
1876#[derive(Debug, Clone, Serialize, Deserialize)]
1877pub struct HookProgressMessage {
1878 pub hook_id: String,
1879 pub hook_name: String,
1880 pub hook_event: String,
1881 #[serde(default, skip_serializing_if = "Option::is_none")]
1882 pub stdout: Option<String>,
1883 #[serde(default, skip_serializing_if = "Option::is_none")]
1884 pub stderr: Option<String>,
1885 #[serde(default, skip_serializing_if = "Option::is_none")]
1886 pub output: Option<String>,
1887 #[serde(default, skip_serializing_if = "Option::is_none")]
1888 pub uuid: Option<String>,
1889 #[serde(default, skip_serializing_if = "Option::is_none")]
1890 pub session_id: Option<String>,
1891}
1892
1893#[derive(Debug, Clone, Serialize, Deserialize)]
1894pub struct HookResponseMessage {
1895 pub hook_id: String,
1896 pub hook_name: String,
1897 pub hook_event: String,
1898 #[serde(default, skip_serializing_if = "Option::is_none")]
1899 pub stdout: Option<String>,
1900 #[serde(default, skip_serializing_if = "Option::is_none")]
1901 pub stderr: Option<String>,
1902 #[serde(default, skip_serializing_if = "Option::is_none")]
1903 pub output: Option<String>,
1904 #[serde(default, skip_serializing_if = "Option::is_none")]
1905 pub exit_code: Option<i32>,
1906 pub outcome: String,
1907 #[serde(default, skip_serializing_if = "Option::is_none")]
1908 pub uuid: Option<String>,
1909 #[serde(default, skip_serializing_if = "Option::is_none")]
1910 pub session_id: Option<String>,
1911}
1912
1913#[derive(Debug, Clone, Serialize, Deserialize)]
1914pub struct PluginInstallMessage {
1915 pub status: String,
1916 #[serde(default, skip_serializing_if = "Option::is_none")]
1917 pub name: Option<String>,
1918 #[serde(default, skip_serializing_if = "Option::is_none")]
1919 pub error: Option<String>,
1920 #[serde(default, skip_serializing_if = "Option::is_none")]
1921 pub uuid: Option<String>,
1922 #[serde(default, skip_serializing_if = "Option::is_none")]
1923 pub session_id: Option<String>,
1924}
1925
1926#[derive(Debug, Clone, Serialize, Deserialize)]
1927pub struct BackgroundTasksChangedMessage {
1928 pub tasks: Vec<BackgroundTaskInfo>,
1929 #[serde(default, skip_serializing_if = "Option::is_none")]
1930 pub uuid: Option<String>,
1931 #[serde(default, skip_serializing_if = "Option::is_none")]
1932 pub session_id: Option<String>,
1933}
1934
1935#[derive(Debug, Clone, Serialize, Deserialize)]
1936pub struct BackgroundTaskInfo {
1937 pub task_id: String,
1938 pub task_type: String,
1939 pub description: String,
1940 #[serde(default, skip_serializing_if = "Option::is_none")]
1943 pub ambient: Option<bool>,
1944}
1945
1946#[derive(Debug, Clone, Serialize, Deserialize)]
1947pub struct SessionStateChangedMessage {
1948 pub state: String,
1949 #[serde(default, skip_serializing_if = "Option::is_none")]
1950 pub uuid: Option<String>,
1951 #[serde(default, skip_serializing_if = "Option::is_none")]
1952 pub session_id: Option<String>,
1953}
1954
1955#[derive(Debug, Clone, Serialize, Deserialize)]
1956pub struct WorkerShuttingDownMessage {
1957 pub reason: String,
1958 #[serde(default, skip_serializing_if = "Option::is_none")]
1959 pub uuid: Option<String>,
1960 #[serde(default, skip_serializing_if = "Option::is_none")]
1961 pub session_id: Option<String>,
1962}
1963
1964#[derive(Debug, Clone, Serialize, Deserialize)]
1965pub struct CommandsChangedMessage {
1966 pub commands: Vec<CommandInfo>,
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)]
1974pub struct CommandInfo {
1975 pub name: String,
1976 pub description: String,
1977 #[serde(rename = "argumentHint")]
1978 pub argument_hint: String,
1979 #[serde(default, skip_serializing_if = "Option::is_none")]
1980 pub aliases: Option<Vec<String>>,
1981}
1982
1983#[derive(Debug, Clone, Serialize, Deserialize)]
1984pub struct NotificationMessage {
1985 pub key: String,
1986 pub text: String,
1987 pub priority: String,
1988 #[serde(default, skip_serializing_if = "Option::is_none")]
1989 pub color: Option<String>,
1990 #[serde(default, skip_serializing_if = "Option::is_none")]
1991 pub timeout_ms: Option<u64>,
1992 #[serde(default, skip_serializing_if = "Option::is_none")]
1993 pub uuid: Option<String>,
1994 #[serde(default, skip_serializing_if = "Option::is_none")]
1995 pub session_id: Option<String>,
1996}
1997
1998#[derive(Debug, Clone, Serialize, Deserialize)]
1999pub struct FilesPersistedMessage {
2000 pub files: Vec<PersistedFile>,
2001 pub failed: Vec<FailedPersistedFile>,
2002 pub processed_at: String,
2003 #[serde(default, skip_serializing_if = "Option::is_none")]
2004 pub uuid: Option<String>,
2005 #[serde(default, skip_serializing_if = "Option::is_none")]
2006 pub session_id: Option<String>,
2007}
2008
2009#[derive(Debug, Clone, Serialize, Deserialize)]
2010pub struct PersistedFile {
2011 pub filename: String,
2012 pub file_id: String,
2013}
2014
2015#[derive(Debug, Clone, Serialize, Deserialize)]
2016pub struct FailedPersistedFile {
2017 pub filename: String,
2018 pub error: String,
2019}
2020
2021#[derive(Debug, Clone, Serialize, Deserialize)]
2022pub struct MemoryRecallMessage {
2023 pub mode: String,
2024 pub memories: Vec<MemoryRecallItem>,
2025 #[serde(default, skip_serializing_if = "Option::is_none")]
2026 pub uuid: Option<String>,
2027 #[serde(default, skip_serializing_if = "Option::is_none")]
2028 pub session_id: Option<String>,
2029}
2030
2031#[derive(Debug, Clone, Serialize, Deserialize)]
2032pub struct MemoryRecallItem {
2033 pub path: String,
2034 pub scope: String,
2035 #[serde(default, skip_serializing_if = "Option::is_none")]
2036 pub content: Option<String>,
2037}
2038
2039#[derive(Debug, Clone, Serialize, Deserialize)]
2040pub struct ElicitationCompleteMessage {
2041 pub mcp_server_name: String,
2042 pub elicitation_id: String,
2043 #[serde(default, skip_serializing_if = "Option::is_none")]
2044 pub uuid: Option<String>,
2045 #[serde(default, skip_serializing_if = "Option::is_none")]
2046 pub session_id: Option<String>,
2047}
2048
2049#[derive(Debug, Clone, Serialize, Deserialize)]
2050pub struct PermissionDeniedMessage {
2051 pub tool_name: String,
2052 pub tool_use_id: String,
2053 #[serde(default, skip_serializing_if = "Option::is_none")]
2054 pub agent_id: Option<String>,
2055 #[serde(default, skip_serializing_if = "Option::is_none")]
2056 pub decision_reason_type: Option<String>,
2057 #[serde(default, skip_serializing_if = "Option::is_none")]
2061 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2062 #[serde(default, skip_serializing_if = "Option::is_none")]
2063 pub decision_reason: Option<String>,
2064 pub message: String,
2065 #[serde(default, skip_serializing_if = "Option::is_none")]
2066 pub uuid: Option<String>,
2067 #[serde(default, skip_serializing_if = "Option::is_none")]
2068 pub session_id: Option<String>,
2069}
2070
2071#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2074pub enum PermissionDeniedReasonCode {
2075 ClassifierTranscriptTooLong,
2077 OutsideReadsBlocked,
2079 MemoryPaused,
2081 Unknown(String),
2083}
2084
2085impl PermissionDeniedReasonCode {
2086 pub fn as_str(&self) -> &str {
2087 match self {
2088 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2089 Self::OutsideReadsBlocked => "outside_reads_blocked",
2090 Self::MemoryPaused => "memory_paused",
2091 Self::Unknown(s) => s.as_str(),
2092 }
2093 }
2094}
2095
2096impl fmt::Display for PermissionDeniedReasonCode {
2097 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2098 f.write_str(self.as_str())
2099 }
2100}
2101
2102impl From<&str> for PermissionDeniedReasonCode {
2103 fn from(s: &str) -> Self {
2104 match s {
2105 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2106 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2107 "memory_paused" => Self::MemoryPaused,
2108 other => Self::Unknown(other.to_string()),
2109 }
2110 }
2111}
2112
2113impl Serialize for PermissionDeniedReasonCode {
2114 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2115 serializer.serialize_str(self.as_str())
2116 }
2117}
2118
2119impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2120 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2121 let s = String::deserialize(deserializer)?;
2122 Ok(Self::from(s.as_str()))
2123 }
2124}
2125
2126#[derive(Debug, Clone, Serialize, Deserialize)]
2127pub struct MirrorErrorMessage {
2128 pub error: String,
2129 pub key: MirrorErrorKey,
2130 #[serde(default, skip_serializing_if = "Option::is_none")]
2131 pub uuid: Option<String>,
2132 #[serde(default, skip_serializing_if = "Option::is_none")]
2133 pub session_id: Option<String>,
2134}
2135
2136#[derive(Debug, Clone, Serialize, Deserialize)]
2137pub struct MirrorErrorKey {
2138 #[serde(rename = "projectKey")]
2139 pub project_key: String,
2140 #[serde(rename = "sessionId")]
2141 pub session_id: String,
2142 #[serde(default, skip_serializing_if = "Option::is_none")]
2143 pub subpath: Option<String>,
2144}
2145
2146#[derive(Debug, Clone, Serialize, Deserialize)]
2147pub struct InformationalMessage {
2148 pub content: String,
2149 pub level: String,
2150 #[serde(default, skip_serializing_if = "Option::is_none")]
2151 pub tool_use_id: Option<String>,
2152 #[serde(default, skip_serializing_if = "Option::is_none")]
2153 pub prevent_continuation: Option<bool>,
2154 #[serde(default, skip_serializing_if = "Option::is_none")]
2155 pub uuid: Option<String>,
2156 #[serde(default, skip_serializing_if = "Option::is_none")]
2157 pub session_id: Option<String>,
2158}
2159
2160#[derive(Debug, Clone, Serialize, Deserialize)]
2162pub struct PluginInfo {
2163 pub name: String,
2165 pub path: String,
2167 #[serde(skip_serializing_if = "Option::is_none")]
2169 pub source: Option<String>,
2170 #[serde(default, skip_serializing_if = "Option::is_none")]
2172 pub version: Option<String>,
2173}
2174
2175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2177pub struct PluginDiagnostic {
2178 pub plugin: String,
2179 #[serde(rename = "type")]
2180 pub diagnostic_type: String,
2181 pub message: String,
2182}
2183
2184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2186pub struct MemoryPaths {
2187 #[serde(default, skip_serializing_if = "Option::is_none")]
2188 pub auto: Option<String>,
2189 #[serde(default, skip_serializing_if = "Option::is_none")]
2190 pub team: Option<String>,
2191 #[serde(flatten)]
2192 pub extra: serde_json::Map<String, Value>,
2193}
2194
2195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2199pub struct McpServerError {
2200 pub name: String,
2201 #[serde(rename = "type")]
2203 pub error_type: String,
2204 pub message: String,
2205}
2206
2207#[derive(Debug, Clone, Serialize, Deserialize)]
2209pub struct InitMessage {
2210 pub session_id: String,
2212 #[serde(skip_serializing_if = "Option::is_none")]
2214 pub cwd: Option<String>,
2215 #[serde(skip_serializing_if = "Option::is_none")]
2217 pub model: Option<String>,
2218 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2220 pub tools: Vec<String>,
2221 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2223 pub mcp_servers: Vec<Value>,
2224 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2226 pub slash_commands: Vec<String>,
2227 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2230 pub terminal_slash_commands: Vec<String>,
2231 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2233 pub agents: Vec<String>,
2234 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2236 pub plugins: Vec<PluginInfo>,
2237 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2239 pub skills: Vec<Value>,
2240 #[serde(skip_serializing_if = "Option::is_none")]
2242 pub claude_code_version: Option<String>,
2243 #[serde(skip_serializing_if = "Option::is_none")]
2246 pub messaging_socket_path: Option<String>,
2247 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2249 pub api_key_source: Option<ApiKeySource>,
2250 #[serde(skip_serializing_if = "Option::is_none")]
2252 pub output_style: Option<OutputStyle>,
2253 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2255 pub permission_mode: Option<InitPermissionMode>,
2256
2257 #[serde(skip_serializing_if = "Option::is_none")]
2259 pub uuid: Option<String>,
2260
2261 #[serde(skip_serializing_if = "Option::is_none")]
2263 pub memory_paths: Option<MemoryPaths>,
2264
2265 #[serde(skip_serializing_if = "Option::is_none")]
2267 pub fast_mode_state: Option<String>,
2268
2269 #[serde(default, skip_serializing_if = "Option::is_none")]
2271 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2272
2273 #[serde(default, skip_serializing_if = "Option::is_none")]
2276 pub mcp_server_errors: Option<Vec<McpServerError>>,
2277
2278 #[serde(default, skip_serializing_if = "Option::is_none")]
2280 pub analytics_disabled: Option<bool>,
2281
2282 #[serde(default, skip_serializing_if = "Option::is_none")]
2284 pub product_feedback_disabled: Option<bool>,
2285
2286 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2288 pub betas: Vec<String>,
2289
2290 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2292 pub capabilities: Vec<String>,
2293
2294 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2296 pub plugin_errors: Vec<PluginDiagnostic>,
2297
2298 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2300 pub plugin_warnings: Vec<PluginDiagnostic>,
2301
2302 #[serde(default, skip_serializing_if = "Option::is_none")]
2307 pub effort: Option<String>,
2308
2309 #[serde(default, skip_serializing_if = "Option::is_none")]
2314 pub cloud_session: Option<Value>,
2315
2316 #[serde(default, skip_serializing_if = "Option::is_none")]
2320 pub footer_indicator: Option<FooterIndicator>,
2321
2322 #[serde(default, skip_serializing_if = "Option::is_none")]
2326 pub worker_epoch: Option<u64>,
2327
2328 #[serde(default, skip_serializing_if = "Option::is_none")]
2332 pub powershell_path: Option<Option<String>>,
2333
2334 #[serde(default, skip_serializing_if = "Option::is_none")]
2338 pub startup_timing: Option<serde_json::Map<String, Value>>,
2339}
2340
2341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2345pub struct FooterIndicator {
2346 pub text: String,
2349}
2350
2351#[derive(Debug, Clone, Serialize, Deserialize)]
2353pub struct StatusMessage {
2354 pub session_id: String,
2356 pub status: Option<StatusMessageStatus>,
2358 #[serde(skip_serializing_if = "Option::is_none")]
2360 pub uuid: Option<String>,
2361 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2363 pub permission_mode: Option<InitPermissionMode>,
2364 #[serde(skip_serializing_if = "Option::is_none")]
2365 pub compact_result: Option<String>,
2366 #[serde(skip_serializing_if = "Option::is_none")]
2367 pub compact_error: Option<String>,
2368}
2369
2370#[derive(Debug, Clone, Serialize, Deserialize)]
2372pub struct CompactBoundaryMessage {
2373 pub session_id: String,
2375 pub compact_metadata: CompactMetadata,
2377 #[serde(
2381 default,
2382 skip_serializing_if = "Option::is_none",
2383 alias = "content",
2384 alias = "text"
2385 )]
2386 pub summary: Option<String>,
2387 #[serde(
2391 default,
2392 skip_serializing_if = "Option::is_none",
2393 alias = "message_count"
2394 )]
2395 pub leaf_message_count: Option<u32>,
2396 #[serde(default, skip_serializing_if = "Option::is_none")]
2398 pub duration_ms: Option<u64>,
2399 #[serde(skip_serializing_if = "Option::is_none")]
2401 pub uuid: Option<String>,
2402 #[serde(skip_serializing_if = "Option::is_none")]
2404 pub logical_parent_uuid: Option<Option<String>>,
2405 #[serde(default, skip_serializing_if = "Option::is_none")]
2409 pub historical: Option<bool>,
2410}
2411
2412#[derive(Debug, Clone, Serialize, Deserialize)]
2414pub struct CompactMetadata {
2415 pub pre_tokens: u64,
2417 pub trigger: CompactionTrigger,
2419 #[serde(default, skip_serializing_if = "Option::is_none")]
2420 pub post_tokens: Option<u64>,
2421 #[serde(default, skip_serializing_if = "Option::is_none")]
2422 pub cumulative_dropped_tokens: Option<u64>,
2423 #[serde(default, skip_serializing_if = "Option::is_none")]
2424 pub duration_ms: Option<u64>,
2425 #[serde(default, skip_serializing_if = "Option::is_none")]
2426 pub user_context: Option<String>,
2427 #[serde(default, skip_serializing_if = "Option::is_none")]
2428 pub messages_summarized: Option<u64>,
2429 #[serde(default, skip_serializing_if = "Option::is_none")]
2430 pub precomputed: Option<bool>,
2431 #[serde(default, skip_serializing_if = "Option::is_none")]
2432 pub pre_compact_discovered_tools: Option<Vec<String>>,
2433 #[serde(default, skip_serializing_if = "Option::is_none")]
2434 pub preserved_segment: Option<PreservedSegment>,
2435 #[serde(default, skip_serializing_if = "Option::is_none")]
2436 pub preserved_messages: Option<PreservedMessages>,
2437}
2438
2439#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2440pub struct PreservedSegment {
2441 pub head_uuid: String,
2442 pub anchor_uuid: String,
2443 pub tail_uuid: String,
2444}
2445
2446#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2447pub struct PreservedMessages {
2448 pub anchor_uuid: String,
2449 pub uuids: Vec<String>,
2450 #[serde(default, skip_serializing_if = "Option::is_none")]
2451 pub all_uuids: Option<Vec<String>>,
2452}
2453
2454#[derive(Debug, Clone, Serialize, Deserialize)]
2460pub struct TaskUsage {
2461 pub duration_ms: u64,
2463 pub tool_uses: u64,
2465 pub total_tokens: u64,
2467}
2468
2469#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2471pub enum TaskType {
2472 LocalAgent,
2474 LocalBash,
2476 LocalWorkflow,
2478 Unknown(String),
2480}
2481
2482impl TaskType {
2483 pub fn as_str(&self) -> &str {
2484 match self {
2485 Self::LocalAgent => "local_agent",
2486 Self::LocalBash => "local_bash",
2487 Self::LocalWorkflow => "local_workflow",
2488 Self::Unknown(s) => s.as_str(),
2489 }
2490 }
2491}
2492
2493impl fmt::Display for TaskType {
2494 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2495 f.write_str(self.as_str())
2496 }
2497}
2498
2499impl From<&str> for TaskType {
2500 fn from(s: &str) -> Self {
2501 match s {
2502 "local_agent" => Self::LocalAgent,
2503 "local_bash" => Self::LocalBash,
2504 "local_workflow" => Self::LocalWorkflow,
2505 other => Self::Unknown(other.to_string()),
2506 }
2507 }
2508}
2509
2510impl Serialize for TaskType {
2511 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2512 serializer.serialize_str(self.as_str())
2513 }
2514}
2515
2516impl<'de> Deserialize<'de> for TaskType {
2517 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2518 let s = String::deserialize(deserializer)?;
2519 Ok(Self::from(s.as_str()))
2520 }
2521}
2522
2523#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2525pub enum TaskStatus {
2526 Pending,
2527 Running,
2528 Completed,
2529 Failed,
2530 Killed,
2531 Paused,
2532 Stopped,
2533 Unknown(String),
2534}
2535
2536impl TaskStatus {
2537 pub fn as_str(&self) -> &str {
2538 match self {
2539 Self::Pending => "pending",
2540 Self::Running => "running",
2541 Self::Completed => "completed",
2542 Self::Failed => "failed",
2543 Self::Killed => "killed",
2544 Self::Paused => "paused",
2545 Self::Stopped => "stopped",
2546 Self::Unknown(s) => s.as_str(),
2547 }
2548 }
2549}
2550
2551impl fmt::Display for TaskStatus {
2552 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2553 f.write_str(self.as_str())
2554 }
2555}
2556
2557impl From<&str> for TaskStatus {
2558 fn from(s: &str) -> Self {
2559 match s {
2560 "pending" => Self::Pending,
2561 "running" => Self::Running,
2562 "completed" => Self::Completed,
2563 "failed" => Self::Failed,
2564 "killed" => Self::Killed,
2565 "paused" => Self::Paused,
2566 "stopped" => Self::Stopped,
2567 other => Self::Unknown(other.to_string()),
2568 }
2569 }
2570}
2571
2572impl Serialize for TaskStatus {
2573 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2574 serializer.serialize_str(self.as_str())
2575 }
2576}
2577
2578impl<'de> Deserialize<'de> for TaskStatus {
2579 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2580 let s = String::deserialize(deserializer)?;
2581 Ok(Self::from(s.as_str()))
2582 }
2583}
2584
2585#[derive(Debug, Clone, Serialize, Deserialize)]
2587pub struct TaskStartedMessage {
2588 pub session_id: String,
2589 pub task_id: String,
2590 #[serde(default, skip_serializing_if = "Option::is_none")]
2591 pub task_type: Option<TaskType>,
2592 #[serde(default, skip_serializing_if = "Option::is_none")]
2593 pub tool_use_id: Option<String>,
2594 pub description: String,
2595 #[serde(default, skip_serializing_if = "Option::is_none")]
2598 pub subagent_type: Option<String>,
2599 #[serde(default, skip_serializing_if = "Option::is_none")]
2605 pub is_backgrounded: Option<bool>,
2606 #[serde(default, skip_serializing_if = "Option::is_none")]
2610 pub spawn_depth: Option<u32>,
2611 #[serde(default, skip_serializing_if = "Option::is_none")]
2613 pub prompt: Option<String>,
2614 #[serde(default, skip_serializing_if = "Option::is_none")]
2615 pub workflow_name: Option<String>,
2616 #[serde(default, skip_serializing_if = "Option::is_none")]
2617 pub skip_transcript: Option<bool>,
2618 #[serde(default, skip_serializing_if = "Option::is_none")]
2623 pub ambient: Option<bool>,
2624 pub uuid: String,
2625}
2626
2627#[derive(Debug, Clone, Serialize, Deserialize)]
2631pub struct TaskUpdatedMessage {
2632 pub session_id: String,
2633 pub task_id: String,
2634 pub patch: TaskPatch,
2635 pub uuid: String,
2636}
2637
2638#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2641pub struct TaskPatch {
2642 #[serde(default, skip_serializing_if = "Option::is_none")]
2643 pub status: Option<TaskStatus>,
2644 #[serde(default, skip_serializing_if = "Option::is_none")]
2647 pub end_time: Option<u64>,
2648 #[serde(default, skip_serializing_if = "Option::is_none")]
2649 pub description: Option<String>,
2650 #[serde(default, skip_serializing_if = "Option::is_none")]
2651 pub total_paused_ms: Option<u64>,
2652 #[serde(default, skip_serializing_if = "Option::is_none")]
2653 pub error: Option<String>,
2654 #[serde(default, skip_serializing_if = "Option::is_none")]
2655 pub is_backgrounded: Option<bool>,
2656}
2657
2658#[derive(Debug, Clone, Serialize, Deserialize)]
2661pub struct ThinkingTokensMessage {
2662 pub session_id: String,
2663 pub estimated_tokens: u64,
2665 pub estimated_tokens_delta: u64,
2667 #[serde(default, skip_serializing_if = "Option::is_none")]
2674 pub user_message_uuid: Option<String>,
2675 pub uuid: String,
2676}
2677
2678#[derive(Debug, Clone, Serialize, Deserialize)]
2681pub struct TaskProgressMessage {
2682 pub session_id: String,
2683 pub task_id: String,
2684 #[serde(default, skip_serializing_if = "Option::is_none")]
2685 pub tool_use_id: Option<String>,
2686 pub description: String,
2687 #[serde(default, skip_serializing_if = "Option::is_none")]
2688 pub last_tool_name: Option<String>,
2689 pub usage: TaskUsage,
2690 #[serde(default, skip_serializing_if = "Option::is_none")]
2692 pub subagent_type: Option<String>,
2693 #[serde(default, skip_serializing_if = "Option::is_none")]
2694 pub summary: Option<String>,
2695 pub uuid: String,
2696}
2697
2698#[derive(Debug, Clone, Serialize, Deserialize)]
2701pub struct TaskNotificationMessage {
2702 pub session_id: String,
2703 pub task_id: String,
2704 pub status: TaskStatus,
2705 #[serde(default, skip_serializing_if = "Option::is_none")]
2708 pub reason: Option<TaskEndReason>,
2709 pub summary: String,
2710 pub output_file: Option<String>,
2711 #[serde(skip_serializing_if = "Option::is_none")]
2712 pub tool_use_id: Option<String>,
2713 #[serde(skip_serializing_if = "Option::is_none")]
2714 pub usage: Option<TaskUsage>,
2715 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2721 pub resource_links: Vec<ResourceLink>,
2722 #[serde(default, skip_serializing_if = "Option::is_none")]
2723 pub skip_transcript: Option<bool>,
2724 #[serde(default, skip_serializing_if = "Option::is_none")]
2727 pub ambient: Option<bool>,
2728 #[serde(skip_serializing_if = "Option::is_none")]
2729 pub uuid: Option<String>,
2730}
2731
2732#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2734pub enum AssistantErrorKind {
2735 AuthenticationFailed,
2736 OauthOrgNotAllowed,
2737 AccountOnHold,
2738 BillingError,
2739 RateLimit,
2740 Overloaded,
2741 InvalidRequest,
2742 ModelNotFound,
2743 ServerError,
2744 UnknownError,
2745 MaxOutputTokens,
2746 Unknown(String),
2747}
2748
2749impl AssistantErrorKind {
2750 pub fn as_str(&self) -> &str {
2751 match self {
2752 Self::AuthenticationFailed => "authentication_failed",
2753 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
2754 Self::AccountOnHold => "account_on_hold",
2755 Self::BillingError => "billing_error",
2756 Self::RateLimit => "rate_limit",
2757 Self::Overloaded => "overloaded",
2758 Self::InvalidRequest => "invalid_request",
2759 Self::ModelNotFound => "model_not_found",
2760 Self::ServerError => "server_error",
2761 Self::UnknownError => "unknown",
2762 Self::MaxOutputTokens => "max_output_tokens",
2763 Self::Unknown(s) => s.as_str(),
2764 }
2765 }
2766}
2767
2768impl fmt::Display for AssistantErrorKind {
2769 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2770 f.write_str(self.as_str())
2771 }
2772}
2773
2774impl From<&str> for AssistantErrorKind {
2775 fn from(s: &str) -> Self {
2776 match s {
2777 "authentication_failed" => Self::AuthenticationFailed,
2778 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
2779 "account_on_hold" => Self::AccountOnHold,
2780 "billing_error" => Self::BillingError,
2781 "rate_limit" => Self::RateLimit,
2782 "overloaded" => Self::Overloaded,
2783 "invalid_request" => Self::InvalidRequest,
2784 "model_not_found" => Self::ModelNotFound,
2785 "server_error" => Self::ServerError,
2786 "unknown" => Self::UnknownError,
2787 "max_output_tokens" => Self::MaxOutputTokens,
2788 other => Self::Unknown(other.to_string()),
2789 }
2790 }
2791}
2792
2793impl Serialize for AssistantErrorKind {
2794 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2795 serializer.serialize_str(self.as_str())
2796 }
2797}
2798
2799impl<'de> Deserialize<'de> for AssistantErrorKind {
2800 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2801 let s = String::deserialize(deserializer)?;
2802 Ok(Self::from(s.as_str()))
2803 }
2804}
2805
2806#[derive(Debug, Clone, Serialize, Deserialize)]
2813pub struct CodeChangePublishedMessage {
2814 pub provider: String,
2818 pub url: String,
2820 pub repo: String,
2823 pub identifier: String,
2825 #[serde(default, skip_serializing_if = "Option::is_none")]
2834 pub action: Option<String>,
2835 #[serde(default, skip_serializing_if = "Option::is_none")]
2843 pub branch: Option<String>,
2844 pub uuid: String,
2845 pub session_id: String,
2846}
2847
2848#[derive(Debug, Clone, Serialize, Deserialize)]
2853pub struct VcsStateChangedMessage {
2854 pub kind: VcsMutationKind,
2857 pub cwd: String,
2860 #[serde(default, skip_serializing_if = "Option::is_none")]
2865 pub branch: Option<String>,
2866 pub uuid: String,
2867 pub session_id: String,
2868}
2869
2870#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2872pub enum VcsMutationKind {
2873 Commit,
2874 Push,
2875 Merge,
2876 Rebase,
2877 Unknown(String),
2879}
2880
2881impl VcsMutationKind {
2882 pub fn as_str(&self) -> &str {
2883 match self {
2884 Self::Commit => "commit",
2885 Self::Push => "push",
2886 Self::Merge => "merge",
2887 Self::Rebase => "rebase",
2888 Self::Unknown(s) => s.as_str(),
2889 }
2890 }
2891}
2892
2893impl fmt::Display for VcsMutationKind {
2894 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2895 f.write_str(self.as_str())
2896 }
2897}
2898
2899impl From<&str> for VcsMutationKind {
2900 fn from(s: &str) -> Self {
2901 match s {
2902 "commit" => Self::Commit,
2903 "push" => Self::Push,
2904 "merge" => Self::Merge,
2905 "rebase" => Self::Rebase,
2906 other => Self::Unknown(other.to_string()),
2907 }
2908 }
2909}
2910
2911impl Serialize for VcsMutationKind {
2912 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2913 serializer.serialize_str(self.as_str())
2914 }
2915}
2916
2917impl<'de> Deserialize<'de> for VcsMutationKind {
2918 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2919 let s = String::deserialize(deserializer)?;
2920 Ok(Self::from(s.as_str()))
2921 }
2922}
2923
2924#[derive(Debug, Clone, Serialize, Deserialize)]
2926pub struct FeedbackDraftQueuedMessage {
2927 pub draft_id: String,
2928 pub draft_type: String,
2929 pub title: String,
2930 pub details_preview: String,
2931 #[serde(default, skip_serializing_if = "Option::is_none")]
2932 pub uuid: Option<String>,
2933 #[serde(default, skip_serializing_if = "Option::is_none")]
2934 pub session_id: Option<String>,
2935 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2936 pub extra: serde_json::Map<String, Value>,
2937}
2938
2939#[derive(Debug, Clone, Serialize, Deserialize)]
2947pub struct CloudSessionDeltaMessage {
2948 pub seq: u64,
2952 pub changed: Vec<String>,
2956 pub cloud_session: Value,
2960 pub uuid: String,
2961 pub session_id: String,
2962 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2963 pub extra: serde_json::Map<String, Value>,
2964}
2965
2966#[derive(Debug, Clone, Serialize, Deserialize)]
2982pub struct DevIntentMessage {
2983 pub kind: DevIntentKind,
2985 #[serde(default, skip_serializing_if = "Option::is_none")]
2991 pub trigger: Option<DevIntentTrigger>,
2992 pub uuid: String,
2993 pub session_id: String,
2994 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2995 pub extra: serde_json::Map<String, Value>,
2996}
2997
2998#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3002pub enum DevIntentKind {
3003 IosApp,
3009 AndroidApp,
3016 Unknown(String),
3018}
3019
3020impl DevIntentKind {
3021 pub fn as_str(&self) -> &str {
3022 match self {
3023 Self::IosApp => "ios_app",
3024 Self::AndroidApp => "android_app",
3025 Self::Unknown(s) => s.as_str(),
3026 }
3027 }
3028}
3029
3030impl fmt::Display for DevIntentKind {
3031 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3032 f.write_str(self.as_str())
3033 }
3034}
3035
3036impl From<&str> for DevIntentKind {
3037 fn from(s: &str) -> Self {
3038 match s {
3039 "ios_app" => Self::IosApp,
3040 "android_app" => Self::AndroidApp,
3041 other => Self::Unknown(other.to_string()),
3042 }
3043 }
3044}
3045
3046impl Serialize for DevIntentKind {
3047 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3048 serializer.serialize_str(self.as_str())
3049 }
3050}
3051
3052impl<'de> Deserialize<'de> for DevIntentKind {
3053 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3054 let s = String::deserialize(deserializer)?;
3055 Ok(Self::from(s.as_str()))
3056 }
3057}
3058
3059#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3063pub enum DevIntentTrigger {
3064 SwiftEdit,
3066 UikitImport,
3068 XcodeProject,
3071 IosCommand,
3074 KotlinEdit,
3076 JavaEdit,
3078 AndroidImport,
3080 AndroidManifest,
3082 GradlePlugin,
3084 AndroidCommand,
3087 ProjectScan,
3090 Unknown(String),
3092}
3093
3094impl DevIntentTrigger {
3095 pub fn as_str(&self) -> &str {
3096 match self {
3097 Self::SwiftEdit => "swift_edit",
3098 Self::UikitImport => "uikit_import",
3099 Self::XcodeProject => "xcode_project",
3100 Self::IosCommand => "ios_command",
3101 Self::KotlinEdit => "kotlin_edit",
3102 Self::JavaEdit => "java_edit",
3103 Self::AndroidImport => "android_import",
3104 Self::AndroidManifest => "android_manifest",
3105 Self::GradlePlugin => "gradle_plugin",
3106 Self::AndroidCommand => "android_command",
3107 Self::ProjectScan => "project_scan",
3108 Self::Unknown(s) => s.as_str(),
3109 }
3110 }
3111}
3112
3113impl fmt::Display for DevIntentTrigger {
3114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3115 f.write_str(self.as_str())
3116 }
3117}
3118
3119impl From<&str> for DevIntentTrigger {
3120 fn from(s: &str) -> Self {
3121 match s {
3122 "swift_edit" => Self::SwiftEdit,
3123 "uikit_import" => Self::UikitImport,
3124 "xcode_project" => Self::XcodeProject,
3125 "ios_command" => Self::IosCommand,
3126 "kotlin_edit" => Self::KotlinEdit,
3127 "java_edit" => Self::JavaEdit,
3128 "android_import" => Self::AndroidImport,
3129 "android_manifest" => Self::AndroidManifest,
3130 "gradle_plugin" => Self::GradlePlugin,
3131 "android_command" => Self::AndroidCommand,
3132 "project_scan" => Self::ProjectScan,
3133 other => Self::Unknown(other.to_string()),
3134 }
3135 }
3136}
3137
3138impl Serialize for DevIntentTrigger {
3139 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3140 serializer.serialize_str(self.as_str())
3141 }
3142}
3143
3144impl<'de> Deserialize<'de> for DevIntentTrigger {
3145 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3146 let s = String::deserialize(deserializer)?;
3147 Ok(Self::from(s.as_str()))
3148 }
3149}
3150
3151#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3155pub enum TaskEndReason {
3156 WorkerRestart,
3159 Unknown(String),
3161}
3162
3163impl TaskEndReason {
3164 pub fn as_str(&self) -> &str {
3165 match self {
3166 Self::WorkerRestart => "worker_restart",
3167 Self::Unknown(s) => s.as_str(),
3168 }
3169 }
3170}
3171
3172impl fmt::Display for TaskEndReason {
3173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3174 f.write_str(self.as_str())
3175 }
3176}
3177
3178impl From<&str> for TaskEndReason {
3179 fn from(s: &str) -> Self {
3180 match s {
3181 "worker_restart" => Self::WorkerRestart,
3182 other => Self::Unknown(other.to_string()),
3183 }
3184 }
3185}
3186
3187impl Serialize for TaskEndReason {
3188 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3189 serializer.serialize_str(self.as_str())
3190 }
3191}
3192
3193impl<'de> Deserialize<'de> for TaskEndReason {
3194 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3195 let s = String::deserialize(deserializer)?;
3196 Ok(Self::from(s.as_str()))
3197 }
3198}
3199
3200#[derive(Debug, Clone, Serialize, Deserialize)]
3209pub struct TurnHandoffAvailableMessage {
3210 pub v: u64,
3212 pub tools: Vec<String>,
3214 pub worker_epoch: u64,
3216 #[serde(default, skip_serializing_if = "Option::is_none")]
3220 pub relay_marker: Option<bool>,
3221 pub uuid: String,
3222 pub session_id: String,
3223 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3224 pub extra: serde_json::Map<String, Value>,
3225}
3226
3227#[derive(Debug, Clone, Serialize, Deserialize)]
3239pub struct TurnPreemptedMessage {
3240 pub reason: String,
3244 pub preempted_by_uuid: String,
3248 #[serde(default)]
3251 pub preempted_message_uuids: Vec<String>,
3252 pub uuid: String,
3253 pub session_id: String,
3254 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3255 pub extra: serde_json::Map<String, Value>,
3256}
3257
3258#[derive(Debug, Clone, Serialize, Deserialize)]
3266pub struct PeerMessageHoldMessage {
3267 pub state: PeerMessageHoldState,
3273 #[serde(default, skip_serializing_if = "Option::is_none")]
3280 pub message_uuid: Option<String>,
3281 pub lane: PeerMessageLane,
3283 pub from: String,
3288 #[serde(default, skip_serializing_if = "Option::is_none")]
3291 pub from_name: Option<String>,
3292 #[serde(default, skip_serializing_if = "Option::is_none")]
3294 pub cause: Option<PeerMessageHoldCause>,
3295 #[serde(default, skip_serializing_if = "Option::is_none")]
3297 pub outcome: Option<PeerMessageHoldOutcome>,
3298 pub uuid: String,
3299 pub session_id: String,
3300 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3301 pub extra: serde_json::Map<String, Value>,
3302}
3303
3304#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3306pub enum PeerMessageHoldState {
3307 Held,
3309 Released,
3311 Dropped,
3313 Unknown(String),
3315}
3316
3317impl PeerMessageHoldState {
3318 pub fn as_str(&self) -> &str {
3319 match self {
3320 Self::Held => "held",
3321 Self::Released => "released",
3322 Self::Dropped => "dropped",
3323 Self::Unknown(s) => s.as_str(),
3324 }
3325 }
3326}
3327
3328impl fmt::Display for PeerMessageHoldState {
3329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3330 f.write_str(self.as_str())
3331 }
3332}
3333
3334impl From<&str> for PeerMessageHoldState {
3335 fn from(s: &str) -> Self {
3336 match s {
3337 "held" => Self::Held,
3338 "released" => Self::Released,
3339 "dropped" => Self::Dropped,
3340 other => Self::Unknown(other.to_string()),
3341 }
3342 }
3343}
3344
3345impl Serialize for PeerMessageHoldState {
3346 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3347 serializer.serialize_str(self.as_str())
3348 }
3349}
3350
3351impl<'de> Deserialize<'de> for PeerMessageHoldState {
3352 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3353 let s = String::deserialize(deserializer)?;
3354 Ok(Self::from(s.as_str()))
3355 }
3356}
3357
3358#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3360pub enum PeerMessageLane {
3361 Bridge,
3363 Stdin,
3365 Socket,
3367 Unknown(String),
3369}
3370
3371impl PeerMessageLane {
3372 pub fn as_str(&self) -> &str {
3373 match self {
3374 Self::Bridge => "bridge",
3375 Self::Stdin => "stdin",
3376 Self::Socket => "socket",
3377 Self::Unknown(s) => s.as_str(),
3378 }
3379 }
3380}
3381
3382impl fmt::Display for PeerMessageLane {
3383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3384 f.write_str(self.as_str())
3385 }
3386}
3387
3388impl From<&str> for PeerMessageLane {
3389 fn from(s: &str) -> Self {
3390 match s {
3391 "bridge" => Self::Bridge,
3392 "stdin" => Self::Stdin,
3393 "socket" => Self::Socket,
3394 other => Self::Unknown(other.to_string()),
3395 }
3396 }
3397}
3398
3399impl Serialize for PeerMessageLane {
3400 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3401 serializer.serialize_str(self.as_str())
3402 }
3403}
3404
3405impl<'de> Deserialize<'de> for PeerMessageLane {
3406 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3407 let s = String::deserialize(deserializer)?;
3408 Ok(Self::from(s.as_str()))
3409 }
3410}
3411
3412#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3419pub enum PeerMessageHoldCause {
3420 ExplicitSetting,
3421 ManagedSetting,
3422 RepoSetting,
3423 InvalidSetting,
3424 BypassDefault,
3425 ModeUnknown,
3426 ModeMismatch,
3427 NoModeAsserted,
3428 Unknown(String),
3430}
3431
3432impl PeerMessageHoldCause {
3433 pub fn as_str(&self) -> &str {
3434 match self {
3435 Self::ExplicitSetting => "explicit-setting",
3436 Self::ManagedSetting => "managed-setting",
3437 Self::RepoSetting => "repo-setting",
3438 Self::InvalidSetting => "invalid-setting",
3439 Self::BypassDefault => "bypass-default",
3440 Self::ModeUnknown => "mode-unknown",
3441 Self::ModeMismatch => "mode-mismatch",
3442 Self::NoModeAsserted => "no-mode-asserted",
3443 Self::Unknown(s) => s.as_str(),
3444 }
3445 }
3446}
3447
3448impl fmt::Display for PeerMessageHoldCause {
3449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3450 f.write_str(self.as_str())
3451 }
3452}
3453
3454impl From<&str> for PeerMessageHoldCause {
3455 fn from(s: &str) -> Self {
3456 match s {
3457 "explicit-setting" => Self::ExplicitSetting,
3458 "managed-setting" => Self::ManagedSetting,
3459 "repo-setting" => Self::RepoSetting,
3460 "invalid-setting" => Self::InvalidSetting,
3461 "bypass-default" => Self::BypassDefault,
3462 "mode-unknown" => Self::ModeUnknown,
3463 "mode-mismatch" => Self::ModeMismatch,
3464 "no-mode-asserted" => Self::NoModeAsserted,
3465 other => Self::Unknown(other.to_string()),
3466 }
3467 }
3468}
3469
3470impl Serialize for PeerMessageHoldCause {
3471 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3472 serializer.serialize_str(self.as_str())
3473 }
3474}
3475
3476impl<'de> Deserialize<'de> for PeerMessageHoldCause {
3477 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3478 let s = String::deserialize(deserializer)?;
3479 Ok(Self::from(s.as_str()))
3480 }
3481}
3482
3483#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3486pub enum PeerMessageHoldOutcome {
3487 Expired,
3491 Refused,
3493 Dropped,
3496 Discarded,
3498 Unknown(String),
3500}
3501
3502impl PeerMessageHoldOutcome {
3503 pub fn as_str(&self) -> &str {
3504 match self {
3505 Self::Expired => "expired",
3506 Self::Refused => "refused",
3507 Self::Dropped => "dropped",
3508 Self::Discarded => "discarded",
3509 Self::Unknown(s) => s.as_str(),
3510 }
3511 }
3512}
3513
3514impl fmt::Display for PeerMessageHoldOutcome {
3515 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3516 f.write_str(self.as_str())
3517 }
3518}
3519
3520impl From<&str> for PeerMessageHoldOutcome {
3521 fn from(s: &str) -> Self {
3522 match s {
3523 "expired" => Self::Expired,
3524 "refused" => Self::Refused,
3525 "dropped" => Self::Dropped,
3526 "discarded" => Self::Discarded,
3527 other => Self::Unknown(other.to_string()),
3528 }
3529 }
3530}
3531
3532impl Serialize for PeerMessageHoldOutcome {
3533 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3534 serializer.serialize_str(self.as_str())
3535 }
3536}
3537
3538impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
3539 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3540 let s = String::deserialize(deserializer)?;
3541 Ok(Self::from(s.as_str()))
3542 }
3543}
3544
3545#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3548pub struct LocalCommandRun {
3549 pub command: String,
3551 pub args: String,
3554}
3555
3556#[derive(Debug, Clone, Serialize, Deserialize)]
3562pub struct UsageReport {
3563 pub session: super::control::UsageSession,
3565 pub rate_limits: Option<UsageReportRateLimits>,
3569 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3570 pub extra: serde_json::Map<String, Value>,
3571}
3572
3573#[derive(Debug, Clone, Serialize, Deserialize)]
3575pub struct UsageReportRateLimits {
3576 pub limits: Option<Vec<UsageReportLimit>>,
3584 #[serde(default, skip_serializing_if = "Option::is_none")]
3587 pub extra_usage: Option<UsageReportExtraUsage>,
3588 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3589 pub extra: serde_json::Map<String, Value>,
3590}
3591
3592#[derive(Debug, Clone, Serialize, Deserialize)]
3594pub struct UsageReportLimit {
3595 pub kind: String,
3598 pub group: String,
3601 pub percent: f64,
3603 pub resets_at: Option<String>,
3605 #[serde(default, skip_serializing_if = "Option::is_none")]
3608 pub scope: Option<UsageReportScope>,
3609 #[serde(default, skip_serializing_if = "Option::is_none")]
3613 pub severity: Option<String>,
3614 #[serde(default, skip_serializing_if = "Option::is_none")]
3616 pub is_active: Option<bool>,
3617 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3618 pub extra: serde_json::Map<String, Value>,
3619}
3620
3621#[derive(Debug, Clone, Serialize, Deserialize)]
3623pub struct UsageReportScope {
3624 #[serde(default, skip_serializing_if = "Option::is_none")]
3625 pub model: Option<UsageReportScopeLabel>,
3626 #[serde(default, skip_serializing_if = "Option::is_none")]
3627 pub surface: Option<UsageReportScopeLabel>,
3628 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3629 pub extra: serde_json::Map<String, Value>,
3630}
3631
3632#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3634pub struct UsageReportScopeLabel {
3635 pub display_name: String,
3636}
3637
3638#[derive(Debug, Clone, Serialize, Deserialize)]
3642pub struct UsageReportExtraUsage {
3643 pub is_enabled: bool,
3645 pub monthly_limit: Option<f64>,
3646 pub used_credits: Option<f64>,
3647 pub utilization: Option<f64>,
3648 #[serde(default, skip_serializing_if = "Option::is_none")]
3649 pub currency: Option<String>,
3650 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3651 pub extra: serde_json::Map<String, Value>,
3652}
3653
3654#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3657pub struct BatchToolUse {
3658 pub id: String,
3659 pub name: String,
3660}
3661
3662#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3667pub struct ContextUsage {
3668 pub model: String,
3670 pub total_tokens: u64,
3673 pub raw_max_tokens: u64,
3676 pub percentage: u64,
3678 #[serde(default, skip_serializing_if = "Option::is_none")]
3680 pub over_limit: Option<ContextOverLimit>,
3681 #[serde(default)]
3683 pub categories: Vec<ContextCategory>,
3684 #[serde(default)]
3686 pub mcp_tools: Vec<ContextMcpTool>,
3687 #[serde(default)]
3689 pub memory_files: Vec<ContextMemoryFile>,
3690 #[serde(default)]
3692 pub agents: Vec<ContextAgent>,
3693 #[serde(default, skip_serializing_if = "Option::is_none")]
3695 pub skills: Option<Vec<ContextSkill>>,
3696}
3697
3698#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3700pub struct ContextOverLimit {
3701 pub tokens_over: u64,
3702 pub kind: String,
3705}
3706
3707#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3709pub struct ContextCategory {
3710 pub name: String,
3713 pub tokens: u64,
3714 pub kind: String,
3718}
3719
3720#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3722pub struct ContextMcpTool {
3723 pub name: String,
3725 pub server_name: String,
3726 pub tokens: u64,
3727}
3728
3729#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3731pub struct ContextMemoryFile {
3732 pub path: String,
3733 #[serde(rename = "type")]
3735 pub file_type: String,
3736 pub tokens: u64,
3737}
3738
3739#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3741pub struct ContextAgent {
3742 pub agent_type: String,
3743 pub source: String,
3745 pub tokens: u64,
3746}
3747
3748#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3750pub struct ContextSkill {
3751 pub name: String,
3752 pub source: String,
3754 #[serde(default, skip_serializing_if = "Option::is_none")]
3755 pub plugin_name: Option<String>,
3756 pub tokens: u64,
3757}
3758
3759#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3761pub struct ToolUseMeta {
3762 pub id: String,
3763 pub display_name: String,
3764 #[serde(default, skip_serializing_if = "Option::is_none")]
3765 pub server_display_name: Option<String>,
3766 #[serde(default, skip_serializing_if = "Option::is_none")]
3767 pub icon_url: Option<String>,
3768}
3769
3770#[derive(Debug, Clone, Serialize, Deserialize)]
3772pub struct AssistantMessage {
3773 pub message: AssistantMessageContent,
3774 #[serde(alias = "sessionId")]
3775 pub session_id: String,
3776 #[serde(skip_serializing_if = "Option::is_none")]
3777 pub uuid: Option<String>,
3778 #[serde(skip_serializing_if = "Option::is_none")]
3779 pub parent_tool_use_id: Option<String>,
3780 #[serde(skip_serializing_if = "Option::is_none")]
3782 pub request_id: Option<String>,
3783 #[serde(default, skip_serializing_if = "Option::is_none")]
3792 pub user_message_uuid: Option<String>,
3793 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3799 pub user_message_uuids: Vec<String>,
3800 #[serde(skip_serializing_if = "Option::is_none")]
3803 pub subagent_type: Option<String>,
3804 #[serde(skip_serializing_if = "Option::is_none")]
3806 pub task_description: Option<String>,
3807 #[serde(skip_serializing_if = "Option::is_none")]
3808 pub error: Option<AssistantErrorKind>,
3809 #[serde(default, skip_serializing_if = "Option::is_none")]
3813 pub aborted: Option<bool>,
3814 #[serde(default, skip_serializing_if = "Option::is_none")]
3819 pub resumed_from_incomplete_thinking: Option<bool>,
3820 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3821 pub supersedes: Vec<String>,
3822 #[serde(skip_serializing_if = "Option::is_none")]
3823 pub timestamp: Option<String>,
3824 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3825 pub tool_use_meta: Vec<ToolUseMeta>,
3826 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3831 pub batch_tool_uses: Vec<BatchToolUse>,
3832 #[serde(default, skip_serializing_if = "Option::is_none")]
3838 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
3839 #[serde(default, skip_serializing_if = "Option::is_none")]
3845 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
3846 #[serde(default, skip_serializing_if = "Option::is_none")]
3850 pub historical: Option<bool>,
3851 #[serde(default, skip_serializing_if = "Option::is_none")]
3859 pub resume_reason: Option<String>,
3860 #[serde(default, skip_serializing_if = "Option::is_none")]
3866 pub local_command_source: Option<String>,
3867 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3876 pub narration_block_indexes: Vec<usize>,
3877 #[serde(default, skip_serializing_if = "Option::is_none")]
3881 pub context_usage: Option<ContextUsage>,
3882 #[serde(default, skip_serializing_if = "Option::is_none")]
3890 pub usage_report: Option<Box<UsageReport>>,
3891 #[serde(default, skip_serializing_if = "Option::is_none")]
3898 pub local_command_run: Option<LocalCommandRun>,
3899 #[serde(default, skip_serializing_if = "Option::is_none")]
3900 pub is_meta: Option<bool>,
3901 #[serde(default, skip_serializing_if = "Option::is_none")]
3902 pub is_virtual: Option<bool>,
3903 #[serde(default, skip_serializing_if = "Option::is_none")]
3904 pub is_api_error_message: Option<bool>,
3905 #[serde(skip_serializing_if = "Option::is_none")]
3906 pub api_error_status: Option<u16>,
3907 #[serde(skip_serializing_if = "Option::is_none")]
3922 pub api_error: Option<String>,
3923 #[serde(default, skip_serializing_if = "Option::is_none")]
3930 pub api_error_code: Option<String>,
3931 #[serde(default, skip_serializing_if = "Option::is_none")]
3934 pub api_error_params: Option<ApiErrorParams>,
3935 #[serde(skip_serializing_if = "Option::is_none")]
3936 pub error_details: Option<String>,
3937 #[serde(skip_serializing_if = "Option::is_none")]
3938 pub advisor_model: Option<String>,
3939 #[serde(skip_serializing_if = "Option::is_none")]
3940 pub attribution_agent: Option<String>,
3941 #[serde(skip_serializing_if = "Option::is_none")]
3942 pub attribution_skill: Option<String>,
3943 #[serde(skip_serializing_if = "Option::is_none")]
3944 pub attribution_plugin: Option<String>,
3945 #[serde(skip_serializing_if = "Option::is_none")]
3946 pub attribution_mcp_server: Option<String>,
3947 #[serde(skip_serializing_if = "Option::is_none")]
3948 pub attribution_mcp_tool: Option<String>,
3949}
3950
3951#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
3955pub struct ApiErrorParams {
3956 #[serde(default, skip_serializing_if = "Option::is_none")]
3958 pub effort: Option<String>,
3959 #[serde(default, skip_serializing_if = "Option::is_none")]
3962 pub provider: Option<ApiErrorProvider>,
3963 #[serde(default, skip_serializing_if = "Option::is_none")]
3966 pub remedy: Option<ApiErrorRemedy>,
3967}
3968
3969#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3971pub enum ApiErrorProvider {
3972 Bedrock,
3973 AnthropicAws,
3974 Mantle,
3975 AnthropicGoogleCloud,
3976 Vertex,
3977 Foundry,
3978 Gateway,
3979 Unknown(String),
3981}
3982
3983impl ApiErrorProvider {
3984 pub fn as_str(&self) -> &str {
3985 match self {
3986 Self::Bedrock => "bedrock",
3987 Self::AnthropicAws => "anthropicAws",
3988 Self::Mantle => "mantle",
3989 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
3990 Self::Vertex => "vertex",
3991 Self::Foundry => "foundry",
3992 Self::Gateway => "gateway",
3993 Self::Unknown(s) => s.as_str(),
3994 }
3995 }
3996}
3997
3998impl fmt::Display for ApiErrorProvider {
3999 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4000 f.write_str(self.as_str())
4001 }
4002}
4003
4004impl From<&str> for ApiErrorProvider {
4005 fn from(s: &str) -> Self {
4006 match s {
4007 "bedrock" => Self::Bedrock,
4008 "anthropicAws" => Self::AnthropicAws,
4009 "mantle" => Self::Mantle,
4010 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4011 "vertex" => Self::Vertex,
4012 "foundry" => Self::Foundry,
4013 "gateway" => Self::Gateway,
4014 other => Self::Unknown(other.to_string()),
4015 }
4016 }
4017}
4018
4019impl Serialize for ApiErrorProvider {
4020 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4021 serializer.serialize_str(self.as_str())
4022 }
4023}
4024
4025impl<'de> Deserialize<'de> for ApiErrorProvider {
4026 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4027 let s = String::deserialize(deserializer)?;
4028 Ok(Self::from(s.as_str()))
4029 }
4030}
4031
4032#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4034pub enum ApiErrorRemedy {
4035 RefreshCommand,
4038 RefreshCredentials,
4040 Adc,
4043 GatewayToken,
4046 HostManaged,
4048 ModelAccess,
4051 Unknown(String),
4053}
4054
4055impl ApiErrorRemedy {
4056 pub fn as_str(&self) -> &str {
4057 match self {
4058 Self::RefreshCommand => "refresh_command",
4059 Self::RefreshCredentials => "refresh_credentials",
4060 Self::Adc => "adc",
4061 Self::GatewayToken => "gateway_token",
4062 Self::HostManaged => "host_managed",
4063 Self::ModelAccess => "model_access",
4064 Self::Unknown(s) => s.as_str(),
4065 }
4066 }
4067}
4068
4069impl fmt::Display for ApiErrorRemedy {
4070 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4071 f.write_str(self.as_str())
4072 }
4073}
4074
4075impl From<&str> for ApiErrorRemedy {
4076 fn from(s: &str) -> Self {
4077 match s {
4078 "refresh_command" => Self::RefreshCommand,
4079 "refresh_credentials" => Self::RefreshCredentials,
4080 "adc" => Self::Adc,
4081 "gateway_token" => Self::GatewayToken,
4082 "host_managed" => Self::HostManaged,
4083 "model_access" => Self::ModelAccess,
4084 other => Self::Unknown(other.to_string()),
4085 }
4086 }
4087}
4088
4089impl Serialize for ApiErrorRemedy {
4090 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4091 serializer.serialize_str(self.as_str())
4092 }
4093}
4094
4095impl<'de> Deserialize<'de> for ApiErrorRemedy {
4096 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4097 let s = String::deserialize(deserializer)?;
4098 Ok(Self::from(s.as_str()))
4099 }
4100}
4101
4102#[derive(Debug, Clone, Serialize, Deserialize)]
4104pub struct AssistantMessageContent {
4105 pub id: String,
4106 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
4108 pub message_type: Option<String>,
4109 pub role: MessageRole,
4110 pub model: String,
4111 pub content: Vec<ContentBlock>,
4112 #[serde(skip_serializing_if = "Option::is_none")]
4113 pub stop_reason: Option<StopReason>,
4114 #[serde(skip_serializing_if = "Option::is_none")]
4115 pub stop_sequence: Option<String>,
4116 #[serde(skip_serializing_if = "Option::is_none")]
4117 pub usage: Option<AssistantUsage>,
4118 #[serde(skip_serializing_if = "Option::is_none")]
4120 pub stop_details: Option<Value>,
4121 #[serde(skip_serializing_if = "Option::is_none")]
4123 pub context_management: Option<Value>,
4124}
4125
4126#[derive(Debug, Clone, Serialize, Deserialize)]
4128pub struct AssistantUsage {
4129 #[serde(default)]
4131 pub input_tokens: u32,
4132
4133 #[serde(default)]
4135 pub output_tokens: u32,
4136
4137 #[serde(default)]
4139 pub cache_creation_input_tokens: u32,
4140
4141 #[serde(default)]
4143 pub cache_read_input_tokens: u32,
4144
4145 #[serde(skip_serializing_if = "Option::is_none")]
4147 pub service_tier: Option<String>,
4148
4149 #[serde(skip_serializing_if = "Option::is_none")]
4151 pub cache_creation: Option<CacheCreationDetails>,
4152
4153 #[serde(skip_serializing_if = "Option::is_none")]
4155 pub inference_geo: Option<String>,
4156}
4157
4158#[derive(Debug, Clone, Serialize, Deserialize)]
4160pub struct CacheCreationDetails {
4161 #[serde(default)]
4163 pub ephemeral_1h_input_tokens: u32,
4164
4165 #[serde(default)]
4167 pub ephemeral_5m_input_tokens: u32,
4168}
4169
4170#[cfg(test)]
4171mod tests {
4172 use crate::io::ClaudeOutput;
4173
4174 #[test]
4175 fn test_subagent_usage_rollup_accumulates_task_results() {
4176 use super::SubagentUsageRollup;
4177
4178 let mut rollup = SubagentUsageRollup::default();
4179
4180 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}}"#;
4181 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
4182 assert!(rollup.observe(&output));
4183 assert_eq!(rollup.subagent_tokens, 10201);
4184 assert_eq!(rollup.agent_count, 1);
4185 assert_eq!(rollup.tool_uses, 3);
4186 assert_eq!(rollup.duration_ms, 1853);
4187
4188 assert!(!rollup.observe(&output));
4190 assert_eq!(rollup.agent_count, 1);
4191 assert_eq!(rollup.subagent_tokens, 10201);
4192
4193 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}}"#;
4195 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
4196 assert!(rollup.observe(&output));
4197 assert_eq!(rollup.agent_count, 2);
4198 assert_eq!(rollup.subagent_tokens, 10701);
4199 }
4200
4201 #[test]
4202 fn test_subagent_usage_rollup_ignores_non_task_results() {
4203 use super::SubagentUsageRollup;
4204
4205 let mut rollup = SubagentUsageRollup::default();
4206
4207 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}}"#;
4210 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
4211 assert!(!rollup.observe(&output));
4212
4213 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4215 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
4216 assert!(!rollup.observe(&output));
4217
4218 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4220 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
4221 assert!(!rollup.observe(&output));
4222
4223 assert_eq!(rollup, SubagentUsageRollup::default());
4224 }
4225
4226 #[test]
4227 fn test_subagent_usage_rollup_over_captured_session() {
4228 use super::SubagentUsageRollup;
4229
4230 let mut rollup = SubagentUsageRollup::default();
4231 let fixture =
4232 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
4233 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
4234 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
4235 rollup.observe(&output);
4236 }
4237 }
4238 assert_eq!(rollup.agent_count, 1);
4239 assert_eq!(rollup.subagent_tokens, 10201);
4240 }
4241
4242 #[test]
4243 fn test_system_message_init() {
4244 let json = r#"{
4245 "type": "system",
4246 "subtype": "init",
4247 "session_id": "test-session-123",
4248 "cwd": "/home/user/project",
4249 "model": "claude-sonnet-4",
4250 "tools": ["Bash", "Read", "Write"],
4251 "mcp_servers": [],
4252 "slash_commands": ["compact", "cost", "review"],
4253 "agents": ["Bash", "Explore", "Plan"],
4254 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
4255 "skills": [],
4256 "claude_code_version": "2.1.15",
4257 "apiKeySource": "none",
4258 "output_style": "default",
4259 "permissionMode": "default"
4260 }"#;
4261
4262 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4263 if let ClaudeOutput::System(sys) = output {
4264 assert!(sys.is_init());
4265 assert!(!sys.is_status());
4266 assert!(!sys.is_compact_boundary());
4267
4268 let init = sys.as_init().expect("Should parse as init");
4269 assert_eq!(init.session_id, "test-session-123");
4270 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
4271 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
4272 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
4273 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
4274 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
4275 assert_eq!(init.plugins.len(), 1);
4276 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4277 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4278 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
4279 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
4280 assert_eq!(
4281 init.permission_mode,
4282 Some(super::InitPermissionMode::Default)
4283 );
4284 } else {
4285 panic!("Expected System message");
4286 }
4287 }
4288
4289 #[test]
4290 fn test_system_message_init_from_real_capture() {
4291 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
4292 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4293 if let ClaudeOutput::System(sys) = output {
4294 let init = sys.as_init().expect("Should parse real init capture");
4295 assert_eq!(init.slash_commands.len(), 8);
4296 assert!(init.slash_commands.contains(&"compact".to_string()));
4297 assert!(init.slash_commands.contains(&"review".to_string()));
4298 assert_eq!(init.agents.len(), 5);
4299 assert!(init.agents.contains(&"Bash".to_string()));
4300 assert!(init.agents.contains(&"Explore".to_string()));
4301 assert_eq!(init.plugins.len(), 1);
4302 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4303 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4304 } else {
4305 panic!("Expected System message");
4306 }
4307 }
4308
4309 #[test]
4310 fn test_system_message_status() {
4311 let json = r#"{
4312 "type": "system",
4313 "subtype": "status",
4314 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4315 "status": "compacting",
4316 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
4317 }"#;
4318
4319 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4320 if let ClaudeOutput::System(sys) = output {
4321 assert!(sys.is_status());
4322 assert!(!sys.is_init());
4323
4324 let status = sys.as_status().expect("Should parse as status");
4325 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4326 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
4327 assert_eq!(
4328 status.uuid,
4329 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
4330 );
4331 } else {
4332 panic!("Expected System message");
4333 }
4334 }
4335
4336 #[test]
4337 fn test_system_message_status_null() {
4338 let json = r#"{
4339 "type": "system",
4340 "subtype": "status",
4341 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4342 "status": null,
4343 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
4344 }"#;
4345
4346 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4347 if let ClaudeOutput::System(sys) = output {
4348 let status = sys.as_status().expect("Should parse as status");
4349 assert_eq!(status.status, None);
4350 } else {
4351 panic!("Expected System message");
4352 }
4353 }
4354
4355 #[test]
4356 fn test_system_message_task_started() {
4357 let json = r#"{
4358 "type": "system",
4359 "subtype": "task_started",
4360 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4361 "task_id": "b6daf3f",
4362 "task_type": "local_bash",
4363 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
4364 "description": "Wait for CI on PR #12",
4365 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
4366 }"#;
4367
4368 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4369 if let ClaudeOutput::System(sys) = output {
4370 assert!(sys.is_task_started());
4371 assert!(!sys.is_task_progress());
4372 assert!(!sys.is_task_notification());
4373
4374 let task = sys.as_task_started().expect("Should parse as task_started");
4375 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
4376 assert_eq!(task.task_id, "b6daf3f");
4377 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
4378 assert_eq!(
4379 task.tool_use_id.as_deref(),
4380 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
4381 );
4382 assert_eq!(task.description, "Wait for CI on PR #12");
4383 } else {
4384 panic!("Expected System message");
4385 }
4386 }
4387
4388 #[test]
4389 fn test_system_message_task_started_agent() {
4390 let json = r#"{
4391 "type": "system",
4392 "subtype": "task_started",
4393 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4394 "task_id": "a4a7e0906e5fc64cc",
4395 "task_type": "local_agent",
4396 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4397 "description": "Explore Scene/ArrayScene duplication",
4398 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4399 }"#;
4400
4401 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4402 if let ClaudeOutput::System(sys) = output {
4403 let task = sys.as_task_started().expect("Should parse as task_started");
4404 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
4405 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
4406 } else {
4407 panic!("Expected System message");
4408 }
4409 }
4410
4411 #[test]
4412 fn test_system_message_task_progress() {
4413 let json = r#"{
4414 "type": "system",
4415 "subtype": "task_progress",
4416 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4417 "task_id": "a4a7e0906e5fc64cc",
4418 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4419 "description": "Reading src/jplephem/chebyshev.rs",
4420 "last_tool_name": "Read",
4421 "usage": {
4422 "duration_ms": 13996,
4423 "tool_uses": 9,
4424 "total_tokens": 38779
4425 },
4426 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4427 }"#;
4428
4429 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4430 if let ClaudeOutput::System(sys) = output {
4431 assert!(sys.is_task_progress());
4432 assert!(!sys.is_task_started());
4433
4434 let progress = sys
4435 .as_task_progress()
4436 .expect("Should parse as task_progress");
4437 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
4438 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
4439 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
4440 assert_eq!(progress.usage.duration_ms, 13996);
4441 assert_eq!(progress.usage.tool_uses, 9);
4442 assert_eq!(progress.usage.total_tokens, 38779);
4443 } else {
4444 panic!("Expected System message");
4445 }
4446 }
4447
4448 #[test]
4449 fn test_system_message_task_notification_completed() {
4450 let json = r#"{
4451 "type": "system",
4452 "subtype": "task_notification",
4453 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4454 "task_id": "a0ba761e9dc9c316f",
4455 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
4456 "status": "completed",
4457 "summary": "Agent \"Write Hipparcos data source doc\" completed",
4458 "output_file": "",
4459 "usage": {
4460 "duration_ms": 172300,
4461 "tool_uses": 11,
4462 "total_tokens": 42005
4463 },
4464 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
4465 }"#;
4466
4467 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4468 if let ClaudeOutput::System(sys) = output {
4469 assert!(sys.is_task_notification());
4470
4471 let notif = sys
4472 .as_task_notification()
4473 .expect("Should parse as task_notification");
4474 assert_eq!(notif.status, super::TaskStatus::Completed);
4475 assert_eq!(
4476 notif.summary,
4477 "Agent \"Write Hipparcos data source doc\" completed"
4478 );
4479 assert_eq!(notif.output_file, Some("".to_string()));
4480 assert_eq!(
4481 notif.tool_use_id,
4482 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
4483 );
4484 let usage = notif.usage.expect("Should have usage");
4485 assert_eq!(usage.duration_ms, 172300);
4486 assert_eq!(usage.tool_uses, 11);
4487 assert_eq!(usage.total_tokens, 42005);
4488 } else {
4489 panic!("Expected System message");
4490 }
4491 }
4492
4493 #[test]
4494 fn test_system_message_task_notification_failed_no_usage() {
4495 let json = r#"{
4496 "type": "system",
4497 "subtype": "task_notification",
4498 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
4499 "task_id": "b98f6a3",
4500 "status": "failed",
4501 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
4502 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
4503 }"#;
4504
4505 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4506 if let ClaudeOutput::System(sys) = output {
4507 let notif = sys
4508 .as_task_notification()
4509 .expect("Should parse as task_notification");
4510 assert_eq!(notif.status, super::TaskStatus::Failed);
4511 assert!(notif.tool_use_id.is_none());
4512 assert!(notif.usage.is_none());
4513 assert_eq!(
4514 notif.output_file,
4515 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
4516 );
4517 } else {
4518 panic!("Expected System message");
4519 }
4520 }
4521
4522 #[test]
4528 fn test_task_messages_roundtrip_through_value() {
4529 let cases = [
4530 r#"{"type":"system","subtype":"task_started","session_id":"s1",
4531 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
4532 "description":"Sleep 3s","uuid":"u1"}"#,
4533 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
4534 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
4535 "last_tool_name":"Bash",
4536 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4537 "uuid":"u2"}"#,
4538 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
4539 "task_id":"t1","tool_use_id":"tu1","status":"completed",
4540 "summary":"done","output_file":"",
4541 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4542 "uuid":"u3"}"#,
4543 ];
4544
4545 for json in cases {
4546 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4547 let value = serde_json::to_value(&output).unwrap();
4548 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
4549
4550 let ClaudeOutput::System(sys) = reparsed else {
4551 panic!("Expected System variant after round-trip");
4552 };
4553
4554 match sys.subtype {
4555 super::SystemSubtype::TaskStarted => {
4556 assert!(
4557 sys.as_task_started().is_some(),
4558 "as_task_started failed after round-trip"
4559 );
4560 }
4561 super::SystemSubtype::TaskProgress => {
4562 assert!(
4563 sys.as_task_progress().is_some(),
4564 "as_task_progress failed after round-trip"
4565 );
4566 }
4567 super::SystemSubtype::TaskNotification => {
4568 assert!(
4569 sys.as_task_notification().is_some(),
4570 "as_task_notification failed after round-trip"
4571 );
4572 }
4573 other => panic!("unexpected subtype after round-trip: {other:?}"),
4574 }
4575 }
4576 }
4577
4578 #[test]
4579 fn test_system_message_compact_boundary() {
4580 let json = r#"{
4581 "type": "system",
4582 "subtype": "compact_boundary",
4583 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4584 "compact_metadata": {
4585 "pre_tokens": 155285,
4586 "trigger": "auto"
4587 },
4588 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
4589 }"#;
4590
4591 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4592 if let ClaudeOutput::System(sys) = output {
4593 assert!(sys.is_compact_boundary());
4594 assert!(!sys.is_init());
4595 assert!(!sys.is_status());
4596
4597 let compact = sys
4598 .as_compact_boundary()
4599 .expect("Should parse as compact_boundary");
4600 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4601 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
4602 assert_eq!(
4603 compact.compact_metadata.trigger,
4604 super::CompactionTrigger::Auto
4605 );
4606 assert!(compact.summary.is_none());
4608 assert!(compact.leaf_message_count.is_none());
4609 assert!(compact.duration_ms.is_none());
4610 } else {
4611 panic!("Expected System message");
4612 }
4613 }
4614
4615 #[test]
4616 fn test_compact_boundary_with_summary_stats() {
4617 let json = r#"{
4619 "type": "system",
4620 "subtype": "compact_boundary",
4621 "session_id": "s1",
4622 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
4623 "summary": "Summarized the earlier exploration.",
4624 "leaf_message_count": 42,
4625 "duration_ms": 1234,
4626 "uuid": "u1"
4627 }"#;
4628 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4629 let ClaudeOutput::System(sys) = output else {
4630 panic!("Expected System message");
4631 };
4632 let compact = sys.as_compact_boundary().expect("compact_boundary");
4633 assert_eq!(
4634 compact.summary.as_deref(),
4635 Some("Summarized the earlier exploration.")
4636 );
4637 assert_eq!(compact.leaf_message_count, Some(42));
4638 assert_eq!(compact.duration_ms, Some(1234));
4639
4640 let json_alt = r#"{
4643 "type": "system",
4644 "subtype": "compact_boundary",
4645 "session_id": "s2",
4646 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
4647 "content": "alt-key summary",
4648 "message_count": 7
4649 }"#;
4650 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
4651 let ClaudeOutput::System(sys) = output else {
4652 panic!("Expected System message");
4653 };
4654 let compact = sys.as_compact_boundary().expect("compact_boundary");
4655 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
4656 assert_eq!(compact.leaf_message_count, Some(7));
4657 }
4658
4659 #[test]
4660 fn test_init_message_with_new_fields() {
4661 let json = r#"{
4662 "type": "system",
4663 "subtype": "init",
4664 "session_id": "test-session",
4665 "cwd": "/home/user",
4666 "model": "claude-opus-4-7",
4667 "tools": ["Bash"],
4668 "mcp_servers": [],
4669 "permissionMode": "default",
4670 "apiKeySource": "none",
4671 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
4672 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
4673 "fast_mode_state": "off",
4674 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
4675 "claude_code_version": "2.1.117"
4676 }"#;
4677
4678 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4679 if let ClaudeOutput::System(sys) = output {
4680 let init = sys.as_init().expect("Should parse as init");
4681 assert_eq!(
4682 init.uuid.as_deref(),
4683 Some("44841a0d-182d-493a-86b5-79800d3d9665")
4684 );
4685 assert!(init.memory_paths.is_some());
4686 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
4687 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
4688 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
4689 } else {
4690 panic!("Expected System message");
4691 }
4692 }
4693
4694 #[test]
4695 fn test_assistant_message_with_new_fields() {
4696 let json = r#"{
4697 "type": "assistant",
4698 "message": {
4699 "id": "msg_1",
4700 "type": "message",
4701 "role": "assistant",
4702 "model": "claude-opus-4-7",
4703 "content": [{"type": "text", "text": "Hello"}],
4704 "stop_reason": "end_turn",
4705 "stop_details": null,
4706 "context_management": null,
4707 "usage": {
4708 "input_tokens": 100,
4709 "output_tokens": 10,
4710 "cache_creation_input_tokens": 50,
4711 "cache_read_input_tokens": 0,
4712 "service_tier": "standard",
4713 "inference_geo": "not_available"
4714 }
4715 },
4716 "session_id": "abc",
4717 "uuid": "msg-uuid-123"
4718 }"#;
4719
4720 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4721 if let ClaudeOutput::Assistant(asst) = output {
4722 assert_eq!(asst.message.stop_details, None);
4723 assert_eq!(asst.message.context_management, None);
4724 let usage = asst.message.usage.unwrap();
4725 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
4726 } else {
4727 panic!("Expected Assistant message");
4728 }
4729 }
4730
4731 #[test]
4732 fn test_user_message_with_new_fields() {
4733 let json = r#"{
4734 "type": "user",
4735 "message": {
4736 "role": "user",
4737 "content": [{"type": "text", "text": "Hello"}]
4738 },
4739 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4740 "parent_tool_use_id": "toolu_123",
4741 "uuid": "user-msg-456"
4742 }"#;
4743
4744 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4745 if let ClaudeOutput::User(user) = output {
4746 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
4747 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
4748 } else {
4749 panic!("Expected User message");
4750 }
4751 }
4752
4753 #[test]
4759 fn test_user_message_preserves_tool_use_result_and_timestamp() {
4760 let json = r#"{
4761 "type":"user",
4762 "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"}]},
4763 "parent_tool_use_id":null,
4764 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4765 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
4766 "timestamp":"2026-05-12T23:12:04.121Z",
4767 "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"}}
4768 }"#;
4769
4770 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4771 let user = match output {
4772 ClaudeOutput::User(u) => u,
4773 other => panic!("Expected User message, got {:?}", other.message_type()),
4774 };
4775
4776 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
4777 let raw = user
4778 .tool_use_result
4779 .as_ref()
4780 .expect("tool_use_result must be captured");
4781 assert_eq!(raw["answers"]["Color"], "Blue");
4782 assert_eq!(raw["questions"][0]["header"], "Color");
4783
4784 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
4788 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
4789 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
4790 assert_eq!(
4791 reser["tool_use_result"]["questions"][0]["question"],
4792 "Which color do you prefer?"
4793 );
4794
4795 let typed: crate::AskUserQuestionInput = user
4798 .tool_use_result_as::<crate::AskUserQuestionInput>()
4799 .expect("tool_use_result present")
4800 .expect("AskUserQuestionInput parses");
4801 assert_eq!(typed.questions.len(), 1);
4802 assert_eq!(typed.questions[0].header, "Color");
4803 let answers = typed.answers.expect("answers populated");
4804 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
4805 }
4806
4807 #[test]
4810 fn test_user_message_without_tool_use_result_omits_field() {
4811 let json = r#"{
4812 "type":"user",
4813 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
4814 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
4815 }"#;
4816
4817 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4818 let user = match output {
4819 ClaudeOutput::User(u) => u,
4820 _ => panic!("Expected User message"),
4821 };
4822 assert!(user.tool_use_result.is_none());
4823 assert!(user.timestamp.is_none());
4824
4825 let reser = serde_json::to_value(&user).unwrap();
4826 assert!(reser.get("tool_use_result").is_none());
4827 assert!(reser.get("timestamp").is_none());
4828 }
4829
4830 #[test]
4834 fn test_subagent_result_exposes_token_accounting() {
4835 let json = r#"{
4836 "type":"user",
4837 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
4838 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
4839 "tool_use_result":{
4840 "status":"completed",
4841 "prompt":"Count the .rs files.",
4842 "agentId":"ac4f0276e9d4b6232",
4843 "agentType":"Explore",
4844 "content":[{"type":"text","text":"21"}],
4845 "resolvedModel":"claude-haiku-4-5-20251001",
4846 "totalDurationMs":6869,
4847 "totalTokens":7834,
4848 "totalToolUseCount":1,
4849 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
4850 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
4851 }
4852 }"#;
4853
4854 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4855 let user = match output {
4856 ClaudeOutput::User(u) => u,
4857 _ => panic!("Expected User message"),
4858 };
4859
4860 let result = user.subagent_result().expect("subagent result parses");
4861 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
4862 assert_eq!(
4863 result.resolved_model.as_deref(),
4864 Some("claude-haiku-4-5-20251001")
4865 );
4866 assert_eq!(result.total_tokens, Some(7834));
4867 assert_eq!(result.total_duration_ms, Some(6869));
4868 assert_eq!(result.total_tool_use_count, Some(1));
4869
4870 let usage = result.usage.expect("nested usage present");
4871 assert_eq!(usage.input_tokens, 6);
4872 assert_eq!(usage.cache_read_input_tokens, 7699);
4873
4874 let stats = result.tool_stats.expect("toolStats present");
4875 assert_eq!(stats.bash_count, 1);
4876 }
4877
4878 #[test]
4882 fn test_subagent_result_absent_for_non_task_result() {
4883 let json = r#"{
4884 "type":"user",
4885 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
4886 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4887 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
4888 }"#;
4889
4890 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4891 let user = match output {
4892 ClaudeOutput::User(u) => u,
4893 _ => panic!("Expected User message"),
4894 };
4895
4896 let result = user.subagent_result().expect("lenient parse");
4897 assert_eq!(result.total_tokens, None);
4898 assert_eq!(result.agent_type, None);
4899 }
4900
4901 #[test]
4902 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
4903 use serde_json::Value;
4904
4905 let raw: Value = serde_json::from_str(
4906 r#"{
4907 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
4908 "fast_mode_state":"off",
4909 "fast_mode_disabled_reason":"not_first_party",
4910 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
4911 }"#,
4912 )
4913 .unwrap();
4914 crate::io::assert_fully_wrapped(&raw);
4915
4916 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4917 let ClaudeOutput::System(sys) = output else {
4918 panic!("expected System");
4919 };
4920 let init = sys.as_init().expect("parses as init");
4921 assert_eq!(
4922 init.fast_mode_disabled_reason,
4923 Some(crate::FastModeDisabledReason::NotFirstParty)
4924 );
4925 let errs = init.mcp_server_errors.unwrap();
4926 assert_eq!(errs.len(), 1);
4927 assert_eq!(errs[0].name, "broken");
4928 assert_eq!(errs[0].error_type, "invalid_config");
4929 }
4930
4931 #[test]
4932 fn test_code_change_published_fully_wrapped() {
4933 use super::{KnownSystemEvent, SystemSubtype};
4934 use serde_json::Value;
4935
4936 let raw: Value = serde_json::from_str(
4937 r#"{
4938 "type":"system","subtype":"code_change_published",
4939 "provider":"github","url":"https://github.com/owner/repo/pull/42",
4940 "repo":"owner/repo","identifier":"42",
4941 "uuid":"u1","session_id":"s1"
4942 }"#,
4943 )
4944 .unwrap();
4945 crate::io::assert_fully_wrapped(&raw);
4946
4947 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4948 let ClaudeOutput::System(sys) = output else {
4949 panic!("expected System");
4950 };
4951 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
4952 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
4953 panic!("expected CodeChangePublished event");
4954 };
4955 assert_eq!(msg.provider, "github");
4956 assert_eq!(msg.repo, "owner/repo");
4957 assert_eq!(msg.identifier, "42");
4958
4959 assert!(sys.is_code_change_published());
4960 assert!(!sys.is_vcs_state_changed());
4961 let direct = sys.as_code_change_published().expect("direct accessor");
4962 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
4963 assert!(sys.as_vcs_state_changed().is_none());
4964 }
4965
4966 #[test]
4967 fn test_feedback_draft_queued_fully_wrapped() {
4968 use super::{KnownSystemEvent, SystemSubtype};
4969 use serde_json::Value;
4970
4971 let raw: Value = serde_json::from_str(
4972 r#"{
4973 "type":"system","subtype":"feedback_draft_queued",
4974 "draft_id":"draft-1","draft_type":"bug_report",
4975 "title":"Tool output was truncated",
4976 "details_preview":"The last command omitted its final lines",
4977 "uuid":"u1","session_id":"s1","future_field":"preserved"
4978 }"#,
4979 )
4980 .unwrap();
4981 crate::io::assert_fully_wrapped(&raw);
4982
4983 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4984 let ClaudeOutput::System(sys) = output else {
4985 panic!("expected System");
4986 };
4987 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
4988 assert!(sys.is_feedback_draft_queued());
4989 assert!(!sys.is_vcs_state_changed());
4990
4991 let direct = sys
4992 .as_feedback_draft_queued()
4993 .expect("direct typed accessor");
4994 assert_eq!(direct.draft_id, "draft-1");
4995 assert_eq!(direct.draft_type, "bug_report");
4996 assert_eq!(direct.extra["future_field"], "preserved");
4997
4998 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
4999 panic!("expected FeedbackDraftQueued event");
5000 };
5001 assert_eq!(known.title, "Tool output was truncated");
5002 assert_eq!(
5003 sys.typed_value().expect("typed value")["future_field"],
5004 "preserved"
5005 );
5006 }
5007
5008 #[test]
5009 fn test_cloud_session_delta_fully_wrapped() {
5010 use super::{KnownSystemEvent, SystemSubtype};
5011 use serde_json::Value;
5012
5013 let raw: Value = serde_json::from_str(
5014 r#"{
5015 "type":"system","subtype":"cloud_session_delta",
5016 "seq":3,"changed":["serving","connection"],
5017 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5018 "serving":{"state":"on"},"connection":{"state":"live"}},
5019 "uuid":"u1","session_id":"s1","future_field":"preserved"
5020 }"#,
5021 )
5022 .unwrap();
5023 crate::io::assert_fully_wrapped(&raw);
5024
5025 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5026 let ClaudeOutput::System(sys) = output else {
5027 panic!("expected System");
5028 };
5029 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5030 assert!(sys.is_cloud_session_delta());
5031 assert!(!sys.is_feedback_draft_queued());
5032
5033 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5034 assert_eq!(direct.seq, 3);
5035 assert_eq!(direct.changed, vec!["serving", "connection"]);
5036 assert_eq!(direct.cloud_session["id"], "session_abc");
5037 assert_eq!(direct.extra["future_field"], "preserved");
5038
5039 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5040 panic!("expected CloudSessionDelta event");
5041 };
5042 assert_eq!(known.session_id, "s1");
5043 assert_eq!(
5044 sys.typed_value().expect("typed value")["future_field"],
5045 "preserved"
5046 );
5047 }
5048
5049 #[test]
5050 fn test_vcs_state_changed_fully_wrapped() {
5051 use super::{KnownSystemEvent, VcsMutationKind};
5052 use serde_json::Value;
5053
5054 for kind in ["commit", "push", "merge", "rebase"] {
5055 let raw: Value = serde_json::from_str(&format!(
5056 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5057 kind
5058 ))
5059 .unwrap();
5060 crate::io::assert_fully_wrapped(&raw);
5061
5062 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5063 let ClaudeOutput::System(sys) = output else {
5064 panic!("expected System");
5065 };
5066 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5067 panic!("expected VcsStateChanged event");
5068 };
5069 assert_eq!(msg.kind.as_str(), kind);
5070 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
5071 }
5072
5073 let raw: Value = serde_json::from_str(
5075 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
5076 )
5077 .unwrap();
5078 crate::io::assert_fully_wrapped(&raw);
5079 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5080 let ClaudeOutput::System(sys) = output else {
5081 panic!("expected System");
5082 };
5083 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5084 panic!("expected VcsStateChanged event");
5085 };
5086 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
5087
5088 assert!(sys.is_vcs_state_changed());
5089 let direct = sys.as_vcs_state_changed().expect("direct accessor");
5090 assert_eq!(direct.cwd, "/repo");
5091 assert!(sys.as_code_change_published().is_none());
5092 }
5093
5094 #[test]
5095 fn test_assistant_aborted_and_resume_flags_roundtrip() {
5096 let json = r#"{
5097 "type":"assistant",
5098 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
5099 "session_id":"s1",
5100 "aborted":true,
5101 "resumed_from_incomplete_thinking":true
5102 }"#;
5103 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5104 let ClaudeOutput::Assistant(msg) = &output else {
5105 panic!("expected Assistant");
5106 };
5107 assert_eq!(msg.aborted, Some(true));
5108 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
5109 let reserialized = serde_json::to_string(&output).unwrap();
5110 assert!(reserialized.contains("\"aborted\":true"));
5111 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
5112
5113 let json = r#"{
5115 "type":"assistant",
5116 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
5117 "session_id":"s2"
5118 }"#;
5119 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5120 let reserialized = serde_json::to_string(&output).unwrap();
5121 assert!(!reserialized.contains("aborted"));
5122 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
5123 }
5124
5125 #[test]
5126 fn test_user_tool_result_meta_roundtrip() {
5127 let json = r#"{
5128 "type":"user",
5129 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
5130 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5131 "tool_result_meta":[
5132 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
5133 {"id":"toolu_2","non_execution_kind":"permission-rule"}
5134 ]
5135 }"#;
5136 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5137 let ClaudeOutput::User(user) = &output else {
5138 panic!("expected User");
5139 };
5140 let meta = user.tool_result_meta.as_ref().unwrap();
5141 assert_eq!(meta.len(), 2);
5142 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
5143 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
5144 assert_eq!(meta[1].user_feedback, None);
5145
5146 let reserialized = serde_json::to_string(&output).unwrap();
5147 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
5148 assert!(!reserialized.contains("\"user_feedback\":null"));
5149 }
5150
5151 #[test]
5155 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
5156 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
5157 let with_scope = serde_json::json!({
5158 "trigger": "refusal",
5159 "direction": "retry",
5160 "scope": "local",
5161 "original_model": "claude-fable-5",
5162 "fallback_model": "claude-opus-5",
5163 "request_id": null,
5164 "content": "Refused; retried on fallback model.",
5165 "uuid": "u1",
5166 "session_id": "s1"
5167 });
5168 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
5169 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
5170 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
5171
5172 let mut without = with_scope.clone();
5175 without.as_object_mut().unwrap().remove("scope");
5176 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
5177 assert_eq!(msg.scope, None);
5178 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
5179
5180 assert_eq!(
5182 RefusalFallbackScope::from("workspace").as_str(),
5183 "workspace"
5184 );
5185 }
5186}