From d2882c26433856731be9dc9bd01e4440051c14aa Mon Sep 17 00:00:00 2001 From: jhyang Date: Fri, 9 Jan 2026 15:31:24 +0800 Subject: [PATCH] Resolve os.homedir() lazily instead of at module load time - Move homedir() calls into functions for lazy evaluation - Add GOOGLE_APPLICATION_CREDENTIALS support for Vertex AI --- packages/ai/CHANGELOG.md | 8 +++++ packages/ai/README.md | 36 ++++++++++++++++++- .../providers/openai-codex/prompts/codex.ts | 3 +- packages/ai/src/stream.ts | 13 +++++-- 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 251e1aff..6d952dd8 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +### Added + +- Added `GOOGLE_APPLICATION_CREDENTIALS` env var support for Vertex AI credential detection (standard for CI/production). + +### Fixed + +- Fixed `os.homedir()` calls at module load time; now resolved lazily when needed. + ## [0.42.0] - 2026-01-09 ### Added diff --git a/packages/ai/README.md b/packages/ai/README.md index 2ce2158c..d121cbbb 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -897,7 +897,41 @@ Several providers require OAuth authentication instead of static API keys: ### Vertex AI (ADC) -Vertex AI models use Application Default Credentials. Run `gcloud auth application-default login`, set `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`), and `GOOGLE_CLOUD_LOCATION`. You can also pass `project`/`location` in the call options. +Vertex AI models use Application Default Credentials (ADC): + +- **Local development**: Run `gcloud auth application-default login` +- **CI/Production**: Set `GOOGLE_APPLICATION_CREDENTIALS` to point to a service account JSON key file + +Also set `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION`. You can also pass `project`/`location` in the call options. + +Example: + +```bash +# Local (uses your user credentials) +gcloud auth application-default login +export GOOGLE_CLOUD_PROJECT="my-project" +export GOOGLE_CLOUD_LOCATION="us-central1" + +# CI/Production (service account key file) +export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json" +``` + +```typescript +import { getModel, complete } from '@mariozechner/pi-ai'; + +(async () => { + const model = getModel('google-vertex', 'gemini-2.5-flash'); + const response = await complete(model, { + messages: [{ role: 'user', content: 'Hello from Vertex AI' }] + }); + + for (const block of response.content) { + if (block.type === 'text') console.log(block.text); + } +})().catch(console.error); +``` + +Official docs: [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) ### CLI Login diff --git a/packages/ai/src/providers/openai-codex/prompts/codex.ts b/packages/ai/src/providers/openai-codex/prompts/codex.ts index 869add2e..618b6d56 100644 --- a/packages/ai/src/providers/openai-codex/prompts/codex.ts +++ b/packages/ai/src/providers/openai-codex/prompts/codex.ts @@ -6,13 +6,12 @@ import { fileURLToPath } from "node:url"; const GITHUB_API_RELEASES = "https://api.github.com/repos/openai/codex/releases/latest"; const GITHUB_HTML_RELEASES = "https://github.com/openai/codex/releases/latest"; -const DEFAULT_AGENT_DIR = join(homedir(), ".pi", "agent"); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const FALLBACK_PROMPT_PATH = join(__dirname, "codex-instructions.md"); function getAgentDir(): string { - return process.env.PI_CODING_AGENT_DIR || DEFAULT_AGENT_DIR; + return process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent"); } function getCacheDir(): string { diff --git a/packages/ai/src/stream.ts b/packages/ai/src/stream.ts index 993919a3..d4fbbd35 100644 --- a/packages/ai/src/stream.ts +++ b/packages/ai/src/stream.ts @@ -26,13 +26,20 @@ import type { ThinkingLevel, } from "./types.js"; -const VERTEX_ADC_CREDENTIALS_PATH = join(homedir(), ".config", "gcloud", "application_default_credentials.json"); - let cachedVertexAdcCredentialsExists: boolean | null = null; function hasVertexAdcCredentials(): boolean { if (cachedVertexAdcCredentialsExists === null) { - cachedVertexAdcCredentialsExists = existsSync(VERTEX_ADC_CREDENTIALS_PATH); + // Check GOOGLE_APPLICATION_CREDENTIALS env var first (standard way) + const gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS; + if (gacPath) { + cachedVertexAdcCredentialsExists = existsSync(gacPath); + } else { + // Fall back to default ADC path (lazy evaluation) + cachedVertexAdcCredentialsExists = existsSync( + join(homedir(), ".config", "gcloud", "application_default_credentials.json"), + ); + } } return cachedVertexAdcCredentialsExists; }