psyclaw 0.29.19 → 0.29.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { createServer } from "node:http";
2
2
  import { randomUUID } from "node:crypto";
3
+ import { homedir } from "node:os";
3
4
  import { lstat, mkdir, readFile, readdir, realpath, writeFile } from "node:fs/promises";
4
5
  import { dirname, join, relative, resolve, sep } from "node:path";
5
6
  import { fileURLToPath } from "node:url";
@@ -540,17 +541,117 @@ async function panelCatalog(root) {
540
541
  },
541
542
  };
542
543
  });
543
- const spec = deepSeekProviderSpec();
544
- const envForProvider = (provider) => ({
544
+ // 1. Read stored keys from auth.json in getAgentDir() and ~/.pi/agent/auth.json
545
+ const authProviders = new Set();
546
+ for (const dir of [getAgentDir(), join(homedir(), ".pi", "agent"), join(homedir(), ".codex")]) {
547
+ try {
548
+ const raw = await readFile(join(dir, "auth.json"), "utf8");
549
+ const parsed = JSON.parse(raw);
550
+ if (parsed && typeof parsed === "object") {
551
+ for (const [key, val] of Object.entries(parsed)) {
552
+ if (val) {
553
+ authProviders.add(key.toLowerCase());
554
+ authProviders.add(key);
555
+ }
556
+ }
557
+ }
558
+ }
559
+ catch { /* ignore */ }
560
+ }
561
+ // 2. Read stored providers from models.json in getAgentDir() and ~/.pi/agent/models.json
562
+ const modelsProviders = new Set();
563
+ for (const dir of [getAgentDir(), join(homedir(), ".pi", "agent")]) {
564
+ try {
565
+ const raw = await readFile(join(dir, "models.json"), "utf8");
566
+ const parsed = JSON.parse(raw);
567
+ if (parsed?.providers && typeof parsed.providers === "object") {
568
+ for (const key of Object.keys(parsed.providers)) {
569
+ modelsProviders.add(key.toLowerCase());
570
+ modelsProviders.add(key);
571
+ }
572
+ }
573
+ }
574
+ catch { /* ignore */ }
575
+ }
576
+ // 3. Read .psyclaw/.env or ~/.psyclaw/.env
577
+ const dotEnvVars = {};
578
+ for (const envFile of [join(root, ".psyclaw", ".env"), join(homedir(), ".psyclaw", ".env")]) {
579
+ try {
580
+ const raw = await readFile(envFile, "utf8");
581
+ for (const line of raw.split("\n")) {
582
+ const trimmed = line.trim();
583
+ if (!trimmed || trimmed.startsWith("#"))
584
+ continue;
585
+ const eqIdx = trimmed.indexOf("=");
586
+ if (eqIdx > 0) {
587
+ const k = trimmed.slice(0, eqIdx).trim();
588
+ const v = trimmed.slice(eqIdx + 1).trim();
589
+ if (k && v)
590
+ dotEnvVars[k] = v;
591
+ }
592
+ }
593
+ }
594
+ catch { /* ignore */ }
595
+ }
596
+ // 4. Read .psyclaw/config.yaml or ~/.psyclaw/config.yaml for configured provider
597
+ let configYamlProvider;
598
+ for (const yamlFile of [join(root, ".psyclaw", "config.yaml"), join(homedir(), ".psyclaw", "config.yaml")]) {
599
+ try {
600
+ const raw = await readFile(yamlFile, "utf8");
601
+ const match = raw.match(/^provider:\s*([^\s#]+)/m);
602
+ if (match && match[1]) {
603
+ configYamlProvider = match[1].trim();
604
+ break;
605
+ }
606
+ }
607
+ catch { /* ignore */ }
608
+ }
609
+ const knownEnvForProvider = {
545
610
  deepseek: "DEEPSEEK_API_KEY",
546
611
  openai: "OPENAI_API_KEY",
547
612
  anthropic: "ANTHROPIC_API_KEY",
548
613
  google: "GEMINI_API_KEY",
549
- }[provider] ?? PROVIDER_PRESETS.find((preset) => preset.id === provider)?.apiKeyEnv);
614
+ gemini: "GEMINI_API_KEY",
615
+ opencode: "OPENCODE_API_KEY",
616
+ "opencode-go": "OPENCODE_API_KEY",
617
+ moonshotai: "MOONSHOT_API_KEY",
618
+ "moonshotai-cn": "MOONSHOT_API_KEY",
619
+ minimax: "MINIMAX_API_KEY",
620
+ "minimax-cn": "MINIMAX_API_KEY",
621
+ zai: "ZAI_API_KEY",
622
+ "zai-coding-cn": "ZAI_API_KEY",
623
+ qwen: "DASHSCOPE_API_KEY",
624
+ dashscope: "DASHSCOPE_API_KEY",
625
+ alibaba: "DASHSCOPE_API_KEY",
626
+ groq: "GROQ_API_KEY",
627
+ mistral: "MISTRAL_API_KEY",
628
+ together: "TOGETHER_API_KEY",
629
+ openrouter: "OPENROUTER_API_KEY",
630
+ xai: "XAI_API_KEY",
631
+ };
632
+ const envForProvider = (provider) => (knownEnvForProvider[provider] ??
633
+ knownEnvForProvider[provider.toLowerCase()] ??
634
+ PROVIDER_PRESETS.find((preset) => preset.id === provider)?.apiKeyEnv ??
635
+ `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`);
550
636
  const configured = (provider) => {
637
+ if (provider === "ollama")
638
+ return true;
639
+ const lower = provider.toLowerCase();
640
+ if (authProviders.has(provider) || authProviders.has(lower))
641
+ return true;
642
+ if (modelsProviders.has(provider) || modelsProviders.has(lower))
643
+ return true;
644
+ if (configYamlProvider && (configYamlProvider === provider || configYamlProvider.toLowerCase() === lower))
645
+ return true;
551
646
  const envName = envForProvider(provider);
552
- return envName === undefined ? false : Boolean(process.env[envName]);
647
+ if (envName && (Boolean(process.env[envName]) || Boolean(dotEnvVars[envName])))
648
+ return true;
649
+ const genericEnv = `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
650
+ if (Boolean(process.env[genericEnv]) || Boolean(dotEnvVars[genericEnv]))
651
+ return true;
652
+ return false;
553
653
  };
654
+ const spec = deepSeekProviderSpec();
554
655
  const helperModels = spec.models.map((model) => ({
555
656
  id: `${spec.id}/${model.id}`,
556
657
  kind: "model",
@@ -1188,11 +1289,51 @@ export function createPanelServer(root, options = {}) {
1188
1289
  if (url.pathname === "/api/active-provider") {
1189
1290
  if (request.method === "GET") {
1190
1291
  const settings = await readAgentSettings();
1292
+ let provider = typeof settings.defaultProvider === "string" ? settings.defaultProvider : undefined;
1293
+ let model = typeof settings.defaultModel === "string" ? settings.defaultModel : undefined;
1294
+ if (!provider || !model) {
1295
+ try {
1296
+ const piSettings = JSON.parse(await readFile(join(homedir(), ".pi", "agent", "settings.json"), "utf8"));
1297
+ if (!provider && typeof piSettings.defaultProvider === "string")
1298
+ provider = piSettings.defaultProvider;
1299
+ if (!model && typeof piSettings.defaultModel === "string")
1300
+ model = piSettings.defaultModel;
1301
+ }
1302
+ catch { /* ignore */ }
1303
+ }
1304
+ if (!provider || !model) {
1305
+ for (const yamlFile of [join(root, ".psyclaw", "config.yaml"), join(homedir(), ".psyclaw", "config.yaml")]) {
1306
+ try {
1307
+ const raw = await readFile(yamlFile, "utf8");
1308
+ const pMatch = raw.match(/^provider:\s*([^\s#]+)/m);
1309
+ const mMatch = raw.match(/^model:\s*([^\s#]+)/m);
1310
+ if (!provider && pMatch?.[1])
1311
+ provider = pMatch[1].trim();
1312
+ if (!model && mMatch?.[1])
1313
+ model = mMatch[1].trim();
1314
+ if (provider && model)
1315
+ break;
1316
+ }
1317
+ catch { /* ignore */ }
1318
+ }
1319
+ }
1320
+ if (!provider) {
1321
+ try {
1322
+ const catalog = await panelCatalog(root);
1323
+ const firstConfigured = catalog.models.find((m) => m.configured);
1324
+ if (firstConfigured) {
1325
+ provider = firstConfigured.provider;
1326
+ if (!model)
1327
+ model = firstConfigured.version || firstConfigured.id.split("/").slice(1).join("/");
1328
+ }
1329
+ }
1330
+ catch { /* ignore */ }
1331
+ }
1191
1332
  response.writeHead(200, { "content-type": "application/json" });
1192
1333
  response.end(JSON.stringify({
1193
1334
  schemaVersion: "psyclaw/active-provider/v1",
1194
- provider: typeof settings.defaultProvider === "string" ? settings.defaultProvider : undefined,
1195
- model: typeof settings.defaultModel === "string" ? settings.defaultModel : undefined,
1335
+ provider,
1336
+ model,
1196
1337
  }));
1197
1338
  return;
1198
1339
  }