llm-env.ts 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { readFile } from "node:fs/promises";
  2. import { homedir } from "node:os";
  3. import { join } from "node:path";
  4. import { parse } from "dotenv";
  5. export const GLOBAL_CONFIG_DIR = join(homedir(), ".inkos");
  6. export const GLOBAL_ENV_PATH = join(GLOBAL_CONFIG_DIR, ".env");
  7. export type LLMEnvMap = Record<string, string | undefined>;
  8. export interface LLMEnvLayers {
  9. readonly global: LLMEnvMap;
  10. readonly project: LLMEnvMap;
  11. readonly process: LLMEnvMap;
  12. }
  13. export async function loadLLMEnvLayers(
  14. root: string,
  15. processEnv: NodeJS.ProcessEnv = process.env,
  16. ): Promise<LLMEnvLayers> {
  17. const global = await parseEnvFile(GLOBAL_ENV_PATH);
  18. const project = await parseEnvFile(join(root, ".env"));
  19. // Compatibility: modelOverrides.apiKeyEnv and detector config still read process.env directly.
  20. hydrateProcessEnvFromEnvFiles(processEnv, global, project);
  21. return {
  22. global,
  23. project,
  24. process: { ...processEnv },
  25. };
  26. }
  27. export function mergeEnvMaps(...layers: readonly LLMEnvMap[]): LLMEnvMap {
  28. const merged: LLMEnvMap = {};
  29. for (const layer of layers) {
  30. for (const [key, value] of Object.entries(layer)) {
  31. if (value !== undefined) merged[key] = value;
  32. }
  33. }
  34. return merged;
  35. }
  36. export function studioIgnoredEnv(layers: LLMEnvLayers): LLMEnvMap {
  37. return mergeEnvMaps(layers.global, layers.project, layers.process);
  38. }
  39. export function cliOverlayEnv(layers: LLMEnvLayers): LLMEnvMap {
  40. return mergeEnvMaps(layers.global, layers.project, layers.process);
  41. }
  42. export function legacyEnv(layers: LLMEnvLayers): LLMEnvMap {
  43. return mergeEnvMaps(layers.global, layers.project, layers.process);
  44. }
  45. async function parseEnvFile(path: string): Promise<LLMEnvMap> {
  46. try {
  47. return parse(await readFile(path, "utf-8"));
  48. } catch {
  49. return {};
  50. }
  51. }
  52. function hydrateProcessEnvFromEnvFiles(
  53. processEnv: NodeJS.ProcessEnv,
  54. global: LLMEnvMap,
  55. project: LLMEnvMap,
  56. ): void {
  57. const fileEnv = mergeEnvMaps(global, project);
  58. for (const [key, value] of Object.entries(fileEnv)) {
  59. if (value !== undefined && processEnv[key] === undefined) {
  60. processEnv[key] = value;
  61. }
  62. }
  63. }