package daemon
import (
"regexp"
"strings"
"unicode/utf8"
)
var (
whitespaceRegex = regexp.MustCompile(`\s+`)
assignStrRegex = regexp.MustCompile(`^([a-zA-Z_]\w*)\s*=\s*[rR]?['"]([^'"]*)['"]`)
assignPathRegex = regexp.MustCompile(`^([a-zA-Z_]\w*)\s*=\s*(?:pathlib\.)?Path\(\s*[rR]?['"]([^'"]*)['"]\s*\)`)
pathMethodRegex = regexp.MustCompile(`(?:(?:pathlib\.)?Path\(\s*[rR]?['"]([^'"]*)['"]\s*\)|([a-zA-Z_]\w*))\.(write_text|write_bytes|read_text|read_bytes|open)\(\s*([^)]*)\)`)
funcCallRegex = regexp.MustCompile(`\b(edit|read|open)\(\s*([^)]*)\)`)
// run(program, *args) and rem.run(...), but not cells.run(id)
runCallRegex = regexp.MustCompile(`(?:^|[^.\w]|\brem\.)run\(([^)]*)`)
runWordRegex = regexp.MustCompile(`^\s*[rR]?['"]([^'"]*)['"]\s*(?:,|$)`)
argExtractRegex = regexp.MustCompile(`(?:(?:path|file)\s*=\s*)?(?:[rR]?['"]([^'"]*)['"]|([a-zA-Z_]\w*))`)
modeRegex = regexp.MustCompile(`['"]([rwaxbt+]+)['"]`)
)
func cleanLabel(s string) string {
s = sanitizeControlRunes(s)
s = whitespaceRegex.ReplaceAllString(s, " ")
s = strings.TrimSpace(s)
if len(s) > 300 {
return s[:300]
}
return s
}
func ParsePythonIntent(code string) *ToolIntent {
bindings := make(map[string]string)
var intents []ToolIntent
for rawLine := range strings.SplitSeq(code, "\n") {
line := strings.TrimSpace(rawLine)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if m := assignStrRegex.FindStringSubmatch(line); m != nil {
bindings[m[1]] = m[2]
continue
}
if m := assignPathRegex.FindStringSubmatch(line); m != nil {
bindings[m[1]] = m[2]
continue
}
if m := pathMethodRegex.FindStringSubmatch(line); m != nil {
pathLiteral := m[1]
pathVariable := m[2]
method := m[3]
args := m[4]
target := pathLiteral
if target == "" && pathVariable != "" {
target = bindings[pathVariable]
}
if target != "" {
label := cleanLabel(target)
switch method {
case "write_text", "write_bytes":
intents = append(intents, ToolIntent{Kind: "write", Target: label})
case "read_text", "read_bytes":
intents = append(intents, ToolIntent{Kind: "read", Target: label})
case "open":
intents = append(intents, ToolIntent{Kind: openIntentKind(args), Target: label})
}
continue
}
}
if m := runCallRegex.FindStringSubmatch(line); m != nil {
if words := runWords(m[1]); len(words) > 0 {
intents = append(intents, ToolIntent{Kind: "run", Target: cleanLabel(strings.Join(words, " "))})
continue
}
}
if m := funcCallRegex.FindStringSubmatch(line); m != nil {
functionName := m[1]
args := m[2]
if am := argExtractRegex.FindStringSubmatch(args); am != nil {
literalValue := am[1]
variableName := am[2]
target := literalValue
if target == "" && variableName != "" {
target = bindings[variableName]
}
if target != "" {
label := cleanLabel(target)
switch functionName {
case "edit":
intents = append(intents, ToolIntent{Kind: "edit", Target: label})
case "read":
intents = append(intents, ToolIntent{Kind: "read", Target: label})
case "open":
intents = append(intents, ToolIntent{Kind: openIntentKind(args), Target: label})
}
}
}
}
}
if len(intents) == 0 {
return nil
}
return &intents[len(intents)-1]
}
func openIntentKind(args string) string {
mode := "r"
if match := modeRegex.FindStringSubmatch(args); match != nil {
mode = match[1]
}
if strings.ContainsAny(mode, "wax+") {
return "write"
}
return "read"
}
// runWords is the leading string literals of run()'s arguments: the program
// and its arguments, up to the first expression or keyword.
func runWords(args string) []string {
var words []string
for {
m := runWordRegex.FindStringSubmatchIndex(args)
if m == nil {
return words
}
words = append(words, args[m[2]:m[3]])
args = args[m[1]:]
}
}
func ExtractPartialJSONCode(s string) string {
codeKeyIndex := strings.Index(s, `"code"`)
if codeKeyIndex == -1 {
return ""
}
colon := strings.Index(s[codeKeyIndex+6:], ":")
if colon == -1 {
return ""
}
colon += codeKeyIndex + 6
quote := strings.Index(s[colon+1:], `"`)
if quote == -1 {
return ""
}
start := colon + 1 + quote + 1
var decodedCode strings.Builder
var decoder JSONStringDecoder
decoder.Append(s[start:], func(text string) { decodedCode.WriteString(text) })
return decodedCode.String()
}
type ToolCallAssembly struct {
ID string
Function struct {
Name string
Arguments string
}
}
type ToolProgressReporter struct {
publish func(*ToolProgress) error
previous *ToolProgress
raw string
code string
}
func NewToolProgressReporter(publish func(*ToolProgress) error) *ToolProgressReporter {
return &ToolProgressReporter{
publish: publish,
}
}
func (r *ToolProgressReporter) Reset() {
r.raw = ""
r.code = ""
}
func (r *ToolProgressReporter) Report(call *ToolCallAssembly, phase string) error {
if call == nil {
if r.previous != nil {
err := r.publish(nil)
r.previous = nil
r.Reset()
return err
}
r.Reset()
return nil
}
name := cleanLabel(call.Function.Name)
if len(name) > 100 {
name = name[:100]
}
if name == "" {
return nil
}
callID := call.ID
if len(callID) > 200 {
callID = callID[:200]
}
args := call.Function.Arguments
sameCall := r.previous != nil && r.previous.CallID == callID && r.previous.Name == name
continued := sameCall && strings.HasPrefix(args, r.raw)
if !continued {
r.Reset()
}
if name == "python" && len(args) > len(r.raw) {
r.code = ExtractPartialJSONCode(args)
r.raw = args
}
var intent *ToolIntent
if continued && r.previous != nil {
intent = r.previous.Intent
}
if parsed := ParsePythonIntent(r.code); parsed != nil {
intent = parsed
}
var preview *ToolCodePreview
if phase == "generating" && len(r.code) > 0 {
runeCount := utf8.RuneCountInString(r.code)
offset := max(0, runeCount-512)
start := 0
if offset > 0 {
start = len(r.code)
for range 512 {
_, width := utf8.DecodeLastRuneInString(r.code[:start])
start -= width
}
}
text := sanitizeControlRunes(r.code[start:])
preview = &ToolCodePreview{
Offset: offset,
Text: text,
}
}
next := &ToolProgress{
CallID: callID,
Name: name,
Phase: phase,
Intent: intent,
Code: preview,
}
if !toolProgressEqual(next, r.previous) {
if err := r.publish(next); err != nil {
return err
}
r.previous = next
}
return nil
}
func toolProgressEqual(a, b *ToolProgress) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
if a.CallID != b.CallID || a.Name != b.Name || a.Phase != b.Phase {
return false
}
if (a.Intent == nil) != (b.Intent == nil) {
return false
}
if a.Intent != nil && *a.Intent != *b.Intent {
return false
}
if (a.Code == nil) != (b.Code == nil) {
return false
}
if a.Code != nil && *a.Code != *b.Code {
return false
}
return true
}