From 3c6c7b736e9f419fcbd02e0ea03f5da3213526be Mon Sep 17 00:00:00 2001 From: Dmitry Rubtsov Date: Wed, 15 Jul 2026 18:21:00 +0600 Subject: [PATCH] perf(translator): build claude system and messages arrays in a single pass - Replaced per-item `sjson.SetRawBytes(out, "system.-1"/"messages.-1", ...)` appends, which reparsed and rewrote the growing output document on every conversation item, causing O(n^2) work on long histories. - Collected system and message blocks into slices and wrote each array once via `common.JoinRawArray`, moving the cache_control post-processing and system-only fallback turn onto the collected slices. --- .../chat-completions/claude_openai_request.go | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/internal/translator/claude/openai/chat-completions/claude_openai_request.go b/internal/translator/claude/openai/chat-completions/claude_openai_request.go index fb7fb2b8..611df009 100644 --- a/internal/translator/claude/openai/chat-completions/claude_openai_request.go +++ b/internal/translator/claude/openai/chat-completions/claude_openai_request.go @@ -163,39 +163,36 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream // Process messages and transform them to Claude Code format if messages := root.Get("messages"); messages.Exists() && messages.IsArray() { - messageIndex := 0 + systemBlocks := make([][]byte, 0) + messageBlocks := make([][]byte, 0) messages.ForEach(func(_, message gjson.Result) bool { role := message.Get("role").String() contentResult := message.Get("content") switch role { case "system": - systemStart := len(gjson.GetBytes(out, "system").Array()) + systemStart := len(systemBlocks) if contentResult.Exists() && contentResult.Type == gjson.String && contentResult.String() != "" { textPart := []byte(`{"type":"text","text":""}`) textPart, _ = sjson.SetBytes(textPart, "text", contentResult.String()) textPart = common.AttachCacheControl(textPart, message) - out, _ = sjson.SetRawBytes(out, "system.-1", textPart) + systemBlocks = append(systemBlocks, textPart) } else if contentResult.Exists() && contentResult.IsArray() { contentResult.ForEach(func(_, part gjson.Result) bool { if part.Get("type").String() == "text" { textPart := []byte(`{"type":"text","text":""}`) textPart, _ = sjson.SetBytes(textPart, "text", part.Get("text").String()) textPart = common.AttachCacheControl(textPart, part) - out, _ = sjson.SetRawBytes(out, "system.-1", textPart) + systemBlocks = append(systemBlocks, textPart) } return true }) // Message-level cache_control applies to the last system block from this message. if message.Get("cache_control").Exists() { - systemArr := gjson.GetBytes(out, "system").Array() - if len(systemArr) > systemStart { - lastIdx := len(systemArr) - 1 - if !systemArr[lastIdx].Get("cache_control").Exists() { - path := fmt.Sprintf("system.%d", lastIdx) - block := []byte(systemArr[lastIdx].Raw) - block = common.AttachCacheControl(block, message) - out, _ = sjson.SetRawBytes(out, path, block) + if len(systemBlocks) > systemStart { + lastIdx := len(systemBlocks) - 1 + if !gjson.GetBytes(systemBlocks[lastIdx], "cache_control").Exists() { + systemBlocks[lastIdx] = common.AttachCacheControl(systemBlocks[lastIdx], message) } } } @@ -258,8 +255,7 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream } msg = common.AttachMessageCacheControl(msg, message) - out, _ = sjson.SetRawBytes(out, "messages.-1", msg) - messageIndex++ + messageBlocks = append(messageBlocks, msg) case "tool": // Handle tool result messages conversion @@ -276,20 +272,22 @@ func ConvertOpenAIRequestToClaude(modelName string, inputRawJSON []byte, stream msg, _ = sjson.SetBytes(msg, "content.0.content", toolResultContent) } msg = common.AttachMessageCacheControl(msg, message) - out, _ = sjson.SetRawBytes(out, "messages.-1", msg) - messageIndex++ + messageBlocks = append(messageBlocks, msg) } return true }) // Preserve a minimal conversational turn for system-only inputs. // Claude payloads with top-level system instructions but no messages are risky for downstream validation. - if messageIndex == 0 { - system := gjson.GetBytes(out, "system") - if system.Exists() && system.IsArray() && len(system.Array()) > 0 { - fallbackMsg := []byte(`{"role":"user","content":[{"type":"text","text":""}]}`) - out, _ = sjson.SetRawBytes(out, "messages.-1", fallbackMsg) - } + if len(messageBlocks) == 0 && len(systemBlocks) > 0 { + messageBlocks = append(messageBlocks, []byte(`{"role":"user","content":[{"type":"text","text":""}]}`)) + } + + if len(systemBlocks) > 0 { + out, _ = sjson.SetRawBytes(out, "system", common.JoinRawArray(systemBlocks)) + } + if len(messageBlocks) > 0 { + out, _ = sjson.SetRawBytes(out, "messages", common.JoinRawArray(messageBlocks)) } }