Files

62 lines
1.7 KiB
JavaScript
Raw Permalink Normal View History

const MODELS_DEV_API_URL = 'https://models.dev/api.json';
const DEFAULT_TTL_MS = 10 * 60 * 1000;
const DEFAULT_TIMEOUT_MS = 8000;
// Shared in-process cache of the models.dev catalog. Used by the
// /api/openchamber/models-metadata route and the small-model resolver so the
// server fetches the catalog once, not per consumer.
let cachedMetadata = null;
let cachedAt = 0;
let inflight = null;
const fetchCatalog = async (url, timeoutMs) => {
const response = await fetch(url, {
headers: { Accept: 'application/json' },
signal: AbortSignal.timeout(timeoutMs),
});
if (!response.ok) {
throw new Error(`models.dev responded with status ${response.status}`);
}
const metadata = await response.json();
if (!metadata || typeof metadata !== 'object') {
throw new Error('models.dev returned an unexpected payload');
}
return metadata;
};
/**
* Returns the models.dev catalog, serving the in-memory copy while fresh.
* On fetch failure a stale cached copy is returned when available; otherwise
* the error propagates.
*/
export async function getModelsMetadata({
url = MODELS_DEV_API_URL,
ttlMs = DEFAULT_TTL_MS,
timeoutMs = DEFAULT_TIMEOUT_MS,
} = {}) {
const now = Date.now();
if (cachedMetadata && now - cachedAt < ttlMs) {
return { metadata: cachedMetadata, fromCache: true };
}
if (!inflight) {
inflight = fetchCatalog(url, timeoutMs).finally(() => {
inflight = null;
});
}
try {
const metadata = await inflight;
cachedMetadata = metadata;
cachedAt = Date.now();
return { metadata, fromCache: false };
} catch (error) {
if (cachedMetadata) {
return { metadata: cachedMetadata, fromCache: true, stale: true };
}
throw error;
}
}
export { MODELS_DEV_API_URL };