fix: normalize provider aliases in auth order
This commit is contained in:
@@ -70,6 +70,7 @@
|
|||||||
- Gateway: add `gateway stop|restart` helpers and surface launchd/systemd/schtasks stop hints when the gateway is already running.
|
- Gateway: add `gateway stop|restart` helpers and surface launchd/systemd/schtasks stop hints when the gateway is already running.
|
||||||
- Gateway: honor `agent.timeoutSeconds` for `chat.send` and share timeout defaults across chat/cron/auto-reply. Thanks @MSch for PR #229.
|
- Gateway: honor `agent.timeoutSeconds` for `chat.send` and share timeout defaults across chat/cron/auto-reply. Thanks @MSch for PR #229.
|
||||||
- Auth: prioritize OAuth profiles but fall back to API keys when refresh fails; stored profiles now load without explicit auth order.
|
- Auth: prioritize OAuth profiles but fall back to API keys when refresh fails; stored profiles now load without explicit auth order.
|
||||||
|
- Auth/CLI: normalize provider ids and Z.AI aliases across auth profile ordering and models list/status. Thanks @mneves75 for PR #303.
|
||||||
- Control UI: harden config Form view with schema normalization, map editing, and guardrails to prevent data loss on save.
|
- Control UI: harden config Form view with schema normalization, map editing, and guardrails to prevent data loss on save.
|
||||||
- Cron: normalize cron.add/update inputs, align channel enums/status fields across gateway/CLI/UI/macOS, and add protocol conformance tests. Thanks @mneves75 for PR #256.
|
- Cron: normalize cron.add/update inputs, align channel enums/status fields across gateway/CLI/UI/macOS, and add protocol conformance tests. Thanks @mneves75 for PR #256.
|
||||||
- Docs: add group chat participation guidance to the AGENTS template.
|
- Docs: add group chat participation guidance to the AGENTS template.
|
||||||
|
|||||||
@@ -120,6 +120,37 @@ describe("resolveAuthProfileOrder", () => {
|
|||||||
expect(order).toEqual(["zai:work", "zai:default"]);
|
expect(order).toEqual(["zai:work", "zai:default"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes provider casing in auth.order keys", () => {
|
||||||
|
const order = resolveAuthProfileOrder({
|
||||||
|
cfg: {
|
||||||
|
auth: {
|
||||||
|
order: { OpenAI: ["openai:work", "openai:default"] },
|
||||||
|
profiles: {
|
||||||
|
"openai:default": { provider: "openai", mode: "api_key" },
|
||||||
|
"openai:work": { provider: "openai", mode: "api_key" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
store: {
|
||||||
|
version: 1,
|
||||||
|
profiles: {
|
||||||
|
"openai:default": {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "openai",
|
||||||
|
key: "sk-default",
|
||||||
|
},
|
||||||
|
"openai:work": {
|
||||||
|
type: "api_key",
|
||||||
|
provider: "openai",
|
||||||
|
key: "sk-work",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
provider: "openai",
|
||||||
|
});
|
||||||
|
expect(order).toEqual(["openai:work", "openai:default"]);
|
||||||
|
});
|
||||||
|
|
||||||
it("normalizes z.ai aliases in auth.profiles", () => {
|
it("normalizes z.ai aliases in auth.profiles", () => {
|
||||||
const order = resolveAuthProfileOrder({
|
const order = resolveAuthProfileOrder({
|
||||||
cfg: {
|
cfg: {
|
||||||
|
|||||||
@@ -542,12 +542,14 @@ export function resolveAuthProfileOrder(params: {
|
|||||||
}): string[] {
|
}): string[] {
|
||||||
const { cfg, store, provider, preferredProfile } = params;
|
const { cfg, store, provider, preferredProfile } = params;
|
||||||
const providerKey = normalizeProviderId(provider);
|
const providerKey = normalizeProviderId(provider);
|
||||||
const configuredOrder =
|
const configuredOrder = (() => {
|
||||||
cfg?.auth?.order?.[providerKey] ??
|
const order = cfg?.auth?.order;
|
||||||
cfg?.auth?.order?.[provider] ??
|
if (!order) return undefined;
|
||||||
(providerKey === "zai"
|
for (const [key, value] of Object.entries(order)) {
|
||||||
? (cfg?.auth?.order?.["z.ai"] ?? cfg?.auth?.order?.["z-ai"])
|
if (normalizeProviderId(key) === providerKey) return value;
|
||||||
: undefined);
|
}
|
||||||
|
return undefined;
|
||||||
|
})();
|
||||||
const explicitProfiles = cfg?.auth?.profiles
|
const explicitProfiles = cfg?.auth?.profiles
|
||||||
? Object.entries(cfg.auth.profiles)
|
? Object.entries(cfg.auth.profiles)
|
||||||
.filter(
|
.filter(
|
||||||
@@ -565,7 +567,7 @@ export function resolveAuthProfileOrder(params: {
|
|||||||
|
|
||||||
const filtered = baseOrder.filter((profileId) => {
|
const filtered = baseOrder.filter((profileId) => {
|
||||||
const cred = store.profiles[profileId];
|
const cred = store.profiles[profileId];
|
||||||
return cred ? cred.provider === provider : true;
|
return cred ? normalizeProviderId(cred.provider) === providerKey : true;
|
||||||
});
|
});
|
||||||
const deduped: string[] = [];
|
const deduped: string[] = [];
|
||||||
for (const entry of filtered) {
|
for (const entry of filtered) {
|
||||||
|
|||||||
@@ -3,8 +3,16 @@ import { describe, expect, it, vi } from "vitest";
|
|||||||
const loadConfig = vi.fn();
|
const loadConfig = vi.fn();
|
||||||
const ensureClawdbotModelsJson = vi.fn().mockResolvedValue(undefined);
|
const ensureClawdbotModelsJson = vi.fn().mockResolvedValue(undefined);
|
||||||
const resolveClawdbotAgentDir = vi.fn().mockReturnValue("/tmp/clawdbot-agent");
|
const resolveClawdbotAgentDir = vi.fn().mockReturnValue("/tmp/clawdbot-agent");
|
||||||
const ensureAuthProfileStore = vi.fn().mockReturnValue({});
|
const ensureAuthProfileStore = vi
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({ version: 1, profiles: {} });
|
||||||
const listProfilesForProvider = vi.fn().mockReturnValue([]);
|
const listProfilesForProvider = vi.fn().mockReturnValue([]);
|
||||||
|
const resolveAuthProfileDisplayLabel = vi.fn(
|
||||||
|
({ profileId }: { profileId: string }) => profileId,
|
||||||
|
);
|
||||||
|
const resolveAuthStorePathForDisplay = vi
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue("/tmp/clawdbot-agent/auth-profiles.json");
|
||||||
const resolveEnvApiKey = vi.fn().mockReturnValue(undefined);
|
const resolveEnvApiKey = vi.fn().mockReturnValue(undefined);
|
||||||
const getCustomProviderApiKey = vi.fn().mockReturnValue(undefined);
|
const getCustomProviderApiKey = vi.fn().mockReturnValue(undefined);
|
||||||
const discoverAuthStorage = vi.fn().mockReturnValue({});
|
const discoverAuthStorage = vi.fn().mockReturnValue({});
|
||||||
@@ -26,6 +34,8 @@ vi.mock("../agents/agent-paths.js", () => ({
|
|||||||
vi.mock("../agents/auth-profiles.js", () => ({
|
vi.mock("../agents/auth-profiles.js", () => ({
|
||||||
ensureAuthProfileStore,
|
ensureAuthProfileStore,
|
||||||
listProfilesForProvider,
|
listProfilesForProvider,
|
||||||
|
resolveAuthProfileDisplayLabel,
|
||||||
|
resolveAuthStorePathForDisplay,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("../agents/model-auth.js", () => ({
|
vi.mock("../agents/model-auth.js", () => ({
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
|
||||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||||
import {
|
import {
|
||||||
discoverAuthStorage,
|
discoverAuthStorage,
|
||||||
@@ -10,6 +12,8 @@ import {
|
|||||||
type AuthProfileStore,
|
type AuthProfileStore,
|
||||||
ensureAuthProfileStore,
|
ensureAuthProfileStore,
|
||||||
listProfilesForProvider,
|
listProfilesForProvider,
|
||||||
|
resolveAuthProfileDisplayLabel,
|
||||||
|
resolveAuthStorePathForDisplay,
|
||||||
} from "../../agents/auth-profiles.js";
|
} from "../../agents/auth-profiles.js";
|
||||||
import {
|
import {
|
||||||
getCustomProviderApiKey,
|
getCustomProviderApiKey,
|
||||||
@@ -27,8 +31,12 @@ import {
|
|||||||
CONFIG_PATH_CLAWDBOT,
|
CONFIG_PATH_CLAWDBOT,
|
||||||
loadConfig,
|
loadConfig,
|
||||||
} from "../../config/config.js";
|
} from "../../config/config.js";
|
||||||
import { info } from "../../globals.js";
|
import {
|
||||||
|
getShellEnvAppliedKeys,
|
||||||
|
shouldEnableShellEnvFallback,
|
||||||
|
} from "../../infra/shell-env.js";
|
||||||
import type { RuntimeEnv } from "../../runtime.js";
|
import type { RuntimeEnv } from "../../runtime.js";
|
||||||
|
import { shortenHomePath } from "../../utils.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_MODEL,
|
DEFAULT_MODEL,
|
||||||
DEFAULT_PROVIDER,
|
DEFAULT_PROVIDER,
|
||||||
@@ -50,12 +58,52 @@ const isRich = (opts?: { json?: boolean; plain?: boolean }) =>
|
|||||||
|
|
||||||
const pad = (value: string, size: number) => value.padEnd(size);
|
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) => {
|
const truncate = (value: string, max: number) => {
|
||||||
if (value.length <= max) return value;
|
if (value.length <= max) return value;
|
||||||
if (max <= 3) return value.slice(0, max);
|
if (max <= 3) return value.slice(0, max);
|
||||||
return `${value.slice(0, max - 3)}...`;
|
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 = {
|
type ConfiguredEntry = {
|
||||||
key: string;
|
key: string;
|
||||||
ref: { provider: string; model: string };
|
ref: { provider: string; model: string };
|
||||||
@@ -101,6 +149,109 @@ const hasAuthForProvider = (
|
|||||||
return false;
|
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 resolveConfiguredEntries = (cfg: ClawdbotConfig) => {
|
||||||
const resolvedDefault = resolveConfiguredModelRef({
|
const resolvedDefault = resolveConfiguredModelRef({
|
||||||
cfg,
|
cfg,
|
||||||
@@ -305,23 +456,45 @@ function printModelTable(
|
|||||||
const keyLabel = pad(truncate(row.key, MODEL_PAD), MODEL_PAD);
|
const keyLabel = pad(truncate(row.key, MODEL_PAD), MODEL_PAD);
|
||||||
const inputLabel = pad(row.input || "-", INPUT_PAD);
|
const inputLabel = pad(row.input || "-", INPUT_PAD);
|
||||||
const ctxLabel = pad(formatTokenK(row.contextWindow), CTX_PAD);
|
const ctxLabel = pad(formatTokenK(row.contextWindow), CTX_PAD);
|
||||||
const localLabel = pad(
|
const localText = row.local === null ? "-" : row.local ? "yes" : "no";
|
||||||
row.local === null ? "-" : row.local ? "yes" : "no",
|
const localLabel = pad(localText, LOCAL_PAD);
|
||||||
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 authLabel = pad(
|
const coloredLocal = colorize(
|
||||||
row.available === null ? "-" : row.available ? "yes" : "no",
|
rich,
|
||||||
AUTH_PAD,
|
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 tagsLabel = row.tags.length > 0 ? row.tags.join(",") : "";
|
|
||||||
|
|
||||||
const line = [
|
const line = [
|
||||||
rich ? chalk.cyan(keyLabel) : keyLabel,
|
rich ? chalk.cyan(keyLabel) : keyLabel,
|
||||||
inputLabel,
|
coloredInput,
|
||||||
ctxLabel,
|
ctxLabel,
|
||||||
localLabel,
|
coloredLocal,
|
||||||
authLabel,
|
coloredAuth,
|
||||||
rich ? chalk.gray(tagsLabel) : tagsLabel,
|
tagsLabel,
|
||||||
].join(" ");
|
].join(" ");
|
||||||
runtime.log(line);
|
runtime.log(line);
|
||||||
}
|
}
|
||||||
@@ -468,18 +641,113 @@ export async function modelsStatusCommand(
|
|||||||
}, {});
|
}, {});
|
||||||
const allowed = Object.keys(cfg.agent?.models ?? {});
|
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) {
|
if (opts.json) {
|
||||||
runtime.log(
|
runtime.log(
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
{
|
||||||
configPath: CONFIG_PATH_CLAWDBOT,
|
configPath: CONFIG_PATH_CLAWDBOT,
|
||||||
defaultModel: rawModel || resolvedLabel,
|
agentDir,
|
||||||
resolvedDefault: `${resolved.provider}/${resolved.model}`,
|
defaultModel: defaultLabel,
|
||||||
|
resolvedDefault: resolvedLabel,
|
||||||
fallbacks,
|
fallbacks,
|
||||||
imageModel: imageModel || null,
|
imageModel: imageModel || null,
|
||||||
imageFallbacks,
|
imageFallbacks,
|
||||||
aliases,
|
aliases,
|
||||||
allowed,
|
allowed,
|
||||||
|
auth: {
|
||||||
|
storePath: resolveAuthStorePathForDisplay(),
|
||||||
|
shellEnvFallback: {
|
||||||
|
enabled: shellFallbackEnabled,
|
||||||
|
appliedKeys: applied,
|
||||||
|
},
|
||||||
|
providersWithOAuth: providersWithOauth,
|
||||||
|
providers: providerAuth,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
@@ -493,33 +761,175 @@ export async function modelsStatusCommand(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime.log(info(`Config: ${CONFIG_PATH_CLAWDBOT}`));
|
const rich = isRich(opts);
|
||||||
|
const label = (value: string) => colorize(rich, chalk.cyan, value.padEnd(14));
|
||||||
|
const displayDefault =
|
||||||
|
rawModel && rawModel !== resolvedLabel
|
||||||
|
? `${resolvedLabel} (from ${rawModel})`
|
||||||
|
: resolvedLabel;
|
||||||
|
|
||||||
runtime.log(
|
runtime.log(
|
||||||
`Default: ${defaultLabel}${
|
`${label("Config")}${colorize(rich, chalk.gray, ":")} ${colorize(rich, chalk.white, CONFIG_PATH_CLAWDBOT)}`,
|
||||||
rawModel && rawModel !== resolvedLabel ? ` (from ${rawModel})` : ""
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
runtime.log(
|
runtime.log(
|
||||||
`Fallbacks (${fallbacks.length || 0}): ${fallbacks.join(", ") || "-"}`,
|
`${label("Agent dir")}${colorize(rich, chalk.gray, ":")} ${colorize(
|
||||||
);
|
rich,
|
||||||
runtime.log(`Image model: ${imageModel || "-"}`);
|
chalk.white,
|
||||||
runtime.log(
|
shortenHomePath(agentDir),
|
||||||
`Image fallbacks (${imageFallbacks.length || 0}): ${
|
)}`,
|
||||||
imageFallbacks.length ? imageFallbacks.join(", ") : "-"
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
runtime.log(
|
runtime.log(
|
||||||
`Aliases (${Object.keys(aliases).length || 0}): ${
|
`${label("Default")}${colorize(rich, chalk.gray, ":")} ${colorize(
|
||||||
|
rich,
|
||||||
|
chalk.green,
|
||||||
|
displayDefault,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
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.keys(aliases).length
|
||||||
? Object.entries(aliases)
|
? Object.entries(aliases)
|
||||||
.map(([alias, target]) => `${alias} -> ${target}`)
|
.map(([alias, target]) =>
|
||||||
|
rich
|
||||||
|
? `${chalk.blue(alias)} ${chalk.gray("->")} ${chalk.white(
|
||||||
|
target,
|
||||||
|
)}`
|
||||||
|
: `${alias} -> ${target}`,
|
||||||
|
)
|
||||||
.join(", ")
|
.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(", ")})`)
|
||||||
|
: ""
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
runtime.log(
|
runtime.log(
|
||||||
`Configured models (${allowed.length || 0}): ${
|
`${label(
|
||||||
allowed.length ? allowed.join(", ") : "all"
|
`Providers w/ OAuth (${providersWithOauth.length || 0})`,
|
||||||
}`,
|
)}${colorize(rich, chalk.gray, ":")} ${colorize(
|
||||||
|
rich,
|
||||||
|
providersWithOauth.length ? chalk.white : chalk.gray,
|
||||||
|
providersWithOauth.length ? providersWithOauth.join(", ") : "-",
|
||||||
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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}${separator}${formatKeyValue(
|
||||||
|
"source",
|
||||||
|
entry.env.source,
|
||||||
|
rich,
|
||||||
|
)}`,
|
||||||
|
rich,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (entry.modelsJson) {
|
||||||
|
bits.push(
|
||||||
|
formatKeyValue(
|
||||||
|
"models.json",
|
||||||
|
`${entry.modelsJson.value}${separator}${formatKeyValue(
|
||||||
|
"source",
|
||||||
|
entry.modelsJson.source,
|
||||||
|
rich,
|
||||||
|
)}`,
|
||||||
|
rich,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
runtime.log(`- ${chalk.bold(entry.provider)} ${bits.join(separator)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user