fix(guardrails): forward the router deps seam through callVisionModel

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
This commit is contained in:
diegosouzapw
2026-08-07 03:33:09 -03:00
parent 5dfc96ae54
commit e4f55c7d92
2 changed files with 26 additions and 8 deletions

View File

@@ -232,13 +232,19 @@ export async function callVisionModel(
imageDataUri: string,
config: VisionModelConfig,
apiKey?: string,
routerConfig?: Partial<import("./visionBridgeRouter").VisionBridgeRouterConfig>
routerConfig?: Partial<import("./visionBridgeRouter").VisionBridgeRouterConfig>,
deps?: import("./visionBridgeRouter").VisionBridgeRouterDeps
): Promise<string> {
// 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++) {

View File

@@ -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;