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 narration_hint: Option<String>,
3923 #[serde(default, skip_serializing_if = "Option::is_none")]
3927 pub context_usage: Option<ContextUsage>,
3928 #[serde(default, skip_serializing_if = "Option::is_none")]
3936 pub usage_report: Option<Box<UsageReport>>,
3937 #[serde(default, skip_serializing_if = "Option::is_none")]
3944 pub local_command_run: Option<LocalCommandRun>,
3945 #[serde(default, skip_serializing_if = "Option::is_none")]
3946 pub is_meta: Option<bool>,
3947 #[serde(default, skip_serializing_if = "Option::is_none")]
3948 pub is_virtual: Option<bool>,
3949 #[serde(default, skip_serializing_if = "Option::is_none")]
3950 pub is_api_error_message: Option<bool>,
3951 #[serde(skip_serializing_if = "Option::is_none")]
3952 pub api_error_status: Option<u16>,
3953 #[serde(skip_serializing_if = "Option::is_none")]
3968 pub api_error: Option<String>,
3969 #[serde(default, skip_serializing_if = "Option::is_none")]
3976 pub api_error_code: Option<String>,
3977 #[serde(default, skip_serializing_if = "Option::is_none")]
3980 pub api_error_params: Option<ApiErrorParams>,
3981 #[serde(skip_serializing_if = "Option::is_none")]
3982 pub error_details: Option<String>,
3983 #[serde(skip_serializing_if = "Option::is_none")]
3984 pub advisor_model: Option<String>,
3985 #[serde(skip_serializing_if = "Option::is_none")]
3986 pub attribution_agent: Option<String>,
3987 #[serde(skip_serializing_if = "Option::is_none")]
3988 pub attribution_skill: Option<String>,
3989 #[serde(skip_serializing_if = "Option::is_none")]
3990 pub attribution_plugin: Option<String>,
3991 #[serde(skip_serializing_if = "Option::is_none")]
3992 pub attribution_mcp_server: Option<String>,
3993 #[serde(skip_serializing_if = "Option::is_none")]
3994 pub attribution_mcp_tool: Option<String>,
3995}
3996
3997#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
4001pub struct ApiErrorParams {
4002 #[serde(default, skip_serializing_if = "Option::is_none")]
4004 pub effort: Option<String>,
4005 #[serde(default, skip_serializing_if = "Option::is_none")]
4008 pub provider: Option<ApiErrorProvider>,
4009 #[serde(default, skip_serializing_if = "Option::is_none")]
4012 pub remedy: Option<ApiErrorRemedy>,
4013}
4014
4015#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4017pub enum ApiErrorProvider {
4018 Bedrock,
4019 AnthropicAws,
4020 Mantle,
4021 AnthropicGoogleCloud,
4022 Vertex,
4023 Foundry,
4024 Gateway,
4025 Unknown(String),
4027}
4028
4029impl ApiErrorProvider {
4030 pub fn as_str(&self) -> &str {
4031 match self {
4032 Self::Bedrock => "bedrock",
4033 Self::AnthropicAws => "anthropicAws",
4034 Self::Mantle => "mantle",
4035 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
4036 Self::Vertex => "vertex",
4037 Self::Foundry => "foundry",
4038 Self::Gateway => "gateway",
4039 Self::Unknown(s) => s.as_str(),
4040 }
4041 }
4042}
4043
4044impl fmt::Display for ApiErrorProvider {
4045 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4046 f.write_str(self.as_str())
4047 }
4048}
4049
4050impl From<&str> for ApiErrorProvider {
4051 fn from(s: &str) -> Self {
4052 match s {
4053 "bedrock" => Self::Bedrock,
4054 "anthropicAws" => Self::AnthropicAws,
4055 "mantle" => Self::Mantle,
4056 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4057 "vertex" => Self::Vertex,
4058 "foundry" => Self::Foundry,
4059 "gateway" => Self::Gateway,
4060 other => Self::Unknown(other.to_string()),
4061 }
4062 }
4063}
4064
4065impl Serialize for ApiErrorProvider {
4066 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4067 serializer.serialize_str(self.as_str())
4068 }
4069}
4070
4071impl<'de> Deserialize<'de> for ApiErrorProvider {
4072 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4073 let s = String::deserialize(deserializer)?;
4074 Ok(Self::from(s.as_str()))
4075 }
4076}
4077
4078#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4080pub enum ApiErrorRemedy {
4081 RefreshCommand,
4084 RefreshCredentials,
4086 Adc,
4089 GatewayToken,
4092 HostManaged,
4094 ModelAccess,
4097 Unknown(String),
4099}
4100
4101impl ApiErrorRemedy {
4102 pub fn as_str(&self) -> &str {
4103 match self {
4104 Self::RefreshCommand => "refresh_command",
4105 Self::RefreshCredentials => "refresh_credentials",
4106 Self::Adc => "adc",
4107 Self::GatewayToken => "gateway_token",
4108 Self::HostManaged => "host_managed",
4109 Self::ModelAccess => "model_access",
4110 Self::Unknown(s) => s.as_str(),
4111 }
4112 }
4113}
4114
4115impl fmt::Display for ApiErrorRemedy {
4116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4117 f.write_str(self.as_str())
4118 }
4119}
4120
4121impl From<&str> for ApiErrorRemedy {
4122 fn from(s: &str) -> Self {
4123 match s {
4124 "refresh_command" => Self::RefreshCommand,
4125 "refresh_credentials" => Self::RefreshCredentials,
4126 "adc" => Self::Adc,
4127 "gateway_token" => Self::GatewayToken,
4128 "host_managed" => Self::HostManaged,
4129 "model_access" => Self::ModelAccess,
4130 other => Self::Unknown(other.to_string()),
4131 }
4132 }
4133}
4134
4135impl Serialize for ApiErrorRemedy {
4136 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4137 serializer.serialize_str(self.as_str())
4138 }
4139}
4140
4141impl<'de> Deserialize<'de> for ApiErrorRemedy {
4142 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4143 let s = String::deserialize(deserializer)?;
4144 Ok(Self::from(s.as_str()))
4145 }
4146}
4147
4148#[derive(Debug, Clone, Serialize, Deserialize)]
4150pub struct AssistantMessageContent {
4151 pub id: String,
4152 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
4154 pub message_type: Option<String>,
4155 pub role: MessageRole,
4156 pub model: String,
4157 pub content: Vec<ContentBlock>,
4158 #[serde(skip_serializing_if = "Option::is_none")]
4159 pub stop_reason: Option<StopReason>,
4160 #[serde(skip_serializing_if = "Option::is_none")]
4161 pub stop_sequence: Option<String>,
4162 #[serde(skip_serializing_if = "Option::is_none")]
4163 pub usage: Option<AssistantUsage>,
4164 #[serde(skip_serializing_if = "Option::is_none")]
4166 pub stop_details: Option<Value>,
4167 #[serde(skip_serializing_if = "Option::is_none")]
4169 pub context_management: Option<Value>,
4170}
4171
4172#[derive(Debug, Clone, Serialize, Deserialize)]
4174pub struct AssistantUsage {
4175 #[serde(default)]
4177 pub input_tokens: u32,
4178
4179 #[serde(default)]
4181 pub output_tokens: u32,
4182
4183 #[serde(default)]
4185 pub cache_creation_input_tokens: u32,
4186
4187 #[serde(default)]
4189 pub cache_read_input_tokens: u32,
4190
4191 #[serde(skip_serializing_if = "Option::is_none")]
4193 pub service_tier: Option<String>,
4194
4195 #[serde(skip_serializing_if = "Option::is_none")]
4197 pub cache_creation: Option<CacheCreationDetails>,
4198
4199 #[serde(skip_serializing_if = "Option::is_none")]
4201 pub inference_geo: Option<String>,
4202}
4203
4204#[derive(Debug, Clone, Serialize, Deserialize)]
4206pub struct CacheCreationDetails {
4207 #[serde(default)]
4209 pub ephemeral_1h_input_tokens: u32,
4210
4211 #[serde(default)]
4213 pub ephemeral_5m_input_tokens: u32,
4214}
4215
4216#[cfg(test)]
4217mod tests {
4218 use crate::io::ClaudeOutput;
4219
4220 #[test]
4221 fn test_subagent_usage_rollup_accumulates_task_results() {
4222 use super::SubagentUsageRollup;
4223
4224 let mut rollup = SubagentUsageRollup::default();
4225
4226 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}}"#;
4227 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
4228 assert!(rollup.observe(&output));
4229 assert_eq!(rollup.subagent_tokens, 10201);
4230 assert_eq!(rollup.agent_count, 1);
4231 assert_eq!(rollup.tool_uses, 3);
4232 assert_eq!(rollup.duration_ms, 1853);
4233
4234 assert!(!rollup.observe(&output));
4236 assert_eq!(rollup.agent_count, 1);
4237 assert_eq!(rollup.subagent_tokens, 10201);
4238
4239 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}}"#;
4241 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
4242 assert!(rollup.observe(&output));
4243 assert_eq!(rollup.agent_count, 2);
4244 assert_eq!(rollup.subagent_tokens, 10701);
4245 }
4246
4247 #[test]
4248 fn test_subagent_usage_rollup_ignores_non_task_results() {
4249 use super::SubagentUsageRollup;
4250
4251 let mut rollup = SubagentUsageRollup::default();
4252
4253 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}}"#;
4256 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
4257 assert!(!rollup.observe(&output));
4258
4259 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4261 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
4262 assert!(!rollup.observe(&output));
4263
4264 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4266 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
4267 assert!(!rollup.observe(&output));
4268
4269 assert_eq!(rollup, SubagentUsageRollup::default());
4270 }
4271
4272 #[test]
4273 fn test_subagent_usage_rollup_over_captured_session() {
4274 use super::SubagentUsageRollup;
4275
4276 let mut rollup = SubagentUsageRollup::default();
4277 let fixture =
4278 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
4279 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
4280 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
4281 rollup.observe(&output);
4282 }
4283 }
4284 assert_eq!(rollup.agent_count, 1);
4285 assert_eq!(rollup.subagent_tokens, 10201);
4286 }
4287
4288 #[test]
4289 fn test_system_message_init() {
4290 let json = r#"{
4291 "type": "system",
4292 "subtype": "init",
4293 "session_id": "test-session-123",
4294 "cwd": "/home/user/project",
4295 "model": "claude-sonnet-4",
4296 "tools": ["Bash", "Read", "Write"],
4297 "mcp_servers": [],
4298 "slash_commands": ["compact", "cost", "review"],
4299 "agents": ["Bash", "Explore", "Plan"],
4300 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
4301 "skills": [],
4302 "claude_code_version": "2.1.15",
4303 "apiKeySource": "none",
4304 "output_style": "default",
4305 "permissionMode": "default"
4306 }"#;
4307
4308 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4309 if let ClaudeOutput::System(sys) = output {
4310 assert!(sys.is_init());
4311 assert!(!sys.is_status());
4312 assert!(!sys.is_compact_boundary());
4313
4314 let init = sys.as_init().expect("Should parse as init");
4315 assert_eq!(init.session_id, "test-session-123");
4316 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
4317 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
4318 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
4319 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
4320 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
4321 assert_eq!(init.plugins.len(), 1);
4322 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4323 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4324 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
4325 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
4326 assert_eq!(
4327 init.permission_mode,
4328 Some(super::InitPermissionMode::Default)
4329 );
4330 } else {
4331 panic!("Expected System message");
4332 }
4333 }
4334
4335 #[test]
4336 fn test_system_message_init_from_real_capture() {
4337 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
4338 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4339 if let ClaudeOutput::System(sys) = output {
4340 let init = sys.as_init().expect("Should parse real init capture");
4341 assert_eq!(init.slash_commands.len(), 8);
4342 assert!(init.slash_commands.contains(&"compact".to_string()));
4343 assert!(init.slash_commands.contains(&"review".to_string()));
4344 assert_eq!(init.agents.len(), 5);
4345 assert!(init.agents.contains(&"Bash".to_string()));
4346 assert!(init.agents.contains(&"Explore".to_string()));
4347 assert_eq!(init.plugins.len(), 1);
4348 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4349 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4350 } else {
4351 panic!("Expected System message");
4352 }
4353 }
4354
4355 #[test]
4356 fn test_system_message_status() {
4357 let json = r#"{
4358 "type": "system",
4359 "subtype": "status",
4360 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4361 "status": "compacting",
4362 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
4363 }"#;
4364
4365 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4366 if let ClaudeOutput::System(sys) = output {
4367 assert!(sys.is_status());
4368 assert!(!sys.is_init());
4369
4370 let status = sys.as_status().expect("Should parse as status");
4371 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4372 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
4373 assert_eq!(
4374 status.uuid,
4375 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
4376 );
4377 } else {
4378 panic!("Expected System message");
4379 }
4380 }
4381
4382 #[test]
4383 fn test_system_message_status_null() {
4384 let json = r#"{
4385 "type": "system",
4386 "subtype": "status",
4387 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4388 "status": null,
4389 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
4390 }"#;
4391
4392 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4393 if let ClaudeOutput::System(sys) = output {
4394 let status = sys.as_status().expect("Should parse as status");
4395 assert_eq!(status.status, None);
4396 } else {
4397 panic!("Expected System message");
4398 }
4399 }
4400
4401 #[test]
4402 fn test_system_message_task_started() {
4403 let json = r#"{
4404 "type": "system",
4405 "subtype": "task_started",
4406 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4407 "task_id": "b6daf3f",
4408 "task_type": "local_bash",
4409 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
4410 "description": "Wait for CI on PR #12",
4411 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
4412 }"#;
4413
4414 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4415 if let ClaudeOutput::System(sys) = output {
4416 assert!(sys.is_task_started());
4417 assert!(!sys.is_task_progress());
4418 assert!(!sys.is_task_notification());
4419
4420 let task = sys.as_task_started().expect("Should parse as task_started");
4421 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
4422 assert_eq!(task.task_id, "b6daf3f");
4423 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
4424 assert_eq!(
4425 task.tool_use_id.as_deref(),
4426 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
4427 );
4428 assert_eq!(task.description, "Wait for CI on PR #12");
4429 } else {
4430 panic!("Expected System message");
4431 }
4432 }
4433
4434 #[test]
4435 fn test_system_message_task_started_agent() {
4436 let json = r#"{
4437 "type": "system",
4438 "subtype": "task_started",
4439 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4440 "task_id": "a4a7e0906e5fc64cc",
4441 "task_type": "local_agent",
4442 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4443 "description": "Explore Scene/ArrayScene duplication",
4444 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4445 }"#;
4446
4447 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4448 if let ClaudeOutput::System(sys) = output {
4449 let task = sys.as_task_started().expect("Should parse as task_started");
4450 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
4451 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
4452 } else {
4453 panic!("Expected System message");
4454 }
4455 }
4456
4457 #[test]
4458 fn test_system_message_task_progress() {
4459 let json = r#"{
4460 "type": "system",
4461 "subtype": "task_progress",
4462 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4463 "task_id": "a4a7e0906e5fc64cc",
4464 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4465 "description": "Reading src/jplephem/chebyshev.rs",
4466 "last_tool_name": "Read",
4467 "usage": {
4468 "duration_ms": 13996,
4469 "tool_uses": 9,
4470 "total_tokens": 38779
4471 },
4472 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4473 }"#;
4474
4475 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4476 if let ClaudeOutput::System(sys) = output {
4477 assert!(sys.is_task_progress());
4478 assert!(!sys.is_task_started());
4479
4480 let progress = sys
4481 .as_task_progress()
4482 .expect("Should parse as task_progress");
4483 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
4484 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
4485 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
4486 assert_eq!(progress.usage.duration_ms, 13996);
4487 assert_eq!(progress.usage.tool_uses, 9);
4488 assert_eq!(progress.usage.total_tokens, 38779);
4489 } else {
4490 panic!("Expected System message");
4491 }
4492 }
4493
4494 #[test]
4495 fn test_system_message_task_notification_completed() {
4496 let json = r#"{
4497 "type": "system",
4498 "subtype": "task_notification",
4499 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4500 "task_id": "a0ba761e9dc9c316f",
4501 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
4502 "status": "completed",
4503 "summary": "Agent \"Write Hipparcos data source doc\" completed",
4504 "output_file": "",
4505 "usage": {
4506 "duration_ms": 172300,
4507 "tool_uses": 11,
4508 "total_tokens": 42005
4509 },
4510 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
4511 }"#;
4512
4513 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4514 if let ClaudeOutput::System(sys) = output {
4515 assert!(sys.is_task_notification());
4516
4517 let notif = sys
4518 .as_task_notification()
4519 .expect("Should parse as task_notification");
4520 assert_eq!(notif.status, super::TaskStatus::Completed);
4521 assert_eq!(
4522 notif.summary,
4523 "Agent \"Write Hipparcos data source doc\" completed"
4524 );
4525 assert_eq!(notif.output_file, Some("".to_string()));
4526 assert_eq!(
4527 notif.tool_use_id,
4528 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
4529 );
4530 let usage = notif.usage.expect("Should have usage");
4531 assert_eq!(usage.duration_ms, 172300);
4532 assert_eq!(usage.tool_uses, 11);
4533 assert_eq!(usage.total_tokens, 42005);
4534 } else {
4535 panic!("Expected System message");
4536 }
4537 }
4538
4539 #[test]
4540 fn test_system_message_task_notification_failed_no_usage() {
4541 let json = r#"{
4542 "type": "system",
4543 "subtype": "task_notification",
4544 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
4545 "task_id": "b98f6a3",
4546 "status": "failed",
4547 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
4548 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
4549 }"#;
4550
4551 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4552 if let ClaudeOutput::System(sys) = output {
4553 let notif = sys
4554 .as_task_notification()
4555 .expect("Should parse as task_notification");
4556 assert_eq!(notif.status, super::TaskStatus::Failed);
4557 assert!(notif.tool_use_id.is_none());
4558 assert!(notif.usage.is_none());
4559 assert_eq!(
4560 notif.output_file,
4561 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
4562 );
4563 } else {
4564 panic!("Expected System message");
4565 }
4566 }
4567
4568 #[test]
4574 fn test_task_messages_roundtrip_through_value() {
4575 let cases = [
4576 r#"{"type":"system","subtype":"task_started","session_id":"s1",
4577 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
4578 "description":"Sleep 3s","uuid":"u1"}"#,
4579 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
4580 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
4581 "last_tool_name":"Bash",
4582 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4583 "uuid":"u2"}"#,
4584 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
4585 "task_id":"t1","tool_use_id":"tu1","status":"completed",
4586 "summary":"done","output_file":"",
4587 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4588 "uuid":"u3"}"#,
4589 ];
4590
4591 for json in cases {
4592 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4593 let value = serde_json::to_value(&output).unwrap();
4594 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
4595
4596 let ClaudeOutput::System(sys) = reparsed else {
4597 panic!("Expected System variant after round-trip");
4598 };
4599
4600 match sys.subtype {
4601 super::SystemSubtype::TaskStarted => {
4602 assert!(
4603 sys.as_task_started().is_some(),
4604 "as_task_started failed after round-trip"
4605 );
4606 }
4607 super::SystemSubtype::TaskProgress => {
4608 assert!(
4609 sys.as_task_progress().is_some(),
4610 "as_task_progress failed after round-trip"
4611 );
4612 }
4613 super::SystemSubtype::TaskNotification => {
4614 assert!(
4615 sys.as_task_notification().is_some(),
4616 "as_task_notification failed after round-trip"
4617 );
4618 }
4619 other => panic!("unexpected subtype after round-trip: {other:?}"),
4620 }
4621 }
4622 }
4623
4624 #[test]
4625 fn test_system_message_compact_boundary() {
4626 let json = r#"{
4627 "type": "system",
4628 "subtype": "compact_boundary",
4629 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4630 "compact_metadata": {
4631 "pre_tokens": 155285,
4632 "trigger": "auto"
4633 },
4634 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
4635 }"#;
4636
4637 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4638 if let ClaudeOutput::System(sys) = output {
4639 assert!(sys.is_compact_boundary());
4640 assert!(!sys.is_init());
4641 assert!(!sys.is_status());
4642
4643 let compact = sys
4644 .as_compact_boundary()
4645 .expect("Should parse as compact_boundary");
4646 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4647 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
4648 assert_eq!(
4649 compact.compact_metadata.trigger,
4650 super::CompactionTrigger::Auto
4651 );
4652 assert!(compact.summary.is_none());
4654 assert!(compact.leaf_message_count.is_none());
4655 assert!(compact.duration_ms.is_none());
4656 } else {
4657 panic!("Expected System message");
4658 }
4659 }
4660
4661 #[test]
4662 fn test_compact_boundary_with_summary_stats() {
4663 let json = r#"{
4665 "type": "system",
4666 "subtype": "compact_boundary",
4667 "session_id": "s1",
4668 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
4669 "summary": "Summarized the earlier exploration.",
4670 "leaf_message_count": 42,
4671 "duration_ms": 1234,
4672 "uuid": "u1"
4673 }"#;
4674 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4675 let ClaudeOutput::System(sys) = output else {
4676 panic!("Expected System message");
4677 };
4678 let compact = sys.as_compact_boundary().expect("compact_boundary");
4679 assert_eq!(
4680 compact.summary.as_deref(),
4681 Some("Summarized the earlier exploration.")
4682 );
4683 assert_eq!(compact.leaf_message_count, Some(42));
4684 assert_eq!(compact.duration_ms, Some(1234));
4685
4686 let json_alt = r#"{
4689 "type": "system",
4690 "subtype": "compact_boundary",
4691 "session_id": "s2",
4692 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
4693 "content": "alt-key summary",
4694 "message_count": 7
4695 }"#;
4696 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
4697 let ClaudeOutput::System(sys) = output else {
4698 panic!("Expected System message");
4699 };
4700 let compact = sys.as_compact_boundary().expect("compact_boundary");
4701 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
4702 assert_eq!(compact.leaf_message_count, Some(7));
4703 }
4704
4705 #[test]
4706 fn test_init_message_with_new_fields() {
4707 let json = r#"{
4708 "type": "system",
4709 "subtype": "init",
4710 "session_id": "test-session",
4711 "cwd": "/home/user",
4712 "model": "claude-opus-4-7",
4713 "tools": ["Bash"],
4714 "mcp_servers": [],
4715 "permissionMode": "default",
4716 "apiKeySource": "none",
4717 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
4718 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
4719 "fast_mode_state": "off",
4720 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
4721 "claude_code_version": "2.1.117"
4722 }"#;
4723
4724 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4725 if let ClaudeOutput::System(sys) = output {
4726 let init = sys.as_init().expect("Should parse as init");
4727 assert_eq!(
4728 init.uuid.as_deref(),
4729 Some("44841a0d-182d-493a-86b5-79800d3d9665")
4730 );
4731 assert!(init.memory_paths.is_some());
4732 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
4733 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
4734 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
4735 } else {
4736 panic!("Expected System message");
4737 }
4738 }
4739
4740 #[test]
4741 fn test_assistant_message_with_new_fields() {
4742 let json = r#"{
4743 "type": "assistant",
4744 "message": {
4745 "id": "msg_1",
4746 "type": "message",
4747 "role": "assistant",
4748 "model": "claude-opus-4-7",
4749 "content": [{"type": "text", "text": "Hello"}],
4750 "stop_reason": "end_turn",
4751 "stop_details": null,
4752 "context_management": null,
4753 "usage": {
4754 "input_tokens": 100,
4755 "output_tokens": 10,
4756 "cache_creation_input_tokens": 50,
4757 "cache_read_input_tokens": 0,
4758 "service_tier": "standard",
4759 "inference_geo": "not_available"
4760 }
4761 },
4762 "session_id": "abc",
4763 "uuid": "msg-uuid-123"
4764 }"#;
4765
4766 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4767 if let ClaudeOutput::Assistant(asst) = output {
4768 assert_eq!(asst.message.stop_details, None);
4769 assert_eq!(asst.message.context_management, None);
4770 let usage = asst.message.usage.unwrap();
4771 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
4772 } else {
4773 panic!("Expected Assistant message");
4774 }
4775 }
4776
4777 #[test]
4778 fn test_user_message_with_new_fields() {
4779 let json = r#"{
4780 "type": "user",
4781 "message": {
4782 "role": "user",
4783 "content": [{"type": "text", "text": "Hello"}]
4784 },
4785 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4786 "parent_tool_use_id": "toolu_123",
4787 "uuid": "user-msg-456"
4788 }"#;
4789
4790 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4791 if let ClaudeOutput::User(user) = output {
4792 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
4793 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
4794 } else {
4795 panic!("Expected User message");
4796 }
4797 }
4798
4799 #[test]
4805 fn test_user_message_preserves_tool_use_result_and_timestamp() {
4806 let json = r#"{
4807 "type":"user",
4808 "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"}]},
4809 "parent_tool_use_id":null,
4810 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4811 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
4812 "timestamp":"2026-05-12T23:12:04.121Z",
4813 "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"}}
4814 }"#;
4815
4816 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4817 let user = match output {
4818 ClaudeOutput::User(u) => u,
4819 other => panic!("Expected User message, got {:?}", other.message_type()),
4820 };
4821
4822 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
4823 let raw = user
4824 .tool_use_result
4825 .as_ref()
4826 .expect("tool_use_result must be captured");
4827 assert_eq!(raw["answers"]["Color"], "Blue");
4828 assert_eq!(raw["questions"][0]["header"], "Color");
4829
4830 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
4834 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
4835 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
4836 assert_eq!(
4837 reser["tool_use_result"]["questions"][0]["question"],
4838 "Which color do you prefer?"
4839 );
4840
4841 let typed: crate::AskUserQuestionInput = user
4844 .tool_use_result_as::<crate::AskUserQuestionInput>()
4845 .expect("tool_use_result present")
4846 .expect("AskUserQuestionInput parses");
4847 assert_eq!(typed.questions.len(), 1);
4848 assert_eq!(typed.questions[0].header, "Color");
4849 let answers = typed.answers.expect("answers populated");
4850 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
4851 }
4852
4853 #[test]
4856 fn test_user_message_without_tool_use_result_omits_field() {
4857 let json = r#"{
4858 "type":"user",
4859 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
4860 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
4861 }"#;
4862
4863 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4864 let user = match output {
4865 ClaudeOutput::User(u) => u,
4866 _ => panic!("Expected User message"),
4867 };
4868 assert!(user.tool_use_result.is_none());
4869 assert!(user.timestamp.is_none());
4870
4871 let reser = serde_json::to_value(&user).unwrap();
4872 assert!(reser.get("tool_use_result").is_none());
4873 assert!(reser.get("timestamp").is_none());
4874 }
4875
4876 #[test]
4880 fn test_subagent_result_exposes_token_accounting() {
4881 let json = r#"{
4882 "type":"user",
4883 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
4884 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
4885 "tool_use_result":{
4886 "status":"completed",
4887 "prompt":"Count the .rs files.",
4888 "agentId":"ac4f0276e9d4b6232",
4889 "agentType":"Explore",
4890 "content":[{"type":"text","text":"21"}],
4891 "resolvedModel":"claude-haiku-4-5-20251001",
4892 "totalDurationMs":6869,
4893 "totalTokens":7834,
4894 "totalToolUseCount":1,
4895 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
4896 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
4897 }
4898 }"#;
4899
4900 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4901 let user = match output {
4902 ClaudeOutput::User(u) => u,
4903 _ => panic!("Expected User message"),
4904 };
4905
4906 let result = user.subagent_result().expect("subagent result parses");
4907 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
4908 assert_eq!(
4909 result.resolved_model.as_deref(),
4910 Some("claude-haiku-4-5-20251001")
4911 );
4912 assert_eq!(result.total_tokens, Some(7834));
4913 assert_eq!(result.total_duration_ms, Some(6869));
4914 assert_eq!(result.total_tool_use_count, Some(1));
4915
4916 let usage = result.usage.expect("nested usage present");
4917 assert_eq!(usage.input_tokens, 6);
4918 assert_eq!(usage.cache_read_input_tokens, 7699);
4919
4920 let stats = result.tool_stats.expect("toolStats present");
4921 assert_eq!(stats.bash_count, 1);
4922 }
4923
4924 #[test]
4928 fn test_subagent_result_absent_for_non_task_result() {
4929 let json = r#"{
4930 "type":"user",
4931 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
4932 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
4933 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
4934 }"#;
4935
4936 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4937 let user = match output {
4938 ClaudeOutput::User(u) => u,
4939 _ => panic!("Expected User message"),
4940 };
4941
4942 let result = user.subagent_result().expect("lenient parse");
4943 assert_eq!(result.total_tokens, None);
4944 assert_eq!(result.agent_type, None);
4945 }
4946
4947 #[test]
4948 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
4949 use serde_json::Value;
4950
4951 let raw: Value = serde_json::from_str(
4952 r#"{
4953 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
4954 "fast_mode_state":"off",
4955 "fast_mode_disabled_reason":"not_first_party",
4956 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
4957 }"#,
4958 )
4959 .unwrap();
4960 crate::io::assert_fully_wrapped(&raw);
4961
4962 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4963 let ClaudeOutput::System(sys) = output else {
4964 panic!("expected System");
4965 };
4966 let init = sys.as_init().expect("parses as init");
4967 assert_eq!(
4968 init.fast_mode_disabled_reason,
4969 Some(crate::FastModeDisabledReason::NotFirstParty)
4970 );
4971 let errs = init.mcp_server_errors.unwrap();
4972 assert_eq!(errs.len(), 1);
4973 assert_eq!(errs[0].name, "broken");
4974 assert_eq!(errs[0].error_type, "invalid_config");
4975 }
4976
4977 #[test]
4978 fn test_code_change_published_fully_wrapped() {
4979 use super::{KnownSystemEvent, SystemSubtype};
4980 use serde_json::Value;
4981
4982 let raw: Value = serde_json::from_str(
4983 r#"{
4984 "type":"system","subtype":"code_change_published",
4985 "provider":"github","url":"https://github.com/owner/repo/pull/42",
4986 "repo":"owner/repo","identifier":"42",
4987 "uuid":"u1","session_id":"s1"
4988 }"#,
4989 )
4990 .unwrap();
4991 crate::io::assert_fully_wrapped(&raw);
4992
4993 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
4994 let ClaudeOutput::System(sys) = output else {
4995 panic!("expected System");
4996 };
4997 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
4998 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
4999 panic!("expected CodeChangePublished event");
5000 };
5001 assert_eq!(msg.provider, "github");
5002 assert_eq!(msg.repo, "owner/repo");
5003 assert_eq!(msg.identifier, "42");
5004
5005 assert!(sys.is_code_change_published());
5006 assert!(!sys.is_vcs_state_changed());
5007 let direct = sys.as_code_change_published().expect("direct accessor");
5008 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
5009 assert!(sys.as_vcs_state_changed().is_none());
5010 }
5011
5012 #[test]
5013 fn test_feedback_draft_queued_fully_wrapped() {
5014 use super::{KnownSystemEvent, SystemSubtype};
5015 use serde_json::Value;
5016
5017 let raw: Value = serde_json::from_str(
5018 r#"{
5019 "type":"system","subtype":"feedback_draft_queued",
5020 "draft_id":"draft-1","draft_type":"bug_report",
5021 "title":"Tool output was truncated",
5022 "details_preview":"The last command omitted its final lines",
5023 "uuid":"u1","session_id":"s1","future_field":"preserved"
5024 }"#,
5025 )
5026 .unwrap();
5027 crate::io::assert_fully_wrapped(&raw);
5028
5029 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5030 let ClaudeOutput::System(sys) = output else {
5031 panic!("expected System");
5032 };
5033 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
5034 assert!(sys.is_feedback_draft_queued());
5035 assert!(!sys.is_vcs_state_changed());
5036
5037 let direct = sys
5038 .as_feedback_draft_queued()
5039 .expect("direct typed accessor");
5040 assert_eq!(direct.draft_id, "draft-1");
5041 assert_eq!(direct.draft_type, "bug_report");
5042 assert_eq!(direct.extra["future_field"], "preserved");
5043
5044 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
5045 panic!("expected FeedbackDraftQueued event");
5046 };
5047 assert_eq!(known.title, "Tool output was truncated");
5048 assert_eq!(
5049 sys.typed_value().expect("typed value")["future_field"],
5050 "preserved"
5051 );
5052 }
5053
5054 #[test]
5055 fn test_cloud_session_delta_fully_wrapped() {
5056 use super::{KnownSystemEvent, SystemSubtype};
5057 use serde_json::Value;
5058
5059 let raw: Value = serde_json::from_str(
5060 r#"{
5061 "type":"system","subtype":"cloud_session_delta",
5062 "seq":3,"changed":["serving","connection"],
5063 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5064 "serving":{"state":"on"},"connection":{"state":"live"}},
5065 "uuid":"u1","session_id":"s1","future_field":"preserved"
5066 }"#,
5067 )
5068 .unwrap();
5069 crate::io::assert_fully_wrapped(&raw);
5070
5071 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5072 let ClaudeOutput::System(sys) = output else {
5073 panic!("expected System");
5074 };
5075 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5076 assert!(sys.is_cloud_session_delta());
5077 assert!(!sys.is_feedback_draft_queued());
5078
5079 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5080 assert_eq!(direct.seq, 3);
5081 assert_eq!(direct.changed, vec!["serving", "connection"]);
5082 assert_eq!(direct.cloud_session["id"], "session_abc");
5083 assert_eq!(direct.extra["future_field"], "preserved");
5084
5085 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5086 panic!("expected CloudSessionDelta event");
5087 };
5088 assert_eq!(known.session_id, "s1");
5089 assert_eq!(
5090 sys.typed_value().expect("typed value")["future_field"],
5091 "preserved"
5092 );
5093 }
5094
5095 #[test]
5096 fn test_vcs_state_changed_fully_wrapped() {
5097 use super::{KnownSystemEvent, VcsMutationKind};
5098 use serde_json::Value;
5099
5100 for kind in ["commit", "push", "merge", "rebase"] {
5101 let raw: Value = serde_json::from_str(&format!(
5102 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5103 kind
5104 ))
5105 .unwrap();
5106 crate::io::assert_fully_wrapped(&raw);
5107
5108 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5109 let ClaudeOutput::System(sys) = output else {
5110 panic!("expected System");
5111 };
5112 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5113 panic!("expected VcsStateChanged event");
5114 };
5115 assert_eq!(msg.kind.as_str(), kind);
5116 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
5117 }
5118
5119 let raw: Value = serde_json::from_str(
5121 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
5122 )
5123 .unwrap();
5124 crate::io::assert_fully_wrapped(&raw);
5125 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5126 let ClaudeOutput::System(sys) = output else {
5127 panic!("expected System");
5128 };
5129 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5130 panic!("expected VcsStateChanged event");
5131 };
5132 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
5133
5134 assert!(sys.is_vcs_state_changed());
5135 let direct = sys.as_vcs_state_changed().expect("direct accessor");
5136 assert_eq!(direct.cwd, "/repo");
5137 assert!(sys.as_code_change_published().is_none());
5138 }
5139
5140 #[test]
5141 fn test_assistant_aborted_and_resume_flags_roundtrip() {
5142 let json = r#"{
5143 "type":"assistant",
5144 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
5145 "session_id":"s1",
5146 "aborted":true,
5147 "resumed_from_incomplete_thinking":true
5148 }"#;
5149 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5150 let ClaudeOutput::Assistant(msg) = &output else {
5151 panic!("expected Assistant");
5152 };
5153 assert_eq!(msg.aborted, Some(true));
5154 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
5155 let reserialized = serde_json::to_string(&output).unwrap();
5156 assert!(reserialized.contains("\"aborted\":true"));
5157 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
5158
5159 let json = r#"{
5161 "type":"assistant",
5162 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
5163 "session_id":"s2"
5164 }"#;
5165 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5166 let reserialized = serde_json::to_string(&output).unwrap();
5167 assert!(!reserialized.contains("aborted"));
5168 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
5169 }
5170
5171 #[test]
5172 fn test_user_tool_result_meta_roundtrip() {
5173 let json = r#"{
5174 "type":"user",
5175 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
5176 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5177 "tool_result_meta":[
5178 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
5179 {"id":"toolu_2","non_execution_kind":"permission-rule"}
5180 ]
5181 }"#;
5182 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5183 let ClaudeOutput::User(user) = &output else {
5184 panic!("expected User");
5185 };
5186 let meta = user.tool_result_meta.as_ref().unwrap();
5187 assert_eq!(meta.len(), 2);
5188 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
5189 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
5190 assert_eq!(meta[1].user_feedback, None);
5191
5192 let reserialized = serde_json::to_string(&output).unwrap();
5193 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
5194 assert!(!reserialized.contains("\"user_feedback\":null"));
5195 }
5196
5197 #[test]
5201 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
5202 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
5203 let with_scope = serde_json::json!({
5204 "trigger": "refusal",
5205 "direction": "retry",
5206 "scope": "local",
5207 "original_model": "claude-fable-5",
5208 "fallback_model": "claude-opus-5",
5209 "request_id": null,
5210 "content": "Refused; retried on fallback model.",
5211 "uuid": "u1",
5212 "session_id": "s1"
5213 });
5214 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
5215 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
5216 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
5217
5218 let mut without = with_scope.clone();
5221 without.as_object_mut().unwrap().remove("scope");
5222 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
5223 assert_eq!(msg.scope, None);
5224 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
5225
5226 assert_eq!(
5228 RefusalFallbackScope::from("workspace").as_str(),
5229 "workspace"
5230 );
5231 }
5232}