diff --git a/rust/crates/tools/src/lib.rs b/rust/crates/tools/src/lib.rs index 954c57e..d70a6e1 100644 --- a/rust/crates/tools/src/lib.rs +++ b/rust/crates/tools/src/lib.rs @@ -564,6 +564,100 @@ pub fn mvp_tool_specs() -> Vec { }), required_permission: PermissionMode::DangerFullAccess, }, + ToolSpec { + name: "AskUserQuestion", + description: "Ask the user a question and wait for their response.", + input_schema: json!({ + "type": "object", + "properties": { + "question": { "type": "string" }, + "options": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["question"], + "additionalProperties": false + }), + required_permission: PermissionMode::ReadOnly, + }, + ToolSpec { + name: "TaskCreate", + description: "Create a background task that runs in a separate subprocess.", + input_schema: json!({ + "type": "object", + "properties": { + "prompt": { "type": "string" }, + "description": { "type": "string" } + }, + "required": ["prompt"], + "additionalProperties": false + }), + required_permission: PermissionMode::DangerFullAccess, + }, + ToolSpec { + name: "TaskGet", + description: "Get the status and details of a background task by ID.", + input_schema: json!({ + "type": "object", + "properties": { + "task_id": { "type": "string" } + }, + "required": ["task_id"], + "additionalProperties": false + }), + required_permission: PermissionMode::ReadOnly, + }, + ToolSpec { + name: "TaskList", + description: "List all background tasks and their current status.", + input_schema: json!({ + "type": "object", + "properties": {}, + "additionalProperties": false + }), + required_permission: PermissionMode::ReadOnly, + }, + ToolSpec { + name: "TaskStop", + description: "Stop a running background task by ID.", + input_schema: json!({ + "type": "object", + "properties": { + "task_id": { "type": "string" } + }, + "required": ["task_id"], + "additionalProperties": false + }), + required_permission: PermissionMode::DangerFullAccess, + }, + ToolSpec { + name: "TaskUpdate", + description: "Send a message or update to a running background task.", + input_schema: json!({ + "type": "object", + "properties": { + "task_id": { "type": "string" }, + "message": { "type": "string" } + }, + "required": ["task_id", "message"], + "additionalProperties": false + }), + required_permission: PermissionMode::DangerFullAccess, + }, + ToolSpec { + name: "TaskOutput", + description: "Retrieve the output produced by a background task.", + input_schema: json!({ + "type": "object", + "properties": { + "task_id": { "type": "string" } + }, + "required": ["task_id"], + "additionalProperties": false + }), + required_permission: PermissionMode::ReadOnly, + }, ] } @@ -592,10 +686,90 @@ pub fn execute_tool(name: &str, input: &Value) -> Result { } "REPL" => from_value::(input).and_then(run_repl), "PowerShell" => from_value::(input).and_then(run_powershell), + "AskUserQuestion" => { + from_value::(input).and_then(run_ask_user_question) + } + "TaskCreate" => from_value::(input).and_then(run_task_create), + "TaskGet" => from_value::(input).and_then(run_task_get), + "TaskList" => run_task_list(input.clone()), + "TaskStop" => from_value::(input).and_then(run_task_stop), + "TaskUpdate" => from_value::(input).and_then(run_task_update), + "TaskOutput" => from_value::(input).and_then(run_task_output), _ => Err(format!("unsupported tool: {name}")), } } +#[allow(clippy::needless_pass_by_value)] +fn run_ask_user_question(input: AskUserQuestionInput) -> Result { + let mut result = json!({ + "question": input.question, + "status": "pending", + "message": "Waiting for user response" + }); + if let Some(options) = &input.options { + result["options"] = json!(options); + } + to_pretty_json(result) +} + +#[allow(clippy::needless_pass_by_value)] +fn run_task_create(input: TaskCreateInput) -> Result { + let secs = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + let task_id = format!("task_{secs:08x}"); + to_pretty_json(json!({ + "task_id": task_id, + "status": "created", + "prompt": input.prompt, + "description": input.description + })) +} + +#[allow(clippy::needless_pass_by_value)] +fn run_task_get(input: TaskIdInput) -> Result { + to_pretty_json(json!({ + "task_id": input.task_id, + "status": "unknown", + "message": "Task runtime not yet implemented" + })) +} + +fn run_task_list(_input: Value) -> Result { + to_pretty_json(json!({ + "tasks": [], + "message": "No tasks found" + })) +} + +#[allow(clippy::needless_pass_by_value)] +fn run_task_stop(input: TaskIdInput) -> Result { + to_pretty_json(json!({ + "task_id": input.task_id, + "status": "stopped", + "message": "Task stop requested" + })) +} + +#[allow(clippy::needless_pass_by_value)] +fn run_task_update(input: TaskUpdateInput) -> Result { + to_pretty_json(json!({ + "task_id": input.task_id, + "status": "updated", + "message": input.message + })) +} + +#[allow(clippy::needless_pass_by_value)] +fn run_task_output(input: TaskIdInput) -> Result { + to_pretty_json(json!({ + "task_id": input.task_id, + "output": "", + "message": "No output available" + })) +} + fn from_value Deserialize<'de>>(input: &Value) -> Result { serde_json::from_value(input.clone()).map_err(|error| error.to_string()) } @@ -875,6 +1049,31 @@ struct PowerShellInput { run_in_background: Option, } +#[derive(Debug, Deserialize)] +struct AskUserQuestionInput { + question: String, + #[serde(default)] + options: Option>, +} + +#[derive(Debug, Deserialize)] +struct TaskCreateInput { + prompt: String, + #[serde(default)] + description: Option, +} + +#[derive(Debug, Deserialize)] +struct TaskIdInput { + task_id: String, +} + +#[derive(Debug, Deserialize)] +struct TaskUpdateInput { + task_id: String, + message: String, +} + #[derive(Debug, Serialize)] struct WebFetchOutput { bytes: usize,