mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-09 01:03:05 +08:00
- Introduce `SetStringWithoutHTMLEscape` to prevent escaping of HTML characters (`<`, `>`, `&`) in tool call arguments and argument deltas across translators. - Handle sequential tool calls in Devin executor that share the same stream index but have distinct tool call IDs.
126 lines
3.4 KiB
Go
126 lines
3.4 KiB
Go
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"strconv"
|
|
|
|
"github.com/tidwall/gjson"
|
|
"github.com/tidwall/sjson"
|
|
)
|
|
|
|
func GeminiTokenCountJSON(count int64) []byte {
|
|
out := make([]byte, 0, 96)
|
|
out = append(out, `{"totalTokens":`...)
|
|
out = strconv.AppendInt(out, count, 10)
|
|
out = append(out, `,"promptTokensDetails":[{"modality":"TEXT","tokenCount":`...)
|
|
out = strconv.AppendInt(out, count, 10)
|
|
out = append(out, `}]}`...)
|
|
return out
|
|
}
|
|
|
|
func ClaudeInputTokensJSON(count int64) []byte {
|
|
out := make([]byte, 0, 32)
|
|
out = append(out, `{"input_tokens":`...)
|
|
out = strconv.AppendInt(out, count, 10)
|
|
out = append(out, '}')
|
|
return out
|
|
}
|
|
|
|
// NewRawArrayItems creates a raw item slice sized for the expected input.
|
|
func NewRawArrayItems(capacity int64) [][]byte {
|
|
if capacity <= 0 {
|
|
return nil
|
|
}
|
|
return make([][]byte, 0, int(capacity))
|
|
}
|
|
|
|
func JoinRawArray(items [][]byte) []byte {
|
|
if len(items) == 0 {
|
|
return []byte("[]")
|
|
}
|
|
size := len(items) + 1
|
|
for _, item := range items {
|
|
size += len(item)
|
|
}
|
|
out := make([]byte, 0, size)
|
|
out = append(out, '[')
|
|
for i, item := range items {
|
|
if i > 0 {
|
|
out = append(out, ',')
|
|
}
|
|
out = append(out, item...)
|
|
}
|
|
return append(out, ']')
|
|
}
|
|
|
|
// SetRawArrayItems replaces an empty JSON array at path with raw items.
|
|
// The single-item path avoids allocating an intermediate joined array.
|
|
func SetRawArrayItems(data []byte, path string, items [][]byte) []byte {
|
|
if len(items) == 0 {
|
|
return data
|
|
}
|
|
if len(items) == 1 {
|
|
array := gjson.GetBytes(data, path)
|
|
if array.Raw == "[]" && array.Index >= 0 && array.Index+len(array.Raw) <= len(data) {
|
|
out := make([]byte, 0, len(data)+len(items[0]))
|
|
out = append(out, data[:array.Index]...)
|
|
out = append(out, '[')
|
|
out = append(out, items[0]...)
|
|
out = append(out, ']')
|
|
return append(out, data[array.Index+len(array.Raw):]...)
|
|
}
|
|
}
|
|
data, _ = sjson.SetRawBytes(data, path, JoinRawArray(items))
|
|
return data
|
|
}
|
|
|
|
func SSEEventData(event string, payload []byte) []byte {
|
|
out := make([]byte, 0, len(event)+len(payload)+14)
|
|
out = append(out, "event: "...)
|
|
out = append(out, event...)
|
|
out = append(out, '\n')
|
|
out = append(out, "data: "...)
|
|
out = append(out, payload...)
|
|
return out
|
|
}
|
|
|
|
func AppendSSEEventString(out []byte, event, payload string, trailingNewlines int) []byte {
|
|
out = append(out, "event: "...)
|
|
out = append(out, event...)
|
|
out = append(out, '\n')
|
|
out = append(out, "data: "...)
|
|
out = append(out, payload...)
|
|
for i := 0; i < trailingNewlines; i++ {
|
|
out = append(out, '\n')
|
|
}
|
|
return out
|
|
}
|
|
|
|
func AppendSSEEventBytes(out []byte, event string, payload []byte, trailingNewlines int) []byte {
|
|
out = append(out, "event: "...)
|
|
out = append(out, event...)
|
|
out = append(out, '\n')
|
|
out = append(out, "data: "...)
|
|
out = append(out, payload...)
|
|
for i := 0; i < trailingNewlines; i++ {
|
|
out = append(out, '\n')
|
|
}
|
|
return out
|
|
}
|
|
|
|
// SetStringWithoutHTMLEscape sets a string field in a JSON payload without escaping
|
|
// HTML characters (<, >, &). Standard sjson.Set/SetBytes delegates string values
|
|
// containing quotes or special characters to encoding/json.Marshal, which unconditionally
|
|
// escapes <, >, & to \u003c, \u003e, \u0026.
|
|
func SetStringWithoutHTMLEscape(data []byte, path, value string) ([]byte, error) {
|
|
buf := &bytes.Buffer{}
|
|
enc := json.NewEncoder(buf)
|
|
enc.SetEscapeHTML(false)
|
|
if errEncode := enc.Encode(value); errEncode != nil {
|
|
return sjson.SetBytes(data, path, value)
|
|
}
|
|
raw := bytes.TrimRight(buf.Bytes(), "\n")
|
|
return sjson.SetRawBytes(data, path, raw)
|
|
}
|