142 lines
3.1 KiB
Go
142 lines
3.1 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type intentLog struct {
|
||
|
|
Path string `json:"path"`
|
||
|
|
Pos int64 `json:"pos"`
|
||
|
|
Fence int64 `json:"fence"`
|
||
|
|
Partial bool `json:"partial,omitempty"`
|
||
|
|
Sealed bool `json:"sealed,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func openIntent(path string) (*intentLog, error) {
|
||
|
|
f, err := os.Open(path)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer func() { _ = f.Close() }()
|
||
|
|
info, err := f.Stat()
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
log := &intentLog{Path: path, Pos: info.Size(), Fence: info.Size()}
|
||
|
|
if info.Size() != 0 {
|
||
|
|
var last [1]byte
|
||
|
|
if _, err := f.ReadAt(last[:], info.Size()-1); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
log.Partial = last[0] != '\n'
|
||
|
|
}
|
||
|
|
return log, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *intentLog) end() (int64, error) {
|
||
|
|
info, err := os.Stat(l.Path)
|
||
|
|
if err != nil {
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
return info.Size(), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *intentLog) seal() error {
|
||
|
|
l.Sealed = true
|
||
|
|
info, err := os.Stat(l.Path)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
l.Fence = info.Size()
|
||
|
|
l.Sealed = false
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *intentLog) read(end int64) (goalStatus, error) {
|
||
|
|
if l.Sealed {
|
||
|
|
if err := l.seal(); err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
f, err := os.Open(l.Path)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
defer func() { _ = f.Close() }()
|
||
|
|
reader := bufio.NewReader(io.NewSectionReader(f, l.Pos, end-l.Pos))
|
||
|
|
var latest goalStatus
|
||
|
|
for {
|
||
|
|
line, err := reader.ReadBytes('\n')
|
||
|
|
if errors.Is(err, io.EOF) {
|
||
|
|
return latest, nil
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
next := l.Pos + int64(len(line))
|
||
|
|
if l.Partial {
|
||
|
|
l.Partial = false
|
||
|
|
l.Pos = next
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if l.Pos < l.Fence {
|
||
|
|
l.Pos = next
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
status, match, err := parseIntent(line)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
l.Pos = next
|
||
|
|
if match {
|
||
|
|
latest = status
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseIntent(line []byte) (goalStatus, bool, error) {
|
||
|
|
var record struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
Payload struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
Role string `json:"role"`
|
||
|
|
Content []struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
Text string `json:"text"`
|
||
|
|
} `json:"content"`
|
||
|
|
Meta struct {
|
||
|
|
Kinds []string `json:"content_item_kinds"`
|
||
|
|
} `json:"internal_chat_message_metadata_passthrough"`
|
||
|
|
} `json:"payload"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(line, &record); err != nil {
|
||
|
|
return "", false, err
|
||
|
|
}
|
||
|
|
p := record.Payload
|
||
|
|
if record.Type != "response_item" || p.Type != "message" || p.Role != "user" || len(p.Content) != 1 ||
|
||
|
|
p.Content[0].Type != "input_text" || len(p.Meta.Kinds) != 1 ||
|
||
|
|
p.Meta.Kinds[0] != "user.goal" && p.Meta.Kinds[0] != "user.goal.omitted" {
|
||
|
|
return "", false, nil
|
||
|
|
}
|
||
|
|
for _, line := range strings.Split(p.Content[0].Text, "\n") {
|
||
|
|
if line == "User cleared the goal." {
|
||
|
|
return "cleared", true, nil
|
||
|
|
}
|
||
|
|
if value, ok := strings.CutPrefix(line, "User set goal status: "); ok {
|
||
|
|
var status goalStatus
|
||
|
|
if err := json.Unmarshal([]byte(strings.TrimSuffix(value, ".")), &status); err != nil {
|
||
|
|
return "", false, err
|
||
|
|
}
|
||
|
|
switch status {
|
||
|
|
case active, paused, blocked, "complete", "budgetLimited", "usageLimited":
|
||
|
|
return status, true, nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return "", true, nil
|
||
|
|
}
|