From bffb760f4f1524b2131feb1499ee9245c17fdc22 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 24 Apr 2026 00:33:07 +0200 Subject: [PATCH] fix(coding-agent): improve auth setup warnings --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/src/cli/list-models.ts | 3 ++- .../coding-agent/src/core/agent-session.ts | 25 +++++++------------ .../coding-agent/src/core/auth-guidance.ts | 25 +++++++++++++++++++ packages/coding-agent/src/core/sdk.ts | 5 ++-- packages/coding-agent/src/main.ts | 8 +++--- 6 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 packages/coding-agent/src/core/auth-guidance.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index fa5de82c..85e63a7c 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -25,6 +25,7 @@ ### Fixed +- Improved no-model and missing-auth warnings to point users to `/login` for OAuth or API key setup. - Fixed `/quit` shutdown ordering to stop the TUI before extension UI teardown can repaint, preserving the final rendered frame while still emitting `session_shutdown` before process exit. - Fixed `SettingsManager.inMemory()` initial settings being lost after reloads triggered by SDK resource loading ([#3616](https://github.com/badlogic/pi-mono/issues/3616)) - Fixed `models.json` provider compatibility to accept `compat.supportsLongCacheRetention`, allowing proxies to opt out of long-retention cache fields when needed while long retention is enabled by default when requested ([#3543](https://github.com/badlogic/pi-mono/issues/3543)) diff --git a/packages/coding-agent/src/cli/list-models.ts b/packages/coding-agent/src/cli/list-models.ts index 88ab041b..6155b822 100644 --- a/packages/coding-agent/src/cli/list-models.ts +++ b/packages/coding-agent/src/cli/list-models.ts @@ -5,6 +5,7 @@ import type { Api, Model } from "@mariozechner/pi-ai"; import { fuzzyFilter } from "@mariozechner/pi-tui"; import chalk from "chalk"; +import { formatNoModelsAvailableMessage } from "../core/auth-guidance.js"; import type { ModelRegistry } from "../core/model-registry.js"; /** @@ -34,7 +35,7 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s const models = modelRegistry.getAvailable(); if (models.length === 0) { - console.log("No models available. Set API keys in environment variables."); + console.log(formatNoModelsAvailableMessage()); return; } diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index f303786a..098dbab7 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -14,7 +14,7 @@ */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { basename, dirname, join, resolve } from "node:path"; +import { basename, dirname, resolve } from "node:path"; import type { Agent, AgentEvent, @@ -25,10 +25,10 @@ import type { } from "@mariozechner/pi-agent-core"; import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@mariozechner/pi-ai"; import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } from "@mariozechner/pi-ai"; -import { getDocsPath } from "../config.js"; import { theme } from "../modes/interactive/theme/theme.js"; import { stripFrontmatter } from "../utils/frontmatter.js"; import { sleep } from "../utils/sleep.js"; +import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js"; import { type BashResult, executeBashWithOperations } from "./bash-executor.js"; import { type CompactionResult, @@ -341,6 +341,9 @@ export class AgentSession { }> { const result = await this._modelRegistry.getApiKeyAndHeaders(model); if (!result.ok) { + if (result.error.startsWith("No API key found")) { + throw new Error(formatNoApiKeyFoundMessage(model.provider)); + } throw new Error(result.error); } if (result.apiKey) { @@ -355,10 +358,7 @@ export class AgentSession { `Run '/login ${model.provider}' to re-authenticate.`, ); } - throw new Error( - `No API key found for ${model.provider}.\n\n` + - `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}`, - ); + throw new Error(formatNoApiKeyFoundMessage(model.provider)); } /** @@ -1002,11 +1002,7 @@ export class AgentSession { // Validate model if (!this.model) { - throw new Error( - "No model selected.\n\n" + - `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}\n\n` + - "Then use /model to select a model.", - ); + throw new Error(formatNoModelSelectedMessage()); } if (!this._modelRegistry.hasConfiguredAuth(this.model)) { @@ -1018,10 +1014,7 @@ export class AgentSession { `Run '/login ${this.model.provider}' to re-authenticate.`, ); } - throw new Error( - `No API key found for ${this.model.provider}.\n\n` + - `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}`, - ); + throw new Error(formatNoApiKeyFoundMessage(this.model.provider)); } // Check if we need to compact before sending (catches aborted responses) @@ -1610,7 +1603,7 @@ export class AgentSession { try { if (!this.model) { - throw new Error("No model selected"); + throw new Error(formatNoModelSelectedMessage()); } const { apiKey, headers } = await this._getRequiredRequestAuth(this.model); diff --git a/packages/coding-agent/src/core/auth-guidance.ts b/packages/coding-agent/src/core/auth-guidance.ts new file mode 100644 index 00000000..567b603d --- /dev/null +++ b/packages/coding-agent/src/core/auth-guidance.ts @@ -0,0 +1,25 @@ +import { join } from "node:path"; +import { getDocsPath } from "../config.js"; + +const UNKNOWN_PROVIDER = "unknown"; + +export function getProviderLoginHelp(): string { + return [ + "Use /login to log into a provider via OAuth or API key. See:", + ` ${join(getDocsPath(), "providers.md")}`, + ` ${join(getDocsPath(), "models.md")}`, + ].join("\n"); +} + +export function formatNoModelsAvailableMessage(): string { + return `No models available. ${getProviderLoginHelp()}`; +} + +export function formatNoModelSelectedMessage(): string { + return `No model selected.\n\n${getProviderLoginHelp()}\n\nThen use /model to select a model.`; +} + +export function formatNoApiKeyFoundMessage(provider: string): string { + const providerDisplay = provider === UNKNOWN_PROVIDER ? "the selected model" : provider; + return `No API key found for ${providerDisplay}.\n\n${getProviderLoginHelp()}`; +} diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index 58ce0af7..47ca4a2a 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -1,8 +1,9 @@ import { join } from "node:path"; import { Agent, type AgentMessage, type ThinkingLevel } from "@mariozechner/pi-agent-core"; import { type Message, type Model, streamSimple } from "@mariozechner/pi-ai"; -import { getAgentDir, getDocsPath } from "../config.js"; +import { getAgentDir } from "../config.js"; import { AgentSession } from "./agent-session.js"; +import { formatNoModelsAvailableMessage } from "./auth-guidance.js"; import { AuthStorage } from "./auth-storage.js"; import { DEFAULT_THINKING_LEVEL } from "./defaults.js"; import type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from "./extensions/index.js"; @@ -227,7 +228,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} }); model = result.model; if (!model) { - modelFallbackMessage = `No models available. Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}. Then use /model to select a model.`; + modelFallbackMessage = formatNoModelsAvailableMessage(); } else if (modelFallbackMessage) { modelFallbackMessage += `. Using ${model.provider}/${model.id}`; } diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 67893774..821f602c 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -15,13 +15,14 @@ import { processFileArguments } from "./cli/file-processor.js"; import { buildInitialMessage } from "./cli/initial-message.js"; import { listModels } from "./cli/list-models.js"; import { selectSession } from "./cli/session-picker.js"; -import { getAgentDir, getModelsPath, VERSION } from "./config.js"; +import { getAgentDir, VERSION } from "./config.js"; import { type CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from "./core/agent-session-runtime.js"; import { type AgentSessionRuntimeDiagnostic, createAgentSessionFromServices, createAgentSessionServices, } from "./core/agent-session-services.js"; +import { formatNoModelsAvailableMessage } from "./core/auth-guidance.js"; import { AuthStorage } from "./core/auth-storage.js"; import { exportFromFile } from "./core/export-html/index.js"; import type { ExtensionFactory } from "./core/extensions/types.js"; @@ -663,10 +664,7 @@ export async function main(args: string[], options?: MainOptions) { time("createAgentSession"); if (appMode !== "interactive" && !session.model) { - console.error(chalk.red("No models available.")); - console.error(chalk.yellow("\nSet an API key environment variable:")); - console.error(" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc."); - console.error(chalk.yellow(`\nOr create ${getModelsPath()}`)); + console.error(chalk.red(formatNoModelsAvailableMessage())); process.exit(1); }