@poolzin/pool-bot 2026.2.4 → 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.
Files changed (37) hide show
  1. package/dist/agents/auth-profiles/profiles.js +9 -0
  2. package/dist/agents/auth-profiles.js +1 -1
  3. package/dist/agents/huggingface-models.js +166 -0
  4. package/dist/agents/model-auth.js +6 -0
  5. package/dist/agents/model-forward-compat.js +187 -0
  6. package/dist/agents/pi-embedded-runner/model.js +10 -56
  7. package/dist/browser/constants.js +1 -1
  8. package/dist/browser/profiles.js +1 -1
  9. package/dist/build-info.json +3 -3
  10. package/dist/cli/config-cli.js +17 -3
  11. package/dist/cli/program/register.onboard.js +38 -5
  12. package/dist/commands/auth-choice-options.js +71 -7
  13. package/dist/commands/auth-choice.apply.api-providers.js +202 -97
  14. package/dist/commands/auth-choice.apply.huggingface.js +130 -0
  15. package/dist/commands/auth-choice.apply.openrouter.js +77 -0
  16. package/dist/commands/auth-choice.apply.plugin-provider.js +1 -56
  17. package/dist/commands/auth-choice.apply.vllm.js +92 -0
  18. package/dist/commands/auth-choice.preferred-provider.js +10 -0
  19. package/dist/commands/models/auth.js +1 -58
  20. package/dist/commands/models/list.errors.js +14 -0
  21. package/dist/commands/models/list.list-command.js +32 -21
  22. package/dist/commands/models/list.registry.js +120 -28
  23. package/dist/commands/models/list.status-command.js +1 -0
  24. package/dist/commands/models/shared.js +14 -0
  25. package/dist/commands/onboard-auth.config-core.js +265 -8
  26. package/dist/commands/onboard-auth.credentials.js +47 -6
  27. package/dist/commands/onboard-auth.js +3 -3
  28. package/dist/commands/onboard-auth.models.js +67 -0
  29. package/dist/commands/onboard-custom.js +181 -70
  30. package/dist/commands/onboard-non-interactive/api-keys.js +10 -1
  31. package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +15 -7
  32. package/dist/commands/onboard-non-interactive/local/auth-choice.js +322 -124
  33. package/dist/commands/provider-auth-helpers.js +61 -0
  34. package/dist/commands/zai-endpoint-detect.js +97 -0
  35. package/dist/config/legacy.migrations.part-3.js +57 -0
  36. package/dist/terminal/theme.js +1 -1
  37. package/package.json +1 -1
@@ -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, CLOUDFLARE_AI_GATEWAY_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, 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,83 +59,78 @@ 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
  }
59
68
  else if (params.opts.tokenProvider === "qianfan") {
60
69
  authChoice = "qianfan-api-key";
61
70
  }
71
+ else if (params.opts.tokenProvider === "nvidia") {
72
+ authChoice = "nvidia-api-key";
73
+ }
62
74
  }
63
75
  if (authChoice === "openrouter-api-key") {
64
- const store = ensureAuthProfileStore(params.agentDir, {
65
- allowKeychainPrompt: false,
66
- });
67
- const profileOrder = resolveAuthProfileOrder({
68
- cfg: nextConfig,
69
- store,
70
- provider: "openrouter",
71
- });
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" });
72
81
  const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId]));
73
82
  const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined;
74
- let profileId = "openrouter:default";
75
- let mode = "api_key";
83
+ let profileId = "litellm:default";
76
84
  let hasCredential = false;
77
- if (existingProfileId && existingCred?.type) {
85
+ if (existingProfileId && existingCred?.type === "api_key") {
78
86
  profileId = existingProfileId;
79
- mode =
80
- existingCred.type === "oauth"
81
- ? "oauth"
82
- : existingCred.type === "token"
83
- ? "token"
84
- : "api_key";
85
87
  hasCredential = true;
86
88
  }
87
- if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "openrouter") {
88
- 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);
89
91
  hasCredential = true;
90
92
  }
91
93
  if (!hasCredential) {
92
- 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");
93
96
  if (envKey) {
94
97
  const useExisting = await params.prompter.confirm({
95
- message: `Use existing OPENROUTER_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
98
+ message: `Use existing LITELLM_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
96
99
  initialValue: true,
97
100
  });
98
101
  if (useExisting) {
99
- await setOpenrouterApiKey(envKey.apiKey, params.agentDir);
102
+ await setLitellmApiKey(envKey.apiKey, params.agentDir);
100
103
  hasCredential = true;
101
104
  }
102
105
  }
103
- }
104
- if (!hasCredential) {
105
- const key = await params.prompter.text({
106
- message: "Enter OpenRouter API key",
107
- validate: validateApiKeyInput,
108
- });
109
- await setOpenrouterApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
110
- 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
+ }
111
114
  }
