mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 07:04:25 +00:00
`hasVertexAdcCredentials()` uses dynamic imports to load `node:fs`, `node:os`, and `node:path` to avoid breaking browser/Vite builds. These imports are fired eagerly but resolve asynchronously. If the function is called during gateway startup before those promises resolve, `_existsSync`, `_homedir`, and `_join` are still null — causing the function to cache `false` permanently and never re-evaluate. This means users with valid `GOOGLE_APPLICATION_CREDENTIALS`, `GOOGLE_CLOUD_PROJECT`, and `GOOGLE_CLOUD_LOCATION` configured are silently treated as unauthenticated for Vertex AI. Calls fall back to the AI Studio endpoint (generativelanguage.googleapis.com) which has much stricter rate limits, causing unexpected 429 errors even though Vertex credentials are correctly configured. Fix: in Node.js/Bun environments, return false without caching when the async modules aren't loaded yet, so the next call retries. Only cache false permanently in browser environments where `fs` is genuinely unavailable. Co-authored-by: Jeremiah Gaylord <jeremiahgaylord-web@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)
|
|
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;
|
|
|
|
// 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;
|
|
});
|
|
import("node:os").then((m) => {
|
|
_homedir = m.homedir;
|
|
});
|
|
import("node:path").then((m) => {
|
|
_join = m.join;
|
|
});
|
|
}
|
|
|
|
import type { KnownProvider } from "./types.js";
|
|
|
|
let cachedVertexAdcCredentialsExists: boolean | null = null;
|
|
|
|
function hasVertexAdcCredentials(): boolean {
|
|
if (cachedVertexAdcCredentialsExists === null) {
|
|
// If node modules haven't loaded yet (async import race at startup),
|
|
// return false WITHOUT caching so the next call retries once they're ready.
|
|
// Only cache false permanently in a browser environment where fs is never available.
|
|
if (!_existsSync || !_homedir || !_join) {
|
|
const isNode = typeof process !== "undefined" && (process.versions?.node || process.versions?.bun);
|
|
if (!isNode) {
|
|
// Definitively in a browser — safe to cache false permanently
|
|
cachedVertexAdcCredentialsExists = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
* Get API key for provider from known environment variables, e.g. OPENAI_API_KEY.
|
|
*
|
|
* Will not return API keys for providers that require OAuth tokens.
|
|
*/
|
|
export function getEnvApiKey(provider: KnownProvider): string | undefined;
|
|
export function getEnvApiKey(provider: string): string | undefined;
|
|
export function getEnvApiKey(provider: any): string | undefined {
|
|
// Fall back to environment variables
|
|
if (provider === "github-copilot") {
|
|
return process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
|
}
|
|
|
|
// ANTHROPIC_OAUTH_TOKEN takes precedence over ANTHROPIC_API_KEY
|
|
if (provider === "anthropic") {
|
|
return process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
|
|
}
|
|
|
|
// Vertex AI uses Application Default Credentials, not API keys.
|
|
// Auth is configured via `gcloud auth application-default login`.
|
|
if (provider === "google-vertex") {
|
|
const hasCredentials = hasVertexAdcCredentials();
|
|
const hasProject = !!(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT);
|
|
const hasLocation = !!process.env.GOOGLE_CLOUD_LOCATION;
|
|
|
|
if (hasCredentials && hasProject && hasLocation) {
|
|
return "<authenticated>";
|
|
}
|
|
}
|
|
|
|
if (provider === "amazon-bedrock") {
|
|
// Amazon Bedrock supports multiple credential sources:
|
|
// 1. AWS_PROFILE - named profile from ~/.aws/credentials
|
|
// 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - standard IAM keys
|
|
// 3. AWS_BEARER_TOKEN_BEDROCK - Bedrock API keys (bearer token)
|
|
// 4. AWS_CONTAINER_CREDENTIALS_RELATIVE_URI - ECS task roles
|
|
// 5. AWS_CONTAINER_CREDENTIALS_FULL_URI - ECS task roles (full URI)
|
|
// 6. AWS_WEB_IDENTITY_TOKEN_FILE - IRSA (IAM Roles for Service Accounts)
|
|
if (
|
|
process.env.AWS_PROFILE ||
|
|
(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||
|
|
process.env.AWS_BEARER_TOKEN_BEDROCK ||
|
|
process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||
|
|
process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI ||
|
|
process.env.AWS_WEB_IDENTITY_TOKEN_FILE
|
|
) {
|
|
return "<authenticated>";
|
|
}
|
|
}
|
|
|
|
const envMap: Record<string, string> = {
|
|
openai: "OPENAI_API_KEY",
|
|
"azure-openai-responses": "AZURE_OPENAI_API_KEY",
|
|
google: "GEMINI_API_KEY",
|
|
groq: "GROQ_API_KEY",
|
|
cerebras: "CEREBRAS_API_KEY",
|
|
xai: "XAI_API_KEY",
|
|
openrouter: "OPENROUTER_API_KEY",
|
|
"vercel-ai-gateway": "AI_GATEWAY_API_KEY",
|
|
zai: "ZAI_API_KEY",
|
|
mistral: "MISTRAL_API_KEY",
|
|
minimax: "MINIMAX_API_KEY",
|
|
"minimax-cn": "MINIMAX_CN_API_KEY",
|
|
huggingface: "HF_TOKEN",
|
|
opencode: "OPENCODE_API_KEY",
|
|
"kimi-coding": "KIMI_API_KEY",
|
|
};
|
|
|
|
const envVar = envMap[provider];
|
|
return envVar ? process.env[envVar] : undefined;
|
|
}
|