feat: complete v0.3.1 agent workflow
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user