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;