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}
1117
1118impl UserMessage {
1119 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1127 &self,
1128 ) -> Option<Result<T, serde_json::Error>> {
1129 self.tool_use_result
1130 .as_ref()
1131 .map(|v| serde_json::from_value(v.clone()))
1132 }
1133
1134 pub fn subagent_result(&self) -> Option<SubagentResult> {
1145 self.tool_use_result
1146 .as_ref()
1147 .and_then(|v| serde_json::from_value(v.clone()).ok())
1148 }
1149}
1150
1151#[derive(Debug, Clone, Serialize, Deserialize)]
1162pub struct SubagentResult {
1163 #[serde(skip_serializing_if = "Option::is_none")]
1165 pub status: Option<String>,
1166 #[serde(skip_serializing_if = "Option::is_none")]
1168 pub prompt: Option<String>,
1169 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1171 pub agent_id: Option<String>,
1172 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1174 pub agent_type: Option<String>,
1175 #[serde(
1177 default,
1178 deserialize_with = "deserialize_content_blocks",
1179 skip_serializing_if = "Vec::is_empty"
1180 )]
1181 pub content: Vec<ContentBlock>,
1182 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1184 pub resolved_model: Option<String>,
1185 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1187 pub total_duration_ms: Option<u64>,
1188 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1190 pub total_tokens: Option<u64>,
1191 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1193 pub total_tool_use_count: Option<u64>,
1194 #[serde(skip_serializing_if = "Option::is_none")]
1196 pub usage: Option<super::result::UsageInfo>,
1197 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1199 pub tool_stats: Option<SubagentToolStats>,
1200}
1201
1202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1207#[serde(rename_all = "camelCase")]
1208pub struct SubagentToolStats {
1209 #[serde(default)]
1210 pub read_count: u64,
1211 #[serde(default)]
1212 pub search_count: u64,
1213 #[serde(default)]
1214 pub bash_count: u64,
1215 #[serde(default)]
1216 pub edit_file_count: u64,
1217 #[serde(default)]
1218 pub lines_added: u64,
1219 #[serde(default)]
1220 pub lines_removed: u64,
1221 #[serde(default)]
1222 pub other_tool_count: u64,
1223 #[serde(flatten)]
1224 pub extra: serde_json::Map<String, Value>,
1225}
1226
1227#[derive(Debug, Clone, Default, PartialEq, Eq)]
1254pub struct SubagentUsageRollup {
1255 pub subagent_tokens: u64,
1258 pub agent_count: u32,
1260 pub tool_uses: u64,
1262 pub duration_ms: u64,
1264 seen_agent_ids: std::collections::BTreeSet<String>,
1265}
1266
1267impl SubagentUsageRollup {
1268 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1274 match output {
1275 ClaudeOutput::User(user) => self.observe_user(user),
1276 _ => false,
1277 }
1278 }
1279
1280 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1287 let Some(result) = user.subagent_result() else {
1288 return false;
1289 };
1290 if result.total_tokens.is_none() && result.agent_id.is_none() {
1291 return false;
1292 }
1293 if let Some(agent_id) = &result.agent_id {
1294 if !self.seen_agent_ids.insert(agent_id.clone()) {
1295 return false;
1296 }
1297 }
1298 self.agent_count += 1;
1299 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1300 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1301 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1302 true
1303 }
1304}
1305
1306#[derive(Debug, Clone, Serialize, Deserialize)]
1308pub struct MessageContent {
1309 pub role: MessageRole,
1310 #[serde(deserialize_with = "deserialize_content_blocks")]
1311 pub content: Vec<ContentBlock>,
1312}
1313
1314#[derive(Debug, Clone, Serialize, Deserialize)]
1316pub struct SystemMessage {
1317 pub subtype: SystemSubtype,
1318 #[serde(flatten)]
1319 pub data: Value, }
1321
1322impl SystemMessage {
1323 pub fn is_init(&self) -> bool {
1325 self.subtype == SystemSubtype::Init
1326 }
1327
1328 pub fn is_status(&self) -> bool {
1330 self.subtype == SystemSubtype::Status
1331 }
1332
1333 pub fn is_compact_boundary(&self) -> bool {
1335 self.subtype == SystemSubtype::CompactBoundary
1336 }
1337
1338 pub fn as_init(&self) -> Option<InitMessage> {
1340 if self.subtype != SystemSubtype::Init {
1341 return None;
1342 }
1343 serde_json::from_value(self.data.clone()).ok()
1344 }
1345
1346 pub fn as_status(&self) -> Option<StatusMessage> {
1348 if self.subtype != SystemSubtype::Status {
1349 return None;
1350 }
1351 serde_json::from_value(self.data.clone()).ok()
1352 }
1353
1354 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1356 if self.subtype != SystemSubtype::CompactBoundary {
1357 return None;
1358 }
1359 serde_json::from_value(self.data.clone()).ok()
1360 }
1361
1362 pub fn is_task_started(&self) -> bool {
1364 self.subtype == SystemSubtype::TaskStarted
1365 }
1366
1367 pub fn is_task_progress(&self) -> bool {
1369 self.subtype == SystemSubtype::TaskProgress
1370 }
1371
1372 pub fn is_task_notification(&self) -> bool {
1374 self.subtype == SystemSubtype::TaskNotification
1375 }
1376
1377 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1379 if self.subtype != SystemSubtype::TaskStarted {
1380 return None;
1381 }
1382 serde_json::from_value(self.data.clone()).ok()
1383 }
1384
1385 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1387 if self.subtype != SystemSubtype::TaskProgress {
1388 return None;
1389 }
1390 serde_json::from_value(self.data.clone()).ok()
1391 }
1392
1393 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1395 if self.subtype != SystemSubtype::TaskNotification {
1396 return None;
1397 }
1398 serde_json::from_value(self.data.clone()).ok()
1399 }
1400
1401 pub fn is_task_updated(&self) -> bool {
1403 self.subtype == SystemSubtype::TaskUpdated
1404 }
1405
1406 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1408 if self.subtype != SystemSubtype::TaskUpdated {
1409 return None;
1410 }
1411 serde_json::from_value(self.data.clone()).ok()
1412 }
1413
1414 pub fn is_thinking_tokens(&self) -> bool {
1416 self.subtype == SystemSubtype::ThinkingTokens
1417 }
1418
1419 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1421 if self.subtype != SystemSubtype::ThinkingTokens {
1422 return None;
1423 }
1424 serde_json::from_value(self.data.clone()).ok()
1425 }
1426
1427 pub fn is_code_change_published(&self) -> bool {
1429 self.subtype == SystemSubtype::CodeChangePublished
1430 }
1431
1432 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1434 if self.subtype != SystemSubtype::CodeChangePublished {
1435 return None;
1436 }
1437 serde_json::from_value(self.data.clone()).ok()
1438 }
1439
1440 pub fn is_vcs_state_changed(&self) -> bool {
1442 self.subtype == SystemSubtype::VcsStateChanged
1443 }
1444
1445 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1447 if self.subtype != SystemSubtype::VcsStateChanged {
1448 return None;
1449 }
1450 serde_json::from_value(self.data.clone()).ok()
1451 }
1452
1453 pub fn is_feedback_draft_queued(&self) -> bool {
1455 self.subtype == SystemSubtype::FeedbackDraftQueued
1456 }
1457
1458 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1460 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1461 return None;
1462 }
1463 serde_json::from_value(self.data.clone()).ok()
1464 }
1465
1466 pub fn is_cloud_session_delta(&self) -> bool {
1468 self.subtype == SystemSubtype::CloudSessionDelta
1469 }
1470
1471 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1473 if self.subtype != SystemSubtype::CloudSessionDelta {
1474 return None;
1475 }
1476 serde_json::from_value(self.data.clone()).ok()
1477 }
1478
1479 pub fn is_dev_intent(&self) -> bool {
1481 self.subtype == SystemSubtype::DevIntent
1482 }
1483
1484 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1486 if self.subtype != SystemSubtype::DevIntent {
1487 return None;
1488 }
1489 serde_json::from_value(self.data.clone()).ok()
1490 }
1491
1492 pub fn is_turn_handoff_available(&self) -> bool {
1494 self.subtype == SystemSubtype::TurnHandoffAvailable
1495 }
1496
1497 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1499 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1500 return None;
1501 }
1502 serde_json::from_value(self.data.clone()).ok()
1503 }
1504
1505 pub fn is_turn_preempted(&self) -> bool {
1507 self.subtype == SystemSubtype::TurnPreempted
1508 }
1509
1510 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1512 if self.subtype != SystemSubtype::TurnPreempted {
1513 return None;
1514 }
1515 serde_json::from_value(self.data.clone()).ok()
1516 }
1517
1518 pub fn is_peer_message_hold(&self) -> bool {
1520 self.subtype == SystemSubtype::PeerMessageHold
1521 }
1522
1523 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1525 if self.subtype != SystemSubtype::PeerMessageHold {
1526 return None;
1527 }
1528 serde_json::from_value(self.data.clone()).ok()
1529 }
1530
1531 pub fn is_per_turn_effort_changed(&self) -> bool {
1533 self.subtype == SystemSubtype::PerTurnEffortChanged
1534 }
1535
1536 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1538 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1539 return None;
1540 }
1541 serde_json::from_value(self.data.clone()).ok()
1542 }
1543
1544 pub fn is_session_title_changed(&self) -> bool {
1546 self.subtype == SystemSubtype::SessionTitleChanged
1547 }
1548
1549 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1551 if self.subtype != SystemSubtype::SessionTitleChanged {
1552 return None;
1553 }
1554 serde_json::from_value(self.data.clone()).ok()
1555 }
1556
1557 pub fn is_ui_focus(&self) -> bool {
1559 self.subtype == SystemSubtype::UiFocus
1560 }
1561
1562 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1564 if self.subtype != SystemSubtype::UiFocus {
1565 return None;
1566 }
1567 serde_json::from_value(self.data.clone()).ok()
1568 }
1569
1570 pub fn is_ui_invalidate(&self) -> bool {
1572 self.subtype == SystemSubtype::UiInvalidate
1573 }
1574
1575 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1577 if self.subtype != SystemSubtype::UiInvalidate {
1578 return None;
1579 }
1580 serde_json::from_value(self.data.clone()).ok()
1581 }
1582
1583 pub fn is_ui_log(&self) -> bool {
1585 self.subtype == SystemSubtype::UiLog
1586 }
1587
1588 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1590 if self.subtype != SystemSubtype::UiLog {
1591 return None;
1592 }
1593 serde_json::from_value(self.data.clone()).ok()
1594 }
1595
1596 pub fn is_ui_panes(&self) -> bool {
1598 self.subtype == SystemSubtype::UiPanes
1599 }
1600
1601 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1603 if self.subtype != SystemSubtype::UiPanes {
1604 return None;
1605 }
1606 serde_json::from_value(self.data.clone()).ok()
1607 }
1608
1609 pub fn is_ui_scroll(&self) -> bool {
1611 self.subtype == SystemSubtype::UiScroll
1612 }
1613
1614 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1616 if self.subtype != SystemSubtype::UiScroll {
1617 return None;
1618 }
1619 serde_json::from_value(self.data.clone()).ok()
1620 }
1621
1622 pub fn is_ui_status(&self) -> bool {
1624 self.subtype == SystemSubtype::UiStatus
1625 }
1626
1627 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1629 if self.subtype != SystemSubtype::UiStatus {
1630 return None;
1631 }
1632 serde_json::from_value(self.data.clone()).ok()
1633 }
1634
1635 pub fn is_ui_toast(&self) -> bool {
1637 self.subtype == SystemSubtype::UiToast
1638 }
1639
1640 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
1642 if self.subtype != SystemSubtype::UiToast {
1643 return None;
1644 }
1645 serde_json::from_value(self.data.clone()).ok()
1646 }
1647
1648 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1650 macro_rules! parse {
1651 ($variant:ident, $ty:ty) => {
1652 serde_json::from_value::<$ty>(self.data.clone())
1653 .ok()
1654 .map(KnownSystemEvent::$variant)
1655 };
1656 }
1657
1658 match self.subtype {
1659 SystemSubtype::Init => parse!(Init, InitMessage),
1660 SystemSubtype::Status => parse!(Status, StatusMessage),
1661 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1662 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1663 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1664 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1665 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1666 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1667 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1668 SystemSubtype::ControlRequestProgress => {
1669 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1670 }
1671 SystemSubtype::ModelRefusalFallback => {
1672 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1673 }
1674 SystemSubtype::ModelRefusalNoFallback => {
1675 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1676 }
1677 SystemSubtype::LocalCommandOutput => {
1678 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1679 }
1680 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1681 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1682 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1683 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1684 SystemSubtype::BackgroundTasksChanged => {
1685 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1686 }
1687 SystemSubtype::SessionStateChanged => {
1688 parse!(SessionStateChanged, SessionStateChangedMessage)
1689 }
1690 SystemSubtype::WorkerShuttingDown => {
1691 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1692 }
1693 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1694 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1695 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1696 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1697 SystemSubtype::ElicitationComplete => {
1698 parse!(ElicitationComplete, ElicitationCompleteMessage)
1699 }
1700 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1701 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1702 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1703 SystemSubtype::CodeChangePublished => {
1704 parse!(CodeChangePublished, CodeChangePublishedMessage)
1705 }
1706 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1707 SystemSubtype::FeedbackDraftQueued => {
1708 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1709 }
1710 SystemSubtype::CloudSessionDelta => {
1711 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1712 }
1713 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1714 SystemSubtype::TurnHandoffAvailable => {
1715 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1716 }
1717 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1718 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1719 SystemSubtype::PerTurnEffortChanged => {
1720 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1721 }
1722 SystemSubtype::SessionTitleChanged => {
1723 parse!(SessionTitleChanged, SessionTitleChangedMessage)
1724 }
1725 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
1726 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
1727 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
1728 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
1729 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
1730 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
1731 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
1732 SystemSubtype::Unknown(_) => None,
1733 }
1734 }
1735
1736 pub fn typed_value(&self) -> Option<Value> {
1745 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1746 parsed.and_then(|v| serde_json::to_value(v).ok())
1747 }
1748 match self.subtype {
1749 SystemSubtype::Init => reserialize(self.as_init()),
1750 SystemSubtype::Status => reserialize(self.as_status()),
1751 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1752 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1753 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1754 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1755 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1756 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1757 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1758 SystemSubtype::ControlRequestProgress => {
1759 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1760 }
1761 SystemSubtype::ModelRefusalFallback => {
1762 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1763 }
1764 SystemSubtype::ModelRefusalNoFallback => {
1765 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1766 }
1767 SystemSubtype::LocalCommandOutput => {
1768 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1769 }
1770 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1771 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1772 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1773 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1774 SystemSubtype::BackgroundTasksChanged => {
1775 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1776 }
1777 SystemSubtype::SessionStateChanged => {
1778 reserialize(parse_system::<SessionStateChangedMessage>(self))
1779 }
1780 SystemSubtype::WorkerShuttingDown => {
1781 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1782 }
1783 SystemSubtype::CommandsChanged => {
1784 reserialize(parse_system::<CommandsChangedMessage>(self))
1785 }
1786 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1787 SystemSubtype::FilesPersisted => {
1788 reserialize(parse_system::<FilesPersistedMessage>(self))
1789 }
1790 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1791 SystemSubtype::ElicitationComplete => {
1792 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1793 }
1794 SystemSubtype::PermissionDenied => {
1795 reserialize(parse_system::<PermissionDeniedMessage>(self))
1796 }
1797 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1798 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1799 SystemSubtype::CodeChangePublished => {
1800 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1801 }
1802 SystemSubtype::VcsStateChanged => {
1803 reserialize(parse_system::<VcsStateChangedMessage>(self))
1804 }
1805 SystemSubtype::FeedbackDraftQueued => {
1806 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1807 }
1808 SystemSubtype::CloudSessionDelta => {
1809 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1810 }
1811 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1812 SystemSubtype::TurnHandoffAvailable => {
1813 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1814 }
1815 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1816 SystemSubtype::PeerMessageHold => {
1817 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1818 }
1819 SystemSubtype::PerTurnEffortChanged => {
1820 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
1821 }
1822 SystemSubtype::SessionTitleChanged => {
1823 reserialize(parse_system::<SessionTitleChangedMessage>(self))
1824 }
1825 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
1826 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
1827 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
1828 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
1829 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
1830 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
1831 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
1832 SystemSubtype::Unknown(_) => None,
1833 }
1834 }
1835}
1836
1837fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1838 serde_json::from_value(message.data.clone()).ok()
1839}
1840
1841#[allow(clippy::large_enum_variant)]
1847#[derive(Debug, Clone, Serialize, Deserialize)]
1848pub enum KnownSystemEvent {
1849 Init(InitMessage),
1850 Status(StatusMessage),
1851 CompactBoundary(CompactBoundaryMessage),
1852 ThinkingTokens(ThinkingTokensMessage),
1853 TaskStarted(TaskStartedMessage),
1854 TaskProgress(TaskProgressMessage),
1855 TaskUpdated(TaskUpdatedMessage),
1856 TaskNotification(TaskNotificationMessage),
1857 ApiRetry(ApiRetryMessage),
1858 ControlRequestProgress(ControlRequestProgressMessage),
1859 ModelRefusalFallback(ModelRefusalFallbackMessage),
1860 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1861 LocalCommandOutput(LocalCommandOutputMessage),
1862 HookStarted(HookStartedMessage),
1863 HookProgress(HookProgressMessage),
1864 HookResponse(HookResponseMessage),
1865 PluginInstall(PluginInstallMessage),
1866 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1867 SessionStateChanged(SessionStateChangedMessage),
1868 WorkerShuttingDown(WorkerShuttingDownMessage),
1869 CommandsChanged(CommandsChangedMessage),
1870 Notification(NotificationMessage),
1871 FilesPersisted(FilesPersistedMessage),
1872 MemoryRecall(MemoryRecallMessage),
1873 ElicitationComplete(ElicitationCompleteMessage),
1874 PermissionDenied(PermissionDeniedMessage),
1875 MirrorError(MirrorErrorMessage),
1876 Informational(InformationalMessage),
1877 CodeChangePublished(CodeChangePublishedMessage),
1878 VcsStateChanged(VcsStateChangedMessage),
1879 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1880 CloudSessionDelta(CloudSessionDeltaMessage),
1881 DevIntent(DevIntentMessage),
1882 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1883 TurnPreempted(TurnPreemptedMessage),
1884 PeerMessageHold(PeerMessageHoldMessage),
1885 PerTurnEffortChanged(PerTurnEffortChangedMessage),
1886 SessionTitleChanged(SessionTitleChangedMessage),
1887 UiFocus(UiFocusMessage),
1888 UiInvalidate(UiInvalidateMessage),
1889 UiLog(UiLogMessage),
1890 UiPanes(UiPanesMessage),
1891 UiScroll(UiScrollMessage),
1892 UiStatus(UiStatusMessage),
1893 UiToast(UiToastMessage),
1894}
1895
1896#[derive(Debug, Clone, Serialize, Deserialize)]
1897pub struct ApiRetryMessage {
1898 pub attempt: u64,
1899 pub max_retries: u64,
1900 pub retry_delay_ms: u64,
1901 pub error_status: Option<u16>,
1902 pub error: String,
1903 #[serde(default, skip_serializing_if = "Option::is_none")]
1908 pub no_response: Option<ApiRetryNoResponse>,
1909 #[serde(default, skip_serializing_if = "Option::is_none")]
1910 pub uuid: Option<String>,
1911 #[serde(default, skip_serializing_if = "Option::is_none")]
1912 pub session_id: Option<String>,
1913}
1914
1915#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1918pub struct ApiRetryNoResponse {
1919 pub waited_ms: u64,
1921 pub retry_wait_ms: u64,
1923}
1924
1925#[derive(Debug, Clone, Serialize, Deserialize)]
1926pub struct ControlRequestProgressMessage {
1927 pub request_id: String,
1928 pub status: String,
1929 #[serde(default, skip_serializing_if = "Option::is_none")]
1930 pub attempt: Option<u64>,
1931 #[serde(default, skip_serializing_if = "Option::is_none")]
1932 pub max_retries: Option<u64>,
1933 #[serde(default, skip_serializing_if = "Option::is_none")]
1934 pub retry_delay_ms: Option<u64>,
1935 #[serde(default, skip_serializing_if = "Option::is_none")]
1936 pub error_status: Option<u16>,
1937 #[serde(default, skip_serializing_if = "Option::is_none")]
1938 pub error: Option<String>,
1939 #[serde(default, skip_serializing_if = "Option::is_none")]
1940 pub uuid: Option<String>,
1941 #[serde(default, skip_serializing_if = "Option::is_none")]
1942 pub session_id: Option<String>,
1943}
1944
1945#[derive(Debug, Clone, Serialize, Deserialize)]
1946pub struct ModelRefusalFallbackMessage {
1947 pub trigger: String,
1948 pub direction: String,
1949 #[serde(default, skip_serializing_if = "Option::is_none")]
1955 pub scope: Option<RefusalFallbackScope>,
1956 pub original_model: String,
1957 pub fallback_model: String,
1958 pub request_id: Option<String>,
1959 #[serde(default, skip_serializing_if = "Option::is_none")]
1960 pub api_refusal_category: Option<String>,
1961 #[serde(default, skip_serializing_if = "Option::is_none")]
1966 pub saw_cyber_refusal: Option<bool>,
1967 #[serde(default, skip_serializing_if = "Option::is_none")]
1968 pub api_refusal_explanation: Option<String>,
1969 #[serde(default, skip_serializing_if = "Option::is_none")]
1970 pub retracted_message_uuids: Option<Vec<String>>,
1971 #[serde(default, skip_serializing_if = "Option::is_none")]
1972 pub refused_user_message_uuid: Option<String>,
1973 pub content: Value,
1974 #[serde(default, skip_serializing_if = "Option::is_none")]
1975 pub uuid: Option<String>,
1976 #[serde(default, skip_serializing_if = "Option::is_none")]
1977 pub session_id: Option<String>,
1978}
1979
1980#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1984pub enum RefusalFallbackScope {
1985 Session,
1987 Local,
1990 Unknown(String),
1992}
1993
1994impl RefusalFallbackScope {
1995 pub fn as_str(&self) -> &str {
1996 match self {
1997 Self::Session => "session",
1998 Self::Local => "local",
1999 Self::Unknown(s) => s.as_str(),
2000 }
2001 }
2002}
2003
2004impl fmt::Display for RefusalFallbackScope {
2005 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2006 f.write_str(self.as_str())
2007 }
2008}
2009
2010impl From<&str> for RefusalFallbackScope {
2011 fn from(s: &str) -> Self {
2012 match s {
2013 "session" => Self::Session,
2014 "local" => Self::Local,
2015 other => Self::Unknown(other.to_string()),
2016 }
2017 }
2018}
2019
2020impl Serialize for RefusalFallbackScope {
2021 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2022 serializer.serialize_str(self.as_str())
2023 }
2024}
2025
2026impl<'de> Deserialize<'de> for RefusalFallbackScope {
2027 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2028 let s = String::deserialize(deserializer)?;
2029 Ok(Self::from(s.as_str()))
2030 }
2031}
2032
2033#[derive(Debug, Clone, Serialize, Deserialize)]
2034pub struct ModelRefusalNoFallbackMessage {
2035 pub original_model: String,
2036 pub request_id: Option<String>,
2037 #[serde(default, skip_serializing_if = "Option::is_none")]
2038 pub api_refusal_category: Option<String>,
2039 #[serde(default, skip_serializing_if = "Option::is_none")]
2040 pub api_refusal_explanation: Option<String>,
2041 pub content: Value,
2042 #[serde(default, skip_serializing_if = "Option::is_none")]
2043 pub uuid: Option<String>,
2044 #[serde(default, skip_serializing_if = "Option::is_none")]
2045 pub session_id: Option<String>,
2046}
2047
2048#[derive(Debug, Clone, Serialize, Deserialize)]
2049pub struct LocalCommandOutputMessage {
2050 pub content: String,
2051 #[serde(default, skip_serializing_if = "Option::is_none")]
2052 pub uuid: Option<String>,
2053 #[serde(default, skip_serializing_if = "Option::is_none")]
2054 pub session_id: Option<String>,
2055}
2056
2057#[derive(Debug, Clone, Serialize, Deserialize)]
2058pub struct HookStartedMessage {
2059 pub hook_id: String,
2060 pub hook_name: String,
2061 pub hook_event: String,
2062 #[serde(default, skip_serializing_if = "Option::is_none")]
2063 pub uuid: Option<String>,
2064 #[serde(default, skip_serializing_if = "Option::is_none")]
2065 pub session_id: Option<String>,
2066}
2067
2068#[derive(Debug, Clone, Serialize, Deserialize)]
2069pub struct HookProgressMessage {
2070 pub hook_id: String,
2071 pub hook_name: String,
2072 pub hook_event: String,
2073 #[serde(default, skip_serializing_if = "Option::is_none")]
2074 pub stdout: Option<String>,
2075 #[serde(default, skip_serializing_if = "Option::is_none")]
2076 pub stderr: Option<String>,
2077 #[serde(default, skip_serializing_if = "Option::is_none")]
2078 pub output: Option<String>,
2079 #[serde(default, skip_serializing_if = "Option::is_none")]
2080 pub uuid: Option<String>,
2081 #[serde(default, skip_serializing_if = "Option::is_none")]
2082 pub session_id: Option<String>,
2083}
2084
2085#[derive(Debug, Clone, Serialize, Deserialize)]
2086pub struct HookResponseMessage {
2087 pub hook_id: String,
2088 pub hook_name: String,
2089 pub hook_event: String,
2090 #[serde(default, skip_serializing_if = "Option::is_none")]
2091 pub stdout: Option<String>,
2092 #[serde(default, skip_serializing_if = "Option::is_none")]
2093 pub stderr: Option<String>,
2094 #[serde(default, skip_serializing_if = "Option::is_none")]
2095 pub output: Option<String>,
2096 #[serde(default, skip_serializing_if = "Option::is_none")]
2097 pub exit_code: Option<i32>,
2098 pub outcome: String,
2099 #[serde(default, skip_serializing_if = "Option::is_none")]
2100 pub uuid: Option<String>,
2101 #[serde(default, skip_serializing_if = "Option::is_none")]
2102 pub session_id: Option<String>,
2103}
2104
2105#[derive(Debug, Clone, Serialize, Deserialize)]
2106pub struct PluginInstallMessage {
2107 pub status: String,
2108 #[serde(default, skip_serializing_if = "Option::is_none")]
2109 pub name: Option<String>,
2110 #[serde(default, skip_serializing_if = "Option::is_none")]
2111 pub error: Option<String>,
2112 #[serde(default, skip_serializing_if = "Option::is_none")]
2113 pub uuid: Option<String>,
2114 #[serde(default, skip_serializing_if = "Option::is_none")]
2115 pub session_id: Option<String>,
2116}
2117
2118#[derive(Debug, Clone, Serialize, Deserialize)]
2119pub struct BackgroundTasksChangedMessage {
2120 pub tasks: Vec<BackgroundTaskInfo>,
2121 #[serde(default, skip_serializing_if = "Option::is_none")]
2122 pub uuid: Option<String>,
2123 #[serde(default, skip_serializing_if = "Option::is_none")]
2124 pub session_id: Option<String>,
2125}
2126
2127#[derive(Debug, Clone, Serialize, Deserialize)]
2128pub struct BackgroundTaskInfo {
2129 pub task_id: String,
2130 pub task_type: String,
2131 pub description: String,
2132 #[serde(default, skip_serializing_if = "Option::is_none")]
2135 pub ambient: Option<bool>,
2136}
2137
2138#[derive(Debug, Clone, Serialize, Deserialize)]
2139pub struct SessionStateChangedMessage {
2140 pub state: String,
2141 #[serde(default, skip_serializing_if = "Option::is_none")]
2142 pub uuid: Option<String>,
2143 #[serde(default, skip_serializing_if = "Option::is_none")]
2144 pub session_id: Option<String>,
2145}
2146
2147#[derive(Debug, Clone, Serialize, Deserialize)]
2148pub struct WorkerShuttingDownMessage {
2149 pub reason: String,
2150 #[serde(default, skip_serializing_if = "Option::is_none")]
2151 pub uuid: Option<String>,
2152 #[serde(default, skip_serializing_if = "Option::is_none")]
2153 pub session_id: Option<String>,
2154}
2155
2156#[derive(Debug, Clone, Serialize, Deserialize)]
2157pub struct CommandsChangedMessage {
2158 pub commands: Vec<CommandInfo>,
2159 #[serde(default, skip_serializing_if = "Option::is_none")]
2160 pub uuid: Option<String>,
2161 #[serde(default, skip_serializing_if = "Option::is_none")]
2162 pub session_id: Option<String>,
2163}
2164
2165#[derive(Debug, Clone, Serialize, Deserialize)]
2166pub struct CommandInfo {
2167 pub name: String,
2168 pub description: String,
2169 #[serde(rename = "argumentHint")]
2170 pub argument_hint: String,
2171 #[serde(default, skip_serializing_if = "Option::is_none")]
2172 pub aliases: Option<Vec<String>>,
2173 #[serde(default, skip_serializing_if = "Option::is_none")]
2179 pub builtin: Option<bool>,
2180}
2181
2182#[derive(Debug, Clone, Serialize, Deserialize)]
2183pub struct NotificationMessage {
2184 pub key: String,
2185 pub text: String,
2186 pub priority: String,
2187 #[serde(default, skip_serializing_if = "Option::is_none")]
2188 pub color: Option<String>,
2189 #[serde(default, skip_serializing_if = "Option::is_none")]
2190 pub timeout_ms: Option<u64>,
2191 #[serde(default, skip_serializing_if = "Option::is_none")]
2192 pub uuid: Option<String>,
2193 #[serde(default, skip_serializing_if = "Option::is_none")]
2194 pub session_id: Option<String>,
2195}
2196
2197#[derive(Debug, Clone, Serialize, Deserialize)]
2198pub struct FilesPersistedMessage {
2199 pub files: Vec<PersistedFile>,
2200 pub failed: Vec<FailedPersistedFile>,
2201 pub processed_at: String,
2202 #[serde(default, skip_serializing_if = "Option::is_none")]
2203 pub uuid: Option<String>,
2204 #[serde(default, skip_serializing_if = "Option::is_none")]
2205 pub session_id: Option<String>,
2206}
2207
2208#[derive(Debug, Clone, Serialize, Deserialize)]
2209pub struct PersistedFile {
2210 pub filename: String,
2211 pub file_id: String,
2212}
2213
2214#[derive(Debug, Clone, Serialize, Deserialize)]
2215pub struct FailedPersistedFile {
2216 pub filename: String,
2217 pub error: String,
2218}
2219
2220#[derive(Debug, Clone, Serialize, Deserialize)]
2221pub struct MemoryRecallMessage {
2222 pub mode: String,
2223 pub memories: Vec<MemoryRecallItem>,
2224 #[serde(default, skip_serializing_if = "Option::is_none")]
2225 pub uuid: Option<String>,
2226 #[serde(default, skip_serializing_if = "Option::is_none")]
2227 pub session_id: Option<String>,
2228}
2229
2230#[derive(Debug, Clone, Serialize, Deserialize)]
2231pub struct MemoryRecallItem {
2232 pub path: String,
2233 pub scope: String,
2234 #[serde(default, skip_serializing_if = "Option::is_none")]
2235 pub content: Option<String>,
2236}
2237
2238#[derive(Debug, Clone, Serialize, Deserialize)]
2239pub struct ElicitationCompleteMessage {
2240 pub mcp_server_name: String,
2241 pub elicitation_id: String,
2242 #[serde(default, skip_serializing_if = "Option::is_none")]
2243 pub uuid: Option<String>,
2244 #[serde(default, skip_serializing_if = "Option::is_none")]
2245 pub session_id: Option<String>,
2246}
2247
2248#[derive(Debug, Clone, Serialize, Deserialize)]
2249pub struct PermissionDeniedMessage {
2250 pub tool_name: String,
2251 pub tool_use_id: String,
2252 #[serde(default, skip_serializing_if = "Option::is_none")]
2253 pub agent_id: Option<String>,
2254 #[serde(default, skip_serializing_if = "Option::is_none")]
2255 pub decision_reason_type: Option<String>,
2256 #[serde(default, skip_serializing_if = "Option::is_none")]
2260 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2261 #[serde(default, skip_serializing_if = "Option::is_none")]
2262 pub decision_reason: Option<String>,
2263 pub message: String,
2264 #[serde(default, skip_serializing_if = "Option::is_none")]
2265 pub uuid: Option<String>,
2266 #[serde(default, skip_serializing_if = "Option::is_none")]
2267 pub session_id: Option<String>,
2268}
2269
2270#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2273pub enum PermissionDeniedReasonCode {
2274 ClassifierTranscriptTooLong,
2276 OutsideReadsBlocked,
2278 MemoryPaused,
2280 Unknown(String),
2282}
2283
2284impl PermissionDeniedReasonCode {
2285 pub fn as_str(&self) -> &str {
2286 match self {
2287 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2288 Self::OutsideReadsBlocked => "outside_reads_blocked",
2289 Self::MemoryPaused => "memory_paused",
2290 Self::Unknown(s) => s.as_str(),
2291 }
2292 }
2293}
2294
2295impl fmt::Display for PermissionDeniedReasonCode {
2296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2297 f.write_str(self.as_str())
2298 }
2299}
2300
2301impl From<&str> for PermissionDeniedReasonCode {
2302 fn from(s: &str) -> Self {
2303 match s {
2304 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2305 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2306 "memory_paused" => Self::MemoryPaused,
2307 other => Self::Unknown(other.to_string()),
2308 }
2309 }
2310}
2311
2312impl Serialize for PermissionDeniedReasonCode {
2313 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2314 serializer.serialize_str(self.as_str())
2315 }
2316}
2317
2318impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2319 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2320 let s = String::deserialize(deserializer)?;
2321 Ok(Self::from(s.as_str()))
2322 }
2323}
2324
2325#[derive(Debug, Clone, Serialize, Deserialize)]
2326pub struct MirrorErrorMessage {
2327 pub error: String,
2328 pub key: MirrorErrorKey,
2329 #[serde(default, skip_serializing_if = "Option::is_none")]
2330 pub uuid: Option<String>,
2331 #[serde(default, skip_serializing_if = "Option::is_none")]
2332 pub session_id: Option<String>,
2333}
2334
2335#[derive(Debug, Clone, Serialize, Deserialize)]
2336pub struct MirrorErrorKey {
2337 #[serde(rename = "projectKey")]
2338 pub project_key: String,
2339 #[serde(rename = "sessionId")]
2340 pub session_id: String,
2341 #[serde(default, skip_serializing_if = "Option::is_none")]
2342 pub subpath: Option<String>,
2343}
2344
2345#[derive(Debug, Clone, Serialize, Deserialize)]
2346pub struct InformationalMessage {
2347 pub content: String,
2348 pub level: String,
2349 #[serde(default, skip_serializing_if = "Option::is_none")]
2350 pub tool_use_id: Option<String>,
2351 #[serde(default, skip_serializing_if = "Option::is_none")]
2352 pub prevent_continuation: Option<bool>,
2353 #[serde(default, skip_serializing_if = "Option::is_none")]
2354 pub uuid: Option<String>,
2355 #[serde(default, skip_serializing_if = "Option::is_none")]
2356 pub session_id: Option<String>,
2357}
2358
2359#[derive(Debug, Clone, Serialize, Deserialize)]
2361pub struct PluginInfo {
2362 pub name: String,
2364 pub path: String,
2366 #[serde(skip_serializing_if = "Option::is_none")]
2368 pub source: Option<String>,
2369 #[serde(default, skip_serializing_if = "Option::is_none")]
2371 pub version: Option<String>,
2372}
2373
2374#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2376pub struct PluginDiagnostic {
2377 pub plugin: String,
2378 #[serde(rename = "type")]
2379 pub diagnostic_type: String,
2380 pub message: String,
2381}
2382
2383#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2385pub struct MemoryPaths {
2386 #[serde(default, skip_serializing_if = "Option::is_none")]
2387 pub auto: Option<String>,
2388 #[serde(default, skip_serializing_if = "Option::is_none")]
2389 pub team: Option<String>,
2390 #[serde(flatten)]
2391 pub extra: serde_json::Map<String, Value>,
2392}
2393
2394#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2398pub struct McpServerError {
2399 pub name: String,
2400 #[serde(rename = "type")]
2402 pub error_type: String,
2403 pub message: String,
2404}
2405
2406#[derive(Debug, Clone, Serialize, Deserialize)]
2408pub struct InitMessage {
2409 pub session_id: String,
2411 #[serde(skip_serializing_if = "Option::is_none")]
2413 pub cwd: Option<String>,
2414 #[serde(skip_serializing_if = "Option::is_none")]
2416 pub model: Option<String>,
2417 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2419 pub tools: Vec<String>,
2420 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2422 pub mcp_servers: Vec<Value>,
2423 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2425 pub slash_commands: Vec<String>,
2426 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2429 pub terminal_slash_commands: Vec<String>,
2430 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2432 pub agents: Vec<String>,
2433 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2435 pub plugins: Vec<PluginInfo>,
2436 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2438 pub skills: Vec<Value>,
2439 #[serde(skip_serializing_if = "Option::is_none")]
2441 pub claude_code_version: Option<String>,
2442 #[serde(skip_serializing_if = "Option::is_none")]
2445 pub messaging_socket_path: Option<String>,
2446 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2448 pub api_key_source: Option<ApiKeySource>,
2449 #[serde(skip_serializing_if = "Option::is_none")]
2451 pub output_style: Option<OutputStyle>,
2452 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2454 pub permission_mode: Option<InitPermissionMode>,
2455
2456 #[serde(skip_serializing_if = "Option::is_none")]
2458 pub uuid: Option<String>,
2459
2460 #[serde(skip_serializing_if = "Option::is_none")]
2462 pub memory_paths: Option<MemoryPaths>,
2463
2464 #[serde(default, skip_serializing_if = "Option::is_none")]
2469 pub scratchpad_path: Option<String>,
2470
2471 #[serde(skip_serializing_if = "Option::is_none")]
2473 pub fast_mode_state: Option<String>,
2474
2475 #[serde(default, skip_serializing_if = "Option::is_none")]
2477 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2478
2479 #[serde(default, skip_serializing_if = "Option::is_none")]
2482 pub mcp_server_errors: Option<Vec<McpServerError>>,
2483
2484 #[serde(default, skip_serializing_if = "Option::is_none")]
2486 pub analytics_disabled: Option<bool>,
2487
2488 #[serde(default, skip_serializing_if = "Option::is_none")]
2490 pub product_feedback_disabled: Option<bool>,
2491
2492 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2494 pub betas: Vec<String>,
2495
2496 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2498 pub capabilities: Vec<String>,
2499
2500 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2502 pub plugin_errors: Vec<PluginDiagnostic>,
2503
2504 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2506 pub plugin_warnings: Vec<PluginDiagnostic>,
2507
2508 #[serde(default, skip_serializing_if = "Option::is_none")]
2513 pub effort: Option<String>,
2514
2515 #[serde(default, skip_serializing_if = "Option::is_none")]
2520 pub cloud_session: Option<Value>,
2521
2522 #[serde(default, skip_serializing_if = "Option::is_none")]
2526 pub footer_indicator: Option<FooterIndicator>,
2527
2528 #[serde(default, skip_serializing_if = "Option::is_none")]
2532 pub worker_epoch: Option<u64>,
2533
2534 #[serde(default, skip_serializing_if = "Option::is_none")]
2538 pub powershell_path: Option<Option<String>>,
2539
2540 #[serde(default, skip_serializing_if = "Option::is_none")]
2544 pub startup_timing: Option<serde_json::Map<String, Value>>,
2545
2546 #[serde(default, skip_serializing_if = "Option::is_none")]
2556 pub per_turn_effort_active: Option<bool>,
2557
2558 #[serde(default, skip_serializing_if = "Option::is_none")]
2565 pub view_mode: Option<ViewMode>,
2566}
2567
2568#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2572pub struct FooterIndicator {
2573 pub text: String,
2576}
2577
2578#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2581pub enum ViewMode {
2582 Focus,
2585 Default,
2587 Unknown(String),
2589}
2590
2591impl ViewMode {
2592 pub fn as_str(&self) -> &str {
2593 match self {
2594 Self::Focus => "focus",
2595 Self::Default => "default",
2596 Self::Unknown(s) => s.as_str(),
2597 }
2598 }
2599}
2600
2601impl fmt::Display for ViewMode {
2602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2603 f.write_str(self.as_str())
2604 }
2605}
2606
2607impl From<&str> for ViewMode {
2608 fn from(s: &str) -> Self {
2609 match s {
2610 "focus" => Self::Focus,
2611 "default" => Self::Default,
2612 other => Self::Unknown(other.to_string()),
2613 }
2614 }
2615}
2616
2617impl Serialize for ViewMode {
2618 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2619 serializer.serialize_str(self.as_str())
2620 }
2621}
2622
2623impl<'de> Deserialize<'de> for ViewMode {
2624 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2625 let s = String::deserialize(deserializer)?;
2626 Ok(Self::from(s.as_str()))
2627 }
2628}
2629
2630#[derive(Debug, Clone, Serialize, Deserialize)]
2632pub struct StatusMessage {
2633 pub session_id: String,
2635 pub status: Option<StatusMessageStatus>,
2637 #[serde(skip_serializing_if = "Option::is_none")]
2639 pub uuid: Option<String>,
2640 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2642 pub permission_mode: Option<InitPermissionMode>,
2643 #[serde(skip_serializing_if = "Option::is_none")]
2644 pub compact_result: Option<String>,
2645 #[serde(skip_serializing_if = "Option::is_none")]
2646 pub compact_error: Option<String>,
2647}
2648
2649#[derive(Debug, Clone, Serialize, Deserialize)]
2651pub struct CompactBoundaryMessage {
2652 pub session_id: String,
2654 pub compact_metadata: CompactMetadata,
2656 #[serde(
2660 default,
2661 skip_serializing_if = "Option::is_none",
2662 alias = "content",
2663 alias = "text"
2664 )]
2665 pub summary: Option<String>,
2666 #[serde(
2670 default,
2671 skip_serializing_if = "Option::is_none",
2672 alias = "message_count"
2673 )]
2674 pub leaf_message_count: Option<u32>,
2675 #[serde(default, skip_serializing_if = "Option::is_none")]
2677 pub duration_ms: Option<u64>,
2678 #[serde(skip_serializing_if = "Option::is_none")]
2680 pub uuid: Option<String>,
2681 #[serde(skip_serializing_if = "Option::is_none")]
2683 pub logical_parent_uuid: Option<Option<String>>,
2684 #[serde(default, skip_serializing_if = "Option::is_none")]
2688 pub historical: Option<bool>,
2689}
2690
2691#[derive(Debug, Clone, Serialize, Deserialize)]
2693pub struct CompactMetadata {
2694 pub pre_tokens: u64,
2696 pub trigger: CompactionTrigger,
2698 #[serde(default, skip_serializing_if = "Option::is_none")]
2699 pub post_tokens: Option<u64>,
2700 #[serde(default, skip_serializing_if = "Option::is_none")]
2701 pub cumulative_dropped_tokens: Option<u64>,
2702 #[serde(default, skip_serializing_if = "Option::is_none")]
2703 pub duration_ms: Option<u64>,
2704 #[serde(default, skip_serializing_if = "Option::is_none")]
2705 pub user_context: Option<String>,
2706 #[serde(default, skip_serializing_if = "Option::is_none")]
2707 pub messages_summarized: Option<u64>,
2708 #[serde(default, skip_serializing_if = "Option::is_none")]
2709 pub precomputed: Option<bool>,
2710 #[serde(default, skip_serializing_if = "Option::is_none")]
2711 pub pre_compact_discovered_tools: Option<Vec<String>>,
2712 #[serde(default, skip_serializing_if = "Option::is_none")]
2717 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
2718 #[serde(default, skip_serializing_if = "Option::is_none")]
2719 pub preserved_segment: Option<PreservedSegment>,
2720 #[serde(default, skip_serializing_if = "Option::is_none")]
2721 pub preserved_messages: Option<PreservedMessages>,
2722}
2723
2724#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2727pub struct ArtifactReadVersion {
2728 pub slug: String,
2730 pub ver: String,
2732}
2733
2734#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2735pub struct PreservedSegment {
2736 pub head_uuid: String,
2737 pub anchor_uuid: String,
2738 pub tail_uuid: String,
2739}
2740
2741#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2742pub struct PreservedMessages {
2743 pub anchor_uuid: String,
2744 pub uuids: Vec<String>,
2745 #[serde(default, skip_serializing_if = "Option::is_none")]
2746 pub all_uuids: Option<Vec<String>>,
2747}
2748
2749#[derive(Debug, Clone, Serialize, Deserialize)]
2755pub struct TaskUsage {
2756 pub duration_ms: u64,
2758 pub tool_uses: u64,
2760 pub total_tokens: u64,
2762}
2763
2764#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2766pub enum TaskType {
2767 LocalAgent,
2769 LocalBash,
2771 LocalWorkflow,
2773 Unknown(String),
2775}
2776
2777impl TaskType {
2778 pub fn as_str(&self) -> &str {
2779 match self {
2780 Self::LocalAgent => "local_agent",
2781 Self::LocalBash => "local_bash",
2782 Self::LocalWorkflow => "local_workflow",
2783 Self::Unknown(s) => s.as_str(),
2784 }
2785 }
2786}
2787
2788impl fmt::Display for TaskType {
2789 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2790 f.write_str(self.as_str())
2791 }
2792}
2793
2794impl From<&str> for TaskType {
2795 fn from(s: &str) -> Self {
2796 match s {
2797 "local_agent" => Self::LocalAgent,
2798 "local_bash" => Self::LocalBash,
2799 "local_workflow" => Self::LocalWorkflow,
2800 other => Self::Unknown(other.to_string()),
2801 }
2802 }
2803}
2804
2805impl Serialize for TaskType {
2806 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2807 serializer.serialize_str(self.as_str())
2808 }
2809}
2810
2811impl<'de> Deserialize<'de> for TaskType {
2812 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2813 let s = String::deserialize(deserializer)?;
2814 Ok(Self::from(s.as_str()))
2815 }
2816}
2817
2818#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2820pub enum TaskStatus {
2821 Pending,
2822 Running,
2823 Completed,
2824 Failed,
2825 Killed,
2826 Paused,
2827 Stopped,
2828 Unknown(String),
2829}
2830
2831impl TaskStatus {
2832 pub fn as_str(&self) -> &str {
2833 match self {
2834 Self::Pending => "pending",
2835 Self::Running => "running",
2836 Self::Completed => "completed",
2837 Self::Failed => "failed",
2838 Self::Killed => "killed",
2839 Self::Paused => "paused",
2840 Self::Stopped => "stopped",
2841 Self::Unknown(s) => s.as_str(),
2842 }
2843 }
2844}
2845
2846impl fmt::Display for TaskStatus {
2847 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2848 f.write_str(self.as_str())
2849 }
2850}
2851
2852impl From<&str> for TaskStatus {
2853 fn from(s: &str) -> Self {
2854 match s {
2855 "pending" => Self::Pending,
2856 "running" => Self::Running,
2857 "completed" => Self::Completed,
2858 "failed" => Self::Failed,
2859 "killed" => Self::Killed,
2860 "paused" => Self::Paused,
2861 "stopped" => Self::Stopped,
2862 other => Self::Unknown(other.to_string()),
2863 }
2864 }
2865}
2866
2867impl Serialize for TaskStatus {
2868 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2869 serializer.serialize_str(self.as_str())
2870 }
2871}
2872
2873impl<'de> Deserialize<'de> for TaskStatus {
2874 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2875 let s = String::deserialize(deserializer)?;
2876 Ok(Self::from(s.as_str()))
2877 }
2878}
2879
2880#[derive(Debug, Clone, Serialize, Deserialize)]
2882pub struct TaskStartedMessage {
2883 pub session_id: String,
2884 pub task_id: String,
2885 #[serde(default, skip_serializing_if = "Option::is_none")]
2886 pub task_type: Option<TaskType>,
2887 #[serde(default, skip_serializing_if = "Option::is_none")]
2888 pub tool_use_id: Option<String>,
2889 pub description: String,
2890 #[serde(default, skip_serializing_if = "Option::is_none")]
2893 pub subagent_type: Option<String>,
2894 #[serde(default, skip_serializing_if = "Option::is_none")]
2900 pub is_backgrounded: Option<bool>,
2901 #[serde(default, skip_serializing_if = "Option::is_none")]
2905 pub spawn_depth: Option<u32>,
2906 #[serde(default, skip_serializing_if = "Option::is_none")]
2908 pub prompt: Option<String>,
2909 #[serde(default, skip_serializing_if = "Option::is_none")]
2910 pub workflow_name: Option<String>,
2911 #[serde(default, skip_serializing_if = "Option::is_none")]
2912 pub skip_transcript: Option<bool>,
2913 #[serde(default, skip_serializing_if = "Option::is_none")]
2918 pub ambient: Option<bool>,
2919 pub uuid: String,
2920}
2921
2922#[derive(Debug, Clone, Serialize, Deserialize)]
2926pub struct TaskUpdatedMessage {
2927 pub session_id: String,
2928 pub task_id: String,
2929 pub patch: TaskPatch,
2930 pub uuid: String,
2931}
2932
2933#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2936pub struct TaskPatch {
2937 #[serde(default, skip_serializing_if = "Option::is_none")]
2938 pub status: Option<TaskStatus>,
2939 #[serde(default, skip_serializing_if = "Option::is_none")]
2942 pub end_time: Option<u64>,
2943 #[serde(default, skip_serializing_if = "Option::is_none")]
2944 pub description: Option<String>,
2945 #[serde(default, skip_serializing_if = "Option::is_none")]
2946 pub total_paused_ms: Option<u64>,
2947 #[serde(default, skip_serializing_if = "Option::is_none")]
2948 pub error: Option<String>,
2949 #[serde(default, skip_serializing_if = "Option::is_none")]
2950 pub is_backgrounded: Option<bool>,
2951}
2952
2953#[derive(Debug, Clone, Serialize, Deserialize)]
2956pub struct ThinkingTokensMessage {
2957 pub session_id: String,
2958 pub estimated_tokens: u64,
2960 pub estimated_tokens_delta: u64,
2962 #[serde(default, skip_serializing_if = "Option::is_none")]
2969 pub user_message_uuid: Option<String>,
2970 pub uuid: String,
2971}
2972
2973#[derive(Debug, Clone, Serialize, Deserialize)]
2976pub struct TaskProgressMessage {
2977 pub session_id: String,
2978 pub task_id: String,
2979 #[serde(default, skip_serializing_if = "Option::is_none")]
2980 pub tool_use_id: Option<String>,
2981 pub description: String,
2982 #[serde(default, skip_serializing_if = "Option::is_none")]
2983 pub last_tool_name: Option<String>,
2984 pub usage: TaskUsage,
2985 #[serde(default, skip_serializing_if = "Option::is_none")]
2987 pub subagent_type: Option<String>,
2988 #[serde(default, skip_serializing_if = "Option::is_none")]
2989 pub summary: Option<String>,
2990 pub uuid: String,
2991}
2992
2993#[derive(Debug, Clone, Serialize, Deserialize)]
2996pub struct TaskNotificationMessage {
2997 pub session_id: String,
2998 pub task_id: String,
2999 pub status: TaskStatus,
3000 #[serde(default, skip_serializing_if = "Option::is_none")]
3003 pub reason: Option<TaskEndReason>,
3004 pub summary: String,
3005 pub output_file: Option<String>,
3006 #[serde(skip_serializing_if = "Option::is_none")]
3007 pub tool_use_id: Option<String>,
3008 #[serde(skip_serializing_if = "Option::is_none")]
3009 pub usage: Option<TaskUsage>,
3010 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3016 pub resource_links: Vec<ResourceLink>,
3017 #[serde(default, skip_serializing_if = "Option::is_none")]
3018 pub skip_transcript: Option<bool>,
3019 #[serde(default, skip_serializing_if = "Option::is_none")]
3022 pub ambient: Option<bool>,
3023 #[serde(skip_serializing_if = "Option::is_none")]
3024 pub uuid: Option<String>,
3025}
3026
3027#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3029pub enum AssistantErrorKind {
3030 AuthenticationFailed,
3031 OauthOrgNotAllowed,
3032 AccountOnHold,
3033 BillingError,
3034 RateLimit,
3035 Overloaded,
3036 InvalidRequest,
3037 ModelNotFound,
3038 ServerError,
3039 UnknownError,
3040 MaxOutputTokens,
3041 Unknown(String),
3042}
3043
3044impl AssistantErrorKind {
3045 pub fn as_str(&self) -> &str {
3046 match self {
3047 Self::AuthenticationFailed => "authentication_failed",
3048 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3049 Self::AccountOnHold => "account_on_hold",
3050 Self::BillingError => "billing_error",
3051 Self::RateLimit => "rate_limit",
3052 Self::Overloaded => "overloaded",
3053 Self::InvalidRequest => "invalid_request",
3054 Self::ModelNotFound => "model_not_found",
3055 Self::ServerError => "server_error",
3056 Self::UnknownError => "unknown",
3057 Self::MaxOutputTokens => "max_output_tokens",
3058 Self::Unknown(s) => s.as_str(),
3059 }
3060 }
3061}
3062
3063impl fmt::Display for AssistantErrorKind {
3064 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3065 f.write_str(self.as_str())
3066 }
3067}
3068
3069impl From<&str> for AssistantErrorKind {
3070 fn from(s: &str) -> Self {
3071 match s {
3072 "authentication_failed" => Self::AuthenticationFailed,
3073 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3074 "account_on_hold" => Self::AccountOnHold,
3075 "billing_error" => Self::BillingError,
3076 "rate_limit" => Self::RateLimit,
3077 "overloaded" => Self::Overloaded,
3078 "invalid_request" => Self::InvalidRequest,
3079 "model_not_found" => Self::ModelNotFound,
3080 "server_error" => Self::ServerError,
3081 "unknown" => Self::UnknownError,
3082 "max_output_tokens" => Self::MaxOutputTokens,
3083 other => Self::Unknown(other.to_string()),
3084 }
3085 }
3086}
3087
3088impl Serialize for AssistantErrorKind {
3089 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3090 serializer.serialize_str(self.as_str())
3091 }
3092}
3093
3094impl<'de> Deserialize<'de> for AssistantErrorKind {
3095 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3096 let s = String::deserialize(deserializer)?;
3097 Ok(Self::from(s.as_str()))
3098 }
3099}
3100
3101#[derive(Debug, Clone, Serialize, Deserialize)]
3108pub struct CodeChangePublishedMessage {
3109 pub provider: String,
3113 pub url: String,
3115 pub repo: String,
3118 pub identifier: String,
3120 #[serde(default, skip_serializing_if = "Option::is_none")]
3129 pub action: Option<String>,
3130 #[serde(default, skip_serializing_if = "Option::is_none")]
3138 pub branch: Option<String>,
3139 pub uuid: String,
3140 pub session_id: String,
3141}
3142
3143#[derive(Debug, Clone, Serialize, Deserialize)]
3148pub struct VcsStateChangedMessage {
3149 pub kind: VcsMutationKind,
3152 pub cwd: String,
3155 #[serde(default, skip_serializing_if = "Option::is_none")]
3160 pub branch: Option<String>,
3161 pub uuid: String,
3162 pub session_id: String,
3163}
3164
3165#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3167pub enum VcsMutationKind {
3168 Commit,
3169 Push,
3170 Merge,
3171 Rebase,
3172 Unknown(String),
3174}
3175
3176impl VcsMutationKind {
3177 pub fn as_str(&self) -> &str {
3178 match self {
3179 Self::Commit => "commit",
3180 Self::Push => "push",
3181 Self::Merge => "merge",
3182 Self::Rebase => "rebase",
3183 Self::Unknown(s) => s.as_str(),
3184 }
3185 }
3186}
3187
3188impl fmt::Display for VcsMutationKind {
3189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3190 f.write_str(self.as_str())
3191 }
3192}
3193
3194impl From<&str> for VcsMutationKind {
3195 fn from(s: &str) -> Self {
3196 match s {
3197 "commit" => Self::Commit,
3198 "push" => Self::Push,
3199 "merge" => Self::Merge,
3200 "rebase" => Self::Rebase,
3201 other => Self::Unknown(other.to_string()),
3202 }
3203 }
3204}
3205
3206impl Serialize for VcsMutationKind {
3207 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3208 serializer.serialize_str(self.as_str())
3209 }
3210}
3211
3212impl<'de> Deserialize<'de> for VcsMutationKind {
3213 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3214 let s = String::deserialize(deserializer)?;
3215 Ok(Self::from(s.as_str()))
3216 }
3217}
3218
3219#[derive(Debug, Clone, Serialize, Deserialize)]
3221pub struct FeedbackDraftQueuedMessage {
3222 pub draft_id: String,
3223 pub draft_type: String,
3224 pub title: String,
3225 pub details_preview: String,
3226 #[serde(default, skip_serializing_if = "Option::is_none")]
3227 pub uuid: Option<String>,
3228 #[serde(default, skip_serializing_if = "Option::is_none")]
3229 pub session_id: Option<String>,
3230 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3231 pub extra: serde_json::Map<String, Value>,
3232}
3233
3234#[derive(Debug, Clone, Serialize, Deserialize)]
3242pub struct CloudSessionDeltaMessage {
3243 pub seq: u64,
3247 pub changed: Vec<String>,
3251 pub cloud_session: Value,
3255 pub uuid: String,
3256 pub session_id: String,
3257 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3258 pub extra: serde_json::Map<String, Value>,
3259}
3260
3261#[derive(Debug, Clone, Serialize, Deserialize)]
3277pub struct DevIntentMessage {
3278 pub kind: DevIntentKind,
3280 #[serde(default, skip_serializing_if = "Option::is_none")]
3286 pub trigger: Option<DevIntentTrigger>,
3287 pub uuid: String,
3288 pub session_id: String,
3289 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3290 pub extra: serde_json::Map<String, Value>,
3291}
3292
3293#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3297pub enum DevIntentKind {
3298 IosApp,
3304 AndroidApp,
3311 Unknown(String),
3313}
3314
3315impl DevIntentKind {
3316 pub fn as_str(&self) -> &str {
3317 match self {
3318 Self::IosApp => "ios_app",
3319 Self::AndroidApp => "android_app",
3320 Self::Unknown(s) => s.as_str(),
3321 }
3322 }
3323}
3324
3325impl fmt::Display for DevIntentKind {
3326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3327 f.write_str(self.as_str())
3328 }
3329}
3330
3331impl From<&str> for DevIntentKind {
3332 fn from(s: &str) -> Self {
3333 match s {
3334 "ios_app" => Self::IosApp,
3335 "android_app" => Self::AndroidApp,
3336 other => Self::Unknown(other.to_string()),
3337 }
3338 }
3339}
3340
3341impl Serialize for DevIntentKind {
3342 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3343 serializer.serialize_str(self.as_str())
3344 }
3345}
3346
3347impl<'de> Deserialize<'de> for DevIntentKind {
3348 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3349 let s = String::deserialize(deserializer)?;
3350 Ok(Self::from(s.as_str()))
3351 }
3352}
3353
3354#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3358pub enum DevIntentTrigger {
3359 SwiftEdit,
3361 UikitImport,
3363 XcodeProject,
3366 IosCommand,
3369 KotlinEdit,
3371 JavaEdit,
3373 AndroidImport,
3375 AndroidManifest,
3377 GradlePlugin,
3379 AndroidCommand,
3382 ProjectScan,
3385 Unknown(String),
3387}
3388
3389impl DevIntentTrigger {
3390 pub fn as_str(&self) -> &str {
3391 match self {
3392 Self::SwiftEdit => "swift_edit",
3393 Self::UikitImport => "uikit_import",
3394 Self::XcodeProject => "xcode_project",
3395 Self::IosCommand => "ios_command",
3396 Self::KotlinEdit => "kotlin_edit",
3397 Self::JavaEdit => "java_edit",
3398 Self::AndroidImport => "android_import",
3399 Self::AndroidManifest => "android_manifest",
3400 Self::GradlePlugin => "gradle_plugin",
3401 Self::AndroidCommand => "android_command",
3402 Self::ProjectScan => "project_scan",
3403 Self::Unknown(s) => s.as_str(),
3404 }
3405 }
3406}
3407
3408impl fmt::Display for DevIntentTrigger {
3409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3410 f.write_str(self.as_str())
3411 }
3412}
3413
3414impl From<&str> for DevIntentTrigger {
3415 fn from(s: &str) -> Self {
3416 match s {
3417 "swift_edit" => Self::SwiftEdit,
3418 "uikit_import" => Self::UikitImport,
3419 "xcode_project" => Self::XcodeProject,
3420 "ios_command" => Self::IosCommand,
3421 "kotlin_edit" => Self::KotlinEdit,
3422 "java_edit" => Self::JavaEdit,
3423 "android_import" => Self::AndroidImport,
3424 "android_manifest" => Self::AndroidManifest,
3425 "gradle_plugin" => Self::GradlePlugin,
3426 "android_command" => Self::AndroidCommand,
3427 "project_scan" => Self::ProjectScan,
3428 other => Self::Unknown(other.to_string()),
3429 }
3430 }
3431}
3432
3433impl Serialize for DevIntentTrigger {
3434 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3435 serializer.serialize_str(self.as_str())
3436 }
3437}
3438
3439impl<'de> Deserialize<'de> for DevIntentTrigger {
3440 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3441 let s = String::deserialize(deserializer)?;
3442 Ok(Self::from(s.as_str()))
3443 }
3444}
3445
3446#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3450pub enum TaskEndReason {
3451 WorkerRestart,
3454 Unknown(String),
3456}
3457
3458impl TaskEndReason {
3459 pub fn as_str(&self) -> &str {
3460 match self {
3461 Self::WorkerRestart => "worker_restart",
3462 Self::Unknown(s) => s.as_str(),
3463 }
3464 }
3465}
3466
3467impl fmt::Display for TaskEndReason {
3468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3469 f.write_str(self.as_str())
3470 }
3471}
3472
3473impl From<&str> for TaskEndReason {
3474 fn from(s: &str) -> Self {
3475 match s {
3476 "worker_restart" => Self::WorkerRestart,
3477 other => Self::Unknown(other.to_string()),
3478 }
3479 }
3480}
3481
3482impl Serialize for TaskEndReason {
3483 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3484 serializer.serialize_str(self.as_str())
3485 }
3486}
3487
3488impl<'de> Deserialize<'de> for TaskEndReason {
3489 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3490 let s = String::deserialize(deserializer)?;
3491 Ok(Self::from(s.as_str()))
3492 }
3493}
3494
3495#[derive(Debug, Clone, Serialize, Deserialize)]
3504pub struct TurnHandoffAvailableMessage {
3505 pub v: u64,
3507 pub tools: Vec<String>,
3509 pub worker_epoch: u64,
3511 #[serde(default, skip_serializing_if = "Option::is_none")]
3515 pub relay_marker: Option<bool>,
3516 #[serde(default, skip_serializing_if = "Option::is_none")]
3521 pub staged_files: Option<bool>,
3522 #[serde(default, skip_serializing_if = "Option::is_none")]
3529 pub no_query_first: Option<bool>,
3530 pub uuid: String,
3531 pub session_id: String,
3532 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3533 pub extra: serde_json::Map<String, Value>,
3534}
3535
3536#[derive(Debug, Clone, Serialize, Deserialize)]
3548pub struct TurnPreemptedMessage {
3549 pub reason: String,
3553 pub preempted_by_uuid: String,
3557 #[serde(default)]
3560 pub preempted_message_uuids: Vec<String>,
3561 pub uuid: String,
3562 pub session_id: String,
3563 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3564 pub extra: serde_json::Map<String, Value>,
3565}
3566
3567#[derive(Debug, Clone, Serialize, Deserialize)]
3575pub struct PeerMessageHoldMessage {
3576 pub state: PeerMessageHoldState,
3582 #[serde(default, skip_serializing_if = "Option::is_none")]
3589 pub message_uuid: Option<String>,
3590 pub lane: PeerMessageLane,
3592 pub from: String,
3597 #[serde(default, skip_serializing_if = "Option::is_none")]
3600 pub from_name: Option<String>,
3601 #[serde(default, skip_serializing_if = "Option::is_none")]
3603 pub cause: Option<PeerMessageHoldCause>,
3604 #[serde(default, skip_serializing_if = "Option::is_none")]
3606 pub outcome: Option<PeerMessageHoldOutcome>,
3607 pub uuid: String,
3608 pub session_id: String,
3609 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3610 pub extra: serde_json::Map<String, Value>,
3611}
3612
3613#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3615pub enum PeerMessageHoldState {
3616 Held,
3618 Released,
3620 Dropped,
3622 Unknown(String),
3624}
3625
3626impl PeerMessageHoldState {
3627 pub fn as_str(&self) -> &str {
3628 match self {
3629 Self::Held => "held",
3630 Self::Released => "released",
3631 Self::Dropped => "dropped",
3632 Self::Unknown(s) => s.as_str(),
3633 }
3634 }
3635}
3636
3637impl fmt::Display for PeerMessageHoldState {
3638 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3639 f.write_str(self.as_str())
3640 }
3641}
3642
3643impl From<&str> for PeerMessageHoldState {
3644 fn from(s: &str) -> Self {
3645 match s {
3646 "held" => Self::Held,
3647 "released" => Self::Released,
3648 "dropped" => Self::Dropped,
3649 other => Self::Unknown(other.to_string()),
3650 }
3651 }
3652}
3653
3654impl Serialize for PeerMessageHoldState {
3655 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3656 serializer.serialize_str(self.as_str())
3657 }
3658}
3659
3660impl<'de> Deserialize<'de> for PeerMessageHoldState {
3661 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3662 let s = String::deserialize(deserializer)?;
3663 Ok(Self::from(s.as_str()))
3664 }
3665}
3666
3667#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3669pub enum PeerMessageLane {
3670 Bridge,
3672 Stdin,
3674 Socket,
3676 Unknown(String),
3678}
3679
3680impl PeerMessageLane {
3681 pub fn as_str(&self) -> &str {
3682 match self {
3683 Self::Bridge => "bridge",
3684 Self::Stdin => "stdin",
3685 Self::Socket => "socket",
3686 Self::Unknown(s) => s.as_str(),
3687 }
3688 }
3689}
3690
3691impl fmt::Display for PeerMessageLane {
3692 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3693 f.write_str(self.as_str())
3694 }
3695}
3696
3697impl From<&str> for PeerMessageLane {
3698 fn from(s: &str) -> Self {
3699 match s {
3700 "bridge" => Self::Bridge,
3701 "stdin" => Self::Stdin,
3702 "socket" => Self::Socket,
3703 other => Self::Unknown(other.to_string()),
3704 }
3705 }
3706}
3707
3708impl Serialize for PeerMessageLane {
3709 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3710 serializer.serialize_str(self.as_str())
3711 }
3712}
3713
3714impl<'de> Deserialize<'de> for PeerMessageLane {
3715 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3716 let s = String::deserialize(deserializer)?;
3717 Ok(Self::from(s.as_str()))
3718 }
3719}
3720
3721#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3728pub enum PeerMessageHoldCause {
3729 ExplicitSetting,
3730 ManagedSetting,
3731 RepoSetting,
3732 InvalidSetting,
3733 BypassDefault,
3734 ModeUnknown,
3735 ModeMismatch,
3736 NoModeAsserted,
3737 Unknown(String),
3739}
3740
3741impl PeerMessageHoldCause {
3742 pub fn as_str(&self) -> &str {
3743 match self {
3744 Self::ExplicitSetting => "explicit-setting",
3745 Self::ManagedSetting => "managed-setting",
3746 Self::RepoSetting => "repo-setting",
3747 Self::InvalidSetting => "invalid-setting",
3748 Self::BypassDefault => "bypass-default",
3749 Self::ModeUnknown => "mode-unknown",
3750 Self::ModeMismatch => "mode-mismatch",
3751 Self::NoModeAsserted => "no-mode-asserted",
3752 Self::Unknown(s) => s.as_str(),
3753 }
3754 }
3755}
3756
3757impl fmt::Display for PeerMessageHoldCause {
3758 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3759 f.write_str(self.as_str())
3760 }
3761}
3762
3763impl From<&str> for PeerMessageHoldCause {
3764 fn from(s: &str) -> Self {
3765 match s {
3766 "explicit-setting" => Self::ExplicitSetting,
3767 "managed-setting" => Self::ManagedSetting,
3768 "repo-setting" => Self::RepoSetting,
3769 "invalid-setting" => Self::InvalidSetting,
3770 "bypass-default" => Self::BypassDefault,
3771 "mode-unknown" => Self::ModeUnknown,
3772 "mode-mismatch" => Self::ModeMismatch,
3773 "no-mode-asserted" => Self::NoModeAsserted,
3774 other => Self::Unknown(other.to_string()),
3775 }
3776 }
3777}
3778
3779impl Serialize for PeerMessageHoldCause {
3780 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3781 serializer.serialize_str(self.as_str())
3782 }
3783}
3784
3785impl<'de> Deserialize<'de> for PeerMessageHoldCause {
3786 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3787 let s = String::deserialize(deserializer)?;
3788 Ok(Self::from(s.as_str()))
3789 }
3790}
3791
3792#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3795pub enum PeerMessageHoldOutcome {
3796 Expired,
3800 Refused,
3802 Dropped,
3805 Discarded,
3807 Unknown(String),
3809}
3810
3811impl PeerMessageHoldOutcome {
3812 pub fn as_str(&self) -> &str {
3813 match self {
3814 Self::Expired => "expired",
3815 Self::Refused => "refused",
3816 Self::Dropped => "dropped",
3817 Self::Discarded => "discarded",
3818 Self::Unknown(s) => s.as_str(),
3819 }
3820 }
3821}
3822
3823impl fmt::Display for PeerMessageHoldOutcome {
3824 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3825 f.write_str(self.as_str())
3826 }
3827}
3828
3829impl From<&str> for PeerMessageHoldOutcome {
3830 fn from(s: &str) -> Self {
3831 match s {
3832 "expired" => Self::Expired,
3833 "refused" => Self::Refused,
3834 "dropped" => Self::Dropped,
3835 "discarded" => Self::Discarded,
3836 other => Self::Unknown(other.to_string()),
3837 }
3838 }
3839}
3840
3841impl Serialize for PeerMessageHoldOutcome {
3842 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3843 serializer.serialize_str(self.as_str())
3844 }
3845}
3846
3847impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
3848 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3849 let s = String::deserialize(deserializer)?;
3850 Ok(Self::from(s.as_str()))
3851 }
3852}
3853
3854#[derive(Debug, Clone, Serialize, Deserialize)]
3865pub struct PerTurnEffortChangedMessage {
3866 pub per_turn_effort_active: bool,
3868 pub uuid: String,
3869 pub session_id: String,
3870}
3871
3872#[derive(Debug, Clone, Serialize, Deserialize)]
3882pub struct SessionTitleChangedMessage {
3883 pub title: String,
3888 pub uuid: String,
3889 pub session_id: String,
3890}
3891
3892#[derive(Debug, Clone, Serialize, Deserialize)]
3896pub struct UiLogMessage {
3897 pub plugin: String,
3899 pub text: String,
3902 pub uuid: String,
3903 pub session_id: String,
3904}
3905
3906#[derive(Debug, Clone, Serialize, Deserialize)]
3910pub struct UiToastMessage {
3911 pub plugin: String,
3913 pub text: String,
3916 pub timeout_ms: u64,
3919 pub uuid: String,
3920 pub session_id: String,
3921}
3922
3923#[derive(Debug, Clone, Serialize, Deserialize)]
3927pub struct UiStatusMessage {
3928 pub plugin: String,
3930 pub text: Option<String>,
3934 pub uuid: String,
3935 pub session_id: String,
3936}
3937
3938#[derive(Debug, Clone, Serialize, Deserialize)]
3943pub struct UiInvalidateMessage {
3944 pub event: String,
3947 #[serde(default, skip_serializing_if = "Option::is_none")]
3951 pub instances: Option<Vec<UiInvalidatedInstance>>,
3952 pub uuid: String,
3953 pub session_id: String,
3954}
3955
3956#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3958pub struct UiInvalidatedInstance {
3959 pub surface: String,
3961 pub component: String,
3963 pub instance_id: String,
3966}
3967
3968#[derive(Debug, Clone, Serialize, Deserialize)]
3975pub struct UiPanesMessage {
3976 pub panes: Vec<UiPane>,
3979 pub shown_id: Option<String>,
3982 pub focused_id: Option<String>,
3985 pub focus_requested_id: Option<String>,
3989 pub uuid: String,
3990 pub session_id: String,
3991}
3992
3993#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
3995pub struct UiPane {
3996 pub id: String,
3999 pub title: String,
4002 pub plugin: String,
4004 #[serde(default, skip_serializing_if = "Option::is_none")]
4007 pub close_on_escape: Option<bool>,
4008 #[serde(default, skip_serializing_if = "Option::is_none")]
4011 pub hold_toasts: Option<bool>,
4012 #[serde(default, skip_serializing_if = "Option::is_none")]
4015 pub rows: Option<u64>,
4016 #[serde(default, skip_serializing_if = "Option::is_none")]
4019 pub columns: Option<u64>,
4020}
4021
4022#[derive(Debug, Clone, Serialize, Deserialize)]
4026pub struct UiScrollMessage {
4027 pub client_id: String,
4029 pub component: UiSiteComponent,
4031 pub instance_id: String,
4033 pub offset: u64,
4036 #[serde(default, skip_serializing_if = "Option::is_none")]
4039 pub follow_end: Option<bool>,
4040 pub uuid: String,
4041 pub session_id: String,
4042}
4043
4044#[derive(Debug, Clone, Serialize, Deserialize)]
4049pub struct UiFocusMessage {
4050 pub client_id: String,
4052 pub component: UiSiteComponent,
4054 pub instance_id: String,
4056 pub plugin: String,
4058 pub key: String,
4060 pub uuid: String,
4061 pub session_id: String,
4062}
4063
4064#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4066pub enum UiSiteComponent {
4067 Pane,
4069 AbovePrompt,
4071 Unknown(String),
4073}
4074
4075impl UiSiteComponent {
4076 pub fn as_str(&self) -> &str {
4077 match self {
4078 Self::Pane => "Pane",
4079 Self::AbovePrompt => "AbovePrompt",
4080 Self::Unknown(s) => s.as_str(),
4081 }
4082 }
4083}
4084
4085impl fmt::Display for UiSiteComponent {
4086 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4087 f.write_str(self.as_str())
4088 }
4089}
4090
4091impl From<&str> for UiSiteComponent {
4092 fn from(s: &str) -> Self {
4093 match s {
4094 "Pane" => Self::Pane,
4095 "AbovePrompt" => Self::AbovePrompt,
4096 other => Self::Unknown(other.to_string()),
4097 }
4098 }
4099}
4100
4101impl Serialize for UiSiteComponent {
4102 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4103 serializer.serialize_str(self.as_str())
4104 }
4105}
4106
4107impl<'de> Deserialize<'de> for UiSiteComponent {
4108 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4109 let s = String::deserialize(deserializer)?;
4110 Ok(Self::from(s.as_str()))
4111 }
4112}
4113
4114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4117pub struct LocalCommandRun {
4118 pub command: String,
4120 pub args: String,
4123}
4124
4125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4129pub struct LocalCommandOutcome {
4130 pub kind: LocalCommandOutcomeKind,
4131 #[serde(default, skip_serializing_if = "Option::is_none")]
4134 pub suggestion: Option<String>,
4135}
4136
4137#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4142pub enum LocalCommandOutcomeKind {
4143 UnavailableHeadless,
4145 Unknown,
4148 Failed,
4150 RestartRequired,
4152 Other(String),
4154}
4155
4156impl LocalCommandOutcomeKind {
4157 pub fn as_str(&self) -> &str {
4158 match self {
4159 Self::UnavailableHeadless => "unavailable_headless",
4160 Self::Unknown => "unknown",
4161 Self::Failed => "failed",
4162 Self::RestartRequired => "restart_required",
4163 Self::Other(s) => s.as_str(),
4164 }
4165 }
4166}
4167
4168impl fmt::Display for LocalCommandOutcomeKind {
4169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4170 f.write_str(self.as_str())
4171 }
4172}
4173
4174impl From<&str> for LocalCommandOutcomeKind {
4175 fn from(s: &str) -> Self {
4176 match s {
4177 "unavailable_headless" => Self::UnavailableHeadless,
4178 "unknown" => Self::Unknown,
4179 "failed" => Self::Failed,
4180 "restart_required" => Self::RestartRequired,
4181 other => Self::Other(other.to_string()),
4182 }
4183 }
4184}
4185
4186impl Serialize for LocalCommandOutcomeKind {
4187 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4188 serializer.serialize_str(self.as_str())
4189 }
4190}
4191
4192impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4193 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4194 let s = String::deserialize(deserializer)?;
4195 Ok(Self::from(s.as_str()))
4196 }
4197}
4198
4199#[derive(Debug, Clone, Serialize, Deserialize)]
4205pub struct UsageReport {
4206 pub session: super::control::UsageSession,
4208 pub rate_limits: Option<UsageReportRateLimits>,
4212 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4213 pub extra: serde_json::Map<String, Value>,
4214}
4215
4216#[derive(Debug, Clone, Serialize, Deserialize)]
4218pub struct UsageReportRateLimits {
4219 pub limits: Option<Vec<UsageReportLimit>>,
4227 #[serde(default, skip_serializing_if = "Option::is_none")]
4230 pub extra_usage: Option<UsageReportExtraUsage>,
4231 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4232 pub extra: serde_json::Map<String, Value>,
4233}
4234
4235#[derive(Debug, Clone, Serialize, Deserialize)]
4237pub struct UsageReportLimit {
4238 pub kind: String,
4241 pub group: String,
4244 pub percent: f64,
4246 pub resets_at: Option<String>,
4248 #[serde(default, skip_serializing_if = "Option::is_none")]
4251 pub scope: Option<UsageReportScope>,
4252 #[serde(default, skip_serializing_if = "Option::is_none")]
4256 pub severity: Option<String>,
4257 #[serde(default, skip_serializing_if = "Option::is_none")]
4259 pub is_active: Option<bool>,
4260 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4261 pub extra: serde_json::Map<String, Value>,
4262}
4263
4264#[derive(Debug, Clone, Serialize, Deserialize)]
4266pub struct UsageReportScope {
4267 #[serde(default, skip_serializing_if = "Option::is_none")]
4268 pub model: Option<UsageReportScopeLabel>,
4269 #[serde(default, skip_serializing_if = "Option::is_none")]
4270 pub surface: Option<UsageReportScopeLabel>,
4271 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4272 pub extra: serde_json::Map<String, Value>,
4273}
4274
4275#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4277pub struct UsageReportScopeLabel {
4278 pub display_name: String,
4279}
4280
4281#[derive(Debug, Clone, Serialize, Deserialize)]
4285pub struct UsageReportExtraUsage {
4286 pub is_enabled: bool,
4288 pub monthly_limit: Option<f64>,
4289 pub used_credits: Option<f64>,
4290 pub utilization: Option<f64>,
4291 #[serde(default, skip_serializing_if = "Option::is_none")]
4292 pub currency: Option<String>,
4293 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4294 pub extra: serde_json::Map<String, Value>,
4295}
4296
4297#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4300pub struct BatchToolUse {
4301 pub id: String,
4302 pub name: String,
4303}
4304
4305#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4310pub struct ContextUsage {
4311 pub model: String,
4313 pub total_tokens: u64,
4316 pub raw_max_tokens: u64,
4319 pub percentage: u64,
4321 #[serde(default, skip_serializing_if = "Option::is_none")]
4323 pub over_limit: Option<ContextOverLimit>,
4324 #[serde(default)]
4326 pub categories: Vec<ContextCategory>,
4327 #[serde(default)]
4329 pub mcp_tools: Vec<ContextMcpTool>,
4330 #[serde(default)]
4332 pub memory_files: Vec<ContextMemoryFile>,
4333 #[serde(default)]
4335 pub agents: Vec<ContextAgent>,
4336 #[serde(default, skip_serializing_if = "Option::is_none")]
4338 pub skills: Option<Vec<ContextSkill>>,
4339}
4340
4341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4343pub struct ContextOverLimit {
4344 pub tokens_over: u64,
4345 pub kind: String,
4348}
4349
4350#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4352pub struct ContextCategory {
4353 pub name: String,
4356 pub tokens: u64,
4357 pub kind: String,
4361}
4362
4363#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4365pub struct ContextMcpTool {
4366 pub name: String,
4368 pub server_name: String,
4369 pub tokens: u64,
4370}
4371
4372#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4374pub struct ContextMemoryFile {
4375 pub path: String,
4376 #[serde(rename = "type")]
4378 pub file_type: String,
4379 pub tokens: u64,
4380}
4381
4382#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4384pub struct ContextAgent {
4385 pub agent_type: String,
4386 pub source: String,
4388 pub tokens: u64,
4389}
4390
4391#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4393pub struct ContextSkill {
4394 pub name: String,
4395 pub source: String,
4397 #[serde(default, skip_serializing_if = "Option::is_none")]
4398 pub plugin_name: Option<String>,
4399 pub tokens: u64,
4400}
4401
4402#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4404pub struct ToolUseMeta {
4405 pub id: String,
4406 pub display_name: String,
4407 #[serde(default, skip_serializing_if = "Option::is_none")]
4408 pub server_display_name: Option<String>,
4409 #[serde(default, skip_serializing_if = "Option::is_none")]
4410 pub icon_url: Option<String>,
4411}
4412
4413#[derive(Debug, Clone, Serialize, Deserialize)]
4415pub struct AssistantMessage {
4416 pub message: AssistantMessageContent,
4417 #[serde(alias = "sessionId")]
4418 pub session_id: String,
4419 #[serde(skip_serializing_if = "Option::is_none")]
4420 pub uuid: Option<String>,
4421 #[serde(skip_serializing_if = "Option::is_none")]
4422 pub parent_tool_use_id: Option<String>,
4423 #[serde(skip_serializing_if = "Option::is_none")]
4425 pub request_id: Option<String>,
4426 #[serde(default, skip_serializing_if = "Option::is_none")]
4435 pub user_message_uuid: Option<String>,
4436 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4442 pub user_message_uuids: Vec<String>,
4443 #[serde(skip_serializing_if = "Option::is_none")]
4446 pub subagent_type: Option<String>,
4447 #[serde(skip_serializing_if = "Option::is_none")]
4449 pub task_description: Option<String>,
4450 #[serde(skip_serializing_if = "Option::is_none")]
4451 pub error: Option<AssistantErrorKind>,
4452 #[serde(default, skip_serializing_if = "Option::is_none")]
4456 pub aborted: Option<bool>,
4457 #[serde(default, skip_serializing_if = "Option::is_none")]
4462 pub resumed_from_incomplete_thinking: Option<bool>,
4463 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4464 pub supersedes: Vec<String>,
4465 #[serde(skip_serializing_if = "Option::is_none")]
4466 pub timestamp: Option<String>,
4467 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4468 pub tool_use_meta: Vec<ToolUseMeta>,
4469 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4474 pub batch_tool_uses: Vec<BatchToolUse>,
4475 #[serde(default, skip_serializing_if = "Option::is_none")]
4481 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
4482 #[serde(default, skip_serializing_if = "Option::is_none")]
4488 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
4489 #[serde(default, skip_serializing_if = "Option::is_none")]
4493 pub historical: Option<bool>,
4494 #[serde(default, skip_serializing_if = "Option::is_none")]
4502 pub resume_reason: Option<String>,
4503 #[serde(default, skip_serializing_if = "Option::is_none")]
4509 pub local_command_source: Option<String>,
4510 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4519 pub narration_block_indexes: Vec<usize>,
4520 #[serde(default, skip_serializing_if = "Option::is_none")]
4524 pub narration_hint: Option<String>,
4525 #[serde(default, skip_serializing_if = "Option::is_none")]
4529 pub context_usage: Option<ContextUsage>,
4530 #[serde(default, skip_serializing_if = "Option::is_none")]
4538 pub usage_report: Option<Box<UsageReport>>,
4539 #[serde(default, skip_serializing_if = "Option::is_none")]
4546 pub local_command_run: Option<LocalCommandRun>,
4547 #[serde(default, skip_serializing_if = "Option::is_none")]
4553 pub local_command_outcome: Option<LocalCommandOutcome>,
4554 #[serde(default, skip_serializing_if = "Option::is_none")]
4555 pub is_meta: Option<bool>,
4556 #[serde(default, skip_serializing_if = "Option::is_none")]
4557 pub is_virtual: Option<bool>,
4558 #[serde(default, skip_serializing_if = "Option::is_none")]
4559 pub is_api_error_message: Option<bool>,
4560 #[serde(skip_serializing_if = "Option::is_none")]
4561 pub api_error_status: Option<u16>,
4562 #[serde(skip_serializing_if = "Option::is_none")]
4577 pub api_error: Option<String>,
4578 #[serde(default, skip_serializing_if = "Option::is_none")]
4585 pub api_error_code: Option<String>,
4586 #[serde(default, skip_serializing_if = "Option::is_none")]
4589 pub api_error_params: Option<ApiErrorParams>,
4590 #[serde(skip_serializing_if = "Option::is_none")]
4591 pub error_details: Option<String>,
4592 #[serde(skip_serializing_if = "Option::is_none")]
4593 pub advisor_model: Option<String>,
4594 #[serde(skip_serializing_if = "Option::is_none")]
4595 pub attribution_agent: Option<String>,
4596 #[serde(skip_serializing_if = "Option::is_none")]
4597 pub attribution_skill: Option<String>,
4598 #[serde(skip_serializing_if = "Option::is_none")]
4599 pub attribution_plugin: Option<String>,
4600 #[serde(skip_serializing_if = "Option::is_none")]
4601 pub attribution_mcp_server: Option<String>,
4602 #[serde(skip_serializing_if = "Option::is_none")]
4603 pub attribution_mcp_tool: Option<String>,
4604}
4605
4606#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
4610pub struct ApiErrorParams {
4611 #[serde(default, skip_serializing_if = "Option::is_none")]
4613 pub effort: Option<String>,
4614 #[serde(default, skip_serializing_if = "Option::is_none")]
4617 pub provider: Option<ApiErrorProvider>,
4618 #[serde(default, skip_serializing_if = "Option::is_none")]
4621 pub remedy: Option<ApiErrorRemedy>,
4622}
4623
4624#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4626pub enum ApiErrorProvider {
4627 Bedrock,
4628 AnthropicAws,
4629 Mantle,
4630 AnthropicGoogleCloud,
4631 Vertex,
4632 Foundry,
4633 Gateway,
4634 Unknown(String),
4636}
4637
4638impl ApiErrorProvider {
4639 pub fn as_str(&self) -> &str {
4640 match self {
4641 Self::Bedrock => "bedrock",
4642 Self::AnthropicAws => "anthropicAws",
4643 Self::Mantle => "mantle",
4644 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
4645 Self::Vertex => "vertex",
4646 Self::Foundry => "foundry",
4647 Self::Gateway => "gateway",
4648 Self::Unknown(s) => s.as_str(),
4649 }
4650 }
4651}
4652
4653impl fmt::Display for ApiErrorProvider {
4654 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4655 f.write_str(self.as_str())
4656 }
4657}
4658
4659impl From<&str> for ApiErrorProvider {
4660 fn from(s: &str) -> Self {
4661 match s {
4662 "bedrock" => Self::Bedrock,
4663 "anthropicAws" => Self::AnthropicAws,
4664 "mantle" => Self::Mantle,
4665 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
4666 "vertex" => Self::Vertex,
4667 "foundry" => Self::Foundry,
4668 "gateway" => Self::Gateway,
4669 other => Self::Unknown(other.to_string()),
4670 }
4671 }
4672}
4673
4674impl Serialize for ApiErrorProvider {
4675 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4676 serializer.serialize_str(self.as_str())
4677 }
4678}
4679
4680impl<'de> Deserialize<'de> for ApiErrorProvider {
4681 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4682 let s = String::deserialize(deserializer)?;
4683 Ok(Self::from(s.as_str()))
4684 }
4685}
4686
4687#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4689pub enum ApiErrorRemedy {
4690 RefreshCommand,
4693 RefreshCredentials,
4695 Adc,
4698 GatewayToken,
4701 HostManaged,
4703 ModelAccess,
4706 Unknown(String),
4708}
4709
4710impl ApiErrorRemedy {
4711 pub fn as_str(&self) -> &str {
4712 match self {
4713 Self::RefreshCommand => "refresh_command",
4714 Self::RefreshCredentials => "refresh_credentials",
4715 Self::Adc => "adc",
4716 Self::GatewayToken => "gateway_token",
4717 Self::HostManaged => "host_managed",
4718 Self::ModelAccess => "model_access",
4719 Self::Unknown(s) => s.as_str(),
4720 }
4721 }
4722}
4723
4724impl fmt::Display for ApiErrorRemedy {
4725 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4726 f.write_str(self.as_str())
4727 }
4728}
4729
4730impl From<&str> for ApiErrorRemedy {
4731 fn from(s: &str) -> Self {
4732 match s {
4733 "refresh_command" => Self::RefreshCommand,
4734 "refresh_credentials" => Self::RefreshCredentials,
4735 "adc" => Self::Adc,
4736 "gateway_token" => Self::GatewayToken,
4737 "host_managed" => Self::HostManaged,
4738 "model_access" => Self::ModelAccess,
4739 other => Self::Unknown(other.to_string()),
4740 }
4741 }
4742}
4743
4744impl Serialize for ApiErrorRemedy {
4745 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4746 serializer.serialize_str(self.as_str())
4747 }
4748}
4749
4750impl<'de> Deserialize<'de> for ApiErrorRemedy {
4751 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4752 let s = String::deserialize(deserializer)?;
4753 Ok(Self::from(s.as_str()))
4754 }
4755}
4756
4757#[derive(Debug, Clone, Serialize, Deserialize)]
4759pub struct AssistantMessageContent {
4760 pub id: String,
4761 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
4763 pub message_type: Option<String>,
4764 pub role: MessageRole,
4765 pub model: String,
4766 pub content: Vec<ContentBlock>,
4767 #[serde(skip_serializing_if = "Option::is_none")]
4768 pub stop_reason: Option<StopReason>,
4769 #[serde(skip_serializing_if = "Option::is_none")]
4770 pub stop_sequence: Option<String>,
4771 #[serde(skip_serializing_if = "Option::is_none")]
4772 pub usage: Option<AssistantUsage>,
4773 #[serde(skip_serializing_if = "Option::is_none")]
4775 pub stop_details: Option<Value>,
4776 #[serde(skip_serializing_if = "Option::is_none")]
4778 pub context_management: Option<Value>,
4779}
4780
4781#[derive(Debug, Clone, Serialize, Deserialize)]
4783pub struct AssistantUsage {
4784 #[serde(default)]
4786 pub input_tokens: u32,
4787
4788 #[serde(default)]
4790 pub output_tokens: u32,
4791
4792 #[serde(default)]
4794 pub cache_creation_input_tokens: u32,
4795
4796 #[serde(default)]
4798 pub cache_read_input_tokens: u32,
4799
4800 #[serde(skip_serializing_if = "Option::is_none")]
4802 pub service_tier: Option<String>,
4803
4804 #[serde(skip_serializing_if = "Option::is_none")]
4806 pub cache_creation: Option<CacheCreationDetails>,
4807
4808 #[serde(skip_serializing_if = "Option::is_none")]
4810 pub inference_geo: Option<String>,
4811
4812 #[serde(default, skip_serializing_if = "Option::is_none")]
4815 pub fallback_credit: Option<Value>,
4816}
4817
4818#[derive(Debug, Clone, Serialize, Deserialize)]
4820pub struct CacheCreationDetails {
4821 #[serde(default)]
4823 pub ephemeral_1h_input_tokens: u32,
4824
4825 #[serde(default)]
4827 pub ephemeral_5m_input_tokens: u32,
4828}
4829
4830#[cfg(test)]
4831mod tests {
4832 use crate::io::ClaudeOutput;
4833
4834 #[test]
4835 fn test_subagent_usage_rollup_accumulates_task_results() {
4836 use super::SubagentUsageRollup;
4837
4838 let mut rollup = SubagentUsageRollup::default();
4839
4840 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}}"#;
4841 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
4842 assert!(rollup.observe(&output));
4843 assert_eq!(rollup.subagent_tokens, 10201);
4844 assert_eq!(rollup.agent_count, 1);
4845 assert_eq!(rollup.tool_uses, 3);
4846 assert_eq!(rollup.duration_ms, 1853);
4847
4848 assert!(!rollup.observe(&output));
4850 assert_eq!(rollup.agent_count, 1);
4851 assert_eq!(rollup.subagent_tokens, 10201);
4852
4853 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}}"#;
4855 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
4856 assert!(rollup.observe(&output));
4857 assert_eq!(rollup.agent_count, 2);
4858 assert_eq!(rollup.subagent_tokens, 10701);
4859 }
4860
4861 #[test]
4862 fn test_subagent_usage_rollup_ignores_non_task_results() {
4863 use super::SubagentUsageRollup;
4864
4865 let mut rollup = SubagentUsageRollup::default();
4866
4867 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}}"#;
4870 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
4871 assert!(!rollup.observe(&output));
4872
4873 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4875 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
4876 assert!(!rollup.observe(&output));
4877
4878 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
4880 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
4881 assert!(!rollup.observe(&output));
4882
4883 assert_eq!(rollup, SubagentUsageRollup::default());
4884 }
4885
4886 #[test]
4887 fn test_subagent_usage_rollup_over_captured_session() {
4888 use super::SubagentUsageRollup;
4889
4890 let mut rollup = SubagentUsageRollup::default();
4891 let fixture =
4892 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
4893 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
4894 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
4895 rollup.observe(&output);
4896 }
4897 }
4898 assert_eq!(rollup.agent_count, 1);
4899 assert_eq!(rollup.subagent_tokens, 10201);
4900 }
4901
4902 #[test]
4903 fn test_system_message_init() {
4904 let json = r#"{
4905 "type": "system",
4906 "subtype": "init",
4907 "session_id": "test-session-123",
4908 "cwd": "/home/user/project",
4909 "model": "claude-sonnet-4",
4910 "tools": ["Bash", "Read", "Write"],
4911 "mcp_servers": [],
4912 "slash_commands": ["compact", "cost", "review"],
4913 "agents": ["Bash", "Explore", "Plan"],
4914 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
4915 "skills": [],
4916 "claude_code_version": "2.1.15",
4917 "apiKeySource": "none",
4918 "output_style": "default",
4919 "permissionMode": "default"
4920 }"#;
4921
4922 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4923 if let ClaudeOutput::System(sys) = output {
4924 assert!(sys.is_init());
4925 assert!(!sys.is_status());
4926 assert!(!sys.is_compact_boundary());
4927
4928 let init = sys.as_init().expect("Should parse as init");
4929 assert_eq!(init.session_id, "test-session-123");
4930 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
4931 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
4932 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
4933 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
4934 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
4935 assert_eq!(init.plugins.len(), 1);
4936 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4937 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4938 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
4939 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
4940 assert_eq!(
4941 init.permission_mode,
4942 Some(super::InitPermissionMode::Default)
4943 );
4944 } else {
4945 panic!("Expected System message");
4946 }
4947 }
4948
4949 #[test]
4950 fn test_system_message_init_from_real_capture() {
4951 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
4952 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4953 if let ClaudeOutput::System(sys) = output {
4954 let init = sys.as_init().expect("Should parse real init capture");
4955 assert_eq!(init.slash_commands.len(), 8);
4956 assert!(init.slash_commands.contains(&"compact".to_string()));
4957 assert!(init.slash_commands.contains(&"review".to_string()));
4958 assert_eq!(init.agents.len(), 5);
4959 assert!(init.agents.contains(&"Bash".to_string()));
4960 assert!(init.agents.contains(&"Explore".to_string()));
4961 assert_eq!(init.plugins.len(), 1);
4962 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
4963 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
4964 } else {
4965 panic!("Expected System message");
4966 }
4967 }
4968
4969 #[test]
4970 fn test_system_message_status() {
4971 let json = r#"{
4972 "type": "system",
4973 "subtype": "status",
4974 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
4975 "status": "compacting",
4976 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
4977 }"#;
4978
4979 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
4980 if let ClaudeOutput::System(sys) = output {
4981 assert!(sys.is_status());
4982 assert!(!sys.is_init());
4983
4984 let status = sys.as_status().expect("Should parse as status");
4985 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
4986 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
4987 assert_eq!(
4988 status.uuid,
4989 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
4990 );
4991 } else {
4992 panic!("Expected System message");
4993 }
4994 }
4995
4996 #[test]
4997 fn test_system_message_status_null() {
4998 let json = r#"{
4999 "type": "system",
5000 "subtype": "status",
5001 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5002 "status": null,
5003 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5004 }"#;
5005
5006 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5007 if let ClaudeOutput::System(sys) = output {
5008 let status = sys.as_status().expect("Should parse as status");
5009 assert_eq!(status.status, None);
5010 } else {
5011 panic!("Expected System message");
5012 }
5013 }
5014
5015 #[test]
5016 fn test_system_message_task_started() {
5017 let json = r#"{
5018 "type": "system",
5019 "subtype": "task_started",
5020 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5021 "task_id": "b6daf3f",
5022 "task_type": "local_bash",
5023 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5024 "description": "Wait for CI on PR #12",
5025 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5026 }"#;
5027
5028 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5029 if let ClaudeOutput::System(sys) = output {
5030 assert!(sys.is_task_started());
5031 assert!(!sys.is_task_progress());
5032 assert!(!sys.is_task_notification());
5033
5034 let task = sys.as_task_started().expect("Should parse as task_started");
5035 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5036 assert_eq!(task.task_id, "b6daf3f");
5037 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5038 assert_eq!(
5039 task.tool_use_id.as_deref(),
5040 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5041 );
5042 assert_eq!(task.description, "Wait for CI on PR #12");
5043 } else {
5044 panic!("Expected System message");
5045 }
5046 }
5047
5048 #[test]
5049 fn test_system_message_task_started_agent() {
5050 let json = r#"{
5051 "type": "system",
5052 "subtype": "task_started",
5053 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5054 "task_id": "a4a7e0906e5fc64cc",
5055 "task_type": "local_agent",
5056 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5057 "description": "Explore Scene/ArrayScene duplication",
5058 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5059 }"#;
5060
5061 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5062 if let ClaudeOutput::System(sys) = output {
5063 let task = sys.as_task_started().expect("Should parse as task_started");
5064 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5065 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5066 } else {
5067 panic!("Expected System message");
5068 }
5069 }
5070
5071 #[test]
5072 fn test_system_message_task_progress() {
5073 let json = r#"{
5074 "type": "system",
5075 "subtype": "task_progress",
5076 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5077 "task_id": "a4a7e0906e5fc64cc",
5078 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5079 "description": "Reading src/jplephem/chebyshev.rs",
5080 "last_tool_name": "Read",
5081 "usage": {
5082 "duration_ms": 13996,
5083 "tool_uses": 9,
5084 "total_tokens": 38779
5085 },
5086 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5087 }"#;
5088
5089 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5090 if let ClaudeOutput::System(sys) = output {
5091 assert!(sys.is_task_progress());
5092 assert!(!sys.is_task_started());
5093
5094 let progress = sys
5095 .as_task_progress()
5096 .expect("Should parse as task_progress");
5097 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5098 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5099 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5100 assert_eq!(progress.usage.duration_ms, 13996);
5101 assert_eq!(progress.usage.tool_uses, 9);
5102 assert_eq!(progress.usage.total_tokens, 38779);
5103 } else {
5104 panic!("Expected System message");
5105 }
5106 }
5107
5108 #[test]
5109 fn test_system_message_task_notification_completed() {
5110 let json = r#"{
5111 "type": "system",
5112 "subtype": "task_notification",
5113 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5114 "task_id": "a0ba761e9dc9c316f",
5115 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
5116 "status": "completed",
5117 "summary": "Agent \"Write Hipparcos data source doc\" completed",
5118 "output_file": "",
5119 "usage": {
5120 "duration_ms": 172300,
5121 "tool_uses": 11,
5122 "total_tokens": 42005
5123 },
5124 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
5125 }"#;
5126
5127 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5128 if let ClaudeOutput::System(sys) = output {
5129 assert!(sys.is_task_notification());
5130
5131 let notif = sys
5132 .as_task_notification()
5133 .expect("Should parse as task_notification");
5134 assert_eq!(notif.status, super::TaskStatus::Completed);
5135 assert_eq!(
5136 notif.summary,
5137 "Agent \"Write Hipparcos data source doc\" completed"
5138 );
5139 assert_eq!(notif.output_file, Some("".to_string()));
5140 assert_eq!(
5141 notif.tool_use_id,
5142 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
5143 );
5144 let usage = notif.usage.expect("Should have usage");
5145 assert_eq!(usage.duration_ms, 172300);
5146 assert_eq!(usage.tool_uses, 11);
5147 assert_eq!(usage.total_tokens, 42005);
5148 } else {
5149 panic!("Expected System message");
5150 }
5151 }
5152
5153 #[test]
5154 fn test_system_message_task_notification_failed_no_usage() {
5155 let json = r#"{
5156 "type": "system",
5157 "subtype": "task_notification",
5158 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
5159 "task_id": "b98f6a3",
5160 "status": "failed",
5161 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
5162 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
5163 }"#;
5164
5165 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5166 if let ClaudeOutput::System(sys) = output {
5167 let notif = sys
5168 .as_task_notification()
5169 .expect("Should parse as task_notification");
5170 assert_eq!(notif.status, super::TaskStatus::Failed);
5171 assert!(notif.tool_use_id.is_none());
5172 assert!(notif.usage.is_none());
5173 assert_eq!(
5174 notif.output_file,
5175 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
5176 );
5177 } else {
5178 panic!("Expected System message");
5179 }
5180 }
5181
5182 #[test]
5188 fn test_task_messages_roundtrip_through_value() {
5189 let cases = [
5190 r#"{"type":"system","subtype":"task_started","session_id":"s1",
5191 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
5192 "description":"Sleep 3s","uuid":"u1"}"#,
5193 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
5194 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
5195 "last_tool_name":"Bash",
5196 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5197 "uuid":"u2"}"#,
5198 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
5199 "task_id":"t1","tool_use_id":"tu1","status":"completed",
5200 "summary":"done","output_file":"",
5201 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5202 "uuid":"u3"}"#,
5203 ];
5204
5205 for json in cases {
5206 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5207 let value = serde_json::to_value(&output).unwrap();
5208 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
5209
5210 let ClaudeOutput::System(sys) = reparsed else {
5211 panic!("Expected System variant after round-trip");
5212 };
5213
5214 match sys.subtype {
5215 super::SystemSubtype::TaskStarted => {
5216 assert!(
5217 sys.as_task_started().is_some(),
5218 "as_task_started failed after round-trip"
5219 );
5220 }
5221 super::SystemSubtype::TaskProgress => {
5222 assert!(
5223 sys.as_task_progress().is_some(),
5224 "as_task_progress failed after round-trip"
5225 );
5226 }
5227 super::SystemSubtype::TaskNotification => {
5228 assert!(
5229 sys.as_task_notification().is_some(),
5230 "as_task_notification failed after round-trip"
5231 );
5232 }
5233 other => panic!("unexpected subtype after round-trip: {other:?}"),
5234 }
5235 }
5236 }
5237
5238 #[test]
5239 fn test_system_message_compact_boundary() {
5240 let json = r#"{
5241 "type": "system",
5242 "subtype": "compact_boundary",
5243 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5244 "compact_metadata": {
5245 "pre_tokens": 155285,
5246 "trigger": "auto"
5247 },
5248 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
5249 }"#;
5250
5251 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5252 if let ClaudeOutput::System(sys) = output {
5253 assert!(sys.is_compact_boundary());
5254 assert!(!sys.is_init());
5255 assert!(!sys.is_status());
5256
5257 let compact = sys
5258 .as_compact_boundary()
5259 .expect("Should parse as compact_boundary");
5260 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5261 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
5262 assert_eq!(
5263 compact.compact_metadata.trigger,
5264 super::CompactionTrigger::Auto
5265 );
5266 assert!(compact.summary.is_none());
5268 assert!(compact.leaf_message_count.is_none());
5269 assert!(compact.duration_ms.is_none());
5270 } else {
5271 panic!("Expected System message");
5272 }
5273 }
5274
5275 #[test]
5276 fn test_compact_boundary_with_summary_stats() {
5277 let json = r#"{
5279 "type": "system",
5280 "subtype": "compact_boundary",
5281 "session_id": "s1",
5282 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
5283 "summary": "Summarized the earlier exploration.",
5284 "leaf_message_count": 42,
5285 "duration_ms": 1234,
5286 "uuid": "u1"
5287 }"#;
5288 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5289 let ClaudeOutput::System(sys) = output else {
5290 panic!("Expected System message");
5291 };
5292 let compact = sys.as_compact_boundary().expect("compact_boundary");
5293 assert_eq!(
5294 compact.summary.as_deref(),
5295 Some("Summarized the earlier exploration.")
5296 );
5297 assert_eq!(compact.leaf_message_count, Some(42));
5298 assert_eq!(compact.duration_ms, Some(1234));
5299
5300 let json_alt = r#"{
5303 "type": "system",
5304 "subtype": "compact_boundary",
5305 "session_id": "s2",
5306 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
5307 "content": "alt-key summary",
5308 "message_count": 7
5309 }"#;
5310 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
5311 let ClaudeOutput::System(sys) = output else {
5312 panic!("Expected System message");
5313 };
5314 let compact = sys.as_compact_boundary().expect("compact_boundary");
5315 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
5316 assert_eq!(compact.leaf_message_count, Some(7));
5317 }
5318
5319 #[test]
5320 fn test_init_message_with_new_fields() {
5321 let json = r#"{
5322 "type": "system",
5323 "subtype": "init",
5324 "session_id": "test-session",
5325 "cwd": "/home/user",
5326 "model": "claude-opus-4-7",
5327 "tools": ["Bash"],
5328 "mcp_servers": [],
5329 "permissionMode": "default",
5330 "apiKeySource": "none",
5331 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
5332 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
5333 "fast_mode_state": "off",
5334 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
5335 "claude_code_version": "2.1.117"
5336 }"#;
5337
5338 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5339 if let ClaudeOutput::System(sys) = output {
5340 let init = sys.as_init().expect("Should parse as init");
5341 assert_eq!(
5342 init.uuid.as_deref(),
5343 Some("44841a0d-182d-493a-86b5-79800d3d9665")
5344 );
5345 assert!(init.memory_paths.is_some());
5346 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
5347 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
5348 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
5349 } else {
5350 panic!("Expected System message");
5351 }
5352 }
5353
5354 #[test]
5355 fn test_assistant_message_with_new_fields() {
5356 let json = r#"{
5357 "type": "assistant",
5358 "message": {
5359 "id": "msg_1",
5360 "type": "message",
5361 "role": "assistant",
5362 "model": "claude-opus-4-7",
5363 "content": [{"type": "text", "text": "Hello"}],
5364 "stop_reason": "end_turn",
5365 "stop_details": null,
5366 "context_management": null,
5367 "usage": {
5368 "input_tokens": 100,
5369 "output_tokens": 10,
5370 "cache_creation_input_tokens": 50,
5371 "cache_read_input_tokens": 0,
5372 "service_tier": "standard",
5373 "inference_geo": "not_available"
5374 }
5375 },
5376 "session_id": "abc",
5377 "uuid": "msg-uuid-123"
5378 }"#;
5379
5380 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5381 if let ClaudeOutput::Assistant(asst) = output {
5382 assert_eq!(asst.message.stop_details, None);
5383 assert_eq!(asst.message.context_management, None);
5384 let usage = asst.message.usage.unwrap();
5385 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
5386 } else {
5387 panic!("Expected Assistant message");
5388 }
5389 }
5390
5391 #[test]
5392 fn test_user_message_with_new_fields() {
5393 let json = r#"{
5394 "type": "user",
5395 "message": {
5396 "role": "user",
5397 "content": [{"type": "text", "text": "Hello"}]
5398 },
5399 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5400 "parent_tool_use_id": "toolu_123",
5401 "uuid": "user-msg-456"
5402 }"#;
5403
5404 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5405 if let ClaudeOutput::User(user) = output {
5406 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
5407 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
5408 } else {
5409 panic!("Expected User message");
5410 }
5411 }
5412
5413 #[test]
5419 fn test_user_message_preserves_tool_use_result_and_timestamp() {
5420 let json = r#"{
5421 "type":"user",
5422 "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"}]},
5423 "parent_tool_use_id":null,
5424 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5425 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
5426 "timestamp":"2026-05-12T23:12:04.121Z",
5427 "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"}}
5428 }"#;
5429
5430 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5431 let user = match output {
5432 ClaudeOutput::User(u) => u,
5433 other => panic!("Expected User message, got {:?}", other.message_type()),
5434 };
5435
5436 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
5437 let raw = user
5438 .tool_use_result
5439 .as_ref()
5440 .expect("tool_use_result must be captured");
5441 assert_eq!(raw["answers"]["Color"], "Blue");
5442 assert_eq!(raw["questions"][0]["header"], "Color");
5443
5444 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
5448 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
5449 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
5450 assert_eq!(
5451 reser["tool_use_result"]["questions"][0]["question"],
5452 "Which color do you prefer?"
5453 );
5454
5455 let typed: crate::AskUserQuestionInput = user
5458 .tool_use_result_as::<crate::AskUserQuestionInput>()
5459 .expect("tool_use_result present")
5460 .expect("AskUserQuestionInput parses");
5461 assert_eq!(typed.questions.len(), 1);
5462 assert_eq!(typed.questions[0].header, "Color");
5463 let answers = typed.answers.expect("answers populated");
5464 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
5465 }
5466
5467 #[test]
5470 fn test_user_message_without_tool_use_result_omits_field() {
5471 let json = r#"{
5472 "type":"user",
5473 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
5474 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
5475 }"#;
5476
5477 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5478 let user = match output {
5479 ClaudeOutput::User(u) => u,
5480 _ => panic!("Expected User message"),
5481 };
5482 assert!(user.tool_use_result.is_none());
5483 assert!(user.timestamp.is_none());
5484
5485 let reser = serde_json::to_value(&user).unwrap();
5486 assert!(reser.get("tool_use_result").is_none());
5487 assert!(reser.get("timestamp").is_none());
5488 }
5489
5490 #[test]
5494 fn test_subagent_result_exposes_token_accounting() {
5495 let json = r#"{
5496 "type":"user",
5497 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
5498 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
5499 "tool_use_result":{
5500 "status":"completed",
5501 "prompt":"Count the .rs files.",
5502 "agentId":"ac4f0276e9d4b6232",
5503 "agentType":"Explore",
5504 "content":[{"type":"text","text":"21"}],
5505 "resolvedModel":"claude-haiku-4-5-20251001",
5506 "totalDurationMs":6869,
5507 "totalTokens":7834,
5508 "totalToolUseCount":1,
5509 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
5510 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
5511 }
5512 }"#;
5513
5514 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5515 let user = match output {
5516 ClaudeOutput::User(u) => u,
5517 _ => panic!("Expected User message"),
5518 };
5519
5520 let result = user.subagent_result().expect("subagent result parses");
5521 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
5522 assert_eq!(
5523 result.resolved_model.as_deref(),
5524 Some("claude-haiku-4-5-20251001")
5525 );
5526 assert_eq!(result.total_tokens, Some(7834));
5527 assert_eq!(result.total_duration_ms, Some(6869));
5528 assert_eq!(result.total_tool_use_count, Some(1));
5529
5530 let usage = result.usage.expect("nested usage present");
5531 assert_eq!(usage.input_tokens, 6);
5532 assert_eq!(usage.cache_read_input_tokens, 7699);
5533
5534 let stats = result.tool_stats.expect("toolStats present");
5535 assert_eq!(stats.bash_count, 1);
5536 }
5537
5538 #[test]
5542 fn test_subagent_result_absent_for_non_task_result() {
5543 let json = r#"{
5544 "type":"user",
5545 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
5546 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5547 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
5548 }"#;
5549
5550 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5551 let user = match output {
5552 ClaudeOutput::User(u) => u,
5553 _ => panic!("Expected User message"),
5554 };
5555
5556 let result = user.subagent_result().expect("lenient parse");
5557 assert_eq!(result.total_tokens, None);
5558 assert_eq!(result.agent_type, None);
5559 }
5560
5561 #[test]
5562 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
5563 use serde_json::Value;
5564
5565 let raw: Value = serde_json::from_str(
5566 r#"{
5567 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
5568 "fast_mode_state":"off",
5569 "fast_mode_disabled_reason":"not_first_party",
5570 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
5571 }"#,
5572 )
5573 .unwrap();
5574 crate::io::assert_fully_wrapped(&raw);
5575
5576 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5577 let ClaudeOutput::System(sys) = output else {
5578 panic!("expected System");
5579 };
5580 let init = sys.as_init().expect("parses as init");
5581 assert_eq!(
5582 init.fast_mode_disabled_reason,
5583 Some(crate::FastModeDisabledReason::NotFirstParty)
5584 );
5585 let errs = init.mcp_server_errors.unwrap();
5586 assert_eq!(errs.len(), 1);
5587 assert_eq!(errs[0].name, "broken");
5588 assert_eq!(errs[0].error_type, "invalid_config");
5589 }
5590
5591 #[test]
5592 fn test_code_change_published_fully_wrapped() {
5593 use super::{KnownSystemEvent, SystemSubtype};
5594 use serde_json::Value;
5595
5596 let raw: Value = serde_json::from_str(
5597 r#"{
5598 "type":"system","subtype":"code_change_published",
5599 "provider":"github","url":"https://github.com/owner/repo/pull/42",
5600 "repo":"owner/repo","identifier":"42",
5601 "uuid":"u1","session_id":"s1"
5602 }"#,
5603 )
5604 .unwrap();
5605 crate::io::assert_fully_wrapped(&raw);
5606
5607 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5608 let ClaudeOutput::System(sys) = output else {
5609 panic!("expected System");
5610 };
5611 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
5612 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
5613 panic!("expected CodeChangePublished event");
5614 };
5615 assert_eq!(msg.provider, "github");
5616 assert_eq!(msg.repo, "owner/repo");
5617 assert_eq!(msg.identifier, "42");
5618
5619 assert!(sys.is_code_change_published());
5620 assert!(!sys.is_vcs_state_changed());
5621 let direct = sys.as_code_change_published().expect("direct accessor");
5622 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
5623 assert!(sys.as_vcs_state_changed().is_none());
5624 }
5625
5626 #[test]
5627 fn test_feedback_draft_queued_fully_wrapped() {
5628 use super::{KnownSystemEvent, SystemSubtype};
5629 use serde_json::Value;
5630
5631 let raw: Value = serde_json::from_str(
5632 r#"{
5633 "type":"system","subtype":"feedback_draft_queued",
5634 "draft_id":"draft-1","draft_type":"bug_report",
5635 "title":"Tool output was truncated",
5636 "details_preview":"The last command omitted its final lines",
5637 "uuid":"u1","session_id":"s1","future_field":"preserved"
5638 }"#,
5639 )
5640 .unwrap();
5641 crate::io::assert_fully_wrapped(&raw);
5642
5643 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5644 let ClaudeOutput::System(sys) = output else {
5645 panic!("expected System");
5646 };
5647 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
5648 assert!(sys.is_feedback_draft_queued());
5649 assert!(!sys.is_vcs_state_changed());
5650
5651 let direct = sys
5652 .as_feedback_draft_queued()
5653 .expect("direct typed accessor");
5654 assert_eq!(direct.draft_id, "draft-1");
5655 assert_eq!(direct.draft_type, "bug_report");
5656 assert_eq!(direct.extra["future_field"], "preserved");
5657
5658 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
5659 panic!("expected FeedbackDraftQueued event");
5660 };
5661 assert_eq!(known.title, "Tool output was truncated");
5662 assert_eq!(
5663 sys.typed_value().expect("typed value")["future_field"],
5664 "preserved"
5665 );
5666 }
5667
5668 #[test]
5669 fn test_cloud_session_delta_fully_wrapped() {
5670 use super::{KnownSystemEvent, SystemSubtype};
5671 use serde_json::Value;
5672
5673 let raw: Value = serde_json::from_str(
5674 r#"{
5675 "type":"system","subtype":"cloud_session_delta",
5676 "seq":3,"changed":["serving","connection"],
5677 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
5678 "serving":{"state":"on"},"connection":{"state":"live"}},
5679 "uuid":"u1","session_id":"s1","future_field":"preserved"
5680 }"#,
5681 )
5682 .unwrap();
5683 crate::io::assert_fully_wrapped(&raw);
5684
5685 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5686 let ClaudeOutput::System(sys) = output else {
5687 panic!("expected System");
5688 };
5689 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
5690 assert!(sys.is_cloud_session_delta());
5691 assert!(!sys.is_feedback_draft_queued());
5692
5693 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
5694 assert_eq!(direct.seq, 3);
5695 assert_eq!(direct.changed, vec!["serving", "connection"]);
5696 assert_eq!(direct.cloud_session["id"], "session_abc");
5697 assert_eq!(direct.extra["future_field"], "preserved");
5698
5699 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
5700 panic!("expected CloudSessionDelta event");
5701 };
5702 assert_eq!(known.session_id, "s1");
5703 assert_eq!(
5704 sys.typed_value().expect("typed value")["future_field"],
5705 "preserved"
5706 );
5707 }
5708
5709 #[test]
5710 fn test_vcs_state_changed_fully_wrapped() {
5711 use super::{KnownSystemEvent, VcsMutationKind};
5712 use serde_json::Value;
5713
5714 for kind in ["commit", "push", "merge", "rebase"] {
5715 let raw: Value = serde_json::from_str(&format!(
5716 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
5717 kind
5718 ))
5719 .unwrap();
5720 crate::io::assert_fully_wrapped(&raw);
5721
5722 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5723 let ClaudeOutput::System(sys) = output else {
5724 panic!("expected System");
5725 };
5726 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5727 panic!("expected VcsStateChanged event");
5728 };
5729 assert_eq!(msg.kind.as_str(), kind);
5730 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
5731 }
5732
5733 let raw: Value = serde_json::from_str(
5735 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
5736 )
5737 .unwrap();
5738 crate::io::assert_fully_wrapped(&raw);
5739 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
5740 let ClaudeOutput::System(sys) = output else {
5741 panic!("expected System");
5742 };
5743 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
5744 panic!("expected VcsStateChanged event");
5745 };
5746 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
5747
5748 assert!(sys.is_vcs_state_changed());
5749 let direct = sys.as_vcs_state_changed().expect("direct accessor");
5750 assert_eq!(direct.cwd, "/repo");
5751 assert!(sys.as_code_change_published().is_none());
5752 }
5753
5754 #[test]
5755 fn test_assistant_aborted_and_resume_flags_roundtrip() {
5756 let json = r#"{
5757 "type":"assistant",
5758 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
5759 "session_id":"s1",
5760 "aborted":true,
5761 "resumed_from_incomplete_thinking":true
5762 }"#;
5763 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5764 let ClaudeOutput::Assistant(msg) = &output else {
5765 panic!("expected Assistant");
5766 };
5767 assert_eq!(msg.aborted, Some(true));
5768 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
5769 let reserialized = serde_json::to_string(&output).unwrap();
5770 assert!(reserialized.contains("\"aborted\":true"));
5771 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
5772
5773 let json = r#"{
5775 "type":"assistant",
5776 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
5777 "session_id":"s2"
5778 }"#;
5779 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5780 let reserialized = serde_json::to_string(&output).unwrap();
5781 assert!(!reserialized.contains("aborted"));
5782 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
5783 }
5784
5785 #[test]
5786 fn test_user_tool_result_meta_roundtrip() {
5787 let json = r#"{
5788 "type":"user",
5789 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
5790 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5791 "tool_result_meta":[
5792 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
5793 {"id":"toolu_2","non_execution_kind":"permission-rule"}
5794 ]
5795 }"#;
5796 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5797 let ClaudeOutput::User(user) = &output else {
5798 panic!("expected User");
5799 };
5800 let meta = user.tool_result_meta.as_ref().unwrap();
5801 assert_eq!(meta.len(), 2);
5802 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
5803 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
5804 assert_eq!(meta[1].user_feedback, None);
5805
5806 let reserialized = serde_json::to_string(&output).unwrap();
5807 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
5808 assert!(!reserialized.contains("\"user_feedback\":null"));
5809 }
5810
5811 #[test]
5815 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
5816 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
5817 let with_scope = serde_json::json!({
5818 "trigger": "refusal",
5819 "direction": "retry",
5820 "scope": "local",
5821 "original_model": "claude-fable-5",
5822 "fallback_model": "claude-opus-5",
5823 "request_id": null,
5824 "content": "Refused; retried on fallback model.",
5825 "uuid": "u1",
5826 "session_id": "s1"
5827 });
5828 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
5829 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
5830 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
5831
5832 let mut without = with_scope.clone();
5835 without.as_object_mut().unwrap().remove("scope");
5836 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
5837 assert_eq!(msg.scope, None);
5838 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
5839
5840 assert_eq!(
5842 RefusalFallbackScope::from("workspace").as_str(),
5843 "workspace"
5844 );
5845 }
5846}