Fix "url field must be base64 encoded image" DSH LM Studio error
DSH rejecting a pasted image sent to a local LM Studio with 400 'url' field must be a base64 encoded image. — the base64 data itself is fine; the root cause is a MIME whitelist: strict OpenAI-compatible backends only accept image/png | jpeg | gif data URIs, and webp/bmp/heic are rejected at the protocol layer. Images copied from browsers or web screenshots are mostly webp and trigger this most often; converting the image to PNG/JPEG before sending bypasses it.
What the DSH base64 encoded image 400 looks like
The error is always HTTP 400 "'url' field must be a base64 encoded image.", it fires when an image is pasted into a chat and sent, and only certain formats trigger it. A reporter reproduced it in practice (discussion thread):
- Environment: dsh
0.1.1-rc.2, providerllm-pi-ai(apiopenai-completions, baseURL pointing at LM Studio), modelqwen/qwen3.8-27b(input: [text, image]); - Repro path: configure a local LM Studio vision model → paste a webp image into the chat (most common when copied from a browser or web screenshot) → send and it fails;
- A PNG image on the same configuration goes through directly — unrelated to the model and to base64 encoding, only to the image format.
Root cause: a MIME whitelist check, not a base64 validity check
The reporter verified against the backend directly: the rejection is a MIME whitelist check, not a base64 validity check. Per-format results (source):
| data URI prefix | LM Studio result |
|---|---|
data:image/png;base64,… | accepted (proceeds to engine decoding) |
data:image/jpeg;base64,… | accepted |
data:image/gif;base64,… | accepted |
data:image/webp;base64,… | 400 'url' field must be a base64 encoded image. |
data:image/bmp;base64,… | 400 (same) |
data:image/heic;base64,… | 400 (same) |
DSH supplies correct base64 bytes; only the MIME type is the problem. The chain: dsh-llm-pi-ai's userContent() pushes { type: "image", data: <base64>, mimeType: <original> } to pi-ai, which serializes it to data:<original>;base64,… — any format outside the whitelist fails on every request.
Troubleshooting: confirm it is a format issue first
Send a PNG on the same configuration — if it goes through, it is a MIME issue; do not touch the model config. Step by step:
- Retest with another format: convert the failing image to PNG or JPEG (macOS:
sips; otherwise re-save with a screenshot tool), resend in the same session — passing locks it in as a format problem:
sips -s format png input.webp --out output.png
- Check the image format: right-click → Properties to see it; webp/bmp/heic are all on LM Studio's rejection list. On the command line,
filetells you directly:
file input.webp
- Separate upstream bugs: if the failing image came from DSH's native
read_imagetool, see Fix DSH read_image "cannot get property fs without inject" — a different bug in the same image neighborhood; - Temporary workaround: convert images to PNG/JPEG before sending (local transcoding), or switch to a backend that accepts webp.
Fix direction: transcode into whitelisted formats before sending
The recommended fix transcodes non-image/png|jpeg|gif image content to PNG in the adapter before sending (sharp is already available in the profile dependency tree; sharp(webpBuf).png().toBuffer() verified working). The reporter and the community add details (source):
- Do not fall back to sending the original bytes when transcoding fails: once a route is known to reject webp, a converter failure should remain a typed local failure — sending the original turns a useful decoder/resource error back into the misleading remote 400;
- Keep the original attachment immutable and derive a rendition: key derived renditions by source digest + target MIME, encoder/version, size limits, animation policy, orientation/color rules, and quality;
- Make the animation policy explicit: animated webp/GIF needs an explicit first-frame/all-frame/reject policy — PNG conversion is not semantically neutral;
- Have the route declare supported MIME types: unsupported input should fail before egress when conversion is unavailable or disallowed, instead of handing the user a 400 to guess at.
Notes
- The error mentions base64 but is actually a MIME issue — retest with a PNG first instead of spending time on model configuration.
- Images pasted from browsers or web screenshots trigger this most often (mostly webp); converting to PNG before sending becomes a useful habit.
- Converting animated GIF/webp to PNG loses animation; keep the policy in mind when the vision task cares about frames.
- When debugging image issues, #4612 lives in the same "image path" neighborhood — reading both together saves time.
Sources: Discussion #4615, #4612
FAQ
The base64 data is fine — the problem is a MIME whitelist: strict OpenAI-compatible backends (LM Studio) only accept image/png, jpeg, and gif data URIs; webp/bmp/heic are rejected with 400 at the protocol layer (source).
LM Studio validates the data URI against a MIME whitelist, not base64 validity: png/jpeg/gif pass, webp/bmp/heic always get 400. DSH supplies correct base64 bytes; only the MIME type is the problem.
Convert the image to PNG/JPEG before pasting and sending (screenshot tools and browser save-as offer the format), or transcode locally: on macOS sips -s format png input.webp --out output.png, or in Node use sharp's sharp(webpBuf).png().toBuffer() before attaching.
Both: LM Studio's three-format-only acceptance (png/jpeg/gif) is its protocol limitation; DSH's adapter not negotiating and sending the original MIME as-is is the defect. The fix direction is transcoding into the whitelisted formats before sending (sharp is already available in the profile dependency tree) — that solves both at once.
Sources
- deepseek-harness Discussion #4615: dsh-llm-pi-ai sends non png/jpeg/gif images as-is; LM Studio rejects webp with 400· deepseek-ai (GitHub Discussions)
- deepseek-harness Discussion #4612: native read_image tool fails with "cannot get property 'fs' without inject" (same image path)· deepseek-ai (GitHub Discussions)