package tools import ( "context" "encoding/json" "fmt" "strings" "sync" "agentu/pkg/llm" ) // ──────────────────────────────────────────────────────────────────────────── // TodoTool — in-memory task tracking // ──────────────────────────────────────────────────────────────────────────── type todoItem struct { Content string `json:"content"` Status string `json:"status"` // "pending", "in_progress", "completed" } type TodoTool struct { mu sync.Mutex items []todoItem } func NewTodoTool() *TodoTool { return &TodoTool{} } func (t *TodoTool) Definition() llm.Tool { return llm.Tool{ Type: "function", Function: llm.ToolFunction{ Name: "todo", Description: "Manage a task list for the current conversation. Create, update, and track multi-step work. Use this to plan complex tasks and show progress.", Parameters: JSONSchema(`{ "type": "object", "properties": { "action": {"type": "string", "enum": ["list", "create", "update", "start", "complete", "clear"], "description": "Operation to perform."}, "items": {"type": "array", "items": {"type": "string"}, "description": "For 'create': list of task descriptions to add."}, "task": {"type": "string", "description": "For 'update'/'start'/'complete': the exact task content string to target."}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"], "description": "For 'update': new status for the task."} }, "required": ["action"], "additionalProperties": false }`), }, } } type todoArgs struct { Action string `json:"action"` Items []string `json:"items"` Task string `json:"task"` Status string `json:"status"` } func (t *TodoTool) Execute(ctx context.Context, raw json.RawMessage) (string, error) { var args todoArgs if err := json.Unmarshal(raw, &args); err != nil { return "", fmt.Errorf("parse arguments: %w", err) } t.mu.Lock() defer t.mu.Unlock() if err := ctx.Err(); err != nil { return "", err } switch args.Action { case "list": return t.listTasks(), nil case "create": return t.createTasks(args.Items) case "start": return t.startTask(args.Task) case "complete": return t.completeTask(args.Task) case "update": return t.updateTask(args.Task, args.Status) case "clear": t.items = nil return "Tasks cleared.", nil default: return "", fmt.Errorf("unknown todo action: %s", args.Action) } } func (t *TodoTool) listTasks() string { if len(t.items) == 0 { return "No tasks." } var b strings.Builder b.WriteString("Tasks:\n") for _, item := range t.items { fmt.Fprintf(&b, "[%s] %s\n", item.Status, item.Content) } return b.String() } func (t *TodoTool) createTasks(items []string) (string, error) { if len(items) == 0 { return "", fmt.Errorf("items is required for create") } for _, content := range items { content = strings.TrimSpace(content) if content == "" { return "", fmt.Errorf("task content must not be empty") } t.items = append(t.items, todoItem{ Content: content, Status: "pending", }) } return t.listTasks(), nil } func (t *TodoTool) startTask(task string) (string, error) { idx := -1 for i, item := range t.items { if item.Content == task { idx = i break } } if idx < 0 { return "", fmt.Errorf("task not found: %s", task) } // Set all in_progress items to pending, then mark the target as in_progress. for i := range t.items { if t.items[i].Status == "in_progress" { t.items[i].Status = "pending" } } t.items[idx].Status = "in_progress" return t.listTasks(), nil } func (t *TodoTool) completeTask(task string) (string, error) { idx := -1 for i, item := range t.items { if item.Content == task { idx = i break } } if idx < 0 { return "", fmt.Errorf("task not found: %s", task) } t.items[idx].Status = "completed" return t.listTasks(), nil } func (t *TodoTool) updateTask(task, status string) (string, error) { if status != "pending" && status != "in_progress" && status != "completed" { return "", fmt.Errorf("status must be one of: pending, in_progress, completed") } idx := -1 for i, item := range t.items { if item.Content == task { idx = i break } } if idx < 0 { return "", fmt.Errorf("task not found: %s", task) } t.items[idx].Status = status return t.listTasks(), nil }