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 UiFocus,
55 UiInvalidate,
56 UiLog,
57 UiPanes,
58 UiScroll,
59 UiStatus,
60 UiToast,
61 Unknown(String),
63}
64
65impl SystemSubtype {
66 pub fn as_str(&self) -> &str {
67 match self {
68 Self::Init => "init",
69 Self::Status => "status",
70 Self::CompactBoundary => "compact_boundary",
71 Self::ThinkingTokens => "thinking_tokens",
72 Self::TaskStarted => "task_started",
73 Self::TaskProgress => "task_progress",
74 Self::TaskUpdated => "task_updated",
75 Self::TaskNotification => "task_notification",
76 Self::ApiRetry => "api_retry",
77 Self::ControlRequestProgress => "control_request_progress",
78 Self::ModelRefusalFallback => "model_refusal_fallback",
79 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
80 Self::LocalCommandOutput => "local_command_output",
81 Self::HookStarted => "hook_started",
82 Self::HookProgress => "hook_progress",
83 Self::HookResponse => "hook_response",
84 Self::PluginInstall => "plugin_install",
85 Self::BackgroundTasksChanged => "background_tasks_changed",
86 Self::SessionStateChanged => "session_state_changed",
87 Self::WorkerShuttingDown => "worker_shutting_down",
88 Self::CommandsChanged => "commands_changed",
89 Self::Notification => "notification",
90 Self::FilesPersisted => "files_persisted",
91 Self::MemoryRecall => "memory_recall",
92 Self::ElicitationComplete => "elicitation_complete",
93 Self::PermissionDenied => "permission_denied",
94 Self::MirrorError => "mirror_error",
95 Self::Informational => "informational",
96 Self::CodeChangePublished => "code_change_published",
97 Self::VcsStateChanged => "vcs_state_changed",
98 Self::FeedbackDraftQueued => "feedback_draft_queued",
99 Self::CloudSessionDelta => "cloud_session_delta",
100 Self::DevIntent => "dev_intent",
101 Self::TurnHandoffAvailable => "turn_handoff_available",
102 Self::TurnPreempted => "turn_preempted",
103 Self::PeerMessageHold => "peer_message_hold",
104 Self::PerTurnEffortChanged => "per_turn_effort_changed",
105 Self::SessionTitleChanged => "session_title_changed",
106 Self::UiFocus => "ui_focus",
107 Self::UiInvalidate => "ui_invalidate",
108 Self::UiLog => "ui_log",
109 Self::UiPanes => "ui_panes",
110 Self::UiScroll => "ui_scroll",
111 Self::UiStatus => "ui_status",
112 Self::UiToast => "ui_toast",
113 Self::Unknown(s) => s.as_str(),
114 }
115 }
116}
117
118impl fmt::Display for SystemSubtype {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 f.write_str(self.as_str())
121 }
122}
123
124impl From<&str> for SystemSubtype {
125 fn from(s: &str) -> Self {
126 match s {
127 "init" => Self::Init,
128 "status" => Self::Status,
129 "compact_boundary" => Self::CompactBoundary,
130 "thinking_tokens" => Self::ThinkingTokens,
131 "task_started" => Self::TaskStarted,
132 "task_progress" => Self::TaskProgress,
133 "task_updated" => Self::TaskUpdated,
134 "task_notification" => Self::TaskNotification,
135 "api_retry" => Self::ApiRetry,
136 "control_request_progress" => Self::ControlRequestProgress,
137 "model_refusal_fallback" => Self::ModelRefusalFallback,
138 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
139 "local_command_output" => Self::LocalCommandOutput,
140 "hook_started" => Self::HookStarted,
141 "hook_progress" => Self::HookProgress,
142 "hook_response" => Self::HookResponse,
143 "plugin_install" => Self::PluginInstall,
144 "background_tasks_changed" => Self::BackgroundTasksChanged,
145 "session_state_changed" => Self::SessionStateChanged,
146 "worker_shutting_down" => Self::WorkerShuttingDown,
147 "commands_changed" => Self::CommandsChanged,
148 "notification" => Self::Notification,
149 "files_persisted" => Self::FilesPersisted,
150 "memory_recall" => Self::MemoryRecall,
151 "elicitation_complete" => Self::ElicitationComplete,
152 "permission_denied" => Self::PermissionDenied,
153 "mirror_error" => Self::MirrorError,
154 "informational" => Self::Informational,
155 "code_change_published" => Self::CodeChangePublished,
156 "vcs_state_changed" => Self::VcsStateChanged,
157 "feedback_draft_queued" => Self::FeedbackDraftQueued,
158 "cloud_session_delta" => Self::CloudSessionDelta,
159 "dev_intent" => Self::DevIntent,
160 "turn_handoff_available" => Self::TurnHandoffAvailable,
161 "turn_preempted" => Self::TurnPreempted,
162 "peer_message_hold" => Self::PeerMessageHold,
163 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
164 "session_title_changed" => Self::SessionTitleChanged,
165 "ui_focus" => Self::UiFocus,
166 "ui_invalidate" => Self::UiInvalidate,
167 "ui_log" => Self::UiLog,
168 "ui_panes" => Self::UiPanes,
169 "ui_scroll" => Self::UiScroll,
170 "ui_status" => Self::UiStatus,
171 "ui_toast" => Self::UiToast,
172 other => Self::Unknown(other.to_string()),
173 }
174 }
175}
176
177impl Serialize for SystemSubtype {
178 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
179 serializer.serialize_str(self.as_str())
180 }
181}
182
183impl<'de> Deserialize<'de> for SystemSubtype {
184 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
185 let s = String::deserialize(deserializer)?;
186 Ok(Self::from(s.as_str()))
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Hash)]
195pub enum MessageRole {
196 User,
197 Assistant,
198 Unknown(String),
200}
201
202impl MessageRole {
203 pub fn as_str(&self) -> &str {
204 match self {
205 Self::User => "user",
206 Self::Assistant => "assistant",
207 Self::Unknown(s) => s.as_str(),
208 }
209 }
210}
211
212impl fmt::Display for MessageRole {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 f.write_str(self.as_str())
215 }
216}
217
218impl From<&str> for MessageRole {
219 fn from(s: &str) -> Self {
220 match s {
221 "user" => Self::User,
222 "assistant" => Self::Assistant,
223 other => Self::Unknown(other.to_string()),
224 }
225 }
226}
227
228impl Serialize for MessageRole {
229 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
230 serializer.serialize_str(self.as_str())
231 }
232}
233
234impl<'de> Deserialize<'de> for MessageRole {
235 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
236 let s = String::deserialize(deserializer)?;
237 Ok(Self::from(s.as_str()))
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Hash)]
243pub enum CompactionTrigger {
244 Auto,
246 Manual,
248 Unknown(String),
250}
251
252impl CompactionTrigger {
253 pub fn as_str(&self) -> &str {
254 match self {
255 Self::Auto => "auto",
256 Self::Manual => "manual",
257 Self::Unknown(s) => s.as_str(),
258 }
259 }
260}
261
262impl fmt::Display for CompactionTrigger {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 f.write_str(self.as_str())
265 }
266}
267
268impl From<&str> for CompactionTrigger {
269 fn from(s: &str) -> Self {
270 match s {
271 "auto" => Self::Auto,
272 "manual" => Self::Manual,
273 other => Self::Unknown(other.to_string()),
274 }
275 }
276}
277
278impl Serialize for CompactionTrigger {
279 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
280 serializer.serialize_str(self.as_str())
281 }
282}
283
284impl<'de> Deserialize<'de> for CompactionTrigger {
285 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
286 let s = String::deserialize(deserializer)?;
287 Ok(Self::from(s.as_str()))
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Hash)]
293pub enum StopReason {
294 EndTurn,
296 MaxTokens,
298 ToolUse,
300 Unknown(String),
302}
303
304impl StopReason {
305 pub fn as_str(&self) -> &str {
306 match self {
307 Self::EndTurn => "end_turn",
308 Self::MaxTokens => "max_tokens",
309 Self::ToolUse => "tool_use",
310 Self::Unknown(s) => s.as_str(),
311 }
312 }
313}
314
315impl fmt::Display for StopReason {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 f.write_str(self.as_str())
318 }
319}
320
321impl From<&str> for StopReason {
322 fn from(s: &str) -> Self {
323 match s {
324 "end_turn" => Self::EndTurn,
325 "max_tokens" => Self::MaxTokens,
326 "tool_use" => Self::ToolUse,
327 other => Self::Unknown(other.to_string()),
328 }
329 }
330}
331
332impl Serialize for StopReason {
333 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
334 serializer.serialize_str(self.as_str())
335 }
336}
337
338impl<'de> Deserialize<'de> for StopReason {
339 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
340 let s = String::deserialize(deserializer)?;
341 Ok(Self::from(s.as_str()))
342 }
343}
344
345#[derive(Debug, Clone, PartialEq, Eq, Hash)]
347pub enum ApiKeySource {
348 None,
350 User,
351 Project,
352 Org,
353 Temporary,
354 Oauth,
355 Unknown(String),
357}
358
359impl ApiKeySource {
360 pub fn as_str(&self) -> &str {
361 match self {
362 Self::None => "none",
363 Self::User => "user",
364 Self::Project => "project",
365 Self::Org => "org",
366 Self::Temporary => "temporary",
367 Self::Oauth => "oauth",
368 Self::Unknown(s) => s.as_str(),
369 }
370 }
371}
372
373impl fmt::Display for ApiKeySource {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 f.write_str(self.as_str())
376 }
377}
378
379impl From<&str> for ApiKeySource {
380 fn from(s: &str) -> Self {
381 match s {
382 "none" => Self::None,
383 "user" => Self::User,
384 "project" => Self::Project,
385 "org" => Self::Org,
386 "temporary" => Self::Temporary,
387 "oauth" => Self::Oauth,
388 other => Self::Unknown(other.to_string()),
389 }
390 }
391}
392
393impl Serialize for ApiKeySource {
394 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
395 serializer.serialize_str(self.as_str())
396 }
397}
398
399impl<'de> Deserialize<'de> for ApiKeySource {
400 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
401 let s = String::deserialize(deserializer)?;
402 Ok(Self::from(s.as_str()))
403 }
404}
405
406#[derive(Debug, Clone, PartialEq, Eq, Hash)]
408pub enum OutputStyle {
409 Default,
411 Unknown(String),
413}
414
415impl OutputStyle {
416 pub fn as_str(&self) -> &str {
417 match self {
418 Self::Default => "default",
419 Self::Unknown(s) => s.as_str(),
420 }
421 }
422}
423
424impl fmt::Display for OutputStyle {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 f.write_str(self.as_str())
427 }
428}
429
430impl From<&str> for OutputStyle {
431 fn from(s: &str) -> Self {
432 match s {
433 "default" => Self::Default,
434 other => Self::Unknown(other.to_string()),
435 }
436 }
437}
438
439impl Serialize for OutputStyle {
440 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
441 serializer.serialize_str(self.as_str())
442 }
443}
444
445impl<'de> Deserialize<'de> for OutputStyle {
446 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
447 let s = String::deserialize(deserializer)?;
448 Ok(Self::from(s.as_str()))
449 }
450}
451
452#[derive(Debug, Clone, PartialEq, Eq, Hash)]
454pub enum InitPermissionMode {
455 Default,
457 AcceptEdits,
458 BypassPermissions,
459 Plan,
460 DontAsk,
461 Auto,
462 Unknown(String),
464}
465
466impl InitPermissionMode {
467 pub fn as_str(&self) -> &str {
468 match self {
469 Self::Default => "default",
470 Self::AcceptEdits => "acceptEdits",
471 Self::BypassPermissions => "bypassPermissions",
472 Self::Plan => "plan",
473 Self::DontAsk => "dontAsk",
474 Self::Auto => "auto",
475 Self::Unknown(s) => s.as_str(),
476 }
477 }
478}
479
480impl fmt::Display for InitPermissionMode {
481 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482 f.write_str(self.as_str())
483 }
484}
485
486impl From<&str> for InitPermissionMode {
487 fn from(s: &str) -> Self {
488 match s {
489 "default" => Self::Default,
490 "acceptEdits" => Self::AcceptEdits,
491 "bypassPermissions" => Self::BypassPermissions,
492 "plan" => Self::Plan,
493 "dontAsk" => Self::DontAsk,
494 "auto" => Self::Auto,
495 other => Self::Unknown(other.to_string()),
496 }
497 }
498}
499
500impl Serialize for InitPermissionMode {
501 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
502 serializer.serialize_str(self.as_str())
503 }
504}
505
506impl<'de> Deserialize<'de> for InitPermissionMode {
507 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
508 let s = String::deserialize(deserializer)?;
509 Ok(Self::from(s.as_str()))
510 }
511}
512
513#[derive(Debug, Clone, PartialEq, Eq, Hash)]
515pub enum StatusMessageStatus {
516 Compacting,
518 Requesting,
520 Unknown(String),
522}
523
524impl StatusMessageStatus {
525 pub fn as_str(&self) -> &str {
526 match self {
527 Self::Compacting => "compacting",
528 Self::Requesting => "requesting",
529 Self::Unknown(s) => s.as_str(),
530 }
531 }
532}
533
534impl fmt::Display for StatusMessageStatus {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 f.write_str(self.as_str())
537 }
538}
539
540impl From<&str> for StatusMessageStatus {
541 fn from(s: &str) -> Self {
542 match s {
543 "compacting" => Self::Compacting,
544 "requesting" => Self::Requesting,
545 other => Self::Unknown(other.to_string()),
546 }
547 }
548}
549
550impl Serialize for StatusMessageStatus {
551 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
552 serializer.serialize_str(self.as_str())
553 }
554}
555
556impl<'de> Deserialize<'de> for StatusMessageStatus {
557 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
558 let s = String::deserialize(deserializer)?;
559 Ok(Self::from(s.as_str()))
560 }
561}
562
563pub(crate) fn serialize_optional_uuid<S>(
565 uuid: &Option<Uuid>,
566 serializer: S,
567) -> Result<S::Ok, S::Error>
568where
569 S: Serializer,
570{
571 match uuid {
572 Some(id) => serializer.serialize_str(&id.to_string()),
573 None => serializer.serialize_none(),
574 }
575}
576
577pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
579where
580 D: Deserializer<'de>,
581{
582 let opt_str: Option<String> = Option::deserialize(deserializer)?;
583 match opt_str {
584 Some(s) => Uuid::parse_str(&s)
585 .map(Some)
586 .map_err(serde::de::Error::custom),
587 None => Ok(None),
588 }
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
594pub struct MessageOrigin {
595 pub kind: String,
596 #[serde(flatten)]
597 pub extra: serde_json::Map<String, Value>,
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
602pub struct SummarizeMetadata {
603 pub messages_summarized: u64,
604 #[serde(default, skip_serializing_if = "Option::is_none")]
605 pub user_context: Option<String>,
606 #[serde(default, skip_serializing_if = "Option::is_none")]
607 pub direction: Option<String>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
612pub struct McpMeta {
613 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
614 pub meta: Option<Value>,
615 #[serde(default, skip_serializing_if = "Option::is_none")]
616 pub structured_content: Option<Value>,
617 #[serde(default, skip_serializing_if = "Vec::is_empty")]
622 pub resource_links: Vec<ResourceLink>,
623}
624
625#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
629pub struct ResourceLink {
630 pub uri: String,
631 pub name: String,
632 #[serde(default, skip_serializing_if = "Option::is_none")]
633 pub title: Option<String>,
634 #[serde(default, skip_serializing_if = "Option::is_none")]
635 pub description: Option<String>,
636 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
637 pub mime_type: Option<String>,
638 #[serde(default, skip_serializing_if = "Option::is_none")]
639 pub size: Option<u64>,
640 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub annotations: Option<Value>,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
646pub struct ToolResultMeta {
647 pub id: String,
649 #[serde(default, skip_serializing_if = "Option::is_none")]
656 pub non_execution_kind: Option<String>,
657 #[serde(default, skip_serializing_if = "Option::is_none")]
659 pub user_feedback: Option<String>,
660 #[serde(default, skip_serializing_if = "Option::is_none")]
665 pub remedy: Option<ToolResultRemedy>,
666}
667
668#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
673pub struct ToolResultRemedy {
674 pub kind: ToolResultRemedyKind,
677 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub servers: Option<Vec<String>>,
682 #[serde(default, skip_serializing_if = "Option::is_none")]
685 pub provider: Option<RemedyLoginProvider>,
686 #[serde(default, skip_serializing_if = "Option::is_none")]
690 pub path: Option<String>,
691 #[serde(default, skip_serializing_if = "Option::is_none")]
696 pub managed: Option<bool>,
697 #[serde(default, skip_serializing_if = "Option::is_none")]
699 pub feature: Option<RemedyFeature>,
700 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub cause: Option<RemedyFeatureCause>,
703 #[serde(default, skip_serializing_if = "Option::is_none")]
705 pub policy_kind: Option<RemedyPolicyKind>,
706}
707
708#[derive(Debug, Clone, PartialEq, Eq, Hash)]
710pub enum ToolResultRemedyKind {
711 McpNeedsAuth,
713 McpDisabled,
715 McpRequiredMissing,
717 AuthExpired,
719 AuthOverridden,
721 AuthMissingScope,
723 DesignNeedsAuthorization,
725 SandboxViolation,
727 OutsideReadsBlocked,
729 MemoryPaused,
731 FeatureDisabled,
733 PolicyDenied,
736 SpawnArgLimit,
738 StagedForReview,
740 Unknown(String),
742}
743
744impl ToolResultRemedyKind {
745 pub fn as_str(&self) -> &str {
746 match self {
747 Self::McpNeedsAuth => "mcp_needs_auth",
748 Self::McpDisabled => "mcp_disabled",
749 Self::McpRequiredMissing => "mcp_required_missing",
750 Self::AuthExpired => "auth_expired",
751 Self::AuthOverridden => "auth_overridden",
752 Self::AuthMissingScope => "auth_missing_scope",
753 Self::DesignNeedsAuthorization => "design_needs_authorization",
754 Self::SandboxViolation => "sandbox_violation",
755 Self::OutsideReadsBlocked => "outside_reads_blocked",
756 Self::MemoryPaused => "memory_paused",
757 Self::FeatureDisabled => "feature_disabled",
758 Self::PolicyDenied => "policy_denied",
759 Self::SpawnArgLimit => "spawn_arg_limit",
760 Self::StagedForReview => "staged_for_review",
761 Self::Unknown(s) => s.as_str(),
762 }
763 }
764}
765
766impl fmt::Display for ToolResultRemedyKind {
767 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
768 f.write_str(self.as_str())
769 }
770}
771
772impl From<&str> for ToolResultRemedyKind {
773 fn from(s: &str) -> Self {
774 match s {
775 "mcp_needs_auth" => Self::McpNeedsAuth,
776 "mcp_disabled" => Self::McpDisabled,
777 "mcp_required_missing" => Self::McpRequiredMissing,
778 "auth_expired" => Self::AuthExpired,
779 "auth_overridden" => Self::AuthOverridden,
780 "auth_missing_scope" => Self::AuthMissingScope,
781 "design_needs_authorization" => Self::DesignNeedsAuthorization,
782 "sandbox_violation" => Self::SandboxViolation,
783 "outside_reads_blocked" => Self::OutsideReadsBlocked,
784 "memory_paused" => Self::MemoryPaused,
785 "feature_disabled" => Self::FeatureDisabled,
786 "policy_denied" => Self::PolicyDenied,
787 "spawn_arg_limit" => Self::SpawnArgLimit,
788 "staged_for_review" => Self::StagedForReview,
789 other => Self::Unknown(other.to_string()),
790 }
791 }
792}
793
794impl Serialize for ToolResultRemedyKind {
795 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
796 serializer.serialize_str(self.as_str())
797 }
798}
799
800impl<'de> Deserialize<'de> for ToolResultRemedyKind {
801 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
802 let s = String::deserialize(deserializer)?;
803 Ok(Self::from(s.as_str()))
804 }
805}
806
807#[derive(Debug, Clone, PartialEq, Eq, Hash)]
809pub enum RemedyLoginProvider {
810 ClaudeAi,
812 ClaudeDesign,
814 Unknown(String),
816}
817
818impl RemedyLoginProvider {
819 pub fn as_str(&self) -> &str {
820 match self {
821 Self::ClaudeAi => "claude_ai",
822 Self::ClaudeDesign => "claude_design",
823 Self::Unknown(s) => s.as_str(),
824 }
825 }
826}
827
828impl fmt::Display for RemedyLoginProvider {
829 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830 f.write_str(self.as_str())
831 }
832}
833
834impl From<&str> for RemedyLoginProvider {
835 fn from(s: &str) -> Self {
836 match s {
837 "claude_ai" => Self::ClaudeAi,
838 "claude_design" => Self::ClaudeDesign,
839 other => Self::Unknown(other.to_string()),
840 }
841 }
842}
843
844impl Serialize for RemedyLoginProvider {
845 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
846 serializer.serialize_str(self.as_str())
847 }
848}
849
850impl<'de> Deserialize<'de> for RemedyLoginProvider {
851 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
852 let s = String::deserialize(deserializer)?;
853 Ok(Self::from(s.as_str()))
854 }
855}
856
857#[derive(Debug, Clone, PartialEq, Eq, Hash)]
860pub enum RemedyFeature {
861 Workflows,
862 Artifacts,
863 Unknown(String),
865}
866
867impl RemedyFeature {
868 pub fn as_str(&self) -> &str {
869 match self {
870 Self::Workflows => "workflows",
871 Self::Artifacts => "artifacts",
872 Self::Unknown(s) => s.as_str(),
873 }
874 }
875}
876
877impl fmt::Display for RemedyFeature {
878 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
879 f.write_str(self.as_str())
880 }
881}
882
883impl From<&str> for RemedyFeature {
884 fn from(s: &str) -> Self {
885 match s {
886 "workflows" => Self::Workflows,
887 "artifacts" => Self::Artifacts,
888 other => Self::Unknown(other.to_string()),
889 }
890 }
891}
892
893impl Serialize for RemedyFeature {
894 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
895 serializer.serialize_str(self.as_str())
896 }
897}
898
899impl<'de> Deserialize<'de> for RemedyFeature {
900 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
901 let s = String::deserialize(deserializer)?;
902 Ok(Self::from(s.as_str()))
903 }
904}
905
906#[derive(Debug, Clone, PartialEq, Eq, Hash)]
909pub enum RemedyFeatureCause {
910 ManagedSettings,
913 OrgPolicy,
915 Unavailable,
917 UserSetting,
920 Unknown(String),
922}
923
924impl RemedyFeatureCause {
925 pub fn as_str(&self) -> &str {
926 match self {
927 Self::ManagedSettings => "managed_settings",
928 Self::OrgPolicy => "org_policy",
929 Self::Unavailable => "unavailable",
930 Self::UserSetting => "user_setting",
931 Self::Unknown(s) => s.as_str(),
932 }
933 }
934}
935
936impl fmt::Display for RemedyFeatureCause {
937 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
938 f.write_str(self.as_str())
939 }
940}
941
942impl From<&str> for RemedyFeatureCause {
943 fn from(s: &str) -> Self {
944 match s {
945 "managed_settings" => Self::ManagedSettings,
946 "org_policy" => Self::OrgPolicy,
947 "unavailable" => Self::Unavailable,
948 "user_setting" => Self::UserSetting,
949 other => Self::Unknown(other.to_string()),
950 }
951 }
952}
953
954impl Serialize for RemedyFeatureCause {
955 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
956 serializer.serialize_str(self.as_str())
957 }
958}
959
960impl<'de> Deserialize<'de> for RemedyFeatureCause {
961 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
962 let s = String::deserialize(deserializer)?;
963 Ok(Self::from(s.as_str()))
964 }
965}
966
967#[derive(Debug, Clone, PartialEq, Eq, Hash)]
970pub enum RemedyPolicyKind {
971 OrgDenied,
973 Latched,
976 Unknown(String),
978}
979
980impl RemedyPolicyKind {
981 pub fn as_str(&self) -> &str {
982 match self {
983 Self::OrgDenied => "org_denied",
984 Self::Latched => "latched",
985 Self::Unknown(s) => s.as_str(),
986 }
987 }
988}
989
990impl fmt::Display for RemedyPolicyKind {
991 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
992 f.write_str(self.as_str())
993 }
994}
995
996impl From<&str> for RemedyPolicyKind {
997 fn from(s: &str) -> Self {
998 match s {
999 "org_denied" => Self::OrgDenied,
1000 "latched" => Self::Latched,
1001 other => Self::Unknown(other.to_string()),
1002 }
1003 }
1004}
1005
1006impl Serialize for RemedyPolicyKind {
1007 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1008 serializer.serialize_str(self.as_str())
1009 }
1010}
1011
1012impl<'de> Deserialize<'de> for RemedyPolicyKind {
1013 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1014 let s = String::deserialize(deserializer)?;
1015 Ok(Self::from(s.as_str()))
1016 }
1017}
1018
1019#[derive(Debug, Clone, Serialize, Deserialize)]
1021pub struct UserMessage {
1022 pub message: MessageContent,
1023 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1024 #[serde(
1025 serialize_with = "serialize_optional_uuid",
1026 deserialize_with = "deserialize_optional_uuid"
1027 )]
1028 pub session_id: Option<Uuid>,
1029 #[serde(skip_serializing_if = "Option::is_none")]
1031 pub parent_tool_use_id: Option<String>,
1032 #[serde(skip_serializing_if = "Option::is_none")]
1034 pub uuid: Option<String>,
1035 #[serde(skip_serializing_if = "Option::is_none")]
1037 pub timestamp: Option<String>,
1038 #[serde(skip_serializing_if = "Option::is_none")]
1045 pub tool_use_result: Option<serde_json::Value>,
1046 #[serde(skip_serializing_if = "Option::is_none")]
1049 pub subagent_type: Option<String>,
1050 #[serde(skip_serializing_if = "Option::is_none")]
1052 pub task_description: Option<String>,
1053 #[serde(skip_serializing_if = "Option::is_none")]
1054 pub origin: Option<MessageOrigin>,
1055 #[serde(skip_serializing_if = "Option::is_none")]
1056 pub priority: Option<String>,
1057 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1058 pub is_synthetic: Option<bool>,
1059 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1060 pub should_query: Option<bool>,
1061 #[serde(default, skip_serializing_if = "Option::is_none")]
1062 pub is_meta: Option<bool>,
1063 #[serde(default, skip_serializing_if = "Option::is_none")]
1064 pub is_visible_in_transcript_only: Option<bool>,
1065 #[serde(default, skip_serializing_if = "Option::is_none")]
1066 pub is_virtual: Option<bool>,
1067 #[serde(default, skip_serializing_if = "Option::is_none")]
1068 pub is_compact_summary: Option<bool>,
1069 #[serde(skip_serializing_if = "Option::is_none")]
1070 pub summarize_metadata: Option<SummarizeMetadata>,
1071 #[serde(skip_serializing_if = "Option::is_none")]
1072 pub mcp_meta: Option<McpMeta>,
1073 #[serde(skip_serializing_if = "Option::is_none")]
1076 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1077 #[serde(skip_serializing_if = "Option::is_none")]
1078 pub source_tool_use_id: Option<String>,
1079 #[serde(skip_serializing_if = "Option::is_none")]
1080 pub source_tool_assistant_uuid: Option<String>,
1081 #[serde(skip_serializing_if = "Option::is_none")]
1082 pub image_paste_ids: Option<Vec<u64>>,
1083 #[serde(skip_serializing_if = "Option::is_none")]
1084 pub client_platform: Option<String>,
1085 #[serde(skip_serializing_if = "Option::is_none")]
1086 pub inbound_origin: Option<String>,
1087 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1088 pub is_replay: Option<bool>,
1089 #[serde(skip_serializing_if = "Option::is_none")]
1090 pub file_attachments: Option<Vec<Value>>,
1091 #[serde(default, skip_serializing_if = "Option::is_none")]
1093 pub seeded_summon: Option<bool>,
1094 #[serde(default, skip_serializing_if = "Option::is_none")]
1097 pub client_composed: Option<bool>,
1098 #[serde(default, skip_serializing_if = "Option::is_none")]
1103 pub initiator: Option<String>,
1104 #[serde(default, skip_serializing_if = "Option::is_none")]
1110 pub pasted_content: Option<Vec<Value>>,
1111 #[serde(default, skip_serializing_if = "Option::is_none")]
1115 pub historical: Option<bool>,
1116 #[serde(default, skip_serializing_if = "Option::is_none")]
1122 pub server_received_wall_ms: Option<f64>,
1123}
1124
1125impl UserMessage {
1126 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1134 &self,
1135 ) -> Option<Result<T, serde_json::Error>> {
1136 self.tool_use_result
1137 .as_ref()
1138 .map(|v| serde_json::from_value(v.clone()))
1139 }
1140
1141 pub fn subagent_result(&self) -> Option<SubagentResult> {
1152 self.tool_use_result
1153 .as_ref()
1154 .and_then(|v| serde_json::from_value(v.clone()).ok())
1155 }
1156}
1157
1158#[derive(Debug, Clone, Serialize, Deserialize)]
1169pub struct SubagentResult {
1170 #[serde(skip_serializing_if = "Option::is_none")]
1172 pub status: Option<String>,
1173 #[serde(skip_serializing_if = "Option::is_none")]
1175 pub prompt: Option<String>,
1176 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1178 pub agent_id: Option<String>,
1179 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1181 pub agent_type: Option<String>,
1182 #[serde(
1184 default,
1185 deserialize_with = "deserialize_content_blocks",
1186 skip_serializing_if = "Vec::is_empty"
1187 )]
1188 pub content: Vec<ContentBlock>,
1189 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1191 pub resolved_model: Option<String>,
1192 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1194 pub total_duration_ms: Option<u64>,
1195 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1197 pub total_tokens: Option<u64>,
1198 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1200 pub total_tool_use_count: Option<u64>,
1201 #[serde(skip_serializing_if = "Option::is_none")]
1203 pub usage: Option<super::result::UsageInfo>,
1204 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1206 pub tool_stats: Option<SubagentToolStats>,
1207}
1208
1209#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1214#[serde(rename_all = "camelCase")]
1215pub struct SubagentToolStats {
1216 #[serde(default)]
1217 pub read_count: u64,
1218 #[serde(default)]
1219 pub search_count: u64,
1220 #[serde(default)]
1221 pub bash_count: u64,
1222 #[serde(default)]
1223 pub edit_file_count: u64,
1224 #[serde(default)]
1225 pub lines_added: u64,
1226 #[serde(default)]
1227 pub lines_removed: u64,
1228 #[serde(default)]
1229 pub other_tool_count: u64,
1230 #[serde(flatten)]
1231 pub extra: serde_json::Map<String, Value>,
1232}
1233
1234#[derive(Debug, Clone, Default, PartialEq, Eq)]
1261pub struct SubagentUsageRollup {
1262 pub subagent_tokens: u64,
1265 pub agent_count: u32,
1267 pub tool_uses: u64,
1269 pub duration_ms: u64,
1271 seen_agent_ids: std::collections::BTreeSet<String>,
1272}
1273
1274impl SubagentUsageRollup {
1275 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1281 match output {
1282 ClaudeOutput::User(user) => self.observe_user(user),
1283 _ => false,
1284 }
1285 }
1286
1287 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1294 let Some(result) = user.subagent_result() else {
1295 return false;
1296 };
1297 if result.total_tokens.is_none() && result.agent_id.is_none() {
1298 return false;
1299 }
1300 if let Some(agent_id) = &result.agent_id {
1301 if !self.seen_agent_ids.insert(agent_id.clone()) {
1302 return false;
1303 }
1304 }
1305 self.agent_count += 1;
1306 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1307 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1308 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1309 true
1310 }
1311}
1312
1313#[derive(Debug, Clone, Serialize, Deserialize)]
1315pub struct MessageContent {
1316 pub role: MessageRole,
1317 #[serde(deserialize_with = "deserialize_content_blocks")]
1318 pub content: Vec<ContentBlock>,
1319}
1320
1321#[derive(Debug, Clone, Serialize, Deserialize)]
1323pub struct SystemMessage {
1324 pub subtype: SystemSubtype,
1325 #[serde(flatten)]
1326 pub data: Value, }
1328
1329impl SystemMessage {
1330 pub fn is_init(&self) -> bool {
1332 self.subtype == SystemSubtype::Init
1333 }
1334
1335 pub fn is_status(&self) -> bool {
1337 self.subtype == SystemSubtype::Status
1338 }
1339
1340 pub fn is_compact_boundary(&self) -> bool {
1342 self.subtype == SystemSubtype::CompactBoundary
1343 }
1344
1345 pub fn as_init(&self) -> Option<InitMessage> {
1347 if self.subtype != SystemSubtype::Init {
1348 return None;
1349 }
1350 serde_json::from_value(self.data.clone()).ok()
1351 }
1352
1353 pub fn as_status(&self) -> Option<StatusMessage> {
1355 if self.subtype != SystemSubtype::Status {
1356 return None;
1357 }
1358 serde_json::from_value(self.data.clone()).ok()
1359 }
1360
1361 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1363 if self.subtype != SystemSubtype::CompactBoundary {
1364 return None;
1365 }
1366 serde_json::from_value(self.data.clone()).ok()
1367 }
1368
1369 pub fn is_task_started(&self) -> bool {
1371 self.subtype == SystemSubtype::TaskStarted
1372 }
1373
1374 pub fn is_task_progress(&self) -> bool {
1376 self.subtype == SystemSubtype::TaskProgress
1377 }
1378
1379 pub fn is_task_notification(&self) -> bool {
1381 self.subtype == SystemSubtype::TaskNotification
1382 }
1383
1384 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1386 if self.subtype != SystemSubtype::TaskStarted {
1387 return None;
1388 }
1389 serde_json::from_value(self.data.clone()).ok()
1390 }
1391
1392 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1394 if self.subtype != SystemSubtype::TaskProgress {
1395 return None;
1396 }
1397 serde_json::from_value(self.data.clone()).ok()
1398 }
1399
1400 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1402 if self.subtype != SystemSubtype::TaskNotification {
1403 return None;
1404 }
1405 serde_json::from_value(self.data.clone()).ok()
1406 }
1407
1408 pub fn is_task_updated(&self) -> bool {
1410 self.subtype == SystemSubtype::TaskUpdated
1411 }
1412
1413 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1415 if self.subtype != SystemSubtype::TaskUpdated {
1416 return None;
1417 }
1418 serde_json::from_value(self.data.clone()).ok()
1419 }
1420
1421 pub fn is_thinking_tokens(&self) -> bool {
1423 self.subtype == SystemSubtype::ThinkingTokens
1424 }
1425
1426 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1428 if self.subtype != SystemSubtype::ThinkingTokens {
1429 return None;
1430 }
1431 serde_json::from_value(self.data.clone()).ok()
1432 }
1433
1434 pub fn is_code_change_published(&self) -> bool {
1436 self.subtype == SystemSubtype::CodeChangePublished
1437 }
1438
1439 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1441 if self.subtype != SystemSubtype::CodeChangePublished {
1442 return None;
1443 }
1444 serde_json::from_value(self.data.clone()).ok()
1445 }
1446
1447 pub fn is_vcs_state_changed(&self) -> bool {
1449 self.subtype == SystemSubtype::VcsStateChanged
1450 }
1451
1452 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1454 if self.subtype != SystemSubtype::VcsStateChanged {
1455 return None;
1456 }
1457 serde_json::from_value(self.data.clone()).ok()
1458 }
1459
1460 pub fn is_feedback_draft_queued(&self) -> bool {
1462 self.subtype == SystemSubtype::FeedbackDraftQueued
1463 }
1464
1465 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1467 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1468 return None;
1469 }
1470 serde_json::from_value(self.data.clone()).ok()
1471 }
1472
1473 pub fn is_cloud_session_delta(&self) -> bool {
1475 self.subtype == SystemSubtype::CloudSessionDelta
1476 }
1477
1478 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1480 if self.subtype != SystemSubtype::CloudSessionDelta {
1481 return None;
1482 }
1483 serde_json::from_value(self.data.clone()).ok()
1484 }
1485
1486 pub fn is_dev_intent(&self) -> bool {
1488 self.subtype == SystemSubtype::DevIntent
1489 }
1490
1491 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1493 if self.subtype != SystemSubtype::DevIntent {
1494 return None;
1495 }
1496 serde_json::from_value(self.data.clone()).ok()
1497 }
1498
1499 pub fn is_turn_handoff_available(&self) -> bool {
1501 self.subtype == SystemSubtype::TurnHandoffAvailable
1502 }
1503
1504 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1506 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1507 return None;
1508 }
1509 serde_json::from_value(self.data.clone()).ok()
1510 }
1511
1512 pub fn is_turn_preempted(&self) -> bool {
1514 self.subtype == SystemSubtype::TurnPreempted
1515 }
1516
1517 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1519 if self.subtype != SystemSubtype::TurnPreempted {
1520 return None;
1521 }
1522 serde_json::from_value(self.data.clone()).ok()
1523 }
1524
1525 pub fn is_peer_message_hold(&self) -> bool {
1527 self.subtype == SystemSubtype::PeerMessageHold
1528 }
1529
1530 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1532 if self.subtype != SystemSubtype::PeerMessageHold {
1533 return None;
1534 }
1535 serde_json::from_value(self.data.clone()).ok()
1536 }
1537
1538 pub fn is_per_turn_effort_changed(&self) -> bool {
1540 self.subtype == SystemSubtype::PerTurnEffortChanged
1541 }
1542
1543 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1545 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1546 return None;
1547 }
1548 serde_json::from_value(self.data.clone()).ok()
1549 }
1550
1551 pub fn is_session_title_changed(&self) -> bool {
1553 self.subtype == SystemSubtype::SessionTitleChanged
1554 }
1555
1556 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1558 if self.subtype != SystemSubtype::SessionTitleChanged {
1559 return None;
1560 }
1561 serde_json::from_value(self.data.clone()).ok()
1562 }
1563
1564 pub fn is_ui_focus(&self) -> bool {
1566 self.subtype == SystemSubtype::UiFocus
1567 }
1568
1569 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1571 if self.subtype != SystemSubtype::UiFocus {
1572 return None;
1573 }
1574 serde_json::from_value(self.data.clone()).ok()
1575 }
1576
1577 pub fn is_ui_invalidate(&self) -> bool {
1579 self.subtype == SystemSubtype::UiInvalidate
1580 }
1581
1582 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1584 if self.subtype != SystemSubtype::UiInvalidate {
1585 return None;
1586 }
1587 serde_json::from_value(self.data.clone()).ok()
1588 }
1589
1590 pub fn is_ui_log(&self) -> bool {
1592 self.subtype == SystemSubtype::UiLog
1593 }
1594
1595 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1597 if self.subtype != SystemSubtype::UiLog {
1598 return None;
1599 }
1600 serde_json::from_value(self.data.clone()).ok()
1601 }
1602
1603 pub fn is_ui_panes(&self) -> bool {
1605 self.subtype == SystemSubtype::UiPanes
1606 }
1607
1608 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1610 if self.subtype != SystemSubtype::UiPanes {
1611 return None;
1612 }
1613 serde_json::from_value(self.data.clone()).ok()
1614 }
1615
1616 pub fn is_ui_scroll(&self) -> bool {
1618 self.subtype == SystemSubtype::UiScroll
1619 }
1620
1621 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1623 if self.subtype != SystemSubtype::UiScroll {
1624 return None;
1625 }
1626 serde_json::from_value(self.data.clone()).ok()
1627 }
1628
1629 pub fn is_ui_status(&self) -> bool {
1631 self.subtype == SystemSubtype::UiStatus
1632 }
1633
1634 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1636 if self.subtype != SystemSubtype::UiStatus {
1637 return None;
1638 }
1639 serde_json::from_value(self.data.clone()).ok()
1640 }
1641
1642 pub fn is_ui_toast(&self) -> bool {
1644 self.subtype == SystemSubtype::UiToast
1645 }
1646
1647 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
1649 if self.subtype != SystemSubtype::UiToast {
1650 return None;
1651 }
1652 serde_json::from_value(self.data.clone()).ok()
1653 }
1654
1655 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1657 macro_rules! parse {
1658 ($variant:ident, $ty:ty) => {
1659 serde_json::from_value::<$ty>(self.data.clone())
1660 .ok()
1661 .map(KnownSystemEvent::$variant)
1662 };
1663 }
1664
1665 match self.subtype {
1666 SystemSubtype::Init => parse!(Init, InitMessage),
1667 SystemSubtype::Status => parse!(Status, StatusMessage),
1668 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1669 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1670 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1671 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1672 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1673 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1674 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1675 SystemSubtype::ControlRequestProgress => {
1676 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1677 }
1678 SystemSubtype::ModelRefusalFallback => {
1679 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1680 }
1681 SystemSubtype::ModelRefusalNoFallback => {
1682 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1683 }
1684 SystemSubtype::LocalCommandOutput => {
1685 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1686 }
1687 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1688 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1689 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1690 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1691 SystemSubtype::BackgroundTasksChanged => {
1692 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1693 }
1694 SystemSubtype::SessionStateChanged => {
1695 parse!(SessionStateChanged, SessionStateChangedMessage)
1696 }
1697 SystemSubtype::WorkerShuttingDown => {
1698 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1699 }
1700 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1701 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1702 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1703 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1704 SystemSubtype::ElicitationComplete => {
1705 parse!(ElicitationComplete, ElicitationCompleteMessage)
1706 }
1707 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1708 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1709 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1710 SystemSubtype::CodeChangePublished => {
1711 parse!(CodeChangePublished, CodeChangePublishedMessage)
1712 }
1713 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1714 SystemSubtype::FeedbackDraftQueued => {
1715 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1716 }
1717 SystemSubtype::CloudSessionDelta => {
1718 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1719 }
1720 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1721 SystemSubtype::TurnHandoffAvailable => {
1722 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1723 }
1724 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1725 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1726 SystemSubtype::PerTurnEffortChanged => {
1727 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1728 }
1729 SystemSubtype::SessionTitleChanged => {
1730 parse!(SessionTitleChanged, SessionTitleChangedMessage)
1731 }
1732 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
1733 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
1734 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
1735 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
1736 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
1737 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
1738 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
1739 SystemSubtype::Unknown(_) => None,
1740 }
1741 }
1742
1743 pub fn typed_value(&self) -> Option<Value> {
1752 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1753 parsed.and_then(|v| serde_json::to_value(v).ok())
1754 }
1755 match self.subtype {
1756 SystemSubtype::Init => reserialize(self.as_init()),
1757 SystemSubtype::Status => reserialize(self.as_status()),
1758 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1759 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1760 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1761 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1762 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1763 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1764 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1765 SystemSubtype::ControlRequestProgress => {
1766 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1767 }
1768 SystemSubtype::ModelRefusalFallback => {
1769 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1770 }
1771 SystemSubtype::ModelRefusalNoFallback => {
1772 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1773 }
1774 SystemSubtype::LocalCommandOutput => {
1775 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1776 }
1777 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1778 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1779 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1780 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1781 SystemSubtype::BackgroundTasksChanged => {
1782 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1783 }
1784 SystemSubtype::SessionStateChanged => {
1785 reserialize(parse_system::<SessionStateChangedMessage>(self))
1786 }
1787 SystemSubtype::WorkerShuttingDown => {
1788 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1789 }
1790 SystemSubtype::CommandsChanged => {
1791 reserialize(parse_system::<CommandsChangedMessage>(self))
1792 }
1793 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1794 SystemSubtype::FilesPersisted => {
1795 reserialize(parse_system::<FilesPersistedMessage>(self))
1796 }
1797 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1798 SystemSubtype::ElicitationComplete => {
1799 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1800 }
1801 SystemSubtype::PermissionDenied => {
1802 reserialize(parse_system::<PermissionDeniedMessage>(self))
1803 }
1804 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1805 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1806 SystemSubtype::CodeChangePublished => {
1807 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1808 }
1809 SystemSubtype::VcsStateChanged => {
1810 reserialize(parse_system::<VcsStateChangedMessage>(self))
1811 }
1812 SystemSubtype::FeedbackDraftQueued => {
1813 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1814 }
1815 SystemSubtype::CloudSessionDelta => {
1816 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1817 }
1818 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1819 SystemSubtype::TurnHandoffAvailable => {
1820 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1821 }
1822 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1823 SystemSubtype::PeerMessageHold => {
1824 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1825 }
1826 SystemSubtype::PerTurnEffortChanged => {
1827 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
1828 }
1829 SystemSubtype::SessionTitleChanged => {
1830 reserialize(parse_system::<SessionTitleChangedMessage>(self))
1831 }
1832 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
1833 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
1834 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
1835 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
1836 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
1837 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
1838 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
1839 SystemSubtype::Unknown(_) => None,
1840 }
1841 }
1842}
1843
1844fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1845 serde_json::from_value(message.data.clone()).ok()
1846}
1847
1848#[allow(clippy::large_enum_variant)]
1854#[derive(Debug, Clone, Serialize, Deserialize)]
1855pub enum KnownSystemEvent {
1856 Init(InitMessage),
1857 Status(StatusMessage),
1858 CompactBoundary(CompactBoundaryMessage),
1859 ThinkingTokens(ThinkingTokensMessage),
1860 TaskStarted(TaskStartedMessage),
1861 TaskProgress(TaskProgressMessage),
1862 TaskUpdated(TaskUpdatedMessage),
1863 TaskNotification(TaskNotificationMessage),
1864 ApiRetry(ApiRetryMessage),
1865 ControlRequestProgress(ControlRequestProgressMessage),
1866 ModelRefusalFallback(ModelRefusalFallbackMessage),
1867 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1868 LocalCommandOutput(LocalCommandOutputMessage),
1869 HookStarted(HookStartedMessage),
1870 HookProgress(HookProgressMessage),
1871 HookResponse(HookResponseMessage),
1872 PluginInstall(PluginInstallMessage),
1873 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1874 SessionStateChanged(SessionStateChangedMessage),
1875 WorkerShuttingDown(WorkerShuttingDownMessage),
1876 CommandsChanged(CommandsChangedMessage),
1877 Notification(NotificationMessage),
1878 FilesPersisted(FilesPersistedMessage),
1879 MemoryRecall(MemoryRecallMessage),
1880 ElicitationComplete(ElicitationCompleteMessage),
1881 PermissionDenied(PermissionDeniedMessage),
1882 MirrorError(MirrorErrorMessage),
1883 Informational(InformationalMessage),
1884 CodeChangePublished(CodeChangePublishedMessage),
1885 VcsStateChanged(VcsStateChangedMessage),
1886 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1887 CloudSessionDelta(CloudSessionDeltaMessage),
1888 DevIntent(DevIntentMessage),
1889 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1890 TurnPreempted(TurnPreemptedMessage),
1891 PeerMessageHold(PeerMessageHoldMessage),
1892 PerTurnEffortChanged(PerTurnEffortChangedMessage),
1893 SessionTitleChanged(SessionTitleChangedMessage),
1894 UiFocus(UiFocusMessage),
1895 UiInvalidate(UiInvalidateMessage),
1896 UiLog(UiLogMessage),
1897 UiPanes(UiPanesMessage),
1898 UiScroll(UiScrollMessage),
1899 UiStatus(UiStatusMessage),
1900 UiToast(UiToastMessage),
1901}
1902
1903#[derive(Debug, Clone, Serialize, Deserialize)]
1904pub struct ApiRetryMessage {
1905 pub attempt: u64,
1906 pub max_retries: u64,
1907 pub retry_delay_ms: u64,
1908 pub error_status: Option<u16>,
1909 pub error: String,
1910 #[serde(default, skip_serializing_if = "Option::is_none")]
1915 pub no_response: Option<ApiRetryNoResponse>,
1916 #[serde(default, skip_serializing_if = "Option::is_none")]
1917 pub uuid: Option<String>,
1918 #[serde(default, skip_serializing_if = "Option::is_none")]
1919 pub session_id: Option<String>,
1920}
1921
1922#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1925pub struct ApiRetryNoResponse {
1926 pub waited_ms: u64,
1928 pub retry_wait_ms: u64,
1930}
1931
1932#[derive(Debug, Clone, Serialize, Deserialize)]
1933pub struct ControlRequestProgressMessage {
1934 pub request_id: String,
1935 pub status: String,
1936 #[serde(default, skip_serializing_if = "Option::is_none")]
1937 pub attempt: Option<u64>,
1938 #[serde(default, skip_serializing_if = "Option::is_none")]
1939 pub max_retries: Option<u64>,
1940 #[serde(default, skip_serializing_if = "Option::is_none")]
1941 pub retry_delay_ms: Option<u64>,
1942 #[serde(default, skip_serializing_if = "Option::is_none")]
1943 pub error_status: Option<u16>,
1944 #[serde(default, skip_serializing_if = "Option::is_none")]
1945 pub error: Option<String>,
1946 #[serde(default, skip_serializing_if = "Option::is_none")]
1947 pub uuid: Option<String>,
1948 #[serde(default, skip_serializing_if = "Option::is_none")]
1949 pub session_id: Option<String>,
1950}
1951
1952#[derive(Debug, Clone, Serialize, Deserialize)]
1953pub struct ModelRefusalFallbackMessage {
1954 pub trigger: String,
1955 pub direction: String,
1956 #[serde(default, skip_serializing_if = "Option::is_none")]
1962 pub scope: Option<RefusalFallbackScope>,
1963 pub original_model: String,
1964 pub fallback_model: String,
1965 pub request_id: Option<String>,
1966 #[serde(default, skip_serializing_if = "Option::is_none")]
1967 pub api_refusal_category: Option<String>,
1968 #[serde(default, skip_serializing_if = "Option::is_none")]
1973 pub saw_cyber_refusal: Option<bool>,
1974 #[serde(default, skip_serializing_if = "Option::is_none")]
1975 pub api_refusal_explanation: Option<String>,
1976 #[serde(default, skip_serializing_if = "Option::is_none")]
1977 pub retracted_message_uuids: Option<Vec<String>>,
1978 #[serde(default, skip_serializing_if = "Option::is_none")]
1979 pub refused_user_message_uuid: Option<String>,
1980 pub content: Value,
1981 #[serde(default, skip_serializing_if = "Option::is_none")]
1982 pub uuid: Option<String>,
1983 #[serde(default, skip_serializing_if = "Option::is_none")]
1984 pub session_id: Option<String>,
1985}
1986
1987#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1991pub enum RefusalFallbackScope {
1992 Session,
1994 Local,
1997 Unknown(String),
1999}
2000
2001impl RefusalFallbackScope {
2002 pub fn as_str(&self) -> &str {
2003 match self {
2004 Self::Session => "session",
2005 Self::Local => "local",
2006 Self::Unknown(s) => s.as_str(),
2007 }
2008 }
2009}
2010
2011impl fmt::Display for RefusalFallbackScope {
2012 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2013 f.write_str(self.as_str())
2014 }
2015}
2016
2017impl From<&str> for RefusalFallbackScope {
2018 fn from(s: &str) -> Self {
2019 match s {
2020 "session" => Self::Session,
2021 "local" => Self::Local,
2022 other => Self::Unknown(other.to_string()),
2023 }
2024 }
2025}
2026
2027impl Serialize for RefusalFallbackScope {
2028 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2029 serializer.serialize_str(self.as_str())
2030 }
2031}
2032
2033impl<'de> Deserialize<'de> for RefusalFallbackScope {
2034 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2035 let s = String::deserialize(deserializer)?;
2036 Ok(Self::from(s.as_str()))
2037 }
2038}
2039
2040#[derive(Debug, Clone, Serialize, Deserialize)]
2041pub struct ModelRefusalNoFallbackMessage {
2042 pub original_model: String,
2043 pub request_id: Option<String>,
2044 #[serde(default, skip_serializing_if = "Option::is_none")]
2045 pub api_refusal_category: Option<String>,
2046 #[serde(default, skip_serializing_if = "Option::is_none")]
2047 pub api_refusal_explanation: Option<String>,
2048 pub content: Value,
2049 #[serde(default, skip_serializing_if = "Option::is_none")]
2050 pub uuid: Option<String>,
2051 #[serde(default, skip_serializing_if = "Option::is_none")]
2052 pub session_id: Option<String>,
2053}
2054
2055#[derive(Debug, Clone, Serialize, Deserialize)]
2056pub struct LocalCommandOutputMessage {
2057 pub content: String,
2058 #[serde(default, skip_serializing_if = "Option::is_none")]
2059 pub uuid: Option<String>,
2060 #[serde(default, skip_serializing_if = "Option::is_none")]
2061 pub session_id: Option<String>,
2062}
2063
2064#[derive(Debug, Clone, Serialize, Deserialize)]
2065pub struct HookStartedMessage {
2066 pub hook_id: String,
2067 pub hook_name: String,
2068 pub hook_event: String,
2069 #[serde(default, skip_serializing_if = "Option::is_none")]
2070 pub uuid: Option<String>,
2071 #[serde(default, skip_serializing_if = "Option::is_none")]
2072 pub session_id: Option<String>,
2073}
2074
2075#[derive(Debug, Clone, Serialize, Deserialize)]
2076pub struct HookProgressMessage {
2077 pub hook_id: String,
2078 pub hook_name: String,
2079 pub hook_event: String,
2080 #[serde(default, skip_serializing_if = "Option::is_none")]
2081 pub stdout: Option<String>,
2082 #[serde(default, skip_serializing_if = "Option::is_none")]
2083 pub stderr: Option<String>,
2084 #[serde(default, skip_serializing_if = "Option::is_none")]
2085 pub output: Option<String>,
2086 #[serde(default, skip_serializing_if = "Option::is_none")]
2087 pub uuid: Option<String>,
2088 #[serde(default, skip_serializing_if = "Option::is_none")]
2089 pub session_id: Option<String>,
2090}
2091
2092#[derive(Debug, Clone, Serialize, Deserialize)]
2093pub struct HookResponseMessage {
2094 pub hook_id: String,
2095 pub hook_name: String,
2096 pub hook_event: String,
2097 #[serde(default, skip_serializing_if = "Option::is_none")]
2098 pub stdout: Option<String>,
2099 #[serde(default, skip_serializing_if = "Option::is_none")]
2100 pub stderr: Option<String>,
2101 #[serde(default, skip_serializing_if = "Option::is_none")]
2102 pub output: Option<String>,
2103 #[serde(default, skip_serializing_if = "Option::is_none")]
2104 pub exit_code: Option<i32>,
2105 pub outcome: String,
2106 #[serde(default, skip_serializing_if = "Option::is_none")]
2107 pub uuid: Option<String>,
2108 #[serde(default, skip_serializing_if = "Option::is_none")]
2109 pub session_id: Option<String>,
2110}
2111
2112#[derive(Debug, Clone, Serialize, Deserialize)]
2113pub struct PluginInstallMessage {
2114 pub status: String,
2115 #[serde(default, skip_serializing_if = "Option::is_none")]
2116 pub name: Option<String>,
2117 #[serde(default, skip_serializing_if = "Option::is_none")]
2118 pub error: Option<String>,
2119 #[serde(default, skip_serializing_if = "Option::is_none")]
2120 pub uuid: Option<String>,
2121 #[serde(default, skip_serializing_if = "Option::is_none")]
2122 pub session_id: Option<String>,
2123}
2124
2125#[derive(Debug, Clone, Serialize, Deserialize)]
2126pub struct BackgroundTasksChangedMessage {
2127 pub tasks: Vec<BackgroundTaskInfo>,
2128 #[serde(default, skip_serializing_if = "Option::is_none")]
2129 pub uuid: Option<String>,
2130 #[serde(default, skip_serializing_if = "Option::is_none")]
2131 pub session_id: Option<String>,
2132}
2133
2134#[derive(Debug, Clone, Serialize, Deserialize)]
2135pub struct BackgroundTaskInfo {
2136 pub task_id: String,
2137 pub task_type: String,
2138 pub description: String,
2139 #[serde(default, skip_serializing_if = "Option::is_none")]
2142 pub ambient: Option<bool>,
2143}
2144
2145#[derive(Debug, Clone, Serialize, Deserialize)]
2146pub struct SessionStateChangedMessage {
2147 pub state: String,
2148 #[serde(default, skip_serializing_if = "Option::is_none")]
2149 pub uuid: Option<String>,
2150 #[serde(default, skip_serializing_if = "Option::is_none")]
2151 pub session_id: Option<String>,
2152}
2153
2154#[derive(Debug, Clone, Serialize, Deserialize)]
2155pub struct WorkerShuttingDownMessage {
2156 pub reason: String,
2157 #[serde(default, skip_serializing_if = "Option::is_none")]
2158 pub uuid: Option<String>,
2159 #[serde(default, skip_serializing_if = "Option::is_none")]
2160 pub session_id: Option<String>,
2161}
2162
2163#[derive(Debug, Clone, Serialize, Deserialize)]
2164pub struct CommandsChangedMessage {
2165 pub commands: Vec<CommandInfo>,
2166 #[serde(default, skip_serializing_if = "Option::is_none")]
2167 pub uuid: Option<String>,
2168 #[serde(default, skip_serializing_if = "Option::is_none")]
2169 pub session_id: Option<String>,
2170}
2171
2172#[derive(Debug, Clone, Serialize, Deserialize)]
2173pub struct CommandInfo {
2174 pub name: String,
2175 pub description: String,
2176 #[serde(rename = "argumentHint")]
2177 pub argument_hint: String,
2178 #[serde(default, skip_serializing_if = "Option::is_none")]
2179 pub aliases: Option<Vec<String>>,
2180 #[serde(default, skip_serializing_if = "Option::is_none")]
2186 pub builtin: Option<bool>,
2187}
2188
2189#[derive(Debug, Clone, Serialize, Deserialize)]
2190pub struct NotificationMessage {
2191 pub key: String,
2192 pub text: String,
2193 pub priority: String,
2194 #[serde(default, skip_serializing_if = "Option::is_none")]
2195 pub color: Option<String>,
2196 #[serde(default, skip_serializing_if = "Option::is_none")]
2197 pub timeout_ms: Option<u64>,
2198 #[serde(default, skip_serializing_if = "Option::is_none")]
2199 pub uuid: Option<String>,
2200 #[serde(default, skip_serializing_if = "Option::is_none")]
2201 pub session_id: Option<String>,
2202}
2203
2204#[derive(Debug, Clone, Serialize, Deserialize)]
2205pub struct FilesPersistedMessage {
2206 pub files: Vec<PersistedFile>,
2207 pub failed: Vec<FailedPersistedFile>,
2208 pub processed_at: String,
2209 #[serde(default, skip_serializing_if = "Option::is_none")]
2210 pub uuid: Option<String>,
2211 #[serde(default, skip_serializing_if = "Option::is_none")]
2212 pub session_id: Option<String>,
2213}
2214
2215#[derive(Debug, Clone, Serialize, Deserialize)]
2216pub struct PersistedFile {
2217 pub filename: String,
2218 pub file_id: String,
2219}
2220
2221#[derive(Debug, Clone, Serialize, Deserialize)]
2222pub struct FailedPersistedFile {
2223 pub filename: String,
2224 pub error: String,
2225}
2226
2227#[derive(Debug, Clone, Serialize, Deserialize)]
2228pub struct MemoryRecallMessage {
2229 pub mode: String,
2230 pub memories: Vec<MemoryRecallItem>,
2231 #[serde(default, skip_serializing_if = "Option::is_none")]
2232 pub uuid: Option<String>,
2233 #[serde(default, skip_serializing_if = "Option::is_none")]
2234 pub session_id: Option<String>,
2235}
2236
2237#[derive(Debug, Clone, Serialize, Deserialize)]
2238pub struct MemoryRecallItem {
2239 pub path: String,
2240 pub scope: String,
2241 #[serde(default, skip_serializing_if = "Option::is_none")]
2242 pub content: Option<String>,
2243}
2244
2245#[derive(Debug, Clone, Serialize, Deserialize)]
2246pub struct ElicitationCompleteMessage {
2247 pub mcp_server_name: String,
2248 pub elicitation_id: String,
2249 #[serde(default, skip_serializing_if = "Option::is_none")]
2250 pub uuid: Option<String>,
2251 #[serde(default, skip_serializing_if = "Option::is_none")]
2252 pub session_id: Option<String>,
2253}
2254
2255#[derive(Debug, Clone, Serialize, Deserialize)]
2256pub struct PermissionDeniedMessage {
2257 pub tool_name: String,
2258 pub tool_use_id: String,
2259 #[serde(default, skip_serializing_if = "Option::is_none")]
2260 pub agent_id: Option<String>,
2261 #[serde(default, skip_serializing_if = "Option::is_none")]
2262 pub decision_reason_type: Option<String>,
2263 #[serde(default, skip_serializing_if = "Option::is_none")]
2267 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2268 #[serde(default, skip_serializing_if = "Option::is_none")]
2269 pub decision_reason: Option<String>,
2270 pub message: String,
2271 #[serde(default, skip_serializing_if = "Option::is_none")]
2272 pub uuid: Option<String>,
2273 #[serde(default, skip_serializing_if = "Option::is_none")]
2274 pub session_id: Option<String>,
2275}
2276
2277#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2280pub enum PermissionDeniedReasonCode {
2281 ClassifierTranscriptTooLong,
2283 OutsideReadsBlocked,
2285 MemoryPaused,
2287 Unknown(String),
2289}
2290
2291impl PermissionDeniedReasonCode {
2292 pub fn as_str(&self) -> &str {
2293 match self {
2294 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2295 Self::OutsideReadsBlocked => "outside_reads_blocked",
2296 Self::MemoryPaused => "memory_paused",
2297 Self::Unknown(s) => s.as_str(),
2298 }
2299 }
2300}
2301
2302impl fmt::Display for PermissionDeniedReasonCode {
2303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2304 f.write_str(self.as_str())
2305 }
2306}
2307
2308impl From<&str> for PermissionDeniedReasonCode {
2309 fn from(s: &str) -> Self {
2310 match s {
2311 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2312 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2313 "memory_paused" => Self::MemoryPaused,
2314 other => Self::Unknown(other.to_string()),
2315 }
2316 }
2317}
2318
2319impl Serialize for PermissionDeniedReasonCode {
2320 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2321 serializer.serialize_str(self.as_str())
2322 }
2323}
2324
2325impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2326 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2327 let s = String::deserialize(deserializer)?;
2328 Ok(Self::from(s.as_str()))
2329 }
2330}
2331
2332#[derive(Debug, Clone, Serialize, Deserialize)]
2333pub struct MirrorErrorMessage {
2334 pub error: String,
2335 pub key: MirrorErrorKey,
2336 #[serde(default, skip_serializing_if = "Option::is_none")]
2337 pub uuid: Option<String>,
2338 #[serde(default, skip_serializing_if = "Option::is_none")]
2339 pub session_id: Option<String>,
2340}
2341
2342#[derive(Debug, Clone, Serialize, Deserialize)]
2343pub struct MirrorErrorKey {
2344 #[serde(rename = "projectKey")]
2345 pub project_key: String,
2346 #[serde(rename = "sessionId")]
2347 pub session_id: String,
2348 #[serde(default, skip_serializing_if = "Option::is_none")]
2349 pub subpath: Option<String>,
2350}
2351
2352#[derive(Debug, Clone, Serialize, Deserialize)]
2353pub struct InformationalMessage {
2354 pub content: String,
2355 pub level: String,
2356 #[serde(default, skip_serializing_if = "Option::is_none")]
2357 pub tool_use_id: Option<String>,
2358 #[serde(default, skip_serializing_if = "Option::is_none")]
2359 pub prevent_continuation: Option<bool>,
2360 #[serde(default, skip_serializing_if = "Option::is_none")]
2361 pub uuid: Option<String>,
2362 #[serde(default, skip_serializing_if = "Option::is_none")]
2363 pub session_id: Option<String>,
2364}
2365
2366#[derive(Debug, Clone, Serialize, Deserialize)]
2368pub struct PluginInfo {
2369 pub name: String,
2371 pub path: String,
2373 #[serde(skip_serializing_if = "Option::is_none")]
2375 pub source: Option<String>,
2376 #[serde(default, skip_serializing_if = "Option::is_none")]
2378 pub version: Option<String>,
2379}
2380
2381#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2383pub struct PluginDiagnostic {
2384 pub plugin: String,
2385 #[serde(rename = "type")]
2386 pub diagnostic_type: String,
2387 pub message: String,
2388}
2389
2390#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2392pub struct MemoryPaths {
2393 #[serde(default, skip_serializing_if = "Option::is_none")]
2394 pub auto: Option<String>,
2395 #[serde(default, skip_serializing_if = "Option::is_none")]
2396 pub team: Option<String>,
2397 #[serde(flatten)]
2398 pub extra: serde_json::Map<String, Value>,
2399}
2400
2401#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2405pub struct McpServerError {
2406 pub name: String,
2407 #[serde(rename = "type")]
2409 pub error_type: String,
2410 pub message: String,
2411}
2412
2413#[derive(Debug, Clone, Serialize, Deserialize)]
2415pub struct InitMessage {
2416 pub session_id: String,
2418 #[serde(skip_serializing_if = "Option::is_none")]
2420 pub cwd: Option<String>,
2421 #[serde(skip_serializing_if = "Option::is_none")]
2423 pub model: Option<String>,
2424 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2426 pub tools: Vec<String>,
2427 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2429 pub mcp_servers: Vec<Value>,
2430 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2432 pub slash_commands: Vec<String>,
2433 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2436 pub terminal_slash_commands: Vec<String>,
2437 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2439 pub agents: Vec<String>,
2440 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2442 pub plugins: Vec<PluginInfo>,
2443 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2445 pub skills: Vec<Value>,
2446 #[serde(skip_serializing_if = "Option::is_none")]
2448 pub claude_code_version: Option<String>,
2449 #[serde(skip_serializing_if = "Option::is_none")]
2452 pub messaging_socket_path: Option<String>,
2453 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2455 pub api_key_source: Option<ApiKeySource>,
2456 #[serde(skip_serializing_if = "Option::is_none")]
2458 pub output_style: Option<OutputStyle>,
2459 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2461 pub permission_mode: Option<InitPermissionMode>,
2462
2463 #[serde(skip_serializing_if = "Option::is_none")]
2465 pub uuid: Option<String>,
2466
2467 #[serde(skip_serializing_if = "Option::is_none")]
2469 pub memory_paths: Option<MemoryPaths>,
2470
2471 #[serde(default, skip_serializing_if = "Option::is_none")]
2476 pub scratchpad_path: Option<String>,
2477
2478 #[serde(skip_serializing_if = "Option::is_none")]
2480 pub fast_mode_state: Option<String>,
2481
2482 #[serde(default, skip_serializing_if = "Option::is_none")]
2484 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2485
2486 #[serde(default, skip_serializing_if = "Option::is_none")]
2489 pub mcp_server_errors: Option<Vec<McpServerError>>,
2490
2491 #[serde(default, skip_serializing_if = "Option::is_none")]
2493 pub analytics_disabled: Option<bool>,
2494
2495 #[serde(default, skip_serializing_if = "Option::is_none")]
2497 pub product_feedback_disabled: Option<bool>,
2498
2499 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2501 pub betas: Vec<String>,
2502
2503 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2505 pub capabilities: Vec<String>,
2506
2507 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2509 pub plugin_errors: Vec<PluginDiagnostic>,
2510
2511 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2513 pub plugin_warnings: Vec<PluginDiagnostic>,
2514
2515 #[serde(default, skip_serializing_if = "Option::is_none")]
2520 pub effort: Option<String>,
2521
2522 #[serde(default, skip_serializing_if = "Option::is_none")]
2527 pub cloud_session: Option<Value>,
2528
2529 #[serde(default, skip_serializing_if = "Option::is_none")]
2533 pub footer_indicator: Option<FooterIndicator>,
2534
2535 #[serde(default, skip_serializing_if = "Option::is_none")]
2539 pub worker_epoch: Option<u64>,
2540
2541 #[serde(default, skip_serializing_if = "Option::is_none")]
2545 pub powershell_path: Option<Option<String>>,
2546
2547 #[serde(default, skip_serializing_if = "Option::is_none")]
2551 pub startup_timing: Option<serde_json::Map<String, Value>>,
2552
2553 #[serde(default, skip_serializing_if = "Option::is_none")]
2563 pub per_turn_effort_active: Option<bool>,
2564
2565 #[serde(default, skip_serializing_if = "Option::is_none")]
2572 pub view_mode: Option<ViewMode>,
2573}
2574
2575#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2579pub struct FooterIndicator {
2580 pub text: String,
2583}
2584
2585#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2588pub enum ViewMode {
2589 Focus,
2592 Default,
2594 Unknown(String),
2596}
2597
2598impl ViewMode {
2599 pub fn as_str(&self) -> &str {
2600 match self {
2601 Self::Focus => "focus",
2602 Self::Default => "default",
2603 Self::Unknown(s) => s.as_str(),
2604 }
2605 }
2606}
2607
2608impl fmt::Display for ViewMode {
2609 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2610 f.write_str(self.as_str())
2611 }
2612}
2613
2614impl From<&str> for ViewMode {
2615 fn from(s: &str) -> Self {
2616 match s {
2617 "focus" => Self::Focus,
2618 "default" => Self::Default,
2619 other => Self::Unknown(other.to_string()),
2620 }
2621 }
2622}
2623
2624impl Serialize for ViewMode {
2625 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2626 serializer.serialize_str(self.as_str())
2627 }
2628}
2629
2630impl<'de> Deserialize<'de> for ViewMode {
2631 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2632 let s = String::deserialize(deserializer)?;
2633 Ok(Self::from(s.as_str()))
2634 }
2635}
2636
2637#[derive(Debug, Clone, Serialize, Deserialize)]
2639pub struct StatusMessage {
2640 pub session_id: String,
2642 pub status: Option<StatusMessageStatus>,
2644 #[serde(skip_serializing_if = "Option::is_none")]
2646 pub uuid: Option<String>,
2647 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2649 pub permission_mode: Option<InitPermissionMode>,
2650 #[serde(skip_serializing_if = "Option::is_none")]
2651 pub compact_result: Option<String>,
2652 #[serde(skip_serializing_if = "Option::is_none")]
2653 pub compact_error: Option<String>,
2654}
2655
2656#[derive(Debug, Clone, Serialize, Deserialize)]
2658pub struct CompactBoundaryMessage {
2659 pub session_id: String,
2661 pub compact_metadata: CompactMetadata,
2663 #[serde(
2667 default,
2668 skip_serializing_if = "Option::is_none",
2669 alias = "content",
2670 alias = "text"
2671 )]
2672 pub summary: Option<String>,
2673 #[serde(
2677 default,
2678 skip_serializing_if = "Option::is_none",
2679 alias = "message_count"
2680 )]
2681 pub leaf_message_count: Option<u32>,
2682 #[serde(default, skip_serializing_if = "Option::is_none")]
2684 pub duration_ms: Option<u64>,
2685 #[serde(skip_serializing_if = "Option::is_none")]
2687 pub uuid: Option<String>,
2688 #[serde(skip_serializing_if = "Option::is_none")]
2690 pub logical_parent_uuid: Option<Option<String>>,
2691 #[serde(default, skip_serializing_if = "Option::is_none")]
2695 pub historical: Option<bool>,
2696}
2697
2698#[derive(Debug, Clone, Serialize, Deserialize)]
2700pub struct CompactMetadata {
2701 pub pre_tokens: u64,
2703 pub trigger: CompactionTrigger,
2705 #[serde(default, skip_serializing_if = "Option::is_none")]
2706 pub post_tokens: Option<u64>,
2707 #[serde(default, skip_serializing_if = "Option::is_none")]
2708 pub cumulative_dropped_tokens: Option<u64>,
2709 #[serde(default, skip_serializing_if = "Option::is_none")]
2710 pub duration_ms: Option<u64>,
2711 #[serde(default, skip_serializing_if = "Option::is_none")]
2712 pub user_context: Option<String>,
2713 #[serde(default, skip_serializing_if = "Option::is_none")]
2714 pub messages_summarized: Option<u64>,
2715 #[serde(default, skip_serializing_if = "Option::is_none")]
2716 pub precomputed: Option<bool>,
2717 #[serde(default, skip_serializing_if = "Option::is_none")]
2718 pub pre_compact_discovered_tools: Option<Vec<String>>,
2719 #[serde(default, skip_serializing_if = "Option::is_none")]
2724 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
2725 #[serde(default, skip_serializing_if = "Option::is_none")]
2726 pub preserved_segment: Option<PreservedSegment>,
2727 #[serde(default, skip_serializing_if = "Option::is_none")]
2728 pub preserved_messages: Option<PreservedMessages>,
2729}
2730
2731#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2734pub struct ArtifactReadVersion {
2735 pub slug: String,
2737 pub ver: String,
2739}
2740
2741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2742pub struct PreservedSegment {
2743 pub head_uuid: String,
2744 pub anchor_uuid: String,
2745 pub tail_uuid: String,
2746}
2747
2748#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2749pub struct PreservedMessages {
2750 pub anchor_uuid: String,
2751 pub uuids: Vec<String>,
2752 #[serde(default, skip_serializing_if = "Option::is_none")]
2753 pub all_uuids: Option<Vec<String>>,
2754}
2755
2756#[derive(Debug, Clone, Serialize, Deserialize)]
2762pub struct TaskUsage {
2763 pub duration_ms: u64,
2765 pub tool_uses: u64,
2767 pub total_tokens: u64,
2769}
2770
2771#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2773pub enum TaskType {
2774 LocalAgent,
2776 LocalBash,
2778 LocalWorkflow,
2780 Unknown(String),
2782}
2783
2784impl TaskType {
2785 pub fn as_str(&self) -> &str {
2786 match self {
2787 Self::LocalAgent => "local_agent",
2788 Self::LocalBash => "local_bash",
2789 Self::LocalWorkflow => "local_workflow",
2790 Self::Unknown(s) => s.as_str(),
2791 }
2792 }
2793}
2794
2795impl fmt::Display for TaskType {
2796 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2797 f.write_str(self.as_str())
2798 }
2799}
2800
2801impl From<&str> for TaskType {
2802 fn from(s: &str) -> Self {
2803 match s {
2804 "local_agent" => Self::LocalAgent,
2805 "local_bash" => Self::LocalBash,
2806 "local_workflow" => Self::LocalWorkflow,
2807 other => Self::Unknown(other.to_string()),
2808 }
2809 }
2810}
2811
2812impl Serialize for TaskType {
2813 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2814 serializer.serialize_str(self.as_str())
2815 }
2816}
2817
2818impl<'de> Deserialize<'de> for TaskType {
2819 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2820 let s = String::deserialize(deserializer)?;
2821 Ok(Self::from(s.as_str()))
2822 }
2823}
2824
2825#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2827pub enum TaskStatus {
2828 Pending,
2829 Running,
2830 Completed,
2831 Failed,
2832 Killed,
2833 Paused,
2834 Stopped,
2835 Unknown(String),
2836}
2837
2838impl TaskStatus {
2839 pub fn as_str(&self) -> &str {
2840 match self {
2841 Self::Pending => "pending",
2842 Self::Running => "running",
2843 Self::Completed => "completed",
2844 Self::Failed => "failed",
2845 Self::Killed => "killed",
2846 Self::Paused => "paused",
2847 Self::Stopped => "stopped",
2848 Self::Unknown(s) => s.as_str(),
2849 }
2850 }
2851}
2852
2853impl fmt::Display for TaskStatus {
2854 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2855 f.write_str(self.as_str())
2856 }
2857}
2858
2859impl From<&str> for TaskStatus {
2860 fn from(s: &str) -> Self {
2861 match s {
2862 "pending" => Self::Pending,
2863 "running" => Self::Running,
2864 "completed" => Self::Completed,
2865 "failed" => Self::Failed,
2866 "killed" => Self::Killed,
2867 "paused" => Self::Paused,
2868 "stopped" => Self::Stopped,
2869 other => Self::Unknown(other.to_string()),
2870 }
2871 }
2872}
2873
2874impl Serialize for TaskStatus {
2875 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2876 serializer.serialize_str(self.as_str())
2877 }
2878}
2879
2880impl<'de> Deserialize<'de> for TaskStatus {
2881 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2882 let s = String::deserialize(deserializer)?;
2883 Ok(Self::from(s.as_str()))
2884 }
2885}
2886
2887#[derive(Debug, Clone, Serialize, Deserialize)]
2889pub struct TaskStartedMessage {
2890 pub session_id: String,
2891 pub task_id: String,
2892 #[serde(default, skip_serializing_if = "Option::is_none")]
2893 pub task_type: Option<TaskType>,
2894 #[serde(default, skip_serializing_if = "Option::is_none")]
2895 pub tool_use_id: Option<String>,
2896 pub description: String,
2897 #[serde(default, skip_serializing_if = "Option::is_none")]
2900 pub subagent_type: Option<String>,
2901 #[serde(default, skip_serializing_if = "Option::is_none")]
2907 pub is_backgrounded: Option<bool>,
2908 #[serde(default, skip_serializing_if = "Option::is_none")]
2912 pub spawn_depth: Option<u32>,
2913 #[serde(default, skip_serializing_if = "Option::is_none")]
2915 pub prompt: Option<String>,
2916 #[serde(default, skip_serializing_if = "Option::is_none")]
2917 pub workflow_name: Option<String>,
2918 #[serde(default, skip_serializing_if = "Option::is_none")]
2919 pub skip_transcript: Option<bool>,
2920 #[serde(default, skip_serializing_if = "Option::is_none")]
2925 pub ambient: Option<bool>,
2926 pub uuid: String,
2927}
2928
2929#[derive(Debug, Clone, Serialize, Deserialize)]
2933pub struct TaskUpdatedMessage {
2934 pub session_id: String,
2935 pub task_id: String,
2936 pub patch: TaskPatch,
2937 pub uuid: String,
2938}
2939
2940#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2943pub struct TaskPatch {
2944 #[serde(default, skip_serializing_if = "Option::is_none")]
2945 pub status: Option<TaskStatus>,
2946 #[serde(default, skip_serializing_if = "Option::is_none")]
2949 pub end_time: Option<u64>,
2950 #[serde(default, skip_serializing_if = "Option::is_none")]
2951 pub description: Option<String>,
2952 #[serde(default, skip_serializing_if = "Option::is_none")]
2953 pub total_paused_ms: Option<u64>,
2954 #[serde(default, skip_serializing_if = "Option::is_none")]
2955 pub error: Option<String>,
2956 #[serde(default, skip_serializing_if = "Option::is_none")]
2957 pub is_backgrounded: Option<bool>,
2958}
2959
2960#[derive(Debug, Clone, Serialize, Deserialize)]
2963pub struct ThinkingTokensMessage {
2964 pub session_id: String,
2965 pub estimated_tokens: u64,
2967 pub estimated_tokens_delta: u64,
2969 #[serde(default, skip_serializing_if = "Option::is_none")]
2976 pub user_message_uuid: Option<String>,
2977 pub uuid: String,
2978}
2979
2980#[derive(Debug, Clone, Serialize, Deserialize)]
2983pub struct TaskProgressMessage {
2984 pub session_id: String,
2985 pub task_id: String,
2986 #[serde(default, skip_serializing_if = "Option::is_none")]
2987 pub tool_use_id: Option<String>,
2988 pub description: String,
2989 #[serde(default, skip_serializing_if = "Option::is_none")]
2990 pub last_tool_name: Option<String>,
2991 pub usage: TaskUsage,
2992 #[serde(default, skip_serializing_if = "Option::is_none")]
2994 pub subagent_type: Option<String>,
2995 #[serde(default, skip_serializing_if = "Option::is_none")]
2996 pub summary: Option<String>,
2997 pub uuid: String,
2998}
2999
3000#[derive(Debug, Clone, Serialize, Deserialize)]
3003pub struct TaskNotificationMessage {
3004 pub session_id: String,
3005 pub task_id: String,
3006 pub status: TaskStatus,
3007 #[serde(default, skip_serializing_if = "Option::is_none")]
3010 pub reason: Option<TaskEndReason>,
3011 pub summary: String,
3012 pub output_file: Option<String>,
3013 #[serde(skip_serializing_if = "Option::is_none")]
3014 pub tool_use_id: Option<String>,
3015 #[serde(skip_serializing_if = "Option::is_none")]
3016 pub usage: Option<TaskUsage>,
3017 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3023 pub resource_links: Vec<ResourceLink>,
3024 #[serde(default, skip_serializing_if = "Option::is_none")]
3025 pub skip_transcript: Option<bool>,
3026 #[serde(default, skip_serializing_if = "Option::is_none")]
3029 pub ambient: Option<bool>,
3030 #[serde(skip_serializing_if = "Option::is_none")]
3031 pub uuid: Option<String>,
3032}
3033
3034#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3036pub enum AssistantErrorKind {
3037 AuthenticationFailed,
3038 OauthOrgNotAllowed,
3039 AccountOnHold,
3040 BillingError,
3041 RateLimit,
3042 Overloaded,
3043 InvalidRequest,
3044 ModelNotFound,
3045 ServerError,
3046 UnknownError,
3047 MaxOutputTokens,
3048 Unknown(String),
3049}
3050
3051impl AssistantErrorKind {
3052 pub fn as_str(&self) -> &str {
3053 match self {
3054 Self::AuthenticationFailed => "authentication_failed",
3055 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3056 Self::AccountOnHold => "account_on_hold",
3057 Self::BillingError => "billing_error",
3058 Self::RateLimit => "rate_limit",
3059 Self::Overloaded => "overloaded",
3060 Self::InvalidRequest => "invalid_request",
3061 Self::ModelNotFound => "model_not_found",
3062 Self::ServerError => "server_error",
3063 Self::UnknownError => "unknown",
3064 Self::MaxOutputTokens => "max_output_tokens",
3065 Self::Unknown(s) => s.as_str(),
3066 }
3067 }
3068}
3069
3070impl fmt::Display for AssistantErrorKind {
3071 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3072 f.write_str(self.as_str())
3073 }
3074}
3075
3076impl From<&str> for AssistantErrorKind {
3077 fn from(s: &str) -> Self {
3078 match s {
3079 "authentication_failed" => Self::AuthenticationFailed,
3080 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3081 "account_on_hold" => Self::AccountOnHold,
3082 "billing_error" => Self::BillingError,
3083 "rate_limit" => Self::RateLimit,
3084 "overloaded" => Self::Overloaded,
3085 "invalid_request" => Self::InvalidRequest,
3086 "model_not_found" => Self::ModelNotFound,
3087 "server_error" => Self::ServerError,
3088 "unknown" => Self::UnknownError,
3089 "max_output_tokens" => Self::MaxOutputTokens,
3090 other => Self::Unknown(other.to_string()),
3091 }
3092 }
3093}
3094
3095impl Serialize for AssistantErrorKind {
3096 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3097 serializer.serialize_str(self.as_str())
3098 }
3099}
3100
3101impl<'de> Deserialize<'de> for AssistantErrorKind {
3102 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3103 let s = String::deserialize(deserializer)?;
3104 Ok(Self::from(s.as_str()))
3105 }
3106}
3107
3108#[derive(Debug, Clone, Serialize, Deserialize)]
3115pub struct CodeChangePublishedMessage {
3116 pub provider: String,
3120 pub url: String,
3122 pub repo: String,
3125 pub identifier: String,
3127 #[serde(default, skip_serializing_if = "Option::is_none")]
3136 pub action: Option<String>,
3137 #[serde(default, skip_serializing_if = "Option::is_none")]
3145 pub branch: Option<String>,
3146 pub uuid: String,
3147 pub session_id: String,
3148}
3149
3150#[derive(Debug, Clone, Serialize, Deserialize)]
3155pub struct VcsStateChangedMessage {
3156 pub kind: VcsMutationKind,
3159 pub cwd: String,
3162 #[serde(default, skip_serializing_if = "Option::is_none")]
3167 pub branch: Option<String>,
3168 pub uuid: String,
3169 pub session_id: String,
3170}
3171
3172#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3174pub enum VcsMutationKind {
3175 Commit,
3176 Push,
3177 Merge,
3178 Rebase,
3179 Unknown(String),
3181}
3182
3183impl VcsMutationKind {
3184 pub fn as_str(&self) -> &str {
3185 match self {
3186 Self::Commit => "commit",
3187 Self::Push => "push",
3188 Self::Merge => "merge",
3189 Self::Rebase => "rebase",
3190 Self::Unknown(s) => s.as_str(),
3191 }
3192 }
3193}
3194
3195impl fmt::Display for VcsMutationKind {
3196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3197 f.write_str(self.as_str())
3198 }
3199}
3200
3201impl From<&str> for VcsMutationKind {
3202 fn from(s: &str) -> Self {
3203 match s {
3204 "commit" => Self::Commit,
3205 "push" => Self::Push,
3206 "merge" => Self::Merge,
3207 "rebase" => Self::Rebase,
3208 other => Self::Unknown(other.to_string()),
3209 }
3210 }
3211}
3212
3213impl Serialize for VcsMutationKind {
3214 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3215 serializer.serialize_str(self.as_str())
3216 }
3217}
3218
3219impl<'de> Deserialize<'de> for VcsMutationKind {
3220 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3221 let s = String::deserialize(deserializer)?;
3222 Ok(Self::from(s.as_str()))
3223 }
3224}
3225
3226#[derive(Debug, Clone, Serialize, Deserialize)]
3228pub struct FeedbackDraftQueuedMessage {
3229 pub draft_id: String,
3230 pub draft_type: String,
3231 pub title: String,
3232 pub details_preview: String,
3233 #[serde(default, skip_serializing_if = "Option::is_none")]
3234 pub uuid: Option<String>,
3235 #[serde(default, skip_serializing_if = "Option::is_none")]
3236 pub session_id: Option<String>,
3237 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3238 pub extra: serde_json::Map<String, Value>,
3239}
3240
3241#[derive(Debug, Clone, Serialize, Deserialize)]
3249pub struct CloudSessionDeltaMessage {
3250 pub seq: u64,
3254 pub changed: Vec<String>,
3258 pub cloud_session: Value,
3262 pub uuid: String,
3263 pub session_id: String,
3264 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3265 pub extra: serde_json::Map<String, Value>,
3266}
3267
3268#[derive(Debug, Clone, Serialize, Deserialize)]
3284pub struct DevIntentMessage {
3285 pub kind: DevIntentKind,
3287 #[serde(default, skip_serializing_if = "Option::is_none")]
3293 pub trigger: Option<DevIntentTrigger>,
3294 pub uuid: String,
3295 pub session_id: String,
3296 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3297 pub extra: serde_json::Map<String, Value>,
3298}
3299
3300#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3304pub enum DevIntentKind {
3305 IosApp,
3311 AndroidApp,
3318 Unknown(String),
3320}
3321
3322impl DevIntentKind {
3323 pub fn as_str(&self) -> &str {
3324 match self {
3325 Self::IosApp => "ios_app",
3326 Self::AndroidApp => "android_app",
3327 Self::Unknown(s) => s.as_str(),
3328 }
3329 }
3330}
3331
3332impl fmt::Display for DevIntentKind {
3333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3334 f.write_str(self.as_str())
3335 }
3336}
3337
3338impl From<&str> for DevIntentKind {
3339 fn from(s: &str) -> Self {
3340 match s {
3341 "ios_app" => Self::IosApp,
3342 "android_app" => Self::AndroidApp,
3343 other => Self::Unknown(other.to_string()),
3344 }
3345 }
3346}
3347
3348impl Serialize for DevIntentKind {
3349 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3350 serializer.serialize_str(self.as_str())
3351 }
3352}
3353
3354impl<'de> Deserialize<'de> for DevIntentKind {
3355 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3356 let s = String::deserialize(deserializer)?;
3357 Ok(Self::from(s.as_str()))
3358 }
3359}
3360
3361#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3365pub enum DevIntentTrigger {
3366 SwiftEdit,
3368 UikitImport,
3370 XcodeProject,
3373 IosCommand,
3376 KotlinEdit,
3378 JavaEdit,
3380 AndroidImport,
3382 AndroidManifest,
3384 GradlePlugin,
3386 AndroidCommand,
3389 ProjectScan,
3392 Unknown(String),
3394}
3395
3396impl DevIntentTrigger {
3397 pub fn as_str(&self) -> &str {
3398 match self {
3399 Self::SwiftEdit => "swift_edit",
3400 Self::UikitImport => "uikit_import",
3401 Self::XcodeProject => "xcode_project",
3402 Self::IosCommand => "ios_command",
3403 Self::KotlinEdit => "kotlin_edit",
3404 Self::JavaEdit => "java_edit",
3405 Self::AndroidImport => "android_import",
3406 Self::AndroidManifest => "android_manifest",
3407 Self::GradlePlugin => "gradle_plugin",
3408 Self::AndroidCommand => "android_command",
3409 Self::ProjectScan => "project_scan",
3410 Self::Unknown(s) => s.as_str(),
3411 }
3412 }
3413}
3414
3415impl fmt::Display for DevIntentTrigger {
3416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3417 f.write_str(self.as_str())
3418 }
3419}
3420
3421impl From<&str> for DevIntentTrigger {
3422 fn from(s: &str) -> Self {
3423 match s {
3424 "swift_edit" => Self::SwiftEdit,
3425 "uikit_import" => Self::UikitImport,
3426 "xcode_project" => Self::XcodeProject,
3427 "ios_command" => Self::IosCommand,
3428 "kotlin_edit" => Self::KotlinEdit,
3429 "java_edit" => Self::JavaEdit,
3430 "android_import" => Self::AndroidImport,
3431 "android_manifest" => Self::AndroidManifest,
3432 "gradle_plugin" => Self::GradlePlugin,
3433 "android_command" => Self::AndroidCommand,
3434 "project_scan" => Self::ProjectScan,
3435 other => Self::Unknown(other.to_string()),
3436 }
3437 }
3438}
3439
3440impl Serialize for DevIntentTrigger {
3441 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3442 serializer.serialize_str(self.as_str())
3443 }
3444}
3445
3446impl<'de> Deserialize<'de> for DevIntentTrigger {
3447 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3448 let s = String::deserialize(deserializer)?;
3449 Ok(Self::from(s.as_str()))
3450 }
3451}
3452
3453#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3457pub enum TaskEndReason {
3458 WorkerRestart,
3461 Unknown(String),
3463}
3464
3465impl TaskEndReason {
3466 pub fn as_str(&self) -> &str {
3467 match self {
3468 Self::WorkerRestart => "worker_restart",
3469 Self::Unknown(s) => s.as_str(),
3470 }
3471 }
3472}
3473
3474impl fmt::Display for TaskEndReason {
3475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3476 f.write_str(self.as_str())
3477 }
3478}
3479
3480impl From<&str> for TaskEndReason {
3481 fn from(s: &str) -> Self {
3482 match s {
3483 "worker_restart" => Self::WorkerRestart,
3484 other => Self::Unknown(other.to_string()),
3485 }
3486 }
3487}
3488
3489impl Serialize for TaskEndReason {
3490 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3491 serializer.serialize_str(self.as_str())
3492 }
3493}
3494
3495impl<'de> Deserialize<'de> for TaskEndReason {
3496 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3497 let s = String::deserialize(deserializer)?;
3498 Ok(Self::from(s.as_str()))
3499 }
3500}
3501
3502#[derive(Debug, Clone, Serialize, Deserialize)]
3511pub struct TurnHandoffAvailableMessage {
3512 pub v: u64,
3514 pub tools: Vec<String>,
3516 pub worker_epoch: u64,
3518 #[serde(default, skip_serializing_if = "Option::is_none")]
3522 pub relay_marker: Option<bool>,
3523 #[serde(default, skip_serializing_if = "Option::is_none")]
3531 pub staged_files: Option<bool>,
3532 #[serde(default, skip_serializing_if = "Option::is_none")]
3539 pub no_query_first: Option<bool>,
3540 #[serde(default, skip_serializing_if = "Option::is_none")]
3547 pub home_files: Option<bool>,
3548 #[serde(default, skip_serializing_if = "Option::is_none")]
3552 pub file_names: Option<bool>,
3553 pub uuid: String,
3554 pub session_id: String,
3555 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3556 pub extra: serde_json::Map<String, Value>,
3557}
3558
3559#[derive(Debug, Clone, Serialize, Deserialize)]
3571pub struct TurnPreemptedMessage {
3572 pub reason: String,
3576 pub preempted_by_uuid: String,
3580 #[serde(default)]
3583 pub preempted_message_uuids: Vec<String>,
3584 pub uuid: String,
3585 pub session_id: String,
3586 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3587 pub extra: serde_json::Map<String, Value>,
3588}
3589
3590#[derive(Debug, Clone, Serialize, Deserialize)]
3598pub struct PeerMessageHoldMessage {
3599 pub state: PeerMessageHoldState,
3605 #[serde(default, skip_serializing_if = "Option::is_none")]
3612 pub message_uuid: Option<String>,
3613 pub lane: PeerMessageLane,
3615 pub from: String,
3620 #[serde(default, skip_serializing_if = "Option::is_none")]
3623 pub from_name: Option<String>,
3624 #[serde(default, skip_serializing_if = "Option::is_none")]
3626 pub cause: Option<PeerMessageHoldCause>,
3627 #[serde(default, skip_serializing_if = "Option::is_none")]
3629 pub outcome: Option<PeerMessageHoldOutcome>,
3630 pub uuid: String,
3631 pub session_id: String,
3632 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3633 pub extra: serde_json::Map<String, Value>,
3634}
3635
3636#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3638pub enum PeerMessageHoldState {
3639 Held,
3641 Released,
3643 Dropped,
3645 Unknown(String),
3647}
3648
3649impl PeerMessageHoldState {
3650 pub fn as_str(&self) -> &str {
3651 match self {
3652 Self::Held => "held",
3653 Self::Released => "released",
3654 Self::Dropped => "dropped",
3655 Self::Unknown(s) => s.as_str(),
3656 }
3657 }
3658}
3659
3660impl fmt::Display for PeerMessageHoldState {
3661 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3662 f.write_str(self.as_str())
3663 }
3664}
3665
3666impl From<&str> for PeerMessageHoldState {
3667 fn from(s: &str) -> Self {
3668 match s {
3669 "held" => Self::Held,
3670 "released" => Self::Released,
3671 "dropped" => Self::Dropped,
3672 other => Self::Unknown(other.to_string()),
3673 }
3674 }
3675}
3676
3677impl Serialize for PeerMessageHoldState {
3678 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3679 serializer.serialize_str(self.as_str())
3680 }
3681}
3682
3683impl<'de> Deserialize<'de> for PeerMessageHoldState {
3684 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3685 let s = String::deserialize(deserializer)?;
3686 Ok(Self::from(s.as_str()))
3687 }
3688}
3689
3690#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3692pub enum PeerMessageLane {
3693 Bridge,
3695 Stdin,
3697 Socket,
3699 Unknown(String),
3701}
3702
3703impl PeerMessageLane {
3704 pub fn as_str(&self) -> &str {
3705 match self {
3706 Self::Bridge => "bridge",
3707 Self::Stdin => "stdin",
3708 Self::Socket => "socket",
3709 Self::Unknown(s) => s.as_str(),
3710 }
3711 }
3712}
3713
3714impl fmt::Display for PeerMessageLane {
3715 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3716 f.write_str(self.as_str())
3717 }
3718}
3719
3720impl From<&str> for PeerMessageLane {
3721 fn from(s: &str) -> Self {
3722 match s {
3723 "bridge" => Self::Bridge,
3724 "stdin" => Self::Stdin,
3725 "socket" => Self::Socket,
3726 other => Self::Unknown(other.to_string()),
3727 }
3728 }
3729}
3730
3731impl Serialize for PeerMessageLane {
3732 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3733 serializer.serialize_str(self.as_str())
3734 }
3735}
3736
3737impl<'de> Deserialize<'de> for PeerMessageLane {
3738 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3739 let s = String::deserialize(deserializer)?;
3740 Ok(Self::from(s.as_str()))
3741 }
3742}
3743
3744#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3751pub enum PeerMessageHoldCause {
3752 ExplicitSetting,
3753 ManagedSetting,
3754 RepoSetting,
3755 InvalidSetting,
3756 BypassDefault,
3757 ModeUnknown,
3758 ModeMismatch,
3759 NoModeAsserted,
3760 Unknown(String),
3762}
3763
3764impl PeerMessageHoldCause {
3765 pub fn as_str(&self) -> &str {
3766 match self {
3767 Self::ExplicitSetting => "explicit-setting",
3768 Self::ManagedSetting => "managed-setting",
3769 Self::RepoSetting => "repo-setting",
3770 Self::InvalidSetting => "invalid-setting",
3771 Self::BypassDefault => "bypass-default",
3772 Self::ModeUnknown => "mode-unknown",
3773 Self::ModeMismatch => "mode-mismatch",
3774 Self::NoModeAsserted => "no-mode-asserted",
3775 Self::Unknown(s) => s.as_str(),
3776 }
3777 }
3778}
3779
3780impl fmt::Display for PeerMessageHoldCause {
3781 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3782 f.write_str(self.as_str())
3783 }
3784}
3785
3786impl From<&str> for PeerMessageHoldCause {
3787 fn from(s: &str) -> Self {
3788 match s {
3789 "explicit-setting" => Self::ExplicitSetting,
3790 "managed-setting" => Self::ManagedSetting,
3791 "repo-setting" => Self::RepoSetting,
3792 "invalid-setting" => Self::InvalidSetting,
3793 "bypass-default" => Self::BypassDefault,
3794 "mode-unknown" => Self::ModeUnknown,
3795 "mode-mismatch" => Self::ModeMismatch,
3796 "no-mode-asserted" => Self::NoModeAsserted,
3797 other => Self::Unknown(other.to_string()),
3798 }
3799 }
3800}
3801
3802impl Serialize for PeerMessageHoldCause {
3803 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3804 serializer.serialize_str(self.as_str())
3805 }
3806}
3807
3808impl<'de> Deserialize<'de> for PeerMessageHoldCause {
3809 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3810 let s = String::deserialize(deserializer)?;
3811 Ok(Self::from(s.as_str()))
3812 }
3813}
3814
3815#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3818pub enum PeerMessageHoldOutcome {
3819 Expired,
3823 Refused,
3825 Dropped,
3828 Discarded,
3830 Unknown(String),
3832}
3833
3834impl PeerMessageHoldOutcome {
3835 pub fn as_str(&self) -> &str {
3836 match self {
3837 Self::Expired => "expired",
3838 Self::Refused => "refused",
3839 Self::Dropped => "dropped",
3840 Self::Discarded => "discarded",
3841 Self::Unknown(s) => s.as_str(),
3842 }
3843 }
3844}
3845
3846impl fmt::Display for PeerMessageHoldOutcome {
3847 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3848 f.write_str(self.as_str())
3849 }
3850}
3851
3852impl From<&str> for PeerMessageHoldOutcome {
3853 fn from(s: &str) -> Self {
3854 match s {
3855 "expired" => Self::Expired,
3856 "refused" => Self::Refused,
3857 "dropped" => Self::Dropped,
3858 "discarded" => Self::Discarded,
3859 other => Self::Unknown(other.to_string()),
3860 }
3861 }
3862}
3863
3864impl Serialize for PeerMessageHoldOutcome {
3865 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3866 serializer.serialize_str(self.as_str())
3867 }
3868}
3869
3870impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
3871 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3872 let s = String::deserialize(deserializer)?;
3873 Ok(Self::from(s.as_str()))
3874 }
3875}
3876
3877#[derive(Debug, Clone, Serialize, Deserialize)]
3888pub struct PerTurnEffortChangedMessage {
3889 pub per_turn_effort_active: bool,
3891 pub uuid: String,
3892 pub session_id: String,
3893}
3894
3895#[derive(Debug, Clone, Serialize, Deserialize)]
3905pub struct SessionTitleChangedMessage {
3906 pub title: String,
3911 pub uuid: String,
3912 pub session_id: String,
3913}
3914
3915#[derive(Debug, Clone, Serialize, Deserialize)]
3919pub struct UiLogMessage {
3920 pub plugin: String,
3922 pub text: String,
3925 pub uuid: String,
3926 pub session_id: String,
3927}
3928
3929#[derive(Debug, Clone, Serialize, Deserialize)]
3933pub struct UiToastMessage {
3934 pub plugin: String,
3936 pub text: String,
3939 pub timeout_ms: u64,
3942 pub uuid: String,
3943 pub session_id: String,
3944}
3945
3946#[derive(Debug, Clone, Serialize, Deserialize)]
3950pub struct UiStatusMessage {
3951 pub plugin: String,
3953 pub text: Option<String>,
3957 pub uuid: String,
3958 pub session_id: String,
3959}
3960
3961#[derive(Debug, Clone, Serialize, Deserialize)]
3966pub struct UiInvalidateMessage {
3967 pub event: String,
3970 #[serde(default, skip_serializing_if = "Option::is_none")]
3974 pub instances: Option<Vec<UiInvalidatedInstance>>,
3975 pub uuid: String,
3976 pub session_id: String,
3977}
3978
3979#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3981pub struct UiInvalidatedInstance {
3982 pub surface: String,
3984 pub component: String,
3986 pub instance_id: String,
3989}
3990
3991#[derive(Debug, Clone, Serialize, Deserialize)]
3998pub struct UiPanesMessage {
3999 pub panes: Vec<UiPane>,
4002 pub shown_id: Option<String>,
4005 pub focused_id: Option<String>,
4008 pub focus_requested_id: Option<String>,
4012 pub uuid: String,
4013 pub session_id: String,
4014}
4015
4016#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4018pub struct UiPane {
4019 pub id: String,
4022 pub title: String,
4025 pub plugin: String,
4027 #[serde(default, skip_serializing_if = "Option::is_none")]
4030 pub close_on_escape: Option<bool>,
4031 #[serde(default, skip_serializing_if = "Option::is_none")]
4034 pub hold_toasts: Option<bool>,
4035 #[serde(default, skip_serializing_if = "Option::is_none")]
4038 pub rows: Option<u64>,
4039 #[serde(default, skip_serializing_if = "Option::is_none")]
4042 pub columns: Option<u64>,
4043}
4044
4045#[derive(Debug, Clone, Serialize, Deserialize)]
4049pub struct UiScrollMessage {
4050 pub client_id: String,
4052 pub component: UiSiteComponent,
4054 pub instance_id: String,
4056 pub offset: u64,
4059 #[serde(default, skip_serializing_if = "Option::is_none")]
4062 pub follow_end: Option<bool>,
4063 pub uuid: String,
4064 pub session_id: String,
4065}
4066
4067#[derive(Debug, Clone, Serialize, Deserialize)]
4072pub struct UiFocusMessage {
4073 pub client_id: String,
4075 pub component: UiSiteComponent,
4077 pub instance_id: String,
4079 pub plugin: String,
4081 pub key: String,
4083 pub uuid: String,
4084 pub session_id: String,
4085}
4086
4087#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4089pub enum UiSiteComponent {
4090 Pane,
4092 AbovePrompt,
4094 Unknown(String),
4096}
4097
4098impl UiSiteComponent {
4099 pub fn as_str(&self) -> &str {
4100 match self {
4101 Self::Pane => "Pane",
4102 Self::AbovePrompt => "AbovePrompt",
4103 Self::Unknown(s) => s.as_str(),
4104 }
4105 }
4106}
4107
4108impl fmt::Display for UiSiteComponent {
4109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4110 f.write_str(self.as_str())
4111 }
4112}
4113
4114impl From<&str> for UiSiteComponent {
4115 fn from(s: &str) -> Self {
4116 match s {
4117 "Pane" => Self::Pane,
4118 "AbovePrompt" => Self::AbovePrompt,
4119 other => Self::Unknown(other.to_string()),
4120 }
4121 }
4122}
4123
4124impl Serialize for UiSiteComponent {
4125 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4126 serializer.serialize_str(self.as_str())
4127 }
4128}
4129
4130impl<'de> Deserialize<'de> for UiSiteComponent {
4131 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4132 let s = String::deserialize(deserializer)?;
4133 Ok(Self::from(s.as_str()))
4134 }
4135}
4136
4137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4140pub struct LocalCommandRun {
4141 pub command: String,
4143 pub args: String,
4146}
4147
4148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4152pub struct LocalCommandOutcome {
4153 pub kind: LocalCommandOutcomeKind,
4154 #[serde(default, skip_serializing_if = "Option::is_none")]
4157 pub suggestion: Option<String>,
4158}
4159
4160#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4165pub enum LocalCommandOutcomeKind {
4166 UnavailableHeadless,
4168 Unknown,
4171 Failed,
4173 RestartRequired,
4175 Other(String),
4177}
4178
4179impl LocalCommandOutcomeKind {
4180 pub fn as_str(&self) -> &str {
4181 match self {
4182 Self::UnavailableHeadless => "unavailable_headless",
4183 Self::Unknown => "unknown",
4184 Self::Failed => "failed",
4185 Self::RestartRequired => "restart_required",
4186 Self::Other(s) => s.as_str(),
4187 }
4188 }
4189}
4190
4191impl fmt::Display for LocalCommandOutcomeKind {
4192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4193 f.write_str(self.as_str())
4194 }
4195}
4196
4197impl From<&str> for LocalCommandOutcomeKind {
4198 fn from(s: &str) -> Self {
4199 match s {
4200 "unavailable_headless" => Self::UnavailableHeadless,
4201 "unknown" => Self::Unknown,
4202 "failed" => Self::Failed,
4203 "restart_required" => Self::RestartRequired,
4204 other => Self::Other(other.to_string()),
4205 }
4206 }
4207}
4208
4209impl Serialize for LocalCommandOutcomeKind {
4210 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4211 serializer.serialize_str(self.as_str())
4212 }
4213}
4214
4215impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4216 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4217 let s = String::deserialize(deserializer)?;
4218 Ok(Self::from(s.as_str()))
4219 }
4220}
4221
4222#[derive(Debug, Clone, Serialize, Deserialize)]
4228pub struct UsageReport {
4229 pub session: super::control::UsageSession,
4231 pub rate_limits: Option<UsageReportRateLimits>,
4235 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4236 pub extra: serde_json::Map<String, Value>,
4237}
4238
4239#[derive(Debug, Clone, Serialize, Deserialize)]
4241pub struct UsageReportRateLimits {
4242 pub limits: Option<Vec<UsageReportLimit>>,
4250 #[serde(default, skip_serializing_if = "Option::is_none")]
4253 pub extra_usage: Option<UsageReportExtraUsage>,
4254 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4255 pub extra: serde_json::Map<String, Value>,
4256}
4257
4258#[derive(Debug, Clone, Serialize, Deserialize)]
4260pub struct UsageReportLimit {
4261 pub kind: String,
4264 pub group: String,
4267 pub percent: f64,
4269 pub resets_at: Option<String>,
4271 #[serde(default, skip_serializing_if = "Option::is_none")]
4274 pub scope: Option<UsageReportScope>,
4275 #[serde(default, skip_serializing_if = "Option::is_none")]
4279 pub severity: Option<String>,
4280 #[serde(default, skip_serializing_if = "Option::is_none")]
4282 pub is_active: Option<bool>,
4283 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4284 pub extra: serde_json::Map<String, Value>,
4285}
4286
4287#[derive(Debug, Clone, Serialize, Deserialize)]
4289pub struct UsageReportScope {
4290 #[serde(default, skip_serializing_if = "Option::is_none")]
4291 pub model: Option<UsageReportScopeLabel>,
4292 #[serde(default, skip_serializing_if = "Option::is_none")]
4293 pub surface: Option<UsageReportScopeLabel>,
4294 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4295 pub extra: serde_json::Map<String, Value>,
4296}
4297
4298#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4300pub struct UsageReportScopeLabel {
4301 pub display_name: String,
4302}
4303
4304#[derive(Debug, Clone, Serialize, Deserialize)]
4308pub struct UsageReportExtraUsage {
4309 pub is_enabled: bool,
4311 pub monthly_limit: Option<f64>,
4312 pub used_credits: Option<f64>,
4313 pub utilization: Option<f64>,
4314 #[serde(default, skip_serializing_if = "Option::is_none")]
4315 pub currency: Option<String>,
4316 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4317 pub extra: serde_json::Map<String, Value>,
4318}
4319
4320#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4323pub struct BatchToolUse {
4324 pub id: String,
4325 pub name: String,
4326}
4327
4328#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4333pub struct ContextUsage {
4334 pub model: String,
4336 pub total_tokens: u64,
4339 pub raw_max_tokens: u64,
4342 pub percentage: u64,
4344 #[serde(default, skip_serializing_if = "Option::is_none")]
4346 pub over_limit: Option<ContextOverLimit>,
4347 #[serde(default)]
4349 pub categories: Vec<ContextCategory>,
4350 #[serde(default)]
4352 pub mcp_tools: Vec<ContextMcpTool>,
4353 #[serde(default)]
4355 pub memory_files: Vec<ContextMemoryFile>,
4356 #[serde(default)]
4358 pub agents: Vec<ContextAgent>,
4359 #[serde(default, skip_serializing_if = "Option::is_none")]
4361 pub skills: Option<Vec<ContextSkill>>,
4362}
4363
4364#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4366pub struct ContextOverLimit {
4367 pub tokens_over: u64,
4368 pub kind: String,
4371}
4372
4373#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4375pub struct ContextCategory {
4376 pub name: String,
4379 pub tokens: u64,
4380 pub kind: String,
4384}
4385
4386#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4388pub struct ContextMcpTool {
4389 pub name: String,
4391 pub server_name: String,
4392 pub tokens: u64,
4393}
4394
4395#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4397pub struct ContextMemoryFile {
4398 pub path: String,
4399 #[serde(rename = "type")]
4401 pub file_type: String,
4402 pub tokens: u64,
4403}
4404
4405#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4407pub struct ContextAgent {
4408 pub agent_type: String,
4409 pub source: String,
4411 pub tokens: u64,
4412}
4413
4414#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4416pub struct ContextSkill {
4417 pub name: String,
4418 pub source: String,
4420 #[serde(default, skip_serializing_if = "Option::is_none")]
4421 pub plugin_name: Option<String>,
4422 pub tokens: u64,
4423}
4424
4425#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4427pub struct ToolUseMeta {
4428 pub id: String,
4429 pub display_name: String,
4430 #[serde(default, skip_serializing_if = "Option::is_none")]
4431 pub server_display_name: Option<String>,
4432 #[serde(default, skip_serializing_if = "Option::is_none")]
4433 pub icon_url: Option<String>,
4434}
4435
4436#[derive(Debug, Clone, Serialize, Deserialize)]
4438pub struct AssistantMessage {
4439 pub message: AssistantMessageContent,
4440 #[serde(alias = "sessionId")]
4441 pub session_id: String,
4442 #[serde(skip_serializing_if = "Option::is_none")]
4443 pub uuid: Option<String>,
4444 #[serde(skip_serializing_if = "Option::is_none")]
4445 pub parent_tool_use_id: Option<String>,
4446 #[serde(skip_serializing_if = "Option::is_none")]
4448 pub request_id: Option<String>,
4449 #[serde(default, skip_serializing_if = "Option::is_none")]
4458 pub user_message_uuid: Option<String>,
4459 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4465 pub user_message_uuids: Vec<String>,
4466 #[serde(skip_serializing_if = "Option::is_none")]
4469 pub subagent_type: Option<String>,
4470 #[serde(skip_serializing_if = "Option::is_none")]
4472 pub task_description: Option<String>,
4473 #[serde(skip_serializing_if = "Option::is_none")]
4474 pub error: Option<AssistantErrorKind>,
4475 #[serde(default, skip_serializing_if = "Option::is_none")]
4479 pub aborted: Option<bool>,
4480 #[serde(default, skip_serializing_if = "Option::is_none")]
4485 pub resumed_from_incomplete_thinking: Option<bool>,
4486 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4487 pub supersedes: Vec<String>,
4488 #[serde(skip_serializing_if = "Option::is_none")]
4489 pub timestamp: Option<String>,
4490 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4491 pub tool_use_meta: Vec<ToolUseMeta>,
4492 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4497 pub batch_tool_uses: Vec<BatchToolUse>,
4498 #[serde(default, skip_serializing_if = "Option::is_none")]
4504 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
4505 #[serde(default, skip_serializing_if = "Option::is_none")]
4511 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
4512 #[serde(default, skip_serializing_if = "Option::is_none")]
4516 pub historical: Option<bool>,
4517 #[serde(default, skip_serializing_if = "Option::is_none")]
4525 pub resume_reason: Option<String>,
4526 #[serde(default, skip_serializing_if = "Option::is_none")]
4532 pub local_command_source: Option<String>,
4533 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4542 pub narration_block_indexes: Vec<usize>,
4543 #[serde(default, skip_serializing_if = "Option::is_none")]
4547 pub narration_hint: Option<String>,
4548 #[serde(default, skip_serializing_if = "Option::is_none")]
4557 pub thinking_duration_ms: Option<u64>,
4558 #[serde(default, skip_serializing_if = "Option::is_none")]
4562 pub context_usage: Option<ContextUsage>,
4563 #[serde(default, skip_serializing_if = "Option::is_none")]
4571 pub usage_report: Option<Box<UsageReport>>,
4572 #[serde(default, skip_serializing_if = "Option::is_none")]
4579 pub local_command_run: Option<LocalCommandRun>,
4580 #[serde(default, skip_serializing_if = "Option::is_none")]
4586 pub local_command_outcome: Option<LocalCommandOutcome>,
4587 #[serde(default, skip_serializing_if = "Option::is_none")]
4588 pub is_meta: Option<bool>,
4589 #[serde(default, skip_serializing_if = "Option::is_none")]
4590 pub is_virtual: Option<bool>,
4591 #[serde(default, skip_serializing_if = "Option::is_none")]
4592 pub is_api_error_message: Option<bool>,
4593 #[serde(skip_serializing_if = "Option::is_none")]
4594 pub api_error_status: Option<u16>,
4595 #[serde(skip_serializing_if = "Option::is_none")]
4610 pub api_error: Option<String>,
4611 #[serde(default, skip_serializing_if = "Option::is_none")]
4618 pub api_error_code: Option<String>,
4619 #[serde(default, skip_serializing_if = "Option::is_none")]
4622 pub api_error_params: Option<ApiErrorParams>,
4623 #[serde(skip_serializing_if = "Option::is_none")]
4624 pub error_details: Option<String>,
4625 #[serde(skip_serializing_if = "Option::is_none")]
4626 pub advisor_model: Option<String>,
4627 #[serde(skip_serializing_if = "Option::is_none")]
4628 pub attribution_agent: Option<String>,
4629 #[serde(skip_serializing_if = "Option::is_none")]
4630 pub attribution_skill: Option<String>,
4631 #[serde(skip_serializing_if = "Option::is_none")]
4632 pub attribution_plugin: Option<String>,
4633 #[serde(skip_serializing_if = "Option::is_none")]
4634 pub attribution_mcp_server: Option<String>,
4635 #[serde(skip_serializing_if = "Option::is_none")]
4636 pub attribution_mcp_tool: Option<String>,
4637}
4638
4639#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
4643pub struct ApiErrorParams {
4644 #[serde(default, skip_serializing_if = "Option::is_none")]
4646 pub effort: Option<String>,
4647 #[serde(default, skip_serializing_if = "Option::is_none")]
4650 pub provider: Option<ApiErrorProvider>,
4651 #[serde(default, skip_serializing_if = "Option::is_none")]
4654 pub remedy: Option<ApiErrorRemedy>,
4655}
4656
4657#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4659pub enum ApiErrorProvider {
4660 Bedrock,
4661 AnthropicAws,
4662 Mantle,
4663 AnthropicGoogleCloud,
4664 Vertex,
4665 Foundry,
4666 Gateway,
4667 Unknown(String),
4669}
4670
4671impl ApiErrorProvider {
4672 pub fn as_str(&self) -> &str {
4673 match self {
4674 Self::Bedrock => "bedrock",
4675 Self::AnthropicAws => "anthropicAws",
4676 Self::Mantle => "mantle",
4677 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
4678 Self::Vertex => "vertex",
4679 Self::Foundry => "foundry",
4680 Self::Gateway => "gateway",
4681 Self::Unknown(s) => s.as_str(),
4682 }
4683 }
4684}
4685
4686impl fmt::Display for ApiErrorProvider {
4687 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4688 f.write_str(self.as_str())
4689 }
4690}
4691
4692impl From<&str> for ApiErrorProvider {
4693 fn from(s: &str) -> Self {
4694 match s {
4695 "bedrock" => Self::Bedrock,
4696 "anthropicAws" => Self::AnthropicAws,
4697 "mantle" => Self::Mantle,
4698 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4699 "vertex" => Self::Vertex,
4700 "foundry" => Self::Foundry,
4701 "gateway" => Self::Gateway,
4702 other => Self::Unknown(other.to_string()),
4703 }
4704 }
4705}
4706
4707impl Serialize for ApiErrorProvider {
4708 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4709 serializer.serialize_str(self.as_str())
4710 }
4711}
4712
4713impl<'de> Deserialize<'de> for ApiErrorProvider {
4714 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4715 let s = String::deserialize(deserializer)?;
4716 Ok(Self::from(s.as_str()))
4717 }
4718}
4719
4720#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4722pub enum ApiErrorRemedy {
4723 RefreshCommand,
4726 RefreshCredentials,
4728 Adc,
4731 GatewayToken,
4734 HostManaged,
4736 ModelAccess,
4739 Unknown(String),
4741}
4742
4743impl ApiErrorRemedy {
4744 pub fn as_str(&self) -> &str {
4745 match self {
4746 Self::RefreshCommand => "refresh_command",
4747 Self::RefreshCredentials => "refresh_credentials",
4748 Self::Adc => "adc",
4749 Self::GatewayToken => "gateway_token",
4750 Self::HostManaged => "host_managed",
4751 Self::ModelAccess => "model_access",
4752 Self::Unknown(s) => s.as_str(),
4753 }
4754 }
4755}
4756
4757impl fmt::Display for ApiErrorRemedy {
4758 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4759 f.write_str(self.as_str())
4760 }
4761}
4762
4763impl From<&str> for ApiErrorRemedy {
4764 fn from(s: &str) -> Self {
4765 match s {
4766 "refresh_command" => Self::RefreshCommand,
4767 "refresh_credentials" => Self::RefreshCredentials,
4768 "adc" => Self::Adc,
4769 "gateway_token" => Self::GatewayToken,
4770 "host_managed" => Self::HostManaged,
4771 "model_access" => Self::ModelAccess,
4772 other => Self::Unknown(other.to_string()),
4773 }
4774 }
4775}
4776
4777impl Serialize for ApiErrorRemedy {
4778 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4779 serializer.serialize_str(self.as_str())
4780 }
4781}
4782
4783impl<'de> Deserialize<'de> for ApiErrorRemedy {
4784 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4785 let s = String::deserialize(deserializer)?;
4786 Ok(Self::from(s.as_str()))
4787 }
4788}
4789
4790#[derive(Debug, Clone, Serialize, Deserialize)]
4792pub struct AssistantMessageContent {
4793 pub id: String,
4794 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
4796 pub message_type: Option<String>,
4797 pub role: MessageRole,
4798 pub model: String,
4799 pub content: Vec<ContentBlock>,
4800 #[serde(skip_serializing_if = "Option::is_none")]
4801 pub stop_reason: Option<StopReason>,
4802 #[serde(skip_serializing_if = "Option::is_none")]
4803 pub stop_sequence: Option<String>,
4804 #[serde(skip_serializing_if = "Option::is_none")]
4805 pub usage: Option<AssistantUsage>,
4806 #[serde(skip_serializing_if = "Option::is_none")]
4808 pub stop_details: Option<Value>,
4809 #[serde(skip_serializing_if = "Option::is_none")]
4811 pub context_management: Option<Value>,
4812}
4813
4814#[derive(Debug, Clone, Serialize, Deserialize)]
4816pub struct AssistantUsage {
4817 #[serde(default)]
4819 pub input_tokens: u32,
4820
4821 #[serde(default)]
4823 pub output_tokens: u32,
4824
4825 #[serde(default)]
4827 pub cache_creation_input_tokens: u32,
4828
4829 #[serde(default)]
4831 pub cache_read_input_tokens: u32,
4832
4833 #[serde(skip_serializing_if = "Option::is_none")]
4835 pub service_tier: Option<String>,
4836
4837 #[serde(skip_serializing_if = "Option::is_none")]
4839 pub cache_creation: Option<CacheCreationDetails>,
4840
4841 #[serde(skip_serializing_if = "Option::is_none")]
4843 pub inference_geo: Option<String>,
4844
4845 #[serde(default, skip_serializing_if = "Option::is_none")]
4848 pub fallback_credit: Option<Value>,
4849}
4850
4851#[derive(Debug, Clone, Serialize, Deserialize)]
4853pub struct CacheCreationDetails {
4854 #[serde(default)]
4856 pub ephemeral_1h_input_tokens: u32,
4857
4858 #[serde(default)]
4860 pub ephemeral_5m_input_tokens: u32,
4861}
4862
4863#[cfg(test)]
4864mod tests {
4865 use crate::io::ClaudeOutput;
4866
4867 #[test]
4868 fn test_subagent_usage_rollup_accumulates_task_results() {
4869 use super::SubagentUsageRollup;
4870
4871 let mut rollup = SubagentUsageRollup::default();
4872
4873 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}}"#;
4874 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
4875 assert!(rollup.observe(&output));
4876 assert_eq!(rollup.subagent_tokens, 10201);
4877 assert_eq!(rollup.agent_count, 1);
4878 assert_eq!(rollup.tool_uses, 3);
4879 assert_eq!(rollup.duration_ms, 1853);
4880
4881 assert!(!rollup.observe(&output));
4883 assert_eq!(rollup.agent_count, 1);
4884 assert_eq!(rollup.subagent_tokens, 10201);
4885
4886 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}}"#;
4888 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
4889 assert!(rollup.observe(&output));
4890 assert_eq!(rollup.agent_count, 2);
4891 assert_eq!(rollup.subagent_tokens, 10701);
4892 }
4893
4894 #[test]
4895 fn test_subagent_usage_rollup_ignores_non_task_results() {
4896 use super::SubagentUsageRollup;
4897
4898 let mut rollup = SubagentUsageRollup::default();
4899
4900 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}}"#;
4903 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
4904 assert!(!rollup.observe(&output));
4905
4906 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4908 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
4909 assert!(!rollup.observe(&output));
4910
4911 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4913 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
4914 assert!(!rollup.observe(&output));
4915
4916 assert_eq!(rollup, SubagentUsageRollup::default());
4917 }
4918
4919 #[test]
4920 fn test_subagent_usage_rollup_over_captured_session() {
4921 use super::SubagentUsageRollup;
4922
4923 let mut rollup = SubagentUsageRollup::default();
4924 let fixture =
4925 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
4926 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
4927 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
4928 rollup.observe(&output);
4929 }
4930 }
4931 assert_eq!(rollup.agent_count, 1);
4932 assert_eq!(rollup.subagent_tokens, 10201);
4933 }
4934
4935 #[test]
4936 fn test_system_message_init() {
4937 let json = r#"{
4938 "type": "system",
4939 "subtype": "init",
4940 "session_id": "test-session-123",
4941 "cwd": "/home/user/project",
4942 "model": "claude-sonnet-4",
4943 "tools": ["Bash", "Read", "Write"],
4944 "mcp_servers": [],
4945 "slash_commands": ["compact", "cost", "review"],
4946 "agents": ["Bash", "Explore", "Plan"],
4947 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
4948 "skills": [],
4949 "claude_code_version": "2.1.15",
4950 "apiKeySource": "none",
4951 "output_style": "default",
4952 "permissionMode": "default"
4953 }"#;
4954
4955 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4956 if let ClaudeOutput::System(sys) = output {
4957 assert!(sys.is_init());
4958 assert!(!sys.is_status());
4959 assert!(!sys.is_compact_boundary());
4960
4961 let init = sys.as_init().expect("Should parse as init");
4962 assert_eq!(init.session_id, "test-session-123");
4963 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
4964 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
4965 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
4966 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
4967 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
4968 assert_eq!(init.plugins.len(), 1);
4969 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4970 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4971 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
4972 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
4973 assert_eq!(
4974 init.permission_mode,
4975 Some(super::InitPermissionMode::Default)
4976 );
4977 } else {
4978 panic!("Expected System message");
4979 }
4980 }
4981
4982 #[test]
4983 fn test_system_message_init_from_real_capture() {
4984 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
4985 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4986 if let ClaudeOutput::System(sys) = output {
4987 let init = sys.as_init().expect("Should parse real init capture");
4988 assert_eq!(init.slash_commands.len(), 8);
4989 assert!(init.slash_commands.contains(&"compact".to_string()));
4990 assert!(init.slash_commands.contains(&"review".to_string()));
4991 assert_eq!(init.agents.len(), 5);
4992 assert!(init.agents.contains(&"Bash".to_string()));
4993 assert!(init.agents.contains(&"Explore".to_string()));
4994 assert_eq!(init.plugins.len(), 1);
4995 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4996 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4997 } else {
4998 panic!("Expected System message");
4999 }
5000 }
5001
5002 #[test]
5003 fn test_system_message_status() {
5004 let json = r#"{
5005 "type": "system",
5006 "subtype": "status",
5007 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5008 "status": "compacting",
5009 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5010 }"#;
5011
5012 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5013 if let ClaudeOutput::System(sys) = output {
5014 assert!(sys.is_status());
5015 assert!(!sys.is_init());
5016
5017 let status = sys.as_status().expect("Should parse as status");
5018 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5019 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5020 assert_eq!(
5021 status.uuid,
5022 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5023 );
5024 } else {
5025 panic!("Expected System message");
5026 }
5027 }
5028
5029 #[test]
5030 fn test_system_message_status_null() {
5031 let json = r#"{
5032 "type": "system",
5033 "subtype": "status",
5034 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5035 "status": null,
5036 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5037 }"#;
5038
5039 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5040 if let ClaudeOutput::System(sys) = output {
5041 let status = sys.as_status().expect("Should parse as status");
5042 assert_eq!(status.status, None);
5043 } else {
5044 panic!("Expected System message");
5045 }
5046 }
5047
5048 #[test]
5049 fn test_system_message_task_started() {
5050 let json = r#"{
5051 "type": "system",
5052 "subtype": "task_started",
5053 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5054 "task_id": "b6daf3f",
5055 "task_type": "local_bash",
5056 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5057 "description": "Wait for CI on PR #12",
5058 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5059 }"#;
5060
5061 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5062 if let ClaudeOutput::System(sys) = output {
5063 assert!(sys.is_task_started());
5064 assert!(!sys.is_task_progress());
5065 assert!(!sys.is_task_notification());
5066
5067 let task = sys.as_task_started().expect("Should parse as task_started");
5068 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5069 assert_eq!(task.task_id, "b6daf3f");
5070 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5071 assert_eq!(
5072 task.tool_use_id.as_deref(),
5073 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5074 );
5075 assert_eq!(task.description, "Wait for CI on PR #12");
5076 } else {
5077 panic!("Expected System message");
5078 }
5079 }
5080
5081 #[test]
5082 fn test_system_message_task_started_agent() {
5083 let json = r#"{
5084 "type": "system",
5085 "subtype": "task_started",
5086 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5087 "task_id": "a4a7e0906e5fc64cc",
5088 "task_type": "local_agent",
5089 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5090 "description": "Explore Scene/ArrayScene duplication",
5091 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5092 }"#;
5093
5094 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5095 if let ClaudeOutput::System(sys) = output {
5096 let task = sys.as_task_started().expect("Should parse as task_started");
5097 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5098 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5099 } else {
5100 panic!("Expected System message");
5101 }
5102 }
5103
5104 #[test]
5105 fn test_system_message_task_progress() {
5106 let json = r#"{
5107 "type": "system",
5108 "subtype": "task_progress",
5109 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5110 "task_id": "a4a7e0906e5fc64cc",
5111 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5112 "description": "Reading src/jplephem/chebyshev.rs",
5113 "last_tool_name": "Read",
5114 "usage": {
5115 "duration_ms": 13996,
5116 "tool_uses": 9,
5117 "total_tokens": 38779
5118 },
5119 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5120 }"#;
5121
5122 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5123 if let ClaudeOutput::System(sys) = output {
5124 assert!(sys.is_task_progress());
5125 assert!(!sys.is_task_started());
5126
5127 let progress = sys
5128 .as_task_progress()
5129 .expect("Should parse as task_progress");
5130 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5131 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5132 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5133 assert_eq!(progress.usage.duration_ms, 13996);
5134 assert_eq!(progress.usage.tool_uses, 9);
5135 assert_eq!(progress.usage.total_tokens, 38779);
5136 } else {
5137 panic!("Expected System message");
5138 }
5139 }
5140
5141 #[test]
5142 fn test_system_message_task_notification_completed() {
5143 let json = r#"{
5144 "type": "system",
5145 "subtype": "task_notification",
5146 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5147 "task_id": "a0ba761e9dc9c316f",
5148 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
5149 "status": "completed",
5150 "summary": "Agent \"Write Hipparcos data source doc\" completed",
5151 "output_file": "",
5152 "usage": {
5153 "duration_ms": 172300,
5154 "tool_uses": 11,
5155 "total_tokens": 42005
5156 },
5157 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
5158 }"#;
5159
5160 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5161 if let ClaudeOutput::System(sys) = output {
5162 assert!(sys.is_task_notification());
5163
5164 let notif = sys
5165 .as_task_notification()
5166 .expect("Should parse as task_notification");
5167 assert_eq!(notif.status, super::TaskStatus::Completed);
5168 assert_eq!(
5169 notif.summary,
5170 "Agent \"Write Hipparcos data source doc\" completed"
5171 );
5172 assert_eq!(notif.output_file, Some("".to_string()));
5173 assert_eq!(
5174 notif.tool_use_id,
5175 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
5176 );
5177 let usage = notif.usage.expect("Should have usage");
5178 assert_eq!(usage.duration_ms, 172300);
5179 assert_eq!(usage.tool_uses, 11);
5180 assert_eq!(usage.total_tokens, 42005);
5181 } else {
5182 panic!("Expected System message");
5183 }
5184 }
5185
5186 #[test]
5187 fn test_system_message_task_notification_failed_no_usage() {
5188 let json = r#"{
5189 "type": "system",
5190 "subtype": "task_notification",
5191 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
5192 "task_id": "b98f6a3",
5193 "status": "failed",
5194 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
5195 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
5196 }"#;
5197
5198 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5199 if let ClaudeOutput::System(sys) = output {
5200 let notif = sys
5201 .as_task_notification()
5202 .expect("Should parse as task_notification");
5203 assert_eq!(notif.status, super::TaskStatus::Failed);
5204 assert!(notif.tool_use_id.is_none());
5205 assert!(notif.usage.is_none());
5206 assert_eq!(
5207 notif.output_file,
5208 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
5209 );
5210 } else {
5211 panic!("Expected System message");
5212 }
5213 }
5214
5215 #[test]
5221 fn test_task_messages_roundtrip_through_value() {
5222 let cases = [
5223 r#"{"type":"system","subtype":"task_started","session_id":"s1",
5224 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
5225 "description":"Sleep 3s","uuid":"u1"}"#,
5226 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
5227 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
5228 "last_tool_name":"Bash",
5229 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5230 "uuid":"u2"}"#,
5231 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
5232 "task_id":"t1","tool_use_id":"tu1","status":"completed",
5233 "summary":"done","output_file":"",
5234 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5235 "uuid":"u3"}"#,
5236 ];
5237
5238 for json in cases {
5239 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5240 let value = serde_json::to_value(&output).unwrap();
5241 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
5242
5243 let ClaudeOutput::System(sys) = reparsed else {
5244 panic!("Expected System variant after round-trip");
5245 };
5246
5247 match sys.subtype {
5248 super::SystemSubtype::TaskStarted => {
5249 assert!(
5250 sys.as_task_started().is_some(),
5251 "as_task_started failed after round-trip"
5252 );
5253 }
5254 super::SystemSubtype::TaskProgress => {
5255 assert!(
5256 sys.as_task_progress().is_some(),
5257 "as_task_progress failed after round-trip"
5258 );
5259 }
5260 super::SystemSubtype::TaskNotification => {
5261 assert!(
5262 sys.as_task_notification().is_some(),
5263 "as_task_notification failed after round-trip"
5264 );
5265 }
5266 other => panic!("unexpected subtype after round-trip: {other:?}"),
5267 }
5268 }
5269 }
5270
5271 #[test]
5272 fn test_system_message_compact_boundary() {
5273 let json = r#"{
5274 "type": "system",
5275 "subtype": "compact_boundary",
5276 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5277 "compact_metadata": {
5278 "pre_tokens": 155285,
5279 "trigger": "auto"
5280 },
5281 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
5282 }"#;
5283
5284 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5285 if let ClaudeOutput::System(sys) = output {
5286 assert!(sys.is_compact_boundary());
5287 assert!(!sys.is_init());
5288 assert!(!sys.is_status());
5289
5290 let compact = sys
5291 .as_compact_boundary()
5292 .expect("Should parse as compact_boundary");
5293 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5294 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
5295 assert_eq!(
5296 compact.compact_metadata.trigger,
5297 super::CompactionTrigger::Auto
5298 );
5299 assert!(compact.summary.is_none());
5301 assert!(compact.leaf_message_count.is_none());
5302 assert!(compact.duration_ms.is_none());
5303 } else {
5304 panic!("Expected System message");
5305 }
5306 }
5307
5308 #[test]
5309 fn test_compact_boundary_with_summary_stats() {
5310 let json = r#"{
5312 "type": "system",
5313 "subtype": "compact_boundary",
5314 "session_id": "s1",
5315 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
5316 "summary": "Summarized the earlier exploration.",
5317 "leaf_message_count": 42,
5318 "duration_ms": 1234,
5319 "uuid": "u1"
5320 }"#;
5321 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5322 let ClaudeOutput::System(sys) = output else {
5323 panic!("Expected System message");
5324 };
5325 let compact = sys.as_compact_boundary().expect("compact_boundary");
5326 assert_eq!(
5327 compact.summary.as_deref(),
5328 Some("Summarized the earlier exploration.")
5329 );
5330 assert_eq!(compact.leaf_message_count, Some(42));
5331 assert_eq!(compact.duration_ms, Some(1234));
5332
5333 let json_alt = r#"{
5336 "type": "system",
5337 "subtype": "compact_boundary",
5338 "session_id": "s2",
5339 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
5340 "content": "alt-key summary",
5341 "message_count": 7
5342 }"#;
5343 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
5344 let ClaudeOutput::System(sys) = output else {
5345 panic!("Expected System message");
5346 };
5347 let compact = sys.as_compact_boundary().expect("compact_boundary");
5348 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
5349 assert_eq!(compact.leaf_message_count, Some(7));
5350 }
5351
5352 #[test]
5353 fn test_init_message_with_new_fields() {
5354 let json = r#"{
5355 "type": "system",
5356 "subtype": "init",
5357 "session_id": "test-session",
5358 "cwd": "/home/user",
5359 "model": "claude-opus-4-7",
5360 "tools": ["Bash"],
5361 "mcp_servers": [],
5362 "permissionMode": "default",
5363 "apiKeySource": "none",
5364 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
5365 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
5366 "fast_mode_state": "off",
5367 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
5368 "claude_code_version": "2.1.117"
5369 }"#;
5370
5371 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5372 if let ClaudeOutput::System(sys) = output {
5373 let init = sys.as_init().expect("Should parse as init");
5374 assert_eq!(
5375 init.uuid.as_deref(),
5376 Some("44841a0d-182d-493a-86b5-79800d3d9665")
5377 );
5378 assert!(init.memory_paths.is_some());
5379 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
5380 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
5381 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
5382 } else {
5383 panic!("Expected System message");
5384 }
5385 }
5386
5387 #[test]
5388 fn test_assistant_message_with_new_fields() {
5389 let json = r#"{
5390 "type": "assistant",
5391 "message": {
5392 "id": "msg_1",
5393 "type": "message",
5394 "role": "assistant",
5395 "model": "claude-opus-4-7",
5396 "content": [{"type": "text", "text": "Hello"}],
5397 "stop_reason": "end_turn",
5398 "stop_details": null,
5399 "context_management": null,
5400 "usage": {
5401 "input_tokens": 100,
5402 "output_tokens": 10,
5403 "cache_creation_input_tokens": 50,
5404 "cache_read_input_tokens": 0,
5405 "service_tier": "standard",
5406 "inference_geo": "not_available"
5407 }
5408 },
5409 "session_id": "abc",
5410 "uuid": "msg-uuid-123"
5411 }"#;
5412
5413 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5414 if let ClaudeOutput::Assistant(asst) = output {
5415 assert_eq!(asst.message.stop_details, None);
5416 assert_eq!(asst.message.context_management, None);
5417 let usage = asst.message.usage.unwrap();
5418 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
5419 } else {
5420 panic!("Expected Assistant message");
5421 }
5422 }
5423
5424 #[test]
5425 fn test_user_message_with_new_fields() {
5426 let json = r#"{
5427 "type": "user",
5428 "message": {
5429 "role": "user",
5430 "content": [{"type": "text", "text": "Hello"}]
5431 },
5432 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5433 "parent_tool_use_id": "toolu_123",
5434 "uuid": "user-msg-456"
5435 }"#;
5436
5437 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5438 if let ClaudeOutput::User(user) = output {
5439 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
5440 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
5441 } else {
5442 panic!("Expected User message");
5443 }
5444 }
5445
5446 #[test]
5452 fn test_user_message_preserves_tool_use_result_and_timestamp() {
5453 let json = r#"{
5454 "type":"user",
5455 "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"}]},
5456 "parent_tool_use_id":null,
5457 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5458 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
5459 "timestamp":"2026-05-12T23:12:04.121Z",
5460 "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"}}
5461 }"#;
5462
5463 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5464 let user = match output {
5465 ClaudeOutput::User(u) => u,
5466 other => panic!("Expected User message, got {:?}", other.message_type()),
5467 };
5468
5469 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
5470 let raw = user
5471 .tool_use_result
5472 .as_ref()
5473 .expect("tool_use_result must be captured");
5474 assert_eq!(raw["answers"]["Color"], "Blue");
5475 assert_eq!(raw["questions"][0]["header"], "Color");
5476
5477 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
5481 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
5482 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
5483 assert_eq!(
5484 reser["tool_use_result"]["questions"][0]["question"],
5485 "Which color do you prefer?"
5486 );
5487
5488 let typed: crate::AskUserQuestionInput = user
5491 .tool_use_result_as::<crate::AskUserQuestionInput>()
5492 .expect("tool_use_result present")
5493 .expect("AskUserQuestionInput parses");
5494 assert_eq!(typed.questions.len(), 1);
5495 assert_eq!(typed.questions[0].header, "Color");
5496 let answers = typed.answers.expect("answers populated");
5497 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
5498 }
5499
5500 #[test]
5503 fn test_user_message_without_tool_use_result_omits_field() {
5504 let json = r#"{
5505 "type":"user",
5506 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
5507 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
5508 }"#;
5509
5510 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5511 let user = match output {
5512 ClaudeOutput::User(u) => u,
5513 _ => panic!("Expected User message"),
5514 };
5515 assert!(user.tool_use_result.is_none());
5516 assert!(user.timestamp.is_none());
5517
5518 let reser = serde_json::to_value(&user).unwrap();
5519 assert!(reser.get("tool_use_result").is_none());
5520 assert!(reser.get("timestamp").is_none());
5521 }
5522
5523 #[test]
5527 fn test_subagent_result_exposes_token_accounting() {
5528 let json = r#"{
5529 "type":"user",
5530 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
5531 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
5532 "tool_use_result":{
5533 "status":"completed",
5534 "prompt":"Count the .rs files.",
5535 "agentId":"ac4f0276e9d4b6232",
5536 "agentType":"Explore",
5537 "content":[{"type":"text","text":"21"}],
5538 "resolvedModel":"claude-haiku-4-5-20251001",
5539 "totalDurationMs":6869,
5540 "totalTokens":7834,
5541 "totalToolUseCount":1,
5542 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
5543 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
5544 }
5545 }"#;
5546
5547 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5548 let user = match output {
5549 ClaudeOutput::User(u) => u,
5550 _ => panic!("Expected User message"),
5551 };
5552
5553 let result = user.subagent_result().expect("subagent result parses");
5554 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
5555 assert_eq!(
5556 result.resolved_model.as_deref(),
5557 Some("claude-haiku-4-5-20251001")
5558 );
5559 assert_eq!(result.total_tokens, Some(7834));
5560 assert_eq!(result.total_duration_ms, Some(6869));
5561 assert_eq!(result.total_tool_use_count, Some(1));
5562
5563 let usage = result.usage.expect("nested usage present");
5564 assert_eq!(usage.input_tokens, 6);
5565 assert_eq!(usage.cache_read_input_tokens, 7699);
5566
5567 let stats = result.tool_stats.expect("toolStats present");
5568 assert_eq!(stats.bash_count, 1);
5569 }
5570
5571 #[test]
5575 fn test_subagent_result_absent_for_non_task_result() {
5576 let json = r#"{
5577 "type":"user",
5578 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
5579 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5580 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
5581 }"#;
5582
5583 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5584 let user = match output {
5585 ClaudeOutput::User(u) => u,
5586 _ => panic!("Expected User message"),
5587 };
5588
5589 let result = user.subagent_result().expect("lenient parse");
5590 assert_eq!(result.total_tokens, None);
5591 assert_eq!(result.agent_type, None);
5592 }
5593
5594 #[test]
5595 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
5596 use serde_json::Value;
5597
5598 let raw: Value = serde_json::from_str(
5599 r#"{
5600 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
5601 "fast_mode_state":"off",
5602 "fast_mode_disabled_reason":"not_first_party",
5603 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
5604 }"#,
5605 )
5606 .unwrap();
5607 crate::io::assert_fully_wrapped(&raw);
5608
5609 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5610 let ClaudeOutput::System(sys) = output else {
5611 panic!("expected System");
5612 };
5613 let init = sys.as_init().expect("parses as init");
5614 assert_eq!(
5615 init.fast_mode_disabled_reason,
5616 Some(crate::FastModeDisabledReason::NotFirstParty)
5617 );
5618 let errs = init.mcp_server_errors.unwrap();
5619 assert_eq!(errs.len(), 1);
5620 assert_eq!(errs[0].name, "broken");
5621 assert_eq!(errs[0].error_type, "invalid_config");
5622 }
5623
5624 #[test]
5625 fn test_code_change_published_fully_wrapped() {
5626 use super::{KnownSystemEvent, SystemSubtype};
5627 use serde_json::Value;
5628
5629 let raw: Value = serde_json::from_str(
5630 r#"{
5631 "type":"system","subtype":"code_change_published",
5632 "provider":"github","url":"https://github.com/owner/repo/pull/42",
5633 "repo":"owner/repo","identifier":"42",
5634 "uuid":"u1","session_id":"s1"
5635 }"#,
5636 )
5637 .unwrap();
5638 crate::io::assert_fully_wrapped(&raw);
5639
5640 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5641 let ClaudeOutput::System(sys) = output else {
5642 panic!("expected System");
5643 };
5644 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
5645 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
5646 panic!("expected CodeChangePublished event");
5647 };
5648 assert_eq!(msg.provider, "github");
5649 assert_eq!(msg.repo, "owner/repo");
5650 assert_eq!(msg.identifier, "42");
5651
5652 assert!(sys.is_code_change_published());
5653 assert!(!sys.is_vcs_state_changed());
5654 let direct = sys.as_code_change_published().expect("direct accessor");
5655 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
5656 assert!(sys.as_vcs_state_changed().is_none());
5657 }
5658
5659 #[test]
5660 fn test_feedback_draft_queued_fully_wrapped() {
5661 use super::{KnownSystemEvent, SystemSubtype};
5662 use serde_json::Value;
5663
5664 let raw: Value = serde_json::from_str(
5665 r#"{
5666 "type":"system","subtype":"feedback_draft_queued",
5667 "draft_id":"draft-1","draft_type":"bug_report",
5668 "title":"Tool output was truncated",
5669 "details_preview":"The last command omitted its final lines",
5670 "uuid":"u1","session_id":"s1","future_field":"preserved"
5671 }"#,
5672 )
5673 .unwrap();
5674 crate::io::assert_fully_wrapped(&raw);
5675
5676 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5677 let ClaudeOutput::System(sys) = output else {
5678 panic!("expected System");
5679 };
5680 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
5681 assert!(sys.is_feedback_draft_queued());
5682 assert!(!sys.is_vcs_state_changed());
5683
5684 let direct = sys
5685 .as_feedback_draft_queued()
5686 .expect("direct typed accessor");
5687 assert_eq!(direct.draft_id, "draft-1");
5688 assert_eq!(direct.draft_type, "bug_report");
5689 assert_eq!(direct.extra["future_field"], "preserved");
5690
5691 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
5692 panic!("expected FeedbackDraftQueued event");
5693 };
5694 assert_eq!(known.title, "Tool output was truncated");
5695 assert_eq!(
5696 sys.typed_value().expect("typed value")["future_field"],
5697 "preserved"
5698 );
5699 }
5700
5701 #[test]
5702 fn test_cloud_session_delta_fully_wrapped() {
5703 use super::{KnownSystemEvent, SystemSubtype};
5704 use serde_json::Value;
5705
5706 let raw: Value = serde_json::from_str(
5707 r#"{
5708 "type":"system","subtype":"cloud_session_delta",
5709 "seq":3,"changed":["serving","connection"],
5710 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5711 "serving":{"state":"on"},"connection":{"state":"live"}},
5712 "uuid":"u1","session_id":"s1","future_field":"preserved"
5713 }"#,
5714 )
5715 .unwrap();
5716 crate::io::assert_fully_wrapped(&raw);
5717
5718 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5719 let ClaudeOutput::System(sys) = output else {
5720 panic!("expected System");
5721 };
5722 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5723 assert!(sys.is_cloud_session_delta());
5724 assert!(!sys.is_feedback_draft_queued());
5725
5726 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5727 assert_eq!(direct.seq, 3);
5728 assert_eq!(direct.changed, vec!["serving", "connection"]);
5729 assert_eq!(direct.cloud_session["id"], "session_abc");
5730 assert_eq!(direct.extra["future_field"], "preserved");
5731
5732 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5733 panic!("expected CloudSessionDelta event");
5734 };
5735 assert_eq!(known.session_id, "s1");
5736 assert_eq!(
5737 sys.typed_value().expect("typed value")["future_field"],
5738 "preserved"
5739 );
5740 }
5741
5742 #[test]
5743 fn test_vcs_state_changed_fully_wrapped() {
5744 use super::{KnownSystemEvent, VcsMutationKind};
5745 use serde_json::Value;
5746
5747 for kind in ["commit", "push", "merge", "rebase"] {
5748 let raw: Value = serde_json::from_str(&format!(
5749 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5750 kind
5751 ))
5752 .unwrap();
5753 crate::io::assert_fully_wrapped(&raw);
5754
5755 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5756 let ClaudeOutput::System(sys) = output else {
5757 panic!("expected System");
5758 };
5759 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5760 panic!("expected VcsStateChanged event");
5761 };
5762 assert_eq!(msg.kind.as_str(), kind);
5763 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
5764 }
5765
5766 let raw: Value = serde_json::from_str(
5768 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
5769 )
5770 .unwrap();
5771 crate::io::assert_fully_wrapped(&raw);
5772 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5773 let ClaudeOutput::System(sys) = output else {
5774 panic!("expected System");
5775 };
5776 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5777 panic!("expected VcsStateChanged event");
5778 };
5779 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
5780
5781 assert!(sys.is_vcs_state_changed());
5782 let direct = sys.as_vcs_state_changed().expect("direct accessor");
5783 assert_eq!(direct.cwd, "/repo");
5784 assert!(sys.as_code_change_published().is_none());
5785 }
5786
5787 #[test]
5788 fn test_assistant_aborted_and_resume_flags_roundtrip() {
5789 let json = r#"{
5790 "type":"assistant",
5791 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
5792 "session_id":"s1",
5793 "aborted":true,
5794 "resumed_from_incomplete_thinking":true
5795 }"#;
5796 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5797 let ClaudeOutput::Assistant(msg) = &output else {
5798 panic!("expected Assistant");
5799 };
5800 assert_eq!(msg.aborted, Some(true));
5801 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
5802 let reserialized = serde_json::to_string(&output).unwrap();
5803 assert!(reserialized.contains("\"aborted\":true"));
5804 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
5805
5806 let json = r#"{
5808 "type":"assistant",
5809 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
5810 "session_id":"s2"
5811 }"#;
5812 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5813 let reserialized = serde_json::to_string(&output).unwrap();
5814 assert!(!reserialized.contains("aborted"));
5815 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
5816 }
5817
5818 #[test]
5819 fn test_user_tool_result_meta_roundtrip() {
5820 let json = r#"{
5821 "type":"user",
5822 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
5823 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5824 "tool_result_meta":[
5825 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
5826 {"id":"toolu_2","non_execution_kind":"permission-rule"}
5827 ]
5828 }"#;
5829 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5830 let ClaudeOutput::User(user) = &output else {
5831 panic!("expected User");
5832 };
5833 let meta = user.tool_result_meta.as_ref().unwrap();
5834 assert_eq!(meta.len(), 2);
5835 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
5836 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
5837 assert_eq!(meta[1].user_feedback, None);
5838
5839 let reserialized = serde_json::to_string(&output).unwrap();
5840 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
5841 assert!(!reserialized.contains("\"user_feedback\":null"));
5842 }
5843
5844 #[test]
5848 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
5849 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
5850 let with_scope = serde_json::json!({
5851 "trigger": "refusal",
5852 "direction": "retry",
5853 "scope": "local",
5854 "original_model": "claude-fable-5",
5855 "fallback_model": "claude-opus-5",
5856 "request_id": null,
5857 "content": "Refused; retried on fallback model.",
5858 "uuid": "u1",
5859 "session_id": "s1"
5860 });
5861 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
5862 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
5863 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
5864
5865 let mut without = with_scope.clone();
5868 without.as_object_mut().unwrap().remove("scope");
5869 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
5870 assert_eq!(msg.scope, None);
5871 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
5872
5873 assert_eq!(
5875 RefusalFallbackScope::from("workspace").as_str(),
5876 "workspace"
5877 );
5878 }
5879}