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)]
708#[serde(untagged)]
709pub enum ToolInput {
710 Edit(EditInput),
712
713 Write(WriteInput),
715
716 AskUserQuestion(AskUserQuestionInput),
718
719 TodoWrite(TodoWriteInput),
721
722 Task(TaskInput),
724
725 NotebookEdit(NotebookEditInput),
727
728 WebFetch(WebFetchInput),
730
731 TaskOutput(TaskOutputInput),
733
734 Bash(BashInput),
736
737 Read(ReadInput),
739
740 Glob(GlobInput),
742
743 Grep(GrepInput),
745
746 WebSearch(WebSearchInput),
748
749 KillShell(KillShellInput),
751
752 Skill(SkillInput),
754
755 ExitPlanMode(ExitPlanModeInput),
757
758 EnterPlanMode(EnterPlanModeInput),
760
761 Unknown(Value),
769}
770
771impl ToolInput {
772 pub fn tool_name(&self) -> Option<&'static str> {
777 match self {
778 ToolInput::Bash(_) => Some("Bash"),
779 ToolInput::Read(_) => Some("Read"),
780 ToolInput::Write(_) => Some("Write"),
781 ToolInput::Edit(_) => Some("Edit"),
782 ToolInput::Glob(_) => Some("Glob"),
783 ToolInput::Grep(_) => Some("Grep"),
784 ToolInput::Task(_) => Some("Task"),
785 ToolInput::WebFetch(_) => Some("WebFetch"),
786 ToolInput::WebSearch(_) => Some("WebSearch"),
787 ToolInput::TodoWrite(_) => Some("TodoWrite"),
788 ToolInput::AskUserQuestion(_) => Some("AskUserQuestion"),
789 ToolInput::NotebookEdit(_) => Some("NotebookEdit"),
790 ToolInput::TaskOutput(_) => Some("TaskOutput"),
791 ToolInput::KillShell(_) => Some("KillShell"),
792 ToolInput::Skill(_) => Some("Skill"),
793 ToolInput::EnterPlanMode(_) => Some("EnterPlanMode"),
794 ToolInput::ExitPlanMode(_) => Some("ExitPlanMode"),
795 ToolInput::Unknown(_) => None,
796 }
797 }
798
799 pub fn as_bash(&self) -> Option<&BashInput> {
801 match self {
802 ToolInput::Bash(input) => Some(input),
803 _ => None,
804 }
805 }
806
807 pub fn as_read(&self) -> Option<&ReadInput> {
809 match self {
810 ToolInput::Read(input) => Some(input),
811 _ => None,
812 }
813 }
814
815 pub fn as_write(&self) -> Option<&WriteInput> {
817 match self {
818 ToolInput::Write(input) => Some(input),
819 _ => None,
820 }
821 }
822
823 pub fn as_edit(&self) -> Option<&EditInput> {
825 match self {
826 ToolInput::Edit(input) => Some(input),
827 _ => None,
828 }
829 }
830
831 pub fn as_glob(&self) -> Option<&GlobInput> {
833 match self {
834 ToolInput::Glob(input) => Some(input),
835 _ => None,
836 }
837 }
838
839 pub fn as_grep(&self) -> Option<&GrepInput> {
841 match self {
842 ToolInput::Grep(input) => Some(input),
843 _ => None,
844 }
845 }
846
847 pub fn as_task(&self) -> Option<&TaskInput> {
849 match self {
850 ToolInput::Task(input) => Some(input),
851 _ => None,
852 }
853 }
854
855 pub fn as_web_fetch(&self) -> Option<&WebFetchInput> {
857 match self {
858 ToolInput::WebFetch(input) => Some(input),
859 _ => None,
860 }
861 }
862
863 pub fn as_web_search(&self) -> Option<&WebSearchInput> {
865 match self {
866 ToolInput::WebSearch(input) => Some(input),
867 _ => None,
868 }
869 }
870
871 pub fn as_todo_write(&self) -> Option<&TodoWriteInput> {
873 match self {
874 ToolInput::TodoWrite(input) => Some(input),
875 _ => None,
876 }
877 }
878
879 pub fn as_ask_user_question(&self) -> Option<&AskUserQuestionInput> {
881 match self {
882 ToolInput::AskUserQuestion(input) => Some(input),
883 _ => None,
884 }
885 }
886
887 pub fn as_notebook_edit(&self) -> Option<&NotebookEditInput> {
889 match self {
890 ToolInput::NotebookEdit(input) => Some(input),
891 _ => None,
892 }
893 }
894
895 pub fn as_task_output(&self) -> Option<&TaskOutputInput> {
897 match self {
898 ToolInput::TaskOutput(input) => Some(input),
899 _ => None,
900 }
901 }
902
903 pub fn as_kill_shell(&self) -> Option<&KillShellInput> {
905 match self {
906 ToolInput::KillShell(input) => Some(input),
907 _ => None,
908 }
909 }
910
911 pub fn as_skill(&self) -> Option<&SkillInput> {
913 match self {
914 ToolInput::Skill(input) => Some(input),
915 _ => None,
916 }
917 }
918
919 pub fn as_unknown(&self) -> Option<&Value> {
921 match self {
922 ToolInput::Unknown(value) => Some(value),
923 _ => None,
924 }
925 }
926
927 pub fn is_unknown(&self) -> bool {
929 matches!(self, ToolInput::Unknown(_))
930 }
931}
932
933impl From<BashInput> for ToolInput {
938 fn from(input: BashInput) -> Self {
939 ToolInput::Bash(input)
940 }
941}
942
943impl From<ReadInput> for ToolInput {
944 fn from(input: ReadInput) -> Self {
945 ToolInput::Read(input)
946 }
947}
948
949impl From<WriteInput> for ToolInput {
950 fn from(input: WriteInput) -> Self {
951 ToolInput::Write(input)
952 }
953}
954
955impl From<EditInput> for ToolInput {
956 fn from(input: EditInput) -> Self {
957 ToolInput::Edit(input)
958 }
959}
960
961impl From<GlobInput> for ToolInput {
962 fn from(input: GlobInput) -> Self {
963 ToolInput::Glob(input)
964 }
965}
966
967impl From<GrepInput> for ToolInput {
968 fn from(input: GrepInput) -> Self {
969 ToolInput::Grep(input)
970 }
971}
972
973impl From<TaskInput> for ToolInput {
974 fn from(input: TaskInput) -> Self {
975 ToolInput::Task(input)
976 }
977}
978
979impl From<WebFetchInput> for ToolInput {
980 fn from(input: WebFetchInput) -> Self {
981 ToolInput::WebFetch(input)
982 }
983}
984
985impl From<WebSearchInput> for ToolInput {
986 fn from(input: WebSearchInput) -> Self {
987 ToolInput::WebSearch(input)
988 }
989}
990
991impl From<TodoWriteInput> for ToolInput {
992 fn from(input: TodoWriteInput) -> Self {
993 ToolInput::TodoWrite(input)
994 }
995}
996
997impl From<AskUserQuestionInput> for ToolInput {
998 fn from(input: AskUserQuestionInput) -> Self {
999 ToolInput::AskUserQuestion(input)
1000 }
1001}
1002
1003#[cfg(test)]
1004mod tests {
1005 use super::*;
1006
1007 #[test]
1008 fn test_bash_input_parsing() {
1009 let json = serde_json::json!({
1010 "command": "ls -la",
1011 "description": "List files",
1012 "timeout": 5000,
1013 "run_in_background": false
1014 });
1015
1016 let input: BashInput = serde_json::from_value(json).unwrap();
1017 assert_eq!(input.command, "ls -la");
1018 assert_eq!(input.description, Some("List files".to_string()));
1019 assert_eq!(input.timeout, Some(5000));
1020 assert_eq!(input.run_in_background, Some(false));
1021 }
1022
1023 #[test]
1024 fn test_bash_input_minimal() {
1025 let json = serde_json::json!({
1026 "command": "echo hello"
1027 });
1028
1029 let input: BashInput = serde_json::from_value(json).unwrap();
1030 assert_eq!(input.command, "echo hello");
1031 assert_eq!(input.description, None);
1032 assert_eq!(input.timeout, None);
1033 }
1034
1035 #[test]
1036 fn test_read_input_parsing() {
1037 let json = serde_json::json!({
1038 "file_path": "/home/user/test.rs",
1039 "offset": 10,
1040 "limit": 100
1041 });
1042
1043 let input: ReadInput = serde_json::from_value(json).unwrap();
1044 assert_eq!(input.file_path, "/home/user/test.rs");
1045 assert_eq!(input.offset, Some(10));
1046 assert_eq!(input.limit, Some(100));
1047 }
1048
1049 #[test]
1050 fn test_write_input_parsing() {
1051 let json = serde_json::json!({
1052 "file_path": "/tmp/test.txt",
1053 "content": "Hello, world!"
1054 });
1055
1056 let input: WriteInput = serde_json::from_value(json).unwrap();
1057 assert_eq!(input.file_path, "/tmp/test.txt");
1058 assert_eq!(input.content, "Hello, world!");
1059 }
1060
1061 #[test]
1062 fn test_edit_input_parsing() {
1063 let json = serde_json::json!({
1064 "file_path": "/home/user/code.rs",
1065 "old_string": "fn old()",
1066 "new_string": "fn new()",
1067 "replace_all": true
1068 });
1069
1070 let input: EditInput = serde_json::from_value(json).unwrap();
1071 assert_eq!(input.file_path, "/home/user/code.rs");
1072 assert_eq!(input.old_string, "fn old()");
1073 assert_eq!(input.new_string, "fn new()");
1074 assert_eq!(input.replace_all, Some(true));
1075 }
1076
1077 #[test]
1078 fn test_glob_input_parsing() {
1079 let json = serde_json::json!({
1080 "pattern": "**/*.rs",
1081 "path": "/home/user/project"
1082 });
1083
1084 let input: GlobInput = serde_json::from_value(json).unwrap();
1085 assert_eq!(input.pattern, "**/*.rs");
1086 assert_eq!(input.path, Some("/home/user/project".to_string()));
1087 }
1088
1089 #[test]
1090 fn test_grep_input_parsing() {
1091 let json = serde_json::json!({
1092 "pattern": "fn\\s+\\w+",
1093 "path": "/home/user/project",
1094 "type": "rust",
1095 "-i": true,
1096 "-C": 3
1097 });
1098
1099 let input: GrepInput = serde_json::from_value(json).unwrap();
1100 assert_eq!(input.pattern, "fn\\s+\\w+");
1101 assert_eq!(input.file_type, Some("rust".to_string()));
1102 assert_eq!(input.case_insensitive, Some(true));
1103 assert_eq!(input.context, Some(3));
1104 }
1105
1106 #[test]
1107 fn test_task_input_parsing() {
1108 let json = serde_json::json!({
1109 "description": "Search codebase",
1110 "prompt": "Find all usages of foo()",
1111 "subagent_type": "Explore",
1112 "run_in_background": true
1113 });
1114
1115 let input: TaskInput = serde_json::from_value(json).unwrap();
1116 assert_eq!(input.description, "Search codebase");
1117 assert_eq!(input.prompt, "Find all usages of foo()");
1118 assert_eq!(input.subagent_type, SubagentType::Explore);
1119 assert_eq!(input.run_in_background, Some(true));
1120 }
1121
1122 #[test]
1123 fn test_web_fetch_input_parsing() {
1124 let json = serde_json::json!({
1125 "url": "https://example.com",
1126 "prompt": "Extract the main content"
1127 });
1128
1129 let input: WebFetchInput = serde_json::from_value(json).unwrap();
1130 assert_eq!(input.url, "https://example.com");
1131 assert_eq!(input.prompt, "Extract the main content");
1132 }
1133
1134 #[test]
1135 fn test_web_search_input_parsing() {
1136 let json = serde_json::json!({
1137 "query": "rust serde tutorial",
1138 "allowed_domains": ["docs.rs", "crates.io"]
1139 });
1140
1141 let input: WebSearchInput = serde_json::from_value(json).unwrap();
1142 assert_eq!(input.query, "rust serde tutorial");
1143 assert_eq!(
1144 input.allowed_domains,
1145 Some(vec!["docs.rs".to_string(), "crates.io".to_string()])
1146 );
1147 }
1148
1149 #[test]
1150 fn test_todo_write_input_parsing() {
1151 let json = serde_json::json!({
1152 "todos": [
1153 {
1154 "content": "Fix the bug",
1155 "status": "in_progress",
1156 "activeForm": "Fixing the bug"
1157 },
1158 {
1159 "content": "Write tests",
1160 "status": "pending",
1161 "activeForm": "Writing tests"
1162 }
1163 ]
1164 });
1165
1166 let input: TodoWriteInput = serde_json::from_value(json).unwrap();
1167 assert_eq!(input.todos.len(), 2);
1168 assert_eq!(input.todos[0].content, "Fix the bug");
1169 assert_eq!(input.todos[0].status, TodoStatus::InProgress);
1170 assert_eq!(input.todos[1].status, TodoStatus::Pending);
1171 }
1172
1173 #[test]
1174 fn test_ask_user_question_input_parsing() {
1175 let json = serde_json::json!({
1176 "questions": [
1177 {
1178 "question": "Which framework?",
1179 "header": "Framework",
1180 "options": [
1181 {"label": "React", "description": "Popular UI library"},
1182 {"label": "Vue", "description": "Progressive framework"}
1183 ],
1184 "multiSelect": false
1185 }
1186 ]
1187 });
1188
1189 let input: AskUserQuestionInput = serde_json::from_value(json).unwrap();
1190 assert_eq!(input.questions.len(), 1);
1191 assert_eq!(input.questions[0].question, "Which framework?");
1192 assert_eq!(input.questions[0].options.len(), 2);
1193 assert_eq!(input.questions[0].options[0].label, "React");
1194 }
1195
1196 #[test]
1197 fn test_tool_input_enum_bash() {
1198 let json = serde_json::json!({
1199 "command": "ls -la"
1200 });
1201
1202 let input: ToolInput = serde_json::from_value(json).unwrap();
1203 assert!(matches!(input, ToolInput::Bash(_)));
1204 assert_eq!(input.tool_name(), Some("Bash"));
1205 assert!(input.as_bash().is_some());
1206 }
1207
1208 #[test]
1209 fn test_tool_input_enum_edit() {
1210 let json = serde_json::json!({
1211 "file_path": "/test.rs",
1212 "old_string": "old",
1213 "new_string": "new"
1214 });
1215
1216 let input: ToolInput = serde_json::from_value(json).unwrap();
1217 assert!(matches!(input, ToolInput::Edit(_)));
1218 assert_eq!(input.tool_name(), Some("Edit"));
1219 }
1220
1221 #[test]
1222 fn test_tool_input_enum_unknown() {
1223 let json = serde_json::json!({
1225 "custom_field": "custom_value",
1226 "another_field": 42
1227 });
1228
1229 let input: ToolInput = serde_json::from_value(json).unwrap();
1230 assert!(matches!(input, ToolInput::Unknown(_)));
1231 assert_eq!(input.tool_name(), None);
1232 assert!(input.is_unknown());
1233
1234 let unknown = input.as_unknown().unwrap();
1235 assert_eq!(unknown.get("custom_field").unwrap(), "custom_value");
1236 }
1237
1238 #[test]
1239 fn test_tool_input_roundtrip() {
1240 let original = BashInput {
1241 command: "echo test".to_string(),
1242 description: Some("Test command".to_string()),
1243 timeout: Some(5000),
1244 run_in_background: None,
1245 };
1246
1247 let tool_input: ToolInput = original.clone().into();
1248 let json = serde_json::to_value(&tool_input).unwrap();
1249 let parsed: ToolInput = serde_json::from_value(json).unwrap();
1250
1251 if let ToolInput::Bash(bash) = parsed {
1252 assert_eq!(bash.command, original.command);
1253 assert_eq!(bash.description, original.description);
1254 } else {
1255 panic!("Expected Bash variant");
1256 }
1257 }
1258
1259 #[test]
1260 fn test_notebook_edit_input_parsing() {
1261 let json = serde_json::json!({
1262 "notebook_path": "/home/user/notebook.ipynb",
1263 "new_source": "print('hello')",
1264 "cell_id": "abc123",
1265 "cell_type": "code",
1266 "edit_mode": "replace"
1267 });
1268
1269 let input: NotebookEditInput = serde_json::from_value(json).unwrap();
1270 assert_eq!(input.notebook_path, "/home/user/notebook.ipynb");
1271 assert_eq!(input.new_source, "print('hello')");
1272 assert_eq!(input.cell_id, Some("abc123".to_string()));
1273 }
1274
1275 #[test]
1276 fn test_task_output_input_parsing() {
1277 let json = serde_json::json!({
1278 "task_id": "task-123",
1279 "block": false,
1280 "timeout": 60000
1281 });
1282
1283 let input: TaskOutputInput = serde_json::from_value(json).unwrap();
1284 assert_eq!(input.task_id, "task-123");
1285 assert!(!input.block);
1286 assert_eq!(input.timeout, 60000);
1287 }
1288
1289 #[test]
1290 fn test_skill_input_parsing() {
1291 let json = serde_json::json!({
1292 "skill": "commit",
1293 "args": "-m 'Fix bug'"
1294 });
1295
1296 let input: SkillInput = serde_json::from_value(json).unwrap();
1297 assert_eq!(input.skill, "commit");
1298 assert_eq!(input.args, Some("-m 'Fix bug'".to_string()));
1299 }
1300}