fix(coding-agent): prefer newer generated model catalogs

This commit is contained in:
Armin Ronacher
2026-07-21 11:11:08 +02:00
parent 890b3547af
commit 54fad505b9
5 changed files with 125 additions and 72 deletions
@@ -143,7 +143,15 @@ export class ModelRuntime implements Models {
const providers = builtinProviderCatalog
.builtinProviders()
.map((provider) =>
provider.id === "radius" ? provider : withRemoteCatalog(provider, options.catalogBaseUrl),
provider.id === "radius"
? provider
: withRemoteCatalog(
provider,
options.catalogBaseUrl,
builtinProviderCatalog.getBuiltinModelDataUrl(
provider.id as builtinProviderCatalog.BuiltinProvider,
),
),
);
const runtime = new ModelRuntime(
credentials,
@@ -1,4 +1,5 @@
import type { Api, Model, Provider } from "@earendil-works/pi-ai";
import { stat } from "node:fs/promises";
import type { Api, Model, ModelsStoreEntry, Provider } from "@earendil-works/pi-ai";
import { VERSION } from "../config.ts";
import { getPiUserAgent } from "../utils/pi-user-agent.ts";
@@ -29,8 +30,26 @@ function parseCatalog(providerId: string, value: unknown): Model<Api>[] {
.map((model) => ({ ...model, provider: providerId }));
}
function remoteModels(
entry: ModelsStoreEntry | undefined,
localLastModified: number | undefined,
): readonly Model<Api>[] {
if (!entry) return [];
if (
localLastModified !== undefined &&
(entry.lastModified === undefined || entry.lastModified <= localLastModified)
) {
return [];
}
return entry.models;
}
/** Add a persisted pi.dev catalog overlay to a static built-in provider. */
export function withRemoteCatalog(provider: Provider, catalogBaseUrl: string = DEFAULT_CATALOG_BASE_URL): Provider {
export function withRemoteCatalog(
provider: Provider,
catalogBaseUrl: string = DEFAULT_CATALOG_BASE_URL,
localCatalogUrl?: URL,
): Provider {
let dynamicModels: readonly Model<Api>[] = [];
let inflightRefresh: Promise<void> | undefined;
@@ -40,12 +59,21 @@ export function withRemoteCatalog(provider: Provider, catalogBaseUrl: string = D
refreshModels: (context) => {
inflightRefresh ??= (async () => {
try {
const localLastModified = localCatalogUrl
? await stat(localCatalogUrl).then(
(value) => value.mtimeMs,
() => undefined,
)
: undefined;
const stored = await context.store.read();
if (stored) dynamicModels = stored.models.filter((model) => model.provider === provider.id);
dynamicModels = remoteModels(stored, localLastModified).filter(
(model) => model.provider === provider.id,
);
if (!context.allowNetwork || context.signal?.aborted) return;
if (
!context.force &&
stored?.checkedAt !== undefined &&
stored.lastModified !== undefined &&
Date.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS
) {
return;
@@ -62,17 +90,23 @@ export function withRemoteCatalog(provider: Provider, catalogBaseUrl: string = D
if (context.signal?.aborted) return;
const checkedAt = Date.now();
if (response.status === 404 || response.status === 501) {
await context.store.write({ models: dynamicModels, checkedAt });
await context.store.write({ ...(stored ?? { models: [] }), checkedAt, lastModified: 0 });
return;
}
if (!response.ok) {
await context.store.write({ models: dynamicModels, checkedAt });
await context.store.write({ ...(stored ?? { models: [] }), checkedAt });
throw new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);
}
const refreshed = parseCatalog(provider.id, await response.json());
const lastModified = Date.parse(response.headers.get("last-modified") ?? "");
if (context.signal?.aborted) return;
dynamicModels = refreshed;
await context.store.write({ models: refreshed, checkedAt });
const entry = {
models: refreshed,
checkedAt,
lastModified: Number.isNaN(lastModified) ? 0 : lastModified,
};
dynamicModels = remoteModels(entry, localLastModified);
await context.store.write(entry);
} finally {
inflightRefresh = undefined;
}