Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions internal/openai/responses_compat.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,58 @@ func sanitizeInputItem(item map[string]any) map[string]any {
out := clone(item)
delete(out, "internal_chat_message_metadata_passthrough")
delete(out, "phase")
if content, exists := out["content"]; exists && String(out, "type", "") == "message" {
out["content"] = sanitizeMessageContent(content)
}
return out
}

func sanitizeMessageContent(content any) any {
parts, ok := content.([]any)
if !ok {
return content
}
out := make([]any, 0, len(parts))
for _, raw := range parts {
part, ok := raw.(map[string]any)
if !ok {
out = append(out, raw)
continue
}
clean := clone(part)
switch String(clean, "type", "") {
case "output_text":
clean["type"] = "input_text"
case "image":
if image := anthropicImageContent(clean); image != nil {
clean = image
}
}
out = append(out, clean)
}
return out
}

func anthropicImageContent(part map[string]any) map[string]any {
source, ok := part["source"].(map[string]any)
if !ok {
return nil
}
var url string
switch String(source, "type", "") {
case "url":
url = String(source, "url", "")
case "base64":
url = "data:" + String(source, "media_type", "") + ";base64," + String(source, "data", "")
Comment on lines +246 to +247

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When source type is base64, if data or media_type is empty or missing, url will be constructed as an invalid data URL (e.g., "data:;base64,"), which is not empty and thus bypasses the url == "" check. This will result in sending an invalid image payload upstream. We should validate that both data and media_type are non-empty before constructing the data URL.

Suggested change
case "base64":
url = "data:" + String(source, "media_type", "") + ";base64," + String(source, "data", "")
case "base64":
data := String(source, "data", "")
mediaType := String(source, "media_type", "")
if data == "" || mediaType == "" {
return nil
}
url = "data:" + mediaType + ";base64," + data

default:
return nil
}
if url == "" {
return nil
}
return map[string]any{"type": "input_image", "image_url": url, "detail": "auto"}
}

func hasReasoningText(item map[string]any) bool {
for _, key := range []string{"summary", "content"} {
if parts, ok := item[key].([]any); ok && len(parts) > 0 {
Expand Down
65 changes: 65 additions & 0 deletions internal/openai/responses_compat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,71 @@ func TestPrepareCompatibleResponsesDropsUnknownInput(t *testing.T) {
}
}

func TestPrepareCompatibleResponsesRewritesAssistantOutputTextHistory(t *testing.T) {
wire, _, err := PrepareCompatibleResponses(map[string]any{
"model": "grok-4.5",
"input": []any{
map[string]any{
"type": "message", "role": "assistant", "id": "msg_1", "status": "completed",
"content": []any{map[string]any{"type": "output_text", "text": "first response"}},
},
map[string]any{"type": "message", "role": "user", "content": []any{map[string]any{"type": "input_text", "text": "continue"}}},
},
})
if err != nil {
t.Fatal(err)
}
input := wire["input"].([]any)
message := input[0].(map[string]any)
content := message["content"].([]any)
part := content[0].(map[string]any)
if part["type"] != "input_text" || part["text"] != "first response" {
t.Fatalf("assistant history content = %#v", content)
}
}

func TestPrepareCompatibleResponsesDoesNotAddMissingMessageContent(t *testing.T) {
wire, _, err := PrepareCompatibleResponses(map[string]any{
"model": "grok-4.5",
"input": []any{
map[string]any{"type": "message", "role": "assistant", "tool_calls": []any{map[string]any{"id": "call_1", "type": "function"}}},
},
})
if err != nil {
t.Fatal(err)
}
input := wire["input"].([]any)
message := input[0].(map[string]any)
if _, exists := message["content"]; exists {
t.Fatalf("missing content was added: %#v", message)
}
}

func TestPrepareCompatibleResponsesRewritesAnthropicImageContent(t *testing.T) {
wire, _, err := PrepareCompatibleResponses(map[string]any{
"model": "grok-4.5",
"input": []any{
map[string]any{
"type": "message", "role": "user",
"content": []any{
map[string]any{"type": "input_text", "text": "describe"},
map[string]any{"type": "image", "source": map[string]any{"type": "base64", "media_type": "image/png", "data": "AAAA"}},
},
},
},
})
if err != nil {
t.Fatal(err)
}
input := wire["input"].([]any)
message := input[0].(map[string]any)
content := message["content"].([]any)
image := content[1].(map[string]any)
if image["type"] != "input_image" || image["image_url"] != "data:image/png;base64,AAAA" {
t.Fatalf("image content = %#v", image)
}
}

func TestPrepareCompatibleResponsesNormalizesCodexHostedTools(t *testing.T) {
wire, _, err := PrepareCompatibleResponses(map[string]any{
"model": "grok-4.5", "input": "hello",
Expand Down
Loading