use async_trait::async_trait;
use serde::Deserialize;
use serde_json::{Value, json};
use super::base::Tool;
use crate::mcp::registry::{ToolContext, ToolResult};
#[derive(Debug, Deserialize)]
struct TaskOutputInput {
task_id: String,
#[serde(default = "default_block")]
block: bool,
#[serde(default = "default_timeout")]
timeout: u64,
}
fn default_block() -> bool {
true
}
fn default_timeout() -> u64 {
30000
}
#[derive(Debug, Default)]
pub struct TaskOutputTool;
impl TaskOutputTool {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Tool for TaskOutputTool {
fn name(&self) -> &str {
"TaskOutput"
}
fn description(&self) -> &str {
"Retrieves output from a running or completed task (background shell, agent, or remote session). \
Takes a task_id parameter identifying the task. Returns the task output along with status information. \
Use block=true (default) to wait for task completion. Use block=false for non-blocking check of current status."
}
fn input_schema(&self) -> Value {
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["task_id"],
"additionalProperties": false,
"properties": {
"task_id": {
"type": "string",
"description": "The task ID to get output from"
},
"block": {
"type": "boolean",
"default": true,
"description": "Whether to wait for completion"
},
"timeout": {
"type": "number",
"default": 30000,
"minimum": 0,
"maximum": 600_000,
"description": "Max wait time in ms"
}
}
})
}
async fn execute(&self, input: Value, context: &ToolContext) -> ToolResult {
let params: TaskOutputInput = match serde_json::from_value(input) {
Ok(p) => p,
Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
};
if params.timeout > 600_000 {
return ToolResult::error("Timeout cannot exceed 600000ms (10 minutes)");
}
tracing::info!(
"TaskOutput request for task: {} (session: {})",
params.task_id,
context.session_id
);
let mut output = format!("Task output for: {}\n\n", params.task_id);
output.push_str(&format!("Blocking: {}\n", params.block));
output.push_str(&format!("Timeout: {}ms\n", params.timeout));
output.push_str("\nStatus: Task not found\n");
output.push_str(
"\nNote: TaskOutput tool requires task registry integration for full functionality. \
Use the /tasks command to see available task IDs.",
);
ToolResult::success(output).with_metadata(json!({
"task_id": params.task_id,
"status": "not_found",
"block": params.block,
"timeout": params.timeout
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_task_output_properties() {
let tool = TaskOutputTool::new();
assert_eq!(tool.name(), "TaskOutput");
assert!(tool.description().contains("task"));
assert!(tool.description().contains("output"));
}
#[test]
fn test_task_output_input_schema() {
let tool = TaskOutputTool::new();
let schema = tool.input_schema();
assert_eq!(schema["type"], "object");
assert!(schema["properties"]["task_id"].is_object());
assert!(schema["properties"]["block"].is_object());
assert!(schema["properties"]["timeout"].is_object());
let required = schema["required"].as_array().unwrap();
assert!(required.contains(&json!("task_id")));
}
#[tokio::test]
async fn test_task_output_execute() {
let temp_dir = TempDir::new().unwrap();
let tool = TaskOutputTool::new();
let context = ToolContext::new("test-session", temp_dir.path());
let result = tool
.execute(
json!({
"task_id": "test-task-123"
}),
&context,
)
.await;
assert!(!result.is_error);
assert!(result.content.contains("test-task-123"));
assert!(result.content.contains("not found"));
}
#[tokio::test]
async fn test_task_output_non_blocking() {
let temp_dir = TempDir::new().unwrap();
let tool = TaskOutputTool::new();
let context = ToolContext::new("test-session", temp_dir.path());
let result = tool
.execute(
json!({
"task_id": "test-task-456",
"block": false
}),
&context,
)
.await;
assert!(!result.is_error);
assert!(result.content.contains("Blocking: false"));
}
#[tokio::test]
async fn test_task_output_with_timeout() {
let temp_dir = TempDir::new().unwrap();
let tool = TaskOutputTool::new();
let context = ToolContext::new("test-session", temp_dir.path());
let result = tool
.execute(
json!({
"task_id": "test-task-789",
"timeout": 60000
}),
&context,
)
.await;
assert!(!result.is_error);
assert!(result.content.contains("Timeout: 60000ms"));
}
#[tokio::test]
async fn test_task_output_timeout_too_large() {
let temp_dir = TempDir::new().unwrap();
let tool = TaskOutputTool::new();
let context = ToolContext::new("test-session", temp_dir.path());
let result = tool
.execute(
json!({
"task_id": "test-task",
"timeout": 999_999
}),
&context,
)
.await;
assert!(result.is_error);
assert!(result.content.contains("600000ms"));
}
}