Files
CLIProxyAPI/internal/translator/common/request.go
Luis Pater 616d1b11e5 fix(claude,gemini,antigravity): centralize tool-call ID generation and harden signature sanitization
- Add shared `GenerateClaudeToolCallID` in `common` and replace duplicated local `toolu_` generators across Claude request translators for consistent, uniform ID generation.
- Replace `bufio.Scanner`-based SSE line handling with manual newline parsing and use `JoinRawArray` for array assembly to avoid scanner buffer limits and extra wrapping allocations.
- Improve carrier/thought signature scrubbing to safely strip internal metadata only when present and detect spoofed/duplicate `thoughtSignature` keys (including raw/unicode-escaped forms) during sanitized JSON traversal.
2026-08-15 21:21:02 +08:00

62 lines
1.6 KiB
Go

package common
import (
"crypto/rand"
"strings"
"github.com/tidwall/gjson"
)
const tooluLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// GenerateClaudeToolCallID generates a random tool use ID prefixed with toolu_
// using rejection sampling to guarantee a uniform distribution across the 62 alphanumeric characters.
func GenerateClaudeToolCallID() string {
const maxValidByte = 256 - (256 % len(tooluLetters)) // 248: exact multiple of 62
var b strings.Builder
b.Grow(len("toolu_") + 24)
b.WriteString("toolu_")
var buf [32]byte
n := 0
for n < 24 {
_, _ = rand.Read(buf[:])
for _, bVal := range buf {
if int(bVal) < maxValidByte {
b.WriteByte(tooluLetters[int(bVal)%len(tooluLetters)])
n++
if n == 24 {
break
}
}
}
}
return b.String()
}
// RequestModelName returns the model name from the original request, falling
// back to the translated request when the original request is unavailable.
func RequestModelName(originalRequestRawJSON, requestRawJSON []byte) string {
for _, rawJSON := range [][]byte{originalRequestRawJSON, requestRawJSON} {
if modelName := requestModelName(rawJSON); modelName != "" {
return modelName
}
}
return ""
}
func requestModelName(rawJSON []byte) string {
if len(rawJSON) == 0 || !gjson.ValidBytes(rawJSON) {
return ""
}
root := gjson.ParseBytes(rawJSON)
for _, path := range []string{"model", "request.model"} {
model := root.Get(path)
if model.Type == gjson.String && strings.TrimSpace(model.String()) != "" {
return model.String()
}
}
return ""
}