comisai 1.0.30 → 1.0.32

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 (28) hide show
  1. package/node_modules/@comis/agent/package.json +1 -1
  2. package/node_modules/@comis/channels/package.json +1 -1
  3. package/node_modules/@comis/cli/dist/cli.js +2 -0
  4. package/node_modules/@comis/cli/dist/client/provider-list.d.ts +40 -0
  5. package/node_modules/@comis/cli/dist/client/provider-list.js +84 -0
  6. package/node_modules/@comis/cli/dist/commands/providers.d.ts +25 -0
  7. package/node_modules/@comis/cli/dist/commands/providers.js +122 -0
  8. package/node_modules/@comis/cli/dist/wizard/index.d.ts +2 -2
  9. package/node_modules/@comis/cli/dist/wizard/index.js +1 -1
  10. package/node_modules/@comis/cli/dist/wizard/non-interactive.js +29 -28
  11. package/node_modules/@comis/cli/dist/wizard/steps/03-provider.d.ts +11 -5
  12. package/node_modules/@comis/cli/dist/wizard/steps/03-provider.js +75 -13
  13. package/node_modules/@comis/cli/dist/wizard/steps/04-credentials.js +70 -12
  14. package/node_modules/@comis/cli/dist/wizard/steps/05-agent.js +6 -20
  15. package/node_modules/@comis/cli/dist/wizard/types.d.ts +0 -14
  16. package/node_modules/@comis/cli/dist/wizard/types.js +0 -24
  17. package/node_modules/@comis/cli/package.json +1 -1
  18. package/node_modules/@comis/core/package.json +1 -1
  19. package/node_modules/@comis/daemon/package.json +1 -1
  20. package/node_modules/@comis/gateway/package.json +1 -1
  21. package/node_modules/@comis/infra/package.json +1 -1
  22. package/node_modules/@comis/memory/package.json +1 -1
  23. package/node_modules/@comis/scheduler/package.json +1 -1
  24. package/node_modules/@comis/shared/package.json +1 -1
  25. package/node_modules/@comis/skills/package.json +1 -1
  26. package/node_modules/@comis/web/package.json +1 -1
  27. package/npm-shrinkwrap.json +5204 -1013
  28. package/package.json +13 -13
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/agent",
3
3
  "private": true,
4
- "version": "1.0.30",
4
+ "version": "1.0.32",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "AI agent executor, budget control, and session management for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/channels",
3
3
  "private": true,
4
- "version": "1.0.30",
4
+ "version": "1.0.32",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Chat platform adapters — Discord, Telegram, Slack, WhatsApp, Signal, iMessage, IRC, LINE",
@@ -23,6 +23,7 @@ import { registerConfigureCommand } from "./commands/configure.js";
23
23
  import { registerStatusCommand } from "./commands/status.js";
24
24
  import { registerHealthCommand } from "./commands/health.js";
25
25
  import { registerModelsCommand } from "./commands/models.js";
26
+ import { registerProvidersCommand } from "./commands/providers.js";
26
27
  import { registerPm2Command } from "./commands/pm2.js";
27
28
  import { registerSessionsCommand } from "./commands/sessions.js";
28
29
  import { registerResetCommand } from "./commands/reset.js";
@@ -46,6 +47,7 @@ registerConfigureCommand(program);
46
47
  registerStatusCommand(program);
47
48
  registerHealthCommand(program);
48
49
  registerModelsCommand(program);
50
+ registerProvidersCommand(program);
49
51
  registerPm2Command(program);
50
52
  registerSessionsCommand(program);
