mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 19:32:20 +03:00
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.
91 lines
3.2 KiB
TypeScript
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;
|
|
}
|