fix: preserve images in wire codec round-trips (issue #187) - #189
fix: preserve images in wire codec round-trips (issue #187)#189ranxianglei wants to merge 1 commit into
Conversation
openai codec: allImageParts no longer filters to data: URLs, so a single remote-URL image gets the rawOpenaiContent sidecar (base64 fields only for data URLs) and multi-image messages keep every part via rawOpenaiContentParts instead of dropping all remote-URL images. responses codec: image-only user items are now tracked as core messages with the [image] placeholder text (anthropic codec precedent) plus the rawResponsesItem sidecar; coreToResponses re-emits the raw item verbatim while the kernel text still equals canonicalUserText, so such items survive rebuilds and enter the compression space.
📦 Built Package ArtifactBranch: Option A — Install from npm PR tag (recommended)In your adapter project: npm install acp-kernel@pr-189Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf acp-kernel-pr189.tgz
npm install ./packageThis comment is automatically updated on each push. |
[bot] 🏷 独立复核完成(在我这边环境对 PR 分支 验证复现(我的环境)
做法:把 PR 的测试文件 + master 的三个源码文件(openai/responses/bili-message)拼在一起跑 正确性分析(逐层)openai (
responses (
发现(均为非阻塞,记录用)① 证据表述不精确:PR 写 "All fail on master and pass with this change",实测 8/9 fail on master—— ② 重复逻辑靠注释同步: ③ 结论实现最小、正确、测试充分,三项 CI 全绿,id-churn 范围被精确界定并验证。我这边无阻塞项,可以合并。按规则我不执行 merge —— 请你自行合并:#189 顺带:#188( |
|
Closing as superseded: master |
Fixes both silent image-loss gaps reported in #187, plus a wider variant found during triage.
Triage findings (verified on master v0.0.49)
Gap 1 (openai) — the report pointed at the
imgs.length === 1 && firstParsedsidecar condition, but current code has an additional filter upstream:allImagePartsonly collected image parts whose url parses as a data URL (introduced by 626f933 to feedrawOpenaiContentParts). A single remote-URL image therefore never reached the sidecar logic at all (imgs.length === 0). Same end state as reported, but the fix must also remove that filter. Reproducing also showed the gap is wider than reported: due to the same filter, multi-image messages dropped every remote-URL part —[data, remote]round-tripped as[data], not just the single-image case.Gap 2 (responses) — reproduced exactly as described: an image-only user item produced zero core messages. One nuance: loss was path-dependent —
patchResponsesInputkept such items via the layout fallback (slot without coreId → original item), whilecoreToResponsesdropped them; and in either path the untracked item stayed invisible to the compression pipeline and would accumulate unbounded. Tracking fixes both paths.Changes
openai codec (
src/wire/openai.ts)allImagePartsnow collects everyimage_urlpart with a string url (data-URL filter removed).{ rawOpenaiContent }(+ base64 fields iff data URL)coreToOpenaineeded no change — its precedence chain already prefers verbatim raw over base64 rebuild.responses codec (
src/wire/responses.ts)"[image]"placeholder text (anthropic codec precedent) +rawResponsesItemsidecar + data-URL base64 split as before, so they enter the compression space with stable ids.input_image, no text) also get"[image]"— previously tracked with the"\n"join artifact, which is not real text. This is the only id churn introduced: those items' ids shift once (deterministically); every previously working shape keeps its exact id.coreToResponsesre-emits the raw item verbatim while the kernel text still equalscanonicalUserText(raw)(content text, or"[image]"when no text part exists); edited/summarized messages rebuild as plain text as before.role: "user"; assistant/system/developer handling is unchanged.Known trade-offs / residual gaps (out of scope, reporting only)
"[image]"shares the base id with the placeholder; ClusterCounter disambiguates deterministically (documented message-id trade-off)."\n"join artifact for non-text parts pre-dates this fix (e.g.[text, image]→"t\n") and is unchanged; normalizing it would shift existing ids.src/wire/mirror.ts) cannot represent images at all (MirrorBlockhas no image type) — pre-existing parity limitation, affects data-URL images too.openaiToCorethrowsTypeErroron{type:"image_url", image_url:null}— filed as openaiToCore 崩溃:image_url 为 null 的 image_url part 触发 TypeError #188.Evidence
tests/wire-bili-message-roundtrip.test.ts(5c–5e, 6b–6g): single/multi/mixed remote-URL OpenAI shapes, image-only responses tracking, verbatim round-trip,patchResponsesInputsurvival + prune-by-id, cross-turn id stability, whitespace-text edge, malformedimage_url. All fail on master and pass with this change (verified against pristine HEAD copies).npm run typecheck✓ ·npm test581/581 ✓ ·npm run build✓