feat: add model scan progress callbacks

This commit is contained in:
Peter Steinberger
2026-01-08 05:20:01 +01:00
parent 28cd2e4c24
commit 2287d32263
3 changed files with 64 additions and 12 deletions

View File

@@ -53,7 +53,11 @@ describe("scanOpenRouterModels", () => {
"acme/free-by-suffix:free",
]);
const byPricing = results[0]!;
const [byPricing] = results;
expect(byPricing).toBeTruthy();
if (!byPricing) {
throw new Error("Expected pricing-based model result.");
}
expect(byPricing.supportsToolsMeta).toBe(true);
expect(byPricing.supportedParametersCount).toBe(3);
expect(byPricing.isFree).toBe(true);

View File

@@ -79,6 +79,11 @@ export type OpenRouterScanOptions = {
maxAgeDays?: number;
providerFilter?: string;
probe?: boolean;
onProgress?: (update: {
phase: "catalog" | "probe";
completed: number;
total: number;
}) => void;
};
type OpenAIModel = Model<"openai-completions">;
@@ -333,10 +338,12 @@ async function mapWithConcurrency<T, R>(
items: T[],
concurrency: number,
fn: (item: T, index: number) => Promise<R>,
opts?: { onProgress?: (completed: number, total: number) => void },
): Promise<R[]> {
const limit = Math.max(1, Math.floor(concurrency));
const results = Array.from({ length: items.length }) as R[];
let nextIndex = 0;
let completed = 0;
const worker = async () => {
while (true) {
@@ -344,9 +351,16 @@ async function mapWithConcurrency<T, R>(
nextIndex += 1;
if (current >= items.length) return;
results[current] = await fn(items[current] as T, current);
completed += 1;
opts?.onProgress?.(completed, items.length);
}
};
if (items.length === 0) {
opts?.onProgress?.(0, 0);
return results;
}
await Promise.all(
Array.from({ length: Math.min(limit, items.length) }, () => worker()),
);
@@ -400,7 +414,16 @@ export async function scanOpenRouterModels(
const baseModel = getModel("openrouter", "openrouter/auto") as OpenAIModel;
return mapWithConcurrency(filtered, concurrency, async (entry) => {
options.onProgress?.({
phase: "probe",
completed: 0,
total: filtered.length,
});
return mapWithConcurrency(
filtered,
concurrency,
async (entry) => {
const isFree = isFreeOpenRouterModel(entry);
if (!probe) {
return {
@@ -454,7 +477,16 @@ export async function scanOpenRouterModels(
tool: toolResult,
image: imageResult,
} satisfies ModelScanResult;
});
},
{
onProgress: (completed, total) =>
options.onProgress?.({
phase: "probe",
completed,
total,
}),
},
);
}
export { OPENROUTER_MODELS_URL };

View File

@@ -4,6 +4,7 @@ import {
type ModelScanResult,
scanOpenRouterModels,
} from "../../agents/model-scan.js";
import { withProgress } from "../../cli/progress.js";
import { CONFIG_PATH_CLAWDBOT, loadConfig } from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js";
import { formatMs, formatTokenK, updateConfig } from "./shared.js";
@@ -188,15 +189,30 @@ export async function modelsScanCommand(
storedKey = undefined;
}
}
const results = await scanOpenRouterModels({
apiKey: storedKey ?? undefined,
minParamB: minParams,
maxAgeDays,
providerFilter: opts.provider,
timeoutMs: timeout,
concurrency,
probe,
});
const results = await withProgress(
{
label: "Scanning OpenRouter models...",
indeterminate: false,
enabled: opts.json !== true,
},
async (progress) =>
await scanOpenRouterModels({
apiKey: storedKey ?? undefined,
minParamB: minParams,
maxAgeDays,
providerFilter: opts.provider,
timeoutMs: timeout,
concurrency,
probe,
onProgress: ({ phase, completed, total }) => {
if (phase !== "probe") return;
if (total <= 0) return;
const labelBase = probe ? "Probing models" : "Scanning models";
progress.setLabel(`${labelBase} (${completed}/${total})`);
progress.setPercent((completed / total) * 100);
},
}),
);
if (!probe) {
if (!opts.json) {