fix: show auth in /model list

This commit is contained in:
Peter Steinberger
2026-01-05 13:49:25 +00:00
parent d9103b387a
commit 5163886694
4 changed files with 62 additions and 2 deletions

View File

@@ -12,6 +12,7 @@
- macOS: Connections settings now use a custom sidebar to avoid toolbar toggle issues, with rounded styling and full-width row hit targets. - macOS: Connections settings now use a custom sidebar to avoid toolbar toggle issues, with rounded styling and full-width row hit targets.
- macOS: drop deprecated `afterMs` from agent wait params to match gateway schema. - macOS: drop deprecated `afterMs` from agent wait params to match gateway schema.
- Auth: add OpenAI Codex OAuth support and migrate legacy oauth.json into auth.json. - Auth: add OpenAI Codex OAuth support and migrate legacy oauth.json into auth.json.
- Model: `/model` list shows auth source (masked key or OAuth email) per provider.
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding. - Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.
- Sandbox: copy inbound media into sandbox workspaces so agent tools can read attachments. - Sandbox: copy inbound media into sandbox workspaces so agent tools can read attachments.
- Status: show runtime (docker/direct) and move shortcuts to `/help`. - Status: show runtime (docker/direct) and move shortcuts to `/help`.

View File

@@ -124,6 +124,13 @@ function migrateOAuthStorageToAuthStorage(
} }
} }
export function hydrateAuthStorage(
authStorage: ReturnType<typeof discoverAuthStorage>,
): void {
ensureOAuthStorage();
migrateOAuthStorageToAuthStorage(authStorage);
}
function isOAuthProvider(provider: string): provider is OAuthProvider { function isOAuthProvider(provider: string): provider is OAuthProvider {
return ( return (
provider === "anthropic" || provider === "anthropic" ||

View File

@@ -578,6 +578,7 @@ describe("directive parsing", () => {
expect(text).toContain("anthropic/claude-opus-4-5"); expect(text).toContain("anthropic/claude-opus-4-5");
expect(text).toContain("openai/gpt-4.1-mini"); expect(text).toContain("openai/gpt-4.1-mini");
expect(text).not.toContain("claude-sonnet-4-1"); expect(text).not.toContain("claude-sonnet-4-1");
expect(text).toContain("auth:");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });
@@ -604,6 +605,7 @@ describe("directive parsing", () => {
expect(text).toContain("anthropic/claude-opus-4-5"); expect(text).toContain("anthropic/claude-opus-4-5");
expect(text).toContain("openai/gpt-4.1-mini"); expect(text).toContain("openai/gpt-4.1-mini");
expect(text).not.toContain("claude-sonnet-4-1"); expect(text).not.toContain("claude-sonnet-4-1");
expect(text).toContain("auth:");
expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
}); });
}); });

View File

@@ -1,9 +1,13 @@
import { getEnvApiKey } from "@mariozechner/pi-ai";
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
import { lookupContextTokens } from "../../agents/context.js"; import { lookupContextTokens } from "../../agents/context.js";
import { import {
DEFAULT_CONTEXT_TOKENS, DEFAULT_CONTEXT_TOKENS,
DEFAULT_MODEL, DEFAULT_MODEL,
DEFAULT_PROVIDER, DEFAULT_PROVIDER,
} from "../../agents/defaults.js"; } from "../../agents/defaults.js";
import { hydrateAuthStorage } from "../../agents/model-auth.js";
import { import {
buildModelAliasIndex, buildModelAliasIndex,
type ModelAliasIndex, type ModelAliasIndex,
@@ -39,6 +43,40 @@ import {
const SYSTEM_MARK = "⚙️"; const SYSTEM_MARK = "⚙️";
const maskApiKey = (value: string): string => {
const trimmed = value.trim();
if (!trimmed) return "missing";
if (trimmed.length <= 12) return trimmed;
return `${trimmed.slice(0, 6)}...${trimmed.slice(-6)}`;
};
const resolveAuthLabel = async (
provider: string,
authStorage: ReturnType<typeof discoverAuthStorage>,
): Promise<string> => {
const stored = authStorage.get(provider);
if (stored?.type === "oauth") {
const email = stored.email?.trim();
return email ? `OAuth ${email}` : "OAuth (unknown)";
}
if (stored?.type === "api_key") {
return maskApiKey(stored.key);
}
const envKey = getEnvApiKey(provider);
if (envKey) return maskApiKey(envKey);
if (provider === "anthropic") {
const oauthEnv = process.env.ANTHROPIC_OAUTH_TOKEN?.trim();
if (oauthEnv) return "OAuth (env)";
}
try {
const key = await authStorage.getApiKey(provider);
if (key) return maskApiKey(key);
} catch {
// ignore missing auth
}
return "missing";
};
export type InlineDirectives = { export type InlineDirectives = {
cleaned: string; cleaned: string;
hasThinkDirective: boolean; hasThinkDirective: boolean;
@@ -202,6 +240,16 @@ export async function handleDirectiveOnly(params: {
if (allowedModelCatalog.length === 0) { if (allowedModelCatalog.length === 0) {
return { text: "No models available." }; return { text: "No models available." };
} }
const authStorage = discoverAuthStorage(resolveClawdbotAgentDir());
hydrateAuthStorage(authStorage);
const authByProvider = new Map<string, string>();
for (const entry of allowedModelCatalog) {
if (authByProvider.has(entry.provider)) continue;
authByProvider.set(
entry.provider,
await resolveAuthLabel(entry.provider, authStorage),
);
}
const current = `${params.provider}/${params.model}`; const current = `${params.provider}/${params.model}`;
const defaultLabel = `${defaultProvider}/${defaultModel}`; const defaultLabel = `${defaultProvider}/${defaultModel}`;
const header = const header =
@@ -219,9 +267,11 @@ export async function handleDirectiveOnly(params: {
aliases && aliases.length > 0 aliases && aliases.length > 0
? ` (alias: ${aliases.join(", ")})` ? ` (alias: ${aliases.join(", ")})`
: ""; : "";
const suffix = const nameSuffix =
entry.name && entry.name !== entry.id ? `${entry.name}` : ""; entry.name && entry.name !== entry.id ? `${entry.name}` : "";
lines.push(`- ${label}${aliasSuffix}${suffix}`); const authLabel = authByProvider.get(entry.provider) ?? "missing";
const authSuffix = ` — auth: ${authLabel}`;
lines.push(`- ${label}${aliasSuffix}${nameSuffix}${authSuffix}`);
} }
return { text: lines.join("\n") }; return { text: lines.join("\n") };
} }