agent/pkg/loop/parse.go

50 lines
1.3 KiB
Go
Raw Normal View History

package loop
import (
"encoding/json"
"regexp"
"strings"
)
var toolBlockRe = regexp.MustCompile("(?s)```tool\\s*\n(.*?)\\s*```")
// ParseToolCalls extracts tool invocations from fenced ```tool blocks in
// model output. Only blocks tagged "tool" are matched; other fenced blocks
// (```go, ```json, etc.) pass through untouched. Malformed JSON is silently
// skipped. Returns the parsed calls and the cleaned text with tool blocks
// removed.
func ParseToolCalls(output string) ([]ToolUse, string) {
matches := toolBlockRe.FindAllStringSubmatchIndex(output, -1)
if len(matches) == 0 {
return nil, output
}
var calls []ToolUse
cleaned := output
// Walk matches in reverse so index arithmetic stays valid after each splice.
for i := len(matches) - 1; i >= 0; i-- {
m := matches[i]
fullStart, fullEnd := m[0], m[1]
bodyStart, bodyEnd := m[2], m[3]
body := strings.TrimSpace(output[bodyStart:bodyEnd])
if body == "" {
cleaned = cleaned[:fullStart] + cleaned[fullEnd:]
continue
}
var call ToolUse
if err := json.Unmarshal([]byte(body), &call); err != nil {
cleaned = cleaned[:fullStart] + cleaned[fullEnd:]
continue
}
calls = append([]ToolUse{call}, calls...)
cleaned = cleaned[:fullStart] + cleaned[fullEnd:]
}
cleaned = strings.TrimSpace(cleaned)
return calls, cleaned
}