zot/packages/provider/openai_codex_test.go
patriceckhart ecb3b022cc fix(provider): deliver tool-result images to the OpenAI Responses route
On the openai-codex (Responses API) route a tool result serialized to a
string-only function_call_output, dropping ImageBlock content, and the
agent loop's tool-image mirror only fired for provider "openai". So
images returned by read reached the TUI but never the model, which then
correctly reported it received no image content.

Extend the mirror to fire for "openai-codex" too (its client already
serializes user-message images as input_image, so the bytes arrive),
and have the codex tool-result serializer emit a short placeholder for
an image-only result instead of an empty output the API may reject.
Adds a test covering both behaviors.
2026-05-29 14:21:51 +02:00

63 lines
1.9 KiB
Go

package provider
import (
"strings"
"testing"
)
// An image-only tool result must not serialize to an empty
// function_call_output (the Responses API may reject it) and a
// following user-message image must serialize as input_image so the
// model actually receives the bytes.
func TestCodexImageToolResultMirror(t *testing.T) {
c := NewOpenAICodex("token", "acct", "").(*codexClient)
wire, err := c.buildRequest(Request{
Model: "gpt-5.5",
Messages: []Message{
{Role: RoleUser, Content: []Content{TextBlock{Text: "look at this"}}},
{Role: RoleAssistant, Content: []Content{
ToolCallBlock{ID: "call_1", Name: "read", Arguments: []byte(`{"path":"x.png"}`)},
}},
{Role: RoleTool, Content: []Content{
ToolResultBlock{CallID: "call_1", Content: []Content{
ImageBlock{MimeType: "image/png", Data: []byte("png-bytes")},
}},
}},
// The agent loop appends this mirror after an image tool result.
{Role: RoleUser, Content: []Content{
TextBlock{Text: "Tool output included the following image content:"},
ImageBlock{MimeType: "image/png", Data: []byte("png-bytes")},
}},
},
})
if err != nil {
t.Fatal(err)
}
var sawFnOutput, sawInputImage bool
for _, item := range wire.Input {
switch v := item.(type) {
case codexFunctionCallOutput:
sawFnOutput = true
if strings.TrimSpace(v.Output) == "" {
t.Fatalf("image-only tool result produced empty function_call_output")
}
if !strings.Contains(strings.ToLower(v.Output), "image") {
t.Fatalf("placeholder should mention image, got %q", v.Output)
}
case codexInputMessage:
for _, ct := range v.Content {
if img, ok := ct.(codexInputImage); ok && img.Type == "input_image" {
sawInputImage = true
}
}
}
}
if !sawFnOutput {
t.Fatalf("no function_call_output emitted")
}
if !sawInputImage {
t.Fatalf("mirrored user image was not serialized as input_image")
}
}