@poolzin/pool-bot 2026.2.5 → 2026.2.6

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,10 +1,13 @@
1
1
  import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js";
2
2
  import { resolveEnvApiKey } from "../agents/model-auth.js";
3
3
  import { formatApiKeyPreview, normalizeApiKeyInput, validateApiKeyInput, } from "./auth-choice.api-key.js";
4
+ import { applyAuthChoiceHuggingface } from "./auth-choice.apply.huggingface.js";
5
+ import { applyAuthChoiceOpenRouter } from "./auth-choice.apply.openrouter.js";
4
6
  import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
5
7
  import { applyGoogleGeminiModelDefault, GOOGLE_GEMINI_DEFAULT_MODEL, } from "./google-gemini-model-default.js";
6
- import { applyAuthProfileConfig, applyCloudflareAiGatewayConfig, applyCloudflareAiGatewayProviderConfig, applyQianfanConfig, applyQianfanProviderConfig, applyKimiCodeConfig, applyKimiCodeProviderConfig, applyMoonshotConfig, applyMoonshotConfigCn, applyMoonshotProviderConfig, applyMoonshotProviderConfigCn, applyOpencodeZenConfig, applyOpencodeZenProviderConfig, applyOpenrouterConfig, applyOpenrouterProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, applyTogetherConfig, applyTogetherProviderConfig, applyVeniceConfig, applyVeniceProviderConfig, applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, applyXiaomiConfig, applyXiaomiProviderConfig, applyZaiConfig, applyNvidiaConfig, applyNvidiaProviderConfig, CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF, NVIDIA_DEFAULT_MODEL_REF, QIANFAN_DEFAULT_MODEL_REF, KIMI_CODING_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, OPENROUTER_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, TOGETHER_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, XIAOMI_DEFAULT_MODEL_REF, setCloudflareAiGatewayConfig, setQianfanApiKey, setGeminiApiKey, setKimiCodingApiKey, setMoonshotApiKey, setNvidiaApiKey, setOpencodeZenApiKey, setOpenrouterApiKey, setSyntheticApiKey, setTogetherApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, setXiaomiApiKey, setZaiApiKey, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.js";
8
+ import { applyAuthProfileConfig, applyCloudflareAiGatewayConfig, applyCloudflareAiGatewayProviderConfig, applyQianfanConfig, applyQianfanProviderConfig, applyKimiCodeConfig, applyKimiCodeProviderConfig, applyLitellmConfig, applyLitellmProviderConfig, applyMoonshotConfig, applyMoonshotConfigCn, applyMoonshotProviderConfig, applyMoonshotProviderConfigCn, applyNvidiaConfig, applyNvidiaProviderConfig, applyOpencodeZenConfig, applyOpencodeZenProviderConfig, applySyntheticConfig, applySyntheticProviderConfig, applyTogetherConfig, applyTogetherProviderConfig, applyVeniceConfig, applyVeniceProviderConfig, applyVercelAiGatewayConfig, applyVercelAiGatewayProviderConfig, applyXiaomiConfig, applyXiaomiProviderConfig, applyZaiConfig, applyZaiProviderConfig, CLOUDFLARE_AI_GATEWAY_DEFAULT_MODEL_REF, LITELLM_DEFAULT_MODEL_REF, NVIDIA_DEFAULT_MODEL_REF, QIANFAN_DEFAULT_MODEL_REF, KIMI_CODING_MODEL_REF, MOONSHOT_DEFAULT_MODEL_REF, SYNTHETIC_DEFAULT_MODEL_REF, TOGETHER_DEFAULT_MODEL_REF, VENICE_DEFAULT_MODEL_REF, VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF, XIAOMI_DEFAULT_MODEL_REF, setCloudflareAiGatewayConfig, setQianfanApiKey, setGeminiApiKey, setLitellmApiKey, setKimiCodingApiKey, setMoonshotApiKey, setNvidiaApiKey, setOpencodeZenApiKey, setSyntheticApiKey, setTogetherApiKey, setVeniceApiKey, setVercelAiGatewayApiKey, setXiaomiApiKey, setZaiApiKey, ZAI_DEFAULT_MODEL_REF, } from "./onboard-auth.js";
7
9
  import { OPENCODE_ZEN_DEFAULT_MODEL } from "./opencode-zen-model-default.js";
10
+ import { detectZaiEndpoint } from "./zai-endpoint-detect.js";
8
11
  export async function applyAuthChoiceApiProviders(params) {
9
12
  let nextConfig = params.config;
10
13
  let agentModelOverride;
@@ -22,6 +25,9 @@ export async function applyAuthChoiceApiProviders(params) {
22
25
  if (params.opts.tokenProvider === "openrouter") {
23
26
  authChoice = "openrouter-api-key";
24
27
  }
28
+ else if (params.opts.tokenProvider === "litellm") {
29
+ authChoice = "litellm-api-key";
30
+ }
25
31
  else if (params.opts.tokenProvider === "vercel-ai-gateway") {
26
32
  authChoice = "ai-gateway-api-key";
27
33
  }
@@ -53,6 +59,9 @@ export async function applyAuthChoiceApiProviders(params) {
53
59
  else if (params.opts.tokenProvider === "together") {
54
60
  authChoice = "together-api-key";
55
61
  }
62
+ else if (params.opts.tokenProvider === "huggingface") {
63
+ authChoice = "huggingface-api-key";
64
+ }
56
65
  else if (params.opts.tokenProvider === "opencode") {
57
66
  authChoice = "opencode-zen";
58
67
  }
@@ -64,75 +73,64 @@ export async function applyAuthChoiceApiProviders(params) {
64
73
  }
65
74
  }
66
75
  if (authChoice === "openrouter-api-key") {
67
- const store = ensureAuthProfileStore(params.agentDir, {
68
- allowKeychainPrompt: false,
69
- });
70
- const profileOrder = resolveAuthProfileOrder({
71
- cfg: nextConfig,
72
- store,
73
- provider: "openrouter",
74
- });
76
+ return applyAuthChoiceOpenRouter(params);
77
+ }
78
+ if (authChoice === "litellm-api-key") {
79
+ const store = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false });
80
+ const profileOrder = resolveAuthProfileOrder({ cfg: nextConfig, store, provider: "litellm" });
75
81
  const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId]));
