Skip to main content

ai_agent/types/
api_types.rs

1// Source: /data/home/swei/claudecode/openclaudecode/src/utils/filePersistence/types.ts
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Message {
9    pub role: MessageRole,
10    pub content: String,
11    /// Unique identifier for this message (used by session memory to track extraction boundary)
12    #[serde(skip_serializing_if = "Option::is_none", default)]
13    pub uuid: Option<String>,
14    #[serde(skip_serializing_if = "Option::is_none", default)]
15    pub attachments: Option<Vec<Attachment>>,
16    /// Tool call ID for tool role messages (required by OpenAI API)
17    #[serde(skip_serializing_if = "Option::is_none", default)]
18    pub tool_call_id: Option<String>,
19    /// Tool calls for assistant messages (required to pair with tool results)
20    #[serde(skip_serializing_if = "Option::is_none", default)]
21    pub tool_calls: Option<Vec<ToolCall>>,
22    /// Indicates if this message is an error (for tool results)
23    #[serde(skip_serializing_if = "Option::is_none", default)]
24    pub is_error: Option<bool>,
25    /// Indicates if this is a meta/system message (e.g., from prependUserContext)
26    #[serde(skip_serializing_if = "Option::is_none", default)]
27    pub is_meta: Option<bool>,
28    /// Indicates this assistant message was generated from an API error (not model-produced text)
29    #[serde(skip_serializing_if = "Option::is_none", default)]
30    pub is_api_error_message: Option<bool>,
31    /// Structured error details for API error messages (raw API error string)
32    #[serde(skip_serializing_if = "Option::is_none", default)]
33    pub error_details: Option<String>,
34}
35
36impl Default for Message {
37    fn default() -> Self {
38        Self {
39            role: MessageRole::User,
40            content: String::new(),
41            uuid: None,
42            attachments: None,
43            tool_call_id: None,
44            tool_calls: None,
45            is_error: None,
46            is_meta: None,
47            is_api_error_message: None,
48            error_details: None,
49        }
50    }
51}
52
53/// A tool call from the model
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ToolCall {
56    pub id: String,
57    #[serde(default = "default_tool_call_type")]
58    pub r#type: String,
59    pub name: String,
60    pub arguments: serde_json::Value,
61}
62
63fn default_tool_call_type() -> String {
64    "function".to_string()
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
68#[serde(rename_all = "lowercase")]
69pub enum MessageRole {
70    #[default]
71    User,
72    Assistant,
73    #[serde(rename = "tool")]
74    Tool,
75    System,
76}
77
78impl MessageRole {
79    /// Convert the role to its string representation for API serialization
80    pub fn as_str(&self) -> &'static str {
81        match self {
82            MessageRole::User => "user",
83            MessageRole::Assistant => "assistant",
84            MessageRole::Tool => "tool",
85            MessageRole::System => "system",
86        }
87    }
88}
89
90/// Attachments for messages - files, images, etc.
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(tag = "type")]
93pub enum Attachment {
94    /// File attachment (at-mentioned file)
95    File { path: String },
96    /// Reference to a file previously read
97    AlreadyReadFile { path: String, content: String },
98    /// PDF reference
99    PdfReference { path: String },
100    /// Text file that was edited
101    EditedTextFile { filename: String, snippet: String },
102    /// Image file that was edited
103    EditedImageFile { filename: String },
104    /// Directory listing
105    Directory {
106        path: String,
107        content: String,
108        display_path: String,
109    },
110    /// Selected lines in IDE
111    SelectedLinesInIde {
112        ide_name: String,
113        filename: String,
114        start_line: u32,
115        end_line: u32,
116    },
117    /// Memory file reference
118    MemoryFile { path: String },
119    /// Skill listing attachment
120    SkillListing { skills: Vec<SkillInfo> },
121    /// Invoked skills attachment
122    InvokedSkills { skills: Vec<InvokedSkill> },
123    /// Task status
124    TaskStatus {
125        task_id: String,
126        description: String,
127        status: String,
128    },
129    /// Plan file reference
130    PlanFileReference { path: String },
131    /// MCP tool resources
132    McpResources { tools: Vec<String> },
133    /// Deferred tools delta
134    DeferredTools { tools: Vec<String> },
135    /// Agent listing
136    AgentListing { agents: Vec<String> },
137    /// Custom attachment
138    Custom {
139        name: String,
140        content: serde_json::Value,
141    },
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct SkillInfo {
146    pub name: String,
147    pub description: String,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct InvokedSkill {
152    pub name: String,
153    pub path: String,
154    pub content: String,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, Default)]
158pub struct TokenUsage {
159    pub input_tokens: u64,
160    pub output_tokens: u64,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub cache_creation_input_tokens: Option<u64>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub cache_read_input_tokens: Option<u64>,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub iterations: Option<Vec<IterationUsage>>,
167}
168
169/// Per-iteration usage from the Anthropic API (server-side tool loops)
170#[derive(Debug, Clone, Serialize, Deserialize, Default)]
171pub struct IterationUsage {
172    pub input_tokens: u64,
173    pub output_tokens: u64,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct ToolDefinition {
178    pub name: String,
179    pub description: String,
180    pub input_schema: ToolInputSchema,
181    /// Optional annotations for tool classification
182    #[serde(default, skip_serializing_if = "Option::is_none")]
183    pub annotations: Option<ToolAnnotations>,
184    /// When true, this tool is deferred (requires ToolSearch to load)
185    #[serde(default, skip_serializing_if = "Option::is_none")]
186    pub should_defer: Option<bool>,
187    /// When true, this tool is never deferred (full schema always sent)
188    #[serde(default, skip_serializing_if = "Option::is_none")]
189    pub always_load: Option<bool>,
190    /// When true, this is an MCP tool
191    #[serde(default, skip_serializing_if = "Option::is_none")]
192    pub is_mcp: Option<bool>,
193    /// Short capability phrase for keyword search (3-10 words)
194    #[serde(default, skip_serializing_if = "Option::is_none")]
195    pub search_hint: Option<String>,
196    /// Optional aliases for backwards compatibility when a tool is renamed
197    #[serde(default, skip_serializing_if = "Option::is_none")]
198    pub aliases: Option<Vec<String>>,
199    /// Human-readable name for display in the UI (e.g., "Update" vs "Edit")
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub user_facing_name: Option<String>,
202    /// Interrupt behavior: 'cancel' (stop on user interrupt) or 'block' (wait for completion)
203    #[serde(rename = "interruptBehavior", default, skip_serializing_if = "Option::is_none")]
204    pub interrupt_behavior: Option<String>,
205}
206
207impl Default for ToolDefinition {
208    fn default() -> Self {
209        Self {
210            name: String::new(),
211            description: String::new(),
212            input_schema: ToolInputSchema::default(),
213            annotations: None,
214            should_defer: None,
215            always_load: None,
216            is_mcp: None,
217            search_hint: None,
218            aliases: None,
219            user_facing_name: None,
220            interrupt_behavior: None,
221        }
222    }
223}
224
225impl ToolDefinition {
226    /// Create a new tool definition (annotations defaults to None)
227    pub fn new(name: &str, description: &str, input_schema: ToolInputSchema) -> Self {
228        Self {
229            name: name.to_string(),
230            description: description.to_string(),
231            input_schema,
232            annotations: None,
233            should_defer: None,
234            always_load: None,
235            is_mcp: None,
236            search_hint: None,
237            aliases: None,
238            user_facing_name: None,
239            interrupt_behavior: None,
240        }
241    }
242
243    /// Build with deferred loading support
244    pub fn with_deferred(mut self, should_defer: bool) -> Self {
245        self.should_defer = Some(should_defer);
246        self
247    }
248
249    /// Mark as always loaded (never deferred)
250    pub fn with_always_load(mut self) -> Self {
251        self.always_load = Some(true);
252        self
253    }
254
255    /// Mark as MCP tool
256    pub fn with_mcp(mut self) -> Self {
257        self.is_mcp = Some(true);
258        self
259    }
260
261    /// Set search hint for keyword search
262    pub fn with_search_hint(mut self, hint: &str) -> Self {
263        self.search_hint = Some(hint.to_string());
264        self
265    }
266
267    /// Get interrupt behavior: 'cancel' (stop on user interrupt) or 'block' (wait for completion)
268    /// Default: 'block'
269    pub fn interrupt_behavior(&self) -> crate::tools::types::InterruptBehavior {
270        match self.interrupt_behavior.as_deref() {
271            Some("cancel") => crate::tools::types::InterruptBehavior::Cancel,
272            _ => crate::tools::types::InterruptBehavior::Block,
273        }
274    }
275
276    /// Backfill observable input before observers see it (hooks, events, transcript).
277    /// Mutates in place to add legacy/derived fields. Must be idempotent.
278    /// Default: no-op. Override via `with_interrupt_behavior` for tools that need it.
279    pub fn backfill_observable_input(&self, _input: &mut serde_json::Value) {
280        // Default no-op. Tools that need backfilling should set interrupt_behavior
281        // or use the Tool trait's backfill_observable_input directly.
282    }
283
284    /// Check if tool can run concurrently (default: false)
285    pub fn is_concurrency_safe(&self, _input: &serde_json::Value) -> bool {
286        self.annotations
287            .as_ref()
288            .and_then(|a| a.concurrency_safe)
289            .unwrap_or(false)
290    }
291
292    /// Check if tool only reads data (default: false)
293    pub fn is_read_only(&self, _input: &serde_json::Value) -> bool {
294        if let Some(ref a) = self.annotations {
295            if let Some(ro) = a.read_only {
296                return ro;
297            }
298        }
299        // Default: tools that only read
300        matches!(
301            self.name.as_str(),
302            "Read" | "Glob" | "Grep" | "Search" | "WebFetch" | "WebSearch"
303        )
304    }
305
306    /// Check if tool performs destructive operations (default: false)
307    pub fn is_destructive(&self, input: &serde_json::Value) -> bool {
308        if let Some(ref a) = self.annotations {
309            if let Some(d) = a.destructive {
310                return d;
311            }
312        }
313        // Default: check input for destructive commands
314        let input_str = input.to_string();
315        matches!(self.name.as_str(), "Bash" | "Write" | "Edit")
316            && (input_str.contains("rm -rf")
317                || input_str.contains("rm /")
318                || input_str.contains("dd if=")
319                || input_str.contains("format"))
320    }
321
322    /// Check if tool is idempotent (can be run multiple times safely)
323    pub fn is_idempotent(&self) -> bool {
324        self.annotations
325            .as_ref()
326            .and_then(|a| a.idempotent)
327            .unwrap_or(false)
328    }
329
330    /// Get tool use summary for compact views
331    pub fn get_use_summary(&self, input: &serde_json::Value) -> String {
332        match self.name.as_str() {
333            "Bash" => {
334                if let Some(cmd) = input.get("command").and_then(|v| v.as_str()) {
335                    let truncated = if cmd.len() > 50 {
336                        format!("{}...", &cmd[..50])
337                    } else {
338                        cmd.to_string()
339                    };
340                    format!("Bash: {}", truncated)
341                } else {
342                    "Bash".to_string()
343                }
344            }
345            "Read" => {
346                if let Some(path) = input.get("path").and_then(|v| v.as_str()) {
347                    format!("Read: {}", path)
348                } else {
349                    "Read".to_string()
350                }
351            }
352            "Write" => {
353                if let Some(path) = input.get("path").and_then(|v| v.as_str()) {
354                    format!("Write: {}", path)
355                } else {
356                    "Write".to_string()
357                }
358            }
359            "Edit" => {
360                if let Some(path) = input.get("file_path").and_then(|v| v.as_str()) {
361                    format!("Edit: {}", path)
362                } else {
363                    "Edit".to_string()
364                }
365            }
366            "Glob" => {
367                if let Some(pattern) = input.get("pattern").and_then(|v| v.as_str()) {
368                    format!("Glob: {}", pattern)
369                } else {
370                    "Glob".to_string()
371                }
372            }
373            "Grep" => {
374                if let Some(pattern) = input.get("pattern").and_then(|v| v.as_str()) {
375                    format!("Grep: {}", pattern)
376                } else {
377                    "Grep".to_string()
378                }
379            }
380            _ => self.name.clone(),
381        }
382    }
383}
384
385/// Tool annotations for classification
386#[derive(Debug, Clone, Serialize, Deserialize, Default)]
387pub struct ToolAnnotations {
388    /// Tool can run concurrently with other tools
389    #[serde(rename = "concurrencySafe", skip_serializing_if = "Option::is_none")]
390    pub concurrency_safe: Option<bool>,
391    /// Tool only reads data (doesn't modify files/system)
392    #[serde(rename = "readOnly", skip_serializing_if = "Option::is_none")]
393    pub read_only: Option<bool>,
394    /// Tool performs destructive operations
395    #[serde(rename = "destructive", skip_serializing_if = "Option::is_none")]
396    pub destructive: Option<bool>,
397    /// Tool is idempotent (safe to run multiple times)
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub idempotent: Option<bool>,
400    /// Tool operates on open world (external URLs, etc.)
401    #[serde(rename = "openWorld", skip_serializing_if = "Option::is_none")]
402    pub open_world: Option<bool>,
403}
404
405impl ToolAnnotations {
406    /// Create annotations for read-only tools
407    pub fn read_only() -> Self {
408        Self {
409            read_only: Some(true),
410            ..Default::default()
411        }
412    }
413
414    /// Create annotations for destructive tools
415    pub fn destructive() -> Self {
416        Self {
417            destructive: Some(true),
418            ..Default::default()
419        }
420    }
421
422    /// Create annotations for concurrent-safe tools
423    pub fn concurrency_safe() -> Self {
424        Self {
425            concurrency_safe: Some(true),
426            ..Default::default()
427        }
428    }
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, Default)]
432pub struct ToolInputSchema {
433    #[serde(rename = "type")]
434    pub schema_type: String,
435    pub properties: serde_json::Value,
436    pub required: Option<Vec<String>>,
437}
438
439#[derive(Debug, Clone, Deserialize)]
440pub struct ToolContext {
441    pub cwd: String,
442    #[serde(skip)]
443    pub abort_signal: std::sync::Arc<crate::utils::AbortSignal>,
444}
445
446// Skip Serialize on ToolContext because Arc<AbortSignal> doesn't implement Serialize
447impl Serialize for ToolContext {
448    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
449    where
450        S: serde::Serializer,
451    {
452        // Serialize only the cwd field, skipping abort_signal
453        use serde::ser::SerializeStruct;
454        let mut state = serializer.serialize_struct("ToolContext", 1)?;
455        state.serialize_field("cwd", &self.cwd)?;
456        state.end()
457    }
458}
459
460impl Default for ToolContext {
461    fn default() -> Self {
462        Self {
463            cwd: String::new(),
464            abort_signal: std::sync::Arc::new(crate::utils::AbortSignal::new(0)),
465        }
466    }
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
470pub struct ToolResult {
471    #[serde(rename = "type")]
472    pub result_type: String,
473    pub tool_use_id: String,
474    pub content: String,
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub is_error: Option<bool>,
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub was_persisted: Option<bool>,
479}
480
481impl Default for ToolResult {
482    fn default() -> Self {
483        Self {
484            result_type: String::new(),
485            tool_use_id: String::new(),
486            content: String::new(),
487            is_error: None,
488            was_persisted: None,
489        }
490    }
491}
492
493/// Content block within a tool_result — either text or tool_reference
494#[derive(Debug, Clone, Serialize, Deserialize)]
495#[serde(untagged)]
496pub enum ToolResultContent {
497    /// Plain text content
498    Text {
499        #[serde(rename = "type")]
500        content_type: String,
501        text: String,
502    },
503    /// Reference to a deferred tool that the API should expand
504    ToolReference {
505        #[serde(rename = "type")]
506        content_type: String,
507        tool_name: String,
508    },
509}
510
511impl ToolResultContent {
512    pub fn text(text: &str) -> Self {
513        Self::Text {
514            content_type: "text".to_string(),
515            text: text.to_string(),
516        }
517    }
518
519    pub fn tool_reference(tool_name: &str) -> Self {
520        Self::ToolReference {
521            content_type: "tool_reference".to_string(),
522            tool_name: tool_name.to_string(),
523        }
524    }
525}
526
527/// Tool result with structured content (supports tool_reference blocks)
528#[derive(Debug, Clone, Serialize, Deserialize)]
529pub struct ToolResultStructured {
530    #[serde(rename = "type")]
531    pub result_type: String,
532    pub tool_use_id: String,
533    pub content: Vec<ToolResultContent>,
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub is_error: Option<bool>,
536}
537
538/// Thinking configuration for the API (matches TypeScript ThinkingConfig)
539#[derive(Debug, Clone, Serialize, Deserialize)]
540#[serde(tag = "type")]
541pub enum ThinkingConfig {
542    /// Adaptive thinking - model decides the best thinking budget
543    Adaptive,
544    /// Enabled with a specific budget token count
545    Enabled {
546        #[serde(rename = "budgetTokens")]
547        budget_tokens: u32,
548    },
549    /// Thinking disabled
550    Disabled,
551}
552
553impl Default for ThinkingConfig {
554    fn default() -> Self {
555        // Default to adaptive thinking (matches TypeScript shouldEnableThinkingByDefault)
556        ThinkingConfig::Adaptive
557    }
558}
559
560/// Exit reasons from the query loop (matches TypeScript Terminal type)
561#[derive(Debug, Clone, Serialize, Deserialize)]
562pub enum ExitReason {
563    /// Normal completion - no more tool calls needed
564    Completed,
565    /// Maximum turns reached
566    MaxTurns { max_turns: u32, turn_count: u32 },
567    /// Aborted during model streaming
568    AbortedStreaming { reason: String },
569    /// Aborted during tool execution
570    AbortedTools { reason: String },
571    /// Hook prevented continuation
572    HookStopped,
573    /// Stop hook prevented continuation
574    StopHookPrevented,
575    /// Context too long (prompt_too_long)
576    PromptTooLong { error: Option<String> },
577    /// Media error (image too large, etc.)
578    ImageError { error: String },
579    /// Model/runtime error
580    ModelError { error: String },
581    /// Token limit reached (blocking_limit)
582    BlockingLimit,
583    /// Token budget continuation ended early
584    TokenBudgetExhausted { reason: String },
585    /// Max output tokens reached (during generation)
586    MaxTokens,
587    /// USD budget exceeded
588    MaxBudgetExceeded { max_budget_usd: f64 },
589}
590
591impl Default for ExitReason {
592    fn default() -> Self {
593        ExitReason::Completed
594    }
595}
596
597/// Compact progress event types
598#[derive(Debug, Clone, Serialize, Deserialize)]
599#[serde(tag = "type")]
600pub enum CompactProgressEvent {
601    #[serde(rename = "hooks_start")]
602    HooksStart {
603        #[serde(rename = "hookType")]
604        hook_type: CompactHookType,
605    },
606    #[serde(rename = "compact_start")]
607    CompactStart,
608    #[serde(rename = "compact_end")]
609    CompactEnd {
610        /// Human-readable summary emitted to TUI/CLI after successful compaction,
611        /// e.g. "Conversation compacted: 120.3k → 8.2k tokens (93%)"
612        #[serde(skip_serializing_if = "Option::is_none")]
613        message: Option<String>,
614    },
615}
616
617/// Compact hook types
618#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
619#[serde(rename_all = "snake_case")]
620pub enum CompactHookType {
621    PreCompact,
622    PostCompact,
623    SessionStart,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct QueryResult {
628    pub text: String,
629    pub usage: TokenUsage,
630    pub num_turns: u32,
631    pub duration_ms: u64,
632    /// Why the query loop terminated
633    pub exit_reason: ExitReason,
634}
635
636/// Agent event types for streaming updates (matches TypeScript behavior)
637#[derive(Debug, Clone)]
638pub enum AgentEvent {
639    /// Tool is about to be executed
640    ToolStart {
641        tool_name: String,
642        tool_call_id: String,
643        input: serde_json::Value,
644        /// Human-readable display name (e.g., "Update" for edits, "Create" for new files).
645        /// Overrides `tool_name` for display in the TUI.
646        display_name: Option<String>,
647        /// Short summary for compact views (e.g., file path for FileEdit).
648        summary: Option<String>,
649        /// Activity description for spinner display (e.g., "Updating file.rs").
650        activity_description: Option<String>,
651    },
652    /// Tool execution completed
653    ToolComplete {
654        tool_name: String,
655        tool_call_id: String,
656        result: ToolResult,
657        /// Human-readable display name (e.g., "Update(file.rs) (1 +, 1 -)").
658        display_name: Option<String>,
659        /// Rendered result message from Tool::render_tool_result_message.
660        rendered_result: Option<String>,
661    },
662    /// Tool execution failed
663    ToolError {
664        tool_name: String,
665        tool_call_id: String,
666        error: String,
667    },
668    /// LLM is thinking (started a new turn)
669    Thinking { turn: u32 },
670    /// Final response ready
671    Done { result: QueryResult },
672    /// Message started (streaming begins)
673    MessageStart { message_id: String },
674    /// Content block started (matches TypeScript StreamEvent content_block_start)
675    ContentBlockStart { index: u32, block_type: String },
676    /// Content block delta (matches TypeScript StreamEvent content_block_delta)
677    ContentBlockDelta { index: u32, delta: ContentDelta },
678    /// Content block stopped (matches TypeScript StreamEvent content_block_stop)
679    ContentBlockStop { index: u32 },
680    /// Message stopped (streaming ends)
681    MessageStop,
682    /// Request started (before API call) - matches TypeScript 'stream_request_start'
683    RequestStart,
684    /// Request completed (API response received, streaming finished)
685    /// Matches TypeScript 'stream_request_end' — useful for TUI spinner management.
686    StreamRequestEnd,
687    /// Rate limit status change — notifies TUI/CLI when a rate limit is hit or cleared
688    RateLimitStatus {
689        /// true if currently rate-limited, false if rate limit has cleared
690        is_rate_limited: bool,
691        /// Optional retry-after seconds (if the server provided it)
692        retry_after_secs: Option<f64>,
693    },
694    /// Max turns reached - matches TypeScript 'max_turns_reached' attachment
695    MaxTurnsReached { max_turns: u32, turn_count: u32 },
696    /// Tombstone event for orphaned messages on streaming fallback
697    /// (matches TypeScript 'tombstone' event)
698    Tombstone { message: String },
699    /// Compact progress event (hooks_start, compact_start, compact_end)
700    /// Matches TypeScript ToolUseContext.onCompactProgress
701    Compact { event: CompactProgressEvent },
702    /// Actual API token usage from message_delta event
703    /// Emitted after all content_block_stop events, before message_stop
704    TokenUsage {
705        usage: TokenUsage,
706        cost: f64,
707    },
708    /// API retry progress — emitted during 429/529 retry backoff
709    /// Matches TypeScript's 'api_retry' subtype yielded by QueryEngine
710    /// from createSystemAPIErrorMessage in withRetry.ts
711    ApiRetry {
712        /// Current retry attempt (1-based)
713        attempt: u32,
714        /// Maximum retries configured
715        max_retries: u32,
716        /// Delay in milliseconds before next retry
717        retry_delay_ms: u64,
718        /// HTTP error status code that triggered the retry
719        error_status: Option<u16>,
720        /// Categorized error type (e.g., "rate_limit", "server_error")
721        error: String,
722    },
723}
724
725/// Content delta types for streaming
726#[derive(Debug, Clone)]
727pub enum ContentDelta {
728    /// Text content delta
729    Text { text: String },
730    /// Thinking content delta (internal reasoning)
731    Thinking { text: String },
732    /// Tool use input delta (streaming tool arguments)
733    ToolUse {
734        id: String,
735        name: String,
736        input: serde_json::Value,
737        is_complete: bool,
738    },
739}
740
741// --------------------------------------------------------------------------
742// MCP Types
743// --------------------------------------------------------------------------
744
745/// MCP server configuration (union type)
746#[derive(Debug, Clone, Serialize, Deserialize)]
747#[serde(untagged)]
748pub enum McpServerConfig {
749    Stdio(McpStdioConfig),
750    Sse(McpSseConfig),
751    Http(McpHttpConfig),
752}
753
754#[derive(Debug, Clone, Serialize, Deserialize)]
755#[serde(rename_all = "camelCase")]
756pub struct McpStdioConfig {
757    #[serde(default = "default_stdio_type")]
758    pub transport_type: Option<String>,
759    pub command: String,
760    pub args: Option<Vec<String>>,
761    pub env: Option<std::collections::HashMap<String, String>>,
762}
763
764fn default_stdio_type() -> Option<String> {
765    Some("stdio".to_string())
766}
767
768#[derive(Debug, Clone, Serialize, Deserialize)]
769#[serde(rename_all = "camelCase")]
770pub struct McpSseConfig {
771    pub transport_type: String,
772    pub url: String,
773    pub headers: Option<std::collections::HashMap<String, String>>,
774}
775
776#[derive(Debug, Clone, Serialize, Deserialize)]
777#[serde(rename_all = "camelCase")]
778pub struct McpHttpConfig {
779    pub transport_type: String,
780    pub url: String,
781    pub headers: Option<std::collections::HashMap<String, String>>,
782}
783
784/// MCP connection status
785#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
786#[serde(rename_all = "lowercase")]
787pub enum McpConnectionStatus {
788    Connected,
789    Disconnected,
790    Error,
791}
792
793/// MCP tool representation from server
794#[derive(Debug, Clone, Serialize, Deserialize)]
795pub struct McpTool {
796    pub name: String,
797    pub description: Option<String>,
798    #[serde(rename = "inputSchema")]
799    pub input_schema: Option<serde_json::Value>,
800}
801
802// --------------------------------------------------------------------------
803// Tool Types (translated from Tool.ts)
804// --------------------------------------------------------------------------
805
806/// Query chain tracking for nested agent calls
807#[derive(Debug, Clone, Serialize, Deserialize)]
808pub struct QueryChainTracking {
809    pub chain_id: String,
810    pub depth: u32,
811}
812
813/// Validation result for tool input
814/// Source: /data/home/swei/claudecode/openclaudecode/src/Tool.ts
815#[derive(Debug, Clone, Serialize, Deserialize)]
816#[serde(tag = "result")]
817pub enum ValidationResult {
818    /// Validation passed
819    #[serde(rename = "true")]
820    Valid,
821    /// Validation failed with error details
822    Invalid {
823        /// Error message describing the validation failure
824        message: String,
825        /// Error code for the validation failure
826        #[serde(rename = "errorCode")]
827        error_code: i32,
828    },
829}
830
831impl ValidationResult {
832    /// Create a valid validation result
833    pub fn valid() -> Self {
834        ValidationResult::Valid
835    }
836
837    /// Create an invalid validation result
838    pub fn invalid(message: String, error_code: i32) -> Self {
839        ValidationResult::Invalid {
840            message,
841            error_code,
842        }
843    }
844
845    /// Check if the validation passed
846    pub fn is_valid(&self) -> bool {
847        matches!(self, ValidationResult::Valid)
848    }
849}
850
851/// Tool permission mode
852#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
853#[serde(rename_all = "lowercase")]
854pub enum PermissionMode {
855    Default,
856    Auto,
857    #[serde(rename = "auto-accept")]
858    AutoAccept,
859    #[serde(rename = "auto-deny")]
860    AutoDeny,
861    Bypass,
862}
863
864/// Additional working directory configuration
865#[derive(Debug, Clone, Serialize, Deserialize)]
866pub struct AdditionalWorkingDirectory {
867    pub path: String,
868    #[serde(rename = "permissionMode")]
869    pub permission_mode: Option<PermissionMode>,
870}
871
872/// Permission result from permission checks
873#[derive(Debug, Clone, Serialize, Deserialize)]
874pub struct PermissionResult {
875    pub behavior: PermissionBehavior,
876    #[serde(rename = "updatedInput")]
877    pub updated_input: Option<serde_json::Value>,
878    #[serde(skip_serializing_if = "Option::is_none")]
879    pub message: Option<String>,
880}
881
882/// Permission behavior types
883#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
884#[serde(rename_all = "kebab-case")]
885pub enum PermissionBehavior {
886    Allow,
887    Deny,
888    Ask,
889}
890
891/// Additional tool permission rules by source
892pub type ToolPermissionRulesBySource = HashMap<String, Vec<String>>;
893
894/// Tool permission context - full context for permission checks
895#[derive(Debug, Clone, Serialize, Deserialize)]
896pub struct ToolPermissionContext {
897    pub mode: PermissionMode,
898    #[serde(rename = "additionalWorkingDirectories")]
899    pub additional_working_directories: HashMap<String, AdditionalWorkingDirectory>,
900    #[serde(rename = "alwaysAllowRules")]
901    pub always_allow_rules: ToolPermissionRulesBySource,
902    #[serde(rename = "alwaysDenyRules")]
903    pub always_deny_rules: ToolPermissionRulesBySource,
904    #[serde(rename = "alwaysAskRules")]
905    pub always_ask_rules: ToolPermissionRulesBySource,
906    #[serde(rename = "isBypassPermissionsModeAvailable")]
907    pub is_bypass_permissions_mode_available: bool,
908    #[serde(
909        rename = "isAutoModeAvailable",
910        skip_serializing_if = "Option::is_none"
911    )]
912    pub is_auto_mode_available: Option<bool>,
913    #[serde(
914        rename = "strippedDangerousRules",
915        skip_serializing_if = "Option::is_none"
916    )]
917    pub stripped_dangerous_rules: Option<ToolPermissionRulesBySource>,
918    #[serde(
919        rename = "shouldAvoidPermissionPrompts",
920        skip_serializing_if = "Option::is_none"
921    )]
922    pub should_avoid_permission_prompts: Option<bool>,
923    #[serde(
924        rename = "awaitAutomatedChecksBeforeDialog",
925        skip_serializing_if = "Option::is_none"
926    )]
927    pub await_automated_checks_before_dialog: Option<bool>,
928    #[serde(rename = "prePlanMode", skip_serializing_if = "Option::is_none")]
929    pub pre_plan_mode: Option<PermissionMode>,
930}
931
932impl Default for ToolPermissionContext {
933    fn default() -> Self {
934        Self {
935            mode: PermissionMode::Default,
936            additional_working_directories: HashMap::new(),
937            always_allow_rules: HashMap::new(),
938            always_deny_rules: HashMap::new(),
939            always_ask_rules: HashMap::new(),
940            is_bypass_permissions_mode_available: false,
941            is_auto_mode_available: None,
942            stripped_dangerous_rules: None,
943            should_avoid_permission_prompts: None,
944            await_automated_checks_before_dialog: None,
945            pre_plan_mode: None,
946        }
947    }
948}
949
950/// Create empty tool permission context
951pub fn get_empty_tool_permission_context() -> ToolPermissionContext {
952    ToolPermissionContext::default()
953}
954
955/// Tool input JSON schema (for MCP tools)
956#[derive(Debug, Clone, Serialize, Deserialize)]
957pub struct ToolInputJSONSchema {
958    #[serde(flatten)]
959    pub properties: serde_json::Value,
960    #[serde(rename = "type")]
961    pub schema_type: String,
962}
963
964// --------------------------------------------------------------------------
965// Tool Progress Types (translated from types/tools.ts)
966// --------------------------------------------------------------------------
967
968/// Bash tool progress data
969#[derive(Debug, Clone, Serialize, Deserialize)]
970pub struct BashProgress {
971    #[serde(rename = "shell")]
972    pub shell: Option<String>,
973    #[serde(rename = "command")]
974    pub command: Option<String>,
975}
976
977/// REPL tool progress data
978#[derive(Debug, Clone, Serialize, Deserialize)]
979pub struct ReplProgress {
980    #[serde(rename = "input")]
981    pub input: Option<String>,
982    #[serde(rename = "toolName")]
983    pub tool_name: Option<String>,
984    #[serde(rename = "toolCallId")]
985    pub tool_call_id: Option<String>,
986}
987
988/// MCP tool progress data
989#[derive(Debug, Clone, Serialize, Deserialize)]
990pub struct McpProgress {
991    #[serde(rename = "serverName")]
992    pub server_name: String,
993    #[serde(rename = "toolName")]
994    pub tool_name: String,
995    #[serde(rename = "progress")]
996    pub progress: Option<serde_json::Value>,
997}
998
999/// Web search progress data
1000#[derive(Debug, Clone, Serialize, Deserialize)]
1001pub struct WebSearchProgress {
1002    #[serde(rename = "query")]
1003    pub query: String,
1004    #[serde(rename = "currentStep")]
1005    pub current_step: Option<String>,
1006}
1007
1008/// Task output progress data
1009#[derive(Debug, Clone, Serialize, Deserialize)]
1010pub struct TaskOutputProgress {
1011    #[serde(rename = "taskId")]
1012    pub task_id: String,
1013    #[serde(rename = "output")]
1014    pub output: Option<String>,
1015}
1016
1017/// Skill tool progress data
1018#[derive(Debug, Clone, Serialize, Deserialize)]
1019pub struct SkillToolProgress {
1020    #[serde(rename = "skill")]
1021    pub skill: String,
1022    #[serde(rename = "step")]
1023    pub step: Option<String>,
1024}
1025
1026/// Agent tool progress data
1027#[derive(Debug, Clone, Serialize, Deserialize)]
1028pub struct AgentToolProgress {
1029    #[serde(rename = "description")]
1030    pub description: String,
1031    #[serde(rename = "subagentType")]
1032    pub subagent_type: Option<String>,
1033}
1034
1035/// Tool progress data - enum of all progress types
1036#[derive(Debug, Clone, Serialize, Deserialize)]
1037#[serde(tag = "type")]
1038pub enum ToolProgressData {
1039    #[serde(rename = "bash_progress")]
1040    BashProgress(BashProgress),
1041    #[serde(rename = "repl_progress")]
1042    ReplProgress(ReplProgress),
1043    #[serde(rename = "mcp_progress")]
1044    McpProgress(McpProgress),
1045    #[serde(rename = "web_search_progress")]
1046    WebSearchProgress(WebSearchProgress),
1047    #[serde(rename = "task_output_progress")]
1048    TaskOutputProgress(TaskOutputProgress),
1049    #[serde(rename = "skill_progress")]
1050    SkillProgress(SkillToolProgress),
1051    #[serde(rename = "agent_progress")]
1052    AgentProgress(AgentToolProgress),
1053}
1054
1055/// Tool progress with tool use ID
1056#[derive(Debug, Clone, Serialize, Deserialize)]
1057pub struct ToolProgress<P: Clone + serde::Serialize> {
1058    #[serde(rename = "toolUseID")]
1059    pub tool_use_id: String,
1060    pub data: P,
1061}
1062
1063/// Filter progress messages to only tool progress (not hook progress)
1064pub fn filter_tool_progress_messages(
1065    progress_messages: &[serde_json::Value],
1066) -> Vec<serde_json::Value> {
1067    progress_messages
1068        .iter()
1069        .filter(|msg| {
1070            let data_type = msg.get("data").and_then(|d| d.get("type"));
1071            data_type.map(|t| t != "hook_progress").unwrap_or(true)
1072        })
1073        .cloned()
1074        .collect()
1075}