package chat
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
func StripThinking(s string) (reasoning string, content string) {
start := strings.Index(s, "")
end := strings.Index(s, "")
if start >= 0 && end > start {
reasoning = s[start+len(""):end]
content = s[:start] + s[end+len(""):]
return strings.Trim(reasoning, "\n"), strings.TrimLeft(content, "\n")
}
// If thinking starts but doesn't end (truncated generation), drop it from visible output.
if start >= 0 && end < 0 {
reasoning = s[start+len(""):]
content = s[:start]
return strings.Trim(reasoning, "\n"), strings.TrimSpace(content)
}
return "", s
}
func ExtractToolCalls(s string) (content string, calls []ToolCall, err error) {
var out strings.Builder
rest := s
for {
start := strings.Index(rest, "")
if start < 0 {
out.WriteString(rest)
break
}
out.WriteString(rest[:start])
rest = rest[start+len(""):]
end := strings.Index(rest, "")
if end < 0 {
return "", nil, fmt.Errorf("unterminated ")
}
block := strings.TrimSpace(rest[:end])
rest = rest[end+len(""):]
// Some models include trailing commas/newlines; keep robust.
block = strings.Trim(block, "\n\r\t ")
var raw struct {
Name string `json:"name"`
Arguments json.RawMessage `json:"arguments"`
}
dec := json.NewDecoder(bytes.NewReader([]byte(block)))
dec.DisallowUnknownFields()
if err := dec.Decode(&raw); err != nil {
// Fallback: allow unknown fields.
if err2 := json.Unmarshal([]byte(block), &raw); err2 != nil {
return "", nil, fmt.Errorf("parse tool_call json: %w", err)
}
}
if raw.Name == "" {
return "", nil, fmt.Errorf("tool_call missing name")
}
calls = append(calls, ToolCall{Name: raw.Name, Arguments: raw.Arguments})
}
return out.String(), calls, nil
}