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};
8use super::rate_limit::RateLimitInfo;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum SystemSubtype {
17 Init,
18 Status,
19 CompactBoundary,
20 ThinkingTokens,
21 TaskStarted,
22 TaskProgress,
23 TaskUpdated,
24 TaskNotification,
25 ApiRetry,
26 ControlRequestProgress,
27 ModelRefusalFallback,
28 ModelRefusalNoFallback,
29 LocalCommandOutput,
30 HookStarted,
31 HookProgress,
32 HookResponse,
33 PluginInstall,
34 BackgroundTasksChanged,
35 SessionStateChanged,
36 WorkerShuttingDown,
37 CommandsChanged,
38 Notification,
39 FilesPersisted,
40 MemoryRecall,
41 ElicitationComplete,
42 PermissionDenied,
43 MirrorError,
44 Informational,
45 CodeChangePublished,
46 VcsStateChanged,
47 FeedbackDraftQueued,
48 CloudSessionDelta,
49 DevIntent,
50 TurnHandoffAvailable,
51 TurnPreempted,
52 PeerMessageHold,
53 PerTurnEffortChanged,
54 SessionTitleChanged,
55 InstructionSizeWarning,
56 FileAttachmentsMissing,
57 PermissionCheckStatus,
58 UiFocus,
59 UiInvalidate,
60 UiLog,
61 UiPanes,
62 UiScroll,
63 UiStatus,
64 UiToast,
65 Unknown(String),
67}
68
69impl SystemSubtype {
70 pub fn as_str(&self) -> &str {
71 match self {
72 Self::Init => "init",
73 Self::Status => "status",
74 Self::CompactBoundary => "compact_boundary",
75 Self::ThinkingTokens => "thinking_tokens",
76 Self::TaskStarted => "task_started",
77 Self::TaskProgress => "task_progress",
78 Self::TaskUpdated => "task_updated",
79 Self::TaskNotification => "task_notification",
80 Self::ApiRetry => "api_retry",
81 Self::ControlRequestProgress => "control_request_progress",
82 Self::ModelRefusalFallback => "model_refusal_fallback",
83 Self::ModelRefusalNoFallback => "model_refusal_no_fallback",
84 Self::LocalCommandOutput => "local_command_output",
85 Self::HookStarted => "hook_started",
86 Self::HookProgress => "hook_progress",
87 Self::HookResponse => "hook_response",
88 Self::PluginInstall => "plugin_install",
89 Self::BackgroundTasksChanged => "background_tasks_changed",
90 Self::SessionStateChanged => "session_state_changed",
91 Self::WorkerShuttingDown => "worker_shutting_down",
92 Self::CommandsChanged => "commands_changed",
93 Self::Notification => "notification",
94 Self::FilesPersisted => "files_persisted",
95 Self::MemoryRecall => "memory_recall",
96 Self::ElicitationComplete => "elicitation_complete",
97 Self::PermissionDenied => "permission_denied",
98 Self::MirrorError => "mirror_error",
99 Self::Informational => "informational",
100 Self::CodeChangePublished => "code_change_published",
101 Self::VcsStateChanged => "vcs_state_changed",
102 Self::FeedbackDraftQueued => "feedback_draft_queued",
103 Self::CloudSessionDelta => "cloud_session_delta",
104 Self::DevIntent => "dev_intent",
105 Self::TurnHandoffAvailable => "turn_handoff_available",
106 Self::TurnPreempted => "turn_preempted",
107 Self::PeerMessageHold => "peer_message_hold",
108 Self::PerTurnEffortChanged => "per_turn_effort_changed",
109 Self::SessionTitleChanged => "session_title_changed",
110 Self::InstructionSizeWarning => "instruction_size_warning",
111 Self::FileAttachmentsMissing => "file_attachments_missing",
112 Self::PermissionCheckStatus => "permission_check_status",
113 Self::UiFocus => "ui_focus",
114 Self::UiInvalidate => "ui_invalidate",
115 Self::UiLog => "ui_log",
116 Self::UiPanes => "ui_panes",
117 Self::UiScroll => "ui_scroll",
118 Self::UiStatus => "ui_status",
119 Self::UiToast => "ui_toast",
120 Self::Unknown(s) => s.as_str(),
121 }
122 }
123}
124
125impl fmt::Display for SystemSubtype {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.write_str(self.as_str())
128 }
129}
130
131impl From<&str> for SystemSubtype {
132 fn from(s: &str) -> Self {
133 match s {
134 "init" => Self::Init,
135 "status" => Self::Status,
136 "compact_boundary" => Self::CompactBoundary,
137 "thinking_tokens" => Self::ThinkingTokens,
138 "task_started" => Self::TaskStarted,
139 "task_progress" => Self::TaskProgress,
140 "task_updated" => Self::TaskUpdated,
141 "task_notification" => Self::TaskNotification,
142 "api_retry" => Self::ApiRetry,
143 "control_request_progress" => Self::ControlRequestProgress,
144 "model_refusal_fallback" => Self::ModelRefusalFallback,
145 "model_refusal_no_fallback" => Self::ModelRefusalNoFallback,
146 "local_command_output" => Self::LocalCommandOutput,
147 "hook_started" => Self::HookStarted,
148 "hook_progress" => Self::HookProgress,
149 "hook_response" => Self::HookResponse,
150 "plugin_install" => Self::PluginInstall,
151 "background_tasks_changed" => Self::BackgroundTasksChanged,
152 "session_state_changed" => Self::SessionStateChanged,
153 "worker_shutting_down" => Self::WorkerShuttingDown,
154 "commands_changed" => Self::CommandsChanged,
155 "notification" => Self::Notification,
156 "files_persisted" => Self::FilesPersisted,
157 "memory_recall" => Self::MemoryRecall,
158 "elicitation_complete" => Self::ElicitationComplete,
159 "permission_denied" => Self::PermissionDenied,
160 "mirror_error" => Self::MirrorError,
161 "informational" => Self::Informational,
162 "code_change_published" => Self::CodeChangePublished,
163 "vcs_state_changed" => Self::VcsStateChanged,
164 "feedback_draft_queued" => Self::FeedbackDraftQueued,
165 "cloud_session_delta" => Self::CloudSessionDelta,
166 "dev_intent" => Self::DevIntent,
167 "turn_handoff_available" => Self::TurnHandoffAvailable,
168 "turn_preempted" => Self::TurnPreempted,
169 "peer_message_hold" => Self::PeerMessageHold,
170 "per_turn_effort_changed" => Self::PerTurnEffortChanged,
171 "session_title_changed" => Self::SessionTitleChanged,
172 "instruction_size_warning" => Self::InstructionSizeWarning,
173 "file_attachments_missing" => Self::FileAttachmentsMissing,
174 "permission_check_status" => Self::PermissionCheckStatus,
175 "ui_focus" => Self::UiFocus,
176 "ui_invalidate" => Self::UiInvalidate,
177 "ui_log" => Self::UiLog,
178 "ui_panes" => Self::UiPanes,
179 "ui_scroll" => Self::UiScroll,
180 "ui_status" => Self::UiStatus,
181 "ui_toast" => Self::UiToast,
182 other => Self::Unknown(other.to_string()),
183 }
184 }
185}
186
187impl Serialize for SystemSubtype {
188 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
189 serializer.serialize_str(self.as_str())
190 }
191}
192
193impl<'de> Deserialize<'de> for SystemSubtype {
194 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
195 let s = String::deserialize(deserializer)?;
196 Ok(Self::from(s.as_str()))
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Hash)]
205pub enum MessageRole {
206 User,
207 Assistant,
208 Unknown(String),
210}
211
212impl MessageRole {
213 pub fn as_str(&self) -> &str {
214 match self {
215 Self::User => "user",
216 Self::Assistant => "assistant",
217 Self::Unknown(s) => s.as_str(),
218 }
219 }
220}
221
222impl fmt::Display for MessageRole {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 f.write_str(self.as_str())
225 }
226}
227
228impl From<&str> for MessageRole {
229 fn from(s: &str) -> Self {
230 match s {
231 "user" => Self::User,
232 "assistant" => Self::Assistant,
233 other => Self::Unknown(other.to_string()),
234 }
235 }
236}
237
238impl Serialize for MessageRole {
239 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
240 serializer.serialize_str(self.as_str())
241 }
242}
243
244impl<'de> Deserialize<'de> for MessageRole {
245 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
246 let s = String::deserialize(deserializer)?;
247 Ok(Self::from(s.as_str()))
248 }
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Hash)]
253pub enum CompactionTrigger {
254 Auto,
256 Manual,
258 Unknown(String),
260}
261
262impl CompactionTrigger {
263 pub fn as_str(&self) -> &str {
264 match self {
265 Self::Auto => "auto",
266 Self::Manual => "manual",
267 Self::Unknown(s) => s.as_str(),
268 }
269 }
270}
271
272impl fmt::Display for CompactionTrigger {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 f.write_str(self.as_str())
275 }
276}
277
278impl From<&str> for CompactionTrigger {
279 fn from(s: &str) -> Self {
280 match s {
281 "auto" => Self::Auto,
282 "manual" => Self::Manual,
283 other => Self::Unknown(other.to_string()),
284 }
285 }
286}
287
288impl Serialize for CompactionTrigger {
289 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
290 serializer.serialize_str(self.as_str())
291 }
292}
293
294impl<'de> Deserialize<'de> for CompactionTrigger {
295 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
296 let s = String::deserialize(deserializer)?;
297 Ok(Self::from(s.as_str()))
298 }
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Hash)]
303pub enum StopReason {
304 EndTurn,
306 MaxTokens,
308 ToolUse,
310 Unknown(String),
312}
313
314impl StopReason {
315 pub fn as_str(&self) -> &str {
316 match self {
317 Self::EndTurn => "end_turn",
318 Self::MaxTokens => "max_tokens",
319 Self::ToolUse => "tool_use",
320 Self::Unknown(s) => s.as_str(),
321 }
322 }
323}
324
325impl fmt::Display for StopReason {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 f.write_str(self.as_str())
328 }
329}
330
331impl From<&str> for StopReason {
332 fn from(s: &str) -> Self {
333 match s {
334 "end_turn" => Self::EndTurn,
335 "max_tokens" => Self::MaxTokens,
336 "tool_use" => Self::ToolUse,
337 other => Self::Unknown(other.to_string()),
338 }
339 }
340}
341
342impl Serialize for StopReason {
343 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
344 serializer.serialize_str(self.as_str())
345 }
346}
347
348impl<'de> Deserialize<'de> for StopReason {
349 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
350 let s = String::deserialize(deserializer)?;
351 Ok(Self::from(s.as_str()))
352 }
353}
354
355#[derive(Debug, Clone, PartialEq, Eq, Hash)]
357pub enum ApiKeySource {
358 None,
360 User,
361 Project,
362 Org,
363 Temporary,
364 Oauth,
365 Unknown(String),
367}
368
369impl ApiKeySource {
370 pub fn as_str(&self) -> &str {
371 match self {
372 Self::None => "none",
373 Self::User => "user",
374 Self::Project => "project",
375 Self::Org => "org",
376 Self::Temporary => "temporary",
377 Self::Oauth => "oauth",
378 Self::Unknown(s) => s.as_str(),
379 }
380 }
381}
382
383impl fmt::Display for ApiKeySource {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 f.write_str(self.as_str())
386 }
387}
388
389impl From<&str> for ApiKeySource {
390 fn from(s: &str) -> Self {
391 match s {
392 "none" => Self::None,
393 "user" => Self::User,
394 "project" => Self::Project,
395 "org" => Self::Org,
396 "temporary" => Self::Temporary,
397 "oauth" => Self::Oauth,
398 other => Self::Unknown(other.to_string()),
399 }
400 }
401}
402
403impl Serialize for ApiKeySource {
404 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
405 serializer.serialize_str(self.as_str())
406 }
407}
408
409impl<'de> Deserialize<'de> for ApiKeySource {
410 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
411 let s = String::deserialize(deserializer)?;
412 Ok(Self::from(s.as_str()))
413 }
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Hash)]
418pub enum OutputStyle {
419 Default,
421 Unknown(String),
423}
424
425impl OutputStyle {
426 pub fn as_str(&self) -> &str {
427 match self {
428 Self::Default => "default",
429 Self::Unknown(s) => s.as_str(),
430 }
431 }
432}
433
434impl fmt::Display for OutputStyle {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 f.write_str(self.as_str())
437 }
438}
439
440impl From<&str> for OutputStyle {
441 fn from(s: &str) -> Self {
442 match s {
443 "default" => Self::Default,
444 other => Self::Unknown(other.to_string()),
445 }
446 }
447}
448
449impl Serialize for OutputStyle {
450 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
451 serializer.serialize_str(self.as_str())
452 }
453}
454
455impl<'de> Deserialize<'de> for OutputStyle {
456 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
457 let s = String::deserialize(deserializer)?;
458 Ok(Self::from(s.as_str()))
459 }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq, Hash)]
464pub enum InitPermissionMode {
465 Default,
467 AcceptEdits,
468 BypassPermissions,
469 Plan,
470 DontAsk,
471 Auto,
472 Unknown(String),
474}
475
476impl InitPermissionMode {
477 pub fn as_str(&self) -> &str {
478 match self {
479 Self::Default => "default",
480 Self::AcceptEdits => "acceptEdits",
481 Self::BypassPermissions => "bypassPermissions",
482 Self::Plan => "plan",
483 Self::DontAsk => "dontAsk",
484 Self::Auto => "auto",
485 Self::Unknown(s) => s.as_str(),
486 }
487 }
488}
489
490impl fmt::Display for InitPermissionMode {
491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
492 f.write_str(self.as_str())
493 }
494}
495
496impl From<&str> for InitPermissionMode {
497 fn from(s: &str) -> Self {
498 match s {
499 "default" => Self::Default,
500 "acceptEdits" => Self::AcceptEdits,
501 "bypassPermissions" => Self::BypassPermissions,
502 "plan" => Self::Plan,
503 "dontAsk" => Self::DontAsk,
504 "auto" => Self::Auto,
505 other => Self::Unknown(other.to_string()),
506 }
507 }
508}
509
510impl Serialize for InitPermissionMode {
511 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
512 serializer.serialize_str(self.as_str())
513 }
514}
515
516impl<'de> Deserialize<'de> for InitPermissionMode {
517 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
518 let s = String::deserialize(deserializer)?;
519 Ok(Self::from(s.as_str()))
520 }
521}
522
523#[derive(Debug, Clone, PartialEq, Eq, Hash)]
525pub enum StatusMessageStatus {
526 Compacting,
528 Requesting,
530 Unknown(String),
532}
533
534impl StatusMessageStatus {
535 pub fn as_str(&self) -> &str {
536 match self {
537 Self::Compacting => "compacting",
538 Self::Requesting => "requesting",
539 Self::Unknown(s) => s.as_str(),
540 }
541 }
542}
543
544impl fmt::Display for StatusMessageStatus {
545 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546 f.write_str(self.as_str())
547 }
548}
549
550impl From<&str> for StatusMessageStatus {
551 fn from(s: &str) -> Self {
552 match s {
553 "compacting" => Self::Compacting,
554 "requesting" => Self::Requesting,
555 other => Self::Unknown(other.to_string()),
556 }
557 }
558}
559
560impl Serialize for StatusMessageStatus {
561 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
562 serializer.serialize_str(self.as_str())
563 }
564}
565
566impl<'de> Deserialize<'de> for StatusMessageStatus {
567 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
568 let s = String::deserialize(deserializer)?;
569 Ok(Self::from(s.as_str()))
570 }
571}
572
573pub(crate) fn serialize_optional_uuid<S>(
575 uuid: &Option<Uuid>,
576 serializer: S,
577) -> Result<S::Ok, S::Error>
578where
579 S: Serializer,
580{
581 match uuid {
582 Some(id) => serializer.serialize_str(&id.to_string()),
583 None => serializer.serialize_none(),
584 }
585}
586
587pub(crate) fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
589where
590 D: Deserializer<'de>,
591{
592 let opt_str: Option<String> = Option::deserialize(deserializer)?;
593 match opt_str {
594 Some(s) => Uuid::parse_str(&s)
595 .map(Some)
596 .map_err(serde::de::Error::custom),
597 None => Ok(None),
598 }
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
604pub struct MessageOrigin {
605 pub kind: String,
606 #[serde(flatten)]
607 pub extra: serde_json::Map<String, Value>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
612pub struct SummarizeMetadata {
613 pub messages_summarized: u64,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
615 pub user_context: Option<String>,
616 #[serde(default, skip_serializing_if = "Option::is_none")]
617 pub direction: Option<String>,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
622pub struct McpMeta {
623 #[serde(default, skip_serializing_if = "Option::is_none", rename = "_meta")]
624 pub meta: Option<Value>,
625 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub structured_content: Option<Value>,
627 #[serde(default, skip_serializing_if = "Vec::is_empty")]
632 pub resource_links: Vec<ResourceLink>,
633}
634
635#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
639pub struct ResourceLink {
640 pub uri: String,
641 pub name: String,
642 #[serde(default, skip_serializing_if = "Option::is_none")]
643 pub title: Option<String>,
644 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub description: Option<String>,
646 #[serde(default, skip_serializing_if = "Option::is_none", rename = "mimeType")]
647 pub mime_type: Option<String>,
648 #[serde(default, skip_serializing_if = "Option::is_none")]
649 pub size: Option<u64>,
650 #[serde(default, skip_serializing_if = "Option::is_none")]
651 pub annotations: Option<Value>,
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
656pub struct ToolResultMeta {
657 pub id: String,
659 #[serde(default, skip_serializing_if = "Option::is_none")]
666 pub non_execution_kind: Option<String>,
667 #[serde(default, skip_serializing_if = "Option::is_none")]
669 pub user_feedback: Option<String>,
670 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub remedy: Option<ToolResultRemedy>,
676}
677
678#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
683pub struct ToolResultRemedy {
684 pub kind: ToolResultRemedyKind,
687 #[serde(default, skip_serializing_if = "Option::is_none")]
691 pub servers: Option<Vec<String>>,
692 #[serde(default, skip_serializing_if = "Option::is_none")]
695 pub provider: Option<RemedyLoginProvider>,
696 #[serde(default, skip_serializing_if = "Option::is_none")]
700 pub path: Option<String>,
701 #[serde(default, skip_serializing_if = "Option::is_none")]
706 pub managed: Option<bool>,
707 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub feature: Option<RemedyFeature>,
710 #[serde(default, skip_serializing_if = "Option::is_none")]
712 pub cause: Option<RemedyFeatureCause>,
713 #[serde(default, skip_serializing_if = "Option::is_none")]
715 pub policy_kind: Option<RemedyPolicyKind>,
716}
717
718#[derive(Debug, Clone, PartialEq, Eq, Hash)]
720pub enum ToolResultRemedyKind {
721 McpNeedsAuth,
723 McpDisabled,
725 McpRequiredMissing,
727 AuthExpired,
729 AuthOverridden,
731 AuthMissingScope,
733 DesignNeedsAuthorization,
735 SandboxViolation,
737 OutsideReadsBlocked,
739 MemoryPaused,
741 FeatureDisabled,
743 PolicyDenied,
746 SpawnArgLimit,
748 StagedForReview,
750 Unknown(String),
752}
753
754impl ToolResultRemedyKind {
755 pub fn as_str(&self) -> &str {
756 match self {
757 Self::McpNeedsAuth => "mcp_needs_auth",
758 Self::McpDisabled => "mcp_disabled",
759 Self::McpRequiredMissing => "mcp_required_missing",
760 Self::AuthExpired => "auth_expired",
761 Self::AuthOverridden => "auth_overridden",
762 Self::AuthMissingScope => "auth_missing_scope",
763 Self::DesignNeedsAuthorization => "design_needs_authorization",
764 Self::SandboxViolation => "sandbox_violation",
765 Self::OutsideReadsBlocked => "outside_reads_blocked",
766 Self::MemoryPaused => "memory_paused",
767 Self::FeatureDisabled => "feature_disabled",
768 Self::PolicyDenied => "policy_denied",
769 Self::SpawnArgLimit => "spawn_arg_limit",
770 Self::StagedForReview => "staged_for_review",
771 Self::Unknown(s) => s.as_str(),
772 }
773 }
774}
775
776impl fmt::Display for ToolResultRemedyKind {
777 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
778 f.write_str(self.as_str())
779 }
780}
781
782impl From<&str> for ToolResultRemedyKind {
783 fn from(s: &str) -> Self {
784 match s {
785 "mcp_needs_auth" => Self::McpNeedsAuth,
786 "mcp_disabled" => Self::McpDisabled,
787 "mcp_required_missing" => Self::McpRequiredMissing,
788 "auth_expired" => Self::AuthExpired,
789 "auth_overridden" => Self::AuthOverridden,
790 "auth_missing_scope" => Self::AuthMissingScope,
791 "design_needs_authorization" => Self::DesignNeedsAuthorization,
792 "sandbox_violation" => Self::SandboxViolation,
793 "outside_reads_blocked" => Self::OutsideReadsBlocked,
794 "memory_paused" => Self::MemoryPaused,
795 "feature_disabled" => Self::FeatureDisabled,
796 "policy_denied" => Self::PolicyDenied,
797 "spawn_arg_limit" => Self::SpawnArgLimit,
798 "staged_for_review" => Self::StagedForReview,
799 other => Self::Unknown(other.to_string()),
800 }
801 }
802}
803
804impl Serialize for ToolResultRemedyKind {
805 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
806 serializer.serialize_str(self.as_str())
807 }
808}
809
810impl<'de> Deserialize<'de> for ToolResultRemedyKind {
811 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
812 let s = String::deserialize(deserializer)?;
813 Ok(Self::from(s.as_str()))
814 }
815}
816
817#[derive(Debug, Clone, PartialEq, Eq, Hash)]
819pub enum RemedyLoginProvider {
820 ClaudeAi,
822 ClaudeDesign,
824 Unknown(String),
826}
827
828impl RemedyLoginProvider {
829 pub fn as_str(&self) -> &str {
830 match self {
831 Self::ClaudeAi => "claude_ai",
832 Self::ClaudeDesign => "claude_design",
833 Self::Unknown(s) => s.as_str(),
834 }
835 }
836}
837
838impl fmt::Display for RemedyLoginProvider {
839 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
840 f.write_str(self.as_str())
841 }
842}
843
844impl From<&str> for RemedyLoginProvider {
845 fn from(s: &str) -> Self {
846 match s {
847 "claude_ai" => Self::ClaudeAi,
848 "claude_design" => Self::ClaudeDesign,
849 other => Self::Unknown(other.to_string()),
850 }
851 }
852}
853
854impl Serialize for RemedyLoginProvider {
855 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
856 serializer.serialize_str(self.as_str())
857 }
858}
859
860impl<'de> Deserialize<'de> for RemedyLoginProvider {
861 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
862 let s = String::deserialize(deserializer)?;
863 Ok(Self::from(s.as_str()))
864 }
865}
866
867#[derive(Debug, Clone, PartialEq, Eq, Hash)]
870pub enum RemedyFeature {
871 Workflows,
872 Artifacts,
873 Unknown(String),
875}
876
877impl RemedyFeature {
878 pub fn as_str(&self) -> &str {
879 match self {
880 Self::Workflows => "workflows",
881 Self::Artifacts => "artifacts",
882 Self::Unknown(s) => s.as_str(),
883 }
884 }
885}
886
887impl fmt::Display for RemedyFeature {
888 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
889 f.write_str(self.as_str())
890 }
891}
892
893impl From<&str> for RemedyFeature {
894 fn from(s: &str) -> Self {
895 match s {
896 "workflows" => Self::Workflows,
897 "artifacts" => Self::Artifacts,
898 other => Self::Unknown(other.to_string()),
899 }
900 }
901}
902
903impl Serialize for RemedyFeature {
904 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
905 serializer.serialize_str(self.as_str())
906 }
907}
908
909impl<'de> Deserialize<'de> for RemedyFeature {
910 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
911 let s = String::deserialize(deserializer)?;
912 Ok(Self::from(s.as_str()))
913 }
914}
915
916#[derive(Debug, Clone, PartialEq, Eq, Hash)]
919pub enum RemedyFeatureCause {
920 ManagedSettings,
923 OrgPolicy,
925 Unavailable,
927 UserSetting,
930 Unknown(String),
932}
933
934impl RemedyFeatureCause {
935 pub fn as_str(&self) -> &str {
936 match self {
937 Self::ManagedSettings => "managed_settings",
938 Self::OrgPolicy => "org_policy",
939 Self::Unavailable => "unavailable",
940 Self::UserSetting => "user_setting",
941 Self::Unknown(s) => s.as_str(),
942 }
943 }
944}
945
946impl fmt::Display for RemedyFeatureCause {
947 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
948 f.write_str(self.as_str())
949 }
950}
951
952impl From<&str> for RemedyFeatureCause {
953 fn from(s: &str) -> Self {
954 match s {
955 "managed_settings" => Self::ManagedSettings,
956 "org_policy" => Self::OrgPolicy,
957 "unavailable" => Self::Unavailable,
958 "user_setting" => Self::UserSetting,
959 other => Self::Unknown(other.to_string()),
960 }
961 }
962}
963
964impl Serialize for RemedyFeatureCause {
965 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
966 serializer.serialize_str(self.as_str())
967 }
968}
969
970impl<'de> Deserialize<'de> for RemedyFeatureCause {
971 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
972 let s = String::deserialize(deserializer)?;
973 Ok(Self::from(s.as_str()))
974 }
975}
976
977#[derive(Debug, Clone, PartialEq, Eq, Hash)]
980pub enum RemedyPolicyKind {
981 OrgDenied,
983 Latched,
986 Unknown(String),
988}
989
990impl RemedyPolicyKind {
991 pub fn as_str(&self) -> &str {
992 match self {
993 Self::OrgDenied => "org_denied",
994 Self::Latched => "latched",
995 Self::Unknown(s) => s.as_str(),
996 }
997 }
998}
999
1000impl fmt::Display for RemedyPolicyKind {
1001 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1002 f.write_str(self.as_str())
1003 }
1004}
1005
1006impl From<&str> for RemedyPolicyKind {
1007 fn from(s: &str) -> Self {
1008 match s {
1009 "org_denied" => Self::OrgDenied,
1010 "latched" => Self::Latched,
1011 other => Self::Unknown(other.to_string()),
1012 }
1013 }
1014}
1015
1016impl Serialize for RemedyPolicyKind {
1017 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1018 serializer.serialize_str(self.as_str())
1019 }
1020}
1021
1022impl<'de> Deserialize<'de> for RemedyPolicyKind {
1023 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1024 let s = String::deserialize(deserializer)?;
1025 Ok(Self::from(s.as_str()))
1026 }
1027}
1028
1029#[derive(Debug, Clone, Serialize, Deserialize)]
1031pub struct UserMessage {
1032 pub message: MessageContent,
1033 #[serde(skip_serializing_if = "Option::is_none", alias = "sessionId")]
1034 #[serde(
1035 serialize_with = "serialize_optional_uuid",
1036 deserialize_with = "deserialize_optional_uuid"
1037 )]
1038 pub session_id: Option<Uuid>,
1039 #[serde(skip_serializing_if = "Option::is_none")]
1041 pub parent_tool_use_id: Option<String>,
1042 #[serde(skip_serializing_if = "Option::is_none")]
1044 pub uuid: Option<String>,
1045 #[serde(skip_serializing_if = "Option::is_none")]
1047 pub timestamp: Option<String>,
1048 #[serde(skip_serializing_if = "Option::is_none")]
1055 pub tool_use_result: Option<serde_json::Value>,
1056 #[serde(default, skip_serializing_if = "Option::is_none")]
1061 pub agent_id: Option<String>,
1062 #[serde(skip_serializing_if = "Option::is_none")]
1065 pub subagent_type: Option<String>,
1066 #[serde(skip_serializing_if = "Option::is_none")]
1068 pub task_description: Option<String>,
1069 #[serde(skip_serializing_if = "Option::is_none")]
1070 pub origin: Option<MessageOrigin>,
1071 #[serde(skip_serializing_if = "Option::is_none")]
1072 pub priority: Option<String>,
1073 #[serde(skip_serializing_if = "Option::is_none", rename = "isSynthetic")]
1074 pub is_synthetic: Option<bool>,
1075 #[serde(skip_serializing_if = "Option::is_none", rename = "shouldQuery")]
1076 pub should_query: Option<bool>,
1077 #[serde(default, skip_serializing_if = "Option::is_none")]
1078 pub is_meta: Option<bool>,
1079 #[serde(default, skip_serializing_if = "Option::is_none")]
1080 pub is_visible_in_transcript_only: Option<bool>,
1081 #[serde(default, skip_serializing_if = "Option::is_none")]
1082 pub is_virtual: Option<bool>,
1083 #[serde(default, skip_serializing_if = "Option::is_none")]
1084 pub is_compact_summary: Option<bool>,
1085 #[serde(skip_serializing_if = "Option::is_none")]
1086 pub summarize_metadata: Option<SummarizeMetadata>,
1087 #[serde(skip_serializing_if = "Option::is_none")]
1088 pub mcp_meta: Option<McpMeta>,
1089 #[serde(skip_serializing_if = "Option::is_none")]
1092 pub tool_result_meta: Option<Vec<ToolResultMeta>>,
1093 #[serde(skip_serializing_if = "Option::is_none")]
1094 pub source_tool_use_id: Option<String>,
1095 #[serde(skip_serializing_if = "Option::is_none")]
1096 pub source_tool_assistant_uuid: Option<String>,
1097 #[serde(skip_serializing_if = "Option::is_none")]
1098 pub image_paste_ids: Option<Vec<u64>>,
1099 #[serde(skip_serializing_if = "Option::is_none")]
1100 pub client_platform: Option<String>,
1101 #[serde(skip_serializing_if = "Option::is_none")]
1102 pub inbound_origin: Option<String>,
1103 #[serde(skip_serializing_if = "Option::is_none", rename = "isReplay")]
1104 pub is_replay: Option<bool>,
1105 #[serde(skip_serializing_if = "Option::is_none")]
1106 pub file_attachments: Option<Vec<Value>>,
1107 #[serde(default, skip_serializing_if = "Option::is_none")]
1109 pub seeded_summon: Option<bool>,
1110 #[serde(default, skip_serializing_if = "Option::is_none")]
1113 pub client_composed: Option<bool>,
1114 #[serde(default, skip_serializing_if = "Option::is_none")]
1119 pub initiator: Option<String>,
1120 #[serde(default, skip_serializing_if = "Option::is_none")]
1126 pub pasted_content: Option<Vec<Value>>,
1127 #[serde(default, skip_serializing_if = "Option::is_none")]
1131 pub historical: Option<bool>,
1132 #[serde(default, skip_serializing_if = "Option::is_none")]
1138 pub server_received_wall_ms: Option<f64>,
1139 #[serde(default, skip_serializing_if = "Option::is_none")]
1143 pub refused_message_id: Option<String>,
1144}
1145
1146impl UserMessage {
1147 pub fn tool_use_result_as<T: serde::de::DeserializeOwned>(
1155 &self,
1156 ) -> Option<Result<T, serde_json::Error>> {
1157 self.tool_use_result
1158 .as_ref()
1159 .map(|v| serde_json::from_value(v.clone()))
1160 }
1161
1162 pub fn subagent_result(&self) -> Option<SubagentResult> {
1173 self.tool_use_result
1174 .as_ref()
1175 .and_then(|v| serde_json::from_value(v.clone()).ok())
1176 }
1177}
1178
1179#[derive(Debug, Clone, Serialize, Deserialize)]
1190pub struct SubagentResult {
1191 #[serde(skip_serializing_if = "Option::is_none")]
1193 pub status: Option<String>,
1194 #[serde(skip_serializing_if = "Option::is_none")]
1196 pub prompt: Option<String>,
1197 #[serde(rename = "agentId", skip_serializing_if = "Option::is_none")]
1199 pub agent_id: Option<String>,
1200 #[serde(rename = "agentType", skip_serializing_if = "Option::is_none")]
1202 pub agent_type: Option<String>,
1203 #[serde(
1205 default,
1206 deserialize_with = "deserialize_content_blocks",
1207 skip_serializing_if = "Vec::is_empty"
1208 )]
1209 pub content: Vec<ContentBlock>,
1210 #[serde(rename = "resolvedModel", skip_serializing_if = "Option::is_none")]
1212 pub resolved_model: Option<String>,
1213 #[serde(rename = "totalDurationMs", skip_serializing_if = "Option::is_none")]
1215 pub total_duration_ms: Option<u64>,
1216 #[serde(rename = "totalTokens", skip_serializing_if = "Option::is_none")]
1218 pub total_tokens: Option<u64>,
1219 #[serde(rename = "totalToolUseCount", skip_serializing_if = "Option::is_none")]
1221 pub total_tool_use_count: Option<u64>,
1222 #[serde(skip_serializing_if = "Option::is_none")]
1224 pub usage: Option<super::result::UsageInfo>,
1225 #[serde(rename = "toolStats", skip_serializing_if = "Option::is_none")]
1227 pub tool_stats: Option<SubagentToolStats>,
1228}
1229
1230#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1235#[serde(rename_all = "camelCase")]
1236pub struct SubagentToolStats {
1237 #[serde(default)]
1238 pub read_count: u64,
1239 #[serde(default)]
1240 pub search_count: u64,
1241 #[serde(default)]
1242 pub bash_count: u64,
1243 #[serde(default)]
1244 pub edit_file_count: u64,
1245 #[serde(default)]
1246 pub lines_added: u64,
1247 #[serde(default)]
1248 pub lines_removed: u64,
1249 #[serde(default)]
1250 pub other_tool_count: u64,
1251 #[serde(flatten)]
1252 pub extra: serde_json::Map<String, Value>,
1253}
1254
1255#[derive(Debug, Clone, Default, PartialEq, Eq)]
1282pub struct SubagentUsageRollup {
1283 pub subagent_tokens: u64,
1286 pub agent_count: u32,
1288 pub tool_uses: u64,
1290 pub duration_ms: u64,
1292 seen_agent_ids: std::collections::BTreeSet<String>,
1293}
1294
1295impl SubagentUsageRollup {
1296 pub fn observe(&mut self, output: &ClaudeOutput) -> bool {
1302 match output {
1303 ClaudeOutput::User(user) => self.observe_user(user),
1304 _ => false,
1305 }
1306 }
1307
1308 pub fn observe_user(&mut self, user: &UserMessage) -> bool {
1315 let Some(result) = user.subagent_result() else {
1316 return false;
1317 };
1318 if result.total_tokens.is_none() && result.agent_id.is_none() {
1319 return false;
1320 }
1321 if let Some(agent_id) = &result.agent_id {
1322 if !self.seen_agent_ids.insert(agent_id.clone()) {
1323 return false;
1324 }
1325 }
1326 self.agent_count += 1;
1327 self.subagent_tokens += result.total_tokens.unwrap_or(0);
1328 self.tool_uses += result.total_tool_use_count.unwrap_or(0);
1329 self.duration_ms += result.total_duration_ms.unwrap_or(0);
1330 true
1331 }
1332}
1333
1334#[derive(Debug, Clone, Serialize, Deserialize)]
1336pub struct MessageContent {
1337 pub role: MessageRole,
1338 #[serde(deserialize_with = "deserialize_content_blocks")]
1339 pub content: Vec<ContentBlock>,
1340}
1341
1342#[derive(Debug, Clone, Serialize, Deserialize)]
1344pub struct SystemMessage {
1345 pub subtype: SystemSubtype,
1346 #[serde(flatten)]
1347 pub data: Value, }
1349
1350impl SystemMessage {
1351 pub fn is_init(&self) -> bool {
1353 self.subtype == SystemSubtype::Init
1354 }
1355
1356 pub fn is_status(&self) -> bool {
1358 self.subtype == SystemSubtype::Status
1359 }
1360
1361 pub fn is_compact_boundary(&self) -> bool {
1363 self.subtype == SystemSubtype::CompactBoundary
1364 }
1365
1366 pub fn as_init(&self) -> Option<InitMessage> {
1368 if self.subtype != SystemSubtype::Init {
1369 return None;
1370 }
1371 serde_json::from_value(self.data.clone()).ok()
1372 }
1373
1374 pub fn as_status(&self) -> Option<StatusMessage> {
1376 if self.subtype != SystemSubtype::Status {
1377 return None;
1378 }
1379 serde_json::from_value(self.data.clone()).ok()
1380 }
1381
1382 pub fn as_compact_boundary(&self) -> Option<CompactBoundaryMessage> {
1384 if self.subtype != SystemSubtype::CompactBoundary {
1385 return None;
1386 }
1387 serde_json::from_value(self.data.clone()).ok()
1388 }
1389
1390 pub fn is_task_started(&self) -> bool {
1392 self.subtype == SystemSubtype::TaskStarted
1393 }
1394
1395 pub fn is_task_progress(&self) -> bool {
1397 self.subtype == SystemSubtype::TaskProgress
1398 }
1399
1400 pub fn is_task_notification(&self) -> bool {
1402 self.subtype == SystemSubtype::TaskNotification
1403 }
1404
1405 pub fn as_task_started(&self) -> Option<TaskStartedMessage> {
1407 if self.subtype != SystemSubtype::TaskStarted {
1408 return None;
1409 }
1410 serde_json::from_value(self.data.clone()).ok()
1411 }
1412
1413 pub fn as_task_progress(&self) -> Option<TaskProgressMessage> {
1415 if self.subtype != SystemSubtype::TaskProgress {
1416 return None;
1417 }
1418 serde_json::from_value(self.data.clone()).ok()
1419 }
1420
1421 pub fn as_task_notification(&self) -> Option<TaskNotificationMessage> {
1423 if self.subtype != SystemSubtype::TaskNotification {
1424 return None;
1425 }
1426 serde_json::from_value(self.data.clone()).ok()
1427 }
1428
1429 pub fn is_task_updated(&self) -> bool {
1431 self.subtype == SystemSubtype::TaskUpdated
1432 }
1433
1434 pub fn as_task_updated(&self) -> Option<TaskUpdatedMessage> {
1436 if self.subtype != SystemSubtype::TaskUpdated {
1437 return None;
1438 }
1439 serde_json::from_value(self.data.clone()).ok()
1440 }
1441
1442 pub fn is_thinking_tokens(&self) -> bool {
1444 self.subtype == SystemSubtype::ThinkingTokens
1445 }
1446
1447 pub fn as_thinking_tokens(&self) -> Option<ThinkingTokensMessage> {
1449 if self.subtype != SystemSubtype::ThinkingTokens {
1450 return None;
1451 }
1452 serde_json::from_value(self.data.clone()).ok()
1453 }
1454
1455 pub fn is_code_change_published(&self) -> bool {
1457 self.subtype == SystemSubtype::CodeChangePublished
1458 }
1459
1460 pub fn as_code_change_published(&self) -> Option<CodeChangePublishedMessage> {
1462 if self.subtype != SystemSubtype::CodeChangePublished {
1463 return None;
1464 }
1465 serde_json::from_value(self.data.clone()).ok()
1466 }
1467
1468 pub fn is_vcs_state_changed(&self) -> bool {
1470 self.subtype == SystemSubtype::VcsStateChanged
1471 }
1472
1473 pub fn as_vcs_state_changed(&self) -> Option<VcsStateChangedMessage> {
1475 if self.subtype != SystemSubtype::VcsStateChanged {
1476 return None;
1477 }
1478 serde_json::from_value(self.data.clone()).ok()
1479 }
1480
1481 pub fn is_feedback_draft_queued(&self) -> bool {
1483 self.subtype == SystemSubtype::FeedbackDraftQueued
1484 }
1485
1486 pub fn as_feedback_draft_queued(&self) -> Option<FeedbackDraftQueuedMessage> {
1488 if self.subtype != SystemSubtype::FeedbackDraftQueued {
1489 return None;
1490 }
1491 serde_json::from_value(self.data.clone()).ok()
1492 }
1493
1494 pub fn is_cloud_session_delta(&self) -> bool {
1496 self.subtype == SystemSubtype::CloudSessionDelta
1497 }
1498
1499 pub fn as_cloud_session_delta(&self) -> Option<CloudSessionDeltaMessage> {
1501 if self.subtype != SystemSubtype::CloudSessionDelta {
1502 return None;
1503 }
1504 serde_json::from_value(self.data.clone()).ok()
1505 }
1506
1507 pub fn is_dev_intent(&self) -> bool {
1509 self.subtype == SystemSubtype::DevIntent
1510 }
1511
1512 pub fn as_dev_intent(&self) -> Option<DevIntentMessage> {
1514 if self.subtype != SystemSubtype::DevIntent {
1515 return None;
1516 }
1517 serde_json::from_value(self.data.clone()).ok()
1518 }
1519
1520 pub fn is_turn_handoff_available(&self) -> bool {
1522 self.subtype == SystemSubtype::TurnHandoffAvailable
1523 }
1524
1525 pub fn as_turn_handoff_available(&self) -> Option<TurnHandoffAvailableMessage> {
1527 if self.subtype != SystemSubtype::TurnHandoffAvailable {
1528 return None;
1529 }
1530 serde_json::from_value(self.data.clone()).ok()
1531 }
1532
1533 pub fn is_turn_preempted(&self) -> bool {
1535 self.subtype == SystemSubtype::TurnPreempted
1536 }
1537
1538 pub fn as_turn_preempted(&self) -> Option<TurnPreemptedMessage> {
1540 if self.subtype != SystemSubtype::TurnPreempted {
1541 return None;
1542 }
1543 serde_json::from_value(self.data.clone()).ok()
1544 }
1545
1546 pub fn is_peer_message_hold(&self) -> bool {
1548 self.subtype == SystemSubtype::PeerMessageHold
1549 }
1550
1551 pub fn as_peer_message_hold(&self) -> Option<PeerMessageHoldMessage> {
1553 if self.subtype != SystemSubtype::PeerMessageHold {
1554 return None;
1555 }
1556 serde_json::from_value(self.data.clone()).ok()
1557 }
1558
1559 pub fn is_per_turn_effort_changed(&self) -> bool {
1561 self.subtype == SystemSubtype::PerTurnEffortChanged
1562 }
1563
1564 pub fn as_per_turn_effort_changed(&self) -> Option<PerTurnEffortChangedMessage> {
1566 if self.subtype != SystemSubtype::PerTurnEffortChanged {
1567 return None;
1568 }
1569 serde_json::from_value(self.data.clone()).ok()
1570 }
1571
1572 pub fn is_session_title_changed(&self) -> bool {
1574 self.subtype == SystemSubtype::SessionTitleChanged
1575 }
1576
1577 pub fn as_session_title_changed(&self) -> Option<SessionTitleChangedMessage> {
1579 if self.subtype != SystemSubtype::SessionTitleChanged {
1580 return None;
1581 }
1582 serde_json::from_value(self.data.clone()).ok()
1583 }
1584
1585 pub fn is_instruction_size_warning(&self) -> bool {
1587 self.subtype == SystemSubtype::InstructionSizeWarning
1588 }
1589
1590 pub fn as_instruction_size_warning(&self) -> Option<InstructionSizeWarningMessage> {
1592 if self.subtype != SystemSubtype::InstructionSizeWarning {
1593 return None;
1594 }
1595 serde_json::from_value(self.data.clone()).ok()
1596 }
1597
1598 pub fn is_permission_check_status(&self) -> bool {
1600 self.subtype == SystemSubtype::PermissionCheckStatus
1601 }
1602
1603 pub fn as_permission_check_status(&self) -> Option<PermissionCheckStatusMessage> {
1605 if self.subtype != SystemSubtype::PermissionCheckStatus {
1606 return None;
1607 }
1608 serde_json::from_value(self.data.clone()).ok()
1609 }
1610
1611 pub fn is_file_attachments_missing(&self) -> bool {
1613 self.subtype == SystemSubtype::FileAttachmentsMissing
1614 }
1615
1616 pub fn as_file_attachments_missing(&self) -> Option<FileAttachmentsMissingMessage> {
1618 if self.subtype != SystemSubtype::FileAttachmentsMissing {
1619 return None;
1620 }
1621 serde_json::from_value(self.data.clone()).ok()
1622 }
1623
1624 pub fn is_ui_focus(&self) -> bool {
1626 self.subtype == SystemSubtype::UiFocus
1627 }
1628
1629 pub fn as_ui_focus(&self) -> Option<UiFocusMessage> {
1631 if self.subtype != SystemSubtype::UiFocus {
1632 return None;
1633 }
1634 serde_json::from_value(self.data.clone()).ok()
1635 }
1636
1637 pub fn is_ui_invalidate(&self) -> bool {
1639 self.subtype == SystemSubtype::UiInvalidate
1640 }
1641
1642 pub fn as_ui_invalidate(&self) -> Option<UiInvalidateMessage> {
1644 if self.subtype != SystemSubtype::UiInvalidate {
1645 return None;
1646 }
1647 serde_json::from_value(self.data.clone()).ok()
1648 }
1649
1650 pub fn is_ui_log(&self) -> bool {
1652 self.subtype == SystemSubtype::UiLog
1653 }
1654
1655 pub fn as_ui_log(&self) -> Option<UiLogMessage> {
1657 if self.subtype != SystemSubtype::UiLog {
1658 return None;
1659 }
1660 serde_json::from_value(self.data.clone()).ok()
1661 }
1662
1663 pub fn is_ui_panes(&self) -> bool {
1665 self.subtype == SystemSubtype::UiPanes
1666 }
1667
1668 pub fn as_ui_panes(&self) -> Option<UiPanesMessage> {
1670 if self.subtype != SystemSubtype::UiPanes {
1671 return None;
1672 }
1673 serde_json::from_value(self.data.clone()).ok()
1674 }
1675
1676 pub fn is_ui_scroll(&self) -> bool {
1678 self.subtype == SystemSubtype::UiScroll
1679 }
1680
1681 pub fn as_ui_scroll(&self) -> Option<UiScrollMessage> {
1683 if self.subtype != SystemSubtype::UiScroll {
1684 return None;
1685 }
1686 serde_json::from_value(self.data.clone()).ok()
1687 }
1688
1689 pub fn is_ui_status(&self) -> bool {
1691 self.subtype == SystemSubtype::UiStatus
1692 }
1693
1694 pub fn as_ui_status(&self) -> Option<UiStatusMessage> {
1696 if self.subtype != SystemSubtype::UiStatus {
1697 return None;
1698 }
1699 serde_json::from_value(self.data.clone()).ok()
1700 }
1701
1702 pub fn is_ui_toast(&self) -> bool {
1704 self.subtype == SystemSubtype::UiToast
1705 }
1706
1707 pub fn as_ui_toast(&self) -> Option<UiToastMessage> {
1709 if self.subtype != SystemSubtype::UiToast {
1710 return None;
1711 }
1712 serde_json::from_value(self.data.clone()).ok()
1713 }
1714
1715 pub fn as_known_system_event(&self) -> Option<KnownSystemEvent> {
1717 macro_rules! parse {
1718 ($variant:ident, $ty:ty) => {
1719 serde_json::from_value::<$ty>(self.data.clone())
1720 .ok()
1721 .map(KnownSystemEvent::$variant)
1722 };
1723 }
1724
1725 match self.subtype {
1726 SystemSubtype::Init => parse!(Init, InitMessage),
1727 SystemSubtype::Status => parse!(Status, StatusMessage),
1728 SystemSubtype::CompactBoundary => parse!(CompactBoundary, CompactBoundaryMessage),
1729 SystemSubtype::ThinkingTokens => parse!(ThinkingTokens, ThinkingTokensMessage),
1730 SystemSubtype::TaskStarted => parse!(TaskStarted, TaskStartedMessage),
1731 SystemSubtype::TaskProgress => parse!(TaskProgress, TaskProgressMessage),
1732 SystemSubtype::TaskUpdated => parse!(TaskUpdated, TaskUpdatedMessage),
1733 SystemSubtype::TaskNotification => parse!(TaskNotification, TaskNotificationMessage),
1734 SystemSubtype::ApiRetry => parse!(ApiRetry, ApiRetryMessage),
1735 SystemSubtype::ControlRequestProgress => {
1736 parse!(ControlRequestProgress, ControlRequestProgressMessage)
1737 }
1738 SystemSubtype::ModelRefusalFallback => {
1739 parse!(ModelRefusalFallback, ModelRefusalFallbackMessage)
1740 }
1741 SystemSubtype::ModelRefusalNoFallback => {
1742 parse!(ModelRefusalNoFallback, ModelRefusalNoFallbackMessage)
1743 }
1744 SystemSubtype::LocalCommandOutput => {
1745 parse!(LocalCommandOutput, LocalCommandOutputMessage)
1746 }
1747 SystemSubtype::HookStarted => parse!(HookStarted, HookStartedMessage),
1748 SystemSubtype::HookProgress => parse!(HookProgress, HookProgressMessage),
1749 SystemSubtype::HookResponse => parse!(HookResponse, HookResponseMessage),
1750 SystemSubtype::PluginInstall => parse!(PluginInstall, PluginInstallMessage),
1751 SystemSubtype::BackgroundTasksChanged => {
1752 parse!(BackgroundTasksChanged, BackgroundTasksChangedMessage)
1753 }
1754 SystemSubtype::SessionStateChanged => {
1755 parse!(SessionStateChanged, SessionStateChangedMessage)
1756 }
1757 SystemSubtype::WorkerShuttingDown => {
1758 parse!(WorkerShuttingDown, WorkerShuttingDownMessage)
1759 }
1760 SystemSubtype::CommandsChanged => parse!(CommandsChanged, CommandsChangedMessage),
1761 SystemSubtype::Notification => parse!(Notification, NotificationMessage),
1762 SystemSubtype::FilesPersisted => parse!(FilesPersisted, FilesPersistedMessage),
1763 SystemSubtype::MemoryRecall => parse!(MemoryRecall, MemoryRecallMessage),
1764 SystemSubtype::ElicitationComplete => {
1765 parse!(ElicitationComplete, ElicitationCompleteMessage)
1766 }
1767 SystemSubtype::PermissionDenied => parse!(PermissionDenied, PermissionDeniedMessage),
1768 SystemSubtype::MirrorError => parse!(MirrorError, MirrorErrorMessage),
1769 SystemSubtype::Informational => parse!(Informational, InformationalMessage),
1770 SystemSubtype::CodeChangePublished => {
1771 parse!(CodeChangePublished, CodeChangePublishedMessage)
1772 }
1773 SystemSubtype::VcsStateChanged => parse!(VcsStateChanged, VcsStateChangedMessage),
1774 SystemSubtype::FeedbackDraftQueued => {
1775 parse!(FeedbackDraftQueued, FeedbackDraftQueuedMessage)
1776 }
1777 SystemSubtype::CloudSessionDelta => {
1778 parse!(CloudSessionDelta, CloudSessionDeltaMessage)
1779 }
1780 SystemSubtype::DevIntent => parse!(DevIntent, DevIntentMessage),
1781 SystemSubtype::TurnHandoffAvailable => {
1782 parse!(TurnHandoffAvailable, TurnHandoffAvailableMessage)
1783 }
1784 SystemSubtype::TurnPreempted => parse!(TurnPreempted, TurnPreemptedMessage),
1785 SystemSubtype::PeerMessageHold => parse!(PeerMessageHold, PeerMessageHoldMessage),
1786 SystemSubtype::PerTurnEffortChanged => {
1787 parse!(PerTurnEffortChanged, PerTurnEffortChangedMessage)
1788 }
1789 SystemSubtype::SessionTitleChanged => {
1790 parse!(SessionTitleChanged, SessionTitleChangedMessage)
1791 }
1792 SystemSubtype::InstructionSizeWarning => {
1793 parse!(InstructionSizeWarning, InstructionSizeWarningMessage)
1794 }
1795 SystemSubtype::FileAttachmentsMissing => {
1796 parse!(FileAttachmentsMissing, FileAttachmentsMissingMessage)
1797 }
1798 SystemSubtype::PermissionCheckStatus => {
1799 parse!(PermissionCheckStatus, PermissionCheckStatusMessage)
1800 }
1801 SystemSubtype::UiFocus => parse!(UiFocus, UiFocusMessage),
1802 SystemSubtype::UiInvalidate => parse!(UiInvalidate, UiInvalidateMessage),
1803 SystemSubtype::UiLog => parse!(UiLog, UiLogMessage),
1804 SystemSubtype::UiPanes => parse!(UiPanes, UiPanesMessage),
1805 SystemSubtype::UiScroll => parse!(UiScroll, UiScrollMessage),
1806 SystemSubtype::UiStatus => parse!(UiStatus, UiStatusMessage),
1807 SystemSubtype::UiToast => parse!(UiToast, UiToastMessage),
1808 SystemSubtype::Unknown(_) => None,
1809 }
1810 }
1811
1812 pub fn typed_value(&self) -> Option<Value> {
1821 fn reserialize<T: Serialize>(parsed: Option<T>) -> Option<Value> {
1822 parsed.and_then(|v| serde_json::to_value(v).ok())
1823 }
1824 match self.subtype {
1825 SystemSubtype::Init => reserialize(self.as_init()),
1826 SystemSubtype::Status => reserialize(self.as_status()),
1827 SystemSubtype::CompactBoundary => reserialize(self.as_compact_boundary()),
1828 SystemSubtype::ThinkingTokens => reserialize(self.as_thinking_tokens()),
1829 SystemSubtype::TaskStarted => reserialize(self.as_task_started()),
1830 SystemSubtype::TaskProgress => reserialize(self.as_task_progress()),
1831 SystemSubtype::TaskUpdated => reserialize(self.as_task_updated()),
1832 SystemSubtype::TaskNotification => reserialize(self.as_task_notification()),
1833 SystemSubtype::ApiRetry => reserialize(parse_system::<ApiRetryMessage>(self)),
1834 SystemSubtype::ControlRequestProgress => {
1835 reserialize(parse_system::<ControlRequestProgressMessage>(self))
1836 }
1837 SystemSubtype::ModelRefusalFallback => {
1838 reserialize(parse_system::<ModelRefusalFallbackMessage>(self))
1839 }
1840 SystemSubtype::ModelRefusalNoFallback => {
1841 reserialize(parse_system::<ModelRefusalNoFallbackMessage>(self))
1842 }
1843 SystemSubtype::LocalCommandOutput => {
1844 reserialize(parse_system::<LocalCommandOutputMessage>(self))
1845 }
1846 SystemSubtype::HookStarted => reserialize(parse_system::<HookStartedMessage>(self)),
1847 SystemSubtype::HookProgress => reserialize(parse_system::<HookProgressMessage>(self)),
1848 SystemSubtype::HookResponse => reserialize(parse_system::<HookResponseMessage>(self)),
1849 SystemSubtype::PluginInstall => reserialize(parse_system::<PluginInstallMessage>(self)),
1850 SystemSubtype::BackgroundTasksChanged => {
1851 reserialize(parse_system::<BackgroundTasksChangedMessage>(self))
1852 }
1853 SystemSubtype::SessionStateChanged => {
1854 reserialize(parse_system::<SessionStateChangedMessage>(self))
1855 }
1856 SystemSubtype::WorkerShuttingDown => {
1857 reserialize(parse_system::<WorkerShuttingDownMessage>(self))
1858 }
1859 SystemSubtype::CommandsChanged => {
1860 reserialize(parse_system::<CommandsChangedMessage>(self))
1861 }
1862 SystemSubtype::Notification => reserialize(parse_system::<NotificationMessage>(self)),
1863 SystemSubtype::FilesPersisted => {
1864 reserialize(parse_system::<FilesPersistedMessage>(self))
1865 }
1866 SystemSubtype::MemoryRecall => reserialize(parse_system::<MemoryRecallMessage>(self)),
1867 SystemSubtype::ElicitationComplete => {
1868 reserialize(parse_system::<ElicitationCompleteMessage>(self))
1869 }
1870 SystemSubtype::PermissionDenied => {
1871 reserialize(parse_system::<PermissionDeniedMessage>(self))
1872 }
1873 SystemSubtype::MirrorError => reserialize(parse_system::<MirrorErrorMessage>(self)),
1874 SystemSubtype::Informational => reserialize(parse_system::<InformationalMessage>(self)),
1875 SystemSubtype::CodeChangePublished => {
1876 reserialize(parse_system::<CodeChangePublishedMessage>(self))
1877 }
1878 SystemSubtype::VcsStateChanged => {
1879 reserialize(parse_system::<VcsStateChangedMessage>(self))
1880 }
1881 SystemSubtype::FeedbackDraftQueued => {
1882 reserialize(parse_system::<FeedbackDraftQueuedMessage>(self))
1883 }
1884 SystemSubtype::CloudSessionDelta => {
1885 reserialize(parse_system::<CloudSessionDeltaMessage>(self))
1886 }
1887 SystemSubtype::DevIntent => reserialize(parse_system::<DevIntentMessage>(self)),
1888 SystemSubtype::TurnHandoffAvailable => {
1889 reserialize(parse_system::<TurnHandoffAvailableMessage>(self))
1890 }
1891 SystemSubtype::TurnPreempted => reserialize(parse_system::<TurnPreemptedMessage>(self)),
1892 SystemSubtype::PeerMessageHold => {
1893 reserialize(parse_system::<PeerMessageHoldMessage>(self))
1894 }
1895 SystemSubtype::PerTurnEffortChanged => {
1896 reserialize(parse_system::<PerTurnEffortChangedMessage>(self))
1897 }
1898 SystemSubtype::SessionTitleChanged => {
1899 reserialize(parse_system::<SessionTitleChangedMessage>(self))
1900 }
1901 SystemSubtype::InstructionSizeWarning => {
1902 reserialize(parse_system::<InstructionSizeWarningMessage>(self))
1903 }
1904 SystemSubtype::FileAttachmentsMissing => {
1905 reserialize(parse_system::<FileAttachmentsMissingMessage>(self))
1906 }
1907 SystemSubtype::PermissionCheckStatus => {
1908 reserialize(parse_system::<PermissionCheckStatusMessage>(self))
1909 }
1910 SystemSubtype::UiFocus => reserialize(parse_system::<UiFocusMessage>(self)),
1911 SystemSubtype::UiInvalidate => reserialize(parse_system::<UiInvalidateMessage>(self)),
1912 SystemSubtype::UiLog => reserialize(parse_system::<UiLogMessage>(self)),
1913 SystemSubtype::UiPanes => reserialize(parse_system::<UiPanesMessage>(self)),
1914 SystemSubtype::UiScroll => reserialize(parse_system::<UiScrollMessage>(self)),
1915 SystemSubtype::UiStatus => reserialize(parse_system::<UiStatusMessage>(self)),
1916 SystemSubtype::UiToast => reserialize(parse_system::<UiToastMessage>(self)),
1917 SystemSubtype::Unknown(_) => None,
1918 }
1919 }
1920}
1921
1922fn parse_system<T: serde::de::DeserializeOwned>(message: &SystemMessage) -> Option<T> {
1923 serde_json::from_value(message.data.clone()).ok()
1924}
1925
1926#[allow(clippy::large_enum_variant)]
1932#[derive(Debug, Clone, Serialize, Deserialize)]
1933pub enum KnownSystemEvent {
1934 Init(InitMessage),
1935 Status(StatusMessage),
1936 CompactBoundary(CompactBoundaryMessage),
1937 ThinkingTokens(ThinkingTokensMessage),
1938 TaskStarted(TaskStartedMessage),
1939 TaskProgress(TaskProgressMessage),
1940 TaskUpdated(TaskUpdatedMessage),
1941 TaskNotification(TaskNotificationMessage),
1942 ApiRetry(ApiRetryMessage),
1943 ControlRequestProgress(ControlRequestProgressMessage),
1944 ModelRefusalFallback(ModelRefusalFallbackMessage),
1945 ModelRefusalNoFallback(ModelRefusalNoFallbackMessage),
1946 LocalCommandOutput(LocalCommandOutputMessage),
1947 HookStarted(HookStartedMessage),
1948 HookProgress(HookProgressMessage),
1949 HookResponse(HookResponseMessage),
1950 PluginInstall(PluginInstallMessage),
1951 BackgroundTasksChanged(BackgroundTasksChangedMessage),
1952 SessionStateChanged(SessionStateChangedMessage),
1953 WorkerShuttingDown(WorkerShuttingDownMessage),
1954 CommandsChanged(CommandsChangedMessage),
1955 Notification(NotificationMessage),
1956 FilesPersisted(FilesPersistedMessage),
1957 MemoryRecall(MemoryRecallMessage),
1958 ElicitationComplete(ElicitationCompleteMessage),
1959 PermissionDenied(PermissionDeniedMessage),
1960 MirrorError(MirrorErrorMessage),
1961 Informational(InformationalMessage),
1962 CodeChangePublished(CodeChangePublishedMessage),
1963 VcsStateChanged(VcsStateChangedMessage),
1964 FeedbackDraftQueued(FeedbackDraftQueuedMessage),
1965 CloudSessionDelta(CloudSessionDeltaMessage),
1966 DevIntent(DevIntentMessage),
1967 TurnHandoffAvailable(TurnHandoffAvailableMessage),
1968 TurnPreempted(TurnPreemptedMessage),
1969 PeerMessageHold(PeerMessageHoldMessage),
1970 PerTurnEffortChanged(PerTurnEffortChangedMessage),
1971 SessionTitleChanged(SessionTitleChangedMessage),
1972 InstructionSizeWarning(InstructionSizeWarningMessage),
1973 FileAttachmentsMissing(FileAttachmentsMissingMessage),
1974 PermissionCheckStatus(PermissionCheckStatusMessage),
1975 UiFocus(UiFocusMessage),
1976 UiInvalidate(UiInvalidateMessage),
1977 UiLog(UiLogMessage),
1978 UiPanes(UiPanesMessage),
1979 UiScroll(UiScrollMessage),
1980 UiStatus(UiStatusMessage),
1981 UiToast(UiToastMessage),
1982}
1983
1984#[derive(Debug, Clone, Serialize, Deserialize)]
1985pub struct ApiRetryMessage {
1986 pub attempt: u64,
1987 pub max_retries: u64,
1988 pub retry_delay_ms: u64,
1989 pub error_status: Option<u16>,
1990 pub error: String,
1991 #[serde(default, skip_serializing_if = "Option::is_none")]
1996 pub no_response: Option<ApiRetryNoResponse>,
1997 #[serde(default, skip_serializing_if = "Option::is_none")]
1998 pub uuid: Option<String>,
1999 #[serde(default, skip_serializing_if = "Option::is_none")]
2000 pub session_id: Option<String>,
2001}
2002
2003#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2006pub struct ApiRetryNoResponse {
2007 pub waited_ms: u64,
2009 pub retry_wait_ms: u64,
2011}
2012
2013#[derive(Debug, Clone, Serialize, Deserialize)]
2014pub struct ControlRequestProgressMessage {
2015 pub request_id: String,
2016 pub status: String,
2017 #[serde(default, skip_serializing_if = "Option::is_none")]
2018 pub attempt: Option<u64>,
2019 #[serde(default, skip_serializing_if = "Option::is_none")]
2020 pub max_retries: Option<u64>,
2021 #[serde(default, skip_serializing_if = "Option::is_none")]
2022 pub retry_delay_ms: Option<u64>,
2023 #[serde(default, skip_serializing_if = "Option::is_none")]
2024 pub error_status: Option<u16>,
2025 #[serde(default, skip_serializing_if = "Option::is_none")]
2026 pub error: Option<String>,
2027 #[serde(default, skip_serializing_if = "Option::is_none")]
2028 pub uuid: Option<String>,
2029 #[serde(default, skip_serializing_if = "Option::is_none")]
2030 pub session_id: Option<String>,
2031}
2032
2033#[derive(Debug, Clone, Serialize, Deserialize)]
2034pub struct ModelRefusalFallbackMessage {
2035 pub trigger: String,
2036 pub direction: String,
2037 #[serde(default, skip_serializing_if = "Option::is_none")]
2043 pub scope: Option<RefusalFallbackScope>,
2044 pub original_model: String,
2045 pub fallback_model: String,
2046 pub request_id: Option<String>,
2047 #[serde(default, skip_serializing_if = "Option::is_none")]
2048 pub api_refusal_category: Option<String>,
2049 #[serde(default, skip_serializing_if = "Option::is_none")]
2054 pub saw_cyber_refusal: Option<bool>,
2055 #[serde(default, skip_serializing_if = "Option::is_none")]
2056 pub api_refusal_explanation: Option<String>,
2057 #[serde(default, skip_serializing_if = "Option::is_none")]
2058 pub retracted_message_uuids: Option<Vec<String>>,
2059 #[serde(default, skip_serializing_if = "Option::is_none")]
2060 pub refused_user_message_uuid: Option<String>,
2061 pub content: Value,
2062 #[serde(default, skip_serializing_if = "Option::is_none")]
2063 pub uuid: Option<String>,
2064 #[serde(default, skip_serializing_if = "Option::is_none")]
2065 pub session_id: Option<String>,
2066}
2067
2068#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2072pub enum RefusalFallbackScope {
2073 Session,
2075 Local,
2078 Unknown(String),
2080}
2081
2082impl RefusalFallbackScope {
2083 pub fn as_str(&self) -> &str {
2084 match self {
2085 Self::Session => "session",
2086 Self::Local => "local",
2087 Self::Unknown(s) => s.as_str(),
2088 }
2089 }
2090}
2091
2092impl fmt::Display for RefusalFallbackScope {
2093 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2094 f.write_str(self.as_str())
2095 }
2096}
2097
2098impl From<&str> for RefusalFallbackScope {
2099 fn from(s: &str) -> Self {
2100 match s {
2101 "session" => Self::Session,
2102 "local" => Self::Local,
2103 other => Self::Unknown(other.to_string()),
2104 }
2105 }
2106}
2107
2108impl Serialize for RefusalFallbackScope {
2109 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2110 serializer.serialize_str(self.as_str())
2111 }
2112}
2113
2114impl<'de> Deserialize<'de> for RefusalFallbackScope {
2115 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2116 let s = String::deserialize(deserializer)?;
2117 Ok(Self::from(s.as_str()))
2118 }
2119}
2120
2121#[derive(Debug, Clone, Serialize, Deserialize)]
2122pub struct ModelRefusalNoFallbackMessage {
2123 pub original_model: String,
2124 pub request_id: Option<String>,
2125 #[serde(default, skip_serializing_if = "Option::is_none")]
2126 pub api_refusal_category: Option<String>,
2127 #[serde(default, skip_serializing_if = "Option::is_none")]
2128 pub api_refusal_explanation: Option<String>,
2129 pub content: Value,
2130 #[serde(default, skip_serializing_if = "Option::is_none")]
2131 pub uuid: Option<String>,
2132 #[serde(default, skip_serializing_if = "Option::is_none")]
2133 pub session_id: Option<String>,
2134}
2135
2136#[derive(Debug, Clone, Serialize, Deserialize)]
2137pub struct LocalCommandOutputMessage {
2138 pub content: String,
2139 #[serde(default, skip_serializing_if = "Option::is_none")]
2140 pub uuid: Option<String>,
2141 #[serde(default, skip_serializing_if = "Option::is_none")]
2142 pub session_id: Option<String>,
2143}
2144
2145#[derive(Debug, Clone, Serialize, Deserialize)]
2146pub struct HookStartedMessage {
2147 pub hook_id: String,
2148 pub hook_name: String,
2149 pub hook_event: String,
2150 #[serde(default, skip_serializing_if = "Option::is_none")]
2151 pub uuid: Option<String>,
2152 #[serde(default, skip_serializing_if = "Option::is_none")]
2153 pub session_id: Option<String>,
2154}
2155
2156#[derive(Debug, Clone, Serialize, Deserialize)]
2157pub struct HookProgressMessage {
2158 pub hook_id: String,
2159 pub hook_name: String,
2160 pub hook_event: String,
2161 #[serde(default, skip_serializing_if = "Option::is_none")]
2162 pub stdout: Option<String>,
2163 #[serde(default, skip_serializing_if = "Option::is_none")]
2164 pub stderr: Option<String>,
2165 #[serde(default, skip_serializing_if = "Option::is_none")]
2166 pub output: Option<String>,
2167 #[serde(default, skip_serializing_if = "Option::is_none")]
2168 pub uuid: Option<String>,
2169 #[serde(default, skip_serializing_if = "Option::is_none")]
2170 pub session_id: Option<String>,
2171}
2172
2173#[derive(Debug, Clone, Serialize, Deserialize)]
2174pub struct HookResponseMessage {
2175 pub hook_id: String,
2176 pub hook_name: String,
2177 pub hook_event: String,
2178 #[serde(default, skip_serializing_if = "Option::is_none")]
2179 pub stdout: Option<String>,
2180 #[serde(default, skip_serializing_if = "Option::is_none")]
2181 pub stderr: Option<String>,
2182 #[serde(default, skip_serializing_if = "Option::is_none")]
2183 pub output: Option<String>,
2184 #[serde(default, skip_serializing_if = "Option::is_none")]
2185 pub exit_code: Option<i32>,
2186 pub outcome: String,
2187 #[serde(default, skip_serializing_if = "Option::is_none")]
2188 pub uuid: Option<String>,
2189 #[serde(default, skip_serializing_if = "Option::is_none")]
2190 pub session_id: Option<String>,
2191}
2192
2193#[derive(Debug, Clone, Serialize, Deserialize)]
2194pub struct PluginInstallMessage {
2195 pub status: String,
2196 #[serde(default, skip_serializing_if = "Option::is_none")]
2197 pub name: Option<String>,
2198 #[serde(default, skip_serializing_if = "Option::is_none")]
2199 pub error: Option<String>,
2200 #[serde(default, skip_serializing_if = "Option::is_none")]
2201 pub uuid: Option<String>,
2202 #[serde(default, skip_serializing_if = "Option::is_none")]
2203 pub session_id: Option<String>,
2204}
2205
2206#[derive(Debug, Clone, Serialize, Deserialize)]
2207pub struct BackgroundTasksChangedMessage {
2208 pub tasks: Vec<BackgroundTaskInfo>,
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 BackgroundTaskInfo {
2217 pub task_id: String,
2218 #[serde(default, skip_serializing_if = "Option::is_none")]
2221 pub run_id: Option<String>,
2222 pub task_type: String,
2223 pub description: String,
2224 #[serde(default, skip_serializing_if = "Option::is_none")]
2227 pub parent_task_id: Option<String>,
2228 #[serde(default, skip_serializing_if = "Option::is_none")]
2231 pub ambient: Option<bool>,
2232}
2233
2234#[derive(Debug, Clone, Serialize, Deserialize)]
2235pub struct SessionStateChangedMessage {
2236 pub state: String,
2237 #[serde(default, skip_serializing_if = "Option::is_none")]
2238 pub uuid: Option<String>,
2239 #[serde(default, skip_serializing_if = "Option::is_none")]
2240 pub session_id: Option<String>,
2241}
2242
2243#[derive(Debug, Clone, Serialize, Deserialize)]
2244pub struct WorkerShuttingDownMessage {
2245 pub reason: String,
2246 #[serde(default, skip_serializing_if = "Option::is_none")]
2247 pub uuid: Option<String>,
2248 #[serde(default, skip_serializing_if = "Option::is_none")]
2249 pub session_id: Option<String>,
2250}
2251
2252#[derive(Debug, Clone, Serialize, Deserialize)]
2253pub struct CommandsChangedMessage {
2254 pub commands: Vec<CommandInfo>,
2255 #[serde(default, skip_serializing_if = "Option::is_none")]
2256 pub uuid: Option<String>,
2257 #[serde(default, skip_serializing_if = "Option::is_none")]
2258 pub session_id: Option<String>,
2259}
2260
2261#[derive(Debug, Clone, Serialize, Deserialize)]
2262pub struct CommandInfo {
2263 pub name: String,
2264 pub description: String,
2265 #[serde(rename = "argumentHint")]
2266 pub argument_hint: String,
2267 #[serde(default, skip_serializing_if = "Option::is_none")]
2268 pub aliases: Option<Vec<String>>,
2269 #[serde(default, skip_serializing_if = "Option::is_none")]
2275 pub builtin: Option<bool>,
2276}
2277
2278#[derive(Debug, Clone, Serialize, Deserialize)]
2279pub struct NotificationMessage {
2280 pub key: String,
2281 pub text: String,
2282 pub priority: String,
2283 #[serde(default, skip_serializing_if = "Option::is_none")]
2284 pub color: Option<String>,
2285 #[serde(default, skip_serializing_if = "Option::is_none")]
2286 pub timeout_ms: Option<u64>,
2287 #[serde(default, skip_serializing_if = "Option::is_none")]
2288 pub uuid: Option<String>,
2289 #[serde(default, skip_serializing_if = "Option::is_none")]
2290 pub session_id: Option<String>,
2291}
2292
2293#[derive(Debug, Clone, Serialize, Deserialize)]
2294pub struct FilesPersistedMessage {
2295 pub files: Vec<PersistedFile>,
2296 pub failed: Vec<FailedPersistedFile>,
2297 pub processed_at: String,
2298 #[serde(default, skip_serializing_if = "Option::is_none")]
2299 pub uuid: Option<String>,
2300 #[serde(default, skip_serializing_if = "Option::is_none")]
2301 pub session_id: Option<String>,
2302}
2303
2304#[derive(Debug, Clone, Serialize, Deserialize)]
2305pub struct PersistedFile {
2306 pub filename: String,
2307 pub file_id: String,
2308}
2309
2310#[derive(Debug, Clone, Serialize, Deserialize)]
2311pub struct FailedPersistedFile {
2312 pub filename: String,
2313 pub error: String,
2314}
2315
2316#[derive(Debug, Clone, Serialize, Deserialize)]
2317pub struct MemoryRecallMessage {
2318 pub mode: String,
2319 pub memories: Vec<MemoryRecallItem>,
2320 #[serde(default, skip_serializing_if = "Option::is_none")]
2321 pub uuid: Option<String>,
2322 #[serde(default, skip_serializing_if = "Option::is_none")]
2323 pub session_id: Option<String>,
2324}
2325
2326#[derive(Debug, Clone, Serialize, Deserialize)]
2327pub struct MemoryRecallItem {
2328 pub path: String,
2329 pub scope: String,
2330 #[serde(default, skip_serializing_if = "Option::is_none")]
2331 pub content: Option<String>,
2332}
2333
2334#[derive(Debug, Clone, Serialize, Deserialize)]
2335pub struct ElicitationCompleteMessage {
2336 pub mcp_server_name: String,
2337 pub elicitation_id: String,
2338 #[serde(default, skip_serializing_if = "Option::is_none")]
2339 pub uuid: Option<String>,
2340 #[serde(default, skip_serializing_if = "Option::is_none")]
2341 pub session_id: Option<String>,
2342}
2343
2344#[derive(Debug, Clone, Serialize, Deserialize)]
2345pub struct PermissionDeniedMessage {
2346 pub tool_name: String,
2347 pub tool_use_id: String,
2348 #[serde(default, skip_serializing_if = "Option::is_none")]
2349 pub agent_id: Option<String>,
2350 #[serde(default, skip_serializing_if = "Option::is_none")]
2351 pub decision_reason_type: Option<String>,
2352 #[serde(default, skip_serializing_if = "Option::is_none")]
2356 pub decision_reason_code: Option<PermissionDeniedReasonCode>,
2357 #[serde(default, skip_serializing_if = "Option::is_none")]
2358 pub decision_reason: Option<String>,
2359 pub message: String,
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, PartialEq, Eq, Hash)]
2369pub enum PermissionDeniedReasonCode {
2370 ClassifierTranscriptTooLong,
2372 OutsideReadsBlocked,
2374 MemoryPaused,
2376 Unknown(String),
2378}
2379
2380impl PermissionDeniedReasonCode {
2381 pub fn as_str(&self) -> &str {
2382 match self {
2383 Self::ClassifierTranscriptTooLong => "classifier_transcript_too_long",
2384 Self::OutsideReadsBlocked => "outside_reads_blocked",
2385 Self::MemoryPaused => "memory_paused",
2386 Self::Unknown(s) => s.as_str(),
2387 }
2388 }
2389}
2390
2391impl fmt::Display for PermissionDeniedReasonCode {
2392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2393 f.write_str(self.as_str())
2394 }
2395}
2396
2397impl From<&str> for PermissionDeniedReasonCode {
2398 fn from(s: &str) -> Self {
2399 match s {
2400 "classifier_transcript_too_long" => Self::ClassifierTranscriptTooLong,
2401 "outside_reads_blocked" => Self::OutsideReadsBlocked,
2402 "memory_paused" => Self::MemoryPaused,
2403 other => Self::Unknown(other.to_string()),
2404 }
2405 }
2406}
2407
2408impl Serialize for PermissionDeniedReasonCode {
2409 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2410 serializer.serialize_str(self.as_str())
2411 }
2412}
2413
2414impl<'de> Deserialize<'de> for PermissionDeniedReasonCode {
2415 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2416 let s = String::deserialize(deserializer)?;
2417 Ok(Self::from(s.as_str()))
2418 }
2419}
2420
2421#[derive(Debug, Clone, Serialize, Deserialize)]
2422pub struct MirrorErrorMessage {
2423 pub error: String,
2424 pub key: MirrorErrorKey,
2425 #[serde(default, skip_serializing_if = "Option::is_none")]
2426 pub uuid: Option<String>,
2427 #[serde(default, skip_serializing_if = "Option::is_none")]
2428 pub session_id: Option<String>,
2429}
2430
2431#[derive(Debug, Clone, Serialize, Deserialize)]
2432pub struct MirrorErrorKey {
2433 #[serde(rename = "projectKey")]
2434 pub project_key: String,
2435 #[serde(rename = "sessionId")]
2436 pub session_id: String,
2437 #[serde(default, skip_serializing_if = "Option::is_none")]
2438 pub subpath: Option<String>,
2439}
2440
2441#[derive(Debug, Clone, Serialize, Deserialize)]
2442pub struct InformationalMessage {
2443 pub content: String,
2444 pub level: String,
2445 #[serde(default, skip_serializing_if = "Option::is_none")]
2446 pub tool_use_id: Option<String>,
2447 #[serde(default, skip_serializing_if = "Option::is_none")]
2448 pub prevent_continuation: Option<bool>,
2449 #[serde(default, skip_serializing_if = "Option::is_none")]
2452 pub tag: Option<String>,
2453 #[serde(default, skip_serializing_if = "Option::is_none")]
2454 pub uuid: Option<String>,
2455 #[serde(default, skip_serializing_if = "Option::is_none")]
2456 pub session_id: Option<String>,
2457}
2458
2459#[derive(Debug, Clone, Serialize, Deserialize)]
2461pub struct PluginInfo {
2462 pub name: String,
2464 pub path: String,
2466 #[serde(skip_serializing_if = "Option::is_none")]
2468 pub source: Option<String>,
2469 #[serde(default, skip_serializing_if = "Option::is_none")]
2471 pub version: Option<String>,
2472}
2473
2474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2476pub struct PluginDiagnostic {
2477 pub plugin: String,
2478 #[serde(rename = "type")]
2479 pub diagnostic_type: String,
2480 pub message: String,
2481}
2482
2483#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2485pub struct MemoryPaths {
2486 #[serde(default, skip_serializing_if = "Option::is_none")]
2487 pub auto: Option<String>,
2488 #[serde(default, skip_serializing_if = "Option::is_none")]
2489 pub team: Option<String>,
2490 #[serde(flatten)]
2491 pub extra: serde_json::Map<String, Value>,
2492}
2493
2494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2498pub struct McpServerError {
2499 pub name: String,
2500 #[serde(rename = "type")]
2502 pub error_type: String,
2503 pub message: String,
2504}
2505
2506#[derive(Debug, Clone, Serialize, Deserialize)]
2508pub struct InitMessage {
2509 pub session_id: String,
2511 #[serde(skip_serializing_if = "Option::is_none")]
2513 pub cwd: Option<String>,
2514 #[serde(skip_serializing_if = "Option::is_none")]
2516 pub model: Option<String>,
2517 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2519 pub tools: Vec<String>,
2520 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2522 pub mcp_servers: Vec<Value>,
2523 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2525 pub slash_commands: Vec<String>,
2526 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2529 pub terminal_slash_commands: Vec<String>,
2530 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2532 pub agents: Vec<String>,
2533 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2535 pub plugins: Vec<PluginInfo>,
2536 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2538 pub skills: Vec<Value>,
2539 #[serde(skip_serializing_if = "Option::is_none")]
2541 pub claude_code_version: Option<String>,
2542 #[serde(skip_serializing_if = "Option::is_none")]
2545 pub messaging_socket_path: Option<String>,
2546 #[serde(skip_serializing_if = "Option::is_none", rename = "apiKeySource")]
2548 pub api_key_source: Option<ApiKeySource>,
2549 #[serde(skip_serializing_if = "Option::is_none")]
2551 pub output_style: Option<OutputStyle>,
2552 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2554 pub permission_mode: Option<InitPermissionMode>,
2555
2556 #[serde(skip_serializing_if = "Option::is_none")]
2558 pub uuid: Option<String>,
2559
2560 #[serde(skip_serializing_if = "Option::is_none")]
2562 pub memory_paths: Option<MemoryPaths>,
2563
2564 #[serde(default, skip_serializing_if = "Option::is_none")]
2569 pub scratchpad_path: Option<String>,
2570
2571 #[serde(skip_serializing_if = "Option::is_none")]
2573 pub fast_mode_state: Option<String>,
2574
2575 #[serde(default, skip_serializing_if = "Option::is_none")]
2577 pub fast_mode_disabled_reason: Option<super::result::FastModeDisabledReason>,
2578
2579 #[serde(default, skip_serializing_if = "Option::is_none")]
2582 pub mcp_server_errors: Option<Vec<McpServerError>>,
2583
2584 #[serde(default, skip_serializing_if = "Option::is_none")]
2586 pub analytics_disabled: Option<bool>,
2587
2588 #[serde(default, skip_serializing_if = "Option::is_none")]
2590 pub product_feedback_disabled: Option<bool>,
2591
2592 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2594 pub betas: Vec<String>,
2595
2596 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2598 pub capabilities: Vec<String>,
2599
2600 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2602 pub plugin_errors: Vec<PluginDiagnostic>,
2603
2604 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2606 pub plugin_warnings: Vec<PluginDiagnostic>,
2607
2608 #[serde(default, skip_serializing_if = "Option::is_none")]
2613 pub effort: Option<String>,
2614
2615 #[serde(default, skip_serializing_if = "Option::is_none")]
2620 pub cloud_session: Option<Value>,
2621
2622 #[serde(default, skip_serializing_if = "Option::is_none")]
2626 pub footer_indicator: Option<FooterIndicator>,
2627
2628 #[serde(default, skip_serializing_if = "Option::is_none")]
2632 pub worker_epoch: Option<u64>,
2633
2634 #[serde(default, skip_serializing_if = "Option::is_none")]
2638 pub powershell_path: Option<Option<String>>,
2639
2640 #[serde(default, skip_serializing_if = "Option::is_none")]
2644 pub startup_timing: Option<serde_json::Map<String, Value>>,
2645
2646 #[serde(default, skip_serializing_if = "Option::is_none")]
2656 pub per_turn_effort_active: Option<bool>,
2657
2658 #[serde(default, skip_serializing_if = "Option::is_none")]
2665 pub view_mode: Option<ViewMode>,
2666}
2667
2668#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2672pub struct FooterIndicator {
2673 pub text: String,
2676}
2677
2678#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2681pub enum ViewMode {
2682 Focus,
2685 Default,
2687 Unknown(String),
2689}
2690
2691impl ViewMode {
2692 pub fn as_str(&self) -> &str {
2693 match self {
2694 Self::Focus => "focus",
2695 Self::Default => "default",
2696 Self::Unknown(s) => s.as_str(),
2697 }
2698 }
2699}
2700
2701impl fmt::Display for ViewMode {
2702 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2703 f.write_str(self.as_str())
2704 }
2705}
2706
2707impl From<&str> for ViewMode {
2708 fn from(s: &str) -> Self {
2709 match s {
2710 "focus" => Self::Focus,
2711 "default" => Self::Default,
2712 other => Self::Unknown(other.to_string()),
2713 }
2714 }
2715}
2716
2717impl Serialize for ViewMode {
2718 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2719 serializer.serialize_str(self.as_str())
2720 }
2721}
2722
2723impl<'de> Deserialize<'de> for ViewMode {
2724 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2725 let s = String::deserialize(deserializer)?;
2726 Ok(Self::from(s.as_str()))
2727 }
2728}
2729
2730#[derive(Debug, Clone, Serialize, Deserialize)]
2732pub struct StatusMessage {
2733 pub session_id: String,
2735 pub status: Option<StatusMessageStatus>,
2737 #[serde(skip_serializing_if = "Option::is_none")]
2739 pub uuid: Option<String>,
2740 #[serde(skip_serializing_if = "Option::is_none", rename = "permissionMode")]
2742 pub permission_mode: Option<InitPermissionMode>,
2743 #[serde(skip_serializing_if = "Option::is_none")]
2744 pub compact_result: Option<String>,
2745 #[serde(skip_serializing_if = "Option::is_none")]
2746 pub compact_error: Option<String>,
2747}
2748
2749#[derive(Debug, Clone, Serialize, Deserialize)]
2751pub struct CompactBoundaryMessage {
2752 pub session_id: String,
2754 pub compact_metadata: CompactMetadata,
2756 #[serde(
2760 default,
2761 skip_serializing_if = "Option::is_none",
2762 alias = "content",
2763 alias = "text"
2764 )]
2765 pub summary: Option<String>,
2766 #[serde(
2770 default,
2771 skip_serializing_if = "Option::is_none",
2772 alias = "message_count"
2773 )]
2774 pub leaf_message_count: Option<u32>,
2775 #[serde(default, skip_serializing_if = "Option::is_none")]
2777 pub duration_ms: Option<u64>,
2778 #[serde(skip_serializing_if = "Option::is_none")]
2780 pub uuid: Option<String>,
2781 #[serde(skip_serializing_if = "Option::is_none")]
2783 pub logical_parent_uuid: Option<Option<String>>,
2784 #[serde(default, skip_serializing_if = "Option::is_none")]
2788 pub historical: Option<bool>,
2789}
2790
2791#[derive(Debug, Clone, Serialize, Deserialize)]
2793pub struct CompactMetadata {
2794 pub pre_tokens: u64,
2796 pub trigger: CompactionTrigger,
2798 #[serde(default, skip_serializing_if = "Option::is_none")]
2799 pub post_tokens: Option<u64>,
2800 #[serde(default, skip_serializing_if = "Option::is_none")]
2801 pub cumulative_dropped_tokens: Option<u64>,
2802 #[serde(default, skip_serializing_if = "Option::is_none")]
2803 pub duration_ms: Option<u64>,
2804 #[serde(default, skip_serializing_if = "Option::is_none")]
2805 pub user_context: Option<String>,
2806 #[serde(default, skip_serializing_if = "Option::is_none")]
2807 pub messages_summarized: Option<u64>,
2808 #[serde(default, skip_serializing_if = "Option::is_none")]
2809 pub precomputed: Option<bool>,
2810 #[serde(default, skip_serializing_if = "Option::is_none")]
2811 pub pre_compact_discovered_tools: Option<Vec<String>>,
2812 #[serde(default, skip_serializing_if = "Option::is_none")]
2817 pub pre_compact_artifact_read_versions: Option<Vec<ArtifactReadVersion>>,
2818 #[serde(default, skip_serializing_if = "Option::is_none")]
2819 pub preserved_segment: Option<PreservedSegment>,
2820 #[serde(default, skip_serializing_if = "Option::is_none")]
2821 pub preserved_messages: Option<PreservedMessages>,
2822}
2823
2824#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
2827pub struct ArtifactReadVersion {
2828 pub slug: String,
2830 pub ver: String,
2832}
2833
2834#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2835pub struct PreservedSegment {
2836 pub head_uuid: String,
2837 pub anchor_uuid: String,
2838 pub tail_uuid: String,
2839}
2840
2841#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2842pub struct PreservedMessages {
2843 pub anchor_uuid: String,
2844 pub uuids: Vec<String>,
2845 #[serde(default, skip_serializing_if = "Option::is_none")]
2846 pub all_uuids: Option<Vec<String>>,
2847}
2848
2849#[derive(Debug, Clone, Serialize, Deserialize)]
2855pub struct TaskUsage {
2856 pub duration_ms: u64,
2858 pub tool_uses: u64,
2860 pub total_tokens: u64,
2862}
2863
2864#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2866pub enum TaskType {
2867 LocalAgent,
2869 LocalBash,
2871 LocalWorkflow,
2873 Unknown(String),
2875}
2876
2877impl TaskType {
2878 pub fn as_str(&self) -> &str {
2879 match self {
2880 Self::LocalAgent => "local_agent",
2881 Self::LocalBash => "local_bash",
2882 Self::LocalWorkflow => "local_workflow",
2883 Self::Unknown(s) => s.as_str(),
2884 }
2885 }
2886}
2887
2888impl fmt::Display for TaskType {
2889 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2890 f.write_str(self.as_str())
2891 }
2892}
2893
2894impl From<&str> for TaskType {
2895 fn from(s: &str) -> Self {
2896 match s {
2897 "local_agent" => Self::LocalAgent,
2898 "local_bash" => Self::LocalBash,
2899 "local_workflow" => Self::LocalWorkflow,
2900 other => Self::Unknown(other.to_string()),
2901 }
2902 }
2903}
2904
2905impl Serialize for TaskType {
2906 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2907 serializer.serialize_str(self.as_str())
2908 }
2909}
2910
2911impl<'de> Deserialize<'de> for TaskType {
2912 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2913 let s = String::deserialize(deserializer)?;
2914 Ok(Self::from(s.as_str()))
2915 }
2916}
2917
2918#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2920pub enum TaskStatus {
2921 Pending,
2922 Running,
2923 Completed,
2924 Failed,
2925 Killed,
2926 Paused,
2927 Stopped,
2928 Unknown(String),
2929}
2930
2931impl TaskStatus {
2932 pub fn as_str(&self) -> &str {
2933 match self {
2934 Self::Pending => "pending",
2935 Self::Running => "running",
2936 Self::Completed => "completed",
2937 Self::Failed => "failed",
2938 Self::Killed => "killed",
2939 Self::Paused => "paused",
2940 Self::Stopped => "stopped",
2941 Self::Unknown(s) => s.as_str(),
2942 }
2943 }
2944}
2945
2946impl fmt::Display for TaskStatus {
2947 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2948 f.write_str(self.as_str())
2949 }
2950}
2951
2952impl From<&str> for TaskStatus {
2953 fn from(s: &str) -> Self {
2954 match s {
2955 "pending" => Self::Pending,
2956 "running" => Self::Running,
2957 "completed" => Self::Completed,
2958 "failed" => Self::Failed,
2959 "killed" => Self::Killed,
2960 "paused" => Self::Paused,
2961 "stopped" => Self::Stopped,
2962 other => Self::Unknown(other.to_string()),
2963 }
2964 }
2965}
2966
2967impl Serialize for TaskStatus {
2968 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
2969 serializer.serialize_str(self.as_str())
2970 }
2971}
2972
2973impl<'de> Deserialize<'de> for TaskStatus {
2974 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
2975 let s = String::deserialize(deserializer)?;
2976 Ok(Self::from(s.as_str()))
2977 }
2978}
2979
2980#[derive(Debug, Clone, Serialize, Deserialize)]
2982pub struct TaskStartedMessage {
2983 pub session_id: String,
2984 pub task_id: String,
2985 #[serde(default, skip_serializing_if = "Option::is_none")]
2991 pub run_id: Option<String>,
2992 #[serde(default, skip_serializing_if = "Option::is_none")]
2998 pub parent_task_id: Option<String>,
2999 #[serde(default, skip_serializing_if = "Option::is_none")]
3000 pub task_type: Option<TaskType>,
3001 #[serde(default, skip_serializing_if = "Option::is_none")]
3002 pub tool_use_id: Option<String>,
3003 pub description: String,
3004 #[serde(default, skip_serializing_if = "Option::is_none")]
3007 pub subagent_type: Option<String>,
3008 #[serde(default, skip_serializing_if = "Option::is_none")]
3014 pub is_backgrounded: Option<bool>,
3015 #[serde(default, skip_serializing_if = "Option::is_none")]
3019 pub spawn_depth: Option<u32>,
3020 #[serde(default, skip_serializing_if = "Option::is_none")]
3022 pub prompt: Option<String>,
3023 #[serde(default, skip_serializing_if = "Option::is_none")]
3024 pub workflow_name: Option<String>,
3025 #[serde(default, skip_serializing_if = "Option::is_none")]
3026 pub skip_transcript: Option<bool>,
3027 #[serde(default, skip_serializing_if = "Option::is_none")]
3032 pub ambient: Option<bool>,
3033 pub uuid: String,
3034}
3035
3036#[derive(Debug, Clone, Serialize, Deserialize)]
3040pub struct TaskUpdatedMessage {
3041 pub session_id: String,
3042 pub task_id: String,
3043 #[serde(default, skip_serializing_if = "Option::is_none")]
3046 pub run_id: Option<String>,
3047 pub patch: TaskPatch,
3048 pub uuid: String,
3049}
3050
3051#[derive(Debug, Clone, Default, Serialize, Deserialize)]
3054pub struct TaskPatch {
3055 #[serde(default, skip_serializing_if = "Option::is_none")]
3056 pub status: Option<TaskStatus>,
3057 #[serde(default, skip_serializing_if = "Option::is_none")]
3060 pub end_time: Option<u64>,
3061 #[serde(default, skip_serializing_if = "Option::is_none")]
3062 pub description: Option<String>,
3063 #[serde(default, skip_serializing_if = "Option::is_none")]
3064 pub total_paused_ms: Option<u64>,
3065 #[serde(default, skip_serializing_if = "Option::is_none")]
3066 pub error: Option<String>,
3067 #[serde(default, skip_serializing_if = "Option::is_none")]
3068 pub is_backgrounded: Option<bool>,
3069}
3070
3071#[derive(Debug, Clone, Serialize, Deserialize)]
3074pub struct ThinkingTokensMessage {
3075 pub session_id: String,
3076 pub estimated_tokens: u64,
3078 pub estimated_tokens_delta: u64,
3080 #[serde(default, skip_serializing_if = "Option::is_none")]
3087 pub user_message_uuid: Option<String>,
3088 pub uuid: String,
3089}
3090
3091#[derive(Debug, Clone, Serialize, Deserialize)]
3094pub struct TaskProgressMessage {
3095 pub session_id: String,
3096 pub task_id: String,
3097 #[serde(default, skip_serializing_if = "Option::is_none")]
3100 pub run_id: Option<String>,
3101 #[serde(default, skip_serializing_if = "Option::is_none")]
3102 pub tool_use_id: Option<String>,
3103 pub description: String,
3104 #[serde(default, skip_serializing_if = "Option::is_none")]
3105 pub last_tool_name: Option<String>,
3106 pub usage: TaskUsage,
3107 #[serde(default, skip_serializing_if = "Option::is_none")]
3109 pub subagent_type: Option<String>,
3110 #[serde(default, skip_serializing_if = "Option::is_none")]
3111 pub summary: Option<String>,
3112 pub uuid: String,
3113}
3114
3115#[derive(Debug, Clone, Serialize, Deserialize)]
3118pub struct TaskNotificationMessage {
3119 pub session_id: String,
3120 pub task_id: String,
3121 #[serde(default, skip_serializing_if = "Option::is_none")]
3124 pub run_id: Option<String>,
3125 pub status: TaskStatus,
3126 #[serde(default, skip_serializing_if = "Option::is_none")]
3129 pub reason: Option<TaskEndReason>,
3130 pub summary: String,
3131 pub output_file: Option<String>,
3132 #[serde(skip_serializing_if = "Option::is_none")]
3133 pub tool_use_id: Option<String>,
3134 #[serde(skip_serializing_if = "Option::is_none")]
3135 pub usage: Option<TaskUsage>,
3136 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3142 pub resource_links: Vec<ResourceLink>,
3143 #[serde(default, skip_serializing_if = "Option::is_none")]
3150 pub handback: Option<TaskHandback>,
3151 #[serde(default, skip_serializing_if = "Option::is_none")]
3155 pub handback_report: Option<HandbackReport>,
3156 #[serde(default, skip_serializing_if = "Option::is_none")]
3157 pub skip_transcript: Option<bool>,
3158 #[serde(default, skip_serializing_if = "Option::is_none")]
3161 pub ambient: Option<bool>,
3162 #[serde(skip_serializing_if = "Option::is_none")]
3163 pub uuid: Option<String>,
3164}
3165
3166#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3169pub enum TaskHandback {
3170 Send,
3172 Flagged,
3174 Withheld,
3176 Unknown(String),
3178}
3179
3180impl TaskHandback {
3181 pub fn as_str(&self) -> &str {
3182 match self {
3183 Self::Send => "send",
3184 Self::Flagged => "flagged",
3185 Self::Withheld => "withheld",
3186 Self::Unknown(s) => s.as_str(),
3187 }
3188 }
3189}
3190
3191impl fmt::Display for TaskHandback {
3192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3193 f.write_str(self.as_str())
3194 }
3195}
3196
3197impl From<&str> for TaskHandback {
3198 fn from(s: &str) -> Self {
3199 match s {
3200 "send" => Self::Send,
3201 "flagged" => Self::Flagged,
3202 "withheld" => Self::Withheld,
3203 other => Self::Unknown(other.to_string()),
3204 }
3205 }
3206}
3207
3208impl Serialize for TaskHandback {
3209 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3210 serializer.serialize_str(self.as_str())
3211 }
3212}
3213
3214impl<'de> Deserialize<'de> for TaskHandback {
3215 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3216 let s = String::deserialize(deserializer)?;
3217 Ok(Self::from(s.as_str()))
3218 }
3219}
3220
3221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3224pub struct HandbackReport {
3225 pub text: String,
3228 #[serde(default, skip_serializing_if = "Option::is_none")]
3231 pub warning: Option<String>,
3232}
3233
3234#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3236pub enum AssistantErrorKind {
3237 AuthenticationFailed,
3238 OauthOrgNotAllowed,
3239 AccountOnHold,
3240 BillingError,
3241 RateLimit,
3242 Overloaded,
3243 InvalidRequest,
3244 ModelNotFound,
3245 ServerError,
3246 UnknownError,
3247 MaxOutputTokens,
3248 Unknown(String),
3249}
3250
3251impl AssistantErrorKind {
3252 pub fn as_str(&self) -> &str {
3253 match self {
3254 Self::AuthenticationFailed => "authentication_failed",
3255 Self::OauthOrgNotAllowed => "oauth_org_not_allowed",
3256 Self::AccountOnHold => "account_on_hold",
3257 Self::BillingError => "billing_error",
3258 Self::RateLimit => "rate_limit",
3259 Self::Overloaded => "overloaded",
3260 Self::InvalidRequest => "invalid_request",
3261 Self::ModelNotFound => "model_not_found",
3262 Self::ServerError => "server_error",
3263 Self::UnknownError => "unknown",
3264 Self::MaxOutputTokens => "max_output_tokens",
3265 Self::Unknown(s) => s.as_str(),
3266 }
3267 }
3268}
3269
3270impl fmt::Display for AssistantErrorKind {
3271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3272 f.write_str(self.as_str())
3273 }
3274}
3275
3276impl From<&str> for AssistantErrorKind {
3277 fn from(s: &str) -> Self {
3278 match s {
3279 "authentication_failed" => Self::AuthenticationFailed,
3280 "oauth_org_not_allowed" => Self::OauthOrgNotAllowed,
3281 "account_on_hold" => Self::AccountOnHold,
3282 "billing_error" => Self::BillingError,
3283 "rate_limit" => Self::RateLimit,
3284 "overloaded" => Self::Overloaded,
3285 "invalid_request" => Self::InvalidRequest,
3286 "model_not_found" => Self::ModelNotFound,
3287 "server_error" => Self::ServerError,
3288 "unknown" => Self::UnknownError,
3289 "max_output_tokens" => Self::MaxOutputTokens,
3290 other => Self::Unknown(other.to_string()),
3291 }
3292 }
3293}
3294
3295impl Serialize for AssistantErrorKind {
3296 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3297 serializer.serialize_str(self.as_str())
3298 }
3299}
3300
3301impl<'de> Deserialize<'de> for AssistantErrorKind {
3302 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3303 let s = String::deserialize(deserializer)?;
3304 Ok(Self::from(s.as_str()))
3305 }
3306}
3307
3308#[derive(Debug, Clone, Serialize, Deserialize)]
3315pub struct CodeChangePublishedMessage {
3316 pub provider: String,
3320 pub url: String,
3322 pub repo: String,
3325 pub identifier: String,
3327 #[serde(default, skip_serializing_if = "Option::is_none")]
3336 pub action: Option<String>,
3337 #[serde(default, skip_serializing_if = "Option::is_none")]
3345 pub branch: Option<String>,
3346 pub uuid: String,
3347 pub session_id: String,
3348}
3349
3350#[derive(Debug, Clone, Serialize, Deserialize)]
3355pub struct VcsStateChangedMessage {
3356 pub kind: VcsMutationKind,
3359 pub cwd: String,
3362 #[serde(default, skip_serializing_if = "Option::is_none")]
3367 pub branch: Option<String>,
3368 pub uuid: String,
3369 pub session_id: String,
3370}
3371
3372#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3374pub enum VcsMutationKind {
3375 Commit,
3376 Push,
3377 Merge,
3378 Rebase,
3379 Unknown(String),
3381}
3382
3383impl VcsMutationKind {
3384 pub fn as_str(&self) -> &str {
3385 match self {
3386 Self::Commit => "commit",
3387 Self::Push => "push",
3388 Self::Merge => "merge",
3389 Self::Rebase => "rebase",
3390 Self::Unknown(s) => s.as_str(),
3391 }
3392 }
3393}
3394
3395impl fmt::Display for VcsMutationKind {
3396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3397 f.write_str(self.as_str())
3398 }
3399}
3400
3401impl From<&str> for VcsMutationKind {
3402 fn from(s: &str) -> Self {
3403 match s {
3404 "commit" => Self::Commit,
3405 "push" => Self::Push,
3406 "merge" => Self::Merge,
3407 "rebase" => Self::Rebase,
3408 other => Self::Unknown(other.to_string()),
3409 }
3410 }
3411}
3412
3413impl Serialize for VcsMutationKind {
3414 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3415 serializer.serialize_str(self.as_str())
3416 }
3417}
3418
3419impl<'de> Deserialize<'de> for VcsMutationKind {
3420 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3421 let s = String::deserialize(deserializer)?;
3422 Ok(Self::from(s.as_str()))
3423 }
3424}
3425
3426#[derive(Debug, Clone, Serialize, Deserialize)]
3428pub struct FeedbackDraftQueuedMessage {
3429 pub draft_id: String,
3430 pub draft_type: String,
3431 pub title: String,
3432 pub details_preview: String,
3433 #[serde(default, skip_serializing_if = "Option::is_none")]
3434 pub uuid: Option<String>,
3435 #[serde(default, skip_serializing_if = "Option::is_none")]
3436 pub session_id: Option<String>,
3437 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3438 pub extra: serde_json::Map<String, Value>,
3439}
3440
3441#[derive(Debug, Clone, Serialize, Deserialize)]
3449pub struct CloudSessionDeltaMessage {
3450 pub seq: u64,
3454 pub changed: Vec<String>,
3458 pub cloud_session: Value,
3462 pub uuid: String,
3463 pub session_id: String,
3464 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3465 pub extra: serde_json::Map<String, Value>,
3466}
3467
3468#[derive(Debug, Clone, Serialize, Deserialize)]
3484pub struct DevIntentMessage {
3485 pub kind: DevIntentKind,
3487 #[serde(default, skip_serializing_if = "Option::is_none")]
3493 pub trigger: Option<DevIntentTrigger>,
3494 pub uuid: String,
3495 pub session_id: String,
3496 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3497 pub extra: serde_json::Map<String, Value>,
3498}
3499
3500#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3504pub enum DevIntentKind {
3505 IosApp,
3511 AndroidApp,
3518 Unknown(String),
3520}
3521
3522impl DevIntentKind {
3523 pub fn as_str(&self) -> &str {
3524 match self {
3525 Self::IosApp => "ios_app",
3526 Self::AndroidApp => "android_app",
3527 Self::Unknown(s) => s.as_str(),
3528 }
3529 }
3530}
3531
3532impl fmt::Display for DevIntentKind {
3533 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3534 f.write_str(self.as_str())
3535 }
3536}
3537
3538impl From<&str> for DevIntentKind {
3539 fn from(s: &str) -> Self {
3540 match s {
3541 "ios_app" => Self::IosApp,
3542 "android_app" => Self::AndroidApp,
3543 other => Self::Unknown(other.to_string()),
3544 }
3545 }
3546}
3547
3548impl Serialize for DevIntentKind {
3549 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3550 serializer.serialize_str(self.as_str())
3551 }
3552}
3553
3554impl<'de> Deserialize<'de> for DevIntentKind {
3555 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3556 let s = String::deserialize(deserializer)?;
3557 Ok(Self::from(s.as_str()))
3558 }
3559}
3560
3561#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3565pub enum DevIntentTrigger {
3566 SwiftEdit,
3568 UikitImport,
3570 XcodeProject,
3573 IosCommand,
3576 KotlinEdit,
3578 JavaEdit,
3580 AndroidImport,
3582 AndroidManifest,
3584 GradlePlugin,
3586 AndroidCommand,
3589 ProjectScan,
3592 Unknown(String),
3594}
3595
3596impl DevIntentTrigger {
3597 pub fn as_str(&self) -> &str {
3598 match self {
3599 Self::SwiftEdit => "swift_edit",
3600 Self::UikitImport => "uikit_import",
3601 Self::XcodeProject => "xcode_project",
3602 Self::IosCommand => "ios_command",
3603 Self::KotlinEdit => "kotlin_edit",
3604 Self::JavaEdit => "java_edit",
3605 Self::AndroidImport => "android_import",
3606 Self::AndroidManifest => "android_manifest",
3607 Self::GradlePlugin => "gradle_plugin",
3608 Self::AndroidCommand => "android_command",
3609 Self::ProjectScan => "project_scan",
3610 Self::Unknown(s) => s.as_str(),
3611 }
3612 }
3613}
3614
3615impl fmt::Display for DevIntentTrigger {
3616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3617 f.write_str(self.as_str())
3618 }
3619}
3620
3621impl From<&str> for DevIntentTrigger {
3622 fn from(s: &str) -> Self {
3623 match s {
3624 "swift_edit" => Self::SwiftEdit,
3625 "uikit_import" => Self::UikitImport,
3626 "xcode_project" => Self::XcodeProject,
3627 "ios_command" => Self::IosCommand,
3628 "kotlin_edit" => Self::KotlinEdit,
3629 "java_edit" => Self::JavaEdit,
3630 "android_import" => Self::AndroidImport,
3631 "android_manifest" => Self::AndroidManifest,
3632 "gradle_plugin" => Self::GradlePlugin,
3633 "android_command" => Self::AndroidCommand,
3634 "project_scan" => Self::ProjectScan,
3635 other => Self::Unknown(other.to_string()),
3636 }
3637 }
3638}
3639
3640impl Serialize for DevIntentTrigger {
3641 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3642 serializer.serialize_str(self.as_str())
3643 }
3644}
3645
3646impl<'de> Deserialize<'de> for DevIntentTrigger {
3647 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3648 let s = String::deserialize(deserializer)?;
3649 Ok(Self::from(s.as_str()))
3650 }
3651}
3652
3653#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3657pub enum TaskEndReason {
3658 WorkerRestart,
3661 Unknown(String),
3663}
3664
3665impl TaskEndReason {
3666 pub fn as_str(&self) -> &str {
3667 match self {
3668 Self::WorkerRestart => "worker_restart",
3669 Self::Unknown(s) => s.as_str(),
3670 }
3671 }
3672}
3673
3674impl fmt::Display for TaskEndReason {
3675 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3676 f.write_str(self.as_str())
3677 }
3678}
3679
3680impl From<&str> for TaskEndReason {
3681 fn from(s: &str) -> Self {
3682 match s {
3683 "worker_restart" => Self::WorkerRestart,
3684 other => Self::Unknown(other.to_string()),
3685 }
3686 }
3687}
3688
3689impl Serialize for TaskEndReason {
3690 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3691 serializer.serialize_str(self.as_str())
3692 }
3693}
3694
3695impl<'de> Deserialize<'de> for TaskEndReason {
3696 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3697 let s = String::deserialize(deserializer)?;
3698 Ok(Self::from(s.as_str()))
3699 }
3700}
3701
3702#[derive(Debug, Clone, Serialize, Deserialize)]
3711pub struct TurnHandoffAvailableMessage {
3712 pub v: u64,
3714 pub tools: Vec<String>,
3716 pub worker_epoch: u64,
3718 #[serde(default, skip_serializing_if = "Option::is_none")]
3722 pub relay_marker: Option<bool>,
3723 #[serde(default, skip_serializing_if = "Option::is_none")]
3731 pub staged_files: Option<bool>,
3732 #[serde(default, skip_serializing_if = "Option::is_none")]
3739 pub no_query_first: Option<bool>,
3740 #[serde(default, skip_serializing_if = "Option::is_none")]
3747 pub home_files: Option<bool>,
3748 #[serde(default, skip_serializing_if = "Option::is_none")]
3752 pub file_names: Option<bool>,
3753 #[serde(default, skip_serializing_if = "Option::is_none")]
3763 pub carried_writes: Option<bool>,
3764 pub uuid: String,
3765 pub session_id: String,
3766 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3767 pub extra: serde_json::Map<String, Value>,
3768}
3769
3770#[derive(Debug, Clone, Serialize, Deserialize)]
3782pub struct TurnPreemptedMessage {
3783 pub reason: String,
3787 pub preempted_by_uuid: String,
3791 #[serde(default)]
3794 pub preempted_message_uuids: Vec<String>,
3795 pub uuid: String,
3796 pub session_id: String,
3797 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3798 pub extra: serde_json::Map<String, Value>,
3799}
3800
3801#[derive(Debug, Clone, Serialize, Deserialize)]
3809pub struct PeerMessageHoldMessage {
3810 pub state: PeerMessageHoldState,
3816 #[serde(default, skip_serializing_if = "Option::is_none")]
3823 pub message_uuid: Option<String>,
3824 pub lane: PeerMessageLane,
3826 pub from: String,
3831 #[serde(default, skip_serializing_if = "Option::is_none")]
3834 pub from_name: Option<String>,
3835 #[serde(default, skip_serializing_if = "Option::is_none")]
3837 pub cause: Option<PeerMessageHoldCause>,
3838 #[serde(default, skip_serializing_if = "Option::is_none")]
3840 pub outcome: Option<PeerMessageHoldOutcome>,
3841 pub uuid: String,
3842 pub session_id: String,
3843 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
3844 pub extra: serde_json::Map<String, Value>,
3845}
3846
3847#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3849pub enum PeerMessageHoldState {
3850 Held,
3852 Released,
3854 Dropped,
3856 Unknown(String),
3858}
3859
3860impl PeerMessageHoldState {
3861 pub fn as_str(&self) -> &str {
3862 match self {
3863 Self::Held => "held",
3864 Self::Released => "released",
3865 Self::Dropped => "dropped",
3866 Self::Unknown(s) => s.as_str(),
3867 }
3868 }
3869}
3870
3871impl fmt::Display for PeerMessageHoldState {
3872 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3873 f.write_str(self.as_str())
3874 }
3875}
3876
3877impl From<&str> for PeerMessageHoldState {
3878 fn from(s: &str) -> Self {
3879 match s {
3880 "held" => Self::Held,
3881 "released" => Self::Released,
3882 "dropped" => Self::Dropped,
3883 other => Self::Unknown(other.to_string()),
3884 }
3885 }
3886}
3887
3888impl Serialize for PeerMessageHoldState {
3889 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3890 serializer.serialize_str(self.as_str())
3891 }
3892}
3893
3894impl<'de> Deserialize<'de> for PeerMessageHoldState {
3895 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3896 let s = String::deserialize(deserializer)?;
3897 Ok(Self::from(s.as_str()))
3898 }
3899}
3900
3901#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3903pub enum PeerMessageLane {
3904 Bridge,
3906 Stdin,
3908 Socket,
3910 Unknown(String),
3912}
3913
3914impl PeerMessageLane {
3915 pub fn as_str(&self) -> &str {
3916 match self {
3917 Self::Bridge => "bridge",
3918 Self::Stdin => "stdin",
3919 Self::Socket => "socket",
3920 Self::Unknown(s) => s.as_str(),
3921 }
3922 }
3923}
3924
3925impl fmt::Display for PeerMessageLane {
3926 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3927 f.write_str(self.as_str())
3928 }
3929}
3930
3931impl From<&str> for PeerMessageLane {
3932 fn from(s: &str) -> Self {
3933 match s {
3934 "bridge" => Self::Bridge,
3935 "stdin" => Self::Stdin,
3936 "socket" => Self::Socket,
3937 other => Self::Unknown(other.to_string()),
3938 }
3939 }
3940}
3941
3942impl Serialize for PeerMessageLane {
3943 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
3944 serializer.serialize_str(self.as_str())
3945 }
3946}
3947
3948impl<'de> Deserialize<'de> for PeerMessageLane {
3949 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
3950 let s = String::deserialize(deserializer)?;
3951 Ok(Self::from(s.as_str()))
3952 }
3953}
3954
3955#[derive(Debug, Clone, PartialEq, Eq, Hash)]
3962pub enum PeerMessageHoldCause {
3963 ExplicitSetting,
3964 ManagedSetting,
3965 RepoSetting,
3966 InvalidSetting,
3967 BypassDefault,
3968 ModeUnknown,
3969 ModeMismatch,
3970 NoModeAsserted,
3971 Unknown(String),
3973}
3974
3975impl PeerMessageHoldCause {
3976 pub fn as_str(&self) -> &str {
3977 match self {
3978 Self::ExplicitSetting => "explicit-setting",
3979 Self::ManagedSetting => "managed-setting",
3980 Self::RepoSetting => "repo-setting",
3981 Self::InvalidSetting => "invalid-setting",
3982 Self::BypassDefault => "bypass-default",
3983 Self::ModeUnknown => "mode-unknown",
3984 Self::ModeMismatch => "mode-mismatch",
3985 Self::NoModeAsserted => "no-mode-asserted",
3986 Self::Unknown(s) => s.as_str(),
3987 }
3988 }
3989}
3990
3991impl fmt::Display for PeerMessageHoldCause {
3992 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3993 f.write_str(self.as_str())
3994 }
3995}
3996
3997impl From<&str> for PeerMessageHoldCause {
3998 fn from(s: &str) -> Self {
3999 match s {
4000 "explicit-setting" => Self::ExplicitSetting,
4001 "managed-setting" => Self::ManagedSetting,
4002 "repo-setting" => Self::RepoSetting,
4003 "invalid-setting" => Self::InvalidSetting,
4004 "bypass-default" => Self::BypassDefault,
4005 "mode-unknown" => Self::ModeUnknown,
4006 "mode-mismatch" => Self::ModeMismatch,
4007 "no-mode-asserted" => Self::NoModeAsserted,
4008 other => Self::Unknown(other.to_string()),
4009 }
4010 }
4011}
4012
4013impl Serialize for PeerMessageHoldCause {
4014 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4015 serializer.serialize_str(self.as_str())
4016 }
4017}
4018
4019impl<'de> Deserialize<'de> for PeerMessageHoldCause {
4020 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4021 let s = String::deserialize(deserializer)?;
4022 Ok(Self::from(s.as_str()))
4023 }
4024}
4025
4026#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4029pub enum PeerMessageHoldOutcome {
4030 Expired,
4034 Refused,
4036 Dropped,
4039 Discarded,
4041 Unknown(String),
4043}
4044
4045impl PeerMessageHoldOutcome {
4046 pub fn as_str(&self) -> &str {
4047 match self {
4048 Self::Expired => "expired",
4049 Self::Refused => "refused",
4050 Self::Dropped => "dropped",
4051 Self::Discarded => "discarded",
4052 Self::Unknown(s) => s.as_str(),
4053 }
4054 }
4055}
4056
4057impl fmt::Display for PeerMessageHoldOutcome {
4058 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4059 f.write_str(self.as_str())
4060 }
4061}
4062
4063impl From<&str> for PeerMessageHoldOutcome {
4064 fn from(s: &str) -> Self {
4065 match s {
4066 "expired" => Self::Expired,
4067 "refused" => Self::Refused,
4068 "dropped" => Self::Dropped,
4069 "discarded" => Self::Discarded,
4070 other => Self::Unknown(other.to_string()),
4071 }
4072 }
4073}
4074
4075impl Serialize for PeerMessageHoldOutcome {
4076 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4077 serializer.serialize_str(self.as_str())
4078 }
4079}
4080
4081impl<'de> Deserialize<'de> for PeerMessageHoldOutcome {
4082 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4083 let s = String::deserialize(deserializer)?;
4084 Ok(Self::from(s.as_str()))
4085 }
4086}
4087
4088#[derive(Debug, Clone, Serialize, Deserialize)]
4099pub struct PerTurnEffortChangedMessage {
4100 pub per_turn_effort_active: bool,
4102 pub uuid: String,
4103 pub session_id: String,
4104}
4105
4106#[derive(Debug, Clone, Serialize, Deserialize)]
4113pub struct InstructionSizeWarningMessage {
4114 pub total_chars: u64,
4117 pub total_limit_chars: u64,
4120 pub file_count: u64,
4123 #[serde(default, skip_serializing_if = "Option::is_none")]
4127 pub largest_chars: Option<u64>,
4128 pub uuid: String,
4129 pub session_id: String,
4130}
4131
4132#[derive(Debug, Clone, Serialize, Deserialize)]
4139pub struct FileAttachmentsMissingMessage {
4140 pub message_uuid: String,
4143 pub sent_count: u64,
4145 pub missing: Vec<MissingFileAttachment>,
4147 pub uuid: String,
4148 pub session_id: String,
4149}
4150
4151#[derive(Debug, Clone, Serialize, Deserialize)]
4161pub struct PermissionCheckStatusMessage {
4162 pub tool_use_id: String,
4163 #[serde(default, skip_serializing_if = "Option::is_none")]
4166 pub agent_id: Option<String>,
4167 pub status: PermissionCheckStatus,
4168 pub uuid: String,
4169 pub session_id: String,
4170}
4171
4172#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4174pub enum PermissionCheckStatus {
4175 Checking,
4178 Done,
4180 Unknown(String),
4182}
4183
4184impl PermissionCheckStatus {
4185 pub fn as_str(&self) -> &str {
4186 match self {
4187 Self::Checking => "checking",
4188 Self::Done => "done",
4189 Self::Unknown(s) => s.as_str(),
4190 }
4191 }
4192}
4193
4194impl fmt::Display for PermissionCheckStatus {
4195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4196 f.write_str(self.as_str())
4197 }
4198}
4199
4200impl From<&str> for PermissionCheckStatus {
4201 fn from(s: &str) -> Self {
4202 match s {
4203 "checking" => Self::Checking,
4204 "done" => Self::Done,
4205 other => Self::Unknown(other.to_string()),
4206 }
4207 }
4208}
4209
4210impl Serialize for PermissionCheckStatus {
4211 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4212 serializer.serialize_str(self.as_str())
4213 }
4214}
4215
4216impl<'de> Deserialize<'de> for PermissionCheckStatus {
4217 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4218 let s = String::deserialize(deserializer)?;
4219 Ok(Self::from(s.as_str()))
4220 }
4221}
4222
4223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4225pub struct MissingFileAttachment {
4226 pub file_uuid: String,
4228 pub reason: String,
4232}
4233
4234#[derive(Debug, Clone, Serialize, Deserialize)]
4244pub struct SessionTitleChangedMessage {
4245 pub title: String,
4250 pub uuid: String,
4251 pub session_id: String,
4252}
4253
4254#[derive(Debug, Clone, Serialize, Deserialize)]
4258pub struct UiLogMessage {
4259 pub plugin: String,
4261 pub text: String,
4265 pub uuid: String,
4266 pub session_id: String,
4267}
4268
4269#[derive(Debug, Clone, Serialize, Deserialize)]
4274pub struct UiToastMessage {
4275 pub plugin: String,
4277 pub text: String,
4281 pub timeout_ms: u64,
4284 pub uuid: String,
4285 pub session_id: String,
4286}
4287
4288#[derive(Debug, Clone, Serialize, Deserialize)]
4292pub struct UiStatusMessage {
4293 pub plugin: String,
4295 pub text: Option<String>,
4300 pub uuid: String,
4301 pub session_id: String,
4302}
4303
4304#[derive(Debug, Clone, Serialize, Deserialize)]
4309pub struct UiInvalidateMessage {
4310 pub event: String,
4313 #[serde(default, skip_serializing_if = "Option::is_none")]
4318 pub instances: Option<Vec<UiInvalidatedInstance>>,
4319 pub uuid: String,
4320 pub session_id: String,
4321}
4322
4323#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4325pub struct UiInvalidatedInstance {
4326 pub surface: String,
4328 pub component: String,
4330 pub instance_id: String,
4333}
4334
4335#[derive(Debug, Clone, Serialize, Deserialize)]
4342pub struct UiPanesMessage {
4343 pub panes: Vec<UiPane>,
4346 pub shown_id: Option<String>,
4349 pub focused_id: Option<String>,
4352 pub focus_requested_id: Option<String>,
4356 pub uuid: String,
4357 pub session_id: String,
4358}
4359
4360#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4362pub struct UiPane {
4363 pub id: String,
4366 pub title: String,
4369 pub plugin: String,
4371 #[serde(default, skip_serializing_if = "Option::is_none")]
4374 pub close_on_escape: Option<bool>,
4375 #[serde(default, skip_serializing_if = "Option::is_none")]
4378 pub hold_toasts: Option<bool>,
4379 #[serde(default, skip_serializing_if = "Option::is_none")]
4382 pub rows: Option<u64>,
4383 #[serde(default, skip_serializing_if = "Option::is_none")]
4386 pub columns: Option<u64>,
4387}
4388
4389#[derive(Debug, Clone, Serialize, Deserialize)]
4393pub struct UiScrollMessage {
4394 pub client_id: String,
4396 pub component: UiSiteComponent,
4398 pub instance_id: String,
4400 pub offset: u64,
4403 #[serde(default, skip_serializing_if = "Option::is_none")]
4406 pub follow_end: Option<bool>,
4407 pub uuid: String,
4408 pub session_id: String,
4409}
4410
4411#[derive(Debug, Clone, Serialize, Deserialize)]
4416pub struct UiFocusMessage {
4417 pub client_id: String,
4419 pub component: UiSiteComponent,
4421 pub instance_id: String,
4423 pub plugin: String,
4425 pub key: String,
4427 pub uuid: String,
4428 pub session_id: String,
4429}
4430
4431#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4433pub enum UiSiteComponent {
4434 Pane,
4436 AbovePrompt,
4438 Unknown(String),
4440}
4441
4442impl UiSiteComponent {
4443 pub fn as_str(&self) -> &str {
4444 match self {
4445 Self::Pane => "Pane",
4446 Self::AbovePrompt => "AbovePrompt",
4447 Self::Unknown(s) => s.as_str(),
4448 }
4449 }
4450}
4451
4452impl fmt::Display for UiSiteComponent {
4453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4454 f.write_str(self.as_str())
4455 }
4456}
4457
4458impl From<&str> for UiSiteComponent {
4459 fn from(s: &str) -> Self {
4460 match s {
4461 "Pane" => Self::Pane,
4462 "AbovePrompt" => Self::AbovePrompt,
4463 other => Self::Unknown(other.to_string()),
4464 }
4465 }
4466}
4467
4468impl Serialize for UiSiteComponent {
4469 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4470 serializer.serialize_str(self.as_str())
4471 }
4472}
4473
4474impl<'de> Deserialize<'de> for UiSiteComponent {
4475 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4476 let s = String::deserialize(deserializer)?;
4477 Ok(Self::from(s.as_str()))
4478 }
4479}
4480
4481#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4484pub struct LocalCommandRun {
4485 pub command: String,
4487 pub args: String,
4490}
4491
4492#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4496pub struct LocalCommandOutcome {
4497 pub kind: LocalCommandOutcomeKind,
4498 #[serde(default, skip_serializing_if = "Option::is_none")]
4501 pub suggestion: Option<String>,
4502}
4503
4504#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4509pub enum LocalCommandOutcomeKind {
4510 UnavailableHeadless,
4512 Unknown,
4515 Failed,
4517 RestartRequired,
4519 Other(String),
4521}
4522
4523impl LocalCommandOutcomeKind {
4524 pub fn as_str(&self) -> &str {
4525 match self {
4526 Self::UnavailableHeadless => "unavailable_headless",
4527 Self::Unknown => "unknown",
4528 Self::Failed => "failed",
4529 Self::RestartRequired => "restart_required",
4530 Self::Other(s) => s.as_str(),
4531 }
4532 }
4533}
4534
4535impl fmt::Display for LocalCommandOutcomeKind {
4536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4537 f.write_str(self.as_str())
4538 }
4539}
4540
4541impl From<&str> for LocalCommandOutcomeKind {
4542 fn from(s: &str) -> Self {
4543 match s {
4544 "unavailable_headless" => Self::UnavailableHeadless,
4545 "unknown" => Self::Unknown,
4546 "failed" => Self::Failed,
4547 "restart_required" => Self::RestartRequired,
4548 other => Self::Other(other.to_string()),
4549 }
4550 }
4551}
4552
4553impl Serialize for LocalCommandOutcomeKind {
4554 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
4555 serializer.serialize_str(self.as_str())
4556 }
4557}
4558
4559impl<'de> Deserialize<'de> for LocalCommandOutcomeKind {
4560 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
4561 let s = String::deserialize(deserializer)?;
4562 Ok(Self::from(s.as_str()))
4563 }
4564}
4565
4566#[derive(Debug, Clone, Serialize, Deserialize)]
4572pub struct UsageReport {
4573 pub session: super::control::UsageSession,
4575 pub rate_limits: Option<UsageReportRateLimits>,
4579 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4580 pub extra: serde_json::Map<String, Value>,
4581}
4582
4583#[derive(Debug, Clone, Serialize, Deserialize)]
4585pub struct UsageReportRateLimits {
4586 pub limits: Option<Vec<UsageReportLimit>>,
4594 #[serde(default, skip_serializing_if = "Option::is_none")]
4597 pub extra_usage: Option<UsageReportExtraUsage>,
4598 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4599 pub extra: serde_json::Map<String, Value>,
4600}
4601
4602#[derive(Debug, Clone, Serialize, Deserialize)]
4604pub struct UsageReportLimit {
4605 pub kind: String,
4608 pub group: String,
4611 pub percent: f64,
4613 pub resets_at: Option<String>,
4615 #[serde(default, skip_serializing_if = "Option::is_none")]
4618 pub scope: Option<UsageReportScope>,
4619 #[serde(default, skip_serializing_if = "Option::is_none")]
4623 pub severity: Option<String>,
4624 #[serde(default, skip_serializing_if = "Option::is_none")]
4626 pub is_active: Option<bool>,
4627 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4628 pub extra: serde_json::Map<String, Value>,
4629}
4630
4631#[derive(Debug, Clone, Serialize, Deserialize)]
4633pub struct UsageReportScope {
4634 #[serde(default, skip_serializing_if = "Option::is_none")]
4635 pub model: Option<UsageReportScopeLabel>,
4636 #[serde(default, skip_serializing_if = "Option::is_none")]
4637 pub surface: Option<UsageReportScopeLabel>,
4638 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4639 pub extra: serde_json::Map<String, Value>,
4640}
4641
4642#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4644pub struct UsageReportScopeLabel {
4645 pub display_name: String,
4646}
4647
4648#[derive(Debug, Clone, Serialize, Deserialize)]
4652pub struct UsageReportExtraUsage {
4653 pub is_enabled: bool,
4655 pub monthly_limit: Option<f64>,
4656 pub used_credits: Option<f64>,
4657 pub utilization: Option<f64>,
4658 #[serde(default, skip_serializing_if = "Option::is_none")]
4659 pub currency: Option<String>,
4660 #[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
4661 pub extra: serde_json::Map<String, Value>,
4662}
4663
4664#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4667pub struct BatchToolUse {
4668 pub id: String,
4669 pub name: String,
4670}
4671
4672#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4677pub struct ContextUsage {
4678 pub model: String,
4680 pub total_tokens: u64,
4683 pub raw_max_tokens: u64,
4686 pub percentage: u64,
4688 #[serde(default, skip_serializing_if = "Option::is_none")]
4690 pub over_limit: Option<ContextOverLimit>,
4691 #[serde(default)]
4693 pub categories: Vec<ContextCategory>,
4694 #[serde(default)]
4696 pub mcp_tools: Vec<ContextMcpTool>,
4697 #[serde(default)]
4699 pub memory_files: Vec<ContextMemoryFile>,
4700 #[serde(default)]
4702 pub agents: Vec<ContextAgent>,
4703 #[serde(default, skip_serializing_if = "Option::is_none")]
4705 pub skills: Option<Vec<ContextSkill>>,
4706}
4707
4708#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4710pub struct ContextOverLimit {
4711 pub tokens_over: u64,
4712 pub kind: String,
4715}
4716
4717#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4719pub struct ContextCategory {
4720 pub name: String,
4723 pub tokens: u64,
4724 pub kind: String,
4728}
4729
4730#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4732pub struct ContextMcpTool {
4733 pub name: String,
4735 pub server_name: String,
4736 pub tokens: u64,
4737}
4738
4739#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4741pub struct ContextMemoryFile {
4742 pub path: String,
4743 #[serde(rename = "type")]
4745 pub file_type: String,
4746 pub tokens: u64,
4747}
4748
4749#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4751pub struct ContextAgent {
4752 pub agent_type: String,
4753 pub source: String,
4755 pub tokens: u64,
4756}
4757
4758#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4760pub struct ContextSkill {
4761 pub name: String,
4762 pub source: String,
4764 #[serde(default, skip_serializing_if = "Option::is_none")]
4765 pub plugin_name: Option<String>,
4766 pub tokens: u64,
4767}
4768
4769#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4771pub struct ToolUseMeta {
4772 pub id: String,
4773 pub display_name: String,
4774 #[serde(default, skip_serializing_if = "Option::is_none")]
4775 pub server_display_name: Option<String>,
4776 #[serde(default, skip_serializing_if = "Option::is_none")]
4777 pub icon_url: Option<String>,
4778}
4779
4780#[derive(Debug, Clone, Serialize, Deserialize)]
4782pub struct AssistantMessage {
4783 pub message: AssistantMessageContent,
4784 #[serde(alias = "sessionId")]
4785 pub session_id: String,
4786 #[serde(skip_serializing_if = "Option::is_none")]
4787 pub uuid: Option<String>,
4788 #[serde(skip_serializing_if = "Option::is_none")]
4789 pub parent_tool_use_id: Option<String>,
4790 #[serde(skip_serializing_if = "Option::is_none")]
4792 pub request_id: Option<String>,
4793 #[serde(default, skip_serializing_if = "Option::is_none")]
4802 pub user_message_uuid: Option<String>,
4803 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4809 pub user_message_uuids: Vec<String>,
4810 #[serde(default, skip_serializing_if = "Option::is_none")]
4814 pub agent_id: Option<String>,
4815 #[serde(skip_serializing_if = "Option::is_none")]
4818 pub subagent_type: Option<String>,
4819 #[serde(skip_serializing_if = "Option::is_none")]
4821 pub task_description: Option<String>,
4822 #[serde(skip_serializing_if = "Option::is_none")]
4823 pub error: Option<AssistantErrorKind>,
4824 #[serde(default, skip_serializing_if = "Option::is_none")]
4828 pub aborted: Option<bool>,
4829 #[serde(default, skip_serializing_if = "Option::is_none")]
4834 pub resumed_from_incomplete_thinking: Option<bool>,
4835 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4836 pub supersedes: Vec<String>,
4837 #[serde(skip_serializing_if = "Option::is_none")]
4838 pub timestamp: Option<String>,
4839 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4840 pub tool_use_meta: Vec<ToolUseMeta>,
4841 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4846 pub batch_tool_uses: Vec<BatchToolUse>,
4847 #[serde(default, skip_serializing_if = "Option::is_none")]
4853 pub wire_tool_inputs: Option<serde_json::Map<String, Value>>,
4854 #[serde(default, skip_serializing_if = "Option::is_none")]
4860 pub wire_ingest_context: Option<serde_json::Map<String, Value>>,
4861 #[serde(default, skip_serializing_if = "Option::is_none")]
4865 pub historical: Option<bool>,
4866 #[serde(default, skip_serializing_if = "Option::is_none")]
4874 pub resume_reason: Option<String>,
4875 #[serde(default, skip_serializing_if = "Option::is_none")]
4881 pub local_command_source: Option<String>,
4882 #[serde(default, skip_serializing_if = "Vec::is_empty")]
4891 pub narration_block_indexes: Vec<usize>,
4892 #[serde(default, skip_serializing_if = "Option::is_none")]
4896 pub narration_hint: Option<String>,
4897 #[serde(default, skip_serializing_if = "Option::is_none")]
4906 pub thinking_duration_ms: Option<u64>,
4907 #[serde(default, skip_serializing_if = "Option::is_none")]
4911 pub context_usage: Option<ContextUsage>,
4912 #[serde(default, skip_serializing_if = "Option::is_none")]
4920 pub usage_report: Option<Box<UsageReport>>,
4921 #[serde(default, skip_serializing_if = "Option::is_none")]
4928 pub local_command_run: Option<LocalCommandRun>,
4929 #[serde(default, skip_serializing_if = "Option::is_none")]
4935 pub local_command_outcome: Option<LocalCommandOutcome>,
4936 #[serde(default, skip_serializing_if = "Option::is_none")]
4937 pub is_meta: Option<bool>,
4938 #[serde(default, skip_serializing_if = "Option::is_none")]
4939 pub is_virtual: Option<bool>,
4940 #[serde(default, skip_serializing_if = "Option::is_none")]
4941 pub is_api_error_message: Option<bool>,
4942 #[serde(skip_serializing_if = "Option::is_none")]
4943 pub api_error_status: Option<u16>,
4944 #[serde(skip_serializing_if = "Option::is_none")]
4964 pub api_error: Option<String>,
4965 #[serde(default, skip_serializing_if = "Option::is_none")]
4972 pub api_error_code: Option<String>,
4973 #[serde(default, skip_serializing_if = "Option::is_none")]
4976 pub api_error_params: Option<ApiErrorParams>,
4977 #[serde(skip_serializing_if = "Option::is_none")]
4978 pub error_details: Option<String>,
4979 #[serde(skip_serializing_if = "Option::is_none")]
4980 pub advisor_model: Option<String>,
4981 #[serde(skip_serializing_if = "Option::is_none")]
4982 pub attribution_agent: Option<String>,
4983 #[serde(skip_serializing_if = "Option::is_none")]
4984 pub attribution_skill: Option<String>,
4985 #[serde(skip_serializing_if = "Option::is_none")]
4986 pub attribution_plugin: Option<String>,
4987 #[serde(skip_serializing_if = "Option::is_none")]
4988 pub attribution_mcp_server: Option<String>,
4989 #[serde(skip_serializing_if = "Option::is_none")]
4990 pub attribution_mcp_tool: Option<String>,
4991}
4992
4993#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
4997pub struct ApiErrorParams {
4998 #[serde(default, skip_serializing_if = "Option::is_none")]
5000 pub effort: Option<String>,
5001 #[serde(default, skip_serializing_if = "Option::is_none")]
5004 pub provider: Option<ApiErrorProvider>,
5005 #[serde(default, skip_serializing_if = "Option::is_none")]
5008 pub remedy: Option<ApiErrorRemedy>,
5009 #[serde(default, skip_serializing_if = "Option::is_none")]
5012 pub media: Option<ApiErrorMedia>,
5013 #[serde(default, skip_serializing_if = "Option::is_none")]
5015 pub media_reason: Option<ApiErrorMediaReason>,
5016 #[serde(default, skip_serializing_if = "Option::is_none")]
5024 pub rate_limit_info: Option<RateLimitInfo>,
5025}
5026
5027#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5029pub enum ApiErrorMedia {
5030 Image,
5031 Document,
5032 Unknown(String),
5034}
5035
5036impl ApiErrorMedia {
5037 pub fn as_str(&self) -> &str {
5038 match self {
5039 Self::Image => "image",
5040 Self::Document => "document",
5041 Self::Unknown(s) => s.as_str(),
5042 }
5043 }
5044}
5045
5046impl fmt::Display for ApiErrorMedia {
5047 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5048 f.write_str(self.as_str())
5049 }
5050}
5051
5052impl From<&str> for ApiErrorMedia {
5053 fn from(s: &str) -> Self {
5054 match s {
5055 "image" => Self::Image,
5056 "document" => Self::Document,
5057 other => Self::Unknown(other.to_string()),
5058 }
5059 }
5060}
5061
5062impl Serialize for ApiErrorMedia {
5063 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5064 serializer.serialize_str(self.as_str())
5065 }
5066}
5067
5068impl<'de> Deserialize<'de> for ApiErrorMedia {
5069 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5070 let s = String::deserialize(deserializer)?;
5071 Ok(Self::from(s.as_str()))
5072 }
5073}
5074
5075#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5078pub enum ApiErrorMediaReason {
5079 Unprocessable,
5082 UnsupportedByModel,
5084 MediaBudget,
5087 Unknown(String),
5089}
5090
5091impl ApiErrorMediaReason {
5092 pub fn as_str(&self) -> &str {
5093 match self {
5094 Self::Unprocessable => "unprocessable",
5095 Self::UnsupportedByModel => "unsupported_by_model",
5096 Self::MediaBudget => "media_budget",
5097 Self::Unknown(s) => s.as_str(),
5098 }
5099 }
5100}
5101
5102impl fmt::Display for ApiErrorMediaReason {
5103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5104 f.write_str(self.as_str())
5105 }
5106}
5107
5108impl From<&str> for ApiErrorMediaReason {
5109 fn from(s: &str) -> Self {
5110 match s {
5111 "unprocessable" => Self::Unprocessable,
5112 "unsupported_by_model" => Self::UnsupportedByModel,
5113 "media_budget" => Self::MediaBudget,
5114 other => Self::Unknown(other.to_string()),
5115 }
5116 }
5117}
5118
5119impl Serialize for ApiErrorMediaReason {
5120 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5121 serializer.serialize_str(self.as_str())
5122 }
5123}
5124
5125impl<'de> Deserialize<'de> for ApiErrorMediaReason {
5126 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5127 let s = String::deserialize(deserializer)?;
5128 Ok(Self::from(s.as_str()))
5129 }
5130}
5131
5132#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5134pub enum ApiErrorProvider {
5135 Bedrock,
5136 AnthropicAws,
5137 Mantle,
5138 AnthropicGoogleCloud,
5139 Vertex,
5140 Foundry,
5141 Gateway,
5142 Unknown(String),
5144}
5145
5146impl ApiErrorProvider {
5147 pub fn as_str(&self) -> &str {
5148 match self {
5149 Self::Bedrock => "bedrock",
5150 Self::AnthropicAws => "anthropicAws",
5151 Self::Mantle => "mantle",
5152 Self::AnthropicGoogleCloud => "anthropicGoogleCloud",
5153 Self::Vertex => "vertex",
5154 Self::Foundry => "foundry",
5155 Self::Gateway => "gateway",
5156 Self::Unknown(s) => s.as_str(),
5157 }
5158 }
5159}
5160
5161impl fmt::Display for ApiErrorProvider {
5162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5163 f.write_str(self.as_str())
5164 }
5165}
5166
5167impl From<&str> for ApiErrorProvider {
5168 fn from(s: &str) -> Self {
5169 match s {
5170 "bedrock" => Self::Bedrock,
5171 "anthropicAws" => Self::AnthropicAws,
5172 "mantle" => Self::Mantle,
5173 "anthropicGoogleCloud" => Self::AnthropicGoogleCloud,
5174 "vertex" => Self::Vertex,
5175 "foundry" => Self::Foundry,
5176 "gateway" => Self::Gateway,
5177 other => Self::Unknown(other.to_string()),
5178 }
5179 }
5180}
5181
5182impl Serialize for ApiErrorProvider {
5183 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5184 serializer.serialize_str(self.as_str())
5185 }
5186}
5187
5188impl<'de> Deserialize<'de> for ApiErrorProvider {
5189 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5190 let s = String::deserialize(deserializer)?;
5191 Ok(Self::from(s.as_str()))
5192 }
5193}
5194
5195#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5197pub enum ApiErrorRemedy {
5198 RefreshCommand,
5201 RefreshCredentials,
5203 Adc,
5206 GatewayToken,
5209 HostManaged,
5211 ModelAccess,
5214 Unknown(String),
5216}
5217
5218impl ApiErrorRemedy {
5219 pub fn as_str(&self) -> &str {
5220 match self {
5221 Self::RefreshCommand => "refresh_command",
5222 Self::RefreshCredentials => "refresh_credentials",
5223 Self::Adc => "adc",
5224 Self::GatewayToken => "gateway_token",
5225 Self::HostManaged => "host_managed",
5226 Self::ModelAccess => "model_access",
5227 Self::Unknown(s) => s.as_str(),
5228 }
5229 }
5230}
5231
5232impl fmt::Display for ApiErrorRemedy {
5233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5234 f.write_str(self.as_str())
5235 }
5236}
5237
5238impl From<&str> for ApiErrorRemedy {
5239 fn from(s: &str) -> Self {
5240 match s {
5241 "refresh_command" => Self::RefreshCommand,
5242 "refresh_credentials" => Self::RefreshCredentials,
5243 "adc" => Self::Adc,
5244 "gateway_token" => Self::GatewayToken,
5245 "host_managed" => Self::HostManaged,
5246 "model_access" => Self::ModelAccess,
5247 other => Self::Unknown(other.to_string()),
5248 }
5249 }
5250}
5251
5252impl Serialize for ApiErrorRemedy {
5253 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
5254 serializer.serialize_str(self.as_str())
5255 }
5256}
5257
5258impl<'de> Deserialize<'de> for ApiErrorRemedy {
5259 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
5260 let s = String::deserialize(deserializer)?;
5261 Ok(Self::from(s.as_str()))
5262 }
5263}
5264
5265#[derive(Debug, Clone, Serialize, Deserialize)]
5267pub struct AssistantMessageContent {
5268 pub id: String,
5269 #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
5271 pub message_type: Option<String>,
5272 pub role: MessageRole,
5273 pub model: String,
5274 pub content: Vec<ContentBlock>,
5275 #[serde(skip_serializing_if = "Option::is_none")]
5276 pub stop_reason: Option<StopReason>,
5277 #[serde(skip_serializing_if = "Option::is_none")]
5278 pub stop_sequence: Option<String>,
5279 #[serde(skip_serializing_if = "Option::is_none")]
5280 pub usage: Option<AssistantUsage>,
5281 #[serde(skip_serializing_if = "Option::is_none")]
5283 pub stop_details: Option<Value>,
5284 #[serde(skip_serializing_if = "Option::is_none")]
5286 pub context_management: Option<Value>,
5287}
5288
5289#[derive(Debug, Clone, Serialize, Deserialize)]
5291pub struct AssistantUsage {
5292 #[serde(default)]
5294 pub input_tokens: u32,
5295
5296 #[serde(default)]
5298 pub output_tokens: u32,
5299
5300 #[serde(default)]
5302 pub cache_creation_input_tokens: u32,
5303
5304 #[serde(default)]
5306 pub cache_read_input_tokens: u32,
5307
5308 #[serde(skip_serializing_if = "Option::is_none")]
5310 pub service_tier: Option<String>,
5311
5312 #[serde(skip_serializing_if = "Option::is_none")]
5314 pub cache_creation: Option<CacheCreationDetails>,
5315
5316 #[serde(skip_serializing_if = "Option::is_none")]
5318 pub inference_geo: Option<String>,
5319
5320 #[serde(default, skip_serializing_if = "Option::is_none")]
5323 pub fallback_credit: Option<Value>,
5324}
5325
5326#[derive(Debug, Clone, Serialize, Deserialize)]
5328pub struct CacheCreationDetails {
5329 #[serde(default)]
5331 pub ephemeral_1h_input_tokens: u32,
5332
5333 #[serde(default)]
5335 pub ephemeral_5m_input_tokens: u32,
5336}
5337
5338#[cfg(test)]
5339mod tests {
5340 use crate::io::ClaudeOutput;
5341
5342 #[test]
5343 fn test_subagent_usage_rollup_accumulates_task_results() {
5344 use super::SubagentUsageRollup;
5345
5346 let mut rollup = SubagentUsageRollup::default();
5347
5348 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}}"#;
5349 let output: ClaudeOutput = serde_json::from_str(task_result).unwrap();
5350 assert!(rollup.observe(&output));
5351 assert_eq!(rollup.subagent_tokens, 10201);
5352 assert_eq!(rollup.agent_count, 1);
5353 assert_eq!(rollup.tool_uses, 3);
5354 assert_eq!(rollup.duration_ms, 1853);
5355
5356 assert!(!rollup.observe(&output));
5358 assert_eq!(rollup.agent_count, 1);
5359 assert_eq!(rollup.subagent_tokens, 10201);
5360
5361 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}}"#;
5363 let output: ClaudeOutput = serde_json::from_str(second).unwrap();
5364 assert!(rollup.observe(&output));
5365 assert_eq!(rollup.agent_count, 2);
5366 assert_eq!(rollup.subagent_tokens, 10701);
5367 }
5368
5369 #[test]
5370 fn test_subagent_usage_rollup_ignores_non_task_results() {
5371 use super::SubagentUsageRollup;
5372
5373 let mut rollup = SubagentUsageRollup::default();
5374
5375 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}}"#;
5378 let output: ClaudeOutput = serde_json::from_str(tool_search).unwrap();
5379 assert!(!rollup.observe(&output));
5380
5381 let plain = r#"{"type":"user","message":{"role":"user","content":[]},"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5383 let output: ClaudeOutput = serde_json::from_str(plain).unwrap();
5384 assert!(!rollup.observe(&output));
5385
5386 let system = r#"{"type":"system","subtype":"status","status":null,"session_id":"7fbc568e-2bd6-45aa-b217-a1cf80004ba1"}"#;
5388 let output: ClaudeOutput = serde_json::from_str(system).unwrap();
5389 assert!(!rollup.observe(&output));
5390
5391 assert_eq!(rollup, SubagentUsageRollup::default());
5392 }
5393
5394 #[test]
5395 fn test_subagent_usage_rollup_over_captured_session() {
5396 use super::SubagentUsageRollup;
5397
5398 let mut rollup = SubagentUsageRollup::default();
5399 let fixture =
5400 include_str!("../../test_cases/subagent_sessions/general_purpose_compute.jsonl");
5401 for line in fixture.lines().filter(|l| !l.trim().is_empty()) {
5402 if let Ok(output) = serde_json::from_str::<ClaudeOutput>(line) {
5403 rollup.observe(&output);
5404 }
5405 }
5406 assert_eq!(rollup.agent_count, 1);
5407 assert_eq!(rollup.subagent_tokens, 10201);
5408 }
5409
5410 #[test]
5411 fn test_system_message_init() {
5412 let json = r#"{
5413 "type": "system",
5414 "subtype": "init",
5415 "session_id": "test-session-123",
5416 "cwd": "/home/user/project",
5417 "model": "claude-sonnet-4",
5418 "tools": ["Bash", "Read", "Write"],
5419 "mcp_servers": [],
5420 "slash_commands": ["compact", "cost", "review"],
5421 "agents": ["Bash", "Explore", "Plan"],
5422 "plugins": [{"name": "rust-analyzer-lsp", "path": "/home/user/.claude/plugins/rust-analyzer-lsp/1.0.0"}],
5423 "skills": [],
5424 "claude_code_version": "2.1.15",
5425 "apiKeySource": "none",
5426 "output_style": "default",
5427 "permissionMode": "default"
5428 }"#;
5429
5430 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5431 if let ClaudeOutput::System(sys) = output {
5432 assert!(sys.is_init());
5433 assert!(!sys.is_status());
5434 assert!(!sys.is_compact_boundary());
5435
5436 let init = sys.as_init().expect("Should parse as init");
5437 assert_eq!(init.session_id, "test-session-123");
5438 assert_eq!(init.cwd, Some("/home/user/project".to_string()));
5439 assert_eq!(init.model, Some("claude-sonnet-4".to_string()));
5440 assert_eq!(init.tools, vec!["Bash", "Read", "Write"]);
5441 assert_eq!(init.slash_commands, vec!["compact", "cost", "review"]);
5442 assert_eq!(init.agents, vec!["Bash", "Explore", "Plan"]);
5443 assert_eq!(init.plugins.len(), 1);
5444 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5445 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5446 assert_eq!(init.api_key_source, Some(super::ApiKeySource::None));
5447 assert_eq!(init.output_style, Some(super::OutputStyle::Default));
5448 assert_eq!(
5449 init.permission_mode,
5450 Some(super::InitPermissionMode::Default)
5451 );
5452 } else {
5453 panic!("Expected System message");
5454 }
5455 }
5456
5457 #[test]
5458 fn test_system_message_init_from_real_capture() {
5459 let json = include_str!("../../test_cases/tool_use_captures/tool_msg_0.json");
5460 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5461 if let ClaudeOutput::System(sys) = output {
5462 let init = sys.as_init().expect("Should parse real init capture");
5463 assert_eq!(init.slash_commands.len(), 8);
5464 assert!(init.slash_commands.contains(&"compact".to_string()));
5465 assert!(init.slash_commands.contains(&"review".to_string()));
5466 assert_eq!(init.agents.len(), 5);
5467 assert!(init.agents.contains(&"Bash".to_string()));
5468 assert!(init.agents.contains(&"Explore".to_string()));
5469 assert_eq!(init.plugins.len(), 1);
5470 assert_eq!(init.plugins[0].name, "rust-analyzer-lsp");
5471 assert_eq!(init.claude_code_version, Some("2.1.15".to_string()));
5472 } else {
5473 panic!("Expected System message");
5474 }
5475 }
5476
5477 #[test]
5478 fn test_system_message_status() {
5479 let json = r#"{
5480 "type": "system",
5481 "subtype": "status",
5482 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5483 "status": "compacting",
5484 "uuid": "32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93"
5485 }"#;
5486
5487 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5488 if let ClaudeOutput::System(sys) = output {
5489 assert!(sys.is_status());
5490 assert!(!sys.is_init());
5491
5492 let status = sys.as_status().expect("Should parse as status");
5493 assert_eq!(status.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5494 assert_eq!(status.status, Some(super::StatusMessageStatus::Compacting));
5495 assert_eq!(
5496 status.uuid,
5497 Some("32eb9f9d-5ef7-47ff-8fce-bbe22fe7ed93".to_string())
5498 );
5499 } else {
5500 panic!("Expected System message");
5501 }
5502 }
5503
5504 #[test]
5505 fn test_system_message_status_null() {
5506 let json = r#"{
5507 "type": "system",
5508 "subtype": "status",
5509 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5510 "status": null,
5511 "uuid": "92d9637e-d00e-418e-acd2-a504e3861c6a"
5512 }"#;
5513
5514 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5515 if let ClaudeOutput::System(sys) = output {
5516 let status = sys.as_status().expect("Should parse as status");
5517 assert_eq!(status.status, None);
5518 } else {
5519 panic!("Expected System message");
5520 }
5521 }
5522
5523 #[test]
5524 fn test_system_message_task_started() {
5525 let json = r#"{
5526 "type": "system",
5527 "subtype": "task_started",
5528 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5529 "task_id": "b6daf3f",
5530 "task_type": "local_bash",
5531 "tool_use_id": "toolu_011rfSTFumpJZdCCfzeD7jaS",
5532 "description": "Wait for CI on PR #12",
5533 "uuid": "c4243261-c128-4747-b8c3-5e1c7c10eeb8"
5534 }"#;
5535
5536 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5537 if let ClaudeOutput::System(sys) = output {
5538 assert!(sys.is_task_started());
5539 assert!(!sys.is_task_progress());
5540 assert!(!sys.is_task_notification());
5541
5542 let task = sys.as_task_started().expect("Should parse as task_started");
5543 assert_eq!(task.session_id, "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9");
5544 assert_eq!(task.task_id, "b6daf3f");
5545 assert_eq!(task.task_type, Some(super::TaskType::LocalBash));
5546 assert_eq!(
5547 task.tool_use_id.as_deref(),
5548 Some("toolu_011rfSTFumpJZdCCfzeD7jaS")
5549 );
5550 assert_eq!(task.description, "Wait for CI on PR #12");
5551 } else {
5552 panic!("Expected System message");
5553 }
5554 }
5555
5556 #[test]
5557 fn test_system_message_task_started_agent() {
5558 let json = r#"{
5559 "type": "system",
5560 "subtype": "task_started",
5561 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5562 "task_id": "a4a7e0906e5fc64cc",
5563 "task_type": "local_agent",
5564 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5565 "description": "Explore Scene/ArrayScene duplication",
5566 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5567 }"#;
5568
5569 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5570 if let ClaudeOutput::System(sys) = output {
5571 let task = sys.as_task_started().expect("Should parse as task_started");
5572 assert_eq!(task.task_type, Some(super::TaskType::LocalAgent));
5573 assert_eq!(task.task_id, "a4a7e0906e5fc64cc");
5574 } else {
5575 panic!("Expected System message");
5576 }
5577 }
5578
5579 #[test]
5580 fn test_system_message_task_progress() {
5581 let json = r#"{
5582 "type": "system",
5583 "subtype": "task_progress",
5584 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5585 "task_id": "a4a7e0906e5fc64cc",
5586 "tool_use_id": "toolu_01SFz9FwZ1cYgCSy8vRM7wep",
5587 "description": "Reading src/jplephem/chebyshev.rs",
5588 "last_tool_name": "Read",
5589 "usage": {
5590 "duration_ms": 13996,
5591 "tool_uses": 9,
5592 "total_tokens": 38779
5593 },
5594 "uuid": "85a39f5a-e4d4-47f7-9a6d-1125f1a8035f"
5595 }"#;
5596
5597 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5598 if let ClaudeOutput::System(sys) = output {
5599 assert!(sys.is_task_progress());
5600 assert!(!sys.is_task_started());
5601
5602 let progress = sys
5603 .as_task_progress()
5604 .expect("Should parse as task_progress");
5605 assert_eq!(progress.task_id, "a4a7e0906e5fc64cc");
5606 assert_eq!(progress.description, "Reading src/jplephem/chebyshev.rs");
5607 assert_eq!(progress.last_tool_name.as_deref(), Some("Read"));
5608 assert_eq!(progress.usage.duration_ms, 13996);
5609 assert_eq!(progress.usage.tool_uses, 9);
5610 assert_eq!(progress.usage.total_tokens, 38779);
5611 } else {
5612 panic!("Expected System message");
5613 }
5614 }
5615
5616 #[test]
5617 fn test_system_message_task_notification_completed() {
5618 let json = r#"{
5619 "type": "system",
5620 "subtype": "task_notification",
5621 "session_id": "bff4f716-17c1-4255-ab7b-eea9d33824e3",
5622 "task_id": "a0ba761e9dc9c316f",
5623 "tool_use_id": "toolu_01Ho6XVXFLVNjTQ9YqowdBXW",
5624 "status": "completed",
5625 "summary": "Agent \"Write Hipparcos data source doc\" completed",
5626 "output_file": "",
5627 "usage": {
5628 "duration_ms": 172300,
5629 "tool_uses": 11,
5630 "total_tokens": 42005
5631 },
5632 "uuid": "269f49b9-218d-4c8d-9f7e-3a5383a0c5b2"
5633 }"#;
5634
5635 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5636 if let ClaudeOutput::System(sys) = output {
5637 assert!(sys.is_task_notification());
5638
5639 let notif = sys
5640 .as_task_notification()
5641 .expect("Should parse as task_notification");
5642 assert_eq!(notif.status, super::TaskStatus::Completed);
5643 assert_eq!(
5644 notif.summary,
5645 "Agent \"Write Hipparcos data source doc\" completed"
5646 );
5647 assert_eq!(notif.output_file, Some("".to_string()));
5648 assert_eq!(
5649 notif.tool_use_id,
5650 Some("toolu_01Ho6XVXFLVNjTQ9YqowdBXW".to_string())
5651 );
5652 let usage = notif.usage.expect("Should have usage");
5653 assert_eq!(usage.duration_ms, 172300);
5654 assert_eq!(usage.tool_uses, 11);
5655 assert_eq!(usage.total_tokens, 42005);
5656 } else {
5657 panic!("Expected System message");
5658 }
5659 }
5660
5661 #[test]
5662 fn test_system_message_task_notification_failed_no_usage() {
5663 let json = r#"{
5664 "type": "system",
5665 "subtype": "task_notification",
5666 "session_id": "ea629737-3c36-48a8-a1c4-ad761ad35784",
5667 "task_id": "b98f6a3",
5668 "status": "failed",
5669 "summary": "Background command \"Run FSM calibration\" failed with exit code 1",
5670 "output_file": "/tmp/claude-1000/tasks/b98f6a3.output"
5671 }"#;
5672
5673 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5674 if let ClaudeOutput::System(sys) = output {
5675 let notif = sys
5676 .as_task_notification()
5677 .expect("Should parse as task_notification");
5678 assert_eq!(notif.status, super::TaskStatus::Failed);
5679 assert!(notif.tool_use_id.is_none());
5680 assert!(notif.usage.is_none());
5681 assert_eq!(
5682 notif.output_file,
5683 Some("/tmp/claude-1000/tasks/b98f6a3.output".to_string())
5684 );
5685 } else {
5686 panic!("Expected System message");
5687 }
5688 }
5689
5690 #[test]
5696 fn test_task_messages_roundtrip_through_value() {
5697 let cases = [
5698 r#"{"type":"system","subtype":"task_started","session_id":"s1",
5699 "task_id":"t1","task_type":"local_bash","tool_use_id":"tu1",
5700 "description":"Sleep 3s","uuid":"u1"}"#,
5701 r#"{"type":"system","subtype":"task_progress","session_id":"s1",
5702 "task_id":"t1","tool_use_id":"tu1","description":"Running ls",
5703 "last_tool_name":"Bash",
5704 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5705 "uuid":"u2"}"#,
5706 r#"{"type":"system","subtype":"task_notification","session_id":"s1",
5707 "task_id":"t1","tool_use_id":"tu1","status":"completed",
5708 "summary":"done","output_file":"",
5709 "usage":{"duration_ms":100,"tool_uses":1,"total_tokens":500},
5710 "uuid":"u3"}"#,
5711 ];
5712
5713 for json in cases {
5714 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5715 let value = serde_json::to_value(&output).unwrap();
5716 let reparsed: ClaudeOutput = serde_json::from_value(value).unwrap();
5717
5718 let ClaudeOutput::System(sys) = reparsed else {
5719 panic!("Expected System variant after round-trip");
5720 };
5721
5722 match sys.subtype {
5723 super::SystemSubtype::TaskStarted => {
5724 assert!(
5725 sys.as_task_started().is_some(),
5726 "as_task_started failed after round-trip"
5727 );
5728 }
5729 super::SystemSubtype::TaskProgress => {
5730 assert!(
5731 sys.as_task_progress().is_some(),
5732 "as_task_progress failed after round-trip"
5733 );
5734 }
5735 super::SystemSubtype::TaskNotification => {
5736 assert!(
5737 sys.as_task_notification().is_some(),
5738 "as_task_notification failed after round-trip"
5739 );
5740 }
5741 other => panic!("unexpected subtype after round-trip: {other:?}"),
5742 }
5743 }
5744 }
5745
5746 #[test]
5747 fn test_system_message_compact_boundary() {
5748 let json = r#"{
5749 "type": "system",
5750 "subtype": "compact_boundary",
5751 "session_id": "879c1a88-3756-4092-aa95-0020c4ed9692",
5752 "compact_metadata": {
5753 "pre_tokens": 155285,
5754 "trigger": "auto"
5755 },
5756 "uuid": "a67780d5-74cb-48b1-9137-7a6e7cee45d7"
5757 }"#;
5758
5759 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5760 if let ClaudeOutput::System(sys) = output {
5761 assert!(sys.is_compact_boundary());
5762 assert!(!sys.is_init());
5763 assert!(!sys.is_status());
5764
5765 let compact = sys
5766 .as_compact_boundary()
5767 .expect("Should parse as compact_boundary");
5768 assert_eq!(compact.session_id, "879c1a88-3756-4092-aa95-0020c4ed9692");
5769 assert_eq!(compact.compact_metadata.pre_tokens, 155285);
5770 assert_eq!(
5771 compact.compact_metadata.trigger,
5772 super::CompactionTrigger::Auto
5773 );
5774 assert!(compact.summary.is_none());
5776 assert!(compact.leaf_message_count.is_none());
5777 assert!(compact.duration_ms.is_none());
5778 } else {
5779 panic!("Expected System message");
5780 }
5781 }
5782
5783 #[test]
5784 fn test_compact_boundary_with_summary_stats() {
5785 let json = r#"{
5787 "type": "system",
5788 "subtype": "compact_boundary",
5789 "session_id": "s1",
5790 "compact_metadata": { "pre_tokens": 1000, "trigger": "manual" },
5791 "summary": "Summarized the earlier exploration.",
5792 "leaf_message_count": 42,
5793 "duration_ms": 1234,
5794 "uuid": "u1"
5795 }"#;
5796 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5797 let ClaudeOutput::System(sys) = output else {
5798 panic!("Expected System message");
5799 };
5800 let compact = sys.as_compact_boundary().expect("compact_boundary");
5801 assert_eq!(
5802 compact.summary.as_deref(),
5803 Some("Summarized the earlier exploration.")
5804 );
5805 assert_eq!(compact.leaf_message_count, Some(42));
5806 assert_eq!(compact.duration_ms, Some(1234));
5807
5808 let json_alt = r#"{
5811 "type": "system",
5812 "subtype": "compact_boundary",
5813 "session_id": "s2",
5814 "compact_metadata": { "pre_tokens": 2000, "trigger": "auto" },
5815 "content": "alt-key summary",
5816 "message_count": 7
5817 }"#;
5818 let output: ClaudeOutput = serde_json::from_str(json_alt).unwrap();
5819 let ClaudeOutput::System(sys) = output else {
5820 panic!("Expected System message");
5821 };
5822 let compact = sys.as_compact_boundary().expect("compact_boundary");
5823 assert_eq!(compact.summary.as_deref(), Some("alt-key summary"));
5824 assert_eq!(compact.leaf_message_count, Some(7));
5825 }
5826
5827 #[test]
5828 fn test_init_message_with_new_fields() {
5829 let json = r#"{
5830 "type": "system",
5831 "subtype": "init",
5832 "session_id": "test-session",
5833 "cwd": "/home/user",
5834 "model": "claude-opus-4-7",
5835 "tools": ["Bash"],
5836 "mcp_servers": [],
5837 "permissionMode": "default",
5838 "apiKeySource": "none",
5839 "uuid": "44841a0d-182d-493a-86b5-79800d3d9665",
5840 "memory_paths": {"auto": "/home/user/.claude/projects/memory/"},
5841 "fast_mode_state": "off",
5842 "plugins": [{"name": "lsp", "path": "/plugins/lsp", "source": "lsp@official"}],
5843 "claude_code_version": "2.1.117"
5844 }"#;
5845
5846 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5847 if let ClaudeOutput::System(sys) = output {
5848 let init = sys.as_init().expect("Should parse as init");
5849 assert_eq!(
5850 init.uuid.as_deref(),
5851 Some("44841a0d-182d-493a-86b5-79800d3d9665")
5852 );
5853 assert!(init.memory_paths.is_some());
5854 assert_eq!(init.fast_mode_state.as_deref(), Some("off"));
5855 assert_eq!(init.plugins[0].source.as_deref(), Some("lsp@official"));
5856 assert_eq!(init.claude_code_version.as_deref(), Some("2.1.117"));
5857 } else {
5858 panic!("Expected System message");
5859 }
5860 }
5861
5862 #[test]
5863 fn test_assistant_message_with_new_fields() {
5864 let json = r#"{
5865 "type": "assistant",
5866 "message": {
5867 "id": "msg_1",
5868 "type": "message",
5869 "role": "assistant",
5870 "model": "claude-opus-4-7",
5871 "content": [{"type": "text", "text": "Hello"}],
5872 "stop_reason": "end_turn",
5873 "stop_details": null,
5874 "context_management": null,
5875 "usage": {
5876 "input_tokens": 100,
5877 "output_tokens": 10,
5878 "cache_creation_input_tokens": 50,
5879 "cache_read_input_tokens": 0,
5880 "service_tier": "standard",
5881 "inference_geo": "not_available"
5882 }
5883 },
5884 "session_id": "abc",
5885 "uuid": "msg-uuid-123"
5886 }"#;
5887
5888 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5889 if let ClaudeOutput::Assistant(asst) = output {
5890 assert_eq!(asst.message.stop_details, None);
5891 assert_eq!(asst.message.context_management, None);
5892 let usage = asst.message.usage.unwrap();
5893 assert_eq!(usage.inference_geo.as_deref(), Some("not_available"));
5894 } else {
5895 panic!("Expected Assistant message");
5896 }
5897 }
5898
5899 #[test]
5900 fn test_user_message_with_new_fields() {
5901 let json = r#"{
5902 "type": "user",
5903 "message": {
5904 "role": "user",
5905 "content": [{"type": "text", "text": "Hello"}]
5906 },
5907 "session_id": "9abbc466-dad0-4b8e-b6b0-cad5eb7a16b9",
5908 "parent_tool_use_id": "toolu_123",
5909 "uuid": "user-msg-456"
5910 }"#;
5911
5912 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5913 if let ClaudeOutput::User(user) = output {
5914 assert_eq!(user.parent_tool_use_id.as_deref(), Some("toolu_123"));
5915 assert_eq!(user.uuid.as_deref(), Some("user-msg-456"));
5916 } else {
5917 panic!("Expected User message");
5918 }
5919 }
5920
5921 #[test]
5927 fn test_user_message_preserves_tool_use_result_and_timestamp() {
5928 let json = r#"{
5929 "type":"user",
5930 "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"}]},
5931 "parent_tool_use_id":null,
5932 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
5933 "uuid":"8ef6e997-a849-4d15-bed3-2837c3d3f4cd",
5934 "timestamp":"2026-05-12T23:12:04.121Z",
5935 "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"}}
5936 }"#;
5937
5938 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5939 let user = match output {
5940 ClaudeOutput::User(u) => u,
5941 other => panic!("Expected User message, got {:?}", other.message_type()),
5942 };
5943
5944 assert_eq!(user.timestamp.as_deref(), Some("2026-05-12T23:12:04.121Z"));
5945 let raw = user
5946 .tool_use_result
5947 .as_ref()
5948 .expect("tool_use_result must be captured");
5949 assert_eq!(raw["answers"]["Color"], "Blue");
5950 assert_eq!(raw["questions"][0]["header"], "Color");
5951
5952 let reser: serde_json::Value = serde_json::to_value(&user).unwrap();
5956 assert_eq!(reser["timestamp"], "2026-05-12T23:12:04.121Z");
5957 assert_eq!(reser["tool_use_result"]["answers"]["Color"], "Blue");
5958 assert_eq!(
5959 reser["tool_use_result"]["questions"][0]["question"],
5960 "Which color do you prefer?"
5961 );
5962
5963 let typed: crate::AskUserQuestionInput = user
5966 .tool_use_result_as::<crate::AskUserQuestionInput>()
5967 .expect("tool_use_result present")
5968 .expect("AskUserQuestionInput parses");
5969 assert_eq!(typed.questions.len(), 1);
5970 assert_eq!(typed.questions[0].header, "Color");
5971 let answers = typed.answers.expect("answers populated");
5972 assert_eq!(answers.get("Color").map(String::as_str), Some("Blue"));
5973 }
5974
5975 #[test]
5978 fn test_user_message_without_tool_use_result_omits_field() {
5979 let json = r#"{
5980 "type":"user",
5981 "message":{"role":"user","content":[{"type":"text","text":"hello"}]},
5982 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d"
5983 }"#;
5984
5985 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
5986 let user = match output {
5987 ClaudeOutput::User(u) => u,
5988 _ => panic!("Expected User message"),
5989 };
5990 assert!(user.tool_use_result.is_none());
5991 assert!(user.timestamp.is_none());
5992
5993 let reser = serde_json::to_value(&user).unwrap();
5994 assert!(reser.get("tool_use_result").is_none());
5995 assert!(reser.get("timestamp").is_none());
5996 }
5997
5998 #[test]
6002 fn test_subagent_result_exposes_token_accounting() {
6003 let json = r#"{
6004 "type":"user",
6005 "message":{"role":"user","content":[{"tool_use_id":"toolu_01","type":"tool_result","content":[{"type":"text","text":"21"}]}]},
6006 "session_id":"d3fc5942-75e5-4aa1-a87d-b9484a176541",
6007 "tool_use_result":{
6008 "status":"completed",
6009 "prompt":"Count the .rs files.",
6010 "agentId":"ac4f0276e9d4b6232",
6011 "agentType":"Explore",
6012 "content":[{"type":"text","text":"21"}],
6013 "resolvedModel":"claude-haiku-4-5-20251001",
6014 "totalDurationMs":6869,
6015 "totalTokens":7834,
6016 "totalToolUseCount":1,
6017 "usage":{"input_tokens":6,"cache_creation_input_tokens":125,"cache_read_input_tokens":7699,"output_tokens":4,"service_tier":"standard"},
6018 "toolStats":{"readCount":0,"searchCount":0,"bashCount":1,"editFileCount":0,"linesAdded":0,"linesRemoved":0,"otherToolCount":0}
6019 }
6020 }"#;
6021
6022 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6023 let user = match output {
6024 ClaudeOutput::User(u) => u,
6025 _ => panic!("Expected User message"),
6026 };
6027
6028 let result = user.subagent_result().expect("subagent result parses");
6029 assert_eq!(result.agent_type.as_deref(), Some("Explore"));
6030 assert_eq!(
6031 result.resolved_model.as_deref(),
6032 Some("claude-haiku-4-5-20251001")
6033 );
6034 assert_eq!(result.total_tokens, Some(7834));
6035 assert_eq!(result.total_duration_ms, Some(6869));
6036 assert_eq!(result.total_tool_use_count, Some(1));
6037
6038 let usage = result.usage.expect("nested usage present");
6039 assert_eq!(usage.input_tokens, 6);
6040 assert_eq!(usage.cache_read_input_tokens, 7699);
6041
6042 let stats = result.tool_stats.expect("toolStats present");
6043 assert_eq!(stats.bash_count, 1);
6044 }
6045
6046 #[test]
6050 fn test_subagent_result_absent_for_non_task_result() {
6051 let json = r#"{
6052 "type":"user",
6053 "message":{"role":"user","content":[{"type":"text","text":"hi"}]},
6054 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6055 "tool_use_result":{"questions":[],"answers":{"Color":"Blue"}}
6056 }"#;
6057
6058 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6059 let user = match output {
6060 ClaudeOutput::User(u) => u,
6061 _ => panic!("Expected User message"),
6062 };
6063
6064 let result = user.subagent_result().expect("lenient parse");
6065 assert_eq!(result.total_tokens, None);
6066 assert_eq!(result.agent_type, None);
6067 }
6068
6069 #[test]
6070 fn test_init_fast_mode_reason_and_mcp_server_errors_fully_wrapped() {
6071 use serde_json::Value;
6072
6073 let raw: Value = serde_json::from_str(
6074 r#"{
6075 "type":"system","subtype":"init","session_id":"s1","uuid":"u1",
6076 "fast_mode_state":"off",
6077 "fast_mode_disabled_reason":"not_first_party",
6078 "mcp_server_errors":[{"name":"broken","type":"invalid_config","message":"url entry with no type"}]
6079 }"#,
6080 )
6081 .unwrap();
6082 crate::io::assert_fully_wrapped(&raw);
6083
6084 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6085 let ClaudeOutput::System(sys) = output else {
6086 panic!("expected System");
6087 };
6088 let init = sys.as_init().expect("parses as init");
6089 assert_eq!(
6090 init.fast_mode_disabled_reason,
6091 Some(crate::FastModeDisabledReason::NotFirstParty)
6092 );
6093 let errs = init.mcp_server_errors.unwrap();
6094 assert_eq!(errs.len(), 1);
6095 assert_eq!(errs[0].name, "broken");
6096 assert_eq!(errs[0].error_type, "invalid_config");
6097 }
6098
6099 #[test]
6100 fn test_code_change_published_fully_wrapped() {
6101 use super::{KnownSystemEvent, SystemSubtype};
6102 use serde_json::Value;
6103
6104 let raw: Value = serde_json::from_str(
6105 r#"{
6106 "type":"system","subtype":"code_change_published",
6107 "provider":"github","url":"https://github.com/owner/repo/pull/42",
6108 "repo":"owner/repo","identifier":"42",
6109 "uuid":"u1","session_id":"s1"
6110 }"#,
6111 )
6112 .unwrap();
6113 crate::io::assert_fully_wrapped(&raw);
6114
6115 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6116 let ClaudeOutput::System(sys) = output else {
6117 panic!("expected System");
6118 };
6119 assert_eq!(sys.subtype, SystemSubtype::CodeChangePublished);
6120 let Some(KnownSystemEvent::CodeChangePublished(msg)) = sys.as_known_system_event() else {
6121 panic!("expected CodeChangePublished event");
6122 };
6123 assert_eq!(msg.provider, "github");
6124 assert_eq!(msg.repo, "owner/repo");
6125 assert_eq!(msg.identifier, "42");
6126
6127 assert!(sys.is_code_change_published());
6128 assert!(!sys.is_vcs_state_changed());
6129 let direct = sys.as_code_change_published().expect("direct accessor");
6130 assert_eq!(direct.url, "https://github.com/owner/repo/pull/42");
6131 assert!(sys.as_vcs_state_changed().is_none());
6132 }
6133
6134 #[test]
6135 fn test_feedback_draft_queued_fully_wrapped() {
6136 use super::{KnownSystemEvent, SystemSubtype};
6137 use serde_json::Value;
6138
6139 let raw: Value = serde_json::from_str(
6140 r#"{
6141 "type":"system","subtype":"feedback_draft_queued",
6142 "draft_id":"draft-1","draft_type":"bug_report",
6143 "title":"Tool output was truncated",
6144 "details_preview":"The last command omitted its final lines",
6145 "uuid":"u1","session_id":"s1","future_field":"preserved"
6146 }"#,
6147 )
6148 .unwrap();
6149 crate::io::assert_fully_wrapped(&raw);
6150
6151 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6152 let ClaudeOutput::System(sys) = output else {
6153 panic!("expected System");
6154 };
6155 assert_eq!(sys.subtype, SystemSubtype::FeedbackDraftQueued);
6156 assert!(sys.is_feedback_draft_queued());
6157 assert!(!sys.is_vcs_state_changed());
6158
6159 let direct = sys
6160 .as_feedback_draft_queued()
6161 .expect("direct typed accessor");
6162 assert_eq!(direct.draft_id, "draft-1");
6163 assert_eq!(direct.draft_type, "bug_report");
6164 assert_eq!(direct.extra["future_field"], "preserved");
6165
6166 let Some(KnownSystemEvent::FeedbackDraftQueued(known)) = sys.as_known_system_event() else {
6167 panic!("expected FeedbackDraftQueued event");
6168 };
6169 assert_eq!(known.title, "Tool output was truncated");
6170 assert_eq!(
6171 sys.typed_value().expect("typed value")["future_field"],
6172 "preserved"
6173 );
6174 }
6175
6176 #[test]
6177 fn test_cloud_session_delta_fully_wrapped() {
6178 use super::{KnownSystemEvent, SystemSubtype};
6179 use serde_json::Value;
6180
6181 let raw: Value = serde_json::from_str(
6182 r#"{
6183 "type":"system","subtype":"cloud_session_delta",
6184 "seq":3,"changed":["serving","connection"],
6185 "cloud_session":{"id":"session_abc","view_url":"https://example.invalid/s/abc",
6186 "serving":{"state":"on"},"connection":{"state":"live"}},
6187 "uuid":"u1","session_id":"s1","future_field":"preserved"
6188 }"#,
6189 )
6190 .unwrap();
6191 crate::io::assert_fully_wrapped(&raw);
6192
6193 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6194 let ClaudeOutput::System(sys) = output else {
6195 panic!("expected System");
6196 };
6197 assert_eq!(sys.subtype, SystemSubtype::CloudSessionDelta);
6198 assert!(sys.is_cloud_session_delta());
6199 assert!(!sys.is_feedback_draft_queued());
6200
6201 let direct = sys.as_cloud_session_delta().expect("direct typed accessor");
6202 assert_eq!(direct.seq, 3);
6203 assert_eq!(direct.changed, vec!["serving", "connection"]);
6204 assert_eq!(direct.cloud_session["id"], "session_abc");
6205 assert_eq!(direct.extra["future_field"], "preserved");
6206
6207 let Some(KnownSystemEvent::CloudSessionDelta(known)) = sys.as_known_system_event() else {
6208 panic!("expected CloudSessionDelta event");
6209 };
6210 assert_eq!(known.session_id, "s1");
6211 assert_eq!(
6212 sys.typed_value().expect("typed value")["future_field"],
6213 "preserved"
6214 );
6215 }
6216
6217 #[test]
6218 fn test_vcs_state_changed_fully_wrapped() {
6219 use super::{KnownSystemEvent, VcsMutationKind};
6220 use serde_json::Value;
6221
6222 for kind in ["commit", "push", "merge", "rebase"] {
6223 let raw: Value = serde_json::from_str(&format!(
6224 r#"{{"type":"system","subtype":"vcs_state_changed","kind":"{}","cwd":"/repo","uuid":"u1","session_id":"s1"}}"#,
6225 kind
6226 ))
6227 .unwrap();
6228 crate::io::assert_fully_wrapped(&raw);
6229
6230 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6231 let ClaudeOutput::System(sys) = output else {
6232 panic!("expected System");
6233 };
6234 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6235 panic!("expected VcsStateChanged event");
6236 };
6237 assert_eq!(msg.kind.as_str(), kind);
6238 assert!(!matches!(msg.kind, VcsMutationKind::Unknown(_)));
6239 }
6240
6241 let raw: Value = serde_json::from_str(
6243 r#"{"type":"system","subtype":"vcs_state_changed","kind":"tag","cwd":"/repo","uuid":"u2","session_id":"s2"}"#,
6244 )
6245 .unwrap();
6246 crate::io::assert_fully_wrapped(&raw);
6247 let output: ClaudeOutput = serde_json::from_value(raw).unwrap();
6248 let ClaudeOutput::System(sys) = output else {
6249 panic!("expected System");
6250 };
6251 let Some(KnownSystemEvent::VcsStateChanged(msg)) = sys.as_known_system_event() else {
6252 panic!("expected VcsStateChanged event");
6253 };
6254 assert_eq!(msg.kind, VcsMutationKind::Unknown("tag".to_string()));
6255
6256 assert!(sys.is_vcs_state_changed());
6257 let direct = sys.as_vcs_state_changed().expect("direct accessor");
6258 assert_eq!(direct.cwd, "/repo");
6259 assert!(sys.as_code_change_published().is_none());
6260 }
6261
6262 #[test]
6263 fn test_assistant_aborted_and_resume_flags_roundtrip() {
6264 let json = r#"{
6265 "type":"assistant",
6266 "message":{"id":"msg_1","role":"assistant","model":"claude-3","content":[{"type":"text","text":"partial"}]},
6267 "session_id":"s1",
6268 "aborted":true,
6269 "resumed_from_incomplete_thinking":true
6270 }"#;
6271 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6272 let ClaudeOutput::Assistant(msg) = &output else {
6273 panic!("expected Assistant");
6274 };
6275 assert_eq!(msg.aborted, Some(true));
6276 assert_eq!(msg.resumed_from_incomplete_thinking, Some(true));
6277 let reserialized = serde_json::to_string(&output).unwrap();
6278 assert!(reserialized.contains("\"aborted\":true"));
6279 assert!(reserialized.contains("\"resumed_from_incomplete_thinking\":true"));
6280
6281 let json = r#"{
6283 "type":"assistant",
6284 "message":{"id":"msg_2","role":"assistant","model":"claude-3","content":[]},
6285 "session_id":"s2"
6286 }"#;
6287 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6288 let reserialized = serde_json::to_string(&output).unwrap();
6289 assert!(!reserialized.contains("aborted"));
6290 assert!(!reserialized.contains("resumed_from_incomplete_thinking"));
6291 }
6292
6293 #[test]
6294 fn test_user_tool_result_meta_roundtrip() {
6295 let json = r#"{
6296 "type":"user",
6297 "message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"denied"}]},
6298 "session_id":"622ae0c3-3d50-4fa7-9ee0-69d691238c6d",
6299 "tool_result_meta":[
6300 {"id":"toolu_1","non_execution_kind":"user-rejected","user_feedback":"use the staging db"},
6301 {"id":"toolu_2","non_execution_kind":"permission-rule"}
6302 ]
6303 }"#;
6304 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
6305 let ClaudeOutput::User(user) = &output else {
6306 panic!("expected User");
6307 };
6308 let meta = user.tool_result_meta.as_ref().unwrap();
6309 assert_eq!(meta.len(), 2);
6310 assert_eq!(meta[0].non_execution_kind.as_deref(), Some("user-rejected"));
6311 assert_eq!(meta[0].user_feedback.as_deref(), Some("use the staging db"));
6312 assert_eq!(meta[1].user_feedback, None);
6313
6314 let reserialized = serde_json::to_string(&output).unwrap();
6315 assert!(reserialized.contains("\"non_execution_kind\":\"user-rejected\""));
6316 assert!(!reserialized.contains("\"user_feedback\":null"));
6317 }
6318
6319 #[test]
6323 fn model_refusal_fallback_scope_roundtrips_and_defaults() {
6324 use super::{ModelRefusalFallbackMessage, RefusalFallbackScope};
6325 let with_scope = serde_json::json!({
6326 "trigger": "refusal",
6327 "direction": "retry",
6328 "scope": "local",
6329 "original_model": "claude-fable-5",
6330 "fallback_model": "claude-opus-5",
6331 "request_id": null,
6332 "content": "Refused; retried on fallback model.",
6333 "uuid": "u1",
6334 "session_id": "s1"
6335 });
6336 let msg: ModelRefusalFallbackMessage = serde_json::from_value(with_scope.clone()).unwrap();
6337 assert_eq!(msg.scope, Some(RefusalFallbackScope::Local));
6338 assert_eq!(serde_json::to_value(&msg).unwrap(), with_scope);
6339
6340 let mut without = with_scope.clone();
6343 without.as_object_mut().unwrap().remove("scope");
6344 let msg: ModelRefusalFallbackMessage = serde_json::from_value(without.clone()).unwrap();
6345 assert_eq!(msg.scope, None);
6346 assert_eq!(serde_json::to_value(&msg).unwrap(), without);
6347
6348 assert_eq!(
6350 RefusalFallbackScope::from("workspace").as_str(),
6351 "workspace"
6352 );
6353 }
6354}