mirror of
https://github.com/mudler/cogito.git
synced 2026-07-23 10:25:44 -04:00
f195b2553f
* feat(todo): add todo-based goal execution similar to cursor agent mode Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * reinforce tests Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * Bump timeout for tests Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat: guided tools Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
187 lines
4.6 KiB
Go
187 lines
4.6 KiB
Go
package structures
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/sashabaranov/go-openai/jsonschema"
|
|
)
|
|
|
|
type TODO struct {
|
|
ID string `json:"id"`
|
|
Description string `json:"description"`
|
|
Completed bool `json:"completed"`
|
|
Feedback string `json:"feedback,omitempty"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type TODOList struct {
|
|
TODOs []TODO `json:"todos"`
|
|
Markdown string `json:"markdown"`
|
|
LastUpdated time.Time `json:"last_updated"`
|
|
}
|
|
|
|
func StructureTODO() (Structure, *TODOList) {
|
|
return structureType[TODOList](jsonschema.Definition{
|
|
Type: jsonschema.Object,
|
|
AdditionalProperties: false,
|
|
Properties: map[string]jsonschema.Definition{
|
|
"todos": {
|
|
Type: jsonschema.Array,
|
|
Description: "List of TODO items",
|
|
Items: &jsonschema.Definition{
|
|
Type: jsonschema.Object,
|
|
Properties: map[string]jsonschema.Definition{
|
|
"id": {
|
|
Type: jsonschema.String,
|
|
Description: "Unique identifier for the TODO",
|
|
},
|
|
"description": {
|
|
Type: jsonschema.String,
|
|
Description: "Description of the TODO item",
|
|
},
|
|
"completed": {
|
|
Type: jsonschema.Boolean,
|
|
Description: "Whether the TODO is completed",
|
|
},
|
|
"feedback": {
|
|
Type: jsonschema.String,
|
|
Description: "Optional feedback for this TODO",
|
|
},
|
|
"metadata": {
|
|
Type: jsonschema.Object,
|
|
Description: "Optional metadata for this TODO",
|
|
},
|
|
},
|
|
Required: []string{"id", "description", "completed"},
|
|
},
|
|
},
|
|
},
|
|
Required: []string{"todos"},
|
|
})
|
|
}
|
|
|
|
// ToMarkdown generates markdown representation with checkboxes
|
|
func (tl *TODOList) ToMarkdown() string {
|
|
var builder strings.Builder
|
|
for _, todo := range tl.TODOs {
|
|
if todo.Completed {
|
|
builder.WriteString(fmt.Sprintf("- [x] %s\n", todo.Description))
|
|
} else {
|
|
builder.WriteString(fmt.Sprintf("- [ ] %s\n", todo.Description))
|
|
}
|
|
if todo.Feedback != "" {
|
|
builder.WriteString(fmt.Sprintf(" Feedback: %s\n", todo.Feedback))
|
|
}
|
|
}
|
|
tl.Markdown = builder.String()
|
|
return tl.Markdown
|
|
}
|
|
|
|
// FromMarkdown parses markdown checkboxes into TODO list
|
|
func (tl *TODOList) FromMarkdown(markdown string) error {
|
|
lines := strings.Split(markdown, "\n")
|
|
tl.TODOs = []TODO{}
|
|
|
|
for i, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
// Check for checkbox pattern: - [x] or - [ ]
|
|
if strings.HasPrefix(line, "- [") {
|
|
completed := strings.Contains(line, "- [x]") || strings.Contains(line, "- [X]")
|
|
description := ""
|
|
|
|
if completed {
|
|
description = strings.TrimPrefix(line, "- [x]")
|
|
description = strings.TrimPrefix(description, "- [X]")
|
|
} else {
|
|
description = strings.TrimPrefix(line, "- [ ]")
|
|
}
|
|
description = strings.TrimSpace(description)
|
|
|
|
// Check if next line is feedback
|
|
feedback := ""
|
|
if i+1 < len(lines) {
|
|
nextLine := strings.TrimSpace(lines[i+1])
|
|
if strings.HasPrefix(nextLine, "Feedback:") {
|
|
feedback = strings.TrimPrefix(nextLine, "Feedback:")
|
|
feedback = strings.TrimSpace(feedback)
|
|
}
|
|
}
|
|
|
|
todo := TODO{
|
|
ID: fmt.Sprintf("%d", len(tl.TODOs)+1),
|
|
Description: description,
|
|
Completed: completed,
|
|
Feedback: feedback,
|
|
}
|
|
tl.TODOs = append(tl.TODOs, todo)
|
|
}
|
|
}
|
|
|
|
tl.Markdown = markdown
|
|
tl.LastUpdated = time.Now()
|
|
return nil
|
|
}
|
|
|
|
// ToJSON serializes TODOList to JSON
|
|
func (tl *TODOList) ToJSON() ([]byte, error) {
|
|
return json.Marshal(tl)
|
|
}
|
|
|
|
// FromJSON deserializes JSON to TODOList
|
|
func (tl *TODOList) FromJSON(data []byte) error {
|
|
return json.Unmarshal(data, tl)
|
|
}
|
|
|
|
// MarkComplete marks a TODO as completed by ID
|
|
func (tl *TODOList) MarkComplete(id string) {
|
|
for i := range tl.TODOs {
|
|
if tl.TODOs[i].ID == id {
|
|
tl.TODOs[i].Completed = true
|
|
tl.LastUpdated = time.Now()
|
|
tl.ToMarkdown()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddFeedback adds feedback to a TODO by ID
|
|
func (tl *TODOList) AddFeedback(id, feedback string) {
|
|
for i := range tl.TODOs {
|
|
if tl.TODOs[i].ID == id {
|
|
tl.TODOs[i].Feedback = feedback
|
|
tl.LastUpdated = time.Now()
|
|
tl.ToMarkdown()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetIncompleteTODOs returns all incomplete TODOs
|
|
func (tl *TODOList) GetIncompleteTODOs() []TODO {
|
|
var incomplete []TODO
|
|
for _, todo := range tl.TODOs {
|
|
if !todo.Completed {
|
|
incomplete = append(incomplete, todo)
|
|
}
|
|
}
|
|
return incomplete
|
|
}
|
|
|
|
// GetCompletedTODOs returns all completed TODOs
|
|
func (tl *TODOList) GetCompletedTODOs() []TODO {
|
|
var completed []TODO
|
|
for _, todo := range tl.TODOs {
|
|
if todo.Completed {
|
|
completed = append(completed, todo)
|
|
}
|
|
}
|
|
return completed
|
|
}
|