fix(guardrails): pass providerId to getResolvedModelCapabilities in checkComboVision (#12112) (#12169)

* fix(guardrails): pass providerId to getResolvedModelCapabilities in checkComboVision (#12112)

Signed-off-by: Minxi Hou <houminxi@gmail.com>

* chore(quality): register combo-vision providerId test in the stryker tap set

---------

Signed-off-by: Minxi Hou <houminxi@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
Bob.Hou
2026-09-01 11:08:04 -04:00
committed by GitHub
parent 17792ce0ad
commit 6dd82b77de
4 changed files with 92 additions and 4 deletions

View File

@@ -529,7 +529,10 @@ function hasKnownCompatibleContextLimit(
requirements: RequestCompatibilityRequirements
): boolean {
if (requirements.requiredContextTokens <= 0) return false;
const capabilities = getResolvedModelCapabilities(target.modelStr);
const capabilities = getResolvedModelCapabilities({
provider: target.providerId || target.provider || null,
model: target.modelStr,
});
return evaluateContextLimit(capabilities, requirements, target.modelStr) === true;
}
@@ -546,7 +549,10 @@ export function isVisionIncompatibleTarget(
requirements: RequestCompatibilityRequirements
): boolean {
if (!requirements.requiresVision) return false;
const capabilities = getResolvedModelCapabilities(target.modelStr);
const capabilities = getResolvedModelCapabilities({
provider: target.providerId || target.provider || null,
model: target.modelStr,
});
return capabilities.supportsVision !== true;
}
@@ -571,7 +577,10 @@ function getTargetCompatibilityFailures(
target: ResolvedComboTarget,
requirements: RequestCompatibilityRequirements
): string[] {
const capabilities = getResolvedModelCapabilities(target.modelStr);
const capabilities = getResolvedModelCapabilities({
provider: target.providerId || target.provider || null,
model: target.modelStr,
});
const failures: string[] = [];
if (

View File

@@ -89,7 +89,16 @@ export async function getComboVisionBridgeDecision(
hasModelStep = true;
const targetModel = s.model;
if (typeof targetModel === "string") {
const caps = getResolvedModelCapabilities(targetModel);
const provider =
typeof s.providerId === "string"
? s.providerId
: typeof s.provider === "string"
? s.provider
: null;
const caps = getResolvedModelCapabilities({
provider,
model: targetModel,
});
if (caps.supportsVision === true) {
hasVisionCapableStep = true;
} else {

View File

@@ -255,6 +255,7 @@
"tests/unit/github-model-not-supported-lockout.test.ts",
"tests/unit/grok-cli-oauth.test.ts",
"tests/unit/guardrails-api-3496.test.ts",
"tests/unit/guardrails/combo-vision-provider-id-12112.test.ts",
"tests/unit/guardrails/visionBridge-responses-9597.test.ts",
"tests/unit/headroom-codex-quota-snapshot-6379.test.ts",
"tests/unit/headroom-proxy-lifecycle.test.ts",

View File

@@ -0,0 +1,69 @@
import test from "node:test";
import assert from "node:assert/strict";
process.env.DATA_DIR = `/tmp/omniroute-test-12112-${Date.now()}`;
const { getComboVisionBridgeDecision } = await import(
"../../../src/lib/guardrails/visionBridge.ts"
);
const combosDb = await import("../../../src/lib/db/combos.ts");
const core = await import("../../../src/lib/db/core.ts");
const { isVisionIncompatibleTarget } = await import(
"../../../open-sse/services/combo/comboStructure.ts"
);
import type { ResolvedComboTarget } from "../../../open-sse/services/combo/types.ts";
test.after(() => {
core.resetDbInstance();
});
test("#12112: checkComboVision respects providerId for namespaced vision models (e.g. nvidia/nemotron-nano-12b-v2-vl)", async () => {
// Model 'nvidia/nemotron-nano-12b-v2-vl' is declared with supportsVision: true in nvidia provider registry.
// It has a slash in model id and requires providerId="nvidia" to resolve capabilities.
await combosDb.createCombo({
name: "nvidia-vision-combo-12112",
models: [
{
providerId: "nvidia",
model: "nvidia/nemotron-nano-12b-v2-vl",
weight: 1,
},
],
});
const decision = await getComboVisionBridgeDecision("nvidia-vision-combo-12112");
assert.equal(
decision,
"skip",
"Combo with explicit nvidia vision model must resolve as 'skip' (vision supported), not 'no-vision'"
);
});
test("#12112: isVisionIncompatibleTarget passes providerId to resolve vision capability", () => {
const target: ResolvedComboTarget = {
kind: "model",
stepId: "step-1",
executionKey: "step-1",
modelStr: "nvidia/nemotron-nano-12b-v2-vl",
provider: "nvidia",
providerId: "nvidia",
connectionId: "conn-1",
weight: 1,
label: null,
};
const incompatible = isVisionIncompatibleTarget(target, {
requiresTools: false,
requiresVision: true,
requiresStructuredOutput: false,
estimatedInputTokens: 10,
requestedOutputTokens: 10,
requiredContextTokens: 10,
});
assert.equal(
incompatible,
false,
"Target with providerId='nvidia' and model='nvidia/nemotron-nano-12b-v2-vl' must be vision-compatible"
);
});