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")]
1076 pub initiator: Option<String>,
1077 #[serde(default, skip_serializing_if = "Option::is_none")]
1083 pub pasted_content: Option<Vec<Value>>,
1084 #[serde(default, skip_serializing_if = "Option::is_none")]
1088 pub historical: Option<bool>,
1089}
1090
1091impl UserMessage {
1092 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1100 &self,
1101 ) -> Option<Result<T, serde_json::Error>> {
1102 self.tool_use_result
1103 .as_ref()
1104 .map(|v| serde_json::from_value(v.clone()))
1105 }
1106
1107 pub fn subagent_result(&self) -> Option<SubagentResult> {
1118 self.tool_use_result
1119 .as_ref()
1120 .and_then(|v| serde_json::from_value(v.clone()).ok())
1121 }
1122}
1123
1124#[derive(Debug, Clone, Serialize, Deserialize)]
1135pub struct SubagentResult {
1136 #[serde(skip_serializing_if = "Option::is_none")]
1138 pub status: Option<String>,
1139 #[serde(skip_serializing_if = "Option::is_none")]
1141 pub prompt: Option<String>,
1142 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1144 pub agent_id: Option<String>,
1145 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1147 pub agent_type: Option<String>,
1148 #[serde(
1150 default,
1151 deserialize_with = "deserialize_content_blocks",
1152 skip_serializing_if = "Vec::is_empty"
1153 )]
1154 pub content: Vec<ContentBlock>,
1155 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1157 pub resolved_model: Option<String>,
1158 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1160 pub total_duration_ms: Option<u64>,
1161 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1163 pub total_tokens: Option<u64>,
1164 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1166 pub total_tool_use_count: Option<u64>,
1167 #[serde(skip_serializing_if = "Option::is_none")]
1169 pub usage: Option<super::result::UsageInfo>,
1170 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1172 pub tool_stats: Option<SubagentToolStats>,
1173}
1174
1175#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1180#[serde(rename_all = "camelCase")]
1181pub struct SubagentToolStats {
1182 #[serde(default)]
1183 pub read_count: u64,
1184 #[serde(default)]
1185 pub search_count: u64,
1186 #[serde(default)]
1187 pub bash_count: u64,
1188 #[serde(default)]
1189 pub edit_file_count: u64,
1190 #[serde(default)]
1191 pub lines_added: u64,
1192 #[serde(default)]
1193 pub lines_removed: u64,
1194 #[serde(default)]
1195 pub other_tool_count: u64,
1196 #[serde(flatten)]
1197 pub extra: serde_json::Map<String, Value>,
1198}
1199
1200#[derive(Debug, Clone, Default, PartialEq, Eq)]
1227pub struct SubagentUsageRollup {
1228 pub subagent_tokens: u64,
1231 pub agent_count: u32,
1233 pub tool_uses: u64,
1235 pub duration_ms: u64,
1237 seen_agent_ids: std::collections::BTreeSet<String>,
1238}
1239
1240impl SubagentUsageRollup {
1241 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1247 match output {
1248 ClaudeOutput::User(user) => self.observe_user(user),
1249 _ => false,
1250 }
1251 }
1252
1253 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1260 let Some(result) = user.subagent_result() else {
1261 return false;
1262 };
1263 if result.total_tokens.is_none() && result.agent_id.is_none() {
1264 return false;
1265 }
1266 if let Some(agent_id) = &result.agent_id {
1267 if !self.seen_agent_ids.insert(agent_id.clone()) {
1268 return false;
1269 }
1270 }
1271 self.agent_count += 1;
1272 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1273 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1274 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1275 true
1276 }
1277}
1278
1279#[derive(Debug, Clone, Serialize, Deserialize)]
1281pub struct MessageContent {
1282 pub role: MessageRole,
1283 #[serde(deserialize_with = "deserialize_content_blocks")]
1284 pub content: Vec<ContentBlock>,
1285}
1286
1287#[derive(Debug, Clone, Serialize, Deserialize)]
1289pub struct SystemMessage {
1290 pub subtype: SystemSubtype,
1291 #[serde(flatten)]
1292 pub data: Value, }
1294
1295impl SystemMessage {
1296 pub fn is_init(&self) -> bool {
1298 self.subtype == SystemSubtype::Init
1299 }
1300
1301 pub fn is_status(&self) -> bool {
1303 self.subtype == SystemSubtype::Status
1304 }
1305
1306 pub fn is_compact_boundary(&self) -> bool {
1308 self.subtype == SystemSubtype::CompactBoundary
1309 }
1310
1311 pub fn as_init(&self) -> Option<InitMessage> {
1313 if self.subtype != SystemSubtype::Init {
1314 return None;
1315 }
1316 serde_json::from_value(self.data.clone()).ok()
1317 }
1318
1319 pub fn as_status(&self) -> Option<StatusMessage> {
1321 if self.subtype != SystemSubtype::Status {
1322 return None;
1323 }
1324 serde_json::from_value(self.data.clone()).ok()
1325 }
1326
1327 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1329 if self.subtype != SystemSubtype::CompactBoundary {
1330 return None;
1331 }
1332 serde_json::from_value(self.data.clone()).ok()
1333 }
1334
1335 pub fn is_task_started(&self) -> bool {
1337 self.subtype == SystemSubtype::TaskStarted
1338 }
1339
1340 pub fn is_task_progress(&self) -> bool {
1342 self.subtype == SystemSubtype::TaskProgress
1343 }
1344
1345 pub fn is_task_notification(&self) -> bool {
1347 self.subtype == SystemSubtype::TaskNotification
1348 }
1349
1350 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1352 if self.subtype != SystemSubtype::TaskStarted {
1353 return None;
1354 }
1355 serde_json::from_value(self.data.clone()).ok()
1356 }
1357
1358 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1360 if self.subtype != SystemSubtype::TaskProgress {
1361 return None;
1362 }
1363 serde_json::from_value(self.data.clone()).ok()
1364 }
1365
1366 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1368 if self.subtype != SystemSubtype::TaskNotification {
1369 return None;
1370 }
1371 serde_json::from_value(self.data.clone()).ok()
1372 }
1373
1374 pub fn is_task_updated(&self) -> bool {
1376 self.subtype == SystemSubtype::TaskUpdated
1377 }
1378
1379 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1381 if self.subtype != SystemSubtype::TaskUpdated {
1382 return None;
1383 }
1384 serde_json::from_value(self.data.clone()).ok()
1385 }
1386
1387 pub fn is_thinking_tokens(&self) -> bool {
1389 self.subtype == SystemSubtype::ThinkingTokens
1390 }
1391
1392 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1394 if self.subtype != SystemSubtype::ThinkingTokens {
1395 return None;
1396 }
1397 serde_json::from_value(self.data.clone()).ok()
1398 }
1399
1400 pub fn is_code_change_published(&self) -> bool {
1402 self.subtype == SystemSubtype::CodeChangePublished
1403 }
1404
1405 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1407 if self.subtype != SystemSubtype::CodeChangePublished {
1408 return None;
1409 }
1410 serde_json::from_value(self.data.clone()).ok()
1411 }
1412
1413 pub fn is_vcs_state_changed(&self) -> bool {
1415 self.subtype == SystemSubtype::VcsStateChanged
1416 }
1417
1418 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1420 if self.subtype != SystemSubtype::VcsStateChanged {
1421 return None;
1422 }
1423 serde_json::from_value(self.data.clone()).ok()
1424 }
1425
1426 pub fn is_feedback_draft_queued(&self) -> bool {
1428 self.subtype == SystemSubtype::FeedbackDraftQueued
1429 }
1430
1431 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1433 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1434 return None;
1435 }
1436 serde_json::from_value(self.data.clone()).ok()
1437 }
1438
1439 pub fn is_cloud_session_delta(&self) -> bool {
1441 self.subtype == SystemSubtype::CloudSessionDelta
1442 }
1443
1444 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1446 if self.subtype != SystemSubtype::CloudSessionDelta {
1447 return None;
1448 }
1449 serde_json::from_value(self.data.clone()).ok()
1450 }
1451
1452 pub fn is_dev_intent(&self) -> bool {
1454 self.subtype == SystemSubtype::DevIntent
1455 }
1456
1457 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1459 if self.subtype != SystemSubtype::DevIntent {
1460 return None;
1461 }
1462 serde_json::from_value(self.data.clone()).ok()
1463 }
1464
1465 pub fn is_turn_handoff_available(&self) -> bool {
1467 self.subtype == SystemSubtype::TurnHandoffAvailable
1468 }
1469
1470 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1472 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1473 return None;
1474 }
1475 serde_json::from_value(self.data.clone()).ok()
1476 }
1477
1478 pub fn is_turn_preempted(&self) -> bool {
1480 self.subtype == SystemSubtype::TurnPreempted
1481 }
1482
1483 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1485 if self.subtype != SystemSubtype::TurnPreempted {
1486 return None;
1487 }
1488 serde_json::from_value(self.data.clone()).ok()
1489 }
1490
1491 pub fn is_peer_message_hold(&self) -> bool {
1493 self.subtype == SystemSubtype::PeerMessageHold
1494 }
1495
1496 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1498 if self.subtype != SystemSubtype::PeerMessageHold {
1499 return None;
1500 }
1501 serde_json::from_value(self.data.clone()).ok()
1502 }
1503
1504 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1506 macro_rules! parse {
1507 ($variant:ident, $ty:ty) => {
1508 serde_json::from_value::<$ty>(self.data.clone())
1509 .ok()
1510 .map(KnownSystemEvent::$variant)
1511 };
1512 }
1513
1514 match self.subtype {
1515 SystemSubtype::Init => parse!(Init, InitMessage),
1516 SystemSubtype::Status => parse!(Status, StatusMessage),
1517 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1518 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1519 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1520 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1521 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1522 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1523 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1524 SystemSubtype::ControlRequestProgress => {
1525 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1526 }
1527 SystemSubtype::ModelRefusalFallback => {
1528 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1529 }
1530 SystemSubtype::ModelRefusalNoFallback => {
1531 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1532 }
1533 SystemSubtype::LocalCommandOutput => {
1534 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1535 }
1536 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1537 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1538 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1539 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1540 SystemSubtype::BackgroundTasksChanged => {
1541 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1542 }
1543 SystemSubtype::SessionStateChanged => {
1544 parse!(SessionStateChanged, SessionStateChangedMessage)
1545 }
1546 SystemSubtype::WorkerShuttingDown => {
1547 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1548 }
1549 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1550 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1551 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1552 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1553 SystemSubtype::ElicitationComplete => {
1554 parse!(ElicitationComplete, ElicitationCompleteMessage)
1555 }
1556 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1557 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1558 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1559 SystemSubtype::CodeChangePublished => {
1560 parse!(CodeChangePublished, CodeChangePublishedMessage)
1561 }
1562 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1563 SystemSubtype::FeedbackDraftQueued => {
1564 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1565 }
1566 SystemSubtype::CloudSessionDelta => {
1567 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1568 }
1569 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1570 SystemSubtype::TurnHandoffAvailable => {
1571 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1572 }
1573 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1574 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1575 SystemSubtype::Unknown(_) => None,
1576 }
1577 }
1578
1579 pub fn typed_value(&self) -> Option<Value> {
1588 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1589 parsed.and_then(|v| serde_json::to_value(v).ok())
1590 }
1591 match self.subtype {
1592 SystemSubtype::Init => reserialize(self.as_init()),
1593 SystemSubtype::Status => reserialize(self.as_status()),
1594 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1595 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1596 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1597 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1598 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1599 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1600 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1601 SystemSubtype::ControlRequestProgress => {
1602 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1603 }
1604 SystemSubtype::ModelRefusalFallback => {
1605 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1606 }
1607 SystemSubtype::ModelRefusalNoFallback => {
1608 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1609 }
1610 SystemSubtype::LocalCommandOutput => {
1611 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1612 }
1613 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1614 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1615 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1616 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1617 SystemSubtype::BackgroundTasksChanged => {
1618 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1619 }
1620 SystemSubtype::SessionStateChanged => {
1621 reserialize(parse_system::<SessionStateChangedMessage>(self))
1622 }
1623 SystemSubtype::WorkerShuttingDown => {
1624 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1625 }
1626 SystemSubtype::CommandsChanged => {
1627 reserialize(parse_system::<CommandsChangedMessage>(self))
1628 }
1629 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1630 SystemSubtype::FilesPersisted => {
1631 reserialize(parse_system::<FilesPersistedMessage>(self))
1632 }
1633 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1634 SystemSubtype::ElicitationComplete => {
1635 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1636 }
1637 SystemSubtype::PermissionDenied => {
1638 reserialize(parse_system::<PermissionDeniedMessage>(self))
1639 }
1640 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1641 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1642 SystemSubtype::CodeChangePublished => {
1643 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1644 }
1645 SystemSubtype::VcsStateChanged => {
1646 reserialize(parse_system::<VcsStateChangedMessage>(self))
1647 }
1648 SystemSubtype::FeedbackDraftQueued => {
1649 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1650 }
1651 SystemSubtype::CloudSessionDelta => {
1652 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1653 }
1654 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1655 SystemSubtype::TurnHandoffAvailable => {
1656 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1657 }
1658 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1659 SystemSubtype::PeerMessageHold => {
1660 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1661 }
1662 SystemSubtype::Unknown(_) => None,
1663 }
1664 }
1665}
1666
1667fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1668 serde_json::from_value(message.data.clone()).ok()
1669}
1670
1671#[allow(clippy::large_enum_variant)]
1677#[derive(Debug, Clone, Serialize, Deserialize)]
1678pub enum KnownSystemEvent {
1679 Init(InitMessage),
1680 Status(StatusMessage),
1681 CompactBoundary(CompactBoundaryMessage),
1682 ThinkingTokens(ThinkingTokensMessage),
1683 TaskStarted(TaskStartedMessage),
1684 TaskProgress(TaskProgressMessage),
1685 TaskUpdated(TaskUpdatedMessage),
1686 TaskNotification(TaskNotificationMessage),
1687 ApiRetry(ApiRetryMessage),
1688 ControlRequestProgress(ControlRequestProgressMessage),
1689 ModelRefusalFallback(ModelRefusalFallbackMessage),
1690 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1691 LocalCommandOutput(LocalCommandOutputMessage),
1692 HookStarted(HookStartedMessage),
1693 HookProgress(HookProgressMessage),
1694 HookResponse(HookResponseMessage),
1695 PluginInstall(PluginInstallMessage),
1696 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1697 SessionStateChanged(SessionStateChangedMessage),
1698 WorkerShuttingDown(WorkerShuttingDownMessage),
1699 CommandsChanged(CommandsChangedMessage),
1700 Notification(NotificationMessage),
1701 FilesPersisted(FilesPersistedMessage),
1702 MemoryRecall(MemoryRecallMessage),
1703 ElicitationComplete(ElicitationCompleteMessage),
1704 PermissionDenied(PermissionDeniedMessage),
1705 MirrorError(MirrorErrorMessage),
1706 Informational(InformationalMessage),
1707 CodeChangePublished(CodeChangePublishedMessage),
1708 VcsStateChanged(VcsStateChangedMessage),
1709 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1710 CloudSessionDelta(CloudSessionDeltaMessage),
1711 DevIntent(DevIntentMessage),
1712 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1713 TurnPreempted(TurnPreemptedMessage),
1714 PeerMessageHold(PeerMessageHoldMessage),
1715}
1716
1717#[derive(Debug, Clone, Serialize, Deserialize)]
1718pub struct ApiRetryMessage {
1719 pub attempt: u64,
1720 pub max_retries: u64,
1721 pub retry_delay_ms: u64,
1722 pub error_status: Option<u16>,
1723 pub error: String,
1724 #[serde(default, skip_serializing_if = "Option::is_none")]
1729 pub no_response: Option<ApiRetryNoResponse>,
1730 #[serde(default, skip_serializing_if = "Option::is_none")]
1731 pub uuid: Option<String>,
1732 #[serde(default, skip_serializing_if = "Option::is_none")]
1733 pub session_id: Option<String>,
1734}
1735
1736#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1739pub struct ApiRetryNoResponse {
1740 pub waited_ms: u64,
1742 pub retry_wait_ms: u64,
1744}
1745
1746#[derive(Debug, Clone, Serialize, Deserialize)]
1747pub struct ControlRequestProgressMessage {
1748 pub request_id: String,
1749 pub status: String,
1750 #[serde(default, skip_serializing_if = "Option::is_none")]
1751 pub attempt: Option<u64>,
1752 #[serde(default, skip_serializing_if = "Option::is_none")]
1753 pub max_retries: Option<u64>,
1754 #[serde(default, skip_serializing_if = "Option::is_none")]
1755 pub retry_delay_ms: Option<u64>,
1756 #[serde(default, skip_serializing_if = "Option::is_none")]
1757 pub error_status: Option<u16>,
1758 #[serde(default, skip_serializing_if = "Option::is_none")]
1759 pub error: Option<String>,
1760 #[serde(default, skip_serializing_if = "Option::is_none")]
1761 pub uuid: Option<String>,
1762 #[serde(default, skip_serializing_if = "Option::is_none")]
1763 pub session_id: Option<String>,
1764}
1765
1766#[derive(Debug, Clone, Serialize, Deserialize)]
1767pub struct ModelRefusalFallbackMessage {
1768 pub trigger: String,
1769 pub direction: String,
1770 #[serde(default, skip_serializing_if = "Option::is_none")]
1776 pub scope: Option<RefusalFallbackScope>,
1777 pub original_model: String,
1778 pub fallback_model: String,
1779 pub request_id: Option<String>,
1780 #[serde(default, skip_serializing_if = "Option::is_none")]
1781 pub api_refusal_category: Option<String>,
1782 #[serde(default, skip_serializing_if = "Option::is_none")]
1787 pub saw_cyber_refusal: Option<bool>,
1788 #[serde(default, skip_serializing_if = "Option::is_none")]
1789 pub api_refusal_explanation: Option<String>,
1790 #[serde(default, skip_serializing_if = "Option::is_none")]
1791 pub retracted_message_uuids: Option<Vec<String>>,
1792 #[serde(default, skip_serializing_if = "Option::is_none")]
1793 pub refused_user_message_uuid: Option<String>,
1794 pub content: Value,
1795 #[serde(default, skip_serializing_if = "Option::is_none")]
1796 pub uuid: Option<String>,
1797 #[serde(default, skip_serializing_if = "Option::is_none")]
1798 pub session_id: Option<String>,
1799}
1800
1801#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1805pub enum RefusalFallbackScope {
1806 Session,
1808 Local,
1811 Unknown(String),
1813}
1814
1815impl RefusalFallbackScope {
1816 pub fn as_str(&self) -> &str {
1817 match self {
1818 Self::Session => "session",
1819 Self::Local => "local",
1820 Self::Unknown(s) => s.as_str(),
1821 }
1822 }
1823}
1824
1825impl fmt::Display for RefusalFallbackScope {
1826 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1827 f.write_str(self.as_str())
1828 }
1829}
1830
1831impl From<&str> for RefusalFallbackScope {
1832 fn from(s: &str) -> Self {
1833 match s {
1834 "session" => Self::Session,
1835 "local" => Self::Local,
1836 other => Self::Unknown(other.to_string()),
1837 }
1838 }
1839}
1840
1841impl Serialize for RefusalFallbackScope {
1842 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1843 serializer.serialize_str(self.as_str())
1844 }
1845}
1846
1847impl<'de> Deserialize<'de> for RefusalFallbackScope {
1848 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1849 let s = String::deserialize(deserializer)?;
1850 Ok(Self::from(s.as_str()))
1851 }
1852}
1853
1854#[derive(Debug, Clone, Serialize, Deserialize)]
1855pub struct ModelRefusalNoFallbackMessage {
1856 pub original_model: String,
1857 pub request_id: Option<String>,
1858 #[serde(default, skip_serializing_if = "Option::is_none")]
1859 pub api_refusal_category: Option<String>,
1860 #[serde(default, skip_serializing_if = "Option::is_none")]
1861 pub api_refusal_explanation: Option<String>,
1862 pub content: Value,
1863 #[serde(default, skip_serializing_if = "Option::is_none")]
1864 pub uuid: Option<String>,
1865 #[serde(default, skip_serializing_if = "Option::is_none")]
1866 pub session_id: Option<String>,
1867}
1868
1869#[derive(Debug, Clone, Serialize, Deserialize)]
1870pub struct LocalCommandOutputMessage {
1871 pub content: String,
1872 #[serde(default, skip_serializing_if = "Option::is_none")]
1873 pub uuid: Option<String>,
1874 #[serde(default, skip_serializing_if = "Option::is_none")]
1875 pub session_id: Option<String>,
1876}
1877
1878#[derive(Debug, Clone, Serialize, Deserialize)]
1879pub struct HookStartedMessage {
1880 pub hook_id: String,
1881 pub hook_name: String,
1882 pub hook_event: String,
1883 #[serde(default, skip_serializing_if = "Option::is_none")]
1884 pub uuid: Option<String>,
1885 #[serde(default, skip_serializing_if = "Option::is_none")]
1886 pub session_id: Option<String>,
1887}
1888
1889#[derive(Debug, Clone, Serialize, Deserialize)]
1890pub struct HookProgressMessage {
1891 pub hook_id: String,
1892 pub hook_name: String,
1893 pub hook_event: String,
1894 #[serde(default, skip_serializing_if = "Option::is_none")]
1895 pub stdout: Option<String>,
1896 #[serde(default, skip_serializing_if = "Option::is_none")]
1897 pub stderr: Option<String>,
1898 #[serde(default, skip_serializing_if = "Option::is_none")]
1899 pub output: Option<String>,
1900 #[serde(default, skip_serializing_if = "Option::is_none")]
1901 pub uuid: Option<String>,
1902 #[serde(default, skip_serializing_if = "Option::is_none")]
1903 pub session_id: Option<String>,
1904}
1905
1906#[derive(Debug, Clone, Serialize, Deserialize)]
1907pub struct HookResponseMessage {
1908 pub hook_id: String,
1909 pub hook_name: String,
1910 pub hook_event: String,
1911 #[serde(default, skip_serializing_if = "Option::is_none")]
1912 pub stdout: Option<String>,
1913 #[serde(default, skip_serializing_if = "Option::is_none")]
1914 pub stderr: Option<String>,
1915 #[serde(default, skip_serializing_if = "Option::is_none")]
1916 pub output: Option<String>,
1917 #[serde(default, skip_serializing_if = "Option::is_none")]
1918 pub exit_code: Option<i32>,
1919 pub outcome: 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 PluginInstallMessage {
1928 pub status: String,
1929 #[serde(default, skip_serializing_if = "Option::is_none")]
1930 pub name: Option<String>,
1931 #[serde(default, skip_serializing_if = "Option::is_none")]
1932 pub error: Option<String>,
1933 #[serde(default, skip_serializing_if = "Option::is_none")]
1934 pub uuid: Option<String>,
1935 #[serde(default, skip_serializing_if = "Option::is_none")]
1936 pub session_id: Option<String>,
1937}
1938
1939#[derive(Debug, Clone, Serialize, Deserialize)]
1940pub struct BackgroundTasksChangedMessage {
1941 pub tasks: Vec<BackgroundTaskInfo>,
1942 #[serde(default, skip_serializing_if = "Option::is_none")]
1943 pub uuid: Option<String>,
1944 #[serde(default, skip_serializing_if = "Option::is_none")]
1945 pub session_id: Option<String>,
1946}
1947
1948#[derive(Debug, Clone, Serialize, Deserialize)]
1949pub struct BackgroundTaskInfo {
1950 pub task_id: String,
1951 pub task_type: String,
1952 pub description: String,
1953 #[serde(default, skip_serializing_if = "Option::is_none")]
1956 pub ambient: Option<bool>,
1957}
1958
1959#[derive(Debug, Clone, Serialize, Deserialize)]
1960pub struct SessionStateChangedMessage {
1961 pub state: String,
1962 #[serde(default, skip_serializing_if = "Option::is_none")]
1963 pub uuid: Option<String>,
1964 #[serde(default, skip_serializing_if = "Option::is_none")]
1965 pub session_id: Option<String>,
1966}
1967
1968#[derive(Debug, Clone, Serialize, Deserialize)]
1969pub struct WorkerShuttingDownMessage {
1970 pub reason: String,
1971 #[serde(default, skip_serializing_if = "Option::is_none")]
1972 pub uuid: Option<String>,
1973 #[serde(default, skip_serializing_if = "Option::is_none")]
1974 pub session_id: Option<String>,
1975}
1976
1977#[derive(Debug, Clone, Serialize, Deserialize)]
1978pub struct CommandsChangedMessage {
1979 pub commands: Vec<CommandInfo>,
1980 #[serde(default, skip_serializing_if = "Option::is_none")]
1981 pub uuid: Option<String>,
1982 #[serde(default, skip_serializing_if = "Option::is_none")]
1983 pub session_id: Option<String>,
1984}
1985
1986#[derive(Debug, Clone, Serialize, Deserialize)]
1987pub struct CommandInfo {
1988 pub name: String,
1989 pub description: String,
1990 #[serde(rename = "argumentHint")]
1991 pub argument_hint: String,
1992 #[serde(default, skip_serializing_if = "Option::is_none")]
1993 pub aliases: Option<Vec<String>>,
1994 #[serde(default, skip_serializing_if = "Option::is_none")]
2000 pub builtin: Option<bool>,
2001}
2002
2003#[derive(Debug, Clone, Serialize, Deserialize)]
2004pub struct NotificationMessage {
2005 pub key: String,
2006 pub text: String,
2007 pub priority: String,
2008 #[serde(default, skip_serializing_if = "Option::is_none")]
2009 pub color: Option<String>,
2010 #[serde(default, skip_serializing_if = "Option::is_none")]
2011 pub timeout_ms: Option<u64>,
2012 #[serde(default, skip_serializing_if = "Option::is_none")]
2013 pub uuid: Option<String>,
2014 #[serde(default, skip_serializing_if = "Option::is_none")]
2015 pub session_id: Option<String>,
2016}
2017
2018#[derive(Debug, Clone, Serialize, Deserialize)]
2019pub struct FilesPersistedMessage {
2020 pub files: Vec<PersistedFile>,
2021 pub failed: Vec<FailedPersistedFile>,
2022 pub processed_at: String,
2023 #[serde(default, skip_serializing_if = "Option::is_none")]
2024 pub uuid: Option<String>,
2025 #[serde(default, skip_serializing_if = "Option::is_none")]
2026 pub session_id: Option<String>,
2027}
2028
2029#[derive(Debug, Clone, Serialize, Deserialize)]
2030pub struct PersistedFile {
2031 pub filename: String,
2032 pub file_id: String,
2033}
2034
2035#[derive(Debug, Clone, Serialize, Deserialize)]
2036pub struct FailedPersistedFile {
2037 pub filename: String,
2038 pub error: String,
2039}
2040
2041#[derive(Debug, Clone, Serialize, Deserialize)]
2042pub struct MemoryRecallMessage {
2043 pub mode: String,
2044 pub memories: Vec<MemoryRecallItem>,
2045 #[serde(default, skip_serializing_if = "Option::is_none")]
2046 pub uuid: Option<String>,
2047 #[serde(default, skip_serializing_if = "Option::is_none")]
2048 pub session_id: Option<String>,
2049}
2050
2051#[derive(Debug, Clone, Serialize, Deserialize)]
2052pub struct MemoryRecallItem {
2053 pub path: String,
2054 pub scope: String,
2055 #[serde(default, skip_serializing_if = "Option::is_none")]
2056 pub content: Option<String>,
2057}
2058
2059#[derive(Debug, Clone, Serialize, Deserialize)]
2060pub struct ElicitationCompleteMessage {
2061 pub mcp_server_name: String,
2062 pub elicitation_id: String,
2063 #[serde(default, skip_serializing_if = "Option::is_none")]
2064 pub uuid: Option<String>,
2065 #[serde(default, skip_serializing_if = "Option::is_none")]
2066 pub session_id: Option<String>,
2067}
2068
2069#[derive(Debug, Clone, Serialize, Deserialize)]
2070pub struct PermissionDeniedMessage {
2071 pub tool_name: String,
2072 pub tool_use_id: String,
2073 #[serde(default, skip_serializing_if = "Option::is_none")]
2074 pub agent_id: Option<String>,
2075 #[serde(default, skip_serializing_if = "Option::is_none")]
2076 pub decision_reason_type: Option<String>,
2077 #[serde(default, skip_serializing_if = "Option::is_none")]
2081 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2082 #[serde(default, skip_serializing_if = "Option::is_none")]
2083 pub decision_reason: Option<String>,
2084 pub message: String,
2085 #[serde(default, skip_serializing_if = "Option::is_none")]
2086 pub uuid: Option<String>,
2087 #[serde(default, skip_serializing_if = "Option::is_none")]
2088 pub session_id: Option<String>,
2089}
2090
2091#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2094pub enum PermissionDeniedReasonCode {
2095 ClassifierTranscriptTooLong,
2097 OutsideReadsBlocked,
2099 MemoryPaused,
2101 Unknown(String),
2103}
2104
2105impl PermissionDeniedReasonCode {
2106 pub fn as_str(&self) -> &str {
2107 match self {
2108 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2109 Self::OutsideReadsBlocked => "outside_reads_blocked",
2110 Self::MemoryPaused => "memory_paused",
2111 Self::Unknown(s) => s.as_str(),
2112 }
2113 }
2114}
2115
2116impl fmt::Display for PermissionDeniedReasonCode {
2117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2118 f.write_str(self.as_str())
2119 }
2120}
2121
2122impl From<&str> for PermissionDeniedReasonCode {
2123 fn from(s: &str) -> Self {
2124 match s {
2125 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2126 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2127 "memory_paused" => Self::MemoryPaused,
2128 other => Self::Unknown(other.to_string()),
2129 }
2130 }
2131}
2132
2133impl Serialize for PermissionDeniedReasonCode {
2134 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2135 serializer.serialize_str(self.as_str())
2136 }
2137}
2138
2139impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2140 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2141 let s = String::deserialize(deserializer)?;
2142 Ok(Self::from(s.as_str()))
2143 }
2144}
2145
2146#[derive(Debug, Clone, Serialize, Deserialize)]
2147pub struct MirrorErrorMessage {
2148 pub error: String,
2149 pub key: MirrorErrorKey,
2150 #[serde(default, skip_serializing_if = "Option::is_none")]
2151 pub uuid: Option<String>,
2152 #[serde(default, skip_serializing_if = "Option::is_none")]
2153 pub session_id: Option<String>,
2154}
2155
2156#[derive(Debug, Clone, Serialize, Deserialize)]
2157pub struct MirrorErrorKey {
2158 #[serde(rename = "projectKey")]
2159 pub project_key: String,
2160 #[serde(rename = "sessionId")]
2161 pub session_id: String,
2162 #[serde(default, skip_serializing_if = "Option::is_none")]
2163 pub subpath: Option<String>,
2164}
2165
2166#[derive(Debug, Clone, Serialize, Deserialize)]
2167pub struct InformationalMessage {
2168 pub content: String,
2169 pub level: String,
2170 #[serde(default, skip_serializing_if = "Option::is_none")]
2171 pub tool_use_id: Option<String>,
2172 #[serde(default, skip_serializing_if = "Option::is_none")]
2173 pub prevent_continuation: Option<bool>,
2174 #[serde(default, skip_serializing_if = "Option::is_none")]
2175 pub uuid: Option<String>,
2176 #[serde(default, skip_serializing_if = "Option::is_none")]
2177 pub session_id: Option<String>,
2178}
2179
2180#[derive(Debug, Clone, Serialize, Deserialize)]
2182pub struct PluginInfo {
2183 pub name: String,
2185 pub path: String,
2187 #[serde(skip_serializing_if = "Option::is_none")]
2189 pub source: Option<String>,
2190 #[serde(default, skip_serializing_if = "Option::is_none")]
2192 pub version: Option<String>,
2193}
2194
2195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2197pub struct PluginDiagnostic {
2198 pub plugin: String,
2199 #[serde(rename = "type")]
2200 pub diagnostic_type: String,
2201 pub message: String,
2202}
2203
2204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2206pub struct MemoryPaths {
2207 #[serde(default, skip_serializing_if = "Option::is_none")]
2208 pub auto: Option<String>,
2209 #[serde(default, skip_serializing_if = "Option::is_none")]
2210 pub team: Option<String>,
2211 #[serde(flatten)]
2212 pub extra: serde_json::Map<String, Value>,
2213}
2214
2215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2219pub struct McpServerError {
2220 pub name: String,
2221 #[serde(rename = "type")]
2223 pub error_type: String,
2224 pub message: String,
2225}
2226
2227#[derive(Debug, Clone, Serialize, Deserialize)]
2229pub struct InitMessage {
2230 pub session_id: String,
2232 #[serde(skip_serializing_if = "Option::is_none")]
2234 pub cwd: Option<String>,
2235 #[serde(skip_serializing_if = "Option::is_none")]
2237 pub model: Option<String>,
2238 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2240 pub tools: Vec<String>,
2241 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2243 pub mcp_servers: Vec<Value>,
2244 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2246 pub slash_commands: Vec<String>,
2247 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2250 pub terminal_slash_commands: Vec<String>,
2251 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2253 pub agents: Vec<String>,
2254 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2256 pub plugins: Vec<PluginInfo>,
2257 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2259 pub skills: Vec<Value>,
2260 #[serde(skip_serializing_if = "Option::is_none")]
2262 pub claude_code_version: Option<String>,
2263 #[serde(skip_serializing_if = "Option::is_none")]
2266 pub messaging_socket_path: Option<String>,
2267 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2269 pub api_key_source: Option<ApiKeySource>,
2270 #[serde(skip_serializing_if = "Option::is_none")]
2272 pub output_style: Option<OutputStyle>,
2273 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2275 pub permission_mode: Option<InitPermissionMode>,
2276
2277 #[serde(skip_serializing_if = "Option::is_none")]
2279 pub uuid: Option<String>,
2280
2281 #[serde(skip_serializing_if = "Option::is_none")]
2283 pub memory_paths: Option<MemoryPaths>,
2284
2285 #[serde(default, skip_serializing_if = "Option::is_none")]
2290 pub scratchpad_path: Option<String>,
2291
2292 #[serde(skip_serializing_if = "Option::is_none")]
2294 pub fast_mode_state: Option<String>,
2295
2296 #[serde(default, skip_serializing_if = "Option::is_none")]
2298 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2299
2300 #[serde(default, skip_serializing_if = "Option::is_none")]
2303 pub mcp_server_errors: Option<Vec<McpServerError>>,
2304
2305 #[serde(default, skip_serializing_if = "Option::is_none")]
2307 pub analytics_disabled: Option<bool>,
2308
2309 #[serde(default, skip_serializing_if = "Option::is_none")]
2311 pub product_feedback_disabled: Option<bool>,
2312
2313 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2315 pub betas: Vec<String>,
2316
2317 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2319 pub capabilities: Vec<String>,
2320
2321 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2323 pub plugin_errors: Vec<PluginDiagnostic>,
2324
2325 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2327 pub plugin_warnings: Vec<PluginDiagnostic>,
2328
2329 #[serde(default, skip_serializing_if = "Option::is_none")]
2334 pub effort: Option<String>,
2335
2336 #[serde(default, skip_serializing_if = "Option::is_none")]
2341 pub cloud_session: Option<Value>,
2342
2343 #[serde(default, skip_serializing_if = "Option::is_none")]
2347 pub footer_indicator: Option<FooterIndicator>,
2348
2349 #[serde(default, skip_serializing_if = "Option::is_none")]
2353 pub worker_epoch: Option<u64>,
2354
2355 #[serde(default, skip_serializing_if = "Option::is_none")]
2359 pub powershell_path: Option<Option<String>>,
2360
2361 #[serde(default, skip_serializing_if = "Option::is_none")]
2365 pub startup_timing: Option<serde_json::Map<String, Value>>,
2366}
2367
2368#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2372pub struct FooterIndicator {
2373 pub text: String,
2376}
2377
2378#[derive(Debug, Clone, Serialize, Deserialize)]
2380pub struct StatusMessage {
2381 pub session_id: String,
2383 pub status: Option<StatusMessageStatus>,
2385 #[serde(skip_serializing_if = "Option::is_none")]
2387 pub uuid: Option<String>,
2388 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2390 pub permission_mode: Option<InitPermissionMode>,
2391 #[serde(skip_serializing_if = "Option::is_none")]
2392 pub compact_result: Option<String>,
2393 #[serde(skip_serializing_if = "Option::is_none")]
2394 pub compact_error: Option<String>,
2395}
2396
2397#[derive(Debug, Clone, Serialize, Deserialize)]
2399pub struct CompactBoundaryMessage {
2400 pub session_id: String,
2402 pub compact_metadata: CompactMetadata,
2404 #[serde(
2408 default,
2409 skip_serializing_if = "Option::is_none",
2410 alias = "content",
2411 alias = "text"
2412 )]
2413 pub summary: Option<String>,
2414 #[serde(
2418 default,
2419 skip_serializing_if = "Option::is_none",
2420 alias = "message_count"
2421 )]
2422 pub leaf_message_count: Option<u32>,
2423 #[serde(default, skip_serializing_if = "Option::is_none")]
2425 pub duration_ms: Option<u64>,
2426 #[serde(skip_serializing_if = "Option::is_none")]
2428 pub uuid: Option<String>,
2429 #[serde(skip_serializing_if = "Option::is_none")]
2431 pub logical_parent_uuid: Option<Option<String>>,
2432 #[serde(default, skip_serializing_if = "Option::is_none")]
2436 pub historical: Option<bool>,
2437}
2438
2439#[derive(Debug, Clone, Serialize, Deserialize)]
2441pub struct CompactMetadata {
2442 pub pre_tokens: u64,
2444 pub trigger: CompactionTrigger,
2446 #[serde(default, skip_serializing_if = "Option::is_none")]
2447 pub post_tokens: Option<u64>,
2448 #[serde(default, skip_serializing_if = "Option::is_none")]
2449 pub cumulative_dropped_tokens: Option<u64>,
2450 #[serde(default, skip_serializing_if = "Option::is_none")]
2451 pub duration_ms: Option<u64>,
2452 #[serde(default, skip_serializing_if = "Option::is_none")]
2453 pub user_context: Option<String>,
2454 #[serde(default, skip_serializing_if = "Option::is_none")]
2455 pub messages_summarized: Option<u64>,
2456 #[serde(default, skip_serializing_if = "Option::is_none")]
2457 pub precomputed: Option<bool>,
2458 #[serde(default, skip_serializing_if = "Option::is_none")]
2459 pub pre_compact_discovered_tools: Option<Vec<String>>,
2460 #[serde(default, skip_serializing_if = "Option::is_none")]
2461 pub preserved_segment: Option<PreservedSegment>,
2462 #[serde(default, skip_serializing_if = "Option::is_none")]
2463 pub preserved_messages: Option<PreservedMessages>,
2464}
2465
2466#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2467pub struct PreservedSegment {
2468 pub head_uuid: String,
2469 pub anchor_uuid: String,
2470 pub tail_uuid: String,
2471}
2472
2473#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2474pub struct PreservedMessages {
2475 pub anchor_uuid: String,
2476 pub uuids: Vec<String>,
2477 #[serde(default, skip_serializing_if = "Option::is_none")]
2478 pub all_uuids: Option<Vec<String>>,
2479}
2480
2481#[derive(Debug, Clone, Serialize, Deserialize)]
2487pub struct TaskUsage {
2488 pub duration_ms: u64,
2490 pub tool_uses: u64,
2492 pub total_tokens: u64,
2494}
2495
2496#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2498pub enum TaskType {
2499 LocalAgent,
2501 LocalBash,
2503 LocalWorkflow,
2505 Unknown(String),
2507}
2508
2509impl TaskType {
2510 pub fn as_str(&self) -> &str {
2511 match self {
2512 Self::LocalAgent => "local_agent",
2513 Self::LocalBash => "local_bash",
2514 Self::LocalWorkflow => "local_workflow",
2515 Self::Unknown(s) => s.as_str(),
2516 }
2517 }
2518}
2519
2520impl fmt::Display for TaskType {
2521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2522 f.write_str(self.as_str())
2523 }
2524}
2525
2526impl From<&str> for TaskType {
2527 fn from(s: &str) -> Self {
2528 match s {
2529 "local_agent" => Self::LocalAgent,
2530 "local_bash" => Self::LocalBash,
2531 "local_workflow" => Self::LocalWorkflow,
2532 other => Self::Unknown(other.to_string()),
2533 }
2534 }
2535}
2536
2537impl Serialize for TaskType {
2538 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2539 serializer.serialize_str(self.as_str())
2540 }
2541}
2542
2543impl<'de> Deserialize<'de> for TaskType {
2544 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2545 let s = String::deserialize(deserializer)?;
2546 Ok(Self::from(s.as_str()))
2547 }
2548}
2549
2550#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2552pub enum TaskStatus {
2553 Pending,
2554 Running,
2555 Completed,
2556 Failed,
2557 Killed,
2558 Paused,
2559 Stopped,
2560 Unknown(String),
2561}
2562
2563impl TaskStatus {
2564 pub fn as_str(&self) -> &str {
2565 match self {
2566 Self::Pending => "pending",
2567 Self::Running => "running",
2568 Self::Completed => "completed",
2569 Self::Failed => "failed",
2570 Self::Killed => "killed",
2571 Self::Paused => "paused",
2572 Self::Stopped => "stopped",
2573 Self::Unknown(s) => s.as_str(),
2574 }
2575 }
2576}
2577
2578impl fmt::Display for TaskStatus {
2579 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2580 f.write_str(self.as_str())
2581 }
2582}
2583
2584impl From<&str> for TaskStatus {
2585 fn from(s: &str) -> Self {
2586 match s {
2587 "pending" => Self::Pending,
2588 "running" => Self::Running,
2589 "completed" => Self::Completed,
2590 "failed" => Self::Failed,
2591 "killed" => Self::Killed,
2592 "paused" => Self::Paused,
2593 "stopped" => Self::Stopped,
2594 other => Self::Unknown(other.to_string()),
2595 }
2596 }
2597}
2598
2599impl Serialize for TaskStatus {
2600 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2601 serializer.serialize_str(self.as_str())
2602 }
2603}
2604
2605impl<'de> Deserialize<'de> for TaskStatus {
2606 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2607 let s = String::deserialize(deserializer)?;
2608 Ok(Self::from(s.as_str()))
2609 }
2610}
2611
2612#[derive(Debug, Clone, Serialize, Deserialize)]
2614pub struct TaskStartedMessage {
2615 pub session_id: String,
2616 pub task_id: String,
2617 #[serde(default, skip_serializing_if = "Option::is_none")]
2618 pub task_type: Option<TaskType>,
2619 #[serde(default, skip_serializing_if = "Option::is_none")]
2620 pub tool_use_id: Option<String>,
2621 pub description: String,
2622 #[serde(default, skip_serializing_if = "Option::is_none")]
2625 pub subagent_type: Option<String>,
2626 #[serde(default, skip_serializing_if = "Option::is_none")]
2632 pub is_backgrounded: Option<bool>,
2633 #[serde(default, skip_serializing_if = "Option::is_none")]
2637 pub spawn_depth: Option<u32>,
2638 #[serde(default, skip_serializing_if = "Option::is_none")]
2640 pub prompt: Option<String>,
2641 #[serde(default, skip_serializing_if = "Option::is_none")]
2642 pub workflow_name: Option<String>,
2643 #[serde(default, skip_serializing_if = "Option::is_none")]
2644 pub skip_transcript: Option<bool>,
2645 #[serde(default, skip_serializing_if = "Option::is_none")]
2650 pub ambient: Option<bool>,
2651 pub uuid: String,
2652}
2653
2654#[derive(Debug, Clone, Serialize, Deserialize)]
2658pub struct TaskUpdatedMessage {
2659 pub session_id: String,
2660 pub task_id: String,
2661 pub patch: TaskPatch,
2662 pub uuid: String,
2663}
2664
2665#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2668pub struct TaskPatch {
2669 #[serde(default, skip_serializing_if = "Option::is_none")]
2670 pub status: Option<TaskStatus>,
2671 #[serde(default, skip_serializing_if = "Option::is_none")]
2674 pub end_time: Option<u64>,
2675 #[serde(default, skip_serializing_if = "Option::is_none")]
2676 pub description: Option<String>,
2677 #[serde(default, skip_serializing_if = "Option::is_none")]
2678 pub total_paused_ms: Option<u64>,
2679 #[serde(default, skip_serializing_if = "Option::is_none")]
2680 pub error: Option<String>,
2681 #[serde(default, skip_serializing_if = "Option::is_none")]
2682 pub is_backgrounded: Option<bool>,
2683}
2684
2685#[derive(Debug, Clone, Serialize, Deserialize)]
2688pub struct ThinkingTokensMessage {
2689 pub session_id: String,
2690 pub estimated_tokens: u64,
2692 pub estimated_tokens_delta: u64,
2694 #[serde(default, skip_serializing_if = "Option::is_none")]
2701 pub user_message_uuid: Option<String>,
2702 pub uuid: String,
2703}
2704
2705#[derive(Debug, Clone, Serialize, Deserialize)]
2708pub struct TaskProgressMessage {
2709 pub session_id: String,
2710 pub task_id: String,
2711 #[serde(default, skip_serializing_if = "Option::is_none")]
2712 pub tool_use_id: Option<String>,
2713 pub description: String,
2714 #[serde(default, skip_serializing_if = "Option::is_none")]
2715 pub last_tool_name: Option<String>,
2716 pub usage: TaskUsage,
2717 #[serde(default, skip_serializing_if = "Option::is_none")]
2719 pub subagent_type: Option<String>,
2720 #[serde(default, skip_serializing_if = "Option::is_none")]
2721 pub summary: Option<String>,
2722 pub uuid: String,
2723}
2724
2725#[derive(Debug, Clone, Serialize, Deserialize)]
2728pub struct TaskNotificationMessage {
2729 pub session_id: String,
2730 pub task_id: String,
2731 pub status: TaskStatus,
2732 #[serde(default, skip_serializing_if = "Option::is_none")]
2735 pub reason: Option<TaskEndReason>,
2736 pub summary: String,
2737 pub output_file: Option<String>,
2738 #[serde(skip_serializing_if = "Option::is_none")]
2739 pub tool_use_id: Option<String>,
2740 #[serde(skip_serializing_if = "Option::is_none")]
2741 pub usage: Option<TaskUsage>,
2742 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2748 pub resource_links: Vec<ResourceLink>,
2749 #[serde(default, skip_serializing_if = "Option::is_none")]
2750 pub skip_transcript: Option<bool>,
2751 #[serde(default, skip_serializing_if = "Option::is_none")]
2754 pub ambient: Option<bool>,
2755 #[serde(skip_serializing_if = "Option::is_none")]
2756 pub uuid: Option<String>,
2757}
2758
2759#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2761pub enum AssistantErrorKind {
2762 AuthenticationFailed,
2763 OauthOrgNotAllowed,
2764 AccountOnHold,
2765 BillingError,
2766 RateLimit,
2767 Overloaded,
2768 InvalidRequest,
2769 ModelNotFound,
2770 ServerError,
2771 UnknownError,
2772 MaxOutputTokens,
2773 Unknown(String),
2774}
2775
2776impl AssistantErrorKind {
2777 pub fn as_str(&self) -> &str {
2778 match self {
2779 Self::AuthenticationFailed => "authentication_failed",
2780 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
2781 Self::AccountOnHold => "account_on_hold",
2782 Self::BillingError => "billing_error",
2783 Self::RateLimit => "rate_limit",
2784 Self::Overloaded => "overloaded",
2785 Self::InvalidRequest => "invalid_request",
2786 Self::ModelNotFound => "model_not_found",
2787 Self::ServerError => "server_error",
2788 Self::UnknownError => "unknown",
2789 Self::MaxOutputTokens => "max_output_tokens",
2790 Self::Unknown(s) => s.as_str(),
2791 }
2792 }
2793}
2794
2795impl fmt::Display for AssistantErrorKind {
2796 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2797 f.write_str(self.as_str())
2798 }
2799}
2800
2801impl From<&str> for AssistantErrorKind {
2802 fn from(s: &str) -> Self {
2803 match s {
2804 "authentication_failed" => Self::AuthenticationFailed,
2805 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
2806 "account_on_hold" => Self::AccountOnHold,
2807 "billing_error" => Self::BillingError,
2808 "rate_limit" => Self::RateLimit,
2809 "overloaded" => Self::Overloaded,
2810 "invalid_request" => Self::InvalidRequest,
2811 "model_not_found" => Self::ModelNotFound,
2812 "server_error" => Self::ServerError,
2813 "unknown" => Self::UnknownError,
2814 "max_output_tokens" => Self::MaxOutputTokens,
2815 other => Self::Unknown(other.to_string()),
2816 }
2817 }
2818}
2819
2820impl Serialize for AssistantErrorKind {
2821 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2822 serializer.serialize_str(self.as_str())
2823 }
2824}
2825
2826impl<'de> Deserialize<'de> for AssistantErrorKind {
2827 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2828 let s = String::deserialize(deserializer)?;
2829 Ok(Self::from(s.as_str()))
2830 }
2831}
2832
2833#[derive(Debug, Clone, Serialize, Deserialize)]
2840pub struct CodeChangePublishedMessage {
2841 pub provider: String,
2845 pub url: String,
2847 pub repo: String,
2850 pub identifier: String,
2852 #[serde(default, skip_serializing_if = "Option::is_none")]
2861 pub action: Option<String>,
2862 #[serde(default, skip_serializing_if = "Option::is_none")]
2870 pub branch: Option<String>,
2871 pub uuid: String,
2872 pub session_id: String,
2873}
2874
2875#[derive(Debug, Clone, Serialize, Deserialize)]
2880pub struct VcsStateChangedMessage {
2881 pub kind: VcsMutationKind,
2884 pub cwd: String,
2887 #[serde(default, skip_serializing_if = "Option::is_none")]
2892 pub branch: Option<String>,
2893 pub uuid: String,
2894 pub session_id: String,
2895}
2896
2897#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2899pub enum VcsMutationKind {
2900 Commit,
2901 Push,
2902 Merge,
2903 Rebase,
2904 Unknown(String),
2906}
2907
2908impl VcsMutationKind {
2909 pub fn as_str(&self) -> &str {
2910 match self {
2911 Self::Commit => "commit",
2912 Self::Push => "push",
2913 Self::Merge => "merge",
2914 Self::Rebase => "rebase",
2915 Self::Unknown(s) => s.as_str(),
2916 }
2917 }
2918}
2919
2920impl fmt::Display for VcsMutationKind {
2921 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2922 f.write_str(self.as_str())
2923 }
2924}
2925
2926impl From<&str> for VcsMutationKind {
2927 fn from(s: &str) -> Self {
2928 match s {
2929 "commit" => Self::Commit,
2930 "push" => Self::Push,
2931 "merge" => Self::Merge,
2932 "rebase" => Self::Rebase,
2933 other => Self::Unknown(other.to_string()),
2934 }
2935 }
2936}
2937
2938impl Serialize for VcsMutationKind {
2939 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2940 serializer.serialize_str(self.as_str())
2941 }
2942}
2943
2944impl<'de> Deserialize<'de> for VcsMutationKind {
2945 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2946 let s = String::deserialize(deserializer)?;
2947 Ok(Self::from(s.as_str()))
2948 }
2949}
2950
2951#[derive(Debug, Clone, Serialize, Deserialize)]
2953pub struct FeedbackDraftQueuedMessage {
2954 pub draft_id: String,
2955 pub draft_type: String,
2956 pub title: String,
2957 pub details_preview: String,
2958 #[serde(default, skip_serializing_if = "Option::is_none")]
2959 pub uuid: Option<String>,
2960 #[serde(default, skip_serializing_if = "Option::is_none")]
2961 pub session_id: Option<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)]
2974pub struct CloudSessionDeltaMessage {
2975 pub seq: u64,
2979 pub changed: Vec<String>,
2983 pub cloud_session: Value,
2987 pub uuid: String,
2988 pub session_id: String,
2989 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
2990 pub extra: serde_json::Map<String, Value>,
2991}
2992
2993#[derive(Debug, Clone, Serialize, Deserialize)]
3009pub struct DevIntentMessage {
3010 pub kind: DevIntentKind,
3012 #[serde(default, skip_serializing_if = "Option::is_none")]
3018 pub trigger: Option<DevIntentTrigger>,
3019 pub uuid: String,
3020 pub session_id: String,
3021 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3022 pub extra: serde_json::Map<String, Value>,
3023}
3024
3025#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3029pub enum DevIntentKind {
3030 IosApp,
3036 AndroidApp,
3043 Unknown(String),
3045}
3046
3047impl DevIntentKind {
3048 pub fn as_str(&self) -> &str {
3049 match self {
3050 Self::IosApp => "ios_app",
3051 Self::AndroidApp => "android_app",
3052 Self::Unknown(s) => s.as_str(),
3053 }
3054 }
3055}
3056
3057impl fmt::Display for DevIntentKind {
3058 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3059 f.write_str(self.as_str())
3060 }
3061}
3062
3063impl From<&str> for DevIntentKind {
3064 fn from(s: &str) -> Self {
3065 match s {
3066 "ios_app" => Self::IosApp,
3067 "android_app" => Self::AndroidApp,
3068 other => Self::Unknown(other.to_string()),
3069 }
3070 }
3071}
3072
3073impl Serialize for DevIntentKind {
3074 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3075 serializer.serialize_str(self.as_str())
3076 }
3077}
3078
3079impl<'de> Deserialize<'de> for DevIntentKind {
3080 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3081 let s = String::deserialize(deserializer)?;
3082 Ok(Self::from(s.as_str()))
3083 }
3084}
3085
3086#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3090pub enum DevIntentTrigger {
3091 SwiftEdit,
3093 UikitImport,
3095 XcodeProject,
3098 IosCommand,
3101 KotlinEdit,
3103 JavaEdit,
3105 AndroidImport,
3107 AndroidManifest,
3109 GradlePlugin,
3111 AndroidCommand,
3114 ProjectScan,
3117 Unknown(String),
3119}
3120
3121impl DevIntentTrigger {
3122 pub fn as_str(&self) -> &str {
3123 match self {
3124 Self::SwiftEdit => "swift_edit",
3125 Self::UikitImport => "uikit_import",
3126 Self::XcodeProject => "xcode_project",
3127 Self::IosCommand => "ios_command",
3128 Self::KotlinEdit => "kotlin_edit",
3129 Self::JavaEdit => "java_edit",
3130 Self::AndroidImport => "android_import",
3131 Self::AndroidManifest => "android_manifest",
3132 Self::GradlePlugin => "gradle_plugin",
3133 Self::AndroidCommand => "android_command",
3134 Self::ProjectScan => "project_scan",
3135 Self::Unknown(s) => s.as_str(),
3136 }
3137 }
3138}
3139
3140impl fmt::Display for DevIntentTrigger {
3141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3142 f.write_str(self.as_str())
3143 }
3144}
3145
3146impl From<&str> for DevIntentTrigger {
3147 fn from(s: &str) -> Self {
3148 match s {
3149 "swift_edit" => Self::SwiftEdit,
3150 "uikit_import" => Self::UikitImport,
3151 "xcode_project" => Self::XcodeProject,
3152 "ios_command" => Self::IosCommand,
3153 "kotlin_edit" => Self::KotlinEdit,
3154 "java_edit" => Self::JavaEdit,
3155 "android_import" => Self::AndroidImport,
3156 "android_manifest" => Self::AndroidManifest,
3157 "gradle_plugin" => Self::GradlePlugin,
3158 "android_command" => Self::AndroidCommand,
3159 "project_scan" => Self::ProjectScan,
3160 other => Self::Unknown(other.to_string()),
3161 }
3162 }
3163}
3164
3165impl Serialize for DevIntentTrigger {
3166 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3167 serializer.serialize_str(self.as_str())
3168 }
3169}
3170
3171impl<'de> Deserialize<'de> for DevIntentTrigger {
3172 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3173 let s = String::deserialize(deserializer)?;
3174 Ok(Self::from(s.as_str()))
3175 }
3176}
3177
3178#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3182pub enum TaskEndReason {
3183 WorkerRestart,
3186 Unknown(String),
3188}
3189
3190impl TaskEndReason {
3191 pub fn as_str(&self) -> &str {
3192 match self {
3193 Self::WorkerRestart => "worker_restart",
3194 Self::Unknown(s) => s.as_str(),
3195 }
3196 }
3197}
3198
3199impl fmt::Display for TaskEndReason {
3200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3201 f.write_str(self.as_str())
3202 }
3203}
3204
3205impl From<&str> for TaskEndReason {
3206 fn from(s: &str) -> Self {
3207 match s {
3208 "worker_restart" => Self::WorkerRestart,
3209 other => Self::Unknown(other.to_string()),
3210 }
3211 }
3212}
3213
3214impl Serialize for TaskEndReason {
3215 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3216 serializer.serialize_str(self.as_str())
3217 }
3218}
3219
3220impl<'de> Deserialize<'de> for TaskEndReason {
3221 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3222 let s = String::deserialize(deserializer)?;
3223 Ok(Self::from(s.as_str()))
3224 }
3225}
3226
3227#[derive(Debug, Clone, Serialize, Deserialize)]
3236pub struct TurnHandoffAvailableMessage {
3237 pub v: u64,
3239 pub tools: Vec<String>,
3241 pub worker_epoch: u64,
3243 #[serde(default, skip_serializing_if = "Option::is_none")]
3247 pub relay_marker: Option<bool>,
3248 #[serde(default, skip_serializing_if = "Option::is_none")]
3253 pub staged_files: Option<bool>,
3254 #[serde(default, skip_serializing_if = "Option::is_none")]
3261 pub no_query_first: Option<bool>,
3262 pub uuid: String,
3263 pub session_id: String,
3264 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3265 pub extra: serde_json::Map<String, Value>,
3266}
3267
3268#[derive(Debug, Clone, Serialize, Deserialize)]
3280pub struct TurnPreemptedMessage {
3281 pub reason: String,
3285 pub preempted_by_uuid: String,
3289 #[serde(default)]
3292 pub preempted_message_uuids: Vec<String>,
3293 pub uuid: String,
3294 pub session_id: String,
3295 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3296 pub extra: serde_json::Map<String, Value>,
3297}
3298
3299#[derive(Debug, Clone, Serialize, Deserialize)]
3307pub struct PeerMessageHoldMessage {
3308 pub state: PeerMessageHoldState,
3314 #[serde(default, skip_serializing_if = "Option::is_none")]
3321 pub message_uuid: Option<String>,
3322 pub lane: PeerMessageLane,
3324 pub from: String,
3329 #[serde(default, skip_serializing_if = "Option::is_none")]
3332 pub from_name: Option<String>,
3333 #[serde(default, skip_serializing_if = "Option::is_none")]
3335 pub cause: Option<PeerMessageHoldCause>,
3336 #[serde(default, skip_serializing_if = "Option::is_none")]
3338 pub outcome: Option<PeerMessageHoldOutcome>,
3339 pub uuid: String,
3340 pub session_id: String,
3341 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3342 pub extra: serde_json::Map<String, Value>,
3343}
3344
3345#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3347pub enum PeerMessageHoldState {
3348 Held,
3350 Released,
3352 Dropped,
3354 Unknown(String),
3356}
3357
3358impl PeerMessageHoldState {
3359 pub fn as_str(&self) -> &str {
3360 match self {
3361 Self::Held => "held",
3362 Self::Released => "released",
3363 Self::Dropped => "dropped",
3364 Self::Unknown(s) => s.as_str(),
3365 }
3366 }
3367}
3368
3369impl fmt::Display for PeerMessageHoldState {
3370 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3371 f.write_str(self.as_str())
3372 }
3373}
3374
3375impl From<&str> for PeerMessageHoldState {
3376 fn from(s: &str) -> Self {
3377 match s {
3378 "held" => Self::Held,
3379 "released" => Self::Released,
3380 "dropped" => Self::Dropped,
3381 other => Self::Unknown(other.to_string()),
3382 }
3383 }
3384}
3385
3386impl Serialize for PeerMessageHoldState {
3387 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3388 serializer.serialize_str(self.as_str())
3389 }
3390}
3391
3392impl<'de> Deserialize<'de> for PeerMessageHoldState {
3393 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3394 let s = String::deserialize(deserializer)?;
3395 Ok(Self::from(s.as_str()))
3396 }
3397}
3398
3399#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3401pub enum PeerMessageLane {
3402 Bridge,
3404 Stdin,
3406 Socket,
3408 Unknown(String),
3410}
3411
3412impl PeerMessageLane {
3413 pub fn as_str(&self) -> &str {
3414 match self {
3415 Self::Bridge => "bridge",
3416 Self::Stdin => "stdin",
3417 Self::Socket => "socket",
3418 Self::Unknown(s) => s.as_str(),
3419 }
3420 }
3421}
3422
3423impl fmt::Display for PeerMessageLane {
3424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3425 f.write_str(self.as_str())
3426 }
3427}
3428
3429impl From<&str> for PeerMessageLane {
3430 fn from(s: &str) -> Self {
3431 match s {
3432 "bridge" => Self::Bridge,
3433 "stdin" => Self::Stdin,
3434 "socket" => Self::Socket,
3435 other => Self::Unknown(other.to_string()),
3436 }
3437 }
3438}
3439
3440impl Serialize for PeerMessageLane {
3441 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3442 serializer.serialize_str(self.as_str())
3443 }
3444}
3445
3446impl<'de> Deserialize<'de> for PeerMessageLane {
3447 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3448 let s = String::deserialize(deserializer)?;
3449 Ok(Self::from(s.as_str()))
3450 }
3451}
3452
3453#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3460pub enum PeerMessageHoldCause {
3461 ExplicitSetting,
3462 ManagedSetting,
3463 RepoSetting,
3464 InvalidSetting,
3465 BypassDefault,
3466 ModeUnknown,
3467 ModeMismatch,
3468 NoModeAsserted,
3469 Unknown(String),
3471}
3472
3473impl PeerMessageHoldCause {
3474 pub fn as_str(&self) -> &str {
3475 match self {
3476 Self::ExplicitSetting => "explicit-setting",
3477 Self::ManagedSetting => "managed-setting",
3478 Self::RepoSetting => "repo-setting",
3479 Self::InvalidSetting => "invalid-setting",
3480 Self::BypassDefault => "bypass-default",
3481 Self::ModeUnknown => "mode-unknown",
3482 Self::ModeMismatch => "mode-mismatch",
3483 Self::NoModeAsserted => "no-mode-asserted",
3484 Self::Unknown(s) => s.as_str(),
3485 }
3486 }
3487}
3488
3489impl fmt::Display for PeerMessageHoldCause {
3490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3491 f.write_str(self.as_str())
3492 }
3493}
3494
3495impl From<&str> for PeerMessageHoldCause {
3496 fn from(s: &str) -> Self {
3497 match s {
3498 "explicit-setting" => Self::ExplicitSetting,
3499 "managed-setting" => Self::ManagedSetting,
3500 "repo-setting" => Self::RepoSetting,
3501 "invalid-setting" => Self::InvalidSetting,
3502 "bypass-default" => Self::BypassDefault,
3503 "mode-unknown" => Self::ModeUnknown,
3504 "mode-mismatch" => Self::ModeMismatch,
3505 "no-mode-asserted" => Self::NoModeAsserted,
3506 other => Self::Unknown(other.to_string()),
3507 }
3508 }
3509}
3510
3511impl Serialize for PeerMessageHoldCause {
3512 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3513 serializer.serialize_str(self.as_str())
3514 }
3515}
3516
3517impl<'de> Deserialize<'de> for PeerMessageHoldCause {
3518 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3519 let s = String::deserialize(deserializer)?;
3520 Ok(Self::from(s.as_str()))
3521 }
3522}
3523
3524#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3527pub enum PeerMessageHoldOutcome {
3528 Expired,
3532 Refused,
3534 Dropped,
3537 Discarded,
3539 Unknown(String),
3541}
3542
3543impl PeerMessageHoldOutcome {
3544 pub fn as_str(&self) -> &str {
3545 match self {
3546 Self::Expired => "expired",
3547 Self::Refused => "refused",
3548 Self::Dropped => "dropped",
3549 Self::Discarded => "discarded",
3550 Self::Unknown(s) => s.as_str(),
3551 }
3552 }
3553}
3554
3555impl fmt::Display for PeerMessageHoldOutcome {
3556 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3557 f.write_str(self.as_str())
3558 }
3559}
3560
3561impl From<&str> for PeerMessageHoldOutcome {
3562 fn from(s: &str) -> Self {
3563 match s {
3564 "expired" => Self::Expired,
3565 "refused" => Self::Refused,
3566 "dropped" => Self::Dropped,
3567 "discarded" => Self::Discarded,
3568 other => Self::Unknown(other.to_string()),
3569 }
3570 }
3571}
3572
3573impl Serialize for PeerMessageHoldOutcome {
3574 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3575 serializer.serialize_str(self.as_str())
3576 }
3577}
3578
3579impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
3580 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3581 let s = String::deserialize(deserializer)?;
3582 Ok(Self::from(s.as_str()))
3583 }
3584}
3585
3586#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3589pub struct LocalCommandRun {
3590 pub command: String,
3592 pub args: String,
3595}
3596
3597#[derive(Debug, Clone, Serialize, Deserialize)]
3603pub struct UsageReport {
3604 pub session: super::control::UsageSession,
3606 pub rate_limits: Option<UsageReportRateLimits>,
3610 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3611 pub extra: serde_json::Map<String, Value>,
3612}
3613
3614#[derive(Debug, Clone, Serialize, Deserialize)]
3616pub struct UsageReportRateLimits {
3617 pub limits: Option<Vec<UsageReportLimit>>,
3625 #[serde(default, skip_serializing_if = "Option::is_none")]
3628 pub extra_usage: Option<UsageReportExtraUsage>,
3629 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3630 pub extra: serde_json::Map<String, Value>,
3631}
3632
3633#[derive(Debug, Clone, Serialize, Deserialize)]
3635pub struct UsageReportLimit {
3636 pub kind: String,
3639 pub group: String,
3642 pub percent: f64,
3644 pub resets_at: Option<String>,
3646 #[serde(default, skip_serializing_if = "Option::is_none")]
3649 pub scope: Option<UsageReportScope>,
3650 #[serde(default, skip_serializing_if = "Option::is_none")]
3654 pub severity: Option<String>,
3655 #[serde(default, skip_serializing_if = "Option::is_none")]
3657 pub is_active: Option<bool>,
3658 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3659 pub extra: serde_json::Map<String, Value>,
3660}
3661
3662#[derive(Debug, Clone, Serialize, Deserialize)]
3664pub struct UsageReportScope {
3665 #[serde(default, skip_serializing_if = "Option::is_none")]
3666 pub model: Option<UsageReportScopeLabel>,
3667 #[serde(default, skip_serializing_if = "Option::is_none")]
3668 pub surface: Option<UsageReportScopeLabel>,
3669 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3670 pub extra: serde_json::Map<String, Value>,
3671}
3672
3673#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3675pub struct UsageReportScopeLabel {
3676 pub display_name: String,
3677}
3678
3679#[derive(Debug, Clone, Serialize, Deserialize)]
3683pub struct UsageReportExtraUsage {
3684 pub is_enabled: bool,
3686 pub monthly_limit: Option<f64>,
3687 pub used_credits: Option<f64>,
3688 pub utilization: Option<f64>,
3689 #[serde(default, skip_serializing_if = "Option::is_none")]
3690 pub currency: Option<String>,
3691 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3692 pub extra: serde_json::Map<String, Value>,
3693}
3694
3695#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3698pub struct BatchToolUse {
3699 pub id: String,
3700 pub name: String,
3701}
3702
3703#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3708pub struct ContextUsage {
3709 pub model: String,
3711 pub total_tokens: u64,
3714 pub raw_max_tokens: u64,
3717 pub percentage: u64,
3719 #[serde(default, skip_serializing_if = "Option::is_none")]
3721 pub over_limit: Option<ContextOverLimit>,
3722 #[serde(default)]
3724 pub categories: Vec<ContextCategory>,
3725 #[serde(default)]
3727 pub mcp_tools: Vec<ContextMcpTool>,
3728 #[serde(default)]
3730 pub memory_files: Vec<ContextMemoryFile>,
3731 #[serde(default)]
3733 pub agents: Vec<ContextAgent>,
3734 #[serde(default, skip_serializing_if = "Option::is_none")]
3736 pub skills: Option<Vec<ContextSkill>>,
3737}
3738
3739#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3741pub struct ContextOverLimit {
3742 pub tokens_over: u64,
3743 pub kind: String,
3746}
3747
3748#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3750pub struct ContextCategory {
3751 pub name: String,
3754 pub tokens: u64,
3755 pub kind: String,
3759}
3760
3761#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3763pub struct ContextMcpTool {
3764 pub name: String,
3766 pub server_name: String,
3767 pub tokens: u64,
3768}
3769
3770#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3772pub struct ContextMemoryFile {
3773 pub path: String,
3774 #[serde(rename = "type")]
3776 pub file_type: String,
3777 pub tokens: u64,
3778}
3779
3780#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3782pub struct ContextAgent {
3783 pub agent_type: String,
3784 pub source: String,
3786 pub tokens: u64,
3787}
3788
3789#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3791pub struct ContextSkill {
3792 pub name: String,
3793 pub source: String,
3795 #[serde(default, skip_serializing_if = "Option::is_none")]
3796 pub plugin_name: Option<String>,
3797 pub tokens: u64,
3798}
3799
3800#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3802pub struct ToolUseMeta {
3803 pub id: String,
3804 pub display_name: String,
3805 #[serde(default, skip_serializing_if = "Option::is_none")]
3806 pub server_display_name: Option<String>,
3807 #[serde(default, skip_serializing_if = "Option::is_none")]
3808 pub icon_url: Option<String>,
3809}
3810
3811#[derive(Debug, Clone, Serialize, Deserialize)]
3813pub struct AssistantMessage {
3814 pub message: AssistantMessageContent,
3815 #[serde(alias = "sessionId")]
3816 pub session_id: String,
3817 #[serde(skip_serializing_if = "Option::is_none")]
3818 pub uuid: Option<String>,
3819 #[serde(skip_serializing_if = "Option::is_none")]
3820 pub parent_tool_use_id: Option<String>,
3821 #[serde(skip_serializing_if = "Option::is_none")]
3823 pub request_id: Option<String>,
3824 #[serde(default, skip_serializing_if = "Option::is_none")]
3833 pub user_message_uuid: Option<String>,
3834 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3840 pub user_message_uuids: Vec<String>,
3841 #[serde(skip_serializing_if = "Option::is_none")]
3844 pub subagent_type: Option<String>,
3845 #[serde(skip_serializing_if = "Option::is_none")]
3847 pub task_description: Option<String>,
3848 #[serde(skip_serializing_if = "Option::is_none")]
3849 pub error: Option<AssistantErrorKind>,
3850 #[serde(default, skip_serializing_if = "Option::is_none")]
3854 pub aborted: Option<bool>,
3855 #[serde(default, skip_serializing_if = "Option::is_none")]
3860 pub resumed_from_incomplete_thinking: Option<bool>,
3861 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3862 pub supersedes: Vec<String>,
3863 #[serde(skip_serializing_if = "Option::is_none")]
3864 pub timestamp: Option<String>,
3865 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3866 pub tool_use_meta: Vec<ToolUseMeta>,
3867 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3872 pub batch_tool_uses: Vec<BatchToolUse>,
3873 #[serde(default, skip_serializing_if = "Option::is_none")]
3879 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
3880 #[serde(default, skip_serializing_if = "Option::is_none")]
3886 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
3887 #[serde(default, skip_serializing_if = "Option::is_none")]
3891 pub historical: Option<bool>,
3892 #[serde(default, skip_serializing_if = "Option::is_none")]
3900 pub resume_reason: Option<String>,
3901 #[serde(default, skip_serializing_if = "Option::is_none")]
3907 pub local_command_source: Option<String>,
3908 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3917 pub narration_block_indexes: Vec<usize>,
3918 #[serde(default, skip_serializing_if = "Option::is_none")]
3922 pub context_usage: Option<ContextUsage>,
3923 #[serde(default, skip_serializing_if = "Option::is_none")]
3931 pub usage_report: Option<Box<UsageReport>>,
3932 #[serde(default, skip_serializing_if = "Option::is_none")]
3939 pub local_command_run: Option<LocalCommandRun>,
3940 #[serde(default, skip_serializing_if = "Option::is_none")]
3941 pub is_meta: Option<bool>,
3942 #[serde(default, skip_serializing_if = "Option::is_none")]
3943 pub is_virtual: Option<bool>,
3944 #[serde(default, skip_serializing_if = "Option::is_none")]
3945 pub is_api_error_message: Option<bool>,
3946 #[serde(skip_serializing_if = "Option::is_none")]
3947 pub api_error_status: Option<u16>,
3948 #[serde(skip_serializing_if = "Option::is_none")]
3963 pub api_error: Option<String>,
3964 #[serde(default, skip_serializing_if = "Option::is_none")]
3971 pub api_error_code: Option<String>,
3972 #[serde(default, skip_serializing_if = "Option::is_none")]
3975 pub api_error_params: Option<ApiErrorParams>,
3976 #[serde(skip_serializing_if = "Option::is_none")]
3977 pub error_details: Option<String>,
3978 #[serde(skip_serializing_if = "Option::is_none")]
3979 pub advisor_model: Option<String>,
3980 #[serde(skip_serializing_if = "Option::is_none")]
3981 pub attribution_agent: Option<String>,
3982 #[serde(skip_serializing_if = "Option::is_none")]
3983 pub attribution_skill: Option<String>,
3984 #[serde(skip_serializing_if = "Option::is_none")]
3985 pub attribution_plugin: Option<String>,
3986 #[serde(skip_serializing_if = "Option::is_none")]
3987 pub attribution_mcp_server: Option<String>,
3988 #[serde(skip_serializing_if = "Option::is_none")]
3989 pub attribution_mcp_tool: Option<String>,
3990}
3991
3992#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
3996pub struct ApiErrorParams {
3997 #[serde(default, skip_serializing_if = "Option::is_none")]
3999 pub effort: Option<String>,
4000 #[serde(default, skip_serializing_if = "Option::is_none")]
4003 pub provider: Option<ApiErrorProvider>,
4004 #[serde(default, skip_serializing_if = "Option::is_none")]
4007 pub remedy: Option<ApiErrorRemedy>,
4008}
4009
4010#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4012pub enum ApiErrorProvider {
4013 Bedrock,
4014 AnthropicAws,
4015 Mantle,
4016 AnthropicGoogleCloud,
4017 Vertex,
4018 Foundry,
4019 Gateway,
4020 Unknown(String),
4022}
4023
4024impl ApiErrorProvider {
4025 pub fn as_str(&self) -> &str {
4026 match self {
4027 Self::Bedrock => "bedrock",
4028 Self::AnthropicAws => "anthropicAws",
4029 Self::Mantle => "mantle",
4030 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
4031 Self::Vertex => "vertex",
4032 Self::Foundry => "foundry",
4033 Self::Gateway => "gateway",
4034 Self::Unknown(s) => s.as_str(),
4035 }
4036 }
4037}
4038
4039impl fmt::Display for ApiErrorProvider {
4040 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4041 f.write_str(self.as_str())
4042 }
4043}
4044
4045impl From<&str> for ApiErrorProvider {
4046 fn from(s: &str) -> Self {
4047 match s {
4048 "bedrock" => Self::Bedrock,
4049 "anthropicAws" => Self::AnthropicAws,
4050 "mantle" => Self::Mantle,
4051 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4052 "vertex" => Self::Vertex,
4053 "foundry" => Self::Foundry,
4054 "gateway" => Self::Gateway,
4055 other => Self::Unknown(other.to_string()),
4056 }
4057 }
4058}
4059
4060impl Serialize for ApiErrorProvider {
4061 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4062 serializer.serialize_str(self.as_str())
4063 }
4064}
4065
4066impl<'de> Deserialize<'de> for ApiErrorProvider {
4067 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4068 let s = String::deserialize(deserializer)?;
4069 Ok(Self::from(s.as_str()))
4070 }
4071}
4072
4073#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4075pub enum ApiErrorRemedy {
4076 RefreshCommand,
4079 RefreshCredentials,
4081 Adc,
4084 GatewayToken,
4087 HostManaged,
4089 ModelAccess,
4092 Unknown(String),
4094}
4095
4096impl ApiErrorRemedy {
4097 pub fn as_str(&self) -> &str {
4098 match self {
4099 Self::RefreshCommand => "refresh_command",
4100 Self::RefreshCredentials => "refresh_credentials",
4101 Self::Adc => "adc",
4102 Self::GatewayToken => "gateway_token",
4103 Self::HostManaged => "host_managed",
4104 Self::ModelAccess => "model_access",
4105 Self::Unknown(s) => s.as_str(),
4106 }
4107 }
4108}
4109
4110impl fmt::Display for ApiErrorRemedy {
4111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4112 f.write_str(self.as_str())
4113 }
4114}
4115
4116impl From<&str> for ApiErrorRemedy {
4117 fn from(s: &str) -> Self {
4118 match s {
4119 "refresh_command" => Self::RefreshCommand,
4120 "refresh_credentials" => Self::RefreshCredentials,
4121 "adc" => Self::Adc,
4122 "gateway_token" => Self::GatewayToken,
4123 "host_managed" => Self::HostManaged,
4124 "model_access" => Self::ModelAccess,
4125 other => Self::Unknown(other.to_string()),
4126 }
4127 }
4128}
4129
4130impl Serialize for ApiErrorRemedy {
4131 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4132 serializer.serialize_str(self.as_str())
4133 }
4134}
4135
4136impl<'de> Deserialize<'de> for ApiErrorRemedy {
4137 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4138 let s = String::deserialize(deserializer)?;
4139 Ok(Self::from(s.as_str()))
4140 }
4141}
4142
4143#[derive(Debug, Clone, Serialize, Deserialize)]
4145pub struct AssistantMessageContent {
4146 pub id: String,
4147 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
4149 pub message_type: Option<String>,
4150 pub role: MessageRole,
4151 pub model: String,
4152 pub content: Vec<ContentBlock>,
4153 #[serde(skip_serializing_if = "Option::is_none")]
4154 pub stop_reason: Option<StopReason>,
4155 #[serde(skip_serializing_if = "Option::is_none")]
4156 pub stop_sequence: Option<String>,
4157 #[serde(skip_serializing_if = "Option::is_none")]
4158 pub usage: Option<AssistantUsage>,
4159 #[serde(skip_serializing_if = "Option::is_none")]
4161 pub stop_details: Option<Value>,
4162 #[serde(skip_serializing_if = "Option::is_none")]
4164 pub context_management: Option<Value>,
4165}
4166
4167#[derive(Debug, Clone, Serialize, Deserialize)]
4169pub struct AssistantUsage {
4170 #[serde(default)]
4172 pub input_tokens: u32,
4173
4174 #[serde(default)]
4176 pub output_tokens: u32,
4177
4178 #[serde(default)]
4180 pub cache_creation_input_tokens: u32,
4181
4182 #[serde(default)]
4184 pub cache_read_input_tokens: u32,
4185
4186 #[serde(skip_serializing_if = "Option::is_none")]
4188 pub service_tier: Option<String>,
4189
4190 #[serde(skip_serializing_if = "Option::is_none")]
4192 pub cache_creation: Option<CacheCreationDetails>,
4193
4194 #[serde(skip_serializing_if = "Option::is_none")]
4196 pub inference_geo: Option<String>,
4197}
4198
4199#[derive(Debug, Clone, Serialize, Deserialize)]
4201pub struct CacheCreationDetails {
4202 #[serde(default)]
4204 pub ephemeral_1h_input_tokens: u32,
4205
4206 #[serde(default)]
4208 pub ephemeral_5m_input_tokens: u32,
4209}
4210
4211#[cfg(test)]
4212mod tests {
4213 use crate::io::ClaudeOutput;
4214
4215 #[test]
4216 fn test_subagent_usage_rollup_accumulates_task_results() {
4217 use super::SubagentUsageRollup;
4218
4219 let mut rollup = SubagentUsageRollup::default();
4220
4221 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}}"#;
4222 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
4223 assert!(rollup.observe(&output));
4224 assert_eq!(rollup.subagent_tokens, 10201);
4225 assert_eq!(rollup.agent_count, 1);
4226 assert_eq!(rollup.tool_uses, 3);
4227 assert_eq!(rollup.duration_ms, 1853);
4228
4229 assert!(!rollup.observe(&output));
4231 assert_eq!(rollup.agent_count, 1);
4232 assert_eq!(rollup.subagent_tokens, 10201);
4233
4234 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}}"#;
4236 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
4237 assert!(rollup.observe(&output));
4238 assert_eq!(rollup.agent_count, 2);
4239 assert_eq!(rollup.subagent_tokens, 10701);
4240 }
4241
4242 #[test]
4243 fn test_subagent_usage_rollup_ignores_non_task_results() {
4244 use super::SubagentUsageRollup;
4245
4246 let mut rollup = SubagentUsageRollup::default();
4247
4248 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}}"#;
4251 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
4252 assert!(!rollup.observe(&output));
4253
4254 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4256 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
4257 assert!(!rollup.observe(&output));
4258
4259 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4261 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
4262 assert!(!rollup.observe(&output));
4263
4264 assert_eq!(rollup, SubagentUsageRollup::default());
4265 }
4266
4267 #[test]
4268 fn test_subagent_usage_rollup_over_captured_session() {
4269 use super::SubagentUsageRollup;
4270
4271 let mut rollup = SubagentUsageRollup::default();
4272 let fixture =
4273 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
4274 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
4275 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
4276 rollup.observe(&output);
4277 }
4278 }
4279 assert_eq!(rollup.agent_count, 1);
4280 assert_eq!(rollup.subagent_tokens, 10201);
4281 }
4282
4283 #[test]
4284 fn test_system_message_init() {
4285 let json = r#"{
4286 "type": "system",
4287 "subtype": "init",
4288 "session_id": "test-session-123",
4289 "cwd": "/home/user/project",
4290 "model": "claude-sonnet-4",
4291 "tools": ["Bash", "Read", "Write"],
4292 "mcp_servers": [],
4293 "slash_commands": ["compact", "cost", "review"],
4294 "agents": ["Bash", "Explore", "Plan"],
4295 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
4296 "skills": [],
4297 "claude_code_version": "2.1.15",
4298 "apiKeySource": "none",
4299 "output_style": "default",
4300 "permissionMode": "default"
4301 }"#;
4302
4303 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4304 if let ClaudeOutput::System(sys) = output {
4305 assert!(sys.is_init());
4306 assert!(!sys.is_status());
4307 assert!(!sys.is_compact_boundary());
4308
4309 let init = sys.as_init().expect("Should parse as init");
4310 assert_eq!(init.session_id, "test-session-123");
4311 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
4312 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
4313 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
4314 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
4315 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
4316 assert_eq!(init.plugins.len(), 1);
4317 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4318 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4319 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
4320 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
4321 assert_eq!(
4322 init.permission_mode,
4323 Some(super::InitPermissionMode::Default)
4324 );
4325 } else {
4326 panic!("Expected System message");
4327 }
4328 }
4329
4330 #[test]
4331 fn test_system_message_init_from_real_capture() {
4332 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
4333 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4334 if let ClaudeOutput::System(sys) = output {
4335 let init = sys.as_init().expect("Should parse real init capture");
4336 assert_eq!(init.slash_commands.len(), 8);
4337 assert!(init.slash_commands.contains(&"compact".to_string()));
4338 assert!(init.slash_commands.contains(&"review".to_string()));
4339 assert_eq!(init.agents.len(), 5);
4340 assert!(init.agents.contains(&"Bash".to_string()));
4341 assert!(init.agents.contains(&"Explore".to_string()));
4342 assert_eq!(init.plugins.len(), 1);
4343 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4344 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4345 } else {
4346 panic!("Expected System message");
4347 }
4348 }
4349
4350 #[test]
4351 fn test_system_message_status() {
4352 let json = r#"{
4353 "type": "system",
4354 "subtype": "status",
4355 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4356 "status": "compacting",
4357 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
4358 }"#;
4359
4360 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4361 if let ClaudeOutput::System(sys) = output {
4362 assert!(sys.is_status());
4363 assert!(!sys.is_init());
4364
4365 let status = sys.as_status().expect("Should parse as status");
4366 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4367 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
4368 assert_eq!(
4369 status.uuid,
4370 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
4371 );
4372 } else {
4373 panic!("Expected System message");
4374 }
4375 }
4376
4377 #[test]
4378 fn test_system_message_status_null() {
4379 let json = r#"{
4380 "type": "system",
4381 "subtype": "status",
4382 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4383 "status": null,
4384 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
4385 }"#;
4386
4387 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4388 if let ClaudeOutput::System(sys) = output {
4389 let status = sys.as_status().expect("Should parse as status");
4390 assert_eq!(status.status, None);
4391 } else {
4392 panic!("Expected System message");
4393 }
4394 }
4395
4396 #[test]
4397 fn test_system_message_task_started() {
4398 let json = r#"{
4399 "type": "system",
4400 "subtype": "task_started",
4401 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4402 "task_id": "b6daf3f",
4403 "task_type": "local_bash",
4404 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
4405 "description": "Wait for CI on PR #12",
4406 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
4407 }"#;
4408
4409 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4410 if let ClaudeOutput::System(sys) = output {
4411 assert!(sys.is_task_started());
4412 assert!(!sys.is_task_progress());
4413 assert!(!sys.is_task_notification());
4414
4415 let task = sys.as_task_started().expect("Should parse as task_started");
4416 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
4417 assert_eq!(task.task_id, "b6daf3f");
4418 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
4419 assert_eq!(
4420 task.tool_use_id.as_deref(),
4421 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
4422 );
4423 assert_eq!(task.description, "Wait for CI on PR #12");
4424 } else {
4425 panic!("Expected System message");
4426 }
4427 }
4428
4429 #[test]
4430 fn test_system_message_task_started_agent() {
4431 let json = r#"{
4432 "type": "system",
4433 "subtype": "task_started",
4434 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4435 "task_id": "a4a7e0906e5fc64cc",
4436 "task_type": "local_agent",
4437 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4438 "description": "Explore Scene/ArrayScene duplication",
4439 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4440 }"#;
4441
4442 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4443 if let ClaudeOutput::System(sys) = output {
4444 let task = sys.as_task_started().expect("Should parse as task_started");
4445 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
4446 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
4447 } else {
4448 panic!("Expected System message");
4449 }
4450 }
4451
4452 #[test]
4453 fn test_system_message_task_progress() {
4454 let json = r#"{
4455 "type": "system",
4456 "subtype": "task_progress",
4457 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4458 "task_id": "a4a7e0906e5fc64cc",
4459 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4460 "description": "Reading src/jplephem/chebyshev.rs",
4461 "last_tool_name": "Read",
4462 "usage": {
4463 "duration_ms": 13996,
4464 "tool_uses": 9,
4465 "total_tokens": 38779
4466 },
4467 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4468 }"#;
4469
4470 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4471 if let ClaudeOutput::System(sys) = output {
4472 assert!(sys.is_task_progress());
4473 assert!(!sys.is_task_started());
4474
4475 let progress = sys
4476 .as_task_progress()
4477 .expect("Should parse as task_progress");
4478 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
4479 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
4480 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
4481 assert_eq!(progress.usage.duration_ms, 13996);
4482 assert_eq!(progress.usage.tool_uses, 9);
4483 assert_eq!(progress.usage.total_tokens, 38779);
4484 } else {
4485 panic!("Expected System message");
4486 }
4487 }
4488
4489 #[test]
4490 fn test_system_message_task_notification_completed() {
4491 let json = r#"{
4492 "type": "system",
4493 "subtype": "task_notification",
4494 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4495 "task_id": "a0ba761e9dc9c316f",
4496 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
4497 "status": "completed",
4498 "summary": "Agent \"Write Hipparcos data source doc\" completed",
4499 "output_file": "",
4500 "usage": {
4501 "duration_ms": 172300,
4502 "tool_uses": 11,
4503 "total_tokens": 42005
4504 },
4505 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
4506 }"#;
4507
4508 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4509 if let ClaudeOutput::System(sys) = output {
4510 assert!(sys.is_task_notification());
4511
4512 let notif = sys
4513 .as_task_notification()
4514 .expect("Should parse as task_notification");
4515 assert_eq!(notif.status, super::TaskStatus::Completed);
4516 assert_eq!(
4517 notif.summary,
4518 "Agent \"Write Hipparcos data source doc\" completed"
4519 );
4520 assert_eq!(notif.output_file, Some("".to_string()));
4521 assert_eq!(
4522 notif.tool_use_id,
4523 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
4524 );
4525 let usage = notif.usage.expect("Should have usage");
4526 assert_eq!(usage.duration_ms, 172300);
4527 assert_eq!(usage.tool_uses, 11);
4528 assert_eq!(usage.total_tokens, 42005);
4529 } else {
4530 panic!("Expected System message");
4531 }
4532 }
4533
4534 #[test]
4535 fn test_system_message_task_notification_failed_no_usage() {
4536 let json = r#"{
4537 "type": "system",
4538 "subtype": "task_notification",
4539 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
4540 "task_id": "b98f6a3",
4541 "status": "failed",
4542 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
4543 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
4544 }"#;
4545
4546 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4547 if let ClaudeOutput::System(sys) = output {
4548 let notif = sys
4549 .as_task_notification()
4550 .expect("Should parse as task_notification");
4551 assert_eq!(notif.status, super::TaskStatus::Failed);
4552 assert!(notif.tool_use_id.is_none());
4553 assert!(notif.usage.is_none());
4554 assert_eq!(
4555 notif.output_file,
4556 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
4557 );
4558 } else {
4559 panic!("Expected System message");
4560 }
4561 }
4562
4563 #[test]
4569 fn test_task_messages_roundtrip_through_value() {
4570 let cases = [
4571 r#"{"type":"system","subtype":"task_started","session_id":"s1",
4572 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
4573 "description":"Sleep 3s","uuid":"u1"}"#,
4574 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
4575 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
4576 "last_tool_name":"Bash",
4577 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4578 "uuid":"u2"}"#,
4579 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
4580 "task_id":"t1","tool_use_id":"tu1","status":"completed",
4581 "summary":"done","output_file":"",
4582 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4583 "uuid":"u3"}"#,
4584 ];
4585
4586 for json in cases {
4587 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4588 let value = serde_json::to_value(&output).unwrap();
4589 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
4590
4591 let ClaudeOutput::System(sys) = reparsed else {
4592 panic!("Expected System variant after round-trip");
4593 };
4594
4595 match sys.subtype {
4596 super::SystemSubtype::TaskStarted => {
4597 assert!(
4598 sys.as_task_started().is_some(),
4599 "as_task_started failed after round-trip"
4600 );
4601 }
4602 super::SystemSubtype::TaskProgress => {
4603 assert!(
4604 sys.as_task_progress().is_some(),
4605 "as_task_progress failed after round-trip"
4606 );
4607 }
4608 super::SystemSubtype::TaskNotification => {
4609 assert!(
4610 sys.as_task_notification().is_some(),
4611 "as_task_notification failed after round-trip"
4612 );
4613 }
4614 other => panic!("unexpected subtype after round-trip: {other:?}"),
4615 }
4616 }
4617 }
4618
4619 #[test]
4620 fn test_system_message_compact_boundary() {
4621 let json = r#"{
4622 "type": "system",
4623 "subtype": "compact_boundary",
4624 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4625 "compact_metadata": {
4626 "pre_tokens": 155285,
4627 "trigger": "auto"
4628 },
4629 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
4630 }"#;
4631
4632 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4633 if let ClaudeOutput::System(sys) = output {
4634 assert!(sys.is_compact_boundary());
4635 assert!(!sys.is_init());
4636 assert!(!sys.is_status());
4637
4638 let compact = sys
4639 .as_compact_boundary()
4640 .expect("Should parse as compact_boundary");
4641 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4642 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
4643 assert_eq!(
4644 compact.compact_metadata.trigger,
4645 super::CompactionTrigger::Auto
4646 );
4647 assert!(compact.summary.is_none());
4649 assert!(compact.leaf_message_count.is_none());
4650 assert!(compact.duration_ms.is_none());
4651 } else {
4652 panic!("Expected System message");
4653 }
4654 }
4655
4656 #[test]
4657 fn test_compact_boundary_with_summary_stats() {
4658 let json = r#"{
4660 "type": "system",
4661 "subtype": "compact_boundary",
4662 "session_id": "s1",
4663 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
4664 "summary": "Summarized the earlier exploration.",
4665 "leaf_message_count": 42,
4666 "duration_ms": 1234,
4667 "uuid": "u1"
4668 }"#;
4669 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4670 let ClaudeOutput::System(sys) = output else {
4671 panic!("Expected System message");
4672 };
4673 let compact = sys.as_compact_boundary().expect("compact_boundary");
4674 assert_eq!(
4675 compact.summary.as_deref(),
4676 Some("Summarized the earlier exploration.")
4677 );
4678 assert_eq!(compact.leaf_message_count, Some(42));
4679 assert_eq!(compact.duration_ms, Some(1234));
4680
4681 let json_alt = r#"{
4684 "type": "system",
4685 "subtype": "compact_boundary",
4686 "session_id": "s2",
4687 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
4688 "content": "alt-key summary",
4689 "message_count": 7
4690 }"#;
4691 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
4692 let ClaudeOutput::System(sys) = output else {
4693 panic!("Expected System message");
4694 };
4695 let compact = sys.as_compact_boundary().expect("compact_boundary");
4696 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
4697 assert_eq!(compact.leaf_message_count, Some(7));
4698 }
4699
4700 #[test]
4701 fn test_init_message_with_new_fields() {
4702 let json = r#"{
4703 "type": "system",
4704 "subtype": "init",
4705 "session_id": "test-session",
4706 "cwd": "/home/user",
4707 "model": "claude-opus-4-7",
4708 "tools": ["Bash"],
4709 "mcp_servers": [],
4710 "permissionMode": "default",
4711 "apiKeySource": "none",
4712 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
4713 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
4714 "fast_mode_state": "off",
4715 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
4716 "claude_code_version": "2.1.117"
4717 }"#;
4718
4719 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4720 if let ClaudeOutput::System(sys) = output {
4721 let init = sys.as_init().expect("Should parse as init");
4722 assert_eq!(
4723 init.uuid.as_deref(),
4724 Some("44841a0d-182d-493a-86b5-79800d3d9665")
4725 );
4726 assert!(init.memory_paths.is_some());
4727 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
4728 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
4729 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
4730 } else {
4731 panic!("Expected System message");
4732 }
4733 }
4734
4735 #[test]
4736 fn test_assistant_message_with_new_fields() {
4737 let json = r#"{
4738 "type": "assistant",
4739 "message": {
4740 "id": "msg_1",
4741 "type": "message",
4742 "role": "assistant",
4743 "model": "claude-opus-4-7",
4744 "content": [{"type": "text", "text": "Hello"}],
4745 "stop_reason": "end_turn",
4746 "stop_details": null,
4747 "context_management": null,
4748 "usage": {
4749 "input_tokens": 100,
4750 "output_tokens": 10,
4751 "cache_creation_input_tokens": 50,
4752 "cache_read_input_tokens": 0,
4753 "service_tier": "standard",
4754 "inference_geo": "not_available"
4755 }
4756 },
4757 "session_id": "abc",
4758 "uuid": "msg-uuid-123"
4759 }"#;
4760
4761 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4762 if let ClaudeOutput::Assistant(asst) = output {
4763 assert_eq!(asst.message.stop_details, None);
4764 assert_eq!(asst.message.context_management, None);
4765 let usage = asst.message.usage.unwrap();
4766 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
4767 } else {
4768 panic!("Expected Assistant message");
4769 }
4770 }
4771
4772 #[test]
4773 fn test_user_message_with_new_fields() {
4774 let json = r#"{
4775 "type": "user",
4776 "message": {
4777 "role": "user",
4778 "content": [{"type": "text", "text": "Hello"}]
4779 },
4780 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4781 "parent_tool_use_id": "toolu_123",
4782 "uuid": "user-msg-456"
4783 }"#;
4784
4785 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4786 if let ClaudeOutput::User(user) = output {
4787 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
4788 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
4789 } else {
4790 panic!("Expected User message");
4791 }
4792 }
4793
4794 #[test]
4800 fn test_user_message_preserves_tool_use_result_and_timestamp() {
4801 let json = r#"{
4802 "type":"user",
4803 "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"}]},
4804 "parent_tool_use_id":null,
4805 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4806 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
4807 "timestamp":"2026-05-12T23:12:04.121Z",
4808 "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"}}
4809 }"#;
4810
4811 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4812 let user = match output {
4813 ClaudeOutput::User(u) => u,
4814 other => panic!("Expected User message, got {:?}", other.message_type()),
4815 };
4816
4817 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
4818 let raw = user
4819 .tool_use_result
4820 .as_ref()
4821 .expect("tool_use_result must be captured");
4822 assert_eq!(raw["answers"]["Color"], "Blue");
4823 assert_eq!(raw["questions"][0]["header"], "Color");
4824
4825 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
4829 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
4830 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
4831 assert_eq!(
4832 reser["tool_use_result"]["questions"][0]["question"],
4833 "Which color do you prefer?"
4834 );
4835
4836 let typed: crate::AskUserQuestionInput = user
4839 .tool_use_result_as::<crate::AskUserQuestionInput>()
4840 .expect("tool_use_result present")
4841 .expect("AskUserQuestionInput parses");
4842 assert_eq!(typed.questions.len(), 1);
4843 assert_eq!(typed.questions[0].header, "Color");
4844 let answers = typed.answers.expect("answers populated");
4845 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
4846 }
4847
4848 #[test]
4851 fn test_user_message_without_tool_use_result_omits_field() {
4852 let json = r#"{
4853 "type":"user",
4854 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
4855 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
4856 }"#;
4857
4858 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4859 let user = match output {
4860 ClaudeOutput::User(u) => u,
4861 _ => panic!("Expected User message"),
4862 };
4863 assert!(user.tool_use_result.is_none());
4864 assert!(user.timestamp.is_none());
4865
4866 let reser = serde_json::to_value(&user).unwrap();
4867 assert!(reser.get("tool_use_result").is_none());
4868 assert!(reser.get("timestamp").is_none());
4869 }
4870
4871 #[test]
4875 fn test_subagent_result_exposes_token_accounting() {
4876 let json = r#"{
4877 "type":"user",
4878 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
4879 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
4880 "tool_use_result":{
4881 "status":"completed",
4882 "prompt":"Count the .rs files.",
4883 "agentId":"ac4f0276e9d4b6232",
4884 "agentType":"Explore",
4885 "content":[{"type":"text","text":"21"}],
4886 "resolvedModel":"claude-haiku-4-5-20251001",
4887 "totalDurationMs":6869,
4888 "totalTokens":7834,
4889 "totalToolUseCount":1,
4890 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
4891 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
4892 }
4893 }"#;
4894
4895 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4896 let user = match output {
4897 ClaudeOutput::User(u) => u,
4898 _ => panic!("Expected User message"),
4899 };
4900
4901 let result = user.subagent_result().expect("subagent result parses");
4902 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
4903 assert_eq!(
4904 result.resolved_model.as_deref(),
4905 Some("claude-haiku-4-5-20251001")
4906 );
4907 assert_eq!(result.total_tokens, Some(7834));
4908 assert_eq!(result.total_duration_ms, Some(6869));
4909 assert_eq!(result.total_tool_use_count, Some(1));
4910
4911 let usage = result.usage.expect("nested usage present");
4912 assert_eq!(usage.input_tokens, 6);
4913 assert_eq!(usage.cache_read_input_tokens, 7699);
4914
4915 let stats = result.tool_stats.expect("toolStats present");
4916 assert_eq!(stats.bash_count, 1);
4917 }
4918
4919 #[test]
4923 fn test_subagent_result_absent_for_non_task_result() {
4924 let json = r#"{
4925 "type":"user",
4926 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
4927 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4928 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
4929 }"#;
4930
4931 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4932 let user = match output {
4933 ClaudeOutput::User(u) => u,
4934 _ => panic!("Expected User message"),
4935 };
4936
4937 let result = user.subagent_result().expect("lenient parse");
4938 assert_eq!(result.total_tokens, None);
4939 assert_eq!(result.agent_type, None);
4940 }
4941
4942 #[test]
4943 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
4944 use serde_json::Value;
4945
4946 let raw: Value = serde_json::from_str(
4947 r#"{
4948 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
4949 "fast_mode_state":"off",
4950 "fast_mode_disabled_reason":"not_first_party",
4951 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
4952 }"#,
4953 )
4954 .unwrap();
4955 crate::io::assert_fully_wrapped(&raw);
4956
4957 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4958 let ClaudeOutput::System(sys) = output else {
4959 panic!("expected System");
4960 };
4961 let init = sys.as_init().expect("parses as init");
4962 assert_eq!(
4963 init.fast_mode_disabled_reason,
4964 Some(crate::FastModeDisabledReason::NotFirstParty)
4965 );
4966 let errs = init.mcp_server_errors.unwrap();
4967 assert_eq!(errs.len(), 1);
4968 assert_eq!(errs[0].name, "broken");
4969 assert_eq!(errs[0].error_type, "invalid_config");
4970 }
4971
4972 #[test]
4973 fn test_code_change_published_fully_wrapped() {
4974 use super::{KnownSystemEvent, SystemSubtype};
4975 use serde_json::Value;
4976
4977 let raw: Value = serde_json::from_str(
4978 r#"{
4979 "type":"system","subtype":"code_change_published",
4980 "provider":"github","url":"https://github.com/owner/repo/pull/42",
4981 "repo":"owner/repo","identifier":"42",
4982 "uuid":"u1","session_id":"s1"
4983 }"#,
4984 )
4985 .unwrap();
4986 crate::io::assert_fully_wrapped(&raw);
4987
4988 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4989 let ClaudeOutput::System(sys) = output else {
4990 panic!("expected System");
4991 };
4992 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
4993 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
4994 panic!("expected CodeChangePublished event");
4995 };
4996 assert_eq!(msg.provider, "github");
4997 assert_eq!(msg.repo, "owner/repo");
4998 assert_eq!(msg.identifier, "42");
4999
5000 assert!(sys.is_code_change_published());
5001 assert!(!sys.is_vcs_state_changed());
5002 let direct = sys.as_code_change_published().expect("direct accessor");
5003 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
5004 assert!(sys.as_vcs_state_changed().is_none());
5005 }
5006
5007 #[test]
5008 fn test_feedback_draft_queued_fully_wrapped() {
5009 use super::{KnownSystemEvent, SystemSubtype};
5010 use serde_json::Value;
5011
5012 let raw: Value = serde_json::from_str(
5013 r#"{
5014 "type":"system","subtype":"feedback_draft_queued",
5015 "draft_id":"draft-1","draft_type":"bug_report",
5016 "title":"Tool output was truncated",
5017 "details_preview":"The last command omitted its final lines",
5018 "uuid":"u1","session_id":"s1","future_field":"preserved"
5019 }"#,
5020 )
5021 .unwrap();
5022 crate::io::assert_fully_wrapped(&raw);
5023
5024 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5025 let ClaudeOutput::System(sys) = output else {
5026 panic!("expected System");
5027 };
5028 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
5029 assert!(sys.is_feedback_draft_queued());
5030 assert!(!sys.is_vcs_state_changed());
5031
5032 let direct = sys
5033 .as_feedback_draft_queued()
5034 .expect("direct typed accessor");
5035 assert_eq!(direct.draft_id, "draft-1");
5036 assert_eq!(direct.draft_type, "bug_report");
5037 assert_eq!(direct.extra["future_field"], "preserved");
5038
5039 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
5040 panic!("expected FeedbackDraftQueued event");
5041 };
5042 assert_eq!(known.title, "Tool output was truncated");
5043 assert_eq!(
5044 sys.typed_value().expect("typed value")["future_field"],
5045 "preserved"
5046 );
5047 }
5048
5049 #[test]
5050 fn test_cloud_session_delta_fully_wrapped() {
5051 use super::{KnownSystemEvent, SystemSubtype};
5052 use serde_json::Value;
5053
5054 let raw: Value = serde_json::from_str(
5055 r#"{
5056 "type":"system","subtype":"cloud_session_delta",
5057 "seq":3,"changed":["serving","connection"],
5058 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5059 "serving":{"state":"on"},"connection":{"state":"live"}},
5060 "uuid":"u1","session_id":"s1","future_field":"preserved"
5061 }"#,
5062 )
5063 .unwrap();
5064 crate::io::assert_fully_wrapped(&raw);
5065
5066 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5067 let ClaudeOutput::System(sys) = output else {
5068 panic!("expected System");
5069 };
5070 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5071 assert!(sys.is_cloud_session_delta());
5072 assert!(!sys.is_feedback_draft_queued());
5073
5074 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5075 assert_eq!(direct.seq, 3);
5076 assert_eq!(direct.changed, vec!["serving", "connection"]);
5077 assert_eq!(direct.cloud_session["id"], "session_abc");
5078 assert_eq!(direct.extra["future_field"], "preserved");
5079
5080 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5081 panic!("expected CloudSessionDelta event");
5082 };
5083 assert_eq!(known.session_id, "s1");
5084 assert_eq!(
5085 sys.typed_value().expect("typed value")["future_field"],
5086 "preserved"
5087 );
5088 }
5089
5090 #[test]
5091 fn test_vcs_state_changed_fully_wrapped() {
5092 use super::{KnownSystemEvent, VcsMutationKind};
5093 use serde_json::Value;
5094
5095 for kind in ["commit", "push", "merge", "rebase"] {
5096 let raw: Value = serde_json::from_str(&format!(
5097 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5098 kind
5099 ))
5100 .unwrap();
5101 crate::io::assert_fully_wrapped(&raw);
5102
5103 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5104 let ClaudeOutput::System(sys) = output else {
5105 panic!("expected System");
5106 };
5107 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5108 panic!("expected VcsStateChanged event");
5109 };
5110 assert_eq!(msg.kind.as_str(), kind);
5111 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
5112 }
5113
5114 let raw: Value = serde_json::from_str(
5116 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
5117 )
5118 .unwrap();
5119 crate::io::assert_fully_wrapped(&raw);
5120 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5121 let ClaudeOutput::System(sys) = output else {
5122 panic!("expected System");
5123 };
5124 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5125 panic!("expected VcsStateChanged event");
5126 };
5127 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
5128
5129 assert!(sys.is_vcs_state_changed());
5130 let direct = sys.as_vcs_state_changed().expect("direct accessor");
5131 assert_eq!(direct.cwd, "/repo");
5132 assert!(sys.as_code_change_published().is_none());
5133 }
5134
5135 #[test]
5136 fn test_assistant_aborted_and_resume_flags_roundtrip() {
5137 let json = r#"{
5138 "type":"assistant",
5139 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
5140 "session_id":"s1",
5141 "aborted":true,
5142 "resumed_from_incomplete_thinking":true
5143 }"#;
5144 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5145 let ClaudeOutput::Assistant(msg) = &output else {
5146 panic!("expected Assistant");
5147 };
5148 assert_eq!(msg.aborted, Some(true));
5149 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
5150 let reserialized = serde_json::to_string(&output).unwrap();
5151 assert!(reserialized.contains("\"aborted\":true"));
5152 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
5153
5154 let json = r#"{
5156 "type":"assistant",
5157 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
5158 "session_id":"s2"
5159 }"#;
5160 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5161 let reserialized = serde_json::to_string(&output).unwrap();
5162 assert!(!reserialized.contains("aborted"));
5163 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
5164 }
5165
5166 #[test]
5167 fn test_user_tool_result_meta_roundtrip() {
5168 let json = r#"{
5169 "type":"user",
5170 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
5171 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5172 "tool_result_meta":[
5173 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
5174 {"id":"toolu_2","non_execution_kind":"permission-rule"}
5175 ]
5176 }"#;
5177 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5178 let ClaudeOutput::User(user) = &output else {
5179 panic!("expected User");
5180 };
5181 let meta = user.tool_result_meta.as_ref().unwrap();
5182 assert_eq!(meta.len(), 2);
5183 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
5184 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
5185 assert_eq!(meta[1].user_feedback, None);
5186
5187 let reserialized = serde_json::to_string(&output).unwrap();
5188 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
5189 assert!(!reserialized.contains("\"user_feedback\":null"));
5190 }
5191
5192 #[test]
5196 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
5197 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
5198 let with_scope = serde_json::json!({
5199 "trigger": "refusal",
5200 "direction": "retry",
5201 "scope": "local",
5202 "original_model": "claude-fable-5",
5203 "fallback_model": "claude-opus-5",
5204 "request_id": null,
5205 "content": "Refused; retried on fallback model.",
5206 "uuid": "u1",
5207 "session_id": "s1"
5208 });
5209 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
5210 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
5211 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
5212
5213 let mut without = with_scope.clone();
5216 without.as_object_mut().unwrap().remove("scope");
5217 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
5218 assert_eq!(msg.scope, None);
5219 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
5220
5221 assert_eq!(
5223 RefusalFallbackScope::from("workspace").as_str(),
5224 "workspace"
5225 );
5226 }
5227}