fix(modality-bridge): preserve capability and telemetry truth

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-15 04:27:44 -03:00
committed by Xiangzhe
parent 67fa0d0286
commit d6a4159871
14 changed files with 295 additions and 71 deletions

View File

@@ -12,6 +12,7 @@ interface BridgeStats {
cacheHits: number;
failures: number;
lastUsedAt: string | null;
latencySamples: number;
successes: number;
totalLatencyMs: number;
}
@@ -36,6 +37,19 @@ function parseStats(value: unknown): BridgeStats | null {
typeof record.attempts === "number" ? record.attempts : record.bridged + record.failures;
const averageLatencyMs =
typeof record.averageLatencyMs === "number" ? record.averageLatencyMs : 0;
const totalLatencyMs =
typeof record.totalLatencyMs === "number" ? record.totalLatencyMs : averageLatencyMs * attempts;
// Compatibility with pre-latencySamples servers: positive latency data was
// sampled, while the legacy all-zero shape means timing was never recorded.
const latencySamples =
typeof record.latencySamples === "number"
? Math.max(0, Math.floor(record.latencySamples))
: totalLatencyMs > 0
? Math.max(
1,
averageLatencyMs > 0 ? Math.round(totalLatencyMs / averageLatencyMs) : attempts
)
: 0;
return {
attempts,
averageLatencyMs,
@@ -43,11 +57,9 @@ function parseStats(value: unknown): BridgeStats | null {
cacheHits: record.cacheHits,
failures: record.failures,
lastUsedAt: typeof lastUsedAt === "string" ? lastUsedAt : null,
latencySamples,
successes: typeof record.successes === "number" ? record.successes : record.bridged,
totalLatencyMs:
typeof record.totalLatencyMs === "number"
? record.totalLatencyMs
: averageLatencyMs * attempts,
totalLatencyMs,
};
}
@@ -82,7 +94,7 @@ export default function ModalityBridgeStatsRow({ kind }: ModalityBridgeStatsRowP
return (
<div className="flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-muted" aria-live="polite">
<span>
{stats.attempts} {tProviderStats("requests").toLowerCase()}
{stats.attempts} {tRoot("requestLogger.attempts").toLowerCase()}
</span>
<span>
{stats.successes} {t("modalityBridgeStatsBridged")}
@@ -94,10 +106,12 @@ export default function ModalityBridgeStatsRow({ kind }: ModalityBridgeStatsRowP
{stats.failures} {t("modalityBridgeStatsFailures")}
</span>
<span>
{tRoot("trafficInspector.timingTotalLatency")}: {Math.round(stats.totalLatencyMs)} ms
{tRoot("trafficInspector.timingTotalLatency")}:{" "}
{stats.latencySamples > 0 ? `${Math.round(stats.totalLatencyMs)} ms` : "—"}
</span>
<span>
{tProviderStats("avgLatency")}: {Math.round(stats.averageLatencyMs)} ms
{tProviderStats("avgLatency")}:{" "}
{stats.latencySamples > 0 ? `${Math.round(stats.averageLatencyMs)} ms` : "—"}
</span>
<span>
{t("modalityBridgeStatsLastUsed")}: {lastUsed}

View File

@@ -5,7 +5,10 @@ import { updateModelAliasSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { hasEligibleConnectionForModel } from "@/domain/connectionModelRules";
import { getSettings } from "@/lib/db/settings";
import { getResolvedModelCapabilities } from "@/lib/modelCapabilities";
import {
createModelCapabilityResolutionSnapshot,
getResolvedModelCapabilities,
} from "@/lib/modelCapabilities";
import { isFreeModel, providerHasFreeModels } from "@/shared/utils/freeModels";
// GET /api/models - Get models with aliases (only from active providers by default)
@@ -77,18 +80,6 @@ export async function GET(request: Request) {
}
}
const models = AI_MODELS.map((m: any) => {
const fullModel = `${m.provider}/${m.model}`;
const available = !activeProviders || activeProviders.has(m.provider);
return {
...m,
fullModel,
alias: modelAliases[fullModel] || m.model,
available,
supportsVision: getResolvedModelCapabilities(fullModel).supportsVision === true,
};
}).filter((m: any) => showAll || m.available);
// #6328 (follow-up to #6495): REMOVE — not just hide — paid models from the
// dashboard model picker when the operator opts into hidePaidModels. Mirrors
// the `shouldHidePaid` guard in `src/app/api/v1/models/catalog.ts` (public
@@ -98,14 +89,33 @@ export async function GET(request: Request) {
const settings = await getSettings();
hidePaid = settings?.hidePaidModels === true;
} catch {}
const filtered = hidePaid
? models.filter(
(m: { provider: string; model: string }) =>
providerHasFreeModels(m.provider) && isFreeModel(m.provider, { id: m.model })
)
: models;
return NextResponse.json({ models: filtered });
// Filter before capability resolution so unavailable/paid rows cannot trigger
// needless capability work. One request-local snapshot supplies all persisted
// capability and custom-vision rows to the remaining resolutions.
const candidates = AI_MODELS.filter((model: any) => {
if (!showAll && activeProviders && !activeProviders.has(model.provider)) return false;
return (
!hidePaid ||
(providerHasFreeModels(model.provider) && isFreeModel(model.provider, { id: model.model }))
);
});
const capabilitySnapshot = createModelCapabilityResolutionSnapshot();
const models = candidates.map((m: any) => {
const fullModel = `${m.provider}/${m.model}`;
const available = !activeProviders || activeProviders.has(m.provider);
return {
...m,
fullModel,
alias: modelAliases[fullModel] || m.model,
available,
supportsVision:
getResolvedModelCapabilities(fullModel, undefined, capabilitySnapshot).supportsVision ===
true,
};
});
return NextResponse.json({ models });
} catch (error) {
console.log("Error fetching models:", error);
return NextResponse.json({ error: "Failed to fetch models" }, { status: 500 });

View File

@@ -90,6 +90,74 @@ export async function getAllCustomModels() {
return result;
}
/** Nested provider → model map of explicit custom-model vision overrides. */
export type CustomModelVisionOverrideMap = ReadonlyMap<string, ReadonlyMap<string, boolean>>;
function readVisionOverrideFromModels(value: string | null, modelId: string): boolean | null {
if (!value) return null;
try {
const models = JSON.parse(value) as unknown;
if (!Array.isArray(models)) return null;
const entry = models.find(
(candidate): candidate is { id: string; supportsVision?: boolean } =>
candidate !== null &&
typeof candidate === "object" &&
!Array.isArray(candidate) &&
(candidate as { id?: unknown }).id === modelId
);
return entry && typeof entry.supportsVision === "boolean" ? entry.supportsVision : null;
} catch {
return null;
}
}
/**
* Resolve one explicit custom-model vision override. A supplied bulk map avoids
* SQLite reads for request/build-local capability resolution.
*/
export function getCustomModelVisionOverride(
providerId: string,
modelId: string,
bulk?: CustomModelVisionOverrideMap | null
): boolean | null {
if (bulk) return bulk.get(providerId)?.get(modelId) ?? null;
const row = getDbInstance()
.prepare("SELECT value FROM key_value WHERE namespace = 'customModels' AND key = ?")
.get(providerId);
return readVisionOverrideFromModels(getKeyValue(row).value, modelId);
}
/** Bulk-load explicit custom-model vision overrides with one SQLite query. */
export function listCustomModelVisionOverrides(): CustomModelVisionOverrideMap {
const rows = getDbInstance()
.prepare("SELECT key, value FROM key_value WHERE namespace = 'customModels'")
.all();
const result = new Map<string, Map<string, boolean>>();
for (const row of rows) {
const { key, value } = getKeyValue(row);
if (!key || !value) continue;
try {
const models = JSON.parse(value) as unknown;
if (!Array.isArray(models)) continue;
const byModel = new Map<string, boolean>();
for (const candidate of models) {
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
const { id, supportsVision } = candidate as {
id?: unknown;
supportsVision?: unknown;
};
if (typeof id === "string" && typeof supportsVision === "boolean") {
byModel.set(id, supportsVision);
}
}
if (byModel.size > 0) result.set(key, byModel);
} catch {
// Malformed custom-model rows do not participate in capability resolution.
}
}
return result;
}
export async function addCustomModel(
providerId: string,
modelId: string,

View File

@@ -27,12 +27,22 @@ export interface BridgeCacheOptions {
now?: () => number;
}
export interface BridgeCacheEntry {
value: string;
/** Actual successful producer, which may differ from the routing-plan model after fallback. */
producerModel?: string;
}
export class BridgeCache {
private readonly entries = new Map<string, { value: string; expiresAt: number }>();
private readonly entries = new Map<string, { entry: BridgeCacheEntry; expiresAt: number }>();
constructor(private readonly opts: BridgeCacheOptions) {}
get(key: string): string | undefined {
return this.getEntry(key)?.value;
}
getEntry(key: string): BridgeCacheEntry | undefined {
const hit = this.entries.get(key);
if (!hit) return undefined;
const now = (this.opts.now ?? Date.now)();
@@ -43,13 +53,17 @@ export class BridgeCache {
// Map preserves insertion order — re-insert to mark as most-recently-used.
this.entries.delete(key);
this.entries.set(key, hit);
return hit.value;
return hit.entry;
}
set(key: string, value: string): void {
this.setEntry(key, { value });
}
setEntry(key: string, entry: BridgeCacheEntry): void {
const now = (this.opts.now ?? Date.now)();
this.entries.delete(key);
this.entries.set(key, { value, expiresAt: now + this.opts.ttlMs });
this.entries.set(key, { entry, expiresAt: now + this.opts.ttlMs });
while (this.entries.size > this.opts.maxEntries) {
const oldest = this.entries.keys().next().value;
if (oldest === undefined) break;

View File

@@ -18,6 +18,7 @@ export interface BridgeModalityStats {
cacheHits: number;
failures: number;
lastUsedAt: string | null;
latencySamples: number;
successes: number;
totalLatencyMs: number;
}
@@ -38,6 +39,7 @@ function emptyStats(): BridgeModalityStats {
cacheHits: 0,
failures: 0,
lastUsedAt: null,
latencySamples: 0,
successes: 0,
totalLatencyMs: 0,
};
@@ -64,8 +66,9 @@ export function recordBridgeUse(
s.cacheHits += cacheHits;
if (typeof opts.latencyMs === "number" && Number.isFinite(opts.latencyMs)) {
s.totalLatencyMs += Math.max(0, opts.latencyMs);
s.latencySamples += 1;
}
s.averageLatencyMs = s.attempts > 0 ? s.totalLatencyMs / s.attempts : 0;
s.averageLatencyMs = s.latencySamples > 0 ? s.totalLatencyMs / s.latencySamples : 0;
s.lastUsedAt = new Date().toISOString();
}

View File

@@ -30,6 +30,12 @@ type VideoBridgeBody = {
[key: string]: unknown;
};
function combineModelIdentities(models: ReadonlySet<string>, fallback: string): string {
if (models.size === 0) return fallback;
if (models.size === 1) return models.values().next().value ?? fallback;
return "mixed";
}
export interface VideoBridgeDependencies {
getSettings?: () => Promise<Record<string, unknown>>;
getCapabilities?: (model: string) => { supportsVideo: boolean | null };
@@ -83,7 +89,8 @@ export class VideoBridgeGuardrail extends BaseGuardrail {
const visionRuntime = resolveVisionBridgeRuntimeSettings(persisted);
const configuredModel = runtime.model.trim() || visionRuntime.model.trim();
let effectiveVideoModel = configuredModel || "auto";
const routingPlanModel = configuredModel || "auto";
const successfulModels = new Set<string>();
let selectedModelPromise: Promise<string | null> | null = null;
const selectVideoModel = (): Promise<string | null> => {
if (!selectedModelPromise) {
@@ -119,7 +126,7 @@ export class VideoBridgeGuardrail extends BaseGuardrail {
context.signal
);
if (context.signal?.aborted) throw new Error("Video Bridge processing was aborted");
if (described.modelUsed) effectiveVideoModel = described.modelUsed;
if (described.modelUsed) successfulModels.add(described.modelUsed);
const videoCacheHits = described.cacheHits ?? 0;
descriptions.push(described.description);
totalFramesRequested += described.framesRequested;
@@ -181,7 +188,7 @@ export class VideoBridgeGuardrail extends BaseGuardrail {
framesUsed: totalFramesUsed,
processingTimeMs: Date.now() - startedAt,
attempts: attemptedParts.length,
videoModel: effectiveVideoModel,
videoModel: combineModelIdentities(successfulModels, routingPlanModel),
videosProcessed,
videosReplaced,
},
@@ -201,6 +208,7 @@ export class VideoBridgeGuardrail extends BaseGuardrail {
const cache = runtime.cacheEnabled ? getSharedBridgeCacheFor(runtime) : null;
const callVisionModel = this.deps.callVisionModel ?? defaultCallVisionModel;
let cacheHits = 0;
const successfulModels = new Set<string>();
const described = await defaultDescribeVideoPart(
part,
{
@@ -213,23 +221,33 @@ export class VideoBridgeGuardrail extends BaseGuardrail {
const key = cache
? bridgeCacheKey(frameDataUri, `${prompt}@${timestampSeconds.toFixed(3)}`, selectedModel)
: null;
const cached = key && cache ? cache.get(key) : undefined;
if (cached !== undefined) {
const cached = key && cache ? cache.getEntry(key) : undefined;
if (cached) {
cacheHits += 1;
return cached;
successfulModels.add(cached.producerModel ?? selectedModel);
return cached.value;
}
let producerModel = selectedModel;
const caption = await callVisionModel(frameDataUri, {
maxImages: 1,
model: selectedModel,
onModelUsed: (model) => {
producerModel = model;
},
prompt,
signal,
timeoutMs: runtime.timeoutMs,
});
if (key && cache) cache.set(key, caption);
successfulModels.add(producerModel);
if (key && cache) cache.setEntry(key, { value: caption, producerModel });
return caption;
},
{ extractFrames: this.deps.extractFrames }
);
return { ...described, cacheHits, modelUsed: selectedModel };
return {
...described,
cacheHits,
modelUsed: combineModelIdentities(successfulModels, selectedModel),
};
}
}

View File

@@ -350,6 +350,8 @@ export interface VisionModelConfig {
signal?: AbortSignal;
/** Injectable fetch (tests). Defaults to undici fetch to bypass the runtime's hooked global fetch. */
fetchImpl?: typeof fetch;
/** Receives the actual successful model while the public return value remains a string. */
onModelUsed?: (model: string) => void;
}
/** Task-aware focus hint (codex-vision-proxy pattern): steer the description
@@ -416,6 +418,11 @@ export async function callVisionModel(
apiKey
);
recordLatency(currentModel, Date.now() - attemptStart, true);
try {
config.onModelUsed?.(currentModel);
} catch {
// Observability callbacks must never turn a successful caption into a retry.
}
return result;
} catch (error) {
recordLatency(currentModel, Date.now() - attemptStart, false);

View File

@@ -14,8 +14,7 @@ import { getSyncedCapability } from "@/lib/modelsDevSync";
import { MODELS_DEV_PROVIDER_MAP } from "@/lib/modelsDevSync/transform";
import { getModelContextOverride } from "@/lib/db/modelContextOverrides";
import { getModelCapabilityOverride } from "@/lib/db/modelCapabilityOverrides";
import { getDbInstance } from "@/lib/db/core";
import { getKeyValue } from "@/lib/db/models/shared";
import { getCustomModelVisionOverride } from "@/lib/db/models";
import type { ModelCapabilityResolutionSnapshot } from "@/lib/modelCapabilityResolutionSnapshot";
import { resolveAudioCapability, resolveVideoCapability } from "@/lib/modelCapabilityModalities";
@@ -462,31 +461,6 @@ function modalitiesDeclareVision(modalities: readonly string[]): boolean {
});
}
/**
* #9195: Read the customModels supportsVision override for a given provider/model
* pair from the database. Returns true/false when an explicit override exists, or
* null if no custom model entry or no explicit flag. Sync read (better-sqlite3).
*/
function getCustomModelVisionOverride(provider: string, model: string): boolean | null {
try {
const db = getDbInstance();
const row = db
.prepare("SELECT value FROM key_value WHERE namespace = 'customModels' AND key = ?")
.get(provider);
if (!row) return null;
const parsed = getKeyValue(row);
if (!parsed.value) return null;
const models: Array<{ id: string; supportsVision?: boolean }> = JSON.parse(parsed.value);
const entry = models.find((m) => m.id === model);
if (entry && typeof entry.supportsVision === "boolean") {
return entry.supportsVision;
}
return null;
} catch {
return null;
}
}
function resolveVisionCapability(
spec: ModelSpec | undefined,
registryModel: { supportsVision?: boolean } | null,
@@ -782,7 +756,11 @@ export function getResolvedModelCapabilities(
// dashboard "Vision capable" toggle affects Combo routing.
const customVisionOverride =
resolved.provider && resolved.model
? getCustomModelVisionOverride(resolved.provider, resolved.model)
? getCustomModelVisionOverride(
resolved.provider,
resolved.model,
snapshot?.customVisionOverrides
)
: null;
const supportsVision = resolveVisionCapability(

View File

@@ -1,7 +1,7 @@
/**
* Build-local capability/context/override resolution snapshot (#9199).
*
* Catalog preparation bulk-loads the three capability tables once into a
* Catalog preparation bulk-loads the capability and custom-model tables once into a
* build-local view for pure in-memory resolution. This must not flip models.dev's
* module-global all-row cache, and ordinary runtime callers keep on-demand DB reads.
*
@@ -10,6 +10,7 @@
*/
import { listModelCapabilityOverrides } from "@/lib/db/modelCapabilityOverrides";
import { listModelContextOverrides } from "@/lib/db/modelContextOverrides";
import { listCustomModelVisionOverrides, type CustomModelVisionOverrideMap } from "@/lib/db/models";
import {
loadAllSyncedCapabilitiesUncached,
type CapabilitiesByProvider,
@@ -23,6 +24,7 @@ export interface ModelCapabilityResolutionSnapshot {
readonly maxTokenOverrides: NestedOverrideMap;
readonly maxInputTokenOverrides: NestedOverrideMap;
readonly contextOverrides: NestedOverrideMap;
readonly customVisionOverrides: CustomModelVisionOverrideMap;
}
function setNestedOverride(
@@ -40,7 +42,7 @@ function setNestedOverride(
}
/**
* Load all three capability tables in one uninterrupted JS turn.
* Load all capability/custom-model tables in one uninterrupted JS turn.
* Callers must not yield between the bulk reads if they need a coherent view;
* existing catalog generation guards remain authoritative across later yields.
*/
@@ -67,5 +69,6 @@ export function createModelCapabilityResolutionSnapshot(): ModelCapabilityResolu
maxTokenOverrides,
maxInputTokenOverrides,
contextOverrides,
customVisionOverrides: listCustomModelVisionOverrides(),
};
}

View File

@@ -38,15 +38,49 @@ test.after(() => {
test("/api/models retains genuine resolved vision capability for the Video Bridge picker", async () => {
await settingsDb.updateSettings({ hidePaidModels: false });
const models = await fetchModels();
const db = core.getDbInstance();
db.prepare(
"INSERT OR REPLACE INTO key_value (namespace, key, value) " + "VALUES ('customModels', ?, ?)"
).run("openai", JSON.stringify([{ id: "gpt-4o", supportsVision: false }]));
const originalPrepare = db.prepare;
const callPrepare = originalPrepare.bind(db);
let customModelReads = 0;
(db as unknown as { prepare: typeof db.prepare }).prepare = ((sql: string) => {
const normalized = String(sql).replace(/\s+/g, " ").trim();
if (normalized.includes("FROM key_value WHERE namespace = 'customModels'")) {
customModelReads++;
}
return callPrepare(sql);
}) as typeof db.prepare;
let models: Awaited<ReturnType<typeof fetchModels>>;
try {
models = await fetchModels();
} finally {
(db as unknown as { prepare: typeof db.prepare }).prepare = originalPrepare;
}
const vision = models.find(
(model) => model.provider === "openai" && model.model === "gpt-4o-mini"
);
const textOnly = models.find((model) => model.provider === "deepgram");
const explicitlyDowngraded = models.find(
(model) => model.provider === "openai" && model.model === "gpt-4o"
);
assert.ok(vision, "known static vision model must be present in the real producer response");
assert.equal(vision.supportsVision, true);
if (textOnly) assert.notEqual(textOnly.supportsVision, true);
assert.ok(explicitlyDowngraded, "custom-overridden model must remain in the real producer");
assert.equal(
explicitlyDowngraded.supportsVision,
false,
"request snapshot must preserve the explicit custom supportsVision override"
);
assert.equal(
customModelReads,
1,
"one request-level capability snapshot must bulk-read custom vision overrides once"
);
});
test("#6328 /api/models removes paid models when hidePaidModels is on", async () => {

View File

@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import test from "node:test";
import { VideoBridgeGuardrail } from "../../../src/lib/guardrails/videoBridge.ts";
import { callVisionModel } from "../../../src/lib/guardrails/visionBridgeHelpers.ts";
import {
buildModalityBridgeHeader,
getBridgeStats,
@@ -339,6 +340,60 @@ test("real Video Bridge cache hit avoids a second model call and records the hit
assert.equal(second.meta?.cacheHits, 1);
});
test("real primary failure reports and caches the successful fallback model identity", async () => {
const primary = "openai/gpt-4o-mini";
const fallback = "anthropic/claude-fable-5";
const attemptedModels: string[] = [];
const fetchImpl: typeof fetch = async (_input, init) => {
const body = JSON.parse(String(init?.body)) as { model: string };
attemptedModels.push(body.model);
if (body.model === "gpt-4o-mini") {
return new Response("primary unavailable", { status: 503 });
}
return Response.json({ content: [{ type: "text", text: "fallback observation" }] });
};
const bridge = new VideoBridgeGuardrail({
deps: {
getSettings: async () => ({
modalityBridgeVideoEnabled: true,
modalityBridgeVideoModel: primary,
modalityBridgeVisionPrompt: "fallback identity integration 9760",
modalityBridgeCacheEnabled: true,
modalityBridgeCacheTtlMinutes: 62,
modalityBridgeCacheMaxEntries: 52,
}),
getCapabilities: () => ({ supportsVideo: false }),
selectVisionModel: async () => primary,
extractFrames: async () => ({
durationSeconds: 1,
frames: [{ timestampSeconds: 0.5, dataUri: "data:image/jpeg;base64,FALLBACK9760" }],
}),
callVisionModel: (image, config) =>
callVisionModel(
image,
{ ...config, fetchImpl },
"sk-fallback-test",
{ maxFallbackAttempts: 2 },
{
hasUsableCredentials: async (model) => model === primary || model === fallback,
}
),
},
});
const first = await bridge.preCall(payload(), {});
const second = await bridge.preCall(payload(), {});
assert.deepEqual(attemptedModels, ["gpt-4o-mini", "claude-fable-5"]);
assert.equal(first.meta?.videoModel, fallback, "meta must name the successful fallback");
assert.equal(second.meta?.videoModel, fallback, "cache hit must retain the producer identity");
assert.equal(second.meta?.cacheHits, 1);
assert.equal(
buildModalityBridgeHeader([{ guardrail: "video-bridge", meta: second.meta }]),
`video->text;model=${fallback};parts=1`
);
});
test("cache keys miss on timestamp, prompt, and effective model changes; failures are not cached", async () => {
let timestamp = 0.25;
let prompt = "prompt-a-9760";

View File

@@ -117,6 +117,10 @@ describe("ModalityBridgeAudioTab", () => {
expect(optionValues).not.toContain("deepgram/aura");
expect(optionValues).not.toContain("openai/gpt-5.6");
expect(el.textContent).toContain("4 modalityBridgeStatsBridged");
expect(el.textContent).toContain("5 requestlogger.attempts");
expect(el.textContent).toContain("trafficInspector.timingTotalLatency: —");
expect(el.textContent).toContain("avgLatency: —");
expect(el.textContent).not.toContain("0 ms");
});
it("PATCHes only the new audio setting when toggled", async () => {

View File

@@ -154,6 +154,10 @@ describe("ModalityBridgeVisionTab", () => {
await waitFor(() => el.textContent?.includes("3 modalityBridgeStatsBridged") ?? false, "stats");
expect(el.textContent).toContain("1 modalityBridgeStatsCacheHits");
expect(el.textContent).toContain("3 requestlogger.attempts");
expect(el.textContent).toContain("trafficInspector.timingTotalLatency: —");
expect(el.textContent).toContain("avgLatency: —");
expect(el.textContent).not.toContain("0 ms");
});
it("clamps advanced numeric settings to the schema bounds before PATCHing", async () => {

View File

@@ -41,10 +41,22 @@ test("tracks attempts, successes, failures, cache hits, and latency without coun
assert.equal(after.cacheHits - before.cacheHits, 2);
assert.equal(after.failures - before.failures, 1);
assert.equal(after.totalLatencyMs - before.totalLatencyMs, 200);
assert.equal(after.averageLatencyMs, after.totalLatencyMs / after.attempts);
assert.equal(after.latencySamples - before.latencySamples, 2);
assert.equal(after.averageLatencyMs, after.totalLatencyMs / after.latencySamples);
assert.match(after.lastUsedAt ?? "", /^\d{4}-\d{2}-\d{2}T/);
});
test("Vision and Audio attempts without timing do not fabricate zero-millisecond samples", () => {
for (const kind of ["vision", "audio"] as const) {
const before = getBridgeStats()[kind];
recordBridgeUse(kind, { cacheHit: true });
const after = getBridgeStats()[kind];
assert.equal(after.attempts - before.attempts, 1);
assert.equal(after.latencySamples, before.latencySamples);
assert.equal(after.totalLatencyMs, before.totalLatencyMs);
}
});
test("video header is omitted when every attempted video failed", () => {
assert.equal(
buildModalityBridgeHeader([