nub/internal/tool/builtin/todo.go
Tom a97013d876 initial commit
- v 0.1.0 siehe CHANGELOG.md
2026-07-25 11:08:01 +02:00

97 lines
2.2 KiB
Go

package builtin
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"nub/internal/tool"
)
// TodoTool hält eine In-Memory-Todo-Liste im Session-State (5.3) — nicht im
// Baum persistiert. Zustand lebt im Tool selbst, deshalb Pointer-Registrierung
// (&TodoTool{}) statt eines Werttyps.
type TodoTool struct {
mu sync.Mutex
items []TodoItem
}
type TodoItem struct {
Content string `json:"content"`
Status string `json:"status"` // pending | in_progress | completed
}
func (*TodoTool) Name() string { return "todo" }
func (*TodoTool) Description() string {
return "Ersetzt die aktuelle Todo-Liste. Für lange Tasks: Fortschritt sichtbar halten."
}
func (*TodoTool) Schema() json.RawMessage {
return json.RawMessage(`{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"content": {"type": "string"},
"status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}
},
"required": ["content", "status"]
}
}
},
"required": ["items"]
}`)
}
type todoInput struct {
Items []TodoItem `json:"items"`
}
func (t *TodoTool) Run(ctx context.Context, input json.RawMessage, env tool.Env) (tool.Result, error) {
var in todoInput
if err := json.Unmarshal(input, &in); err != nil {
return tool.Result{ForModel: "invalid input: " + err.Error(), IsError: true}, nil
}
for _, it := range in.Items {
switch it.Status {
case "pending", "in_progress", "completed":
default:
return tool.Result{ForModel: fmt.Sprintf("invalid status %q", it.Status), IsError: true}, nil
}
}
t.mu.Lock()
t.items = in.Items
items := make([]TodoItem, len(t.items))
copy(items, t.items)
t.mu.Unlock()
return tool.Result{ForModel: renderTodos(items), ForUI: TodoResult{Items: items}}, nil
}
type TodoResult struct {
Items []TodoItem
}
func renderTodos(items []TodoItem) string {
if len(items) == 0 {
return "todo list is empty"
}
var b strings.Builder
for _, it := range items {
mark := " "
switch it.Status {
case "in_progress":
mark = "~"
case "completed":
mark = "x"
}
fmt.Fprintf(&b, "[%s] %s\n", mark, it.Content)
}
return b.String()
}