@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
@@ -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
+ }
@@ -1,7 +1,6 @@
1
1
  import { resolvePoolbotAgentDir } from "../agents/agent-paths.js";
2
2
  import { resolveDefaultAgentId, resolveAgentDir, resolveAgentWorkspaceDir, } from "../agents/agent-scope.js";
3
3
  import { upsertAuthProfile } from "../agents/auth-profiles.js";
4
- import { normalizeProviderId } from "../agents/model-selection.js";
5
4
  import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js";
6
5
  import { enablePluginInConfig } from "../plugins/enable.js";
7
6
  import { resolvePluginProviders } from "../plugins/providers.js";
@@ -9,61 +8,7 @@ import { applyAuthProfileConfig } from "./onboard-auth.js";
9
8
  import { openUrl } from "./onboard-helpers.js";
10
9
  import { createVpsAwareOAuthHandlers } from "./oauth-flow.js";
11
10
  import { isRemoteEnvironment } from "./oauth-env.js";
12
- function resolveProviderMatch(providers, rawProvider) {
13
- const normalized = normalizeProviderId(rawProvider);
14
- return (providers.find((provider) => normalizeProviderId(provider.id) === normalized) ??
15
- providers.find((provider) => provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false) ??
16
- null);
17
- }
18
- function pickAuthMethod(provider, rawMethod) {
19
- const raw = rawMethod?.trim();
20
- if (!raw)
21
- return null;
22
- const normalized = raw.toLowerCase();
23
- return (provider.auth.find((method) => method.id.toLowerCase() === normalized) ??
24
- provider.auth.find((method) => method.label.toLowerCase() === normalized) ??
25
- null);
26
- }
27
- function isPlainRecord(value) {
28
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
29
- }
30
- function mergeConfigPatch(base, patch) {
31
- if (!isPlainRecord(base) || !isPlainRecord(patch)) {
32
- return patch;
33
- }
34
- const next = { ...base };
35
- for (const [key, value] of Object.entries(patch)) {
36
- const existing = next[key];
37
- if (isPlainRecord(existing) && isPlainRecord(value)) {
38
- next[key] = mergeConfigPatch(existing, value);
39
- }
40
- else {
41
- next[key] = value;
42
- }
43
- }
44
- return next;
45
- }
46
- function applyDefaultModel(cfg, model) {
47
- const models = { ...cfg.agents?.defaults?.models };
48
- models[model] = models[model] ?? {};
49
- const existingModel = cfg.agents?.defaults?.model;
50
- return {
51
- ...cfg,
52
- agents: {
53
- ...cfg.agents,
54
- defaults: {
55
- ...cfg.agents?.defaults,
56
- models,
57
- model: {
58
- ...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
59
- ? { fallbacks: existingModel.fallbacks }
60
- : undefined),
61
- primary: model,
62
- },
63
- },
64
- },
65
- };
66
- }
11
+ import { applyDefaultModel, mergeConfigPatch, pickAuthMethod, resolveProviderMatch, } from "./provider-auth-helpers.js";
67
12
  export async function applyAuthChoicePluginProvider(params, options) {
68
13
  if (params.authChoice !== options.authChoice)
69
14
  return null;
@@ -0,0 +1,92 @@
1
+ import { upsertAuthProfileWithLock } from "../agents/auth-profiles.js";
2
+ const VLLM_DEFAULT_BASE_URL = "http://127.0.0.1:8000/v1";
3
+ const VLLM_DEFAULT_CONTEXT_WINDOW = 128000;
4
+ const VLLM_DEFAULT_MAX_TOKENS = 8192;
5
+ const VLLM_DEFAULT_COST = {
6
+ input: 0,
7
+ output: 0,
8
+ cacheRead: 0,
9
+ cacheWrite: 0,
10
+ };
11
+ function applyVllmDefaultModel(cfg, modelRef) {
12
+ const existingModel = cfg.agents?.defaults?.model;
13
+ const fallbacks = existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
14
+ ? existingModel.fallbacks
15
+ : undefined;
16
+ return {
17
+ ...cfg,
18
+ agents: {
19
+ ...cfg.agents,
20
+ defaults: {
21
+ ...cfg.agents?.defaults,
22
+ model: {
23
+ ...(fallbacks ? { fallbacks } : undefined),
24
+ primary: modelRef,
25
+ },
26
+ },
27
+ },
28
+ };
29
+ }
30
+ export async function applyAuthChoiceVllm(params) {
31
+ if (params.authChoice !== "vllm") {
32
+ return null;
33
+ }
34
+ const baseUrlRaw = await params.prompter.text({
35
+ message: "vLLM base URL",
36
+ initialValue: VLLM_DEFAULT_BASE_URL,
37
+ placeholder: VLLM_DEFAULT_BASE_URL,
38
+ validate: (value) => (value?.trim() ? undefined : "Required"),
39
+ });
40
+ const apiKeyRaw = await params.prompter.text({
41
+ message: "vLLM API key",
42
+ placeholder: "sk-... (or any non-empty string)",
43
+ validate: (value) => (value?.trim() ? undefined : "Required"),
44
+ });
45
+ const modelIdRaw = await params.prompter.text({
46
+ message: "vLLM model",
47
+ placeholder: "meta-llama/Meta-Llama-3-8B-Instruct",
48
+ validate: (value) => (value?.trim() ? undefined : "Required"),
49
+ });
50
+ const baseUrl = String(baseUrlRaw ?? "")
51
+ .trim()
52
+ .replace(/\/+$/, "");
53
+ const apiKey = String(apiKeyRaw ?? "").trim();
54
+ const modelId = String(modelIdRaw ?? "").trim();
55
+ const modelRef = `vllm/${modelId}`;
56
+ await upsertAuthProfileWithLock({
57
+ profileId: "vllm:default",
58
+ credential: { type: "api_key", provider: "vllm", key: apiKey },
59
+ agentDir: params.agentDir,
60
+ });
61
+ const nextConfig = {
62
+ ...params.config,
63
+ models: {
64
+ ...params.config.models,
65
+ mode: params.config.models?.mode ?? "merge",
66
+ providers: {
67
+ ...params.config.models?.providers,
68
+ vllm: {
69
+ baseUrl,
70
+ api: "openai-completions",
71
+ apiKey: "VLLM_API_KEY",
72
+ models: [
73
+ {
74
+ id: modelId,
75
+ name: modelId,
76
+ reasoning: false,
77
+ input: ["text"],
78
+ cost: VLLM_DEFAULT_COST,
79
+ contextWindow: VLLM_DEFAULT_CONTEXT_WINDOW,
80
+ maxTokens: VLLM_DEFAULT_MAX_TOKENS,
81
+ },
82
+ ],
83
+ },
84
+ },
85
+ },
86
+ };
87
+ if (!params.setDefaultModel) {
88
+ return { config: nextConfig, agentModelOverride: modelRef };
89
+ }
90
+ await params.prompter.note(`Default model set to ${modelRef}`, "Model configured");
91
+ return { config: applyVllmDefaultModel(nextConfig, modelRef) };
92
+ }
@@ -4,6 +4,7 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE = {
4
4
  "claude-cli": "anthropic",
5
5
  token: "anthropic",
6
6
  apiKey: "anthropic",
7
+ vllm: "vllm",
7
8
  "openai-codex": "openai-codex",
8
9
  "codex-cli": "openai-codex",
9
10
  chutes: "chutes",
@@ -18,10 +19,15 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE = {
18
19
  "google-antigravity": "google-antigravity",
19
20
  "google-gemini-cli": "google-gemini-cli",
20
21
  "zai-api-key": "zai",
22
+ "zai-coding-global": "zai",
23
+ "zai-coding-cn": "zai",
24
+ "zai-global": "zai",
25
+ "zai-cn": "zai",
21
26
  "xiaomi-api-key": "xiaomi",
22
27
  "synthetic-api-key": "synthetic",
23
28
  "venice-api-key": "venice",
24
29
  "together-api-key": "together",
30
+ "huggingface-api-key": "huggingface",
25
31
  "github-copilot": "github-copilot",
26
32
  "copilot-proxy": "copilot-proxy",
27
33
  "minimax-cloud": "minimax",
@@ -30,8 +36,12 @@ const PREFERRED_PROVIDER_BY_AUTH_CHOICE = {
30
36
  minimax: "lmstudio",
31
37
  "opencode-zen": "opencode",
32
38
  "xai-api-key": "xai",
39
+ "litellm-api-key": "litellm",
33
40
  "qwen-portal": "qwen-portal",
41
+ "minimax-portal": "minimax-portal",
34
42
  "qianfan-api-key": "qianfan",
43
+ "nvidia-api-key": "nvidia",
44
+ "custom-api-key": "custom",
35
45
  };
36
46
  export function resolvePreferredProviderForAuthChoice(choice) {
37
47
  return PREFERRED_PROVIDER_BY_AUTH_CHOICE[choice];
@@ -12,6 +12,7 @@ import { applyAuthProfileConfig } from "../onboard-auth.js";
12
12
  import { isRemoteEnvironment } from "../oauth-env.js";
13
13
  import { openUrl } from "../onboard-helpers.js";
14
14
  import { createVpsAwareOAuthHandlers } from "../oauth-flow.js";
15
+ import { applyDefaultModel, mergeConfigPatch, pickAuthMethod, resolveProviderMatch, } from "../provider-auth-helpers.js";
15
16
  import { updateConfig } from "./shared.js";
16
17
  import { resolvePluginProviders } from "../../plugins/providers.js";
17
18
  import { createClackPrompter } from "../../wizard/clack-prompter.js";
@@ -167,64 +168,6 @@ export async function modelsAuthAddCommand(_opts, runtime) {
167
168
  : undefined;
168
169
  await modelsAuthPasteTokenCommand({ provider: providerId, profileId, expiresIn }, runtime);
169
170
  }
170
- function resolveProviderMatch(providers, rawProvider) {
171
- const raw = rawProvider?.trim();
172
- if (!raw)
173
- return null;
174
- const normalized = normalizeProviderId(raw);
175
- return (providers.find((provider) => normalizeProviderId(provider.id) === normalized) ??
176
- providers.find((provider) => provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false) ??
177
- null);
178
- }
179
- function pickAuthMethod(provider, rawMethod) {
180
- const raw = rawMethod?.trim();
181
- if (!raw)
182
- return null;
183
- const normalized = raw.toLowerCase();
184
- return (provider.auth.find((method) => method.id.toLowerCase() === normalized) ??
185
- provider.auth.find((method) => method.label.toLowerCase() === normalized) ??
186
- null);
187
- }
188
- function isPlainRecord(value) {
189
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
190
- }
191
- function mergeConfigPatch(base, patch) {
192
- if (!isPlainRecord(base) || !isPlainRecord(patch)) {
193
- return patch;
194
- }
195
- const next = { ...base };
196
- for (const [key, value] of Object.entries(patch)) {
197
- const existing = next[key];
198
- if (isPlainRecord(existing) && isPlainRecord(value)) {
199
- next[key] = mergeConfigPatch(existing, value);
200
- }
201
- else {
202
- next[key] = value;
203
- }
204
- }
205
- return next;
206
- }
207
- function applyDefaultModel(cfg, model) {
208
- const models = { ...cfg.agents?.defaults?.models };
209
- models[model] = models[model] ?? {};
210
- const existingModel = cfg.agents?.defaults?.model;
211
- return {
212
- ...cfg,
213
- agents: {
214
- ...cfg.agents,
215
- defaults: {
216
- ...cfg.agents?.defaults,
217
- models,
218
- model: {
219
- ...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
220
- ? { fallbacks: existingModel.fallbacks }
221
- : undefined),
222
- primary: model,
223
- },
224
- },
225
- },
226
- };
227
- }
228
171
  function credentialMode(credential) {
229
172
  if (credential.type === "api_key")
230
173
  return "api_key";
@@ -0,0 +1,14 @@
1
+ export const MODEL_AVAILABILITY_UNAVAILABLE_CODE = "MODEL_AVAILABILITY_UNAVAILABLE";
2
+ export function formatErrorWithStack(err) {
3
+ if (err instanceof Error) {
4
+ return err.stack ?? `${err.name}: ${err.message}`;
5
+ }
6
+ return String(err);
7
+ }
8
+ export function shouldFallbackToAuthHeuristics(err) {
9
+ if (!(err instanceof Error)) {
10
+ return false;
11
+ }
12
+ const code = err.code;
13
+ return code === MODEL_AVAILABILITY_UNAVAILABLE_CODE;
14
+ }
@@ -1,10 +1,13 @@
1
1
  import { ensureAuthProfileStore } from "../../agents/auth-profiles.js";
2
+ import { resolveForwardCompatModel } from "../../agents/model-forward-compat.js";
2
3
  import { parseModelRef } from "../../agents/model-selection.js";
4
+ import { resolveModel } from "../../agents/pi-embedded-runner/model.js";
3
5
  import { loadConfig } from "../../config/config.js";
4
6
  import { resolveConfiguredEntries } from "./list.configured.js";
7
+ import { formatErrorWithStack } from "./list.errors.js";
5
8
  import { loadModelRegistry, toModelRow } from "./list.registry.js";
6
9
  import { printModelTable } from "./list.table.js";
7
- import { DEFAULT_PROVIDER, ensureFlagCompatibility, modelKey } from "./shared.js";
10
+ import { DEFAULT_PROVIDER, ensureFlagCompatibility, isLocalBaseUrl, modelKey } from "./shared.js";
8
11
  export async function modelsListCommand(opts, runtime) {
9
12
  ensureFlagCompatibility(opts);
10
13
  const cfg = loadConfig();
@@ -17,35 +20,30 @@ export async function modelsListCommand(opts, runtime) {
17
20
  return parsed?.provider ?? raw.toLowerCase();
18
21
  })();
19
22
  let models = [];
23
+ let modelRegistry;
20
24
  let availableKeys;
25
+ let availabilityErrorMessage;
21
26
  try {
22
27
  const loaded = await loadModelRegistry(cfg);
28
+ modelRegistry = loaded.registry;
23
29
  models = loaded.models;
24
30
  availableKeys = loaded.availableKeys;
31
+ availabilityErrorMessage = loaded.availabilityErrorMessage;
25
32
  }
26
33
  catch (err) {
27
- runtime.error(`Model registry unavailable: ${String(err)}`);
34
+ runtime.error(`Model registry unavailable:\n${formatErrorWithStack(err)}`);
35
+ process.exitCode = 1;
36
+ return;
37
+ }
38
+ if (availabilityErrorMessage !== undefined) {
39
+ runtime.error(`Model availability lookup failed; falling back to auth heuristics for discovered models: ${availabilityErrorMessage}`);
28
40
  }
29
41
  const modelByKey = new Map(models.map((model) => [modelKey(model.provider, model.id), model]));
30
42
  const { entries } = resolveConfiguredEntries(cfg);
31
43
  const configuredByKey = new Map(entries.map((entry) => [entry.key, entry]));
32
44
  const rows = [];
33
- const isLocalBaseUrl = (baseUrl) => {
34
- try {
35
- const url = new URL(baseUrl);
36
- const host = url.hostname.toLowerCase();
37
- return (host === "localhost" ||
38
- host === "127.0.0.1" ||
39
- host === "0.0.0.0" ||
40
- host === "::1" ||
41
- host.endsWith(".local"));
42
- }
43
- catch {
44
- return false;
45
- }
46
- };
47
45
  if (opts.all) {
48
- const sorted = [...models].sort((a, b) => {
46
+ const sorted = [...models].toSorted((a, b) => {
49
47
  const p = a.provider.localeCompare(b.provider);
50
48
  if (p !== 0)
51
49
  return p;
@@ -55,8 +53,9 @@ export async function modelsListCommand(opts, runtime) {
55
53
  if (providerFilter && model.provider.toLowerCase() !== providerFilter) {
56
54
  continue;
57
55
  }
58
- if (opts.local && !isLocalBaseUrl(model.baseUrl))
56
+ if (opts.local && !isLocalBaseUrl(model.baseUrl)) {
59
57
  continue;
58
+ }
60
59
  const key = modelKey(model.provider, model.id);
61
60
  const configured = configuredByKey.get(key);
62
61
  rows.push(toModelRow({
@@ -75,11 +74,23 @@ export async function modelsListCommand(opts, runtime) {
75
74
  if (providerFilter && entry.ref.provider.toLowerCase() !== providerFilter) {
76
75
  continue;
77
76
  }
78
- const model = modelByKey.get(entry.key);
79
- if (opts.local && model && !isLocalBaseUrl(model.baseUrl))
77
+ let model = modelByKey.get(entry.key);
78
+ if (!model && modelRegistry) {
79
+ const forwardCompat = resolveForwardCompatModel(entry.ref.provider, entry.ref.model, modelRegistry);
80
+ if (forwardCompat) {
81
+ model = forwardCompat;
82
+ modelByKey.set(entry.key, forwardCompat);
83
+ }
84
+ }
85
+ if (!model) {
86
+ model = resolveModel(entry.ref.provider, entry.ref.model, undefined, cfg).model;
87
+ }
88
+ if (opts.local && model && !isLocalBaseUrl(model.baseUrl)) {
80
89
  continue;
81
- if (opts.local && !model)
90
+ }
91
+ if (opts.local && !model) {
82
92
  continue;
93
+ }
83
94
  rows.push(toModelRow({
84
95
  model,
85
96
  key: entry.key,
@@ -1,43 +1,129 @@
1
- import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
2
1
  import { resolvePoolbotAgentDir } from "../../agents/agent-paths.js";
3
2
  import { listProfilesForProvider } from "../../agents/auth-profiles.js";
4
3
  import { getCustomProviderApiKey, resolveAwsSdkEnvVarName, resolveEnvApiKey, } from "../../agents/model-auth.js";
4
+ import { ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES, resolveForwardCompatModel, } from "../../agents/model-forward-compat.js";
5
5
  import { ensurePoolbotModelsJson } from "../../agents/models-config.js";
6
- import { modelKey } from "./shared.js";
7
- const isLocalBaseUrl = (baseUrl) => {
8
- try {
9
- const url = new URL(baseUrl);
10
- const host = url.hostname.toLowerCase();
11
- return (host === "localhost" ||
12
- host === "127.0.0.1" ||
13
- host === "0.0.0.0" ||
14
- host === "::1" ||
15
- host.endsWith(".local"));
16
- }
17
- catch {
6
+ import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
7
+ import { formatErrorWithStack, MODEL_AVAILABILITY_UNAVAILABLE_CODE, shouldFallbackToAuthHeuristics, } from "./list.errors.js";
8
+ import { isLocalBaseUrl, modelKey } from "./shared.js";
9
+ const hasAuthForProvider = (provider, cfg, authStore) => {
10
+ if (!cfg || !authStore) {
18
11
  return false;
19
12
  }
20
- };
21
- const hasAuthForProvider = (provider, cfg, authStore) => {
22
- if (listProfilesForProvider(authStore, provider).length > 0)
13
+ if (listProfilesForProvider(authStore, provider).length > 0) {
23
14
  return true;
24
- if (provider === "amazon-bedrock" && resolveAwsSdkEnvVarName())
15
+ }
16
+ if (provider === "amazon-bedrock" && resolveAwsSdkEnvVarName()) {
25
17
  return true;
26
- if (resolveEnvApiKey(provider))
18
+ }
19
+ if (resolveEnvApiKey(provider)) {
27
20
  return true;
28
- if (getCustomProviderApiKey(cfg, provider))
21
+ }
22
+ if (getCustomProviderApiKey(cfg, provider)) {
29
23
  return true;
24
+ }
30
25
  return false;
31
26
  };
27
+ function createAvailabilityUnavailableError(message) {
28
+ const err = new Error(message);
29
+ err.code = MODEL_AVAILABILITY_UNAVAILABLE_CODE;
30
+ return err;
31
+ }
32
+ function normalizeAvailabilityError(err) {
33
+ if (shouldFallbackToAuthHeuristics(err) && err instanceof Error) {
34
+ return err;
35
+ }
36
+ return createAvailabilityUnavailableError(`Model availability unavailable: getAvailable() failed.\n${formatErrorWithStack(err)}`);
37
+ }
38
+ function validateAvailableModels(availableModels) {
39
+ if (!Array.isArray(availableModels)) {
40
+ throw createAvailabilityUnavailableError("Model availability unavailable: getAvailable() returned a non-array value.");
41
+ }
42
+ for (const model of availableModels) {
43
+ if (!model ||
44
+ typeof model !== "object" ||
45
+ typeof model.provider !== "string" ||
46
+ typeof model.id !== "string") {
47
+ throw createAvailabilityUnavailableError("Model availability unavailable: getAvailable() returned invalid model entries.");
48
+ }
49
+ }
50
+ return availableModels;
51
+ }
52
+ function loadAvailableModels(registry) {
53
+ let availableModels;
54
+ try {
55
+ availableModels = registry.getAvailable();
56
+ }
57
+ catch (err) {
58
+ throw normalizeAvailabilityError(err);
59
+ }
60
+ try {
61
+ return validateAvailableModels(availableModels);
62
+ }
63
+ catch (err) {
64
+ throw normalizeAvailabilityError(err);
65
+ }
66
+ }
32
67
  export async function loadModelRegistry(cfg) {
33
68
  await ensurePoolbotModelsJson(cfg);
34
69
  const agentDir = resolvePoolbotAgentDir();
35
70
  const authStorage = discoverAuthStorage(agentDir);
36
71
  const registry = discoverModels(authStorage, agentDir);
37
- const models = registry.getAll();
38
- const availableModels = registry.getAvailable();
39
- const availableKeys = new Set(availableModels.map((model) => modelKey(model.provider, model.id)));
40
- return { registry, models, availableKeys };
72
+ const appended = appendAntigravityForwardCompatModels(registry.getAll(), registry);
73
+ const models = appended.models;
74
+ const synthesizedForwardCompat = appended.synthesizedForwardCompat;
75
+ let availableKeys;
76
+ let availabilityErrorMessage;
77
+ try {
78
+ const availableModels = loadAvailableModels(registry);
79
+ availableKeys = new Set(availableModels.map((model) => modelKey(model.provider, model.id)));
80
+ for (const synthesized of synthesizedForwardCompat) {
81
+ if (hasAvailableTemplate(availableKeys, synthesized.templatePrefixes)) {
82
+ availableKeys.add(synthesized.key);
83
+ }
84
+ }
85
+ }
86
+ catch (err) {
87
+ if (!shouldFallbackToAuthHeuristics(err)) {
88
+ throw err;
89
+ }
90
+ // Some providers can report model-level availability as unavailable.
91
+ // Fall back to provider-level auth heuristics when availability is undefined.
92
+ availableKeys = undefined;
93
+ if (!availabilityErrorMessage) {
94
+ availabilityErrorMessage = formatErrorWithStack(err);
95
+ }
96
+ }
97
+ return { registry, models, availableKeys, availabilityErrorMessage };
98
+ }
99
+ function appendAntigravityForwardCompatModels(models, modelRegistry) {
100
+ const nextModels = [...models];
101
+ const synthesizedForwardCompat = [];
102
+ for (const candidate of ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES) {
103
+ const key = modelKey("google-antigravity", candidate.id);
104
+ const hasForwardCompat = nextModels.some((model) => modelKey(model.provider, model.id) === key);
105
+ if (hasForwardCompat) {
106
+ continue;
107
+ }
108
+ const fallback = resolveForwardCompatModel("google-antigravity", candidate.id, modelRegistry);
109
+ if (!fallback) {
110
+ continue;
111
+ }
112
+ nextModels.push(fallback);
113
+ synthesizedForwardCompat.push({
114
+ key,
115
+ templatePrefixes: candidate.templatePrefixes,
116
+ });
117
+ }
118
+ return { models: nextModels, synthesizedForwardCompat };
119
+ }
120
+ function hasAvailableTemplate(availableKeys, templatePrefixes) {
121
+ for (const key of availableKeys) {
122
+ if (templatePrefixes.some((prefix) => key.startsWith(prefix))) {
123
+ return true;
124
+ }
125
+ }
126
+ return false;
41
127
  }
42
128
  export function toModelRow(params) {
43
129
  const { model, key, tags, aliases = [], availableKeys, cfg, authStore } = params;
@@ -55,18 +141,24 @@ export function toModelRow(params) {
55
141
  }
56
142
  const input = model.input.join("+") || "text";
57
143
  const local = isLocalBaseUrl(model.baseUrl);
58
- const available = cfg && authStore
59
- ? hasAuthForProvider(model.provider, cfg, authStore)
60
- : (availableKeys?.has(modelKey(model.provider, model.id)) ?? false);
144
+ // Prefer model-level registry availability when present.
145
+ // Fall back to provider-level auth heuristics only if registry availability isn't available.
146
+ const available = availableKeys !== undefined
147
+ ? availableKeys.has(modelKey(model.provider, model.id))
148
+ : cfg && authStore
149
+ ? hasAuthForProvider(model.provider, cfg, authStore)
150
+ : false;
61
151
  const aliasTags = aliases.length > 0 ? [`alias:${aliases.join(",")}`] : [];
62
152
  const mergedTags = new Set(tags);
63
153
  if (aliasTags.length > 0) {
64
154
  for (const tag of mergedTags) {
65
- if (tag === "alias" || tag.startsWith("alias:"))
155
+ if (tag === "alias" || tag.startsWith("alias:")) {
66
156
  mergedTags.delete(tag);
157
+ }
67
158
  }
68
- for (const tag of aliasTags)
159
+ for (const tag of aliasTags) {
69
160
  mergedTags.add(tag);
161
+ }
70
162
  }
71
163
  return {
72
164
  key,
@@ -89,6 +89,7 @@ export async function modelsStatusCommand(opts, runtime) {
89
89
  "zai",
90
90
  "mistral",
91
91
  "synthetic",
92
+ "nvidia",
92
93
  ];
93
94
  for (const provider of envProbeProviders) {
94
95
  if (resolveEnvApiKey(provider))
@@ -82,6 +82,20 @@ export function resolveKnownAgentId(params) {
82
82
  }
83
83
  return agentId;
84
84
  }
85
+ export const isLocalBaseUrl = (baseUrl) => {
86
+ try {
87
+ const url = new URL(baseUrl);
88
+ const host = url.hostname.toLowerCase();
89
+ return (host === "localhost" ||
90
+ host === "127.0.0.1" ||
91
+ host === "0.0.0.0" ||
92
+ host === "::1" ||
93
+ host.endsWith(".local"));
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ };
85
99
  export { modelKey };
86
100
  export { DEFAULT_MODEL, DEFAULT_PROVIDER };
87
101
  /**