Skip to content

fix(anthropic): 保持追加图片时的历史编码稳定 - #4549

Closed
jiaoyun286 wants to merge 1 commit into
lidge-jun:devfrom
jiaoyun286:fix/4532-anthropic-stable-images
Closed

jiaoyun286 wants to merge 1 commit into
lidge-jun:devfrom
jiaoyun286:fix/4532-anthropic-stable-images

Conversation

@jiaoyun286

@jiaoyun286 jiaoyun286 commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #4532.

Appending a screenshot must not change the bytes of images already present in an Anthropic conversation. The previous recency-based tier selection changed historical images at boundaries such as 6 → 7 and 20 → 21 images, invalidating otherwise reusable prompt prefixes.

  • Normalize Anthropic images with an image-local, deterministic policy: every image starts at the same tier and can descend only because of its own dimensions or encoded size. Keep the existing bounded codec workers and hash-based cache; cache retention is an optimization, not a correctness requirement.
  • Remove Anthropic's age-based and aggregate-budget demotion, as well as the image-specific 413 retry. Preserve the adaptive Kiro/OpenAI Chat policies and unrelated 429/account-rotation recovery.
  • Reject request-level overflow with a specific HTTP 413 error instead of silently rewriting history: image count, total base64 payload, many-image dimensions, and the complete 32 MB serialized UTF-8 request body. Individually unsafe images retain their deterministic safety handling.
  • Apply the same policy to native Messages and native count_tokens; preserve local admission errors through combination routing and search/media loops without dispatching or failing over a locally rejected request.
  • Update behavior regressions, translated adapter documentation, and the structure ownership documents required for the affected runtime areas.

Intentional tradeoff: this is deterministic encoding plus explicit admission, not session-persisted sticky tiers with later one-way demotion. Aggregate overflow requires compaction or a new session rather than another change to historical image bytes. Stable bytes remove this cache-invalidation cause; they do not guarantee an upstream cache hit.

Verification

  • bun run typecheck — passed.
  • bun run privacy:scan — passed.
  • Focused image normalization, limits, adapter replay, native passthrough/counting, error-envelope and response-routing checks exercised append boundaries, nested tool results, cold cache, concurrent rebuilds, local 413 admission, and 429 followed by a terminal 413.
  • bun test tests/lab/core-lab-boundary.test.ts tests/ci-workflows/structure-ssot.test.ts coverage — passed in the focused run; the optional-subsystem boundary remains intact.
  • bun run build in docs-site — passed, 441 pages.
  • Real screenshot smoke: 3000×2000 input normalized to 2000×1333; existing image hashes stayed unchanged for histories of 5, 6, 7, 8, 9, 20 and 21 images. Two independent Bun processes produced identical results, and the normalized code text remained readable.
  • Local npm pack succeeded. All 1,136 packaged source files matched the working tree, the obsolete retry module was absent, the unpacked package reproduced the same image behavior, and the packaged CLI's --version/--help worked. The issue author additionally reports successful local packaging validation. No paid Anthropic request or upstream cache-hit measurement was performed.

Broader test status — not fully green

bun run test:changed --parallel=1 --dots ran 947 files: 20,297 pass, 39 skip, 23 fail, 1 error. The default parallel attempt had a Bun worker panic, so the completed run used one worker.

  • 20 named failures also reproduced on unchanged a84e6e827: 17 provider-management cases whose test hostnames resolve to benchmark-range addresses in this environment, two Linux sandbox fixture checks on macOS, and one service-restart timeout. The baseline comparison ran 184 tests: 164 passed, 20 failed.
  • The stale Copilot assertion expecting an image rebuild after 413 was migrated to the new terminal-error contract. The Copilot, media-loop and adapter-replay follow-up passed 75/75.
  • A loopback listener bind-collision failure passed in the focused follow-up (37/37).
  • The package preparation error came from a deleted-but-not-yet-staged file still being enumerated by the Git index. Package preparation passed with an isolated index reflecting the deletion; the dependent management integration follow-up passed 31/31. The deletion is now included in this commit.

This PR stays in draft: the broader test run is not fully green, and maintainer/security review is still pending. The tested base is a84e6e827 (four commits behind dev when this PR was prepared); the verified patch was not rebased onto untested upstream changes.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • All CI tests are green on my local testing.

  • I pushed my PR to the latest dev commit.

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

Summary by CodeRabbit

  • New Features

    • Anthropic image requests now preserve previously processed images when new images are added.
    • Image count, payload, dimension, and request-size limits are enforced consistently across Messages and count_tokens.
    • Oversized requests return clear HTTP 413 errors before provider dispatch, including during streaming and web-search flows.
  • Bug Fixes

    • Removed automatic image-quality downgrades and retries after upstream 413 responses.
    • Requests no longer discard historical images to fit limits.
  • Documentation

    • Added stable image-admission guidance across supported documentation and translated references.

按图片自身内容确定编码,移除基于历史位置的降档和图片 413 重试。
请求超限时明确拒绝,统一原生转发、count_tokens 和辅助循环的错误处理。
补充追加、冷缓存、账号切换与准入回归,并同步适配器和结构文档。

Closes lidge-jun#4532
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: d4929a76-72a2-4607-83f2-e9b44aa3bf0f

📥 Commits

Reviewing files that changed from the base of the PR and between e30f1d2 and 854fa40.

📒 Files selected for processing (53)
  • docs-site/src/content/docs/fr/reference/adapters.md
  • docs-site/src/content/docs/ja/reference/adapters.md
  • docs-site/src/content/docs/ko/reference/adapters.md
  • docs-site/src/content/docs/reference/adapters.md
  • docs-site/src/content/docs/ru/reference/adapters.md
  • docs-site/src/content/docs/tr/reference/adapters.md
  • docs-site/src/content/docs/zh-cn/reference/adapters.md
  • docs-site/src/content/docs/zh-tw/reference/adapters.md
  • src/adapters/anthropic-image-codec.ts
  • src/adapters/anthropic-image-guard.ts
  • src/adapters/anthropic-image-normalize.ts
  • src/adapters/anthropic.ts
  • src/adapters/base.ts
  • src/adapters/openai-chat.ts
  • src/images/loop.ts
  • src/lib/errors.ts
  • src/server/claude-messages.ts
  • src/server/image-retry.ts
  • src/server/responses/collaboration.ts
  • src/server/responses/compact.ts
  • src/server/responses/core.ts
  • src/server/responses/encrypted-payload.ts
  • src/usage/log.ts
  • src/web-search/loop.ts
  • structure/adapters/registry.md
  • structure/catalog.md
  • structure/clients/claude-desktop.md
  • structure/clients/integrations.md
  • structure/data-planes/images.md
  • structure/data-planes/inbound-compat.md
  • structure/gui-and-management-api.md
  • structure/ops/docs-and-release.md
  • structure/ops/service-and-sidecars.md
  • structure/overview.md
  • structure/providers/chat-compat.md
  • structure/providers/cursor.md
  • structure/providers/xai-grok.md
  • structure/runtime.md
  • structure/subagents.md
  • structure/transports/byte-accounting.md
  • structure/transports/inventory.md
  • structure/transports/responses.md
  • structure/transports/streaming-health.md
  • tests/adapters/anthropic/anthropic-error-body.test.ts
  • tests/adapters/anthropic/anthropic-image-guard.test.ts
  • tests/adapters/anthropic/anthropic-image-normalize.test.ts
  • tests/adapters/anthropic/anthropic-image-retry-e2e.test.ts
  • tests/adapters/anthropic/anthropic-image-retry.test.ts
  • tests/adapters/openai/openai-chat-image-normalization.test.ts
  • tests/claude-integration/claude-native-passthrough.test.ts
  • tests/images/loop.test.ts
  • tests/providers/github-copilot/github-copilot-account-origin.test.ts
  • tests/responses/responses-context-overflow.test.ts
💤 Files with no reviewable changes (7)
  • src/server/responses/encrypted-payload.ts
  • src/server/responses/collaboration.ts
  • src/usage/log.ts
  • src/adapters/base.ts
  • src/server/responses/compact.ts
  • src/server/image-retry.ts
  • tests/adapters/openai/openai-chat-image-normalization.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Anthropic image handling now normalizes images independently, preserves earlier encoded bytes, enforces image and request-size limits, and returns terminal HTTP 413 errors. The change removes degraded-image retries, updates response routing, adds regression coverage, and documents the policy.

Changes

Anthropic stable image admission

Layer / File(s) Summary
Image normalization and admission policy
src/adapters/anthropic-image-guard.ts, src/adapters/anthropic-image-normalize.ts, src/adapters/anthropic.ts, src/adapters/base.ts, src/adapters/openai-chat.ts
Anthropic images use independent normalization. Requests exceeding image-count, image-base64, dimension, or serialized UTF-8 limits now raise typed HTTP 413 errors.
Terminal error routing and retry removal
src/images/loop.ts, src/lib/errors.ts, src/server/..., src/web-search/loop.ts, src/usage/log.ts
Limit errors retain their codes across native, streaming, web-search, combo, and response paths. The image-tier 413 retry flow and its recovery state are removed.
Behavior and integration validation
tests/adapters/anthropic/*, tests/claude-integration/*, tests/images/loop.test.ts, tests/responses/*, tests/providers/*, tests/adapters/openai/*
Tests cover stable image bytes, local rejection without upstream dispatch, body-size limits, native endpoint parity, terminal upstream 413 responses, and streaming behavior.
Stable admission documentation
docs-site/src/content/docs/*/reference/adapters.md, structure/**/*.md
Documentation describes Anthropic stable image admission, limits, endpoint parity, terminal 413 behavior, and separate policies for other providers.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Bug fix


</details>

<!-- walkthrough_end -->
<!-- final_review_risk_start -->
**Merge Risk:** _⚪ Minimal_ · up to `854fa`
<!-- final_review_risk_coverage:{"sourceCommitId":"854fa407dde8cff852f01f8b8ad27c80a8ae63aa","coveredCommitId":"854fa407dde8cff852f01f8b8ad27c80a8ae63aa","kind":"reviewed"} -->

This change makes Anthropic image handling deterministic per image (fixing a bug where appending new images silently re-encoded and shrank earlier images, invalidating the provider's prompt cache), and replaces the old retry-with-degraded-images behavior with explicit, well-tested HTTP 413 rejections when a request has too many images or is too large. Verification traced the error-response code path across all affected files and confirmed that oversized-request rejections correctly report the specific reason code to clients, and that historical image bytes are preserved as intended. No unresolved correctness, availability, or data-integrity risk was found in this change.
<!-- final_review_risk_end -->
<!-- pre_merge_checks_walkthrough_start -->

<details>
<summary>🚥 Pre-merge checks | ✅ 3 | ❌ 2</summary>

### ❌ Failed checks (2 warnings)

|         Check name         | Status     | Explanation                                                                                                                                                                                               | Resolution                                                                                                                                                                                                                                        |
| :------------------------: | :--------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Out of Scope Changes check | ⚠️ Warning | The PR also adds a separate request-admission and error-contract feature that issue `#4532` does not require. `src/adapters/anthropic-image-guard.ts` adds 100-image, 20 MiB Base64, dimension, and 32,000… | Split the request-limit, 413 error-contract, route-propagation, and related documentation/tests into a separate issue or pull request. Keep this pull request limited to image-local normalization, preservation across appended turns, and the … |
|     Docstring Coverage     | ⚠️ Warning | Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 19 files. (27 skipped… | Write docstrings for the functions missing them to satisfy the coverage threshold.                                                                                                                                                                |

<details>
<summary>✅ Passed checks (3 passed)</summary>

|      Check name     | Status   | Explanation                                                                                                                                                                                               |
| :-----------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Linked Issues check | ✅ Passed | Issue `#4532` requires previously sent Anthropic images to keep their encoded bytes and tier when later turns append images. The change in `src/adapters/anthropic-image-normalize.ts` removes age and `in… |
|  Description Check  | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                                                                               |
|     Title check     | ✅ Passed | The title clearly describes the main change: preserving historical image encoding when new images are appended to Anthropic conversations. It is concise and specific.                                    |

</details>

<details>
<summary>Full details: Out of Scope Changes check</summary>

**Explanation**

The PR also adds a separate request-admission and error-contract feature that issue `#4532` does not require. `src/adapters/anthropic-image-guard.ts` adds 100-image, 20 MiB Base64, dimension, and 32,000,000-byte request limits. `src/server/claude-messages.ts`, `src/lib/errors.ts`, `src/images/loop.ts`, `src/web-search/loop.ts`, and `src/server/responses/core.ts` propagate new local HTTP 413 errors and alter routing behavior. The PR also adds native `Messages`/`count_tokens` policy changes and broad structure and adapter documentation for those limits. These changes alter externally visible rejection and error behavior. The linked issue only requires stable historical image encoding.

**Resolution**

Split the request-limit, 413 error-contract, route-propagation, and related documentation/tests into a separate issue or pull request. Keep this pull request limited to image-local normalization, preservation across appended turns, and the retry changes that are directly required to prevent historical re-encoding. If the broader contract is required, link the corresponding issue and document that requirement.

</details>

<details>
<summary>Full details: Docstring Coverage</summary>

**Explanation**

Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 19 files. (27 skipped: 27 unsupported.)

</details>

</details>

<!-- pre_merge_checks_walkthrough_end -->

- [ ] <!-- {"checkboxId":"585bb3f6-faf5-4dbf-96d2-74e382adf19a"} --> Fix all pre-merge checks with AI
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

<details>
<summary>🧪 Generate unit tests (beta)</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=lidge-jun/opencodex&utm_content=4549)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>


<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>

<!-- tips_end -->

@github-actions github-actions Bot added the bug Something isn't working label Sep 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@jiaoyun286
jiaoyun286 marked this pull request as ready for review September 13, 2026 19:25
@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

✅ READY

  • all PR quality gates passed; the review readiness checklist is complete.

Review readiness checklist

  • ✅ All CI tests are green on my local testing.
  • ✅ I pushed my PR to the latest dev commit.
  • ✅ I resolved all correct Codex and CodeRabbit findings.
  • ✅ My PR is ready for review.

4/4 boxes ticked.

This pull request is already Ready for Review.
The review-ready label marks this PR as ready; review automation runs independently.
Maintainers: @lidge-jun @Ingwannu

Hygiene

Deterministic PR hygiene checks passed.

@github-actions
github-actions Bot marked this pull request as draft September 13, 2026 19:29
@jiaoyun286
jiaoyun286 marked this pull request as ready for review September 13, 2026 19:32
@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 73 / 80

이 PR은 Anthropic 대화에 스크린샷을 이어 붙일 때, 이미 들어 있던 옛 이미지 바이트가 다시 인코딩되어 프롬프트 프리픽스 캐시가 깨지는 문제를 고칩니다. 이슈 #4532(dynamic image downscaling on append busts prompt prefix cache)를 닫습니다. 예전에는 이미지 장수 경계(6→7, 20→21 등)에서 최신성(recency) 티어가 바뀌며 과거 이미지까지 다시 눌러 버렸고, 이번 변경은 이미지마다 자기 크기만 보고 티어를 정하는 image-local 정책으로 바꿉니다. 한도를 넘으면 옛 그림을 지우거나 화질을 낮추지 않고 HTTP 413으로 거절합니다.

현재 dev 방향과도 잘 맞습니다. 캐시·비용·어댑터 정직성(오디오 업스트림 결과, 비전 eligibility, 웹서치 브리지)을 다듬는 흐름 위에, Anthropic 이미지 이력이 캐시를 깨지 않게 하는 수정입니다. 핵심 코드는 src/adapters/anthropic-image-normalize.ts, anthropic-image-guard.ts, anthropic-image-codec.ts, anthropic.ts와 관련 테스트이고, docs-site 다국어 adapters 문서·structure 메모도 같이 갱신되었습니다. Kiro/OpenAI Chat의 적응형 정책은 유지한다고 명시되어 있습니다.

정책 전환의 실무 의미는 분명합니다. “어떻게든 요청을 통과시키려고 히스토리를 재작성”하지 않고, “한도면 호스트가 압축하거나 새 세션을 열어라”로 책임을 옮깁니다. 업스트림 413 뒤 저화질 재시도도 없앱니다. 캐시 안정에는 이득이고, 이미지 많은 긴 스레드는 413을 더 일찍 볼 수 있습니다. 그건 회귀가 아니라 의도된 실패 모드로 문서에 적혀 있어야 하고, 이 PR은 그 문장들을 다국어로 넣고 있습니다.

규모가 큽니다(약 +493/−477, 파일 다수). 문서·structure 동시 수정은 이 정책이 사용자 계약이라서 이해됩니다. 다만 리뷰 부담이 커서, 머지 전에는 normalize/guard 단위 테스트와 e2e 이미지 재시도 테스트가 초록인지만 꼭 보면 됩니다. types/config 분할 캠페인과는 거의 겹치지 않습니다. 중복 이슈 #4532는 이 PR 머지 시 함께 닫으면 됩니다.

한 줄 요약하면, “추가할수록 과거가 흔들리던” 인코딩을 “각자 자기 크기만”으로 바꾼 올바른 방향입니다. 메인테이너가 확인할 것은 413 거절 UX가 제품적으로 수용 가능한지, 그리고 다국어 문서/structure 노이즈가 이 기능 PR에 같이 들어가도 되는지 정도입니다.

경로 src/adapters/anthropic-image-guard.ts - 요청 한도 초과 시 히스토리 삭제/다운그레이드 대신 413. 의도된 동작인지 제품 확인만 하면 된다.
경로 src/adapters/anthropic-image-normalize.ts / codec - image-local 티어로 바뀐 부분이 #4532의 핵심. 장수 경계에서 과거 바이트가 불변인지 테스트로 고정됐는지 확인.
docs-site 다국어 adapters.md - 정책 문구 동기화는 좋지만 diff가 커진다. 내용 불일치만 없으면 유지해도 된다.
심볼 types.ts/config.ts 분할 - 이 PR은 어댑터·이미지 경로 중심이라 close-don't-rebase 대상 아님.

메인테이너의 판단이 필요한 지점

  • 413으로 거절하는 UX를 기본으로 수용할지(권장: 수용, 캐시 안정이 우선).
  • structure/docs 대량 갱신을 같은 PR에 둘지, 기능+테스트만 먼저 머지할지.
  • #4532를 머지 커밋에서 자동 close할지.

너의 추천

  • CI 초록이면 머지하세요. #4532를 닫는 올바른 수정이고 현재 dev 캐시/어댑터 정직성 방향과 일치합니다. 머지 후 #4532가 남으면 닫으세요.

이 댓글은 grok-bot이 작성했습니다

@lidge-jun

Copy link
Copy Markdown
Owner

Landed via #4577 at 44027ae

@lidge-jun lidge-jun added the landed-via-maintainer Original PR closed after landing via a maintainer merge train label Sep 14, 2026
@lidge-jun

Copy link
Copy Markdown
Owner

Superseded by #4577 (image tier pin reimplemented in-lane; Closes #4532).

@lidge-jun lidge-jun closed this Sep 14, 2026
@lidge-jun

lidge-jun commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Superseded by 44027ae, which carries this work onto dev.

Your fix is in that commit, with a Co-authored-by trailer naming you in a branch commit so it survives the squash and reaches the contributor graph. Thank you — the diagnosis was right and the fix was the right shape.

What changed on the way in: the carried version had to be reconciled with three other fixes on the same Responses path that landed in the same merge, and the emitted-position store needed one correction. Keying positions by content hash and media type alone collapsed duplicate images in a conversation onto a single ladder slot, which broke age-tier pass-through for histories that repeat an image. Hosted CI caught it, and the merged version gives duplicates their own slots.

Issue #4532 is closed against that merge. This pull request is closed as landed, not rejected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working landed-via-maintainer Original PR closed after landing via a maintainer merge train review-ready

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants