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 as_bash(&self) -> Option<&BashInput> {
882 match self {
883 ToolInput::Bash(input) => Some(input),
884 _ => None,
885 }
886 }
887
888 pub fn as_read(&self) -> Option<&ReadInput> {
890 match self {
891 ToolInput::Read(input) => Some(input),
892 _ => None,
893 }
894 }
895
896 pub fn as_write(&self) -> Option<&WriteInput> {
898 match self {
899 ToolInput::Write(input) => Some(input),
900 _ => None,
901 }
902 }
903
904 pub fn as_edit(&self) -> Option<&EditInput> {
906 match self {
907 ToolInput::Edit(input) => Some(input),
908 _ => None,
909 }
910 }
911
912 pub fn as_glob(&self) -> Option<&GlobInput> {
914 match self {
915 ToolInput::Glob(input) => Some(input),
916 _ => None,
917 }
918 }
919
920 pub fn as_grep(&self) -> Option<&GrepInput> {
922 match self {
923 ToolInput::Grep(input) => Some(input),
924 _ => None,
925 }
926 }
927
928 pub fn as_task(&self) -> Option<&TaskInput> {
930 match self {
931 ToolInput::Task(input) => Some(input),
932 _ => None,
933 }
934 }
935
936 pub fn as_web_fetch(&self) -> Option<&WebFetchInput> {
938 match self {
939 ToolInput::WebFetch(input) => Some(input),
940 _ => None,
941 }
942 }
943
944 pub fn as_web_search(&self) -> Option<&WebSearchInput> {
946 match self {
947 ToolInput::WebSearch(input) => Some(input),
948 _ => None,
949 }
950 }
951
952 pub fn as_todo_write(&self) -> Option<&TodoWriteInput> {
954 match self {
955 ToolInput::TodoWrite(input) => Some(input),
956 _ => None,
957 }
958 }
959
960 pub fn as_ask_user_question(&self) -> Option<&AskUserQuestionInput> {
962 match self {
963 ToolInput::AskUserQuestion(input) => Some(input),
964 _ => None,
965 }
966 }
967
968 pub fn as_notebook_edit(&self) -> Option<&NotebookEditInput> {
970 match self {
971 ToolInput::NotebookEdit(input) => Some(input),
972 _ => None,
973 }
974 }
975
976 pub fn as_task_output(&self) -> Option<&TaskOutputInput> {
978 match self {
979 ToolInput::TaskOutput(input) => Some(input),
980 _ => None,
981 }
982 }
983
984 pub fn as_kill_shell(&self) -> Option<&KillShellInput> {
986 match self {
987 ToolInput::KillShell(input) => Some(input),
988 _ => None,
989 }
990 }
991
992 pub fn as_skill(&self) -> Option<&SkillInput> {
994 match self {
995 ToolInput::Skill(input) => Some(input),
996 _ => None,
997 }
998 }
999
1000 pub fn as_unknown(&self) -> Option<&Value> {
1002 match self {
1003 ToolInput::Unknown(value) => Some(value),
1004 _ => None,
1005 }
1006 }
1007
1008 pub fn is_unknown(&self) -> bool {
1010 matches!(self, ToolInput::Unknown(_))
1011 }
1012}
1013
1014impl 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 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 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}