Files
oh-my-pi/packages/utils/src/env.ts
T
can1357 e6f8001d9e feat: added 11 AI providers with OAuth and $pickenv() fallback support
- Added support for 11 new AI providers (Hugging Face, NVIDIA, Together, Ollama, LiteLLM, Xiaomi, Moonshot, Venice, Qwen Portal, vLLM, Cloudflare AI Gateway) with API key authentication and login flows.
- Implemented $pickenv() utility for environment variable fallback chains, enabling multi-key resolution for providers with alternative credential names.
- Extended KnownProvider and OAuthProvider types to include all 11 new providers with corresponding model manager functions and OAuth handlers.
- Expanded models.json with thousands of new model entries across all new providers and replaced deprecated opencode provider with cloudflare-ai-gateway.
- Refactored model generation script to use unified fetchProviderModelsFromCatalog() and centralized API key resolution for all providers.
2026-02-18 17:47:59 +01:00

85 lines
2.6 KiB
TypeScript

import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { getAgentDir, getConfigRootDir } from "./dirs";
/**
* Parses a .env file synchronously and extracts key-value string pairs.
* Ignores lines that are empty or start with '#'. Trims whitespace.
* Allows values to be quoted with single or double quotes.
* Returns an object of key-value pairs.
*/
function parseEnvFile(filePath: string): Record<string, string> {
const result: Record<string, string> = {};
try {
const content = fs.readFileSync(filePath, "utf-8");
for (const line of content.split("\n")) {
const trimmed = line.trim();
// Skip comments and blank lines
if (!trimmed || trimmed.startsWith("#")) continue;
const eqIndex = trimmed.indexOf("=");
if (eqIndex === -1) continue;
const key = trimmed.slice(0, eqIndex).trim();
let value = trimmed.slice(eqIndex + 1).trim();
// Remove surrounding quotes (" or ')
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
result[key] = value;
}
} catch {
// File doesn't exist or can't be read - return empty result
}
// OMP_ overrides PI_
for (const k in result) {
if (k.startsWith("OMP_")) {
result[`PI_${k.slice(4)}`] = result[k];
}
}
return result;
}
// Eagerly parse the user's $HOME/.env and the current project's .env (from cwd)
const homeEnv = parseEnvFile(path.join(os.homedir(), ".env"));
const piEnv = parseEnvFile(path.join(getConfigRootDir(), ".env"));
const agentEnv = parseEnvFile(path.join(getAgentDir(), ".env"));
const projectEnv = parseEnvFile(path.join(process.cwd(), ".env"));
for (const file of [projectEnv, agentEnv, piEnv, homeEnv]) {
for (const [key, value] of Object.entries(file)) {
if (!Bun.env[key]) {
Bun.env[key] = value;
}
}
}
/**
* Intentional re-export of Bun.env.
*
* All users should import this env module (import { $env } from "@oh-my-pi/pi-utils")
* before using environment variables. This ensures that .env files have been loaded and
* overrides (project, home) have been applied, so $env always reflects the correct values.
*/
export const $env: Record<string, string> = Bun.env as Record<string, string>;
/**
* Resolve the first environment variable value from the given keys.
* @param keys - The keys to resolve.
* @returns The first environment variable value, or undefined if no value is found.
*/
export function $pickenv(...keys: string[]): string | undefined {
for (const key of keys) {
const value = Bun.env[key]?.trim();
if (value) {
return value;
}
}
return undefined;
}