chore: format models CLI

This commit is contained in:
Peter Steinberger
2026-01-04 18:11:41 +01:00
parent 8e5153ba10
commit ff46f8ce58
14 changed files with 74 additions and 71 deletions

View File

@@ -59,7 +59,10 @@ function resolveFallbackCandidates(params: {
const seen = new Set<string>(); const seen = new Set<string>();
const candidates: ModelCandidate[] = []; const candidates: ModelCandidate[] = [];
const addCandidate = (candidate: ModelCandidate, enforceAllowlist: boolean) => { const addCandidate = (
candidate: ModelCandidate,
enforceAllowlist: boolean,
) => {
if (!candidate.provider || !candidate.model) return; if (!candidate.provider || !candidate.model) return;
const key = modelKey(candidate.provider, candidate.model); const key = modelKey(candidate.provider, candidate.model);
if (seen.has(key)) return; if (seen.has(key)) return;

View File

@@ -1,13 +1,13 @@
import { Type } from "@sinclair/typebox";
import { import {
type Context,
complete, complete,
getEnvApiKey, getEnvApiKey,
getModel, getModel,
type Context,
type Model, type Model,
type Tool,
type OpenAICompletionsOptions, type OpenAICompletionsOptions,
type Tool,
} from "@mariozechner/pi-ai"; } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models"; const OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
const DEFAULT_TIMEOUT_MS = 12_000; const DEFAULT_TIMEOUT_MS = 12_000;
@@ -130,9 +130,7 @@ async function fetchOpenRouterModels(
const id = typeof obj.id === "string" ? obj.id.trim() : ""; const id = typeof obj.id === "string" ? obj.id.trim() : "";
if (!id) return null; if (!id) return null;
const name = const name =
typeof obj.name === "string" && obj.name.trim() typeof obj.name === "string" && obj.name.trim() ? obj.name.trim() : id;
? obj.name.trim()
: id;
const contextLength = const contextLength =
typeof obj.context_length === "number" && typeof obj.context_length === "number" &&
@@ -274,7 +272,7 @@ async function mapWithConcurrency<T, R>(
fn: (item: T, index: number) => Promise<R>, fn: (item: T, index: number) => Promise<R>,
): Promise<R[]> { ): Promise<R[]> {
const limit = Math.max(1, Math.floor(concurrency)); const limit = Math.max(1, Math.floor(concurrency));
const results: R[] = new Array(items.length); const results = Array.from({ length: items.length }) as R[];
let nextIndex = 0; let nextIndex = 0;
const worker = async () => { const worker = async () => {
@@ -296,8 +294,7 @@ export async function scanOpenRouterModels(
options: OpenRouterScanOptions = {}, options: OpenRouterScanOptions = {},
): Promise<ModelScanResult[]> { ): Promise<ModelScanResult[]> {
const fetchImpl = options.fetchImpl ?? fetch; const fetchImpl = options.fetchImpl ?? fetch;
const apiKey = const apiKey = options.apiKey?.trim() || getEnvApiKey("openrouter") || "";
options.apiKey?.trim() || getEnvApiKey("openrouter") || "";
if (!apiKey) { if (!apiKey) {
throw new Error( throw new Error(
"Missing OpenRouter API key. Set OPENROUTER_API_KEY to run models scan.", "Missing OpenRouter API key. Set OPENROUTER_API_KEY to run models scan.",
@@ -337,10 +334,7 @@ export async function scanOpenRouterModels(
return true; return true;
}); });
const baseModel = getModel( const baseModel = getModel("openrouter", "openrouter/auto") as OpenAIModel;
"openrouter",
"openrouter/auto",
) as OpenAIModel;
return mapWithConcurrency(filtered, concurrency, async (entry) => { return mapWithConcurrency(filtered, concurrency, async (entry) => {
const model: OpenAIModel = { const model: OpenAIModel = {

View File

@@ -1,11 +1,11 @@
import crypto from "node:crypto"; import crypto from "node:crypto";
import { lookupContextTokens } from "../../agents/context.js"; import { lookupContextTokens } from "../../agents/context.js";
import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js"; import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
import { runWithModelFallback } from "../../agents/model-fallback.js";
import { import {
queueEmbeddedPiMessage, queueEmbeddedPiMessage,
runEmbeddedPiAgent, runEmbeddedPiAgent,
} from "../../agents/pi-embedded.js"; } from "../../agents/pi-embedded.js";
import { runWithModelFallback } from "../../agents/model-fallback.js";
import { import {
loadSessionStore, loadSessionStore,
type SessionEntry, type SessionEntry,
@@ -209,7 +209,9 @@ export async function runReplyAgent(params: {
}); });
if (stripped.didStrip && !didLogHeartbeatStrip) { if (stripped.didStrip && !didLogHeartbeatStrip) {
didLogHeartbeatStrip = true; didLogHeartbeatStrip = true;
logVerbose("Stripped stray HEARTBEAT_OK token from reply"); logVerbose(
"Stripped stray HEARTBEAT_OK token from reply",
);
} }
if ( if (
stripped.shouldSkip && stripped.shouldSkip &&
@@ -297,7 +299,9 @@ export async function runReplyAgent(params: {
}); });
if (stripped.didStrip && !didLogHeartbeatStrip) { if (stripped.didStrip && !didLogHeartbeatStrip) {
didLogHeartbeatStrip = true; didLogHeartbeatStrip = true;
logVerbose("Stripped stray HEARTBEAT_OK token from reply"); logVerbose(
"Stripped stray HEARTBEAT_OK token from reply",
);
} }
if ( if (
stripped.shouldSkip && stripped.shouldSkip &&

View File

@@ -133,7 +133,8 @@ export function buildStatusMessage(args: StatusArgs): string {
defaultProvider: DEFAULT_PROVIDER, defaultProvider: DEFAULT_PROVIDER,
defaultModel: DEFAULT_MODEL, defaultModel: DEFAULT_MODEL,
}); });
const provider = entry?.modelProvider ?? resolved.provider ?? DEFAULT_PROVIDER; const provider =
entry?.modelProvider ?? resolved.provider ?? DEFAULT_PROVIDER;
let model = entry?.model ?? resolved.model ?? DEFAULT_MODEL; let model = entry?.model ?? resolved.model ?? DEFAULT_MODEL;
let contextTokens = let contextTokens =
entry?.contextTokens ?? entry?.contextTokens ??

View File

@@ -64,9 +64,7 @@ export function registerModelsCli(program: Command) {
} }
}); });
const aliases = models const aliases = models.command("aliases").description("Manage model aliases");
.command("aliases")
.description("Manage model aliases");
aliases aliases
.command("list") .command("list")

View File

@@ -6,13 +6,13 @@ import {
DEFAULT_PROVIDER, DEFAULT_PROVIDER,
} from "../agents/defaults.js"; } from "../agents/defaults.js";
import { loadModelCatalog } from "../agents/model-catalog.js"; import { loadModelCatalog } from "../agents/model-catalog.js";
import { runWithModelFallback } from "../agents/model-fallback.js";
import { import {
buildAllowedModelSet, buildAllowedModelSet,
modelKey, modelKey,
resolveConfiguredModelRef, resolveConfiguredModelRef,
resolveThinkingDefault, resolveThinkingDefault,
} from "../agents/model-selection.js"; } from "../agents/model-selection.js";
import { runWithModelFallback } from "../agents/model-fallback.js";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import { buildWorkspaceSkillSnapshot } from "../agents/skills.js"; import { buildWorkspaceSkillSnapshot } from "../agents/skills.js";
import { import {
@@ -443,8 +443,7 @@ export async function agentCommand(
// Update token+model fields in the session store. // Update token+model fields in the session store.
if (sessionStore && sessionKey) { if (sessionStore && sessionKey) {
const usage = result.meta.agentMeta?.usage; const usage = result.meta.agentMeta?.usage;
const modelUsed = const modelUsed = result.meta.agentMeta?.model ?? fallbackModel ?? model;
result.meta.agentMeta?.model ?? fallbackModel ?? model;
const providerUsed = const providerUsed =
result.meta.agentMeta?.provider ?? fallbackProvider ?? provider; result.meta.agentMeta?.provider ?? fallbackProvider ?? provider;
const contextTokens = const contextTokens =

View File

@@ -1,4 +1,3 @@
export { modelsListCommand, modelsStatusCommand } from "./models/list.js";
export { export {
modelsAliasesAddCommand, modelsAliasesAddCommand,
modelsAliasesListCommand, modelsAliasesListCommand,
@@ -10,5 +9,6 @@ export {
modelsFallbacksListCommand, modelsFallbacksListCommand,
modelsFallbacksRemoveCommand, modelsFallbacksRemoveCommand,
} from "./models/fallbacks.js"; } from "./models/fallbacks.js";
export { modelsListCommand, modelsStatusCommand } from "./models/list.js";
export { modelsScanCommand } from "./models/scan.js"; export { modelsScanCommand } from "./models/scan.js";
export { modelsSetCommand } from "./models/set.js"; export { modelsSetCommand } from "./models/set.js";

View File

@@ -1,7 +1,4 @@
import { import { CONFIG_PATH_CLAWDBOT, loadConfig } from "../../config/config.js";
CONFIG_PATH_CLAWDBOT,
loadConfig,
} from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js"; import type { RuntimeEnv } from "../../runtime.js";
import { import {
ensureFlagCompatibility, ensureFlagCompatibility,
@@ -47,7 +44,7 @@ export async function modelsAliasesAddCommand(
const alias = normalizeAlias(aliasRaw); const alias = normalizeAlias(aliasRaw);
const updated = await updateConfig((cfg) => { const updated = await updateConfig((cfg) => {
const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const resolved = resolveModelTarget({ raw: modelRaw, cfg });
const nextAliases = { ...(cfg.agent?.modelAliases ?? {}) }; const nextAliases = { ...cfg.agent?.modelAliases };
nextAliases[alias] = `${resolved.provider}/${resolved.model}`; nextAliases[alias] = `${resolved.provider}/${resolved.model}`;
return { return {
...cfg, ...cfg,
@@ -68,7 +65,7 @@ export async function modelsAliasesRemoveCommand(
) { ) {
const alias = normalizeAlias(aliasRaw); const alias = normalizeAlias(aliasRaw);
const updated = await updateConfig((cfg) => { const updated = await updateConfig((cfg) => {
const nextAliases = { ...(cfg.agent?.modelAliases ?? {}) }; const nextAliases = { ...cfg.agent?.modelAliases };
if (!nextAliases[alias]) { if (!nextAliases[alias]) {
throw new Error(`Alias not found: ${alias}`); throw new Error(`Alias not found: ${alias}`);
} }
@@ -83,7 +80,10 @@ export async function modelsAliasesRemoveCommand(
}); });
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`); runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
if (!updated.agent?.modelAliases || Object.keys(updated.agent.modelAliases).length === 0) { if (
!updated.agent?.modelAliases ||
Object.keys(updated.agent.modelAliases).length === 0
) {
runtime.log("No aliases configured."); runtime.log("No aliases configured.");
} }
} }

View File

@@ -1,12 +1,9 @@
import {
CONFIG_PATH_CLAWDBOT,
loadConfig,
} from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js";
import { import {
buildModelAliasIndex, buildModelAliasIndex,
resolveModelRefFromString, resolveModelRefFromString,
} from "../../agents/model-selection.js"; } from "../../agents/model-selection.js";
import { CONFIG_PATH_CLAWDBOT, loadConfig } from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js";
import { import {
DEFAULT_PROVIDER, DEFAULT_PROVIDER,
ensureFlagCompatibility, ensureFlagCompatibility,
@@ -60,8 +57,8 @@ export async function modelsFallbacksAddCommand(
aliasIndex, aliasIndex,
}), }),
) )
.filter(Boolean) .filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))
.map((entry) => modelKey(entry!.ref.provider, entry!.ref.model)); .map((entry) => modelKey(entry.ref.provider, entry.ref.model));
if (existingKeys.includes(targetKey)) return cfg; if (existingKeys.includes(targetKey)) return cfg;

View File

@@ -1,22 +1,22 @@
import chalk from "chalk"; import { type Api, getEnvApiKey, type Model } from "@mariozechner/pi-ai";
import { import {
discoverAuthStorage, discoverAuthStorage,
discoverModels, discoverModels,
} from "@mariozechner/pi-coding-agent"; } from "@mariozechner/pi-coding-agent";
import { getEnvApiKey, type Api, type Model } from "@mariozechner/pi-ai"; import chalk from "chalk";
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js"; import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
import { ensureClawdbotModelsJson } from "../../agents/models-config.js";
import { import {
buildModelAliasIndex, buildModelAliasIndex,
parseModelRef, parseModelRef,
resolveModelRefFromString,
resolveConfiguredModelRef, resolveConfiguredModelRef,
resolveModelRefFromString,
} from "../../agents/model-selection.js"; } from "../../agents/model-selection.js";
import { ensureClawdbotModelsJson } from "../../agents/models-config.js";
import { import {
type ClawdbotConfig,
CONFIG_PATH_CLAWDBOT, CONFIG_PATH_CLAWDBOT,
loadConfig, loadConfig,
type ClawdbotConfig,
} from "../../config/config.js"; } from "../../config/config.js";
import { info } from "../../globals.js"; import { info } from "../../globals.js";
import type { RuntimeEnv } from "../../runtime.js"; import type { RuntimeEnv } from "../../runtime.js";
@@ -35,7 +35,9 @@ const LOCAL_PAD = 5;
const AUTH_PAD = 5; const AUTH_PAD = 5;
const isRich = (opts?: { json?: boolean; plain?: boolean }) => const isRich = (opts?: { json?: boolean; plain?: boolean }) =>
Boolean(process.stdout.isTTY && chalk.level > 0 && !opts?.json && !opts?.plain); Boolean(
process.stdout.isTTY && chalk.level > 0 && !opts?.json && !opts?.plain,
);
const pad = (value: string, size: number) => value.padEnd(size); const pad = (value: string, size: number) => value.padEnd(size);

View File

@@ -3,9 +3,10 @@ import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js"; import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
import { import {
scanOpenRouterModels,
type ModelScanResult, type ModelScanResult,
scanOpenRouterModels,
} from "../../agents/model-scan.js"; } from "../../agents/model-scan.js";
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
import { warn } from "../../globals.js"; import { warn } from "../../globals.js";
import type { RuntimeEnv } from "../../runtime.js"; import type { RuntimeEnv } from "../../runtime.js";
import { import {
@@ -14,7 +15,6 @@ import {
formatTokenK, formatTokenK,
updateConfig, updateConfig,
} from "./shared.js"; } from "./shared.js";
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
const MODEL_PAD = 42; const MODEL_PAD = 42;
const CTX_PAD = 8; const CTX_PAD = 8;
@@ -27,7 +27,6 @@ const truncate = (value: string, max: number) => {
return `${value.slice(0, max - 3)}...`; return `${value.slice(0, max - 3)}...`;
}; };
function sortScanResults(results: ModelScanResult[]): ModelScanResult[] { function sortScanResults(results: ModelScanResult[]): ModelScanResult[] {
return results.slice().sort((a, b) => { return results.slice().sort((a, b) => {
const aImage = a.image.ok ? 1 : 0; const aImage = a.image.ok ? 1 : 0;
@@ -133,16 +132,20 @@ export async function modelsScanCommand(
runtime: RuntimeEnv, runtime: RuntimeEnv,
) { ) {
const minParams = opts.minParams ? Number(opts.minParams) : undefined; const minParams = opts.minParams ? Number(opts.minParams) : undefined;
if (minParams !== undefined && (!Number.isFinite(minParams) || minParams < 0)) { if (
minParams !== undefined &&
(!Number.isFinite(minParams) || minParams < 0)
) {
throw new Error("--min-params must be >= 0"); throw new Error("--min-params must be >= 0");
} }
const maxAgeDays = opts.maxAgeDays ? Number(opts.maxAgeDays) : undefined; const maxAgeDays = opts.maxAgeDays ? Number(opts.maxAgeDays) : undefined;
if (maxAgeDays !== undefined && (!Number.isFinite(maxAgeDays) || maxAgeDays < 0)) { if (
maxAgeDays !== undefined &&
(!Number.isFinite(maxAgeDays) || maxAgeDays < 0)
) {
throw new Error("--max-age-days must be >= 0"); throw new Error("--max-age-days must be >= 0");
} }
const maxCandidates = opts.maxCandidates const maxCandidates = opts.maxCandidates ? Number(opts.maxCandidates) : 6;
? Number(opts.maxCandidates)
: 6;
if (!Number.isFinite(maxCandidates) || maxCandidates <= 0) { if (!Number.isFinite(maxCandidates) || maxCandidates <= 0) {
throw new Error("--max-candidates must be > 0"); throw new Error("--max-candidates must be > 0");
} }
@@ -151,7 +154,10 @@ export async function modelsScanCommand(
throw new Error("--timeout must be > 0"); throw new Error("--timeout must be > 0");
} }
const concurrency = opts.concurrency ? Number(opts.concurrency) : undefined; const concurrency = opts.concurrency ? Number(opts.concurrency) : undefined;
if (concurrency !== undefined && (!Number.isFinite(concurrency) || concurrency <= 0)) { if (
concurrency !== undefined &&
(!Number.isFinite(concurrency) || concurrency <= 0)
) {
throw new Error("--concurrency must be > 0"); throw new Error("--concurrency must be > 0");
} }
@@ -226,9 +232,7 @@ export async function modelsScanCommand(
const allowlist = buildAllowlistSet(updated); const allowlist = buildAllowlistSet(updated);
const allowlistMissing = const allowlistMissing =
allowlist.size > 0 allowlist.size > 0 ? selected.filter((entry) => !allowlist.has(entry)) : [];
? selected.filter((entry) => !allowlist.has(entry))
: [];
if (opts.json) { if (opts.json) {
runtime.log( runtime.log(

View File

@@ -1,11 +1,13 @@
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js"; import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
import type { RuntimeEnv } from "../../runtime.js"; import type { RuntimeEnv } from "../../runtime.js";
import { buildAllowlistSet, modelKey, resolveModelTarget, updateConfig } from "./shared.js"; import {
buildAllowlistSet,
modelKey,
resolveModelTarget,
updateConfig,
} from "./shared.js";
export async function modelsSetCommand( export async function modelsSetCommand(modelRaw: string, runtime: RuntimeEnv) {
modelRaw: string,
runtime: RuntimeEnv,
) {
const updated = await updateConfig((cfg) => { const updated = await updateConfig((cfg) => {
const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const resolved = resolveModelTarget({ raw: modelRaw, cfg });
const allowlist = buildAllowlistSet(cfg); const allowlist = buildAllowlistSet(cfg);

View File

@@ -1,7 +1,4 @@
import { import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
DEFAULT_MODEL,
DEFAULT_PROVIDER,
} from "../../agents/defaults.js";
import { import {
buildModelAliasIndex, buildModelAliasIndex,
modelKey, modelKey,
@@ -9,9 +6,9 @@ import {
resolveModelRefFromString, resolveModelRefFromString,
} from "../../agents/model-selection.js"; } from "../../agents/model-selection.js";
import { import {
type ClawdbotConfig,
readConfigFileSnapshot, readConfigFileSnapshot,
writeConfigFile, writeConfigFile,
type ClawdbotConfig,
} from "../../config/config.js"; } from "../../config/config.js";
export const ensureFlagCompatibility = (opts: { export const ensureFlagCompatibility = (opts: {

View File

@@ -6,11 +6,11 @@ import {
DEFAULT_PROVIDER, DEFAULT_PROVIDER,
} from "../agents/defaults.js"; } from "../agents/defaults.js";
import { loadModelCatalog } from "../agents/model-catalog.js"; import { loadModelCatalog } from "../agents/model-catalog.js";
import { runWithModelFallback } from "../agents/model-fallback.js";
import { import {
resolveConfiguredModelRef, resolveConfiguredModelRef,
resolveThinkingDefault, resolveThinkingDefault,
} from "../agents/model-selection.js"; } from "../agents/model-selection.js";
import { runWithModelFallback } from "../agents/model-fallback.js";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import { buildWorkspaceSkillSnapshot } from "../agents/skills.js"; import { buildWorkspaceSkillSnapshot } from "../agents/skills.js";
import { import {
@@ -294,7 +294,10 @@ export async function runCronIsolatedAgentTurn(params: {
model: modelOverride, model: modelOverride,
thinkLevel, thinkLevel,
verboseLevel: verboseLevel:
(cronSession.sessionEntry.verboseLevel as "on" | "off" | undefined) ?? (cronSession.sessionEntry.verboseLevel as
| "on"
| "off"
| undefined) ??
(agentCfg?.verboseDefault as "on" | "off" | undefined), (agentCfg?.verboseDefault as "on" | "off" | undefined),
timeoutMs, timeoutMs,
runId: cronSession.sessionEntry.sessionId, runId: cronSession.sessionEntry.sessionId,
@@ -312,8 +315,7 @@ export async function runCronIsolatedAgentTurn(params: {
// Update token+model fields in the session store. // Update token+model fields in the session store.
{ {
const usage = runResult.meta.agentMeta?.usage; const usage = runResult.meta.agentMeta?.usage;
const modelUsed = const modelUsed = runResult.meta.agentMeta?.model ?? fallbackModel ?? model;
runResult.meta.agentMeta?.model ?? fallbackModel ?? model;
const providerUsed = const providerUsed =
runResult.meta.agentMeta?.provider ?? fallbackProvider ?? provider; runResult.meta.agentMeta?.provider ?? fallbackProvider ?? provider;
const contextTokens = const contextTokens =