From e4f55c7d92bf15d1e37cc80ddda731a5a6eb0231 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Fri, 7 Aug 2026 03:33:09 -0300 Subject: [PATCH] fix(guardrails): forward the router deps seam through callVisionModel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/unit/guardrails/vision-bridge-sse-and-reasoning.test.ts was 7/7 red on any clean box (CI shard 3/4): callVisionModel() called getBestVisionModel()/ getFallbackModels() WITHOUT the routers' existing VisionBridgeRouterDeps seam, so the credential check always hit the live connections DB — no vision-capable connection meant 'No vision-capable provider connected' before the mocked fetch was ever reached, and on a dev box auto-selection could swap the fixed model under the assertions. The routers already accepted deps; only the forwarding was missing. Added the optional 5th param (backward compatible — the sole production caller, visionBridge.ts, injects its own callVisionModel and is unaffected) and the suite now pins selection with hasUsableCredentials: async () => null (indeterminate → the fixed model is honored, DB untouched). 7/7. Sibling suites re-run green: vision-bridge-callmodel 2/2, visionBridge 25/25, visionBridgeHelpers.callVisionModel 8/8, visionBridgeRouter 10/10, vision-bridge-cc-no-reroute 8/8. Refs #9298 --- src/lib/guardrails/visionBridgeHelpers.ts | 20 ++++++++++++------- .../vision-bridge-sse-and-reasoning.test.ts | 14 ++++++++++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/lib/guardrails/visionBridgeHelpers.ts b/src/lib/guardrails/visionBridgeHelpers.ts index f910be659d..b8376103fd 100644 --- a/src/lib/guardrails/visionBridgeHelpers.ts +++ b/src/lib/guardrails/visionBridgeHelpers.ts @@ -232,13 +232,19 @@ export async function callVisionModel( imageDataUri: string, config: VisionModelConfig, apiKey?: string, - routerConfig?: Partial + routerConfig?: Partial, + deps?: import("./visionBridgeRouter").VisionBridgeRouterDeps ): Promise { - // Auto-select the best vision model - const modelToUse = await getBestVisionModel({ - fixedModel: config.model, - ...routerConfig, - }); + // Auto-select the best vision model. `deps` is the router's existing + // injectable credential-check seam — without forwarding it, tests (and any + // embedder) cannot keep model selection away from the live connections DB. + const modelToUse = await getBestVisionModel( + { + fixedModel: config.model, + ...routerConfig, + }, + deps + ); // (#8430) When no vision-capable provider has usable credentials on this // instance, surface a clear error instead of attempting a describe call that // would fail with an opaque auth/serde error upstream. @@ -248,7 +254,7 @@ export async function callVisionModel( let lastError: Error | null = null; // Try primary model + fallbacks - const modelsToTry = [modelToUse, ...(await getFallbackModels(modelToUse, routerConfig))]; + const modelsToTry = [modelToUse, ...(await getFallbackModels(modelToUse, routerConfig, deps))]; const maxAttempts = Math.min(modelsToTry.length, routerConfig?.maxFallbackAttempts ?? 3); for (let attempt = 0; attempt < maxAttempts; attempt++) { diff --git a/tests/unit/guardrails/vision-bridge-sse-and-reasoning.test.ts b/tests/unit/guardrails/vision-bridge-sse-and-reasoning.test.ts index 41f96543bd..a9e9659fca 100644 --- a/tests/unit/guardrails/vision-bridge-sse-and-reasoning.test.ts +++ b/tests/unit/guardrails/vision-bridge-sse-and-reasoning.test.ts @@ -21,7 +21,19 @@ */ import test from "node:test"; import assert from "node:assert/strict"; -import { callVisionModel, type VisionModelConfig } from "@/lib/guardrails/visionBridgeHelpers"; +import { + callVisionModel as callVisionModelRaw, + type VisionModelConfig, +} from "@/lib/guardrails/visionBridgeHelpers"; + +// Inject the router's credential-check seam as INDETERMINATE (null): the fixed +// model is used as-is and selection never touches the live connections DB — on a +// clean box the suite otherwise dies with "No vision-capable provider connected", +// and on a dev box auto-selection may swap the model under the assertions. +const callVisionModel = (img: string, config: VisionModelConfig) => + callVisionModelRaw(img, config, undefined, undefined, { + hasUsableCredentials: async () => null, + }); const originalFetch = globalThis.fetch;