1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::Value;
3use std::fmt;
4use uuid::Uuid;
5
6use super::claude_output::ClaudeOutput;
7use super::content_blocks::{deserialize_content_blocks, ContentBlock};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum SystemSubtype {
16 Init,
17 Status,
18 CompactBoundary,
19 ThinkingTokens,
20 TaskStarted,
21 TaskProgress,
22 TaskUpdated,
23 TaskNotification,
24 ApiRetry,
25 ControlRequestProgress,
26 ModelRefusalFallback,
27 ModelRefusalNoFallback,
28 LocalCommandOutput,
29 HookStarted,
30 HookProgress,
31 HookResponse,
32 PluginInstall,
33 BackgroundTasksChanged,
34 SessionStateChanged,
35 WorkerShuttingDown,
36 CommandsChanged,
37 Notification,
38 FilesPersisted,
39 MemoryRecall,
40 ElicitationComplete,
41 PermissionDenied,
42 MirrorError,
43 Informational,
44 CodeChangePublished,
45 VcsStateChanged,
46 FeedbackDraftQueued,
47 CloudSessionDelta,
48 DevIntent,
49 TurnHandoffAvailable,
50 TurnPreempted,
51 PeerMessageHold,
52 PerTurnEffortChanged,
53 Unknown(String),
55}
56
57impl SystemSubtype {
58 pub fn as_str(&self) -> &str {
59 match self {
60 Self::Init => "init",
61 Self::Status => "status",
62 Self::CompactBoundary => "compact_boundary",
63 Self::ThinkingTokens => "thinking_tokens",
64 Self::TaskStarted => "task_started",
65 Self::TaskProgress => "task_progress",
66 Self::TaskUpdated => "task_updated",
67 Self::TaskNotification => "task_notification",
68 Self::ApiRetry => "api_retry",
69 Self::ControlRequestProgress => "control_request_progress",
70 Self::ModelRefusalFallback => "model_refusal_fallback",
71 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
72 Self::LocalCommandOutput => "local_command_output",
73 Self::HookStarted => "hook_started",
74 Self::HookProgress => "hook_progress",
75 Self::HookResponse => "hook_response",
76 Self::PluginInstall => "plugin_install",
77 Self::BackgroundTasksChanged => "background_tasks_changed",
78 Self::SessionStateChanged => "session_state_changed",
79 Self::WorkerShuttingDown => "worker_shutting_down",
80 Self::CommandsChanged => "commands_changed",
81 Self::Notification => "notification",
82 Self::FilesPersisted => "files_persisted",
83 Self::MemoryRecall => "memory_recall",
84 Self::ElicitationComplete => "elicitation_complete",
85 Self::PermissionDenied => "permission_denied",
86 Self::MirrorError => "mirror_error",
87 Self::Informational => "informational",
88 Self::CodeChangePublished => "code_change_published",
89 Self::VcsStateChanged => "vcs_state_changed",
90 Self::FeedbackDraftQueued => "feedback_draft_queued",
91 Self::CloudSessionDelta => "cloud_session_delta",
92 Self::DevIntent => "dev_intent",
93 Self::TurnHandoffAvailable => "turn_handoff_available",
94 Self::TurnPreempted => "turn_preempted",
95 Self::PeerMessageHold => "peer_message_hold",
96 Self::PerTurnEffortChanged => "per_turn_effort_changed",
97 Self::Unknown(s) => s.as_str(),
98 }
99 }
100}
101
102impl fmt::Display for SystemSubtype {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.write_str(self.as_str())
105 }
106}
107
108impl From<&str> for SystemSubtype {
109 fn from(s: &str) -> Self {
110 match s {
111 "init" => Self::Init,
112 "status" => Self::Status,
113 "compact_boundary" => Self::CompactBoundary,
114 "thinking_tokens" => Self::ThinkingTokens,
115 "task_started" => Self::TaskStarted,
116 "task_progress" => Self::TaskProgress,
117 "task_updated" => Self::TaskUpdated,
118 "task_notification" => Self::TaskNotification,
119 "api_retry" => Self::ApiRetry,
120 "control_request_progress" => Self::ControlRequestProgress,
121 "model_refusal_fallback" => Self::ModelRefusalFallback,
122 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
123 "local_command_output" => Self::LocalCommandOutput,
124 "hook_started" => Self::HookStarted,
125 "hook_progress" => Self::HookProgress,
126 "hook_response" => Self::HookResponse,
127 "plugin_install" => Self::PluginInstall,
128 "background_tasks_changed" => Self::BackgroundTasksChanged,
129 "session_state_changed" => Self::SessionStateChanged,
130 "worker_shutting_down" => Self::WorkerShuttingDown,
131 "commands_changed" => Self::CommandsChanged,
132 "notification" => Self::Notification,
133 "files_persisted" => Self::FilesPersisted,
134 "memory_recall" => Self::MemoryRecall,
135 "elicitation_complete" => Self::ElicitationComplete,
136 "permission_denied" => Self::PermissionDenied,
137 "mirror_error" => Self::MirrorError,
138 "informational" => Self::Informational,
139 "code_change_published" => Self::CodeChangePublished,
140 "vcs_state_changed" => Self::VcsStateChanged,
141 "feedback_draft_queued" => Self::FeedbackDraftQueued,
142 "cloud_session_delta" => Self::CloudSessionDelta,
143 "dev_intent" => Self::DevIntent,
144 "turn_handoff_available" => Self::TurnHandoffAvailable,
145 "turn_preempted" => Self::TurnPreempted,
146 "peer_message_hold" => Self::PeerMessageHold,
147 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
148 other => Self::Unknown(other.to_string()),
149 }
150 }
151}
152
153impl Serialize for SystemSubtype {
154 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
155 serializer.serialize_str(self.as_str())
156 }
157}
158
159impl<'de> Deserialize<'de> for SystemSubtype {
160 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
161 let s = String::deserialize(deserializer)?;
162 Ok(Self::from(s.as_str()))
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Hash)]
171pub enum MessageRole {
172 User,
173 Assistant,
174 Unknown(String),
176}
177
178impl MessageRole {
179 pub fn as_str(&self) -> &str {
180 match self {
181 Self::User => "user",
182 Self::Assistant => "assistant",
183 Self::Unknown(s) => s.as_str(),
184 }
185 }
186}
187
188impl fmt::Display for MessageRole {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 f.write_str(self.as_str())
191 }
192}
193
194impl From<&str> for MessageRole {
195 fn from(s: &str) -> Self {
196 match s {
197 "user" => Self::User,
198 "assistant" => Self::Assistant,
199 other => Self::Unknown(other.to_string()),
200 }
201 }
202}
203
204impl Serialize for MessageRole {
205 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
206 serializer.serialize_str(self.as_str())
207 }
208}
209
210impl<'de> Deserialize<'de> for MessageRole {
211 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
212 let s = String::deserialize(deserializer)?;
213 Ok(Self::from(s.as_str()))
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Hash)]
219pub enum CompactionTrigger {
220 Auto,
222 Manual,
224 Unknown(String),
226}
227
228impl CompactionTrigger {
229 pub fn as_str(&self) -> &str {
230 match self {
231 Self::Auto => "auto",
232 Self::Manual => "manual",
233 Self::Unknown(s) => s.as_str(),
234 }
235 }
236}
237
238impl fmt::Display for CompactionTrigger {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 f.write_str(self.as_str())
241 }
242}
243
244impl From<&str> for CompactionTrigger {
245 fn from(s: &str) -> Self {
246 match s {
247 "auto" => Self::Auto,
248 "manual" => Self::Manual,
249 other => Self::Unknown(other.to_string()),
250 }
251 }
252}
253
254impl Serialize for CompactionTrigger {
255 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
256 serializer.serialize_str(self.as_str())
257 }
258}
259
260impl<'de> Deserialize<'de> for CompactionTrigger {
261 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
262 let s = String::deserialize(deserializer)?;
263 Ok(Self::from(s.as_str()))
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, Hash)]
269pub enum StopReason {
270 EndTurn,
272 MaxTokens,
274 ToolUse,
276 Unknown(String),
278}
279
280impl StopReason {
281 pub fn as_str(&self) -> &str {
282 match self {
283 Self::EndTurn => "end_turn",
284 Self::MaxTokens => "max_tokens",
285 Self::ToolUse => "tool_use",
286 Self::Unknown(s) => s.as_str(),
287 }
288 }
289}
290
291impl fmt::Display for StopReason {
292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 f.write_str(self.as_str())
294 }
295}
296
297impl From<&str> for StopReason {
298 fn from(s: &str) -> Self {
299 match s {
300 "end_turn" => Self::EndTurn,
301 "max_tokens" => Self::MaxTokens,
302 "tool_use" => Self::ToolUse,
303 other => Self::Unknown(other.to_string()),
304 }
305 }
306}
307
308impl Serialize for StopReason {
309 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
310 serializer.serialize_str(self.as_str())
311 }
312}
313
314impl<'de> Deserialize<'de> for StopReason {
315 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
316 let s = String::deserialize(deserializer)?;
317 Ok(Self::from(s.as_str()))
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Hash)]
323pub enum ApiKeySource {
324 None,
326 User,
327 Project,
328 Org,
329 Temporary,
330 Oauth,
331 Unknown(String),
333}
334
335impl ApiKeySource {
336 pub fn as_str(&self) -> &str {
337 match self {
338 Self::None => "none",
339 Self::User => "user",
340 Self::Project => "project",
341 Self::Org => "org",
342 Self::Temporary => "temporary",
343 Self::Oauth => "oauth",
344 Self::Unknown(s) => s.as_str(),
345 }
346 }
347}
348
349impl fmt::Display for ApiKeySource {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 f.write_str(self.as_str())
352 }
353}
354
355impl From<&str> for ApiKeySource {
356 fn from(s: &str) -> Self {
357 match s {
358 "none" => Self::None,
359 "user" => Self::User,
360 "project" => Self::Project,
361 "org" => Self::Org,
362 "temporary" => Self::Temporary,
363 "oauth" => Self::Oauth,
364 other => Self::Unknown(other.to_string()),
365 }
366 }
367}
368
369impl Serialize for ApiKeySource {
370 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
371 serializer.serialize_str(self.as_str())
372 }
373}
374
375impl<'de> Deserialize<'de> for ApiKeySource {
376 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
377 let s = String::deserialize(deserializer)?;
378 Ok(Self::from(s.as_str()))
379 }
380}
381
382#[derive(Debug, Clone, PartialEq, Eq, Hash)]
384pub enum OutputStyle {
385 Default,
387 Unknown(String),
389}
390
391impl OutputStyle {
392 pub fn as_str(&self) -> &str {
393 match self {
394 Self::Default => "default",
395 Self::Unknown(s) => s.as_str(),
396 }
397 }
398}
399
400impl fmt::Display for OutputStyle {
401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402 f.write_str(self.as_str())
403 }
404}
405
406impl From<&str> for OutputStyle {
407 fn from(s: &str) -> Self {
408 match s {
409 "default" => Self::Default,
410 other => Self::Unknown(other.to_string()),
411 }
412 }
413}
414
415impl Serialize for OutputStyle {
416 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
417 serializer.serialize_str(self.as_str())
418 }
419}
420
421impl<'de> Deserialize<'de> for OutputStyle {
422 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
423 let s = String::deserialize(deserializer)?;
424 Ok(Self::from(s.as_str()))
425 }
426}
427
428#[derive(Debug, Clone, PartialEq, Eq, Hash)]
430pub enum InitPermissionMode {
431 Default,
433 AcceptEdits,
434 BypassPermissions,
435 Plan,
436 DontAsk,
437 Auto,
438 Unknown(String),
440}
441
442impl InitPermissionMode {
443 pub fn as_str(&self) -> &str {
444 match self {
445 Self::Default => "default",
446 Self::AcceptEdits => "acceptEdits",
447 Self::BypassPermissions => "bypassPermissions",
448 Self::Plan => "plan",
449 Self::DontAsk => "dontAsk",
450 Self::Auto => "auto",
451 Self::Unknown(s) => s.as_str(),
452 }
453 }
454}
455
456impl fmt::Display for InitPermissionMode {
457 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458 f.write_str(self.as_str())
459 }
460}
461
462impl From<&str> for InitPermissionMode {
463 fn from(s: &str) -> Self {
464 match s {
465 "default" => Self::Default,
466 "acceptEdits" => Self::AcceptEdits,
467 "bypassPermissions" => Self::BypassPermissions,
468 "plan" => Self::Plan,
469 "dontAsk" => Self::DontAsk,
470 "auto" => Self::Auto,
471 other => Self::Unknown(other.to_string()),
472 }
473 }
474}
475
476impl Serialize for InitPermissionMode {
477 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
478 serializer.serialize_str(self.as_str())
479 }
480}
481
482impl<'de> Deserialize<'de> for InitPermissionMode {
483 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
484 let s = String::deserialize(deserializer)?;
485 Ok(Self::from(s.as_str()))
486 }
487}
488
489#[derive(Debug, Clone, PartialEq, Eq, Hash)]
491pub enum StatusMessageStatus {
492 Compacting,
494 Requesting,
496 Unknown(String),
498}
499
500impl StatusMessageStatus {
501 pub fn as_str(&self) -> &str {
502 match self {
503 Self::Compacting => "compacting",
504 Self::Requesting => "requesting",
505 Self::Unknown(s) => s.as_str(),
506 }
507 }
508}
509
510impl fmt::Display for StatusMessageStatus {
511 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512 f.write_str(self.as_str())
513 }
514}
515
516impl From<&str> for StatusMessageStatus {
517 fn from(s: &str) -> Self {
518 match s {
519 "compacting" => Self::Compacting,
520 "requesting" => Self::Requesting,
521 other => Self::Unknown(other.to_string()),
522 }
523 }
524}
525
526impl Serialize for StatusMessageStatus {
527 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
528 serializer.serialize_str(self.as_str())
529 }
530}
531
532impl<'de> Deserialize<'de> for StatusMessageStatus {
533 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
534 let s = String::deserialize(deserializer)?;
535 Ok(Self::from(s.as_str()))
536 }
537}
538
539pub(crate) fn serialize_optional_uuid<S>(
541 uuid: &Option<Uuid>,
542 serializer: S,
543) -> Result<S::Ok, S::Error>
544where
545 S: Serializer,
546{
547 match uuid {
548 Some(id) => serializer.serialize_str(&id.to_string()),
549 None => serializer.serialize_none(),
550 }
551}
552
553pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
555where
556 D: Deserializer<'de>,
557{
558 let opt_str: Option<String> = Option::deserialize(deserializer)?;
559 match opt_str {
560 Some(s) => Uuid::parse_str(&s)
561 .map(Some)
562 .map_err(serde::de::Error::custom),
563 None => Ok(None),
564 }
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
570pub struct MessageOrigin {
571 pub kind: String,
572 #[serde(flatten)]
573 pub extra: serde_json::Map<String, Value>,
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
578pub struct SummarizeMetadata {
579 pub messages_summarized: u64,
580 #[serde(default, skip_serializing_if = "Option::is_none")]
581 pub user_context: Option<String>,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
583 pub direction: Option<String>,
584}
585
586#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
588pub struct McpMeta {
589 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
590 pub meta: Option<Value>,
591 #[serde(default, skip_serializing_if = "Option::is_none")]
592 pub structured_content: Option<Value>,
593 #[serde(default, skip_serializing_if = "Vec::is_empty")]
598 pub resource_links: Vec<ResourceLink>,
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
605pub struct ResourceLink {
606 pub uri: String,
607 pub name: String,
608 #[serde(default, skip_serializing_if = "Option::is_none")]
609 pub title: Option<String>,
610 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub description: Option<String>,
612 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
613 pub mime_type: Option<String>,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 pub size: Option<u64>,
616 #[serde(default, skip_serializing_if = "Option::is_none")]
617 pub annotations: Option<Value>,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
622pub struct ToolResultMeta {
623 pub id: String,
625 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub non_execution_kind: Option<String>,
633 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub user_feedback: Option<String>,
636 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub remedy: Option<ToolResultRemedy>,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
649pub struct ToolResultRemedy {
650 pub kind: ToolResultRemedyKind,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
657 pub servers: Option<Vec<String>>,
658 #[serde(default, skip_serializing_if = "Option::is_none")]
661 pub provider: Option<RemedyLoginProvider>,
662 #[serde(default, skip_serializing_if = "Option::is_none")]
666 pub path: Option<String>,
667 #[serde(default, skip_serializing_if = "Option::is_none")]
672 pub managed: Option<bool>,
673 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub feature: Option<RemedyFeature>,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
678 pub cause: Option<RemedyFeatureCause>,
679 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub policy_kind: Option<RemedyPolicyKind>,
682}
683
684#[derive(Debug, Clone, PartialEq, Eq, Hash)]
686pub enum ToolResultRemedyKind {
687 McpNeedsAuth,
689 McpDisabled,
691 McpRequiredMissing,
693 AuthExpired,
695 AuthOverridden,
697 AuthMissingScope,
699 DesignNeedsAuthorization,
701 SandboxViolation,
703 OutsideReadsBlocked,
705 MemoryPaused,
707 FeatureDisabled,
709 PolicyDenied,
712 SpawnArgLimit,
714 StagedForReview,
716 Unknown(String),
718}
719
720impl ToolResultRemedyKind {
721 pub fn as_str(&self) -> &str {
722 match self {
723 Self::McpNeedsAuth => "mcp_needs_auth",
724 Self::McpDisabled => "mcp_disabled",
725 Self::McpRequiredMissing => "mcp_required_missing",
726 Self::AuthExpired => "auth_expired",
727 Self::AuthOverridden => "auth_overridden",
728 Self::AuthMissingScope => "auth_missing_scope",
729 Self::DesignNeedsAuthorization => "design_needs_authorization",
730 Self::SandboxViolation => "sandbox_violation",
731 Self::OutsideReadsBlocked => "outside_reads_blocked",
732 Self::MemoryPaused => "memory_paused",
733 Self::FeatureDisabled => "feature_disabled",
734 Self::PolicyDenied => "policy_denied",
735 Self::SpawnArgLimit => "spawn_arg_limit",
736 Self::StagedForReview => "staged_for_review",
737 Self::Unknown(s) => s.as_str(),
738 }
739 }
740}
741
742impl fmt::Display for ToolResultRemedyKind {
743 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
744 f.write_str(self.as_str())
745 }
746}
747
748impl From<&str> for ToolResultRemedyKind {
749 fn from(s: &str) -> Self {
750 match s {
751 "mcp_needs_auth" => Self::McpNeedsAuth,
752 "mcp_disabled" => Self::McpDisabled,
753 "mcp_required_missing" => Self::McpRequiredMissing,
754 "auth_expired" => Self::AuthExpired,
755 "auth_overridden" => Self::AuthOverridden,
756 "auth_missing_scope" => Self::AuthMissingScope,
757 "design_needs_authorization" => Self::DesignNeedsAuthorization,
758 "sandbox_violation" => Self::SandboxViolation,
759 "outside_reads_blocked" => Self::OutsideReadsBlocked,
760 "memory_paused" => Self::MemoryPaused,
761 "feature_disabled" => Self::FeatureDisabled,
762 "policy_denied" => Self::PolicyDenied,
763 "spawn_arg_limit" => Self::SpawnArgLimit,
764 "staged_for_review" => Self::StagedForReview,
765 other => Self::Unknown(other.to_string()),
766 }
767 }
768}
769
770impl Serialize for ToolResultRemedyKind {
771 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
772 serializer.serialize_str(self.as_str())
773 }
774}
775
776impl<'de> Deserialize<'de> for ToolResultRemedyKind {
777 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
778 let s = String::deserialize(deserializer)?;
779 Ok(Self::from(s.as_str()))
780 }
781}
782
783#[derive(Debug, Clone, PartialEq, Eq, Hash)]
785pub enum RemedyLoginProvider {
786 ClaudeAi,
788 ClaudeDesign,
790 Unknown(String),
792}
793
794impl RemedyLoginProvider {
795 pub fn as_str(&self) -> &str {
796 match self {
797 Self::ClaudeAi => "claude_ai",
798 Self::ClaudeDesign => "claude_design",
799 Self::Unknown(s) => s.as_str(),
800 }
801 }
802}
803
804impl fmt::Display for RemedyLoginProvider {
805 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
806 f.write_str(self.as_str())
807 }
808}
809
810impl From<&str> for RemedyLoginProvider {
811 fn from(s: &str) -> Self {
812 match s {
813 "claude_ai" => Self::ClaudeAi,
814 "claude_design" => Self::ClaudeDesign,
815 other => Self::Unknown(other.to_string()),
816 }
817 }
818}
819
820impl Serialize for RemedyLoginProvider {
821 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
822 serializer.serialize_str(self.as_str())
823 }
824}
825
826impl<'de> Deserialize<'de> for RemedyLoginProvider {
827 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
828 let s = String::deserialize(deserializer)?;
829 Ok(Self::from(s.as_str()))
830 }
831}
832
833#[derive(Debug, Clone, PartialEq, Eq, Hash)]
836pub enum RemedyFeature {
837 Workflows,
838 Artifacts,
839 Unknown(String),
841}
842
843impl RemedyFeature {
844 pub fn as_str(&self) -> &str {
845 match self {
846 Self::Workflows => "workflows",
847 Self::Artifacts => "artifacts",
848 Self::Unknown(s) => s.as_str(),
849 }
850 }
851}
852
853impl fmt::Display for RemedyFeature {
854 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
855 f.write_str(self.as_str())
856 }
857}
858
859impl From<&str> for RemedyFeature {
860 fn from(s: &str) -> Self {
861 match s {
862 "workflows" => Self::Workflows,
863 "artifacts" => Self::Artifacts,
864 other => Self::Unknown(other.to_string()),
865 }
866 }
867}
868
869impl Serialize for RemedyFeature {
870 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
871 serializer.serialize_str(self.as_str())
872 }
873}
874
875impl<'de> Deserialize<'de> for RemedyFeature {
876 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
877 let s = String::deserialize(deserializer)?;
878 Ok(Self::from(s.as_str()))
879 }
880}
881
882#[derive(Debug, Clone, PartialEq, Eq, Hash)]
885pub enum RemedyFeatureCause {
886 ManagedSettings,
889 OrgPolicy,
891 Unavailable,
893 UserSetting,
896 Unknown(String),
898}
899
900impl RemedyFeatureCause {
901 pub fn as_str(&self) -> &str {
902 match self {
903 Self::ManagedSettings => "managed_settings",
904 Self::OrgPolicy => "org_policy",
905 Self::Unavailable => "unavailable",
906 Self::UserSetting => "user_setting",
907 Self::Unknown(s) => s.as_str(),
908 }
909 }
910}
911
912impl fmt::Display for RemedyFeatureCause {
913 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
914 f.write_str(self.as_str())
915 }
916}
917
918impl From<&str> for RemedyFeatureCause {
919 fn from(s: &str) -> Self {
920 match s {
921 "managed_settings" => Self::ManagedSettings,
922 "org_policy" => Self::OrgPolicy,
923 "unavailable" => Self::Unavailable,
924 "user_setting" => Self::UserSetting,
925 other => Self::Unknown(other.to_string()),
926 }
927 }
928}
929
930impl Serialize for RemedyFeatureCause {
931 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
932 serializer.serialize_str(self.as_str())
933 }
934}
935
936impl<'de> Deserialize<'de> for RemedyFeatureCause {
937 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
938 let s = String::deserialize(deserializer)?;
939 Ok(Self::from(s.as_str()))
940 }
941}
942
943#[derive(Debug, Clone, PartialEq, Eq, Hash)]
946pub enum RemedyPolicyKind {
947 OrgDenied,
949 Latched,
952 Unknown(String),
954}
955
956impl RemedyPolicyKind {
957 pub fn as_str(&self) -> &str {
958 match self {
959 Self::OrgDenied => "org_denied",
960 Self::Latched => "latched",
961 Self::Unknown(s) => s.as_str(),
962 }
963 }
964}
965
966impl fmt::Display for RemedyPolicyKind {
967 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
968 f.write_str(self.as_str())
969 }
970}
971
972impl From<&str> for RemedyPolicyKind {
973 fn from(s: &str) -> Self {
974 match s {
975 "org_denied" => Self::OrgDenied,
976 "latched" => Self::Latched,
977 other => Self::Unknown(other.to_string()),
978 }
979 }
980}
981
982impl Serialize for RemedyPolicyKind {
983 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
984 serializer.serialize_str(self.as_str())
985 }
986}
987
988impl<'de> Deserialize<'de> for RemedyPolicyKind {
989 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
990 let s = String::deserialize(deserializer)?;
991 Ok(Self::from(s.as_str()))
992 }
993}
994
995#[derive(Debug, Clone, Serialize, Deserialize)]
997pub struct UserMessage {
998 pub message: MessageContent,
999 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1000 #[serde(
1001 serialize_with = "serialize_optional_uuid",
1002 deserialize_with = "deserialize_optional_uuid"
1003 )]
1004 pub session_id: Option<Uuid>,
1005 #[serde(skip_serializing_if = "Option::is_none")]
1007 pub parent_tool_use_id: Option<String>,
1008 #[serde(skip_serializing_if = "Option::is_none")]
1010 pub uuid: Option<String>,
1011 #[serde(skip_serializing_if = "Option::is_none")]
1013 pub timestamp: Option<String>,
1014 #[serde(skip_serializing_if = "Option::is_none")]
1021 pub tool_use_result: Option<serde_json::Value>,
1022 #[serde(skip_serializing_if = "Option::is_none")]
1025 pub subagent_type: Option<String>,
1026 #[serde(skip_serializing_if = "Option::is_none")]
1028 pub task_description: Option<String>,
1029 #[serde(skip_serializing_if = "Option::is_none")]
1030 pub origin: Option<MessageOrigin>,
1031 #[serde(skip_serializing_if = "Option::is_none")]
1032 pub priority: Option<String>,
1033 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1034 pub is_synthetic: Option<bool>,
1035 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1036 pub should_query: Option<bool>,
1037 #[serde(default, skip_serializing_if = "Option::is_none")]
1038 pub is_meta: Option<bool>,
1039 #[serde(default, skip_serializing_if = "Option::is_none")]
1040 pub is_visible_in_transcript_only: Option<bool>,
1041 #[serde(default, skip_serializing_if = "Option::is_none")]
1042 pub is_virtual: Option<bool>,
1043 #[serde(default, skip_serializing_if = "Option::is_none")]
1044 pub is_compact_summary: Option<bool>,
1045 #[serde(skip_serializing_if = "Option::is_none")]
1046 pub summarize_metadata: Option<SummarizeMetadata>,
1047 #[serde(skip_serializing_if = "Option::is_none")]
1048 pub mcp_meta: Option<McpMeta>,
1049 #[serde(skip_serializing_if = "Option::is_none")]
1052 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1053 #[serde(skip_serializing_if = "Option::is_none")]
1054 pub source_tool_use_id: Option<String>,
1055 #[serde(skip_serializing_if = "Option::is_none")]
1056 pub source_tool_assistant_uuid: Option<String>,
1057 #[serde(skip_serializing_if = "Option::is_none")]
1058 pub image_paste_ids: Option<Vec<u64>>,
1059 #[serde(skip_serializing_if = "Option::is_none")]
1060 pub client_platform: Option<String>,
1061 #[serde(skip_serializing_if = "Option::is_none")]
1062 pub inbound_origin: Option<String>,
1063 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1064 pub is_replay: Option<bool>,
1065 #[serde(skip_serializing_if = "Option::is_none")]
1066 pub file_attachments: Option<Vec<Value>>,
1067 #[serde(default, skip_serializing_if = "Option::is_none")]
1069 pub seeded_summon: Option<bool>,
1070 #[serde(default, skip_serializing_if = "Option::is_none")]
1073 pub client_composed: Option<bool>,
1074 #[serde(default, skip_serializing_if = "Option::is_none")]
1079 pub initiator: Option<String>,
1080 #[serde(default, skip_serializing_if = "Option::is_none")]
1086 pub pasted_content: Option<Vec<Value>>,
1087 #[serde(default, skip_serializing_if = "Option::is_none")]
1091 pub historical: Option<bool>,
1092}
1093
1094impl UserMessage {
1095 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1103 &self,
1104 ) -> Option<Result<T, serde_json::Error>> {
1105 self.tool_use_result
1106 .as_ref()
1107 .map(|v| serde_json::from_value(v.clone()))
1108 }
1109
1110 pub fn subagent_result(&self) -> Option<SubagentResult> {
1121 self.tool_use_result
1122 .as_ref()
1123 .and_then(|v| serde_json::from_value(v.clone()).ok())
1124 }
1125}
1126
1127#[derive(Debug, Clone, Serialize, Deserialize)]
1138pub struct SubagentResult {
1139 #[serde(skip_serializing_if = "Option::is_none")]
1141 pub status: Option<String>,
1142 #[serde(skip_serializing_if = "Option::is_none")]
1144 pub prompt: Option<String>,
1145 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1147 pub agent_id: Option<String>,
1148 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1150 pub agent_type: Option<String>,
1151 #[serde(
1153 default,
1154 deserialize_with = "deserialize_content_blocks",
1155 skip_serializing_if = "Vec::is_empty"
1156 )]
1157 pub content: Vec<ContentBlock>,
1158 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1160 pub resolved_model: Option<String>,
1161 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1163 pub total_duration_ms: Option<u64>,
1164 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1166 pub total_tokens: Option<u64>,
1167 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1169 pub total_tool_use_count: Option<u64>,
1170 #[serde(skip_serializing_if = "Option::is_none")]
1172 pub usage: Option<super::result::UsageInfo>,
1173 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1175 pub tool_stats: Option<SubagentToolStats>,
1176}
1177
1178#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1183#[serde(rename_all = "camelCase")]
1184pub struct SubagentToolStats {
1185 #[serde(default)]
1186 pub read_count: u64,
1187 #[serde(default)]
1188 pub search_count: u64,
1189 #[serde(default)]
1190 pub bash_count: u64,
1191 #[serde(default)]
1192 pub edit_file_count: u64,
1193 #[serde(default)]
1194 pub lines_added: u64,
1195 #[serde(default)]
1196 pub lines_removed: u64,
1197 #[serde(default)]
1198 pub other_tool_count: u64,
1199 #[serde(flatten)]
1200 pub extra: serde_json::Map<String, Value>,
1201}
1202
1203#[derive(Debug, Clone, Default, PartialEq, Eq)]
1230pub struct SubagentUsageRollup {
1231 pub subagent_tokens: u64,
1234 pub agent_count: u32,
1236 pub tool_uses: u64,
1238 pub duration_ms: u64,
1240 seen_agent_ids: std::collections::BTreeSet<String>,
1241}
1242
1243impl SubagentUsageRollup {
1244 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1250 match output {
1251 ClaudeOutput::User(user) => self.observe_user(user),
1252 _ => false,
1253 }
1254 }
1255
1256 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1263 let Some(result) = user.subagent_result() else {
1264 return false;
1265 };
1266 if result.total_tokens.is_none() && result.agent_id.is_none() {
1267 return false;
1268 }
1269 if let Some(agent_id) = &result.agent_id {
1270 if !self.seen_agent_ids.insert(agent_id.clone()) {
1271 return false;
1272 }
1273 }
1274 self.agent_count += 1;
1275 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1276 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1277 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1278 true
1279 }
1280}
1281
1282#[derive(Debug, Clone, Serialize, Deserialize)]
1284pub struct MessageContent {
1285 pub role: MessageRole,
1286 #[serde(deserialize_with = "deserialize_content_blocks")]
1287 pub content: Vec<ContentBlock>,
1288}
1289
1290#[derive(Debug, Clone, Serialize, Deserialize)]
1292pub struct SystemMessage {
1293 pub subtype: SystemSubtype,
1294 #[serde(flatten)]
1295 pub data: Value, }
1297
1298impl SystemMessage {
1299 pub fn is_init(&self) -> bool {
1301 self.subtype == SystemSubtype::Init
1302 }
1303
1304 pub fn is_status(&self) -> bool {
1306 self.subtype == SystemSubtype::Status
1307 }
1308
1309 pub fn is_compact_boundary(&self) -> bool {
1311 self.subtype == SystemSubtype::CompactBoundary
1312 }
1313
1314 pub fn as_init(&self) -> Option<InitMessage> {
1316 if self.subtype != SystemSubtype::Init {
1317 return None;
1318 }
1319 serde_json::from_value(self.data.clone()).ok()
1320 }
1321
1322 pub fn as_status(&self) -> Option<StatusMessage> {
1324 if self.subtype != SystemSubtype::Status {
1325 return None;
1326 }
1327 serde_json::from_value(self.data.clone()).ok()
1328 }
1329
1330 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1332 if self.subtype != SystemSubtype::CompactBoundary {
1333 return None;
1334 }
1335 serde_json::from_value(self.data.clone()).ok()
1336 }
1337
1338 pub fn is_task_started(&self) -> bool {
1340 self.subtype == SystemSubtype::TaskStarted
1341 }
1342
1343 pub fn is_task_progress(&self) -> bool {
1345 self.subtype == SystemSubtype::TaskProgress
1346 }
1347
1348 pub fn is_task_notification(&self) -> bool {
1350 self.subtype == SystemSubtype::TaskNotification
1351 }
1352
1353 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1355 if self.subtype != SystemSubtype::TaskStarted {
1356 return None;
1357 }
1358 serde_json::from_value(self.data.clone()).ok()
1359 }
1360
1361 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1363 if self.subtype != SystemSubtype::TaskProgress {
1364 return None;
1365 }
1366 serde_json::from_value(self.data.clone()).ok()
1367 }
1368
1369 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1371 if self.subtype != SystemSubtype::TaskNotification {
1372 return None;
1373 }
1374 serde_json::from_value(self.data.clone()).ok()
1375 }
1376
1377 pub fn is_task_updated(&self) -> bool {
1379 self.subtype == SystemSubtype::TaskUpdated
1380 }
1381
1382 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1384 if self.subtype != SystemSubtype::TaskUpdated {
1385 return None;
1386 }
1387 serde_json::from_value(self.data.clone()).ok()
1388 }
1389
1390 pub fn is_thinking_tokens(&self) -> bool {
1392 self.subtype == SystemSubtype::ThinkingTokens
1393 }
1394
1395 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1397 if self.subtype != SystemSubtype::ThinkingTokens {
1398 return None;
1399 }
1400 serde_json::from_value(self.data.clone()).ok()
1401 }
1402
1403 pub fn is_code_change_published(&self) -> bool {
1405 self.subtype == SystemSubtype::CodeChangePublished
1406 }
1407
1408 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1410 if self.subtype != SystemSubtype::CodeChangePublished {
1411 return None;
1412 }
1413 serde_json::from_value(self.data.clone()).ok()
1414 }
1415
1416 pub fn is_vcs_state_changed(&self) -> bool {
1418 self.subtype == SystemSubtype::VcsStateChanged
1419 }
1420
1421 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1423 if self.subtype != SystemSubtype::VcsStateChanged {
1424 return None;
1425 }
1426 serde_json::from_value(self.data.clone()).ok()
1427 }
1428
1429 pub fn is_feedback_draft_queued(&self) -> bool {
1431 self.subtype == SystemSubtype::FeedbackDraftQueued
1432 }
1433
1434 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1436 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1437 return None;
1438 }
1439 serde_json::from_value(self.data.clone()).ok()
1440 }
1441
1442 pub fn is_cloud_session_delta(&self) -> bool {
1444 self.subtype == SystemSubtype::CloudSessionDelta
1445 }
1446
1447 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1449 if self.subtype != SystemSubtype::CloudSessionDelta {
1450 return None;
1451 }
1452 serde_json::from_value(self.data.clone()).ok()
1453 }
1454
1455 pub fn is_dev_intent(&self) -> bool {
1457 self.subtype == SystemSubtype::DevIntent
1458 }
1459
1460 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1462 if self.subtype != SystemSubtype::DevIntent {
1463 return None;
1464 }
1465 serde_json::from_value(self.data.clone()).ok()
1466 }
1467
1468 pub fn is_turn_handoff_available(&self) -> bool {
1470 self.subtype == SystemSubtype::TurnHandoffAvailable
1471 }
1472
1473 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1475 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1476 return None;
1477 }
1478 serde_json::from_value(self.data.clone()).ok()
1479 }
1480
1481 pub fn is_turn_preempted(&self) -> bool {
1483 self.subtype == SystemSubtype::TurnPreempted
1484 }
1485
1486 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1488 if self.subtype != SystemSubtype::TurnPreempted {
1489 return None;
1490 }
1491 serde_json::from_value(self.data.clone()).ok()
1492 }
1493
1494 pub fn is_peer_message_hold(&self) -> bool {
1496 self.subtype == SystemSubtype::PeerMessageHold
1497 }
1498
1499 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1501 if self.subtype != SystemSubtype::PeerMessageHold {
1502 return None;
1503 }
1504 serde_json::from_value(self.data.clone()).ok()
1505 }
1506
1507 pub fn is_per_turn_effort_changed(&self) -> bool {
1509 self.subtype == SystemSubtype::PerTurnEffortChanged
1510 }
1511
1512 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1514 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1515 return None;
1516 }
1517 serde_json::from_value(self.data.clone()).ok()
1518 }
1519
1520 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1522 macro_rules! parse {
1523 ($variant:ident, $ty:ty) => {
1524 serde_json::from_value::<$ty>(self.data.clone())
1525 .ok()
1526 .map(KnownSystemEvent::$variant)
1527 };
1528 }
1529
1530 match self.subtype {
1531 SystemSubtype::Init => parse!(Init, InitMessage),
1532 SystemSubtype::Status => parse!(Status, StatusMessage),
1533 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1534 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1535 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1536 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1537 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1538 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1539 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1540 SystemSubtype::ControlRequestProgress => {
1541 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1542 }
1543 SystemSubtype::ModelRefusalFallback => {
1544 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1545 }
1546 SystemSubtype::ModelRefusalNoFallback => {
1547 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1548 }
1549 SystemSubtype::LocalCommandOutput => {
1550 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1551 }
1552 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1553 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1554 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1555 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1556 SystemSubtype::BackgroundTasksChanged => {
1557 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1558 }
1559 SystemSubtype::SessionStateChanged => {
1560 parse!(SessionStateChanged, SessionStateChangedMessage)
1561 }
1562 SystemSubtype::WorkerShuttingDown => {
1563 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1564 }
1565 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1566 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1567 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1568 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1569 SystemSubtype::ElicitationComplete => {
1570 parse!(ElicitationComplete, ElicitationCompleteMessage)
1571 }
1572 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1573 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1574 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1575 SystemSubtype::CodeChangePublished => {
1576 parse!(CodeChangePublished, CodeChangePublishedMessage)
1577 }
1578 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1579 SystemSubtype::FeedbackDraftQueued => {
1580 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1581 }
1582 SystemSubtype::CloudSessionDelta => {
1583 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1584 }
1585 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1586 SystemSubtype::TurnHandoffAvailable => {
1587 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1588 }
1589 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1590 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1591 SystemSubtype::PerTurnEffortChanged => {
1592 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1593 }
1594 SystemSubtype::Unknown(_) => None,
1595 }
1596 }
1597
1598 pub fn typed_value(&self) -> Option<Value> {
1607 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1608 parsed.and_then(|v| serde_json::to_value(v).ok())
1609 }
1610 match self.subtype {
1611 SystemSubtype::Init => reserialize(self.as_init()),
1612 SystemSubtype::Status => reserialize(self.as_status()),
1613 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1614 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1615 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1616 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1617 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1618 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1619 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1620 SystemSubtype::ControlRequestProgress => {
1621 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1622 }
1623 SystemSubtype::ModelRefusalFallback => {
1624 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1625 }
1626 SystemSubtype::ModelRefusalNoFallback => {
1627 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1628 }
1629 SystemSubtype::LocalCommandOutput => {
1630 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1631 }
1632 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1633 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1634 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1635 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1636 SystemSubtype::BackgroundTasksChanged => {
1637 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1638 }
1639 SystemSubtype::SessionStateChanged => {
1640 reserialize(parse_system::<SessionStateChangedMessage>(self))
1641 }
1642 SystemSubtype::WorkerShuttingDown => {
1643 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1644 }
1645 SystemSubtype::CommandsChanged => {
1646 reserialize(parse_system::<CommandsChangedMessage>(self))
1647 }
1648 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1649 SystemSubtype::FilesPersisted => {
1650 reserialize(parse_system::<FilesPersistedMessage>(self))
1651 }
1652 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1653 SystemSubtype::ElicitationComplete => {
1654 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1655 }
1656 SystemSubtype::PermissionDenied => {
1657 reserialize(parse_system::<PermissionDeniedMessage>(self))
1658 }
1659 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1660 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1661 SystemSubtype::CodeChangePublished => {
1662 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1663 }
1664 SystemSubtype::VcsStateChanged => {
1665 reserialize(parse_system::<VcsStateChangedMessage>(self))
1666 }
1667 SystemSubtype::FeedbackDraftQueued => {
1668 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1669 }
1670 SystemSubtype::CloudSessionDelta => {
1671 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1672 }
1673 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1674 SystemSubtype::TurnHandoffAvailable => {
1675 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1676 }
1677 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1678 SystemSubtype::PeerMessageHold => {
1679 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1680 }
1681 SystemSubtype::PerTurnEffortChanged => {
1682 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
1683 }
1684 SystemSubtype::Unknown(_) => None,
1685 }
1686 }
1687}
1688
1689fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1690 serde_json::from_value(message.data.clone()).ok()
1691}
1692
1693#[allow(clippy::large_enum_variant)]
1699#[derive(Debug, Clone, Serialize, Deserialize)]
1700pub enum KnownSystemEvent {
1701 Init(InitMessage),
1702 Status(StatusMessage),
1703 CompactBoundary(CompactBoundaryMessage),
1704 ThinkingTokens(ThinkingTokensMessage),
1705 TaskStarted(TaskStartedMessage),
1706 TaskProgress(TaskProgressMessage),
1707 TaskUpdated(TaskUpdatedMessage),
1708 TaskNotification(TaskNotificationMessage),
1709 ApiRetry(ApiRetryMessage),
1710 ControlRequestProgress(ControlRequestProgressMessage),
1711 ModelRefusalFallback(ModelRefusalFallbackMessage),
1712 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1713 LocalCommandOutput(LocalCommandOutputMessage),
1714 HookStarted(HookStartedMessage),
1715 HookProgress(HookProgressMessage),
1716 HookResponse(HookResponseMessage),
1717 PluginInstall(PluginInstallMessage),
1718 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1719 SessionStateChanged(SessionStateChangedMessage),
1720 WorkerShuttingDown(WorkerShuttingDownMessage),
1721 CommandsChanged(CommandsChangedMessage),
1722 Notification(NotificationMessage),
1723 FilesPersisted(FilesPersistedMessage),
1724 MemoryRecall(MemoryRecallMessage),
1725 ElicitationComplete(ElicitationCompleteMessage),
1726 PermissionDenied(PermissionDeniedMessage),
1727 MirrorError(MirrorErrorMessage),
1728 Informational(InformationalMessage),
1729 CodeChangePublished(CodeChangePublishedMessage),
1730 VcsStateChanged(VcsStateChangedMessage),
1731 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1732 CloudSessionDelta(CloudSessionDeltaMessage),
1733 DevIntent(DevIntentMessage),
1734 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1735 TurnPreempted(TurnPreemptedMessage),
1736 PeerMessageHold(PeerMessageHoldMessage),
1737 PerTurnEffortChanged(PerTurnEffortChangedMessage),
1738}
1739
1740#[derive(Debug, Clone, Serialize, Deserialize)]
1741pub struct ApiRetryMessage {
1742 pub attempt: u64,
1743 pub max_retries: u64,
1744 pub retry_delay_ms: u64,
1745 pub error_status: Option<u16>,
1746 pub error: String,
1747 #[serde(default, skip_serializing_if = "Option::is_none")]
1752 pub no_response: Option<ApiRetryNoResponse>,
1753 #[serde(default, skip_serializing_if = "Option::is_none")]
1754 pub uuid: Option<String>,
1755 #[serde(default, skip_serializing_if = "Option::is_none")]
1756 pub session_id: Option<String>,
1757}
1758
1759#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1762pub struct ApiRetryNoResponse {
1763 pub waited_ms: u64,
1765 pub retry_wait_ms: u64,
1767}
1768
1769#[derive(Debug, Clone, Serialize, Deserialize)]
1770pub struct ControlRequestProgressMessage {
1771 pub request_id: String,
1772 pub status: String,
1773 #[serde(default, skip_serializing_if = "Option::is_none")]
1774 pub attempt: Option<u64>,
1775 #[serde(default, skip_serializing_if = "Option::is_none")]
1776 pub max_retries: Option<u64>,
1777 #[serde(default, skip_serializing_if = "Option::is_none")]
1778 pub retry_delay_ms: Option<u64>,
1779 #[serde(default, skip_serializing_if = "Option::is_none")]
1780 pub error_status: Option<u16>,
1781 #[serde(default, skip_serializing_if = "Option::is_none")]
1782 pub error: Option<String>,
1783 #[serde(default, skip_serializing_if = "Option::is_none")]
1784 pub uuid: Option<String>,
1785 #[serde(default, skip_serializing_if = "Option::is_none")]
1786 pub session_id: Option<String>,
1787}
1788
1789#[derive(Debug, Clone, Serialize, Deserialize)]
1790pub struct ModelRefusalFallbackMessage {
1791 pub trigger: String,
1792 pub direction: String,
1793 #[serde(default, skip_serializing_if = "Option::is_none")]
1799 pub scope: Option<RefusalFallbackScope>,
1800 pub original_model: String,
1801 pub fallback_model: String,
1802 pub request_id: Option<String>,
1803 #[serde(default, skip_serializing_if = "Option::is_none")]
1804 pub api_refusal_category: Option<String>,
1805 #[serde(default, skip_serializing_if = "Option::is_none")]
1810 pub saw_cyber_refusal: Option<bool>,
1811 #[serde(default, skip_serializing_if = "Option::is_none")]
1812 pub api_refusal_explanation: Option<String>,
1813 #[serde(default, skip_serializing_if = "Option::is_none")]
1814 pub retracted_message_uuids: Option<Vec<String>>,
1815 #[serde(default, skip_serializing_if = "Option::is_none")]
1816 pub refused_user_message_uuid: Option<String>,
1817 pub content: Value,
1818 #[serde(default, skip_serializing_if = "Option::is_none")]
1819 pub uuid: Option<String>,
1820 #[serde(default, skip_serializing_if = "Option::is_none")]
1821 pub session_id: Option<String>,
1822}
1823
1824#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1828pub enum RefusalFallbackScope {
1829 Session,
1831 Local,
1834 Unknown(String),
1836}
1837
1838impl RefusalFallbackScope {
1839 pub fn as_str(&self) -> &str {
1840 match self {
1841 Self::Session => "session",
1842 Self::Local => "local",
1843 Self::Unknown(s) => s.as_str(),
1844 }
1845 }
1846}
1847
1848impl fmt::Display for RefusalFallbackScope {
1849 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1850 f.write_str(self.as_str())
1851 }
1852}
1853
1854impl From<&str> for RefusalFallbackScope {
1855 fn from(s: &str) -> Self {
1856 match s {
1857 "session" => Self::Session,
1858 "local" => Self::Local,
1859 other => Self::Unknown(other.to_string()),
1860 }
1861 }
1862}
1863
1864impl Serialize for RefusalFallbackScope {
1865 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1866 serializer.serialize_str(self.as_str())
1867 }
1868}
1869
1870impl<'de> Deserialize<'de> for RefusalFallbackScope {
1871 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1872 let s = String::deserialize(deserializer)?;
1873 Ok(Self::from(s.as_str()))
1874 }
1875}
1876
1877#[derive(Debug, Clone, Serialize, Deserialize)]
1878pub struct ModelRefusalNoFallbackMessage {
1879 pub original_model: String,
1880 pub request_id: Option<String>,
1881 #[serde(default, skip_serializing_if = "Option::is_none")]
1882 pub api_refusal_category: Option<String>,
1883 #[serde(default, skip_serializing_if = "Option::is_none")]
1884 pub api_refusal_explanation: Option<String>,
1885 pub content: Value,
1886 #[serde(default, skip_serializing_if = "Option::is_none")]
1887 pub uuid: Option<String>,
1888 #[serde(default, skip_serializing_if = "Option::is_none")]
1889 pub session_id: Option<String>,
1890}
1891
1892#[derive(Debug, Clone, Serialize, Deserialize)]
1893pub struct LocalCommandOutputMessage {
1894 pub content: String,
1895 #[serde(default, skip_serializing_if = "Option::is_none")]
1896 pub uuid: Option<String>,
1897 #[serde(default, skip_serializing_if = "Option::is_none")]
1898 pub session_id: Option<String>,
1899}
1900
1901#[derive(Debug, Clone, Serialize, Deserialize)]
1902pub struct HookStartedMessage {
1903 pub hook_id: String,
1904 pub hook_name: String,
1905 pub hook_event: String,
1906 #[serde(default, skip_serializing_if = "Option::is_none")]
1907 pub uuid: Option<String>,
1908 #[serde(default, skip_serializing_if = "Option::is_none")]
1909 pub session_id: Option<String>,
1910}
1911
1912#[derive(Debug, Clone, Serialize, Deserialize)]
1913pub struct HookProgressMessage {
1914 pub hook_id: String,
1915 pub hook_name: String,
1916 pub hook_event: String,
1917 #[serde(default, skip_serializing_if = "Option::is_none")]
1918 pub stdout: Option<String>,
1919 #[serde(default, skip_serializing_if = "Option::is_none")]
1920 pub stderr: Option<String>,
1921 #[serde(default, skip_serializing_if = "Option::is_none")]
1922 pub output: Option<String>,
1923 #[serde(default, skip_serializing_if = "Option::is_none")]
1924 pub uuid: Option<String>,
1925 #[serde(default, skip_serializing_if = "Option::is_none")]
1926 pub session_id: Option<String>,
1927}
1928
1929#[derive(Debug, Clone, Serialize, Deserialize)]
1930pub struct HookResponseMessage {
1931 pub hook_id: String,
1932 pub hook_name: String,
1933 pub hook_event: String,
1934 #[serde(default, skip_serializing_if = "Option::is_none")]
1935 pub stdout: Option<String>,
1936 #[serde(default, skip_serializing_if = "Option::is_none")]
1937 pub stderr: Option<String>,
1938 #[serde(default, skip_serializing_if = "Option::is_none")]
1939 pub output: Option<String>,
1940 #[serde(default, skip_serializing_if = "Option::is_none")]
1941 pub exit_code: Option<i32>,
1942 pub outcome: String,
1943 #[serde(default, skip_serializing_if = "Option::is_none")]
1944 pub uuid: Option<String>,
1945 #[serde(default, skip_serializing_if = "Option::is_none")]
1946 pub session_id: Option<String>,
1947}
1948
1949#[derive(Debug, Clone, Serialize, Deserialize)]
1950pub struct PluginInstallMessage {
1951 pub status: String,
1952 #[serde(default, skip_serializing_if = "Option::is_none")]
1953 pub name: Option<String>,
1954 #[serde(default, skip_serializing_if = "Option::is_none")]
1955 pub error: Option<String>,
1956 #[serde(default, skip_serializing_if = "Option::is_none")]
1957 pub uuid: Option<String>,
1958 #[serde(default, skip_serializing_if = "Option::is_none")]
1959 pub session_id: Option<String>,
1960}
1961
1962#[derive(Debug, Clone, Serialize, Deserialize)]
1963pub struct BackgroundTasksChangedMessage {
1964 pub tasks: Vec<BackgroundTaskInfo>,
1965 #[serde(default, skip_serializing_if = "Option::is_none")]
1966 pub uuid: Option<String>,
1967 #[serde(default, skip_serializing_if = "Option::is_none")]
1968 pub session_id: Option<String>,
1969}
1970
1971#[derive(Debug, Clone, Serialize, Deserialize)]
1972pub struct BackgroundTaskInfo {
1973 pub task_id: String,
1974 pub task_type: String,
1975 pub description: String,
1976 #[serde(default, skip_serializing_if = "Option::is_none")]
1979 pub ambient: Option<bool>,
1980}
1981
1982#[derive(Debug, Clone, Serialize, Deserialize)]
1983pub struct SessionStateChangedMessage {
1984 pub state: String,
1985 #[serde(default, skip_serializing_if = "Option::is_none")]
1986 pub uuid: Option<String>,
1987 #[serde(default, skip_serializing_if = "Option::is_none")]
1988 pub session_id: Option<String>,
1989}
1990
1991#[derive(Debug, Clone, Serialize, Deserialize)]
1992pub struct WorkerShuttingDownMessage {
1993 pub reason: String,
1994 #[serde(default, skip_serializing_if = "Option::is_none")]
1995 pub uuid: Option<String>,
1996 #[serde(default, skip_serializing_if = "Option::is_none")]
1997 pub session_id: Option<String>,
1998}
1999
2000#[derive(Debug, Clone, Serialize, Deserialize)]
2001pub struct CommandsChangedMessage {
2002 pub commands: Vec<CommandInfo>,
2003 #[serde(default, skip_serializing_if = "Option::is_none")]
2004 pub uuid: Option<String>,
2005 #[serde(default, skip_serializing_if = "Option::is_none")]
2006 pub session_id: Option<String>,
2007}
2008
2009#[derive(Debug, Clone, Serialize, Deserialize)]
2010pub struct CommandInfo {
2011 pub name: String,
2012 pub description: String,
2013 #[serde(rename = "argumentHint")]
2014 pub argument_hint: String,
2015 #[serde(default, skip_serializing_if = "Option::is_none")]
2016 pub aliases: Option<Vec<String>>,
2017 #[serde(default, skip_serializing_if = "Option::is_none")]
2023 pub builtin: Option<bool>,
2024}
2025
2026#[derive(Debug, Clone, Serialize, Deserialize)]
2027pub struct NotificationMessage {
2028 pub key: String,
2029 pub text: String,
2030 pub priority: String,
2031 #[serde(default, skip_serializing_if = "Option::is_none")]
2032 pub color: Option<String>,
2033 #[serde(default, skip_serializing_if = "Option::is_none")]
2034 pub timeout_ms: Option<u64>,
2035 #[serde(default, skip_serializing_if = "Option::is_none")]
2036 pub uuid: Option<String>,
2037 #[serde(default, skip_serializing_if = "Option::is_none")]
2038 pub session_id: Option<String>,
2039}
2040
2041#[derive(Debug, Clone, Serialize, Deserialize)]
2042pub struct FilesPersistedMessage {
2043 pub files: Vec<PersistedFile>,
2044 pub failed: Vec<FailedPersistedFile>,
2045 pub processed_at: String,
2046 #[serde(default, skip_serializing_if = "Option::is_none")]
2047 pub uuid: Option<String>,
2048 #[serde(default, skip_serializing_if = "Option::is_none")]
2049 pub session_id: Option<String>,
2050}
2051
2052#[derive(Debug, Clone, Serialize, Deserialize)]
2053pub struct PersistedFile {
2054 pub filename: String,
2055 pub file_id: String,
2056}
2057
2058#[derive(Debug, Clone, Serialize, Deserialize)]
2059pub struct FailedPersistedFile {
2060 pub filename: String,
2061 pub error: String,
2062}
2063
2064#[derive(Debug, Clone, Serialize, Deserialize)]
2065pub struct MemoryRecallMessage {
2066 pub mode: String,
2067 pub memories: Vec<MemoryRecallItem>,
2068 #[serde(default, skip_serializing_if = "Option::is_none")]
2069 pub uuid: Option<String>,
2070 #[serde(default, skip_serializing_if = "Option::is_none")]
2071 pub session_id: Option<String>,
2072}
2073
2074#[derive(Debug, Clone, Serialize, Deserialize)]
2075pub struct MemoryRecallItem {
2076 pub path: String,
2077 pub scope: String,
2078 #[serde(default, skip_serializing_if = "Option::is_none")]
2079 pub content: Option<String>,
2080}
2081
2082#[derive(Debug, Clone, Serialize, Deserialize)]
2083pub struct ElicitationCompleteMessage {
2084 pub mcp_server_name: String,
2085 pub elicitation_id: String,
2086 #[serde(default, skip_serializing_if = "Option::is_none")]
2087 pub uuid: Option<String>,
2088 #[serde(default, skip_serializing_if = "Option::is_none")]
2089 pub session_id: Option<String>,
2090}
2091
2092#[derive(Debug, Clone, Serialize, Deserialize)]
2093pub struct PermissionDeniedMessage {
2094 pub tool_name: String,
2095 pub tool_use_id: String,
2096 #[serde(default, skip_serializing_if = "Option::is_none")]
2097 pub agent_id: Option<String>,
2098 #[serde(default, skip_serializing_if = "Option::is_none")]
2099 pub decision_reason_type: Option<String>,
2100 #[serde(default, skip_serializing_if = "Option::is_none")]
2104 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2105 #[serde(default, skip_serializing_if = "Option::is_none")]
2106 pub decision_reason: Option<String>,
2107 pub message: String,
2108 #[serde(default, skip_serializing_if = "Option::is_none")]
2109 pub uuid: Option<String>,
2110 #[serde(default, skip_serializing_if = "Option::is_none")]
2111 pub session_id: Option<String>,
2112}
2113
2114#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2117pub enum PermissionDeniedReasonCode {
2118 ClassifierTranscriptTooLong,
2120 OutsideReadsBlocked,
2122 MemoryPaused,
2124 Unknown(String),
2126}
2127
2128impl PermissionDeniedReasonCode {
2129 pub fn as_str(&self) -> &str {
2130 match self {
2131 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2132 Self::OutsideReadsBlocked => "outside_reads_blocked",
2133 Self::MemoryPaused => "memory_paused",
2134 Self::Unknown(s) => s.as_str(),
2135 }
2136 }
2137}
2138
2139impl fmt::Display for PermissionDeniedReasonCode {
2140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2141 f.write_str(self.as_str())
2142 }
2143}
2144
2145impl From<&str> for PermissionDeniedReasonCode {
2146 fn from(s: &str) -> Self {
2147 match s {
2148 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2149 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2150 "memory_paused" => Self::MemoryPaused,
2151 other => Self::Unknown(other.to_string()),
2152 }
2153 }
2154}
2155
2156impl Serialize for PermissionDeniedReasonCode {
2157 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2158 serializer.serialize_str(self.as_str())
2159 }
2160}
2161
2162impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2163 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2164 let s = String::deserialize(deserializer)?;
2165 Ok(Self::from(s.as_str()))
2166 }
2167}
2168
2169#[derive(Debug, Clone, Serialize, Deserialize)]
2170pub struct MirrorErrorMessage {
2171 pub error: String,
2172 pub key: MirrorErrorKey,
2173 #[serde(default, skip_serializing_if = "Option::is_none")]
2174 pub uuid: Option<String>,
2175 #[serde(default, skip_serializing_if = "Option::is_none")]
2176 pub session_id: Option<String>,
2177}
2178
2179#[derive(Debug, Clone, Serialize, Deserialize)]
2180pub struct MirrorErrorKey {
2181 #[serde(rename = "projectKey")]
2182 pub project_key: String,
2183 #[serde(rename = "sessionId")]
2184 pub session_id: String,
2185 #[serde(default, skip_serializing_if = "Option::is_none")]
2186 pub subpath: Option<String>,
2187}
2188
2189#[derive(Debug, Clone, Serialize, Deserialize)]
2190pub struct InformationalMessage {
2191 pub content: String,
2192 pub level: String,
2193 #[serde(default, skip_serializing_if = "Option::is_none")]
2194 pub tool_use_id: Option<String>,
2195 #[serde(default, skip_serializing_if = "Option::is_none")]
2196 pub prevent_continuation: Option<bool>,
2197 #[serde(default, skip_serializing_if = "Option::is_none")]
2198 pub uuid: Option<String>,
2199 #[serde(default, skip_serializing_if = "Option::is_none")]
2200 pub session_id: Option<String>,
2201}
2202
2203#[derive(Debug, Clone, Serialize, Deserialize)]
2205pub struct PluginInfo {
2206 pub name: String,
2208 pub path: String,
2210 #[serde(skip_serializing_if = "Option::is_none")]
2212 pub source: Option<String>,
2213 #[serde(default, skip_serializing_if = "Option::is_none")]
2215 pub version: Option<String>,
2216}
2217
2218#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2220pub struct PluginDiagnostic {
2221 pub plugin: String,
2222 #[serde(rename = "type")]
2223 pub diagnostic_type: String,
2224 pub message: String,
2225}
2226
2227#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2229pub struct MemoryPaths {
2230 #[serde(default, skip_serializing_if = "Option::is_none")]
2231 pub auto: Option<String>,
2232 #[serde(default, skip_serializing_if = "Option::is_none")]
2233 pub team: Option<String>,
2234 #[serde(flatten)]
2235 pub extra: serde_json::Map<String, Value>,
2236}
2237
2238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2242pub struct McpServerError {
2243 pub name: String,
2244 #[serde(rename = "type")]
2246 pub error_type: String,
2247 pub message: String,
2248}
2249
2250#[derive(Debug, Clone, Serialize, Deserialize)]
2252pub struct InitMessage {
2253 pub session_id: String,
2255 #[serde(skip_serializing_if = "Option::is_none")]
2257 pub cwd: Option<String>,
2258 #[serde(skip_serializing_if = "Option::is_none")]
2260 pub model: Option<String>,
2261 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2263 pub tools: Vec<String>,
2264 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2266 pub mcp_servers: Vec<Value>,
2267 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2269 pub slash_commands: Vec<String>,
2270 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2273 pub terminal_slash_commands: Vec<String>,
2274 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2276 pub agents: Vec<String>,
2277 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2279 pub plugins: Vec<PluginInfo>,
2280 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2282 pub skills: Vec<Value>,
2283 #[serde(skip_serializing_if = "Option::is_none")]
2285 pub claude_code_version: Option<String>,
2286 #[serde(skip_serializing_if = "Option::is_none")]
2289 pub messaging_socket_path: Option<String>,
2290 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2292 pub api_key_source: Option<ApiKeySource>,
2293 #[serde(skip_serializing_if = "Option::is_none")]
2295 pub output_style: Option<OutputStyle>,
2296 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2298 pub permission_mode: Option<InitPermissionMode>,
2299
2300 #[serde(skip_serializing_if = "Option::is_none")]
2302 pub uuid: Option<String>,
2303
2304 #[serde(skip_serializing_if = "Option::is_none")]
2306 pub memory_paths: Option<MemoryPaths>,
2307
2308 #[serde(default, skip_serializing_if = "Option::is_none")]
2313 pub scratchpad_path: Option<String>,
2314
2315 #[serde(skip_serializing_if = "Option::is_none")]
2317 pub fast_mode_state: Option<String>,
2318
2319 #[serde(default, skip_serializing_if = "Option::is_none")]
2321 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2322
2323 #[serde(default, skip_serializing_if = "Option::is_none")]
2326 pub mcp_server_errors: Option<Vec<McpServerError>>,
2327
2328 #[serde(default, skip_serializing_if = "Option::is_none")]
2330 pub analytics_disabled: Option<bool>,
2331
2332 #[serde(default, skip_serializing_if = "Option::is_none")]
2334 pub product_feedback_disabled: Option<bool>,
2335
2336 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2338 pub betas: Vec<String>,
2339
2340 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2342 pub capabilities: Vec<String>,
2343
2344 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2346 pub plugin_errors: Vec<PluginDiagnostic>,
2347
2348 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2350 pub plugin_warnings: Vec<PluginDiagnostic>,
2351
2352 #[serde(default, skip_serializing_if = "Option::is_none")]
2357 pub effort: Option<String>,
2358
2359 #[serde(default, skip_serializing_if = "Option::is_none")]
2364 pub cloud_session: Option<Value>,
2365
2366 #[serde(default, skip_serializing_if = "Option::is_none")]
2370 pub footer_indicator: Option<FooterIndicator>,
2371
2372 #[serde(default, skip_serializing_if = "Option::is_none")]
2376 pub worker_epoch: Option<u64>,
2377
2378 #[serde(default, skip_serializing_if = "Option::is_none")]
2382 pub powershell_path: Option<Option<String>>,
2383
2384 #[serde(default, skip_serializing_if = "Option::is_none")]
2388 pub startup_timing: Option<serde_json::Map<String, Value>>,
2389
2390 #[serde(default, skip_serializing_if = "Option::is_none")]
2400 pub per_turn_effort_active: Option<bool>,
2401
2402 #[serde(default, skip_serializing_if = "Option::is_none")]
2409 pub view_mode: Option<ViewMode>,
2410}
2411
2412#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2416pub struct FooterIndicator {
2417 pub text: String,
2420}
2421
2422#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2425pub enum ViewMode {
2426 Focus,
2429 Default,
2431 Unknown(String),
2433}
2434
2435impl ViewMode {
2436 pub fn as_str(&self) -> &str {
2437 match self {
2438 Self::Focus => "focus",
2439 Self::Default => "default",
2440 Self::Unknown(s) => s.as_str(),
2441 }
2442 }
2443}
2444
2445impl fmt::Display for ViewMode {
2446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2447 f.write_str(self.as_str())
2448 }
2449}
2450
2451impl From<&str> for ViewMode {
2452 fn from(s: &str) -> Self {
2453 match s {
2454 "focus" => Self::Focus,
2455 "default" => Self::Default,
2456 other => Self::Unknown(other.to_string()),
2457 }
2458 }
2459}
2460
2461impl Serialize for ViewMode {
2462 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2463 serializer.serialize_str(self.as_str())
2464 }
2465}
2466
2467impl<'de> Deserialize<'de> for ViewMode {
2468 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2469 let s = String::deserialize(deserializer)?;
2470 Ok(Self::from(s.as_str()))
2471 }
2472}
2473
2474#[derive(Debug, Clone, Serialize, Deserialize)]
2476pub struct StatusMessage {
2477 pub session_id: String,
2479 pub status: Option<StatusMessageStatus>,
2481 #[serde(skip_serializing_if = "Option::is_none")]
2483 pub uuid: Option<String>,
2484 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2486 pub permission_mode: Option<InitPermissionMode>,
2487 #[serde(skip_serializing_if = "Option::is_none")]
2488 pub compact_result: Option<String>,
2489 #[serde(skip_serializing_if = "Option::is_none")]
2490 pub compact_error: Option<String>,
2491}
2492
2493#[derive(Debug, Clone, Serialize, Deserialize)]
2495pub struct CompactBoundaryMessage {
2496 pub session_id: String,
2498 pub compact_metadata: CompactMetadata,
2500 #[serde(
2504 default,
2505 skip_serializing_if = "Option::is_none",
2506 alias = "content",
2507 alias = "text"
2508 )]
2509 pub summary: Option<String>,
2510 #[serde(
2514 default,
2515 skip_serializing_if = "Option::is_none",
2516 alias = "message_count"
2517 )]
2518 pub leaf_message_count: Option<u32>,
2519 #[serde(default, skip_serializing_if = "Option::is_none")]
2521 pub duration_ms: Option<u64>,
2522 #[serde(skip_serializing_if = "Option::is_none")]
2524 pub uuid: Option<String>,
2525 #[serde(skip_serializing_if = "Option::is_none")]
2527 pub logical_parent_uuid: Option<Option<String>>,
2528 #[serde(default, skip_serializing_if = "Option::is_none")]
2532 pub historical: Option<bool>,
2533}
2534
2535#[derive(Debug, Clone, Serialize, Deserialize)]
2537pub struct CompactMetadata {
2538 pub pre_tokens: u64,
2540 pub trigger: CompactionTrigger,
2542 #[serde(default, skip_serializing_if = "Option::is_none")]
2543 pub post_tokens: Option<u64>,
2544 #[serde(default, skip_serializing_if = "Option::is_none")]
2545 pub cumulative_dropped_tokens: Option<u64>,
2546 #[serde(default, skip_serializing_if = "Option::is_none")]
2547 pub duration_ms: Option<u64>,
2548 #[serde(default, skip_serializing_if = "Option::is_none")]
2549 pub user_context: Option<String>,
2550 #[serde(default, skip_serializing_if = "Option::is_none")]
2551 pub messages_summarized: Option<u64>,
2552 #[serde(default, skip_serializing_if = "Option::is_none")]
2553 pub precomputed: Option<bool>,
2554 #[serde(default, skip_serializing_if = "Option::is_none")]
2555 pub pre_compact_discovered_tools: Option<Vec<String>>,
2556 #[serde(default, skip_serializing_if = "Option::is_none")]
2557 pub preserved_segment: Option<PreservedSegment>,
2558 #[serde(default, skip_serializing_if = "Option::is_none")]
2559 pub preserved_messages: Option<PreservedMessages>,
2560}
2561
2562#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2563pub struct PreservedSegment {
2564 pub head_uuid: String,
2565 pub anchor_uuid: String,
2566 pub tail_uuid: String,
2567}
2568
2569#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2570pub struct PreservedMessages {
2571 pub anchor_uuid: String,
2572 pub uuids: Vec<String>,
2573 #[serde(default, skip_serializing_if = "Option::is_none")]
2574 pub all_uuids: Option<Vec<String>>,
2575}
2576
2577#[derive(Debug, Clone, Serialize, Deserialize)]
2583pub struct TaskUsage {
2584 pub duration_ms: u64,
2586 pub tool_uses: u64,
2588 pub total_tokens: u64,
2590}
2591
2592#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2594pub enum TaskType {
2595 LocalAgent,
2597 LocalBash,
2599 LocalWorkflow,
2601 Unknown(String),
2603}
2604
2605impl TaskType {
2606 pub fn as_str(&self) -> &str {
2607 match self {
2608 Self::LocalAgent => "local_agent",
2609 Self::LocalBash => "local_bash",
2610 Self::LocalWorkflow => "local_workflow",
2611 Self::Unknown(s) => s.as_str(),
2612 }
2613 }
2614}
2615
2616impl fmt::Display for TaskType {
2617 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2618 f.write_str(self.as_str())
2619 }
2620}
2621
2622impl From<&str> for TaskType {
2623 fn from(s: &str) -> Self {
2624 match s {
2625 "local_agent" => Self::LocalAgent,
2626 "local_bash" => Self::LocalBash,
2627 "local_workflow" => Self::LocalWorkflow,
2628 other => Self::Unknown(other.to_string()),
2629 }
2630 }
2631}
2632
2633impl Serialize for TaskType {
2634 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2635 serializer.serialize_str(self.as_str())
2636 }
2637}
2638
2639impl<'de> Deserialize<'de> for TaskType {
2640 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2641 let s = String::deserialize(deserializer)?;
2642 Ok(Self::from(s.as_str()))
2643 }
2644}
2645
2646#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2648pub enum TaskStatus {
2649 Pending,
2650 Running,
2651 Completed,
2652 Failed,
2653 Killed,
2654 Paused,
2655 Stopped,
2656 Unknown(String),
2657}
2658
2659impl TaskStatus {
2660 pub fn as_str(&self) -> &str {
2661 match self {
2662 Self::Pending => "pending",
2663 Self::Running => "running",
2664 Self::Completed => "completed",
2665 Self::Failed => "failed",
2666 Self::Killed => "killed",
2667 Self::Paused => "paused",
2668 Self::Stopped => "stopped",
2669 Self::Unknown(s) => s.as_str(),
2670 }
2671 }
2672}
2673
2674impl fmt::Display for TaskStatus {
2675 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2676 f.write_str(self.as_str())
2677 }
2678}
2679
2680impl From<&str> for TaskStatus {
2681 fn from(s: &str) -> Self {
2682 match s {
2683 "pending" => Self::Pending,
2684 "running" => Self::Running,
2685 "completed" => Self::Completed,
2686 "failed" => Self::Failed,
2687 "killed" => Self::Killed,
2688 "paused" => Self::Paused,
2689 "stopped" => Self::Stopped,
2690 other => Self::Unknown(other.to_string()),
2691 }
2692 }
2693}
2694
2695impl Serialize for TaskStatus {
2696 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2697 serializer.serialize_str(self.as_str())
2698 }
2699}
2700
2701impl<'de> Deserialize<'de> for TaskStatus {
2702 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2703 let s = String::deserialize(deserializer)?;
2704 Ok(Self::from(s.as_str()))
2705 }
2706}
2707
2708#[derive(Debug, Clone, Serialize, Deserialize)]
2710pub struct TaskStartedMessage {
2711 pub session_id: String,
2712 pub task_id: String,
2713 #[serde(default, skip_serializing_if = "Option::is_none")]
2714 pub task_type: Option<TaskType>,
2715 #[serde(default, skip_serializing_if = "Option::is_none")]
2716 pub tool_use_id: Option<String>,
2717 pub description: String,
2718 #[serde(default, skip_serializing_if = "Option::is_none")]
2721 pub subagent_type: Option<String>,
2722 #[serde(default, skip_serializing_if = "Option::is_none")]
2728 pub is_backgrounded: Option<bool>,
2729 #[serde(default, skip_serializing_if = "Option::is_none")]
2733 pub spawn_depth: Option<u32>,
2734 #[serde(default, skip_serializing_if = "Option::is_none")]
2736 pub prompt: Option<String>,
2737 #[serde(default, skip_serializing_if = "Option::is_none")]
2738 pub workflow_name: Option<String>,
2739 #[serde(default, skip_serializing_if = "Option::is_none")]
2740 pub skip_transcript: Option<bool>,
2741 #[serde(default, skip_serializing_if = "Option::is_none")]
2746 pub ambient: Option<bool>,
2747 pub uuid: String,
2748}
2749
2750#[derive(Debug, Clone, Serialize, Deserialize)]
2754pub struct TaskUpdatedMessage {
2755 pub session_id: String,
2756 pub task_id: String,
2757 pub patch: TaskPatch,
2758 pub uuid: String,
2759}
2760
2761#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2764pub struct TaskPatch {
2765 #[serde(default, skip_serializing_if = "Option::is_none")]
2766 pub status: Option<TaskStatus>,
2767 #[serde(default, skip_serializing_if = "Option::is_none")]
2770 pub end_time: Option<u64>,
2771 #[serde(default, skip_serializing_if = "Option::is_none")]
2772 pub description: Option<String>,
2773 #[serde(default, skip_serializing_if = "Option::is_none")]
2774 pub total_paused_ms: Option<u64>,
2775 #[serde(default, skip_serializing_if = "Option::is_none")]
2776 pub error: Option<String>,
2777 #[serde(default, skip_serializing_if = "Option::is_none")]
2778 pub is_backgrounded: Option<bool>,
2779}
2780
2781#[derive(Debug, Clone, Serialize, Deserialize)]
2784pub struct ThinkingTokensMessage {
2785 pub session_id: String,
2786 pub estimated_tokens: u64,
2788 pub estimated_tokens_delta: u64,
2790 #[serde(default, skip_serializing_if = "Option::is_none")]
2797 pub user_message_uuid: Option<String>,
2798 pub uuid: String,
2799}
2800
2801#[derive(Debug, Clone, Serialize, Deserialize)]
2804pub struct TaskProgressMessage {
2805 pub session_id: String,
2806 pub task_id: String,
2807 #[serde(default, skip_serializing_if = "Option::is_none")]
2808 pub tool_use_id: Option<String>,
2809 pub description: String,
2810 #[serde(default, skip_serializing_if = "Option::is_none")]
2811 pub last_tool_name: Option<String>,
2812 pub usage: TaskUsage,
2813 #[serde(default, skip_serializing_if = "Option::is_none")]
2815 pub subagent_type: Option<String>,
2816 #[serde(default, skip_serializing_if = "Option::is_none")]
2817 pub summary: Option<String>,
2818 pub uuid: String,
2819}
2820
2821#[derive(Debug, Clone, Serialize, Deserialize)]
2824pub struct TaskNotificationMessage {
2825 pub session_id: String,
2826 pub task_id: String,
2827 pub status: TaskStatus,
2828 #[serde(default, skip_serializing_if = "Option::is_none")]
2831 pub reason: Option<TaskEndReason>,
2832 pub summary: String,
2833 pub output_file: Option<String>,
2834 #[serde(skip_serializing_if = "Option::is_none")]
2835 pub tool_use_id: Option<String>,
2836 #[serde(skip_serializing_if = "Option::is_none")]
2837 pub usage: Option<TaskUsage>,
2838 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2844 pub resource_links: Vec<ResourceLink>,
2845 #[serde(default, skip_serializing_if = "Option::is_none")]
2846 pub skip_transcript: Option<bool>,
2847 #[serde(default, skip_serializing_if = "Option::is_none")]
2850 pub ambient: Option<bool>,
2851 #[serde(skip_serializing_if = "Option::is_none")]
2852 pub uuid: Option<String>,
2853}
2854
2855#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2857pub enum AssistantErrorKind {
2858 AuthenticationFailed,
2859 OauthOrgNotAllowed,
2860 AccountOnHold,
2861 BillingError,
2862 RateLimit,
2863 Overloaded,
2864 InvalidRequest,
2865 ModelNotFound,
2866 ServerError,
2867 UnknownError,
2868 MaxOutputTokens,
2869 Unknown(String),
2870}
2871
2872impl AssistantErrorKind {
2873 pub fn as_str(&self) -> &str {
2874 match self {
2875 Self::AuthenticationFailed => "authentication_failed",
2876 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
2877 Self::AccountOnHold => "account_on_hold",
2878 Self::BillingError => "billing_error",
2879 Self::RateLimit => "rate_limit",
2880 Self::Overloaded => "overloaded",
2881 Self::InvalidRequest => "invalid_request",
2882 Self::ModelNotFound => "model_not_found",
2883 Self::ServerError => "server_error",
2884 Self::UnknownError => "unknown",
2885 Self::MaxOutputTokens => "max_output_tokens",
2886 Self::Unknown(s) => s.as_str(),
2887 }
2888 }
2889}
2890
2891impl fmt::Display for AssistantErrorKind {
2892 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2893 f.write_str(self.as_str())
2894 }
2895}
2896
2897impl From<&str> for AssistantErrorKind {
2898 fn from(s: &str) -> Self {
2899 match s {
2900 "authentication_failed" => Self::AuthenticationFailed,
2901 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
2902 "account_on_hold" => Self::AccountOnHold,
2903 "billing_error" => Self::BillingError,
2904 "rate_limit" => Self::RateLimit,
2905 "overloaded" => Self::Overloaded,
2906 "invalid_request" => Self::InvalidRequest,
2907 "model_not_found" => Self::ModelNotFound,
2908 "server_error" => Self::ServerError,
2909 "unknown" => Self::UnknownError,
2910 "max_output_tokens" => Self::MaxOutputTokens,
2911 other => Self::Unknown(other.to_string()),
2912 }
2913 }
2914}
2915
2916impl Serialize for AssistantErrorKind {
2917 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2918 serializer.serialize_str(self.as_str())
2919 }
2920}
2921
2922impl<'de> Deserialize<'de> for AssistantErrorKind {
2923 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2924 let s = String::deserialize(deserializer)?;
2925 Ok(Self::from(s.as_str()))
2926 }
2927}
2928
2929#[derive(Debug, Clone, Serialize, Deserialize)]
2936pub struct CodeChangePublishedMessage {
2937 pub provider: String,
2941 pub url: String,
2943 pub repo: String,
2946 pub identifier: String,
2948 #[serde(default, skip_serializing_if = "Option::is_none")]
2957 pub action: Option<String>,
2958 #[serde(default, skip_serializing_if = "Option::is_none")]
2966 pub branch: Option<String>,
2967 pub uuid: String,
2968 pub session_id: String,
2969}
2970
2971#[derive(Debug, Clone, Serialize, Deserialize)]
2976pub struct VcsStateChangedMessage {
2977 pub kind: VcsMutationKind,
2980 pub cwd: String,
2983 #[serde(default, skip_serializing_if = "Option::is_none")]
2988 pub branch: Option<String>,
2989 pub uuid: String,
2990 pub session_id: String,
2991}
2992
2993#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2995pub enum VcsMutationKind {
2996 Commit,
2997 Push,
2998 Merge,
2999 Rebase,
3000 Unknown(String),
3002}
3003
3004impl VcsMutationKind {
3005 pub fn as_str(&self) -> &str {
3006 match self {
3007 Self::Commit => "commit",
3008 Self::Push => "push",
3009 Self::Merge => "merge",
3010 Self::Rebase => "rebase",
3011 Self::Unknown(s) => s.as_str(),
3012 }
3013 }
3014}
3015
3016impl fmt::Display for VcsMutationKind {
3017 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3018 f.write_str(self.as_str())
3019 }
3020}
3021
3022impl From<&str> for VcsMutationKind {
3023 fn from(s: &str) -> Self {
3024 match s {
3025 "commit" => Self::Commit,
3026 "push" => Self::Push,
3027 "merge" => Self::Merge,
3028 "rebase" => Self::Rebase,
3029 other => Self::Unknown(other.to_string()),
3030 }
3031 }
3032}
3033
3034impl Serialize for VcsMutationKind {
3035 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3036 serializer.serialize_str(self.as_str())
3037 }
3038}
3039
3040impl<'de> Deserialize<'de> for VcsMutationKind {
3041 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3042 let s = String::deserialize(deserializer)?;
3043 Ok(Self::from(s.as_str()))
3044 }
3045}
3046
3047#[derive(Debug, Clone, Serialize, Deserialize)]
3049pub struct FeedbackDraftQueuedMessage {
3050 pub draft_id: String,
3051 pub draft_type: String,
3052 pub title: String,
3053 pub details_preview: String,
3054 #[serde(default, skip_serializing_if = "Option::is_none")]
3055 pub uuid: Option<String>,
3056 #[serde(default, skip_serializing_if = "Option::is_none")]
3057 pub session_id: Option<String>,
3058 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3059 pub extra: serde_json::Map<String, Value>,
3060}
3061
3062#[derive(Debug, Clone, Serialize, Deserialize)]
3070pub struct CloudSessionDeltaMessage {
3071 pub seq: u64,
3075 pub changed: Vec<String>,
3079 pub cloud_session: Value,
3083 pub uuid: String,
3084 pub session_id: String,
3085 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3086 pub extra: serde_json::Map<String, Value>,
3087}
3088
3089#[derive(Debug, Clone, Serialize, Deserialize)]
3105pub struct DevIntentMessage {
3106 pub kind: DevIntentKind,
3108 #[serde(default, skip_serializing_if = "Option::is_none")]
3114 pub trigger: Option<DevIntentTrigger>,
3115 pub uuid: String,
3116 pub session_id: String,
3117 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3118 pub extra: serde_json::Map<String, Value>,
3119}
3120
3121#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3125pub enum DevIntentKind {
3126 IosApp,
3132 AndroidApp,
3139 Unknown(String),
3141}
3142
3143impl DevIntentKind {
3144 pub fn as_str(&self) -> &str {
3145 match self {
3146 Self::IosApp => "ios_app",
3147 Self::AndroidApp => "android_app",
3148 Self::Unknown(s) => s.as_str(),
3149 }
3150 }
3151}
3152
3153impl fmt::Display for DevIntentKind {
3154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3155 f.write_str(self.as_str())
3156 }
3157}
3158
3159impl From<&str> for DevIntentKind {
3160 fn from(s: &str) -> Self {
3161 match s {
3162 "ios_app" => Self::IosApp,
3163 "android_app" => Self::AndroidApp,
3164 other => Self::Unknown(other.to_string()),
3165 }
3166 }
3167}
3168
3169impl Serialize for DevIntentKind {
3170 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3171 serializer.serialize_str(self.as_str())
3172 }
3173}
3174
3175impl<'de> Deserialize<'de> for DevIntentKind {
3176 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3177 let s = String::deserialize(deserializer)?;
3178 Ok(Self::from(s.as_str()))
3179 }
3180}
3181
3182#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3186pub enum DevIntentTrigger {
3187 SwiftEdit,
3189 UikitImport,
3191 XcodeProject,
3194 IosCommand,
3197 KotlinEdit,
3199 JavaEdit,
3201 AndroidImport,
3203 AndroidManifest,
3205 GradlePlugin,
3207 AndroidCommand,
3210 ProjectScan,
3213 Unknown(String),
3215}
3216
3217impl DevIntentTrigger {
3218 pub fn as_str(&self) -> &str {
3219 match self {
3220 Self::SwiftEdit => "swift_edit",
3221 Self::UikitImport => "uikit_import",
3222 Self::XcodeProject => "xcode_project",
3223 Self::IosCommand => "ios_command",
3224 Self::KotlinEdit => "kotlin_edit",
3225 Self::JavaEdit => "java_edit",
3226 Self::AndroidImport => "android_import",
3227 Self::AndroidManifest => "android_manifest",
3228 Self::GradlePlugin => "gradle_plugin",
3229 Self::AndroidCommand => "android_command",
3230 Self::ProjectScan => "project_scan",
3231 Self::Unknown(s) => s.as_str(),
3232 }
3233 }
3234}
3235
3236impl fmt::Display for DevIntentTrigger {
3237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3238 f.write_str(self.as_str())
3239 }
3240}
3241
3242impl From<&str> for DevIntentTrigger {
3243 fn from(s: &str) -> Self {
3244 match s {
3245 "swift_edit" => Self::SwiftEdit,
3246 "uikit_import" => Self::UikitImport,
3247 "xcode_project" => Self::XcodeProject,
3248 "ios_command" => Self::IosCommand,
3249 "kotlin_edit" => Self::KotlinEdit,
3250 "java_edit" => Self::JavaEdit,
3251 "android_import" => Self::AndroidImport,
3252 "android_manifest" => Self::AndroidManifest,
3253 "gradle_plugin" => Self::GradlePlugin,
3254 "android_command" => Self::AndroidCommand,
3255 "project_scan" => Self::ProjectScan,
3256 other => Self::Unknown(other.to_string()),
3257 }
3258 }
3259}
3260
3261impl Serialize for DevIntentTrigger {
3262 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3263 serializer.serialize_str(self.as_str())
3264 }
3265}
3266
3267impl<'de> Deserialize<'de> for DevIntentTrigger {
3268 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3269 let s = String::deserialize(deserializer)?;
3270 Ok(Self::from(s.as_str()))
3271 }
3272}
3273
3274#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3278pub enum TaskEndReason {
3279 WorkerRestart,
3282 Unknown(String),
3284}
3285
3286impl TaskEndReason {
3287 pub fn as_str(&self) -> &str {
3288 match self {
3289 Self::WorkerRestart => "worker_restart",
3290 Self::Unknown(s) => s.as_str(),
3291 }
3292 }
3293}
3294
3295impl fmt::Display for TaskEndReason {
3296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3297 f.write_str(self.as_str())
3298 }
3299}
3300
3301impl From<&str> for TaskEndReason {
3302 fn from(s: &str) -> Self {
3303 match s {
3304 "worker_restart" => Self::WorkerRestart,
3305 other => Self::Unknown(other.to_string()),
3306 }
3307 }
3308}
3309
3310impl Serialize for TaskEndReason {
3311 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3312 serializer.serialize_str(self.as_str())
3313 }
3314}
3315
3316impl<'de> Deserialize<'de> for TaskEndReason {
3317 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3318 let s = String::deserialize(deserializer)?;
3319 Ok(Self::from(s.as_str()))
3320 }
3321}
3322
3323#[derive(Debug, Clone, Serialize, Deserialize)]
3332pub struct TurnHandoffAvailableMessage {
3333 pub v: u64,
3335 pub tools: Vec<String>,
3337 pub worker_epoch: u64,
3339 #[serde(default, skip_serializing_if = "Option::is_none")]
3343 pub relay_marker: Option<bool>,
3344 #[serde(default, skip_serializing_if = "Option::is_none")]
3349 pub staged_files: Option<bool>,
3350 #[serde(default, skip_serializing_if = "Option::is_none")]
3357 pub no_query_first: Option<bool>,
3358 pub uuid: String,
3359 pub session_id: String,
3360 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3361 pub extra: serde_json::Map<String, Value>,
3362}
3363
3364#[derive(Debug, Clone, Serialize, Deserialize)]
3376pub struct TurnPreemptedMessage {
3377 pub reason: String,
3381 pub preempted_by_uuid: String,
3385 #[serde(default)]
3388 pub preempted_message_uuids: Vec<String>,
3389 pub uuid: String,
3390 pub session_id: String,
3391 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3392 pub extra: serde_json::Map<String, Value>,
3393}
3394
3395#[derive(Debug, Clone, Serialize, Deserialize)]
3403pub struct PeerMessageHoldMessage {
3404 pub state: PeerMessageHoldState,
3410 #[serde(default, skip_serializing_if = "Option::is_none")]
3417 pub message_uuid: Option<String>,
3418 pub lane: PeerMessageLane,
3420 pub from: String,
3425 #[serde(default, skip_serializing_if = "Option::is_none")]
3428 pub from_name: Option<String>,
3429 #[serde(default, skip_serializing_if = "Option::is_none")]
3431 pub cause: Option<PeerMessageHoldCause>,
3432 #[serde(default, skip_serializing_if = "Option::is_none")]
3434 pub outcome: Option<PeerMessageHoldOutcome>,
3435 pub uuid: String,
3436 pub session_id: String,
3437 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3438 pub extra: serde_json::Map<String, Value>,
3439}
3440
3441#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3443pub enum PeerMessageHoldState {
3444 Held,
3446 Released,
3448 Dropped,
3450 Unknown(String),
3452}
3453
3454impl PeerMessageHoldState {
3455 pub fn as_str(&self) -> &str {
3456 match self {
3457 Self::Held => "held",
3458 Self::Released => "released",
3459 Self::Dropped => "dropped",
3460 Self::Unknown(s) => s.as_str(),
3461 }
3462 }
3463}
3464
3465impl fmt::Display for PeerMessageHoldState {
3466 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3467 f.write_str(self.as_str())
3468 }
3469}
3470
3471impl From<&str> for PeerMessageHoldState {
3472 fn from(s: &str) -> Self {
3473 match s {
3474 "held" => Self::Held,
3475 "released" => Self::Released,
3476 "dropped" => Self::Dropped,
3477 other => Self::Unknown(other.to_string()),
3478 }
3479 }
3480}
3481
3482impl Serialize for PeerMessageHoldState {
3483 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3484 serializer.serialize_str(self.as_str())
3485 }
3486}
3487
3488impl<'de> Deserialize<'de> for PeerMessageHoldState {
3489 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3490 let s = String::deserialize(deserializer)?;
3491 Ok(Self::from(s.as_str()))
3492 }
3493}
3494
3495#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3497pub enum PeerMessageLane {
3498 Bridge,
3500 Stdin,
3502 Socket,
3504 Unknown(String),
3506}
3507
3508impl PeerMessageLane {
3509 pub fn as_str(&self) -> &str {
3510 match self {
3511 Self::Bridge => "bridge",
3512 Self::Stdin => "stdin",
3513 Self::Socket => "socket",
3514 Self::Unknown(s) => s.as_str(),
3515 }
3516 }
3517}
3518
3519impl fmt::Display for PeerMessageLane {
3520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3521 f.write_str(self.as_str())
3522 }
3523}
3524
3525impl From<&str> for PeerMessageLane {
3526 fn from(s: &str) -> Self {
3527 match s {
3528 "bridge" => Self::Bridge,
3529 "stdin" => Self::Stdin,
3530 "socket" => Self::Socket,
3531 other => Self::Unknown(other.to_string()),
3532 }
3533 }
3534}
3535
3536impl Serialize for PeerMessageLane {
3537 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3538 serializer.serialize_str(self.as_str())
3539 }
3540}
3541
3542impl<'de> Deserialize<'de> for PeerMessageLane {
3543 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3544 let s = String::deserialize(deserializer)?;
3545 Ok(Self::from(s.as_str()))
3546 }
3547}
3548
3549#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3556pub enum PeerMessageHoldCause {
3557 ExplicitSetting,
3558 ManagedSetting,
3559 RepoSetting,
3560 InvalidSetting,
3561 BypassDefault,
3562 ModeUnknown,
3563 ModeMismatch,
3564 NoModeAsserted,
3565 Unknown(String),
3567}
3568
3569impl PeerMessageHoldCause {
3570 pub fn as_str(&self) -> &str {
3571 match self {
3572 Self::ExplicitSetting => "explicit-setting",
3573 Self::ManagedSetting => "managed-setting",
3574 Self::RepoSetting => "repo-setting",
3575 Self::InvalidSetting => "invalid-setting",
3576 Self::BypassDefault => "bypass-default",
3577 Self::ModeUnknown => "mode-unknown",
3578 Self::ModeMismatch => "mode-mismatch",
3579 Self::NoModeAsserted => "no-mode-asserted",
3580 Self::Unknown(s) => s.as_str(),
3581 }
3582 }
3583}
3584
3585impl fmt::Display for PeerMessageHoldCause {
3586 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3587 f.write_str(self.as_str())
3588 }
3589}
3590
3591impl From<&str> for PeerMessageHoldCause {
3592 fn from(s: &str) -> Self {
3593 match s {
3594 "explicit-setting" => Self::ExplicitSetting,
3595 "managed-setting" => Self::ManagedSetting,
3596 "repo-setting" => Self::RepoSetting,
3597 "invalid-setting" => Self::InvalidSetting,
3598 "bypass-default" => Self::BypassDefault,
3599 "mode-unknown" => Self::ModeUnknown,
3600 "mode-mismatch" => Self::ModeMismatch,
3601 "no-mode-asserted" => Self::NoModeAsserted,
3602 other => Self::Unknown(other.to_string()),
3603 }
3604 }
3605}
3606
3607impl Serialize for PeerMessageHoldCause {
3608 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3609 serializer.serialize_str(self.as_str())
3610 }
3611}
3612
3613impl<'de> Deserialize<'de> for PeerMessageHoldCause {
3614 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3615 let s = String::deserialize(deserializer)?;
3616 Ok(Self::from(s.as_str()))
3617 }
3618}
3619
3620#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3623pub enum PeerMessageHoldOutcome {
3624 Expired,
3628 Refused,
3630 Dropped,
3633 Discarded,
3635 Unknown(String),
3637}
3638
3639impl PeerMessageHoldOutcome {
3640 pub fn as_str(&self) -> &str {
3641 match self {
3642 Self::Expired => "expired",
3643 Self::Refused => "refused",
3644 Self::Dropped => "dropped",
3645 Self::Discarded => "discarded",
3646 Self::Unknown(s) => s.as_str(),
3647 }
3648 }
3649}
3650
3651impl fmt::Display for PeerMessageHoldOutcome {
3652 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3653 f.write_str(self.as_str())
3654 }
3655}
3656
3657impl From<&str> for PeerMessageHoldOutcome {
3658 fn from(s: &str) -> Self {
3659 match s {
3660 "expired" => Self::Expired,
3661 "refused" => Self::Refused,
3662 "dropped" => Self::Dropped,
3663 "discarded" => Self::Discarded,
3664 other => Self::Unknown(other.to_string()),
3665 }
3666 }
3667}
3668
3669impl Serialize for PeerMessageHoldOutcome {
3670 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3671 serializer.serialize_str(self.as_str())
3672 }
3673}
3674
3675impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
3676 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3677 let s = String::deserialize(deserializer)?;
3678 Ok(Self::from(s.as_str()))
3679 }
3680}
3681
3682#[derive(Debug, Clone, Serialize, Deserialize)]
3693pub struct PerTurnEffortChangedMessage {
3694 pub per_turn_effort_active: bool,
3696 pub uuid: String,
3697 pub session_id: String,
3698}
3699
3700#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3703pub struct LocalCommandRun {
3704 pub command: String,
3706 pub args: String,
3709}
3710
3711#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3715pub struct LocalCommandOutcome {
3716 pub kind: LocalCommandOutcomeKind,
3717 #[serde(default, skip_serializing_if = "Option::is_none")]
3720 pub suggestion: Option<String>,
3721}
3722
3723#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3728pub enum LocalCommandOutcomeKind {
3729 UnavailableHeadless,
3731 Unknown,
3734 Failed,
3736 RestartRequired,
3738 Other(String),
3740}
3741
3742impl LocalCommandOutcomeKind {
3743 pub fn as_str(&self) -> &str {
3744 match self {
3745 Self::UnavailableHeadless => "unavailable_headless",
3746 Self::Unknown => "unknown",
3747 Self::Failed => "failed",
3748 Self::RestartRequired => "restart_required",
3749 Self::Other(s) => s.as_str(),
3750 }
3751 }
3752}
3753
3754impl fmt::Display for LocalCommandOutcomeKind {
3755 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3756 f.write_str(self.as_str())
3757 }
3758}
3759
3760impl From<&str> for LocalCommandOutcomeKind {
3761 fn from(s: &str) -> Self {
3762 match s {
3763 "unavailable_headless" => Self::UnavailableHeadless,
3764 "unknown" => Self::Unknown,
3765 "failed" => Self::Failed,
3766 "restart_required" => Self::RestartRequired,
3767 other => Self::Other(other.to_string()),
3768 }
3769 }
3770}
3771
3772impl Serialize for LocalCommandOutcomeKind {
3773 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3774 serializer.serialize_str(self.as_str())
3775 }
3776}
3777
3778impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
3779 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3780 let s = String::deserialize(deserializer)?;
3781 Ok(Self::from(s.as_str()))
3782 }
3783}
3784
3785#[derive(Debug, Clone, Serialize, Deserialize)]
3791pub struct UsageReport {
3792 pub session: super::control::UsageSession,
3794 pub rate_limits: Option<UsageReportRateLimits>,
3798 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3799 pub extra: serde_json::Map<String, Value>,
3800}
3801
3802#[derive(Debug, Clone, Serialize, Deserialize)]
3804pub struct UsageReportRateLimits {
3805 pub limits: Option<Vec<UsageReportLimit>>,
3813 #[serde(default, skip_serializing_if = "Option::is_none")]
3816 pub extra_usage: Option<UsageReportExtraUsage>,
3817 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3818 pub extra: serde_json::Map<String, Value>,
3819}
3820
3821#[derive(Debug, Clone, Serialize, Deserialize)]
3823pub struct UsageReportLimit {
3824 pub kind: String,
3827 pub group: String,
3830 pub percent: f64,
3832 pub resets_at: Option<String>,
3834 #[serde(default, skip_serializing_if = "Option::is_none")]
3837 pub scope: Option<UsageReportScope>,
3838 #[serde(default, skip_serializing_if = "Option::is_none")]
3842 pub severity: Option<String>,
3843 #[serde(default, skip_serializing_if = "Option::is_none")]
3845 pub is_active: Option<bool>,
3846 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3847 pub extra: serde_json::Map<String, Value>,
3848}
3849
3850#[derive(Debug, Clone, Serialize, Deserialize)]
3852pub struct UsageReportScope {
3853 #[serde(default, skip_serializing_if = "Option::is_none")]
3854 pub model: Option<UsageReportScopeLabel>,
3855 #[serde(default, skip_serializing_if = "Option::is_none")]
3856 pub surface: Option<UsageReportScopeLabel>,
3857 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3858 pub extra: serde_json::Map<String, Value>,
3859}
3860
3861#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3863pub struct UsageReportScopeLabel {
3864 pub display_name: String,
3865}
3866
3867#[derive(Debug, Clone, Serialize, Deserialize)]
3871pub struct UsageReportExtraUsage {
3872 pub is_enabled: bool,
3874 pub monthly_limit: Option<f64>,
3875 pub used_credits: Option<f64>,
3876 pub utilization: Option<f64>,
3877 #[serde(default, skip_serializing_if = "Option::is_none")]
3878 pub currency: Option<String>,
3879 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3880 pub extra: serde_json::Map<String, Value>,
3881}
3882
3883#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3886pub struct BatchToolUse {
3887 pub id: String,
3888 pub name: String,
3889}
3890
3891#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3896pub struct ContextUsage {
3897 pub model: String,
3899 pub total_tokens: u64,
3902 pub raw_max_tokens: u64,
3905 pub percentage: u64,
3907 #[serde(default, skip_serializing_if = "Option::is_none")]
3909 pub over_limit: Option<ContextOverLimit>,
3910 #[serde(default)]
3912 pub categories: Vec<ContextCategory>,
3913 #[serde(default)]
3915 pub mcp_tools: Vec<ContextMcpTool>,
3916 #[serde(default)]
3918 pub memory_files: Vec<ContextMemoryFile>,
3919 #[serde(default)]
3921 pub agents: Vec<ContextAgent>,
3922 #[serde(default, skip_serializing_if = "Option::is_none")]
3924 pub skills: Option<Vec<ContextSkill>>,
3925}
3926
3927#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3929pub struct ContextOverLimit {
3930 pub tokens_over: u64,
3931 pub kind: String,
3934}
3935
3936#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3938pub struct ContextCategory {
3939 pub name: String,
3942 pub tokens: u64,
3943 pub kind: String,
3947}
3948
3949#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3951pub struct ContextMcpTool {
3952 pub name: String,
3954 pub server_name: String,
3955 pub tokens: u64,
3956}
3957
3958#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3960pub struct ContextMemoryFile {
3961 pub path: String,
3962 #[serde(rename = "type")]
3964 pub file_type: String,
3965 pub tokens: u64,
3966}
3967
3968#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3970pub struct ContextAgent {
3971 pub agent_type: String,
3972 pub source: String,
3974 pub tokens: u64,
3975}
3976
3977#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3979pub struct ContextSkill {
3980 pub name: String,
3981 pub source: String,
3983 #[serde(default, skip_serializing_if = "Option::is_none")]
3984 pub plugin_name: Option<String>,
3985 pub tokens: u64,
3986}
3987
3988#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
3990pub struct ToolUseMeta {
3991 pub id: String,
3992 pub display_name: String,
3993 #[serde(default, skip_serializing_if = "Option::is_none")]
3994 pub server_display_name: Option<String>,
3995 #[serde(default, skip_serializing_if = "Option::is_none")]
3996 pub icon_url: Option<String>,
3997}
3998
3999#[derive(Debug, Clone, Serialize, Deserialize)]
4001pub struct AssistantMessage {
4002 pub message: AssistantMessageContent,
4003 #[serde(alias = "sessionId")]
4004 pub session_id: String,
4005 #[serde(skip_serializing_if = "Option::is_none")]
4006 pub uuid: Option<String>,
4007 #[serde(skip_serializing_if = "Option::is_none")]
4008 pub parent_tool_use_id: Option<String>,
4009 #[serde(skip_serializing_if = "Option::is_none")]
4011 pub request_id: Option<String>,
4012 #[serde(default, skip_serializing_if = "Option::is_none")]
4021 pub user_message_uuid: Option<String>,
4022 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4028 pub user_message_uuids: Vec<String>,
4029 #[serde(skip_serializing_if = "Option::is_none")]
4032 pub subagent_type: Option<String>,
4033 #[serde(skip_serializing_if = "Option::is_none")]
4035 pub task_description: Option<String>,
4036 #[serde(skip_serializing_if = "Option::is_none")]
4037 pub error: Option<AssistantErrorKind>,
4038 #[serde(default, skip_serializing_if = "Option::is_none")]
4042 pub aborted: Option<bool>,
4043 #[serde(default, skip_serializing_if = "Option::is_none")]
4048 pub resumed_from_incomplete_thinking: Option<bool>,
4049 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4050 pub supersedes: Vec<String>,
4051 #[serde(skip_serializing_if = "Option::is_none")]
4052 pub timestamp: Option<String>,
4053 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4054 pub tool_use_meta: Vec<ToolUseMeta>,
4055 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4060 pub batch_tool_uses: Vec<BatchToolUse>,
4061 #[serde(default, skip_serializing_if = "Option::is_none")]
4067 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
4068 #[serde(default, skip_serializing_if = "Option::is_none")]
4074 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
4075 #[serde(default, skip_serializing_if = "Option::is_none")]
4079 pub historical: Option<bool>,
4080 #[serde(default, skip_serializing_if = "Option::is_none")]
4088 pub resume_reason: Option<String>,
4089 #[serde(default, skip_serializing_if = "Option::is_none")]
4095 pub local_command_source: Option<String>,
4096 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4105 pub narration_block_indexes: Vec<usize>,
4106 #[serde(default, skip_serializing_if = "Option::is_none")]
4110 pub narration_hint: Option<String>,
4111 #[serde(default, skip_serializing_if = "Option::is_none")]
4115 pub context_usage: Option<ContextUsage>,
4116 #[serde(default, skip_serializing_if = "Option::is_none")]
4124 pub usage_report: Option<Box<UsageReport>>,
4125 #[serde(default, skip_serializing_if = "Option::is_none")]
4132 pub local_command_run: Option<LocalCommandRun>,
4133 #[serde(default, skip_serializing_if = "Option::is_none")]
4139 pub local_command_outcome: Option<LocalCommandOutcome>,
4140 #[serde(default, skip_serializing_if = "Option::is_none")]
4141 pub is_meta: Option<bool>,
4142 #[serde(default, skip_serializing_if = "Option::is_none")]
4143 pub is_virtual: Option<bool>,
4144 #[serde(default, skip_serializing_if = "Option::is_none")]
4145 pub is_api_error_message: Option<bool>,
4146 #[serde(skip_serializing_if = "Option::is_none")]
4147 pub api_error_status: Option<u16>,
4148 #[serde(skip_serializing_if = "Option::is_none")]
4163 pub api_error: Option<String>,
4164 #[serde(default, skip_serializing_if = "Option::is_none")]
4171 pub api_error_code: Option<String>,
4172 #[serde(default, skip_serializing_if = "Option::is_none")]
4175 pub api_error_params: Option<ApiErrorParams>,
4176 #[serde(skip_serializing_if = "Option::is_none")]
4177 pub error_details: Option<String>,
4178 #[serde(skip_serializing_if = "Option::is_none")]
4179 pub advisor_model: Option<String>,
4180 #[serde(skip_serializing_if = "Option::is_none")]
4181 pub attribution_agent: Option<String>,
4182 #[serde(skip_serializing_if = "Option::is_none")]
4183 pub attribution_skill: Option<String>,
4184 #[serde(skip_serializing_if = "Option::is_none")]
4185 pub attribution_plugin: Option<String>,
4186 #[serde(skip_serializing_if = "Option::is_none")]
4187 pub attribution_mcp_server: Option<String>,
4188 #[serde(skip_serializing_if = "Option::is_none")]
4189 pub attribution_mcp_tool: Option<String>,
4190}
4191
4192#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
4196pub struct ApiErrorParams {
4197 #[serde(default, skip_serializing_if = "Option::is_none")]
4199 pub effort: Option<String>,
4200 #[serde(default, skip_serializing_if = "Option::is_none")]
4203 pub provider: Option<ApiErrorProvider>,
4204 #[serde(default, skip_serializing_if = "Option::is_none")]
4207 pub remedy: Option<ApiErrorRemedy>,
4208}
4209
4210#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4212pub enum ApiErrorProvider {
4213 Bedrock,
4214 AnthropicAws,
4215 Mantle,
4216 AnthropicGoogleCloud,
4217 Vertex,
4218 Foundry,
4219 Gateway,
4220 Unknown(String),
4222}
4223
4224impl ApiErrorProvider {
4225 pub fn as_str(&self) -> &str {
4226 match self {
4227 Self::Bedrock => "bedrock",
4228 Self::AnthropicAws => "anthropicAws",
4229 Self::Mantle => "mantle",
4230 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
4231 Self::Vertex => "vertex",
4232 Self::Foundry => "foundry",
4233 Self::Gateway => "gateway",
4234 Self::Unknown(s) => s.as_str(),
4235 }
4236 }
4237}
4238
4239impl fmt::Display for ApiErrorProvider {
4240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4241 f.write_str(self.as_str())
4242 }
4243}
4244
4245impl From<&str> for ApiErrorProvider {
4246 fn from(s: &str) -> Self {
4247 match s {
4248 "bedrock" => Self::Bedrock,
4249 "anthropicAws" => Self::AnthropicAws,
4250 "mantle" => Self::Mantle,
4251 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4252 "vertex" => Self::Vertex,
4253 "foundry" => Self::Foundry,
4254 "gateway" => Self::Gateway,
4255 other => Self::Unknown(other.to_string()),
4256 }
4257 }
4258}
4259
4260impl Serialize for ApiErrorProvider {
4261 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4262 serializer.serialize_str(self.as_str())
4263 }
4264}
4265
4266impl<'de> Deserialize<'de> for ApiErrorProvider {
4267 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4268 let s = String::deserialize(deserializer)?;
4269 Ok(Self::from(s.as_str()))
4270 }
4271}
4272
4273#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4275pub enum ApiErrorRemedy {
4276 RefreshCommand,
4279 RefreshCredentials,
4281 Adc,
4284 GatewayToken,
4287 HostManaged,
4289 ModelAccess,
4292 Unknown(String),
4294}
4295
4296impl ApiErrorRemedy {
4297 pub fn as_str(&self) -> &str {
4298 match self {
4299 Self::RefreshCommand => "refresh_command",
4300 Self::RefreshCredentials => "refresh_credentials",
4301 Self::Adc => "adc",
4302 Self::GatewayToken => "gateway_token",
4303 Self::HostManaged => "host_managed",
4304 Self::ModelAccess => "model_access",
4305 Self::Unknown(s) => s.as_str(),
4306 }
4307 }
4308}
4309
4310impl fmt::Display for ApiErrorRemedy {
4311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4312 f.write_str(self.as_str())
4313 }
4314}
4315
4316impl From<&str> for ApiErrorRemedy {
4317 fn from(s: &str) -> Self {
4318 match s {
4319 "refresh_command" => Self::RefreshCommand,
4320 "refresh_credentials" => Self::RefreshCredentials,
4321 "adc" => Self::Adc,
4322 "gateway_token" => Self::GatewayToken,
4323 "host_managed" => Self::HostManaged,
4324 "model_access" => Self::ModelAccess,
4325 other => Self::Unknown(other.to_string()),
4326 }
4327 }
4328}
4329
4330impl Serialize for ApiErrorRemedy {
4331 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4332 serializer.serialize_str(self.as_str())
4333 }
4334}
4335
4336impl<'de> Deserialize<'de> for ApiErrorRemedy {
4337 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4338 let s = String::deserialize(deserializer)?;
4339 Ok(Self::from(s.as_str()))
4340 }
4341}
4342
4343#[derive(Debug, Clone, Serialize, Deserialize)]
4345pub struct AssistantMessageContent {
4346 pub id: String,
4347 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
4349 pub message_type: Option<String>,
4350 pub role: MessageRole,
4351 pub model: String,
4352 pub content: Vec<ContentBlock>,
4353 #[serde(skip_serializing_if = "Option::is_none")]
4354 pub stop_reason: Option<StopReason>,
4355 #[serde(skip_serializing_if = "Option::is_none")]
4356 pub stop_sequence: Option<String>,
4357 #[serde(skip_serializing_if = "Option::is_none")]
4358 pub usage: Option<AssistantUsage>,
4359 #[serde(skip_serializing_if = "Option::is_none")]
4361 pub stop_details: Option<Value>,
4362 #[serde(skip_serializing_if = "Option::is_none")]
4364 pub context_management: Option<Value>,
4365}
4366
4367#[derive(Debug, Clone, Serialize, Deserialize)]
4369pub struct AssistantUsage {
4370 #[serde(default)]
4372 pub input_tokens: u32,
4373
4374 #[serde(default)]
4376 pub output_tokens: u32,
4377
4378 #[serde(default)]
4380 pub cache_creation_input_tokens: u32,
4381
4382 #[serde(default)]
4384 pub cache_read_input_tokens: u32,
4385
4386 #[serde(skip_serializing_if = "Option::is_none")]
4388 pub service_tier: Option<String>,
4389
4390 #[serde(skip_serializing_if = "Option::is_none")]
4392 pub cache_creation: Option<CacheCreationDetails>,
4393
4394 #[serde(skip_serializing_if = "Option::is_none")]
4396 pub inference_geo: Option<String>,
4397}
4398
4399#[derive(Debug, Clone, Serialize, Deserialize)]
4401pub struct CacheCreationDetails {
4402 #[serde(default)]
4404 pub ephemeral_1h_input_tokens: u32,
4405
4406 #[serde(default)]
4408 pub ephemeral_5m_input_tokens: u32,
4409}
4410
4411#[cfg(test)]
4412mod tests {
4413 use crate::io::ClaudeOutput;
4414
4415 #[test]
4416 fn test_subagent_usage_rollup_accumulates_task_results() {
4417 use super::SubagentUsageRollup;
4418
4419 let mut rollup = SubagentUsageRollup::default();
4420
4421 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}}"#;
4422 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
4423 assert!(rollup.observe(&output));
4424 assert_eq!(rollup.subagent_tokens, 10201);
4425 assert_eq!(rollup.agent_count, 1);
4426 assert_eq!(rollup.tool_uses, 3);
4427 assert_eq!(rollup.duration_ms, 1853);
4428
4429 assert!(!rollup.observe(&output));
4431 assert_eq!(rollup.agent_count, 1);
4432 assert_eq!(rollup.subagent_tokens, 10201);
4433
4434 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}}"#;
4436 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
4437 assert!(rollup.observe(&output));
4438 assert_eq!(rollup.agent_count, 2);
4439 assert_eq!(rollup.subagent_tokens, 10701);
4440 }
4441
4442 #[test]
4443 fn test_subagent_usage_rollup_ignores_non_task_results() {
4444 use super::SubagentUsageRollup;
4445
4446 let mut rollup = SubagentUsageRollup::default();
4447
4448 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}}"#;
4451 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
4452 assert!(!rollup.observe(&output));
4453
4454 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4456 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
4457 assert!(!rollup.observe(&output));
4458
4459 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4461 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
4462 assert!(!rollup.observe(&output));
4463
4464 assert_eq!(rollup, SubagentUsageRollup::default());
4465 }
4466
4467 #[test]
4468 fn test_subagent_usage_rollup_over_captured_session() {
4469 use super::SubagentUsageRollup;
4470
4471 let mut rollup = SubagentUsageRollup::default();
4472 let fixture =
4473 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
4474 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
4475 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
4476 rollup.observe(&output);
4477 }
4478 }
4479 assert_eq!(rollup.agent_count, 1);
4480 assert_eq!(rollup.subagent_tokens, 10201);
4481 }
4482
4483 #[test]
4484 fn test_system_message_init() {
4485 let json = r#"{
4486 "type": "system",
4487 "subtype": "init",
4488 "session_id": "test-session-123",
4489 "cwd": "/home/user/project",
4490 "model": "claude-sonnet-4",
4491 "tools": ["Bash", "Read", "Write"],
4492 "mcp_servers": [],
4493 "slash_commands": ["compact", "cost", "review"],
4494 "agents": ["Bash", "Explore", "Plan"],
4495 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
4496 "skills": [],
4497 "claude_code_version": "2.1.15",
4498 "apiKeySource": "none",
4499 "output_style": "default",
4500 "permissionMode": "default"
4501 }"#;
4502
4503 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4504 if let ClaudeOutput::System(sys) = output {
4505 assert!(sys.is_init());
4506 assert!(!sys.is_status());
4507 assert!(!sys.is_compact_boundary());
4508
4509 let init = sys.as_init().expect("Should parse as init");
4510 assert_eq!(init.session_id, "test-session-123");
4511 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
4512 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
4513 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
4514 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
4515 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
4516 assert_eq!(init.plugins.len(), 1);
4517 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4518 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4519 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
4520 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
4521 assert_eq!(
4522 init.permission_mode,
4523 Some(super::InitPermissionMode::Default)
4524 );
4525 } else {
4526 panic!("Expected System message");
4527 }
4528 }
4529
4530 #[test]
4531 fn test_system_message_init_from_real_capture() {
4532 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
4533 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4534 if let ClaudeOutput::System(sys) = output {
4535 let init = sys.as_init().expect("Should parse real init capture");
4536 assert_eq!(init.slash_commands.len(), 8);
4537 assert!(init.slash_commands.contains(&"compact".to_string()));
4538 assert!(init.slash_commands.contains(&"review".to_string()));
4539 assert_eq!(init.agents.len(), 5);
4540 assert!(init.agents.contains(&"Bash".to_string()));
4541 assert!(init.agents.contains(&"Explore".to_string()));
4542 assert_eq!(init.plugins.len(), 1);
4543 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4544 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4545 } else {
4546 panic!("Expected System message");
4547 }
4548 }
4549
4550 #[test]
4551 fn test_system_message_status() {
4552 let json = r#"{
4553 "type": "system",
4554 "subtype": "status",
4555 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4556 "status": "compacting",
4557 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
4558 }"#;
4559
4560 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4561 if let ClaudeOutput::System(sys) = output {
4562 assert!(sys.is_status());
4563 assert!(!sys.is_init());
4564
4565 let status = sys.as_status().expect("Should parse as status");
4566 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4567 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
4568 assert_eq!(
4569 status.uuid,
4570 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
4571 );
4572 } else {
4573 panic!("Expected System message");
4574 }
4575 }
4576
4577 #[test]
4578 fn test_system_message_status_null() {
4579 let json = r#"{
4580 "type": "system",
4581 "subtype": "status",
4582 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4583 "status": null,
4584 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
4585 }"#;
4586
4587 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4588 if let ClaudeOutput::System(sys) = output {
4589 let status = sys.as_status().expect("Should parse as status");
4590 assert_eq!(status.status, None);
4591 } else {
4592 panic!("Expected System message");
4593 }
4594 }
4595
4596 #[test]
4597 fn test_system_message_task_started() {
4598 let json = r#"{
4599 "type": "system",
4600 "subtype": "task_started",
4601 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4602 "task_id": "b6daf3f",
4603 "task_type": "local_bash",
4604 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
4605 "description": "Wait for CI on PR #12",
4606 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
4607 }"#;
4608
4609 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4610 if let ClaudeOutput::System(sys) = output {
4611 assert!(sys.is_task_started());
4612 assert!(!sys.is_task_progress());
4613 assert!(!sys.is_task_notification());
4614
4615 let task = sys.as_task_started().expect("Should parse as task_started");
4616 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
4617 assert_eq!(task.task_id, "b6daf3f");
4618 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
4619 assert_eq!(
4620 task.tool_use_id.as_deref(),
4621 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
4622 );
4623 assert_eq!(task.description, "Wait for CI on PR #12");
4624 } else {
4625 panic!("Expected System message");
4626 }
4627 }
4628
4629 #[test]
4630 fn test_system_message_task_started_agent() {
4631 let json = r#"{
4632 "type": "system",
4633 "subtype": "task_started",
4634 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4635 "task_id": "a4a7e0906e5fc64cc",
4636 "task_type": "local_agent",
4637 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4638 "description": "Explore Scene/ArrayScene duplication",
4639 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4640 }"#;
4641
4642 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4643 if let ClaudeOutput::System(sys) = output {
4644 let task = sys.as_task_started().expect("Should parse as task_started");
4645 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
4646 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
4647 } else {
4648 panic!("Expected System message");
4649 }
4650 }
4651
4652 #[test]
4653 fn test_system_message_task_progress() {
4654 let json = r#"{
4655 "type": "system",
4656 "subtype": "task_progress",
4657 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4658 "task_id": "a4a7e0906e5fc64cc",
4659 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
4660 "description": "Reading src/jplephem/chebyshev.rs",
4661 "last_tool_name": "Read",
4662 "usage": {
4663 "duration_ms": 13996,
4664 "tool_uses": 9,
4665 "total_tokens": 38779
4666 },
4667 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
4668 }"#;
4669
4670 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4671 if let ClaudeOutput::System(sys) = output {
4672 assert!(sys.is_task_progress());
4673 assert!(!sys.is_task_started());
4674
4675 let progress = sys
4676 .as_task_progress()
4677 .expect("Should parse as task_progress");
4678 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
4679 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
4680 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
4681 assert_eq!(progress.usage.duration_ms, 13996);
4682 assert_eq!(progress.usage.tool_uses, 9);
4683 assert_eq!(progress.usage.total_tokens, 38779);
4684 } else {
4685 panic!("Expected System message");
4686 }
4687 }
4688
4689 #[test]
4690 fn test_system_message_task_notification_completed() {
4691 let json = r#"{
4692 "type": "system",
4693 "subtype": "task_notification",
4694 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
4695 "task_id": "a0ba761e9dc9c316f",
4696 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
4697 "status": "completed",
4698 "summary": "Agent \"Write Hipparcos data source doc\" completed",
4699 "output_file": "",
4700 "usage": {
4701 "duration_ms": 172300,
4702 "tool_uses": 11,
4703 "total_tokens": 42005
4704 },
4705 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
4706 }"#;
4707
4708 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4709 if let ClaudeOutput::System(sys) = output {
4710 assert!(sys.is_task_notification());
4711
4712 let notif = sys
4713 .as_task_notification()
4714 .expect("Should parse as task_notification");
4715 assert_eq!(notif.status, super::TaskStatus::Completed);
4716 assert_eq!(
4717 notif.summary,
4718 "Agent \"Write Hipparcos data source doc\" completed"
4719 );
4720 assert_eq!(notif.output_file, Some("".to_string()));
4721 assert_eq!(
4722 notif.tool_use_id,
4723 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
4724 );
4725 let usage = notif.usage.expect("Should have usage");
4726 assert_eq!(usage.duration_ms, 172300);
4727 assert_eq!(usage.tool_uses, 11);
4728 assert_eq!(usage.total_tokens, 42005);
4729 } else {
4730 panic!("Expected System message");
4731 }
4732 }
4733
4734 #[test]
4735 fn test_system_message_task_notification_failed_no_usage() {
4736 let json = r#"{
4737 "type": "system",
4738 "subtype": "task_notification",
4739 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
4740 "task_id": "b98f6a3",
4741 "status": "failed",
4742 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
4743 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
4744 }"#;
4745
4746 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4747 if let ClaudeOutput::System(sys) = output {
4748 let notif = sys
4749 .as_task_notification()
4750 .expect("Should parse as task_notification");
4751 assert_eq!(notif.status, super::TaskStatus::Failed);
4752 assert!(notif.tool_use_id.is_none());
4753 assert!(notif.usage.is_none());
4754 assert_eq!(
4755 notif.output_file,
4756 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
4757 );
4758 } else {
4759 panic!("Expected System message");
4760 }
4761 }
4762
4763 #[test]
4769 fn test_task_messages_roundtrip_through_value() {
4770 let cases = [
4771 r#"{"type":"system","subtype":"task_started","session_id":"s1",
4772 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
4773 "description":"Sleep 3s","uuid":"u1"}"#,
4774 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
4775 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
4776 "last_tool_name":"Bash",
4777 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4778 "uuid":"u2"}"#,
4779 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
4780 "task_id":"t1","tool_use_id":"tu1","status":"completed",
4781 "summary":"done","output_file":"",
4782 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
4783 "uuid":"u3"}"#,
4784 ];
4785
4786 for json in cases {
4787 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4788 let value = serde_json::to_value(&output).unwrap();
4789 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
4790
4791 let ClaudeOutput::System(sys) = reparsed else {
4792 panic!("Expected System variant after round-trip");
4793 };
4794
4795 match sys.subtype {
4796 super::SystemSubtype::TaskStarted => {
4797 assert!(
4798 sys.as_task_started().is_some(),
4799 "as_task_started failed after round-trip"
4800 );
4801 }
4802 super::SystemSubtype::TaskProgress => {
4803 assert!(
4804 sys.as_task_progress().is_some(),
4805 "as_task_progress failed after round-trip"
4806 );
4807 }
4808 super::SystemSubtype::TaskNotification => {
4809 assert!(
4810 sys.as_task_notification().is_some(),
4811 "as_task_notification failed after round-trip"
4812 );
4813 }
4814 other => panic!("unexpected subtype after round-trip: {other:?}"),
4815 }
4816 }
4817 }
4818
4819 #[test]
4820 fn test_system_message_compact_boundary() {
4821 let json = r#"{
4822 "type": "system",
4823 "subtype": "compact_boundary",
4824 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4825 "compact_metadata": {
4826 "pre_tokens": 155285,
4827 "trigger": "auto"
4828 },
4829 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
4830 }"#;
4831
4832 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4833 if let ClaudeOutput::System(sys) = output {
4834 assert!(sys.is_compact_boundary());
4835 assert!(!sys.is_init());
4836 assert!(!sys.is_status());
4837
4838 let compact = sys
4839 .as_compact_boundary()
4840 .expect("Should parse as compact_boundary");
4841 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4842 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
4843 assert_eq!(
4844 compact.compact_metadata.trigger,
4845 super::CompactionTrigger::Auto
4846 );
4847 assert!(compact.summary.is_none());
4849 assert!(compact.leaf_message_count.is_none());
4850 assert!(compact.duration_ms.is_none());
4851 } else {
4852 panic!("Expected System message");
4853 }
4854 }
4855
4856 #[test]
4857 fn test_compact_boundary_with_summary_stats() {
4858 let json = r#"{
4860 "type": "system",
4861 "subtype": "compact_boundary",
4862 "session_id": "s1",
4863 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
4864 "summary": "Summarized the earlier exploration.",
4865 "leaf_message_count": 42,
4866 "duration_ms": 1234,
4867 "uuid": "u1"
4868 }"#;
4869 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4870 let ClaudeOutput::System(sys) = output else {
4871 panic!("Expected System message");
4872 };
4873 let compact = sys.as_compact_boundary().expect("compact_boundary");
4874 assert_eq!(
4875 compact.summary.as_deref(),
4876 Some("Summarized the earlier exploration.")
4877 );
4878 assert_eq!(compact.leaf_message_count, Some(42));
4879 assert_eq!(compact.duration_ms, Some(1234));
4880
4881 let json_alt = r#"{
4884 "type": "system",
4885 "subtype": "compact_boundary",
4886 "session_id": "s2",
4887 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
4888 "content": "alt-key summary",
4889 "message_count": 7
4890 }"#;
4891 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
4892 let ClaudeOutput::System(sys) = output else {
4893 panic!("Expected System message");
4894 };
4895 let compact = sys.as_compact_boundary().expect("compact_boundary");
4896 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
4897 assert_eq!(compact.leaf_message_count, Some(7));
4898 }
4899
4900 #[test]
4901 fn test_init_message_with_new_fields() {
4902 let json = r#"{
4903 "type": "system",
4904 "subtype": "init",
4905 "session_id": "test-session",
4906 "cwd": "/home/user",
4907 "model": "claude-opus-4-7",
4908 "tools": ["Bash"],
4909 "mcp_servers": [],
4910 "permissionMode": "default",
4911 "apiKeySource": "none",
4912 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
4913 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
4914 "fast_mode_state": "off",
4915 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
4916 "claude_code_version": "2.1.117"
4917 }"#;
4918
4919 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4920 if let ClaudeOutput::System(sys) = output {
4921 let init = sys.as_init().expect("Should parse as init");
4922 assert_eq!(
4923 init.uuid.as_deref(),
4924 Some("44841a0d-182d-493a-86b5-79800d3d9665")
4925 );
4926 assert!(init.memory_paths.is_some());
4927 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
4928 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
4929 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
4930 } else {
4931 panic!("Expected System message");
4932 }
4933 }
4934
4935 #[test]
4936 fn test_assistant_message_with_new_fields() {
4937 let json = r#"{
4938 "type": "assistant",
4939 "message": {
4940 "id": "msg_1",
4941 "type": "message",
4942 "role": "assistant",
4943 "model": "claude-opus-4-7",
4944 "content": [{"type": "text", "text": "Hello"}],
4945 "stop_reason": "end_turn",
4946 "stop_details": null,
4947 "context_management": null,
4948 "usage": {
4949 "input_tokens": 100,
4950 "output_tokens": 10,
4951 "cache_creation_input_tokens": 50,
4952 "cache_read_input_tokens": 0,
4953 "service_tier": "standard",
4954 "inference_geo": "not_available"
4955 }
4956 },
4957 "session_id": "abc",
4958 "uuid": "msg-uuid-123"
4959 }"#;
4960
4961 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4962 if let ClaudeOutput::Assistant(asst) = output {
4963 assert_eq!(asst.message.stop_details, None);
4964 assert_eq!(asst.message.context_management, None);
4965 let usage = asst.message.usage.unwrap();
4966 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
4967 } else {
4968 panic!("Expected Assistant message");
4969 }
4970 }
4971
4972 #[test]
4973 fn test_user_message_with_new_fields() {
4974 let json = r#"{
4975 "type": "user",
4976 "message": {
4977 "role": "user",
4978 "content": [{"type": "text", "text": "Hello"}]
4979 },
4980 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
4981 "parent_tool_use_id": "toolu_123",
4982 "uuid": "user-msg-456"
4983 }"#;
4984
4985 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4986 if let ClaudeOutput::User(user) = output {
4987 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
4988 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
4989 } else {
4990 panic!("Expected User message");
4991 }
4992 }
4993
4994 #[test]
5000 fn test_user_message_preserves_tool_use_result_and_timestamp() {
5001 let json = r#"{
5002 "type":"user",
5003 "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"}]},
5004 "parent_tool_use_id":null,
5005 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5006 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
5007 "timestamp":"2026-05-12T23:12:04.121Z",
5008 "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"}}
5009 }"#;
5010
5011 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5012 let user = match output {
5013 ClaudeOutput::User(u) => u,
5014 other => panic!("Expected User message, got {:?}", other.message_type()),
5015 };
5016
5017 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
5018 let raw = user
5019 .tool_use_result
5020 .as_ref()
5021 .expect("tool_use_result must be captured");
5022 assert_eq!(raw["answers"]["Color"], "Blue");
5023 assert_eq!(raw["questions"][0]["header"], "Color");
5024
5025 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
5029 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
5030 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
5031 assert_eq!(
5032 reser["tool_use_result"]["questions"][0]["question"],
5033 "Which color do you prefer?"
5034 );
5035
5036 let typed: crate::AskUserQuestionInput = user
5039 .tool_use_result_as::<crate::AskUserQuestionInput>()
5040 .expect("tool_use_result present")
5041 .expect("AskUserQuestionInput parses");
5042 assert_eq!(typed.questions.len(), 1);
5043 assert_eq!(typed.questions[0].header, "Color");
5044 let answers = typed.answers.expect("answers populated");
5045 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
5046 }
5047
5048 #[test]
5051 fn test_user_message_without_tool_use_result_omits_field() {
5052 let json = r#"{
5053 "type":"user",
5054 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
5055 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
5056 }"#;
5057
5058 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5059 let user = match output {
5060 ClaudeOutput::User(u) => u,
5061 _ => panic!("Expected User message"),
5062 };
5063 assert!(user.tool_use_result.is_none());
5064 assert!(user.timestamp.is_none());
5065
5066 let reser = serde_json::to_value(&user).unwrap();
5067 assert!(reser.get("tool_use_result").is_none());
5068 assert!(reser.get("timestamp").is_none());
5069 }
5070
5071 #[test]
5075 fn test_subagent_result_exposes_token_accounting() {
5076 let json = r#"{
5077 "type":"user",
5078 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
5079 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
5080 "tool_use_result":{
5081 "status":"completed",
5082 "prompt":"Count the .rs files.",
5083 "agentId":"ac4f0276e9d4b6232",
5084 "agentType":"Explore",
5085 "content":[{"type":"text","text":"21"}],
5086 "resolvedModel":"claude-haiku-4-5-20251001",
5087 "totalDurationMs":6869,
5088 "totalTokens":7834,
5089 "totalToolUseCount":1,
5090 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
5091 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
5092 }
5093 }"#;
5094
5095 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5096 let user = match output {
5097 ClaudeOutput::User(u) => u,
5098 _ => panic!("Expected User message"),
5099 };
5100
5101 let result = user.subagent_result().expect("subagent result parses");
5102 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
5103 assert_eq!(
5104 result.resolved_model.as_deref(),
5105 Some("claude-haiku-4-5-20251001")
5106 );
5107 assert_eq!(result.total_tokens, Some(7834));
5108 assert_eq!(result.total_duration_ms, Some(6869));
5109 assert_eq!(result.total_tool_use_count, Some(1));
5110
5111 let usage = result.usage.expect("nested usage present");
5112 assert_eq!(usage.input_tokens, 6);
5113 assert_eq!(usage.cache_read_input_tokens, 7699);
5114
5115 let stats = result.tool_stats.expect("toolStats present");
5116 assert_eq!(stats.bash_count, 1);
5117 }
5118
5119 #[test]
5123 fn test_subagent_result_absent_for_non_task_result() {
5124 let json = r#"{
5125 "type":"user",
5126 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
5127 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5128 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
5129 }"#;
5130
5131 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5132 let user = match output {
5133 ClaudeOutput::User(u) => u,
5134 _ => panic!("Expected User message"),
5135 };
5136
5137 let result = user.subagent_result().expect("lenient parse");
5138 assert_eq!(result.total_tokens, None);
5139 assert_eq!(result.agent_type, None);
5140 }
5141
5142 #[test]
5143 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
5144 use serde_json::Value;
5145
5146 let raw: Value = serde_json::from_str(
5147 r#"{
5148 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
5149 "fast_mode_state":"off",
5150 "fast_mode_disabled_reason":"not_first_party",
5151 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
5152 }"#,
5153 )
5154 .unwrap();
5155 crate::io::assert_fully_wrapped(&raw);
5156
5157 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5158 let ClaudeOutput::System(sys) = output else {
5159 panic!("expected System");
5160 };
5161 let init = sys.as_init().expect("parses as init");
5162 assert_eq!(
5163 init.fast_mode_disabled_reason,
5164 Some(crate::FastModeDisabledReason::NotFirstParty)
5165 );
5166 let errs = init.mcp_server_errors.unwrap();
5167 assert_eq!(errs.len(), 1);
5168 assert_eq!(errs[0].name, "broken");
5169 assert_eq!(errs[0].error_type, "invalid_config");
5170 }
5171
5172 #[test]
5173 fn test_code_change_published_fully_wrapped() {
5174 use super::{KnownSystemEvent, SystemSubtype};
5175 use serde_json::Value;
5176
5177 let raw: Value = serde_json::from_str(
5178 r#"{
5179 "type":"system","subtype":"code_change_published",
5180 "provider":"github","url":"https://github.com/owner/repo/pull/42",
5181 "repo":"owner/repo","identifier":"42",
5182 "uuid":"u1","session_id":"s1"
5183 }"#,
5184 )
5185 .unwrap();
5186 crate::io::assert_fully_wrapped(&raw);
5187
5188 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5189 let ClaudeOutput::System(sys) = output else {
5190 panic!("expected System");
5191 };
5192 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
5193 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
5194 panic!("expected CodeChangePublished event");
5195 };
5196 assert_eq!(msg.provider, "github");
5197 assert_eq!(msg.repo, "owner/repo");
5198 assert_eq!(msg.identifier, "42");
5199
5200 assert!(sys.is_code_change_published());
5201 assert!(!sys.is_vcs_state_changed());
5202 let direct = sys.as_code_change_published().expect("direct accessor");
5203 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
5204 assert!(sys.as_vcs_state_changed().is_none());
5205 }
5206
5207 #[test]
5208 fn test_feedback_draft_queued_fully_wrapped() {
5209 use super::{KnownSystemEvent, SystemSubtype};
5210 use serde_json::Value;
5211
5212 let raw: Value = serde_json::from_str(
5213 r#"{
5214 "type":"system","subtype":"feedback_draft_queued",
5215 "draft_id":"draft-1","draft_type":"bug_report",
5216 "title":"Tool output was truncated",
5217 "details_preview":"The last command omitted its final lines",
5218 "uuid":"u1","session_id":"s1","future_field":"preserved"
5219 }"#,
5220 )
5221 .unwrap();
5222 crate::io::assert_fully_wrapped(&raw);
5223
5224 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5225 let ClaudeOutput::System(sys) = output else {
5226 panic!("expected System");
5227 };
5228 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
5229 assert!(sys.is_feedback_draft_queued());
5230 assert!(!sys.is_vcs_state_changed());
5231
5232 let direct = sys
5233 .as_feedback_draft_queued()
5234 .expect("direct typed accessor");
5235 assert_eq!(direct.draft_id, "draft-1");
5236 assert_eq!(direct.draft_type, "bug_report");
5237 assert_eq!(direct.extra["future_field"], "preserved");
5238
5239 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
5240 panic!("expected FeedbackDraftQueued event");
5241 };
5242 assert_eq!(known.title, "Tool output was truncated");
5243 assert_eq!(
5244 sys.typed_value().expect("typed value")["future_field"],
5245 "preserved"
5246 );
5247 }
5248
5249 #[test]
5250 fn test_cloud_session_delta_fully_wrapped() {
5251 use super::{KnownSystemEvent, SystemSubtype};
5252 use serde_json::Value;
5253
5254 let raw: Value = serde_json::from_str(
5255 r#"{
5256 "type":"system","subtype":"cloud_session_delta",
5257 "seq":3,"changed":["serving","connection"],
5258 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5259 "serving":{"state":"on"},"connection":{"state":"live"}},
5260 "uuid":"u1","session_id":"s1","future_field":"preserved"
5261 }"#,
5262 )
5263 .unwrap();
5264 crate::io::assert_fully_wrapped(&raw);
5265
5266 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5267 let ClaudeOutput::System(sys) = output else {
5268 panic!("expected System");
5269 };
5270 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5271 assert!(sys.is_cloud_session_delta());
5272 assert!(!sys.is_feedback_draft_queued());
5273
5274 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5275 assert_eq!(direct.seq, 3);
5276 assert_eq!(direct.changed, vec!["serving", "connection"]);
5277 assert_eq!(direct.cloud_session["id"], "session_abc");
5278 assert_eq!(direct.extra["future_field"], "preserved");
5279
5280 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5281 panic!("expected CloudSessionDelta event");
5282 };
5283 assert_eq!(known.session_id, "s1");
5284 assert_eq!(
5285 sys.typed_value().expect("typed value")["future_field"],
5286 "preserved"
5287 );
5288 }
5289
5290 #[test]
5291 fn test_vcs_state_changed_fully_wrapped() {
5292 use super::{KnownSystemEvent, VcsMutationKind};
5293 use serde_json::Value;
5294
5295 for kind in ["commit", "push", "merge", "rebase"] {
5296 let raw: Value = serde_json::from_str(&format!(
5297 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5298 kind
5299 ))
5300 .unwrap();
5301 crate::io::assert_fully_wrapped(&raw);
5302
5303 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5304 let ClaudeOutput::System(sys) = output else {
5305 panic!("expected System");
5306 };
5307 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5308 panic!("expected VcsStateChanged event");
5309 };
5310 assert_eq!(msg.kind.as_str(), kind);
5311 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
5312 }
5313
5314 let raw: Value = serde_json::from_str(
5316 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
5317 )
5318 .unwrap();
5319 crate::io::assert_fully_wrapped(&raw);
5320 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5321 let ClaudeOutput::System(sys) = output else {
5322 panic!("expected System");
5323 };
5324 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5325 panic!("expected VcsStateChanged event");
5326 };
5327 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
5328
5329 assert!(sys.is_vcs_state_changed());
5330 let direct = sys.as_vcs_state_changed().expect("direct accessor");
5331 assert_eq!(direct.cwd, "/repo");
5332 assert!(sys.as_code_change_published().is_none());
5333 }
5334
5335 #[test]
5336 fn test_assistant_aborted_and_resume_flags_roundtrip() {
5337 let json = r#"{
5338 "type":"assistant",
5339 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
5340 "session_id":"s1",
5341 "aborted":true,
5342 "resumed_from_incomplete_thinking":true
5343 }"#;
5344 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5345 let ClaudeOutput::Assistant(msg) = &output else {
5346 panic!("expected Assistant");
5347 };
5348 assert_eq!(msg.aborted, Some(true));
5349 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
5350 let reserialized = serde_json::to_string(&output).unwrap();
5351 assert!(reserialized.contains("\"aborted\":true"));
5352 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
5353
5354 let json = r#"{
5356 "type":"assistant",
5357 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
5358 "session_id":"s2"
5359 }"#;
5360 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5361 let reserialized = serde_json::to_string(&output).unwrap();
5362 assert!(!reserialized.contains("aborted"));
5363 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
5364 }
5365
5366 #[test]
5367 fn test_user_tool_result_meta_roundtrip() {
5368 let json = r#"{
5369 "type":"user",
5370 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
5371 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5372 "tool_result_meta":[
5373 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
5374 {"id":"toolu_2","non_execution_kind":"permission-rule"}
5375 ]
5376 }"#;
5377 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5378 let ClaudeOutput::User(user) = &output else {
5379 panic!("expected User");
5380 };
5381 let meta = user.tool_result_meta.as_ref().unwrap();
5382 assert_eq!(meta.len(), 2);
5383 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
5384 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
5385 assert_eq!(meta[1].user_feedback, None);
5386
5387 let reserialized = serde_json::to_string(&output).unwrap();
5388 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
5389 assert!(!reserialized.contains("\"user_feedback\":null"));
5390 }
5391
5392 #[test]
5396 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
5397 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
5398 let with_scope = serde_json::json!({
5399 "trigger": "refusal",
5400 "direction": "retry",
5401 "scope": "local",
5402 "original_model": "claude-fable-5",
5403 "fallback_model": "claude-opus-5",
5404 "request_id": null,
5405 "content": "Refused; retried on fallback model.",
5406 "uuid": "u1",
5407 "session_id": "s1"
5408 });
5409 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
5410 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
5411 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
5412
5413 let mut without = with_scope.clone();
5416 without.as_object_mut().unwrap().remove("scope");
5417 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
5418 assert_eq!(msg.scope, None);
5419 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
5420
5421 assert_eq!(
5423 RefusalFallbackScope::from("workspace").as_str(),
5424 "workspace"
5425 );
5426 }
5427}