mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(no-think): normalize provider prefix to canonical in no-think variants (#4531)
Adds pure normalizeProviderPrefix helper + opt-in aliasToCanonical map so no-think variant ids use canonical provider prefixes in canonical catalog mode. Tested. Rebuilt onto release/v3.8.33. Integrated into release/v3.8.33.
This commit is contained in:
@@ -102,22 +102,47 @@ export function shouldExposeNoThinkingAlias(model: CatalogModelEntry): boolean {
|
||||
if (spec.noThinkingAlias === false) return false;
|
||||
|
||||
return (
|
||||
spec.supportsThinking === true &&
|
||||
spec.rejectsThinkingDisabled !== true &&
|
||||
/claude/i.test(name)
|
||||
spec.supportsThinking === true && spec.rejectsThinkingDisabled !== true && /claude/i.test(name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the provider prefix inside a qualified model id using an alias→canonical map.
|
||||
* e.g. "cc/claude-opus-4-6" → "claude/claude-opus-4-6" when aliasToCanonical["cc"]="claude".
|
||||
* Ids without a "/" or whose prefix is not in the map are returned unchanged.
|
||||
*/
|
||||
function normalizeProviderPrefix(
|
||||
qualifiedId: string,
|
||||
aliasToCanonical: Record<string, string>
|
||||
): string {
|
||||
const slash = qualifiedId.indexOf("/");
|
||||
if (slash < 0) return qualifiedId;
|
||||
const prefix = qualifiedId.slice(0, slash);
|
||||
const canonical = aliasToCanonical[prefix];
|
||||
return canonical && canonical !== prefix
|
||||
? `${canonical}${qualifiedId.slice(slash)}`
|
||||
: qualifiedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a no-thinking variant for every eligible model. Returns the original array
|
||||
* reference unchanged when nothing is eligible (no allocation in the common case).
|
||||
*
|
||||
* @param aliasToCanonical - When provided, the inner provider prefix of each variant id is
|
||||
* normalized to its canonical form (e.g. "cc" → "claude"). Pass this when the catalog is
|
||||
* emitting canonical-prefixed ids so no-think variants stay consistent with the prefix mode.
|
||||
*/
|
||||
export function appendNoThinkingVariants<T extends CatalogModelEntry>(models: T[]): T[] {
|
||||
export function appendNoThinkingVariants<T extends CatalogModelEntry>(
|
||||
models: T[],
|
||||
aliasToCanonical?: Record<string, string>
|
||||
): T[] {
|
||||
if (!Array.isArray(models)) return models;
|
||||
const variants: T[] = [];
|
||||
for (const model of models) {
|
||||
if (!shouldExposeNoThinkingAlias(model)) continue;
|
||||
const aliasId = toNoThinkingAlias(model.id as string);
|
||||
const rawId = model.id as string;
|
||||
const qualifiedId = aliasToCanonical ? normalizeProviderPrefix(rawId, aliasToCanonical) : rawId;
|
||||
const aliasId = toNoThinkingAlias(qualifiedId);
|
||||
const variant: T = { ...model, id: aliasId, root: aliasId };
|
||||
if (typeof model.name === "string" && model.name) {
|
||||
variant.name = `${model.name} (no thinking)`;
|
||||
|
||||
@@ -1397,7 +1397,10 @@ export async function getUnifiedModelsResponse(
|
||||
|
||||
// Advertise no-thinking gateway variants (Fase 8.1). Derived from the already
|
||||
// key-filtered list, so a variant only appears when its real model is permitted.
|
||||
finalModels = appendNoThinkingVariants(finalModels);
|
||||
finalModels = appendNoThinkingVariants(
|
||||
finalModels,
|
||||
prefixMode === "canonical" ? aliasToProviderId : undefined
|
||||
);
|
||||
|
||||
// #4424 follow-up — drop exact-duplicate ids that slip through the per-source push
|
||||
// guards (e.g. `codex/gpt-5.5`, `veo-free/seedance` listed twice). Keyed by listing
|
||||
|
||||
@@ -75,18 +75,29 @@ test("applyNoThinkingAlias strips reasoning fields without a thinking block (Ope
|
||||
});
|
||||
|
||||
test("applyNoThinkingAlias is a no-op for plain models", () => {
|
||||
const body: Record<string, unknown> = { model: "anthropic/claude-opus-4-5", thinking: { type: "enabled" } };
|
||||
const body: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
thinking: { type: "enabled" },
|
||||
};
|
||||
const res = applyNoThinkingAlias(body, { claudeFormat: true });
|
||||
assert.equal(res.applied, false);
|
||||
assert.equal(body.model, "anthropic/claude-opus-4-5");
|
||||
assert.deepEqual(body.thinking, { type: "enabled" }, "thinking is left untouched when not an alias");
|
||||
assert.deepEqual(
|
||||
body.thinking,
|
||||
{ type: "enabled" },
|
||||
"thinking is left untouched when not an alias"
|
||||
);
|
||||
});
|
||||
|
||||
test("applyNoThinkingAlias ignores a malformed prefix-only model", () => {
|
||||
const body: Record<string, unknown> = { model: "claude-3-omniroute-no-thinking/" };
|
||||
const res = applyNoThinkingAlias(body, { claudeFormat: true });
|
||||
assert.equal(res.applied, false);
|
||||
assert.equal(body.model, "claude-3-omniroute-no-thinking/", "left untouched when nothing follows the prefix");
|
||||
assert.equal(
|
||||
body.model,
|
||||
"claude-3-omniroute-no-thinking/",
|
||||
"left untouched when nothing follows the prefix"
|
||||
);
|
||||
});
|
||||
|
||||
// ── catalog gating ───────────────────────────────────────────────────────────
|
||||
@@ -113,16 +124,21 @@ test("shouldExposeNoThinkingAlias rejects models where suppression is meaningles
|
||||
});
|
||||
|
||||
test("appendNoThinkingVariants adds one variant per eligible model and preserves the rest", () => {
|
||||
const models = [
|
||||
entry("claude-opus-4-5"),
|
||||
entry("gpt-4o", "openai"),
|
||||
entry("claude-fable-5"),
|
||||
];
|
||||
const models = [entry("claude-opus-4-5"), entry("gpt-4o", "openai"), entry("claude-fable-5")];
|
||||
const out = appendNoThinkingVariants(models);
|
||||
const ids = out.map((m) => m.id);
|
||||
assert.ok(ids.includes("claude-3-omniroute-no-thinking/claude-opus-4-5"), "eligible model gets a variant");
|
||||
assert.ok(!ids.includes("claude-3-omniroute-no-thinking/gpt-4o"), "non-thinking model has no variant");
|
||||
assert.ok(!ids.includes("claude-3-omniroute-no-thinking/claude-fable-5"), "reject-disabled model has no variant");
|
||||
assert.ok(
|
||||
ids.includes("claude-3-omniroute-no-thinking/claude-opus-4-5"),
|
||||
"eligible model gets a variant"
|
||||
);
|
||||
assert.ok(
|
||||
!ids.includes("claude-3-omniroute-no-thinking/gpt-4o"),
|
||||
"non-thinking model has no variant"
|
||||
);
|
||||
assert.ok(
|
||||
!ids.includes("claude-3-omniroute-no-thinking/claude-fable-5"),
|
||||
"reject-disabled model has no variant"
|
||||
);
|
||||
assert.equal(out.length, models.length + 1, "exactly one variant appended");
|
||||
// originals preserved up front
|
||||
assert.deepEqual(out.slice(0, 3), models);
|
||||
@@ -132,3 +148,28 @@ test("appendNoThinkingVariants returns the same array reference when nothing is
|
||||
const models = [entry("gpt-4o", "openai")];
|
||||
assert.equal(appendNoThinkingVariants(models), models);
|
||||
});
|
||||
|
||||
test("appendNoThinkingVariants normalizes alias prefix to canonical when aliasToCanonical map is provided", () => {
|
||||
const models = [entry("cc/claude-opus-4-5")];
|
||||
const aliasToCanonical = { cc: "claude" };
|
||||
const out = appendNoThinkingVariants(models, aliasToCanonical);
|
||||
const ids = out.map((m) => m.id);
|
||||
assert.ok(
|
||||
ids.includes("claude-3-omniroute-no-thinking/claude/claude-opus-4-5"),
|
||||
"uses canonical prefix"
|
||||
);
|
||||
assert.ok(
|
||||
!ids.includes("claude-3-omniroute-no-thinking/cc/claude-opus-4-5"),
|
||||
"alias prefix not used"
|
||||
);
|
||||
});
|
||||
|
||||
test("appendNoThinkingVariants keeps alias prefix when no map is provided", () => {
|
||||
const models = [entry("cc/claude-opus-4-5")];
|
||||
const out = appendNoThinkingVariants(models);
|
||||
const ids = out.map((m) => m.id);
|
||||
assert.ok(
|
||||
ids.includes("claude-3-omniroute-no-thinking/cc/claude-opus-4-5"),
|
||||
"alias prefix preserved"
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user