112
115
  if (hasCredential) {
113
116
  nextConfig = applyAuthProfileConfig(nextConfig, {
114
117
  profileId,
115
- provider: "openrouter",
116
- mode,
117
- });
118
- }
119
- {
120
- const applied = await applyDefaultModelChoice({
121
- config: nextConfig,
122
- setDefaultModel: params.setDefaultModel,
123
- defaultModel: OPENROUTER_DEFAULT_MODEL_REF,
124
- applyDefaultConfig: applyOpenrouterConfig,
125
- applyProviderConfig: applyOpenrouterProviderConfig,
126
- noteDefault: OPENROUTER_DEFAULT_MODEL_REF,
127
- noteAgentModel,
128
- prompter: params.prompter,
118
+ provider: "litellm",
119
+ mode: "api_key",
129
120
  });
130
- nextConfig = applied.config;
131
- agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
132
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;
133
134
  return { config: nextConfig, agentModelOverride };
134
135
  }
135
136
  if (authChoice === "ai-gateway-api-key") {
@@ -156,7 +157,7 @@ export async function applyAuthChoiceApiProviders(params) {
156
157
  message: "Enter Vercel AI Gateway API key",
157
158
  validate: validateApiKeyInput,
158
159
  });
159
- await setVercelAiGatewayApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
160
+ await setVercelAiGatewayApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
160
161
  }
161
162
  nextConfig = applyAuthProfileConfig(nextConfig, {
162
163
  profileId: "vercel-ai-gateway:default",
@@ -187,16 +188,16 @@ export async function applyAuthChoiceApiProviders(params) {
187
188
  if (!accountId) {
188
189
  const value = await params.prompter.text({
189
190
  message: "Enter Cloudflare Account ID",
190
- validate: (val) => (String(val).trim() ? undefined : "Account ID is required"),
191
+ validate: (val) => (String(val ?? "").trim() ? undefined : "Account ID is required"),
191
192
  });
192
- accountId = String(value).trim();
193
+ accountId = String(value ?? "").trim();
193
194
  }
194
195
  if (!gatewayId) {
195
196
  const value = await params.prompter.text({
196
197
  message: "Enter Cloudflare AI Gateway ID",
197
- validate: (val) => (String(val).trim() ? undefined : "Gateway ID is required"),
198
+ validate: (val) => (String(val ?? "").trim() ? undefined : "Gateway ID is required"),
198
199
  });
199
- gatewayId = String(value).trim();
200
+ gatewayId = String(value ?? "").trim();
200
201
  }
201
202
  };
202
203
  const optsApiKey = normalizeApiKeyInput(params.opts?.cloudflareAiGatewayApiKey ?? "");
@@ -227,7 +228,7 @@ export async function applyAuthChoiceApiProviders(params) {
227
228
  message: "Enter Cloudflare AI Gateway API key",
228
229
  validate: validateApiKeyInput,
229
230
  });
230
- await setCloudflareAiGatewayConfig(accountId, gatewayId, normalizeApiKeyInput(String(key)), params.agentDir);
231
+ await setCloudflareAiGatewayConfig(accountId, gatewayId, normalizeApiKeyInput(String(key ?? "")), params.agentDir);
231
232
  hasCredential = true;
232
233
  }
233
234
  if (hasCredential) {
@@ -281,7 +282,7 @@ export async function applyAuthChoiceApiProviders(params) {
281
282
  message: "Enter Moonshot API key",
282
283
  validate: validateApiKeyInput,
283
284
  });
284
- await setMoonshotApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
285
+ await setMoonshotApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
285
286
  }
286
287
  nextConfig = applyAuthProfileConfig(nextConfig, {
287
288
  profileId: "moonshot:default",
@@ -325,7 +326,7 @@ export async function applyAuthChoiceApiProviders(params) {
325
326
  message: "Enter Moonshot API key (.cn)",
326
327
  validate: validateApiKeyInput,
327
328
  });
328
- await setMoonshotApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
329
+ await setMoonshotApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
329
330
  }
330
331
  nextConfig = applyAuthProfileConfig(nextConfig, {
331
332
  profileId: "moonshot:default",
@@ -378,7 +379,7 @@ export async function applyAuthChoiceApiProviders(params) {
378
379
  message: "Enter Kimi Coding API key",
379
380
  validate: validateApiKeyInput,
380
381
  });
381
- await setKimiCodingApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
382
+ await setKimiCodingApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
382
383
  }
383
384
  nextConfig = applyAuthProfileConfig(nextConfig, {
384
385
  profileId: "kimi-coding:default",
@@ -423,7 +424,7 @@ export async function applyAuthChoiceApiProviders(params) {
423
424
  message: "Enter Gemini API key",
424
425
  validate: validateApiKeyInput,
425
426
  });
426
- await setGeminiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
427
+ await setGeminiApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
427
428
  }
428
429
  nextConfig = applyAuthProfileConfig(nextConfig, {
429
430
  profileId: "google:default",
@@ -443,10 +444,30 @@ export async function applyAuthChoiceApiProviders(params) {
443
444
  }
444
445
  return { config: nextConfig, agentModelOverride };
445
446
  }
446
- 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
447
466
  let hasCredential = false;
467
+ let apiKey = "";
448
468
  if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "zai") {
449
- await setZaiApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
469
+ apiKey = normalizeApiKeyInput(params.opts.token);
470
+ await setZaiApiKey(apiKey, params.agentDir);
450
471
  hasCredential = true;
451
472
  }
452
473
  const envKey = resolveEnvApiKey("zai");
@@ -456,7 +477,8 @@ export async function applyAuthChoiceApiProviders(params) {
456
477
  initialValue: true,
457
478
  });
458
479
  if (useExisting) {
459
- await setZaiApiKey(envKey.apiKey, params.agentDir);
480
+ apiKey = envKey.apiKey;
481
+ await setZaiApiKey(apiKey, params.agentDir);
460
482
  hasCredential = true;
461
483
  }
462
484
  }
@@ -465,42 +487,71 @@ export async function applyAuthChoiceApiProviders(params) {
465
487
  message: "Enter Z.AI API key",
466
488
  validate: validateApiKeyInput,
467
489
  });
468
- 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
+ }
469
530
  }
470
531
  nextConfig = applyAuthProfileConfig(nextConfig, {
471
532
  profileId: "zai:default",
472
533
  provider: "zai",
473
534
  mode: "api_key",
474
535
  });
475
- {
476
- const applied = await applyDefaultModelChoice({
477
- config: nextConfig,
478
- setDefaultModel: params.setDefaultModel,
479
- defaultModel: ZAI_DEFAULT_MODEL_REF,
480
- applyDefaultConfig: applyZaiConfig,
481
- applyProviderConfig: (config) => ({
482
- ...config,
483
- agents: {
484
- ...config.agents,
485
- defaults: {
486
- ...config.agents?.defaults,
487
- models: {
488
- ...config.agents?.defaults?.models,
489
- [ZAI_DEFAULT_MODEL_REF]: {
490
- ...config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF],
491
- alias: config.agents?.defaults?.models?.[ZAI_DEFAULT_MODEL_REF]?.alias ?? "GLM",
492
- },
493
- },
494
- },
495
- },
496
- }),
497
- noteDefault: ZAI_DEFAULT_MODEL_REF,
498
- noteAgentModel,
499
- prompter: params.prompter,
500
- });
501
- nextConfig = applied.config;
502
- agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
503
- }
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;
504
555
  return { config: nextConfig, agentModelOverride };
505
556
  }
506
557
  if (authChoice === "xiaomi-api-key") {
@@ -525,7 +576,7 @@ export async function applyAuthChoiceApiProviders(params) {
525
576
  message: "Enter Xiaomi API key",
526
577
  validate: validateApiKeyInput,
527
578
  });
528
- await setXiaomiApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
579
+ await setXiaomiApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
529
580
  }
