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.
This commit is contained in:
Dmitry Rubtsov
2026-07-15 18:21:00 +06:00
parent 613255e0d0
commit 3c6c7b736e
@@ -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))
}
}