mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-19 22:19:12 +08:00
perf(translator): optimize array allocation logic and replace JoinRawArray with SetRawArrayItems methods
This commit is contained in:
@@ -342,7 +342,7 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
}
|
||||
|
||||
// contents
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(gjson.GetBytes(rawJSON, "messages.#").Int())
|
||||
|
||||
// tool_use_id → tool_name lookup, populated incrementally during the main loop.
|
||||
// Claude's tool_result references tool_use by ID; Gemini requires functionResponse.name.
|
||||
@@ -629,7 +629,7 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
allowedToolKeys := []string{"name", "description", "behavior", "parameters", "parametersJsonSchema", "response", "responseJsonSchema"}
|
||||
toolsResult := gjson.GetBytes(rawJSON, "tools")
|
||||
if toolsResult.IsArray() {
|
||||
functionDeclarations := make([][]byte, 0, 8)
|
||||
var functionDeclarations [][]byte
|
||||
toolsResults := toolsResult.Array()
|
||||
for i := 0; i < len(toolsResults); i++ {
|
||||
toolResult := toolsResults[i]
|
||||
@@ -686,7 +686,7 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
out, _ = sjson.SetRawBytes(out, "request.systemInstruction", antigravityClaudeContent("user", systemParts))
|
||||
}
|
||||
if len(contentItems) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "request.contents", translatorcommon.JoinRawArray(contentItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "request.contents", contentItems)
|
||||
}
|
||||
if toolDeclCount > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "request.tools", toolsJSON)
|
||||
|
||||
@@ -60,7 +60,7 @@ func ConvertGeminiRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
// Normalize roles in request.contents: default to valid values if missing/invalid
|
||||
contents := gjson.GetBytes(rawJSON, "request.contents")
|
||||
if contents.IsArray() {
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(contents.Get("#").Int())
|
||||
rolesChanged := false
|
||||
previousRole := ""
|
||||
contents.ForEach(func(_, value gjson.Result) bool {
|
||||
@@ -87,7 +87,7 @@ func ConvertGeminiRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
toolsResult := gjson.GetBytes(rawJSON, "request.tools")
|
||||
if toolsResult.IsArray() {
|
||||
seenFunctionNames := make(map[string]struct{})
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
toolsResult.ForEach(func(toolIndex, tool gjson.Result) bool {
|
||||
toolJSON := []byte(tool.Raw)
|
||||
for _, key := range []string{"functionDeclarations", "function_declarations"} {
|
||||
@@ -96,7 +96,7 @@ func ConvertGeminiRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
continue
|
||||
}
|
||||
|
||||
declarationItems := make([][]byte, 0, 8)
|
||||
var declarationItems [][]byte
|
||||
declarations.ForEach(func(_, declaration gjson.Result) bool {
|
||||
mappedName := util.MapSanitizedFunctionName(functionNameMap, declaration.Get("name").String())
|
||||
if mappedName != "" {
|
||||
@@ -140,7 +140,7 @@ func ConvertGeminiRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
|
||||
func removeEmptyGeminiFunctionTools(rawJSON []byte) []byte {
|
||||
tools := gjson.GetBytes(rawJSON, "request.tools")
|
||||
cleanedTools := make([][]byte, 0, 8)
|
||||
var cleanedTools [][]byte
|
||||
for _, tool := range tools.Array() {
|
||||
toolJSON := []byte(tool.Raw)
|
||||
if tool.IsObject() {
|
||||
@@ -178,7 +178,7 @@ func rewriteGeminiFunctionNames(rawJSON []byte, functionNameMap map[string]strin
|
||||
}
|
||||
if canBatchContents {
|
||||
contentsChanged := false
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(contents.Get("#").Int())
|
||||
contents.ForEach(func(_, content gjson.Result) bool {
|
||||
contentJSON := []byte(content.Raw)
|
||||
partsChanged := false
|
||||
@@ -512,7 +512,7 @@ func fixCLIToolResponse(input string) (string, error) {
|
||||
}
|
||||
|
||||
// Initialize data structures for processing and grouping
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(contents.Get("#").Int())
|
||||
var pendingGroups []*FunctionCallGroup // Groups awaiting completion with responses
|
||||
var collectedResponses []gjson.Result // Standalone responses to be matched
|
||||
appendFunctionResponses := func(responses []gjson.Result, callNames []string) {
|
||||
|
||||
@@ -22,9 +22,9 @@ func ConvertInteractionsRequestToAntigravity(modelName string, inputRawJSON []by
|
||||
}
|
||||
out = copyInteractionsSystemToAntigravity(out, root)
|
||||
out = copyInteractionsGenerationConfigToAntigravity(out, root)
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(root.Get("input.#").Int())
|
||||
appendInteractionsInputToAntigravity(&contentItems, root.Get("input"))
|
||||
out, _ = sjson.SetRawBytes(out, "request.contents", translatorcommon.JoinRawArray(contentItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "request.contents", contentItems)
|
||||
out = copyInteractionsToolsToAntigravity(out, root, functionNameMap)
|
||||
out = rewriteInteractionsFunctionNames(out, functionNameMap)
|
||||
out = attachDefaultAntigravitySafetySettings(out)
|
||||
@@ -46,7 +46,7 @@ func rewriteInteractionsFunctionNames(out []byte, functionNameMap map[string]str
|
||||
}
|
||||
if canBatchContents {
|
||||
contentsChanged := false
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(contents.Get("#").Int())
|
||||
contents.ForEach(func(_, content gjson.Result) bool {
|
||||
contentJSON := []byte(content.Raw)
|
||||
partsChanged := false
|
||||
@@ -507,8 +507,8 @@ func copyInteractionsToolsToAntigravity(out []byte, root gjson.Result, functionN
|
||||
out, _ = sjson.SetRawBytes(out, "request.tools", []byte(tools.Raw))
|
||||
return out
|
||||
}
|
||||
functionDeclarations := make([][]byte, 0, 8)
|
||||
otherTools := make([][]byte, 0)
|
||||
var functionDeclarations [][]byte
|
||||
var otherTools [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if decls := tool.Get("functionDeclarations"); decls.Exists() && decls.IsArray() {
|
||||
decls.ForEach(func(_, decl gjson.Result) bool {
|
||||
|
||||
@@ -302,7 +302,7 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
if len(systemParts) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "request.systemInstruction", antigravityOpenAIContent("user", systemParts))
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "request.contents", translatorcommon.JoinRawArray(contentItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "request.contents", contentItems)
|
||||
}
|
||||
|
||||
// tools -> request.tools[].functionDeclarations + request.tools[].googleSearch/codeExecution/urlContext passthrough
|
||||
|
||||
@@ -67,7 +67,7 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream
|
||||
out := []byte(fmt.Sprintf(`{"model":"","max_tokens":32000,"messages":[],"metadata":{"user_id":"%s"}}`, userID))
|
||||
|
||||
root := gjson.ParseBytes(rawJSON)
|
||||
messageItems := make([][]byte, 0, 16)
|
||||
messageItems := translatorcommon.NewRawArrayItems(root.Get("contents.#").Int())
|
||||
|
||||
// Helper for generating tool call IDs in the form: toolu_<alphanum>
|
||||
// This ensures unique identifiers for tool calls in the Claude Code format
|
||||
@@ -359,7 +359,7 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream
|
||||
return true
|
||||
})
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "messages", translatorcommon.JoinRawArray(messageItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "messages", messageItems)
|
||||
|
||||
// Tools mapping: Gemini functionDeclarations -> Claude Code tools
|
||||
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
|
||||
|
||||
@@ -20,9 +20,9 @@ func ConvertInteractionsRequestToClaude(modelName string, inputRawJSON []byte, s
|
||||
}
|
||||
out = copyInteractionsSystemToClaude(out, root)
|
||||
out = copyInteractionsGenerationConfigToClaude(out, root)
|
||||
messageItems := make([][]byte, 0, 16)
|
||||
messageItems := translatorcommon.NewRawArrayItems(root.Get("input.#").Int())
|
||||
appendInteractionsInputToClaudeMessages(&messageItems, root.Get("input"))
|
||||
out, _ = sjson.SetRawBytes(out, "messages", translatorcommon.JoinRawArray(messageItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "messages", messageItems)
|
||||
out = copyInteractionsToolsToClaude(out, root)
|
||||
return out
|
||||
}
|
||||
@@ -297,7 +297,7 @@ func copyInteractionsToolsToClaude(out []byte, root gjson.Result) []byte {
|
||||
if !tools.Exists() || !tools.IsArray() {
|
||||
return out
|
||||
}
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if tool.Get("function_declarations").IsArray() {
|
||||
tool.Get("function_declarations").ForEach(func(_, decl gjson.Result) bool {
|
||||
|
||||
@@ -289,13 +289,13 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream
|
||||
out, _ = sjson.SetRawBytes(out, "system", common.JoinRawArray(systemBlocks))
|
||||
}
|
||||
if len(messageBlocks) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "messages", common.JoinRawArray(messageBlocks))
|
||||
out = common.SetRawArrayItems(out, "messages", messageBlocks)
|
||||
}
|
||||
}
|
||||
|
||||
// Tools mapping: OpenAI tools -> Claude Code tools
|
||||
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() && len(tools.Array()) > 0 {
|
||||
anthropicTools := make([][]byte, 0, 8)
|
||||
var anthropicTools [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if tool.Get("type").String() == "function" {
|
||||
function := tool.Get("function")
|
||||
|
||||
@@ -128,7 +128,11 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
|
||||
out, _ = sjson.SetBytes(out, "stream", stream)
|
||||
|
||||
// instructions -> as a leading message (use role user for Claude API compatibility)
|
||||
messageBlocks := make([][]byte, 0, 16)
|
||||
messageCapacity := root.Get("input.#").Int()
|
||||
if instructions := root.Get("instructions"); instructions.Type == gjson.String && instructions.String() != "" {
|
||||
messageCapacity++
|
||||
}
|
||||
messageBlocks := common.NewRawArrayItems(messageCapacity)
|
||||
instructionsText := ""
|
||||
extractedFromSystem := false
|
||||
if instr := root.Get("instructions"); instr.Exists() && instr.Type == gjson.String {
|
||||
@@ -419,14 +423,14 @@ func ConvertOpenAIResponsesRequestToClaude(modelName string, inputRawJSON []byte
|
||||
}
|
||||
flushPendingReasoning()
|
||||
flushPendingToolUses()
|
||||
out, _ = sjson.SetRawBytes(out, "messages", common.JoinRawArray(messageBlocks))
|
||||
out = common.SetRawArrayItems(out, "messages", messageBlocks)
|
||||
|
||||
includedToolNames := map[string]struct{}{}
|
||||
toolNameMap := map[string]string{}
|
||||
|
||||
// tools mapping: parameters -> input_schema
|
||||
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
convertedTools := convertResponsesToolToClaudeTools(tool, toolNameMap)
|
||||
for _, tJSON := range convertedTools {
|
||||
|
||||
@@ -46,7 +46,7 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
rootResult := gjson.ParseBytes(rawJSON)
|
||||
toolNameMap := buildReverseMapFromClaudeOriginalToShort(rawJSON)
|
||||
template, _ = sjson.SetBytes(template, "model", modelName)
|
||||
inputItems := make([][]byte, 0, 16)
|
||||
inputItems := translatorcommon.NewRawArrayItems(rootResult.Get("messages.#").Int())
|
||||
|
||||
// Process system messages and convert them to input content format.
|
||||
systemsResult := rootResult.Get("system")
|
||||
@@ -347,7 +347,7 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
if toolsResult.IsArray() {
|
||||
template, _ = sjson.SetRawBytes(template, "tools", translatorcommon.JoinRawArray(toolItems))
|
||||
}
|
||||
template, _ = sjson.SetRawBytes(template, "input", translatorcommon.JoinRawArray(inputItems))
|
||||
template = translatorcommon.SetRawArrayItems(template, "input", inputItems)
|
||||
|
||||
return template
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
out := []byte(`{"model":"","instructions":"","input":[]}`)
|
||||
|
||||
root := gjson.ParseBytes(rawJSON)
|
||||
inputItems := make([][]byte, 0, 16)
|
||||
inputItems := translatorcommon.NewRawArrayItems(root.Get("contents.#").Int())
|
||||
|
||||
// Pre-compute tool name shortening map from declared functionDeclarations
|
||||
shortMap := map[string]string{}
|
||||
@@ -230,12 +230,12 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
|
||||
}
|
||||
}
|
||||
|
||||
out, _ = sjson.SetRawBytes(out, "input", translatorcommon.JoinRawArray(inputItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "input", inputItems)
|
||||
|
||||
// Tools mapping: Gemini functionDeclarations -> Codex tools
|
||||
tools := root.Get("tools")
|
||||
if tools.IsArray() {
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
out, _ = sjson.SetBytes(out, "tool_choice", "auto")
|
||||
tarr := tools.Array()
|
||||
for i := 0; i < len(tarr); i++ {
|
||||
|
||||
@@ -20,9 +20,9 @@ func ConvertInteractionsRequestToCodex(modelName string, inputRawJSON []byte, st
|
||||
}
|
||||
out = copyInteractionsSystemToCodex(out, root)
|
||||
out = copyInteractionsGenerationConfigToCodex(out, root)
|
||||
inputItems := make([][]byte, 0, 16)
|
||||
inputItems := translatorcommon.NewRawArrayItems(root.Get("input.#").Int())
|
||||
appendInteractionsInputToCodex(&inputItems, root.Get("input"))
|
||||
out, _ = sjson.SetRawBytes(out, "input", translatorcommon.JoinRawArray(inputItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "input", inputItems)
|
||||
out = copyInteractionsToolsToCodex(out, root)
|
||||
out = copyInteractionsCodexTopLevel(out, root)
|
||||
return out
|
||||
|
||||
@@ -122,7 +122,7 @@ func ConvertOpenAIRequestToCodex(modelName string, inputRawJSON []byte, stream b
|
||||
|
||||
// Build input from messages, handling all message types including tool calls
|
||||
out, _ = sjson.SetRawBytes(out, "input", []byte(`[]`))
|
||||
inputItems := make([][]byte, 0, 16)
|
||||
inputItems := translatorcommon.NewRawArrayItems(messages.Get("#").Int())
|
||||
if messages.IsArray() {
|
||||
arr := messages.Array()
|
||||
for i := 0; i < len(arr); i++ {
|
||||
@@ -343,7 +343,7 @@ func ConvertOpenAIRequestToCodex(modelName string, inputRawJSON []byte, stream b
|
||||
}
|
||||
}
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "input", translatorcommon.JoinRawArray(inputItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "input", inputItems)
|
||||
|
||||
// Map response_format and text settings to Responses API text.format
|
||||
rf := gjson.GetBytes(rawJSON, "response_format")
|
||||
|
||||
@@ -121,7 +121,7 @@ func normalizeCodexBuiltinToolArray(rawJSON []byte, path string) []byte {
|
||||
}
|
||||
|
||||
changed := false
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
item := []byte(tool.Raw)
|
||||
currentType := tool.Get("type").String()
|
||||
|
||||
@@ -2,6 +2,9 @@ package common
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
func GeminiTokenCountJSON(count int64) []byte {
|
||||
@@ -22,6 +25,14 @@ func ClaudeInputTokensJSON(count int64) []byte {
|
||||
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("[]")
|
||||
@@ -41,6 +52,27 @@ func JoinRawArray(items [][]byte) []byte {
|
||||
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: "...)
|
||||
|
||||
@@ -21,3 +21,36 @@ func TestJoinRawArray(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRawArrayItems(t *testing.T) {
|
||||
if items := NewRawArrayItems(0); items != nil {
|
||||
t.Fatalf("NewRawArrayItems(0) = %#v, want nil", items)
|
||||
}
|
||||
if items := NewRawArrayItems(3); len(items) != 0 || cap(items) != 3 {
|
||||
t.Fatalf("NewRawArrayItems(3) len = %d, cap = %d; want len 0, cap 3", len(items), cap(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRawArrayItems(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data string
|
||||
path string
|
||||
items [][]byte
|
||||
want string
|
||||
}{
|
||||
{name: "empty", data: `{"items":[]}`, path: "items", want: `{"items":[]}`},
|
||||
{name: "single nested", data: `{"before":1,"request":{"contents":[]},"after":2}`, path: "request.contents", items: [][]byte{[]byte(`{"id":1}`)}, want: `{"before":1,"request":{"contents":[{"id":1}]},"after":2}`},
|
||||
{name: "single fallback", data: `{"items":[{"old":1},{"old":2}]}`, path: "items", items: [][]byte{[]byte(`{"id":1}`)}, want: `{"items":[{"id":1}]}`},
|
||||
{name: "multiple", data: `{"items":[]}`, path: "items", items: [][]byte{[]byte(`{"id":1}`), []byte(`{"id":2}`)}, want: `{"items":[{"id":1},{"id":2}]}`},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := SetRawArrayItems([]byte(test.data), test.path, test.items)
|
||||
if string(got) != test.want {
|
||||
t.Fatalf("SetRawArrayItems() = %s, want %s", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
||||
|
||||
// contents
|
||||
if messagesResult := gjson.GetBytes(rawJSON, "messages"); messagesResult.IsArray() {
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(messagesResult.Get("#").Int())
|
||||
messagesResult.ForEach(func(_, messageResult gjson.Result) bool {
|
||||
roleResult := messageResult.Get("role")
|
||||
if roleResult.Type != gjson.String {
|
||||
@@ -187,12 +187,12 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
||||
}
|
||||
}
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "contents", translatorcommon.JoinRawArray(contentItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "contents", contentItems)
|
||||
}
|
||||
|
||||
// tools
|
||||
if toolsResult := gjson.GetBytes(rawJSON, "tools"); toolsResult.IsArray() {
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
toolsResult.ForEach(func(_, toolResult gjson.Result) bool {
|
||||
inputSchemaResult := toolResult.Get("input_schema")
|
||||
if inputSchemaResult.Exists() && inputSchemaResult.IsObject() {
|
||||
|
||||
@@ -31,7 +31,7 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte
|
||||
|
||||
toolsResult := gjson.GetBytes(rawJSON, "tools")
|
||||
if toolsResult.Exists() && toolsResult.IsArray() {
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
toolsChanged := false
|
||||
toolsResult.ForEach(func(_, toolResult gjson.Result) bool {
|
||||
tool := []byte(toolResult.Raw)
|
||||
@@ -44,7 +44,7 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte
|
||||
|
||||
declarations := gjson.GetBytes(tool, "function_declarations")
|
||||
if declarations.IsArray() {
|
||||
declarationItems := make([][]byte, 0, 8)
|
||||
var declarationItems [][]byte
|
||||
declarationsChanged := false
|
||||
declarations.ForEach(func(_, declarationResult gjson.Result) bool {
|
||||
declaration := []byte(declarationResult.Raw)
|
||||
@@ -86,7 +86,7 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte
|
||||
})
|
||||
if rolesChanged {
|
||||
prevRole = ""
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(contents.Get("#").Int())
|
||||
contents.ForEach(func(_, value gjson.Result) bool {
|
||||
role := value.Get("role").String()
|
||||
item := []byte(value.Raw)
|
||||
@@ -161,7 +161,7 @@ func backfillEmptyFunctionResponseNames(data []byte) []byte {
|
||||
}
|
||||
|
||||
changed := false
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(contents.Get("#").Int())
|
||||
var pendingCallNames []string
|
||||
|
||||
contents.ForEach(func(contentIdx, content gjson.Result) bool {
|
||||
|
||||
@@ -39,9 +39,9 @@ func ConvertInteractionsRequestToGemini(modelName string, inputRawJSON []byte, s
|
||||
out = copyInteractionsTools(out, root)
|
||||
out = copyInteractionsToolChoice(out, root)
|
||||
out = copyInteractionsServiceTier(out, root)
|
||||
contentItems := make([][]byte, 0, 16)
|
||||
contentItems := translatorcommon.NewRawArrayItems(root.Get("input.#").Int())
|
||||
appendInteractionsInput(&contentItems, root.Get("input"))
|
||||
out, _ = sjson.SetRawBytes(out, "contents", translatorcommon.JoinRawArray(contentItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "contents", contentItems)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func ConvertGeminiRequestToInteractions(modelName string, inputRawJSON []byte, s
|
||||
out = normalizeGeminiThinkingConfigForInteractions(out)
|
||||
}
|
||||
out = copyGeminiToolsToInteractions(out, root)
|
||||
inputItems := make([][]byte, 0, 16)
|
||||
inputItems := translatorcommon.NewRawArrayItems(root.Get("contents.#").Int())
|
||||
root.Get("contents").ForEach(func(_, content gjson.Result) bool {
|
||||
role := content.Get("role").String()
|
||||
stepType := "user_input"
|
||||
@@ -88,13 +88,13 @@ func ConvertGeminiRequestToInteractions(modelName string, inputRawJSON []byte, s
|
||||
}
|
||||
step := []byte(`{"type":"","content":[]}`)
|
||||
step, _ = sjson.SetBytes(step, "type", currentStepType)
|
||||
step, _ = sjson.SetRawBytes(step, "content", translatorcommon.JoinRawArray([][]byte{item}))
|
||||
step = translatorcommon.SetRawArrayItems(step, "content", [][]byte{item})
|
||||
inputItems = append(inputItems, step)
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
out, _ = sjson.SetRawBytes(out, "input", translatorcommon.JoinRawArray(inputItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "input", inputItems)
|
||||
out, _ = sjson.SetBytes(out, "stream", stream)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
||||
if len(contentItems) > 0 && gjson.GetBytes(contentItems[len(contentItems)-1], "role").String() == "model" {
|
||||
contentItems = contentItems[:len(contentItems)-1]
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "contents", translatorcommon.JoinRawArray(contentItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "contents", contentItems)
|
||||
}
|
||||
|
||||
// tools -> tools[].functionDeclarations + tools[].googleSearch/codeExecution/urlContext passthrough
|
||||
|
||||
@@ -359,12 +359,12 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte
|
||||
if len(contentItems) > 0 && shouldStripTrailingOpenAIResponsesModelPrefill(gjson.ParseBytes(contentItems[len(contentItems)-1])) {
|
||||
contentItems = contentItems[:len(contentItems)-1]
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "contents", translatorcommon.JoinRawArray(contentItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "contents", contentItems)
|
||||
} else if input.Exists() && input.Type == gjson.String {
|
||||
// Simple string input conversion to user message.
|
||||
part := []byte(`{"text":""}`)
|
||||
part, _ = sjson.SetBytes(part, "text", input.String())
|
||||
out, _ = sjson.SetRawBytes(out, "contents", translatorcommon.JoinRawArray([][]byte{geminiContent("user", [][]byte{part})}))
|
||||
out = translatorcommon.SetRawArrayItems(out, "contents", [][]byte{geminiContent("user", [][]byte{part})})
|
||||
}
|
||||
if len(systemParts) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "systemInstruction", geminiSystemInstruction(systemParts))
|
||||
@@ -372,7 +372,7 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte
|
||||
|
||||
// Convert tools to Gemini functionDeclarations format
|
||||
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
|
||||
functionDeclarations := make([][]byte, 0, 8)
|
||||
var functionDeclarations [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if tool.Get("type").String() == "function" {
|
||||
funcDecl := []byte(`{"name":"","description":"","parametersJsonSchema":{}}`)
|
||||
|
||||
@@ -116,12 +116,12 @@ func appendClaudeMessagesToInteractions(out []byte, messages gjson.Result) []byt
|
||||
if !messages.Exists() || !messages.IsArray() {
|
||||
return out
|
||||
}
|
||||
inputItems := make([][]byte, 0, 16)
|
||||
inputItems := translatorcommon.NewRawArrayItems(messages.Get("#").Int())
|
||||
messages.ForEach(func(_, message gjson.Result) bool {
|
||||
appendClaudeMessageToInteractions(&inputItems, message)
|
||||
return true
|
||||
})
|
||||
out, _ = sjson.SetRawBytes(out, "input", translatorcommon.JoinRawArray(inputItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "input", inputItems)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ func copyClaudeToolsToInteractions(out []byte, root gjson.Result) []byte {
|
||||
if !tools.Exists() || !tools.IsArray() {
|
||||
return out
|
||||
}
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
name := strings.TrimSpace(tool.Get("name").String())
|
||||
if name == "" {
|
||||
|
||||
@@ -99,7 +99,11 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
}
|
||||
|
||||
// Process messages and system.
|
||||
messageItems := make([][]byte, 0, 16)
|
||||
messageCapacity := root.Get("messages.#").Int()
|
||||
if root.Get("system").Exists() {
|
||||
messageCapacity++
|
||||
}
|
||||
messageItems := translatorcommon.NewRawArrayItems(messageCapacity)
|
||||
|
||||
// Handle system message first.
|
||||
systemContentItems := make([][]byte, 0, 2)
|
||||
@@ -285,12 +289,12 @@ func ConvertClaudeRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
|
||||
// Set messages.
|
||||
if len(messageItems) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "messages", translatorcommon.JoinRawArray(messageItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "messages", messageItems)
|
||||
}
|
||||
|
||||
// Process tools - convert Anthropic tools to OpenAI functions
|
||||
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
openAIToolJSON := []byte(`{"type":"function","function":{"name":"","description":""}}`)
|
||||
openAIToolJSON, _ = sjson.SetBytes(openAIToolJSON, "function.name", tool.Get("name").String())
|
||||
|
||||
@@ -134,7 +134,11 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
}
|
||||
|
||||
// Process contents (Gemini messages) -> OpenAI messages
|
||||
messageItems := make([][]byte, 0, 16)
|
||||
messageCapacity := root.Get("contents.#").Int()
|
||||
if root.Get("systemInstruction").Exists() || root.Get("system_instruction").Exists() {
|
||||
messageCapacity++
|
||||
}
|
||||
messageItems := translatorcommon.NewRawArrayItems(messageCapacity)
|
||||
var toolCallIDs []string // Track tool call IDs for matching with tool results
|
||||
toolCallConsumeIdx := 0
|
||||
|
||||
@@ -288,11 +292,11 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
|
||||
return true
|
||||
})
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "messages", translatorcommon.JoinRawArray(messageItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "messages", messageItems)
|
||||
|
||||
// Tools mapping: Gemini tools -> OpenAI tools
|
||||
if tools := root.Get("tools"); tools.Exists() && tools.IsArray() {
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if functionDeclarations := tool.Get("functionDeclarations"); functionDeclarations.Exists() && functionDeclarations.IsArray() {
|
||||
functionDeclarations.ForEach(func(_, funcDecl gjson.Result) bool {
|
||||
|
||||
@@ -16,10 +16,14 @@ func ConvertInteractionsRequestToOpenAI(modelName string, inputRawJSON []byte, s
|
||||
if stream || root.Get("stream").Bool() {
|
||||
out, _ = sjson.SetBytes(out, "stream", true)
|
||||
}
|
||||
messageItems := make([][]byte, 0, 16)
|
||||
messageCapacity := root.Get("input.#").Int()
|
||||
if interactionsText(root.Get("system_instruction")) != "" {
|
||||
messageCapacity++
|
||||
}
|
||||
messageItems := translatorcommon.NewRawArrayItems(messageCapacity)
|
||||
appendInteractionsSystemToOpenAI(&messageItems, root)
|
||||
appendInteractionsInputToOpenAIMessages(&messageItems, root.Get("input"))
|
||||
out, _ = sjson.SetRawBytes(out, "messages", translatorcommon.JoinRawArray(messageItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "messages", messageItems)
|
||||
out = copyInteractionsToolsToOpenAI(out, root)
|
||||
out = copyInteractionsGenerationConfigToOpenAI(out, root)
|
||||
out = copyInteractionsOpenAITopLevel(out, root)
|
||||
@@ -143,7 +147,7 @@ func appendInteractionsFunctionCallToOpenAI(items *[][]byte, step gjson.Result)
|
||||
toolCall, _ = sjson.SetBytes(toolCall, "id", callID)
|
||||
toolCall, _ = sjson.SetBytes(toolCall, "function.name", step.Get("name").String())
|
||||
toolCall, _ = sjson.SetBytes(toolCall, "function.arguments", jsonStringValue(step.Get("arguments"), "{}"))
|
||||
msg, _ = sjson.SetRawBytes(msg, "tool_calls", translatorcommon.JoinRawArray([][]byte{toolCall}))
|
||||
msg = translatorcommon.SetRawArrayItems(msg, "tool_calls", [][]byte{toolCall})
|
||||
*items = append(*items, msg)
|
||||
}
|
||||
|
||||
@@ -159,7 +163,7 @@ func copyInteractionsToolsToOpenAI(out []byte, root gjson.Result) []byte {
|
||||
if !tools.Exists() || !tools.IsArray() {
|
||||
return out
|
||||
}
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if converted, ok := openAIToolFromInteractionsTool(tool); ok {
|
||||
toolItems = append(toolItems, converted)
|
||||
|
||||
@@ -35,7 +35,7 @@ func appendOpenAIMessagesToInteractions(out []byte, messages gjson.Result) []byt
|
||||
if !messages.Exists() || !messages.IsArray() {
|
||||
return out
|
||||
}
|
||||
inputItems := make([][]byte, 0, 16)
|
||||
inputItems := translatorcommon.NewRawArrayItems(messages.Get("#").Int())
|
||||
var systemBuilder strings.Builder
|
||||
messages.ForEach(func(_, message gjson.Result) bool {
|
||||
role := strings.ToLower(strings.TrimSpace(message.Get("role").String()))
|
||||
@@ -55,7 +55,7 @@ func appendOpenAIMessagesToInteractions(out []byte, messages gjson.Result) []byt
|
||||
if systemBuilder.Len() > 0 {
|
||||
out, _ = sjson.SetBytes(out, "system_instruction", systemBuilder.String())
|
||||
}
|
||||
out, _ = sjson.SetRawBytes(out, "input", translatorcommon.JoinRawArray(inputItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "input", inputItems)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func appendOpenAIChatToolsToInteractions(out []byte, tools gjson.Result) []byte
|
||||
if !tools.Exists() || !tools.IsArray() {
|
||||
return out
|
||||
}
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if converted, ok := openAIChatToolToInteractions(tool); ok {
|
||||
toolItems = append(toolItems, converted)
|
||||
|
||||
@@ -227,7 +227,7 @@ func responsesInputItemToInteractions(item gjson.Result, functionNamesByCallID m
|
||||
}
|
||||
|
||||
func appendResponsesContentToInteractions(step []byte, content gjson.Result) []byte {
|
||||
contentItems := make([][]byte, 0, 4)
|
||||
var contentItems [][]byte
|
||||
if content.Type == gjson.String {
|
||||
part := []byte(`{"type":"text","text":""}`)
|
||||
part, _ = sjson.SetBytes(part, "text", content.String())
|
||||
@@ -245,7 +245,7 @@ func appendResponsesContentToInteractions(step []byte, content gjson.Result) []b
|
||||
}
|
||||
}
|
||||
if len(contentItems) > 0 {
|
||||
step, _ = sjson.SetRawBytes(step, "content", translatorcommon.JoinRawArray(contentItems))
|
||||
step = translatorcommon.SetRawArrayItems(step, "content", contentItems)
|
||||
}
|
||||
return step
|
||||
}
|
||||
@@ -328,7 +328,7 @@ func appendResponsesToolsToInteractions(out []byte, tools gjson.Result) []byte {
|
||||
if !tools.Exists() || !tools.IsArray() {
|
||||
return out
|
||||
}
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
switch tool.Get("type").String() {
|
||||
case "function", "":
|
||||
@@ -434,7 +434,7 @@ func interactionsInputItemToResponses(item gjson.Result) []byte {
|
||||
}
|
||||
|
||||
func interactionsMessageToResponses(item gjson.Result, role string) []byte {
|
||||
contentItems := make([][]byte, 0, 4)
|
||||
var contentItems [][]byte
|
||||
content := item.Get("content")
|
||||
if content.Type == gjson.String {
|
||||
partType := "input_text"
|
||||
@@ -455,19 +455,19 @@ func interactionsMessageToResponses(item gjson.Result, role string) []byte {
|
||||
}
|
||||
out := []byte(`{"type":"message","role":"","content":[]}`)
|
||||
out, _ = sjson.SetBytes(out, "role", role)
|
||||
out, _ = sjson.SetRawBytes(out, "content", translatorcommon.JoinRawArray(contentItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "content", contentItems)
|
||||
return out
|
||||
}
|
||||
|
||||
func interactionsThoughtToResponses(item gjson.Result) []byte {
|
||||
summaryItems := make([][]byte, 0, 2)
|
||||
var summaryItems [][]byte
|
||||
for _, text := range interactionsContentTexts(item.Get("content")) {
|
||||
part := []byte(`{"type":"summary_text","text":""}`)
|
||||
part, _ = sjson.SetBytes(part, "text", text)
|
||||
summaryItems = append(summaryItems, part)
|
||||
}
|
||||
out := []byte(`{"type":"reasoning","summary":[]}`)
|
||||
out, _ = sjson.SetRawBytes(out, "summary", translatorcommon.JoinRawArray(summaryItems))
|
||||
out = translatorcommon.SetRawArrayItems(out, "summary", summaryItems)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -551,7 +551,7 @@ func appendInteractionsToolsToResponses(out []byte, tools gjson.Result) []byte {
|
||||
if !tools.Exists() || !tools.IsArray() {
|
||||
return out
|
||||
}
|
||||
toolItems := make([][]byte, 0, 8)
|
||||
var toolItems [][]byte
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if converted, ok := responsesToolFromInteractionsTool(tool); ok {
|
||||
toolItems = append(toolItems, converted)
|
||||
|
||||
@@ -163,7 +163,7 @@ func ConvertOpenAIResponsesRequestToOpenAIChatCompletions(modelName string, inpu
|
||||
message, _ = sjson.SetBytes(message, "role", role)
|
||||
|
||||
if content := item.Get("content"); content.Exists() && content.IsArray() {
|
||||
contentItems := make([][]byte, 0, 4)
|
||||
var contentItems [][]byte
|
||||
content.ForEach(func(_, contentItem gjson.Result) bool {
|
||||
contentType := contentItem.Get("type").String()
|
||||
if contentType == "" {
|
||||
@@ -187,7 +187,7 @@ func ConvertOpenAIResponsesRequestToOpenAIChatCompletions(modelName string, inpu
|
||||
}
|
||||
return true
|
||||
})
|
||||
message, _ = sjson.SetRawBytes(message, "content", translatorcommon.JoinRawArray(contentItems))
|
||||
message = translatorcommon.SetRawArrayItems(message, "content", contentItems)
|
||||
} else if content.Type == gjson.String {
|
||||
message, _ = sjson.SetBytes(message, "content", content.String())
|
||||
}
|
||||
|
||||
@@ -11,12 +11,24 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkRequestTranslationLargeHistory(b *testing.B) {
|
||||
benchmarkRequestTranslation(b, 64)
|
||||
}
|
||||
|
||||
func BenchmarkRequestTranslationHistorySizes(b *testing.B) {
|
||||
for _, turns := range []int{0, 1, 4, 16, 64} {
|
||||
b.Run(fmt.Sprintf("turns_%d", turns), func(b *testing.B) {
|
||||
benchmarkRequestTranslation(b, turns)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkRequestTranslation(b *testing.B, turns int) {
|
||||
requests := map[string][]byte{
|
||||
"claude": benchmarkClaudeRequest(64),
|
||||
"gemini": benchmarkGeminiRequest(64),
|
||||
"openai": benchmarkOpenAIRequest(64),
|
||||
"openai-response": benchmarkOpenAIResponsesRequest(64),
|
||||
"interactions": benchmarkInteractionsRequest(64),
|
||||
"claude": benchmarkClaudeRequest(turns),
|
||||
"gemini": benchmarkGeminiRequest(turns),
|
||||
"openai": benchmarkOpenAIRequest(turns),
|
||||
"openai-response": benchmarkOpenAIResponsesRequest(turns),
|
||||
"interactions": benchmarkInteractionsRequest(turns),
|
||||
}
|
||||
routes := []struct {
|
||||
source string
|
||||
|
||||
Reference in New Issue
Block a user