use crate::types::*;
use crate::utils::task_list::{get_task, get_task_list_id, is_todo_v2_enabled, Task, TaskStatus};
use super::constants::TASK_GET_TOOL_NAME;
use super::prompt::{DESCRIPTION, PROMPT};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TaskGetOutput {
pub task: Option<TaskInfo>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TaskInfo {
pub id: String,
pub subject: String,
pub description: String,
pub status: String,
pub blocks: Vec<String>,
pub blocked_by: Vec<String>,
}
pub struct TaskGetTool;
impl TaskGetTool {
pub fn new() -> Self {
Self
}
pub fn name(&self) -> &str {
TASK_GET_TOOL_NAME
}
pub fn description(&self) -> &str {
DESCRIPTION
}
pub fn prompt(&self) -> &str {
PROMPT
}
pub fn input_schema(&self) -> ToolInputSchema {
ToolInputSchema {
schema_type: "object".to_string(),
properties: serde_json::json!({
"taskId": {
"type": "string",
"description": "The ID of the task to retrieve"
}
}),
required: Some(vec!["taskId".to_string()]),
}
}
pub fn is_enabled(&self) -> bool {
is_todo_v2_enabled()
}
pub fn is_concurrency_safe(&self) -> bool {
true
}
pub fn is_read_only(&self) -> bool {
true
}
pub fn should_defer(&self) -> bool {
true
}
pub async fn execute(
&self,
input: serde_json::Value,
_context: &ToolContext,
) -> Result<ToolResult, crate::error::AgentError> {
let task_id = input["taskId"].as_str().ok_or_else(|| {
crate::error::AgentError::Tool("Missing taskId parameter".to_string())
})?;
let task_list_id = get_task_list_id();
let task = get_task(&task_list_id, task_id)
.await
.map_err(|e| crate::error::AgentError::Tool(e))?;
let output = TaskGetOutput {
task: task.map(|t| TaskInfo {
id: t.id,
subject: t.subject,
description: t.description,
status: t.status.to_string(),
blocks: t.blocks,
blocked_by: t.blocked_by,
}),
};
let content = serde_json::to_string(&output)
.unwrap_or_else(|_| "Failed to serialize task".to_string());
Ok(ToolResult {
result_type: "text".to_string(),
tool_use_id: "task_get".to_string(),
content,
is_error: Some(false),
was_persisted: None,
})
}
pub fn format_result(content: &serde_json::Value, tool_use_id: &str) -> String {
if let Some(task) = content.get("task") {
if task.is_null() {
return "Task not found".to_string();
}
if let Some(task_obj) = task.as_object() {
let id = task_obj.get("id").and_then(|v| v.as_str()).unwrap_or("");
let subject = task_obj
.get("subject")
.and_then(|v| v.as_str())
.unwrap_or("");
let status = task_obj
.get("status")
.and_then(|v| v.as_str())
.unwrap_or("");
let description = task_obj
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("");
let mut lines = vec![
format!("Task #{id}: {subject}"),
format!("Status: {status}"),
format!("Description: {description}"),
];
if let Some(blocked_by) = task_obj.get("blockedBy").and_then(|v| v.as_array()) {
if !blocked_by.is_empty() {
let ids: Vec<String> = blocked_by
.iter()
.filter_map(|v| v.as_str().map(|s| format!("#{s}")))
.collect();
lines.push(format!("Blocked by: {}", ids.join(", ")));
}
}
if let Some(blocks) = task_obj.get("blocks").and_then(|v| v.as_array()) {
if !blocks.is_empty() {
let ids: Vec<String> = blocks
.iter()
.filter_map(|v| v.as_str().map(|s| format!("#{s}")))
.collect();
lines.push(format!("Blocks: {}", ids.join(", ")));
}
}
return lines.join("\n");
}
}
format!("Failed to parse task result for tool {tool_use_id}")
}
}
impl Default for TaskGetTool {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_task_get_tool_name() {
let tool = TaskGetTool::new();
assert_eq!(tool.name(), TASK_GET_TOOL_NAME);
}
#[test]
fn test_task_get_tool_schema() {
let tool = TaskGetTool::new();
let schema = tool.input_schema();
assert!(schema.properties.get("taskId").is_some());
assert_eq!(schema.required, Some(vec!["taskId".to_string()]));
}
#[test]
fn test_task_get_tool_is_read_only() {
let tool = TaskGetTool::new();
assert!(tool.is_read_only());
}
#[test]
fn test_task_get_tool_is_concurrency_safe() {
let tool = TaskGetTool::new();
assert!(tool.is_concurrency_safe());
}
#[test]
fn test_task_get_tool_should_defer() {
let tool = TaskGetTool::new();
assert!(tool.should_defer());
}
#[test]
fn test_task_get_format_result_null() {
let result = serde_json::json!({ "task": null });
let formatted = TaskGetTool::format_result(&result, "test-id");
assert_eq!(formatted, "Task not found");
}
#[test]
fn test_task_get_format_result() {
let result = serde_json::json!({
"task": {
"id": "1",
"subject": "Test task",
"description": "Do something",
"status": "pending",
"blocks": ["2", "3"],
"blockedBy": ["0"]
}
});
let formatted = TaskGetTool::format_result(&result, "test-id");
assert!(formatted.contains("Task #1: Test task"));
assert!(formatted.contains("Status: pending"));
assert!(formatted.contains("Blocked by: #0"));
assert!(formatted.contains("Blocks: #2, #3"));
}
}