Repository navigation
feat: image archive — folded ranges keep re-readable image pointers instead of dropping pixels (#478) - #479
Conversation
…read pointers into folded summaries (#478) Images attached as data-URL file parts were silently dropped when their range was folded, with no recovery path once OpenCode storage lost the originals. Port the billion-context #1995/#2640 principle: - archiveSessionImages(): per-turn pass after assignMessageRefs spills qualifying images (data URL, binary mime, >= minBytes) to <storageDir>/images/<dirName(sessionId)>/ as 0600 files named <sha8(rawId)>-p<i>-<sha8(content)>.<ext>; metadata index persisted on SessionState.imageArchive keyed by raw message ID (survives ref re-issue); never throws. - appendArchivedImagePointers(): last step of the range-mode summary chain appends absolute-path pointer lines for archived images covered by the folded range; model re-reads pixels with its own file tools. - pruneImageArchive(): best-effort TTL sweep once per process per session. - Config: compress.images { enabled=true, minBytes=16384, ttlDays=7 }, excluded from the per-model override cascade; disabled = exact status quo. Schema + validation + docs updated. Closes #478
📦 Built Plugin ArtifactBranch: Option A — Install from npm PR tag (recommended)opencode plugin opencode-acp@pr-479 --globalEach push to this PR publishes a new version under the Option B — Install from GitHubopencode plugin "github:ranxianglei/opencode-acp#2026-10-15_image-archive" --globalOption C — Download artifact
tar xzf opencode-acp-pr479.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
This comment is automatically updated on each push. |
Review hardening for the image-archive feature: the documented contract
('excluded from the per-model override cascade') only held at the type
level. resolveCompressOverrides spreads whatever user JSON sits under
compress.providers/<p> or .models/<m>, so an images entry there reached
applyCompressOverrides and was blanket-applied as a PARTIAL object —
replacing the global images config wholesale (minBytes undefined disables
the size filter, ttlDays undefined makes the TTL cutoff NaN).
Add 'images' to OVERRIDE_BLANKET_APPLY_EXCLUDE (widening its element type,
since CompressModelOverrides Omits it) plus a regression test covering both
the runtime no-leak behavior and the existing validation rejection of the
misplacement. No behavior change for valid configs.
[bot] 🏷 Review 完成。结论:可合入——审核中发现一个真实缺陷(「images 排除在级联之外」只在类型层生效,运行时可被绕过),已直接推修复 commit 到 PR 分支( 结构化八问
其他审核记录
判定:可合入。 |
Completes the AGENTS.md 5.4.2 artifacts the one-click workflow's fallback PR
does not generate: CHANGELOG.md / CHANGELOG.zh-CN.md entries and
devlog/2026-10-15_release-v1.18.4/{REQ,WORKLOG}.md for the image-archive
feature shipped since v1.18.3. Version bump itself already landed via the
workflow's Bump step.
What & why
Images attached as data-URL file parts were silently dropped when their range was folded: the summary carried no record of them and the pixels were unrecoverable once OpenCode storage lost the originals. This ports the billion-context #1995/#2640 principle per issue #478: images are not summarized — they are dumped to a file that the model re-reads with its own tools.
How
lib/compress/image-archive.ts, new): a per-turn pass afterassignMessageRefsdecodes qualifying images (data:URL, binary mime png/jpeg/gif/webp/avif/bmp — SVG excluded as scriptable text, size ≥minBytes) into<storageDir>/images/<dirName(sessionId)>/as mode-0600files named<sha8(rawMsgId)>-p<i>-<sha8(content)>.<ext>(content-salted so skip-if-exists is safe across ref re-issue). A metadata-only index (path/mediaType/bytes, keyed by raw message ID) persists onSessionState.imageArchive; it survives compaction ref re-issue without clearing.lib/compress/range.ts): last step of the summary chain appends- <ref>: <abs path> (<mime>, <bytes>)lines for archived images covered by the folded range. No new tool, no system-prompt surface — the model re-reads pixels with its existing file tools.ttlDays, once per process per session, never blocking a request. All archive I/O failures are warn-logged, never thrown.Behavior changes (disclosed)
<storageDir>/images/<dirName(sid)>/(0600), TTL-pruned after 7 days defaultimageArchiveindex + optionalstats.imagesArchived/imageBytesArchived; old state files load unchangedcompress.images { enabled=true, minBytes=16384, ttlDays=7 }, top-levelcompressnode only (excluded from per-model override cascade — archive location is session-scoped)enabled: falserestores pre-change behavior exactly (no disk writes, no appendix). No changes to internaldcp-*tags, block schema, or prompt-cache strategy.Tests
tests/image-archive.test.ts: 8 unit tests (parse/ext/dir-name helpers; spill permissions/dedup/skip matrix; TTL prune semantics incl. once-per-state guard; pointer rendering incl. ref resolution).scripts/ci/check-pr.shpasses.Closes #478