pi-nvidia-nim 1.1.21 → 1.1.23

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 (3) hide show
  1. package/README.md +17 -12
  2. package/index.ts +101 -84
  3. package/package.json +9 -4
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-nvidia-nim
2
2
 
3
- NVIDIA NIM API provider extension for [pi coding agent](https://github.com/earendil-works/pi-mono) - access 100+ models from [build.nvidia.com](https://build.nvidia.com) including DeepSeek V4 Flash/Pro, DeepSeek V3.2, Kimi K2.6, MiniMax M2.1, GLM-5, GLM-4.7, Qwen3, Llama 4, and many more.
3
+ NVIDIA NIM API provider extension for [pi coding agent](https://github.com/earendil-works/pi-mono) - access 100+ models from [build.nvidia.com](https://build.nvidia.com) including Inkling, MiniMax M3, DeepSeek V4 Flash/Pro, DeepSeek V3.2, Kimi K2.6, GLM-5, GLM-4.7, Qwen3, Llama 4, and many more.
4
4
 
5
5
  https://github.com/user-attachments/assets/f44773e4-9bf8-4bb5-a9c0-d5938030701c
6
6
 
@@ -74,35 +74,40 @@ NVIDIA NIM models use a non-standard `chat_template_kwargs` parameter to enable
74
74
 
75
75
  When you change the thinking level in pi (`Shift+Tab` to cycle), the extension:
76
76
 
77
- 1. **Maps thinking levels** to values each NIM model accepts. For DeepSeek V4, `xhigh` maps to `max`; lower enabled levels use `high`.
77
+ 1. **Maps thinking levels** to values each NIM model accepts. MiniMax M3 uses its native `disabled`/`adaptive`/`enabled` modes; Inkling and DeepSeek V4 map extended levels to `max`.
78
78
  2. **Injects `chat_template_kwargs`** per model to actually enable thinking:
79
+ - Inkling: `{ reasoning_effort: "none" | "minimal" | "low" | "medium" | "high" | "max" }`
80
+ - MiniMax M3: `{ thinking_mode: "disabled" | "adaptive" | "enabled" }`
79
81
  - DeepSeek V4: `{ thinking: true, reasoning_effort: "high" | "max" }`
80
82
  - DeepSeek V3.x, R1 distills: `{ thinking: true }`
81
83
  - GLM-5, GLM-4.7: `{ enable_thinking: true, clear_thinking: false }`
82
84
  - Kimi K2.6, K2-thinking: `{ thinking: true }`
83
85
  - Qwen3, QwQ: `{ enable_thinking: true }`
84
- 3. **Explicitly disables thinking** when the level is "off" for models that think by default (e.g., GLM-5, GLM-4.7).
86
+ 3. **Explicitly selects the lowest/off setting** when thinking is "off" for configurable models. MiniMax M3 uses `disabled`; Inkling uses `none` as a conditioning hint rather than a guarantee of zero reasoning tokens.
85
87
  4. **Uses `system` role** instead of `developer` for all NIM models - the `developer` role combined with `chat_template_kwargs` causes 500 errors on NIM.
86
88
 
87
89
  ### Supported thinking levels
88
90
 
89
91
  | pi Level | NIM Mapping | Effect |
90
92
  |----------|-------------|--------|
91
- | off | No kwargs (or explicit disable) | No reasoning output |
92
- | minimal | low, or high for DeepSeek V4 | Thinking enabled |
93
- | low | low, or high for DeepSeek V4 | Thinking enabled |
94
- | medium | medium, or high for DeepSeek V4 | Thinking enabled |
95
- | high | high | Thinking enabled |
96
- | xhigh | high, or max for DeepSeek V4 | Maximum supported thinking |
93
+ | off | MiniMax M3 `disabled`; Inkling `none`; explicit disable elsewhere | No/lowest reasoning effort |
94
+ | minimal | MiniMax M3 `adaptive`; Inkling `minimal`; low/high where required | Thinking enabled when useful |
95
+ | low | MiniMax M3 `adaptive`; low, or high for DeepSeek V4 | Thinking enabled when useful |
96
+ | medium | MiniMax M3 `adaptive`; medium, or high for DeepSeek V4 | Thinking enabled when useful |
97
+ | high | MiniMax M3 `enabled`; high elsewhere | Thinking enabled |
98
+ | xhigh | MiniMax M3 `enabled`; max for Inkling/DeepSeek V4 | Extended thinking |
99
+ | max | MiniMax M3 `enabled`; max for Inkling/DeepSeek V4 | Maximum supported thinking |
97
100
 
98
101
  ## Available Models
99
102
 
100
- The extension ships with curated metadata for 42 featured models. At startup, it also queries the NVIDIA NIM API to discover additional models automatically.
103
+ The extension ships with curated metadata for 44 featured models. At startup, it also queries the NVIDIA NIM API to discover additional models automatically.
101
104
 
102
105
  ### Featured Models
103
106
 
104
107
  | Model | Reasoning | Vision | Context |
105
108
  |-------|-----------|--------|---------|
109
+ | `thinkingmachines/inkling` | ✅ | ✅ | 1M |
110
+ | `minimaxai/minimax-m3` | ✅ | ✅ | 1M |
106
111
  | `deepseek-ai/deepseek-v4-flash` | ✅ | | 1M |
107
112
  | `deepseek-ai/deepseek-v4-pro` | ✅ | | 1M |
108
113
  | `deepseek-ai/deepseek-v3.2` | ✅ | | 128K |
@@ -138,7 +143,7 @@ This extension uses `pi.registerProvider()` to register NVIDIA NIM as a custom p
138
143
  The custom streamer:
139
144
  1. Intercepts the request payload via `onPayload` callback
140
145
  2. Injects `chat_template_kwargs` for models that need it to enable thinking
141
- 3. Maps unsupported thinking levels to NIM-compatible values (`minimal` `low`; `xhigh` `high` or DeepSeek V4 `max`)
146
+ 3. Maps unsupported thinking levels to NIM-compatible values while preserving MiniMax M3's native modes and Inkling's native effort presets
142
147
  4. Suppresses `reasoning_effort` for models that don't respond to it (e.g., DeepSeek without kwargs)
143
148
  5. Uses the standard OpenAI SSE streaming format - pi already parses `reasoning_content` and `reasoning` fields from streaming deltas
144
149
 
@@ -153,7 +158,7 @@ The only configuration needed is either the `NVIDIA_NIM_API_KEY` or `NVIDIA_API_
153
158
  - If a model isn't in the curated list, it gets a conservative 32K context window and 8K max output tokens
154
159
  - The extension filters out embedding, reward, safety, and other non-chat models automatically
155
160
  - Rate limits on free preview keys are relatively strict; you may encounter 429 errors during heavy usage
156
- - MiniMax models use `<think>` tags inline in content rather than the `reasoning_content` field
161
+ - MiniMax M3 streams thinking through `reasoning_content`; older MiniMax models may use `<think>` tags inline in content
157
162
 
158
163
  ## License
159
164
 
package/index.ts CHANGED
@@ -34,17 +34,16 @@
34
34
  * thinking settings.
35
35
  */
36
36
 
37
- import { existsSync, readFileSync } from "node:fs";
38
- import { join } from "node:path";
39
37
  import type {
40
38
  Api,
41
39
  AssistantMessageEventStream,
42
40
  Context,
43
41
  Model,
44
42
  SimpleStreamOptions,
43
+ ThinkingLevelMap,
45
44
  } from "@earendil-works/pi-ai";
46
- import { streamSimpleOpenAICompletions } from "@earendil-works/pi-ai";
47
- import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
45
+ import { streamSimpleOpenAICompletions } from "@earendil-works/pi-ai/compat";
46
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
48
47
 
49
48
  // =============================================================================
50
49
  // Constants
@@ -55,6 +54,8 @@ const NVIDIA_NIM_API_KEY_ENV = "NVIDIA_NIM_API_KEY";
55
54
  const NVIDIA_API_KEY_ENV = "NVIDIA_API_KEY";
56
55
  const NVIDIA_API_KEY_ENV_NAMES = [NVIDIA_NIM_API_KEY_ENV, NVIDIA_API_KEY_ENV] as const;
57
56
  const PROVIDER_NAME = "nvidia-nim";
57
+ const INKLING_MODEL_ID = "thinkingmachines/inkling";
58
+ const MINIMAX_M3_MODEL_ID = "minimaxai/minimax-m3";
58
59
 
59
60
  // =============================================================================
60
61
  // Per-model thinking configuration
@@ -185,7 +186,11 @@ const THINKING_CONFIGS: Record<string, ThinkingConfig> = {
185
186
  // Reasoning models and their capabilities
186
187
  // =============================================================================
187
188
 
188
- const REASONING_MODELS = new Set(Object.keys(THINKING_CONFIGS));
189
+ const REASONING_MODELS = new Set([
190
+ ...Object.keys(THINKING_CONFIGS),
191
+ INKLING_MODEL_ID,
192
+ MINIMAX_M3_MODEL_ID,
193
+ ]);
189
194
 
190
195
  // Models known to support image/vision input
191
196
  const VISION_MODELS = new Set([
@@ -197,6 +202,8 @@ const VISION_MODELS = new Set([
197
202
  "nvidia/llama-3.1-nemotron-nano-vl-8b-v1",
198
203
  "nvidia/nemotron-nano-12b-v2-vl",
199
204
  "nvidia/cosmos-reason2-8b",
205
+ INKLING_MODEL_ID,
206
+ MINIMAX_M3_MODEL_ID,
200
207
  ]);
201
208
 
202
209
  // Embedding / non-chat models to skip
@@ -269,6 +276,7 @@ const CONTEXT_WINDOWS: Record<string, number> = {
269
276
  "minimaxai/minimax-m2": 1048576,
270
277
  "minimaxai/minimax-m2.1": 1048576,
271
278
  "minimaxai/minimax-m2.7": 204800,
279
+ [MINIMAX_M3_MODEL_ID]: 1_048_576,
272
280
  // Meta Llama
273
281
  "meta/llama-3.1-405b-instruct": 131072,
274
282
  "meta/llama-3.1-70b-instruct": 131072,
@@ -339,6 +347,8 @@ const CONTEXT_WINDOWS: Record<string, number> = {
339
347
  "nvidia/llama-3.3-nemotron-super-49b-v1.5": 131072,
340
348
  "nvidia/nemotron-4-340b-instruct": 4096,
341
349
  "nvidia/nvidia-nemotron-nano-9b-v2": 131072,
350
+ // Thinking Machines
351
+ [INKLING_MODEL_ID]: 1_048_576,
342
352
  // OpenAI open-source
343
353
  "openai/gpt-oss-120b": 131072,
344
354
  "openai/gpt-oss-20b": 131072,
@@ -396,6 +406,7 @@ const MAX_TOKENS: Record<string, number> = {
396
406
  "minimaxai/minimax-m2": 8192,
397
407
  "minimaxai/minimax-m2.1": 8192,
398
408
  "minimaxai/minimax-m2.7": 8192,
409
+ [MINIMAX_M3_MODEL_ID]: 16_384,
399
410
  "meta/llama-4-maverick-17b-128e-instruct": 16384,
400
411
  "meta/llama-4-scout-17b-16e-instruct": 16384,
401
412
  "z-ai/glm4.7": 16384,
@@ -406,6 +417,7 @@ const MAX_TOKENS: Record<string, number> = {
406
417
  "openai/gpt-oss-20b": 16384,
407
418
  "mistralai/mistral-large-3-675b-instruct-2512": 16384,
408
419
  "mistralai/devstral-2-123b-instruct-2512": 32768,
420
+ [INKLING_MODEL_ID]: 16_384,
409
421
  };
410
422
 
411
423
  // =============================================================================
@@ -423,6 +435,7 @@ const FEATURED_MODELS = [
423
435
  "moonshotai/kimi-k2-thinking",
424
436
  "moonshotai/kimi-k2-instruct",
425
437
  "moonshotai/kimi-k2-instruct-0905",
438
+ MINIMAX_M3_MODEL_ID,
426
439
  "minimaxai/minimax-m2.1",
427
440
  "minimaxai/minimax-m2",
428
441
  "minimaxai/minimax-m2.7",
@@ -432,6 +445,7 @@ const FEATURED_MODELS = [
432
445
  "openai/gpt-oss-20b",
433
446
  "stepfun-ai/step-3.5-flash",
434
447
  "bytedance/seed-oss-36b-instruct",
448
+ INKLING_MODEL_ID,
435
449
  // Qwen
436
450
  "qwen/qwen3-coder-480b-a35b-instruct",
437
451
  "qwen/qwen3-235b-a22b",
@@ -479,14 +493,6 @@ const FEATURED_MODELS = [
479
493
  * 4. Uses onPayload callback to mutate request params before they're sent
480
494
  */
481
495
  type NimApiKeyEnvName = (typeof NVIDIA_API_KEY_ENV_NAMES)[number];
482
- type AuthStorageLike = {
483
- get?: (provider: string) => unknown;
484
- };
485
-
486
- interface NimApiKeyCredential {
487
- type: "api_key";
488
- key: string;
489
- }
490
496
 
491
497
  function getNimApiKeyEnv(): NimApiKeyEnvName | undefined {
492
498
  return NVIDIA_API_KEY_ENV_NAMES.find((envName) => !!process.env[envName]);
@@ -498,59 +504,27 @@ function getNimApiKey(): string | undefined {
498
504
  return apiKey?.trim() || undefined;
499
505
  }
500
506
 
501
- function isNimApiKeyEnvName(value: string): value is NimApiKeyEnvName {
502
- return NVIDIA_API_KEY_ENV_NAMES.includes(value as NimApiKeyEnvName);
503
- }
504
-
505
- function isNimApiKeyEnvValue(value: string): boolean {
506
- return NVIDIA_API_KEY_ENV_NAMES.some((envName) => process.env[envName]?.trim() === value);
507
+ function getNimProviderApiKeyConfig(): string {
508
+ return `$${getNimApiKeyEnv() ?? NVIDIA_NIM_API_KEY_ENV}`;
507
509
  }
508
510
 
509
- function isNimApiKeyCredential(credential: unknown): credential is NimApiKeyCredential {
510
- return (
511
- typeof credential === "object" &&
512
- credential !== null &&
513
- (credential as { type?: unknown }).type === "api_key" &&
514
- typeof (credential as { key?: unknown }).key === "string"
515
- );
516
- }
517
-
518
- function readStoredNimApiKeyConfig(): string | undefined {
519
- try {
520
- const authPath = join(getAgentDir(), "auth.json");
521
- if (!existsSync(authPath)) return undefined;
522
-
523
- const data = JSON.parse(readFileSync(authPath, "utf-8")) as Record<string, unknown>;
524
- const credential = data[PROVIDER_NAME];
525
- return isNimApiKeyCredential(credential) ? credential.key : undefined;
526
- } catch {
527
- return undefined;
528
- }
511
+ function isNimApiKeyEnvName(value: string): value is NimApiKeyEnvName {
512
+ return NVIDIA_API_KEY_ENV_NAMES.includes(value as NimApiKeyEnvName);
529
513
  }
530
514
 
531
- function getStoredNimApiKeyConfig(authStorage?: AuthStorageLike): string | undefined {
532
- if (authStorage) {
533
- try {
534
- const credential = authStorage.get?.(PROVIDER_NAME);
535
- return isNimApiKeyCredential(credential) ? credential.key : undefined;
536
- } catch {
537
- return undefined;
538
- }
539
- }
540
-
541
- return readStoredNimApiKeyConfig();
515
+ function isNimApiKeyEnvReference(value: string): boolean {
516
+ return value.startsWith("$") && isNimApiKeyEnvName(value.slice(1));
542
517
  }
543
518
 
544
- function hasStoredNimCommandCredential(authStorage?: AuthStorageLike): boolean {
545
- return getStoredNimApiKeyConfig(authStorage)?.startsWith("!") ?? false;
519
+ function isNimApiKeyEnvPlaceholder(value: string): boolean {
520
+ return isNimApiKeyEnvName(value) || isNimApiKeyEnvReference(value);
546
521
  }
547
522
 
548
- function getStoredResolvedNimApiKey(authStorage?: AuthStorageLike): string | undefined {
549
- const configuredApiKey = getStoredNimApiKeyConfig(authStorage)?.trim();
550
- if (!configuredApiKey || configuredApiKey.startsWith("!")) return undefined;
523
+ function resolveNimApiKeyEnvReference(value: string): string | undefined {
524
+ if (!isNimApiKeyEnvReference(value)) return undefined;
551
525
 
552
- const envValue = process.env[configuredApiKey]?.trim();
553
- return envValue || configuredApiKey;
526
+ const envValue = process.env[value.slice(1)]?.trim();
527
+ return envValue || undefined;
554
528
  }
555
529
 
556
530
  function normalizeResolvedNimApiKey(apiKey: string | undefined): string | undefined {
@@ -564,21 +538,19 @@ function normalizeResolvedNimApiKey(apiKey: string | undefined): string | undefi
564
538
  return trimmed;
565
539
  }
566
540
 
567
- function resolveNimApiKey(apiKey: string | undefined, authStorage?: AuthStorageLike): string | undefined {
541
+ function resolveNimApiKey(apiKey: string | undefined): string | undefined {
568
542
  const resolvedApiKey = normalizeResolvedNimApiKey(apiKey);
569
- const hasStoredCommandCredential = hasStoredNimCommandCredential(authStorage);
570
543
 
571
- if (
572
- hasStoredCommandCredential &&
573
- (!resolvedApiKey || isNimApiKeyEnvName(resolvedApiKey) || isNimApiKeyEnvValue(resolvedApiKey))
574
- ) {
575
- throw new Error("NVIDIA NIM API key command resolved to an empty value.");
576
- }
544
+ if (resolvedApiKey) {
545
+ const envReferenceApiKey = resolveNimApiKeyEnvReference(resolvedApiKey);
546
+ if (envReferenceApiKey) return envReferenceApiKey;
577
547
 
578
- const storedApiKey = getStoredResolvedNimApiKey(authStorage);
579
- if (storedApiKey && (!resolvedApiKey || isNimApiKeyEnvName(resolvedApiKey))) return storedApiKey;
548
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(resolvedApiKey) && Object.hasOwn(process.env, resolvedApiKey)) {
549
+ return normalizeResolvedNimApiKey(process.env[resolvedApiKey]);
550
+ }
580
551
 
581
- if (resolvedApiKey && !isNimApiKeyEnvName(resolvedApiKey)) return resolvedApiKey;
552
+ if (!isNimApiKeyEnvPlaceholder(resolvedApiKey)) return resolvedApiKey;
553
+ }
582
554
 
583
555
  return getNimApiKey();
584
556
  }
@@ -615,6 +587,20 @@ function buildThinkingKwargs(
615
587
  return kwargs;
616
588
  }
617
589
 
590
+ function buildNimRequestHeaders(headers: SimpleStreamOptions["headers"], apiKey: string): Record<string, string> {
591
+ const resolvedHeaders: Record<string, string> = {};
592
+
593
+ for (const [key, value] of Object.entries(headers ?? {})) {
594
+ if (key.toLowerCase() === "authorization" || value === null) continue;
595
+ resolvedHeaders[key] = value;
596
+ }
597
+
598
+ return {
599
+ ...resolvedHeaders,
600
+ Authorization: `Bearer ${apiKey}`,
601
+ };
602
+ }
603
+
618
604
  function nimStreamSimple(
619
605
  model: Model<Api>,
620
606
  context: Context,
@@ -632,9 +618,9 @@ function nimStreamSimple(
632
618
  const reasoning = options?.reasoning;
633
619
  const isThinkingEnabled = !!reasoning;
634
620
 
635
- // Map provider-agnostic pi levels to NIM's accepted top-level values.
636
- // Model-specific chat_template_kwargs may apply a different mapping below.
637
- const mappedReasoning = mapNimTopLevelReasoning(reasoning);
621
+ // Custom NIM thinking configs use the provider-wide effort mapping. Models that
622
+ // declare native chat-template compatibility keep their own named levels.
623
+ const mappedReasoning = thinkingConfig ? mapNimTopLevelReasoning(reasoning) : reasoning;
638
624
 
639
625
  // For models that have a thinking config: we handle thinking via chat_template_kwargs.
640
626
  // Suppress reasoning_effort (set reasoning to undefined) unless the model explicitly
@@ -654,6 +640,7 @@ function nimStreamSimple(
654
640
  ...options,
655
641
  reasoning: effectiveReasoning,
656
642
  apiKey: nimApiKey,
643
+ headers: buildNimRequestHeaders(options?.headers, nimApiKey),
657
644
  onPayload: (params: unknown) => {
658
645
  const p = params as Record<string, unknown>;
659
646
 
@@ -705,6 +692,7 @@ interface NimModelEntry {
705
692
  id: string;
706
693
  name: string;
707
694
  reasoning: boolean;
695
+ thinkingLevelMap?: ThinkingLevelMap;
708
696
  input: ("text" | "image")[];
709
697
  contextWindow: number;
710
698
  maxTokens: number;
@@ -750,6 +738,35 @@ function buildModelEntry(modelId: string): NimModelEntry | null {
750
738
  maxTokensField: "max_tokens",
751
739
  };
752
740
 
741
+ // Inkling conditions reasoning through its chat template rather than a top-level
742
+ // reasoning_effort field. NVIDIA NIM accepts the same named effort presets as
743
+ // Inkling's tokenizer; map pi's extended levels to Inkling's 0.99 preset.
744
+ if (modelId === INKLING_MODEL_ID) {
745
+ entry.thinkingLevelMap = { off: "none", xhigh: "max", max: "max" };
746
+ entry.compat.thinkingFormat = "chat-template";
747
+ entry.compat.chatTemplateKwargs = {
748
+ reasoning_effort: { $var: "thinking.effort" },
749
+ };
750
+ }
751
+
752
+ // MiniMax M3 exposes discrete disabled/adaptive/enabled reasoning modes through
753
+ // chat_template_kwargs.thinking_mode. Map pi's effort scale to the closest mode.
754
+ if (modelId === MINIMAX_M3_MODEL_ID) {
755
+ entry.thinkingLevelMap = {
756
+ off: "disabled",
757
+ minimal: "adaptive",
758
+ low: "adaptive",
759
+ medium: "adaptive",
760
+ high: "enabled",
761
+ xhigh: "enabled",
762
+ max: "enabled",
763
+ };
764
+ entry.compat.thinkingFormat = "chat-template";
765
+ entry.compat.chatTemplateKwargs = {
766
+ thinking_mode: { $var: "thinking.effort" },
767
+ };
768
+ }
769
+
753
770
  // Mistral models on NIM need extra compat flags
754
771
  if (modelId.startsWith("mistralai/")) {
755
772
  entry.compat.requiresToolResultName = true;
@@ -781,14 +798,17 @@ function sanitizeNimLogMessage(message: string): string {
781
798
  return message.replace(/nvapi-[A-Za-z0-9._-]+/g, "nvapi-[REDACTED]");
782
799
  }
783
800
 
784
- function notifyNimDiscoveryCredentialWarning(ctx: any): void {
785
- ctx?.ui?.notify?.(NIM_DISCOVERY_CREDENTIAL_WARNING, "warning");
801
+ function notifyNimDiscoveryCredentialWarning(ctx: ExtensionContext): void {
802
+ ctx.ui.notify(NIM_DISCOVERY_CREDENTIAL_WARNING, "warning");
786
803
  }
787
804
 
788
- async function resolveNimDiscoveryApiKey(ctx: any): Promise<string | undefined> {
805
+ async function resolveNimDiscoveryApiKey(ctx: ExtensionContext): Promise<string | undefined> {
789
806
  try {
790
- const apiKey = await ctx?.modelRegistry?.getApiKeyForProvider?.(PROVIDER_NAME);
791
- return resolveNimApiKey(apiKey, ctx?.modelRegistry?.authStorage);
807
+ const apiKey = await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_NAME);
808
+ if (apiKey === undefined && ctx.modelRegistry.getProviderAuthStatus(PROVIDER_NAME).configured) {
809
+ throw new Error("NVIDIA NIM configured credential resolved to an empty value.");
810
+ }
811
+ return resolveNimApiKey(apiKey);
792
812
  } catch (error) {
793
813
  const message = error instanceof Error ? error.message : String(error);
794
814
  console.warn(`pi-nvidia-nim: ${sanitizeNimLogMessage(message)}`);
@@ -834,7 +854,7 @@ async function fetchNimModels(apiKey: string): Promise<NimModelFetchResult> {
834
854
  // =============================================================================
835
855
 
836
856
  export default function (pi: ExtensionAPI) {
837
- const providerApiKeyConfig = getNimApiKeyEnv() ?? NVIDIA_NIM_API_KEY_ENV;
857
+ const providerApiKeyConfig = getNimProviderApiKeyConfig();
838
858
 
839
859
  // Always register the curated model list. The request path resolves credentials
840
860
  // through pi first (CLI override, auth.json, shell command), then falls back to
@@ -863,7 +883,7 @@ export default function (pi: ExtensionAPI) {
863
883
  });
864
884
 
865
885
  // On session start, discover additional models from the API
866
- pi.on("session_start", async (_event: any, ctx: any) => {
886
+ pi.on("session_start", async (_event, ctx) => {
867
887
  const apiKey = await resolveNimDiscoveryApiKey(ctx);
868
888
  if (!apiKey) return;
869
889
 
@@ -889,15 +909,12 @@ export default function (pi: ExtensionAPI) {
889
909
  }
890
910
  }
891
911
 
892
- // Re-register with full model list if we found new ones.
893
- // NOTE: must use ctx.modelRegistry.registerProvider() here, not pi.registerProvider().
894
- // pi.registerProvider() only queues registrations for the initial extension load.
895
- // From event handlers/commands, we need to call the registry directly.
912
+ // Runtime provider registrations now apply immediately through the extension API.
896
913
  if (newModelsAdded > 0) {
897
914
  const allModels = Array.from(modelMap.values());
898
- ctx.modelRegistry.registerProvider(PROVIDER_NAME, {
915
+ pi.registerProvider(PROVIDER_NAME, {
899
916
  baseUrl: NVIDIA_NIM_BASE_URL,
900
- apiKey: getNimApiKeyEnv() ?? NVIDIA_NIM_API_KEY_ENV,
917
+ apiKey: getNimProviderApiKeyConfig(),
901
918
  api: "openai-completions",
902
919
  authHeader: true,
903
920
  models: allModels,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-nvidia-nim",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "NVIDIA NIM API provider extension for pi coding agent — access 100+ models from build.nvidia.com",
5
5
  "type": "module",
6
6
  "files": [
@@ -23,9 +23,14 @@
23
23
  "test": "node --test test/*.test.mjs"
24
24
  },
25
25
  "devDependencies": {
26
- "@earendil-works/pi-ai": "^0.74.0",
27
- "@earendil-works/pi-coding-agent": "^0.74.0",
28
- "@types/node": "^22.0.0"
26
+ "@earendil-works/pi-ai": "^0.80.10",
27
+ "@earendil-works/pi-coding-agent": "^0.80.10",
28
+ "@types/node": "^24.0.0",
29
+ "typescript": "^6.0.3"
30
+ },
31
+ "peerDependencies": {
32
+ "@earendil-works/pi-ai": "*",
33
+ "@earendil-works/pi-coding-agent": "*"
29
34
  },
30
35
  "repository": {
31
36
  "type": "git",