1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use serde_json::Value;
3use std::fmt;
4use uuid::Uuid;
5
6use super::claude_output::ClaudeOutput;
7use super::content_blocks::{deserialize_content_blocks, ContentBlock};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum SystemSubtype {
16 Init,
17 Status,
18 CompactBoundary,
19 ThinkingTokens,
20 TaskStarted,
21 TaskProgress,
22 TaskUpdated,
23 TaskNotification,
24 ApiRetry,
25 ControlRequestProgress,
26 ModelRefusalFallback,
27 ModelRefusalNoFallback,
28 LocalCommandOutput,
29 HookStarted,
30 HookProgress,
31 HookResponse,
32 PluginInstall,
33 BackgroundTasksChanged,
34 SessionStateChanged,
35 WorkerShuttingDown,
36 CommandsChanged,
37 Notification,
38 FilesPersisted,
39 MemoryRecall,
40 ElicitationComplete,
41 PermissionDenied,
42 MirrorError,
43 Informational,
44 CodeChangePublished,
45 VcsStateChanged,
46 FeedbackDraftQueued,
47 CloudSessionDelta,
48 DevIntent,
49 TurnHandoffAvailable,
50 TurnPreempted,
51 PeerMessageHold,
52 PerTurnEffortChanged,
53 SessionTitleChanged,
54 InstructionSizeWarning,
55 UiFocus,
56 UiInvalidate,
57 UiLog,
58 UiPanes,
59 UiScroll,
60 UiStatus,
61 UiToast,
62 Unknown(String),
64}
65
66impl SystemSubtype {
67 pub fn as_str(&self) -> &str {
68 match self {
69 Self::Init => "init",
70 Self::Status => "status",
71 Self::CompactBoundary => "compact_boundary",
72 Self::ThinkingTokens => "thinking_tokens",
73 Self::TaskStarted => "task_started",
74 Self::TaskProgress => "task_progress",
75 Self::TaskUpdated => "task_updated",
76 Self::TaskNotification => "task_notification",
77 Self::ApiRetry => "api_retry",
78 Self::ControlRequestProgress => "control_request_progress",
79 Self::ModelRefusalFallback => "model_refusal_fallback",
80 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
81 Self::LocalCommandOutput => "local_command_output",
82 Self::HookStarted => "hook_started",
83 Self::HookProgress => "hook_progress",
84 Self::HookResponse => "hook_response",
85 Self::PluginInstall => "plugin_install",
86 Self::BackgroundTasksChanged => "background_tasks_changed",
87 Self::SessionStateChanged => "session_state_changed",
88 Self::WorkerShuttingDown => "worker_shutting_down",
89 Self::CommandsChanged => "commands_changed",
90 Self::Notification => "notification",
91 Self::FilesPersisted => "files_persisted",
92 Self::MemoryRecall => "memory_recall",
93 Self::ElicitationComplete => "elicitation_complete",
94 Self::PermissionDenied => "permission_denied",
95 Self::MirrorError => "mirror_error",
96 Self::Informational => "informational",
97 Self::CodeChangePublished => "code_change_published",
98 Self::VcsStateChanged => "vcs_state_changed",
99 Self::FeedbackDraftQueued => "feedback_draft_queued",
100 Self::CloudSessionDelta => "cloud_session_delta",
101 Self::DevIntent => "dev_intent",
102 Self::TurnHandoffAvailable => "turn_handoff_available",
103 Self::TurnPreempted => "turn_preempted",
104 Self::PeerMessageHold => "peer_message_hold",
105 Self::PerTurnEffortChanged => "per_turn_effort_changed",
106 Self::SessionTitleChanged => "session_title_changed",
107 Self::InstructionSizeWarning => "instruction_size_warning",
108 Self::UiFocus => "ui_focus",
109 Self::UiInvalidate => "ui_invalidate",
110 Self::UiLog => "ui_log",
111 Self::UiPanes => "ui_panes",
112 Self::UiScroll => "ui_scroll",
113 Self::UiStatus => "ui_status",
114 Self::UiToast => "ui_toast",
115 Self::Unknown(s) => s.as_str(),
116 }
117 }
118}
119
120impl fmt::Display for SystemSubtype {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 f.write_str(self.as_str())
123 }
124}
125
126impl From<&str> for SystemSubtype {
127 fn from(s: &str) -> Self {
128 match s {
129 "init" => Self::Init,
130 "status" => Self::Status,
131 "compact_boundary" => Self::CompactBoundary,
132 "thinking_tokens" => Self::ThinkingTokens,
133 "task_started" => Self::TaskStarted,
134 "task_progress" => Self::TaskProgress,
135 "task_updated" => Self::TaskUpdated,
136 "task_notification" => Self::TaskNotification,
137 "api_retry" => Self::ApiRetry,
138 "control_request_progress" => Self::ControlRequestProgress,
139 "model_refusal_fallback" => Self::ModelRefusalFallback,
140 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
141 "local_command_output" => Self::LocalCommandOutput,
142 "hook_started" => Self::HookStarted,
143 "hook_progress" => Self::HookProgress,
144 "hook_response" => Self::HookResponse,
145 "plugin_install" => Self::PluginInstall,
146 "background_tasks_changed" => Self::BackgroundTasksChanged,
147 "session_state_changed" => Self::SessionStateChanged,
148 "worker_shutting_down" => Self::WorkerShuttingDown,
149 "commands_changed" => Self::CommandsChanged,
150 "notification" => Self::Notification,
151 "files_persisted" => Self::FilesPersisted,
152 "memory_recall" => Self::MemoryRecall,
153 "elicitation_complete" => Self::ElicitationComplete,
154 "permission_denied" => Self::PermissionDenied,
155 "mirror_error" => Self::MirrorError,
156 "informational" => Self::Informational,
157 "code_change_published" => Self::CodeChangePublished,
158 "vcs_state_changed" => Self::VcsStateChanged,
159 "feedback_draft_queued" => Self::FeedbackDraftQueued,
160 "cloud_session_delta" => Self::CloudSessionDelta,
161 "dev_intent" => Self::DevIntent,
162 "turn_handoff_available" => Self::TurnHandoffAvailable,
163 "turn_preempted" => Self::TurnPreempted,
164 "peer_message_hold" => Self::PeerMessageHold,
165 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
166 "session_title_changed" => Self::SessionTitleChanged,
167 "instruction_size_warning" => Self::InstructionSizeWarning,
168 "ui_focus" => Self::UiFocus,
169 "ui_invalidate" => Self::UiInvalidate,
170 "ui_log" => Self::UiLog,
171 "ui_panes" => Self::UiPanes,
172 "ui_scroll" => Self::UiScroll,
173 "ui_status" => Self::UiStatus,
174 "ui_toast" => Self::UiToast,
175 other => Self::Unknown(other.to_string()),
176 }
177 }
178}
179
180impl Serialize for SystemSubtype {
181 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
182 serializer.serialize_str(self.as_str())
183 }
184}
185
186impl<'de> Deserialize<'de> for SystemSubtype {
187 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
188 let s = String::deserialize(deserializer)?;
189 Ok(Self::from(s.as_str()))
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Hash)]
198pub enum MessageRole {
199 User,
200 Assistant,
201 Unknown(String),
203}
204
205impl MessageRole {
206 pub fn as_str(&self) -> &str {
207 match self {
208 Self::User => "user",
209 Self::Assistant => "assistant",
210 Self::Unknown(s) => s.as_str(),
211 }
212 }
213}
214
215impl fmt::Display for MessageRole {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 f.write_str(self.as_str())
218 }
219}
220
221impl From<&str> for MessageRole {
222 fn from(s: &str) -> Self {
223 match s {
224 "user" => Self::User,
225 "assistant" => Self::Assistant,
226 other => Self::Unknown(other.to_string()),
227 }
228 }
229}
230
231impl Serialize for MessageRole {
232 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
233 serializer.serialize_str(self.as_str())
234 }
235}
236
237impl<'de> Deserialize<'de> for MessageRole {
238 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
239 let s = String::deserialize(deserializer)?;
240 Ok(Self::from(s.as_str()))
241 }
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, Hash)]
246pub enum CompactionTrigger {
247 Auto,
249 Manual,
251 Unknown(String),
253}
254
255impl CompactionTrigger {
256 pub fn as_str(&self) -> &str {
257 match self {
258 Self::Auto => "auto",
259 Self::Manual => "manual",
260 Self::Unknown(s) => s.as_str(),
261 }
262 }
263}
264
265impl fmt::Display for CompactionTrigger {
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 f.write_str(self.as_str())
268 }
269}
270
271impl From<&str> for CompactionTrigger {
272 fn from(s: &str) -> Self {
273 match s {
274 "auto" => Self::Auto,
275 "manual" => Self::Manual,
276 other => Self::Unknown(other.to_string()),
277 }
278 }
279}
280
281impl Serialize for CompactionTrigger {
282 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
283 serializer.serialize_str(self.as_str())
284 }
285}
286
287impl<'de> Deserialize<'de> for CompactionTrigger {
288 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
289 let s = String::deserialize(deserializer)?;
290 Ok(Self::from(s.as_str()))
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq, Hash)]
296pub enum StopReason {
297 EndTurn,
299 MaxTokens,
301 ToolUse,
303 Unknown(String),
305}
306
307impl StopReason {
308 pub fn as_str(&self) -> &str {
309 match self {
310 Self::EndTurn => "end_turn",
311 Self::MaxTokens => "max_tokens",
312 Self::ToolUse => "tool_use",
313 Self::Unknown(s) => s.as_str(),
314 }
315 }
316}
317
318impl fmt::Display for StopReason {
319 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320 f.write_str(self.as_str())
321 }
322}
323
324impl From<&str> for StopReason {
325 fn from(s: &str) -> Self {
326 match s {
327 "end_turn" => Self::EndTurn,
328 "max_tokens" => Self::MaxTokens,
329 "tool_use" => Self::ToolUse,
330 other => Self::Unknown(other.to_string()),
331 }
332 }
333}
334
335impl Serialize for StopReason {
336 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
337 serializer.serialize_str(self.as_str())
338 }
339}
340
341impl<'de> Deserialize<'de> for StopReason {
342 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
343 let s = String::deserialize(deserializer)?;
344 Ok(Self::from(s.as_str()))
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq, Hash)]
350pub enum ApiKeySource {
351 None,
353 User,
354 Project,
355 Org,
356 Temporary,
357 Oauth,
358 Unknown(String),
360}
361
362impl ApiKeySource {
363 pub fn as_str(&self) -> &str {
364 match self {
365 Self::None => "none",
366 Self::User => "user",
367 Self::Project => "project",
368 Self::Org => "org",
369 Self::Temporary => "temporary",
370 Self::Oauth => "oauth",
371 Self::Unknown(s) => s.as_str(),
372 }
373 }
374}
375
376impl fmt::Display for ApiKeySource {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378 f.write_str(self.as_str())
379 }
380}
381
382impl From<&str> for ApiKeySource {
383 fn from(s: &str) -> Self {
384 match s {
385 "none" => Self::None,
386 "user" => Self::User,
387 "project" => Self::Project,
388 "org" => Self::Org,
389 "temporary" => Self::Temporary,
390 "oauth" => Self::Oauth,
391 other => Self::Unknown(other.to_string()),
392 }
393 }
394}
395
396impl Serialize for ApiKeySource {
397 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
398 serializer.serialize_str(self.as_str())
399 }
400}
401
402impl<'de> Deserialize<'de> for ApiKeySource {
403 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
404 let s = String::deserialize(deserializer)?;
405 Ok(Self::from(s.as_str()))
406 }
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Hash)]
411pub enum OutputStyle {
412 Default,
414 Unknown(String),
416}
417
418impl OutputStyle {
419 pub fn as_str(&self) -> &str {
420 match self {
421 Self::Default => "default",
422 Self::Unknown(s) => s.as_str(),
423 }
424 }
425}
426
427impl fmt::Display for OutputStyle {
428 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429 f.write_str(self.as_str())
430 }
431}
432
433impl From<&str> for OutputStyle {
434 fn from(s: &str) -> Self {
435 match s {
436 "default" => Self::Default,
437 other => Self::Unknown(other.to_string()),
438 }
439 }
440}
441
442impl Serialize for OutputStyle {
443 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
444 serializer.serialize_str(self.as_str())
445 }
446}
447
448impl<'de> Deserialize<'de> for OutputStyle {
449 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
450 let s = String::deserialize(deserializer)?;
451 Ok(Self::from(s.as_str()))
452 }
453}
454
455#[derive(Debug, Clone, PartialEq, Eq, Hash)]
457pub enum InitPermissionMode {
458 Default,
460 AcceptEdits,
461 BypassPermissions,
462 Plan,
463 DontAsk,
464 Auto,
465 Unknown(String),
467}
468
469impl InitPermissionMode {
470 pub fn as_str(&self) -> &str {
471 match self {
472 Self::Default => "default",
473 Self::AcceptEdits => "acceptEdits",
474 Self::BypassPermissions => "bypassPermissions",
475 Self::Plan => "plan",
476 Self::DontAsk => "dontAsk",
477 Self::Auto => "auto",
478 Self::Unknown(s) => s.as_str(),
479 }
480 }
481}
482
483impl fmt::Display for InitPermissionMode {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 f.write_str(self.as_str())
486 }
487}
488
489impl From<&str> for InitPermissionMode {
490 fn from(s: &str) -> Self {
491 match s {
492 "default" => Self::Default,
493 "acceptEdits" => Self::AcceptEdits,
494 "bypassPermissions" => Self::BypassPermissions,
495 "plan" => Self::Plan,
496 "dontAsk" => Self::DontAsk,
497 "auto" => Self::Auto,
498 other => Self::Unknown(other.to_string()),
499 }
500 }
501}
502
503impl Serialize for InitPermissionMode {
504 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
505 serializer.serialize_str(self.as_str())
506 }
507}
508
509impl<'de> Deserialize<'de> for InitPermissionMode {
510 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
511 let s = String::deserialize(deserializer)?;
512 Ok(Self::from(s.as_str()))
513 }
514}
515
516#[derive(Debug, Clone, PartialEq, Eq, Hash)]
518pub enum StatusMessageStatus {
519 Compacting,
521 Requesting,
523 Unknown(String),
525}
526
527impl StatusMessageStatus {
528 pub fn as_str(&self) -> &str {
529 match self {
530 Self::Compacting => "compacting",
531 Self::Requesting => "requesting",
532 Self::Unknown(s) => s.as_str(),
533 }
534 }
535}
536
537impl fmt::Display for StatusMessageStatus {
538 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539 f.write_str(self.as_str())
540 }
541}
542
543impl From<&str> for StatusMessageStatus {
544 fn from(s: &str) -> Self {
545 match s {
546 "compacting" => Self::Compacting,
547 "requesting" => Self::Requesting,
548 other => Self::Unknown(other.to_string()),
549 }
550 }
551}
552
553impl Serialize for StatusMessageStatus {
554 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
555 serializer.serialize_str(self.as_str())
556 }
557}
558
559impl<'de> Deserialize<'de> for StatusMessageStatus {
560 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
561 let s = String::deserialize(deserializer)?;
562 Ok(Self::from(s.as_str()))
563 }
564}
565
566pub(crate) fn serialize_optional_uuid<S>(
568 uuid: &Option<Uuid>,
569 serializer: S,
570) -> Result<S::Ok, S::Error>
571where
572 S: Serializer,
573{
574 match uuid {
575 Some(id) => serializer.serialize_str(&id.to_string()),
576 None => serializer.serialize_none(),
577 }
578}
579
580pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
582where
583 D: Deserializer<'de>,
584{
585 let opt_str: Option<String> = Option::deserialize(deserializer)?;
586 match opt_str {
587 Some(s) => Uuid::parse_str(&s)
588 .map(Some)
589 .map_err(serde::de::Error::custom),
590 None => Ok(None),
591 }
592}
593
594#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
597pub struct MessageOrigin {
598 pub kind: String,
599 #[serde(flatten)]
600 pub extra: serde_json::Map<String, Value>,
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
605pub struct SummarizeMetadata {
606 pub messages_summarized: u64,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
608 pub user_context: Option<String>,
609 #[serde(default, skip_serializing_if = "Option::is_none")]
610 pub direction: Option<String>,
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
615pub struct McpMeta {
616 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
617 pub meta: Option<Value>,
618 #[serde(default, skip_serializing_if = "Option::is_none")]
619 pub structured_content: Option<Value>,
620 #[serde(default, skip_serializing_if = "Vec::is_empty")]
625 pub resource_links: Vec<ResourceLink>,
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
632pub struct ResourceLink {
633 pub uri: String,
634 pub name: String,
635 #[serde(default, skip_serializing_if = "Option::is_none")]
636 pub title: Option<String>,
637 #[serde(default, skip_serializing_if = "Option::is_none")]
638 pub description: Option<String>,
639 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
640 pub mime_type: Option<String>,
641 #[serde(default, skip_serializing_if = "Option::is_none")]
642 pub size: Option<u64>,
643 #[serde(default, skip_serializing_if = "Option::is_none")]
644 pub annotations: Option<Value>,
645}
646
647#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
649pub struct ToolResultMeta {
650 pub id: String,
652 #[serde(default, skip_serializing_if = "Option::is_none")]
659 pub non_execution_kind: Option<String>,
660 #[serde(default, skip_serializing_if = "Option::is_none")]
662 pub user_feedback: Option<String>,
663 #[serde(default, skip_serializing_if = "Option::is_none")]
668 pub remedy: Option<ToolResultRemedy>,
669}
670
671#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
676pub struct ToolResultRemedy {
677 pub kind: ToolResultRemedyKind,
680 #[serde(default, skip_serializing_if = "Option::is_none")]
684 pub servers: Option<Vec<String>>,
685 #[serde(default, skip_serializing_if = "Option::is_none")]
688 pub provider: Option<RemedyLoginProvider>,
689 #[serde(default, skip_serializing_if = "Option::is_none")]
693 pub path: Option<String>,
694 #[serde(default, skip_serializing_if = "Option::is_none")]
699 pub managed: Option<bool>,
700 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub feature: Option<RemedyFeature>,
703 #[serde(default, skip_serializing_if = "Option::is_none")]
705 pub cause: Option<RemedyFeatureCause>,
706 #[serde(default, skip_serializing_if = "Option::is_none")]
708 pub policy_kind: Option<RemedyPolicyKind>,
709}
710
711#[derive(Debug, Clone, PartialEq, Eq, Hash)]
713pub enum ToolResultRemedyKind {
714 McpNeedsAuth,
716 McpDisabled,
718 McpRequiredMissing,
720 AuthExpired,
722 AuthOverridden,
724 AuthMissingScope,
726 DesignNeedsAuthorization,
728 SandboxViolation,
730 OutsideReadsBlocked,
732 MemoryPaused,
734 FeatureDisabled,
736 PolicyDenied,
739 SpawnArgLimit,
741 StagedForReview,
743 Unknown(String),
745}
746
747impl ToolResultRemedyKind {
748 pub fn as_str(&self) -> &str {
749 match self {
750 Self::McpNeedsAuth => "mcp_needs_auth",
751 Self::McpDisabled => "mcp_disabled",
752 Self::McpRequiredMissing => "mcp_required_missing",
753 Self::AuthExpired => "auth_expired",
754 Self::AuthOverridden => "auth_overridden",
755 Self::AuthMissingScope => "auth_missing_scope",
756 Self::DesignNeedsAuthorization => "design_needs_authorization",
757 Self::SandboxViolation => "sandbox_violation",
758 Self::OutsideReadsBlocked => "outside_reads_blocked",
759 Self::MemoryPaused => "memory_paused",
760 Self::FeatureDisabled => "feature_disabled",
761 Self::PolicyDenied => "policy_denied",
762 Self::SpawnArgLimit => "spawn_arg_limit",
763 Self::StagedForReview => "staged_for_review",
764 Self::Unknown(s) => s.as_str(),
765 }
766 }
767}
768
769impl fmt::Display for ToolResultRemedyKind {
770 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771 f.write_str(self.as_str())
772 }
773}
774
775impl From<&str> for ToolResultRemedyKind {
776 fn from(s: &str) -> Self {
777 match s {
778 "mcp_needs_auth" => Self::McpNeedsAuth,
779 "mcp_disabled" => Self::McpDisabled,
780 "mcp_required_missing" => Self::McpRequiredMissing,
781 "auth_expired" => Self::AuthExpired,
782 "auth_overridden" => Self::AuthOverridden,
783 "auth_missing_scope" => Self::AuthMissingScope,
784 "design_needs_authorization" => Self::DesignNeedsAuthorization,
785 "sandbox_violation" => Self::SandboxViolation,
786 "outside_reads_blocked" => Self::OutsideReadsBlocked,
787 "memory_paused" => Self::MemoryPaused,
788 "feature_disabled" => Self::FeatureDisabled,
789 "policy_denied" => Self::PolicyDenied,
790 "spawn_arg_limit" => Self::SpawnArgLimit,
791 "staged_for_review" => Self::StagedForReview,
792 other => Self::Unknown(other.to_string()),
793 }
794 }
795}
796
797impl Serialize for ToolResultRemedyKind {
798 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
799 serializer.serialize_str(self.as_str())
800 }
801}
802
803impl<'de> Deserialize<'de> for ToolResultRemedyKind {
804 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
805 let s = String::deserialize(deserializer)?;
806 Ok(Self::from(s.as_str()))
807 }
808}
809
810#[derive(Debug, Clone, PartialEq, Eq, Hash)]
812pub enum RemedyLoginProvider {
813 ClaudeAi,
815 ClaudeDesign,
817 Unknown(String),
819}
820
821impl RemedyLoginProvider {
822 pub fn as_str(&self) -> &str {
823 match self {
824 Self::ClaudeAi => "claude_ai",
825 Self::ClaudeDesign => "claude_design",
826 Self::Unknown(s) => s.as_str(),
827 }
828 }
829}
830
831impl fmt::Display for RemedyLoginProvider {
832 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
833 f.write_str(self.as_str())
834 }
835}
836
837impl From<&str> for RemedyLoginProvider {
838 fn from(s: &str) -> Self {
839 match s {
840 "claude_ai" => Self::ClaudeAi,
841 "claude_design" => Self::ClaudeDesign,
842 other => Self::Unknown(other.to_string()),
843 }
844 }
845}
846
847impl Serialize for RemedyLoginProvider {
848 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
849 serializer.serialize_str(self.as_str())
850 }
851}
852
853impl<'de> Deserialize<'de> for RemedyLoginProvider {
854 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
855 let s = String::deserialize(deserializer)?;
856 Ok(Self::from(s.as_str()))
857 }
858}
859
860#[derive(Debug, Clone, PartialEq, Eq, Hash)]
863pub enum RemedyFeature {
864 Workflows,
865 Artifacts,
866 Unknown(String),
868}
869
870impl RemedyFeature {
871 pub fn as_str(&self) -> &str {
872 match self {
873 Self::Workflows => "workflows",
874 Self::Artifacts => "artifacts",
875 Self::Unknown(s) => s.as_str(),
876 }
877 }
878}
879
880impl fmt::Display for RemedyFeature {
881 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
882 f.write_str(self.as_str())
883 }
884}
885
886impl From<&str> for RemedyFeature {
887 fn from(s: &str) -> Self {
888 match s {
889 "workflows" => Self::Workflows,
890 "artifacts" => Self::Artifacts,
891 other => Self::Unknown(other.to_string()),
892 }
893 }
894}
895
896impl Serialize for RemedyFeature {
897 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
898 serializer.serialize_str(self.as_str())
899 }
900}
901
902impl<'de> Deserialize<'de> for RemedyFeature {
903 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
904 let s = String::deserialize(deserializer)?;
905 Ok(Self::from(s.as_str()))
906 }
907}
908
909#[derive(Debug, Clone, PartialEq, Eq, Hash)]
912pub enum RemedyFeatureCause {
913 ManagedSettings,
916 OrgPolicy,
918 Unavailable,
920 UserSetting,
923 Unknown(String),
925}
926
927impl RemedyFeatureCause {
928 pub fn as_str(&self) -> &str {
929 match self {
930 Self::ManagedSettings => "managed_settings",
931 Self::OrgPolicy => "org_policy",
932 Self::Unavailable => "unavailable",
933 Self::UserSetting => "user_setting",
934 Self::Unknown(s) => s.as_str(),
935 }
936 }
937}
938
939impl fmt::Display for RemedyFeatureCause {
940 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
941 f.write_str(self.as_str())
942 }
943}
944
945impl From<&str> for RemedyFeatureCause {
946 fn from(s: &str) -> Self {
947 match s {
948 "managed_settings" => Self::ManagedSettings,
949 "org_policy" => Self::OrgPolicy,
950 "unavailable" => Self::Unavailable,
951 "user_setting" => Self::UserSetting,
952 other => Self::Unknown(other.to_string()),
953 }
954 }
955}
956
957impl Serialize for RemedyFeatureCause {
958 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
959 serializer.serialize_str(self.as_str())
960 }
961}
962
963impl<'de> Deserialize<'de> for RemedyFeatureCause {
964 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
965 let s = String::deserialize(deserializer)?;
966 Ok(Self::from(s.as_str()))
967 }
968}
969
970#[derive(Debug, Clone, PartialEq, Eq, Hash)]
973pub enum RemedyPolicyKind {
974 OrgDenied,
976 Latched,
979 Unknown(String),
981}
982
983impl RemedyPolicyKind {
984 pub fn as_str(&self) -> &str {
985 match self {
986 Self::OrgDenied => "org_denied",
987 Self::Latched => "latched",
988 Self::Unknown(s) => s.as_str(),
989 }
990 }
991}
992
993impl fmt::Display for RemedyPolicyKind {
994 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
995 f.write_str(self.as_str())
996 }
997}
998
999impl From<&str> for RemedyPolicyKind {
1000 fn from(s: &str) -> Self {
1001 match s {
1002 "org_denied" => Self::OrgDenied,
1003 "latched" => Self::Latched,
1004 other => Self::Unknown(other.to_string()),
1005 }
1006 }
1007}
1008
1009impl Serialize for RemedyPolicyKind {
1010 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1011 serializer.serialize_str(self.as_str())
1012 }
1013}
1014
1015impl<'de> Deserialize<'de> for RemedyPolicyKind {
1016 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1017 let s = String::deserialize(deserializer)?;
1018 Ok(Self::from(s.as_str()))
1019 }
1020}
1021
1022#[derive(Debug, Clone, Serialize, Deserialize)]
1024pub struct UserMessage {
1025 pub message: MessageContent,
1026 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1027 #[serde(
1028 serialize_with = "serialize_optional_uuid",
1029 deserialize_with = "deserialize_optional_uuid"
1030 )]
1031 pub session_id: Option<Uuid>,
1032 #[serde(skip_serializing_if = "Option::is_none")]
1034 pub parent_tool_use_id: Option<String>,
1035 #[serde(skip_serializing_if = "Option::is_none")]
1037 pub uuid: Option<String>,
1038 #[serde(skip_serializing_if = "Option::is_none")]
1040 pub timestamp: Option<String>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1048 pub tool_use_result: Option<serde_json::Value>,
1049 #[serde(skip_serializing_if = "Option::is_none")]
1052 pub subagent_type: Option<String>,
1053 #[serde(skip_serializing_if = "Option::is_none")]
1055 pub task_description: Option<String>,
1056 #[serde(skip_serializing_if = "Option::is_none")]
1057 pub origin: Option<MessageOrigin>,
1058 #[serde(skip_serializing_if = "Option::is_none")]
1059 pub priority: Option<String>,
1060 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1061 pub is_synthetic: Option<bool>,
1062 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1063 pub should_query: Option<bool>,
1064 #[serde(default, skip_serializing_if = "Option::is_none")]
1065 pub is_meta: Option<bool>,
1066 #[serde(default, skip_serializing_if = "Option::is_none")]
1067 pub is_visible_in_transcript_only: Option<bool>,
1068 #[serde(default, skip_serializing_if = "Option::is_none")]
1069 pub is_virtual: Option<bool>,
1070 #[serde(default, skip_serializing_if = "Option::is_none")]
1071 pub is_compact_summary: Option<bool>,
1072 #[serde(skip_serializing_if = "Option::is_none")]
1073 pub summarize_metadata: Option<SummarizeMetadata>,
1074 #[serde(skip_serializing_if = "Option::is_none")]
1075 pub mcp_meta: Option<McpMeta>,
1076 #[serde(skip_serializing_if = "Option::is_none")]
1079 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1080 #[serde(skip_serializing_if = "Option::is_none")]
1081 pub source_tool_use_id: Option<String>,
1082 #[serde(skip_serializing_if = "Option::is_none")]
1083 pub source_tool_assistant_uuid: Option<String>,
1084 #[serde(skip_serializing_if = "Option::is_none")]
1085 pub image_paste_ids: Option<Vec<u64>>,
1086 #[serde(skip_serializing_if = "Option::is_none")]
1087 pub client_platform: Option<String>,
1088 #[serde(skip_serializing_if = "Option::is_none")]
1089 pub inbound_origin: Option<String>,
1090 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1091 pub is_replay: Option<bool>,
1092 #[serde(skip_serializing_if = "Option::is_none")]
1093 pub file_attachments: Option<Vec<Value>>,
1094 #[serde(default, skip_serializing_if = "Option::is_none")]
1096 pub seeded_summon: Option<bool>,
1097 #[serde(default, skip_serializing_if = "Option::is_none")]
1100 pub client_composed: Option<bool>,
1101 #[serde(default, skip_serializing_if = "Option::is_none")]
1106 pub initiator: Option<String>,
1107 #[serde(default, skip_serializing_if = "Option::is_none")]
1113 pub pasted_content: Option<Vec<Value>>,
1114 #[serde(default, skip_serializing_if = "Option::is_none")]
1118 pub historical: Option<bool>,
1119 #[serde(default, skip_serializing_if = "Option::is_none")]
1125 pub server_received_wall_ms: Option<f64>,
1126}
1127
1128impl UserMessage {
1129 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1137 &self,
1138 ) -> Option<Result<T, serde_json::Error>> {
1139 self.tool_use_result
1140 .as_ref()
1141 .map(|v| serde_json::from_value(v.clone()))
1142 }
1143
1144 pub fn subagent_result(&self) -> Option<SubagentResult> {
1155 self.tool_use_result
1156 .as_ref()
1157 .and_then(|v| serde_json::from_value(v.clone()).ok())
1158 }
1159}
1160
1161#[derive(Debug, Clone, Serialize, Deserialize)]
1172pub struct SubagentResult {
1173 #[serde(skip_serializing_if = "Option::is_none")]
1175 pub status: Option<String>,
1176 #[serde(skip_serializing_if = "Option::is_none")]
1178 pub prompt: Option<String>,
1179 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1181 pub agent_id: Option<String>,
1182 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1184 pub agent_type: Option<String>,
1185 #[serde(
1187 default,
1188 deserialize_with = "deserialize_content_blocks",
1189 skip_serializing_if = "Vec::is_empty"
1190 )]
1191 pub content: Vec<ContentBlock>,
1192 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1194 pub resolved_model: Option<String>,
1195 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1197 pub total_duration_ms: Option<u64>,
1198 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1200 pub total_tokens: Option<u64>,
1201 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1203 pub total_tool_use_count: Option<u64>,
1204 #[serde(skip_serializing_if = "Option::is_none")]
1206 pub usage: Option<super::result::UsageInfo>,
1207 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1209 pub tool_stats: Option<SubagentToolStats>,
1210}
1211
1212#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1217#[serde(rename_all = "camelCase")]
1218pub struct SubagentToolStats {
1219 #[serde(default)]
1220 pub read_count: u64,
1221 #[serde(default)]
1222 pub search_count: u64,
1223 #[serde(default)]
1224 pub bash_count: u64,
1225 #[serde(default)]
1226 pub edit_file_count: u64,
1227 #[serde(default)]
1228 pub lines_added: u64,
1229 #[serde(default)]
1230 pub lines_removed: u64,
1231 #[serde(default)]
1232 pub other_tool_count: u64,
1233 #[serde(flatten)]
1234 pub extra: serde_json::Map<String, Value>,
1235}
1236
1237#[derive(Debug, Clone, Default, PartialEq, Eq)]
1264pub struct SubagentUsageRollup {
1265 pub subagent_tokens: u64,
1268 pub agent_count: u32,
1270 pub tool_uses: u64,
1272 pub duration_ms: u64,
1274 seen_agent_ids: std::collections::BTreeSet<String>,
1275}
1276
1277impl SubagentUsageRollup {
1278 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1284 match output {
1285 ClaudeOutput::User(user) => self.observe_user(user),
1286 _ => false,
1287 }
1288 }
1289
1290 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1297 let Some(result) = user.subagent_result() else {
1298 return false;
1299 };
1300 if result.total_tokens.is_none() && result.agent_id.is_none() {
1301 return false;
1302 }
1303 if let Some(agent_id) = &result.agent_id {
1304 if !self.seen_agent_ids.insert(agent_id.clone()) {
1305 return false;
1306 }
1307 }
1308 self.agent_count += 1;
1309 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1310 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1311 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1312 true
1313 }
1314}
1315
1316#[derive(Debug, Clone, Serialize, Deserialize)]
1318pub struct MessageContent {
1319 pub role: MessageRole,
1320 #[serde(deserialize_with = "deserialize_content_blocks")]
1321 pub content: Vec<ContentBlock>,
1322}
1323
1324#[derive(Debug, Clone, Serialize, Deserialize)]
1326pub struct SystemMessage {
1327 pub subtype: SystemSubtype,
1328 #[serde(flatten)]
1329 pub data: Value, }
1331
1332impl SystemMessage {
1333 pub fn is_init(&self) -> bool {
1335 self.subtype == SystemSubtype::Init
1336 }
1337
1338 pub fn is_status(&self) -> bool {
1340 self.subtype == SystemSubtype::Status
1341 }
1342
1343 pub fn is_compact_boundary(&self) -> bool {
1345 self.subtype == SystemSubtype::CompactBoundary
1346 }
1347
1348 pub fn as_init(&self) -> Option<InitMessage> {
1350 if self.subtype != SystemSubtype::Init {
1351 return None;
1352 }
1353 serde_json::from_value(self.data.clone()).ok()
1354 }
1355
1356 pub fn as_status(&self) -> Option<StatusMessage> {
1358 if self.subtype != SystemSubtype::Status {
1359 return None;
1360 }
1361 serde_json::from_value(self.data.clone()).ok()
1362 }
1363
1364 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1366 if self.subtype != SystemSubtype::CompactBoundary {
1367 return None;
1368 }
1369 serde_json::from_value(self.data.clone()).ok()
1370 }
1371
1372 pub fn is_task_started(&self) -> bool {
1374 self.subtype == SystemSubtype::TaskStarted
1375 }
1376
1377 pub fn is_task_progress(&self) -> bool {
1379 self.subtype == SystemSubtype::TaskProgress
1380 }
1381
1382 pub fn is_task_notification(&self) -> bool {
1384 self.subtype == SystemSubtype::TaskNotification
1385 }
1386
1387 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1389 if self.subtype != SystemSubtype::TaskStarted {
1390 return None;
1391 }
1392 serde_json::from_value(self.data.clone()).ok()
1393 }
1394
1395 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1397 if self.subtype != SystemSubtype::TaskProgress {
1398 return None;
1399 }
1400 serde_json::from_value(self.data.clone()).ok()
1401 }
1402
1403 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1405 if self.subtype != SystemSubtype::TaskNotification {
1406 return None;
1407 }
1408 serde_json::from_value(self.data.clone()).ok()
1409 }
1410
1411 pub fn is_task_updated(&self) -> bool {
1413 self.subtype == SystemSubtype::TaskUpdated
1414 }
1415
1416 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1418 if self.subtype != SystemSubtype::TaskUpdated {
1419 return None;
1420 }
1421 serde_json::from_value(self.data.clone()).ok()
1422 }
1423
1424 pub fn is_thinking_tokens(&self) -> bool {
1426 self.subtype == SystemSubtype::ThinkingTokens
1427 }
1428
1429 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1431 if self.subtype != SystemSubtype::ThinkingTokens {
1432 return None;
1433 }
1434 serde_json::from_value(self.data.clone()).ok()
1435 }
1436
1437 pub fn is_code_change_published(&self) -> bool {
1439 self.subtype == SystemSubtype::CodeChangePublished
1440 }
1441
1442 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1444 if self.subtype != SystemSubtype::CodeChangePublished {
1445 return None;
1446 }
1447 serde_json::from_value(self.data.clone()).ok()
1448 }
1449
1450 pub fn is_vcs_state_changed(&self) -> bool {
1452 self.subtype == SystemSubtype::VcsStateChanged
1453 }
1454
1455 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1457 if self.subtype != SystemSubtype::VcsStateChanged {
1458 return None;
1459 }
1460 serde_json::from_value(self.data.clone()).ok()
1461 }
1462
1463 pub fn is_feedback_draft_queued(&self) -> bool {
1465 self.subtype == SystemSubtype::FeedbackDraftQueued
1466 }
1467
1468 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1470 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1471 return None;
1472 }
1473 serde_json::from_value(self.data.clone()).ok()
1474 }
1475
1476 pub fn is_cloud_session_delta(&self) -> bool {
1478 self.subtype == SystemSubtype::CloudSessionDelta
1479 }
1480
1481 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1483 if self.subtype != SystemSubtype::CloudSessionDelta {
1484 return None;
1485 }
1486 serde_json::from_value(self.data.clone()).ok()
1487 }
1488
1489 pub fn is_dev_intent(&self) -> bool {
1491 self.subtype == SystemSubtype::DevIntent
1492 }
1493
1494 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1496 if self.subtype != SystemSubtype::DevIntent {
1497 return None;
1498 }
1499 serde_json::from_value(self.data.clone()).ok()
1500 }
1501
1502 pub fn is_turn_handoff_available(&self) -> bool {
1504 self.subtype == SystemSubtype::TurnHandoffAvailable
1505 }
1506
1507 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1509 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1510 return None;
1511 }
1512 serde_json::from_value(self.data.clone()).ok()
1513 }
1514
1515 pub fn is_turn_preempted(&self) -> bool {
1517 self.subtype == SystemSubtype::TurnPreempted
1518 }
1519
1520 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1522 if self.subtype != SystemSubtype::TurnPreempted {
1523 return None;
1524 }
1525 serde_json::from_value(self.data.clone()).ok()
1526 }
1527
1528 pub fn is_peer_message_hold(&self) -> bool {
1530 self.subtype == SystemSubtype::PeerMessageHold
1531 }
1532
1533 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1535 if self.subtype != SystemSubtype::PeerMessageHold {
1536 return None;
1537 }
1538 serde_json::from_value(self.data.clone()).ok()
1539 }
1540
1541 pub fn is_per_turn_effort_changed(&self) -> bool {
1543 self.subtype == SystemSubtype::PerTurnEffortChanged
1544 }
1545
1546 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1548 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1549 return None;
1550 }
1551 serde_json::from_value(self.data.clone()).ok()
1552 }
1553
1554 pub fn is_session_title_changed(&self) -> bool {
1556 self.subtype == SystemSubtype::SessionTitleChanged
1557 }
1558
1559 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1561 if self.subtype != SystemSubtype::SessionTitleChanged {
1562 return None;
1563 }
1564 serde_json::from_value(self.data.clone()).ok()
1565 }
1566
1567 pub fn is_instruction_size_warning(&self) -> bool {
1569 self.subtype == SystemSubtype::InstructionSizeWarning
1570 }
1571
1572 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1574 if self.subtype != SystemSubtype::InstructionSizeWarning {
1575 return None;
1576 }
1577 serde_json::from_value(self.data.clone()).ok()
1578 }
1579
1580 pub fn is_ui_focus(&self) -> bool {
1582 self.subtype == SystemSubtype::UiFocus
1583 }
1584
1585 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1587 if self.subtype != SystemSubtype::UiFocus {
1588 return None;
1589 }
1590 serde_json::from_value(self.data.clone()).ok()
1591 }
1592
1593 pub fn is_ui_invalidate(&self) -> bool {
1595 self.subtype == SystemSubtype::UiInvalidate
1596 }
1597
1598 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1600 if self.subtype != SystemSubtype::UiInvalidate {
1601 return None;
1602 }
1603 serde_json::from_value(self.data.clone()).ok()
1604 }
1605
1606 pub fn is_ui_log(&self) -> bool {
1608 self.subtype == SystemSubtype::UiLog
1609 }
1610
1611 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1613 if self.subtype != SystemSubtype::UiLog {
1614 return None;
1615 }
1616 serde_json::from_value(self.data.clone()).ok()
1617 }
1618
1619 pub fn is_ui_panes(&self) -> bool {
1621 self.subtype == SystemSubtype::UiPanes
1622 }
1623
1624 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1626 if self.subtype != SystemSubtype::UiPanes {
1627 return None;
1628 }
1629 serde_json::from_value(self.data.clone()).ok()
1630 }
1631
1632 pub fn is_ui_scroll(&self) -> bool {
1634 self.subtype == SystemSubtype::UiScroll
1635 }
1636
1637 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1639 if self.subtype != SystemSubtype::UiScroll {
1640 return None;
1641 }
1642 serde_json::from_value(self.data.clone()).ok()
1643 }
1644
1645 pub fn is_ui_status(&self) -> bool {
1647 self.subtype == SystemSubtype::UiStatus
1648 }
1649
1650 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1652 if self.subtype != SystemSubtype::UiStatus {
1653 return None;
1654 }
1655 serde_json::from_value(self.data.clone()).ok()
1656 }
1657
1658 pub fn is_ui_toast(&self) -> bool {
1660 self.subtype == SystemSubtype::UiToast
1661 }
1662
1663 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
1665 if self.subtype != SystemSubtype::UiToast {
1666 return None;
1667 }
1668 serde_json::from_value(self.data.clone()).ok()
1669 }
1670
1671 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1673 macro_rules! parse {
1674 ($variant:ident, $ty:ty) => {
1675 serde_json::from_value::<$ty>(self.data.clone())
1676 .ok()
1677 .map(KnownSystemEvent::$variant)
1678 };
1679 }
1680
1681 match self.subtype {
1682 SystemSubtype::Init => parse!(Init, InitMessage),
1683 SystemSubtype::Status => parse!(Status, StatusMessage),
1684 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1685 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1686 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1687 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1688 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1689 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1690 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1691 SystemSubtype::ControlRequestProgress => {
1692 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1693 }
1694 SystemSubtype::ModelRefusalFallback => {
1695 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1696 }
1697 SystemSubtype::ModelRefusalNoFallback => {
1698 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1699 }
1700 SystemSubtype::LocalCommandOutput => {
1701 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1702 }
1703 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1704 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1705 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1706 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1707 SystemSubtype::BackgroundTasksChanged => {
1708 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1709 }
1710 SystemSubtype::SessionStateChanged => {
1711 parse!(SessionStateChanged, SessionStateChangedMessage)
1712 }
1713 SystemSubtype::WorkerShuttingDown => {
1714 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1715 }
1716 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1717 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1718 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1719 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1720 SystemSubtype::ElicitationComplete => {
1721 parse!(ElicitationComplete, ElicitationCompleteMessage)
1722 }
1723 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1724 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1725 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1726 SystemSubtype::CodeChangePublished => {
1727 parse!(CodeChangePublished, CodeChangePublishedMessage)
1728 }
1729 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1730 SystemSubtype::FeedbackDraftQueued => {
1731 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1732 }
1733 SystemSubtype::CloudSessionDelta => {
1734 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1735 }
1736 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1737 SystemSubtype::TurnHandoffAvailable => {
1738 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1739 }
1740 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1741 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1742 SystemSubtype::PerTurnEffortChanged => {
1743 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1744 }
1745 SystemSubtype::SessionTitleChanged => {
1746 parse!(SessionTitleChanged, SessionTitleChangedMessage)
1747 }
1748 SystemSubtype::InstructionSizeWarning => {
1749 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
1750 }
1751 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
1752 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
1753 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
1754 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
1755 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
1756 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
1757 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
1758 SystemSubtype::Unknown(_) => None,
1759 }
1760 }
1761
1762 pub fn typed_value(&self) -> Option<Value> {
1771 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1772 parsed.and_then(|v| serde_json::to_value(v).ok())
1773 }
1774 match self.subtype {
1775 SystemSubtype::Init => reserialize(self.as_init()),
1776 SystemSubtype::Status => reserialize(self.as_status()),
1777 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1778 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1779 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1780 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1781 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1782 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1783 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1784 SystemSubtype::ControlRequestProgress => {
1785 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1786 }
1787 SystemSubtype::ModelRefusalFallback => {
1788 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1789 }
1790 SystemSubtype::ModelRefusalNoFallback => {
1791 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1792 }
1793 SystemSubtype::LocalCommandOutput => {
1794 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1795 }
1796 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1797 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1798 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1799 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1800 SystemSubtype::BackgroundTasksChanged => {
1801 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1802 }
1803 SystemSubtype::SessionStateChanged => {
1804 reserialize(parse_system::<SessionStateChangedMessage>(self))
1805 }
1806 SystemSubtype::WorkerShuttingDown => {
1807 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1808 }
1809 SystemSubtype::CommandsChanged => {
1810 reserialize(parse_system::<CommandsChangedMessage>(self))
1811 }
1812 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1813 SystemSubtype::FilesPersisted => {
1814 reserialize(parse_system::<FilesPersistedMessage>(self))
1815 }
1816 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1817 SystemSubtype::ElicitationComplete => {
1818 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1819 }
1820 SystemSubtype::PermissionDenied => {
1821 reserialize(parse_system::<PermissionDeniedMessage>(self))
1822 }
1823 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1824 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1825 SystemSubtype::CodeChangePublished => {
1826 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1827 }
1828 SystemSubtype::VcsStateChanged => {
1829 reserialize(parse_system::<VcsStateChangedMessage>(self))
1830 }
1831 SystemSubtype::FeedbackDraftQueued => {
1832 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1833 }
1834 SystemSubtype::CloudSessionDelta => {
1835 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1836 }
1837 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1838 SystemSubtype::TurnHandoffAvailable => {
1839 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1840 }
1841 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1842 SystemSubtype::PeerMessageHold => {
1843 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1844 }
1845 SystemSubtype::PerTurnEffortChanged => {
1846 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
1847 }
1848 SystemSubtype::SessionTitleChanged => {
1849 reserialize(parse_system::<SessionTitleChangedMessage>(self))
1850 }
1851 SystemSubtype::InstructionSizeWarning => {
1852 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
1853 }
1854 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
1855 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
1856 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
1857 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
1858 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
1859 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
1860 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
1861 SystemSubtype::Unknown(_) => None,
1862 }
1863 }
1864}
1865
1866fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1867 serde_json::from_value(message.data.clone()).ok()
1868}
1869
1870#[allow(clippy::large_enum_variant)]
1876#[derive(Debug, Clone, Serialize, Deserialize)]
1877pub enum KnownSystemEvent {
1878 Init(InitMessage),
1879 Status(StatusMessage),
1880 CompactBoundary(CompactBoundaryMessage),
1881 ThinkingTokens(ThinkingTokensMessage),
1882 TaskStarted(TaskStartedMessage),
1883 TaskProgress(TaskProgressMessage),
1884 TaskUpdated(TaskUpdatedMessage),
1885 TaskNotification(TaskNotificationMessage),
1886 ApiRetry(ApiRetryMessage),
1887 ControlRequestProgress(ControlRequestProgressMessage),
1888 ModelRefusalFallback(ModelRefusalFallbackMessage),
1889 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1890 LocalCommandOutput(LocalCommandOutputMessage),
1891 HookStarted(HookStartedMessage),
1892 HookProgress(HookProgressMessage),
1893 HookResponse(HookResponseMessage),
1894 PluginInstall(PluginInstallMessage),
1895 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1896 SessionStateChanged(SessionStateChangedMessage),
1897 WorkerShuttingDown(WorkerShuttingDownMessage),
1898 CommandsChanged(CommandsChangedMessage),
1899 Notification(NotificationMessage),
1900 FilesPersisted(FilesPersistedMessage),
1901 MemoryRecall(MemoryRecallMessage),
1902 ElicitationComplete(ElicitationCompleteMessage),
1903 PermissionDenied(PermissionDeniedMessage),
1904 MirrorError(MirrorErrorMessage),
1905 Informational(InformationalMessage),
1906 CodeChangePublished(CodeChangePublishedMessage),
1907 VcsStateChanged(VcsStateChangedMessage),
1908 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1909 CloudSessionDelta(CloudSessionDeltaMessage),
1910 DevIntent(DevIntentMessage),
1911 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1912 TurnPreempted(TurnPreemptedMessage),
1913 PeerMessageHold(PeerMessageHoldMessage),
1914 PerTurnEffortChanged(PerTurnEffortChangedMessage),
1915 SessionTitleChanged(SessionTitleChangedMessage),
1916 InstructionSizeWarning(InstructionSizeWarningMessage),
1917 UiFocus(UiFocusMessage),
1918 UiInvalidate(UiInvalidateMessage),
1919 UiLog(UiLogMessage),
1920 UiPanes(UiPanesMessage),
1921 UiScroll(UiScrollMessage),
1922 UiStatus(UiStatusMessage),
1923 UiToast(UiToastMessage),
1924}
1925
1926#[derive(Debug, Clone, Serialize, Deserialize)]
1927pub struct ApiRetryMessage {
1928 pub attempt: u64,
1929 pub max_retries: u64,
1930 pub retry_delay_ms: u64,
1931 pub error_status: Option<u16>,
1932 pub error: String,
1933 #[serde(default, skip_serializing_if = "Option::is_none")]
1938 pub no_response: Option<ApiRetryNoResponse>,
1939 #[serde(default, skip_serializing_if = "Option::is_none")]
1940 pub uuid: Option<String>,
1941 #[serde(default, skip_serializing_if = "Option::is_none")]
1942 pub session_id: Option<String>,
1943}
1944
1945#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1948pub struct ApiRetryNoResponse {
1949 pub waited_ms: u64,
1951 pub retry_wait_ms: u64,
1953}
1954
1955#[derive(Debug, Clone, Serialize, Deserialize)]
1956pub struct ControlRequestProgressMessage {
1957 pub request_id: String,
1958 pub status: String,
1959 #[serde(default, skip_serializing_if = "Option::is_none")]
1960 pub attempt: Option<u64>,
1961 #[serde(default, skip_serializing_if = "Option::is_none")]
1962 pub max_retries: Option<u64>,
1963 #[serde(default, skip_serializing_if = "Option::is_none")]
1964 pub retry_delay_ms: Option<u64>,
1965 #[serde(default, skip_serializing_if = "Option::is_none")]
1966 pub error_status: Option<u16>,
1967 #[serde(default, skip_serializing_if = "Option::is_none")]
1968 pub error: Option<String>,
1969 #[serde(default, skip_serializing_if = "Option::is_none")]
1970 pub uuid: Option<String>,
1971 #[serde(default, skip_serializing_if = "Option::is_none")]
1972 pub session_id: Option<String>,
1973}
1974
1975#[derive(Debug, Clone, Serialize, Deserialize)]
1976pub struct ModelRefusalFallbackMessage {
1977 pub trigger: String,
1978 pub direction: String,
1979 #[serde(default, skip_serializing_if = "Option::is_none")]
1985 pub scope: Option<RefusalFallbackScope>,
1986 pub original_model: String,
1987 pub fallback_model: String,
1988 pub request_id: Option<String>,
1989 #[serde(default, skip_serializing_if = "Option::is_none")]
1990 pub api_refusal_category: Option<String>,
1991 #[serde(default, skip_serializing_if = "Option::is_none")]
1996 pub saw_cyber_refusal: Option<bool>,
1997 #[serde(default, skip_serializing_if = "Option::is_none")]
1998 pub api_refusal_explanation: Option<String>,
1999 #[serde(default, skip_serializing_if = "Option::is_none")]
2000 pub retracted_message_uuids: Option<Vec<String>>,
2001 #[serde(default, skip_serializing_if = "Option::is_none")]
2002 pub refused_user_message_uuid: Option<String>,
2003 pub content: Value,
2004 #[serde(default, skip_serializing_if = "Option::is_none")]
2005 pub uuid: Option<String>,
2006 #[serde(default, skip_serializing_if = "Option::is_none")]
2007 pub session_id: Option<String>,
2008}
2009
2010#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2014pub enum RefusalFallbackScope {
2015 Session,
2017 Local,
2020 Unknown(String),
2022}
2023
2024impl RefusalFallbackScope {
2025 pub fn as_str(&self) -> &str {
2026 match self {
2027 Self::Session => "session",
2028 Self::Local => "local",
2029 Self::Unknown(s) => s.as_str(),
2030 }
2031 }
2032}
2033
2034impl fmt::Display for RefusalFallbackScope {
2035 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2036 f.write_str(self.as_str())
2037 }
2038}
2039
2040impl From<&str> for RefusalFallbackScope {
2041 fn from(s: &str) -> Self {
2042 match s {
2043 "session" => Self::Session,
2044 "local" => Self::Local,
2045 other => Self::Unknown(other.to_string()),
2046 }
2047 }
2048}
2049
2050impl Serialize for RefusalFallbackScope {
2051 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2052 serializer.serialize_str(self.as_str())
2053 }
2054}
2055
2056impl<'de> Deserialize<'de> for RefusalFallbackScope {
2057 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2058 let s = String::deserialize(deserializer)?;
2059 Ok(Self::from(s.as_str()))
2060 }
2061}
2062
2063#[derive(Debug, Clone, Serialize, Deserialize)]
2064pub struct ModelRefusalNoFallbackMessage {
2065 pub original_model: String,
2066 pub request_id: Option<String>,
2067 #[serde(default, skip_serializing_if = "Option::is_none")]
2068 pub api_refusal_category: Option<String>,
2069 #[serde(default, skip_serializing_if = "Option::is_none")]
2070 pub api_refusal_explanation: Option<String>,
2071 pub content: Value,
2072 #[serde(default, skip_serializing_if = "Option::is_none")]
2073 pub uuid: Option<String>,
2074 #[serde(default, skip_serializing_if = "Option::is_none")]
2075 pub session_id: Option<String>,
2076}
2077
2078#[derive(Debug, Clone, Serialize, Deserialize)]
2079pub struct LocalCommandOutputMessage {
2080 pub content: String,
2081 #[serde(default, skip_serializing_if = "Option::is_none")]
2082 pub uuid: Option<String>,
2083 #[serde(default, skip_serializing_if = "Option::is_none")]
2084 pub session_id: Option<String>,
2085}
2086
2087#[derive(Debug, Clone, Serialize, Deserialize)]
2088pub struct HookStartedMessage {
2089 pub hook_id: String,
2090 pub hook_name: String,
2091 pub hook_event: String,
2092 #[serde(default, skip_serializing_if = "Option::is_none")]
2093 pub uuid: Option<String>,
2094 #[serde(default, skip_serializing_if = "Option::is_none")]
2095 pub session_id: Option<String>,
2096}
2097
2098#[derive(Debug, Clone, Serialize, Deserialize)]
2099pub struct HookProgressMessage {
2100 pub hook_id: String,
2101 pub hook_name: String,
2102 pub hook_event: String,
2103 #[serde(default, skip_serializing_if = "Option::is_none")]
2104 pub stdout: Option<String>,
2105 #[serde(default, skip_serializing_if = "Option::is_none")]
2106 pub stderr: Option<String>,
2107 #[serde(default, skip_serializing_if = "Option::is_none")]
2108 pub output: Option<String>,
2109 #[serde(default, skip_serializing_if = "Option::is_none")]
2110 pub uuid: Option<String>,
2111 #[serde(default, skip_serializing_if = "Option::is_none")]
2112 pub session_id: Option<String>,
2113}
2114
2115#[derive(Debug, Clone, Serialize, Deserialize)]
2116pub struct HookResponseMessage {
2117 pub hook_id: String,
2118 pub hook_name: String,
2119 pub hook_event: String,
2120 #[serde(default, skip_serializing_if = "Option::is_none")]
2121 pub stdout: Option<String>,
2122 #[serde(default, skip_serializing_if = "Option::is_none")]
2123 pub stderr: Option<String>,
2124 #[serde(default, skip_serializing_if = "Option::is_none")]
2125 pub output: Option<String>,
2126 #[serde(default, skip_serializing_if = "Option::is_none")]
2127 pub exit_code: Option<i32>,
2128 pub outcome: String,
2129 #[serde(default, skip_serializing_if = "Option::is_none")]
2130 pub uuid: Option<String>,
2131 #[serde(default, skip_serializing_if = "Option::is_none")]
2132 pub session_id: Option<String>,
2133}
2134
2135#[derive(Debug, Clone, Serialize, Deserialize)]
2136pub struct PluginInstallMessage {
2137 pub status: String,
2138 #[serde(default, skip_serializing_if = "Option::is_none")]
2139 pub name: Option<String>,
2140 #[serde(default, skip_serializing_if = "Option::is_none")]
2141 pub error: Option<String>,
2142 #[serde(default, skip_serializing_if = "Option::is_none")]
2143 pub uuid: Option<String>,
2144 #[serde(default, skip_serializing_if = "Option::is_none")]
2145 pub session_id: Option<String>,
2146}
2147
2148#[derive(Debug, Clone, Serialize, Deserialize)]
2149pub struct BackgroundTasksChangedMessage {
2150 pub tasks: Vec<BackgroundTaskInfo>,
2151 #[serde(default, skip_serializing_if = "Option::is_none")]
2152 pub uuid: Option<String>,
2153 #[serde(default, skip_serializing_if = "Option::is_none")]
2154 pub session_id: Option<String>,
2155}
2156
2157#[derive(Debug, Clone, Serialize, Deserialize)]
2158pub struct BackgroundTaskInfo {
2159 pub task_id: String,
2160 pub task_type: String,
2161 pub description: String,
2162 #[serde(default, skip_serializing_if = "Option::is_none")]
2165 pub ambient: Option<bool>,
2166}
2167
2168#[derive(Debug, Clone, Serialize, Deserialize)]
2169pub struct SessionStateChangedMessage {
2170 pub state: String,
2171 #[serde(default, skip_serializing_if = "Option::is_none")]
2172 pub uuid: Option<String>,
2173 #[serde(default, skip_serializing_if = "Option::is_none")]
2174 pub session_id: Option<String>,
2175}
2176
2177#[derive(Debug, Clone, Serialize, Deserialize)]
2178pub struct WorkerShuttingDownMessage {
2179 pub reason: String,
2180 #[serde(default, skip_serializing_if = "Option::is_none")]
2181 pub uuid: Option<String>,
2182 #[serde(default, skip_serializing_if = "Option::is_none")]
2183 pub session_id: Option<String>,
2184}
2185
2186#[derive(Debug, Clone, Serialize, Deserialize)]
2187pub struct CommandsChangedMessage {
2188 pub commands: Vec<CommandInfo>,
2189 #[serde(default, skip_serializing_if = "Option::is_none")]
2190 pub uuid: Option<String>,
2191 #[serde(default, skip_serializing_if = "Option::is_none")]
2192 pub session_id: Option<String>,
2193}
2194
2195#[derive(Debug, Clone, Serialize, Deserialize)]
2196pub struct CommandInfo {
2197 pub name: String,
2198 pub description: String,
2199 #[serde(rename = "argumentHint")]
2200 pub argument_hint: String,
2201 #[serde(default, skip_serializing_if = "Option::is_none")]
2202 pub aliases: Option<Vec<String>>,
2203 #[serde(default, skip_serializing_if = "Option::is_none")]
2209 pub builtin: Option<bool>,
2210}
2211
2212#[derive(Debug, Clone, Serialize, Deserialize)]
2213pub struct NotificationMessage {
2214 pub key: String,
2215 pub text: String,
2216 pub priority: String,
2217 #[serde(default, skip_serializing_if = "Option::is_none")]
2218 pub color: Option<String>,
2219 #[serde(default, skip_serializing_if = "Option::is_none")]
2220 pub timeout_ms: Option<u64>,
2221 #[serde(default, skip_serializing_if = "Option::is_none")]
2222 pub uuid: Option<String>,
2223 #[serde(default, skip_serializing_if = "Option::is_none")]
2224 pub session_id: Option<String>,
2225}
2226
2227#[derive(Debug, Clone, Serialize, Deserialize)]
2228pub struct FilesPersistedMessage {
2229 pub files: Vec<PersistedFile>,
2230 pub failed: Vec<FailedPersistedFile>,
2231 pub processed_at: String,
2232 #[serde(default, skip_serializing_if = "Option::is_none")]
2233 pub uuid: Option<String>,
2234 #[serde(default, skip_serializing_if = "Option::is_none")]
2235 pub session_id: Option<String>,
2236}
2237
2238#[derive(Debug, Clone, Serialize, Deserialize)]
2239pub struct PersistedFile {
2240 pub filename: String,
2241 pub file_id: String,
2242}
2243
2244#[derive(Debug, Clone, Serialize, Deserialize)]
2245pub struct FailedPersistedFile {
2246 pub filename: String,
2247 pub error: String,
2248}
2249
2250#[derive(Debug, Clone, Serialize, Deserialize)]
2251pub struct MemoryRecallMessage {
2252 pub mode: String,
2253 pub memories: Vec<MemoryRecallItem>,
2254 #[serde(default, skip_serializing_if = "Option::is_none")]
2255 pub uuid: Option<String>,
2256 #[serde(default, skip_serializing_if = "Option::is_none")]
2257 pub session_id: Option<String>,
2258}
2259
2260#[derive(Debug, Clone, Serialize, Deserialize)]
2261pub struct MemoryRecallItem {
2262 pub path: String,
2263 pub scope: String,
2264 #[serde(default, skip_serializing_if = "Option::is_none")]
2265 pub content: Option<String>,
2266}
2267
2268#[derive(Debug, Clone, Serialize, Deserialize)]
2269pub struct ElicitationCompleteMessage {
2270 pub mcp_server_name: String,
2271 pub elicitation_id: String,
2272 #[serde(default, skip_serializing_if = "Option::is_none")]
2273 pub uuid: Option<String>,
2274 #[serde(default, skip_serializing_if = "Option::is_none")]
2275 pub session_id: Option<String>,
2276}
2277
2278#[derive(Debug, Clone, Serialize, Deserialize)]
2279pub struct PermissionDeniedMessage {
2280 pub tool_name: String,
2281 pub tool_use_id: String,
2282 #[serde(default, skip_serializing_if = "Option::is_none")]
2283 pub agent_id: Option<String>,
2284 #[serde(default, skip_serializing_if = "Option::is_none")]
2285 pub decision_reason_type: Option<String>,
2286 #[serde(default, skip_serializing_if = "Option::is_none")]
2290 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2291 #[serde(default, skip_serializing_if = "Option::is_none")]
2292 pub decision_reason: Option<String>,
2293 pub message: String,
2294 #[serde(default, skip_serializing_if = "Option::is_none")]
2295 pub uuid: Option<String>,
2296 #[serde(default, skip_serializing_if = "Option::is_none")]
2297 pub session_id: Option<String>,
2298}
2299
2300#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2303pub enum PermissionDeniedReasonCode {
2304 ClassifierTranscriptTooLong,
2306 OutsideReadsBlocked,
2308 MemoryPaused,
2310 Unknown(String),
2312}
2313
2314impl PermissionDeniedReasonCode {
2315 pub fn as_str(&self) -> &str {
2316 match self {
2317 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2318 Self::OutsideReadsBlocked => "outside_reads_blocked",
2319 Self::MemoryPaused => "memory_paused",
2320 Self::Unknown(s) => s.as_str(),
2321 }
2322 }
2323}
2324
2325impl fmt::Display for PermissionDeniedReasonCode {
2326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2327 f.write_str(self.as_str())
2328 }
2329}
2330
2331impl From<&str> for PermissionDeniedReasonCode {
2332 fn from(s: &str) -> Self {
2333 match s {
2334 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2335 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2336 "memory_paused" => Self::MemoryPaused,
2337 other => Self::Unknown(other.to_string()),
2338 }
2339 }
2340}
2341
2342impl Serialize for PermissionDeniedReasonCode {
2343 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2344 serializer.serialize_str(self.as_str())
2345 }
2346}
2347
2348impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2349 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2350 let s = String::deserialize(deserializer)?;
2351 Ok(Self::from(s.as_str()))
2352 }
2353}
2354
2355#[derive(Debug, Clone, Serialize, Deserialize)]
2356pub struct MirrorErrorMessage {
2357 pub error: String,
2358 pub key: MirrorErrorKey,
2359 #[serde(default, skip_serializing_if = "Option::is_none")]
2360 pub uuid: Option<String>,
2361 #[serde(default, skip_serializing_if = "Option::is_none")]
2362 pub session_id: Option<String>,
2363}
2364
2365#[derive(Debug, Clone, Serialize, Deserialize)]
2366pub struct MirrorErrorKey {
2367 #[serde(rename = "projectKey")]
2368 pub project_key: String,
2369 #[serde(rename = "sessionId")]
2370 pub session_id: String,
2371 #[serde(default, skip_serializing_if = "Option::is_none")]
2372 pub subpath: Option<String>,
2373}
2374
2375#[derive(Debug, Clone, Serialize, Deserialize)]
2376pub struct InformationalMessage {
2377 pub content: String,
2378 pub level: String,
2379 #[serde(default, skip_serializing_if = "Option::is_none")]
2380 pub tool_use_id: Option<String>,
2381 #[serde(default, skip_serializing_if = "Option::is_none")]
2382 pub prevent_continuation: Option<bool>,
2383 #[serde(default, skip_serializing_if = "Option::is_none")]
2386 pub tag: Option<String>,
2387 #[serde(default, skip_serializing_if = "Option::is_none")]
2388 pub uuid: Option<String>,
2389 #[serde(default, skip_serializing_if = "Option::is_none")]
2390 pub session_id: Option<String>,
2391}
2392
2393#[derive(Debug, Clone, Serialize, Deserialize)]
2395pub struct PluginInfo {
2396 pub name: String,
2398 pub path: String,
2400 #[serde(skip_serializing_if = "Option::is_none")]
2402 pub source: Option<String>,
2403 #[serde(default, skip_serializing_if = "Option::is_none")]
2405 pub version: Option<String>,
2406}
2407
2408#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2410pub struct PluginDiagnostic {
2411 pub plugin: String,
2412 #[serde(rename = "type")]
2413 pub diagnostic_type: String,
2414 pub message: String,
2415}
2416
2417#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2419pub struct MemoryPaths {
2420 #[serde(default, skip_serializing_if = "Option::is_none")]
2421 pub auto: Option<String>,
2422 #[serde(default, skip_serializing_if = "Option::is_none")]
2423 pub team: Option<String>,
2424 #[serde(flatten)]
2425 pub extra: serde_json::Map<String, Value>,
2426}
2427
2428#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2432pub struct McpServerError {
2433 pub name: String,
2434 #[serde(rename = "type")]
2436 pub error_type: String,
2437 pub message: String,
2438}
2439
2440#[derive(Debug, Clone, Serialize, Deserialize)]
2442pub struct InitMessage {
2443 pub session_id: String,
2445 #[serde(skip_serializing_if = "Option::is_none")]
2447 pub cwd: Option<String>,
2448 #[serde(skip_serializing_if = "Option::is_none")]
2450 pub model: Option<String>,
2451 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2453 pub tools: Vec<String>,
2454 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2456 pub mcp_servers: Vec<Value>,
2457 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2459 pub slash_commands: Vec<String>,
2460 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2463 pub terminal_slash_commands: Vec<String>,
2464 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2466 pub agents: Vec<String>,
2467 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2469 pub plugins: Vec<PluginInfo>,
2470 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2472 pub skills: Vec<Value>,
2473 #[serde(skip_serializing_if = "Option::is_none")]
2475 pub claude_code_version: Option<String>,
2476 #[serde(skip_serializing_if = "Option::is_none")]
2479 pub messaging_socket_path: Option<String>,
2480 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2482 pub api_key_source: Option<ApiKeySource>,
2483 #[serde(skip_serializing_if = "Option::is_none")]
2485 pub output_style: Option<OutputStyle>,
2486 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2488 pub permission_mode: Option<InitPermissionMode>,
2489
2490 #[serde(skip_serializing_if = "Option::is_none")]
2492 pub uuid: Option<String>,
2493
2494 #[serde(skip_serializing_if = "Option::is_none")]
2496 pub memory_paths: Option<MemoryPaths>,
2497
2498 #[serde(default, skip_serializing_if = "Option::is_none")]
2503 pub scratchpad_path: Option<String>,
2504
2505 #[serde(skip_serializing_if = "Option::is_none")]
2507 pub fast_mode_state: Option<String>,
2508
2509 #[serde(default, skip_serializing_if = "Option::is_none")]
2511 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2512
2513 #[serde(default, skip_serializing_if = "Option::is_none")]
2516 pub mcp_server_errors: Option<Vec<McpServerError>>,
2517
2518 #[serde(default, skip_serializing_if = "Option::is_none")]
2520 pub analytics_disabled: Option<bool>,
2521
2522 #[serde(default, skip_serializing_if = "Option::is_none")]
2524 pub product_feedback_disabled: Option<bool>,
2525
2526 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2528 pub betas: Vec<String>,
2529
2530 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2532 pub capabilities: Vec<String>,
2533
2534 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2536 pub plugin_errors: Vec<PluginDiagnostic>,
2537
2538 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2540 pub plugin_warnings: Vec<PluginDiagnostic>,
2541
2542 #[serde(default, skip_serializing_if = "Option::is_none")]
2547 pub effort: Option<String>,
2548
2549 #[serde(default, skip_serializing_if = "Option::is_none")]
2554 pub cloud_session: Option<Value>,
2555
2556 #[serde(default, skip_serializing_if = "Option::is_none")]
2560 pub footer_indicator: Option<FooterIndicator>,
2561
2562 #[serde(default, skip_serializing_if = "Option::is_none")]
2566 pub worker_epoch: Option<u64>,
2567
2568 #[serde(default, skip_serializing_if = "Option::is_none")]
2572 pub powershell_path: Option<Option<String>>,
2573
2574 #[serde(default, skip_serializing_if = "Option::is_none")]
2578 pub startup_timing: Option<serde_json::Map<String, Value>>,
2579
2580 #[serde(default, skip_serializing_if = "Option::is_none")]
2590 pub per_turn_effort_active: Option<bool>,
2591
2592 #[serde(default, skip_serializing_if = "Option::is_none")]
2599 pub view_mode: Option<ViewMode>,
2600}
2601
2602#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2606pub struct FooterIndicator {
2607 pub text: String,
2610}
2611
2612#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2615pub enum ViewMode {
2616 Focus,
2619 Default,
2621 Unknown(String),
2623}
2624
2625impl ViewMode {
2626 pub fn as_str(&self) -> &str {
2627 match self {
2628 Self::Focus => "focus",
2629 Self::Default => "default",
2630 Self::Unknown(s) => s.as_str(),
2631 }
2632 }
2633}
2634
2635impl fmt::Display for ViewMode {
2636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2637 f.write_str(self.as_str())
2638 }
2639}
2640
2641impl From<&str> for ViewMode {
2642 fn from(s: &str) -> Self {
2643 match s {
2644 "focus" => Self::Focus,
2645 "default" => Self::Default,
2646 other => Self::Unknown(other.to_string()),
2647 }
2648 }
2649}
2650
2651impl Serialize for ViewMode {
2652 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2653 serializer.serialize_str(self.as_str())
2654 }
2655}
2656
2657impl<'de> Deserialize<'de> for ViewMode {
2658 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2659 let s = String::deserialize(deserializer)?;
2660 Ok(Self::from(s.as_str()))
2661 }
2662}
2663
2664#[derive(Debug, Clone, Serialize, Deserialize)]
2666pub struct StatusMessage {
2667 pub session_id: String,
2669 pub status: Option<StatusMessageStatus>,
2671 #[serde(skip_serializing_if = "Option::is_none")]
2673 pub uuid: Option<String>,
2674 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2676 pub permission_mode: Option<InitPermissionMode>,
2677 #[serde(skip_serializing_if = "Option::is_none")]
2678 pub compact_result: Option<String>,
2679 #[serde(skip_serializing_if = "Option::is_none")]
2680 pub compact_error: Option<String>,
2681}
2682
2683#[derive(Debug, Clone, Serialize, Deserialize)]
2685pub struct CompactBoundaryMessage {
2686 pub session_id: String,
2688 pub compact_metadata: CompactMetadata,
2690 #[serde(
2694 default,
2695 skip_serializing_if = "Option::is_none",
2696 alias = "content",
2697 alias = "text"
2698 )]
2699 pub summary: Option<String>,
2700 #[serde(
2704 default,
2705 skip_serializing_if = "Option::is_none",
2706 alias = "message_count"
2707 )]
2708 pub leaf_message_count: Option<u32>,
2709 #[serde(default, skip_serializing_if = "Option::is_none")]
2711 pub duration_ms: Option<u64>,
2712 #[serde(skip_serializing_if = "Option::is_none")]
2714 pub uuid: Option<String>,
2715 #[serde(skip_serializing_if = "Option::is_none")]
2717 pub logical_parent_uuid: Option<Option<String>>,
2718 #[serde(default, skip_serializing_if = "Option::is_none")]
2722 pub historical: Option<bool>,
2723}
2724
2725#[derive(Debug, Clone, Serialize, Deserialize)]
2727pub struct CompactMetadata {
2728 pub pre_tokens: u64,
2730 pub trigger: CompactionTrigger,
2732 #[serde(default, skip_serializing_if = "Option::is_none")]
2733 pub post_tokens: Option<u64>,
2734 #[serde(default, skip_serializing_if = "Option::is_none")]
2735 pub cumulative_dropped_tokens: Option<u64>,
2736 #[serde(default, skip_serializing_if = "Option::is_none")]
2737 pub duration_ms: Option<u64>,
2738 #[serde(default, skip_serializing_if = "Option::is_none")]
2739 pub user_context: Option<String>,
2740 #[serde(default, skip_serializing_if = "Option::is_none")]
2741 pub messages_summarized: Option<u64>,
2742 #[serde(default, skip_serializing_if = "Option::is_none")]
2743 pub precomputed: Option<bool>,
2744 #[serde(default, skip_serializing_if = "Option::is_none")]
2745 pub pre_compact_discovered_tools: Option<Vec<String>>,
2746 #[serde(default, skip_serializing_if = "Option::is_none")]
2751 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
2752 #[serde(default, skip_serializing_if = "Option::is_none")]
2753 pub preserved_segment: Option<PreservedSegment>,
2754 #[serde(default, skip_serializing_if = "Option::is_none")]
2755 pub preserved_messages: Option<PreservedMessages>,
2756}
2757
2758#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2761pub struct ArtifactReadVersion {
2762 pub slug: String,
2764 pub ver: String,
2766}
2767
2768#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2769pub struct PreservedSegment {
2770 pub head_uuid: String,
2771 pub anchor_uuid: String,
2772 pub tail_uuid: String,
2773}
2774
2775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2776pub struct PreservedMessages {
2777 pub anchor_uuid: String,
2778 pub uuids: Vec<String>,
2779 #[serde(default, skip_serializing_if = "Option::is_none")]
2780 pub all_uuids: Option<Vec<String>>,
2781}
2782
2783#[derive(Debug, Clone, Serialize, Deserialize)]
2789pub struct TaskUsage {
2790 pub duration_ms: u64,
2792 pub tool_uses: u64,
2794 pub total_tokens: u64,
2796}
2797
2798#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2800pub enum TaskType {
2801 LocalAgent,
2803 LocalBash,
2805 LocalWorkflow,
2807 Unknown(String),
2809}
2810
2811impl TaskType {
2812 pub fn as_str(&self) -> &str {
2813 match self {
2814 Self::LocalAgent => "local_agent",
2815 Self::LocalBash => "local_bash",
2816 Self::LocalWorkflow => "local_workflow",
2817 Self::Unknown(s) => s.as_str(),
2818 }
2819 }
2820}
2821
2822impl fmt::Display for TaskType {
2823 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2824 f.write_str(self.as_str())
2825 }
2826}
2827
2828impl From<&str> for TaskType {
2829 fn from(s: &str) -> Self {
2830 match s {
2831 "local_agent" => Self::LocalAgent,
2832 "local_bash" => Self::LocalBash,
2833 "local_workflow" => Self::LocalWorkflow,
2834 other => Self::Unknown(other.to_string()),
2835 }
2836 }
2837}
2838
2839impl Serialize for TaskType {
2840 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2841 serializer.serialize_str(self.as_str())
2842 }
2843}
2844
2845impl<'de> Deserialize<'de> for TaskType {
2846 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2847 let s = String::deserialize(deserializer)?;
2848 Ok(Self::from(s.as_str()))
2849 }
2850}
2851
2852#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2854pub enum TaskStatus {
2855 Pending,
2856 Running,
2857 Completed,
2858 Failed,
2859 Killed,
2860 Paused,
2861 Stopped,
2862 Unknown(String),
2863}
2864
2865impl TaskStatus {
2866 pub fn as_str(&self) -> &str {
2867 match self {
2868 Self::Pending => "pending",
2869 Self::Running => "running",
2870 Self::Completed => "completed",
2871 Self::Failed => "failed",
2872 Self::Killed => "killed",
2873 Self::Paused => "paused",
2874 Self::Stopped => "stopped",
2875 Self::Unknown(s) => s.as_str(),
2876 }
2877 }
2878}
2879
2880impl fmt::Display for TaskStatus {
2881 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2882 f.write_str(self.as_str())
2883 }
2884}
2885
2886impl From<&str> for TaskStatus {
2887 fn from(s: &str) -> Self {
2888 match s {
2889 "pending" => Self::Pending,
2890 "running" => Self::Running,
2891 "completed" => Self::Completed,
2892 "failed" => Self::Failed,
2893 "killed" => Self::Killed,
2894 "paused" => Self::Paused,
2895 "stopped" => Self::Stopped,
2896 other => Self::Unknown(other.to_string()),
2897 }
2898 }
2899}
2900
2901impl Serialize for TaskStatus {
2902 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2903 serializer.serialize_str(self.as_str())
2904 }
2905}
2906
2907impl<'de> Deserialize<'de> for TaskStatus {
2908 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2909 let s = String::deserialize(deserializer)?;
2910 Ok(Self::from(s.as_str()))
2911 }
2912}
2913
2914#[derive(Debug, Clone, Serialize, Deserialize)]
2916pub struct TaskStartedMessage {
2917 pub session_id: String,
2918 pub task_id: String,
2919 #[serde(default, skip_serializing_if = "Option::is_none")]
2920 pub task_type: Option<TaskType>,
2921 #[serde(default, skip_serializing_if = "Option::is_none")]
2922 pub tool_use_id: Option<String>,
2923 pub description: String,
2924 #[serde(default, skip_serializing_if = "Option::is_none")]
2927 pub subagent_type: Option<String>,
2928 #[serde(default, skip_serializing_if = "Option::is_none")]
2934 pub is_backgrounded: Option<bool>,
2935 #[serde(default, skip_serializing_if = "Option::is_none")]
2939 pub spawn_depth: Option<u32>,
2940 #[serde(default, skip_serializing_if = "Option::is_none")]
2942 pub prompt: Option<String>,
2943 #[serde(default, skip_serializing_if = "Option::is_none")]
2944 pub workflow_name: Option<String>,
2945 #[serde(default, skip_serializing_if = "Option::is_none")]
2946 pub skip_transcript: Option<bool>,
2947 #[serde(default, skip_serializing_if = "Option::is_none")]
2952 pub ambient: Option<bool>,
2953 pub uuid: String,
2954}
2955
2956#[derive(Debug, Clone, Serialize, Deserialize)]
2960pub struct TaskUpdatedMessage {
2961 pub session_id: String,
2962 pub task_id: String,
2963 pub patch: TaskPatch,
2964 pub uuid: String,
2965}
2966
2967#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2970pub struct TaskPatch {
2971 #[serde(default, skip_serializing_if = "Option::is_none")]
2972 pub status: Option<TaskStatus>,
2973 #[serde(default, skip_serializing_if = "Option::is_none")]
2976 pub end_time: Option<u64>,
2977 #[serde(default, skip_serializing_if = "Option::is_none")]
2978 pub description: Option<String>,
2979 #[serde(default, skip_serializing_if = "Option::is_none")]
2980 pub total_paused_ms: Option<u64>,
2981 #[serde(default, skip_serializing_if = "Option::is_none")]
2982 pub error: Option<String>,
2983 #[serde(default, skip_serializing_if = "Option::is_none")]
2984 pub is_backgrounded: Option<bool>,
2985}
2986
2987#[derive(Debug, Clone, Serialize, Deserialize)]
2990pub struct ThinkingTokensMessage {
2991 pub session_id: String,
2992 pub estimated_tokens: u64,
2994 pub estimated_tokens_delta: u64,
2996 #[serde(default, skip_serializing_if = "Option::is_none")]
3003 pub user_message_uuid: Option<String>,
3004 pub uuid: String,
3005}
3006
3007#[derive(Debug, Clone, Serialize, Deserialize)]
3010pub struct TaskProgressMessage {
3011 pub session_id: String,
3012 pub task_id: String,
3013 #[serde(default, skip_serializing_if = "Option::is_none")]
3014 pub tool_use_id: Option<String>,
3015 pub description: String,
3016 #[serde(default, skip_serializing_if = "Option::is_none")]
3017 pub last_tool_name: Option<String>,
3018 pub usage: TaskUsage,
3019 #[serde(default, skip_serializing_if = "Option::is_none")]
3021 pub subagent_type: Option<String>,
3022 #[serde(default, skip_serializing_if = "Option::is_none")]
3023 pub summary: Option<String>,
3024 pub uuid: String,
3025}
3026
3027#[derive(Debug, Clone, Serialize, Deserialize)]
3030pub struct TaskNotificationMessage {
3031 pub session_id: String,
3032 pub task_id: String,
3033 pub status: TaskStatus,
3034 #[serde(default, skip_serializing_if = "Option::is_none")]
3037 pub reason: Option<TaskEndReason>,
3038 pub summary: String,
3039 pub output_file: Option<String>,
3040 #[serde(skip_serializing_if = "Option::is_none")]
3041 pub tool_use_id: Option<String>,
3042 #[serde(skip_serializing_if = "Option::is_none")]
3043 pub usage: Option<TaskUsage>,
3044 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3050 pub resource_links: Vec<ResourceLink>,
3051 #[serde(default, skip_serializing_if = "Option::is_none")]
3052 pub skip_transcript: Option<bool>,
3053 #[serde(default, skip_serializing_if = "Option::is_none")]
3056 pub ambient: Option<bool>,
3057 #[serde(skip_serializing_if = "Option::is_none")]
3058 pub uuid: Option<String>,
3059}
3060
3061#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3063pub enum AssistantErrorKind {
3064 AuthenticationFailed,
3065 OauthOrgNotAllowed,
3066 AccountOnHold,
3067 BillingError,
3068 RateLimit,
3069 Overloaded,
3070 InvalidRequest,
3071 ModelNotFound,
3072 ServerError,
3073 UnknownError,
3074 MaxOutputTokens,
3075 Unknown(String),
3076}
3077
3078impl AssistantErrorKind {
3079 pub fn as_str(&self) -> &str {
3080 match self {
3081 Self::AuthenticationFailed => "authentication_failed",
3082 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3083 Self::AccountOnHold => "account_on_hold",
3084 Self::BillingError => "billing_error",
3085 Self::RateLimit => "rate_limit",
3086 Self::Overloaded => "overloaded",
3087 Self::InvalidRequest => "invalid_request",
3088 Self::ModelNotFound => "model_not_found",
3089 Self::ServerError => "server_error",
3090 Self::UnknownError => "unknown",
3091 Self::MaxOutputTokens => "max_output_tokens",
3092 Self::Unknown(s) => s.as_str(),
3093 }
3094 }
3095}
3096
3097impl fmt::Display for AssistantErrorKind {
3098 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3099 f.write_str(self.as_str())
3100 }
3101}
3102
3103impl From<&str> for AssistantErrorKind {
3104 fn from(s: &str) -> Self {
3105 match s {
3106 "authentication_failed" => Self::AuthenticationFailed,
3107 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3108 "account_on_hold" => Self::AccountOnHold,
3109 "billing_error" => Self::BillingError,
3110 "rate_limit" => Self::RateLimit,
3111 "overloaded" => Self::Overloaded,
3112 "invalid_request" => Self::InvalidRequest,
3113 "model_not_found" => Self::ModelNotFound,
3114 "server_error" => Self::ServerError,
3115 "unknown" => Self::UnknownError,
3116 "max_output_tokens" => Self::MaxOutputTokens,
3117 other => Self::Unknown(other.to_string()),
3118 }
3119 }
3120}
3121
3122impl Serialize for AssistantErrorKind {
3123 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3124 serializer.serialize_str(self.as_str())
3125 }
3126}
3127
3128impl<'de> Deserialize<'de> for AssistantErrorKind {
3129 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3130 let s = String::deserialize(deserializer)?;
3131 Ok(Self::from(s.as_str()))
3132 }
3133}
3134
3135#[derive(Debug, Clone, Serialize, Deserialize)]
3142pub struct CodeChangePublishedMessage {
3143 pub provider: String,
3147 pub url: String,
3149 pub repo: String,
3152 pub identifier: String,
3154 #[serde(default, skip_serializing_if = "Option::is_none")]
3163 pub action: Option<String>,
3164 #[serde(default, skip_serializing_if = "Option::is_none")]
3172 pub branch: Option<String>,
3173 pub uuid: String,
3174 pub session_id: String,
3175}
3176
3177#[derive(Debug, Clone, Serialize, Deserialize)]
3182pub struct VcsStateChangedMessage {
3183 pub kind: VcsMutationKind,
3186 pub cwd: String,
3189 #[serde(default, skip_serializing_if = "Option::is_none")]
3194 pub branch: Option<String>,
3195 pub uuid: String,
3196 pub session_id: String,
3197}
3198
3199#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3201pub enum VcsMutationKind {
3202 Commit,
3203 Push,
3204 Merge,
3205 Rebase,
3206 Unknown(String),
3208}
3209
3210impl VcsMutationKind {
3211 pub fn as_str(&self) -> &str {
3212 match self {
3213 Self::Commit => "commit",
3214 Self::Push => "push",
3215 Self::Merge => "merge",
3216 Self::Rebase => "rebase",
3217 Self::Unknown(s) => s.as_str(),
3218 }
3219 }
3220}
3221
3222impl fmt::Display for VcsMutationKind {
3223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3224 f.write_str(self.as_str())
3225 }
3226}
3227
3228impl From<&str> for VcsMutationKind {
3229 fn from(s: &str) -> Self {
3230 match s {
3231 "commit" => Self::Commit,
3232 "push" => Self::Push,
3233 "merge" => Self::Merge,
3234 "rebase" => Self::Rebase,
3235 other => Self::Unknown(other.to_string()),
3236 }
3237 }
3238}
3239
3240impl Serialize for VcsMutationKind {
3241 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3242 serializer.serialize_str(self.as_str())
3243 }
3244}
3245
3246impl<'de> Deserialize<'de> for VcsMutationKind {
3247 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3248 let s = String::deserialize(deserializer)?;
3249 Ok(Self::from(s.as_str()))
3250 }
3251}
3252
3253#[derive(Debug, Clone, Serialize, Deserialize)]
3255pub struct FeedbackDraftQueuedMessage {
3256 pub draft_id: String,
3257 pub draft_type: String,
3258 pub title: String,
3259 pub details_preview: String,
3260 #[serde(default, skip_serializing_if = "Option::is_none")]
3261 pub uuid: Option<String>,
3262 #[serde(default, skip_serializing_if = "Option::is_none")]
3263 pub session_id: Option<String>,
3264 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3265 pub extra: serde_json::Map<String, Value>,
3266}
3267
3268#[derive(Debug, Clone, Serialize, Deserialize)]
3276pub struct CloudSessionDeltaMessage {
3277 pub seq: u64,
3281 pub changed: Vec<String>,
3285 pub cloud_session: Value,
3289 pub uuid: String,
3290 pub session_id: String,
3291 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3292 pub extra: serde_json::Map<String, Value>,
3293}
3294
3295#[derive(Debug, Clone, Serialize, Deserialize)]
3311pub struct DevIntentMessage {
3312 pub kind: DevIntentKind,
3314 #[serde(default, skip_serializing_if = "Option::is_none")]
3320 pub trigger: Option<DevIntentTrigger>,
3321 pub uuid: String,
3322 pub session_id: String,
3323 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3324 pub extra: serde_json::Map<String, Value>,
3325}
3326
3327#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3331pub enum DevIntentKind {
3332 IosApp,
3338 AndroidApp,
3345 Unknown(String),
3347}
3348
3349impl DevIntentKind {
3350 pub fn as_str(&self) -> &str {
3351 match self {
3352 Self::IosApp => "ios_app",
3353 Self::AndroidApp => "android_app",
3354 Self::Unknown(s) => s.as_str(),
3355 }
3356 }
3357}
3358
3359impl fmt::Display for DevIntentKind {
3360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3361 f.write_str(self.as_str())
3362 }
3363}
3364
3365impl From<&str> for DevIntentKind {
3366 fn from(s: &str) -> Self {
3367 match s {
3368 "ios_app" => Self::IosApp,
3369 "android_app" => Self::AndroidApp,
3370 other => Self::Unknown(other.to_string()),
3371 }
3372 }
3373}
3374
3375impl Serialize for DevIntentKind {
3376 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3377 serializer.serialize_str(self.as_str())
3378 }
3379}
3380
3381impl<'de> Deserialize<'de> for DevIntentKind {
3382 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3383 let s = String::deserialize(deserializer)?;
3384 Ok(Self::from(s.as_str()))
3385 }
3386}
3387
3388#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3392pub enum DevIntentTrigger {
3393 SwiftEdit,
3395 UikitImport,
3397 XcodeProject,
3400 IosCommand,
3403 KotlinEdit,
3405 JavaEdit,
3407 AndroidImport,
3409 AndroidManifest,
3411 GradlePlugin,
3413 AndroidCommand,
3416 ProjectScan,
3419 Unknown(String),
3421}
3422
3423impl DevIntentTrigger {
3424 pub fn as_str(&self) -> &str {
3425 match self {
3426 Self::SwiftEdit => "swift_edit",
3427 Self::UikitImport => "uikit_import",
3428 Self::XcodeProject => "xcode_project",
3429 Self::IosCommand => "ios_command",
3430 Self::KotlinEdit => "kotlin_edit",
3431 Self::JavaEdit => "java_edit",
3432 Self::AndroidImport => "android_import",
3433 Self::AndroidManifest => "android_manifest",
3434 Self::GradlePlugin => "gradle_plugin",
3435 Self::AndroidCommand => "android_command",
3436 Self::ProjectScan => "project_scan",
3437 Self::Unknown(s) => s.as_str(),
3438 }
3439 }
3440}
3441
3442impl fmt::Display for DevIntentTrigger {
3443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3444 f.write_str(self.as_str())
3445 }
3446}
3447
3448impl From<&str> for DevIntentTrigger {
3449 fn from(s: &str) -> Self {
3450 match s {
3451 "swift_edit" => Self::SwiftEdit,
3452 "uikit_import" => Self::UikitImport,
3453 "xcode_project" => Self::XcodeProject,
3454 "ios_command" => Self::IosCommand,
3455 "kotlin_edit" => Self::KotlinEdit,
3456 "java_edit" => Self::JavaEdit,
3457 "android_import" => Self::AndroidImport,
3458 "android_manifest" => Self::AndroidManifest,
3459 "gradle_plugin" => Self::GradlePlugin,
3460 "android_command" => Self::AndroidCommand,
3461 "project_scan" => Self::ProjectScan,
3462 other => Self::Unknown(other.to_string()),
3463 }
3464 }
3465}
3466
3467impl Serialize for DevIntentTrigger {
3468 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3469 serializer.serialize_str(self.as_str())
3470 }
3471}
3472
3473impl<'de> Deserialize<'de> for DevIntentTrigger {
3474 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3475 let s = String::deserialize(deserializer)?;
3476 Ok(Self::from(s.as_str()))
3477 }
3478}
3479
3480#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3484pub enum TaskEndReason {
3485 WorkerRestart,
3488 Unknown(String),
3490}
3491
3492impl TaskEndReason {
3493 pub fn as_str(&self) -> &str {
3494 match self {
3495 Self::WorkerRestart => "worker_restart",
3496 Self::Unknown(s) => s.as_str(),
3497 }
3498 }
3499}
3500
3501impl fmt::Display for TaskEndReason {
3502 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3503 f.write_str(self.as_str())
3504 }
3505}
3506
3507impl From<&str> for TaskEndReason {
3508 fn from(s: &str) -> Self {
3509 match s {
3510 "worker_restart" => Self::WorkerRestart,
3511 other => Self::Unknown(other.to_string()),
3512 }
3513 }
3514}
3515
3516impl Serialize for TaskEndReason {
3517 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3518 serializer.serialize_str(self.as_str())
3519 }
3520}
3521
3522impl<'de> Deserialize<'de> for TaskEndReason {
3523 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3524 let s = String::deserialize(deserializer)?;
3525 Ok(Self::from(s.as_str()))
3526 }
3527}
3528
3529#[derive(Debug, Clone, Serialize, Deserialize)]
3538pub struct TurnHandoffAvailableMessage {
3539 pub v: u64,
3541 pub tools: Vec<String>,
3543 pub worker_epoch: u64,
3545 #[serde(default, skip_serializing_if = "Option::is_none")]
3549 pub relay_marker: Option<bool>,
3550 #[serde(default, skip_serializing_if = "Option::is_none")]
3558 pub staged_files: Option<bool>,
3559 #[serde(default, skip_serializing_if = "Option::is_none")]
3566 pub no_query_first: Option<bool>,
3567 #[serde(default, skip_serializing_if = "Option::is_none")]
3574 pub home_files: Option<bool>,
3575 #[serde(default, skip_serializing_if = "Option::is_none")]
3579 pub file_names: Option<bool>,
3580 #[serde(default, skip_serializing_if = "Option::is_none")]
3590 pub carried_writes: Option<bool>,
3591 pub uuid: String,
3592 pub session_id: String,
3593 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3594 pub extra: serde_json::Map<String, Value>,
3595}
3596
3597#[derive(Debug, Clone, Serialize, Deserialize)]
3609pub struct TurnPreemptedMessage {
3610 pub reason: String,
3614 pub preempted_by_uuid: String,
3618 #[serde(default)]
3621 pub preempted_message_uuids: Vec<String>,
3622 pub uuid: String,
3623 pub session_id: String,
3624 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3625 pub extra: serde_json::Map<String, Value>,
3626}
3627
3628#[derive(Debug, Clone, Serialize, Deserialize)]
3636pub struct PeerMessageHoldMessage {
3637 pub state: PeerMessageHoldState,
3643 #[serde(default, skip_serializing_if = "Option::is_none")]
3650 pub message_uuid: Option<String>,
3651 pub lane: PeerMessageLane,
3653 pub from: String,
3658 #[serde(default, skip_serializing_if = "Option::is_none")]
3661 pub from_name: Option<String>,
3662 #[serde(default, skip_serializing_if = "Option::is_none")]
3664 pub cause: Option<PeerMessageHoldCause>,
3665 #[serde(default, skip_serializing_if = "Option::is_none")]
3667 pub outcome: Option<PeerMessageHoldOutcome>,
3668 pub uuid: String,
3669 pub session_id: String,
3670 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3671 pub extra: serde_json::Map<String, Value>,
3672}
3673
3674#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3676pub enum PeerMessageHoldState {
3677 Held,
3679 Released,
3681 Dropped,
3683 Unknown(String),
3685}
3686
3687impl PeerMessageHoldState {
3688 pub fn as_str(&self) -> &str {
3689 match self {
3690 Self::Held => "held",
3691 Self::Released => "released",
3692 Self::Dropped => "dropped",
3693 Self::Unknown(s) => s.as_str(),
3694 }
3695 }
3696}
3697
3698impl fmt::Display for PeerMessageHoldState {
3699 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3700 f.write_str(self.as_str())
3701 }
3702}
3703
3704impl From<&str> for PeerMessageHoldState {
3705 fn from(s: &str) -> Self {
3706 match s {
3707 "held" => Self::Held,
3708 "released" => Self::Released,
3709 "dropped" => Self::Dropped,
3710 other => Self::Unknown(other.to_string()),
3711 }
3712 }
3713}
3714
3715impl Serialize for PeerMessageHoldState {
3716 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3717 serializer.serialize_str(self.as_str())
3718 }
3719}
3720
3721impl<'de> Deserialize<'de> for PeerMessageHoldState {
3722 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3723 let s = String::deserialize(deserializer)?;
3724 Ok(Self::from(s.as_str()))
3725 }
3726}
3727
3728#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3730pub enum PeerMessageLane {
3731 Bridge,
3733 Stdin,
3735 Socket,
3737 Unknown(String),
3739}
3740
3741impl PeerMessageLane {
3742 pub fn as_str(&self) -> &str {
3743 match self {
3744 Self::Bridge => "bridge",
3745 Self::Stdin => "stdin",
3746 Self::Socket => "socket",
3747 Self::Unknown(s) => s.as_str(),
3748 }
3749 }
3750}
3751
3752impl fmt::Display for PeerMessageLane {
3753 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3754 f.write_str(self.as_str())
3755 }
3756}
3757
3758impl From<&str> for PeerMessageLane {
3759 fn from(s: &str) -> Self {
3760 match s {
3761 "bridge" => Self::Bridge,
3762 "stdin" => Self::Stdin,
3763 "socket" => Self::Socket,
3764 other => Self::Unknown(other.to_string()),
3765 }
3766 }
3767}
3768
3769impl Serialize for PeerMessageLane {
3770 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3771 serializer.serialize_str(self.as_str())
3772 }
3773}
3774
3775impl<'de> Deserialize<'de> for PeerMessageLane {
3776 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3777 let s = String::deserialize(deserializer)?;
3778 Ok(Self::from(s.as_str()))
3779 }
3780}
3781
3782#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3789pub enum PeerMessageHoldCause {
3790 ExplicitSetting,
3791 ManagedSetting,
3792 RepoSetting,
3793 InvalidSetting,
3794 BypassDefault,
3795 ModeUnknown,
3796 ModeMismatch,
3797 NoModeAsserted,
3798 Unknown(String),
3800}
3801
3802impl PeerMessageHoldCause {
3803 pub fn as_str(&self) -> &str {
3804 match self {
3805 Self::ExplicitSetting => "explicit-setting",
3806 Self::ManagedSetting => "managed-setting",
3807 Self::RepoSetting => "repo-setting",
3808 Self::InvalidSetting => "invalid-setting",
3809 Self::BypassDefault => "bypass-default",
3810 Self::ModeUnknown => "mode-unknown",
3811 Self::ModeMismatch => "mode-mismatch",
3812 Self::NoModeAsserted => "no-mode-asserted",
3813 Self::Unknown(s) => s.as_str(),
3814 }
3815 }
3816}
3817
3818impl fmt::Display for PeerMessageHoldCause {
3819 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3820 f.write_str(self.as_str())
3821 }
3822}
3823
3824impl From<&str> for PeerMessageHoldCause {
3825 fn from(s: &str) -> Self {
3826 match s {
3827 "explicit-setting" => Self::ExplicitSetting,
3828 "managed-setting" => Self::ManagedSetting,
3829 "repo-setting" => Self::RepoSetting,
3830 "invalid-setting" => Self::InvalidSetting,
3831 "bypass-default" => Self::BypassDefault,
3832 "mode-unknown" => Self::ModeUnknown,
3833 "mode-mismatch" => Self::ModeMismatch,
3834 "no-mode-asserted" => Self::NoModeAsserted,
3835 other => Self::Unknown(other.to_string()),
3836 }
3837 }
3838}
3839
3840impl Serialize for PeerMessageHoldCause {
3841 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3842 serializer.serialize_str(self.as_str())
3843 }
3844}
3845
3846impl<'de> Deserialize<'de> for PeerMessageHoldCause {
3847 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3848 let s = String::deserialize(deserializer)?;
3849 Ok(Self::from(s.as_str()))
3850 }
3851}
3852
3853#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3856pub enum PeerMessageHoldOutcome {
3857 Expired,
3861 Refused,
3863 Dropped,
3866 Discarded,
3868 Unknown(String),
3870}
3871
3872impl PeerMessageHoldOutcome {
3873 pub fn as_str(&self) -> &str {
3874 match self {
3875 Self::Expired => "expired",
3876 Self::Refused => "refused",
3877 Self::Dropped => "dropped",
3878 Self::Discarded => "discarded",
3879 Self::Unknown(s) => s.as_str(),
3880 }
3881 }
3882}
3883
3884impl fmt::Display for PeerMessageHoldOutcome {
3885 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3886 f.write_str(self.as_str())
3887 }
3888}
3889
3890impl From<&str> for PeerMessageHoldOutcome {
3891 fn from(s: &str) -> Self {
3892 match s {
3893 "expired" => Self::Expired,
3894 "refused" => Self::Refused,
3895 "dropped" => Self::Dropped,
3896 "discarded" => Self::Discarded,
3897 other => Self::Unknown(other.to_string()),
3898 }
3899 }
3900}
3901
3902impl Serialize for PeerMessageHoldOutcome {
3903 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3904 serializer.serialize_str(self.as_str())
3905 }
3906}
3907
3908impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
3909 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3910 let s = String::deserialize(deserializer)?;
3911 Ok(Self::from(s.as_str()))
3912 }
3913}
3914
3915#[derive(Debug, Clone, Serialize, Deserialize)]
3926pub struct PerTurnEffortChangedMessage {
3927 pub per_turn_effort_active: bool,
3929 pub uuid: String,
3930 pub session_id: String,
3931}
3932
3933#[derive(Debug, Clone, Serialize, Deserialize)]
3940pub struct InstructionSizeWarningMessage {
3941 pub total_chars: u64,
3944 pub total_limit_chars: u64,
3947 pub file_count: u64,
3950 #[serde(default, skip_serializing_if = "Option::is_none")]
3954 pub largest_chars: Option<u64>,
3955 pub uuid: String,
3956 pub session_id: String,
3957}
3958
3959#[derive(Debug, Clone, Serialize, Deserialize)]
3969pub struct SessionTitleChangedMessage {
3970 pub title: String,
3975 pub uuid: String,
3976 pub session_id: String,
3977}
3978
3979#[derive(Debug, Clone, Serialize, Deserialize)]
3983pub struct UiLogMessage {
3984 pub plugin: String,
3986 pub text: String,
3989 pub uuid: String,
3990 pub session_id: String,
3991}
3992
3993#[derive(Debug, Clone, Serialize, Deserialize)]
3997pub struct UiToastMessage {
3998 pub plugin: String,
4000 pub text: String,
4003 pub timeout_ms: u64,
4006 pub uuid: String,
4007 pub session_id: String,
4008}
4009
4010#[derive(Debug, Clone, Serialize, Deserialize)]
4014pub struct UiStatusMessage {
4015 pub plugin: String,
4017 pub text: Option<String>,
4021 pub uuid: String,
4022 pub session_id: String,
4023}
4024
4025#[derive(Debug, Clone, Serialize, Deserialize)]
4030pub struct UiInvalidateMessage {
4031 pub event: String,
4034 #[serde(default, skip_serializing_if = "Option::is_none")]
4038 pub instances: Option<Vec<UiInvalidatedInstance>>,
4039 pub uuid: String,
4040 pub session_id: String,
4041}
4042
4043#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4045pub struct UiInvalidatedInstance {
4046 pub surface: String,
4048 pub component: String,
4050 pub instance_id: String,
4053}
4054
4055#[derive(Debug, Clone, Serialize, Deserialize)]
4062pub struct UiPanesMessage {
4063 pub panes: Vec<UiPane>,
4066 pub shown_id: Option<String>,
4069 pub focused_id: Option<String>,
4072 pub focus_requested_id: Option<String>,
4076 pub uuid: String,
4077 pub session_id: String,
4078}
4079
4080#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4082pub struct UiPane {
4083 pub id: String,
4086 pub title: String,
4089 pub plugin: String,
4091 #[serde(default, skip_serializing_if = "Option::is_none")]
4094 pub close_on_escape: Option<bool>,
4095 #[serde(default, skip_serializing_if = "Option::is_none")]
4098 pub hold_toasts: Option<bool>,
4099 #[serde(default, skip_serializing_if = "Option::is_none")]
4102 pub rows: Option<u64>,
4103 #[serde(default, skip_serializing_if = "Option::is_none")]
4106 pub columns: Option<u64>,
4107}
4108
4109#[derive(Debug, Clone, Serialize, Deserialize)]
4113pub struct UiScrollMessage {
4114 pub client_id: String,
4116 pub component: UiSiteComponent,
4118 pub instance_id: String,
4120 pub offset: u64,
4123 #[serde(default, skip_serializing_if = "Option::is_none")]
4126 pub follow_end: Option<bool>,
4127 pub uuid: String,
4128 pub session_id: String,
4129}
4130
4131#[derive(Debug, Clone, Serialize, Deserialize)]
4136pub struct UiFocusMessage {
4137 pub client_id: String,
4139 pub component: UiSiteComponent,
4141 pub instance_id: String,
4143 pub plugin: String,
4145 pub key: String,
4147 pub uuid: String,
4148 pub session_id: String,
4149}
4150
4151#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4153pub enum UiSiteComponent {
4154 Pane,
4156 AbovePrompt,
4158 Unknown(String),
4160}
4161
4162impl UiSiteComponent {
4163 pub fn as_str(&self) -> &str {
4164 match self {
4165 Self::Pane => "Pane",
4166 Self::AbovePrompt => "AbovePrompt",
4167 Self::Unknown(s) => s.as_str(),
4168 }
4169 }
4170}
4171
4172impl fmt::Display for UiSiteComponent {
4173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4174 f.write_str(self.as_str())
4175 }
4176}
4177
4178impl From<&str> for UiSiteComponent {
4179 fn from(s: &str) -> Self {
4180 match s {
4181 "Pane" => Self::Pane,
4182 "AbovePrompt" => Self::AbovePrompt,
4183 other => Self::Unknown(other.to_string()),
4184 }
4185 }
4186}
4187
4188impl Serialize for UiSiteComponent {
4189 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4190 serializer.serialize_str(self.as_str())
4191 }
4192}
4193
4194impl<'de> Deserialize<'de> for UiSiteComponent {
4195 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4196 let s = String::deserialize(deserializer)?;
4197 Ok(Self::from(s.as_str()))
4198 }
4199}
4200
4201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4204pub struct LocalCommandRun {
4205 pub command: String,
4207 pub args: String,
4210}
4211
4212#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4216pub struct LocalCommandOutcome {
4217 pub kind: LocalCommandOutcomeKind,
4218 #[serde(default, skip_serializing_if = "Option::is_none")]
4221 pub suggestion: Option<String>,
4222}
4223
4224#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4229pub enum LocalCommandOutcomeKind {
4230 UnavailableHeadless,
4232 Unknown,
4235 Failed,
4237 RestartRequired,
4239 Other(String),
4241}
4242
4243impl LocalCommandOutcomeKind {
4244 pub fn as_str(&self) -> &str {
4245 match self {
4246 Self::UnavailableHeadless => "unavailable_headless",
4247 Self::Unknown => "unknown",
4248 Self::Failed => "failed",
4249 Self::RestartRequired => "restart_required",
4250 Self::Other(s) => s.as_str(),
4251 }
4252 }
4253}
4254
4255impl fmt::Display for LocalCommandOutcomeKind {
4256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4257 f.write_str(self.as_str())
4258 }
4259}
4260
4261impl From<&str> for LocalCommandOutcomeKind {
4262 fn from(s: &str) -> Self {
4263 match s {
4264 "unavailable_headless" => Self::UnavailableHeadless,
4265 "unknown" => Self::Unknown,
4266 "failed" => Self::Failed,
4267 "restart_required" => Self::RestartRequired,
4268 other => Self::Other(other.to_string()),
4269 }
4270 }
4271}
4272
4273impl Serialize for LocalCommandOutcomeKind {
4274 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4275 serializer.serialize_str(self.as_str())
4276 }
4277}
4278
4279impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4280 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4281 let s = String::deserialize(deserializer)?;
4282 Ok(Self::from(s.as_str()))
4283 }
4284}
4285
4286#[derive(Debug, Clone, Serialize, Deserialize)]
4292pub struct UsageReport {
4293 pub session: super::control::UsageSession,
4295 pub rate_limits: Option<UsageReportRateLimits>,
4299 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4300 pub extra: serde_json::Map<String, Value>,
4301}
4302
4303#[derive(Debug, Clone, Serialize, Deserialize)]
4305pub struct UsageReportRateLimits {
4306 pub limits: Option<Vec<UsageReportLimit>>,
4314 #[serde(default, skip_serializing_if = "Option::is_none")]
4317 pub extra_usage: Option<UsageReportExtraUsage>,
4318 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4319 pub extra: serde_json::Map<String, Value>,
4320}
4321
4322#[derive(Debug, Clone, Serialize, Deserialize)]
4324pub struct UsageReportLimit {
4325 pub kind: String,
4328 pub group: String,
4331 pub percent: f64,
4333 pub resets_at: Option<String>,
4335 #[serde(default, skip_serializing_if = "Option::is_none")]
4338 pub scope: Option<UsageReportScope>,
4339 #[serde(default, skip_serializing_if = "Option::is_none")]
4343 pub severity: Option<String>,
4344 #[serde(default, skip_serializing_if = "Option::is_none")]
4346 pub is_active: Option<bool>,
4347 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4348 pub extra: serde_json::Map<String, Value>,
4349}
4350
4351#[derive(Debug, Clone, Serialize, Deserialize)]
4353pub struct UsageReportScope {
4354 #[serde(default, skip_serializing_if = "Option::is_none")]
4355 pub model: Option<UsageReportScopeLabel>,
4356 #[serde(default, skip_serializing_if = "Option::is_none")]
4357 pub surface: Option<UsageReportScopeLabel>,
4358 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4359 pub extra: serde_json::Map<String, Value>,
4360}
4361
4362#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4364pub struct UsageReportScopeLabel {
4365 pub display_name: String,
4366}
4367
4368#[derive(Debug, Clone, Serialize, Deserialize)]
4372pub struct UsageReportExtraUsage {
4373 pub is_enabled: bool,
4375 pub monthly_limit: Option<f64>,
4376 pub used_credits: Option<f64>,
4377 pub utilization: Option<f64>,
4378 #[serde(default, skip_serializing_if = "Option::is_none")]
4379 pub currency: Option<String>,
4380 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4381 pub extra: serde_json::Map<String, Value>,
4382}
4383
4384#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4387pub struct BatchToolUse {
4388 pub id: String,
4389 pub name: String,
4390}
4391
4392#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4397pub struct ContextUsage {
4398 pub model: String,
4400 pub total_tokens: u64,
4403 pub raw_max_tokens: u64,
4406 pub percentage: u64,
4408 #[serde(default, skip_serializing_if = "Option::is_none")]
4410 pub over_limit: Option<ContextOverLimit>,
4411 #[serde(default)]
4413 pub categories: Vec<ContextCategory>,
4414 #[serde(default)]
4416 pub mcp_tools: Vec<ContextMcpTool>,
4417 #[serde(default)]
4419 pub memory_files: Vec<ContextMemoryFile>,
4420 #[serde(default)]
4422 pub agents: Vec<ContextAgent>,
4423 #[serde(default, skip_serializing_if = "Option::is_none")]
4425 pub skills: Option<Vec<ContextSkill>>,
4426}
4427
4428#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4430pub struct ContextOverLimit {
4431 pub tokens_over: u64,
4432 pub kind: String,
4435}
4436
4437#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4439pub struct ContextCategory {
4440 pub name: String,
4443 pub tokens: u64,
4444 pub kind: String,
4448}
4449
4450#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4452pub struct ContextMcpTool {
4453 pub name: String,
4455 pub server_name: String,
4456 pub tokens: u64,
4457}
4458
4459#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4461pub struct ContextMemoryFile {
4462 pub path: String,
4463 #[serde(rename = "type")]
4465 pub file_type: String,
4466 pub tokens: u64,
4467}
4468
4469#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4471pub struct ContextAgent {
4472 pub agent_type: String,
4473 pub source: String,
4475 pub tokens: u64,
4476}
4477
4478#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4480pub struct ContextSkill {
4481 pub name: String,
4482 pub source: String,
4484 #[serde(default, skip_serializing_if = "Option::is_none")]
4485 pub plugin_name: Option<String>,
4486 pub tokens: u64,
4487}
4488
4489#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4491pub struct ToolUseMeta {
4492 pub id: String,
4493 pub display_name: String,
4494 #[serde(default, skip_serializing_if = "Option::is_none")]
4495 pub server_display_name: Option<String>,
4496 #[serde(default, skip_serializing_if = "Option::is_none")]
4497 pub icon_url: Option<String>,
4498}
4499
4500#[derive(Debug, Clone, Serialize, Deserialize)]
4502pub struct AssistantMessage {
4503 pub message: AssistantMessageContent,
4504 #[serde(alias = "sessionId")]
4505 pub session_id: String,
4506 #[serde(skip_serializing_if = "Option::is_none")]
4507 pub uuid: Option<String>,
4508 #[serde(skip_serializing_if = "Option::is_none")]
4509 pub parent_tool_use_id: Option<String>,
4510 #[serde(skip_serializing_if = "Option::is_none")]
4512 pub request_id: Option<String>,
4513 #[serde(default, skip_serializing_if = "Option::is_none")]
4522 pub user_message_uuid: Option<String>,
4523 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4529 pub user_message_uuids: Vec<String>,
4530 #[serde(skip_serializing_if = "Option::is_none")]
4533 pub subagent_type: Option<String>,
4534 #[serde(skip_serializing_if = "Option::is_none")]
4536 pub task_description: Option<String>,
4537 #[serde(skip_serializing_if = "Option::is_none")]
4538 pub error: Option<AssistantErrorKind>,
4539 #[serde(default, skip_serializing_if = "Option::is_none")]
4543 pub aborted: Option<bool>,
4544 #[serde(default, skip_serializing_if = "Option::is_none")]
4549 pub resumed_from_incomplete_thinking: Option<bool>,
4550 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4551 pub supersedes: Vec<String>,
4552 #[serde(skip_serializing_if = "Option::is_none")]
4553 pub timestamp: Option<String>,
4554 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4555 pub tool_use_meta: Vec<ToolUseMeta>,
4556 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4561 pub batch_tool_uses: Vec<BatchToolUse>,
4562 #[serde(default, skip_serializing_if = "Option::is_none")]
4568 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
4569 #[serde(default, skip_serializing_if = "Option::is_none")]
4575 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
4576 #[serde(default, skip_serializing_if = "Option::is_none")]
4580 pub historical: Option<bool>,
4581 #[serde(default, skip_serializing_if = "Option::is_none")]
4589 pub resume_reason: Option<String>,
4590 #[serde(default, skip_serializing_if = "Option::is_none")]
4596 pub local_command_source: Option<String>,
4597 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4606 pub narration_block_indexes: Vec<usize>,
4607 #[serde(default, skip_serializing_if = "Option::is_none")]
4611 pub narration_hint: Option<String>,
4612 #[serde(default, skip_serializing_if = "Option::is_none")]
4621 pub thinking_duration_ms: Option<u64>,
4622 #[serde(default, skip_serializing_if = "Option::is_none")]
4626 pub context_usage: Option<ContextUsage>,
4627 #[serde(default, skip_serializing_if = "Option::is_none")]
4635 pub usage_report: Option<Box<UsageReport>>,
4636 #[serde(default, skip_serializing_if = "Option::is_none")]
4643 pub local_command_run: Option<LocalCommandRun>,
4644 #[serde(default, skip_serializing_if = "Option::is_none")]
4650 pub local_command_outcome: Option<LocalCommandOutcome>,
4651 #[serde(default, skip_serializing_if = "Option::is_none")]
4652 pub is_meta: Option<bool>,
4653 #[serde(default, skip_serializing_if = "Option::is_none")]
4654 pub is_virtual: Option<bool>,
4655 #[serde(default, skip_serializing_if = "Option::is_none")]
4656 pub is_api_error_message: Option<bool>,
4657 #[serde(skip_serializing_if = "Option::is_none")]
4658 pub api_error_status: Option<u16>,
4659 #[serde(skip_serializing_if = "Option::is_none")]
4676 pub api_error: Option<String>,
4677 #[serde(default, skip_serializing_if = "Option::is_none")]
4684 pub api_error_code: Option<String>,
4685 #[serde(default, skip_serializing_if = "Option::is_none")]
4688 pub api_error_params: Option<ApiErrorParams>,
4689 #[serde(skip_serializing_if = "Option::is_none")]
4690 pub error_details: Option<String>,
4691 #[serde(skip_serializing_if = "Option::is_none")]
4692 pub advisor_model: Option<String>,
4693 #[serde(skip_serializing_if = "Option::is_none")]
4694 pub attribution_agent: Option<String>,
4695 #[serde(skip_serializing_if = "Option::is_none")]
4696 pub attribution_skill: Option<String>,
4697 #[serde(skip_serializing_if = "Option::is_none")]
4698 pub attribution_plugin: Option<String>,
4699 #[serde(skip_serializing_if = "Option::is_none")]
4700 pub attribution_mcp_server: Option<String>,
4701 #[serde(skip_serializing_if = "Option::is_none")]
4702 pub attribution_mcp_tool: Option<String>,
4703}
4704
4705#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
4709pub struct ApiErrorParams {
4710 #[serde(default, skip_serializing_if = "Option::is_none")]
4712 pub effort: Option<String>,
4713 #[serde(default, skip_serializing_if = "Option::is_none")]
4716 pub provider: Option<ApiErrorProvider>,
4717 #[serde(default, skip_serializing_if = "Option::is_none")]
4720 pub remedy: Option<ApiErrorRemedy>,
4721 #[serde(default, skip_serializing_if = "Option::is_none")]
4724 pub media: Option<ApiErrorMedia>,
4725 #[serde(default, skip_serializing_if = "Option::is_none")]
4727 pub media_reason: Option<ApiErrorMediaReason>,
4728}
4729
4730#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4732pub enum ApiErrorMedia {
4733 Image,
4734 Document,
4735 Unknown(String),
4737}
4738
4739impl ApiErrorMedia {
4740 pub fn as_str(&self) -> &str {
4741 match self {
4742 Self::Image => "image",
4743 Self::Document => "document",
4744 Self::Unknown(s) => s.as_str(),
4745 }
4746 }
4747}
4748
4749impl fmt::Display for ApiErrorMedia {
4750 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4751 f.write_str(self.as_str())
4752 }
4753}
4754
4755impl From<&str> for ApiErrorMedia {
4756 fn from(s: &str) -> Self {
4757 match s {
4758 "image" => Self::Image,
4759 "document" => Self::Document,
4760 other => Self::Unknown(other.to_string()),
4761 }
4762 }
4763}
4764
4765impl Serialize for ApiErrorMedia {
4766 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4767 serializer.serialize_str(self.as_str())
4768 }
4769}
4770
4771impl<'de> Deserialize<'de> for ApiErrorMedia {
4772 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4773 let s = String::deserialize(deserializer)?;
4774 Ok(Self::from(s.as_str()))
4775 }
4776}
4777
4778#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4781pub enum ApiErrorMediaReason {
4782 Unprocessable,
4785 UnsupportedByModel,
4787 MediaBudget,
4790 Unknown(String),
4792}
4793
4794impl ApiErrorMediaReason {
4795 pub fn as_str(&self) -> &str {
4796 match self {
4797 Self::Unprocessable => "unprocessable",
4798 Self::UnsupportedByModel => "unsupported_by_model",
4799 Self::MediaBudget => "media_budget",
4800 Self::Unknown(s) => s.as_str(),
4801 }
4802 }
4803}
4804
4805impl fmt::Display for ApiErrorMediaReason {
4806 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4807 f.write_str(self.as_str())
4808 }
4809}
4810
4811impl From<&str> for ApiErrorMediaReason {
4812 fn from(s: &str) -> Self {
4813 match s {
4814 "unprocessable" => Self::Unprocessable,
4815 "unsupported_by_model" => Self::UnsupportedByModel,
4816 "media_budget" => Self::MediaBudget,
4817 other => Self::Unknown(other.to_string()),
4818 }
4819 }
4820}
4821
4822impl Serialize for ApiErrorMediaReason {
4823 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4824 serializer.serialize_str(self.as_str())
4825 }
4826}
4827
4828impl<'de> Deserialize<'de> for ApiErrorMediaReason {
4829 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4830 let s = String::deserialize(deserializer)?;
4831 Ok(Self::from(s.as_str()))
4832 }
4833}
4834
4835#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4837pub enum ApiErrorProvider {
4838 Bedrock,
4839 AnthropicAws,
4840 Mantle,
4841 AnthropicGoogleCloud,
4842 Vertex,
4843 Foundry,
4844 Gateway,
4845 Unknown(String),
4847}
4848
4849impl ApiErrorProvider {
4850 pub fn as_str(&self) -> &str {
4851 match self {
4852 Self::Bedrock => "bedrock",
4853 Self::AnthropicAws => "anthropicAws",
4854 Self::Mantle => "mantle",
4855 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
4856 Self::Vertex => "vertex",
4857 Self::Foundry => "foundry",
4858 Self::Gateway => "gateway",
4859 Self::Unknown(s) => s.as_str(),
4860 }
4861 }
4862}
4863
4864impl fmt::Display for ApiErrorProvider {
4865 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4866 f.write_str(self.as_str())
4867 }
4868}
4869
4870impl From<&str> for ApiErrorProvider {
4871 fn from(s: &str) -> Self {
4872 match s {
4873 "bedrock" => Self::Bedrock,
4874 "anthropicAws" => Self::AnthropicAws,
4875 "mantle" => Self::Mantle,
4876 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4877 "vertex" => Self::Vertex,
4878 "foundry" => Self::Foundry,
4879 "gateway" => Self::Gateway,
4880 other => Self::Unknown(other.to_string()),
4881 }
4882 }
4883}
4884
4885impl Serialize for ApiErrorProvider {
4886 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4887 serializer.serialize_str(self.as_str())
4888 }
4889}
4890
4891impl<'de> Deserialize<'de> for ApiErrorProvider {
4892 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4893 let s = String::deserialize(deserializer)?;
4894 Ok(Self::from(s.as_str()))
4895 }
4896}
4897
4898#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4900pub enum ApiErrorRemedy {
4901 RefreshCommand,
4904 RefreshCredentials,
4906 Adc,
4909 GatewayToken,
4912 HostManaged,
4914 ModelAccess,
4917 Unknown(String),
4919}
4920
4921impl ApiErrorRemedy {
4922 pub fn as_str(&self) -> &str {
4923 match self {
4924 Self::RefreshCommand => "refresh_command",
4925 Self::RefreshCredentials => "refresh_credentials",
4926 Self::Adc => "adc",
4927 Self::GatewayToken => "gateway_token",
4928 Self::HostManaged => "host_managed",
4929 Self::ModelAccess => "model_access",
4930 Self::Unknown(s) => s.as_str(),
4931 }
4932 }
4933}
4934
4935impl fmt::Display for ApiErrorRemedy {
4936 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4937 f.write_str(self.as_str())
4938 }
4939}
4940
4941impl From<&str> for ApiErrorRemedy {
4942 fn from(s: &str) -> Self {
4943 match s {
4944 "refresh_command" => Self::RefreshCommand,
4945 "refresh_credentials" => Self::RefreshCredentials,
4946 "adc" => Self::Adc,
4947 "gateway_token" => Self::GatewayToken,
4948 "host_managed" => Self::HostManaged,
4949 "model_access" => Self::ModelAccess,
4950 other => Self::Unknown(other.to_string()),
4951 }
4952 }
4953}
4954
4955impl Serialize for ApiErrorRemedy {
4956 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4957 serializer.serialize_str(self.as_str())
4958 }
4959}
4960
4961impl<'de> Deserialize<'de> for ApiErrorRemedy {
4962 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4963 let s = String::deserialize(deserializer)?;
4964 Ok(Self::from(s.as_str()))
4965 }
4966}
4967
4968#[derive(Debug, Clone, Serialize, Deserialize)]
4970pub struct AssistantMessageContent {
4971 pub id: String,
4972 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
4974 pub message_type: Option<String>,
4975 pub role: MessageRole,
4976 pub model: String,
4977 pub content: Vec<ContentBlock>,
4978 #[serde(skip_serializing_if = "Option::is_none")]
4979 pub stop_reason: Option<StopReason>,
4980 #[serde(skip_serializing_if = "Option::is_none")]
4981 pub stop_sequence: Option<String>,
4982 #[serde(skip_serializing_if = "Option::is_none")]
4983 pub usage: Option<AssistantUsage>,
4984 #[serde(skip_serializing_if = "Option::is_none")]
4986 pub stop_details: Option<Value>,
4987 #[serde(skip_serializing_if = "Option::is_none")]
4989 pub context_management: Option<Value>,
4990}
4991
4992#[derive(Debug, Clone, Serialize, Deserialize)]
4994pub struct AssistantUsage {
4995 #[serde(default)]
4997 pub input_tokens: u32,
4998
4999 #[serde(default)]
5001 pub output_tokens: u32,
5002
5003 #[serde(default)]
5005 pub cache_creation_input_tokens: u32,
5006
5007 #[serde(default)]
5009 pub cache_read_input_tokens: u32,
5010
5011 #[serde(skip_serializing_if = "Option::is_none")]
5013 pub service_tier: Option<String>,
5014
5015 #[serde(skip_serializing_if = "Option::is_none")]
5017 pub cache_creation: Option<CacheCreationDetails>,
5018
5019 #[serde(skip_serializing_if = "Option::is_none")]
5021 pub inference_geo: Option<String>,
5022
5023 #[serde(default, skip_serializing_if = "Option::is_none")]
5026 pub fallback_credit: Option<Value>,
5027}
5028
5029#[derive(Debug, Clone, Serialize, Deserialize)]
5031pub struct CacheCreationDetails {
5032 #[serde(default)]
5034 pub ephemeral_1h_input_tokens: u32,
5035
5036 #[serde(default)]
5038 pub ephemeral_5m_input_tokens: u32,
5039}
5040
5041#[cfg(test)]
5042mod tests {
5043 use crate::io::ClaudeOutput;
5044
5045 #[test]
5046 fn test_subagent_usage_rollup_accumulates_task_results() {
5047 use super::SubagentUsageRollup;
5048
5049 let mut rollup = SubagentUsageRollup::default();
5050
5051 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}}"#;
5052 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5053 assert!(rollup.observe(&output));
5054 assert_eq!(rollup.subagent_tokens, 10201);
5055 assert_eq!(rollup.agent_count, 1);
5056 assert_eq!(rollup.tool_uses, 3);
5057 assert_eq!(rollup.duration_ms, 1853);
5058
5059 assert!(!rollup.observe(&output));
5061 assert_eq!(rollup.agent_count, 1);
5062 assert_eq!(rollup.subagent_tokens, 10201);
5063
5064 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}}"#;
5066 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5067 assert!(rollup.observe(&output));
5068 assert_eq!(rollup.agent_count, 2);
5069 assert_eq!(rollup.subagent_tokens, 10701);
5070 }
5071
5072 #[test]
5073 fn test_subagent_usage_rollup_ignores_non_task_results() {
5074 use super::SubagentUsageRollup;
5075
5076 let mut rollup = SubagentUsageRollup::default();
5077
5078 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}}"#;
5081 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5082 assert!(!rollup.observe(&output));
5083
5084 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5086 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5087 assert!(!rollup.observe(&output));
5088
5089 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5091 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5092 assert!(!rollup.observe(&output));
5093
5094 assert_eq!(rollup, SubagentUsageRollup::default());
5095 }
5096
5097 #[test]
5098 fn test_subagent_usage_rollup_over_captured_session() {
5099 use super::SubagentUsageRollup;
5100
5101 let mut rollup = SubagentUsageRollup::default();
5102 let fixture =
5103 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5104 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5105 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5106 rollup.observe(&output);
5107 }
5108 }
5109 assert_eq!(rollup.agent_count, 1);
5110 assert_eq!(rollup.subagent_tokens, 10201);
5111 }
5112
5113 #[test]
5114 fn test_system_message_init() {
5115 let json = r#"{
5116 "type": "system",
5117 "subtype": "init",
5118 "session_id": "test-session-123",
5119 "cwd": "/home/user/project",
5120 "model": "claude-sonnet-4",
5121 "tools": ["Bash", "Read", "Write"],
5122 "mcp_servers": [],
5123 "slash_commands": ["compact", "cost", "review"],
5124 "agents": ["Bash", "Explore", "Plan"],
5125 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5126 "skills": [],
5127 "claude_code_version": "2.1.15",
5128 "apiKeySource": "none",
5129 "output_style": "default",
5130 "permissionMode": "default"
5131 }"#;
5132
5133 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5134 if let ClaudeOutput::System(sys) = output {
5135 assert!(sys.is_init());
5136 assert!(!sys.is_status());
5137 assert!(!sys.is_compact_boundary());
5138
5139 let init = sys.as_init().expect("Should parse as init");
5140 assert_eq!(init.session_id, "test-session-123");
5141 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5142 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5143 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5144 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5145 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5146 assert_eq!(init.plugins.len(), 1);
5147 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5148 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5149 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5150 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5151 assert_eq!(
5152 init.permission_mode,
5153 Some(super::InitPermissionMode::Default)
5154 );
5155 } else {
5156 panic!("Expected System message");
5157 }
5158 }
5159
5160 #[test]
5161 fn test_system_message_init_from_real_capture() {
5162 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5163 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5164 if let ClaudeOutput::System(sys) = output {
5165 let init = sys.as_init().expect("Should parse real init capture");
5166 assert_eq!(init.slash_commands.len(), 8);
5167 assert!(init.slash_commands.contains(&"compact".to_string()));
5168 assert!(init.slash_commands.contains(&"review".to_string()));
5169 assert_eq!(init.agents.len(), 5);
5170 assert!(init.agents.contains(&"Bash".to_string()));
5171 assert!(init.agents.contains(&"Explore".to_string()));
5172 assert_eq!(init.plugins.len(), 1);
5173 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5174 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5175 } else {
5176 panic!("Expected System message");
5177 }
5178 }
5179
5180 #[test]
5181 fn test_system_message_status() {
5182 let json = r#"{
5183 "type": "system",
5184 "subtype": "status",
5185 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5186 "status": "compacting",
5187 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5188 }"#;
5189
5190 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5191 if let ClaudeOutput::System(sys) = output {
5192 assert!(sys.is_status());
5193 assert!(!sys.is_init());
5194
5195 let status = sys.as_status().expect("Should parse as status");
5196 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5197 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5198 assert_eq!(
5199 status.uuid,
5200 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5201 );
5202 } else {
5203 panic!("Expected System message");
5204 }
5205 }
5206
5207 #[test]
5208 fn test_system_message_status_null() {
5209 let json = r#"{
5210 "type": "system",
5211 "subtype": "status",
5212 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5213 "status": null,
5214 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5215 }"#;
5216
5217 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5218 if let ClaudeOutput::System(sys) = output {
5219 let status = sys.as_status().expect("Should parse as status");
5220 assert_eq!(status.status, None);
5221 } else {
5222 panic!("Expected System message");
5223 }
5224 }
5225
5226 #[test]
5227 fn test_system_message_task_started() {
5228 let json = r#"{
5229 "type": "system",
5230 "subtype": "task_started",
5231 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5232 "task_id": "b6daf3f",
5233 "task_type": "local_bash",
5234 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5235 "description": "Wait for CI on PR #12",
5236 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5237 }"#;
5238
5239 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5240 if let ClaudeOutput::System(sys) = output {
5241 assert!(sys.is_task_started());
5242 assert!(!sys.is_task_progress());
5243 assert!(!sys.is_task_notification());
5244
5245 let task = sys.as_task_started().expect("Should parse as task_started");
5246 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5247 assert_eq!(task.task_id, "b6daf3f");
5248 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5249 assert_eq!(
5250 task.tool_use_id.as_deref(),
5251 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5252 );
5253 assert_eq!(task.description, "Wait for CI on PR #12");
5254 } else {
5255 panic!("Expected System message");
5256 }
5257 }
5258
5259 #[test]
5260 fn test_system_message_task_started_agent() {
5261 let json = r#"{
5262 "type": "system",
5263 "subtype": "task_started",
5264 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5265 "task_id": "a4a7e0906e5fc64cc",
5266 "task_type": "local_agent",
5267 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5268 "description": "Explore Scene/ArrayScene duplication",
5269 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5270 }"#;
5271
5272 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5273 if let ClaudeOutput::System(sys) = output {
5274 let task = sys.as_task_started().expect("Should parse as task_started");
5275 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5276 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5277 } else {
5278 panic!("Expected System message");
5279 }
5280 }
5281
5282 #[test]
5283 fn test_system_message_task_progress() {
5284 let json = r#"{
5285 "type": "system",
5286 "subtype": "task_progress",
5287 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5288 "task_id": "a4a7e0906e5fc64cc",
5289 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5290 "description": "Reading src/jplephem/chebyshev.rs",
5291 "last_tool_name": "Read",
5292 "usage": {
5293 "duration_ms": 13996,
5294 "tool_uses": 9,
5295 "total_tokens": 38779
5296 },
5297 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5298 }"#;
5299
5300 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5301 if let ClaudeOutput::System(sys) = output {
5302 assert!(sys.is_task_progress());
5303 assert!(!sys.is_task_started());
5304
5305 let progress = sys
5306 .as_task_progress()
5307 .expect("Should parse as task_progress");
5308 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5309 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5310 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5311 assert_eq!(progress.usage.duration_ms, 13996);
5312 assert_eq!(progress.usage.tool_uses, 9);
5313 assert_eq!(progress.usage.total_tokens, 38779);
5314 } else {
5315 panic!("Expected System message");
5316 }
5317 }
5318
5319 #[test]
5320 fn test_system_message_task_notification_completed() {
5321 let json = r#"{
5322 "type": "system",
5323 "subtype": "task_notification",
5324 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5325 "task_id": "a0ba761e9dc9c316f",
5326 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
5327 "status": "completed",
5328 "summary": "Agent \"Write Hipparcos data source doc\" completed",
5329 "output_file": "",
5330 "usage": {
5331 "duration_ms": 172300,
5332 "tool_uses": 11,
5333 "total_tokens": 42005
5334 },
5335 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
5336 }"#;
5337
5338 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5339 if let ClaudeOutput::System(sys) = output {
5340 assert!(sys.is_task_notification());
5341
5342 let notif = sys
5343 .as_task_notification()
5344 .expect("Should parse as task_notification");
5345 assert_eq!(notif.status, super::TaskStatus::Completed);
5346 assert_eq!(
5347 notif.summary,
5348 "Agent \"Write Hipparcos data source doc\" completed"
5349 );
5350 assert_eq!(notif.output_file, Some("".to_string()));
5351 assert_eq!(
5352 notif.tool_use_id,
5353 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
5354 );
5355 let usage = notif.usage.expect("Should have usage");
5356 assert_eq!(usage.duration_ms, 172300);
5357 assert_eq!(usage.tool_uses, 11);
5358 assert_eq!(usage.total_tokens, 42005);
5359 } else {
5360 panic!("Expected System message");
5361 }
5362 }
5363
5364 #[test]
5365 fn test_system_message_task_notification_failed_no_usage() {
5366 let json = r#"{
5367 "type": "system",
5368 "subtype": "task_notification",
5369 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
5370 "task_id": "b98f6a3",
5371 "status": "failed",
5372 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
5373 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
5374 }"#;
5375
5376 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5377 if let ClaudeOutput::System(sys) = output {
5378 let notif = sys
5379 .as_task_notification()
5380 .expect("Should parse as task_notification");
5381 assert_eq!(notif.status, super::TaskStatus::Failed);
5382 assert!(notif.tool_use_id.is_none());
5383 assert!(notif.usage.is_none());
5384 assert_eq!(
5385 notif.output_file,
5386 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
5387 );
5388 } else {
5389 panic!("Expected System message");
5390 }
5391 }
5392
5393 #[test]
5399 fn test_task_messages_roundtrip_through_value() {
5400 let cases = [
5401 r#"{"type":"system","subtype":"task_started","session_id":"s1",
5402 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
5403 "description":"Sleep 3s","uuid":"u1"}"#,
5404 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
5405 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
5406 "last_tool_name":"Bash",
5407 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5408 "uuid":"u2"}"#,
5409 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
5410 "task_id":"t1","tool_use_id":"tu1","status":"completed",
5411 "summary":"done","output_file":"",
5412 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5413 "uuid":"u3"}"#,
5414 ];
5415
5416 for json in cases {
5417 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5418 let value = serde_json::to_value(&output).unwrap();
5419 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
5420
5421 let ClaudeOutput::System(sys) = reparsed else {
5422 panic!("Expected System variant after round-trip");
5423 };
5424
5425 match sys.subtype {
5426 super::SystemSubtype::TaskStarted => {
5427 assert!(
5428 sys.as_task_started().is_some(),
5429 "as_task_started failed after round-trip"
5430 );
5431 }
5432 super::SystemSubtype::TaskProgress => {
5433 assert!(
5434 sys.as_task_progress().is_some(),
5435 "as_task_progress failed after round-trip"
5436 );
5437 }
5438 super::SystemSubtype::TaskNotification => {
5439 assert!(
5440 sys.as_task_notification().is_some(),
5441 "as_task_notification failed after round-trip"
5442 );
5443 }
5444 other => panic!("unexpected subtype after round-trip: {other:?}"),
5445 }
5446 }
5447 }
5448
5449 #[test]
5450 fn test_system_message_compact_boundary() {
5451 let json = r#"{
5452 "type": "system",
5453 "subtype": "compact_boundary",
5454 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5455 "compact_metadata": {
5456 "pre_tokens": 155285,
5457 "trigger": "auto"
5458 },
5459 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
5460 }"#;
5461
5462 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5463 if let ClaudeOutput::System(sys) = output {
5464 assert!(sys.is_compact_boundary());
5465 assert!(!sys.is_init());
5466 assert!(!sys.is_status());
5467
5468 let compact = sys
5469 .as_compact_boundary()
5470 .expect("Should parse as compact_boundary");
5471 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5472 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
5473 assert_eq!(
5474 compact.compact_metadata.trigger,
5475 super::CompactionTrigger::Auto
5476 );
5477 assert!(compact.summary.is_none());
5479 assert!(compact.leaf_message_count.is_none());
5480 assert!(compact.duration_ms.is_none());
5481 } else {
5482 panic!("Expected System message");
5483 }
5484 }
5485
5486 #[test]
5487 fn test_compact_boundary_with_summary_stats() {
5488 let json = r#"{
5490 "type": "system",
5491 "subtype": "compact_boundary",
5492 "session_id": "s1",
5493 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
5494 "summary": "Summarized the earlier exploration.",
5495 "leaf_message_count": 42,
5496 "duration_ms": 1234,
5497 "uuid": "u1"
5498 }"#;
5499 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5500 let ClaudeOutput::System(sys) = output else {
5501 panic!("Expected System message");
5502 };
5503 let compact = sys.as_compact_boundary().expect("compact_boundary");
5504 assert_eq!(
5505 compact.summary.as_deref(),
5506 Some("Summarized the earlier exploration.")
5507 );
5508 assert_eq!(compact.leaf_message_count, Some(42));
5509 assert_eq!(compact.duration_ms, Some(1234));
5510
5511 let json_alt = r#"{
5514 "type": "system",
5515 "subtype": "compact_boundary",
5516 "session_id": "s2",
5517 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
5518 "content": "alt-key summary",
5519 "message_count": 7
5520 }"#;
5521 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
5522 let ClaudeOutput::System(sys) = output else {
5523 panic!("Expected System message");
5524 };
5525 let compact = sys.as_compact_boundary().expect("compact_boundary");
5526 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
5527 assert_eq!(compact.leaf_message_count, Some(7));
5528 }
5529
5530 #[test]
5531 fn test_init_message_with_new_fields() {
5532 let json = r#"{
5533 "type": "system",
5534 "subtype": "init",
5535 "session_id": "test-session",
5536 "cwd": "/home/user",
5537 "model": "claude-opus-4-7",
5538 "tools": ["Bash"],
5539 "mcp_servers": [],
5540 "permissionMode": "default",
5541 "apiKeySource": "none",
5542 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
5543 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
5544 "fast_mode_state": "off",
5545 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
5546 "claude_code_version": "2.1.117"
5547 }"#;
5548
5549 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5550 if let ClaudeOutput::System(sys) = output {
5551 let init = sys.as_init().expect("Should parse as init");
5552 assert_eq!(
5553 init.uuid.as_deref(),
5554 Some("44841a0d-182d-493a-86b5-79800d3d9665")
5555 );
5556 assert!(init.memory_paths.is_some());
5557 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
5558 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
5559 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
5560 } else {
5561 panic!("Expected System message");
5562 }
5563 }
5564
5565 #[test]
5566 fn test_assistant_message_with_new_fields() {
5567 let json = r#"{
5568 "type": "assistant",
5569 "message": {
5570 "id": "msg_1",
5571 "type": "message",
5572 "role": "assistant",
5573 "model": "claude-opus-4-7",
5574 "content": [{"type": "text", "text": "Hello"}],
5575 "stop_reason": "end_turn",
5576 "stop_details": null,
5577 "context_management": null,
5578 "usage": {
5579 "input_tokens": 100,
5580 "output_tokens": 10,
5581 "cache_creation_input_tokens": 50,
5582 "cache_read_input_tokens": 0,
5583 "service_tier": "standard",
5584 "inference_geo": "not_available"
5585 }
5586 },
5587 "session_id": "abc",
5588 "uuid": "msg-uuid-123"
5589 }"#;
5590
5591 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5592 if let ClaudeOutput::Assistant(asst) = output {
5593 assert_eq!(asst.message.stop_details, None);
5594 assert_eq!(asst.message.context_management, None);
5595 let usage = asst.message.usage.unwrap();
5596 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
5597 } else {
5598 panic!("Expected Assistant message");
5599 }
5600 }
5601
5602 #[test]
5603 fn test_user_message_with_new_fields() {
5604 let json = r#"{
5605 "type": "user",
5606 "message": {
5607 "role": "user",
5608 "content": [{"type": "text", "text": "Hello"}]
5609 },
5610 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5611 "parent_tool_use_id": "toolu_123",
5612 "uuid": "user-msg-456"
5613 }"#;
5614
5615 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5616 if let ClaudeOutput::User(user) = output {
5617 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
5618 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
5619 } else {
5620 panic!("Expected User message");
5621 }
5622 }
5623
5624 #[test]
5630 fn test_user_message_preserves_tool_use_result_and_timestamp() {
5631 let json = r#"{
5632 "type":"user",
5633 "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"}]},
5634 "parent_tool_use_id":null,
5635 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5636 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
5637 "timestamp":"2026-05-12T23:12:04.121Z",
5638 "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"}}
5639 }"#;
5640
5641 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5642 let user = match output {
5643 ClaudeOutput::User(u) => u,
5644 other => panic!("Expected User message, got {:?}", other.message_type()),
5645 };
5646
5647 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
5648 let raw = user
5649 .tool_use_result
5650 .as_ref()
5651 .expect("tool_use_result must be captured");
5652 assert_eq!(raw["answers"]["Color"], "Blue");
5653 assert_eq!(raw["questions"][0]["header"], "Color");
5654
5655 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
5659 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
5660 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
5661 assert_eq!(
5662 reser["tool_use_result"]["questions"][0]["question"],
5663 "Which color do you prefer?"
5664 );
5665
5666 let typed: crate::AskUserQuestionInput = user
5669 .tool_use_result_as::<crate::AskUserQuestionInput>()
5670 .expect("tool_use_result present")
5671 .expect("AskUserQuestionInput parses");
5672 assert_eq!(typed.questions.len(), 1);
5673 assert_eq!(typed.questions[0].header, "Color");
5674 let answers = typed.answers.expect("answers populated");
5675 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
5676 }
5677
5678 #[test]
5681 fn test_user_message_without_tool_use_result_omits_field() {
5682 let json = r#"{
5683 "type":"user",
5684 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
5685 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
5686 }"#;
5687
5688 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5689 let user = match output {
5690 ClaudeOutput::User(u) => u,
5691 _ => panic!("Expected User message"),
5692 };
5693 assert!(user.tool_use_result.is_none());
5694 assert!(user.timestamp.is_none());
5695
5696 let reser = serde_json::to_value(&user).unwrap();
5697 assert!(reser.get("tool_use_result").is_none());
5698 assert!(reser.get("timestamp").is_none());
5699 }
5700
5701 #[test]
5705 fn test_subagent_result_exposes_token_accounting() {
5706 let json = r#"{
5707 "type":"user",
5708 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
5709 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
5710 "tool_use_result":{
5711 "status":"completed",
5712 "prompt":"Count the .rs files.",
5713 "agentId":"ac4f0276e9d4b6232",
5714 "agentType":"Explore",
5715 "content":[{"type":"text","text":"21"}],
5716 "resolvedModel":"claude-haiku-4-5-20251001",
5717 "totalDurationMs":6869,
5718 "totalTokens":7834,
5719 "totalToolUseCount":1,
5720 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
5721 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
5722 }
5723 }"#;
5724
5725 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5726 let user = match output {
5727 ClaudeOutput::User(u) => u,
5728 _ => panic!("Expected User message"),
5729 };
5730
5731 let result = user.subagent_result().expect("subagent result parses");
5732 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
5733 assert_eq!(
5734 result.resolved_model.as_deref(),
5735 Some("claude-haiku-4-5-20251001")
5736 );
5737 assert_eq!(result.total_tokens, Some(7834));
5738 assert_eq!(result.total_duration_ms, Some(6869));
5739 assert_eq!(result.total_tool_use_count, Some(1));
5740
5741 let usage = result.usage.expect("nested usage present");
5742 assert_eq!(usage.input_tokens, 6);
5743 assert_eq!(usage.cache_read_input_tokens, 7699);
5744
5745 let stats = result.tool_stats.expect("toolStats present");
5746 assert_eq!(stats.bash_count, 1);
5747 }
5748
5749 #[test]
5753 fn test_subagent_result_absent_for_non_task_result() {
5754 let json = r#"{
5755 "type":"user",
5756 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
5757 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5758 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
5759 }"#;
5760
5761 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5762 let user = match output {
5763 ClaudeOutput::User(u) => u,
5764 _ => panic!("Expected User message"),
5765 };
5766
5767 let result = user.subagent_result().expect("lenient parse");
5768 assert_eq!(result.total_tokens, None);
5769 assert_eq!(result.agent_type, None);
5770 }
5771
5772 #[test]
5773 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
5774 use serde_json::Value;
5775
5776 let raw: Value = serde_json::from_str(
5777 r#"{
5778 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
5779 "fast_mode_state":"off",
5780 "fast_mode_disabled_reason":"not_first_party",
5781 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
5782 }"#,
5783 )
5784 .unwrap();
5785 crate::io::assert_fully_wrapped(&raw);
5786
5787 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5788 let ClaudeOutput::System(sys) = output else {
5789 panic!("expected System");
5790 };
5791 let init = sys.as_init().expect("parses as init");
5792 assert_eq!(
5793 init.fast_mode_disabled_reason,
5794 Some(crate::FastModeDisabledReason::NotFirstParty)
5795 );
5796 let errs = init.mcp_server_errors.unwrap();
5797 assert_eq!(errs.len(), 1);
5798 assert_eq!(errs[0].name, "broken");
5799 assert_eq!(errs[0].error_type, "invalid_config");
5800 }
5801
5802 #[test]
5803 fn test_code_change_published_fully_wrapped() {
5804 use super::{KnownSystemEvent, SystemSubtype};
5805 use serde_json::Value;
5806
5807 let raw: Value = serde_json::from_str(
5808 r#"{
5809 "type":"system","subtype":"code_change_published",
5810 "provider":"github","url":"https://github.com/owner/repo/pull/42",
5811 "repo":"owner/repo","identifier":"42",
5812 "uuid":"u1","session_id":"s1"
5813 }"#,
5814 )
5815 .unwrap();
5816 crate::io::assert_fully_wrapped(&raw);
5817
5818 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5819 let ClaudeOutput::System(sys) = output else {
5820 panic!("expected System");
5821 };
5822 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
5823 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
5824 panic!("expected CodeChangePublished event");
5825 };
5826 assert_eq!(msg.provider, "github");
5827 assert_eq!(msg.repo, "owner/repo");
5828 assert_eq!(msg.identifier, "42");
5829
5830 assert!(sys.is_code_change_published());
5831 assert!(!sys.is_vcs_state_changed());
5832 let direct = sys.as_code_change_published().expect("direct accessor");
5833 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
5834 assert!(sys.as_vcs_state_changed().is_none());
5835 }
5836
5837 #[test]
5838 fn test_feedback_draft_queued_fully_wrapped() {
5839 use super::{KnownSystemEvent, SystemSubtype};
5840 use serde_json::Value;
5841
5842 let raw: Value = serde_json::from_str(
5843 r#"{
5844 "type":"system","subtype":"feedback_draft_queued",
5845 "draft_id":"draft-1","draft_type":"bug_report",
5846 "title":"Tool output was truncated",
5847 "details_preview":"The last command omitted its final lines",
5848 "uuid":"u1","session_id":"s1","future_field":"preserved"
5849 }"#,
5850 )
5851 .unwrap();
5852 crate::io::assert_fully_wrapped(&raw);
5853
5854 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5855 let ClaudeOutput::System(sys) = output else {
5856 panic!("expected System");
5857 };
5858 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
5859 assert!(sys.is_feedback_draft_queued());
5860 assert!(!sys.is_vcs_state_changed());
5861
5862 let direct = sys
5863 .as_feedback_draft_queued()
5864 .expect("direct typed accessor");
5865 assert_eq!(direct.draft_id, "draft-1");
5866 assert_eq!(direct.draft_type, "bug_report");
5867 assert_eq!(direct.extra["future_field"], "preserved");
5868
5869 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
5870 panic!("expected FeedbackDraftQueued event");
5871 };
5872 assert_eq!(known.title, "Tool output was truncated");
5873 assert_eq!(
5874 sys.typed_value().expect("typed value")["future_field"],
5875 "preserved"
5876 );
5877 }
5878
5879 #[test]
5880 fn test_cloud_session_delta_fully_wrapped() {
5881 use super::{KnownSystemEvent, SystemSubtype};
5882 use serde_json::Value;
5883
5884 let raw: Value = serde_json::from_str(
5885 r#"{
5886 "type":"system","subtype":"cloud_session_delta",
5887 "seq":3,"changed":["serving","connection"],
5888 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5889 "serving":{"state":"on"},"connection":{"state":"live"}},
5890 "uuid":"u1","session_id":"s1","future_field":"preserved"
5891 }"#,
5892 )
5893 .unwrap();
5894 crate::io::assert_fully_wrapped(&raw);
5895
5896 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5897 let ClaudeOutput::System(sys) = output else {
5898 panic!("expected System");
5899 };
5900 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5901 assert!(sys.is_cloud_session_delta());
5902 assert!(!sys.is_feedback_draft_queued());
5903
5904 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5905 assert_eq!(direct.seq, 3);
5906 assert_eq!(direct.changed, vec!["serving", "connection"]);
5907 assert_eq!(direct.cloud_session["id"], "session_abc");
5908 assert_eq!(direct.extra["future_field"], "preserved");
5909
5910 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5911 panic!("expected CloudSessionDelta event");
5912 };
5913 assert_eq!(known.session_id, "s1");
5914 assert_eq!(
5915 sys.typed_value().expect("typed value")["future_field"],
5916 "preserved"
5917 );
5918 }
5919
5920 #[test]
5921 fn test_vcs_state_changed_fully_wrapped() {
5922 use super::{KnownSystemEvent, VcsMutationKind};
5923 use serde_json::Value;
5924
5925 for kind in ["commit", "push", "merge", "rebase"] {
5926 let raw: Value = serde_json::from_str(&format!(
5927 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5928 kind
5929 ))
5930 .unwrap();
5931 crate::io::assert_fully_wrapped(&raw);
5932
5933 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5934 let ClaudeOutput::System(sys) = output else {
5935 panic!("expected System");
5936 };
5937 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5938 panic!("expected VcsStateChanged event");
5939 };
5940 assert_eq!(msg.kind.as_str(), kind);
5941 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
5942 }
5943
5944 let raw: Value = serde_json::from_str(
5946 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
5947 )
5948 .unwrap();
5949 crate::io::assert_fully_wrapped(&raw);
5950 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5951 let ClaudeOutput::System(sys) = output else {
5952 panic!("expected System");
5953 };
5954 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5955 panic!("expected VcsStateChanged event");
5956 };
5957 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
5958
5959 assert!(sys.is_vcs_state_changed());
5960 let direct = sys.as_vcs_state_changed().expect("direct accessor");
5961 assert_eq!(direct.cwd, "/repo");
5962 assert!(sys.as_code_change_published().is_none());
5963 }
5964
5965 #[test]
5966 fn test_assistant_aborted_and_resume_flags_roundtrip() {
5967 let json = r#"{
5968 "type":"assistant",
5969 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
5970 "session_id":"s1",
5971 "aborted":true,
5972 "resumed_from_incomplete_thinking":true
5973 }"#;
5974 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5975 let ClaudeOutput::Assistant(msg) = &output else {
5976 panic!("expected Assistant");
5977 };
5978 assert_eq!(msg.aborted, Some(true));
5979 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
5980 let reserialized = serde_json::to_string(&output).unwrap();
5981 assert!(reserialized.contains("\"aborted\":true"));
5982 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
5983
5984 let json = r#"{
5986 "type":"assistant",
5987 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
5988 "session_id":"s2"
5989 }"#;
5990 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5991 let reserialized = serde_json::to_string(&output).unwrap();
5992 assert!(!reserialized.contains("aborted"));
5993 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
5994 }
5995
5996 #[test]
5997 fn test_user_tool_result_meta_roundtrip() {
5998 let json = r#"{
5999 "type":"user",
6000 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6001 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6002 "tool_result_meta":[
6003 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6004 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6005 ]
6006 }"#;
6007 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6008 let ClaudeOutput::User(user) = &output else {
6009 panic!("expected User");
6010 };
6011 let meta = user.tool_result_meta.as_ref().unwrap();
6012 assert_eq!(meta.len(), 2);
6013 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6014 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6015 assert_eq!(meta[1].user_feedback, None);
6016
6017 let reserialized = serde_json::to_string(&output).unwrap();
6018 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6019 assert!(!reserialized.contains("\"user_feedback\":null"));
6020 }
6021
6022 #[test]
6026 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6027 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6028 let with_scope = serde_json::json!({
6029 "trigger": "refusal",
6030 "direction": "retry",
6031 "scope": "local",
6032 "original_model": "claude-fable-5",
6033 "fallback_model": "claude-opus-5",
6034 "request_id": null,
6035 "content": "Refused; retried on fallback model.",
6036 "uuid": "u1",
6037 "session_id": "s1"
6038 });
6039 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6040 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6041 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6042
6043 let mut without = with_scope.clone();
6046 without.as_object_mut().unwrap().remove("scope");
6047 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6048 assert_eq!(msg.scope, None);
6049 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6050
6051 assert_eq!(
6053 RefusalFallbackScope::from("workspace").as_str(),
6054 "workspace"
6055 );
6056 }
6057}