mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 14:52:09 +03:00
fix(models): expose combo model token limits (#4189)
Integrated into release/v3.8.29. Thanks @megamen32! Reconciled on your branch: the branch had drifted behind release and would have reverted the centralized vision detection (#4072), the no-thinking gateway variants (#4145) and #4164's auto/* loop — those are kept, and your genuine improvement is applied on top: each auto/* /v1/models entry now carries advertised context/output limits + capabilities (createBuiltinAutoCombo, with a fallback to the minimal entry on resolve failure). Added a regression test (Rule #18) and bumped the catalog.ts file-size baseline 1440→1463 with justification.
This commit is contained in:
committed by
GitHub
parent
0abe9a3a40
commit
31f519b9b0
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"_comment": "Catraca de tamanho (check-file-size.mjs). frozen so pode encolher; arquivos novos <= cap. --update ratcheta.",
|
||||
"_rebaseline_2026_06_18_4189_combo_token_limits": "PR #4189 (megamen32) own growth: catalog.ts 1440->1463 (+23 at the existing #4164 auto/* emission chokepoint). The bare auto/* /v1/models entries are enriched with the combo's advertised context/output limits (createBuiltinAutoCombo → advertisedContextLength/advertisedMaxOutputTokens computed from the candidate pool, 128000/8192 fallback) + baseline capabilities, with a try/catch that emits the minimal #4164 entry on resolve failure so the id is never dropped. OpenAI-compatible pickers (Hermes) need a context window before the first request. Cohesive emission at the single auto/* loop; not extractable.",
|
||||
"_rebaseline_2026_06_18_4180_gemini_default_thinking": "PR #4180 own growth: openai-to-gemini.ts 844->864 (+20 = default includeThoughts for modern Gemini 2.5+ at the existing openaiToGeminiBase chokepoint — when the client set no thinkingConfig, inject one (includeThoughts:true + a capped budget) so the model's reasoning is marked thought:true and routed to reasoning_content instead of leaking into visible content, #4170). Cohesive translator branch gated to gemini-2.5+/3 (excludes gemini-1.x and non-thinking 2.0); not extractable. Reconciled here because #4180 merged without the baseline bump.",
|
||||
"_rebaseline_2026_06_18_qg9_chatcore_split_prA": "QG v2 Fase 9 T5 C2-C3-C5: chatCore.ts 5445->5265 (wc -l 5264 + 1). Fase 2 of the chatCore split following #4159 (which created 10 leaf modules). Three more sibling leaves created under open-sse/handlers/chatCore/, all <cap, pure byte-identical moves (no runtime change): passthroughHelpers.ts (C2: shouldUseNativeCodexPassthrough/redactPassthroughThinkingSignatures/isClaudeCodeSemanticPassthroughRequest), responseHeaders.ts (C3: STREAMING_RESPONSE_HEADER_DENYLIST/buildStreamingResponseHeaders/materializeDeduplicatedExecutionResult/stripStaleForwardingHeaders), telemetryHelpers.ts (C5: forwardDashboardEventToLiveWs/maybeSyncClaudeExtraUsageState). chatCore.ts imports all 8 still-referenced symbols back from the leaves and re-exports the 5 previously-public ones (shouldUseNativeCodexPassthrough/redactPassthroughThinkingSignatures/isClaudeCodeSemanticPassthroughRequest/buildStreamingResponseHeaders/stripStaleForwardingHeaders) so existing test importers keep resolving. No barrel imports in the new leaves.",
|
||||
"_rebaseline_2026_06_18_4176_free_models": "PR #4176 own growth: AddApiKeyModal.tsx 845->866 (+21) and EditConnectionModal.tsx 1174->1204 (+30) = the 'import only free models' connection option — a free-models Toggle gated by providerHasFreeModels() plus its form-state wiring (importFreeModelsOnly field + providerSpecificData persistence, explicit-false on edit so the PUT merge doesn't keep a stale true) added to both connection modals, mirroring the prior per-modal toggle bumps #3879 (redact-thinking) and #2997 (disable-cooling). Detection lives in the new shared src/shared/utils/freeModels.ts (112 LOC, <cap); the duplicated Toggle is ~6 lines and the per-modal state wiring is intrinsic, so the remaining growth is cohesive UI, not an extractable block.",
|
||||
@@ -121,7 +122,7 @@
|
||||
"src/app/api/providers/[id]/models/route.ts": 2531,
|
||||
"src/app/api/providers/[id]/test/route.ts": 842,
|
||||
"src/app/api/usage/analytics/route.ts": 941,
|
||||
"src/app/api/v1/models/catalog.ts": 1440,
|
||||
"src/app/api/v1/models/catalog.ts": 1463,
|
||||
"src/lib/cloudflaredTunnel.ts": 934,
|
||||
"src/lib/db/apiKeys.ts": 1661,
|
||||
"src/lib/db/core.ts": 1820,
|
||||
|
||||
@@ -20,7 +20,10 @@ import { getAllMusicModels } from "@omniroute/open-sse/config/musicRegistry";
|
||||
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry";
|
||||
import { CODEX_NATIVE_UNPREFIXED_MODELS } from "@omniroute/open-sse/services/model";
|
||||
import { resolveNestedComboTargets } from "@omniroute/open-sse/services/combo";
|
||||
import { AUTO_TEMPLATE_VARIANTS } from "@omniroute/open-sse/services/autoCombo/builtinCatalog";
|
||||
import {
|
||||
AUTO_TEMPLATE_VARIANTS,
|
||||
createBuiltinAutoCombo,
|
||||
} from "@omniroute/open-sse/services/autoCombo/builtinCatalog";
|
||||
import { getAllSyncedAvailableModels, type SyncedAvailableModel } from "@/lib/db/models";
|
||||
import { getCompatibleFallbackModels } from "@/lib/providers/managedAvailableModels";
|
||||
import { getOpenRouterCatalog } from "@/lib/catalog/openrouterCatalog";
|
||||
@@ -656,14 +659,16 @@ export async function getUnifiedModelsResponse(
|
||||
const listedIds = new Set<string>();
|
||||
|
||||
// #4164: advertise the built-in zero-setup `auto/*` combos at the very top.
|
||||
// They resolve on demand (createBuiltinAutoCombo) so they have no fixed
|
||||
// targets/metadata to derive — emit a minimal combo-owned entry. The dashboard
|
||||
// already shows these; clients that build their picker from /v1/models (e.g.
|
||||
// Hermes) need them here too.
|
||||
// #4189: enrich each with the combo's advertised context/output limits (computed
|
||||
// by createBuiltinAutoCombo from its candidate pool) + baseline capabilities, so
|
||||
// OpenAI-compatible clients that build their picker from /v1/models (e.g. Hermes)
|
||||
// receive token metadata before the first request instead of a bare entry. If the
|
||||
// combo cannot be materialized (e.g. no eligible connections yet) the minimal
|
||||
// #4164 entry is emitted instead, so the id is never dropped.
|
||||
for (const autoId of Object.keys(AUTO_TEMPLATE_VARIANTS)) {
|
||||
if (listedIds.has(autoId)) continue;
|
||||
listedIds.add(autoId);
|
||||
models.push({
|
||||
const baseAutoEntry = {
|
||||
id: autoId,
|
||||
object: "model",
|
||||
created: timestamp,
|
||||
@@ -671,7 +676,28 @@ export async function getUnifiedModelsResponse(
|
||||
permission: [],
|
||||
root: autoId,
|
||||
parent: null,
|
||||
});
|
||||
};
|
||||
try {
|
||||
const suffix = autoId.replace(/^auto\/?/, "");
|
||||
const virtualCombo = await createBuiltinAutoCombo(autoId, suffix);
|
||||
const contextLength = virtualCombo.advertisedContextLength || 128000;
|
||||
const maxOutputTokens = virtualCombo.advertisedMaxOutputTokens || 8192;
|
||||
models.push({
|
||||
...baseAutoEntry,
|
||||
context_length: contextLength,
|
||||
max_input_tokens: contextLength,
|
||||
max_output_tokens: maxOutputTokens,
|
||||
capabilities: {
|
||||
tool_calling: true,
|
||||
reasoning: true,
|
||||
thinking: true,
|
||||
temperature: true,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(`[catalog] Could not materialize built-in auto model ${autoId}:`, err);
|
||||
models.push(baseAutoEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Add combos first (they appear at the top) — only active ones
|
||||
|
||||
@@ -83,3 +83,46 @@ test("#4164 no duplicate auto/* ids even if a persisted combo shadows one", asyn
|
||||
const autoIds = body.data.map((m) => m.id).filter((id) => id.startsWith("auto/"));
|
||||
assert.equal(autoIds.length, new Set(autoIds).size, "auto/* ids must be unique");
|
||||
});
|
||||
|
||||
test("#4189 every auto/* entry exposes token limits + baseline capabilities", async () => {
|
||||
// #4164 emitted a bare auto/* entry (no token metadata). #4189 enriches each with
|
||||
// the combo's advertised context/output limits + baseline capabilities so
|
||||
// OpenAI-compatible clients that build their picker from /v1/models get a context
|
||||
// window before the first request. Without the fix these fields are absent.
|
||||
const response = await v1ModelsCatalog.getUnifiedModelsResponse(
|
||||
new Request("http://localhost/api/v1/models")
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
const body = (await response.json()) as {
|
||||
data: Array<{
|
||||
id: string;
|
||||
context_length?: number;
|
||||
max_input_tokens?: number;
|
||||
max_output_tokens?: number;
|
||||
capabilities?: Record<string, boolean>;
|
||||
}>;
|
||||
};
|
||||
|
||||
const autoEntries = body.data.filter((m) => m.id.startsWith("auto/"));
|
||||
assert.ok(autoEntries.length > 0, "sanity: at least one auto/* entry is listed");
|
||||
|
||||
for (const entry of autoEntries) {
|
||||
assert.equal(
|
||||
typeof entry.context_length,
|
||||
"number",
|
||||
`${entry.id} must expose a numeric context_length`
|
||||
);
|
||||
assert.ok((entry.context_length ?? 0) > 0, `${entry.id} context_length must be positive`);
|
||||
assert.equal(typeof entry.max_input_tokens, "number", `${entry.id} must expose max_input_tokens`);
|
||||
assert.equal(
|
||||
typeof entry.max_output_tokens,
|
||||
"number",
|
||||
`${entry.id} must expose max_output_tokens`
|
||||
);
|
||||
assert.ok((entry.max_output_tokens ?? 0) > 0, `${entry.id} max_output_tokens must be positive`);
|
||||
assert.ok(
|
||||
entry.capabilities && typeof entry.capabilities === "object",
|
||||
`${entry.id} must expose a capabilities map`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user