Files
HealthLog/src/lib/ai/__tests__/vision-capability.test.ts
T
Marc-André Bombeck 1e2b1d2c6a fix(ai): treat gpt-5.x-codex slugs as vision-capable
The document/lab-OCR vision gate excluded every `-codex` slug from the
GPT-5.x line as text-only. That is stale: the `codex/responses` wire reads
an `input_image` block on these slugs (docs/codex-protocol-spec.md plus the
current model lineup), and the 5.2-Codex line is vision-tuned. An operator
pinned to a `-codex` slug therefore resolved no vision-capable provider and
hit a 422 "No vision-capable AI provider is configured" when reading a
document with AI.

Drop the `-codex` exclusion from the gpt-5 branch so a codex-only chain
resolves a document-vision provider; keep the `-mini`/`-preview`
reasoning-slug guard. Lab-OCR on a `-codex` pin gains vision the same way.
2026-07-09 13:41:59 +02:00

118 lines
4.4 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
supportsVisionForConfig,
supportsPdfForProvider,
} from "../vision-capability";
describe("supportsVisionForConfig", () => {
it("accepts the Claude 3 / 3.5 / 3.7 / 4 families for anthropic", () => {
for (const model of [
"claude-3-opus-20240229",
"claude-3-sonnet-20240229",
"claude-3-haiku-20240307",
"claude-3-5-sonnet-latest",
"claude-3-7-sonnet",
"claude-sonnet-4-6",
"claude-opus-4-1",
"claude-haiku-4",
]) {
expect(supportsVisionForConfig("anthropic", model)).toBe(true);
}
});
it("rejects legacy / text-only anthropic models", () => {
expect(supportsVisionForConfig("anthropic", "claude-2.1")).toBe(false);
expect(supportsVisionForConfig("anthropic", "claude-instant-1")).toBe(
false,
);
expect(supportsVisionForConfig("anthropic", null)).toBe(false);
});
it("accepts gpt-4o/4.1/turbo/o-series for openai + admin variants", () => {
for (const providerType of [
"openai",
"admin-openai",
"admin-key",
] as const) {
expect(supportsVisionForConfig(providerType, "gpt-4o")).toBe(true);
expect(supportsVisionForConfig(providerType, "gpt-4o-mini")).toBe(true);
expect(supportsVisionForConfig(providerType, "gpt-4.1")).toBe(true);
expect(supportsVisionForConfig(providerType, "gpt-4-turbo")).toBe(true);
// Full o-series reasoning models read images.
expect(supportsVisionForConfig(providerType, "o1")).toBe(true);
expect(supportsVisionForConfig(providerType, "o3")).toBe(true);
expect(supportsVisionForConfig(providerType, "o4")).toBe(true);
}
});
it("rejects non-vision openai models", () => {
expect(supportsVisionForConfig("openai", "gpt-3.5-turbo")).toBe(false);
expect(supportsVisionForConfig("openai", "gpt-4")).toBe(false);
expect(supportsVisionForConfig("openai", null)).toBe(false);
});
it("rejects text-only o-series variants (-mini / -preview) for openai", () => {
// o1-mini / o1-preview / o3-mini accept the `o*` prefix but have no image
// input — claiming vision here 400s the user at the image block.
for (const providerType of [
"openai",
"admin-openai",
"admin-key",
] as const) {
expect(supportsVisionForConfig(providerType, "o1-mini")).toBe(false);
expect(supportsVisionForConfig(providerType, "o1-preview")).toBe(false);
expect(supportsVisionForConfig(providerType, "o3-mini")).toBe(false);
}
});
it("trusts local (operator opted in) regardless of model string", () => {
expect(supportsVisionForConfig("local", "llava")).toBe(true);
expect(supportsVisionForConfig("local", null)).toBe(true);
});
it("reports vision for codex on multimodal slugs, including -codex", () => {
for (const model of [
"gpt-5.5",
"gpt-5.4",
"gpt-5.4-mini",
"gpt-4o",
"gpt-4.1",
"o1",
// The GPT-5.x `-codex` specialists are vision-capable: the wire reads
// an `input_image` block on these slugs (docs/codex-protocol-spec.md +
// OpenAI's current model lineup). The old "-codex is text-only"
// exclusion was stale.
"gpt-5-codex",
"gpt-5.3-codex",
"gpt-5.1-codex-mini",
]) {
expect(supportsVisionForConfig("codex", model)).toBe(true);
}
// Non-multimodal / unknown slugs and a missing slug → false.
expect(supportsVisionForConfig("codex", "gpt-4")).toBe(false);
expect(supportsVisionForConfig("codex", "gpt-3.5-turbo")).toBe(false);
expect(supportsVisionForConfig("codex", null)).toBe(false);
// Text-only o-series variants are excluded for codex too.
expect(supportsVisionForConfig("codex", "o1-mini")).toBe(false);
expect(supportsVisionForConfig("codex", "o3-mini")).toBe(false);
expect(supportsVisionForConfig("codex", "o1-preview")).toBe(false);
// Full o-series stays vision-capable.
expect(supportsVisionForConfig("codex", "o3")).toBe(true);
});
it("never reports vision for none", () => {
expect(supportsVisionForConfig("none", "gpt-4o")).toBe(false);
});
});
describe("supportsPdfForProvider", () => {
it("only anthropic supports native PDF", () => {
expect(supportsPdfForProvider("anthropic")).toBe(true);
expect(supportsPdfForProvider("openai")).toBe(false);
expect(supportsPdfForProvider("admin-openai")).toBe(false);
expect(supportsPdfForProvider("local")).toBe(false);
expect(supportsPdfForProvider("codex")).toBe(false);
});
});