fix: show auth in /model list
This commit is contained in:
@@ -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`.
|
||||||
|
|||||||
@@ -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" ||
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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") };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user