imageBlockFor
imageBlockFor(
provider,mimeType,data): {data:string;mime_type:string;source_type:"base64";type:"image"; } | {image_url:string;source?:undefined;type:"image_url"; } | {image_url: {url:string; };source?:undefined;type:"image_url"; } | {image_url?:undefined;source: {data:string;media_type:string;type:"base64"; };type:"image"; }
Defined in: _worktrees/docs-release/gaunt-sloth/packages/agent/src/middleware/frontendImageInjectionMiddleware.ts:121
A vision content block the target provider’s @langchain converter actually decodes. Verified
against the installed converters (RC-21):
- ollama →
{ type:'image_url', image_url:'<data-URL string>' }. ChatOllama’sconvertToOllamaMessagesonly handlesimage_urlblocks (extractBase64FromDataUrl); the LangChain standardsource_typeblock throws “Unsupported content type: image”. image_url-consuming converters (openai,openrouter,deepseek,xai,groq,huggingface,xai-responses) →{ type:'image_url', image_url:{ url:'<data-URL>' } }. The grouping is by what the converter CONSUMES, not by client family: the first six are served by an OpenAI-compatible Chat Completions API whichever LangChain class fronts them, whilexai-responsesreaches the same block by a different route entirely (below). This native OpenAI shape is correct on BOTH the Completions API AND the Responses API (GS2-74 flips reasoning-capable openai models to Responses). A rawsource_typestandard block serialises to an invalid image part on the Responses path, so we emit the provider-native shape rather than lean on@langchain/core’s (deprecated, internal) auto-conversion.huggingface(CFG-45) belongs here by MEASUREMENT, not by “it constructs aChatOpenAI”: a fetch-capture probe of the installed@langchain/openai(no network — the client’sfetchwas stubbed), built exactly asproviders/huggingface.tsbuilds it, showed the request going tohttps://router.huggingface.co/v1/chat/completions— the Completions path, never Responses, since nothing on the HF path setsuseResponsesApiand the library’s own model-name flip matches only OpenAI-specific ids — andopenai@7.6.0’sChatCompletionContentPartunion accepts exactly one image part there:image_url:{url}. There is noinput_imagepart on that endpoint, so the OpenAI Responses shape does not apply. The same probe showed the standard block ALSO arriving asimage_url:{url}on the Completions path — but only because@langchain/core’sconvertToProviderContentBlock(now@deprecated: “Don’t use data content blocks”) rewrites it, which is precisely the auto-conversion the through-line below refuses to depend on, and which yields a bare, invalidimage_urlpart the moment the same block reaches a Responses endpoint.xai-responses(CFG-45) is the same emitted block for an UNRELATED reason, and it is the case that shows why this switch exists. It isChatXAIResponses._llmType(), reachable only viaresolveVisionProvider’s_llmType()fallback (gth’s ownxaiprovider buildsChatXAI), and that class extendsBaseChatModeldirectly with its OWN converter — no OpenAI client and no@langchain/coredata-block conversion anywhere on the path. Its human-message branch (@langchain/xai@1.4.10dist/converters/responses.js) recognises exactlytextandimage_urland rewrites every other part to{ type:'input_text', text:'' }. AglobalThis.fetch-capture probe (no network — that class calls the global fetch directly and accepts no injectable client) measured four blocks againsthttps://api.x.ai/v1/responses: the standard base64 block arrived as{"type":"input_text","text":""}— the image SILENTLY DESTROYED in-process, never rejected — whileimage_url:{url}arrived as the vendor’s declared{"type":"input_image","image_url":"data:…","detail":"auto"}. Emitting the wire shape{ type:'input_image', … }from here was measured too and is destroyed identically: the translation is the converter’s to make, so pre-empting it defeats it. Not establishable offline, and stated rather than assumed: whetherapi.x.aiaccepts adata:URL ininput_image.image_url, whose vendor type documents it as a public URL. The ruling does not rest on that — the standard block is provably destroyed before any request is built, while this block provably survives into the vendor’s own declared image item.- anthropic → the provider-native block
{ type:'image', source:{ type:'base64', media_type, data } }. The LangChain standard block is NOT usable here (RC-32): in@langchain/anthropic’s_formatContentBlocks, theisDataContentBlockbranch yields its conversion and then FALLS THROUGH — nocontinue— into the chain below, wheretype === 'image'matches the very same block and yields a SECOND one whosemedia_typeis read from camelCasemimeType(a key the snake_case standard block never has) and so defaults to the literalimage/jpeg. Every frame is therefore sent twice, and a non-JPEG capture 400s outright on the mislabelled copy. The native block is recognised earlier by_isAnthropicImageBlockParamand passed through untouched, exactly once. - Gemini (
google-genai,vertexai, and thegooglelabel both of them report from_llmType()) → the LangChain standard base64 data content block{ type:'image', source_type:'base64', mime_type, data }, which those native converters decode directly. - anything else → the same standard block, as a last-resort fallback rather than as a
Gemini alias (see the
defaultarm below).
The through-line: emit what the target provider’s converter consumes natively rather than lean on a generic auto-conversion — the same lesson as GS2-75 on the OpenAI Responses path.
Exported so each provider branch can be unit-tested directly; its only effect beyond the returned block is one debug-log line on the fallback arm.
Parameters
Section titled “Parameters”provider
Section titled “provider”string
mimeType
Section titled “mimeType”string
string
Returns
Section titled “Returns”{ data: string; mime_type: string; source_type: "base64"; type: "image"; } | { image_url: string; source?: undefined; type: "image_url"; } | { image_url: { url: string; }; source?: undefined; type: "image_url"; } | { image_url?: undefined; source: { data: string; media_type: string; type: "base64"; }; type: "image"; }