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,
3990 pub uuid: String,
3991 pub session_id: String,
3992}
3993
3994#[derive(Debug, Clone, Serialize, Deserialize)]
3999pub struct UiToastMessage {
4000 pub plugin: String,
4002 pub text: String,
4006 pub timeout_ms: u64,
4009 pub uuid: String,
4010 pub session_id: String,
4011}
4012
4013#[derive(Debug, Clone, Serialize, Deserialize)]
4017pub struct UiStatusMessage {
4018 pub plugin: String,
4020 pub text: Option<String>,
4025 pub uuid: String,
4026 pub session_id: String,
4027}
4028
4029#[derive(Debug, Clone, Serialize, Deserialize)]
4034pub struct UiInvalidateMessage {
4035 pub event: String,
4038 #[serde(default, skip_serializing_if = "Option::is_none")]
4043 pub instances: Option<Vec<UiInvalidatedInstance>>,
4044 pub uuid: String,
4045 pub session_id: String,
4046}
4047
4048#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4050pub struct UiInvalidatedInstance {
4051 pub surface: String,
4053 pub component: String,
4055 pub instance_id: String,
4058}
4059
4060#[derive(Debug, Clone, Serialize, Deserialize)]
4067pub struct UiPanesMessage {
4068 pub panes: Vec<UiPane>,
4071 pub shown_id: Option<String>,
4074 pub focused_id: Option<String>,
4077 pub focus_requested_id: Option<String>,
4081 pub uuid: String,
4082 pub session_id: String,
4083}
4084
4085#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4087pub struct UiPane {
4088 pub id: String,
4091 pub title: String,
4094 pub plugin: String,
4096 #[serde(default, skip_serializing_if = "Option::is_none")]
4099 pub close_on_escape: Option<bool>,
4100 #[serde(default, skip_serializing_if = "Option::is_none")]
4103 pub hold_toasts: Option<bool>,
4104 #[serde(default, skip_serializing_if = "Option::is_none")]
4107 pub rows: Option<u64>,
4108 #[serde(default, skip_serializing_if = "Option::is_none")]
4111 pub columns: Option<u64>,
4112}
4113
4114#[derive(Debug, Clone, Serialize, Deserialize)]
4118pub struct UiScrollMessage {
4119 pub client_id: String,
4121 pub component: UiSiteComponent,
4123 pub instance_id: String,
4125 pub offset: u64,
4128 #[serde(default, skip_serializing_if = "Option::is_none")]
4131 pub follow_end: Option<bool>,
4132 pub uuid: String,
4133 pub session_id: String,
4134}
4135
4136#[derive(Debug, Clone, Serialize, Deserialize)]
4141pub struct UiFocusMessage {
4142 pub client_id: String,
4144 pub component: UiSiteComponent,
4146 pub instance_id: String,
4148 pub plugin: String,
4150 pub key: String,
4152 pub uuid: String,
4153 pub session_id: String,
4154}
4155
4156#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4158pub enum UiSiteComponent {
4159 Pane,
4161 AbovePrompt,
4163 Unknown(String),
4165}
4166
4167impl UiSiteComponent {
4168 pub fn as_str(&self) -> &str {
4169 match self {
4170 Self::Pane => "Pane",
4171 Self::AbovePrompt => "AbovePrompt",
4172 Self::Unknown(s) => s.as_str(),
4173 }
4174 }
4175}
4176
4177impl fmt::Display for UiSiteComponent {
4178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4179 f.write_str(self.as_str())
4180 }
4181}
4182
4183impl From<&str> for UiSiteComponent {
4184 fn from(s: &str) -> Self {
4185 match s {
4186 "Pane" => Self::Pane,
4187 "AbovePrompt" => Self::AbovePrompt,
4188 other => Self::Unknown(other.to_string()),
4189 }
4190 }
4191}
4192
4193impl Serialize for UiSiteComponent {
4194 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4195 serializer.serialize_str(self.as_str())
4196 }
4197}
4198
4199impl<'de> Deserialize<'de> for UiSiteComponent {
4200 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4201 let s = String::deserialize(deserializer)?;
4202 Ok(Self::from(s.as_str()))
4203 }
4204}
4205
4206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4209pub struct LocalCommandRun {
4210 pub command: String,
4212 pub args: String,
4215}
4216
4217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4221pub struct LocalCommandOutcome {
4222 pub kind: LocalCommandOutcomeKind,
4223 #[serde(default, skip_serializing_if = "Option::is_none")]
4226 pub suggestion: Option<String>,
4227}
4228
4229#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4234pub enum LocalCommandOutcomeKind {
4235 UnavailableHeadless,
4237 Unknown,
4240 Failed,
4242 RestartRequired,
4244 Other(String),
4246}
4247
4248impl LocalCommandOutcomeKind {
4249 pub fn as_str(&self) -> &str {
4250 match self {
4251 Self::UnavailableHeadless => "unavailable_headless",
4252 Self::Unknown => "unknown",
4253 Self::Failed => "failed",
4254 Self::RestartRequired => "restart_required",
4255 Self::Other(s) => s.as_str(),
4256 }
4257 }
4258}
4259
4260impl fmt::Display for LocalCommandOutcomeKind {
4261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4262 f.write_str(self.as_str())
4263 }
4264}
4265
4266impl From<&str> for LocalCommandOutcomeKind {
4267 fn from(s: &str) -> Self {
4268 match s {
4269 "unavailable_headless" => Self::UnavailableHeadless,
4270 "unknown" => Self::Unknown,
4271 "failed" => Self::Failed,
4272 "restart_required" => Self::RestartRequired,
4273 other => Self::Other(other.to_string()),
4274 }
4275 }
4276}
4277
4278impl Serialize for LocalCommandOutcomeKind {
4279 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4280 serializer.serialize_str(self.as_str())
4281 }
4282}
4283
4284impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4285 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4286 let s = String::deserialize(deserializer)?;
4287 Ok(Self::from(s.as_str()))
4288 }
4289}
4290
4291#[derive(Debug, Clone, Serialize, Deserialize)]
4297pub struct UsageReport {
4298 pub session: super::control::UsageSession,
4300 pub rate_limits: Option<UsageReportRateLimits>,
4304 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4305 pub extra: serde_json::Map<String, Value>,
4306}
4307
4308#[derive(Debug, Clone, Serialize, Deserialize)]
4310pub struct UsageReportRateLimits {
4311 pub limits: Option<Vec<UsageReportLimit>>,
4319 #[serde(default, skip_serializing_if = "Option::is_none")]
4322 pub extra_usage: Option<UsageReportExtraUsage>,
4323 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4324 pub extra: serde_json::Map<String, Value>,
4325}
4326
4327#[derive(Debug, Clone, Serialize, Deserialize)]
4329pub struct UsageReportLimit {
4330 pub kind: String,
4333 pub group: String,
4336 pub percent: f64,
4338 pub resets_at: Option<String>,
4340 #[serde(default, skip_serializing_if = "Option::is_none")]
4343 pub scope: Option<UsageReportScope>,
4344 #[serde(default, skip_serializing_if = "Option::is_none")]
4348 pub severity: Option<String>,
4349 #[serde(default, skip_serializing_if = "Option::is_none")]
4351 pub is_active: Option<bool>,
4352 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4353 pub extra: serde_json::Map<String, Value>,
4354}
4355
4356#[derive(Debug, Clone, Serialize, Deserialize)]
4358pub struct UsageReportScope {
4359 #[serde(default, skip_serializing_if = "Option::is_none")]
4360 pub model: Option<UsageReportScopeLabel>,
4361 #[serde(default, skip_serializing_if = "Option::is_none")]
4362 pub surface: Option<UsageReportScopeLabel>,
4363 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4364 pub extra: serde_json::Map<String, Value>,
4365}
4366
4367#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4369pub struct UsageReportScopeLabel {
4370 pub display_name: String,
4371}
4372
4373#[derive(Debug, Clone, Serialize, Deserialize)]
4377pub struct UsageReportExtraUsage {
4378 pub is_enabled: bool,
4380 pub monthly_limit: Option<f64>,
4381 pub used_credits: Option<f64>,
4382 pub utilization: Option<f64>,
4383 #[serde(default, skip_serializing_if = "Option::is_none")]
4384 pub currency: Option<String>,
4385 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4386 pub extra: serde_json::Map<String, Value>,
4387}
4388
4389#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4392pub struct BatchToolUse {
4393 pub id: String,
4394 pub name: String,
4395}
4396
4397#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4402pub struct ContextUsage {
4403 pub model: String,
4405 pub total_tokens: u64,
4408 pub raw_max_tokens: u64,
4411 pub percentage: u64,
4413 #[serde(default, skip_serializing_if = "Option::is_none")]
4415 pub over_limit: Option<ContextOverLimit>,
4416 #[serde(default)]
4418 pub categories: Vec<ContextCategory>,
4419 #[serde(default)]
4421 pub mcp_tools: Vec<ContextMcpTool>,
4422 #[serde(default)]
4424 pub memory_files: Vec<ContextMemoryFile>,
4425 #[serde(default)]
4427 pub agents: Vec<ContextAgent>,
4428 #[serde(default, skip_serializing_if = "Option::is_none")]
4430 pub skills: Option<Vec<ContextSkill>>,
4431}
4432
4433#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4435pub struct ContextOverLimit {
4436 pub tokens_over: u64,
4437 pub kind: String,
4440}
4441
4442#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4444pub struct ContextCategory {
4445 pub name: String,
4448 pub tokens: u64,
4449 pub kind: String,
4453}
4454
4455#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4457pub struct ContextMcpTool {
4458 pub name: String,
4460 pub server_name: String,
4461 pub tokens: u64,
4462}
4463
4464#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4466pub struct ContextMemoryFile {
4467 pub path: String,
4468 #[serde(rename = "type")]
4470 pub file_type: String,
4471 pub tokens: u64,
4472}
4473
4474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4476pub struct ContextAgent {
4477 pub agent_type: String,
4478 pub source: String,
4480 pub tokens: u64,
4481}
4482
4483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4485pub struct ContextSkill {
4486 pub name: String,
4487 pub source: String,
4489 #[serde(default, skip_serializing_if = "Option::is_none")]
4490 pub plugin_name: Option<String>,
4491 pub tokens: u64,
4492}
4493
4494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4496pub struct ToolUseMeta {
4497 pub id: String,
4498 pub display_name: String,
4499 #[serde(default, skip_serializing_if = "Option::is_none")]
4500 pub server_display_name: Option<String>,
4501 #[serde(default, skip_serializing_if = "Option::is_none")]
4502 pub icon_url: Option<String>,
4503}
4504
4505#[derive(Debug, Clone, Serialize, Deserialize)]
4507pub struct AssistantMessage {
4508 pub message: AssistantMessageContent,
4509 #[serde(alias = "sessionId")]
4510 pub session_id: String,
4511 #[serde(skip_serializing_if = "Option::is_none")]
4512 pub uuid: Option<String>,
4513 #[serde(skip_serializing_if = "Option::is_none")]
4514 pub parent_tool_use_id: Option<String>,
4515 #[serde(skip_serializing_if = "Option::is_none")]
4517 pub request_id: Option<String>,
4518 #[serde(default, skip_serializing_if = "Option::is_none")]
4527 pub user_message_uuid: Option<String>,
4528 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4534 pub user_message_uuids: Vec<String>,
4535 #[serde(skip_serializing_if = "Option::is_none")]
4538 pub subagent_type: Option<String>,
4539 #[serde(skip_serializing_if = "Option::is_none")]
4541 pub task_description: Option<String>,
4542 #[serde(skip_serializing_if = "Option::is_none")]
4543 pub error: Option<AssistantErrorKind>,
4544 #[serde(default, skip_serializing_if = "Option::is_none")]
4548 pub aborted: Option<bool>,
4549 #[serde(default, skip_serializing_if = "Option::is_none")]
4554 pub resumed_from_incomplete_thinking: Option<bool>,
4555 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4556 pub supersedes: Vec<String>,
4557 #[serde(skip_serializing_if = "Option::is_none")]
4558 pub timestamp: Option<String>,
4559 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4560 pub tool_use_meta: Vec<ToolUseMeta>,
4561 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4566 pub batch_tool_uses: Vec<BatchToolUse>,
4567 #[serde(default, skip_serializing_if = "Option::is_none")]
4573 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
4574 #[serde(default, skip_serializing_if = "Option::is_none")]
4580 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
4581 #[serde(default, skip_serializing_if = "Option::is_none")]
4585 pub historical: Option<bool>,
4586 #[serde(default, skip_serializing_if = "Option::is_none")]
4594 pub resume_reason: Option<String>,
4595 #[serde(default, skip_serializing_if = "Option::is_none")]
4601 pub local_command_source: Option<String>,
4602 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4611 pub narration_block_indexes: Vec<usize>,
4612 #[serde(default, skip_serializing_if = "Option::is_none")]
4616 pub narration_hint: Option<String>,
4617 #[serde(default, skip_serializing_if = "Option::is_none")]
4626 pub thinking_duration_ms: Option<u64>,
4627 #[serde(default, skip_serializing_if = "Option::is_none")]
4631 pub context_usage: Option<ContextUsage>,
4632 #[serde(default, skip_serializing_if = "Option::is_none")]
4640 pub usage_report: Option<Box<UsageReport>>,
4641 #[serde(default, skip_serializing_if = "Option::is_none")]
4648 pub local_command_run: Option<LocalCommandRun>,
4649 #[serde(default, skip_serializing_if = "Option::is_none")]
4655 pub local_command_outcome: Option<LocalCommandOutcome>,
4656 #[serde(default, skip_serializing_if = "Option::is_none")]
4657 pub is_meta: Option<bool>,
4658 #[serde(default, skip_serializing_if = "Option::is_none")]
4659 pub is_virtual: Option<bool>,
4660 #[serde(default, skip_serializing_if = "Option::is_none")]
4661 pub is_api_error_message: Option<bool>,
4662 #[serde(skip_serializing_if = "Option::is_none")]
4663 pub api_error_status: Option<u16>,
4664 #[serde(skip_serializing_if = "Option::is_none")]
4681 pub api_error: Option<String>,
4682 #[serde(default, skip_serializing_if = "Option::is_none")]
4689 pub api_error_code: Option<String>,
4690 #[serde(default, skip_serializing_if = "Option::is_none")]
4693 pub api_error_params: Option<ApiErrorParams>,
4694 #[serde(skip_serializing_if = "Option::is_none")]
4695 pub error_details: Option<String>,
4696 #[serde(skip_serializing_if = "Option::is_none")]
4697 pub advisor_model: Option<String>,
4698 #[serde(skip_serializing_if = "Option::is_none")]
4699 pub attribution_agent: Option<String>,
4700 #[serde(skip_serializing_if = "Option::is_none")]
4701 pub attribution_skill: Option<String>,
4702 #[serde(skip_serializing_if = "Option::is_none")]
4703 pub attribution_plugin: Option<String>,
4704 #[serde(skip_serializing_if = "Option::is_none")]
4705 pub attribution_mcp_server: Option<String>,
4706 #[serde(skip_serializing_if = "Option::is_none")]
4707 pub attribution_mcp_tool: Option<String>,
4708}
4709
4710#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
4714pub struct ApiErrorParams {
4715 #[serde(default, skip_serializing_if = "Option::is_none")]
4717 pub effort: Option<String>,
4718 #[serde(default, skip_serializing_if = "Option::is_none")]
4721 pub provider: Option<ApiErrorProvider>,
4722 #[serde(default, skip_serializing_if = "Option::is_none")]
4725 pub remedy: Option<ApiErrorRemedy>,
4726 #[serde(default, skip_serializing_if = "Option::is_none")]
4729 pub media: Option<ApiErrorMedia>,
4730 #[serde(default, skip_serializing_if = "Option::is_none")]
4732 pub media_reason: Option<ApiErrorMediaReason>,
4733}
4734
4735#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4737pub enum ApiErrorMedia {
4738 Image,
4739 Document,
4740 Unknown(String),
4742}
4743
4744impl ApiErrorMedia {
4745 pub fn as_str(&self) -> &str {
4746 match self {
4747 Self::Image => "image",
4748 Self::Document => "document",
4749 Self::Unknown(s) => s.as_str(),
4750 }
4751 }
4752}
4753
4754impl fmt::Display for ApiErrorMedia {
4755 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4756 f.write_str(self.as_str())
4757 }
4758}
4759
4760impl From<&str> for ApiErrorMedia {
4761 fn from(s: &str) -> Self {
4762 match s {
4763 "image" => Self::Image,
4764 "document" => Self::Document,
4765 other => Self::Unknown(other.to_string()),
4766 }
4767 }
4768}
4769
4770impl Serialize for ApiErrorMedia {
4771 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4772 serializer.serialize_str(self.as_str())
4773 }
4774}
4775
4776impl<'de> Deserialize<'de> for ApiErrorMedia {
4777 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4778 let s = String::deserialize(deserializer)?;
4779 Ok(Self::from(s.as_str()))
4780 }
4781}
4782
4783#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4786pub enum ApiErrorMediaReason {
4787 Unprocessable,
4790 UnsupportedByModel,
4792 MediaBudget,
4795 Unknown(String),
4797}
4798
4799impl ApiErrorMediaReason {
4800 pub fn as_str(&self) -> &str {
4801 match self {
4802 Self::Unprocessable => "unprocessable",
4803 Self::UnsupportedByModel => "unsupported_by_model",
4804 Self::MediaBudget => "media_budget",
4805 Self::Unknown(s) => s.as_str(),
4806 }
4807 }
4808}
4809
4810impl fmt::Display for ApiErrorMediaReason {
4811 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4812 f.write_str(self.as_str())
4813 }
4814}
4815
4816impl From<&str> for ApiErrorMediaReason {
4817 fn from(s: &str) -> Self {
4818 match s {
4819 "unprocessable" => Self::Unprocessable,
4820 "unsupported_by_model" => Self::UnsupportedByModel,
4821 "media_budget" => Self::MediaBudget,
4822 other => Self::Unknown(other.to_string()),
4823 }
4824 }
4825}
4826
4827impl Serialize for ApiErrorMediaReason {
4828 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4829 serializer.serialize_str(self.as_str())
4830 }
4831}
4832
4833impl<'de> Deserialize<'de> for ApiErrorMediaReason {
4834 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4835 let s = String::deserialize(deserializer)?;
4836 Ok(Self::from(s.as_str()))
4837 }
4838}
4839
4840#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4842pub enum ApiErrorProvider {
4843 Bedrock,
4844 AnthropicAws,
4845 Mantle,
4846 AnthropicGoogleCloud,
4847 Vertex,
4848 Foundry,
4849 Gateway,
4850 Unknown(String),
4852}
4853
4854impl ApiErrorProvider {
4855 pub fn as_str(&self) -> &str {
4856 match self {
4857 Self::Bedrock => "bedrock",
4858 Self::AnthropicAws => "anthropicAws",
4859 Self::Mantle => "mantle",
4860 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
4861 Self::Vertex => "vertex",
4862 Self::Foundry => "foundry",
4863 Self::Gateway => "gateway",
4864 Self::Unknown(s) => s.as_str(),
4865 }
4866 }
4867}
4868
4869impl fmt::Display for ApiErrorProvider {
4870 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4871 f.write_str(self.as_str())
4872 }
4873}
4874
4875impl From<&str> for ApiErrorProvider {
4876 fn from(s: &str) -> Self {
4877 match s {
4878 "bedrock" => Self::Bedrock,
4879 "anthropicAws" => Self::AnthropicAws,
4880 "mantle" => Self::Mantle,
4881 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4882 "vertex" => Self::Vertex,
4883 "foundry" => Self::Foundry,
4884 "gateway" => Self::Gateway,
4885 other => Self::Unknown(other.to_string()),
4886 }
4887 }
4888}
4889
4890impl Serialize for ApiErrorProvider {
4891 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4892 serializer.serialize_str(self.as_str())
4893 }
4894}
4895
4896impl<'de> Deserialize<'de> for ApiErrorProvider {
4897 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4898 let s = String::deserialize(deserializer)?;
4899 Ok(Self::from(s.as_str()))
4900 }
4901}
4902
4903#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4905pub enum ApiErrorRemedy {
4906 RefreshCommand,
4909 RefreshCredentials,
4911 Adc,
4914 GatewayToken,
4917 HostManaged,
4919 ModelAccess,
4922 Unknown(String),
4924}
4925
4926impl ApiErrorRemedy {
4927 pub fn as_str(&self) -> &str {
4928 match self {
4929 Self::RefreshCommand => "refresh_command",
4930 Self::RefreshCredentials => "refresh_credentials",
4931 Self::Adc => "adc",
4932 Self::GatewayToken => "gateway_token",
4933 Self::HostManaged => "host_managed",
4934 Self::ModelAccess => "model_access",
4935 Self::Unknown(s) => s.as_str(),
4936 }
4937 }
4938}
4939
4940impl fmt::Display for ApiErrorRemedy {
4941 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4942 f.write_str(self.as_str())
4943 }
4944}
4945
4946impl From<&str> for ApiErrorRemedy {
4947 fn from(s: &str) -> Self {
4948 match s {
4949 "refresh_command" => Self::RefreshCommand,
4950 "refresh_credentials" => Self::RefreshCredentials,
4951 "adc" => Self::Adc,
4952 "gateway_token" => Self::GatewayToken,
4953 "host_managed" => Self::HostManaged,
4954 "model_access" => Self::ModelAccess,
4955 other => Self::Unknown(other.to_string()),
4956 }
4957 }
4958}
4959
4960impl Serialize for ApiErrorRemedy {
4961 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4962 serializer.serialize_str(self.as_str())
4963 }
4964}
4965
4966impl<'de> Deserialize<'de> for ApiErrorRemedy {
4967 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4968 let s = String::deserialize(deserializer)?;
4969 Ok(Self::from(s.as_str()))
4970 }
4971}
4972
4973#[derive(Debug, Clone, Serialize, Deserialize)]
4975pub struct AssistantMessageContent {
4976 pub id: String,
4977 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
4979 pub message_type: Option<String>,
4980 pub role: MessageRole,
4981 pub model: String,
4982 pub content: Vec<ContentBlock>,
4983 #[serde(skip_serializing_if = "Option::is_none")]
4984 pub stop_reason: Option<StopReason>,
4985 #[serde(skip_serializing_if = "Option::is_none")]
4986 pub stop_sequence: Option<String>,
4987 #[serde(skip_serializing_if = "Option::is_none")]
4988 pub usage: Option<AssistantUsage>,
4989 #[serde(skip_serializing_if = "Option::is_none")]
4991 pub stop_details: Option<Value>,
4992 #[serde(skip_serializing_if = "Option::is_none")]
4994 pub context_management: Option<Value>,
4995}
4996
4997#[derive(Debug, Clone, Serialize, Deserialize)]
4999pub struct AssistantUsage {
5000 #[serde(default)]
5002 pub input_tokens: u32,
5003
5004 #[serde(default)]
5006 pub output_tokens: u32,
5007
5008 #[serde(default)]
5010 pub cache_creation_input_tokens: u32,
5011
5012 #[serde(default)]
5014 pub cache_read_input_tokens: u32,
5015
5016 #[serde(skip_serializing_if = "Option::is_none")]
5018 pub service_tier: Option<String>,
5019
5020 #[serde(skip_serializing_if = "Option::is_none")]
5022 pub cache_creation: Option<CacheCreationDetails>,
5023
5024 #[serde(skip_serializing_if = "Option::is_none")]
5026 pub inference_geo: Option<String>,
5027
5028 #[serde(default, skip_serializing_if = "Option::is_none")]
5031 pub fallback_credit: Option<Value>,
5032}
5033
5034#[derive(Debug, Clone, Serialize, Deserialize)]
5036pub struct CacheCreationDetails {
5037 #[serde(default)]
5039 pub ephemeral_1h_input_tokens: u32,
5040
5041 #[serde(default)]
5043 pub ephemeral_5m_input_tokens: u32,
5044}
5045
5046#[cfg(test)]
5047mod tests {
5048 use crate::io::ClaudeOutput;
5049
5050 #[test]
5051 fn test_subagent_usage_rollup_accumulates_task_results() {
5052 use super::SubagentUsageRollup;
5053
5054 let mut rollup = SubagentUsageRollup::default();
5055
5056 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}}"#;
5057 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5058 assert!(rollup.observe(&output));
5059 assert_eq!(rollup.subagent_tokens, 10201);
5060 assert_eq!(rollup.agent_count, 1);
5061 assert_eq!(rollup.tool_uses, 3);
5062 assert_eq!(rollup.duration_ms, 1853);
5063
5064 assert!(!rollup.observe(&output));
5066 assert_eq!(rollup.agent_count, 1);
5067 assert_eq!(rollup.subagent_tokens, 10201);
5068
5069 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}}"#;
5071 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5072 assert!(rollup.observe(&output));
5073 assert_eq!(rollup.agent_count, 2);
5074 assert_eq!(rollup.subagent_tokens, 10701);
5075 }
5076
5077 #[test]
5078 fn test_subagent_usage_rollup_ignores_non_task_results() {
5079 use super::SubagentUsageRollup;
5080
5081 let mut rollup = SubagentUsageRollup::default();
5082
5083 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}}"#;
5086 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5087 assert!(!rollup.observe(&output));
5088
5089 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5091 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5092 assert!(!rollup.observe(&output));
5093
5094 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5096 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5097 assert!(!rollup.observe(&output));
5098
5099 assert_eq!(rollup, SubagentUsageRollup::default());
5100 }
5101
5102 #[test]
5103 fn test_subagent_usage_rollup_over_captured_session() {
5104 use super::SubagentUsageRollup;
5105
5106 let mut rollup = SubagentUsageRollup::default();
5107 let fixture =
5108 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5109 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5110 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5111 rollup.observe(&output);
5112 }
5113 }
5114 assert_eq!(rollup.agent_count, 1);
5115 assert_eq!(rollup.subagent_tokens, 10201);
5116 }
5117
5118 #[test]
5119 fn test_system_message_init() {
5120 let json = r#"{
5121 "type": "system",
5122 "subtype": "init",
5123 "session_id": "test-session-123",
5124 "cwd": "/home/user/project",
5125 "model": "claude-sonnet-4",
5126 "tools": ["Bash", "Read", "Write"],
5127 "mcp_servers": [],
5128 "slash_commands": ["compact", "cost", "review"],
5129 "agents": ["Bash", "Explore", "Plan"],
5130 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5131 "skills": [],
5132 "claude_code_version": "2.1.15",
5133 "apiKeySource": "none",
5134 "output_style": "default",
5135 "permissionMode": "default"
5136 }"#;
5137
5138 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5139 if let ClaudeOutput::System(sys) = output {
5140 assert!(sys.is_init());
5141 assert!(!sys.is_status());
5142 assert!(!sys.is_compact_boundary());
5143
5144 let init = sys.as_init().expect("Should parse as init");
5145 assert_eq!(init.session_id, "test-session-123");
5146 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5147 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5148 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5149 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5150 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5151 assert_eq!(init.plugins.len(), 1);
5152 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5153 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5154 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5155 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5156 assert_eq!(
5157 init.permission_mode,
5158 Some(super::InitPermissionMode::Default)
5159 );
5160 } else {
5161 panic!("Expected System message");
5162 }
5163 }
5164
5165 #[test]
5166 fn test_system_message_init_from_real_capture() {
5167 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5168 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5169 if let ClaudeOutput::System(sys) = output {
5170 let init = sys.as_init().expect("Should parse real init capture");
5171 assert_eq!(init.slash_commands.len(), 8);
5172 assert!(init.slash_commands.contains(&"compact".to_string()));
5173 assert!(init.slash_commands.contains(&"review".to_string()));
5174 assert_eq!(init.agents.len(), 5);
5175 assert!(init.agents.contains(&"Bash".to_string()));
5176 assert!(init.agents.contains(&"Explore".to_string()));
5177 assert_eq!(init.plugins.len(), 1);
5178 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5179 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5180 } else {
5181 panic!("Expected System message");
5182 }
5183 }
5184
5185 #[test]
5186 fn test_system_message_status() {
5187 let json = r#"{
5188 "type": "system",
5189 "subtype": "status",
5190 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5191 "status": "compacting",
5192 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5193 }"#;
5194
5195 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5196 if let ClaudeOutput::System(sys) = output {
5197 assert!(sys.is_status());
5198 assert!(!sys.is_init());
5199
5200 let status = sys.as_status().expect("Should parse as status");
5201 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5202 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5203 assert_eq!(
5204 status.uuid,
5205 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5206 );
5207 } else {
5208 panic!("Expected System message");
5209 }
5210 }
5211
5212 #[test]
5213 fn test_system_message_status_null() {
5214 let json = r#"{
5215 "type": "system",
5216 "subtype": "status",
5217 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5218 "status": null,
5219 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5220 }"#;
5221
5222 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5223 if let ClaudeOutput::System(sys) = output {
5224 let status = sys.as_status().expect("Should parse as status");
5225 assert_eq!(status.status, None);
5226 } else {
5227 panic!("Expected System message");
5228 }
5229 }
5230
5231 #[test]
5232 fn test_system_message_task_started() {
5233 let json = r#"{
5234 "type": "system",
5235 "subtype": "task_started",
5236 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5237 "task_id": "b6daf3f",
5238 "task_type": "local_bash",
5239 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5240 "description": "Wait for CI on PR #12",
5241 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5242 }"#;
5243
5244 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5245 if let ClaudeOutput::System(sys) = output {
5246 assert!(sys.is_task_started());
5247 assert!(!sys.is_task_progress());
5248 assert!(!sys.is_task_notification());
5249
5250 let task = sys.as_task_started().expect("Should parse as task_started");
5251 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5252 assert_eq!(task.task_id, "b6daf3f");
5253 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5254 assert_eq!(
5255 task.tool_use_id.as_deref(),
5256 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5257 );
5258 assert_eq!(task.description, "Wait for CI on PR #12");
5259 } else {
5260 panic!("Expected System message");
5261 }
5262 }
5263
5264 #[test]
5265 fn test_system_message_task_started_agent() {
5266 let json = r#"{
5267 "type": "system",
5268 "subtype": "task_started",
5269 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5270 "task_id": "a4a7e0906e5fc64cc",
5271 "task_type": "local_agent",
5272 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5273 "description": "Explore Scene/ArrayScene duplication",
5274 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5275 }"#;
5276
5277 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5278 if let ClaudeOutput::System(sys) = output {
5279 let task = sys.as_task_started().expect("Should parse as task_started");
5280 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5281 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5282 } else {
5283 panic!("Expected System message");
5284 }
5285 }
5286
5287 #[test]
5288 fn test_system_message_task_progress() {
5289 let json = r#"{
5290 "type": "system",
5291 "subtype": "task_progress",
5292 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5293 "task_id": "a4a7e0906e5fc64cc",
5294 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5295 "description": "Reading src/jplephem/chebyshev.rs",
5296 "last_tool_name": "Read",
5297 "usage": {
5298 "duration_ms": 13996,
5299 "tool_uses": 9,
5300 "total_tokens": 38779
5301 },
5302 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5303 }"#;
5304
5305 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5306 if let ClaudeOutput::System(sys) = output {
5307 assert!(sys.is_task_progress());
5308 assert!(!sys.is_task_started());
5309
5310 let progress = sys
5311 .as_task_progress()
5312 .expect("Should parse as task_progress");
5313 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5314 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5315 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5316 assert_eq!(progress.usage.duration_ms, 13996);
5317 assert_eq!(progress.usage.tool_uses, 9);
5318 assert_eq!(progress.usage.total_tokens, 38779);
5319 } else {
5320 panic!("Expected System message");
5321 }
5322 }
5323
5324 #[test]
5325 fn test_system_message_task_notification_completed() {
5326 let json = r#"{
5327 "type": "system",
5328 "subtype": "task_notification",
5329 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5330 "task_id": "a0ba761e9dc9c316f",
5331 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
5332 "status": "completed",
5333 "summary": "Agent \"Write Hipparcos data source doc\" completed",
5334 "output_file": "",
5335 "usage": {
5336 "duration_ms": 172300,
5337 "tool_uses": 11,
5338 "total_tokens": 42005
5339 },
5340 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
5341 }"#;
5342
5343 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5344 if let ClaudeOutput::System(sys) = output {
5345 assert!(sys.is_task_notification());
5346
5347 let notif = sys
5348 .as_task_notification()
5349 .expect("Should parse as task_notification");
5350 assert_eq!(notif.status, super::TaskStatus::Completed);
5351 assert_eq!(
5352 notif.summary,
5353 "Agent \"Write Hipparcos data source doc\" completed"
5354 );
5355 assert_eq!(notif.output_file, Some("".to_string()));
5356 assert_eq!(
5357 notif.tool_use_id,
5358 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
5359 );
5360 let usage = notif.usage.expect("Should have usage");
5361 assert_eq!(usage.duration_ms, 172300);
5362 assert_eq!(usage.tool_uses, 11);
5363 assert_eq!(usage.total_tokens, 42005);
5364 } else {
5365 panic!("Expected System message");
5366 }
5367 }
5368
5369 #[test]
5370 fn test_system_message_task_notification_failed_no_usage() {
5371 let json = r#"{
5372 "type": "system",
5373 "subtype": "task_notification",
5374 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
5375 "task_id": "b98f6a3",
5376 "status": "failed",
5377 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
5378 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
5379 }"#;
5380
5381 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5382 if let ClaudeOutput::System(sys) = output {
5383 let notif = sys
5384 .as_task_notification()
5385 .expect("Should parse as task_notification");
5386 assert_eq!(notif.status, super::TaskStatus::Failed);
5387 assert!(notif.tool_use_id.is_none());
5388 assert!(notif.usage.is_none());
5389 assert_eq!(
5390 notif.output_file,
5391 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
5392 );
5393 } else {
5394 panic!("Expected System message");
5395 }
5396 }
5397
5398 #[test]
5404 fn test_task_messages_roundtrip_through_value() {
5405 let cases = [
5406 r#"{"type":"system","subtype":"task_started","session_id":"s1",
5407 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
5408 "description":"Sleep 3s","uuid":"u1"}"#,
5409 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
5410 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
5411 "last_tool_name":"Bash",
5412 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5413 "uuid":"u2"}"#,
5414 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
5415 "task_id":"t1","tool_use_id":"tu1","status":"completed",
5416 "summary":"done","output_file":"",
5417 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5418 "uuid":"u3"}"#,
5419 ];
5420
5421 for json in cases {
5422 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5423 let value = serde_json::to_value(&output).unwrap();
5424 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
5425
5426 let ClaudeOutput::System(sys) = reparsed else {
5427 panic!("Expected System variant after round-trip");
5428 };
5429
5430 match sys.subtype {
5431 super::SystemSubtype::TaskStarted => {
5432 assert!(
5433 sys.as_task_started().is_some(),
5434 "as_task_started failed after round-trip"
5435 );
5436 }
5437 super::SystemSubtype::TaskProgress => {
5438 assert!(
5439 sys.as_task_progress().is_some(),
5440 "as_task_progress failed after round-trip"
5441 );
5442 }
5443 super::SystemSubtype::TaskNotification => {
5444 assert!(
5445 sys.as_task_notification().is_some(),
5446 "as_task_notification failed after round-trip"
5447 );
5448 }
5449 other => panic!("unexpected subtype after round-trip: {other:?}"),
5450 }
5451 }
5452 }
5453
5454 #[test]
5455 fn test_system_message_compact_boundary() {
5456 let json = r#"{
5457 "type": "system",
5458 "subtype": "compact_boundary",
5459 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5460 "compact_metadata": {
5461 "pre_tokens": 155285,
5462 "trigger": "auto"
5463 },
5464 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
5465 }"#;
5466
5467 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5468 if let ClaudeOutput::System(sys) = output {
5469 assert!(sys.is_compact_boundary());
5470 assert!(!sys.is_init());
5471 assert!(!sys.is_status());
5472
5473 let compact = sys
5474 .as_compact_boundary()
5475 .expect("Should parse as compact_boundary");
5476 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5477 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
5478 assert_eq!(
5479 compact.compact_metadata.trigger,
5480 super::CompactionTrigger::Auto
5481 );
5482 assert!(compact.summary.is_none());
5484 assert!(compact.leaf_message_count.is_none());
5485 assert!(compact.duration_ms.is_none());
5486 } else {
5487 panic!("Expected System message");
5488 }
5489 }
5490
5491 #[test]
5492 fn test_compact_boundary_with_summary_stats() {
5493 let json = r#"{
5495 "type": "system",
5496 "subtype": "compact_boundary",
5497 "session_id": "s1",
5498 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
5499 "summary": "Summarized the earlier exploration.",
5500 "leaf_message_count": 42,
5501 "duration_ms": 1234,
5502 "uuid": "u1"
5503 }"#;
5504 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5505 let ClaudeOutput::System(sys) = output else {
5506 panic!("Expected System message");
5507 };
5508 let compact = sys.as_compact_boundary().expect("compact_boundary");
5509 assert_eq!(
5510 compact.summary.as_deref(),
5511 Some("Summarized the earlier exploration.")
5512 );
5513 assert_eq!(compact.leaf_message_count, Some(42));
5514 assert_eq!(compact.duration_ms, Some(1234));
5515
5516 let json_alt = r#"{
5519 "type": "system",
5520 "subtype": "compact_boundary",
5521 "session_id": "s2",
5522 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
5523 "content": "alt-key summary",
5524 "message_count": 7
5525 }"#;
5526 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
5527 let ClaudeOutput::System(sys) = output else {
5528 panic!("Expected System message");
5529 };
5530 let compact = sys.as_compact_boundary().expect("compact_boundary");
5531 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
5532 assert_eq!(compact.leaf_message_count, Some(7));
5533 }
5534
5535 #[test]
5536 fn test_init_message_with_new_fields() {
5537 let json = r#"{
5538 "type": "system",
5539 "subtype": "init",
5540 "session_id": "test-session",
5541 "cwd": "/home/user",
5542 "model": "claude-opus-4-7",
5543 "tools": ["Bash"],
5544 "mcp_servers": [],
5545 "permissionMode": "default",
5546 "apiKeySource": "none",
5547 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
5548 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
5549 "fast_mode_state": "off",
5550 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
5551 "claude_code_version": "2.1.117"
5552 }"#;
5553
5554 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5555 if let ClaudeOutput::System(sys) = output {
5556 let init = sys.as_init().expect("Should parse as init");
5557 assert_eq!(
5558 init.uuid.as_deref(),
5559 Some("44841a0d-182d-493a-86b5-79800d3d9665")
5560 );
5561 assert!(init.memory_paths.is_some());
5562 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
5563 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
5564 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
5565 } else {
5566 panic!("Expected System message");
5567 }
5568 }
5569
5570 #[test]
5571 fn test_assistant_message_with_new_fields() {
5572 let json = r#"{
5573 "type": "assistant",
5574 "message": {
5575 "id": "msg_1",
5576 "type": "message",
5577 "role": "assistant",
5578 "model": "claude-opus-4-7",
5579 "content": [{"type": "text", "text": "Hello"}],
5580 "stop_reason": "end_turn",
5581 "stop_details": null,
5582 "context_management": null,
5583 "usage": {
5584 "input_tokens": 100,
5585 "output_tokens": 10,
5586 "cache_creation_input_tokens": 50,
5587 "cache_read_input_tokens": 0,
5588 "service_tier": "standard",
5589 "inference_geo": "not_available"
5590 }
5591 },
5592 "session_id": "abc",
5593 "uuid": "msg-uuid-123"
5594 }"#;
5595
5596 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5597 if let ClaudeOutput::Assistant(asst) = output {
5598 assert_eq!(asst.message.stop_details, None);
5599 assert_eq!(asst.message.context_management, None);
5600 let usage = asst.message.usage.unwrap();
5601 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
5602 } else {
5603 panic!("Expected Assistant message");
5604 }
5605 }
5606
5607 #[test]
5608 fn test_user_message_with_new_fields() {
5609 let json = r#"{
5610 "type": "user",
5611 "message": {
5612 "role": "user",
5613 "content": [{"type": "text", "text": "Hello"}]
5614 },
5615 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5616 "parent_tool_use_id": "toolu_123",
5617 "uuid": "user-msg-456"
5618 }"#;
5619
5620 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5621 if let ClaudeOutput::User(user) = output {
5622 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
5623 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
5624 } else {
5625 panic!("Expected User message");
5626 }
5627 }
5628
5629 #[test]
5635 fn test_user_message_preserves_tool_use_result_and_timestamp() {
5636 let json = r#"{
5637 "type":"user",
5638 "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"}]},
5639 "parent_tool_use_id":null,
5640 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5641 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
5642 "timestamp":"2026-05-12T23:12:04.121Z",
5643 "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"}}
5644 }"#;
5645
5646 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5647 let user = match output {
5648 ClaudeOutput::User(u) => u,
5649 other => panic!("Expected User message, got {:?}", other.message_type()),
5650 };
5651
5652 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
5653 let raw = user
5654 .tool_use_result
5655 .as_ref()
5656 .expect("tool_use_result must be captured");
5657 assert_eq!(raw["answers"]["Color"], "Blue");
5658 assert_eq!(raw["questions"][0]["header"], "Color");
5659
5660 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
5664 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
5665 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
5666 assert_eq!(
5667 reser["tool_use_result"]["questions"][0]["question"],
5668 "Which color do you prefer?"
5669 );
5670
5671 let typed: crate::AskUserQuestionInput = user
5674 .tool_use_result_as::<crate::AskUserQuestionInput>()
5675 .expect("tool_use_result present")
5676 .expect("AskUserQuestionInput parses");
5677 assert_eq!(typed.questions.len(), 1);
5678 assert_eq!(typed.questions[0].header, "Color");
5679 let answers = typed.answers.expect("answers populated");
5680 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
5681 }
5682
5683 #[test]
5686 fn test_user_message_without_tool_use_result_omits_field() {
5687 let json = r#"{
5688 "type":"user",
5689 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
5690 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
5691 }"#;
5692
5693 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5694 let user = match output {
5695 ClaudeOutput::User(u) => u,
5696 _ => panic!("Expected User message"),
5697 };
5698 assert!(user.tool_use_result.is_none());
5699 assert!(user.timestamp.is_none());
5700
5701 let reser = serde_json::to_value(&user).unwrap();
5702 assert!(reser.get("tool_use_result").is_none());
5703 assert!(reser.get("timestamp").is_none());
5704 }
5705
5706 #[test]
5710 fn test_subagent_result_exposes_token_accounting() {
5711 let json = r#"{
5712 "type":"user",
5713 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
5714 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
5715 "tool_use_result":{
5716 "status":"completed",
5717 "prompt":"Count the .rs files.",
5718 "agentId":"ac4f0276e9d4b6232",
5719 "agentType":"Explore",
5720 "content":[{"type":"text","text":"21"}],
5721 "resolvedModel":"claude-haiku-4-5-20251001",
5722 "totalDurationMs":6869,
5723 "totalTokens":7834,
5724 "totalToolUseCount":1,
5725 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
5726 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
5727 }
5728 }"#;
5729
5730 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5731 let user = match output {
5732 ClaudeOutput::User(u) => u,
5733 _ => panic!("Expected User message"),
5734 };
5735
5736 let result = user.subagent_result().expect("subagent result parses");
5737 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
5738 assert_eq!(
5739 result.resolved_model.as_deref(),
5740 Some("claude-haiku-4-5-20251001")
5741 );
5742 assert_eq!(result.total_tokens, Some(7834));
5743 assert_eq!(result.total_duration_ms, Some(6869));
5744 assert_eq!(result.total_tool_use_count, Some(1));
5745
5746 let usage = result.usage.expect("nested usage present");
5747 assert_eq!(usage.input_tokens, 6);
5748 assert_eq!(usage.cache_read_input_tokens, 7699);
5749
5750 let stats = result.tool_stats.expect("toolStats present");
5751 assert_eq!(stats.bash_count, 1);
5752 }
5753
5754 #[test]
5758 fn test_subagent_result_absent_for_non_task_result() {
5759 let json = r#"{
5760 "type":"user",
5761 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
5762 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5763 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
5764 }"#;
5765
5766 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5767 let user = match output {
5768 ClaudeOutput::User(u) => u,
5769 _ => panic!("Expected User message"),
5770 };
5771
5772 let result = user.subagent_result().expect("lenient parse");
5773 assert_eq!(result.total_tokens, None);
5774 assert_eq!(result.agent_type, None);
5775 }
5776
5777 #[test]
5778 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
5779 use serde_json::Value;
5780
5781 let raw: Value = serde_json::from_str(
5782 r#"{
5783 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
5784 "fast_mode_state":"off",
5785 "fast_mode_disabled_reason":"not_first_party",
5786 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
5787 }"#,
5788 )
5789 .unwrap();
5790 crate::io::assert_fully_wrapped(&raw);
5791
5792 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5793 let ClaudeOutput::System(sys) = output else {
5794 panic!("expected System");
5795 };
5796 let init = sys.as_init().expect("parses as init");
5797 assert_eq!(
5798 init.fast_mode_disabled_reason,
5799 Some(crate::FastModeDisabledReason::NotFirstParty)
5800 );
5801 let errs = init.mcp_server_errors.unwrap();
5802 assert_eq!(errs.len(), 1);
5803 assert_eq!(errs[0].name, "broken");
5804 assert_eq!(errs[0].error_type, "invalid_config");
5805 }
5806
5807 #[test]
5808 fn test_code_change_published_fully_wrapped() {
5809 use super::{KnownSystemEvent, SystemSubtype};
5810 use serde_json::Value;
5811
5812 let raw: Value = serde_json::from_str(
5813 r#"{
5814 "type":"system","subtype":"code_change_published",
5815 "provider":"github","url":"https://github.com/owner/repo/pull/42",
5816 "repo":"owner/repo","identifier":"42",
5817 "uuid":"u1","session_id":"s1"
5818 }"#,
5819 )
5820 .unwrap();
5821 crate::io::assert_fully_wrapped(&raw);
5822
5823 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5824 let ClaudeOutput::System(sys) = output else {
5825 panic!("expected System");
5826 };
5827 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
5828 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
5829 panic!("expected CodeChangePublished event");
5830 };
5831 assert_eq!(msg.provider, "github");
5832 assert_eq!(msg.repo, "owner/repo");
5833 assert_eq!(msg.identifier, "42");
5834
5835 assert!(sys.is_code_change_published());
5836 assert!(!sys.is_vcs_state_changed());
5837 let direct = sys.as_code_change_published().expect("direct accessor");
5838 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
5839 assert!(sys.as_vcs_state_changed().is_none());
5840 }
5841
5842 #[test]
5843 fn test_feedback_draft_queued_fully_wrapped() {
5844 use super::{KnownSystemEvent, SystemSubtype};
5845 use serde_json::Value;
5846
5847 let raw: Value = serde_json::from_str(
5848 r#"{
5849 "type":"system","subtype":"feedback_draft_queued",
5850 "draft_id":"draft-1","draft_type":"bug_report",
5851 "title":"Tool output was truncated",
5852 "details_preview":"The last command omitted its final lines",
5853 "uuid":"u1","session_id":"s1","future_field":"preserved"
5854 }"#,
5855 )
5856 .unwrap();
5857 crate::io::assert_fully_wrapped(&raw);
5858
5859 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5860 let ClaudeOutput::System(sys) = output else {
5861 panic!("expected System");
5862 };
5863 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
5864 assert!(sys.is_feedback_draft_queued());
5865 assert!(!sys.is_vcs_state_changed());
5866
5867 let direct = sys
5868 .as_feedback_draft_queued()
5869 .expect("direct typed accessor");
5870 assert_eq!(direct.draft_id, "draft-1");
5871 assert_eq!(direct.draft_type, "bug_report");
5872 assert_eq!(direct.extra["future_field"], "preserved");
5873
5874 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
5875 panic!("expected FeedbackDraftQueued event");
5876 };
5877 assert_eq!(known.title, "Tool output was truncated");
5878 assert_eq!(
5879 sys.typed_value().expect("typed value")["future_field"],
5880 "preserved"
5881 );
5882 }
5883
5884 #[test]
5885 fn test_cloud_session_delta_fully_wrapped() {
5886 use super::{KnownSystemEvent, SystemSubtype};
5887 use serde_json::Value;
5888
5889 let raw: Value = serde_json::from_str(
5890 r#"{
5891 "type":"system","subtype":"cloud_session_delta",
5892 "seq":3,"changed":["serving","connection"],
5893 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5894 "serving":{"state":"on"},"connection":{"state":"live"}},
5895 "uuid":"u1","session_id":"s1","future_field":"preserved"
5896 }"#,
5897 )
5898 .unwrap();
5899 crate::io::assert_fully_wrapped(&raw);
5900
5901 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5902 let ClaudeOutput::System(sys) = output else {
5903 panic!("expected System");
5904 };
5905 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5906 assert!(sys.is_cloud_session_delta());
5907 assert!(!sys.is_feedback_draft_queued());
5908
5909 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5910 assert_eq!(direct.seq, 3);
5911 assert_eq!(direct.changed, vec!["serving", "connection"]);
5912 assert_eq!(direct.cloud_session["id"], "session_abc");
5913 assert_eq!(direct.extra["future_field"], "preserved");
5914
5915 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5916 panic!("expected CloudSessionDelta event");
5917 };
5918 assert_eq!(known.session_id, "s1");
5919 assert_eq!(
5920 sys.typed_value().expect("typed value")["future_field"],
5921 "preserved"
5922 );
5923 }
5924
5925 #[test]
5926 fn test_vcs_state_changed_fully_wrapped() {
5927 use super::{KnownSystemEvent, VcsMutationKind};
5928 use serde_json::Value;
5929
5930 for kind in ["commit", "push", "merge", "rebase"] {
5931 let raw: Value = serde_json::from_str(&format!(
5932 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5933 kind
5934 ))
5935 .unwrap();
5936 crate::io::assert_fully_wrapped(&raw);
5937
5938 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5939 let ClaudeOutput::System(sys) = output else {
5940 panic!("expected System");
5941 };
5942 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5943 panic!("expected VcsStateChanged event");
5944 };
5945 assert_eq!(msg.kind.as_str(), kind);
5946 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
5947 }
5948
5949 let raw: Value = serde_json::from_str(
5951 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
5952 )
5953 .unwrap();
5954 crate::io::assert_fully_wrapped(&raw);
5955 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5956 let ClaudeOutput::System(sys) = output else {
5957 panic!("expected System");
5958 };
5959 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5960 panic!("expected VcsStateChanged event");
5961 };
5962 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
5963
5964 assert!(sys.is_vcs_state_changed());
5965 let direct = sys.as_vcs_state_changed().expect("direct accessor");
5966 assert_eq!(direct.cwd, "/repo");
5967 assert!(sys.as_code_change_published().is_none());
5968 }
5969
5970 #[test]
5971 fn test_assistant_aborted_and_resume_flags_roundtrip() {
5972 let json = r#"{
5973 "type":"assistant",
5974 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
5975 "session_id":"s1",
5976 "aborted":true,
5977 "resumed_from_incomplete_thinking":true
5978 }"#;
5979 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5980 let ClaudeOutput::Assistant(msg) = &output else {
5981 panic!("expected Assistant");
5982 };
5983 assert_eq!(msg.aborted, Some(true));
5984 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
5985 let reserialized = serde_json::to_string(&output).unwrap();
5986 assert!(reserialized.contains("\"aborted\":true"));
5987 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
5988
5989 let json = r#"{
5991 "type":"assistant",
5992 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
5993 "session_id":"s2"
5994 }"#;
5995 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5996 let reserialized = serde_json::to_string(&output).unwrap();
5997 assert!(!reserialized.contains("aborted"));
5998 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
5999 }
6000
6001 #[test]
6002 fn test_user_tool_result_meta_roundtrip() {
6003 let json = r#"{
6004 "type":"user",
6005 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6006 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6007 "tool_result_meta":[
6008 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6009 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6010 ]
6011 }"#;
6012 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6013 let ClaudeOutput::User(user) = &output else {
6014 panic!("expected User");
6015 };
6016 let meta = user.tool_result_meta.as_ref().unwrap();
6017 assert_eq!(meta.len(), 2);
6018 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6019 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6020 assert_eq!(meta[1].user_feedback, None);
6021
6022 let reserialized = serde_json::to_string(&output).unwrap();
6023 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6024 assert!(!reserialized.contains("\"user_feedback\":null"));
6025 }
6026
6027 #[test]
6031 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6032 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6033 let with_scope = serde_json::json!({
6034 "trigger": "refusal",
6035 "direction": "retry",
6036 "scope": "local",
6037 "original_model": "claude-fable-5",
6038 "fallback_model": "claude-opus-5",
6039 "request_id": null,
6040 "content": "Refused; retried on fallback model.",
6041 "uuid": "u1",
6042 "session_id": "s1"
6043 });
6044 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6045 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6046 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6047
6048 let mut without = with_scope.clone();
6051 without.as_object_mut().unwrap().remove("scope");
6052 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6053 assert_eq!(msg.scope, None);
6054 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6055
6056 assert_eq!(
6058 RefusalFallbackScope::from("workspace").as_str(),
6059 "workspace"
6060 );
6061 }
6062}