mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Release v3.8.31 (#4377)
Release v3.8.31 — see CHANGELOG.md [3.8.31] for full notes and contributors. Merged over known non-blocking reds (all correctness gates green): Integration Tests (2/2) is env/flaky (polls a real upstream batch that did not complete in the poll window); SonarQube/SonarCloud is the advisory server-side new-code quality gate. Unit (8 shards), Coverage, Node 22/24/26, Lint, PR Test Policy, Quality Ratchet, Docs-Strict, Quality-Extended and all 4 CodeQL analyses are green.
This commit is contained in:
committed by
GitHub
parent
3b2a2f02a9
commit
d0396c200d
@@ -708,7 +708,14 @@ export function mapRawModelToModelV2(
|
||||
const outMods = new Set(raw.output_modalities ?? ["text"]);
|
||||
|
||||
return {
|
||||
id: raw.id,
|
||||
// OC's static-catalog reader parses the key on `/` to recover
|
||||
// `(providerID, modelID)`. If the raw id is already provider-prefixed
|
||||
// (e.g. `cc/claude-opus-4-7` from the `cc` Claude Code alias, or
|
||||
// `nvidia/llama-3-70b` from a provider that ships prefixed ids), leave
|
||||
// it as-is — double-prefixing breaks OC's lookup. Otherwise prefix with
|
||||
// the resolved `providerId` so a bare key like `claude-opus-4` parses as
|
||||
// `(omniroute, claude-opus-4)` and the credentials resolve correctly.
|
||||
id: raw.id.includes("/") ? raw.id : `${ctx.providerId}/${raw.id}`,
|
||||
/**
|
||||
* Display name. Falls back to raw.id when no enrichment is available;
|
||||
* the caller (`createOmniRouteProviderHook`) overlays
|
||||
@@ -1166,6 +1173,10 @@ export function mapAutoComboToStaticEntry(
|
||||
typeof autoCombo.max_output_tokens === "number" && autoCombo.max_output_tokens > 0
|
||||
? autoCombo.max_output_tokens
|
||||
: AUTO_COMBO_FALLBACK_OUTPUT;
|
||||
// No `providerID` field on static-catalog entries — OC ignores it on the static
|
||||
// path, and stamping it on auto-combos but not on raw/combo entries was an
|
||||
// internal inconsistency. The dynamic-hook path builds its ModelV2 from the
|
||||
// individual fields below and never read this field either.
|
||||
return {
|
||||
name,
|
||||
attachment: false,
|
||||
@@ -2248,26 +2259,38 @@ export function slugifyComboName(name: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a combo's static-block key (`combo/<slug>`), guaranteeing uniqueness
|
||||
* across an entire static catalog. If `<slug>` is already present in `used`,
|
||||
* suffixes a short UUID-prefix disambiguator from `combo.id` so the second
|
||||
* combo doesn't silently overwrite the first. Mutates `used` in place by
|
||||
* recording the chosen key. Returns the final `combo/<...>` key.
|
||||
* Build a combo's static-block key, provider-prefixed as `<providerId>/<slug>`
|
||||
* (e.g. `omniroute/MASTER`, `omniroute/MASTER-LIGHT`), guaranteeing uniqueness
|
||||
* across an entire static catalog. If `<providerId>/<slug>` is already present in
|
||||
* `used`, suffixes a short UUID-prefix disambiguator from `combo.id` so the second
|
||||
* combo doesn't silently overwrite the first. Mutates `used` in place by recording
|
||||
* the chosen key. Returns the final `<providerId>/<slug>` key.
|
||||
*
|
||||
* Falls back to `combo/<id>` when the friendly name slugifies to the empty
|
||||
* NOTE: the key MUST carry the OWNING provider prefix (`omniroute/…`), never a
|
||||
* `combo/` namespace — OpenCode parses model IDs on `/` to extract the provider,
|
||||
* so `combo/MASTER` would resolve provider=`combo` (no credentials) and fail with
|
||||
* "Unable to determine provider", whereas `omniroute/MASTER` resolves provider=
|
||||
* `omniroute` and the openai-compatible adapter strips the prefix and sends the
|
||||
* bare slug upstream, which the server resolves via getComboByName. See PR #4184.
|
||||
*
|
||||
* Falls back to `<providerId>/<id>` when the friendly name slugifies to the empty
|
||||
* string (e.g. a combo named just punctuation).
|
||||
*/
|
||||
export function buildComboKey(combo: OmniRouteRawCombo, used: Set<string>): string {
|
||||
export function buildComboKey(
|
||||
combo: OmniRouteRawCombo,
|
||||
used: Set<string>,
|
||||
providerId: string
|
||||
): string {
|
||||
const friendlyName = combo.name && combo.name.trim().length > 0 ? combo.name.trim() : combo.id;
|
||||
let slug = slugifyComboName(friendlyName);
|
||||
if (slug.length === 0) slug = combo.id;
|
||||
let key = `combo/${slug}`;
|
||||
let key = `${providerId}/${slug}`;
|
||||
if (used.has(key)) {
|
||||
const tail = combo.id.split("-")[0] ?? combo.id;
|
||||
key = `combo/${slug}-${tail}`;
|
||||
key = `${providerId}/${slug}-${tail}`;
|
||||
// Defensive: in the (impossible) event the disambiguated key also
|
||||
// collides, append the full id.
|
||||
if (used.has(key)) key = `combo/${slug}-${combo.id}`;
|
||||
if (used.has(key)) key = `${providerId}/${slug}-${combo.id}`;
|
||||
}
|
||||
used.add(key);
|
||||
return key;
|
||||
@@ -2651,7 +2674,12 @@ export function createOmniRouteProviderHook(
|
||||
);
|
||||
applyProviderTag(model, tagEntry);
|
||||
}
|
||||
models[entry.id] = model;
|
||||
// OC's static-catalog reader parses the key on `/` to recover
|
||||
// (providerID, modelID). `mapRawModelToModelV2` already stamps the
|
||||
// prefixed id on `model.id` (e.g. `omniroute/claude-primary`), so we
|
||||
// must key by `model.id` — not by the raw `entry.id` which would be
|
||||
// a bare slug and parse as `providerID=slug, modelID=""`.
|
||||
models[model.id] = model;
|
||||
}
|
||||
|
||||
// Default compression combo (used to decorate ALL combo names when
|
||||
@@ -2801,18 +2829,6 @@ export function createOmniRouteProviderHook(
|
||||
// models with curated names).
|
||||
applyEnrichment(mapped, rawEnrichment.get(combo.id));
|
||||
|
||||
// `Combo: ` prefix surfaces the combo nature in OC's model picker.
|
||||
// Idempotent guard covers the case where enrichment overwrote
|
||||
// mapped.name with an already-prefixed string. Mirrors the
|
||||
// static-hook Combo:-prefix decoration.
|
||||
if (!mapped.name.startsWith("Combo: ")) {
|
||||
mapped.name = `Combo: ${mapped.name}`;
|
||||
}
|
||||
|
||||
// Optionally decorate combo name with its compression pipeline.
|
||||
// Only fires when features.compressionMetadata: true, OmniRoute
|
||||
// returned at least one default compression combo, AND the
|
||||
// combo has resolvable members — claiming compression on an
|
||||
// unroutable combo would mislead the picker.
|
||||
if (hasMembers && defaultCompression && defaultCompression.pipeline.length > 0) {
|
||||
const tag = formatCompressionPipeline(defaultCompression.pipeline);
|
||||
@@ -2821,18 +2837,37 @@ export function createOmniRouteProviderHook(
|
||||
}
|
||||
}
|
||||
|
||||
const comboKey = buildComboKey(combo, usedComboKeys);
|
||||
const comboKey = buildComboKey(combo, usedComboKeys, resolved.providerId);
|
||||
|
||||
// Collision policy: combos win. Warn ONCE per (cacheKey, comboKey)
|
||||
// when overwriting a same-key raw model so the operator can spot
|
||||
// the unusual naming choice without log spam.
|
||||
// the unusual naming choice without log spam. Suppress the warning
|
||||
// when the collision is the intentional dedup pattern (combo.name
|
||||
// exactly matches an existing raw model's id) — /v1/models
|
||||
// pre-mirrors combos as raw entries and the operator's intent is
|
||||
// always "combo wins" in that case.
|
||||
if (Object.prototype.hasOwnProperty.call(models, comboKey)) {
|
||||
const dedupeKey = `${cacheKey}::${comboKey}`;
|
||||
if (!collisionWarned.has(dedupeKey)) {
|
||||
collisionWarned.add(dedupeKey);
|
||||
console.warn(
|
||||
`[omniroute-plugin] combo key "${comboKey}" collides with a model id; combo wins.`
|
||||
);
|
||||
const existing = models[comboKey];
|
||||
// Intentional dedup: `/v1/models` pre-mirrors combos as raw
|
||||
// entries, so the bare combo name appears as the model id in
|
||||
// `rawModels`. After our prefixing the existing entry's id is
|
||||
// `${providerId}/${raw.id}` — the combo name is a substring of
|
||||
// that prefixed id (or, for already-prefixed raw models, the
|
||||
// exact id). Use `endsWith` to avoid matching substrings of
|
||||
// unrelated prefixed ids.
|
||||
const isIntentionalDedup =
|
||||
existing &&
|
||||
combo.name &&
|
||||
combo.name.trim().length > 0 &&
|
||||
(existing.id === combo.name.trim() || existing.id.endsWith(`/${combo.name.trim()}`));
|
||||
if (!isIntentionalDedup) {
|
||||
const dedupeKey = `${cacheKey}::${comboKey}`;
|
||||
if (!collisionWarned.has(dedupeKey)) {
|
||||
collisionWarned.add(dedupeKey);
|
||||
console.warn(
|
||||
`[omniroute-plugin] combo key "${comboKey}" collides with a model id; combo wins.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
models[comboKey] = mapped;
|
||||
@@ -3346,8 +3381,18 @@ function normaliseModalities(raw: unknown): OmniRouteModalityKind[] {
|
||||
}
|
||||
|
||||
export interface OmniRouteStaticModelEntry {
|
||||
/** Owning provider id. SHOULD match the parent `provider.<id>` key so OC's
|
||||
* static-catalog reader resolves credentials via `providerID` instead of
|
||||
* parsing the model key on `/`. Optional: OC's schema validator may
|
||||
* reject the entire provider block when this field is present but the
|
||||
* model KEY already carries the provider prefix (e.g. `omniroute/MASTER`),
|
||||
* since the prefix makes the field redundant and the field is not part of
|
||||
* OC's expected schema. We omit it from entries and rely on the prefix
|
||||
* on the KEY alone. See PR #4184. */
|
||||
providerID?: string;
|
||||
/** Display label rendered in OC's model picker. Defaults to the model id. */
|
||||
name: string;
|
||||
|
||||
/** ISO date the model was released. Surfaces in OC's model card when present. */
|
||||
release_date?: string;
|
||||
/** Model accepts image / file attachments. */
|
||||
@@ -3545,6 +3590,12 @@ export function buildStaticProviderEntry(
|
||||
if (!displayName.startsWith(prefix)) displayName = `${prefix}${displayName}`;
|
||||
}
|
||||
}
|
||||
// OC's static-catalog schema doesn't expect a `providerID` field on
|
||||
// individual entries — the parent block ID is the provider. Adding
|
||||
// unknown fields here can cause OC's schema validator to reject the
|
||||
// entire provider block, hiding ALL models. The provider prefix on the
|
||||
// model KEY (e.g. `omniroute/claude-opus-4`) is what OC uses to recover
|
||||
// (providerID, modelID) when the user selects a model.
|
||||
const entry: OmniRouteStaticModelEntry = { name: displayName };
|
||||
|
||||
const attachment = caps.attachment ?? caps.vision;
|
||||
@@ -3608,7 +3659,12 @@ export function buildStaticProviderEntry(
|
||||
entry.release_date = raw.release_date;
|
||||
}
|
||||
|
||||
models[raw.id] = entry;
|
||||
// OC's static-catalog reader parses each key on `/` and rejects the
|
||||
// entire provider block if ANY key resolves to a parsed providerID that
|
||||
// has no corresponding provider block. So bare keys (no `/`) MUST be
|
||||
// prefixed with the resolved providerId. Already-prefixed keys
|
||||
// (e.g. `cc/claude-opus-4-7`) are left as-is to avoid double-prefixing.
|
||||
models[raw.id.includes("/") ? raw.id : `${opts.providerId}/${raw.id}`] = entry;
|
||||
}
|
||||
|
||||
// Combo entries → stripped LCD shape. Each combo is keyed as
|
||||
@@ -3717,12 +3773,11 @@ export function buildStaticProviderEntry(
|
||||
const hasMembers = memberEntries.length > 0;
|
||||
const friendlyName =
|
||||
combo.name && combo.name.trim().length > 0 ? combo.name.trim() : combo.id;
|
||||
// `Combo: ` prefix surfaces the combo nature in OC's model picker — the
|
||||
// catalog key (`combo/<slug>`) is already namespaced, but the picker
|
||||
// shows `name`, so prefix the display string too.
|
||||
const prefixedName = `Combo: ${friendlyName}`;
|
||||
const displayName =
|
||||
hasMembers && compressionSuffix ? `${prefixedName}${compressionSuffix}` : prefixedName;
|
||||
hasMembers && compressionSuffix ? `${friendlyName} ${compressionSuffix}` : friendlyName;
|
||||
// See the raw-model entry comment above — `providerID` on entries is
|
||||
// not part of OC's static-catalog schema; the parent block ID is the
|
||||
// provider and the KEY prefix (`omniroute/<slug>`) is what OC parses.
|
||||
const entry: OmniRouteStaticModelEntry = { name: displayName };
|
||||
|
||||
if (hasMembers) {
|
||||
@@ -3790,12 +3845,12 @@ export function buildStaticProviderEntry(
|
||||
entry.tool_call = false;
|
||||
}
|
||||
|
||||
// Key under `combo/<slug>` (e.g. `combo/claude-primary`) so the
|
||||
// namespace cleanly separates combos from raw provider/model pairs
|
||||
// and so the key is copy/paste-friendly. Slug collisions across
|
||||
// Key under bare slug (e.g. `claude-primary`) — no `combo/` prefix
|
||||
// because OpenCode parses model IDs on `/` and would treat
|
||||
// `combo/MASTER` as provider=`combo`. Slug collisions across
|
||||
// combos are disambiguated with a short UUID-prefix suffix; see
|
||||
// `buildComboKey` for the policy.
|
||||
models[buildComboKey(combo, usedComboKeys)] = entry;
|
||||
models[buildComboKey(combo, usedComboKeys, opts.providerId)] = entry;
|
||||
|
||||
// Make this combo's resolved entry available to parent combos
|
||||
// that reference it via combo-ref. Use the friendly name since
|
||||
|
||||
@@ -447,13 +447,13 @@ test("models() returns combo entries merged into the map", async () => {
|
||||
|
||||
// 3 raw models + 1 combo = 4 entries
|
||||
assert.equal(Object.keys(out).length, 4);
|
||||
assert.ok(out["claude-primary"]);
|
||||
assert.ok(out["claude-secondary"]);
|
||||
assert.ok(out["gemini-3-flash"]);
|
||||
assert.ok(out["combo/claude-tier"]);
|
||||
assert.ok(out["omniroute/claude-primary"]);
|
||||
assert.ok(out["omniroute/claude-secondary"]);
|
||||
assert.ok(out["omniroute/gemini-3-flash"]);
|
||||
assert.ok(out["omniroute/claude-tier"]);
|
||||
|
||||
const combo = out["combo/claude-tier"];
|
||||
assert.equal(combo.name, "Combo: Claude Tier");
|
||||
const combo = out["omniroute/claude-tier"];
|
||||
assert.equal(combo.name, "Claude Tier");
|
||||
assert.equal(combo.providerID, "omniroute");
|
||||
// LCD over claude-primary (200k, reasoning) + claude-secondary (100k, no reasoning)
|
||||
assert.equal(combo.limit.context, 100_000);
|
||||
@@ -478,11 +478,11 @@ test("models(): combo with unknown member ids degrades to all-false LCD posture"
|
||||
{ fetcher: modelsFetcher, combosFetcher }
|
||||
);
|
||||
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
|
||||
assert.ok(out["combo/phantom-combo"]);
|
||||
assert.ok(out["omniroute/phantom-combo"]);
|
||||
// With zero resolvable members, LCD = all-false (defensive posture).
|
||||
assert.equal(out["combo/phantom-combo"].capabilities.toolcall, false);
|
||||
assert.equal(out["combo/phantom-combo"].capabilities.reasoning, false);
|
||||
assert.equal(out["combo/phantom-combo"].limit.context, 0);
|
||||
assert.equal(out["omniroute/phantom-combo"].capabilities.toolcall, false);
|
||||
assert.equal(out["omniroute/phantom-combo"].capabilities.reasoning, false);
|
||||
assert.equal(out["omniroute/phantom-combo"].limit.context, 0);
|
||||
});
|
||||
|
||||
test("models(): hidden combos are excluded from the map", async () => {
|
||||
@@ -505,11 +505,11 @@ test("models(): hidden combos are excluded from the map", async () => {
|
||||
{ fetcher: modelsFetcher, combosFetcher }
|
||||
);
|
||||
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
|
||||
assert.ok(out["combo/visible"]);
|
||||
assert.ok(!out["combo/hidden"], "hidden combo must be omitted");
|
||||
assert.ok(out["omniroute/visible"]);
|
||||
assert.ok(!out["omniroute/hidden"], "hidden combo must be omitted");
|
||||
});
|
||||
|
||||
test("models(): combo name exactly matches raw model id → raw deleted, combo lives at combo/ key, no warn", async () => {
|
||||
test("models(): combo name exactly matches raw model id → raw deleted, raw deleted, no warn", async () => {
|
||||
// Combo.name === raw model id triggers the dedup deletion. This mirrors
|
||||
// the real OmniRoute payload where /v1/models pre-mirrors combos as
|
||||
// no-slash raw entries whose ids match /api/combos friendly names.
|
||||
@@ -529,10 +529,9 @@ test("models(): combo name exactly matches raw model id → raw deleted, combo l
|
||||
return hook.models!({} as never, { auth: apiAuth("sk-z") as never });
|
||||
});
|
||||
|
||||
// Raw model deleted by combo-name dedup; combo surfaces under combo/<slug>.
|
||||
assert.equal(out["claude-primary"], undefined, "raw deleted by combo-name dedup");
|
||||
assert.ok(out["combo/claude-primary"], "combo surfaces under combo/ namespace");
|
||||
assert.equal(out["combo/claude-primary"].name, "Combo: claude-primary");
|
||||
// Raw model replaced by combo of the same key; combo now lives at the bare slug.
|
||||
assert.ok(out["omniroute/claude-primary"], "combo surfaces under prefixed key");
|
||||
assert.equal(out["omniroute/claude-primary"].name, "claude-primary");
|
||||
|
||||
// No collision warning fires — dedup makes keys disjoint.
|
||||
const collisionWarns = warnings.filter((w) => {
|
||||
@@ -543,7 +542,7 @@ test("models(): combo name exactly matches raw model id → raw deleted, combo l
|
||||
});
|
||||
|
||||
test("models(): two combos with same slug → second gets disambiguator suffix", async () => {
|
||||
// Both combos slug to `claude` — second must get `combo/claude-<id-prefix>`.
|
||||
// Both combos slug to `claude` — second must get `claude-<id-prefix>`.
|
||||
const combos: OmniRouteRawCombo[] = [
|
||||
{
|
||||
id: "uuid-a",
|
||||
@@ -566,8 +565,8 @@ test("models(): two combos with same slug → second gets disambiguator suffix",
|
||||
|
||||
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
|
||||
// First combo gets the bare slug; second gets disambiguated.
|
||||
assert.ok(out["combo/claude"], "first combo at bare slug");
|
||||
assert.ok(out["combo/claude-uuid"], "second combo disambiguated by id prefix");
|
||||
assert.ok(out["omniroute/claude"], "first combo at prefixed slug");
|
||||
assert.ok(out["omniroute/claude-uuid"], "second combo disambiguated by id prefix");
|
||||
});
|
||||
|
||||
test("models(): combos fetch fails → falls back to models-only, warn emitted, no throw", async () => {
|
||||
@@ -584,8 +583,8 @@ test("models(): combos fetch fails → falls back to models-only, warn emitted,
|
||||
|
||||
// Catalog includes the models but NOT any combo entries.
|
||||
assert.equal(Object.keys(out).length, 2);
|
||||
assert.ok(out["claude-primary"]);
|
||||
assert.ok(out["claude-secondary"]);
|
||||
assert.ok(out["omniroute/claude-primary"]);
|
||||
assert.ok(out["omniroute/claude-secondary"]);
|
||||
|
||||
// Soft-fail warning surfaced.
|
||||
const softFail = warnings.find((w) => {
|
||||
@@ -610,7 +609,7 @@ test("models(): combos cached + reused within TTL (one combo fetch per TTL windo
|
||||
const second = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
|
||||
assert.equal(combosFetcher.callCount(), 1, "combos fetched only once within TTL");
|
||||
assert.equal(modelsFetcher.callCount(), 1, "models fetched only once within TTL");
|
||||
assert.ok(second["combo/claude-tier"]);
|
||||
assert.ok(second["omniroute/claude-tier"]);
|
||||
});
|
||||
|
||||
test("models(): combos refetched after TTL expiry (same key as models)", async () => {
|
||||
@@ -702,7 +701,7 @@ test("models(): nested combo-ref context is the min of nested + raw members", as
|
||||
{ fetcher: modelsFetcher, combosFetcher }
|
||||
);
|
||||
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
|
||||
const masterLight = out["combo/master-light"];
|
||||
const masterLight = out["omniroute/master-light"];
|
||||
assert.ok(masterLight, "MASTER-LIGHT entry must exist");
|
||||
assert.equal(
|
||||
masterLight.limit.context,
|
||||
|
||||
@@ -227,7 +227,7 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
|
||||
// Stripped per-model shape: name + cap flags + modalities + (optional)
|
||||
// cost. OC's SDK static schema accepts only `limit.{context,output}` —
|
||||
// `limit.input` is NOT in the SDK shape and gets dropped silently.
|
||||
const claude = entry.models["claude-sonnet-4-6"];
|
||||
const claude = entry.models["omniroute/claude-sonnet-4-6"];
|
||||
assert.ok(claude, "claude model surfaced");
|
||||
assert.equal(claude.name, "claude-sonnet-4-6");
|
||||
assert.equal(claude.attachment, true);
|
||||
@@ -246,11 +246,11 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
|
||||
assert.deepEqual(claude.modalities?.input, ["text", "image"]);
|
||||
assert.deepEqual(claude.modalities?.output, ["text"]);
|
||||
|
||||
// Combo surfaces under `combo/<friendly-name>` namespace + LCD'd
|
||||
// Combo surfaces under bare key + LCD'd
|
||||
// (gemini's reasoning=false → combo reasoning=false).
|
||||
const combo = entry.models["combo/claude-tier"];
|
||||
assert.ok(combo, "combo surfaced under combo/ namespace");
|
||||
assert.equal(combo.name, "Combo: Claude Tier");
|
||||
const combo = entry.models["omniroute/claude-tier"];
|
||||
assert.ok(combo, "combo surfaced under bare key");
|
||||
assert.equal(combo.name, "Claude Tier");
|
||||
assert.equal(combo.reasoning, false, "LCD: any member reasoning=false → combo reasoning=false");
|
||||
assert.equal(combo.tool_call, true);
|
||||
assert.equal(combo.limit?.context, 200_000, "LCD: min(200_000, 1_000_000)");
|
||||
@@ -409,8 +409,8 @@ test("config: combos fetcher throws → emit models-only catalog (no combos in m
|
||||
.omniroute;
|
||||
assert.ok(entry);
|
||||
const ids = Object.keys(entry.models).sort();
|
||||
assert.deepEqual(ids, ["claude-sonnet-4-6", "gemini-3-flash"]);
|
||||
assert.equal(entry.models["combo-claude-tier"], undefined, "no combo entry");
|
||||
assert.deepEqual(ids, ["omniroute/claude-sonnet-4-6", "omniroute/gemini-3-flash"]);
|
||||
assert.equal(entry.models["omniroute/claude-tier"], undefined, "no combo entry");
|
||||
assert.ok(
|
||||
logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")),
|
||||
"combos-fetch breadcrumb emitted"
|
||||
@@ -638,6 +638,7 @@ test("buildStaticProviderEntry: stripped per-model shape matches sibling @omniro
|
||||
"cost",
|
||||
"limit",
|
||||
"modalities",
|
||||
"providerID",
|
||||
]);
|
||||
for (const [id, entry] of Object.entries(block.models)) {
|
||||
for (const key of Object.keys(entry)) {
|
||||
@@ -653,7 +654,7 @@ test("buildStaticProviderEntry: stripped per-model shape matches sibling @omniro
|
||||
}
|
||||
|
||||
// Sanity: claude entry has all expected stripped fields.
|
||||
const claude = block.models["claude-sonnet-4-6"];
|
||||
const claude = block.models["omniroute/claude-sonnet-4-6"];
|
||||
assert.equal(typeof claude.name, "string");
|
||||
assert.equal(typeof claude.attachment, "boolean");
|
||||
assert.equal(typeof claude.reasoning, "boolean");
|
||||
@@ -678,8 +679,8 @@ test("buildStaticProviderEntry: hidden combos are excluded", () => {
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
assert.equal(block.models["combo-claude-tier"], undefined);
|
||||
assert.ok(block.models["claude-sonnet-4-6"]);
|
||||
assert.equal(block.models["omniroute/claude-tier"], undefined);
|
||||
assert.ok(block.models["omniroute/claude-sonnet-4-6"]);
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
@@ -695,7 +696,7 @@ test("buildStaticProviderEntry: emits modalities.input from raw.input_modalities
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
const claude = block.models["claude-sonnet-4-6"];
|
||||
const claude = block.models["omniroute/claude-sonnet-4-6"];
|
||||
assert.deepEqual(claude.modalities?.input, ["text", "image"]);
|
||||
assert.deepEqual(claude.modalities?.output, ["text"]);
|
||||
});
|
||||
@@ -709,7 +710,7 @@ test("buildStaticProviderEntry: never emits limit.input (OC SDK rejects it)", ()
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
const claude = block.models["claude-sonnet-4-6"];
|
||||
const claude = block.models["omniroute/claude-sonnet-4-6"];
|
||||
assert.equal((claude.limit as Record<string, unknown>).input, undefined);
|
||||
assert.equal(typeof claude.limit?.context, "number");
|
||||
assert.equal(typeof claude.limit?.output, "number");
|
||||
@@ -737,7 +738,7 @@ test("buildStaticProviderEntry: emits cost when enrichment carries pricing", ()
|
||||
"sk-test",
|
||||
enrichment
|
||||
);
|
||||
const claude = block.models["claude-sonnet-4-6"];
|
||||
const claude = block.models["omniroute/claude-sonnet-4-6"];
|
||||
assert.equal(claude.cost?.input, 3);
|
||||
assert.equal(claude.cost?.output, 15);
|
||||
assert.equal(claude.cost?.cache_read, 0.3);
|
||||
@@ -758,8 +759,8 @@ test("buildStaticProviderEntry: emits release_date when raw carries it; omits wh
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
assert.equal(block.models["claude-with-date"].release_date, "2026-02-19");
|
||||
assert.equal(block.models["gemini-3-flash"].release_date, undefined);
|
||||
assert.equal(block.models["omniroute/claude-with-date"].release_date, "2026-02-19");
|
||||
assert.equal(block.models["omniroute/gemini-3-flash"].release_date, undefined);
|
||||
});
|
||||
|
||||
test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)", () => {
|
||||
@@ -788,7 +789,7 @@ test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
const combo = block.models["combo/mixed-tier"];
|
||||
const combo = block.models["omniroute/mixed-tier"];
|
||||
assert.ok(combo, "combo emitted under slug key");
|
||||
// claude has text+image, text-only has text → intersection drops image.
|
||||
assert.deepEqual(combo.modalities?.input, ["text"]);
|
||||
@@ -896,10 +897,10 @@ test("config: enrichment fetched + name overlaid on raw-model entries", async ()
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.ok(entry);
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["gemini-3-flash"].name, "Gemini 3 Flash");
|
||||
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["omniroute/gemini-3-flash"].name, "Gemini 3 Flash");
|
||||
// Combo names still come from /api/combos — enrichment overlay does NOT touch combos.
|
||||
assert.equal(entry.models["combo/claude-tier"].name, "Combo: Claude Tier");
|
||||
assert.equal(entry.models["omniroute/claude-tier"].name, "Claude Tier");
|
||||
assert.equal(enrichmentFetcher.callCount(), 1);
|
||||
});
|
||||
|
||||
@@ -927,7 +928,11 @@ test("config: features.enrichment=false skips enrichment fetch + keeps raw-id na
|
||||
.omniroute;
|
||||
assert.ok(entry);
|
||||
assert.equal(enrichmentFetcher.callCount(), 0, "enrichment fetch suppressed by feature flag");
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id retained");
|
||||
assert.equal(
|
||||
entry.models["omniroute/claude-sonnet-4-6"].name,
|
||||
"claude-sonnet-4-6",
|
||||
"raw id retained"
|
||||
);
|
||||
});
|
||||
|
||||
test("config: enrichment fetcher throws → soft-fail (warn + raw-id static catalog)", async () => {
|
||||
@@ -949,7 +954,11 @@ test("config: enrichment fetcher throws → soft-fail (warn + raw-id static cata
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.ok(entry, "static block still published on enrichment failure");
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id retained");
|
||||
assert.equal(
|
||||
entry.models["omniroute/claude-sonnet-4-6"].name,
|
||||
"claude-sonnet-4-6",
|
||||
"raw id retained"
|
||||
);
|
||||
assert.equal(enrichmentFetcher.callCount(), 1);
|
||||
assert.ok(
|
||||
logger.entries.some((e) => String(e[0]).includes("/api/pricing/models fetch failed")),
|
||||
@@ -1143,9 +1152,12 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
|
||||
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.ok(entry.models["claude-sonnet-4-6"], "stale snapshot hydrated into static block");
|
||||
assert.ok(
|
||||
entry.models["omniroute/claude-sonnet-4-6"],
|
||||
"stale snapshot hydrated into static block"
|
||||
);
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
entry.models["omniroute/claude-sonnet-4-6"].name,
|
||||
"Claude Sonnet 4.6 (cached)",
|
||||
"stale enrichment also reused"
|
||||
);
|
||||
@@ -1192,7 +1204,7 @@ test("config: cached rawEnrichment from earlier provider hook is reused (no refe
|
||||
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
@@ -1241,10 +1253,10 @@ test("config: providerTag (default-on) prepends '<provider> - ' to enriched raw-
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.ok(entry);
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["gemini-3-flash"].name, "Gemini-cli - Gemini 3 Flash");
|
||||
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["omniroute/gemini-3-flash"].name, "Gemini-cli - Gemini 3 Flash");
|
||||
// Combos stay untouched — `Combo: ` prefix already conveys multi-upstream.
|
||||
assert.equal(entry.models["combo/claude-tier"].name, "Combo: Claude Tier");
|
||||
assert.equal(entry.models["omniroute/claude-tier"].name, "Claude Tier");
|
||||
});
|
||||
|
||||
test("config: providerTag=false suppresses the suffix", async () => {
|
||||
@@ -1270,7 +1282,7 @@ test("config: providerTag=false suppresses the suffix", async () => {
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
entry.models["omniroute/claude-sonnet-4-6"].name,
|
||||
"Claude Sonnet 4.6",
|
||||
"enriched name kept, provider tag suppressed"
|
||||
);
|
||||
@@ -1301,7 +1313,7 @@ test("config: providerTag falls back to UPPER(alias) when providerDisplayName mi
|
||||
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "CC - Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "CC - Claude Sonnet 4.6");
|
||||
});
|
||||
|
||||
test("config: providerTag skipped entirely when neither providerDisplayName nor providerAlias set", async () => {
|
||||
@@ -1327,7 +1339,7 @@ test("config: providerTag skipped entirely when neither providerDisplayName nor
|
||||
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
});
|
||||
|
||||
test("config: providerTag is idempotent — second hook call doesn't double-suffix", async () => {
|
||||
@@ -1353,14 +1365,14 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
|
||||
await hook(inputA);
|
||||
const entryA = (inputA as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.equal(entryA.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
assert.equal(entryA.models["omniroute/claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
|
||||
// Second invocation (cache hit) — name must still be single-suffixed.
|
||||
const inputB = makeInput();
|
||||
await hook(inputB);
|
||||
const entryB = (inputB as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider
|
||||
.omniroute;
|
||||
assert.equal(entryB.models["claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
assert.equal(entryB.models["omniroute/claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6");
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
@@ -1412,7 +1424,7 @@ test("buildStaticProviderEntry: nested combo-ref context is the bottleneck acros
|
||||
);
|
||||
// Pre-fix: Parent would advertise 200_000 (only raw-big counted).
|
||||
// Post-fix: Parent should advertise 8_000 (TinyCombo bottleneck).
|
||||
const parent = block.models["combo/parent"];
|
||||
const parent = block.models["omniroute/parent"];
|
||||
assert.ok(parent, "Parent combo must be in the static catalog");
|
||||
assert.equal(parent.limit?.context, 8_000);
|
||||
});
|
||||
|
||||
@@ -376,7 +376,7 @@ test("provider hook: enrichment fetcher called when features.enrichment !== fals
|
||||
);
|
||||
const out = await hook.models!({} as never, { auth: apiAuth("sk") as never });
|
||||
assert.equal(called, 1, "enrichment fetcher called once");
|
||||
const m = out["claude-sonnet-4-6"];
|
||||
const m = out["omniroute/claude-sonnet-4-6"];
|
||||
assert.equal(m.name, "Claude Sonnet 4.6", "enrichment name overlay applied");
|
||||
assert.equal(m.cost.input, 3, "enrichment pricing applied");
|
||||
assert.equal(m.cost.output, 15);
|
||||
@@ -401,7 +401,7 @@ test("provider hook: enrichment fetcher NOT called when features.enrichment:fals
|
||||
);
|
||||
const out = await hook.models!({} as never, { auth: apiAuth("sk") as never });
|
||||
assert.equal(called, 0, "enrichment fetcher NOT called when gated off");
|
||||
assert.equal(out["claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id preserved");
|
||||
assert.equal(out["omniroute/claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id preserved");
|
||||
});
|
||||
|
||||
test("provider hook: compression metadata fetcher NOT called by default (opt-in)", async () => {
|
||||
@@ -459,7 +459,7 @@ test("provider hook: compression metadata fetcher called when opted in", async (
|
||||
);
|
||||
const out = await hook.models!({} as never, { auth: apiAuth("sk") as never });
|
||||
assert.equal(called, 1, "compression metadata fetcher called");
|
||||
const combo = out["combo/claude-primary"];
|
||||
const combo = out["omniroute/claude-primary"];
|
||||
assert.ok(combo, "combo entry present");
|
||||
assert.match(
|
||||
combo.name,
|
||||
|
||||
@@ -101,7 +101,7 @@ test("models: extracts apiKey from ctx.auth (type=api) and calls fetcher with it
|
||||
assert.equal(fetcher.callCount(), 1);
|
||||
assert.deepEqual(fetcher.callsBy()[0], ["https://or.example.com/v1", "sk-abc"]);
|
||||
assert.equal(Object.keys(out).length, 3);
|
||||
assert.ok(out["claude-primary"]);
|
||||
assert.ok(out["omniroute/claude-primary"]);
|
||||
});
|
||||
|
||||
test("models: returns {} when ctx.auth is null/undefined/wrong-type/empty-key", async () => {
|
||||
@@ -152,9 +152,11 @@ test("models: maps a sample /v1/models entry to ModelV2 (sanity)", async () => {
|
||||
{ fetcher, combosFetcher: async () => [] }
|
||||
);
|
||||
const out = await hook.models!({} as never, { auth: apiAuth("sk-abc") as never });
|
||||
const claude = out["claude-primary"];
|
||||
const claude = out["omniroute/claude-primary"];
|
||||
assert.ok(claude, "claude-primary present");
|
||||
assert.equal(claude.id, "claude-primary");
|
||||
// `mapRawModelToModelV2` stamps the provider prefix on the id so OC's
|
||||
// static-catalog reader resolves `(providerID, modelID)` from the key.
|
||||
assert.equal(claude.id, "omniroute/claude-primary");
|
||||
assert.equal(claude.name, "claude-primary");
|
||||
assert.equal(claude.providerID, "omniroute");
|
||||
assert.equal(claude.api.id, "openai-compatible");
|
||||
|
||||
Reference in New Issue
Block a user