fix(attachments): preserve original image filenames - #463
Conversation
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the full diff at 320a9a60: an optional name on the image content block, threaded from attachment capture through the wire to the opencode file part and the image preview.
- Schema + wire version —
name: z.string().optional()on theimagevariant;WIRE_PROTOCOL_VERSION77→78 withMIN_COMPATIBLE_WIRE_VERSIONleft at 76. - Both producers updated —
composer-attachments.ts:94takesfile.nameon the browser paste/drop path,:163takesfileNameFromPath(file.path)on the native "Attach" picker. imageBlocksFromwidened — a field-copyingreducebecame a type-guardfilter, so adapters now receive whole image blocks.- opencode forwarding —
filename: image.nameon theFilePartInput. - Preview —
alt={block.name ?? ''}plus atitletooltip, with historical unnamed blocks still rendering. - Tests — new coverage on the wire schema, the opencode prompt dispatch, the native-read attachment (Windows path basename), and the image preview; the command-menu send assertion was updated.
Four things I checked that hold up:
- The
imageBlocksFromwidening leaks nothing into vendor payloads. All three callers build fresh objects rather than spreading the block, so the newly-exposeduri/annotationsgo nowhere:claude-code.ts:765,opencode/adapter.ts:491,pi/adapter.ts:396. codex does not use the helper at all. - The version bump is the right half of Invariant 1. The change is purely additive and an older peer's zod strips the unknown key, so the compatibility floor correctly stays at 76. Nothing outside
wire/message.tshardcodes the version. filenameis metadata-only on the opencode side. Verified against upstreampackages/opencode/src/session/prompt.ts:resolvePartdispatches onurl.protocoland thenmime, never onfilename, so adding it cannot re-route adata:image/*part, and the value is never path-joined or read from disk. Thefile:branch that does resolve paths is unreachable for adata:URL. Worth stating explicitly because the adapter test mockspromptAsyncand so proves argument shape only.- Every new test can actually fail. zod strips unknown keys, so the wire test's
toMatchObjectgenuinely depends on the schema field; the other three use exact deep equality or an accessible-name lookup.
ℹ️ The filename reaches the model on opencode only
Nothing here is wrong, but it is worth being deliberate about where this stops. The name is captured, crosses the wire, and shows in the UI for every agent — yet only opencode passes it to the model. The other three image-capable adapters have no filename slot in their input shapes, so a claude/codex/pi session still cannot tell two attached screenshots apart. If the goal of CODE-599 is the tooltip, this is complete; if it is the model disambiguating attachments, it lands for one of four agents.
Technical details
# Filename delivery is opencode-only
## Affected sites
- `packages/host/agent-adapter/src/native/opencode/adapter.ts:495` — the only adapter that
forwards the name.
- `packages/host/agent-adapter/src/native/claude-code.ts:770` — builds `ClaudeImageBlock`. The
SDK has no slot: `ImageBlockParam` is `{source, type, cache_control?}` and `Base64ImageSource`
is `{data, media_type, type}` (`node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts`).
- `packages/host/agent-adapter/src/native/pi/adapter.ts:396` — `ImageContent` is exactly
`{type, data, mimeType}` (`node_modules/@earendil-works/pi-ai/dist/types.d.ts:236`).
- `packages/host/agent-adapter/src/native/codex/adapter.ts:652` — pushes a bare
`{type: 'image', url: 'data:<mime>;base64,<data>'}`; the live-verified `turn/start` image item
shape has no filename field.
- `packages/integrations/im-render/src/blocks.ts:12` — the one other surface that renders image
blocks, still printing `🖼️ *image (image/png)*` with the mime but not the name.
## Required outcome
- A conscious decision, recorded on the PR or the Linear issue: either opencode-only is the
intended landing for CODE-599, or the remaining adapters need the name carried by some other
means.
## Suggested approach (optional)
- For the three adapters with no filename slot, the only channel is the text part — e.g. naming
the attachments in the prompt text the way a file mention already does. That is a visible
prompt-content change, so it belongs in its own change rather than bolted on here.
## Open questions for the human
- Is opencode-only sufficient for CODE-599, or is a follow-up expected?
- Should the IM renderer use the name when one is present?Claude Opus | 𝕏



Summary
WIRE_PROTOCOL_VERSIONto 78 while leaving the compatibility floor at 76.Linear: CODE-599
Verification
pnpm check:cipnpm test— 346 files passed, 2977 tests passed, 1 skippedicon-dock.pngandicon.png, observed distinct filename tooltips, sent both through the OpenCode transport, and verified the sent images retained their names with no page errorsChecklist
pnpm check:ciandpnpm testboth pass (pluscargo fmt/clippy/testfor Rust changes)WIRE_PROTOCOL_VERSIONis bumped