Skip to content

inline document bytes do not survive the inbound parse #5212

Description

@Android-Login-Analysis

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

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions