Files
OmniRoute/src/shared/utils/keyedMutex.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

38 lines
1.4 KiB
TypeScript

/**
* Generic keyed serialization lock. `run(key, fn)` queues onto whatever call
* is currently pending for `key` (or runs immediately if none), always
* invoking a NEW call to `fn()` for every caller — this is a SERIALIZATION
* queue, not a dedup cache. Contrast with
* `open-sse/services/tokenRefresh.ts`'s `connectionRefreshMutex`, which
* intentionally shares one execution's result across concurrent callers
* because they all want the identical "current access token" outcome; here,
* concurrent callers for the same key each get their own `fn`'s own result,
* just run one at a time.
*/
export function createKeyedMutex<T = unknown>(): {
run(key: string, fn: () => Promise<T>): Promise<T>;
} {
const queue = new Map<string, Promise<void>>();
function run(key: string, fn: () => Promise<T>): Promise<T> {
const prior = queue.get(key) ?? Promise.resolve();
const result = prior.then(fn, fn);
// Neutral marker (never rejects) used only to chain subsequent callers
// and to identify — by reference — whether this call is still the last
// one queued for `key` once it settles.
const marker: Promise<void> = result.then(
() => undefined,
() => undefined
);
queue.set(key, marker);
marker.finally(() => {
if (queue.get(key) === marker) {
queue.delete(key);
}
});
return result;
}
return { run };
}