fix(coding-agent): improve auth setup warnings
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### 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 `/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 `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))
|
- 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))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||||
import { fuzzyFilter } from "@mariozechner/pi-tui";
|
import { fuzzyFilter } from "@mariozechner/pi-tui";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
import { formatNoModelsAvailableMessage } from "../core/auth-guidance.js";
|
||||||
import type { ModelRegistry } from "../core/model-registry.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();
|
const models = modelRegistry.getAvailable();
|
||||||
|
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
console.log("No models available. Set API keys in environment variables.");
|
console.log(formatNoModelsAvailableMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
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 {
|
import type {
|
||||||
Agent,
|
Agent,
|
||||||
AgentEvent,
|
AgentEvent,
|
||||||
@@ -25,10 +25,10 @@ import type {
|
|||||||
} from "@mariozechner/pi-agent-core";
|
} from "@mariozechner/pi-agent-core";
|
||||||
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@mariozechner/pi-ai";
|
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@mariozechner/pi-ai";
|
||||||
import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } 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 { theme } from "../modes/interactive/theme/theme.js";
|
||||||
import { stripFrontmatter } from "../utils/frontmatter.js";
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
||||||
import { sleep } from "../utils/sleep.js";
|
import { sleep } from "../utils/sleep.js";
|
||||||
|
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
||||||
import { type BashResult, executeBashWithOperations } from "./bash-executor.js";
|
import { type BashResult, executeBashWithOperations } from "./bash-executor.js";
|
||||||
import {
|
import {
|
||||||
type CompactionResult,
|
type CompactionResult,
|
||||||
@@ -341,6 +341,9 @@ export class AgentSession {
|
|||||||
}> {
|
}> {
|
||||||
const result = await this._modelRegistry.getApiKeyAndHeaders(model);
|
const result = await this._modelRegistry.getApiKeyAndHeaders(model);
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
|
if (result.error.startsWith("No API key found")) {
|
||||||
|
throw new Error(formatNoApiKeyFoundMessage(model.provider));
|
||||||
|
}
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
}
|
}
|
||||||
if (result.apiKey) {
|
if (result.apiKey) {
|
||||||
@@ -355,10 +358,7 @@ export class AgentSession {
|
|||||||
`Run '/login ${model.provider}' to re-authenticate.`,
|
`Run '/login ${model.provider}' to re-authenticate.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(formatNoApiKeyFoundMessage(model.provider));
|
||||||
`No API key found for ${model.provider}.\n\n` +
|
|
||||||
`Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1002,11 +1002,7 @@ export class AgentSession {
|
|||||||
|
|
||||||
// Validate model
|
// Validate model
|
||||||
if (!this.model) {
|
if (!this.model) {
|
||||||
throw new Error(
|
throw new Error(formatNoModelSelectedMessage());
|
||||||
"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.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._modelRegistry.hasConfiguredAuth(this.model)) {
|
if (!this._modelRegistry.hasConfiguredAuth(this.model)) {
|
||||||
@@ -1018,10 +1014,7 @@ export class AgentSession {
|
|||||||
`Run '/login ${this.model.provider}' to re-authenticate.`,
|
`Run '/login ${this.model.provider}' to re-authenticate.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
|
||||||
`No API key found for ${this.model.provider}.\n\n` +
|
|
||||||
`Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we need to compact before sending (catches aborted responses)
|
// Check if we need to compact before sending (catches aborted responses)
|
||||||
@@ -1610,7 +1603,7 @@ export class AgentSession {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.model) {
|
if (!this.model) {
|
||||||
throw new Error("No model selected");
|
throw new Error(formatNoModelSelectedMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
const { apiKey, headers } = await this._getRequiredRequestAuth(this.model);
|
const { apiKey, headers } = await this._getRequiredRequestAuth(this.model);
|
||||||
|
|||||||
25
packages/coding-agent/src/core/auth-guidance.ts
Normal file
25
packages/coding-agent/src/core/auth-guidance.ts
Normal file
@@ -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()}`;
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { Agent, type AgentMessage, type ThinkingLevel } from "@mariozechner/pi-agent-core";
|
import { Agent, type AgentMessage, type ThinkingLevel } from "@mariozechner/pi-agent-core";
|
||||||
import { type Message, type Model, streamSimple } from "@mariozechner/pi-ai";
|
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 { AgentSession } from "./agent-session.js";
|
||||||
|
import { formatNoModelsAvailableMessage } from "./auth-guidance.js";
|
||||||
import { AuthStorage } from "./auth-storage.js";
|
import { AuthStorage } from "./auth-storage.js";
|
||||||
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
||||||
import type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from "./extensions/index.js";
|
import type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from "./extensions/index.js";
|
||||||
@@ -227,7 +228,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||||||
});
|
});
|
||||||
model = result.model;
|
model = result.model;
|
||||||
if (!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) {
|
} else if (modelFallbackMessage) {
|
||||||
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ import { processFileArguments } from "./cli/file-processor.js";
|
|||||||
import { buildInitialMessage } from "./cli/initial-message.js";
|
import { buildInitialMessage } from "./cli/initial-message.js";
|
||||||
import { listModels } from "./cli/list-models.js";
|
import { listModels } from "./cli/list-models.js";
|
||||||
import { selectSession } from "./cli/session-picker.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 CreateAgentSessionRuntimeFactory, createAgentSessionRuntime } from "./core/agent-session-runtime.js";
|
||||||
import {
|
import {
|
||||||
type AgentSessionRuntimeDiagnostic,
|
type AgentSessionRuntimeDiagnostic,
|
||||||
createAgentSessionFromServices,
|
createAgentSessionFromServices,
|
||||||
createAgentSessionServices,
|
createAgentSessionServices,
|
||||||
} from "./core/agent-session-services.js";
|
} from "./core/agent-session-services.js";
|
||||||
|
import { formatNoModelsAvailableMessage } from "./core/auth-guidance.js";
|
||||||
import { AuthStorage } from "./core/auth-storage.js";
|
import { AuthStorage } from "./core/auth-storage.js";
|
||||||
import { exportFromFile } from "./core/export-html/index.js";
|
import { exportFromFile } from "./core/export-html/index.js";
|
||||||
import type { ExtensionFactory } from "./core/extensions/types.js";
|
import type { ExtensionFactory } from "./core/extensions/types.js";
|
||||||
@@ -663,10 +664,7 @@ export async function main(args: string[], options?: MainOptions) {
|
|||||||
time("createAgentSession");
|
time("createAgentSession");
|
||||||
|
|
||||||
if (appMode !== "interactive" && !session.model) {
|
if (appMode !== "interactive" && !session.model) {
|
||||||
console.error(chalk.red("No models available."));
|
console.error(chalk.red(formatNoModelsAvailableMessage()));
|
||||||
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()}`));
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user