51
53
  registerResetCommand(program);
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Shared provider-list utility (RPC-first, local catalog fallback).
3
+ *
4
+ * Used by BOTH the wizard's provider selection step
5
+ * (`wizard/steps/03-provider.ts`) AND the `comis providers list` command
6
+ * (`commands/providers.ts`). The single utility avoids duplicating the
7
+ * "try daemon, fall back to pi-ai locally" decision tree across two call
8
+ * sites.
9
+ *
10
+ * RPC-first because the daemon's catalog may be enriched with live scan
11
+ * results that the local pi-ai static registry doesn't know about.
12
+ *
13
+ * Local fallback handles the pre-init use case: the wizard runs *before*
14
+ * the daemon exists for first-time users, and `comis providers list`
15
+ * remains useful when the daemon is stopped.
16
+ *
17
+ * Logging: silent. The catch arms here represent the *normal* fallback
18
+ * flow (daemon not running, daemon returned an unexpected shape), not
19
+ * error conditions. Adding a logger would create noise on every wizard
20
+ * boot. Surfaces above this layer (the wizard prompter, the providers
21
+ * command's output) report the resulting state to the user.
22
+ *
23
+ * @module
24
+ */
25
+ /**
26
+ * Load the catalog provider list, preferring the daemon RPC and falling
27
+ * back to the local pi-ai catalog when the daemon is unreachable.
28
+ *
29
+ * Contract:
30
+ * - Returns `string[]` (the provider IDs).
31
+ * - Never throws. All error paths are caught internally; the worst-case
32
+ * return is `[]`, which callers translate into a "no providers" UX.
33
+ * - When the RPC succeeds with a valid `{providers, count}` shape, the
34
+ * array is returned verbatim (the daemon already sorts; we trust it).
35
+ * - When the local fallback runs, the result is deduped and sorted.
36
+ *
37
+ * @returns A list of provider IDs from the catalog, or `[]` on total
38
+ * failure.
39
+ */
40
+ export declare function loadProvidersWithFallback(): Promise<string[]>;
@@ -0,0 +1,84 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Shared provider-list utility (RPC-first, local catalog fallback).
4
+ *
5
+ * Used by BOTH the wizard's provider selection step
6
+ * (`wizard/steps/03-provider.ts`) AND the `comis providers list` command
7
+ * (`commands/providers.ts`). The single utility avoids duplicating the
8
+ * "try daemon, fall back to pi-ai locally" decision tree across two call
9
+ * sites.
10
+ *
11
+ * RPC-first because the daemon's catalog may be enriched with live scan
12
+ * results that the local pi-ai static registry doesn't know about.
13
+ *
14
+ * Local fallback handles the pre-init use case: the wizard runs *before*
15
+ * the daemon exists for first-time users, and `comis providers list`
16
+ * remains useful when the daemon is stopped.
17
+ *
18
+ * Logging: silent. The catch arms here represent the *normal* fallback
19
+ * flow (daemon not running, daemon returned an unexpected shape), not
20
+ * error conditions. Adding a logger would create noise on every wizard
21
+ * boot. Surfaces above this layer (the wizard prompter, the providers
22
+ * command's output) report the resulting state to the user.
23
+ *
24
+ * @module
25
+ */
26
+ import { withClient } from "./rpc-client.js";
27
+ import { createModelCatalog } from "@comis/agent";
28
+ /**
29
+ * Defensive shape narrowing for the daemon RPC response.
30
+ *
31
+ * The daemon's `models.list_providers` handler is expected to return
32
+ * `{ providers: string[]; count: number }` (verified in
33
+ * `packages/daemon/src/rpc/model-handlers.ts:99-106`). We narrow at the
34
+ * call site so a malformed response (e.g., daemon version skew, future
35
+ * shape change) cannot crash the wizard.
36
+ */
37
+ function isValidProvidersResponse(value) {
38
+ if (value === null || typeof value !== "object")
39
+ return false;
40
+ const candidate = value;
41
+ if (!Array.isArray(candidate.providers))
42
+ return false;
43
+ return candidate.providers.every((p) => typeof p === "string");
44
+ }
45
+ /**
46
+ * Load the catalog provider list, preferring the daemon RPC and falling
47
+ * back to the local pi-ai catalog when the daemon is unreachable.
48
+ *
49
+ * Contract:
50
+ * - Returns `string[]` (the provider IDs).
51
+ * - Never throws. All error paths are caught internally; the worst-case
52
+ * return is `[]`, which callers translate into a "no providers" UX.
53
+ * - When the RPC succeeds with a valid `{providers, count}` shape, the
54
+ * array is returned verbatim (the daemon already sorts; we trust it).
55
+ * - When the local fallback runs, the result is deduped and sorted.
56
+ *
57
+ * @returns A list of provider IDs from the catalog, or `[]` on total
58
+ * failure.
59
+ */
60
+ export async function loadProvidersWithFallback() {
61
+ // RPC-first: daemon may have a richer/scanned catalog.
62
+ try {
63
+ const result = await withClient(async (client) => client.call("models.list_providers", {}));
64
+ if (isValidProvidersResponse(result)) {
65
+ return result.providers;
66
+ }
67
+ // Malformed shape -- fall through to local fallback (defensive).
68
+ }
69
+ catch {
70
+ // Daemon not running, RPC error, or timeout -- fall through.
71
+ }
72
+ // Local fallback via pi-ai static catalog.
73
+ try {
74
+ const catalog = createModelCatalog();
75
+ catalog.loadStatic();
76
+ const providers = [...new Set(catalog.getAll().map((e) => e.provider))].sort();
77
+ return providers;
78
+ }
79
+ catch {
80
+ // Catastrophic failure (rare): pi-ai SDK boot failure or similar.
81
+ // Caller's UX layer reports "no providers" on empty result.
82
+ return [];
83
+ }
84
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Provider listing CLI command.
3
+ *
4
+ * Provides `comis providers list` for browsing available providers from
5
+ * the live pi-ai catalog (with daemon RPC + local fallback). Status
6
+ * column indicates whether a provider's API key is resolvable from the
7
+ * env (mirrors credential-resolver.ts Source B semantics from
8
+ * 260501-2pz).
9
+ *
10
+ * Mirrors `commands/models.ts` shape -- RPC-first, local catalog
11
+ * fallback, `--format` flag, no `set` subcommand (provider switching
12
+ * goes through `comis agent configure --provider X`).
13
+ *
14
+ * @module
15
+ */
16
+ import type { Command } from "commander";
17
+ /**
18
+ * Register the `providers` command group on the program.
19
+ *
20
+ * Provides:
21
+ * - `comis providers list` -- browse available providers
22
+ *
23
+ * @param program - The root Commander program
24
+ */
25
+ export declare function registerProvidersCommand(program: Command): void;
@@ -0,0 +1,122 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Provider listing CLI command.
4
+ *
5
+ * Provides `comis providers list` for browsing available providers from
6
+ * the live pi-ai catalog (with daemon RPC + local fallback). Status
7
+ * column indicates whether a provider's API key is resolvable from the
8
+ * env (mirrors credential-resolver.ts Source B semantics from
9
+ * 260501-2pz).
10
+ *
11
+ * Mirrors `commands/models.ts` shape -- RPC-first, local catalog
12
+ * fallback, `--format` flag, no `set` subcommand (provider switching
13
+ * goes through `comis agent configure --provider X`).
14
+ *
15
+ * @module
16
+ */
17
+ import { getEnvApiKey } from "@mariozechner/pi-ai";
18
+ import { withClient } from "../client/rpc-client.js";
19
+ import { loadProvidersWithFallback } from "../client/provider-list.js";
20
+ import { createModelCatalog } from "@comis/agent";
21
+ import { error, info, json } from "../output/format.js";
22
+ import { withSpinner } from "../output/spinner.js";
23
+ import { renderTable } from "../output/table.js";
24
+ /**
25
+ * Provider IDs that don't need an API key.
26
+ *
27
+ * Mirrors `credential-resolver.ts:25 KEYLESS_PROVIDER_TYPES`. Kept as
28
+ * an independent set here because the CLI's status-column logic must
29
+ * answer "is this keyless?" without booting the full credential-
30
+ * resolver dep graph.
31
+ */
32
+ const KEYLESS_PROVIDERS = new Set(["ollama", "lm-studio"]);
33
+ /**
34
+ * Load the model count for a single provider via RPC, falling back to
35
+ * the local catalog. Returns 0 if neither source resolves.
36
+ *
37
+ * Mirrors `commands/models.ts:62-83 loadModels()` shape -- same
38
+ * try/catch ladder, same defensive `Array.isArray` narrow.
39
+ */
40
+ async function getModelCount(provider) {
41
+ try {
42
+ const result = await withClient(async (client) => {
43
+ return (await client.call("models.list", { provider }));
44
+ });
45
+ if (Array.isArray(result))
46
+ return result.length;
47
+ }
48
+ catch {
49
+ // Daemon not running -- fall through to local catalog.
50
+ }
51
+ try {
52
+ const catalog = createModelCatalog();
53
+ catalog.loadStatic();
54
+ return catalog.getByProvider(provider).length;
55
+ }
56
+ catch {
57
+ return 0;
58
+ }
59
+ }
60
+ /**
61
+ * Resolve the Status column value for a provider.
62
+ *
63
+ * - `keyless` : provider is in `KEYLESS_PROVIDERS` (ollama, lm-studio)
64
+ * - `configured` : pi-ai's `getEnvApiKey` resolves a non-empty key
65
+ * - `missing key` : no env key found
66
+ *
67
+ * Mirrors `credential-resolver.ts` Source B semantics from 260501-2pz.
68
+ * Status reflects only env-key presence; it does NOT include the key
69
+ * value itself (T-260501-kqq-02 information-disclosure threat).
70
+ */
71
+ function getProviderStatus(provider) {
72
+ if (KEYLESS_PROVIDERS.has(provider))
73
+ return "keyless";
74
+ const key = getEnvApiKey(provider);
75
+ return key && key.length > 0 ? "configured" : "missing key";
76
+ }
77
+ /**
78
+ * Register the `providers` command group on the program.
79
+ *
80
+ * Provides:
81
+ * - `comis providers list` -- browse available providers
82
+ *
83
+ * @param program - The root Commander program
84
+ */
85
+ export function registerProvidersCommand(program) {
86
+ const providers = program
87
+ .command("providers")
88
+ .description("Provider management");
89
+ providers
90
+ .command("list")
91
+ .description("List available providers from the catalog")
92
+ .option("--format <format>", 'Output format: "table" or "json"', "table")
93
+ .action(async (options) => {
94
+ try {
95
+ const ids = await withSpinner("Loading providers...", () => loadProvidersWithFallback());
96
+ if (ids.length === 0) {
97
+ info("No providers found in catalog");
98
+ return;
99
+ }
100
+ // Sequentially fetch model counts. With ~11-23 providers this
101
+ // is acceptable (single-digit RPC roundtrips). N+1 batching is
102
+ // a v1.5 enhancement (T-260501-kqq-03 DoS disposition: accept).
103
+ const rows = [];
104
+ for (const id of ids) {
105
+ const modelCount = await getModelCount(id);
106
+ const status = getProviderStatus(id);
107
+ rows.push({ provider: id, modelCount, status });
108
+ }
109
+ if (options.format === "json") {
110
+ json(rows);
111
+ return;
112
+ }
113
+ renderTable(["Provider", "Models", "Status"], rows.map((r) => [r.provider, String(r.modelCount), r.status]));
114
+ info(`${rows.length} provider${rows.length !== 1 ? "s" : ""} listed`);
115
+ }
116
+ catch (err) {
117
+ const msg = err instanceof Error ? err.message : String(err);
118
+ error(`Failed to list providers: ${msg}`);
119
+ process.exit(1);
120
+ }
121
+ });
122
+ }
@@ -11,8 +11,8 @@
11
11
  *
12
12
  * @module
13
13
  */
14
- export type { FlowType, WizardStepId, WizardState, WizardStep, WizardResult, WizardError, ValidationResult, AuthMethod, ChannelConfig, GatewayConfig, ProviderConfig, ToolProviderConfig, SupportedProvider, SupportedToolProvider, } from "./types.js";
15
- export { INITIAL_STATE, SUPPORTED_PROVIDERS, SUPPORTED_CHANNELS, SUPPORTED_TOOL_PROVIDERS, PROVIDER_ENV_KEYS, CHANNEL_ENV_KEYS, TOOL_PROVIDER_ENV_KEYS, } from "./types.js";
14
+ export type { FlowType, WizardStepId, WizardState, WizardStep, WizardResult, WizardError, ValidationResult, AuthMethod, ChannelConfig, GatewayConfig, ProviderConfig, ToolProviderConfig, SupportedToolProvider, } from "./types.js";
15
+ export { INITIAL_STATE, SUPPORTED_CHANNELS, SUPPORTED_TOOL_PROVIDERS, PROVIDER_ENV_KEYS, CHANNEL_ENV_KEYS, TOOL_PROVIDER_ENV_KEYS, } from "./types.js";
16
16
  export type { WizardPrompter, SelectOpts, MultiselectOpts, TextOpts, PasswordOpts, ConfirmOpts, Spinner, } from "./prompter.js";
17
17
  export { CancelError } from "./prompter.js";
18
18
  export { ClackAdapter, createClackAdapter } from "./clack-adapter.js";
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * @module
14
14
  */
15
- export { INITIAL_STATE, SUPPORTED_PROVIDERS, SUPPORTED_CHANNELS, SUPPORTED_TOOL_PROVIDERS, PROVIDER_ENV_KEYS, CHANNEL_ENV_KEYS, TOOL_PROVIDER_ENV_KEYS, } from "./types.js";
15
+ export { INITIAL_STATE, SUPPORTED_CHANNELS, SUPPORTED_TOOL_PROVIDERS, PROVIDER_ENV_KEYS, CHANNEL_ENV_KEYS, TOOL_PROVIDER_ENV_KEYS, } from "./types.js";
16
16
  export { CancelError } from "./prompter.js";
17
17
  // ---------- Clack Adapter ----------
18
18
  export { ClackAdapter, createClackAdapter } from "./clack-adapter.js";
@@ -16,7 +16,7 @@
16
16
  import { randomBytes } from "node:crypto";
17
17
  import { homedir } from "node:os";
18
18
  import { safePath } from "@comis/core";
19
- import { SUPPORTED_PROVIDERS } from "./types.js";
19
+ import { createModelCatalog } from "@comis/agent";
20
20
  import { validatePort } from "./validators/port.js";
21
21
  import { validateAgentName } from "./validators/agent-name.js";
22
22
  // ---------- Error ----------
@@ -36,26 +36,6 @@ export class NonInteractiveError extends Error {
36
36
  this.field = field;
37
37
  }
38
38
  }
39
- // ---------- Recommended Models ----------
40
- /**
41
- * Default model per provider, matching step 05-agent logic.
42
- *
43
- * When no --model flag is provided, the build function selects
44
- * the recommended model for the given provider.
45
- */
46
- const RECOMMENDED_MODELS = {
47
- anthropic: "claude-sonnet-4-5-20250929",
48
- openai: "gpt-4o",
49
- google: "gemini-2.0-flash",
50
- groq: "llama-3.3-70b-versatile",
51
- mistral: "mistral-large-latest",
52
- deepseek: "deepseek-chat",
53
- xai: "grok-2",
54
- together: "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
55
- cerebras: "llama-3.3-70b",
56
- openrouter: "anthropic/claude-sonnet-4-5-20250929",
57
- ollama: "llama3",
58
- };
59
39
  // ---------- Validation ----------
60
40
  /**
61
41
  * Validate non-interactive options before building state.
@@ -76,11 +56,28 @@ export function validateNonInteractiveOptions(opts) {
76
56
  if (!opts.provider || opts.provider.trim().length === 0) {
77
57
  throw new NonInteractiveError("--provider is required in non-interactive mode", "provider");
78
58
  }
79
- // Validate provider is known (allow "custom" and "ollama" for forward compat)
80
- const knownIds = SUPPORTED_PROVIDERS.map((p) => p.id);
81
- if (!knownIds.includes(opts.provider)) {
82
- // Unknown provider is allowed but not silently -- the caller can
83
- // decide to warn. We do not throw here for forward compatibility.
59
+ // Soft validation: warn for unknown providers but do not throw.
60
+ // Daemon-side guards (260501-2pz credential-resolver, 260501-gyy
61
+ // builtin-provider-guard) catch genuinely-invalid providers downstream
62
+ // when the agent attempts to use the config. This loosening enables
63
+ // forward compat when a new pi-ai version adds a provider before
64
+ // comis releases. The "custom" provider is always allowed (synthetic).
65
+ if (opts.provider !== "custom") {
66
+ try {
67
+ const catalog = createModelCatalog();
68
+ catalog.loadStatic();
69
+ const known = new Set(catalog.getAll().map((e) => e.provider));
70
+ if (!known.has(opts.provider)) {
71
+ // Soft WARN to stderr -- do not throw, do not log credentials.
72
+ // Note: this path runs in CLI bootstrap; we use console.warn
73
+ // because this function may run before any prompter is wired.
74
+ console.warn(` WARN: provider "${opts.provider}" is not in the pi-ai catalog. Continuing for forward compatibility -- daemon-side validation will catch invalid providers.`);
75
+ }
76
+ }
77
+ catch {
78
+ // Catalog load failed (rare) -- skip the check entirely; let
79
+ // downstream daemon-side guards catch invalid providers.
80
+ }
84
81
  }
85
82
  // Validate gateway port if specified
86
83
  if (opts.gatewayPort !== undefined) {
@@ -161,8 +158,12 @@ export function buildNonInteractiveState(opts) {
161
158
  ...(opts.apiKey !== undefined && { apiKey: opts.apiKey }),
162
159
  validated: !!opts.skipValidation,
163
160
  };
164
- // Model selection -- use explicit flag or provider default
165
- const model = opts.model ?? RECOMMENDED_MODELS[opts.provider] ?? "default";
161
+ // Model selection -- delegate to daemon when not specified.
162
+ // The literal "default" is resolved at agent-execution time via the
163
+ // pi-ai catalog (builtin-provider-guard.ts:45 baseUrl pattern). Pre-
164
+ // 260501-kqq, this read a hardcoded provider->model map; that lookup
165
+ // was removed -- the daemon decides at runtime.
166
+ const model = opts.model ?? "default";
166
167
  // Channel configs
167
168
  const channels = [];
168
169
  if (opts.channels && opts.channels.length > 0) {
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * Provider selection step -- step 03 of the init wizard.
3
3
  *
4
- * Presents all supported LLM providers in a flat select list
5
- * with per-provider hints. A category overview note is shown
6
- * before the selection to help the user orient. Anthropic is
4
+ * Presents the providers from the live pi-ai catalog (loaded via
5
+ * `loadProvidersWithFallback` -- daemon RPC first, local pi-ai catalog
6
+ * fallback for the pre-init use case) plus a synthetic "custom" option
7
+ * appended last for OpenAI-compatible endpoints. A category overview
8
+ * note is shown before selection to help the user orient. Anthropic is
7
9
  * pre-selected as the recommended default.
8
10
  *
9
- * This step only captures the provider choice. API credentials
10
- * are collected in step 04 (credentials).
11
+ * Provider IDs not in `PROVIDER_UI_HINTS` render with a capitalize-
12
+ * fallback label and no hint -- new providers added by future pi-ai
13
+ * upgrades flow through automatically with no comis code change.
14
+ *
15
+ * This step only captures the provider choice. API credentials are
16
+ * collected in step 04 (credentials).
11
17
  *
12
18
  * @module
13
19
  */
@@ -2,21 +2,77 @@
2
2
  /**
3
3
  * Provider selection step -- step 03 of the init wizard.
4
4
  *
5
- * Presents all supported LLM providers in a flat select list
6
- * with per-provider hints. A category overview note is shown
7
- * before the selection to help the user orient. Anthropic is
5
+ * Presents the providers from the live pi-ai catalog (loaded via
6
+ * `loadProvidersWithFallback` -- daemon RPC first, local pi-ai catalog
7
+ * fallback for the pre-init use case) plus a synthetic "custom" option
8
+ * appended last for OpenAI-compatible endpoints. A category overview
9
+ * note is shown before selection to help the user orient. Anthropic is
8
10
  * pre-selected as the recommended default.
9
11
  *
10
- * This step only captures the provider choice. API credentials
11
- * are collected in step 04 (credentials).
12
+ * Provider IDs not in `PROVIDER_UI_HINTS` render with a capitalize-
13
+ * fallback label and no hint -- new providers added by future pi-ai
14
+ * upgrades flow through automatically with no comis code change.
15
+ *
16
+ * This step only captures the provider choice. API credentials are
17
+ * collected in step 04 (credentials).
12
18
  *
13
19
  * @module
14
20
  */
15
- import { updateState, sectionSeparator, SUPPORTED_PROVIDERS } from "../index.js";
21
+ import { updateState, sectionSeparator } from "../index.js";
22
+ import { loadProvidersWithFallback } from "../../client/provider-list.js";
23
+ /**
24
+ * UX-only labels and hints for known providers.
25
+ *
26
+ * Keys here are NOT a closed set of supported providers -- the actual
27
+ * supported set is whatever the pi-ai catalog exposes via
28
+ * `loadProvidersWithFallback()`. This map provides nicer labels for
29
+ * commonly-known providers; unknown providers use the capitalize-
30
+ * fallback path in `getProviderHint`.
31
+ *
32
+ * Excluded: `together` and `ollama`. Neither is in pi-ai 0.71.0's
33
+ * `getProviders()` catalog (`getModels(p)[0]?.baseUrl` is undefined for
34
+ * both), so an entry here would be dead code -- the live catalog
35
+ * never returns those IDs. The capitalize-fallback handles them if
36
+ * pi-ai adds them in a future release.
37
+ */
38
+ const PROVIDER_UI_HINTS = {
39
+ anthropic: { label: "Anthropic (Claude)", hint: "Recommended for agents", category: "recommended" },
40
+ openai: { label: "OpenAI (GPT)", hint: "GPT-4o, o1, o3 models", category: "recommended" },
41
+ google: { label: "Google (Gemini)", hint: "Gemini models", category: "other" },
42
+ groq: { label: "Groq", hint: "Fast inference (Llama, Mixtral)", category: "other" },
43
+ mistral: { label: "Mistral", hint: "Mistral models", category: "other" },
44
+ deepseek: { label: "DeepSeek", hint: "DeepSeek models", category: "other" },
45
+ xai: { label: "xAI (Grok)", hint: "Grok models", category: "other" },
46
+ cerebras: { label: "Cerebras", hint: "Fast inference", category: "other" },
47
+ openrouter: { label: "OpenRouter", hint: "Multi-provider routing", category: "other" },
48
+ };
49
+ /**
50
+ * Resolve the UX hint for a provider id. Returns the static hint when
51
+ * present, otherwise computes a capitalize-fallback (matches the web
52
+ * wizard's pattern at packages/web/src/views/setup-wizard.ts:180-193).
53
+ */
54
+ function getProviderHint(id) {
55
+ // eslint-disable-next-line security/detect-object-injection -- gated by Object.hasOwn against literal record (mirrors web wizard pattern)
56
+ if (Object.hasOwn(PROVIDER_UI_HINTS, id))
57
+ return PROVIDER_UI_HINTS[id];
58
+ return {
59
+ label: id.charAt(0).toUpperCase() + id.slice(1),
60
+ hint: undefined,
61
+ category: "other",
62
+ };
63
+ }
16
64
  // ---------- Category Overview ----------
65
+ /**
66
+ * Static overview note shown before the provider select.
67
+ *
68
+ * May drift from the live catalog (e.g., if pi-ai adds a new provider,
69
+ * users still see the same note). Computing this from
70
+ * PROVIDER_UI_HINTS + the loaded catalog is a future enhancement;
71
+ * static is acceptable for v1 since the categories rarely change.
72
+ */
17
73
  const CATEGORY_NOTE = [
18
74
  "Recommended: Anthropic, OpenAI",
19
- "Other: Google, Groq, Mistral, DeepSeek, xAI, Together, Cerebras, OpenRouter",
75
+ "Other: Google, Groq, Mistral, DeepSeek, xAI, Cerebras, OpenRouter",
20
76
  "Local: Ollama (no API key)",
21
77
  "Custom: Your own endpoint",
22
78
  ].join("\n");
@@ -28,12 +84,18 @@ export const providerStep = {
28
84
  prompter.note(sectionSeparator("LLM Provider"));
29
85
  // Show category grouping overview before selection
30
86
  prompter.note(CATEGORY_NOTE, "Available Providers");
31
- // Build flat options list from SUPPORTED_PROVIDERS
32
- const options = SUPPORTED_PROVIDERS.map((p) => ({
33
- value: p.id,
34
- label: p.label,
35
- hint: p.hint,
36
- }));
87
+ // Build option list from live catalog (RPC-first, local fallback)
88
+ const providerIds = await loadProvidersWithFallback();
89
+ const options = providerIds.map((id) => {
90
+ const hint = getProviderHint(id);
91
+ return { value: id, label: hint.label, hint: hint.hint };
92
+ });
93
+ // Synthetic Custom option (always last, never in catalog)
94
+ options.push({
95
+ value: "custom",
96
+ label: "Custom endpoint",
97
+ hint: "OpenAI-compatible API",
98
+ });
37
99
  const selectedId = await prompter.select({
38
100
  message: "Which LLM provider will you use?",
39
101
  options,