From 8b3b304952ed7241f8a0f8afc285404937f05adf Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Sun, 16 Aug 2026 01:53:27 +0800 Subject: [PATCH] perf(translator): switch response translators to batched raw-array insertion via `SetRawArrayItems` --- .../interactions/interactions_claude_response.go | 10 ++++------ .../responses/claude_openai-responses_response.go | 4 ++-- .../translator/codex/gemini/codex_gemini_response.go | 10 +++++----- .../openai/claude/openai_claude_response.go | 11 ++++++++--- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/internal/translator/claude/interactions/interactions_claude_response.go b/internal/translator/claude/interactions/interactions_claude_response.go index 0d21ab20..2157c9ba 100644 --- a/internal/translator/claude/interactions/interactions_claude_response.go +++ b/internal/translator/claude/interactions/interactions_claude_response.go @@ -251,14 +251,12 @@ func claudeContentBlockToInteractionsStep(part gjson.Result) []byte { step := []byte(`{"type":"model_output","content":[]}`) content := []byte(`{"type":"text","text":""}`) content, _ = sjson.SetBytes(content, "text", part.Get("text").String()) - step, _ = sjson.SetRawBytes(step, "content.-1", content) - return step + return translatorcommon.SetRawArrayItems(step, "content", [][]byte{content}) case "thinking": step := []byte(`{"type":"thought","content":[]}`) content := []byte(`{"type":"text","text":""}`) content, _ = sjson.SetBytes(content, "text", part.Get("thinking").String()) - step, _ = sjson.SetRawBytes(step, "content.-1", content) - return step + return translatorcommon.SetRawArrayItems(step, "content", [][]byte{content}) case "tool_use": return claudeToolUseToInteractionsStep(part, strings.TrimSpace(part.Get("input").Raw)) } @@ -357,7 +355,7 @@ func claudeNonStreamContentBlockStop(root gjson.Result, st *claudeToInteractions step = []byte(`{"type":"thought","content":[]}`) content := []byte(`{"type":"text","text":""}`) content, _ = sjson.SetBytes(content, "text", text) - step, _ = sjson.SetRawBytes(step, "content.-1", content) + step = translatorcommon.SetRawArrayItems(step, "content", [][]byte{content}) case "function_call": part := []byte(`{"type":"tool_use","id":"","name":"","input":{}}`) part, _ = sjson.SetBytes(part, "id", st.ToolIDs[index]) @@ -367,7 +365,7 @@ func claudeNonStreamContentBlockStop(root gjson.Result, st *claudeToInteractions step = []byte(`{"type":"model_output","content":[]}`) content := []byte(`{"type":"text","text":""}`) content, _ = sjson.SetBytes(content, "text", text) - step, _ = sjson.SetRawBytes(step, "content.-1", content) + step = translatorcommon.SetRawArrayItems(step, "content", [][]byte{content}) } delete(st.CurrentStepByIndex, index) delete(st.ToolNames, index) diff --git a/internal/translator/claude/openai/responses/claude_openai-responses_response.go b/internal/translator/claude/openai/responses/claude_openai-responses_response.go index 2dc5f8ad..0fffa651 100644 --- a/internal/translator/claude/openai/responses/claude_openai-responses_response.go +++ b/internal/translator/claude/openai/responses/claude_openai-responses_response.go @@ -528,7 +528,7 @@ func ConvertClaudeResponseToOpenAIResponses(ctx context.Context, modelName strin itemDone, _ = sjson.SetBytes(itemDone, "item.encrypted_content", st.ReasoningSignature) summary := []byte(`{"type":"summary_text","text":""}`) summary, _ = sjson.SetBytes(summary, "text", full) - itemDone, _ = sjson.SetRawBytes(itemDone, "item.summary.-1", summary) + itemDone = translatorcommon.SetRawArrayItems(itemDone, "item.summary", [][]byte{summary}) out = append(out, emitEvent("response.output_item.done", itemDone)) st.ReasoningItems = append(st.ReasoningItems, claudeResponsesReasoningItem{ ID: st.ReasoningItemID, @@ -629,7 +629,7 @@ func ConvertClaudeResponseToOpenAIResponses(ctx context.Context, modelName strin item, _ = sjson.SetBytes(item, "encrypted_content", reasoning.Signature) summary := []byte(`{"type":"summary_text","text":""}`) summary, _ = sjson.SetBytes(summary, "text", reasoning.Text) - item, _ = sjson.SetRawBytes(item, "summary.-1", summary) + item = translatorcommon.SetRawArrayItems(item, "summary", [][]byte{summary}) outputsWrapper, _ = sjson.SetRawBytes(outputsWrapper, fmt.Sprintf("arr.%d", reasoning.OutputIndex), item) } // assistant message items diff --git a/internal/translator/codex/gemini/codex_gemini_response.go b/internal/translator/codex/gemini/codex_gemini_response.go index 625b63ce..f533bbdf 100644 --- a/internal/translator/codex/gemini/codex_gemini_response.go +++ b/internal/translator/codex/gemini/codex_gemini_response.go @@ -101,7 +101,7 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR part := []byte(`{"inlineData":{"data":"","mimeType":""}}`) part, _ = sjson.SetBytes(part, "inlineData.data", b64) part, _ = sjson.SetBytes(part, "inlineData.mimeType", mimeType) - template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", part) + template = translatorcommon.SetRawArrayItems(template, "candidates.0.content.parts", [][]byte{part}) return [][]byte{template} } @@ -132,7 +132,7 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR part := []byte(`{"inlineData":{"data":"","mimeType":""}}`) part, _ = sjson.SetBytes(part, "inlineData.data", b64) part, _ = sjson.SetBytes(part, "inlineData.mimeType", mimeType) - template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", part) + template = translatorcommon.SetRawArrayItems(template, "candidates.0.content.parts", [][]byte{part}) return [][]byte{template} } if itemType == "function_call" { @@ -158,7 +158,7 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR } functionCall = setGeminiFunctionCallID(functionCall, itemResult) - template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", functionCall) + template = translatorcommon.SetRawArrayItems(template, "candidates.0.content.parts", [][]byte{functionCall}) template, _ = sjson.SetBytes(template, "candidates.0.finishReason", "STOP") params.LastStorageOutput = append([]byte(nil), template...) @@ -175,12 +175,12 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR } else if typeStr == "response.reasoning_summary_text.delta" { // Handle reasoning/thinking content delta part := []byte(`{"thought":true,"text":""}`) part, _ = sjson.SetBytes(part, "text", rootResult.Get("delta").String()) - template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", part) + template = translatorcommon.SetRawArrayItems(template, "candidates.0.content.parts", [][]byte{part}) } else if typeStr == "response.output_text.delta" { // Handle regular text content delta params.HasOutputTextDelta = true part := []byte(`{"text":""}`) part, _ = sjson.SetBytes(part, "text", rootResult.Get("delta").String()) - template, _ = sjson.SetRawBytes(template, "candidates.0.content.parts.-1", part) + template = translatorcommon.SetRawArrayItems(template, "candidates.0.content.parts", [][]byte{part}) } else if typeStr == "response.output_item.done" { // Fallback: emit final message text when no delta chunks were received itemResult := rootResult.Get("item") if itemResult.Get("type").String() != "message" || params.HasOutputTextDelta { diff --git a/internal/translator/openai/claude/openai_claude_response.go b/internal/translator/openai/claude/openai_claude_response.go index d45e57b0..19c3bcc3 100644 --- a/internal/translator/openai/claude/openai_claude_response.go +++ b/internal/translator/openai/claude/openai_claude_response.go @@ -416,6 +416,7 @@ func convertOpenAINonStreamingToAnthropic(rawJSON []byte) [][]byte { // Process message content and tool calls if choices := root.Get("choices"); choices.Exists() && choices.IsArray() && len(choices.Array()) > 0 { choice := choices.Array()[0] // Take first choice + var contentBlocks [][]byte reasoningNode := choice.Get("message.reasoning_content") for _, reasoningText := range collectOpenAIReasoningTexts(reasoningNode) { @@ -424,14 +425,14 @@ func convertOpenAINonStreamingToAnthropic(rawJSON []byte) [][]byte { } block := []byte(`{"type":"thinking","thinking":""}`) block, _ = sjson.SetBytes(block, "thinking", reasoningText) - out, _ = sjson.SetRawBytes(out, "content.-1", block) + contentBlocks = append(contentBlocks, block) } // Handle text content if content := choice.Get("message.content"); content.Exists() && content.String() != "" { block := []byte(`{"type":"text","text":""}`) block, _ = sjson.SetBytes(block, "text", content.String()) - out, _ = sjson.SetRawBytes(out, "content.-1", block) + contentBlocks = append(contentBlocks, block) } // Handle tool calls @@ -453,11 +454,15 @@ func convertOpenAINonStreamingToAnthropic(rawJSON []byte) [][]byte { toolUseBlock, _ = sjson.SetRawBytes(toolUseBlock, "input", []byte(`{}`)) } - out, _ = sjson.SetRawBytes(out, "content.-1", toolUseBlock) + contentBlocks = append(contentBlocks, toolUseBlock) return true }) } + if len(contentBlocks) > 0 { + out = translatorcommon.SetRawArrayItems(out, "content", contentBlocks) + } + // Set stop reason if finishReason := choice.Get("finish_reason"); finishReason.Exists() { out, _ = sjson.SetBytes(out, "stop_reason", mapOpenAIFinishReasonToAnthropic(finishReason.String())) -- 2.51.2