fix(codex): preserve base64 PDF document blocks when converting Claude requests to Codex

Closes: #4765
This commit is contained in:
Luis Pater
2026-08-04 03:07:00 +08:00
parent 4f5ec105b1
commit 44d5e0bebc
2 changed files with 61 additions and 1 deletions

View File

@@ -26,7 +26,7 @@ import (
// The function performs the following transformations:
// 1. Sets up a template with the model name and empty instructions field
// 2. Processes system messages and converts them to developer input content
// 3. Transforms message contents (text, image, tool_use, tool_result) to appropriate formats
// 3. Transforms message contents (text, image, document, tool_use, tool_result) to appropriate formats
// 4. Converts tools declarations to the expected format
// 5. Adds additional configuration parameters for the Codex API
// 6. Maps Claude thinking configuration to Codex reasoning settings
@@ -129,6 +129,12 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
contentItems = append(contentItems, content)
}
appendDocumentContent := func(dataURL string) {
content := []byte(`{"type":"input_file","file_data":"","filename":"document.pdf"}`)
content, _ = sjson.SetBytes(content, "file_data", dataURL)
contentItems = append(contentItems, content)
}
appendReasoningContent := func(part gjson.Result) {
if messageRole != "assistant" {
return
@@ -182,6 +188,22 @@ func ConvertClaudeRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
appendImageContent(dataURL)
}
}
case "document":
sourceResult := messageContentResult.Get("source")
if sourceResult.Get("type").String() != "base64" {
continue
}
mediaType := strings.TrimSpace(sourceResult.Get("media_type").String())
if !strings.EqualFold(mediaType, "application/pdf") {
continue
}
data := sourceResult.Get("data").String()
if data == "" {
data = sourceResult.Get("base64").String()
}
if data != "" {
appendDocumentContent(fmt.Sprintf("data:%s;base64,%s", mediaType, data))
}
case "tool_use":
flushMessage()
functionCallMessage := []byte(`{"type":"function_call"}`)

View File

@@ -508,6 +508,44 @@ func TestConvertClaudeRequestToCodex_AssistantThinkingSignatureToReasoningItem(t
}
}
func TestConvertClaudeRequestToCodex_PreservesBase64PDFDocumentContent(t *testing.T) {
inputJSON := `{
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "before"},
{"type": "document", "source": {"type": "base64", "media_type": "application/pdf", "data": "JVBERi0xLjQK"}},
{"type": "text", "text": "after"}
]
}]
}`
result := ConvertClaudeRequestToCodex("gpt-5.6-sol", []byte(inputJSON), false)
content := gjson.GetBytes(result, "input.0.content").Array()
if len(content) != 3 {
t.Fatalf("got %d content items, want 3. Output: %s", len(content), result)
}
wantTypes := []string{"input_text", "input_file", "input_text"}
for i, wantType := range wantTypes {
if got := content[i].Get("type").String(); got != wantType {
t.Fatalf("content[%d].type = %q, want %q. Output: %s", i, got, wantType, result)
}
}
if got := content[0].Get("text").String(); got != "before" {
t.Fatalf("content[0].text = %q, want %q", got, "before")
}
if got := content[1].Get("file_data").String(); got != "data:application/pdf;base64,JVBERi0xLjQK" {
t.Fatalf("content[1].file_data = %q, want PDF data URL", got)
}
if got := content[1].Get("filename").String(); got != "document.pdf" {
t.Fatalf("content[1].filename = %q, want %q", got, "document.pdf")
}
if got := content[2].Get("text").String(); got != "after" {
t.Fatalf("content[2].text = %q, want %q", got, "after")
}
}
func TestConvertClaudeRequestToCodex_PreservesContentOrderAcrossToolAndReasoningItems(t *testing.T) {
signature := validCodexReasoningSignature()
inputJSON := `{