Files
OmniRoute/open-sse/utils/thinkingBudget.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

73 lines
2.8 KiB
TypeScript

/**
* Thinking-budget helpers extracted from base.ts.
*
* Pure utilities for reading / clamping the thinking budget fields that
* different providers nest inside the request body.
*/
export function hasActiveClaudeThinking(body: Record<string, unknown>): boolean {
const thinking = body.thinking as Record<string, unknown> | undefined;
return thinking?.type === "enabled" || thinking?.type === "adaptive";
}
/**
* Collect every `thinkingConfig` object in a transformed request body that holds
* a thinking budget, wherever the provider's envelope nests it:
* - body.generationConfig.thinkingConfig (native Gemini / openai→gemini)
* - body.request.generationConfig.thinkingConfig (Antigravity Cloud Code envelope)
* Returns only objects that actually carry a `thinkingBudget`/`thinking_budget`
* field — a request without thinking config is never mutated.
*/
export function collectThinkingConfigs(body: unknown): Array<Record<string, unknown>> {
if (!body || typeof body !== "object") return [];
const root = body as Record<string, unknown>;
const configs: Array<Record<string, unknown>> = [];
const envelopes: unknown[] = [
root.generationConfig,
(root.request as Record<string, unknown> | undefined)?.generationConfig,
];
for (const env of envelopes) {
if (!env || typeof env !== "object") continue;
const tc = (env as Record<string, unknown>).thinkingConfig;
if (tc && typeof tc === "object") {
const tcr = tc as Record<string, unknown>;
if ("thinkingBudget" in tcr || "thinking_budget" in tcr) configs.push(tcr);
}
}
return configs;
}
/**
* Read the first thinking budget found in the body (any supported nest / naming).
* Returns null when the body carries no readable numeric budget.
*/
export function readNestedThinkingBudget(body: unknown): number | null {
for (const tc of collectThinkingConfigs(body)) {
const raw = tc.thinkingBudget ?? tc.thinking_budget;
const n = Number(raw);
if (Number.isFinite(n)) return n;
}
return null;
}
/**
* Clamp every thinking budget in the body down to `max` (only lowers; never
* raises a budget already below max). Mutates in place. Returns true when at
* least one budget was actually lowered (i.e. a retry would send a different
* body) — false means the 400 was not caused by an over-max budget we hold, so
* retrying would resend an identical body and loop.
*/
export function clampNestedThinkingBudget(body: unknown, max: number): boolean {
let changed = false;
for (const tc of collectThinkingConfigs(body)) {
for (const key of ["thinkingBudget", "thinking_budget"] as const) {
const n = Number(tc[key]);
if (Number.isFinite(n) && n > max) {
tc[key] = max;
changed = true;
}
}
}
return changed;
}