use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BashInput {
pub command: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub run_in_background: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ReadInput {
pub file_path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WriteInput {
pub file_path: String,
pub content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EditInput {
pub file_path: String,
pub old_string: String,
pub new_string: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub replace_all: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct GlobInput {
pub pattern: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GrepInput {
pub pattern: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub glob: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub file_type: Option<String>,
#[serde(rename = "-i", skip_serializing_if = "Option::is_none")]
pub case_insensitive: Option<bool>,
#[serde(rename = "-n", skip_serializing_if = "Option::is_none")]
pub line_numbers: Option<bool>,
#[serde(rename = "-A", skip_serializing_if = "Option::is_none")]
pub after_context: Option<u32>,
#[serde(rename = "-B", skip_serializing_if = "Option::is_none")]
pub before_context: Option<u32>,
#[serde(rename = "-C", skip_serializing_if = "Option::is_none")]
pub context: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_mode: Option<GrepOutputMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multiline: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub head_limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TaskInput {
pub description: String,
pub prompt: String,
pub subagent_type: SubagentType,
#[serde(skip_serializing_if = "Option::is_none")]
pub run_in_background: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_turns: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resume: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WebFetchInput {
pub url: String,
pub prompt: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WebSearchInput {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_domains: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blocked_domains: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TodoWriteInput {
pub todos: Vec<TodoItem>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TodoStatus {
Pending,
InProgress,
Completed,
Unknown(String),
}
impl TodoStatus {
pub fn as_str(&self) -> &str {
match self {
Self::Pending => "pending",
Self::InProgress => "in_progress",
Self::Completed => "completed",
Self::Unknown(s) => s.as_str(),
}
}
}
impl fmt::Display for TodoStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&str> for TodoStatus {
fn from(s: &str) -> Self {
match s {
"pending" => Self::Pending,
"in_progress" => Self::InProgress,
"completed" => Self::Completed,
other => Self::Unknown(other.to_string()),
}
}
}
impl Serialize for TodoStatus {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for TodoStatus {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Self::from(s.as_str()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum GrepOutputMode {
Content,
FilesWithMatches,
Count,
Unknown(String),
}
impl GrepOutputMode {
pub fn as_str(&self) -> &str {
match self {
Self::Content => "content",
Self::FilesWithMatches => "files_with_matches",
Self::Count => "count",
Self::Unknown(s) => s.as_str(),
}
}
}
impl fmt::Display for GrepOutputMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&str> for GrepOutputMode {
fn from(s: &str) -> Self {
match s {
"content" => Self::Content,
"files_with_matches" => Self::FilesWithMatches,
"count" => Self::Count,
other => Self::Unknown(other.to_string()),
}
}
}
impl Serialize for GrepOutputMode {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for GrepOutputMode {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Self::from(s.as_str()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SubagentType {
Bash,
Explore,
Plan,
GeneralPurpose,
Unknown(String),
}
impl SubagentType {
pub fn as_str(&self) -> &str {
match self {
Self::Bash => "Bash",
Self::Explore => "Explore",
Self::Plan => "Plan",
Self::GeneralPurpose => "general-purpose",
Self::Unknown(s) => s.as_str(),
}
}
}
impl fmt::Display for SubagentType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&str> for SubagentType {
fn from(s: &str) -> Self {
match s {
"Bash" => Self::Bash,
"Explore" => Self::Explore,
"Plan" => Self::Plan,
"general-purpose" => Self::GeneralPurpose,
other => Self::Unknown(other.to_string()),
}
}
}
impl Serialize for SubagentType {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for SubagentType {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Self::from(s.as_str()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum NotebookCellType {
Code,
Markdown,
Unknown(String),
}
impl NotebookCellType {
pub fn as_str(&self) -> &str {
match self {
Self::Code => "code",
Self::Markdown => "markdown",
Self::Unknown(s) => s.as_str(),
}
}
}
impl fmt::Display for NotebookCellType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&str> for NotebookCellType {
fn from(s: &str) -> Self {
match s {
"code" => Self::Code,
"markdown" => Self::Markdown,
other => Self::Unknown(other.to_string()),
}
}
}
impl Serialize for NotebookCellType {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for NotebookCellType {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Self::from(s.as_str()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum NotebookEditMode {
Replace,
Insert,
Delete,
Unknown(String),
}
impl NotebookEditMode {
pub fn as_str(&self) -> &str {
match self {
Self::Replace => "replace",
Self::Insert => "insert",
Self::Delete => "delete",
Self::Unknown(s) => s.as_str(),
}
}
}
impl fmt::Display for NotebookEditMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<&str> for NotebookEditMode {
fn from(s: &str) -> Self {
match s {
"replace" => Self::Replace,
"insert" => Self::Insert,
"delete" => Self::Delete,
other => Self::Unknown(other.to_string()),
}
}
}
impl Serialize for NotebookEditMode {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for NotebookEditMode {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Self::from(s.as_str()))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TodoItem {
pub content: String,
pub status: TodoStatus,
#[serde(rename = "activeForm")]
pub active_form: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AskUserQuestionInput {
pub questions: Vec<Question>,
#[serde(skip_serializing_if = "Option::is_none")]
pub answers: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<QuestionMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Question {
pub question: String,
pub header: String,
pub options: Vec<QuestionOption>,
#[serde(rename = "multiSelect", default)]
pub multi_select: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct QuestionOption {
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct QuestionMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NotebookEditInput {
pub notebook_path: String,
pub new_source: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_type: Option<NotebookCellType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub edit_mode: Option<NotebookEditMode>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TaskOutputInput {
pub task_id: String,
#[serde(default = "default_true")]
pub block: bool,
#[serde(default = "default_timeout")]
pub timeout: u64,
}
fn default_true() -> bool {
true
}
fn default_timeout() -> u64 {
30000
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct KillShellInput {
pub shell_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SkillInput {
pub skill: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub args: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(deny_unknown_fields)]
pub struct EnterPlanModeInput {}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(deny_unknown_fields)]
pub struct ExitPlanModeInput {
#[serde(rename = "allowedPrompts", skip_serializing_if = "Option::is_none")]
pub allowed_prompts: Option<Vec<AllowedPrompt>>,
#[serde(rename = "pushToRemote", skip_serializing_if = "Option::is_none")]
pub push_to_remote: Option<bool>,
#[serde(rename = "remoteSessionId", skip_serializing_if = "Option::is_none")]
pub remote_session_id: Option<String>,
#[serde(rename = "remoteSessionUrl", skip_serializing_if = "Option::is_none")]
pub remote_session_url: Option<String>,
#[serde(rename = "remoteSessionTitle", skip_serializing_if = "Option::is_none")]
pub remote_session_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plan: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AllowedPrompt {
pub tool: String,
pub prompt: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MultiEditInput {
pub file_path: String,
pub edits: Vec<MultiEditOperation>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MultiEditOperation {
pub old_string: String,
pub new_string: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct LsInput {
pub path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NotebookReadInput {
pub notebook_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ScheduleWakeupInput {
#[serde(rename = "delaySeconds")]
pub delay_seconds: f64,
pub reason: String,
pub prompt: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct ToolSearchInput {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_results: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum ToolInput {
Edit(EditInput),
Write(WriteInput),
MultiEdit(MultiEditInput),
AskUserQuestion(AskUserQuestionInput),
TodoWrite(TodoWriteInput),
Task(TaskInput),
NotebookEdit(NotebookEditInput),
WebFetch(WebFetchInput),
TaskOutput(TaskOutputInput),
Bash(BashInput),
Read(ReadInput),
Glob(GlobInput),
Grep(GrepInput),
ToolSearch(ToolSearchInput),
WebSearch(WebSearchInput),
KillShell(KillShellInput),
Skill(SkillInput),
ExitPlanMode(ExitPlanModeInput),
ScheduleWakeup(ScheduleWakeupInput),
NotebookRead(NotebookReadInput),
LS(LsInput),
EnterPlanMode(EnterPlanModeInput),
Unknown(Value),
}
impl ToolInput {
pub fn tool_name(&self) -> Option<&'static str> {
match self {
ToolInput::Bash(_) => Some("Bash"),
ToolInput::Read(_) => Some("Read"),
ToolInput::Write(_) => Some("Write"),
ToolInput::Edit(_) => Some("Edit"),
ToolInput::Glob(_) => Some("Glob"),
ToolInput::Grep(_) => Some("Grep"),
ToolInput::Task(_) => Some("Task"),
ToolInput::WebFetch(_) => Some("WebFetch"),
ToolInput::WebSearch(_) => Some("WebSearch"),
ToolInput::TodoWrite(_) => Some("TodoWrite"),
ToolInput::AskUserQuestion(_) => Some("AskUserQuestion"),
ToolInput::NotebookEdit(_) => Some("NotebookEdit"),
ToolInput::TaskOutput(_) => Some("TaskOutput"),
ToolInput::KillShell(_) => Some("KillShell"),
ToolInput::Skill(_) => Some("Skill"),
ToolInput::EnterPlanMode(_) => Some("EnterPlanMode"),
ToolInput::ExitPlanMode(_) => Some("ExitPlanMode"),
ToolInput::MultiEdit(_) => Some("MultiEdit"),
ToolInput::ScheduleWakeup(_) => Some("ScheduleWakeup"),
ToolInput::NotebookRead(_) => Some("NotebookRead"),
ToolInput::ToolSearch(_) => Some("ToolSearch"),
ToolInput::LS(_) => Some("LS"),
ToolInput::Unknown(_) => None,
}
}
pub fn as_bash(&self) -> Option<&BashInput> {
match self {
ToolInput::Bash(input) => Some(input),
_ => None,
}
}
pub fn as_read(&self) -> Option<&ReadInput> {
match self {
ToolInput::Read(input) => Some(input),
_ => None,
}
}
pub fn as_write(&self) -> Option<&WriteInput> {
match self {
ToolInput::Write(input) => Some(input),
_ => None,
}
}
pub fn as_edit(&self) -> Option<&EditInput> {
match self {
ToolInput::Edit(input) => Some(input),
_ => None,
}
}
pub fn as_glob(&self) -> Option<&GlobInput> {
match self {
ToolInput::Glob(input) => Some(input),
_ => None,
}
}
pub fn as_grep(&self) -> Option<&GrepInput> {
match self {
ToolInput::Grep(input) => Some(input),
_ => None,
}
}
pub fn as_task(&self) -> Option<&TaskInput> {
match self {
ToolInput::Task(input) => Some(input),
_ => None,
}
}
pub fn as_web_fetch(&self) -> Option<&WebFetchInput> {
match self {
ToolInput::WebFetch(input) => Some(input),
_ => None,
}
}
pub fn as_web_search(&self) -> Option<&WebSearchInput> {
match self {
ToolInput::WebSearch(input) => Some(input),
_ => None,
}
}
pub fn as_todo_write(&self) -> Option<&TodoWriteInput> {
match self {
ToolInput::TodoWrite(input) => Some(input),
_ => None,
}
}
pub fn as_ask_user_question(&self) -> Option<&AskUserQuestionInput> {
match self {
ToolInput::AskUserQuestion(input) => Some(input),
_ => None,
}
}
pub fn as_notebook_edit(&self) -> Option<&NotebookEditInput> {
match self {
ToolInput::NotebookEdit(input) => Some(input),
_ => None,
}
}
pub fn as_task_output(&self) -> Option<&TaskOutputInput> {
match self {
ToolInput::TaskOutput(input) => Some(input),
_ => None,
}
}
pub fn as_kill_shell(&self) -> Option<&KillShellInput> {
match self {
ToolInput::KillShell(input) => Some(input),
_ => None,
}
}
pub fn as_skill(&self) -> Option<&SkillInput> {
match self {
ToolInput::Skill(input) => Some(input),
_ => None,
}
}
pub fn as_unknown(&self) -> Option<&Value> {
match self {
ToolInput::Unknown(value) => Some(value),
_ => None,
}
}
pub fn is_unknown(&self) -> bool {
matches!(self, ToolInput::Unknown(_))
}
}
impl From<BashInput> for ToolInput {
fn from(input: BashInput) -> Self {
ToolInput::Bash(input)
}
}
impl From<ReadInput> for ToolInput {
fn from(input: ReadInput) -> Self {
ToolInput::Read(input)
}
}
impl From<WriteInput> for ToolInput {
fn from(input: WriteInput) -> Self {
ToolInput::Write(input)
}
}
impl From<EditInput> for ToolInput {
fn from(input: EditInput) -> Self {
ToolInput::Edit(input)
}
}
impl From<GlobInput> for ToolInput {
fn from(input: GlobInput) -> Self {
ToolInput::Glob(input)
}
}
impl From<GrepInput> for ToolInput {
fn from(input: GrepInput) -> Self {
ToolInput::Grep(input)
}
}
impl From<TaskInput> for ToolInput {
fn from(input: TaskInput) -> Self {
ToolInput::Task(input)
}
}
impl From<WebFetchInput> for ToolInput {
fn from(input: WebFetchInput) -> Self {
ToolInput::WebFetch(input)
}
}
impl From<WebSearchInput> for ToolInput {
fn from(input: WebSearchInput) -> Self {
ToolInput::WebSearch(input)
}
}
impl From<TodoWriteInput> for ToolInput {
fn from(input: TodoWriteInput) -> Self {
ToolInput::TodoWrite(input)
}
}
impl From<AskUserQuestionInput> for ToolInput {
fn from(input: AskUserQuestionInput) -> Self {
ToolInput::AskUserQuestion(input)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bash_input_parsing() {
let json = serde_json::json!({
"command": "ls -la",
"description": "List files",
"timeout": 5000,
"run_in_background": false
});
let input: BashInput = serde_json::from_value(json).unwrap();
assert_eq!(input.command, "ls -la");
assert_eq!(input.description, Some("List files".to_string()));
assert_eq!(input.timeout, Some(5000));
assert_eq!(input.run_in_background, Some(false));
}
#[test]
fn test_bash_input_minimal() {
let json = serde_json::json!({
"command": "echo hello"
});
let input: BashInput = serde_json::from_value(json).unwrap();
assert_eq!(input.command, "echo hello");
assert_eq!(input.description, None);
assert_eq!(input.timeout, None);
}
#[test]
fn test_read_input_parsing() {
let json = serde_json::json!({
"file_path": "/home/user/test.rs",
"offset": 10,
"limit": 100
});
let input: ReadInput = serde_json::from_value(json).unwrap();
assert_eq!(input.file_path, "/home/user/test.rs");
assert_eq!(input.offset, Some(10));
assert_eq!(input.limit, Some(100));
}
#[test]
fn test_write_input_parsing() {
let json = serde_json::json!({
"file_path": "/tmp/test.txt",
"content": "Hello, world!"
});
let input: WriteInput = serde_json::from_value(json).unwrap();
assert_eq!(input.file_path, "/tmp/test.txt");
assert_eq!(input.content, "Hello, world!");
}
#[test]
fn test_edit_input_parsing() {
let json = serde_json::json!({
"file_path": "/home/user/code.rs",
"old_string": "fn old()",
"new_string": "fn new()",
"replace_all": true
});
let input: EditInput = serde_json::from_value(json).unwrap();
assert_eq!(input.file_path, "/home/user/code.rs");
assert_eq!(input.old_string, "fn old()");
assert_eq!(input.new_string, "fn new()");
assert_eq!(input.replace_all, Some(true));
}
#[test]
fn test_glob_input_parsing() {
let json = serde_json::json!({
"pattern": "**/*.rs",
"path": "/home/user/project"
});
let input: GlobInput = serde_json::from_value(json).unwrap();
assert_eq!(input.pattern, "**/*.rs");
assert_eq!(input.path, Some("/home/user/project".to_string()));
}
#[test]
fn test_grep_input_parsing() {
let json = serde_json::json!({
"pattern": "fn\\s+\\w+",
"path": "/home/user/project",
"type": "rust",
"-i": true,
"-C": 3
});
let input: GrepInput = serde_json::from_value(json).unwrap();
assert_eq!(input.pattern, "fn\\s+\\w+");
assert_eq!(input.file_type, Some("rust".to_string()));
assert_eq!(input.case_insensitive, Some(true));
assert_eq!(input.context, Some(3));
}
#[test]
fn test_task_input_parsing() {
let json = serde_json::json!({
"description": "Search codebase",
"prompt": "Find all usages of foo()",
"subagent_type": "Explore",
"run_in_background": true
});
let input: TaskInput = serde_json::from_value(json).unwrap();
assert_eq!(input.description, "Search codebase");
assert_eq!(input.prompt, "Find all usages of foo()");
assert_eq!(input.subagent_type, SubagentType::Explore);
assert_eq!(input.run_in_background, Some(true));
}
#[test]
fn test_web_fetch_input_parsing() {
let json = serde_json::json!({
"url": "https://example.com",
"prompt": "Extract the main content"
});
let input: WebFetchInput = serde_json::from_value(json).unwrap();
assert_eq!(input.url, "https://example.com");
assert_eq!(input.prompt, "Extract the main content");
}
#[test]
fn test_web_search_input_parsing() {
let json = serde_json::json!({
"query": "rust serde tutorial",
"allowed_domains": ["docs.rs", "crates.io"]
});
let input: WebSearchInput = serde_json::from_value(json).unwrap();
assert_eq!(input.query, "rust serde tutorial");
assert_eq!(
input.allowed_domains,
Some(vec!["docs.rs".to_string(), "crates.io".to_string()])
);
}
#[test]
fn test_todo_write_input_parsing() {
let json = serde_json::json!({
"todos": [
{
"content": "Fix the bug",
"status": "in_progress",
"activeForm": "Fixing the bug"
},
{
"content": "Write tests",
"status": "pending",
"activeForm": "Writing tests"
}
]
});
let input: TodoWriteInput = serde_json::from_value(json).unwrap();
assert_eq!(input.todos.len(), 2);
assert_eq!(input.todos[0].content, "Fix the bug");
assert_eq!(input.todos[0].status, TodoStatus::InProgress);
assert_eq!(input.todos[1].status, TodoStatus::Pending);
}
#[test]
fn test_ask_user_question_input_parsing() {
let json = serde_json::json!({
"questions": [
{
"question": "Which framework?",
"header": "Framework",
"options": [
{"label": "React", "description": "Popular UI library"},
{"label": "Vue", "description": "Progressive framework"}
],
"multiSelect": false
}
]
});
let input: AskUserQuestionInput = serde_json::from_value(json).unwrap();
assert_eq!(input.questions.len(), 1);
assert_eq!(input.questions[0].question, "Which framework?");
assert_eq!(input.questions[0].options.len(), 2);
assert_eq!(input.questions[0].options[0].label, "React");
}
#[test]
fn test_tool_input_enum_bash() {
let json = serde_json::json!({
"command": "ls -la"
});
let input: ToolInput = serde_json::from_value(json).unwrap();
assert!(matches!(input, ToolInput::Bash(_)));
assert_eq!(input.tool_name(), Some("Bash"));
assert!(input.as_bash().is_some());
}
#[test]
fn test_tool_input_enum_edit() {
let json = serde_json::json!({
"file_path": "/test.rs",
"old_string": "old",
"new_string": "new"
});
let input: ToolInput = serde_json::from_value(json).unwrap();
assert!(matches!(input, ToolInput::Edit(_)));
assert_eq!(input.tool_name(), Some("Edit"));
}
#[test]
fn test_tool_input_enum_unknown() {
let json = serde_json::json!({
"custom_field": "custom_value",
"another_field": 42
});
let input: ToolInput = serde_json::from_value(json).unwrap();
assert!(matches!(input, ToolInput::Unknown(_)));
assert_eq!(input.tool_name(), None);
assert!(input.is_unknown());
let unknown = input.as_unknown().unwrap();
assert_eq!(unknown.get("custom_field").unwrap(), "custom_value");
}
#[test]
fn test_tool_input_roundtrip() {
let original = BashInput {
command: "echo test".to_string(),
description: Some("Test command".to_string()),
timeout: Some(5000),
run_in_background: None,
};
let tool_input: ToolInput = original.clone().into();
let json = serde_json::to_value(&tool_input).unwrap();
let parsed: ToolInput = serde_json::from_value(json).unwrap();
if let ToolInput::Bash(bash) = parsed {
assert_eq!(bash.command, original.command);
assert_eq!(bash.description, original.description);
} else {
panic!("Expected Bash variant");
}
}
#[test]
fn test_notebook_edit_input_parsing() {
let json = serde_json::json!({
"notebook_path": "/home/user/notebook.ipynb",
"new_source": "print('hello')",
"cell_id": "abc123",
"cell_type": "code",
"edit_mode": "replace"
});
let input: NotebookEditInput = serde_json::from_value(json).unwrap();
assert_eq!(input.notebook_path, "/home/user/notebook.ipynb");
assert_eq!(input.new_source, "print('hello')");
assert_eq!(input.cell_id, Some("abc123".to_string()));
}
#[test]
fn test_task_output_input_parsing() {
let json = serde_json::json!({
"task_id": "task-123",
"block": false,
"timeout": 60000
});
let input: TaskOutputInput = serde_json::from_value(json).unwrap();
assert_eq!(input.task_id, "task-123");
assert!(!input.block);
assert_eq!(input.timeout, 60000);
}
#[test]
fn test_skill_input_parsing() {
let json = serde_json::json!({
"skill": "commit",
"args": "-m 'Fix bug'"
});
let input: SkillInput = serde_json::from_value(json).unwrap();
assert_eq!(input.skill, "commit");
assert_eq!(input.args, Some("-m 'Fix bug'".to_string()));
}
#[test]
fn test_multi_edit_input_parsing() {
let json = serde_json::json!({
"file_path": "/tmp/test.rs",
"edits": [
{"old_string": "foo", "new_string": "bar"},
{"old_string": "baz", "new_string": "qux"}
]
});
let input: MultiEditInput = serde_json::from_value(json.clone()).unwrap();
assert_eq!(input.file_path, "/tmp/test.rs");
assert_eq!(input.edits.len(), 2);
assert_eq!(input.edits[0].old_string, "foo");
assert_eq!(input.edits[1].new_string, "qux");
let tool_input: ToolInput = serde_json::from_value(json).unwrap();
assert_eq!(tool_input.tool_name(), Some("MultiEdit"));
}
#[test]
fn test_ls_input_parsing() {
let json = serde_json::json!({"path": "/home/user/project"});
let input: LsInput = serde_json::from_value(json.clone()).unwrap();
assert_eq!(input.path, "/home/user/project");
let tool_input: ToolInput = serde_json::from_value(json).unwrap();
assert_eq!(tool_input.tool_name(), Some("LS"));
}
#[test]
fn test_notebook_read_input_parsing() {
let json = serde_json::json!({"notebook_path": "/tmp/analysis.ipynb"});
let input: NotebookReadInput = serde_json::from_value(json.clone()).unwrap();
assert_eq!(input.notebook_path, "/tmp/analysis.ipynb");
let tool_input: ToolInput = serde_json::from_value(json).unwrap();
assert_eq!(tool_input.tool_name(), Some("NotebookRead"));
}
#[test]
fn test_schedule_wakeup_input_parsing() {
let json = serde_json::json!({
"delaySeconds": 270.0,
"reason": "checking build status",
"prompt": "check the build"
});
let input: ScheduleWakeupInput = serde_json::from_value(json.clone()).unwrap();
assert_eq!(input.delay_seconds, 270.0);
assert_eq!(input.reason, "checking build status");
assert_eq!(input.prompt, "check the build");
let tool_input: ToolInput = serde_json::from_value(json).unwrap();
assert_eq!(tool_input.tool_name(), Some("ScheduleWakeup"));
}
#[test]
fn test_tool_search_input_parsing() {
let json = serde_json::json!({
"query": "select:Read,Edit,Grep",
"max_results": 5
});
let input: ToolSearchInput = serde_json::from_value(json.clone()).unwrap();
assert_eq!(input.query, "select:Read,Edit,Grep");
assert_eq!(input.max_results, Some(5));
let tool_input: ToolInput = serde_json::from_value(json).unwrap();
assert_eq!(tool_input.tool_name(), Some("ToolSearch"));
}
}