refactor(llm): drop the image content block until a path can honor it

ImageBlock had no production producer and every consumer dropped it:
the deepseek serializer skipped it, the pi-ai converter skipped it as
unrepresentable, the ACP bridge neither advertises image prompt
capability nor forwards image blocks, and compact-basic charged a flat
85-token estimate and rendered an [image] placeholder. A block
constructed today would silently vanish from the wire — the vocabulary
advertised a capability no path honors, the silent-data-loss shape the
defensive patterns warn against. The only constructors were tests
pinning the skip/estimate branches.

Remove ImageBlock and its ContentBlockMap entry (its cache?: CacheHint
field leaves with it; CacheHint itself and the other two cache? fields
are out of scope). compact-basic loses its explicit image estimate and
placeholder arms (the merge-extensible default arms absorb the case);
the deepseek serializer, pi-ai converter, and ACP codec already handled
image in their default arms, so only their image-naming comments
change. The codec's inbound rejection of ACP-protocol image prompt
content stays — that guards wire content a client can send regardless
of our vocabulary.

Tests that constructed harness image blocks to pin the removed branches
are dropped (the 85-token estimate pin) or retargeted onto plugin-added
block types / other non-text blocks, which the surviving default arms
own. Docs, the type-equiv pastes, and the content-block vocabulary
RFC's block list and multimodal-home consequence are updated in the
same change; the RFC moves to implemented/ and the index is
regenerated. A real multimodal feature reintroduces image via
declaration merging together with the adapter mapping, ACP
advertisement, and compaction pricing that honor it.
This commit is contained in:
Tianyi Cui
2026-07-04 17:21:13 +08:00
parent 226a8b5e4c
commit 2745879132
25 files changed
+97 -107

No files matched your search

+4 -13
View File
@@ -44,9 +44,6 @@ export { resolveConfig } from './types.ts'
/** Per-block structural overhead for JSON framing / type tag. */
const BLOCK_OVERHEAD = 4
/** Heuristic token count for an image block (~85 tokens for low-res URL). */
const IMAGE_TOKEN_COST = 85
/** Role-field framing overhead added per message in {@link BasicCompactService.estimateTokens}. */
const ROLE_OVERHEAD = 4
@@ -230,9 +227,6 @@ export class BasicCompactService extends CompactService {
case 'tool-result':
tokens += this.estimateContentTokens(block.content) + BLOCK_OVERHEAD
break
case 'image':
tokens += IMAGE_TOKEN_COST
break
default:
// Unknown block types (merge-extensible ContentBlockMap):
// estimate conservatively via JSON stringify.
@@ -706,10 +700,10 @@ export class BasicCompactService extends CompactService {
/**
* Render content blocks to a single plain-text string for the summarization
* prompt. Text and reasoning contribute their text; every other block type
* contributes a type-tagged placeholder (`[image]`, `[tool-call: name(args)]`,
* …) so the summarizer is told what non-text content existed in the region
* rather than silently losing it. Blocks join with newlines; empty-text
* blocks contribute nothing.
* contributes a type-tagged placeholder (`[tool-call: name(args)]`,
* `[tool-result: …]`, …) so the summarizer is told what non-text content
* existed in the region rather than silently losing it. Blocks join with
* newlines; empty-text blocks contribute nothing.
*/
private _blocksToText(blocks: readonly ContentBlock[]): string {
const parts: string[] = []
@@ -729,9 +723,6 @@ export class BasicCompactService extends CompactService {
parts.push(inner ? `[tool-result: ${inner}]` : '[tool-result]')
break
}
case 'image':
parts.push('[image]')
break
// ContentBlockMap is merge-extensible — render an unknown block as a
// bare type-tagged placeholder so a plugin-added block type is still
// signalled to the summarizer rather than dropped.