Files
OmniRoute/tests/unit/vision-bridge-native-skip.test.ts
Diego Rodrigues de Sa e Souza a7ddcf16ba feat(bridge): native-vision skip guard + configurable describe output cap (#10289)
* test(bridge): explicit native-vision skip guard + skip log

* feat(bridge): configurable describe output cap (modalityBridgeVisionMaxChars)

* feat(dashboard): maxChars field on Modality Bridge vision tab

Add the "Max description characters" field to the Vision tab's Advanced
panel (modalityBridgeVisionMaxChars, clamped to the 100-50000 schema
range with 0 treated as the explicit "unlimited" sentinel), wire the
en.json copy and sync it across all 42 locales, and document the new
setting in GUARDRAILS.md.

* fix(bridge): allow explicit 0 to disable the describe cap

updateSettingsSchema previously rejected modalityBridgeVisionMaxChars: 0
because the field's range was min(100).max(50000), so a dashboard PATCH
sending the explicit "unlimited" sentinel would 400. Widen the schema to
z.union([z.literal(0), z.number().int().min(100).max(50000)]) so 0
validates as its own valid value, not just an implicit default.

* chore(i18n): resync locale keys after release merge

* chore(quality): rebaseline deadExports for the OCR/image-to-text series

---------

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
2026-08-14 13:06:29 -03:00

113 lines
3.8 KiB
TypeScript

/**
* Explicit guard for the "target model already supports vision natively" skip
* path (openclaw `runner.ts:711` prompted this — OmniRoute already relied on
* the behavior implicitly, this test makes the contract explicit and asserts
* the dedicated skip log).
*
* Follows the DI harness pattern from `vision-bridge-cc-no-reroute.test.ts`:
* settings/vision-call/combo-mapping are all injected, so this exercises
* `VisionBridgeGuardrail.preCall` without going through any real network call.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { VisionBridgeGuardrail } = await import("../../src/lib/guardrails/visionBridge.ts");
import type { GuardrailContext } from "../../src/lib/guardrails/base.ts";
import type { VisionModelConfig } from "../../src/lib/guardrails/visionBridgeHelpers.ts";
// Known registry model with supportsVision: true (see
// vision-bridge-cc-no-reroute.test.ts CC-VB-SANITY).
const VISION_CAPABLE_MODEL = "command-code/gpt-5.5";
function imagePayload(model: string): Record<string, unknown> {
return {
model,
messages: [
{
role: "user",
content: [
{ type: "text", text: "What is in this image?" },
{
type: "image_url",
image_url: { url: "https://example.com/image.png" },
},
],
},
],
};
}
/** Records every (level, category, message) call so the test can assert on it. */
class LogSpy {
calls: Array<{ level: string; category: string; message: string }> = [];
private record(level: string) {
return (category: string, message: string) => {
this.calls.push({ level, category, message });
};
}
debug = this.record("debug");
info = this.record("info");
warn = this.record("warn");
error = this.record("error");
}
test("native-vision skip: messages untouched, zero vision calls, dedicated skip log", async () => {
let visionCallCount = 0;
const guardrail = new VisionBridgeGuardrail({
deps: {
getSettings: async () => ({
visionBridgeEnabled: true,
visionBridgeModel: "openai/gpt-4o-mini",
}),
callVisionModel: async (_imageDataUri: string, _config: VisionModelConfig) => {
visionCallCount++;
return "should never be produced";
},
// No `checkModelHasComboMapping` override: let the real (DB-backed)
// combo lookup run, which resolves to "not-combo" for a plain model
// string that is neither a combo name nor combo-mapped — exactly the
// path that must hit the native-vision skip + log.
},
});
const originalPayload = imagePayload(VISION_CAPABLE_MODEL);
const payloadSnapshot = JSON.parse(JSON.stringify(originalPayload));
const logSpy = new LogSpy();
const context: GuardrailContext = {
model: VISION_CAPABLE_MODEL,
log: logSpy as unknown as GuardrailContext["log"],
};
const result = await guardrail.preCall(originalPayload, context);
// (a) messages intact — guardrail must not modify the payload at all.
assert.strictEqual(result.block, false);
assert.strictEqual(
result.modifiedPayload,
undefined,
"native-vision skip must not rewrite payload"
);
assert.deepStrictEqual(
originalPayload,
payloadSnapshot,
"native-vision skip must not mutate the original messages"
);
// (b) zero calls to the vision (describe) model.
assert.strictEqual(
visionCallCount,
0,
"must not call the vision model when target has native vision"
);
// (c) dedicated skip log emitted.
const skipLog = logSpy.calls.find((c) =>
c.message.includes("Skipping: target model supports vision natively")
);
assert.ok(
skipLog,
`expected a skip log containing "Skipping: target model supports vision natively", got: ${JSON.stringify(logSpy.calls)}`
);
assert.equal(skipLog?.category, "VISION_BRIDGE");
});