mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 22:52:19 +03:00
⭐5 — Variantes de modelo com sufixo de effort (ex: model-max/model-xhigh) são sintetizadas no catálogo a partir de supportedThinkingEfforts do modelo base, sem linha própria no registry — getResolvedModelCapabilities resolvia NULL para tool/vision/context, o que fazia combos com tools reordenar silenciosamente essas variantes para trás. Fix: quando todas as fontes diretas falham, resolve as capacidades a partir do modelo base após strip do sufixo. TDD, 65 testes focados verdes.
127 lines
5.7 KiB
TypeScript
127 lines
5.7 KiB
TypeScript
/**
|
|
* Verify that command-code registry models with `supportsVision: true` resolve
|
|
* correctly via `getResolvedModelCapabilities`.
|
|
*
|
|
* Before the fix: the command-code registry had NO `supportsVision` flags.
|
|
* The guardrail used `getResolvedModelCapabilities` → `resolveVisionCapability`,
|
|
* which had no registry flag and no heuristic match, returning `null`/`false`.
|
|
* This caused the Vision Bridge to incorrectly reroute to opencode-zen (401).
|
|
*
|
|
* After the fix: the registry declares `supportsVision: true` for CC
|
|
* vision-capable models, so the guardrail sees native vision support and
|
|
* passes through unmodified — EXCEPT the text-only codex variant
|
|
* (`gpt-5.3-codex` via the Command Code gateway), which issue #10703 confirms
|
|
* cannot see images and was previously mis-flagged as vision in the registry.
|
|
* It follows the same `KNOWN_TEXT_ONLY_DESPITE_SYNC` hard-override path as
|
|
* `mimo-v2.5-pro`. `gpt-5.4-mini` is intentionally left as vision-capable —
|
|
* it's a real OpenAI multimodal model and the Command Code gateway forwards
|
|
* images correctly for it.
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { getResolvedModelCapabilities } from "../../src/lib/modelCapabilities.ts";
|
|
|
|
// ── Models that SHOULD have supportsVision: true ────────────────────────────
|
|
|
|
const CC_VISION: [string, string][] = [
|
|
["Claude Opus 4.7 (CC)", "command-code/claude-opus-4-7"],
|
|
["Claude Opus 4.6 (CC)", "command-code/claude-opus-4-6"],
|
|
["Claude Sonnet 4.6 (CC)", "command-code/claude-sonnet-4-6"],
|
|
["Claude Haiku 4.5 (CC)", "command-code/claude-haiku-4-5-20251001"],
|
|
["GPT-5.5 (CC)", "command-code/gpt-5.5"],
|
|
["GPT-5.4 (CC)", "command-code/gpt-5.4"],
|
|
["GPT-5.4 Mini (CC)", "command-code/gpt-5.4-mini"],
|
|
["Kimi K2.6 (CC)", "command-code/moonshotai/Kimi-K2.6"],
|
|
["Kimi K2.5 (CC)", "command-code/moonshotai/Kimi-K2.5"],
|
|
["Qwen 3.6 Plus (CC)", "command-code/Qwen/Qwen3.6-Plus"],
|
|
];
|
|
|
|
// ── Models that MUST NOT claim vision (text-only) ──────────────────────────
|
|
|
|
const CC_TEXT_ONLY: [string, string][] = [
|
|
["GPT-5.3 Codex (CC)", "command-code/gpt-5.3-codex"],
|
|
["DeepSeek V4 Pro (CC)", "command-code/deepseek/deepseek-v4-pro"],
|
|
["DeepSeek V4 Flash (CC)", "command-code/deepseek/deepseek-v4-flash"],
|
|
["GLM-5.1 (CC)", "command-code/zai-org/GLM-5.1"],
|
|
["GLM-5 (CC)", "command-code/zai-org/GLM-5"],
|
|
["MiniMax M2.7 (CC)", "command-code/MiniMaxAI/MiniMax-M2.7"],
|
|
["MiniMax M2.5 (CC)", "command-code/MiniMaxAI/MiniMax-M2.5"],
|
|
["Qwen 3.6 Max Preview (CC)", "command-code/Qwen/Qwen3.6-Max-Preview"],
|
|
];
|
|
|
|
for (const [name, modelId] of CC_VISION) {
|
|
test(`${name} resolves supportsVision: true`, () => {
|
|
const caps = getResolvedModelCapabilities(modelId);
|
|
assert.equal(caps.supportsVision, true, `${modelId} must have supportsVision: true`);
|
|
assert.equal(caps.provider, "command-code");
|
|
});
|
|
}
|
|
|
|
for (const [name, modelId] of CC_TEXT_ONLY) {
|
|
test(`${name} does not falsely claim vision`, () => {
|
|
const caps = getResolvedModelCapabilities(modelId);
|
|
assert.notEqual(
|
|
caps.supportsVision,
|
|
true,
|
|
`${modelId} is text-only — must not have supportsVision: true`
|
|
);
|
|
assert.equal(caps.provider, "command-code");
|
|
});
|
|
}
|
|
|
|
test("MiniMax M3 via command-code keeps existing vision capability (no regression)", () => {
|
|
const caps = getResolvedModelCapabilities("command-code/MiniMaxAI/MiniMax-M3");
|
|
assert.equal(caps.supportsVision, true);
|
|
});
|
|
|
|
test("command-code effort-suffixed variants resolve capabilities from their base model", async () => {
|
|
// Effort variants (e.g. `-max`, `-xhigh`) are synthesized from the base's
|
|
// supportedThinkingEfforts and have no registry/synced row of their own.
|
|
// Without the base-model fallback they resolve NULL tool/vision/context,
|
|
// which makes a tool-bearing combo request drop them behind confirmed
|
|
// targets (observed: orchestrator tried opencode-go/mimo-v2.5-max at
|
|
// position 2 while command-code deepseek sat unused at its declared
|
|
// priority position 2).
|
|
//
|
|
// Seed the models.dev capability store (the source getResolvedModelCapabilities
|
|
// reads for tool/vision/context) with the base models, then verify the
|
|
// effort-suffixed variants inherit those capabilities via the base fallback.
|
|
const modelsDevSync = await import("../../src/lib/modelsDevSync.ts");
|
|
modelsDevSync.saveModelsDevCapabilities({
|
|
"command-code": {
|
|
"deepseek/deepseek-v4-flash": {
|
|
tool_call: true,
|
|
reasoning: true,
|
|
attachment: false,
|
|
limit_context: 1000000,
|
|
limit_input: 1000000,
|
|
limit_output: 131072,
|
|
},
|
|
"meta/muse-spark-1.2-contributor": {
|
|
tool_call: true,
|
|
reasoning: true,
|
|
attachment: true,
|
|
limit_context: 1048576,
|
|
limit_input: 1048576,
|
|
limit_output: 1048576,
|
|
},
|
|
},
|
|
});
|
|
|
|
const cases: Array<[string, boolean]> = [
|
|
["command-code/deepseek/deepseek-v4-flash-max", false], // text-only base
|
|
["command-code/deepseek/deepseek-v4-flash-high", false],
|
|
["command-code/meta/muse-spark-1.2-contributor-xhigh", true], // vision base
|
|
["command-code/meta/muse-spark-1.2-contributor-high", true],
|
|
];
|
|
for (const [modelId, vision] of cases) {
|
|
const caps = getResolvedModelCapabilities(modelId);
|
|
assert.equal(caps.provider, "command-code", `${modelId} provider`);
|
|
assert.equal(caps.supportsTools, true, `${modelId} must inherit tool support from base`);
|
|
assert.equal(caps.supportsVision, vision, `${modelId} must inherit vision from base`);
|
|
assert.equal(typeof caps.contextWindow, "number", `${modelId} must inherit a context window`);
|
|
assert.equal(caps.supportsThinking, true, `${modelId} must inherit reasoning from base`);
|
|
}
|
|
});
|