Skip to content

支持图片压缩,参考billion-context的对图片支持原理 #478

Closed

Description

@ranxianglei

支持图片压缩,参考billion-context的对图片支持原理

大概是图片不进入压缩,但是会dump到一个文件,使用的时候模型自己重新read

Activity

  1. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    [bot] 🏷 查重:不重复(issue/PR 全量检索「图片 / image / attachment / billion-context」均无既有条目)。

    按功能类流程先给评估结论,方向确认后开工。已核实两侧代码:

    现状(opencode-acp 侧,已验证)

    • 图片以 FilePart({type:"file", mime:"image/*", url:"data:…"})随消息进入 transform hook;
    • lib/token-utils.ts 只统计 text / tool / reasoning —— 图片对 ACP 的 token 账本完全不可见;
    • 含图消息被折叠后,FilePart 随原消息一起消失,摘要里没有任何关于这张图的记录——模型不知道它存在过,更不知道去哪找回;
    • 目前唯一恢复路径是 decompress 还原原消息(含 FilePart),但依赖 OpenCode 存储还留着原消息;一旦原生 compaction / 消息被删,图片永久丢失。

    billion-context 的实际原理(读源码确认)

    参考实现是 billion-context/src/image-restore.ts(#1995/#2607/#2640),机制是:

    1. 落盘存档:每次收到请求时扫描全部已知消息的图片字节,幂等写到 <stateDir>/retrieve/img/<sessionId>/mNNNNN[-i]-<内容hash8>.<ext>(0600,skip-if-exists;文件名带内容哈希盐防 ref 复用串号);
    2. 索引只存元数据+路径(不存 base64),长会话内存占用 O(refs);
    3. 折叠照折:带图消息正常进入压缩,像素不参与摘要(不可恢复性由落盘兜底);
    4. file-first 恢复:恢复渠道就是给模型一个宿主可读路径,模型用自己的 read 工具打开——不需要新工具、不需要按协议重建内联图片;
    5. TTL 7 天清理;旧版滑动窗口 stripImages 已被移除(每轮破坏 prompt cache,#2640 注释明确记载)。

    注:内核里的 image-compress.ts(#353,到达时降采样 + image_full 恢复工具)是另一个特性(有损降采样),不是本 issue 要的东西。

    方案对比

    A:字面实现(图片消息硬排除出压缩范围 + 指针替换) B:移植 BC 现行原理(推荐)
    语义 图片消息永不折叠,常驻上下文 像素永不进摘要;消息可折叠,摘要里留文件指针行
    驻留增长 每张图一条消息永久常驻 → 破坏亚线性驻留(论文核心性质) 无新增常驻,指针行随块衰减
    回归面 硬排除与 Bug-39 protected-tools 边界机制交互,面大 只在「range 含已存档图 && 发生折叠」时生效
    缓存 延迟换指针:每图一次前缀变更(可接受) 仅新生成摘要多一行(与压缩事件同量级)
    收益 同左 同左 + 长会话图片堆积不再永久占位

    推荐 B:owner 说「参考 billion-context 的原理」,BC 当前的生产原理就是 B(fold + archive + file-first);A 的「永不压缩」会让马拉松会话里几十张截图变成永久常驻区,和这个项目的亚线性驻留设计直接冲突。B 同样满足「图片不进入压缩」的本意——像素从不被有损摘要,只被无损归档。

    v1 范围(不含「老图提前卸载」——把还没折叠的老图主动换成指针省 token:UX 风险是模型任务中途失去直接视图,且 BC 在滑动窗口变体上踩过缓存坑;token 释放放在折叠时刻,与今天静默丢失的时刻相同,严格更优。如需要可做 v2 配置开关)。

    结构化八问(决策请求)

    1. 是什么问题:长会话里的截图/图片每张固定占 token(provider 端单张上限约 1.6K),被压缩折叠后静默永久消失——摘要不记录其存在,会话内没来得及 decompress 就再也看不到了。
    2. 什么原因导致的:ACP 全流水线对图片 FilePart 不可见——token 计数忽略它(lib/token-utils.ts)、折叠直接丢弃、摘要生成没有引用图片的通道。BC 已用「落盘存档 + file-first 恢复」解决同一问题,opencode-acp 未移植。
    3. 影响范围与严重程度(分选项):都只影响含图消息的会话,纯文本会话零变化。严重度 = 数据丢失类(折叠后图片不可恢复)+ 体验瑕疵(模型不知图存在过),不阻塞功能。A 额外引入永久驻留增长;B 无新增常驻开销。
    4. 怎么修复的(推荐 B):① 每轮 transform 扫描未存档的图片 FilePart(data: URL),解码写入 storage/plugin/acp/images/<sessionSafe>/mNNNNN[-i]-<sha8>.<ext>(0600、skip-if-exists、内容哈希盐命名);② 折叠完成时若 range 含已存档图,向块摘要的 protected-content 附录追加指针行 [Archived image: m00042 (png, 1.2MB) → <绝对路径> — use read tool to view];③ state 只记元数据+路径,compaction reset 时清空(ref 会重发号);④ TTL 清理。不加新工具——模型用自己的 read 重读(OpenCode read 支持图片输入),即「使用的时候模型自己重新 read」。
    5. 回归风险(分选项):A = 硬排除新增永久常驻区 + 与既有边界机制交互,面大;B = 只在「含已存档图的 range 被折叠」时生效,IO 失败降级为 warn 不阻断请求(BC 同款姿态),state 新字段可选、旧持久化状态向后兼容,quality gate / nudge / GC 路径不动。
    6. 是否破坏缓存:B 不改任何既有输出格式,指针行只出现在新生成的摘要里(一次性前缀变更,与压缩事件本身同量级)。A 的即时换指针变体每轮破缓存——BC 实测后移除,否决。
    7. 是否改了设计方向:不改。纯增量新模块(拟 lib/compress/image-archive.ts),不动既有数据流;SessionState 加可选字段;存储加 images/ 子树。
    8. 是否引入新配置:compress.images:enabled(默认 true,false = 行为与现状逐字节一致)、minBytes(默认 16384,小图不落盘避免垃圾文件)、ttlDays(默认 7)。无需迁移。

    待拍板

    • ① 方向:B(推荐)还是 A?
    • ② 默认值:enabled=true(推荐,纯增量)还是保守 off-by-default?
    • ③ v1 是否要「老图提前卸载」:建议不要,v2 再说。

    确认后交付:分支 + devlog(REQ/WORKLOG/DESIGN,涉及新模块与存储布局需 DESIGN)+ 实现 + 定向测试(命名/幂等/state 合并/折叠注入/disabled 零差异电池)+ 双 agent review + PR。远端 http(s) URL 图片与非 image mime 不在 v1 范围(DESIGN 中注明)。

  2. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    OwnerAuthor

    B 开搞

  3. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    已完成,PR: #479 (Closes #478,等 CI 与双 agent review)

    按 B 方案实现:图片不进摘要、落盘成文件、模型自己重读。

    八问

    1. 是什么问题:带图片(data-URL 附件)的消息被折叠时,像素随原消息一起被丢弃,摘要里不留任何痕迹——OpenCode 存储一旦清掉原件就永久找不回。
    2. 什么原因导致的:FilePart 只活在原消息里;折叠用块摘要替换原消息,整条管线没有任何环节为图片留痕。文本靠摘要存活,图片没有等价物。
    3. 影响范围与严重程度:所有含图片附件且发生折叠的会话。严重度=信息丢失而非功能损坏——会话照常工作,只是图没了。
    4. 怎么修复的:移植 billion-context #1995/#2640 原理。① 索引期即落盘:每轮 transform 在 assignMessageRefs 后把符合条件的图片(data URL、二进制 mime、≥minBytes)解码写到 <storageDir>/images/<dirName(sessionId)>/(0600,文件名含内容哈希,skip-if-exists 安全);② 折叠时摘要链最后一步追加指针附录 - m00042: /abs/path.png (image/png, 24576 bytes),模型用自己的文件工具重读;③ TTL 清扫(默认 7 天)每进程每会话一次,best-effort。不加新工具、不动 system prompt。
    5. 是否有回归问题:逐项审过既有行为——禁用开关下字节级等同现状;质量门看到的附录与其他 protected-content 附录同类;state 文件新增字段全部可选,旧状态文件原样加载;索引按 raw message ID 建键,不耦合压缩重置/ref 重发。定向回归 105/105 绿(compress-range/config-validation/compress-state/e2e×2),全量交 CI。
    6. 是否破坏缓存:否。归档全程 off-wire,不改 prompt-cache 策略;附录只出现在新生成的块摘要里(那本来就是换缓存的事件)。
    7. 是否改了设计方向:无转向。纯增量能力,走 BC 已验证的 file-first recovery 路线。v1 明确不做:主动卸载未折叠的老图(会剥夺模型任务中的直接视图)、远程/file URL、非图片 mime、有损缩放。
    8. 是否引入了新的配置:是。compress.images { enabled=true, minBytes=16384, ttlDays=7 },仅顶层 compress 节点(不参与按模型级联——归档位置是会话级的)。无需迁移,默认值开箱生效;enabled:false 完全回退旧行为。

    行为变更披露:默认开启后,含归档图片的折叠块摘要会比以前多几行路径附录;磁盘新增 images/ 目录(TTL 自动清理)。

    测试:新增 tests/image-archive.test.ts 8 例(落盘权限/去重/skip 矩阵/TTL 语义/指针渲染)。devlog REQ+WORKLOG+DESIGN 齐备,check-pr.sh 通过。

  4. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    OwnerAuthor

    🤖 Powered by ework · qwen3.8-27b

    CI 全绿:pr-validation / build / test(22) / test(24) / build-artifact / e2e 全部 success。PR #479 可以进入双 agent review → 人工合并流程。

  5. added 2 commits that reference this issue on Oct 15, 2026
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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions