mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 05:02:15 +03:00
fix(sse): keep no-think and CC-discovery catalog variant roots unprefixed
This commit is contained in:
@@ -69,6 +69,13 @@ function isMirrorableId(id: string): boolean {
|
||||
return !EFFORT_SUFFIX_RE.test(id);
|
||||
}
|
||||
|
||||
/** Strip a `<provider>/` prefix to get the bare model name, matching the convention in
|
||||
* claudeEffortVariants.ts / noThinkingAlias.ts. */
|
||||
function bareModelName(id: string): string {
|
||||
const slash = id.lastIndexOf("/");
|
||||
return slash >= 0 ? id.slice(slash + 1) : id;
|
||||
}
|
||||
|
||||
export function appendCcDiscoveryAliases<T extends CcDiscoveryCatalogEntry>(
|
||||
models: T[],
|
||||
isEnabled: (entry: T) => boolean
|
||||
@@ -90,7 +97,10 @@ export function appendCcDiscoveryAliases<T extends CcDiscoveryCatalogEntry>(
|
||||
aliases.push({
|
||||
...model,
|
||||
id: aliasId,
|
||||
root: id,
|
||||
// Combo names may legally contain "/" (comboNameSchema allows it), so a combo's
|
||||
// root must stay the full name verbatim — only real provider-qualified ids get
|
||||
// the "/" stripped down to the bare model name.
|
||||
root: isCombo ? id : bareModelName(id),
|
||||
display_name: `${label} (OmniRoute)`,
|
||||
} as T);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,15 @@ import { getModelSpec } from "@/shared/constants/modelSpecs";
|
||||
|
||||
export const NO_THINKING_PREFIX = "no-think/";
|
||||
|
||||
// Ids that already carry a Claude reasoning-effort suffix (see
|
||||
// claudeEffortVariants.ts's identical constant) — a no-think variant of an effort
|
||||
// variant would combine two independent OmniRoute catalog conventions on the same
|
||||
// id. Dispatch-time, applyNoThinkingAlias pre-sets reasoning_effort:"none" before
|
||||
// applyClaudeEffortVariant's hasExplicitClaudeEffort() check runs, so the pre-set
|
||||
// "none" is treated as explicit and the suffix's implied effort is silently
|
||||
// discarded — semantically incoherent, so never advertise the combination.
|
||||
const CLAUDE_EFFORT_SUFFIX_RE = /-(?:xhigh|high|medium|low)$/i;
|
||||
|
||||
/** True when `modelId` carries the no-thinking gateway prefix. */
|
||||
export function isNoThinkingAlias(modelId: unknown): modelId is string {
|
||||
return typeof modelId === "string" && modelId.startsWith(NO_THINKING_PREFIX);
|
||||
@@ -108,6 +117,7 @@ export function shouldExposeNoThinkingAlias(model: CatalogModelEntry): boolean {
|
||||
if (typeof id !== "string" || id.length === 0) return false;
|
||||
if (model.owned_by === "combo") return false; // combos are virtual
|
||||
if (isNoThinkingAlias(id)) return false; // never double-alias
|
||||
if (CLAUDE_EFFORT_SUFFIX_RE.test(id)) return false; // never combine with an effort-suffix id
|
||||
|
||||
const name = bareModelName(id);
|
||||
const spec = getModelSpec(name);
|
||||
@@ -158,7 +168,8 @@ export function appendNoThinkingVariants<T extends CatalogModelEntry>(
|
||||
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 };
|
||||
const bareRoot = toNoThinkingAlias(bareModelName(qualifiedId));
|
||||
const variant: T = { ...model, id: aliasId, root: bareRoot };
|
||||
if (typeof model.name === "string" && model.name) {
|
||||
variant.name = `${model.name} (no thinking)`;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,25 @@ test("adds a claude/ mirror with display_name and root for an eligible model", (
|
||||
assert.deepEqual(out[0], models[0]);
|
||||
const alias = out[1];
|
||||
assert.equal(alias.id, "claude/kimi/kimi-k2.6");
|
||||
assert.equal(alias.root, "kimi/kimi-k2.6");
|
||||
assert.equal(alias.root, "kimi-k2.6");
|
||||
assert.equal(alias.display_name, "Kimi K2.6 (OmniRoute)");
|
||||
assert.equal(alias.owned_by, "kimi");
|
||||
});
|
||||
|
||||
test("keeps root bare even when the original id carries a provider prefix", () => {
|
||||
const models: CatalogEntry[] = [
|
||||
{ id: "vertex/claude-sonnet-5", owned_by: "vertex", name: "Claude Sonnet 5 (Vertex)" },
|
||||
];
|
||||
const out = appendCcDiscoveryAliases(models, alwaysEnabled);
|
||||
const alias = out.find((m) => m.id === "claude/vertex/claude-sonnet-5");
|
||||
assert.ok(alias, "mirror entry with the fully-qualified id must exist");
|
||||
assert.equal(
|
||||
alias!.root,
|
||||
"claude-sonnet-5",
|
||||
"root must be bare, matching the no-think/effort-variant convention"
|
||||
);
|
||||
});
|
||||
|
||||
test("falls back to the id for display_name when name is missing", () => {
|
||||
const models: CatalogEntry[] = [{ id: "kimi/kimi-k2.6" }];
|
||||
const out = appendCcDiscoveryAliases(models, alwaysEnabled);
|
||||
@@ -86,6 +100,17 @@ test("mirrors combo names containing spaces (comboNameSchema allows them)", () =
|
||||
assert.equal(out[1].root, "Custo Otimizado BR");
|
||||
});
|
||||
|
||||
test("keeps a combo's root the full name verbatim when the combo name contains a slash", () => {
|
||||
// comboNameSchema (src/shared/validation/schemas/combo.ts) explicitly allows "/" in
|
||||
// combo names, so bareModelName must NOT be applied to combo entries — only to real
|
||||
// provider-qualified model ids.
|
||||
const models: CatalogEntry[] = [{ id: "Team/Alpha", owned_by: "combo", name: "Team/Alpha" }];
|
||||
const out = appendCcDiscoveryAliases(models, alwaysEnabled);
|
||||
assert.equal(out.length, 2);
|
||||
assert.equal(out[1].id, "claude/combo/Team/Alpha");
|
||||
assert.equal(out[1].root, "Team/Alpha", "root must be the full combo name, not truncated");
|
||||
});
|
||||
|
||||
test("skips disabled entries and returns the same array reference when nothing is eligible", () => {
|
||||
const models: CatalogEntry[] = [{ id: "kimi/kimi-k2.6", owned_by: "kimi" }];
|
||||
const out = appendCcDiscoveryAliases(models, () => false);
|
||||
|
||||
@@ -73,7 +73,11 @@ test("applyNoThinkingAlias expresses reasoning_effort:none without a thinking bl
|
||||
// #6879: a thinks-by-default OpenAI-shape model must carry reasoning_effort:"none"
|
||||
// explicitly (not merely have the field deleted), so suppression actually takes
|
||||
// effect downstream; the Responses-shaped `reasoning` object is still dropped.
|
||||
assert.equal(body.reasoning_effort, "none", "reasoning_effort must express none, not be stripped");
|
||||
assert.equal(
|
||||
body.reasoning_effort,
|
||||
"none",
|
||||
"reasoning_effort must express none, not be stripped"
|
||||
);
|
||||
assert.ok(!("reasoning" in body), "reasoning object must be dropped");
|
||||
});
|
||||
|
||||
@@ -96,11 +100,7 @@ test("applyNoThinkingAlias ignores a malformed prefix-only model", () => {
|
||||
const body: Record<string, unknown> = { model: "no-think/" };
|
||||
const res = applyNoThinkingAlias(body, { claudeFormat: true });
|
||||
assert.equal(res.applied, false);
|
||||
assert.equal(
|
||||
body.model,
|
||||
"no-think/",
|
||||
"left untouched when nothing follows the prefix"
|
||||
);
|
||||
assert.equal(body.model, "no-think/", "left untouched when nothing follows the prefix");
|
||||
});
|
||||
|
||||
// ── catalog gating ───────────────────────────────────────────────────────────
|
||||
@@ -120,28 +120,16 @@ test("shouldExposeNoThinkingAlias rejects models where suppression is meaningles
|
||||
// combos are virtual, never aliased
|
||||
assert.equal(shouldExposeNoThinkingAlias(entry("my-combo", "combo")), false);
|
||||
// never double-alias
|
||||
assert.equal(
|
||||
shouldExposeNoThinkingAlias(entry("no-think/anthropic/claude-opus-4-5")),
|
||||
false
|
||||
);
|
||||
assert.equal(shouldExposeNoThinkingAlias(entry("no-think/anthropic/claude-opus-4-5")), false);
|
||||
});
|
||||
|
||||
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 out = appendNoThinkingVariants(models);
|
||||
const ids = out.map((m) => m.id);
|
||||
assert.ok(
|
||||
ids.includes("no-think/claude-opus-4-5"),
|
||||
"eligible model gets a variant"
|
||||
);
|
||||
assert.ok(
|
||||
!ids.includes("no-think/gpt-4o"),
|
||||
"non-thinking model has no variant"
|
||||
);
|
||||
assert.ok(
|
||||
!ids.includes("no-think/claude-fable-5"),
|
||||
"reject-disabled model has no variant"
|
||||
);
|
||||
assert.ok(ids.includes("no-think/claude-opus-4-5"), "eligible model gets a variant");
|
||||
assert.ok(!ids.includes("no-think/gpt-4o"), "non-thinking model has no variant");
|
||||
assert.ok(!ids.includes("no-think/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);
|
||||
@@ -157,22 +145,42 @@ test("appendNoThinkingVariants normalizes alias prefix to canonical when aliasTo
|
||||
const aliasToCanonical = { cc: "claude" };
|
||||
const out = appendNoThinkingVariants(models, aliasToCanonical);
|
||||
const ids = out.map((m) => m.id);
|
||||
assert.ok(
|
||||
ids.includes("no-think/claude/claude-opus-4-5"),
|
||||
"uses canonical prefix"
|
||||
);
|
||||
assert.ok(
|
||||
!ids.includes("no-think/cc/claude-opus-4-5"),
|
||||
"alias prefix not used"
|
||||
);
|
||||
assert.ok(ids.includes("no-think/claude/claude-opus-4-5"), "uses canonical prefix");
|
||||
assert.ok(!ids.includes("no-think/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("no-think/cc/claude-opus-4-5"), "alias prefix preserved");
|
||||
});
|
||||
|
||||
test("appendNoThinkingVariants keeps root bare even when id carries a provider prefix", () => {
|
||||
const models = [entry("vertex/claude-opus-4-5", "vertex")];
|
||||
const out = appendNoThinkingVariants(models);
|
||||
const variant = out.find((m) => m.id === "no-think/vertex/claude-opus-4-5");
|
||||
assert.ok(variant, "variant with the fully-qualified id must exist");
|
||||
assert.equal(
|
||||
variant!.root,
|
||||
"no-think/claude-opus-4-5",
|
||||
"root must be bare (no embedded provider segment), matching the effort-variant convention"
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldExposeNoThinkingAlias rejects an already effort-suffixed id", () => {
|
||||
assert.equal(shouldExposeNoThinkingAlias(entry("vertex/claude-sonnet-5-high")), false);
|
||||
assert.equal(shouldExposeNoThinkingAlias(entry("claude-opus-4-5-xhigh")), false);
|
||||
});
|
||||
|
||||
test("appendNoThinkingVariants does not synthesize a no-think variant of an effort variant", () => {
|
||||
// Simulates the real pipeline order in catalogResponse.ts: appendClaudeEffortVariants
|
||||
// runs first and produces an id like this before appendNoThinkingVariants ever sees it.
|
||||
const models = [entry("vertex/claude-sonnet-5-high")];
|
||||
const out = appendNoThinkingVariants(models);
|
||||
assert.equal(out, models, "no variant should be added for an effort-suffixed id");
|
||||
assert.ok(
|
||||
ids.includes("no-think/cc/claude-opus-4-5"),
|
||||
"alias prefix preserved"
|
||||
!out.some((m) => m.id === "no-think/vertex/claude-sonnet-5-high"),
|
||||
"the incoherent combined id must never be advertised"
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user