feat: show llama presets if autoload enabled (#8558)

This commit is contained in:
Cristina Poncela Cubeiro 2026-08-24 13:29:51 +02:00 • committed by GitHub
parent 97fa14e39c
commit dcd461925d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 116 additions and 8 deletions

View file

@ -28,6 +28,10 @@ export interface LlamaModelsResponse {
object?: string;
}
export interface LlamaServerProps {
models_autoload?: boolean;
}
export interface LlamaModelEvent {
model: string;
event: string;
@ -189,6 +193,13 @@ export class LlamaClient {
return data;
}
async props(options: { signal?: AbortSignal } = {}): Promise<LlamaServerProps> {
const payload = await this.request("/props", { signal: options.signal });
if (typeof payload !== "object" || payload === null) return {};
const { models_autoload: modelsAutoload } = payload as Record<string, unknown>;
return typeof modelsAutoload === "boolean" ? { models_autoload: modelsAutoload } : {};
}
async load(model: string, signal?: AbortSignal): Promise<void> {
await this.request("/models/load", { method: "POST", body: JSON.stringify({ model }), signal });
}

View file

@ -25,9 +25,25 @@ async function resolveServerUrl(
return configured ? normalizeLlamaServerUrl(configured) : undefined;
}
function modelIsSelectable(model: LlamaModelInfo): boolean {
function modelIsSelectable(model: LlamaModelInfo, routerAutoload: boolean): boolean {
if (model.status.value === "loaded") return true;
// llama.cpp reports idle-slept models as "sleeping"; requests wake them automatically.
return model.status.value === "loaded" || model.status.value === "sleeping";
if (model.status.value === "sleeping") return true;
// Unloaded presets are routable only when llama.cpp router autoload can load them on first use.
return routerAutoload && model.status.value === "unloaded" && !model.status.failed && model.source === "preset";
}
async function routerAutoloadEnabled(
client: LlamaClient,
catalog: readonly LlamaModelInfo[],
signal: AbortSignal,
): Promise<boolean> {
if (!catalog.some((model) => model.status.value === "unloaded" && model.source === "preset")) return false;
try {
return (await client.props({ signal })).models_autoload === true;
} catch {
return false;
}
}
function toPiModel(model: LlamaModelInfo, serverUrl: string): Model<"openai-completions"> {
@ -57,14 +73,20 @@ function toPiModel(model: LlamaModelInfo, serverUrl: string): Model<"openai-comp
export interface LlamaProviderController {
provider: Provider<"openai-completions">;
setCatalog(models: readonly LlamaModelInfo[], serverUrl: string): void;
setCatalog(models: readonly LlamaModelInfo[], serverUrl: string, options?: { routerAutoload?: boolean }): void;
}
export function createLlamaProvider(): LlamaProviderController {
let models: readonly Model<"openai-completions">[] = [];
const setCatalog = (catalog: readonly LlamaModelInfo[], serverUrl: string): void => {
models = catalog.filter((model) => modelIsSelectable(model)).map((model) => toPiModel(model, serverUrl));
const setCatalog = (
catalog: readonly LlamaModelInfo[],
serverUrl: string,
options: { routerAutoload?: boolean } = {},
): void => {
models = catalog
.filter((model) => modelIsSelectable(model, options.routerAutoload === true))
.map((model) => toPiModel(model, serverUrl));
};
const provider: Provider<"openai-completions"> = {
@ -135,10 +157,13 @@ export function createLlamaProvider(): LlamaProviderController {
if (!context.allowNetwork || context.signal.aborted || context.credential?.type !== "api_key") return;
const serverUrl = credentialServerUrl(context.credential);
if (!serverUrl) return;
const catalog = await new LlamaClient(serverUrl, context.credential.key).list({ signal: context.signal });
const client = new LlamaClient(serverUrl, context.credential.key);
const catalog = await client.list({ signal: context.signal });
if (context.signal.aborted) return;
const routerAutoload = await routerAutoloadEnabled(client, catalog, context.signal);
if (context.signal.aborted) return;
const refreshed = catalog
.filter((model) => modelIsSelectable(model))
.filter((model) => modelIsSelectable(model, routerAutoload))
.map((model) => toPiModel(model, serverUrl));
await context.publish({
persist: { models: refreshed, checkedAt: Date.now() },