76
82
  const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined;
77
- let profileId = "openrouter:default";
78
- let mode = "api_key";
83
+ let profileId = "litellm:default";
79
84
  let hasCredential = false;
80
- if (existingProfileId && existingCred?.type) {
85
+ if (existingProfileId && existingCred?.type === "api_key") {
81
86
  profileId = existingProfileId;
82
- mode =
83
- existingCred.type === "oauth"
84
- ? "oauth"
85
- : existingCred.type === "token"
86
- ? "token"
87
- : "api_key";
88
87
  hasCredential = true;
89
88
  }
90
- if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "openrouter") {
91
- await setOpenrouterApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
89
+ if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "litellm") {
90
+ await setLitellmApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
92
91
  hasCredential = true;
93
92
  }
94
93
  if (!hasCredential) {
95
- const envKey = resolveEnvApiKey("openrouter");
94
+ await params.prompter.note("LiteLLM provides a unified API to 100+ LLM providers.\nGet your API key from your LiteLLM proxy or https://litellm.ai\nDefault proxy runs on http://localhost:4000", "LiteLLM");
95
+ const envKey = resolveEnvApiKey("litellm");
96
96
  if (envKey) {
97
97
  const useExisting = await params.prompter.confirm({
98
- message: `Use existing OPENROUTER_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
98
+ message: `Use existing LITELLM_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
99
99
  initialValue: true,
100
100
  });
101
101
  if (useExisting) {
102
- await setOpenrouterApiKey(envKey.apiKey, params.agentDir);
102
+ await setLitellmApiKey(envKey.apiKey, params.agentDir);
103
103
  hasCredential = true;
104
104
  }
105
105
  }
106
- }
107
- if (!hasCredential) {
108
- const key = await params.prompter.text({
109
- message: "Enter OpenRouter API key",
110
- validate: validateApiKeyInput,
111
- });
112
- await setOpenrouterApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
113
- hasCredential = true;
106
+ if (!hasCredential) {
107
+ const key = await params.prompter.text({
108
+ message: "Enter LiteLLM API key",
109
+ validate: validateApiKeyInput,
110
+ });
111
+ await setLitellmApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
112
+ hasCredential = true;
113
+ }
114
114
  }
115
115
  if (hasCredential) {
116
116
  nextConfig = applyAuthProfileConfig(nextConfig, {
117
117
  profileId,
118
- provider: "openrouter",
119
- mode,
120
- });
121
- }
122
- {
123
- const applied = await applyDefaultModelChoice({
124
- config: nextConfig,
125
- setDefaultModel: params.setDefaultModel,
126
- defaultModel: OPENROUTER_DEFAULT_MODEL_REF,
127
- applyDefaultConfig: applyOpenrouterConfig,
128
- applyProviderConfig: applyOpenrouterProviderConfig,
129
- noteDefault: OPENROUTER_DEFAULT_MODEL_REF,
130
- noteAgentModel,
131
- prompter: params.prompter,
118
+ provider: "litellm",
119
+ mode: "api_key",
132
120
  });
133
- nextConfig = applied.config;
134
- agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
135
121
  }
122
+ const applied = await applyDefaultModelChoice({
123
+ config: nextConfig,
124
+ setDefaultModel: params.setDefaultModel,
125
+ defaultModel: LITELLM_DEFAULT_MODEL_REF,
126
+ applyDefaultConfig: applyLitellmConfig,
127
+ applyProviderConfig: applyLitellmProviderConfig,
128
+ noteDefault: LITELLM_DEFAULT_MODEL_REF,
129
+ noteAgentModel,
130
+ prompter: params.prompter,
131
+ });
132
+ nextConfig = applied.config;
133
+ agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
136
134
  return { config: nextConfig, agentModelOverride };
137
135
  }
138
136
  if (authChoice === "ai-gateway-api-key") {
@@ -159,7 +157,7 @@ export async function applyAuthChoiceApiProviders(params) {
159
157
  message: "Enter Vercel AI Gateway API key",
160
158
  validate: validateApiKeyInput,
161
159
  });
162
- await setVercelAiGatewayApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
160
+ await setVercelAiGatewayApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
163
161
  }
164
162
  nextConfig = applyAuthProfileConfig(nextConfig, {
165
163
  profileId: "vercel-ai-gateway:default",
@@ -190,16 +188,16 @@ export async function applyAuthChoiceApiProviders(params) {
190
188
  if (!accountId) {
191
189
  const value = await params.prompter.text({
192
190
  message: "Enter Cloudflare Account ID",
193
- validate: (val) => (String(val).trim() ? undefined : "Account ID is required"),
191
+ validate: (val) => (String(val ?? "").trim() ? undefined : "Account ID is required"),
194
192
  });
195
- accountId = String(value).trim();
193
+ accountId = String(value ?? "").trim();
196
194
  }
197
195
  if (!gatewayId) {
198
196
  const value = await params.prompter.text({
199
197
  message: "Enter Cloudflare AI Gateway ID",
200
- validate: (val) => (String(val).trim() ? undefined : "Gateway ID is required"),
198
+ validate: (val) => (String(val ?? "").trim() ? undefined : "Gateway ID is required"),
201
199
  });
202
- gatewayId = String(value).trim();
200
+ gatewayId = String(value ?? "").trim();
203
201
  }
204
202
  };
205
203
  const optsApiKey = normalizeApiKeyInput(params.opts?.cloudflareAiGatewayApiKey ?? "");
@@ -230,7 +228,7 @@ export async function applyAuthChoiceApiProviders(params) {
230
228
  message: "Enter Cloudflare AI Gateway API key",
231
229
  validate: validateApiKeyInput,
232
230
  });
233
- await setCloudflareAiGatewayConfig(accountId, gatewayId, normalizeApiKeyInput(String(key)), params.agentDir);
231
+ await setCloudflareAiGatewayConfig(accountId, gatewayId, normalizeApiKeyInput(String(key ?? "")), params.agentDir);
234
232
  hasCredential = true;
235
233
  }
236
234
  if (hasCredential) {
@@ -284,7 +282,7 @@ export async function applyAuthChoiceApiProviders(params) {
284
282
  message: "Enter Moonshot API key",
285
283
  validate: validateApiKeyInput,
286
284
  });
287
- await setMoonshotApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
285
+ await setMoonshotApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
288
286
  }
289
287
  nextConfig = applyAuthProfileConfig(nextConfig, {
290
288
  profileId: "moonshot:default",
@@ -328,7 +326,7 @@ export async function applyAuthChoiceApiProviders(params) {
328
326
  message: "Enter Moonshot API key (.cn)",
329
327
  validate: validateApiKeyInput,
330
328
  });
331
- await setMoonshotApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
329
+ await setMoonshotApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
332
330
  }
333
331
  nextConfig = applyAuthProfileConfig(nextConfig, {
334
332
  profileId: "moonshot:default",
@@ -381,7 +379,7 @@ export async function applyAuthChoiceApiProviders(params) {
381
379
  message: "Enter Kimi Coding API key",
382
380
  validate: validateApiKeyInput,
383
381
  });
384
- await setKimiCodingApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
382
+ await setKimiCodingApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
385
383
  }
386
384
  nextConfig = applyAuthProfileConfig(nextConfig, {
387
385
  profileId: "kimi-coding:default",
@@ -426,7 +424,7 @@ export async function applyAuthChoiceApiProviders(params) {
426
424
  message: "Enter Gemini API key",
427
425
  validate: validateApiKeyInput,
428
426
  });
429
- await setGeminiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
427
+ await setGeminiApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
430
428
  }
431
429
  nextConfig = applyAuthProfileConfig(nextConfig, {
432
430
  profileId: "google:default",
@@ -446,10 +444,30 @@ export async function applyAuthChoiceApiProviders(params) {
446
444
  }
447
445
  return { config: nextConfig, agentModelOverride };
448
446
  }
449
- if (authChoice === "zai-api-key") {
447
+ if (authChoice === "zai-api-key" ||
448
+ authChoice === "zai-coding-global" ||
449
+ authChoice === "zai-coding-cn" ||
450
+ authChoice === "zai-global" ||
451
+ authChoice === "zai-cn") {
452
+ let endpoint;
453
+ if (authChoice === "zai-coding-global") {
454
+ endpoint = "coding-global";
455
+ }
456
+ else if (authChoice === "zai-coding-cn") {
457
+ endpoint = "coding-cn";
458
+ }
459
+ else if (authChoice === "zai-global") {
460
+ endpoint = "global";
461
+ }
462
+ else if (authChoice === "zai-cn") {
463
+ endpoint = "cn";
464
+ }
465
+ // Input API key
450
466
  let hasCredential = false;
467
+ let apiKey = "";
451
468
  if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zai") {
452
- await setZaiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
469
+ apiKey = normalizeApiKeyInput(params.opts.token);
470
+ await setZaiApiKey(apiKey, params.agentDir);
453
471
  hasCredential = true;
454
472
  }
455
473
  const envKey = resolveEnvApiKey("zai");
@@ -459,7 +477,8 @@ export async function applyAuthChoiceApiProviders(params) {
459
477
  initialValue: true,
460
478
  });
461
479
  if (useExisting) {
462
- await setZaiApiKey(envKey.apiKey, params.agentDir);
480
+ apiKey = envKey.apiKey;
481
+ await setZaiApiKey(apiKey, params.agentDir);
463
482
  hasCredential = true;
464
483
  }
465
484
  }
@@ -468,42 +487,71 @@ export async function applyAuthChoiceApiProviders(params) {
468
487
  message: "Enter Z.AI API key",
469
488
  validate: validateApiKeyInput,
470
489
  });
471
- await setZaiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
490
+ apiKey = normalizeApiKeyInput(String(key ?? ""));
491
+ await setZaiApiKey(apiKey, params.agentDir);
492
+ }
493
+ // zai-api-key: auto-detect endpoint + choose a working default model.
494
+ let modelIdOverride;
495
+ if (!endpoint) {
496
+ const detected = await detectZaiEndpoint({ apiKey });
497
+ if (detected) {
498
+ endpoint = detected.endpoint;
499
+ modelIdOverride = detected.modelId;
500
+ await params.prompter.note(detected.note, "Z.AI endpoint");
501
+ }
502
+ else {
503
+ endpoint = await params.prompter.select({
504
+ message: "Select Z.AI endpoint",
505
+ options: [
506
+ {
507
+ value: "coding-global",
508
+ label: "Coding-Plan-Global",
509
+ hint: "GLM Coding Plan Global (api.z.ai)",
510
+ },
511
+ {
512
+ value: "coding-cn",
513
+ label: "Coding-Plan-CN",
514
+ hint: "GLM Coding Plan CN (open.bigmodel.cn)",
515
+ },
516
+ {
517
+ value: "global",
518
+ label: "Global",
519
+ hint: "Z.AI Global (api.z.ai)",
520
+ },
521
+ {
522
+ value: "cn",
523
+ label: "CN",
524
+ hint: "Z.AI CN (open.bigmodel.cn)",
525
+ },
526
+ ],
527
+ initialValue: "global",
528
+ });
529
+ }
472
530
  }
473
531
  nextConfig = applyAuthProfileConfig(nextConfig, {
474
532
  profileId: "zai:default",
475
533
  provider: "zai",
476
534
  mode: "api_key",
477
535
  });
478
- {
479
- const applied = await applyDefaultModelChoice({
480
- config: nextConfig,
481
- setDefaultModel: params.setDefaultModel,
482
- defaultModel: ZAI_DEFAULT_MODEL_REF,
483
- applyDefaultConfig: applyZaiConfig,
484
- applyProviderConfig: (config) => ({
485
- ...config,
486
- agents: {
487
- ...config.agents,
488
- defaults: {
489
- ...config.agents?.defaults,
490
- models: {
491
- ...config.agents?.defaults?.models,
492
- [ZAI_DEFAULT_MODEL_REF]: {
493
- ...config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF],
494
- alias: config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM",
495
- },
496
- },
497
- },
498
- },
499
- }),
500
- noteDefault: ZAI_DEFAULT_MODEL_REF,
501
- noteAgentModel,
502
- prompter: params.prompter,
503
- });
504
- nextConfig = applied.config;
505
- agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
506
- }
536
+ const defaultModel = modelIdOverride ? `zai/${modelIdOverride}` : ZAI_DEFAULT_MODEL_REF;
537
+ const applied = await applyDefaultModelChoice({
538
+ config: nextConfig,
539
+ setDefaultModel: params.setDefaultModel,
540
+ defaultModel,
541
+ applyDefaultConfig: (config) => applyZaiConfig(config, {
542
+ endpoint,
543
+ ...(modelIdOverride ? { modelId: modelIdOverride } : {}),
544
+ }),
545
+ applyProviderConfig: (config) => applyZaiProviderConfig(config, {
546
+ endpoint,
547
+ ...(modelIdOverride ? { modelId: modelIdOverride } : {}),
548
+ }),
549
+ noteDefault: defaultModel,
550
+ noteAgentModel,
551
+ prompter: params.prompter,
552
+ });
553
+ nextConfig = applied.config;
554
+ agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
507
555
  return { config: nextConfig, agentModelOverride };
508
556
  }
509
557
  if (authChoice === "xiaomi-api-key") {
@@ -528,7 +576,7 @@ export async function applyAuthChoiceApiProviders(params) {
528
576
  message: "Enter Xiaomi API key",
529
577
  validate: validateApiKeyInput,
530
578
  });
531
- await setXiaomiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
579
+ await setXiaomiApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
532
580
  }
533
581
  nextConfig = applyAuthProfileConfig(nextConfig, {
534
582
  profileId: "xiaomi:default",
@@ -553,14 +601,14 @@ export async function applyAuthChoiceApiProviders(params) {
553
601
  }
554
602
  if (authChoice === "synthetic-api-key") {
555
603
  if (params.opts?.token && params.opts?.tokenProvider === "synthetic") {
556
- await setSyntheticApiKey(String(params.opts.token).trim(), params.agentDir);
604
+ await setSyntheticApiKey(String(params.opts.token ?? "").trim(), params.agentDir);
557
605
  }
558
606
  else {
559
607
  const key = await params.prompter.text({
560
608
  message: "Enter Synthetic API key",
561
609
  validate: (value) => (value?.trim() ? undefined : "Required"),
562
610
  });
563
- await setSyntheticApiKey(String(key).trim(), params.agentDir);
611
+ await setSyntheticApiKey(String(key ?? "").trim(), params.agentDir);
564
612
  }
565
613
  nextConfig = applyAuthProfileConfig(nextConfig, {
566
614
  profileId: "synthetic:default",
@@ -612,7 +660,7 @@ export async function applyAuthChoiceApiProviders(params) {
612
660
  message: "Enter Venice AI API key",
613
661
  validate: validateApiKeyInput,
614
662
  });
615
- await setVeniceApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
663
+ await setVeniceApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
616
664
  }
617
665
  nextConfig = applyAuthProfileConfig(nextConfig, {
618
666
  profileId: "venice:default",
@@ -664,7 +712,7 @@ export async function applyAuthChoiceApiProviders(params) {
664
712
  message: "Enter OpenCode Zen API key",
665
713
  validate: validateApiKeyInput,
666
714
  });
667
- await setOpencodeZenApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
715
+ await setOpencodeZenApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
668
716
  }
669
717
  nextConfig = applyAuthProfileConfig(nextConfig, {
670
718
  profileId: "opencode:default",
@@ -715,7 +763,7 @@ export async function applyAuthChoiceApiProviders(params) {
715
763
  message: "Enter Together AI API key",
716
764
  validate: validateApiKeyInput,
717
765
  });
718
- await setTogetherApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
766
+ await setTogetherApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
719
767
  }
720
768
  nextConfig = applyAuthProfileConfig(nextConfig, {
721
769
  profileId: "together:default",
@@ -738,6 +786,9 @@ export async function applyAuthChoiceApiProviders(params) {
738
786
  }
739
787
  return { config: nextConfig, agentModelOverride };
740
788
  }
789
+ if (authChoice === "huggingface-api-key") {
790
+ return applyAuthChoiceHuggingface({ ...params, authChoice });
791
+ }
741
792
  if (authChoice === "qianfan-api-key") {
742
793
  let hasCredential = false;
743
794
  if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "qianfan") {
@@ -766,7 +817,7 @@ export async function applyAuthChoiceApiProviders(params) {
766
817
  message: "Enter QIANFAN API key",
767
818
  validate: validateApiKeyInput,
768
819
  });
769
- setQianfanApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
820
+ setQianfanApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
770
821
  }
771
822
  nextConfig = applyAuthProfileConfig(nextConfig, {
772
823
  profileId: "qianfan:default",
@@ -817,7 +868,7 @@ export async function applyAuthChoiceApiProviders(params) {
817
868
  message: "Enter NVIDIA API key",
818
869
  validate: validateApiKeyInput,
819
870
  });
820
- setNvidiaApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
871
+ setNvidiaApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
821
872
  }
822
873
  nextConfig = applyAuthProfileConfig(nextConfig, {
823
874
  profileId: "nvidia:default",
@@ -0,0 +1,130 @@
1
+ import { discoverHuggingfaceModels, isHuggingfacePolicyLocked, } from "../agents/huggingface-models.js";
2
+ import { resolveEnvApiKey } from "../agents/model-auth.js";
3
+ import { formatApiKeyPreview, normalizeApiKeyInput, validateApiKeyInput, } from "./auth-choice.api-key.js";
4
+ import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
5
+ import { ensureModelAllowlistEntry } from "./model-allowlist.js";
6
+ import { applyAuthProfileConfig, applyHuggingfaceProviderConfig, setHuggingfaceApiKey, HUGGINGFACE_DEFAULT_MODEL_REF, } from "./onboard-auth.js";
7
+ export async function applyAuthChoiceHuggingface(params) {
8
+ if (params.authChoice !== "huggingface-api-key") {
9
+ return null;
10
+ }
11
+ let nextConfig = params.config;
12
+ let agentModelOverride;
13
+ const noteAgentModel = async (model) => {
14
+ if (!params.agentId) {
15
+ return;
16
+ }
17
+ await params.prompter.note(`Default model set to ${model} for agent "${params.agentId}".`, "Model configured");
18
+ };
19
+ let hasCredential = false;
20
+ let hfKey = "";
21
+ if (!hasCredential && params.opts?.token && params.opts.tokenProvider === "huggingface") {
22
+ hfKey = normalizeApiKeyInput(params.opts.token);
23
+ await setHuggingfaceApiKey(hfKey, params.agentDir);
24
+ hasCredential = true;
25
+ }
26
+ if (!hasCredential) {
27
+ await params.prompter.note([
28
+ "Hugging Face Inference Providers offer OpenAI-compatible chat completions.",
29
+ "Create a token at: https://huggingface.co/settings/tokens (fine-grained, 'Make calls to Inference Providers').",
30
+ ].join("\n"), "Hugging Face");
31
+ }
32
+ if (!hasCredential) {
33
+ const envKey = resolveEnvApiKey("huggingface");
34
+ if (envKey) {
35
+ const useExisting = await params.prompter.confirm({
36
+ message: `Use existing Hugging Face token (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
37
+ initialValue: true,
38
+ });
39
+ if (useExisting) {
40
+ hfKey = envKey.apiKey;
41
+ await setHuggingfaceApiKey(hfKey, params.agentDir);
42
+ hasCredential = true;
43
+ }
44
+ }
45
+ }
46
+ if (!hasCredential) {
47
+ const key = await params.prompter.text({
48
+ message: "Enter Hugging Face API key (HF token)",
49
+ validate: validateApiKeyInput,
50
+ });
51
+ hfKey = normalizeApiKeyInput(String(key ?? ""));
52
+ await setHuggingfaceApiKey(hfKey, params.agentDir);
53
+ }
54
+ nextConfig = applyAuthProfileConfig(nextConfig, {
55
+ profileId: "huggingface:default",
56
+ provider: "huggingface",
57
+ mode: "api_key",
58
+ });
59
+ const models = await discoverHuggingfaceModels(hfKey);
60
+ const modelRefPrefix = "huggingface/";
61
+ const options = [];
62
+ for (const m of models) {
63
+ const baseRef = `${modelRefPrefix}${m.id}`;
64
+ const label = m.name ?? m.id;
65
+ options.push({ value: baseRef, label });
66
+ options.push({ value: `${baseRef}:cheapest`, label: `${label} (cheapest)` });
67
+ options.push({ value: `${baseRef}:fastest`, label: `${label} (fastest)` });
68
+ }
69
+ const defaultRef = HUGGINGFACE_DEFAULT_MODEL_REF;
70
+ options.sort((a, b) => {
71
+ if (a.value === defaultRef) {
72
+ return -1;
73
+ }
74
+ if (b.value === defaultRef) {
75
+ return 1;
76
+ }
77
+ return a.label.localeCompare(b.label, undefined, { sensitivity: "base" });
78
+ });
79
+ const selectedModelRef = options.length === 0
80
+ ? defaultRef
81
+ : options.length === 1
82
+ ? options[0].value
83
+ : await params.prompter.select({
84
+ message: "Default Hugging Face model",
85
+ options,
86
+ initialValue: options.some((o) => o.value === defaultRef)
87
+ ? defaultRef
88
+ : options[0].value,
89
+ });
90
+ if (isHuggingfacePolicyLocked(selectedModelRef)) {
91
+ await params.prompter.note("Provider locked — router will choose backend by cost or speed.", "Hugging Face");
92
+ }
93
+ const applied = await applyDefaultModelChoice({
94
+ config: nextConfig,
95
+ setDefaultModel: params.setDefaultModel,
96
+ defaultModel: selectedModelRef,
97
+ applyDefaultConfig: (config) => {
98
+ const withProvider = applyHuggingfaceProviderConfig(config);
99
+ const existingModel = withProvider.agents?.defaults?.model;
100
+ const withPrimary = {
101
+ ...withProvider,
102
+ agents: {
103
+ ...withProvider.agents,
104
+ defaults: {
105
+ ...withProvider.agents?.defaults,
106
+ model: {
107
+ ...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
108
+ ? {
109
+ fallbacks: existingModel.fallbacks,
110
+ }
111
+ : {}),
112
+ primary: selectedModelRef,
113
+ },
114
+ },
115
+ },
116
+ };
117
+ return ensureModelAllowlistEntry({
118
+ cfg: withPrimary,
119
+ modelRef: selectedModelRef,
120
+ });
121
+ },
122
+ applyProviderConfig: applyHuggingfaceProviderConfig,
123
+ noteDefault: selectedModelRef,
124
+ noteAgentModel,
125
+ prompter: params.prompter,
126
+ });
127
+ nextConfig = applied.config;
128
+ agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
129
+ return { config: nextConfig, agentModelOverride };
130
+ }
@@ -0,0 +1,77 @@
1
+ import { ensureAuthProfileStore, resolveAuthProfileOrder } from "../agents/auth-profiles.js";
2
+ import { resolveEnvApiKey } from "../agents/model-auth.js";
3
+ import { formatApiKeyPreview, normalizeApiKeyInput, validateApiKeyInput, } from "./auth-choice.api-key.js";
4
+ import { applyDefaultModelChoice } from "./auth-choice.default-model.js";
5
+ import { applyAuthProfileConfig, applyOpenrouterConfig, applyOpenrouterProviderConfig, setOpenrouterApiKey, OPENROUTER_DEFAULT_MODEL_REF, } from "./onboard-auth.js";
6
+ export async function applyAuthChoiceOpenRouter(params) {
7
+ let nextConfig = params.config;
8
+ let agentModelOverride;
9
+ const noteAgentModel = async (model) => {
10
+ if (!params.agentId) {
11
+ return;
12
+ }
13
+ await params.prompter.note(`Default model set to ${model} for agent "${params.agentId}".`, "Model configured");
14
+ };
15
+ const store = ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false });
16
+ const profileOrder = resolveAuthProfileOrder({
17
+ cfg: nextConfig,
18
+ store,
19
+ provider: "openrouter",
20
+ });
21
+ const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId]));
22
+ const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined;
23
+ let profileId = "openrouter:default";
24
+ let mode = "api_key";
25
+ let hasCredential = false;
26
+ if (existingProfileId && existingCred?.type) {
27
+ profileId = existingProfileId;
28
+ mode =
29
+ existingCred.type === "oauth" ? "oauth" : existingCred.type === "token" ? "token" : "api_key";
30
+ hasCredential = true;
31
+ }
32
+ if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "openrouter") {
33
+ await setOpenrouterApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
34
+ hasCredential = true;
35
+ }
36
+ if (!hasCredential) {
37
+ const envKey = resolveEnvApiKey("openrouter");
38
+ if (envKey) {
39
+ const useExisting = await params.prompter.confirm({
40
+ message: `Use existing OPENROUTER_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
41
+ initialValue: true,
42
+ });
43
+ if (useExisting) {
44
+ await setOpenrouterApiKey(envKey.apiKey, params.agentDir);
45
+ hasCredential = true;
46
+ }
47
+ }
48
+ }
49
+ if (!hasCredential) {
50
+ const key = await params.prompter.text({
51
+ message: "Enter OpenRouter API key",
52
+ validate: validateApiKeyInput,
53
+ });
54
+ await setOpenrouterApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
55
+ hasCredential = true;
56
+ }
57
+ if (hasCredential) {
58
+ nextConfig = applyAuthProfileConfig(nextConfig, {
59
+ profileId,
60
+ provider: "openrouter",
61
+ mode,
62
+ });
63
+ }
64
+ const applied = await applyDefaultModelChoice({
65
+ config: nextConfig,
66
+ setDefaultModel: params.setDefaultModel,
67
+ defaultModel: OPENROUTER_DEFAULT_MODEL_REF,
68
+ applyDefaultConfig: applyOpenrouterConfig,
69
+ applyProviderConfig: applyOpenrouterProviderConfig,
70
+ noteDefault: OPENROUTER_DEFAULT_MODEL_REF,
71
+ noteAgentModel,
72
+ prompter: params.prompter,
73
+ });
74
+ nextConfig = applied.config;
75
+ agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
76
+ return { config: nextConfig, agentModelOverride };
77
+ }