来源: ranxianglei/billion-context#488 分析 multimodal payload accounting 时发现(billion-context PR #489 review)
Two silent image-loss gaps in the wire codecs (v0.0.47, dist/wire/index.js). Both drop image content from rebuilt request bodies without any error.
1. openaiToCore: single non-data-URL image gets no sidecar → dropped on re-encode
openaiToCore case "user" (dist/wire/index.js:243-257):
...imgs.length === 1 && firstParsed ? { rawOpenaiContent: imgs[0], imageMediaType: firstParsed.mediaType, imageBase64: firstParsed.base64 }
: imgs.length > 1 ? { rawOpenaiContentParts: imgs } : {}
For a user message containing exactly ONE image whose url is a remote http(s) URL (not a data: URL): firstParsed = parseDataUrl(url) is undefined → first branch false; imgs.length > 1 false → no sidecar field at all. coreToOpenai then rebuilds content from text alone — the image is silently lost from the forwarded request. Data URLs and multi-image messages are preserved; only the single-remote-URL shape drops.
Repro: {messages:[{role:"user",content:[{type:"text",text:"t"},{type:"image_url",image_url:{url:"https://example.com/a.png"}}]}]} through openaiToCore → coreToOpenai: output content has no image part.
Suggested fix: keep the raw part whenever there is exactly one image, splitting out base64 only when it is a data URL:
...imgs.length === 1
? { rawOpenaiContent: imgs[0], ...(firstParsed ? { imageMediaType: firstParsed.mediaType, imageBase64: firstParsed.base64 } : {}) }
: imgs.length > 1 ? { rawOpenaiContentParts: imgs } : {}
coreToOpenai already re-emits rawOpenaiContent verbatim when present (dist/wire/index.js:355-367), so no encode-side change is needed.
2. responsesToCore: image-only user item produces no core message → dropped from rebuilt input
responsesToCore case "message" (dist/wire/index.js:~505-531): the user branch computes effText = messageContent(item.content) where partText returns "" for non-text parts, and only pushes a core message when if (effText) is truthy. A user message item whose content is exclusively input_image parts (no input_text) yields effText === "" → no core message → coreToResponses never sees it → the item (and its images) is absent from the rebuilt input array.
Repro: {input:[{type:"message",role:"user",content:[{type:"input_image",image_url:"data:image/png;base64,AAA="}]}]} through responsesToCore → zero core messages.
Note the contrast with the Anthropic codec, which handles this correctly: anthropicToCore gives image blocks the placeholder text "[image]" plus a rawAnthropicBlock sidecar, and coreToAnthropic re-emits the raw block unconditionally — so image-only Anthropic content survives the round trip.
Fix needs to span both sides of the Responses codec: e.g. push a placeholder-text core message for image-bearing items with no text while keeping rawResponsesItem, and teach coreToResponses to re-emit the raw item verbatim for it (its current verbatim condition messageContent(raw.content) === message.text cannot match an image-only item because its content text is "").
Impact
For billion-context proxy mode (the only consumer that round-trips through these codecs): any OpenAI-chat or Responses client sending images in these shapes has them silently removed from what is forwarded upstream — the model never sees the image, with no error anywhere. A host-side size estimator counting the raw body will overcount relative to what is actually forwarded (conservative direction for fit-gates, but inconsistent with self-heal learning, which measures the rejected forwarded body).
来源: ranxianglei/billion-context#488 分析 multimodal payload accounting 时发现(billion-context PR #489 review)
Two silent image-loss gaps in the wire codecs (v0.0.47, dist/wire/index.js). Both drop image content from rebuilt request bodies without any error.
1. openaiToCore: single non-data-URL image gets no sidecar → dropped on re-encode
openaiToCorecase "user" (dist/wire/index.js:243-257):For a user message containing exactly ONE image whose url is a remote http(s) URL (not a data: URL):
firstParsed = parseDataUrl(url)is undefined → first branch false;imgs.length > 1false → no sidecar field at all.coreToOpenaithen rebuilds content from text alone — the image is silently lost from the forwarded request. Data URLs and multi-image messages are preserved; only the single-remote-URL shape drops.Repro:
{messages:[{role:"user",content:[{type:"text",text:"t"},{type:"image_url",image_url:{url:"https://example.com/a.png"}}]}]}throughopenaiToCore→coreToOpenai: output content has no image part.Suggested fix: keep the raw part whenever there is exactly one image, splitting out base64 only when it is a data URL:
coreToOpenaialready re-emitsrawOpenaiContentverbatim when present (dist/wire/index.js:355-367), so no encode-side change is needed.2. responsesToCore: image-only user item produces no core message → dropped from rebuilt input
responsesToCorecase "message" (dist/wire/index.js:~505-531): the user branch computeseffText = messageContent(item.content)wherepartTextreturns "" for non-text parts, and only pushes a core message whenif (effText)is truthy. A user message item whose content is exclusivelyinput_imageparts (noinput_text) yieldseffText === ""→ no core message →coreToResponsesnever sees it → the item (and its images) is absent from the rebuilt input array.Repro:
{input:[{type:"message",role:"user",content:[{type:"input_image",image_url:"data:image/png;base64,AAA="}]}]}throughresponsesToCore→ zero core messages.Note the contrast with the Anthropic codec, which handles this correctly:
anthropicToCoregives image blocks the placeholder text "[image]" plus arawAnthropicBlocksidecar, andcoreToAnthropicre-emits the raw block unconditionally — so image-only Anthropic content survives the round trip.Fix needs to span both sides of the Responses codec: e.g. push a placeholder-text core message for image-bearing items with no text while keeping
rawResponsesItem, and teachcoreToResponsesto re-emit the raw item verbatim for it (its current verbatim conditionmessageContent(raw.content) === message.textcannot match an image-only item because its content text is "").Impact
For billion-context proxy mode (the only consumer that round-trips through these codecs): any OpenAI-chat or Responses client sending images in these shapes has them silently removed from what is forwarded upstream — the model never sees the image, with no error anywhere. A host-side size estimator counting the raw body will overcount relative to what is actually forwarded (conservative direction for fit-gates, but inconsistent with self-heal learning, which measures the rejected forwarded body).