530
581
  nextConfig = applyAuthProfileConfig(nextConfig, {
531
582
  profileId: "xiaomi:default",
@@ -550,14 +601,14 @@ export async function applyAuthChoiceApiProviders(params) {
550
601
  }
551
602
  if (authChoice === "synthetic-api-key") {
552
603
  if (params.opts?.token && params.opts?.tokenProvider === "synthetic") {
553
- await setSyntheticApiKey(String(params.opts.token).trim(), params.agentDir);
604
+ await setSyntheticApiKey(String(params.opts.token ?? "").trim(), params.agentDir);
554
605
  }
555
606
  else {
556
607
  const key = await params.prompter.text({
557
608
  message: "Enter Synthetic API key",
558
609
  validate: (value) => (value?.trim() ? undefined : "Required"),
559
610
  });
560
- await setSyntheticApiKey(String(key).trim(), params.agentDir);
611
+ await setSyntheticApiKey(String(key ?? "").trim(), params.agentDir);
561
612
  }
562
613
  nextConfig = applyAuthProfileConfig(nextConfig, {
563
614
  profileId: "synthetic:default",
@@ -609,7 +660,7 @@ export async function applyAuthChoiceApiProviders(params) {
609
660
  message: "Enter Venice AI API key",
610
661
  validate: validateApiKeyInput,
611
662
  });
612
- await setVeniceApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
663
+ await setVeniceApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
613
664
  }
614
665
  nextConfig = applyAuthProfileConfig(nextConfig, {
615
666
  profileId: "venice:default",
@@ -661,7 +712,7 @@ export async function applyAuthChoiceApiProviders(params) {
661
712
  message: "Enter OpenCode Zen API key",
662
713
  validate: validateApiKeyInput,
663
714
  });
664
- await setOpencodeZenApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
715
+ await setOpencodeZenApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
665
716
  }
666
717
  nextConfig = applyAuthProfileConfig(nextConfig, {
667
718
  profileId: "opencode:default",
@@ -712,7 +763,7 @@ export async function applyAuthChoiceApiProviders(params) {
712
763
  message: "Enter Together AI API key",
713
764
  validate: validateApiKeyInput,
714
765
  });
715
- await setTogetherApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
766
+ await setTogetherApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
716
767
  }
717
768
  nextConfig = applyAuthProfileConfig(nextConfig, {
718
769
  profileId: "together:default",
@@ -735,6 +786,9 @@ export async function applyAuthChoiceApiProviders(params) {
735
786
  }
736
787
  return { config: nextConfig, agentModelOverride };
737
788
  }
789
+ if (authChoice === "huggingface-api-key") {
790
+ return applyAuthChoiceHuggingface({ ...params, authChoice });
791
+ }
738
792
  if (authChoice === "qianfan-api-key") {
739
793
  let hasCredential = false;
740
794
  if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "qianfan") {
@@ -763,7 +817,7 @@ export async function applyAuthChoiceApiProviders(params) {
763
817
  message: "Enter QIANFAN API key",
764
818
  validate: validateApiKeyInput,
765
819
  });
766
- setQianfanApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
820
+ setQianfanApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
767
821
  }
768
822
  nextConfig = applyAuthProfileConfig(nextConfig, {
769
823
  profileId: "qianfan:default",
@@ -786,5 +840,56 @@ export async function applyAuthChoiceApiProviders(params) {
786
840
  }
787
841
  return { config: nextConfig, agentModelOverride };
788
842
  }
843
+ if (authChoice === "nvidia-api-key") {
844
+ let hasCredential = false;
845
+ if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "nvidia") {
846
+ setNvidiaApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
847
+ hasCredential = true;
848
+ }
849
+ if (!hasCredential) {
850
+ await params.prompter.note([
851
+ "Get your API key at: https://build.nvidia.com/settings/api-key",
852
+ "Free tier includes 1,000 requests/day across 23+ frontier models.",
853
+ ].join("\n"), "NVIDIA");
854
+ }
855
+ const envKey = resolveEnvApiKey("nvidia");
856
+ if (envKey) {
857
+ const useExisting = await params.prompter.confirm({
858
+ message: `Use existing NVIDIA_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
859
+ initialValue: true,
860
+ });
861
+ if (useExisting) {
862
+ setNvidiaApiKey(envKey.apiKey, params.agentDir);
863
+ hasCredential = true;
864
+ }
865
+ }
866
+ if (!hasCredential) {
867
+ const key = await params.prompter.text({
868
+ message: "Enter NVIDIA API key",
869
+ validate: validateApiKeyInput,
870
+ });
871
+ setNvidiaApiKey(normalizeApiKeyInput(String(key ?? "")), params.agentDir);
872
+ }
873
+ nextConfig = applyAuthProfileConfig(nextConfig, {
874
+ profileId: "nvidia:default",
875
+ provider: "nvidia",
876
+ mode: "api_key",
877
+ });
878
+ {
879
+ const applied = await applyDefaultModelChoice({
880
+ config: nextConfig,
881
+ setDefaultModel: params.setDefaultModel,
882
+ defaultModel: NVIDIA_DEFAULT_MODEL_REF,
883
+ applyDefaultConfig: applyNvidiaConfig,
884
+ applyProviderConfig: applyNvidiaProviderConfig,
885
+ noteDefault: NVIDIA_DEFAULT_MODEL_REF,
886
+ noteAgentModel,
887
+ prompter: params.prompter,
888
+ });
889
+ nextConfig = applied.config;
890
+ agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
891
+ }
892
+ return { config: nextConfig, agentModelOverride };
893
+ }
789
894
  return null;
790
895
  }
@@ -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
+ }