fix(ai,coding-agent): make pi-ai browser-safe and move OAuth runtime exports

- add browser smoke bundling check to root check + pre-commit

- lazy-load Bedrock provider registration to avoid browser graph traversal

- remove top-level OAuth runtime exports from @mariozechner/pi-ai

- add @mariozechner/pi-ai/oauth subpath export and update coding-agent imports

- move proxy dispatcher init to coding-agent CLI (Node-only)

- document Bedrock/OAuth browser limitations

closes #1814
This commit is contained in:
Mario Zechner 2026-03-04 20:20:54 +01:00
parent 2af0c98b5f
commit e0754fdbb3
26 changed files with 216 additions and 59 deletions

View file

@ -7,6 +7,9 @@
*/
process.title = "pi";
import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
import { main } from "./main.js";
setGlobalDispatcher(new EnvHttpProxyAgent());
main(process.argv.slice(2));

View file

@ -8,13 +8,11 @@
import {
getEnvApiKey,
getOAuthApiKey,
getOAuthProvider,
getOAuthProviders,
type OAuthCredentials,
type OAuthLoginCallbacks,
type OAuthProviderId,
} from "@mariozechner/pi-ai";
import { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from "@mariozechner/pi-ai/oauth";
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import lockfile from "proper-lockfile";

View file

@ -14,11 +14,10 @@ import {
type OpenAICompletionsCompat,
type OpenAIResponsesCompat,
registerApiProvider,
registerOAuthProvider,
resetApiProviders,
resetOAuthProviders,
type SimpleStreamOptions,
} from "@mariozechner/pi-ai";
import { registerOAuthProvider, resetOAuthProviders } from "@mariozechner/pi-ai/oauth";
import { type Static, Type } from "@sinclair/typebox";
import AjvModule from "ajv";
import { existsSync, readFileSync } from "fs";

View file

@ -1,4 +1,4 @@
import { getOAuthProviders } from "@mariozechner/pi-ai";
import { getOAuthProviders } from "@mariozechner/pi-ai/oauth";
import { Container, type Focusable, getEditorKeybindings, Input, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
import { exec } from "child_process";
import { theme } from "../theme/theme.js";

View file

@ -1,4 +1,5 @@
import { getOAuthProviders, type OAuthProviderInterface } from "@mariozechner/pi-ai";
import type { OAuthProviderInterface } from "@mariozechner/pi-ai";
import { getOAuthProviders } from "@mariozechner/pi-ai/oauth";
import { Container, getEditorKeybindings, Spacer, TruncatedText } from "@mariozechner/pi-tui";
import type { AuthStorage } from "../../../core/auth-storage.js";
import { theme } from "../theme/theme.js";

View file

@ -8,14 +8,7 @@ import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import {
type AssistantMessage,
getOAuthProviders,
type ImageContent,
type Message,
type Model,
type OAuthProvider,
} from "@mariozechner/pi-ai";
import type { AssistantMessage, ImageContent, Message, Model, OAuthProviderId } from "@mariozechner/pi-ai";
import type {
AutocompleteItem,
EditorAction,
@ -3625,7 +3618,9 @@ export class InteractiveMode {
await this.showLoginDialog(providerId);
} else {
// Logout flow
const providerInfo = getOAuthProviders().find((p) => p.id === providerId);
const providerInfo = this.session.modelRegistry.authStorage
.getOAuthProviders()
.find((p) => p.id === providerId);
const providerName = providerInfo?.name || providerId;
try {
@ -3648,7 +3643,7 @@ export class InteractiveMode {
}
private async showLoginDialog(providerId: string): Promise<void> {
const providerInfo = getOAuthProviders().find((p) => p.id === providerId);
const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId);
const providerName = providerInfo?.name || providerId;
// Providers that use callback servers (can paste redirect URL)
@ -3682,7 +3677,7 @@ export class InteractiveMode {
};
try {
await this.session.modelRegistry.authStorage.login(providerId as OAuthProvider, {
await this.session.modelRegistry.authStorage.login(providerId as OAuthProviderId, {
onAuth: (info: { url: string; instructions?: string }) => {
dialog.showAuth(info.url, info.instructions);