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