97 lines
2.2 KiB
Go
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()
|
|
}
|