mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-24 08:02:14 +03:00
fix(combo): resolve effort variants via base model capabilities (#11034)
⭐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.
This commit is contained in:
1
changelog.d/fixes/command-code-effort-capabilities.md
Normal file
1
changelog.d/fixes/command-code-effort-capabilities.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
- fix(combo): resolve effort-suffixed command-code variants (e.g. `deepseek-v4-flash-max`) to their base model for capability lookups, so tool-bearing combo requests keep the declared priority order instead of reordering behind models with confirmed capabilities
|
||||||
@@ -254,6 +254,39 @@ function leafModelId(modelId: string | null | undefined): string | null {
|
|||||||
return leaf && leaf !== modelId ? leaf : null;
|
return leaf && leaf !== modelId ? leaf : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effort suffixes the catalog synthesizes as `<base>-<tier>` variant ids from a
|
||||||
|
* base model's `supportedThinkingEfforts` (mirrors REGISTERED_EFFORT_SUFFIXES
|
||||||
|
* in open-sse/utils/registeredEffortVariants.ts, plus `minimal` for muse).
|
||||||
|
*/
|
||||||
|
const EFFORT_VARIANT_SUFFIXES = [
|
||||||
|
"none",
|
||||||
|
"minimal",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high",
|
||||||
|
"xhigh",
|
||||||
|
"max",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip a trailing effort-tier suffix off a model id (e.g.
|
||||||
|
* `deepseek-v4-flash-max` → `deepseek-v4-flash`). Longest token first so
|
||||||
|
* `xhigh` is matched before `high`. Returns null when no known suffix matches
|
||||||
|
* or the id would be left empty.
|
||||||
|
*/
|
||||||
|
function stripKnownEffortSuffix(modelId: string): string | null {
|
||||||
|
const normalized = String(modelId || "").trim();
|
||||||
|
if (!normalized) return null;
|
||||||
|
for (const suffix of EFFORT_VARIANT_SUFFIXES) {
|
||||||
|
const token = `-${suffix}`;
|
||||||
|
if (normalized.length > token.length && normalized.endsWith(token)) {
|
||||||
|
return normalized.slice(0, -token.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function getStaticSpec(modelId: string | null, rawModel: string | null): ModelSpec | undefined {
|
function getStaticSpec(modelId: string | null, rawModel: string | null): ModelSpec | undefined {
|
||||||
if (modelId) {
|
if (modelId) {
|
||||||
const byCanonical = getModelSpec(modelId);
|
const byCanonical = getModelSpec(modelId);
|
||||||
@@ -704,15 +737,38 @@ export function getResolvedModelCapabilities(
|
|||||||
// persisted override never feeds back into the comparison that (re)writes it.
|
// persisted override never feeds back into the comparison that (re)writes it.
|
||||||
const usePersistedOverrides = options?.persistedOverrides !== false;
|
const usePersistedOverrides = options?.persistedOverrides !== false;
|
||||||
const resolved = resolveCapabilityInput(input);
|
const resolved = resolveCapabilityInput(input);
|
||||||
const spec = getStaticSpec(resolved.model, resolved.rawModel);
|
let spec = getStaticSpec(resolved.model, resolved.rawModel);
|
||||||
const registryModel = getRegistryModel(resolved.provider, resolved.model);
|
let registryModel = getRegistryModel(resolved.provider, resolved.model);
|
||||||
const synced = getSyncedCapabilityForResolved(
|
let synced = getSyncedCapabilityForResolved(
|
||||||
resolved.provider,
|
resolved.provider,
|
||||||
resolved.model,
|
resolved.model,
|
||||||
resolved.rawModel,
|
resolved.rawModel,
|
||||||
snapshot
|
snapshot
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Effort-suffixed variants (e.g. command-code `deepseek-v4-flash-max`,
|
||||||
|
// `meta/muse-spark-1.2-contributor-xhigh`) are synthesized in the catalog
|
||||||
|
// from the base model's `supportedThinkingEfforts`; they have no registry
|
||||||
|
// row, synced row, or static spec of their own. Without a base-model
|
||||||
|
// fallback the variant resolves with NULL tool/vision/context capabilities,
|
||||||
|
// so a tool-bearing combo request treats the target as incompatible and
|
||||||
|
// silently reorders it behind models with confirmed capabilities. Resolve
|
||||||
|
// the variant's capabilities from its base model when every direct source
|
||||||
|
// misses.
|
||||||
|
if (!spec && !registryModel && !synced && resolved.provider && resolved.model) {
|
||||||
|
const baseModelId = stripKnownEffortSuffix(resolved.model);
|
||||||
|
if (baseModelId && baseModelId !== resolved.model) {
|
||||||
|
spec = getStaticSpec(baseModelId, resolved.rawModel);
|
||||||
|
registryModel = getRegistryModel(resolved.provider, baseModelId);
|
||||||
|
synced = getSyncedCapabilityForResolved(
|
||||||
|
resolved.provider,
|
||||||
|
baseModelId,
|
||||||
|
resolved.rawModel,
|
||||||
|
snapshot
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const modalitiesInput = parseModalities(synced?.modalities_input);
|
const modalitiesInput = parseModalities(synced?.modalities_input);
|
||||||
const modalitiesOutput = parseModalities(synced?.modalities_output);
|
const modalitiesOutput = parseModalities(synced?.modalities_output);
|
||||||
const lookupKey =
|
const lookupKey =
|
||||||
|
|||||||
@@ -74,3 +74,53 @@ test("MiniMax M3 via command-code keeps existing vision capability (no regressio
|
|||||||
const caps = getResolvedModelCapabilities("command-code/MiniMaxAI/MiniMax-M3");
|
const caps = getResolvedModelCapabilities("command-code/MiniMaxAI/MiniMax-M3");
|
||||||
assert.equal(caps.supportsVision, true);
|
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`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user