Repository navigation
feat: egress image downscale + read_original_image (#3400) - #3425
Conversation
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.4121-pr.3425.387ebefThis exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr3425.tgz
npm install -g packageInstall instructions are refreshed on each push. |
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复。 按失败签名/主题检索 open+closed issue 与 PR(image rescale / downscale / read_original_image / image compression):本 PR 是该主题唯一在途实现。相关条目均为已终结的旧链——#3266(38 轮猜→拒→重发病灶,closed)、#3270/#3295(删除链,closed)、#1095/#1211(旧版起源,closed)——以及触发重做的 #3398;无并行重复工作,也不存在同根因的在途修复 PR。 开始审核本 PR(feat: egress image downscale + read_original_image)。已核对:分支 |
(ai: vllm-qwen/qwen3.8-27b) Review 结论:方向正确、实现扎实。初头 4 个红门禁已全部修掉并推送(现头 9e885f6,11/11 命名门禁绿),建议 CI test lanes 全绿后合并。 查重(首条已答):不重复——本 PR 是 #3400 的唯一在途实现;相关条目为已关闭的删除链(#3266→#3270→#3295)、旧起源(#1095/#1211)或触发 issue(#3398)。 结构化八问
测试有效性18 个单测有牙:精确模板正则、对象身份钉死(不变消息同引用)、跨缓存清理的字节相等、Jimp 重解码验尺寸、计数器/日志断言。wire-contract 台账 BASE_EXPECTED 已纳入 read_original_image(初头此处过期,D 车道 5/5 红,已修)。新增持久化 pin 见第 5 问。全量以 CI 为准。 diff 整洁度22 个文件全部与本 PR 目的相关;lockfile churn = jimp 依赖树(预期);tsup 死代码 external:["sharp"] 清理已标注且相关(#3295 遗留)。 CI 状态初头(1bf309e)4 个门禁红(unused-exports / docs-coverage / config-surface-guard / docs-sync),直接卡住全量 test lanes——根因:死导出、yaml seed 缺失、手改生成块、wire-contract 期望过期。现已全部修复:并行会话 3 个 commit(含上述持久化 bug 修复 + 存储隔离)+ 我 3 个 commit(orphan 注释清理 + 1600 常量单源化 bdd1bca、wire-contract 台账 02dfa83、持久化 pin 9e885f6)。新头 9e885f6:11/11 命名门禁绿(version/kernel/docs-coverage/pi-subagents/import-graph/plan/config-surface/lock-sync/lane-guard/unused-exports/docs-sync),全量 test lanes 正在跑,判定以远端 CI 为准(§7.2)。 合并判定建议 CI test lanes 全绿后合并。 auto-merge 不适用(feat + 配置面 + wire 工具面变更);按仓库规则合并由你执行:#3425 另:按标题国际化惯例已将 #3400 标题改为「英文 | 原标题」格式(正文未动)。 |
|
ci失败 |
(ai: vllm-qwen/qwen3.8-27b) 收到,CI 红已在处理中。当前 head(9e885f6)上 4 个 test shard 失败,共 6 处,已逐一定位根因并全部本地复现:
修完本地定向全绿后 push,CI 转绿再回复闭环。 |
Redoes the image pre-compression removed in #3295 in simplified form: on every outbound request (after kernel folding, before protocol encoding, one shared pass over the generic message structure) an image whose longest edge exceeds 1600px (BILI_IMAGE_RESCALE_MAX_DIM) or whose base64 body exceeds 100KB is re-encoded fit-inside (never upscaled, original format kept) on an outbound COPY only - session storage keeps the original bytes byte-exact. A re-encode saving less than 20% of the bytes is discarded (keep-smaller), so small and low-benefit images pass through untouched. Only PNG/JPEG/BMP are handled (pure-JS jimp encoder, zero native dependencies - sharp does not return). Each shrunken image carries a stable annotation beside it reporting current vs original size. Lanes that register bili tools name read_original_image("mNNNNN") in the annotation: proxy-injection lanes get it via the per-protocol manifest lists, plugin lanes (DSH/opencode) via handlePluginManifest; tool-rejecting lanes (codex) get size-only notes and no tool. read_original_image returns the original bytes from local session storage (incomingImageIndex spill files); its results are structurally exempt from re-shrinking - the zero-state loop guard against the #3266 guess-reject-resend disease. Output is deterministic: content-hash encode cache (one encode per unique image), stable note text derived only from dims/bytes/ref - the same history produces byte-identical outbound bodies, so upstream prefix caching is undisturbed (#3266 lesson). Observability: one log line per unique image and one per original-image read (ref, sizes, session cumulative count+bytes); no hard cap. Knobs (config surface): top-level config key imageRescale (boolean, default true), env BILI_IMAGE_RESCALE ("0"/"false" off, live-read), env BILI_IMAGE_RESCALE_MAX_DIM (default 1600, env-only). Documented in CONFIGURATION.md en+zh. Legacy compress.imageCompression.* INERT warnings remain owned by #3344/#3355. Also removes the dead external:["sharp"] leftover from #3295 in tsup.config.ts and adds jimp to the inlined devDependencies list. kernel/** untouched.
(ai: vllm-qwen/qwen3.8-27b) 收到。当前红的那轮(11 个门禁绿、4 个 test shard 红)跑的是修复前的 head——修复还没推上去,所以它必然还是红的。 状态:6 个失败面已全部在本地定位并修完验证中,还剩最后一组断言更新(plugin-fork 的 manifest 工具列表)和一个同族集成缺口(#1897 all-bili demotion 的名字集没登记新工具,会导致插件车道的 title/侧请求把 ~734B×N 的工具 schema 泄漏到上游——这正是该机制要防的 #1897 类问题)。全部本地绿后我会一次性推送并盯 CI 到全绿,到时再回一条完整结论。预计很快。 |
CONFIGURATION.md en+zh: top-level imageRescale key row, detailed section, env quick-table rows and long-form env entries for BILI_IMAGE_RESCALE / BILI_IMAGE_RESCALE_MAX_DIM. CHANGELOG [Unreleased] entry stating the behavior change (since #3295 images passed through byte-identical at original resolution; now large outbound images are sent shrunken by default on an outbound copy only).
IMAGE_RESCALE_DEFAULT_MAX_DIM was dead (knobs.ts carries its own default); rescaleImage and RescaleCtx are module-internal.
The rescale pass reassigned processedMessages in-place, so Prepared (and thus rememberPluginMessages) persisted the rescaled copies plus synthetic note messages instead of the wire originals. Encode from a separate egressMessages local; persistence keeps byte-exact originals (spec: storage stores what the wire received).
The egress image rescale changes outbound digests by design (disclosed behavior change); the storage-parity proof keeps its committed reference shape and stays decoupled from encoder bytes.
…0 default in knobs (#3400) Review fix (follows 31ea189): removing the dead export left its doc comment orphaned above RescaledMime — delete it. The 1600 default now lives next to the knob it defaults (imageRescaleMaxDim) as a file-local const, so the magic number has one source of truth instead of one literal plus a stale comment.
… surface (#3400) Review fix (wire-contract D lanes red on CI): the egress rescale pass defaults ON, so proxy-injection lanes forward read_original_image on every wire. BASE_EXPECTED is derived from the BILI_ACP_TOOLS_* arrays only, which predate the tool — same treatment as acp_rule's #1399 base-surface note.
…e bytes (egress-only shrink) Regression pin for the aliasing leak fixed in 9dd4052: prepare-* once reassigned its local processedMessages to the rescaled array and Prepared returned that local, so rememberPluginMessages() stored shrunken bytes plus synthetic acp_imgnote_* messages in session.transient.pluginMessages — the session storage no longer kept what the wire received (#3400 spec goal 2). Drives one real request through startServer (anthropic lane): asserts the upstream saw the shrunk image while the remembered view keeps the original base64 byte-exact and carries no synthetic note ids.
…acy wire shape in render/cache fidelity suites (#3400)
…nism (#3400) The fixtures carry no images; the wire-economics shift comes from the default-on read_original_image tool schema (~200 tokens/request) moving nudge timing, not from rescaled image bytes.
…e-request.ts addition (#3400, #2170) The #3400 egress rescale adds READ_ORIGINAL_IMAGE_TOOL_NAME to BILI_TOOL_NAMES in src/server/side-request.ts so a session-sidecar request leaking the armed tool set is still demoted by #1897. Per the #2170 lane-guard discipline (lane logic moves with its dedicated suite), test (J) pins the membership next to the post-#3293 precedent.
read_original_image was added to the plugin manifest but the committed snapshots were not regenerated, so wire-contract-golden drifted on CI. Regenerated with scripts/update-wire-contract-goldens.ts; the diff is purely additive: the tool name plus its schema entry on the three wires the manifest carries (anthropic/openai/responses).
c159399 to
b396861
Compare
(ai: vllm-qwen/qwen3.8-27b) 查重: 不适用——本条是 PR #3425 自身的上游冲突处理(系统转发 merge_conflict 通知),未新建任何 issue/PR。 冲突已解决并推送: 八问:
行为变更披露(Deliverables 第 4 条):本 PR 改变既有行为——出站大图(长边 >1600px 或 base64 >100KB)默认降采样后发送(旧行为:#3295 起的字节级透传);原图在会话存储中保持字节级原样, 判定:冲突已清除,分支干净、本地定向验证通过,待 CI 绿后可由人工合并。 |
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.520-master.5107447 # this exact build
npm install -g billion-context@master # always the newest merged build
|
…lout) Bare cpSync of the billion-context package left zod unresolved once #3425's jimp made dist/index.js import it at module load: the lane's self-spawned proxy exited code 1 on every attempt and traffic silently went direct (uncompressed), failing 8 assertions. npm install --no-save of the tarball installs the candidate and its deps while keeping the dsh profile manifest untouched.
…ist-level P0s 8a772ac made the dsh setup npm-install the candidate tarball so its runtime deps (zod, pulled into dist by #3425's jimp) resolve. That is not a gate fix, it is burying the defect: the bare cpSync deliberately reproduces the real thin-install shape (no dependency tree beside the package), which is exactly how production dsh plugin installs ship. If dist/index.js requires an external package at module load, the lane must go red (self-spawned proxy dies, fold/nudge assertions fail) — that is the gate doing its job, not a lane bug to be absorbed. Reverting to the bare copy until the product-side fix (runtime dynamic import of jimp + dist "no static zod" build assertion) lands; after that, this lane doubles as the regression proof.
Gives the two guard helpers real consumers (unused-exports gate) and locks the regex: the four #3425 leak forms are violations, the sanctioned opaque lazy import and zod vendor strings are not.
…xianglei#3511 P0) ranxianglei#3425 inlined jimp (devDependency, noExternal), and jimp imports zod at module scope: 16 STATIC from"zod" imports landed in dist/index.js and dist/agent/pi-native.js. Static imports have no degrade path, so installs shipping the package without its node_modules (dsh profile bare copies, thin pi plugin copies) died with ERR_MODULE_NOT_FOUND and traffic silently went direct, uncompressed. Bundle zod like every other transitive of an inlined dep (~1.4 MB on the two affected entries). The one sanctioned external consumer — opencode V1 host interop, which wants the HOST zod instance and already degrades when unresolvable — keeps its runtime resolution via an opaque specifier, unchanged behavior. New build gate scripts/check-dist-external-zod.mjs fails the build if a static zod import ever reappears; verified a bare dist copy with zero node_modules now runs --version clean.
…d + optional dep + dist-no-zod build gate ranxianglei#3516 P0 (from ranxianglei#3511 final pre-release review): ranxianglei#3425's jimp swap was inlined via noExternal, carrying jimp's transitive zod import verbatim into dist/index.js & dist/agent/pi-native.js (16 static imports each). Installs without a node_modules tree (dsh profile bundles) then die at startup with ERR_MODULE_NOT_FOUND while model traffic silently bypasses compression. - jimp removed from tsup noExternal and added to explicit external (tsup only auto-externalizes dependencies/peerDeps, not optionalDependencies) - src/image-rescale.ts resolves jimp once at first use; a failed resolution is permanent per process and degrades rescaling to pass-through - jimp moved devDependencies -> optionalDependencies (sharp precedent: installed where possible, silently skipped where not) - new build gate scripts/check-dist-no-zod.mjs fails the build on any static zod import in dist (the original offender class, forever)
Closes #3400
一句话
#3295 删掉图片降采样后重做的简化版:发模型前把大图压小(收益 <20% 的不动),原图在会话存储里逐字节原样保留,模型在图片旁被告知两个尺寸后可用 read_original_image 自读原图。kernel 零改动、零原生依赖、无状态。
Behavior change(旧值→新值)
Config surface(新增配置面声明)
imageRescale(boolean,默认 true)——现有面无法表达:feat!: remove image pre-compression and the image_full tool (#3266) #3295 删除的旧键属于「到达时压缩 + restore 状态机」语义,本次是无状态出站 pass,语义不同不复用旧键。BILI_IMAGE_RESCALE("0"/"false"关闭,其余开启,实时读取)与BILI_IMAGE_RESCALE_MAX_DIM(默认 1600,仅环境变量、无文件镜像,与 spec §6 一致)。实现要点
kernel 边界
kernel/** 零 diff(无版本 bump、无 golden ledger/wire-contract 变更、无 Kernel Boundary 流程)。
验收对照(spec 清单 + owner 补充第 8 条)
本地证据(定向)
tests/issue3400-image-rescale.test.ts 17/17;npm run typecheck 干净;npm run build 后 dist/index.js 5.4MB 且零 require("jimp")(jimp 全内联);tests/e2e/e2e-image-billing.test.ts 5/5 冒烟。全量回归以 push 后 CI 为准。