Files
OmniRoute/open-sse/utils/proxyFamilyResolve.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

93 lines
3.3 KiB
TypeScript

import dns from "node:dns/promises";
import { detectIpLiteralFamily, stripIpv6Brackets } from "./proxyFamily.ts";
export type FamilyLookupFn = (
hostname: string
) => Promise<Array<{ address: string; family: number }>>;
const defaultLookup: FamilyLookupFn = (hostname) => dns.lookup(hostname, { all: true });
/** Positive family checks are trusted for 5 minutes (DNS TTLs are typically short). */
const FAMILY_CHECK_POSITIVE_TTL_MS = 300_000;
/** Negative results change fast (DNS provisioning) — only 2 seconds. */
const FAMILY_CHECK_NEGATIVE_TTL_MS = 2_000;
interface FamilyCheckCacheEntry {
lookupFn: FamilyLookupFn;
checkedAt: number;
ok: boolean;
message?: string;
}
/** Cached family-check results keyed by `${host}:${family}`. */
const familyCheckCache = new Map<string, FamilyCheckCacheEntry>();
/** In-flight family checks keyed by `${host}:${family}` — dedupes concurrent probes. */
const familyCheckInflight = new Map<string, Promise<void>>();
/**
* Fail-closed guarantee for an IPv6-only (or IPv4-only) proxy given as a hostname:
* refuse early if the hostname has no record in the required family. No-op for IP
* literals (their family is intrinsic). Results are cached per (host, family,
* lookupFn) and concurrent checks for the same key are single-flighted.
*/
export async function assertHostnameSupportsFamily(
host: string,
family: 4 | 6,
lookupFn: FamilyLookupFn = defaultLookup
): Promise<void> {
if (detectIpLiteralFamily(host) !== null) return;
const cacheKey = `${host}:${family}`;
const cached = familyCheckCache.get(cacheKey);
if (cached && cached.lookupFn === lookupFn) {
const ttl = cached.ok ? FAMILY_CHECK_POSITIVE_TTL_MS : FAMILY_CHECK_NEGATIVE_TTL_MS;
if (Date.now() - cached.checkedAt < ttl) {
if (!cached.ok) throw new Error(cached.message);
return;
}
familyCheckCache.delete(cacheKey);
}
const inflight = familyCheckInflight.get(cacheKey);
if (inflight) {
await inflight;
return;
}
const probe = (async () => {
let records: Array<{ address: string; family: number }>;
try {
records = await lookupFn(stripIpv6Brackets(host));
} catch (err) {
const message = `[ProxyFamily] DNS resolution failed for ${host}; refusing to egress (fail-closed): ${
err instanceof Error ? err.message : String(err)
}`;
familyCheckCache.set(cacheKey, { lookupFn, checkedAt: Date.now(), ok: false, message });
throw new Error(message);
}
const hasFamily = records.some((r) => r.family === family);
if (!hasFamily) {
const message = `[ProxyFamily] Proxy host ${host} has no ${family === 6 ? "IPv6 (AAAA)" : "IPv4 (A)"} record; refusing ${
family === 6 ? "IPv6" : "IPv4"
}-only egress (fail-closed)`;
familyCheckCache.set(cacheKey, { lookupFn, checkedAt: Date.now(), ok: false, message });
throw new Error(message);
}
familyCheckCache.set(cacheKey, { lookupFn, checkedAt: Date.now(), ok: true });
})();
familyCheckInflight.set(cacheKey, probe);
try {
await probe;
} finally {
if (familyCheckInflight.get(cacheKey) === probe) {
familyCheckInflight.delete(cacheKey);
}
}
}
/** Test hook: drop all cached and in-flight family checks. */
export function __clearFamilyCheckCacheForTest(): void {
familyCheckCache.clear();
familyCheckInflight.clear();
}