mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
Introduce an operator-level ChatGPT-subscription (Codex) credential that the operator connects once and shares server-wide, plus a per-user switch to route AI egress through it. - Schema (migration 0235): three encrypted columns on AppSettings for the operator's central Codex access/refresh tokens and the ChatGPT-Account-ID claim, plus expiry/connected-at/status; a per-user useCentralCodex flag (default false). Register the new encrypted columns in the rotation registry and script. - Resolution: add the admin-codex chain type; append it to a user's resolved chain only when they opted in AND the operator connected it. Tokens refresh server-side against the singleton row (CAS-style), same primitives as the per-user Codex path. - Budget: bill an admin-codex primary against the operator cost cap, not the user plan — it drains the operator's account. - Consent: admin-codex is server-managed external egress, gated by the existing consent machinery (self-snapshot and document surfaces alike). No existing consent path is weakened. - Document order: rank admin-codex with the subscription tier (last), so the document class stays local-first. - UI: admin device-code connect/disconnect card (cookie-only admin routes) with honest copy on account pooling, shared rate limits, and training-by-default; a per-user opt-in switch shown only once the operator has connected it, off by default, behind a one-time honesty confirm. - i18n across all six locales; unit tests for the chain type, budget cap, consent gate, vision capability, and document order; a mocked e2e for both the admin connect surface and the per-user switch. The central credential's tokens are encrypted at rest and never returned to a non-admin API; the opt-in only takes effect once the operator has connected the shared account.
128 lines
5.0 KiB
TypeScript
128 lines
5.0 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("mirrors codex vision capability for the shared central codex (admin-codex)", () => {
|
|
// The operator-shared central Codex runs the same wire on the same resolved
|
|
// slug, so its vision gate is identical to `codex`.
|
|
expect(supportsVisionForConfig("admin-codex", "gpt-5.5")).toBe(true);
|
|
expect(supportsVisionForConfig("admin-codex", "gpt-5.3-codex")).toBe(true);
|
|
expect(supportsVisionForConfig("admin-codex", "gpt-4")).toBe(false);
|
|
expect(supportsVisionForConfig("admin-codex", "o3-mini")).toBe(false);
|
|
expect(supportsVisionForConfig("admin-codex", null)).toBe(false);
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|