Client or integration
Codex CLI
Provider or upstream service
openai-compatible, anthropic
OpenCodex version
2.59.0 / commit 134c92a01b120162f00c7275189cc47858720379
Endpoint or capability
message content part translation
Current behaviour
Two inbound parsers lose an attached document before any adapter runs, so no adapter can carry it even to a target that has a representation for it.
| # |
inbound |
caller sent |
what reaches the adapter |
| 1 |
/v1/chat/completions |
file part with inline file_data |
nothing, the part is dropped |
| 2 |
/v1/messages |
document block with base64 bytes |
a text marker [document: title] |
Case 1. src/chat/inbound.ts:75 walks the content parts and recognises three shapes:
if (typeof raw === "string") { ... continue; }
if (!isRec(raw)) continue;
if ((raw.type === "text" || raw.type === "input_text" || raw.type === "output_text") && typeof raw.text === "string") {
blocks.push({ type: "input_text", text: raw.text });
continue;
}
const imageUrl = imageUrlFromPart(raw);
if (imageUrl) { ... continue; }
const videoUrl = videoUrlFromPart(raw);
if (videoUrl) blocks.push({ type: "input_video", video_url: videoUrl });
A {"type": "file", "file": {"filename": ..., "file_data": ...}} part matches none of these. The loop reaches its end with nothing pushed and no else branch, so the part is dropped in silence:
grep -c "file_data" src/chat/inbound.ts
0
Case 2. src/claude/inbound.ts:210 replaces the block with a marker:
case "document":
// No Responses equivalent for raw document blocks; surface the title so the
...
pending.push({ type: "input_text", text: `[document${typeof raw.title === "string" ? `: ${raw.title}` : ""}]` });
The same substitution appears at src/claude/inbound.ts:57 for the other block position.
The marker is a real improvement over dropping the block, which is what #939 fixed, and this report does not ask for that to be reverted. The remaining gap is narrower. For a target that can hold the bytes, the marker is now the only thing sent, and the caller cannot distinguish
"the model read the document" from "the model was told a document existed".
Because both losses happen at the inbound, they apply to every routed target, not only to the ones that cannot represent a document.
Expected behaviour
Case 1. A file part carrying inline file_data should be represented internally rather than dropped, so that adapters targeting a format with a counterpart can forward it. If the part cannot be carried to the selected target, the caller should be told rather than receiving a confident answer about a document the model never saw.
Case 2. Keep the marker for targets that have no representation, and carry the payload where the target does have one. The Chat Completions file part with file_data is the direct counterpart for an Anthropic document block with a base64 source.
Minimal redacted request or reproduction
Case 1:
curl http://127.0.0.1:10100/v1/chat/completions \
-H 'content-type: application/json' \
-H 'authorization: Bearer REDACTED' \
-d '{
"model": "REDACTED_PROVIDER/REDACTED_MODEL",
"messages": [{"role": "user", "content": [
{"type": "file", "file": {"filename": "doc.pdf", "file_data": "data:application/pdf;base64,JVBERi0xLjQK"}}
]}]
}'
Case 2:
curl http://127.0.0.1:10100/v1/messages \
-H 'content-type: application/json' \
-H 'x-api-key: REDACTED' \
-H 'anthropic-version: 2023-06-01' \
-d '{
"model": "REDACTED_PROVIDER/REDACTED_MODEL",
"max_tokens": 64,
"messages": [{"role": "user", "content": [
{"type": "document", "title": "spec",
"source": {"type": "base64", "media_type": "application/pdf", "data": "JVBERi0xLjQK"}}
]}]
}'
Route case 2 to an OpenAI-compatible provider so the translated path is used.
Actual response or error
Case 1, the forwarded request carries a user message with no content derived from the file part.
Case 2, the forwarded request carries the text `[document: spec]` in place of the bytes.
Upstream documentation
https://developers.openai.com/api/reference/resources/chat.md
The Chat Completions content part set includes a file part whose file_data carries inline bytes, at $.messages[*].content[*].file.file_data. That is both the construct dropped in case 1 and the counterpart representation for the Anthropic document block in case 2.
Suggested mapping or implementation notes
Case 1. Add a branch to the content loop in src/chat/inbound.ts for a file part, alongside the existing image and video branches, and carry it on the internal block set.
Case 2. Once an internal representation for inline document bytes exists, the Messages inbound can populate it and keep the marker as the fallback for targets with no counterpart. The decision of which targets qualify is already made per adapter elsewhere, so the marker path does
not need to change for those.
The two cases share a dependency. Both need one internal carrier for inline document bytes, which is why they are filed together.
Additional context and attachments
#939 introduced the [document] marker to replace silent dropping. That was an improvement and this report builds on it rather than reversing it. Nothing was found covering the Chat inbound file part.
Checks
Client or integration
Codex CLI
Provider or upstream service
openai-compatible, anthropic
OpenCodex version
2.59.0 / commit
134c92a01b120162f00c7275189cc47858720379Endpoint or capability
message content part translation
Current behaviour
Two inbound parsers lose an attached document before any adapter runs, so no adapter can carry it even to a target that has a representation for it.
/v1/chat/completionsfilepart with inlinefile_data/v1/messagesdocumentblock with base64 bytes[document: title]Case 1.
src/chat/inbound.ts:75walks the content parts and recognises three shapes:A
{"type": "file", "file": {"filename": ..., "file_data": ...}}part matches none of these. The loop reaches its end with nothing pushed and no else branch, so the part is dropped in silence:Case 2.
src/claude/inbound.ts:210replaces the block with a marker:The same substitution appears at
src/claude/inbound.ts:57for the other block position.The marker is a real improvement over dropping the block, which is what #939 fixed, and this report does not ask for that to be reverted. The remaining gap is narrower. For a target that can hold the bytes, the marker is now the only thing sent, and the caller cannot distinguish
"the model read the document" from "the model was told a document existed".
Because both losses happen at the inbound, they apply to every routed target, not only to the ones that cannot represent a document.
Expected behaviour
Case 1. A
filepart carrying inlinefile_datashould be represented internally rather than dropped, so that adapters targeting a format with a counterpart can forward it. If the part cannot be carried to the selected target, the caller should be told rather than receiving a confident answer about a document the model never saw.Case 2. Keep the marker for targets that have no representation, and carry the payload where the target does have one. The Chat Completions
filepart withfile_datais the direct counterpart for an Anthropicdocumentblock with a base64 source.Minimal redacted request or reproduction
Actual response or error
Upstream documentation
https://developers.openai.com/api/reference/resources/chat.md
The Chat Completions content part set includes a
filepart whosefile_datacarries inline bytes, at$.messages[*].content[*].file.file_data. That is both the construct dropped in case 1 and the counterpart representation for the Anthropicdocumentblock in case 2.Suggested mapping or implementation notes
Case 1. Add a branch to the content loop in
src/chat/inbound.tsfor afilepart, alongside the existing image and video branches, and carry it on the internal block set.Case 2. Once an internal representation for inline document bytes exists, the Messages inbound can populate it and keep the marker as the fallback for targets with no counterpart. The decision of which targets qualify is already made per adapter elsewhere, so the marker path does
not need to change for those.
The two cases share a dependency. Both need one internal carrier for inline document bytes, which is why they are filed together.
Additional context and attachments
#939 introduced the
[document]marker to replace silent dropping. That was an improvement and this report builds on it rather than reversing it. Nothing was found covering the Chat inboundfilepart.Checks