1use serde::{Deserialize, Deserializer, Serialize, Serializer};
28use serde_json::Value;
29use std::collections::HashMap;
30use std::fmt;
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38pub struct BashInput {
39 pub command: String,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub description: Option<String>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub timeout: Option<u64>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub run_in_background: Option<bool>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
57pub struct ReadInput {
58 pub file_path: String,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 pub offset: Option<i64>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub limit: Option<i64>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72pub struct WriteInput {
73 pub file_path: String,
75
76 pub content: String,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
82pub struct EditInput {
83 pub file_path: String,
85
86 pub old_string: String,
88
89 pub new_string: String,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub replace_all: Option<bool>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
103#[serde(deny_unknown_fields)]
104pub struct GlobInput {
105 pub pattern: String,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub path: Option<String>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
115pub struct GrepInput {
116 pub pattern: String,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub path: Option<String>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub glob: Option<String>,
126
127 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
129 pub file_type: Option<String>,
130
131 #[serde(rename = "-i", skip_serializing_if = "Option::is_none")]
133 pub case_insensitive: Option<bool>,
134
135 #[serde(rename = "-n", skip_serializing_if = "Option::is_none")]
137 pub line_numbers: Option<bool>,
138
139 #[serde(rename = "-A", skip_serializing_if = "Option::is_none")]
141 pub after_context: Option<u32>,
142
143 #[serde(rename = "-B", skip_serializing_if = "Option::is_none")]
145 pub before_context: Option<u32>,
146
147 #[serde(rename = "-C", skip_serializing_if = "Option::is_none")]
149 pub context: Option<u32>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub output_mode: Option<GrepOutputMode>,
154
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub multiline: Option<bool>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub head_limit: Option<u32>,
162
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub offset: Option<u32>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170pub struct TaskInput {
171 pub description: String,
173
174 pub prompt: String,
176
177 pub subagent_type: SubagentType,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub run_in_background: Option<bool>,
183
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub model: Option<String>,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub max_turns: Option<u32>,
191
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub resume: Option<String>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
199pub struct WebFetchInput {
200 pub url: String,
202
203 pub prompt: String,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
209pub struct WebSearchInput {
210 pub query: String,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub allowed_domains: Option<Vec<String>>,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub blocked_domains: Option<Vec<String>>,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
224pub struct TodoWriteInput {
225 pub todos: Vec<TodoItem>,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Hash)]
231pub enum TodoStatus {
232 Pending,
233 InProgress,
234 Completed,
235 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
282pub enum GrepOutputMode {
283 Content,
285 FilesWithMatches,
287 Count,
289 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
336pub enum SubagentType {
337 Bash,
339 Explore,
341 Plan,
343 GeneralPurpose,
345 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
394pub enum NotebookCellType {
395 Code,
397 Markdown,
399 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
444pub enum NotebookEditMode {
445 Replace,
447 Insert,
449 Delete,
451 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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
498pub struct TodoItem {
499 pub content: String,
501
502 pub status: TodoStatus,
504
505 #[serde(rename = "activeForm")]
507 pub active_form: String,
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
512pub struct AskUserQuestionInput {
513 pub questions: Vec<Question>,
515
516 #[serde(skip_serializing_if = "Option::is_none")]
518 pub answers: Option<HashMap<String, String>>,
519
520 #[serde(skip_serializing_if = "Option::is_none")]
522 pub metadata: Option<QuestionMetadata>,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
527pub struct Question {
528 pub question: String,
530
531 pub header: String,
533
534 pub options: Vec<QuestionOption>,
536
537 #[serde(rename = "multiSelect", default)]
539 pub multi_select: bool,
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
544pub struct QuestionOption {
545 pub label: String,
547
548 #[serde(skip_serializing_if = "Option::is_none")]
550 pub description: Option<String>,
551}
552
553#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
555pub struct QuestionMetadata {
556 #[serde(skip_serializing_if = "Option::is_none")]
558 pub source: Option<String>,
559}
560
561#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
563pub struct NotebookEditInput {
564 pub notebook_path: String,
566
567 pub new_source: String,
569
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub cell_id: Option<String>,
573
574 #[serde(skip_serializing_if = "Option::is_none")]
576 pub cell_type: Option<NotebookCellType>,
577
578 #[serde(skip_serializing_if = "Option::is_none")]
580 pub edit_mode: Option<NotebookEditMode>,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
585pub struct TaskOutputInput {
586 pub task_id: String,
588
589 #[serde(default = "default_true")]
591 pub block: bool,
592
593 #[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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
608pub struct KillShellInput {
609 pub shell_id: String,
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
615pub struct SkillInput {
616 pub skill: String,
618
619 #[serde(skip_serializing_if = "Option::is_none")]
621 pub args: Option<String>,
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
630#[serde(deny_unknown_fields)]
631pub struct EnterPlanModeInput {}
632
633#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
638#[serde(deny_unknown_fields)]
639pub struct ExitPlanModeInput {
640 #[serde(rename = "allowedPrompts", skip_serializing_if = "Option::is_none")]
642 pub allowed_prompts: Option<Vec<AllowedPrompt>>,
643
644 #[serde(rename = "pushToRemote", skip_serializing_if = "Option::is_none")]
646 pub push_to_remote: Option<bool>,
647
648 #[serde(rename = "remoteSessionId", skip_serializing_if = "Option::is_none")]
650 pub remote_session_id: Option<String>,
651
652 #[serde(rename = "remoteSessionUrl", skip_serializing_if = "Option::is_none")]
654 pub remote_session_url: Option<String>,
655
656 #[serde(rename = "remoteSessionTitle", skip_serializing_if = "Option::is_none")]
658 pub remote_session_title: Option<String>,
659
660 #[serde(skip_serializing_if = "Option::is_none")]
662 pub plan: Option<String>,
663}
664
665#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
667pub struct AllowedPrompt {
668 pub tool: String,
670
671 pub prompt: String,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
677pub struct MultiEditInput {
678 pub file_path: String,
680
681 pub edits: Vec<MultiEditOperation>,
683}
684
685#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
687pub struct MultiEditOperation {
688 pub old_string: String,
690
691 pub new_string: String,
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
697#[serde(deny_unknown_fields)]
698pub struct LsInput {
699 pub path: String,
701}
702
703#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
705pub struct NotebookReadInput {
706 pub notebook_path: String,
708}
709
710#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
712pub struct ScheduleWakeupInput {
713 #[serde(rename = "delaySeconds")]
715 pub delay_seconds: f64,
716
717 pub reason: String,
719
720 pub prompt: String,
722}
723
724#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
726#[serde(deny_unknown_fields)]
727pub struct ToolSearchInput {
728 pub query: String,
730
731 #[serde(skip_serializing_if = "Option::is_none")]
733 pub max_results: Option<u32>,
734}
735
736#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
769#[serde(untagged)]
770pub enum ToolInput {
771 Edit(EditInput),
773
774 Write(WriteInput),
776
777 MultiEdit(MultiEditInput),
779
780 AskUserQuestion(AskUserQuestionInput),
782
783 TodoWrite(TodoWriteInput),
785
786 Task(TaskInput),
788
789 NotebookEdit(NotebookEditInput),
791
792 WebFetch(WebFetchInput),
794
795 TaskOutput(TaskOutputInput),
797
798 Bash(BashInput),
800
801 Read(ReadInput),
803
804 Glob(GlobInput),
806
807 Grep(GrepInput),
809
810 ToolSearch(ToolSearchInput),
812
813 WebSearch(WebSearchInput),
815
816 KillShell(KillShellInput),
818
819 Skill(SkillInput),
821
822 ExitPlanMode(ExitPlanModeInput),
824
825 ScheduleWakeup(ScheduleWakeupInput),
827
828 NotebookRead(NotebookReadInput),
830
831 LS(LsInput),
833
834 EnterPlanMode(EnterPlanModeInput),
836
837 Unknown(Value),
845}
846
847impl ToolInput {
848 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 pub fn from_named_input(name: &str, input: Value) -> Self {
908 match name {
909 "Bash" => Self::parse_named(input, ToolInput::Bash),
910 "Read" => Self::parse_named(input, ToolInput::Read),
911 "Write" => Self::parse_named(input, ToolInput::Write),
912 "Edit" => Self::parse_named(input, ToolInput::Edit),
913 "Glob" => Self::parse_named(input, ToolInput::Glob),
914 "Grep" => Self::parse_named(input, ToolInput::Grep),
915 "Task" => Self::parse_named(input, ToolInput::Task),
916 "WebFetch" => Self::parse_named(input, ToolInput::WebFetch),
917 "WebSearch" => Self::parse_named(input, ToolInput::WebSearch),
918 "TodoWrite" => Self::parse_named(input, ToolInput::TodoWrite),
919 "AskUserQuestion" => Self::parse_named(input, ToolInput::AskUserQuestion),
920 "NotebookEdit" => Self::parse_named(input, ToolInput::NotebookEdit),
921 "TaskOutput" => Self::parse_named(input, ToolInput::TaskOutput),
922 "KillShell" => Self::parse_named(input, ToolInput::KillShell),
923 "Skill" => Self::parse_named(input, ToolInput::Skill),
924 "EnterPlanMode" => Self::parse_named(input, ToolInput::EnterPlanMode),
925 "ExitPlanMode" => Self::parse_named(input, ToolInput::ExitPlanMode),
926 "MultiEdit" => Self::parse_named(input, ToolInput::MultiEdit),
927 "ScheduleWakeup" => Self::parse_named(input, ToolInput::ScheduleWakeup),
928 "NotebookRead" => Self::parse_named(input, ToolInput::NotebookRead),
929 "ToolSearch" => Self::parse_named(input, ToolInput::ToolSearch),
930 "LS" => Self::parse_named(input, ToolInput::LS),
931 _ => serde_json::from_value(input.clone()).unwrap_or(ToolInput::Unknown(input)),
934 }
935 }
936
937 fn parse_named<T>(input: Value, wrap: fn(T) -> ToolInput) -> ToolInput
941 where
942 T: serde::de::DeserializeOwned,
943 {
944 match serde_json::from_value::<T>(input.clone()) {
945 Ok(parsed) => wrap(parsed),
946 Err(_) => ToolInput::Unknown(input),
947 }
948 }
949
950 pub fn as_bash(&self) -> Option<&BashInput> {
952 match self {
953 ToolInput::Bash(input) => Some(input),
954 _ => None,
955 }
956 }
957
958 pub fn as_read(&self) -> Option<&ReadInput> {
960 match self {
961 ToolInput::Read(input) => Some(input),
962 _ => None,
963 }
964 }
965
966 pub fn as_write(&self) -> Option<&WriteInput> {
968 match self {
969 ToolInput::Write(input) => Some(input),
970 _ => None,
971 }
972 }
973
974 pub fn as_edit(&self) -> Option<&EditInput> {
976 match self {
977 ToolInput::Edit(input) => Some(input),
978 _ => None,
979 }
980 }
981
982 pub fn as_glob(&self) -> Option<&GlobInput> {
984 match self {
985 ToolInput::Glob(input) => Some(input),
986 _ => None,
987 }
988 }
989
990 pub fn as_grep(&self) -> Option<&GrepInput> {
992 match self {
993 ToolInput::Grep(input) => Some(input),
994 _ => None,
995 }
996 }
997
998 pub fn as_task(&self) -> Option<&TaskInput> {
1000 match self {
1001 ToolInput::Task(input) => Some(input),
1002 _ => None,
1003 }
1004 }
1005
1006 pub fn as_web_fetch(&self) -> Option<&WebFetchInput> {
1008 match self {
1009 ToolInput::WebFetch(input) => Some(input),
1010 _ => None,
1011 }
1012 }
1013
1014 pub fn as_web_search(&self) -> Option<&WebSearchInput> {
1016 match self {
1017 ToolInput::WebSearch(input) => Some(input),
1018 _ => None,
1019 }
1020 }
1021
1022 pub fn as_todo_write(&self) -> Option<&TodoWriteInput> {
1024 match self {
1025 ToolInput::TodoWrite(input) => Some(input),
1026 _ => None,
1027 }
1028 }
1029
1030 pub fn as_ask_user_question(&self) -> Option<&AskUserQuestionInput> {
1032 match self {
1033 ToolInput::AskUserQuestion(input) => Some(input),
1034 _ => None,
1035 }
1036 }
1037
1038 pub fn as_notebook_edit(&self) -> Option<&NotebookEditInput> {
1040 match self {
1041 ToolInput::NotebookEdit(input) => Some(input),
1042 _ => None,
1043 }
1044 }
1045
1046 pub fn as_task_output(&self) -> Option<&TaskOutputInput> {
1048 match self {
1049 ToolInput::TaskOutput(input) => Some(input),
1050 _ => None,
1051 }
1052 }
1053
1054 pub fn as_kill_shell(&self) -> Option<&KillShellInput> {
1056 match self {
1057 ToolInput::KillShell(input) => Some(input),
1058 _ => None,
1059 }
1060 }
1061
1062 pub fn as_skill(&self) -> Option<&SkillInput> {
1064 match self {
1065 ToolInput::Skill(input) => Some(input),
1066 _ => None,
1067 }
1068 }
1069
1070 pub fn as_unknown(&self) -> Option<&Value> {
1072 match self {
1073 ToolInput::Unknown(value) => Some(value),
1074 _ => None,
1075 }
1076 }
1077
1078 pub fn is_unknown(&self) -> bool {
1080 matches!(self, ToolInput::Unknown(_))
1081 }
1082}
1083
1084impl From<BashInput> for ToolInput {
1089 fn from(input: BashInput) -> Self {
1090 ToolInput::Bash(input)
1091 }
1092}
1093
1094impl From<ReadInput> for ToolInput {
1095 fn from(input: ReadInput) -> Self {
1096 ToolInput::Read(input)
1097 }
1098}
1099
1100impl From<WriteInput> for ToolInput {
1101 fn from(input: WriteInput) -> Self {
1102 ToolInput::Write(input)
1103 }
1104}
1105
1106impl From<EditInput> for ToolInput {
1107 fn from(input: EditInput) -> Self {
1108 ToolInput::Edit(input)
1109 }
1110}
1111
1112impl From<GlobInput> for ToolInput {
1113 fn from(input: GlobInput) -> Self {
1114 ToolInput::Glob(input)
1115 }
1116}
1117
1118impl From<GrepInput> for ToolInput {
1119 fn from(input: GrepInput) -> Self {
1120 ToolInput::Grep(input)
1121 }
1122}
1123
1124impl From<TaskInput> for ToolInput {
1125 fn from(input: TaskInput) -> Self {
1126 ToolInput::Task(input)
1127 }
1128}
1129
1130impl From<WebFetchInput> for ToolInput {
1131 fn from(input: WebFetchInput) -> Self {
1132 ToolInput::WebFetch(input)
1133 }
1134}
1135
1136impl From<WebSearchInput> for ToolInput {
1137 fn from(input: WebSearchInput) -> Self {
1138 ToolInput::WebSearch(input)
1139 }
1140}
1141
1142impl From<TodoWriteInput> for ToolInput {
1143 fn from(input: TodoWriteInput) -> Self {
1144 ToolInput::TodoWrite(input)
1145 }
1146}
1147
1148impl From<AskUserQuestionInput> for ToolInput {
1149 fn from(input: AskUserQuestionInput) -> Self {
1150 ToolInput::AskUserQuestion(input)
1151 }
1152}
1153
1154#[cfg(test)]
1155mod tests {
1156 use super::*;
1157
1158 #[test]
1159 fn test_bash_input_parsing() {
1160 let json = serde_json::json!({
1161 "command": "ls -la",
1162 "description": "List files",
1163 "timeout": 5000,
1164 "run_in_background": false
1165 });
1166
1167 let input: BashInput = serde_json::from_value(json).unwrap();
1168 assert_eq!(input.command, "ls -la");
1169 assert_eq!(input.description, Some("List files".to_string()));
1170 assert_eq!(input.timeout, Some(5000));
1171 assert_eq!(input.run_in_background, Some(false));
1172 }
1173
1174 #[test]
1175 fn test_bash_input_minimal() {
1176 let json = serde_json::json!({
1177 "command": "echo hello"
1178 });
1179
1180 let input: BashInput = serde_json::from_value(json).unwrap();
1181 assert_eq!(input.command, "echo hello");
1182 assert_eq!(input.description, None);
1183 assert_eq!(input.timeout, None);
1184 }
1185
1186 #[test]
1187 fn test_read_input_parsing() {
1188 let json = serde_json::json!({
1189 "file_path": "/home/user/test.rs",
1190 "offset": 10,
1191 "limit": 100
1192 });
1193
1194 let input: ReadInput = serde_json::from_value(json).unwrap();
1195 assert_eq!(input.file_path, "/home/user/test.rs");
1196 assert_eq!(input.offset, Some(10));
1197 assert_eq!(input.limit, Some(100));
1198 }
1199
1200 #[test]
1201 fn test_write_input_parsing() {
1202 let json = serde_json::json!({
1203 "file_path": "/tmp/test.txt",
1204 "content": "Hello, world!"
1205 });
1206
1207 let input: WriteInput = serde_json::from_value(json).unwrap();
1208 assert_eq!(input.file_path, "/tmp/test.txt");
1209 assert_eq!(input.content, "Hello, world!");
1210 }
1211
1212 #[test]
1213 fn test_edit_input_parsing() {
1214 let json = serde_json::json!({
1215 "file_path": "/home/user/code.rs",
1216 "old_string": "fn old()",
1217 "new_string": "fn new()",
1218 "replace_all": true
1219 });
1220
1221 let input: EditInput = serde_json::from_value(json).unwrap();
1222 assert_eq!(input.file_path, "/home/user/code.rs");
1223 assert_eq!(input.old_string, "fn old()");
1224 assert_eq!(input.new_string, "fn new()");
1225 assert_eq!(input.replace_all, Some(true));
1226 }
1227
1228 #[test]
1229 fn test_glob_input_parsing() {
1230 let json = serde_json::json!({
1231 "pattern": "**/*.rs",
1232 "path": "/home/user/project"
1233 });
1234
1235 let input: GlobInput = serde_json::from_value(json).unwrap();
1236 assert_eq!(input.pattern, "**/*.rs");
1237 assert_eq!(input.path, Some("/home/user/project".to_string()));
1238 }
1239
1240 #[test]
1241 fn test_grep_input_parsing() {
1242 let json = serde_json::json!({
1243 "pattern": "fn\\s+\\w+",
1244 "path": "/home/user/project",
1245 "type": "rust",
1246 "-i": true,
1247 "-C": 3
1248 });
1249
1250 let input: GrepInput = serde_json::from_value(json).unwrap();
1251 assert_eq!(input.pattern, "fn\\s+\\w+");
1252 assert_eq!(input.file_type, Some("rust".to_string()));
1253 assert_eq!(input.case_insensitive, Some(true));
1254 assert_eq!(input.context, Some(3));
1255 }
1256
1257 #[test]
1258 fn test_task_input_parsing() {
1259 let json = serde_json::json!({
1260 "description": "Search codebase",
1261 "prompt": "Find all usages of foo()",
1262 "subagent_type": "Explore",
1263 "run_in_background": true
1264 });
1265
1266 let input: TaskInput = serde_json::from_value(json).unwrap();
1267 assert_eq!(input.description, "Search codebase");
1268 assert_eq!(input.prompt, "Find all usages of foo()");
1269 assert_eq!(input.subagent_type, SubagentType::Explore);
1270 assert_eq!(input.run_in_background, Some(true));
1271 }
1272
1273 #[test]
1274 fn test_web_fetch_input_parsing() {
1275 let json = serde_json::json!({
1276 "url": "https://example.com",
1277 "prompt": "Extract the main content"
1278 });
1279
1280 let input: WebFetchInput = serde_json::from_value(json).unwrap();
1281 assert_eq!(input.url, "https://example.com");
1282 assert_eq!(input.prompt, "Extract the main content");
1283 }
1284
1285 #[test]
1286 fn test_web_search_input_parsing() {
1287 let json = serde_json::json!({
1288 "query": "rust serde tutorial",
1289 "allowed_domains": ["docs.rs", "crates.io"]
1290 });
1291
1292 let input: WebSearchInput = serde_json::from_value(json).unwrap();
1293 assert_eq!(input.query, "rust serde tutorial");
1294 assert_eq!(
1295 input.allowed_domains,
1296 Some(vec!["docs.rs".to_string(), "crates.io".to_string()])
1297 );
1298 }
1299
1300 #[test]
1301 fn test_todo_write_input_parsing() {
1302 let json = serde_json::json!({
1303 "todos": [
1304 {
1305 "content": "Fix the bug",
1306 "status": "in_progress",
1307 "activeForm": "Fixing the bug"
1308 },
1309 {
1310 "content": "Write tests",
1311 "status": "pending",
1312 "activeForm": "Writing tests"
1313 }
1314 ]
1315 });
1316
1317 let input: TodoWriteInput = serde_json::from_value(json).unwrap();
1318 assert_eq!(input.todos.len(), 2);
1319 assert_eq!(input.todos[0].content, "Fix the bug");
1320 assert_eq!(input.todos[0].status, TodoStatus::InProgress);
1321 assert_eq!(input.todos[1].status, TodoStatus::Pending);
1322 }
1323
1324 #[test]
1325 fn test_ask_user_question_input_parsing() {
1326 let json = serde_json::json!({
1327 "questions": [
1328 {
1329 "question": "Which framework?",
1330 "header": "Framework",
1331 "options": [
1332 {"label": "React", "description": "Popular UI library"},
1333 {"label": "Vue", "description": "Progressive framework"}
1334 ],
1335 "multiSelect": false
1336 }
1337 ]
1338 });
1339
1340 let input: AskUserQuestionInput = serde_json::from_value(json).unwrap();
1341 assert_eq!(input.questions.len(), 1);
1342 assert_eq!(input.questions[0].question, "Which framework?");
1343 assert_eq!(input.questions[0].options.len(), 2);
1344 assert_eq!(input.questions[0].options[0].label, "React");
1345 }
1346
1347 #[test]
1348 fn test_tool_input_enum_bash() {
1349 let json = serde_json::json!({
1350 "command": "ls -la"
1351 });
1352
1353 let input: ToolInput = serde_json::from_value(json).unwrap();
1354 assert!(matches!(input, ToolInput::Bash(_)));
1355 assert_eq!(input.tool_name(), Some("Bash"));
1356 assert!(input.as_bash().is_some());
1357 }
1358
1359 #[test]
1360 fn test_tool_input_enum_edit() {
1361 let json = serde_json::json!({
1362 "file_path": "/test.rs",
1363 "old_string": "old",
1364 "new_string": "new"
1365 });
1366
1367 let input: ToolInput = serde_json::from_value(json).unwrap();
1368 assert!(matches!(input, ToolInput::Edit(_)));
1369 assert_eq!(input.tool_name(), Some("Edit"));
1370 }
1371
1372 #[test]
1373 fn test_tool_input_enum_unknown() {
1374 let json = serde_json::json!({
1376 "custom_field": "custom_value",
1377 "another_field": 42
1378 });
1379
1380 let input: ToolInput = serde_json::from_value(json).unwrap();
1381 assert!(matches!(input, ToolInput::Unknown(_)));
1382 assert_eq!(input.tool_name(), None);
1383 assert!(input.is_unknown());
1384
1385 let unknown = input.as_unknown().unwrap();
1386 assert_eq!(unknown.get("custom_field").unwrap(), "custom_value");
1387 }
1388
1389 #[test]
1390 fn test_tool_input_roundtrip() {
1391 let original = BashInput {
1392 command: "echo test".to_string(),
1393 description: Some("Test command".to_string()),
1394 timeout: Some(5000),
1395 run_in_background: None,
1396 };
1397
1398 let tool_input: ToolInput = original.clone().into();
1399 let json = serde_json::to_value(&tool_input).unwrap();
1400 let parsed: ToolInput = serde_json::from_value(json).unwrap();
1401
1402 if let ToolInput::Bash(bash) = parsed {
1403 assert_eq!(bash.command, original.command);
1404 assert_eq!(bash.description, original.description);
1405 } else {
1406 panic!("Expected Bash variant");
1407 }
1408 }
1409
1410 #[test]
1411 fn test_notebook_edit_input_parsing() {
1412 let json = serde_json::json!({
1413 "notebook_path": "/home/user/notebook.ipynb",
1414 "new_source": "print('hello')",
1415 "cell_id": "abc123",
1416 "cell_type": "code",
1417 "edit_mode": "replace"
1418 });
1419
1420 let input: NotebookEditInput = serde_json::from_value(json).unwrap();
1421 assert_eq!(input.notebook_path, "/home/user/notebook.ipynb");
1422 assert_eq!(input.new_source, "print('hello')");
1423 assert_eq!(input.cell_id, Some("abc123".to_string()));
1424 }
1425
1426 #[test]
1427 fn test_task_output_input_parsing() {
1428 let json = serde_json::json!({
1429 "task_id": "task-123",
1430 "block": false,
1431 "timeout": 60000
1432 });
1433
1434 let input: TaskOutputInput = serde_json::from_value(json).unwrap();
1435 assert_eq!(input.task_id, "task-123");
1436 assert!(!input.block);
1437 assert_eq!(input.timeout, 60000);
1438 }
1439
1440 #[test]
1441 fn test_skill_input_parsing() {
1442 let json = serde_json::json!({
1443 "skill": "commit",
1444 "args": "-m 'Fix bug'"
1445 });
1446
1447 let input: SkillInput = serde_json::from_value(json).unwrap();
1448 assert_eq!(input.skill, "commit");
1449 assert_eq!(input.args, Some("-m 'Fix bug'".to_string()));
1450 }
1451
1452 #[test]
1453 fn test_multi_edit_input_parsing() {
1454 let json = serde_json::json!({
1455 "file_path": "/tmp/test.rs",
1456 "edits": [
1457 {"old_string": "foo", "new_string": "bar"},
1458 {"old_string": "baz", "new_string": "qux"}
1459 ]
1460 });
1461
1462 let input: MultiEditInput = serde_json::from_value(json.clone()).unwrap();
1463 assert_eq!(input.file_path, "/tmp/test.rs");
1464 assert_eq!(input.edits.len(), 2);
1465 assert_eq!(input.edits[0].old_string, "foo");
1466 assert_eq!(input.edits[1].new_string, "qux");
1467
1468 let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1470 assert_eq!(tool_input.tool_name(), Some("MultiEdit"));
1471 }
1472
1473 #[test]
1474 fn test_ls_input_parsing() {
1475 let json = serde_json::json!({"path": "/home/user/project"});
1476
1477 let input: LsInput = serde_json::from_value(json.clone()).unwrap();
1478 assert_eq!(input.path, "/home/user/project");
1479
1480 let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1481 assert_eq!(tool_input.tool_name(), Some("LS"));
1482 }
1483
1484 #[test]
1485 fn test_notebook_read_input_parsing() {
1486 let json = serde_json::json!({"notebook_path": "/tmp/analysis.ipynb"});
1487
1488 let input: NotebookReadInput = serde_json::from_value(json.clone()).unwrap();
1489 assert_eq!(input.notebook_path, "/tmp/analysis.ipynb");
1490
1491 let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1492 assert_eq!(tool_input.tool_name(), Some("NotebookRead"));
1493 }
1494
1495 #[test]
1496 fn test_schedule_wakeup_input_parsing() {
1497 let json = serde_json::json!({
1498 "delaySeconds": 270.0,
1499 "reason": "checking build status",
1500 "prompt": "check the build"
1501 });
1502
1503 let input: ScheduleWakeupInput = serde_json::from_value(json.clone()).unwrap();
1504 assert_eq!(input.delay_seconds, 270.0);
1505 assert_eq!(input.reason, "checking build status");
1506 assert_eq!(input.prompt, "check the build");
1507
1508 let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1509 assert_eq!(tool_input.tool_name(), Some("ScheduleWakeup"));
1510 }
1511
1512 #[test]
1513 fn test_tool_search_input_parsing() {
1514 let json = serde_json::json!({
1515 "query": "select:Read,Edit,Grep",
1516 "max_results": 5
1517 });
1518
1519 let input: ToolSearchInput = serde_json::from_value(json.clone()).unwrap();
1520 assert_eq!(input.query, "select:Read,Edit,Grep");
1521 assert_eq!(input.max_results, Some(5));
1522
1523 let tool_input: ToolInput = serde_json::from_value(json).unwrap();
1524 assert_eq!(tool_input.tool_name(), Some("ToolSearch"));
1525 }
1526
1527 #[test]
1528 fn test_from_named_input_disambiguates_websearch_from_toolsearch() {
1529 let bare_query = serde_json::json!({ "query": "rust async" });
1530
1531 let guessed: ToolInput = serde_json::from_value(bare_query.clone()).unwrap();
1534 assert!(matches!(guessed, ToolInput::ToolSearch(_)));
1535
1536 let named = ToolInput::from_named_input("WebSearch", bare_query.clone());
1538 assert!(matches!(named, ToolInput::WebSearch(_)));
1539 assert_eq!(named.tool_name(), Some("WebSearch"));
1540
1541 let named = ToolInput::from_named_input("ToolSearch", bare_query);
1543 assert!(matches!(named, ToolInput::ToolSearch(_)));
1544 assert_eq!(named.tool_name(), Some("ToolSearch"));
1545 }
1546
1547 #[test]
1548 fn test_from_named_input_known_tool() {
1549 let json = serde_json::json!({ "command": "ls -la" });
1550 let parsed = ToolInput::from_named_input("Bash", json);
1551 match parsed {
1552 ToolInput::Bash(b) => assert_eq!(b.command, "ls -la"),
1553 other => panic!("expected Bash, got {other:?}"),
1554 }
1555 }
1556
1557 #[test]
1558 fn test_from_named_input_unknown_name_defers_to_structural() {
1559 let json = serde_json::json!({ "custom_field": 42 });
1562 let parsed = ToolInput::from_named_input("mcp__server__custom", json.clone());
1563 assert!(matches!(parsed, ToolInput::Unknown(_)));
1564
1565 let bash_shaped = serde_json::json!({ "command": "echo hi" });
1568 let parsed = ToolInput::from_named_input("mcp__server__shell", bash_shaped);
1569 assert!(matches!(parsed, ToolInput::Bash(_)));
1570 }
1571
1572 #[test]
1573 fn test_from_named_input_malformed_payload_falls_back_to_unknown() {
1574 let bad = serde_json::json!({ "not_a_command": true });
1577 let parsed = ToolInput::from_named_input("Bash", bad.clone());
1578 match parsed {
1579 ToolInput::Unknown(v) => assert_eq!(v, bad),
1580 other => panic!("expected Unknown, got {other:?}"),
1581 }
1582 }
1583}