Files
picoclaw/pkg/tools/integration/helpers.go
T

135 lines
3.0 KiB
Go

package integrationtools
import (
"fmt"
"math"
"mime"
"path/filepath"
"regexp"
"strconv"
"strings"
"unicode"
)
var (
inlineMarkdownDataURLRe = regexp.MustCompile(`!\[[^\]]*\]\((data:[^)]+)\)`)
inlineRawDataURLRe = regexp.MustCompile(`data:[^;\s]+;base64,[A-Za-z0-9+/=\r\n]+`)
)
const (
largeBase64OmittedMessage = "[Tool returned a large base64-like payload; omitted from model context.]"
inlineMediaOmittedMessage = "[Tool returned inline media content; omitted from model context.]"
)
func sanitizeToolLLMContent(text string) string {
trimmed := strings.TrimSpace(text)
if trimmed == "" {
return text
}
if inlineMarkdownDataURLRe.MatchString(trimmed) || inlineRawDataURLRe.MatchString(trimmed) {
cleaned := inlineMarkdownDataURLRe.ReplaceAllString(trimmed, "")
cleaned = inlineRawDataURLRe.ReplaceAllString(cleaned, "")
cleaned = strings.TrimSpace(cleaned)
if cleaned == "" {
return inlineMediaOmittedMessage
}
return cleaned + "\n" + inlineMediaOmittedMessage
}
if looksLikeLargeBase64Payload(trimmed) {
return largeBase64OmittedMessage
}
return text
}
func looksLikeLargeBase64Payload(text string) bool {
trimmed := strings.TrimSpace(text)
if len(trimmed) < 1024 {
return false
}
nonSpace := 0
base64Like := 0
spaceCount := 0
for _, r := range trimmed {
if unicode.IsSpace(r) {
spaceCount++
continue
}
nonSpace++
if (r >= 'A' && r <= 'Z') ||
(r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') ||
r == '+' || r == '/' || r == '=' {
base64Like++
}
}
if nonSpace == 0 {
return false
}
ratio := float64(base64Like) / float64(nonSpace)
return ratio >= 0.97 && spaceCount <= len(trimmed)/128
}
func extensionForMIMEType(mimeType string) string {
if mimeType == "" {
return ".bin"
}
if exts, err := mime.ExtensionsByType(mimeType); err == nil && len(exts) > 0 {
return exts[0]
}
switch strings.ToLower(mimeType) {
case "image/jpeg":
return ".jpg"
case "image/png":
return ".png"
case "image/gif":
return ".gif"
case "image/webp":
return ".webp"
case "audio/wav", "audio/x-wav":
return ".wav"
case "audio/mpeg":
return ".mp3"
case "audio/ogg":
return ".ogg"
case "video/mp4":
return ".mp4"
default:
return filepath.Ext(mimeType)
}
}
func getInt64Arg(args map[string]any, key string, defaultVal int64) (int64, error) {
raw, exists := args[key]
if !exists {
return defaultVal, nil
}
switch v := raw.(type) {
case float64:
if v != math.Trunc(v) {
return 0, fmt.Errorf("%s must be an integer, got float %v", key, v)
}
if v > math.MaxInt64 || v < math.MinInt64 {
return 0, fmt.Errorf("%s value %v overflows int64", key, v)
}
return int64(v), nil
case int:
return int64(v), nil
case int64:
return v, nil
case string:
parsed, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, fmt.Errorf("invalid integer format for %s parameter: %w", key, err)
}
return parsed, nil
default:
return 0, fmt.Errorf("unsupported type %T for %s parameter", raw, key)
}
}