Skip to main content

claude_codes/
tool_inputs.rs

1//! Typed tool input definitions for Claude Code tools.
2//!
3//! This module provides strongly-typed structs for the input parameters of each
4//! Claude Code tool. Using these types instead of raw `serde_json::Value` provides:
5//!
6//! - Compile-time type checking
7//! - IDE autocompletion and documentation
8//! - Self-documenting API
9//!
10//! # Example
11//!
12//! ```
13//! use claude_codes::{ToolInput, BashInput};
14//!
15//! // Parse a tool input from JSON
16//! let json = serde_json::json!({
17//!     "command": "ls -la",
18//!     "description": "List files in current directory"
19//! });
20//!
21//! let input: ToolInput = serde_json::from_value(json).unwrap();
22//! if let ToolInput::Bash(bash) = input {
23//!     assert_eq!(bash.command, "ls -la");
24//! }
25//! ```
26
27use serde::{Deserialize, Deserializer, Serialize, Serializer};
28use serde_json::Value;
29use std::collections::HashMap;
30use std::fmt;
31
32// ============================================================================
33// Individual Tool Input Structs
34// ============================================================================
35
36/// Input for the Bash tool - executes shell commands.
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38pub struct BashInput {
39    /// The bash command to execute (required)
40    pub command: String,
41
42    /// Human-readable description of what the command does
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub description: Option<String>,
45
46    /// Timeout in milliseconds (max 600000)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub timeout: Option<u64>,
49
50    /// Whether to run the command in the background
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub run_in_background: Option<bool>,
53}
54
55/// Input for the Read tool - reads file contents.
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
57pub struct ReadInput {
58    /// The absolute path to the file to read
59    pub file_path: String,
60
61    /// The line number to start reading from (1-indexed)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub offset: Option<i64>,
64
65    /// The number of lines to read
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub limit: Option<i64>,
68}
69
70/// Input for the Write tool - writes content to a file.
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72pub struct WriteInput {
73    /// The absolute path to the file to write
74    pub file_path: String,
75
76    /// The content to write to the file
77    pub content: String,
78}
79
80/// Input for the Edit tool - performs string replacements in files.
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
82pub struct EditInput {
83    /// The absolute path to the file to modify
84    pub file_path: String,
85
86    /// The text to replace
87    pub old_string: String,
88
89    /// The text to replace it with
90    pub new_string: String,
91
92    /// Replace all occurrences of old_string (default false)
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub replace_all: Option<bool>,
95}
96
97/// Input for the Glob tool - finds files matching a pattern.
98///
99/// The `deny_unknown_fields` attribute ensures Glob only matches exact
100/// Glob inputs and doesn't accidentally match Grep inputs (which share
101/// the `pattern` field but have additional Grep-specific fields).
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
103#[serde(deny_unknown_fields)]
104pub struct GlobInput {
105    /// The glob pattern to match files against (e.g., "**/*.rs")
106    pub pattern: String,
107
108    /// The directory to search in (defaults to current working directory)
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub path: Option<String>,
111}
112
113/// Input for the Grep tool - searches file contents.
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
115pub struct GrepInput {
116    /// The regular expression pattern to search for
117    pub pattern: String,
118
119    /// File or directory to search in
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub path: Option<String>,
122
123    /// Glob pattern to filter files (e.g., "*.js")
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub glob: Option<String>,
126
127    /// File type to search (e.g., "js", "py", "rust")
128    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
129    pub file_type: Option<String>,
130
131    /// Case insensitive search
132    #[serde(rename = "-i", skip_serializing_if = "Option::is_none")]
133    pub case_insensitive: Option<bool>,
134
135    /// Show line numbers in output
136    #[serde(rename = "-n", skip_serializing_if = "Option::is_none")]
137    pub line_numbers: Option<bool>,
138
139    /// Number of lines to show after each match
140    #[serde(rename = "-A", skip_serializing_if = "Option::is_none")]
141    pub after_context: Option<u32>,
142
143    /// Number of lines to show before each match
144    #[serde(rename = "-B", skip_serializing_if = "Option::is_none")]
145    pub before_context: Option<u32>,
146
147    /// Number of lines to show before and after each match
148    #[serde(rename = "-C", skip_serializing_if = "Option::is_none")]
149    pub context: Option<u32>,
150
151    /// Output mode: content, files_with_matches, or count
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub output_mode: Option<GrepOutputMode>,
154
155    /// Enable multiline mode
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub multiline: Option<bool>,
158
159    /// Limit output to first N lines/entries
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub head_limit: Option<u32>,
162
163    /// Skip first N lines/entries
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub offset: Option<u32>,
166}
167
168/// Input for the Task tool - launches subagents.
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170pub struct TaskInput {
171    /// A short (3-5 word) description of the task
172    pub description: String,
173
174    /// The task for the agent to perform
175    pub prompt: String,
176
177    /// The type of specialized agent to use
178    pub subagent_type: SubagentType,
179
180    /// Whether to run the agent in the background
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub run_in_background: Option<bool>,
183
184    /// Optional model to use for this agent
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub model: Option<String>,
187
188    /// Maximum number of agentic turns
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub max_turns: Option<u32>,
191
192    /// Optional agent ID to resume from
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub resume: Option<String>,
195}
196
197/// Input for the WebFetch tool - fetches and processes web content.
198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
199pub struct WebFetchInput {
200    /// The URL to fetch content from
201    pub url: String,
202
203    /// The prompt to run on the fetched content
204    pub prompt: String,
205}
206
207/// Input for the WebSearch tool - searches the web.
208#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
209pub struct WebSearchInput {
210    /// The search query to use
211    pub query: String,
212
213    /// Only include search results from these domains
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub allowed_domains: Option<Vec<String>>,
216
217    /// Never include search results from these domains
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub blocked_domains: Option<Vec<String>>,
220}
221
222/// Input for the TodoWrite tool - manages task lists.
223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
224pub struct TodoWriteInput {
225    /// The updated todo list
226    pub todos: Vec<TodoItem>,
227}
228
229/// Status of a todo item.
230#[derive(Debug, Clone, PartialEq, Eq, Hash)]
231pub enum TodoStatus {
232    Pending,
233    InProgress,
234    Completed,
235    /// A status not yet known to this version of the crate.
236    Unknown(String),
237}
238
239impl TodoStatus {
240    pub fn as_str(&self) -> &str {
241        match self {
242            Self::Pending => "pending",
243            Self::InProgress => "in_progress",
244            Self::Completed => "completed",
245            Self::Unknown(s) => s.as_str(),
246        }
247    }
248}
249
250impl fmt::Display for TodoStatus {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        f.write_str(self.as_str())
253    }
254}
255
256impl From<&str> for TodoStatus {
257    fn from(s: &str) -> Self {
258        match s {
259            "pending" => Self::Pending,
260            "in_progress" => Self::InProgress,
261            "completed" => Self::Completed,
262            other => Self::Unknown(other.to_string()),
263        }
264    }
265}
266
267impl Serialize for TodoStatus {
268    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
269        serializer.serialize_str(self.as_str())
270    }
271}
272
273impl<'de> Deserialize<'de> for TodoStatus {
274    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
275        let s = String::deserialize(deserializer)?;
276        Ok(Self::from(s.as_str()))
277    }
278}
279
280/// Output mode for the Grep tool.
281#[derive(Debug, Clone, PartialEq, Eq, Hash)]
282pub enum GrepOutputMode {
283    /// Show matching lines with context.
284    Content,
285    /// Show only file paths containing matches.
286    FilesWithMatches,
287    /// Show match counts per file.
288    Count,
289    /// A mode not yet known to this version of the crate.
290    Unknown(String),
291}
292
293impl GrepOutputMode {
294    pub fn as_str(&self) -> &str {
295        match self {
296            Self::Content => "content",
297            Self::FilesWithMatches => "files_with_matches",
298            Self::Count => "count",
299            Self::Unknown(s) => s.as_str(),
300        }
301    }
302}
303
304impl fmt::Display for GrepOutputMode {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.write_str(self.as_str())
307    }
308}
309
310impl From<&str> for GrepOutputMode {
311    fn from(s: &str) -> Self {
312        match s {
313            "content" => Self::Content,
314            "files_with_matches" => Self::FilesWithMatches,
315            "count" => Self::Count,
316            other => Self::Unknown(other.to_string()),
317        }
318    }
319}
320
321impl Serialize for GrepOutputMode {
322    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
323        serializer.serialize_str(self.as_str())
324    }
325}
326
327impl<'de> Deserialize<'de> for GrepOutputMode {
328    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
329        let s = String::deserialize(deserializer)?;
330        Ok(Self::from(s.as_str()))
331    }
332}
333
334/// Type of specialized subagent for the Task tool.
335#[derive(Debug, Clone, PartialEq, Eq, Hash)]
336pub enum SubagentType {
337    /// Command execution specialist.
338    Bash,
339    /// Fast codebase exploration agent.
340    Explore,
341    /// Software architect agent for planning.
342    Plan,
343    /// General-purpose agent.
344    GeneralPurpose,
345    /// A subagent type not yet known to this version of the crate.
346    Unknown(String),
347}
348
349impl SubagentType {
350    pub fn as_str(&self) -> &str {
351        match self {
352            Self::Bash => "Bash",
353            Self::Explore => "Explore",
354            Self::Plan => "Plan",
355            Self::GeneralPurpose => "general-purpose",
356            Self::Unknown(s) => s.as_str(),
357        }
358    }
359}
360
361impl fmt::Display for SubagentType {
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        f.write_str(self.as_str())
364    }
365}
366
367impl From<&str> for SubagentType {
368    fn from(s: &str) -> Self {
369        match s {
370            "Bash" => Self::Bash,
371            "Explore" => Self::Explore,
372            "Plan" => Self::Plan,
373            "general-purpose" => Self::GeneralPurpose,
374            other => Self::Unknown(other.to_string()),
375        }
376    }
377}
378
379impl Serialize for SubagentType {
380    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
381        serializer.serialize_str(self.as_str())
382    }
383}
384
385impl<'de> Deserialize<'de> for SubagentType {
386    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
387        let s = String::deserialize(deserializer)?;
388        Ok(Self::from(s.as_str()))
389    }
390}
391
392/// Type of Jupyter notebook cell.
393#[derive(Debug, Clone, PartialEq, Eq, Hash)]
394pub enum NotebookCellType {
395    /// Code cell.
396    Code,
397    /// Markdown cell.
398    Markdown,
399    /// A cell type not yet known to this version of the crate.
400    Unknown(String),
401}
402
403impl NotebookCellType {
404    pub fn as_str(&self) -> &str {
405        match self {
406            Self::Code => "code",
407            Self::Markdown => "markdown",
408            Self::Unknown(s) => s.as_str(),
409        }
410    }
411}
412
413impl fmt::Display for NotebookCellType {
414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415        f.write_str(self.as_str())
416    }
417}
418
419impl From<&str> for NotebookCellType {
420    fn from(s: &str) -> Self {
421        match s {
422            "code" => Self::Code,
423            "markdown" => Self::Markdown,
424            other => Self::Unknown(other.to_string()),
425        }
426    }
427}
428
429impl Serialize for NotebookCellType {
430    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
431        serializer.serialize_str(self.as_str())
432    }
433}
434
435impl<'de> Deserialize<'de> for NotebookCellType {
436    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
437        let s = String::deserialize(deserializer)?;
438        Ok(Self::from(s.as_str()))
439    }
440}
441
442/// Type of edit to perform on a notebook cell.
443#[derive(Debug, Clone, PartialEq, Eq, Hash)]
444pub enum NotebookEditMode {
445    /// Replace the cell's content.
446    Replace,
447    /// Insert a new cell.
448    Insert,
449    /// Delete the cell.
450    Delete,
451    /// An edit mode not yet known to this version of the crate.
452    Unknown(String),
453}
454
455impl NotebookEditMode {
456    pub fn as_str(&self) -> &str {
457        match self {
458            Self::Replace => "replace",
459            Self::Insert => "insert",
460            Self::Delete => "delete",
461            Self::Unknown(s) => s.as_str(),
462        }
463    }
464}
465
466impl fmt::Display for NotebookEditMode {
467    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468        f.write_str(self.as_str())
469    }
470}
471
472impl From<&str> for NotebookEditMode {
473    fn from(s: &str) -> Self {
474        match s {
475            "replace" => Self::Replace,
476            "insert" => Self::Insert,
477            "delete" => Self::Delete,
478            other => Self::Unknown(other.to_string()),
479        }
480    }
481}
482
483impl Serialize for NotebookEditMode {
484    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
485        serializer.serialize_str(self.as_str())
486    }
487}
488
489impl<'de> Deserialize<'de> for NotebookEditMode {
490    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
491        let s = String::deserialize(deserializer)?;
492        Ok(Self::from(s.as_str()))
493    }
494}
495
496/// A single todo item in a task list.
497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
498pub struct TodoItem {
499    /// The task description (imperative form)
500    pub content: String,
501
502    /// Current status
503    pub status: TodoStatus,
504
505    /// The present continuous form shown during execution
506    #[serde(rename = "activeForm")]
507    pub active_form: String,
508}
509
510/// Input for the AskUserQuestion tool - asks the user questions.
511#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
512pub struct AskUserQuestionInput {
513    /// Questions to ask the user (1-4 questions)
514    pub questions: Vec<Question>,
515
516    /// User answers collected by the permission component
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub answers: Option<HashMap<String, String>>,
519
520    /// Optional metadata for tracking and analytics
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub metadata: Option<QuestionMetadata>,
523}
524
525/// A question to ask the user.
526#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
527pub struct Question {
528    /// The complete question to ask the user
529    pub question: String,
530
531    /// Very short label displayed as a chip/tag (max 12 chars)
532    pub header: String,
533
534    /// The available choices for this question (2-4 options)
535    pub options: Vec<QuestionOption>,
536
537    /// Whether multiple options can be selected
538    #[serde(rename = "multiSelect", default)]
539    pub multi_select: bool,
540}
541
542/// An option for a question.
543#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
544pub struct QuestionOption {
545    /// The display text for this option
546    pub label: String,
547
548    /// Explanation of what this option means
549    #[serde(skip_serializing_if = "Option::is_none")]
550    pub description: Option<String>,
551}
552
553/// Metadata for questions.
554#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
555pub struct QuestionMetadata {
556    /// Optional identifier for the source of this question
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub source: Option<String>,
559}
560
561/// Input for the NotebookEdit tool - edits Jupyter notebooks.
562#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
563pub struct NotebookEditInput {
564    /// The absolute path to the Jupyter notebook file
565    pub notebook_path: String,
566
567    /// The new source for the cell
568    pub new_source: String,
569
570    /// The ID of the cell to edit
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub cell_id: Option<String>,
573
574    /// The type of the cell (code or markdown)
575    #[serde(skip_serializing_if = "Option::is_none")]
576    pub cell_type: Option<NotebookCellType>,
577
578    /// The type of edit to make (replace, insert, delete)
579    #[serde(skip_serializing_if = "Option::is_none")]
580    pub edit_mode: Option<NotebookEditMode>,
581}
582
583/// Input for the TaskOutput tool - retrieves output from background tasks.
584#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
585pub struct TaskOutputInput {
586    /// The task ID to get output from
587    pub task_id: String,
588
589    /// Whether to wait for completion (default true)
590    #[serde(default = "default_true")]
591    pub block: bool,
592
593    /// Max wait time in ms (default 30000, max 600000)
594    #[serde(default = "default_timeout")]
595    pub timeout: u64,
596}
597
598fn default_true() -> bool {
599    true
600}
601
602fn default_timeout() -> u64 {
603    30000
604}
605
606/// Input for the KillShell tool - kills a running background shell.
607#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
608pub struct KillShellInput {
609    /// The ID of the background shell to kill
610    pub shell_id: String,
611}
612
613/// Input for the Skill tool - executes a skill.
614#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
615pub struct SkillInput {
616    /// The skill name (e.g., "commit", "review-pr")
617    pub skill: String,
618
619    /// Optional arguments for the skill
620    #[serde(skip_serializing_if = "Option::is_none")]
621    pub args: Option<String>,
622}
623
624/// Input for the EnterPlanMode tool - enters planning mode.
625///
626/// This is an empty struct as EnterPlanMode takes no parameters.
627/// The `deny_unknown_fields` attribute ensures this only matches
628/// empty JSON objects `{}`, not arbitrary JSON.
629#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
630#[serde(deny_unknown_fields)]
631pub struct EnterPlanModeInput {}
632
633/// Input for the ExitPlanMode tool - exits planning mode.
634///
635/// The `deny_unknown_fields` attribute ensures this only matches JSON objects
636/// that contain known fields (or are empty), not arbitrary JSON.
637#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
638#[serde(deny_unknown_fields)]
639pub struct ExitPlanModeInput {
640    /// Prompt-based permissions needed to implement the plan
641    #[serde(rename = "allowedPrompts", skip_serializing_if = "Option::is_none")]
642    pub allowed_prompts: Option<Vec<AllowedPrompt>>,
643
644    /// Whether to push the plan to a remote Claude.ai session
645    #[serde(rename = "pushToRemote", skip_serializing_if = "Option::is_none")]
646    pub push_to_remote: Option<bool>,
647
648    /// The remote session ID if pushed to remote
649    #[serde(rename = "remoteSessionId", skip_serializing_if = "Option::is_none")]
650    pub remote_session_id: Option<String>,
651
652    /// The remote session URL if pushed to remote
653    #[serde(rename = "remoteSessionUrl", skip_serializing_if = "Option::is_none")]
654    pub remote_session_url: Option<String>,
655
656    /// The remote session title if pushed to remote
657    #[serde(rename = "remoteSessionTitle", skip_serializing_if = "Option::is_none")]
658    pub remote_session_title: Option<String>,
659
660    /// The plan content from plan mode
661    #[serde(skip_serializing_if = "Option::is_none")]
662    pub plan: Option<String>,
663}
664
665/// An allowed prompt permission for plan mode.
666#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
667pub struct AllowedPrompt {
668    /// The tool this prompt applies to
669    pub tool: String,
670
671    /// Semantic description of the action
672    pub prompt: String,
673}
674
675/// Input for the MultiEdit tool - batch file edits.
676#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
677pub struct MultiEditInput {
678    /// The absolute path to the file to modify
679    pub file_path: String,
680
681    /// Array of edit operations to apply
682    pub edits: Vec<MultiEditOperation>,
683}
684
685/// A single edit operation within a MultiEdit.
686#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
687pub struct MultiEditOperation {
688    /// The text to replace
689    pub old_string: String,
690
691    /// The text to replace it with
692    pub new_string: String,
693}
694
695/// Input for the LS tool - lists files and directories.
696#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
697#[serde(deny_unknown_fields)]
698pub struct LsInput {
699    /// The absolute path to the directory to list
700    pub path: String,
701}
702
703/// Input for the NotebookRead tool - reads notebook cells.
704#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
705pub struct NotebookReadInput {
706    /// The absolute path to the notebook file
707    pub notebook_path: String,
708}
709
710/// Input for the ScheduleWakeup tool - schedules delayed loop actions.
711#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
712pub struct ScheduleWakeupInput {
713    /// Seconds from now to wake up (clamped to [60, 3600])
714    #[serde(rename = "delaySeconds")]
715    pub delay_seconds: f64,
716
717    /// Short explanation of the chosen delay
718    pub reason: String,
719
720    /// The /loop prompt to fire on wake-up
721    pub prompt: String,
722}
723
724/// Input for the ToolSearch tool - fetches deferred tool schemas.
725#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
726#[serde(deny_unknown_fields)]
727pub struct ToolSearchInput {
728    /// Query to find deferred tools
729    pub query: String,
730
731    /// Maximum number of results to return
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub max_results: Option<u32>,
734}
735
736// ============================================================================
737// ToolInput Enum - Unified type for all tool inputs
738// ============================================================================
739
740/// Unified enum representing input for any Claude Code tool.
741///
742/// This enum uses `#[serde(untagged)]` to automatically deserialize based on
743/// the structure of the JSON. The `Unknown` variant serves as a fallback for:
744/// - New tools added in future Claude CLI versions
745/// - Custom MCP tools provided by users
746/// - Any tool input that doesn't match known schemas
747///
748/// # Example
749///
750/// ```
751/// use claude_codes::ToolInput;
752///
753/// // Known tool - deserializes to specific variant
754/// let bash_json = serde_json::json!({"command": "ls"});
755/// let input: ToolInput = serde_json::from_value(bash_json).unwrap();
756/// assert!(matches!(input, ToolInput::Bash(_)));
757///
758/// // Unknown tool - falls back to Unknown variant
759/// let custom_json = serde_json::json!({"custom_field": "value"});
760/// let input: ToolInput = serde_json::from_value(custom_json).unwrap();
761/// assert!(matches!(input, ToolInput::Unknown(_)));
762/// ```
763///
764/// # Note on Ordering
765///
766/// The variants are ordered from most specific (most required fields) to least
767/// specific to ensure correct deserialization with `#[serde(untagged)]`.
768#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
769#[serde(untagged)]
770pub enum ToolInput {
771    /// Edit tool - has unique field combination (file_path, old_string, new_string)
772    Edit(EditInput),
773
774    /// Write tool - file_path + content
775    Write(WriteInput),
776
777    /// MultiEdit tool - batch file edits (file_path + edits, before Read)
778    MultiEdit(MultiEditInput),
779
780    /// AskUserQuestion tool - has questions array
781    AskUserQuestion(AskUserQuestionInput),
782
783    /// TodoWrite tool - has todos array
784    TodoWrite(TodoWriteInput),
785
786    /// Task tool - description + prompt + subagent_type
787    Task(TaskInput),
788
789    /// NotebookEdit tool - notebook_path + new_source
790    NotebookEdit(NotebookEditInput),
791
792    /// WebFetch tool - url + prompt
793    WebFetch(WebFetchInput),
794
795    /// TaskOutput tool - task_id + block + timeout
796    TaskOutput(TaskOutputInput),
797
798    /// Bash tool - has command field
799    Bash(BashInput),
800
801    /// Read tool - has file_path
802    Read(ReadInput),
803
804    /// Glob tool - has pattern field (with deny_unknown_fields, must come before Grep)
805    Glob(GlobInput),
806
807    /// Grep tool - has pattern field plus many optional fields
808    Grep(GrepInput),
809
810    /// ToolSearch tool - fetch deferred tool schemas (query + max_results)
811    ToolSearch(ToolSearchInput),
812
813    /// WebSearch tool - has query field
814    WebSearch(WebSearchInput),
815
816    /// KillShell tool - has shell_id
817    KillShell(KillShellInput),
818
819    /// Skill tool - has skill field
820    Skill(SkillInput),
821
822    /// ExitPlanMode tool
823    ExitPlanMode(ExitPlanModeInput),
824
825    /// ScheduleWakeup tool - schedule delayed wakeup (3 required fields)
826    ScheduleWakeup(ScheduleWakeupInput),
827
828    /// NotebookRead tool - read notebook cells (notebook_path required)
829    NotebookRead(NotebookReadInput),
830
831    /// LS tool - list files and directories
832    LS(LsInput),
833
834    /// EnterPlanMode tool (empty input)
835    EnterPlanMode(EnterPlanModeInput),
836
837    /// Unknown tool input - fallback for custom/new tools
838    ///
839    /// This variant captures any tool input that doesn't match the known schemas.
840    /// Use this for:
841    /// - MCP tools provided by users
842    /// - New tools in future Claude CLI versions
843    /// - Any custom tool integration
844    Unknown(Value),
845}
846
847impl ToolInput {
848    /// Returns the tool name if it can be determined from the input type.
849    ///
850    /// For `Unknown` variants, returns `None` since the tool name cannot be
851    /// determined from the input structure alone.
852    pub fn tool_name(&self) -> Option<&'static str> {
853        match self {
854            ToolInput::Bash(_) => Some("Bash"),
855            ToolInput::Read(_) => Some("Read"),
856            ToolInput::Write(_) => Some("Write"),
857            ToolInput::Edit(_) => Some("Edit"),
858            ToolInput::Glob(_) => Some("Glob"),
859            ToolInput::Grep(_) => Some("Grep"),
860            ToolInput::Task(_) => Some("Task"),
861            ToolInput::WebFetch(_) => Some("WebFetch"),
862            ToolInput::WebSearch(_) => Some("WebSearch"),
863            ToolInput::TodoWrite(_) => Some("TodoWrite"),
864            ToolInput::AskUserQuestion(_) => Some("AskUserQuestion"),
865            ToolInput::NotebookEdit(_) => Some("NotebookEdit"),
866            ToolInput::TaskOutput(_) => Some("TaskOutput"),
867            ToolInput::KillShell(_) => Some("KillShell"),
868            ToolInput::Skill(_) => Some("Skill"),
869            ToolInput::EnterPlanMode(_) => Some("EnterPlanMode"),
870            ToolInput::ExitPlanMode(_) => Some("ExitPlanMode"),
871            ToolInput::MultiEdit(_) => Some("MultiEdit"),
872            ToolInput::ScheduleWakeup(_) => Some("ScheduleWakeup"),
873            ToolInput::NotebookRead(_) => Some("NotebookRead"),
874            ToolInput::ToolSearch(_) => Some("ToolSearch"),
875            ToolInput::LS(_) => Some("LS"),
876            ToolInput::Unknown(_) => None,
877        }
878    }
879
880    /// Try to get the input as a Bash input.
881    pub fn as_bash(&self) -> Option<&BashInput> {
882        match self {
883            ToolInput::Bash(input) => Some(input),
884            _ => None,
885        }
886    }
887
888    /// Try to get the input as a Read input.
889    pub fn as_read(&self) -> Option<&ReadInput> {
890        match self {
891            ToolInput::Read(input) => Some(input),
892            _ => None,
893        }
894    }
895
896    /// Try to get the input as a Write input.
897    pub fn as_write(&self) -> Option<&WriteInput> {
898        match self {
899            ToolInput::Write(input) => Some(input),
900            _ => None,
901        }
902    }
903
904    /// Try to get the input as an Edit input.
905    pub fn as_edit(&self) -> Option<&EditInput> {
906        match self {
907            ToolInput::Edit(input) => Some(input),
908            _ => None,
909        }
910    }
911
912    /// Try to get the input as a Glob input.
913    pub fn as_glob(&self) -> Option<&GlobInput> {
914        match self {
915            ToolInput::Glob(input) => Some(input),
916            _ => None,
917        }
918    }
919
920    /// Try to get the input as a Grep input.
921    pub fn as_grep(&self) -> Option<&GrepInput> {
922        match self {
923            ToolInput::Grep(input) => Some(input),
924            _ => None,
925        }
926    }
927
928    /// Try to get the input as a Task input.
929    pub fn as_task(&self) -> Option<&TaskInput> {
930        match self {
931            ToolInput::Task(input) => Some(input),
932            _ => None,
933        }
934    }
935
936    /// Try to get the input as a WebFetch input.
937    pub fn as_web_fetch(&self) -> Option<&WebFetchInput> {
938        match self {
939            ToolInput::WebFetch(input) => Some(input),
940            _ => None,
941        }
942    }
943
944    /// Try to get the input as a WebSearch input.
945    pub fn as_web_search(&self) -> Option<&WebSearchInput> {
946        match self {
947            ToolInput::WebSearch(input) => Some(input),
948            _ => None,
949        }
950    }
951
952    /// Try to get the input as a TodoWrite input.
953    pub fn as_todo_write(&self) -> Option<&TodoWriteInput> {
954        match self {
955            ToolInput::TodoWrite(input) => Some(input),
956            _ => None,
957        }
958    }
959
960    /// Try to get the input as an AskUserQuestion input.
961    pub fn as_ask_user_question(&self) -> Option<&AskUserQuestionInput> {
962        match self {
963            ToolInput::AskUserQuestion(input) => Some(input),
964            _ => None,
965        }
966    }
967
968    /// Try to get the input as a NotebookEdit input.
969    pub fn as_notebook_edit(&self) -> Option<&NotebookEditInput> {
970        match self {
971            ToolInput::NotebookEdit(input) => Some(input),
972            _ => None,
973        }
974    }
975
976    /// Try to get the input as a TaskOutput input.
977    pub fn as_task_output(&self) -> Option<&TaskOutputInput> {
978        match self {
979            ToolInput::TaskOutput(input) => Some(input),
980            _ => None,
981        }
982    }
983
984    /// Try to get the input as a KillShell input.
985    pub fn as_kill_shell(&self) -> Option<&KillShellInput> {
986        match self {
987            ToolInput::KillShell(input) => Some(input),
988            _ => None,
989        }
990    }
991
992    /// Try to get the input as a Skill input.
993    pub fn as_skill(&self) -> Option<&SkillInput> {
994        match self {
995            ToolInput::Skill(input) => Some(input),
996            _ => None,
997        }
998    }
999
1000    /// Try to get the input as an unknown Value.
1001    pub fn as_unknown(&self) -> Option<&Value> {
1002        match self {
1003            ToolInput::Unknown(value) => Some(value),
1004            _ => None,
1005        }
1006    }
1007
1008    /// Check if this is an unknown tool input.
1009    pub fn is_unknown(&self) -> bool {
1010        matches!(self, ToolInput::Unknown(_))
1011    }
1012}
1013
1014// ============================================================================
1015// Conversion implementations
1016// ============================================================================
1017
1018impl From<BashInput> for ToolInput {
1019    fn from(input: BashInput) -> Self {
1020        ToolInput::Bash(input)
1021    }
1022}
1023
1024impl From<ReadInput> for ToolInput {
1025    fn from(input: ReadInput) -> Self {
1026        ToolInput::Read(input)
1027    }
1028}
1029
1030impl From<WriteInput> for ToolInput {
1031    fn from(input: WriteInput) -> Self {
1032        ToolInput::Write(input)
1033    }
1034}
1035
1036impl From<EditInput> for ToolInput {
1037    fn from(input: EditInput) -> Self {
1038        ToolInput::Edit(input)
1039    }
1040}
1041
1042impl From<GlobInput> for ToolInput {
1043    fn from(input: GlobInput) -> Self {
1044        ToolInput::Glob(input)
1045    }
1046}
1047
1048impl From<GrepInput> for ToolInput {
1049    fn from(input: GrepInput) -> Self {
1050        ToolInput::Grep(input)
1051    }
1052}
1053
1054impl From<TaskInput> for ToolInput {
1055    fn from(input: TaskInput) -> Self {
1056        ToolInput::Task(input)
1057    }
1058}
1059
1060impl From<WebFetchInput> for ToolInput {
1061    fn from(input: WebFetchInput) -> Self {
1062        ToolInput::WebFetch(input)
1063    }
1064}
1065
1066impl From<WebSearchInput> for ToolInput {
1067    fn from(input: WebSearchInput) -> Self {
1068        ToolInput::WebSearch(input)
1069    }
1070}
1071
1072impl From<TodoWriteInput> for ToolInput {
1073    fn from(input: TodoWriteInput) -> Self {
1074        ToolInput::TodoWrite(input)
1075    }
1076}
1077
1078impl From<AskUserQuestionInput> for ToolInput {
1079    fn from(input: AskUserQuestionInput) -> Self {
1080        ToolInput::AskUserQuestion(input)
1081    }
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086    use super::*;
1087
1088    #[test]
1089    fn test_bash_input_parsing() {
1090        let json = serde_json::json!({
1091            "command": "ls -la",
1092            "description": "List files",
1093            "timeout": 5000,
1094            "run_in_background": false
1095        });
1096
1097        let input: BashInput = serde_json::from_value(json).unwrap();
1098        assert_eq!(input.command, "ls -la");
1099        assert_eq!(input.description, Some("List files".to_string()));
1100        assert_eq!(input.timeout, Some(5000));
1101        assert_eq!(input.run_in_background, Some(false));
1102    }
1103
1104    #[test]
1105    fn test_bash_input_minimal() {
1106        let json = serde_json::json!({
1107            "command": "echo hello"
1108        });
1109
1110        let input: BashInput = serde_json::from_value(json).unwrap();
1111        assert_eq!(input.command, "echo hello");
1112        assert_eq!(input.description, None);
1113        assert_eq!(input.timeout, None);
1114    }
1115
1116    #[test]
1117    fn test_read_input_parsing() {
1118        let json = serde_json::json!({
1119            "file_path": "/home/user/test.rs",
1120            "offset": 10,
1121            "limit": 100
1122        });
1123
1124        let input: ReadInput = serde_json::from_value(json).unwrap();
1125        assert_eq!(input.file_path, "/home/user/test.rs");
1126        assert_eq!(input.offset, Some(10));
1127        assert_eq!(input.limit, Some(100));
1128    }
1129
1130    #[test]
1131    fn test_write_input_parsing() {
1132        let json = serde_json::json!({
1133            "file_path": "/tmp/test.txt",
1134            "content": "Hello, world!"
1135        });
1136
1137        let input: WriteInput = serde_json::from_value(json).unwrap();
1138        assert_eq!(input.file_path, "/tmp/test.txt");
1139        assert_eq!(input.content, "Hello, world!");
1140    }
1141
1142    #[test]
1143    fn test_edit_input_parsing() {
1144        let json = serde_json::json!({
1145            "file_path": "/home/user/code.rs",
1146            "old_string": "fn old()",
1147            "new_string": "fn new()",
1148            "replace_all": true
1149        });
1150
1151        let input: EditInput = serde_json::from_value(json).unwrap();
1152        assert_eq!(input.file_path, "/home/user/code.rs");
1153        assert_eq!(input.old_string, "fn old()");
1154        assert_eq!(input.new_string, "fn new()");
1155        assert_eq!(input.replace_all, Some(true));
1156    }
1157
1158    #[test]
1159    fn test_glob_input_parsing() {
1160        let json = serde_json::json!({
1161            "pattern": "**/*.rs",
1162            "path": "/home/user/project"
1163        });
1164
1165        let input: GlobInput = serde_json::from_value(json).unwrap();
1166        assert_eq!(input.pattern, "**/*.rs");
1167        assert_eq!(input.path, Some("/home/user/project".to_string()));
1168    }
1169
1170    #[test]
1171    fn test_grep_input_parsing() {
1172        let json = serde_json::json!({
1173            "pattern": "fn\\s+\\w+",
1174            "path": "/home/user/project",
1175            "type": "rust",
1176            "-i": true,
1177            "-C": 3
1178        });
1179
1180        let input: GrepInput = serde_json::from_value(json).unwrap();
1181        assert_eq!(input.pattern, "fn\\s+\\w+");
1182        assert_eq!(input.file_type, Some("rust".to_string()));
1183        assert_eq!(input.case_insensitive, Some(true));
1184        assert_eq!(input.context, Some(3));
1185    }
1186
1187    #[test]
1188    fn test_task_input_parsing() {
1189        let json = serde_json::json!({
1190            "description": "Search codebase",
1191            "prompt": "Find all usages of foo()",
1192            "subagent_type": "Explore",
1193            "run_in_background": true
1194        });
1195
1196        let input: TaskInput = serde_json::from_value(json).unwrap();
1197        assert_eq!(input.description, "Search codebase");
1198        assert_eq!(input.prompt, "Find all usages of foo()");
1199        assert_eq!(input.subagent_type, SubagentType::Explore);
1200        assert_eq!(input.run_in_background, Some(true));
1201    }
1202
1203    #[test]
1204    fn test_web_fetch_input_parsing() {
1205        let json = serde_json::json!({
1206            "url": "https://example.com",
1207            "prompt": "Extract the main content"
1208        });
1209
1210        let input: WebFetchInput = serde_json::from_value(json).unwrap();
1211        assert_eq!(input.url, "https://example.com");
1212        assert_eq!(input.prompt, "Extract the main content");
1213    }
1214
1215    #[test]
1216    fn test_web_search_input_parsing() {
1217        let json = serde_json::json!({
1218            "query": "rust serde tutorial",
1219            "allowed_domains": ["docs.rs", "crates.io"]
1220        });
1221
1222        let input: WebSearchInput = serde_json::from_value(json).unwrap();
1223        assert_eq!(input.query, "rust serde tutorial");
1224        assert_eq!(
1225            input.allowed_domains,
1226            Some(vec!["docs.rs".to_string(), "crates.io".to_string()])
1227        );
1228    }
1229
1230    #[test]
1231    fn test_todo_write_input_parsing() {
1232        let json = serde_json::json!({
1233            "todos": [
1234                {
1235                    "content": "Fix the bug",
1236                    "status": "in_progress",
1237                    "activeForm": "Fixing the bug"
1238                },
1239                {
1240                    "content": "Write tests",
1241                    "status": "pending",
1242                    "activeForm": "Writing tests"
1243                }
1244            ]
1245        });
1246
1247        let input: TodoWriteInput = serde_json::from_value(json).unwrap();
1248        assert_eq!(input.todos.len(), 2);
1249        assert_eq!(input.todos[0].content, "Fix the bug");
1250        assert_eq!(input.todos[0].status, TodoStatus::InProgress);
1251        assert_eq!(input.todos[1].status, TodoStatus::Pending);
1252    }
1253
1254    #[test]
1255    fn test_ask_user_question_input_parsing() {
1256        let json = serde_json::json!({
1257            "questions": [
1258                {
1259                    "question": "Which framework?",
1260                    "header": "Framework",
1261                    "options": [
1262                        {"label": "React", "description": "Popular UI library"},
1263                        {"label": "Vue", "description": "Progressive framework"}
1264                    ],
1265                    "multiSelect": false
1266                }
1267            ]
1268        });
1269
1270        let input: AskUserQuestionInput = serde_json::from_value(json).unwrap();
1271        assert_eq!(input.questions.len(), 1);
1272        assert_eq!(input.questions[0].question, "Which framework?");
1273        assert_eq!(input.questions[0].options.len(), 2);
1274        assert_eq!(input.questions[0].options[0].label, "React");
1275    }
1276
1277    #[test]
1278    fn test_tool_input_enum_bash() {
1279        let json = serde_json::json!({
1280            "command": "ls -la"
1281        });
1282
1283        let input: ToolInput = serde_json::from_value(json).unwrap();
1284        assert!(matches!(input, ToolInput::Bash(_)));
1285        assert_eq!(input.tool_name(), Some("Bash"));
1286        assert!(input.as_bash().is_some());
1287    }
1288
1289    #[test]
1290    fn test_tool_input_enum_edit() {
1291        let json = serde_json::json!({
1292            "file_path": "/test.rs",
1293            "old_string": "old",
1294            "new_string": "new"
1295        });
1296
1297        let input: ToolInput = serde_json::from_value(json).unwrap();
1298        assert!(matches!(input, ToolInput::Edit(_)));
1299        assert_eq!(input.tool_name(), Some("Edit"));
1300    }
1301
1302    #[test]
1303    fn test_tool_input_enum_unknown() {
1304        // Custom MCP tool with unknown structure
1305        let json = serde_json::json!({
1306            "custom_field": "custom_value",
1307            "another_field": 42
1308        });
1309
1310        let input: ToolInput = serde_json::from_value(json).unwrap();
1311        assert!(matches!(input, ToolInput::Unknown(_)));
1312        assert_eq!(input.tool_name(), None);
1313        assert!(input.is_unknown());
1314
1315        let unknown = input.as_unknown().unwrap();
1316        assert_eq!(unknown.get("custom_field").unwrap(), "custom_value");
1317    }
1318
1319    #[test]
1320    fn test_tool_input_roundtrip() {
1321        let original = BashInput {
1322            command: "echo test".to_string(),
1323            description: Some("Test command".to_string()),
1324            timeout: Some(5000),
1325            run_in_background: None,
1326        };
1327
1328        let tool_input: ToolInput = original.clone().into();
1329        let json = serde_json::to_value(&tool_input).unwrap();
1330        let parsed: ToolInput = serde_json::from_value(json).unwrap();
1331
1332        if let ToolInput::Bash(bash) = parsed {
1333            assert_eq!(bash.command, original.command);
1334            assert_eq!(bash.description, original.description);
1335        } else {
1336            panic!("Expected Bash variant");
1337        }
1338    }
1339
1340    #[test]
1341    fn test_notebook_edit_input_parsing() {
1342        let json = serde_json::json!({
1343            "notebook_path": "/home/user/notebook.ipynb",
1344            "new_source": "print('hello')",
1345            "cell_id": "abc123",
1346            "cell_type": "code",
1347            "edit_mode": "replace"
1348        });
1349
1350        let input: NotebookEditInput = serde_json::from_value(json).unwrap();
1351        assert_eq!(input.notebook_path, "/home/user/notebook.ipynb");
1352        assert_eq!(input.new_source, "print('hello')");
1353        assert_eq!(input.cell_id, Some("abc123".to_string()));
1354    }
1355
1356    #[test]
1357    fn test_task_output_input_parsing() {
1358        let json = serde_json::json!({
1359            "task_id": "task-123",
1360            "block": false,
1361            "timeout": 60000
1362        });
1363
1364        let input: TaskOutputInput = serde_json::from_value(json).unwrap();
1365        assert_eq!(input.task_id, "task-123");
1366        assert!(!input.block);
1367        assert_eq!(input.timeout, 60000);
1368    }
1369
1370    #[test]
1371    fn test_skill_input_parsing() {
1372        let json = serde_json::json!({
1373            "skill": "commit",
1374            "args": "-m 'Fix bug'"
1375        });
1376
1377        let input: SkillInput = serde_json::from_value(json).unwrap();
1378        assert_eq!(input.skill, "commit");
1379        assert_eq!(input.args, Some("-m 'Fix bug'".to_string()));
1380    }
1381
1382    #[test]
1383    fn test_multi_edit_input_parsing() {
1384        let json = serde_json::json!({
1385            "file_path": "/tmp/test.rs",
1386            "edits": [
1387                {"old_string": "foo", "new_string": "bar"},
1388                {"old_string": "baz", "new_string": "qux"}
1389            ]
1390        });
1391
1392        let input: MultiEditInput = serde_json::from_value(json.clone()).unwrap();
1393        assert_eq!(input.file_path, "/tmp/test.rs");
1394        assert_eq!(input.edits.len(), 2);
1395        assert_eq!(input.edits[0].old_string, "foo");
1396        assert_eq!(input.edits[1].new_string, "qux");
1397
1398        // Also test via ToolInput enum
1399        let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1400        assert_eq!(tool_input.tool_name(), Some("MultiEdit"));
1401    }
1402
1403    #[test]
1404    fn test_ls_input_parsing() {
1405        let json = serde_json::json!({"path": "/home/user/project"});
1406
1407        let input: LsInput = serde_json::from_value(json.clone()).unwrap();
1408        assert_eq!(input.path, "/home/user/project");
1409
1410        let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1411        assert_eq!(tool_input.tool_name(), Some("LS"));
1412    }
1413
1414    #[test]
1415    fn test_notebook_read_input_parsing() {
1416        let json = serde_json::json!({"notebook_path": "/tmp/analysis.ipynb"});
1417
1418        let input: NotebookReadInput = serde_json::from_value(json.clone()).unwrap();
1419        assert_eq!(input.notebook_path, "/tmp/analysis.ipynb");
1420
1421        let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1422        assert_eq!(tool_input.tool_name(), Some("NotebookRead"));
1423    }
1424
1425    #[test]
1426    fn test_schedule_wakeup_input_parsing() {
1427        let json = serde_json::json!({
1428            "delaySeconds": 270.0,
1429            "reason": "checking build status",
1430            "prompt": "check the build"
1431        });
1432
1433        let input: ScheduleWakeupInput = serde_json::from_value(json.clone()).unwrap();
1434        assert_eq!(input.delay_seconds, 270.0);
1435        assert_eq!(input.reason, "checking build status");
1436        assert_eq!(input.prompt, "check the build");
1437
1438        let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1439        assert_eq!(tool_input.tool_name(), Some("ScheduleWakeup"));
1440    }
1441
1442    #[test]
1443    fn test_tool_search_input_parsing() {
1444        let json = serde_json::json!({
1445            "query": "select:Read,Edit,Grep",
1446            "max_results": 5
1447        });
1448
1449        let input: ToolSearchInput = serde_json::from_value(json.clone()).unwrap();
1450        assert_eq!(input.query, "select:Read,Edit,Grep");
1451        assert_eq!(input.max_results, Some(5));
1452
1453        let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1454        assert_eq!(tool_input.tool_name(), Some("ToolSearch"));
1455    }
1456}