From 7f3f73af1c49a134fe439c4a1d5a6bd711e40e42 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 5 Jan 2026 15:50:14 +0100 Subject: [PATCH] fix: show model auth in status --- CHANGELOG.md | 1 + src/auto-reply/reply/commands.ts | 59 ++++++++++++++++++++++++++++++++ src/auto-reply/status.ts | 3 ++ 3 files changed, 63 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cbf2ee5c..f2176cffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding. - 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 model auth source (api-key/oauth). ### Maintenance - Deps: bump pi-* stack, Slack SDK, discord-api-types, file-type, zod, and Biome. diff --git a/src/auto-reply/reply/commands.ts b/src/auto-reply/reply/commands.ts index 638f4d369..3b25d80e5 100644 --- a/src/auto-reply/reply/commands.ts +++ b/src/auto-reply/reply/commands.ts @@ -1,3 +1,5 @@ +import fs from "node:fs"; + import type { ClawdbotConfig } from "../../config/config.js"; import { type SessionEntry, @@ -10,6 +12,10 @@ import { resolveSendPolicy } from "../../sessions/send-policy.js"; import { normalizeE164 } from "../../utils.js"; import { resolveHeartbeatSeconds } from "../../web/reconnect.js"; import { getWebAuthAgeMs, webAuthExists } from "../../web/session.js"; +import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js"; +import { resolveOAuthPath } from "../../config/paths.js"; +import { getEnvApiKey } from "@mariozechner/pi-ai"; +import { discoverAuthStorage } from "@mariozechner/pi-coding-agent"; import { normalizeGroupActivation, parseActivationCommand, @@ -36,6 +42,58 @@ export type CommandContext = { to?: string; }; +function hasOAuthCredentials(provider: string): boolean { + try { + const oauthPath = resolveOAuthPath(); + if (!fs.existsSync(oauthPath)) return false; + const raw = fs.readFileSync(oauthPath, "utf8"); + const parsed = JSON.parse(raw) as Record; + const entry = parsed?.[provider] as + | { + refresh?: string; + refresh_token?: string; + refreshToken?: string; + access?: string; + access_token?: string; + accessToken?: string; + } + | undefined; + if (!entry) return false; + const refresh = + entry.refresh ?? entry.refresh_token ?? entry.refreshToken ?? ""; + const access = entry.access ?? entry.access_token ?? entry.accessToken ?? ""; + return Boolean(refresh.trim() && access.trim()); + } catch { + return false; + } +} + +function resolveModelAuthLabel(provider?: string): string | undefined { + const resolved = provider?.trim(); + if (!resolved) return undefined; + + try { + const authStorage = discoverAuthStorage(resolveClawdbotAgentDir()); + const stored = authStorage.get(resolved); + if (stored?.type === "oauth") return "oauth"; + if (stored?.type === "api_key") return "api-key"; + } catch { + // ignore auth storage errors + } + + if (resolved === "anthropic") { + const oauthEnv = process.env.ANTHROPIC_OAUTH_TOKEN; + if (oauthEnv?.trim()) return "oauth"; + } + + if (hasOAuthCredentials(resolved)) return "oauth"; + + const envKey = getEnvApiKey(resolved); + if (envKey?.trim()) return "api-key"; + + return "unknown"; +} + export function buildCommandContext(params: { ctx: MsgContext; cfg: ClawdbotConfig; @@ -314,6 +372,7 @@ export async function handleCommands(params: { resolvedThinkLevel ?? (await resolveDefaultThinkingLevel()), resolvedVerbose: resolvedVerboseLevel, resolvedElevated: resolvedElevatedLevel, + modelAuth: resolveModelAuthLabel(provider), webLinked, webAuthAgeMs, heartbeatSeconds, diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 32e8a4482..281f25824 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -35,6 +35,7 @@ type StatusArgs = { resolvedThink?: ThinkLevel; resolvedVerbose?: VerboseLevel; resolvedElevated?: ElevatedLevel; + modelAuth?: string; now?: number; webLinked?: boolean; webAuthAgeMs?: number | null; @@ -236,6 +237,7 @@ export function buildStatusMessage(args: StatusArgs): string { const modelLabel = model ? `${provider}/${model}` : "unknown"; const agentLine = `Agent: embedded pi • ${modelLabel}`; + const authLine = args.modelAuth ? `Model auth: ${args.modelAuth}` : undefined; const workspaceLine = args.workspaceDir ? `Workspace: ${shortenHomePath(args.workspaceDir)}` @@ -245,6 +247,7 @@ export function buildStatusMessage(args: StatusArgs): string { "⚙️ Status", webLine, agentLine, + authLine, runtime.line, workspaceLine, contextLine,