Files
OmniRoute/tests/unit/vision-compression-authoritative-capability-7237.test.ts
Diego Rodrigues de Sa e Souza 5f75abe4a2 cherry-pick(pr-9634): fix(test): reconcile base-drifted test expectations on release/v3.8.50 (#9874)
* fix(combo): restore routing module load

* fix(db): resolve ccr migration version collision

Renumber the CCR block-store migration from 134 to 139, reconcile databases that already applied the legacy slot, and add regression coverage for both upgrade paths.

Co-Authored-By: GPT-5 <noreply@openai.com>

* fix(changelog): format the aggregator balance fragment as a bullet

The fragment landed with YAML frontmatter rather than the bullet the
aggregator reads, so check:changelog-integrity exits 1 on every branch and
takes the merge-integrity job down with it regardless of what the branch
changed.

Only the format changes. The entry text is the author's, unedited, and now
carries the link to the pull request that shipped it.

* fix(test): update expected auth/vision/provider schema for base-drifted expectations

* fix(test): narrow this branch to the drifted test expectations

Three other PRs already cover what this one was carrying. #9618 renumbers the
colliding ccr_blocks migration, #9632 repairs the malformed aggregator changelog
fragment, and #9676 restores the combo module load by implementing the selection
helper the import was reaching for, rather than deleting the caller the way this
branch did. Keeping any of it here would put two files back on the same migration
slot and overwrite a better fix with a worse one.

What survives is the part none of them touch. Once the combo barrel loads again,
three assertions in the context-window filter suite start failing: they demand
that catalog-too-small targets be dropped, while the file's own header and its
four neighbouring tests say those targets stay available as runtime fallback.
The unresolved import was masking them. A new case pins the output-token limit
as a genuine hard requirement so the relaxation cannot drift further.

The provider count assertion kept one literal at the old value after the rest of
the file moved to 198, so the partition check failed on a sum that was correct.

* chore(quality): re-time migrationRunner for the 139 guard on the new tip

---------

Co-authored-by: alexey.nazarov@softmg.ru <alexey.nazarov@softmg.ru>
Co-authored-by: GPT-5 <noreply@openai.com>
Co-authored-by: Minxi Hou <houminxi@gmail.com>
2026-08-09 10:06:57 -03:00

91 lines
4.6 KiB
TypeScript

/**
* #7237 — vision-capable models lose image_url blocks under compression.
*
* `open-sse/handlers/chatCore.ts` fed `applyCompressionAsync`'s `supportsVision` option
* from `isVisionModelId(effectiveModel)` — the deliberately-conservative model-id
* fragment heuristic in `src/shared/constants/visionModels.ts` — instead of the
* authoritative `getResolvedModelCapabilities().supportsVision` that every other
* vision-aware code path (e.g. the vision-bridge guardrail) uses.
*
* `gpt-5.5` is registered with `supportsVision: true` in `src/shared/constants/modelSpecs.ts`
* but has no gpt-5.x entry in the fragment list, so the heuristic wrongly returned `false`.
* `open-sse/services/compression/lite.ts::replaceImageUrls()` gates on
* `supportsVision !== false`, so that spurious `false` made it silently strip every
* `image_url` block from the request before it ever reached the executor.
*
* This test asserts the CORRECT, authoritative-capability-driven behavior: gpt-5.5
* keeps its images through the lite-compression path.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { isVisionModelId } from "../../src/shared/constants/visionModels.ts";
import { getResolvedModelCapabilities } from "../../src/lib/modelCapabilities.ts";
import { replaceImageUrls } from "../../open-sse/services/compression/lite.ts";
import { applyCompressionAsync } from "../../open-sse/services/compression/strategySelector.ts";
function imageBody() {
return {
messages: [
{
role: "user",
content: [{ type: "image_url", image_url: { url: "data:image/png;base64,iVBOR" } }],
},
],
};
}
describe("#7237 vision-capable models keep their images through compression", () => {
it("the id-fragment heuristic now agrees with the authoritative spec for gpt-5.5", () => {
// Originally this case documented a DRIFT: the fragment list had no gpt-5.x
// entry, so the heuristic said false while modelSpecs said true. Commit
// 68cb678780 (enable vision flags for CC models) added the "gpt-5" fragment
// and closed that gap, so the two sources now converge. The invariant worth
// guarding is the AGREEMENT plus the fact that modelSpecs stays authoritative.
assert.equal(isVisionModelId("gpt-5.5"), true);
assert.equal(
getResolvedModelCapabilities({ model: "gpt-5.5" }).supportsVision,
true,
"modelSpecs.ts registers gpt-5.5 with supportsVision:true — authoritative source agrees"
);
// The heuristic stays deliberately conservative for ids it does not know;
// chatCore must still read the authoritative capability, never this fallback.
assert.equal(isVisionModelId("some-unknown-text-only-model"), false);
});
it("replaceImageUrls preserves the image when fed the heuristic value (both paths agree on gpt-5.5)", () => {
const heuristicSupportsVision = isVisionModelId("gpt-5.5");
const result = replaceImageUrls(imageBody(), { supportsVision: heuristicSupportsVision });
assert.equal(result.applied, false, "the image must be KEPT, not stripped to a placeholder");
const content = result.body.messages?.[0]?.content as Array<Record<string, unknown>>;
assert.equal(content[0].type, "image_url", "the block must remain a real image_url block");
});
it("reproduces the bug SHAPE: a false supportsVision strips the image to a placeholder", () => {
// The original case derived the wrong value from isVisionModelId("gpt-5.5").
// That no longer returns false (68cb678780), so the false is now supplied
// directly — what this guards is the stripping behaviour itself, which is
// exactly why chatCore must pass the authoritative capability and not a guess.
const result = replaceImageUrls(imageBody(), { supportsVision: false });
assert.equal(
result.applied,
true,
"sanity check: this reproduces the bug shape when fed the wrong value"
);
});
it("applyCompressionAsync end-to-end (lite mode) keeps image_url blocks for gpt-5.5 when fed the authoritative capability", async () => {
const model = "gpt-5.5";
const supportsVision = isVisionModelId(model);
const result = await applyCompressionAsync(imageBody(), "lite", { model, supportsVision });
const content = (result.body as { messages: Array<{ content: unknown }> }).messages[0]
.content as Array<Record<string, unknown>>;
assert.equal(content[0].type, "image_url", "gpt-5.5 must keep its image_url block intact");
assert.equal(
(content[0].image_url as Record<string, unknown>)?.url,
"data:image/png;base64,iVBOR",
"the original data URL must survive unchanged"
);
});
});