Skip to content

fix: count + warn about OpenAI file parts dropped by the wire codec (#1205) - #1221

Merged
ranxianglei merged 1 commit into
masterfrom
2026-09-24_fix-1205-file-part-drop
Sep 24, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-09-24_fix-1205-file-part-drop

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Part of #1205 — the in-repo portion only. This does NOT close #1205: the actual part drop happens inside acp-kernel's wire codec (openaiToCore keeps only text parts + image_url on user messages), so DeepSeek Files API attachments ({"type":"file","file_id":"file-api-…"}) still vanish on every request until the kernel ships opaque part carry-through. That root fix is cross-repo and awaits owner decision in the issue thread; this PR lands the two in-repo mitigations from the issue's fix list, so accounting stops being blind and the silent drop becomes loud.

What changed

  1. src/image-tokens.ts — charge type:"file" parts (issue fix item 2). The openai branch of imageTokensInParsedBody now handles file content parts following the google fileData precedent already in the same function:
    • inline file.file_data data-URL → sized from its base64 payload like any other data URL;
    • opaque refs (file_id, file.url) → flat REMOTE_IMAGE_TOKENS (4096), same as unsizeable remote image URLs.
      The cheap raw-body probe in imageTokensInRawBody now also fires on "type":"file" markers, so file-only bodies are no longer short-circuited to 0 before parsing.
  2. src/wire-drop-warn.ts + src/server.ts — one-time warn (issue fix item 4). New pure detector droppedOpenaiParts(body) scans the raw request against the codec's preserved set (user: text + image_url; other roles: text only) and reports count / sorted type union / first affected message index. prepareOpenai calls warnDroppedOpenaiParts() right after openaiToCore(parsed); the proxy logs one warn per session per distinct type-set (bounded FIFO, same shape as [Bug] 链检测把 bili 自己注入的 ACP 产物判成 bili→bili 链 —— 压缩内核永久跳过,长会话撞 1,048,576 上限 400(v0.1.133 回归) #1086's warnedChainSessions) naming exactly which part types will be dropped. When the kernel learns opaque carry-through, the call site re-points at what actually survived the round trip (noted in the module header).

Not touched here (deliberately): src/image-note.ts placeholders depend on the kernel sidecar carrying the part into BiliMessage — nothing to recognize until the kernel fix exists; the anthropic/responses sibling blind spots are the same kernel-side change.

