From e0754fdbb35908798729deea7b63ea9dfe345614 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 4 Mar 2026 20:20:54 +0100 Subject: [PATCH] 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 --- .husky/pre-commit | 21 +++- package.json | 3 +- packages/ai/CHANGELOG.md | 5 + packages/ai/README.md | 17 ++- packages/ai/oauth.d.ts | 1 + packages/ai/oauth.js | 1 + packages/ai/package.json | 12 ++ packages/ai/src/env-api-keys.ts | 16 ++- packages/ai/src/index.ts | 11 +- packages/ai/src/oauth.ts | 1 + .../src/providers/openai-codex-responses.ts | 17 ++- .../ai/src/providers/register-builtins.ts | 108 +++++++++++++++++- packages/ai/src/stream.ts | 1 - packages/ai/src/utils/http-proxy.ts | 13 --- packages/ai/src/utils/oauth/index.ts | 3 - packages/coding-agent/package.json | 1 + packages/coding-agent/src/cli.ts | 3 + .../coding-agent/src/core/auth-storage.ts | 4 +- .../coding-agent/src/core/model-registry.ts | 3 +- .../interactive/components/login-dialog.ts | 2 +- .../interactive/components/oauth-selector.ts | 3 +- .../src/modes/interactive/interactive-mode.ts | 17 +-- .../coding-agent/test/auth-storage.test.ts | 2 +- .../coding-agent/test/model-registry.test.ts | 3 +- packages/coding-agent/test/utilities.ts | 3 +- scripts/browser-smoke-entry.ts | 4 + 26 files changed, 216 insertions(+), 59 deletions(-) create mode 100644 packages/ai/oauth.d.ts create mode 100644 packages/ai/oauth.js create mode 100644 packages/ai/src/oauth.ts delete mode 100644 packages/ai/src/utils/http-proxy.ts create mode 100644 scripts/browser-smoke-entry.ts diff --git a/.husky/pre-commit b/.husky/pre-commit index ea358f72..27d18fbc 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -11,6 +11,25 @@ if [ $? -ne 0 ]; then exit 1 fi +RUN_BROWSER_SMOKE=0 +for file in $STAGED_FILES; do + case "$file" in + packages/ai/*|packages/web-ui/*|package.json|package-lock.json) + RUN_BROWSER_SMOKE=1 + break + ;; + esac +done + +if [ $RUN_BROWSER_SMOKE -eq 1 ]; then + echo "Running browser smoke check..." + npm run check:browser-smoke + if [ $? -ne 0 ]; then + echo "❌ Browser smoke check failed." + exit 1 + fi +fi + # Restage files that were previously staged and may have been modified by formatting for file in $STAGED_FILES; do if [ -f "$file" ]; then @@ -18,4 +37,4 @@ for file in $STAGED_FILES; do fi done -echo "✅ All pre-commit checks passed!" \ No newline at end of file +echo "✅ All pre-commit checks passed!" diff --git a/package.json b/package.json index c9071bbb..e37a3172 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "build": "cd packages/tui && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../coding-agent && npm run build && cd ../mom && npm run build && cd ../web-ui && npm run build && cd ../pods && npm run build", "dev": "concurrently --names \"ai,agent,coding-agent,mom,web-ui,tui\" --prefix-colors \"cyan,yellow,red,white,green,magenta\" \"cd packages/ai && npm run dev\" \"cd packages/agent && npm run dev\" \"cd packages/coding-agent && npm run dev\" \"cd packages/mom && npm run dev\" \"cd packages/web-ui && npm run dev\" \"cd packages/tui && npm run dev\"", "dev:tsc": "concurrently --names \"ai,web-ui\" --prefix-colors \"cyan,green\" \"cd packages/ai && npm run dev:tsc\" \"cd packages/web-ui && npm run dev:tsc\"", - "check": "biome check --write --error-on-warnings . && tsgo --noEmit && cd packages/web-ui && npm run check", + "check": "biome check --write --error-on-warnings . && tsgo --noEmit && npm run check:browser-smoke && cd packages/web-ui && npm run check", + "check:browser-smoke": "sh -c 'esbuild scripts/browser-smoke-entry.ts --bundle --platform=browser --format=esm --log-limit=0 --outfile=/tmp/pi-browser-smoke.js > /tmp/pi-browser-smoke-errors.log 2>&1 || { echo \"Browser smoke check failed. See /tmp/pi-browser-smoke-errors.log\"; exit 1; }'", "test": "npm run test --workspaces --if-present", "version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install", "version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install", diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index cc782746..8f7af8bc 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Breaking Changes + +- Moved Node OAuth runtime exports off the top-level package entry. Import OAuth login/refresh functions from `@mariozechner/pi-ai/oauth` instead of `@mariozechner/pi-ai` ([#1814](https://github.com/badlogic/pi-mono/issues/1814)) + ### Added - Added `gemini-3.1-flash-lite-preview` fallback model entry for the `google` provider so it remains selectable until upstream model catalogs include it ([#1785](https://github.com/badlogic/pi-mono/issues/1785), thanks [@n-WN](https://github.com/n-WN)). @@ -9,6 +13,7 @@ ### Fixed - Fixed Gemini 3.1 thinking-level detection in `google` and `google-vertex` providers so `gemini-3.1-*` models use Gemini 3 level-based thinking config instead of budget fallback ([#1785](https://github.com/badlogic/pi-mono/issues/1785), thanks [@n-WN](https://github.com/n-WN)). +- Fixed browser bundling failures by lazy-loading the Bedrock provider and removing Node-only side effects from the default browser import graph ([#1814](https://github.com/badlogic/pi-mono/issues/1814)) ## [0.55.4] - 2026-03-02 diff --git a/packages/ai/README.md b/packages/ai/README.md index 18b3f1c3..4efed089 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -33,6 +33,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an - [Cross-Provider Handoffs](#cross-provider-handoffs) - [Context Serialization](#context-serialization) - [Browser Usage](#browser-usage) + - [Browser Compatibility Notes](#browser-compatibility-notes) - [Environment Variables](#environment-variables-nodejs-only) - [Checking Environment Variables](#checking-environment-variables) - [OAuth Providers](#oauth-providers) @@ -888,6 +889,13 @@ const response = await complete(model, { > **Security Warning**: Exposing API keys in frontend code is dangerous. Anyone can extract and abuse your keys. Only use this approach for internal tools or demos. For production applications, use a backend proxy that keeps your API keys secure. +### Browser Compatibility Notes + +- Amazon Bedrock (`bedrock-converse-stream`) is not supported in browser environments. +- OAuth login flows are not supported in browser environments. Use the `@mariozechner/pi-ai/oauth` entry point in Node.js. +- In browser builds, Bedrock can still appear in model lists. Calls to Bedrock models fail at runtime. +- Use a server-side proxy or backend service if you need Bedrock or OAuth-based auth from a web app. + ### Environment Variables (Node.js only) In Node.js environments, you can set environment variables to avoid passing API keys: @@ -1018,7 +1026,7 @@ Credentials are saved to `auth.json` in the current directory. ### Programmatic OAuth -The library provides login and token refresh functions. Credential storage is the caller's responsibility. +The library provides login and token refresh functions via the `@mariozechner/pi-ai/oauth` entry point. Credential storage is the caller's responsibility. ```typescript import { @@ -1036,13 +1044,13 @@ import { // Types type OAuthProvider, // 'anthropic' | 'openai-codex' | 'github-copilot' | 'google-gemini-cli' | 'google-antigravity' type OAuthCredentials, -} from '@mariozechner/pi-ai'; +} from '@mariozechner/pi-ai/oauth'; ``` ### Login Flow Example ```typescript -import { loginGitHubCopilot } from '@mariozechner/pi-ai'; +import { loginGitHubCopilot } from '@mariozechner/pi-ai/oauth'; import { writeFileSync } from 'fs'; const credentials = await loginGitHubCopilot({ @@ -1066,7 +1074,8 @@ writeFileSync('auth.json', JSON.stringify(auth, null, 2)); Use `getOAuthApiKey()` to get an API key, automatically refreshing if expired: ```typescript -import { getModel, complete, getOAuthApiKey } from '@mariozechner/pi-ai'; +import { getModel, complete } from '@mariozechner/pi-ai'; +import { getOAuthApiKey } from '@mariozechner/pi-ai/oauth'; import { readFileSync, writeFileSync } from 'fs'; // Load your stored credentials diff --git a/packages/ai/oauth.d.ts b/packages/ai/oauth.d.ts new file mode 100644 index 00000000..1e5280f7 --- /dev/null +++ b/packages/ai/oauth.d.ts @@ -0,0 +1 @@ +export * from "./src/oauth.js"; diff --git a/packages/ai/oauth.js b/packages/ai/oauth.js new file mode 100644 index 00000000..49714498 --- /dev/null +++ b/packages/ai/oauth.js @@ -0,0 +1 @@ +export * from "./dist/oauth.js"; diff --git a/packages/ai/package.json b/packages/ai/package.json index 07e38fcd..9bd2721a 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -5,11 +5,23 @@ "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./oauth": { + "types": "./oauth.d.ts", + "import": "./oauth.js" + } + }, "bin": { "pi-ai": "./dist/cli.js" }, "files": [ "dist", + "oauth.js", + "oauth.d.ts", "README.md" ], "scripts": { diff --git a/packages/ai/src/env-api-keys.ts b/packages/ai/src/env-api-keys.ts index 51a6e3ac..3f24a9df 100644 --- a/packages/ai/src/env-api-keys.ts +++ b/packages/ai/src/env-api-keys.ts @@ -3,16 +3,20 @@ let _existsSync: typeof import("node:fs").existsSync | null = null; let _homedir: typeof import("node:os").homedir | null = null; let _join: typeof import("node:path").join | null = null; +type DynamicImport = (specifier: string) => Promise; + +const dynamicImport = new Function("specifier", "return import(specifier);") as DynamicImport; + // Eagerly load in Node.js/Bun environment only if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) { - import("node:fs").then((m) => { - _existsSync = m.existsSync; + dynamicImport("node:fs").then((m) => { + _existsSync = (m as typeof import("node:fs")).existsSync; }); - import("node:os").then((m) => { - _homedir = m.homedir; + dynamicImport("node:os").then((m) => { + _homedir = (m as typeof import("node:os")).homedir; }); - import("node:path").then((m) => { - _join = m.join; + dynamicImport("node:path").then((m) => { + _join = (m as typeof import("node:path")).join; }); } diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts index 48c74953..d1d1350e 100644 --- a/packages/ai/src/index.ts +++ b/packages/ai/src/index.ts @@ -16,7 +16,16 @@ export * from "./stream.js"; export * from "./types.js"; export * from "./utils/event-stream.js"; export * from "./utils/json-parse.js"; -export * from "./utils/oauth/index.js"; +export type { + OAuthAuthInfo, + OAuthCredentials, + OAuthLoginCallbacks, + OAuthPrompt, + OAuthProvider, + OAuthProviderId, + OAuthProviderInfo, + OAuthProviderInterface, +} from "./utils/oauth/types.js"; export * from "./utils/overflow.js"; export * from "./utils/typebox-helpers.js"; export * from "./utils/validation.js"; diff --git a/packages/ai/src/oauth.ts b/packages/ai/src/oauth.ts new file mode 100644 index 00000000..d768a0fe --- /dev/null +++ b/packages/ai/src/oauth.ts @@ -0,0 +1 @@ +export * from "./utils/oauth/index.js"; diff --git a/packages/ai/src/providers/openai-codex-responses.ts b/packages/ai/src/providers/openai-codex-responses.ts index f92c374d..81f23f03 100644 --- a/packages/ai/src/providers/openai-codex-responses.ts +++ b/packages/ai/src/providers/openai-codex-responses.ts @@ -1,12 +1,19 @@ -// NEVER convert to top-level import - breaks browser/Vite builds (web-ui) -let _os: typeof import("node:os") | null = null; +import type * as NodeOs from "node:os"; +import type { Tool as OpenAITool, ResponseInput, ResponseStreamEvent } from "openai/resources/responses/responses.js"; + +// NEVER convert to top-level runtime imports - breaks browser/Vite builds (web-ui) +let _os: typeof NodeOs | null = null; + +type DynamicImport = (specifier: string) => Promise; + +const dynamicImport = new Function("specifier", "return import(specifier);") as DynamicImport; + if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) { - import("node:os").then((m) => { - _os = m; + dynamicImport("node:os").then((m) => { + _os = m as typeof NodeOs; }); } -import type { Tool as OpenAITool, ResponseInput, ResponseStreamEvent } from "openai/resources/responses/responses.js"; import { getEnvApiKey } from "../env-api-keys.js"; import { supportsXhigh } from "../models.js"; import type { diff --git a/packages/ai/src/providers/register-builtins.ts b/packages/ai/src/providers/register-builtins.ts index 1645f21f..6c13095c 100644 --- a/packages/ai/src/providers/register-builtins.ts +++ b/packages/ai/src/providers/register-builtins.ts @@ -1,5 +1,13 @@ import { clearApiProviders, registerApiProvider } from "../api-registry.js"; -import { streamBedrock, streamSimpleBedrock } from "./amazon-bedrock.js"; +import type { + AssistantMessage, + AssistantMessageEvent, + Context, + Model, + SimpleStreamOptions, + StreamOptions, +} from "../types.js"; +import { AssistantMessageEventStream } from "../utils/event-stream.js"; import { streamAnthropic, streamSimpleAnthropic } from "./anthropic.js"; import { streamAzureOpenAIResponses, streamSimpleAzureOpenAIResponses } from "./azure-openai-responses.js"; import { streamGoogle, streamSimpleGoogle } from "./google.js"; @@ -9,6 +17,100 @@ import { streamOpenAICodexResponses, streamSimpleOpenAICodexResponses } from "./ import { streamOpenAICompletions, streamSimpleOpenAICompletions } from "./openai-completions.js"; import { streamOpenAIResponses, streamSimpleOpenAIResponses } from "./openai-responses.js"; +interface BedrockProviderModule { + streamBedrock: ( + model: Model<"bedrock-converse-stream">, + context: Context, + options?: StreamOptions, + ) => AssistantMessageEventStream; + streamSimpleBedrock: ( + model: Model<"bedrock-converse-stream">, + context: Context, + options?: SimpleStreamOptions, + ) => AssistantMessageEventStream; +} + +type DynamicImport = (specifier: string) => Promise; + +const dynamicImport = new Function("specifier", "return import(specifier);") as DynamicImport; + +async function loadBedrockProviderModule(): Promise { + const module = await dynamicImport("./amazon-bedrock.js"); + return module as BedrockProviderModule; +} + +function forwardStream(target: AssistantMessageEventStream, source: AssistantMessageEventStream): void { + (async () => { + for await (const event of source as AsyncIterable) { + target.push(event); + } + target.end(); + })(); +} + +function createLazyLoadErrorMessage(model: Model<"bedrock-converse-stream">, error: unknown): AssistantMessage { + return { + role: "assistant", + content: [], + api: "bedrock-converse-stream", + provider: model.provider, + model: model.id, + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "error", + errorMessage: error instanceof Error ? error.message : String(error), + timestamp: Date.now(), + }; +} + +function streamBedrockLazy( + model: Model<"bedrock-converse-stream">, + context: Context, + options?: StreamOptions, +): AssistantMessageEventStream { + const outer = new AssistantMessageEventStream(); + + loadBedrockProviderModule() + .then((module) => { + const inner = module.streamBedrock(model, context, options); + forwardStream(outer, inner); + }) + .catch((error) => { + const message = createLazyLoadErrorMessage(model, error); + outer.push({ type: "error", reason: "error", error: message }); + outer.end(message); + }); + + return outer; +} + +function streamSimpleBedrockLazy( + model: Model<"bedrock-converse-stream">, + context: Context, + options?: SimpleStreamOptions, +): AssistantMessageEventStream { + const outer = new AssistantMessageEventStream(); + + loadBedrockProviderModule() + .then((module) => { + const inner = module.streamSimpleBedrock(model, context, options); + forwardStream(outer, inner); + }) + .catch((error) => { + const message = createLazyLoadErrorMessage(model, error); + outer.push({ type: "error", reason: "error", error: message }); + outer.end(message); + }); + + return outer; +} + export function registerBuiltInApiProviders(): void { registerApiProvider({ api: "anthropic-messages", @@ -60,8 +162,8 @@ export function registerBuiltInApiProviders(): void { registerApiProvider({ api: "bedrock-converse-stream", - stream: streamBedrock, - streamSimple: streamSimpleBedrock, + stream: streamBedrockLazy, + streamSimple: streamSimpleBedrockLazy, }); } diff --git a/packages/ai/src/stream.ts b/packages/ai/src/stream.ts index fdb4494b..e8a2e50e 100644 --- a/packages/ai/src/stream.ts +++ b/packages/ai/src/stream.ts @@ -1,5 +1,4 @@ import "./providers/register-builtins.js"; -import "./utils/http-proxy.js"; import { getApiProvider } from "./api-registry.js"; import type { diff --git a/packages/ai/src/utils/http-proxy.ts b/packages/ai/src/utils/http-proxy.ts deleted file mode 100644 index 1102e875..00000000 --- a/packages/ai/src/utils/http-proxy.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Set up HTTP proxy according to env variables for `fetch` based SDKs in Node.js. - * Bun has builtin support for this. - * - * This module should be imported early by any code that needs proxy support for fetch(). - * ES modules are cached, so importing multiple times is safe - setup only runs once. - */ -if (typeof process !== "undefined" && process.versions?.node) { - import("undici").then((m) => { - const { EnvHttpProxyAgent, setGlobalDispatcher } = m; - setGlobalDispatcher(new EnvHttpProxyAgent()); - }); -} diff --git a/packages/ai/src/utils/oauth/index.ts b/packages/ai/src/utils/oauth/index.ts index 814d2601..256562a8 100644 --- a/packages/ai/src/utils/oauth/index.ts +++ b/packages/ai/src/utils/oauth/index.ts @@ -9,9 +9,6 @@ * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud) */ -// Set up HTTP proxy for fetch() calls (respects HTTP_PROXY, HTTPS_PROXY env vars) -import "../http-proxy.js"; - // Anthropic export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js"; // GitHub Copilot diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index 6415199a..928f389e 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -56,6 +56,7 @@ "minimatch": "^10.2.3", "proper-lockfile": "^4.1.2", "strip-ansi": "^7.1.0", + "undici": "^7.19.1", "yaml": "^2.8.2" }, "overrides": { diff --git a/packages/coding-agent/src/cli.ts b/packages/coding-agent/src/cli.ts index 87fa85bc..131fb6ad 100644 --- a/packages/coding-agent/src/cli.ts +++ b/packages/coding-agent/src/cli.ts @@ -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)); diff --git a/packages/coding-agent/src/core/auth-storage.ts b/packages/coding-agent/src/core/auth-storage.ts index 9724422b..324938d2 100644 --- a/packages/coding-agent/src/core/auth-storage.ts +++ b/packages/coding-agent/src/core/auth-storage.ts @@ -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"; diff --git a/packages/coding-agent/src/core/model-registry.ts b/packages/coding-agent/src/core/model-registry.ts index fe9d764b..94f80a0f 100644 --- a/packages/coding-agent/src/core/model-registry.ts +++ b/packages/coding-agent/src/core/model-registry.ts @@ -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"; diff --git a/packages/coding-agent/src/modes/interactive/components/login-dialog.ts b/packages/coding-agent/src/modes/interactive/components/login-dialog.ts index 0f875f7b..dceb4a5b 100644 --- a/packages/coding-agent/src/modes/interactive/components/login-dialog.ts +++ b/packages/coding-agent/src/modes/interactive/components/login-dialog.ts @@ -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"; diff --git a/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts b/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts index 640e38fe..64cd12d2 100644 --- a/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/oauth-selector.ts @@ -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"; diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index b8992628..50e7197d 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -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 { - 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); diff --git a/packages/coding-agent/test/auth-storage.test.ts b/packages/coding-agent/test/auth-storage.test.ts index a91102f6..09e66de5 100644 --- a/packages/coding-agent/test/auth-storage.test.ts +++ b/packages/coding-agent/test/auth-storage.test.ts @@ -1,7 +1,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { registerOAuthProvider } from "@mariozechner/pi-ai"; +import { registerOAuthProvider } from "@mariozechner/pi-ai/oauth"; import lockfile from "proper-lockfile"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { AuthStorage } from "../src/core/auth-storage.js"; diff --git a/packages/coding-agent/test/model-registry.test.ts b/packages/coding-agent/test/model-registry.test.ts index f1fd7bbd..42c07359 100644 --- a/packages/coding-agent/test/model-registry.test.ts +++ b/packages/coding-agent/test/model-registry.test.ts @@ -2,7 +2,8 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node import { tmpdir } from "node:os"; import { join } from "node:path"; import type { Api, Context, Model, OpenAICompletionsCompat } from "@mariozechner/pi-ai"; -import { getApiProvider, getOAuthProvider } from "@mariozechner/pi-ai"; +import { getApiProvider } from "@mariozechner/pi-ai"; +import { getOAuthProvider } from "@mariozechner/pi-ai/oauth"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import { AuthStorage } from "../src/core/auth-storage.js"; import { clearApiKeyCache, ModelRegistry } from "../src/core/model-registry.js"; diff --git a/packages/coding-agent/test/utilities.ts b/packages/coding-agent/test/utilities.ts index ff82173b..18e90f8c 100644 --- a/packages/coding-agent/test/utilities.ts +++ b/packages/coding-agent/test/utilities.ts @@ -6,7 +6,8 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } import { homedir, tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { Agent } from "@mariozechner/pi-agent-core"; -import { getModel, getOAuthApiKey, type OAuthCredentials, type OAuthProvider } from "@mariozechner/pi-ai"; +import { getModel, type OAuthCredentials, type OAuthProvider } from "@mariozechner/pi-ai"; +import { getOAuthApiKey } from "@mariozechner/pi-ai/oauth"; import { AgentSession } from "../src/core/agent-session.js"; import { AuthStorage } from "../src/core/auth-storage.js"; import { createExtensionRuntime } from "../src/core/extensions/loader.js"; diff --git a/scripts/browser-smoke-entry.ts b/scripts/browser-smoke-entry.ts new file mode 100644 index 00000000..3bd0dea3 --- /dev/null +++ b/scripts/browser-smoke-entry.ts @@ -0,0 +1,4 @@ +import { complete, getModel } from "@mariozechner/pi-ai"; + +const model = getModel("google", "gemini-2.5-flash"); +console.log(model.id, typeof complete);