Feat: normalize z.ai provider ids

This commit is contained in:
mneves75
2026-01-06 11:35:23 -03:00
committed by Peter Steinberger
parent 388796253a
commit 0ddfbf5534
2 changed files with 55 additions and 438 deletions

View File

@@ -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 };

View File

@@ -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<Api>[] = [];
let availableKeys: Set<string> | 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<string>();
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<string>();
// 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)}`);
}
}