Skip to content

feat(providers): convert OpenAI tool result media like user files - #74

Open
ethanndickson wants to merge 8 commits into
codagt-1430-tool-media-corefrom
codagt-1430-tool-media-openai
Open

ethanndickson wants to merge 8 commits into
codagt-1430-tool-media-corefrom
codagt-1430-tool-media-openai

Conversation

@ethanndickson

@ethanndickson ethanndickson commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

A PDF tool result never reached the model on the OpenAI paths, as the tool media conversion only knew about images (and audio, on Chat Completions). On Responses, a PDF went out as the placeholder "...see the following user message." with no message after it. On Chat Completions (openai, openaicompat, openrouter and vercel), it became a note saying the content can't be displayed.

For Responses, function_call_output.output accepts an array of input_text, input_image and input_file, so we'll now put tool media there, converted the same way as a user file part, followed by the tool's text. That's the same order as the Anthropic tool_result. Before, images went in a synthetic user message after each output, which also landed between the outputs of a parallel tool batch.

Chat Completions tool messages can only hold text, so the media still goes in the user message after the run of tool messages, but that message now uses the user file part converter. I've exported it as openai.FilePartToChatContentPart, so openaicompat can drop its copy. A PDF becomes a file part with the tool's filename. I've also dropped the text/* case from that converter, which sent text files as a file part with bare base64 in file_data. User text files on Chat Completions now get the same unsupported warning that Responses, openrouter and vercel already give, and text tool media gets the note without a special case. This is a change from upstream for user files on openai and openaicompat. Where we can't convert the media, the tool message now keeps the tool's text and adds the note. Upstream sends the text alone, so the model could assume it saw a file that was never sent.

I've checked a PDF and a PNG tool result on Responses, openai Chat and openaicompat Chat through our AI gateway, and since then through chatd on a local coderd, where the model read every file on all three, including from parallel tool calls and with Responses reasoning replayed in the tool loop. I haven't been able to try Bedrock Mantle, which coder/coder uses for non-Claude Bedrock models over Responses, so I'm not sure it accepts the array output. If it doesn't, image tool results there would break where they worked before.

Part of CODAGT-1430

@linear-code

linear-code Bot commented Oct 26, 2026

Copy link
Copy Markdown

CODAGT-1430

@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T09:00:15.059032Z 91401a1 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 4cbcb150a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ethanndickson
ethanndickson marked this pull request as ready for review October 26, 2026 03:39
@ethanndickson
ethanndickson force-pushed the codagt-1430-tool-media-openai branch from 4cbcb15 to 837d14b Compare October 26, 2026 05:21
@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e909cdae2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread providers/openai/tool_messages.go Outdated
The Responses API accepts an array of input_text, input_image and
input_file items as function_call_output. Send media tool results in
that array instead of a synthetic user message after the output. The
media uses the same conversion as a user file part, so PDFs now reach
the model too. A parallel batch no longer has a user message between
its outputs.

Media that cannot be converted becomes a text output that says the
content cannot be displayed, plus a warning.
Move the Chat Completions conversion of a user file part into
openai.FilePartToChatContentPart. The openai and openaicompat user
message paths now call it instead of keeping two copies of the same
switch. Behavior does not change.

The converter also lets tool-result media use the same rules as a user
file part.
toolResultMediaUserPart only knew images and audio, so a PDF from a tool
reached Chat Completions models as a placeholder and a warning. It now
decodes the media and converts it with FilePartToChatContentPart, the
same rules as a user file. A PDF becomes a file part that keeps the
tool's filename, in the deferred user message. openai, openaicompat,
openrouter and vercel all get this through ToolMessages.

When the media cannot be converted and the tool also returned text, the
tool message now keeps the text and adds the "cannot be displayed"
notice after a blank line. Before, the text went out alone, so the
model could assume it saw media that was never sent. Media data that is
not valid base64 now gets a warning and the placeholder instead of
going out as a broken data URL.

The prompttest corpus gains a PDF case and an unsupported-with-text
case.
The user file rules send text/* as a file part with bare base64
file_data and no filename. On the Chat tool path that part reached
openai, openaicompat, openrouter and vercel, and a provider that
rejects it fails the request. The tool result stays in the chat, so
every later request fails too.

Text tool media now gets the "cannot be displayed" placeholder and the
unsupported warning again, as before this stack. PDF, image and audio
still use the shared converter. The user path and
FilePartToChatContentPart do not change.
The Chat Completions converter sent text/* files as a file part with bare base64 file_data. The Responses, OpenRouter and Vercel converters already refuse them, so user text files now get the same unsupported warning, and tool-result text media no longer needs its own exception.
The function_call_output array now holds the media item and then the accompanying text, the same order as the Anthropic tool_result content.
… its text

The notice takes the place of the media, so it now comes first, the same order as media then text in the Anthropic tool_result content. Media without a media type is named "file" in the notice.
… trip

Tool-result media data is already base64, but the Chat and Responses
converters decoded it with FilePart() and then encoded it again. For a
4 MiB image in a Chat request this took 4.4 ms and 21 MB; it now takes
0.5 ms and 5.6 MB, the same as a user image.

Both converters now take a providers/internal/filepart.File and send
its base64 unchanged. A tool-result PDF is no longer read as an OpenAI
file ID; only user file parts can carry one. Invalid base64 goes to the
API unchanged, the same as upstream.
@ethanndickson
ethanndickson force-pushed the codagt-1430-tool-media-openai branch from 7e909cd to 91401a1 Compare October 26, 2026 08:07
@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

1 similar comment
@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 91401a10ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant