[agent] fix: normalize inbound Chat images before route selection and preserve an explicit reasoning disable - #4534
[agent] fix: normalize inbound Chat images before route selection and preserve an explicit reasoning disable#4534lidge-jun wants to merge 4 commits into
Conversation
… preserve an explicit reasoning disable
The native Chat fast path recognized only `image_url` parts, while the translated
path also understood Pi/MCP `{type:"image", data, mimeType}` and Anthropic-shaped
`{type:"image", source}` parts. Two failures followed from that single gap: a
text-only routed model kept an image-bearing body because `isNativeChatRouteEligible`
could not see the image, and the native whitelist passthrough forwarded the foreign
part verbatim to an OpenAI-compatible upstream that does not accept it.
Recognition now lives once in `src/chat/image-parts.ts`, and
`normalizeChatImageParts` runs before `routeModel` so the diversion decision and the
forwarded wire observe the same parts. A body with no foreign image part is returned
by reference and stays byte-identical. A remote reference is recognized and
rewritten, never fetched.
Separately, the Chat inbound effort allowlist dropped `none`. That is the runtime's
disable sentinel, not an unknown value: `src/reasoning-effort.ts` maps it to omitting
the wire parameter and the Pi export maps Pi's `off` level onto it. Dropping it let a
provider default re-enable thinking the caller had turned off, which is not neutral
for Anthropic families that think by default.
Audit findings F1 and F7 (2026-09-14).
Local verification NOT RUN BY USER INSTRUCTION.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✅ Deterministic PR hygiene checks passed. |
…omain Hosted CI caught this: tests/test-layout-tooling.test.ts reported "chat-inbound-reasoning-none.test.ts: seed responses != server" for both new files. scripts/test-layout/layout.json seeds a `chat-` prefix to the `responses` domain, which is where the sibling Chat-translation tests already live, so registering them under `server` contradicted the seed. Moves both files to tests/responses/ and registers them there. Import depth is unchanged, so no import edits were needed. Local verification NOT RUN BY USER INSTRUCTION.
리뷰 · 우선순위 74 / 80이 PR은 2026-09-14 프로바이더/PI 호환 감사의 1층(F1·F7) 입니다. 지금 첫째, 운영자가 텍스트만 받는 모델로 라우팅해도, 본문에 Pi/Anthropic 모양 이미지가 있으면 같은 PR의 F7은 Chat inbound effort 허용 목록에 스택으로 보면 이 PR이 바닥입니다. base는 라인 - 메인테이너의 판단이 필요한 지점
너의 추천 이 댓글은 grok-bot이 작성했습니다 |
… variant Independent review found this comment now states a false fact about this stack's own change. It said the chat ingress allowlist OUTPUT_CONFIG_EFFORTS "has no none", which stopped being true in 63fbe66 when F7 added the disable sentinel to that allowlist. The filter itself is kept, narrowly and on a stated basis: emitting the variant would change what this exporter writes into a user's opencode config, and whether opencode's picker round-trips reasoningEffort "none" back to a wire this proxy reads has not been verified. Re-enabling it is a scoped follow-up needing that check, not a side effect of an ingress fix. MCode and ZCode filter none for their own separate reasons, which remain accurate at their call sites. Comment-only; no behavior change. Local verification NOT RUN BY USER INSTRUCTION.
|
Maintainer integration per MAINTAINERS.md: landing this maintainer-authored PR on dev without a second maintainer approval.\n\nExact-head verification on c7e863b: 28 checks pass, 2 skipped (macos control, windows matrix placeholder), 0 failed/cancelled. Run set: 34790779603 plus metadata workflows (enforce-target, label, resolve-pr, hygiene) — all green on this head.\n\nLocal suite/typecheck/build: NOT RUN (hosted exact-head CI is the evidence). |
Summary
The native Chat fast path recognized only
image_urlcontent parts, while the translated path also understood Pi/MCP{type:"image", data, mimeType}parts (Asideread_filetool results) and Anthropic-shaped{type:"image", source}parts. Two user-visible failures followed from that one gap:isNativeChatRouteEligiblecould not see the image and so never diverted the request to the Responses pipeline that describes or strips it.Recognition now lives once in
src/chat/image-parts.ts, andnormalizeChatImagePartsruns inhandleChatCompletionsWithBudgetimmediately after routing-body validation and beforerouteModel, so the diversion decision and the forwarded wire observe the same parts.Identity is preserved deliberately: a body with no foreign image part is returned by reference and stays byte-identical, as is one whose images are already
image_url. Only themessagesarray, the messages holding a rewritten part, and theircontentarrays are rebuilt — the native path is a whitelist passthrough, so an incidental deep clone would itself be a behavior change. A remotesource.type:"url"reference is recognized and rewritten, never fetched; this PR adds no outbound request.Separately, the Chat inbound effort allowlist dropped
none. That is the runtime's disable sentinel, not an unknown value:src/reasoning-effort.tsaccepts it and maps it to omitting the wire parameter, andsrc/clients/config-export.tsmaps Pi'soffthinking level onto it. Dropping it let a provider default re-enable thinking the caller had explicitly turned off — not neutral for the Anthropic families that think by default and require an explicitthinking:{type:"disabled"}to stop (src/adapters/anthropic.ts:960-966).Addresses audit findings F1 and F7 from the 2026-09-14 provider/PI compatibility audit.
Stack (merge bottom-up)
Base is
dev. Layer 2 genuinely depends on this one — both changesrc/server/chat-completions.tsandsrc/chat/inbound.ts. Review this PR's diff only.The plan unit is
devlog/_plan/260914_provider_parity_stack/;003_blocker_corrections.mdis authoritative over the per-layer docs and records the corrections independent review required.Verification
Local verification NOT RUN BY USER INSTRUCTION. The repository owner directed that no local product check execute on this machine for this work. No
bun test,bun run test,typecheck,build,lint,structure:check,privacy:scanor prepush script was run by the authoring session, and none is claimed as passing, provisional, or assumed. This PR is therefore opened as a draft.Because red-first execution is impossible under that restriction, the regressions below were written to assert the desired behavior and reviewed statically rather than driven red first.
What real evidence exists:
c7e863b9e5fff52c224574981d7b6860a34ce0f2) is the gate for this layer. Results are not pre-judged here.df7dc1be53, before this unit's changes —bun run typecheckexit 0,bun run structure:checkexit 0,bun run privacy:scanexit 0. That is a baseline of unmodified source and is not coverage of anything this PR adds.Regression coverage added (not executed locally):
tests/server/chat-native-image-normalization.test.ts— recognizer accepts both OpenAI spellings, Pidata/mimeType, and both Anthropicsourceforms; returns null for a part with no usable reference. Normalization rewrites a Pi part, preserves adetailhint, handles image-only and tool-message content, returns the identical object reference when there is no image and when images are alreadyimage_url, and leaves other body fields untouched. Text-only diversion is asserted for Pi, Anthropic base64, Anthropic remote-url and tool-carried images; a text-only body still takes the native path and a vision-capable route still keeps an image body on it.tests/server/chat-inbound-reasoning-none.test.ts— flat and nestednonespellings survive intoreasoning.effort, the produced body still validates againstresponsesRequestSchema, every other ladder value is unchanged, and an unknown effort is still ignored rather than forwarded.Both files are registered in
scripts/test-layout/layout.jsonandtests/fixtures/test-layout-expected.json(2-line insertions each, no reordering).Checklist
structure/data-planes/inbound-compat.mdgains the two sections that own this behavior, perstructure/INDEX.mdownership forsrc/chat/andsrc/server/.Current stack synchronization
The manual stack was synchronized bottom-up with
dev@246b5cab432b03cbec1766c2faffac13d6e39321.The repository's existing
2.55.0version change came from that parent; no artificial feature-branch version bump or release-test suppression was used.Order: #4534 → #4535 → #4536 → #4539 → #4562.
Current head:
c7e863b9e5fff52c224574981d7b6860a34ce0f2. Current base:dev.Each parent is an ancestor of its child. All five branches were pushed using
git push --no-verify.Fresh hosted CI is required at these new heads. Earlier green jobs or the historical
2.54.0release-line failure are not represented as new-head results. The PR remains draft; no merge or release was performed. No product validation ran on the connected Mac.