Skip to content

Chat: show images agents read or produce inline for pi, omp, Claude Code and Codex #992

Closed

Description

@zerodice0

Problem

When an agent reads a screenshot, captures a browser page or generates an image, I want to see the picture in Chat next to what the agent says about it, the way Kitty or Tern show images inline with the output. I use pi, omp, Claude Code and Codex, and I would like this to work the same way for all four. Line numbers below are from main at 8dc1774.

Today it depends on the agent and on where the image sits in the transcript:

Agent Image the user attached Image a tool returned (read, view, screenshot, generate)
pi dropped shown
omp / omo / gjc dropped dropped
Claude Code shown dropped
Codex shown dropped

And for every agent, a Markdown image in an answer (![shot](./out/shot.png)) renders as ! plus a link.

What the code and real transcripts on my PC show:

  • pi and omp user images. They share parseOmpTranscript, whose user branch keeps text only and skips image-only prompts (server/transcript-records.ts:309-317). opensTurn also needs text to start a turn (server/conversation.ts:682-684).
  • omp tool images. toolImages is on only for pi-transcript (server/conversation.ts:903-907) and conversationImage has no omp/omo/gjc branch (server/conversation.ts:1597-1610). Turning the flag on is not enough: real omp image blocks hold data: "blob:sha256:<digest>" with the bytes in ~/.omp/agent/blobs/, while piTranscriptImage decodes data as base64 (server/conversation.ts:1732). Some omp call IDs contain |, which PI_IMAGE_REF rejects (server/conversation.ts:1739).
  • Claude Code tool images. Read of an image file records tool_result.content[] with an inline base64 image block. The parser keeps only the result text (server/conversation.ts:389-399); images are taken from top-level user blocks only (server/conversation.ts:400-406).
  • Codex tool images. view_image, image generation and wrapped exec output record custom_tool_call_output.output[] with input_image data URLs. imageValues reads user records only (server/codex-images.ts:16-28). Generation also writes an item_completed Extension event with the same base64 and a savedPath, so a naive reader would show each generated image twice.
  • Markdown images. Chat provides no MarkdownImageContext (src/components/Markdown.tsx:196-199; src/AGENTS.md documents this). The file viewer already resolves local Markdown images with a stat check and a 20 MiB limit.

Counts from the transcripts I sampled (a handful of sessions per agent, not a census):

Agent Carrier Bytes Seen
omp user image, read/eval tool result blob:sha256: 11 user, 175 tool blocks
omp xd://generate_image details.xdev.inner.images[] blob, plus file paths 4
Claude Code Read tool result inline base64 (duplicated in toolUseResult.file) 6
Codex user input_image data URL 16
Codex custom_tool_call_output.output[] (view_image, image gen, exec_command, write_stdin, apply_patch) data URL 196 blocks in 114 outputs
Codex image gen Extension event base64 + savedPath 19, duplicates of tool output

I had no local pi sessions or omo/gjc stores, so their record shapes come from the existing parser and tests, not real transcripts.

Proposal

One rule for every agent: an image the agent saw or produced appears in Chat as a thumbnail, using the parts that already exist (kind: "image" for user turns, tool.images for a tool row) and the existing GET /api/pane/conversation/image, answered from the pane's bound transcript. #951's viewer opens all of them. No new endpoints or wire fields.

Common to all agents:

  • Refs only in the conversation response, never base64. Bytes are read when a thumbnail is shown; tool images still load only when the work row is open.
  • PNG/JPEG/GIF/WebP only, with the same size cap and byte sniffing Codex already has (8 MiB) applied to every source.
  • Refs are built from the same identity the fetcher accepts, so the parser never emits a ref that cannot be answered.
  • Image-only user turns open a turn for paging in every source.
  • History hidden by /clear, a pi branch switch or a Codex backtrack stays unreachable.
  • A carrier that cannot be resolved stays text, never a broken image.

Per agent:

  • pi: keep user image blocks, image-only prompts included.
  • omp / omo / gjc: keep user image blocks; turn on tool-result images with their own fetcher that reads the linear history and resolves blob:sha256:<hex> against the session's agent blob store with a strict digest check. Inline base64 keeps working.
  • Claude Code: read images inside tool_result.content[] into tool.images, with a ref that addresses the nested block.
  • Codex: read input_image blocks in tool output into tool.images, any tool name, and ignore the Extension copy.
  • All agents, answers: a Chat MarkdownImageContext reusing the file viewer's resolver (stat first, 20 MiB, machine-scoped /api/fs/file, download link when too large). Absolute paths and file:// always; relative paths against the pane's cwd, and a link when no cwd is known. http(s) stays a link (no CSP change). Clicking opens feat(chat): port t3code chat rendering, composer and settings features #951's viewer.

Proposed PRs, each independent:

  1. Shared: ref/fetch alignment, size cap and sniffing for every source, image-only paging.
  2. Claude Code tool-result images.
  3. Codex tool-output images.
  4. pi and omp-family user images, omp blob tool images.
  5. Local Markdown images in answers.

Where

  • Chat and prompt cards
  • Remote PCs

Workarounds and alternatives

  • Open the file: feat(chat): open the files a tool's output and a plain code block name #691 made paths in tool output clickable, so a screenshot can be opened in the file viewer, but it is a separate screen and needs the path to be printed.
  • Attach with @path: user attachments show thumbnails, but that covers my own uploads, not what the agent produced.
  • Use Kitty or Tern: works at the desk, not from the phone, which is why I use the web UI.
  • Kitty graphics in the terminal lens is a different feature (herdr's attach stream and xterm) and is not part of this proposal.

Questions

  1. Is "every agent's native images show in Chat" a direction you want for the project?
  2. Is the PR split above acceptable, or would you rather have one PR per agent including its share of the common part?
  3. For omp, is reading the agent's blob store from the server acceptable, or would you rather omp write inline data?
  4. Should generated images whose bytes live only outside the content array (omp details.xdev.inner.images) be in scope? Codex already puts them in the tool output.
  5. Should local Markdown images show by default or behind a setting?

If the approach is acceptable, I will open the PRs with parser, contract and browser tests.

Activity

  1. changed the title [-]Chat: show agent images inline (omp tool and user images, local Markdown images)[/-] [+]Chat: show images agents read or produce inline for pi, omp, Claude Code and Codex[/+] on Oct 13, 2026
  2. devswha commented on Oct 27, 2026

    @devswha
    Owner

    Closing: all four agents now show their pictures in Chat on main.

    Transcript pictures are served by conversationImage in server/conversation.ts (Codex in server/codex-images.ts), and a Markdown image in an answer renders through MarkdownImage in src/components/Markdown.tsx. omp's blob-store pictures on macOS and Windows still need a descriptor-relative open; that is tracked in #2004. Thanks @zerodice0 for the detailed report.

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

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions