From 0ddfbf553496b33b58bdb2781aaf28545f14a44f Mon Sep 17 00:00:00 2001 From: mneves75 Date: Tue, 6 Jan 2026 11:35:23 -0300 Subject: [PATCH] Feat: normalize z.ai provider ids --- src/agents/model-selection.ts | 11 +- src/commands/models/list.ts | 482 ++++------------------------------ 2 files changed, 55 insertions(+), 438 deletions(-) diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index f342700dd..ac25155df 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -21,6 +21,12 @@ export function modelKey(provider: string, model: string) { return `${provider}/${model}`; } +function normalizeProvider(provider: string): string { + const normalized = provider.trim().toLowerCase(); + if (normalized === "z.ai" || normalized === "z-ai") return "zai"; + return normalized; +} + export function parseModelRef( raw: string, defaultProvider: string, @@ -29,9 +35,10 @@ export function parseModelRef( if (!trimmed) return null; const slash = trimmed.indexOf("/"); if (slash === -1) { - return { provider: defaultProvider, model: trimmed }; + return { provider: normalizeProvider(defaultProvider), model: trimmed }; } - const provider = trimmed.slice(0, slash).trim(); + const providerRaw = trimmed.slice(0, slash).trim(); + const provider = normalizeProvider(providerRaw); const model = trimmed.slice(slash + 1).trim(); if (!provider || !model) return null; return { provider, model }; diff --git a/src/commands/models/list.ts b/src/commands/models/list.ts index c65e43d30..052b00ecf 100644 --- a/src/commands/models/list.ts +++ b/src/commands/models/list.ts @@ -1,5 +1,3 @@ -import path from "node:path"; - import type { Api, Model } from "@mariozechner/pi-ai"; import { discoverAuthStorage, @@ -12,8 +10,6 @@ import { type AuthProfileStore, ensureAuthProfileStore, listProfilesForProvider, - resolveAuthProfileDisplayLabel, - resolveAuthStorePathForDisplay, } from "../../agents/auth-profiles.js"; import { getCustomProviderApiKey, @@ -31,12 +27,8 @@ import { CONFIG_PATH_CLAWDBOT, loadConfig, } from "../../config/config.js"; -import { - getShellEnvAppliedKeys, - shouldEnableShellEnvFallback, -} from "../../infra/shell-env.js"; +import { info } from "../../globals.js"; import type { RuntimeEnv } from "../../runtime.js"; -import { shortenHomePath } from "../../utils.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER, @@ -58,52 +50,12 @@ const isRich = (opts?: { json?: boolean; plain?: boolean }) => const pad = (value: string, size: number) => value.padEnd(size); -const colorize = ( - rich: boolean, - color: (value: string) => string, - value: string, -) => (rich ? color(value) : value); - -const formatKey = (key: string, rich: boolean) => - colorize(rich, chalk.yellow, key); - -const formatValue = (value: string, rich: boolean) => - colorize(rich, chalk.white, value); - -const formatKeyValue = ( - key: string, - value: string, - rich: boolean, - valueColor: (value: string) => string = chalk.white, -) => `${formatKey(key, rich)}=${colorize(rich, valueColor, value)}`; - -const formatSeparator = (rich: boolean) => colorize(rich, chalk.gray, " | "); - -const formatTag = (tag: string, rich: boolean) => { - if (!rich) return tag; - if (tag === "default") return chalk.greenBright(tag); - if (tag === "image") return chalk.magentaBright(tag); - if (tag === "configured") return chalk.cyan(tag); - if (tag === "missing") return chalk.red(tag); - if (tag.startsWith("fallback#")) return chalk.yellow(tag); - if (tag.startsWith("img-fallback#")) return chalk.yellowBright(tag); - if (tag.startsWith("alias:")) return chalk.blue(tag); - return chalk.gray(tag); -}; - const truncate = (value: string, max: number) => { if (value.length <= max) return value; if (max <= 3) return value.slice(0, max); return `${value.slice(0, max - 3)}...`; }; -const maskApiKey = (value: string): string => { - const trimmed = value.trim(); - if (!trimmed) return "missing"; - if (trimmed.length <= 16) return trimmed; - return `${trimmed.slice(0, 8)}...${trimmed.slice(-8)}`; -}; - type ConfiguredEntry = { key: string; ref: { provider: string; model: string }; @@ -149,109 +101,6 @@ const hasAuthForProvider = ( return false; }; -type ProviderAuthOverview = { - provider: string; - effective: { - kind: "profiles" | "env" | "models.json" | "missing"; - detail: string; - }; - profiles: { - count: number; - oauth: number; - apiKey: number; - labels: string[]; - }; - env?: { value: string; source: string }; - modelsJson?: { value: string; source: string }; -}; - -function resolveProviderAuthOverview(params: { - provider: string; - cfg: ClawdbotConfig; - store: AuthProfileStore; - modelsPath: string; -}): ProviderAuthOverview { - const { provider, cfg, store } = params; - const profiles = listProfilesForProvider(store, provider); - const labels = profiles.map((profileId) => { - const profile = store.profiles[profileId]; - if (!profile) return `${profileId}=missing`; - if (profile.type === "api_key") { - return `${profileId}=${maskApiKey(profile.key)}`; - } - const display = resolveAuthProfileDisplayLabel({ cfg, store, profileId }); - const suffix = - display === profileId - ? "" - : display.startsWith(profileId) - ? display.slice(profileId.length).trim() - : `(${display})`; - return `${profileId}=OAuth${suffix ? ` ${suffix}` : ""}`; - }); - const oauthCount = profiles.filter( - (id) => store.profiles[id]?.type === "oauth", - ).length; - const apiKeyCount = profiles.filter( - (id) => store.profiles[id]?.type === "api_key", - ).length; - - const envKey = resolveEnvApiKey(provider); - const customKey = getCustomProviderApiKey(cfg, provider); - - const effective: ProviderAuthOverview["effective"] = (() => { - if (profiles.length > 0) { - return { - kind: "profiles", - detail: shortenHomePath(resolveAuthStorePathForDisplay()), - }; - } - if (envKey) { - const isOAuthEnv = - envKey.source.includes("OAUTH_TOKEN") || - envKey.source.toLowerCase().includes("oauth"); - return { - kind: "env", - detail: isOAuthEnv ? "OAuth (env)" : maskApiKey(envKey.apiKey), - }; - } - if (customKey) { - return { kind: "models.json", detail: maskApiKey(customKey) }; - } - return { kind: "missing", detail: "missing" }; - })(); - - return { - provider, - effective, - profiles: { - count: profiles.length, - oauth: oauthCount, - apiKey: apiKeyCount, - labels, - }, - ...(envKey - ? { - env: { - value: - envKey.source.includes("OAUTH_TOKEN") || - envKey.source.toLowerCase().includes("oauth") - ? "OAuth (env)" - : maskApiKey(envKey.apiKey), - source: envKey.source, - }, - } - : {}), - ...(customKey - ? { - modelsJson: { - value: maskApiKey(customKey), - source: `models.json: ${shortenHomePath(params.modelsPath)}`, - }, - } - : {}), - }; -} - const resolveConfiguredEntries = (cfg: ClawdbotConfig) => { const resolvedDefault = resolveConfiguredModelRef({ cfg, @@ -456,45 +305,23 @@ function printModelTable( const keyLabel = pad(truncate(row.key, MODEL_PAD), MODEL_PAD); const inputLabel = pad(row.input || "-", INPUT_PAD); const ctxLabel = pad(formatTokenK(row.contextWindow), CTX_PAD); - const localText = row.local === null ? "-" : row.local ? "yes" : "no"; - const localLabel = pad(localText, LOCAL_PAD); - const authText = - row.available === null ? "-" : row.available ? "yes" : "no"; - const authLabel = pad(authText, AUTH_PAD); - const tagsLabel = - row.tags.length > 0 - ? rich - ? row.tags.map((tag) => formatTag(tag, rich)).join(",") - : row.tags.join(",") - : ""; - - const coloredInput = colorize( - rich, - row.input.includes("image") ? chalk.magenta : chalk.white, - inputLabel, + const localLabel = pad( + row.local === null ? "-" : row.local ? "yes" : "no", + LOCAL_PAD, ); - const coloredLocal = colorize( - rich, - row.local === null ? chalk.gray : row.local ? chalk.green : chalk.gray, - localLabel, - ); - const coloredAuth = colorize( - rich, - row.available === null - ? chalk.gray - : row.available - ? chalk.green - : chalk.red, - authLabel, + const authLabel = pad( + row.available === null ? "-" : row.available ? "yes" : "no", + AUTH_PAD, ); + const tagsLabel = row.tags.length > 0 ? row.tags.join(",") : ""; const line = [ rich ? chalk.cyan(keyLabel) : keyLabel, - coloredInput, + inputLabel, ctxLabel, - coloredLocal, - coloredAuth, - tagsLabel, + localLabel, + authLabel, + rich ? chalk.gray(tagsLabel) : tagsLabel, ].join(" "); runtime.log(line); } @@ -513,7 +340,12 @@ export async function modelsListCommand( ensureFlagCompatibility(opts); const cfg = loadConfig(); const authStore = ensureAuthProfileStore(); - const providerFilter = opts.provider?.trim().toLowerCase(); + const providerFilter = (() => { + const raw = opts.provider?.trim(); + if (!raw) return undefined; + const parsed = parseModelRef(`${raw}/_`, DEFAULT_PROVIDER); + return parsed?.provider ?? raw.toLowerCase(); + })(); let models: Model[] = []; let availableKeys: Set | undefined; @@ -617,7 +449,8 @@ export async function modelsStatusCommand( typeof modelConfig === "string" ? modelConfig.trim() : (modelConfig?.primary?.trim() ?? ""); - const defaultLabel = rawModel || `${resolved.provider}/${resolved.model}`; + const resolvedLabel = `${resolved.provider}/${resolved.model}`; + const defaultLabel = rawModel || resolvedLabel; const fallbacks = typeof modelConfig === "object" ? (modelConfig?.fallbacks ?? []) : []; const imageModel = @@ -635,113 +468,18 @@ export async function modelsStatusCommand( }, {}); const allowed = Object.keys(cfg.agent?.models ?? {}); - const agentDir = resolveClawdbotAgentDir(); - const store = ensureAuthProfileStore(); - const modelsPath = path.join(agentDir, "models.json"); - - const providersFromStore = new Set( - Object.values(store.profiles) - .map((profile) => profile.provider) - .filter((p): p is string => Boolean(p)), - ); - const providersFromConfig = new Set( - Object.keys(cfg.models?.providers ?? {}) - .map((p) => p.trim()) - .filter(Boolean), - ); - const providersFromModels = new Set(); - for (const raw of [ - defaultLabel, - ...fallbacks, - imageModel, - ...imageFallbacks, - ...allowed, - ]) { - const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER); - if (parsed?.provider) providersFromModels.add(parsed.provider); - } - - const providersFromEnv = new Set(); - // Keep in sync with resolveEnvApiKey() mappings (we want visibility even when - // a provider isn't currently selected in config/models). - const envProbeProviders = [ - "anthropic", - "github-copilot", - "google-vertex", - "openai", - "google", - "groq", - "cerebras", - "xai", - "openrouter", - "zai", - "mistral", - ]; - for (const provider of envProbeProviders) { - if (resolveEnvApiKey(provider)) providersFromEnv.add(provider); - } - - const providers = Array.from( - new Set([ - ...providersFromStore, - ...providersFromConfig, - ...providersFromModels, - ...providersFromEnv, - ]), - ) - .map((p) => p.trim()) - .filter(Boolean) - .sort((a, b) => a.localeCompare(b)); - - const applied = getShellEnvAppliedKeys(); - const shellFallbackEnabled = - shouldEnableShellEnvFallback(process.env) || - cfg.env?.shellEnv?.enabled === true; - - const providerAuth = providers - .map((provider) => - resolveProviderAuthOverview({ provider, cfg, store, modelsPath }), - ) - .filter((entry) => { - const hasAny = - entry.profiles.count > 0 || - Boolean(entry.env) || - Boolean(entry.modelsJson); - return hasAny; - }); - - const providersWithOauth = providerAuth - .filter( - (entry) => entry.profiles.oauth > 0 || entry.env?.value === "OAuth (env)", - ) - .map((entry) => { - const count = - entry.profiles.oauth || (entry.env?.value === "OAuth (env)" ? 1 : 0); - return `${entry.provider} (${count})`; - }); - if (opts.json) { runtime.log( JSON.stringify( { configPath: CONFIG_PATH_CLAWDBOT, - agentDir, - defaultModel: defaultLabel, + defaultModel: rawModel || resolvedLabel, resolvedDefault: `${resolved.provider}/${resolved.model}`, fallbacks, imageModel: imageModel || null, imageFallbacks, aliases, allowed, - auth: { - storePath: resolveAuthStorePathForDisplay(), - shellEnvFallback: { - enabled: shellFallbackEnabled, - appliedKeys: applied, - }, - providersWithOAuth: providersWithOauth, - providers: providerAuth, - }, }, null, 2, @@ -751,165 +489,37 @@ export async function modelsStatusCommand( } if (opts.plain) { - runtime.log(defaultLabel); + runtime.log(resolvedLabel); return; } - const rich = isRich(opts); - const label = (value: string) => colorize(rich, chalk.cyan, value.padEnd(14)); - + runtime.log(info(`Config: ${CONFIG_PATH_CLAWDBOT}`)); runtime.log( - `${label("Config")}${colorize(rich, chalk.gray, ":")} ${colorize(rich, chalk.white, CONFIG_PATH_CLAWDBOT)}`, - ); - runtime.log( - `${label("Agent dir")}${colorize(rich, chalk.gray, ":")} ${colorize( - rich, - chalk.white, - shortenHomePath(agentDir), - )}`, - ); - runtime.log( - `${label("Default")}${colorize(rich, chalk.gray, ":")} ${colorize( - rich, - chalk.green, - defaultLabel, - )}`, - ); - runtime.log( - `${label(`Fallbacks (${fallbacks.length || 0})`)}${colorize( - rich, - chalk.gray, - ":", - )} ${colorize( - rich, - fallbacks.length ? chalk.yellow : chalk.gray, - fallbacks.length ? fallbacks.join(", ") : "-", - )}`, - ); - runtime.log( - `${label("Image model")}${colorize(rich, chalk.gray, ":")} ${colorize( - rich, - imageModel ? chalk.magenta : chalk.gray, - imageModel || "-", - )}`, - ); - runtime.log( - `${label(`Image fallbacks (${imageFallbacks.length || 0})`)}${colorize( - rich, - chalk.gray, - ":", - )} ${colorize( - rich, - imageFallbacks.length ? chalk.magentaBright : chalk.gray, - imageFallbacks.length ? imageFallbacks.join(", ") : "-", - )}`, - ); - runtime.log( - `${label(`Aliases (${Object.keys(aliases).length || 0})`)}${colorize( - rich, - chalk.gray, - ":", - )} ${colorize( - rich, - Object.keys(aliases).length ? chalk.cyan : chalk.gray, - Object.keys(aliases).length - ? Object.entries(aliases) - .map(([alias, target]) => - rich - ? `${chalk.blue(alias)} ${chalk.gray("->")} ${chalk.white( - target, - )}` - : `${alias} -> ${target}`, - ) - .join(", ") - : "-", - )}`, - ); - runtime.log( - `${label(`Configured models (${allowed.length || 0})`)}${colorize( - rich, - chalk.gray, - ":", - )} ${colorize( - rich, - allowed.length ? chalk.white : chalk.gray, - allowed.length ? allowed.join(", ") : "all", - )}`, - ); - - runtime.log(""); - runtime.log(colorize(rich, chalk.bold, "Auth overview")); - runtime.log( - `${label("Auth store")}${colorize(rich, chalk.gray, ":")} ${colorize( - rich, - chalk.white, - shortenHomePath(resolveAuthStorePathForDisplay()), - )}`, - ); - runtime.log( - `${label("Shell env")}${colorize(rich, chalk.gray, ":")} ${colorize( - rich, - shellFallbackEnabled ? chalk.green : chalk.gray, - shellFallbackEnabled ? "on" : "off", - )}${ - applied.length - ? colorize(rich, chalk.gray, ` (applied: ${applied.join(", ")})`) - : "" + `Default: ${defaultLabel}${ + rawModel && rawModel !== resolvedLabel ? ` (from ${rawModel})` : "" }`, ); runtime.log( - `${label( - `Providers w/ OAuth (${providersWithOauth.length || 0})`, - )}${colorize(rich, chalk.gray, ":")} ${colorize( - rich, - providersWithOauth.length ? chalk.white : chalk.gray, - providersWithOauth.length ? providersWithOauth.join(", ") : "-", - )}`, + `Fallbacks (${fallbacks.length || 0}): ${fallbacks.join(", ") || "-"}`, + ); + runtime.log(`Image model: ${imageModel || "-"}`); + runtime.log( + `Image fallbacks (${imageFallbacks.length || 0}): ${ + imageFallbacks.length ? imageFallbacks.join(", ") : "-" + }`, + ); + runtime.log( + `Aliases (${Object.keys(aliases).length || 0}): ${ + Object.keys(aliases).length + ? Object.entries(aliases) + .map(([alias, target]) => `${alias} -> ${target}`) + .join(", ") + : "-" + }`, + ); + runtime.log( + `Configured models (${allowed.length || 0}): ${ + allowed.length ? allowed.join(", ") : "all" + }`, ); - - for (const entry of providerAuth) { - const separator = formatSeparator(rich); - const bits: string[] = []; - bits.push( - formatKeyValue( - "effective", - `${colorize(rich, chalk.magenta, entry.effective.kind)}:${colorize( - rich, - chalk.gray, - entry.effective.detail, - )}`, - rich, - (value) => value, - ), - ); - if (entry.profiles.count > 0) { - bits.push( - formatKeyValue( - "profiles", - `${entry.profiles.count} (oauth=${entry.profiles.oauth}, api_key=${entry.profiles.apiKey})`, - rich, - ), - ); - if (entry.profiles.labels.length > 0) { - bits.push(formatValue(entry.profiles.labels.join(", "), rich)); - } - } - if (entry.env) { - bits.push( - formatKeyValue( - "env", - `${entry.env.value} (${entry.env.source})`, - rich, - chalk.gray, - ), - ); - } - if (entry.modelsJson) { - bits.push( - formatKeyValue("models.json", entry.modelsJson.value, rich, chalk.gray), - ); - } - const providerLabel = colorize(rich, chalk.cyan, entry.provider); - runtime.log(`${providerLabel}: ${bits.join(separator)}`); - } }