Merge remote-tracking branch 'origin/main' into fix/issue-6647-retry-summary-requests-2

This commit is contained in:
David Brailovsky
2026-07-21 17:15:54 +02:00
102 changed files with 3220 additions and 504 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;
}
@@ -107,9 +107,11 @@ function parseLoadProgress(data: unknown): LlamaProgress | undefined {
function parseDownloadProgress(data: unknown): LlamaProgress | undefined {
if (typeof data !== "object" || data === null) return undefined;
const nested = (data as { progress?: unknown }).progress;
const files = typeof nested === "object" && nested !== null ? nested : data;
let done = 0;
let total = 0;
for (const value of Object.values(data as Record<string, unknown>)) {
for (const value of Object.values(files as Record<string, unknown>)) {
if (typeof value !== "object" || value === null) continue;
const entry = value as { done?: unknown; total?: unknown };
if (typeof entry.done !== "number" || typeof entry.total !== "number") continue;
@@ -88,7 +88,7 @@ export default function llamaExtension(pi: ExtensionAPI): void {
model: target.id,
initialMessage: "Starting…",
cancelTitle: "Stop loading?",
cancelMessage: `Stop loading ${target.id}?`,
cancelMessage: target.id,
run: (signal, update) => client.loadAndWait(target.id, update, signal),
cancel: () => client.unload(target.id),
});
@@ -119,7 +119,7 @@ export default function llamaExtension(pi: ExtensionAPI): void {
client: LlamaClient,
model: LlamaModelInfo,
): Promise<void> => {
if (!(await ui.confirm("Unload model?", `Unload ${model.id}?`))) return;
if (!(await ui.confirm("Unload model?", model.id))) return;
await client.unloadAndWait(model.id);
await syncCatalog(ctx, client);
ctx.ui.notify(`Unloaded ${model.id}`);
@@ -162,7 +162,7 @@ export default function llamaExtension(pi: ExtensionAPI): void {
model,
initialMessage: "Starting…",
cancelTitle: "Stop download?",
cancelMessage: `Stop downloading ${model}?`,
cancelMessage: model,
run: (signal, update) => client.downloadAndWait(model, update, signal),
cancel: () => client.unload(model),
});