Files
OmniRoute/open-sse/services/combo/runtimeUnitCapacity.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

91 lines
3.2 KiB
TypeScript

/**
* @file runtimeUnitCapacity.ts
* @description Concurrency-capacity checks for nested combo execute-mode units so
* ordered strategies overflow to the next slot instead of queueing on a full connection.
*
* @changes
* - [2026-07-24] [Composer] - Initial capacity pre-check for execute-mode runtime units
*/
import { isAccountSemaphoreFull } from "../accountSemaphore.ts";
import { resolveComboTargets } from "./comboStructure.ts";
import { lookupPositiveCap } from "./concurrencyCaps.ts";
import type {
ComboCollectionLike,
ComboLike,
HiddenModelsByProvider,
ResolvedComboUnit,
} from "./types.ts";
type CapLookup = (connectionId: string) => Promise<number | null>;
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}
function getCombosList(allCombos: ComboCollectionLike): ComboLike[] {
const combos = Array.isArray(allCombos) ? allCombos : allCombos?.combos || [];
return combos.filter(
(combo): combo is ComboLike => isRecord(combo) && typeof combo.name === "string"
);
}
function findComboByName(allCombos: ComboCollectionLike, name: string): ComboLike | null {
return getCombosList(allCombos).find((combo) => combo.name === name) || null;
}
async function isConnectionAtConcurrencyCap(
provider: string,
connectionId: string,
lookupCap: CapLookup
): Promise<boolean> {
const cap = await lookupCap(connectionId);
if (!cap) return false;
return isAccountSemaphoreFull(provider, connectionId, cap);
}
/**
* Returns true when the runtime unit should be skipped because every limited
* connection it would use is already at max_concurrent.
*/
export async function isRuntimeUnitAtConcurrencyCap(
unit: ResolvedComboUnit,
allCombos: ComboCollectionLike,
lookupCap: CapLookup = lookupPositiveCap,
// Threaded from the caller so the hidden-model snapshot resolved once per
// request is reused. Without it resolveComboTargets falls back to its default
// getHiddenModelsByProvider(), i.e. a fresh full key_value read per nested
// combo-ref unit on EVERY request (#8878 threaded the other call sites).
hiddenModelsByProvider?: HiddenModelsByProvider
): Promise<boolean> {
if (unit.kind === "model") {
if (!unit.connectionId || !unit.provider) return false;
return isConnectionAtConcurrencyCap(unit.provider, unit.connectionId, lookupCap);
}
const childCombo = findComboByName(allCombos, unit.comboName);
if (!childCombo) return false;
const targets = resolveComboTargets(childCombo, allCombos, 1, hiddenModelsByProvider);
const byConnection = new Map<string, { provider: string; connectionId: string }>();
for (const target of targets) {
if (!target.connectionId || !target.provider) continue;
byConnection.set(target.connectionId, {
provider: target.provider,
connectionId: target.connectionId,
});
}
if (byConnection.size === 0) return false;
let sawLimitedConnection = false;
for (const { provider, connectionId } of byConnection.values()) {
const cap = await lookupCap(connectionId);
if (!cap) continue;
sawLimitedConnection = true;
if (!isAccountSemaphoreFull(provider, connectionId, cap)) {
return false;
}
}
return sawLimitedConnection;
}