Verification

  • Repro (pre-fix): driving openaiToCore→coreToOpenai directly from acp-kernel 0.0.84: [text, file] degrades to plain string "what is in this image?\n" (the trailing \n is the dropped part's empty slot in stringContent's join); [text, image_url] survives intact; [text, file, image_url] loses the file, keeps the image. Matches the issue report case-for-case, with zero compression involved (max_tokens=8000).
  • E2E through the built proxy (real dist/index.js + mock upstream, 2 turns resending the same history): client gets HTTP 200; forwarded user content is "\nwhat is in this image?\n" (drop persists, as expected pre-kernel-fix); exactly one warn line across both turns — dedup works end-to-end:
    wire codec will drop 1 content part(s) with unrecognized type(s) [file] (first at message #0) — e.g. DeepSeek Files API file refs vanish on the wire before any compression (#1205); inline base64 images work around it
  • Tests: new tests/fix-1205-file-parts.test.ts (14 tests) pins all three shapes' billing, the raw-body probe, the detector's preserved-set logic (incl. non-user roles, typeless parts, degenerate bodies), and the once-per-session-per-type-set rate limit incl. reset.
  • Pre-flight on this head: npm run typecheck clean, npm test 2365 pass / 0 fail (2 gated e2e skips), npm run build clean. Base is current master (03dd9c78).

Behavior change disclosure

Token estimates for requests carrying OpenAI-family file parts go old → new: +4096 per opaque file ref (or the real base64 size for inline file_data). Until the kernel fix ships these parts are not actually forwarded, so this is a bounded conservative overestimate (same policy as remote image URLs); after the kernel fix it becomes the accurate value. Direction is safe — higher estimates only make fit/compress decisions more conservative, never looser. A new warn log line appears for sessions whose bodies contain unrecognized content-part types (one per session per type-set). No config schema, persistence format, or wire-shape changes.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-09-24_fix-1205-file-part-drop (92cbf98)

Option A — Install from npm PR tag (recommended)

npm install -g billion-context@pr-1221

Each push to this PR publishes a new version under the pr-1221 npm tag.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr1221.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 开始 review PR #1221(fix: count + warn about OpenAI file parts dropped by the wire codec)。已确认:head commit CI 全绿(8/8);PR 基于 03dd9c78,当前 master 已前进到 aae4de1c(#1202/#1203 release v0.1.141 等)——正在核对新落地提交与本 PR 触碰文件(server.ts / image-tokens.ts)的重叠,并按规范在 rebase 后的 head 上重跑 typecheck/test/build。结论稍后回帖。

…#1205)

The acp-kernel openai codec keeps only text parts (+ image_url on user
messages), so DeepSeek Files API attachments ({type:"file","file_id":…})
vanish on every request before any compression happens. Until the kernel
gains opaque part carry-through, mitigate in-repo:

- image-tokens: charge type:"file" parts like remote references (inline
  file_data sized from its base64 payload; opaque id/url refs flat
  REMOTE_IMAGE_TOKENS), mirroring the google fileData precedent; raw-body
  probe now also fires on "type":"file" markers.
- server: one-time-per-session warn naming the part types the codec will
  drop (bounded FIFO like #1086's chain warnings) — silent drops become loud.

Behavior change: token estimates for sessions with Files-API attachments go
up by ~4096 per file ref (conservative overestimate until the kernel ships
opaque carry-through, exact after). Root fix tracked in acp-kernel.
@ranxianglei
ranxianglei force-pushed the 2026-09-24_fix-1205-file-part-drop branch from 9ba6bb8 to 92cbf98 Compare September 23, 2026 21:32
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 Review 完成:可以合并。 已把分支 rebase 到当前 master(aae4de1c)并推回(9ba6bb85 → 92cbf983,仅 force-with-lease 该 feature 分支),CI 会在新 head 上重跑。以下按规范逐关说明。

① 原 issue(#1205)方向关

② 回归审查(触碰路径的既有行为清单)

既有行为 结论
imageTokensInParsedBody openai 分支此前只计 image_url 有意变更(已披露):新增计 type:"file"——opaque ref(+4096)/inline file_data(base64 实际尺寸)。其余 part 类型计费不变;纯文本 body 仍为 0(有测试钉住)
anthropic/responses/google 三个分支 未触碰
raw-body probe 只在见 image_url 标记时解析 有意变更:追加 "type":"file"(紧凑+带空格两形态)。误报代价仅为一次 JSON.parse 后返回 0,无计费影响
估算的消费方逐一核过:输入侧 fit 决策(src/server.ts:2631 用 parsed)——codec 已把 file part 从 msgs 丢掉,旧估算对其完全隐形,现在计入,方向只会更保守;输出 clamp(:2723/:2753 用 rebuilt)——往返后 file part 已不在,贡献 0,正确(kernel 修复前它们本来就没上线);side-request guard(src/server/side-request.ts:127)——side request 是逐字转发的,file part 在这条路径上真实发出,+4096 让 guard 更准而非回归,且 SIDE_REQUEST_GUARD_TOLERANCE 本就吸收估算偏差 保持/修正,无回归
server.ts 管线行为 纯增量(import + 模块级 Set + 测试助手 + prepareOpenai 内一行调用,openaiToCore(parsed) 之后、与 codec 同一输入对象);side request 在 prepareOpenai 之前就走了 passthrough 分支(~:1773),不会对逐字转发的请求误发 warn
内存 warnedWireDropKeys 上限 4096、FIFO 淘汰,与 #1086 warnedChainSessions 同形,有界
config schema / persistence / wire shape / 协议 无任何变更

行为变更披露核对:PR 描述的「每个 opaque file 引用 +4096」与实际代码一致,方向安全(只让 fit/compress 更保守,不会放行超窗 payload),新增一条限频 warn 日志。无未披露漂移。

③ 代码审查

  • 计费逻辑与 google fileData 先例(src/image-tokens.ts:226-227)对齐;两条路径都过了 applyCap;pixels 模式下非图像容器落 PIXEL_IMAGE_FALLBACK_TOKENS,与 google inlineData 同策略。DeepSeek {type:"file",file_id} / OpenAI {type:"file",file:{file_data|url}} 三种形状全覆盖(有测试)。
  • 检测器保留集(user: text+image_url;其他角色: text)与 pinned codec 逐项一致。已知退化边界:image_url 但 url 非字符串的畸形 part 会被 kernel 丢却不被 flag(allImageParts 要求 string url)——warn 属尽力而为的响亮化,此欠报可接受,不拦。
  • 日志走请求作用域 log(即 loggerLog 通道),无 console.error;无 as any/@ts-ignore;测试助手命名沿用 _chainWarnSetForTest 惯例。
  • 小观察(不值得 commit):openai 分支末尾的孤立 continue; 在循环体末尾是 no-op,纯风格,不动。

④ 测试有效性

14 个用例断言都有牙:精确期望值(4096、ceil(800/4)=200、report 结构 deepEqual)、三种 file 形状、紧凑/带空格 JSON 探测、非 user 角色更严保留集、无 type part(<no-type>)、退化 body(null/string/{}/纯字符串 content)、dedup 语义(每会话×每类型集一次 + reset 后重发)。若计费分支被删或 detector 保留集写错,第一个相关用例即红。缺口:没有经真实 proxy 的 e2e 钉住 warn(作者按 PR 描述手工跑过 mock-upstream 两轮重发、恰好一行 warn;套件级 e2e 是 gated 的)——对此范围可接受。

⑤ diff 整洁度

4 文件 +219/-4,每一行都与本 PR 目的相关;无空行/重排噪音、无锁文件连带(version 未动,非 release 分支,合规);单一线性 commit。

预检证据(rebase 后的 head 92cbf983)

  • typecheck 干净;build 干净;全量测试 2391 pass / 3 fail / 2 skip。
  • 3 个失败全部在 tests/claude-native.test.ts(claude host ps-walk e2e,kill ESRCH)——在未含本 PR 的干净 origin/master 上以完全相同方式失败,是本沙箱的环境性既有问题(claude shim 进程生命周期竞态),与本 PR 无关;CI(ubuntu 22/24 + windows)在原 head 上 8/8 全绿,新 head 会重跑。
  • 陈旧基座问题已消除:rebase 零冲突(master 在 src/server.ts 上的后续改动来自 dsh acp rule报错 #1192/feat: bili CLI + debug in config + README rewrite #7,与本 PR 区域不相交),rebase 前后 patch 逐字节相同(4 文件 +219/-4)。

结论

§7.4 自动合并门槛基本满足(单 issue 范围、回归测试钉住行为、无 config/persistence/wire/跨仓变更、纯 fix:、diff 干净);唯一变通是引用方式——标题/正文用 "Part of #1205" 而不用 Fixes #1205,这是正确的:根修未落地,不能关 issue。合并动作按规矩由你来:#1221 。

后续提醒:acp-kernel#365 合并并发布到 npm 后,需要跟进 pin bump PR 收口 #1205(opaque sidecar 落地后:本 PR 的 warn 调用点按模块注释重指到真实存活集合、src/image-note.ts 占位符识别补上修复清单第 3 条)。


中文摘要:审了 PR #1221(#1205 的本仓库侧两项缓解——file part 计入 token 估算 + 每会话限频 warn):bug 真实存在(pinned kernel 0.0.84 codec 复核确认),方案方向对、代码正确、测试有牙、diff 干净;已 rebase 到当前 master 并推回分支,在该 head 上 typecheck/build/全量测试通过(3 个 claude-native e2e 失败为沙箱既有环境问题,master 同样失败),可以合并。

@ranxianglei
ranxianglei merged commit 6f02d11 into master Sep 24, 2026
8 checks passed
@ranxianglei
ranxianglei deleted the 2026-09-24_fix-1205-file-part-drop branch October 15, 2026 05:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] OpenAI 线解析/重建无条件丢弃非 text/image_url 的 content part(如 DeepSeek Files API file part)——不压缩也丢图

1 participant