From 31f519b9b093af03c3474bf3d9268bc8f00eb095 Mon Sep 17 00:00:00 2001 From: Demiurge The Single Date: Thu, 18 Jun 2026 22:00:27 +0300 Subject: [PATCH] fix(models): expose combo model token limits (#4189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- config/quality/file-size-baseline.json | 3 +- src/app/api/v1/models/catalog.ts | 40 ++++++++++++++--- .../models-catalog-auto-combos-4164.test.ts | 43 +++++++++++++++++++ 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index d09cd9e328..59d65c5210 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -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 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, (); // #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 diff --git a/tests/unit/models-catalog-auto-combos-4164.test.ts b/tests/unit/models-catalog-auto-combos-4164.test.ts index 6ae712564d..b83fc174a3 100644 --- a/tests/unit/models-catalog-auto-combos-4164.test.ts +++ b/tests/unit/models-catalog-auto-combos-4164.test.ts @@ -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; + }>; + }; + + 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` + ); + } +});