mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
fix(api): enumerate tiered auto combo endpoints in /api/combos/auto (#7662)
* fix(api): enumerate tiered auto combo endpoints in /api/combos/auto The backend already supports auto/<category>[:<tier>] routing via suffixComposition.ts + virtualFactory.ts, but GET /api/combos/auto only exposed 6 flat variants. This adds a second loop enumerating the 10 curated AUTO_SUFFIX_VARIANTS (auto/coding:free, auto/coding:cheap, auto/coding:pro, auto/reasoning, auto/vision, etc.). Fixes #7619 * fix(combos): enumerate template and family auto variants in GET /api/combos/auto The endpoint was missing 27 auto variants that /v1/models already advertises, causing 404s when clients tried to use them: - 20 template variants (auto/best-coding, auto/pro-*, auto/claude-*, auto/best-free, etc.) - 7 family variants (auto/glm, auto/minimax, auto/mimo, auto/zai, auto/gemma, auto/llama, auto/gemini) Fixes #7619 Refs #6453 * fix(combos): swap Phase B/C ordering to match catalog.ts Template variants (Phase C) now enumerate before suffix variants (Phase B) so that overlapping ids like auto/reasoning and auto/vision use template resolution (variant-based) rather than suffix resolution (category-based), matching the behavior in catalog.ts. * fix(combos): fix comment labels and redundant as const * fix(api): fall back to a positive max_output_tokens for /api/combos/auto computeAdvertisedLimits() has no generic default for maxOutputTokens the way getTokenLimit() does for context length: when a combo's candidate pool is entirely unregistered models (e.g. a no-auth provider's model like duckduckgo-web/llama-4-scout), it legitimately returns null. The new template/suffix/family enumeration surfaces exactly that case (e.g. auto/llama), so /api/combos/auto advertised max_output_tokens: null and broke tests/unit/auto-combo-context-advertising.test.ts. Mirror the existing fallback already used by src/app/api/v1/models/catalog.ts (advertisedContextLength || 128000, advertisedMaxOutputTokens || 8192) at all 4 combo-push sites in this route so clients never see a disabling null/0 for a non-empty candidate pool. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(changelog): prefix #7662 fragment with markdown bullet Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Erick Kinnee <erick@ekinnee.dev> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- GET /api/combos/auto now enumerates template variants (auto/best-coding, auto/pro-_, auto/claude-_, auto/best-free) and family variants (auto/glm, auto/llama, auto/gemini, etc.) in addition to the flat and suffix variants it already exposed. Previously these 27 variants were advertised by /v1/models but missing from the combos endpoint, causing 404s when clients tried to use them.
|
||||
@@ -4,6 +4,12 @@ import {
|
||||
VALID_VARIANTS,
|
||||
type AutoVariant,
|
||||
} from "@omniroute/open-sse/services/autoCombo/autoPrefix";
|
||||
import {
|
||||
AUTO_SUFFIX_VARIANTS,
|
||||
AUTO_TEMPLATE_VARIANTS,
|
||||
AUTO_FAMILY_IDS,
|
||||
} from "@omniroute/open-sse/services/autoCombo/builtinCatalog";
|
||||
import { parseAutoSuffix } from "@omniroute/open-sse/services/autoCombo/suffixComposition";
|
||||
|
||||
const ALL_VARIANTS: Array<{ variant: AutoVariant | undefined; name: string }> = [
|
||||
{ variant: undefined, name: "Auto" },
|
||||
@@ -23,11 +29,14 @@ export async function GET(request: Request) {
|
||||
await import("@omniroute/open-sse/services/autoCombo/virtualFactory");
|
||||
|
||||
const combos = [];
|
||||
const seenIds = new Set<string>();
|
||||
for (const { variant, name } of ALL_VARIANTS) {
|
||||
try {
|
||||
const virtual = await createVirtualAutoCombo(variant);
|
||||
const id = variant ? `auto/${variant}` : "auto";
|
||||
seenIds.add(id);
|
||||
combos.push({
|
||||
id: variant ? `auto/${variant}` : "auto",
|
||||
id,
|
||||
name,
|
||||
variant: variant ?? null,
|
||||
type: "auto",
|
||||
@@ -36,8 +45,12 @@ export async function GET(request: Request) {
|
||||
candidateCount: virtual.candidatePool?.length ?? 0,
|
||||
// MAX of candidates' windows — consumers (opencode plugin) need a
|
||||
// real value here: advertising 0 disables client auto-compaction.
|
||||
context_length: virtual.advertisedContextLength ?? null,
|
||||
max_output_tokens: virtual.advertisedMaxOutputTokens ?? null,
|
||||
// #7662: mirror catalog.ts's established fallback (advertisedMaxOutputTokens
|
||||
// has no generic default in computeAdvertisedLimits() the way context length
|
||||
// does — an all-unregistered candidate pool, e.g. a no-auth provider's model,
|
||||
// otherwise advertises null and disables client auto-compaction).
|
||||
context_length: virtual.advertisedContextLength || 128000,
|
||||
max_output_tokens: virtual.advertisedMaxOutputTokens || 8192,
|
||||
config: virtual.config ?? {},
|
||||
});
|
||||
} catch {
|
||||
@@ -45,6 +58,124 @@ export async function GET(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase B: enumerate template variants (auto/best-coding, auto/pro-*,
|
||||
// auto/claude-*, auto/best-free, etc.) that the backend already supports
|
||||
// via builtinCatalog.ts but were not exposed by this endpoint.
|
||||
// Run BEFORE suffix variants so template resolution wins for overlapping
|
||||
// ids (auto/reasoning, auto/vision), matching catalog.ts behavior.
|
||||
for (const modelStr of Object.keys(AUTO_TEMPLATE_VARIANTS)) {
|
||||
if (seenIds.has(modelStr)) continue;
|
||||
try {
|
||||
const variant = AUTO_TEMPLATE_VARIANTS[modelStr];
|
||||
const spec = modelStr === "auto/best-free" ? { tier: "free" as const } : undefined;
|
||||
const virtual = await createVirtualAutoCombo(variant, spec);
|
||||
|
||||
const displayName = variant
|
||||
? `Auto ${variant.charAt(0).toUpperCase() + variant.slice(1)}`
|
||||
: "Auto Chat";
|
||||
|
||||
combos.push({
|
||||
id: modelStr,
|
||||
name: displayName,
|
||||
variant: null,
|
||||
type: "auto",
|
||||
isHidden: false,
|
||||
candidatePool: virtual.candidatePool ?? [],
|
||||
candidateCount: virtual.candidatePool?.length ?? 0,
|
||||
// #7662: mirror catalog.ts's established fallback (advertisedMaxOutputTokens
|
||||
// has no generic default in computeAdvertisedLimits() the way context length
|
||||
// does — an all-unregistered candidate pool, e.g. a no-auth provider's model,
|
||||
// otherwise advertises null and disables client auto-compaction).
|
||||
context_length: virtual.advertisedContextLength || 128000,
|
||||
max_output_tokens: virtual.advertisedMaxOutputTokens || 8192,
|
||||
config: virtual.config ?? {},
|
||||
});
|
||||
seenIds.add(modelStr);
|
||||
} catch {
|
||||
// Individual variant failure — skip, don't break the whole list
|
||||
}
|
||||
}
|
||||
|
||||
// Phase C: enumerate tiered `auto/<category>[:<tier>]` variants
|
||||
// (e.g. auto/coding:free, auto/reasoning:pro) that the backend already
|
||||
// supports via suffixComposition.ts + virtualFactory.ts but were not
|
||||
// exposed by this endpoint.
|
||||
for (const modelStr of AUTO_SUFFIX_VARIANTS) {
|
||||
if (seenIds.has(modelStr)) continue;
|
||||
try {
|
||||
const suffix = modelStr.slice("auto/".length);
|
||||
const parsed = parseAutoSuffix(suffix);
|
||||
if (!parsed.valid) continue;
|
||||
|
||||
const virtual = await createVirtualAutoCombo(undefined, {
|
||||
category: parsed.category,
|
||||
tier: parsed.tier,
|
||||
});
|
||||
|
||||
// Build a human-readable name from the category and tier
|
||||
const catName = parsed.category
|
||||
? parsed.category.charAt(0).toUpperCase() + parsed.category.slice(1)
|
||||
: "";
|
||||
const tierName = parsed.tier
|
||||
? `${parsed.tier.charAt(0).toUpperCase() + parsed.tier.slice(1)}`
|
||||
: "";
|
||||
const displayName = tierName ? `${catName} ${tierName}` : catName;
|
||||
|
||||
combos.push({
|
||||
id: modelStr,
|
||||
name: `Auto ${displayName}`,
|
||||
variant: null,
|
||||
type: "auto",
|
||||
isHidden: false,
|
||||
candidatePool: virtual.candidatePool ?? [],
|
||||
candidateCount: virtual.candidatePool?.length ?? 0,
|
||||
// #7662: mirror catalog.ts's established fallback (advertisedMaxOutputTokens
|
||||
// has no generic default in computeAdvertisedLimits() the way context length
|
||||
// does — an all-unregistered candidate pool, e.g. a no-auth provider's model,
|
||||
// otherwise advertises null and disables client auto-compaction).
|
||||
context_length: virtual.advertisedContextLength || 128000,
|
||||
max_output_tokens: virtual.advertisedMaxOutputTokens || 8192,
|
||||
config: virtual.config ?? {},
|
||||
});
|
||||
seenIds.add(modelStr);
|
||||
} catch {
|
||||
// Individual variant failure — skip, don't break the whole list
|
||||
}
|
||||
}
|
||||
|
||||
// Phase D: enumerate family variants (auto/glm, auto/llama,
|
||||
// auto/gemini, etc.) that the backend already supports via modelFamily.ts
|
||||
// but were not exposed by this endpoint.
|
||||
for (const modelStr of AUTO_FAMILY_IDS) {
|
||||
if (seenIds.has(modelStr)) continue;
|
||||
try {
|
||||
const suffix = modelStr.slice("auto/".length);
|
||||
const virtual = await createVirtualAutoCombo(undefined, { family: suffix });
|
||||
|
||||
const displayName = `Auto ${suffix.charAt(0).toUpperCase() + suffix.slice(1)}`;
|
||||
|
||||
combos.push({
|
||||
id: modelStr,
|
||||
name: displayName,
|
||||
variant: null,
|
||||
type: "auto",
|
||||
isHidden: false,
|
||||
candidatePool: virtual.candidatePool ?? [],
|
||||
candidateCount: virtual.candidatePool?.length ?? 0,
|
||||
// #7662: mirror catalog.ts's established fallback (advertisedMaxOutputTokens
|
||||
// has no generic default in computeAdvertisedLimits() the way context length
|
||||
// does — an all-unregistered candidate pool, e.g. a no-auth provider's model,
|
||||
// otherwise advertises null and disables client auto-compaction).
|
||||
context_length: virtual.advertisedContextLength || 128000,
|
||||
max_output_tokens: virtual.advertisedMaxOutputTokens || 8192,
|
||||
config: virtual.config ?? {},
|
||||
});
|
||||
seenIds.add(modelStr);
|
||||
} catch {
|
||||
// Individual variant failure — skip, don't break the whole list
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ combos });
|
||||
} catch (error) {
|
||||
console.error("Error fetching auto combos:", error);
|
||||
|
||||
Reference in New Issue
Block a user