Files
deepseek-harness/.agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md
T
Tianyi Cui 3ca9c7d489 rename code-mode to ptc (PTC mode), except session-persistent vocabulary
Rename the tool-presentation transport from code-mode to ptc everywhere
that is not written into session logs: the mode config value becomes 'ptc',
the preset directory/id becomes ptc, the demo becomes demo:ptc, the
dispatch waterfall becomes tools/ptc-dispatch-log (types PtcDispatch*), the
prompt rule becomes tools:ptc-only, source/test files become ptc.ts etc.,
and prose says PTC mode / PTC 模式. The session-persistent vocabulary
(durable events tool/code-dispatch*, logged plugin name tools-code-mode,
sub-call id segment :code:) intentionally stays and moves in the stacked
persistence PR, which is blocked until the SESSION_FORMAT_VERSION v0→v1
migration lands with it. run_code, its code parameter, CodeSdkLanguage,
CodeRunFailedError, the dsh-code-runtime family, third-party codex names,
and frozen archived notes keep their names.
2026-08-27 23:14:31 +08:00

3.8 KiB

Agent Note: A minimal read_image tool over existing seams

Status: implemented

English | 中文

Problem

The multimodal attachment work gave user uploads a complete durable path, but the model itself had no way to inspect an image on disk. read rejects binary content by contract, so an agent asked about a screenshot or rendered chart either failed or used a lossy workaround. A standalone attempt in PR #598 combined the tool with loop-level route scoping, per-route schema visibility, and new session-log concepts. Those features were not required to publish a logged image tool result.

Decision

Both image-reading operations live in dsh-tool-fs and publish ordinary logged tool results over existing extension points.

  • read_image reads a filesystem path. Extension selects the declared PNG/JPEG/WebP/GIF media type; the attachment store's magic-byte and pixel validation stays authoritative. Bytes travel ctx.fs.stat → bounded ctx.fs.readBytesctx.attachments.saveImagefs/observed. The tool result contains metadata and an ImageBlock.
  • FileSystem.readBytes(target, signal, maxBytes) is a new required provider primitive: the byte bound lives at the seam so no backend can buffer an unbounded file, with the stat-size short-circuit and a one-byte-past-cap stream guard against post-stat growth (FS_TOO_LARGE).
  • Registration is composition-conditional, execution is route-gated. The tools register only under ctx.inject(['attachments'], …). Before I/O, the strict gate resolves the calling route through ctx.llm.resolveModelInfo and requires image in inputModalities; unknown capability refuses. A text-only route can still consume prior durable images because the shared LLM runtime projects them to placeholders at request assembly.
  • PTC mode forwards the image out-of-band: a nested dispatch returns the canonical value (execution-local, no image block) and defers a user-role context message carrying the envelope and image, so the picture still reaches the next request.
  • llm-replay models may declare inputModalities, which lets keyless ACP snapshots cover the image-capable result and the text-only refusal.

Alternatives considered

  • PR #598's route-scoped design used a request-ready extension point, per-route schema visibility, reversible projection, and three durable concepts. Shared LLM request projection now handles text-only routes without putting tool registration or session formats into agent-loop.
  • agent.inject() instead of the image-bearing tool result — routes the image around the tool result as a separate injected user message. Rejected: the image is the tool's result; splitting them adds a second logged message with no gain, and the tool-result path already works end to end.
  • Magic-byte sniffing instead of extension declaration — sniffing duplicates detection the attachment store already owns (sharp-backed, authoritative). The extension is only a declaration; a mismatch fails closed with a rename remedy rather than being silently accepted, which also keeps the model's mental map (file name ↔ content) honest.
  • Registering unconditionally and failing on a missing store — rejected; a deployment without an attachment store cannot ever satisfy the tool, so its schema would be a standing lie. The route gate, by contrast, is per-call state and correctly lives at the execution boundary.

Consequences

  • The tools refuse execution on a text-only route, while existing images in session history are represented by request-local placeholders.
  • Repeated image results accumulate request cost until request projection or compaction removes them; content addressing deduplicates durable bytes.
  • The tool-result card renders the durable reference, not pixels; inline preview is deferred to the UI packages.