@aliou/pi-neuralwatt 0.7.0 → 0.7.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aliou/pi-neuralwatt",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "private": false,
package/schema.json CHANGED
@@ -5,6 +5,10 @@
5
5
  "NeuralwattConfig": {
6
6
  "additionalProperties": false,
7
7
  "properties": {
8
+ "includeHiddenModels": {
9
+ "description": "Include hidden Neuralwatt models discovered via the authenticated API.",
10
+ "type": "boolean"
11
+ },
8
12
  "includeLegacyModelIds": {
9
13
  "description": "Include legacy Neuralwatt model IDs in the model picker.",
10
14
  "type": "boolean"
package/src/config.ts CHANGED
@@ -30,6 +30,8 @@ export interface NeuralwattConfig {
30
30
  subBarIntegration?: boolean;
31
31
  /** Include legacy Neuralwatt model IDs in the model picker. */
32
32
  includeLegacyModelIds?: boolean;
33
+ /** Include hidden Neuralwatt models discovered via the authenticated API. */
34
+ includeHiddenModels?: boolean;
33
35
  }
34
36
 
35
37
  export interface ResolvedNeuralwattConfig {
@@ -37,6 +39,7 @@ export interface ResolvedNeuralwattConfig {
37
39
  quotaWarnings: boolean;
38
40
  subBarIntegration: boolean;
39
41
  includeLegacyModelIds: boolean;
42
+ includeHiddenModels: boolean;
40
43
  }
41
44
 
42
45
  const DEFAULTS: ResolvedNeuralwattConfig = {
@@ -44,6 +47,7 @@ const DEFAULTS: ResolvedNeuralwattConfig = {
44
47
  quotaWarnings: true,
45
48
  subBarIntegration: true,
46
49
  includeLegacyModelIds: false,
50
+ includeHiddenModels: false,
47
51
  };
48
52
 
49
53
  export const configLoader = new ConfigLoader<
@@ -157,19 +161,36 @@ export function registerNeuralwattSettings(
157
161
  : "ignore",
158
162
  values: ["include", "ignore"],
159
163
  },
164
+ {
165
+ id: "includeHiddenModels",
166
+ label: "Hidden models",
167
+ description:
168
+ "Include Neuralwatt models that are accessible via API key but not advertised in the public model list",
169
+ currentValue:
170
+ (tabConfig?.includeHiddenModels ?? resolved.includeHiddenModels)
171
+ ? "include"
172
+ : "ignore",
173
+ values: ["include", "ignore"],
174
+ },
160
175
  ],
161
176
  },
162
177
  ];
163
178
  },
164
179
  onSettingChange: (id, newValue, config) => {
165
- if (!getLoadedFeatures().has(id as NeuralwattFeatureId)) {
166
- return null;
167
- }
168
-
180
+ // Non-feature toggles are handled first so they are not blocked by the
181
+ // loaded-features guard (they are managed directly by the provider).
169
182
  if (id === "includeLegacyModelIds") {
170
183
  return { ...config, includeLegacyModelIds: newValue === "include" };
171
184
  }
172
185
 
186
+ if (id === "includeHiddenModels") {
187
+ return { ...config, includeHiddenModels: newValue === "include" };
188
+ }
189
+
190
+ if (!getLoadedFeatures().has(id as NeuralwattFeatureId)) {
191
+ return null;
192
+ }
193
+
173
194
  const enabled = newValue === "enabled";
174
195
  switch (id) {
175
196
  case "quotaCommand":
@@ -4,7 +4,7 @@ import {
4
4
  getAgentDir,
5
5
  } from "@earendil-works/pi-coding-agent";
6
6
  import { getNeuralwattApiKey } from "../../lib/env";
7
- import { fetchQuotas } from "../../utils/quotas";
7
+ import { fetchQuotas } from "../../lib/neuralwatt-api";
8
8
  import { QuotasComponent } from "./components/quotas-display";
9
9
 
10
10
  function missingAuthMessage(): string {
@@ -1,5 +1,8 @@
1
1
  import { getApiProvider } from "@earendil-works/pi-ai";
2
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type {
3
+ ExtensionAPI,
4
+ ProviderModelConfig,
5
+ } from "@earendil-works/pi-coding-agent";
3
6
  import {
4
7
  configLoader,
5
8
  emitConfigUpdated,
@@ -10,15 +13,15 @@ import {
10
13
  registerNeuralwattSettings,
11
14
  } from "../../config";
12
15
  import { getNeuralwattApiKey } from "../../lib/env";
16
+ import { fetchQuotas } from "../../lib/neuralwatt-api";
13
17
  import type { NeuralwattQuotas } from "../../types/quota-api";
14
18
  import {
15
19
  NEURALWATT_QUOTAS_REQUEST_EVENT,
16
20
  NEURALWATT_QUOTAS_UPDATED_EVENT,
17
21
  type NeuralwattQuotasUpdatedPayload,
18
22
  } from "../../types/quota-events";
19
- import { fetchQuotas } from "../../utils/quotas";
20
23
  import { normalizeNeuralwattContextOverflowError } from "./context-overflow";
21
- import { getNeuralwattModels } from "./models";
24
+ import { getNeuralwattModels, loadHiddenModels } from "./models";
22
25
  import { buildQuotasFromHeaders, fetchRequestedQuotas } from "./quota-store";
23
26
  import {
24
27
  type NeuralwattRateLimitInfo,
@@ -33,8 +36,10 @@ const HEADER_EMIT_THROTTLE_MS = 5_000;
33
36
  function registerNeuralwattProvider(
34
37
  pi: ExtensionAPI,
35
38
  onSseQuota: (line: string) => void,
39
+ hiddenModels: ProviderModelConfig[] = [],
36
40
  ): void {
37
- const { includeLegacyModelIds } = configLoader.getConfig();
41
+ const { includeLegacyModelIds, includeHiddenModels } =
42
+ configLoader.getConfig();
38
43
 
39
44
  const config: Parameters<ExtensionAPI["registerProvider"]>[1] = {
40
45
  baseUrl: "https://api.neuralwatt.com/v1",
@@ -45,9 +50,10 @@ function registerNeuralwattProvider(
45
50
  Referer: "https://pi.dev",
46
51
  "X-Title": "npm:@aliou/pi-neuralwatt",
47
52
  },
48
- models: getNeuralwattModels({
49
- includeLegacyModelIds,
50
- }),
53
+ models: [
54
+ ...getNeuralwattModels({ includeLegacyModelIds }),
55
+ ...(includeHiddenModels ? hiddenModels : []),
56
+ ],
51
57
  };
52
58
 
53
59
  const provider = getApiProvider("openai-completions");
@@ -66,6 +72,8 @@ export default async function (pi: ExtensionAPI) {
66
72
  await configLoader.load();
67
73
 
68
74
  let latestQuotas: NeuralwattQuotas | undefined;
75
+ let hiddenModels: ProviderModelConfig[] = [];
76
+ let hiddenModelsLoaded = false;
69
77
 
70
78
  const handleSseQuota = (line: string) => {
71
79
  const quotas = updateQuotasFromSseComment(latestQuotas, line);
@@ -87,7 +95,7 @@ export default async function (pi: ExtensionAPI) {
87
95
  });
88
96
 
89
97
  pi.events.on(NEURALWATT_CONFIG_UPDATED_EVENT, () => {
90
- registerNeuralwattProvider(pi, handleSseQuota);
98
+ registerNeuralwattProvider(pi, handleSseQuota, hiddenModels);
91
99
  });
92
100
 
93
101
  let lastHeaderEmitAt = 0;
@@ -193,6 +201,15 @@ export default async function (pi: ExtensionAPI) {
193
201
  pi.events.emit(NEURALWATT_EXTENSIONS_REQUEST_EVENT, undefined);
194
202
  emitConfigUpdated(pi);
195
203
 
204
+ if (!hiddenModelsLoaded && configLoader.getConfig().includeHiddenModels) {
205
+ hiddenModelsLoaded = true;
206
+ const fetched = await loadHiddenModels(ctx.modelRegistry.authStorage);
207
+ if (fetched.length > 0) {
208
+ hiddenModels = fetched;
209
+ registerNeuralwattProvider(pi, handleSseQuota, hiddenModels);
210
+ }
211
+ }
212
+
196
213
  if (ctx.model?.provider !== "neuralwatt") return;
197
214
  const apiKey = await getNeuralwattApiKey(ctx.modelRegistry.authStorage);
198
215
  if (!apiKey) return;
@@ -0,0 +1,127 @@
1
+ import type {
2
+ AuthStorage,
3
+ ProviderModelConfig,
4
+ } from "@earendil-works/pi-coding-agent";
5
+ import { getNeuralwattApiKey } from "../../../lib/env";
6
+ import { fetchNeuralwattModels } from "../../../lib/neuralwatt-api";
7
+ import type { NeuralwattApiModel } from "../../../types/models-api";
8
+ import { NEURALWATT_MODELS } from "./public-models";
9
+
10
+ // Per-ID overrides for known hidden models. The authenticated /v1/models endpoint
11
+ // exposes pricing and capabilities, but some Pi-specific behavior (thinking levels,
12
+ // compat flags) has to be supplied by hand.
13
+ const HIDDEN_MODEL_OVERRIDES: Partial<
14
+ Record<string, Partial<ProviderModelConfig>>
15
+ > = {
16
+ "glm-5.2-short": {
17
+ thinkingLevelMap: {
18
+ minimal: null,
19
+ low: null,
20
+ medium: null,
21
+ high: "high",
22
+ xhigh: "max",
23
+ },
24
+ },
25
+ };
26
+
27
+ function buildHiddenModel(apiModel: NeuralwattApiModel): ProviderModelConfig {
28
+ const meta = apiModel.metadata;
29
+ const reasoning = meta?.capabilities.reasoning ?? false;
30
+ const override = HIDDEN_MODEL_OVERRIDES[apiModel.id];
31
+
32
+ const compat: NonNullable<ProviderModelConfig["compat"]> = {
33
+ supportsDeveloperRole: false,
34
+ maxTokensField: "max_tokens",
35
+ };
36
+ if (reasoning) {
37
+ compat.requiresReasoningContentOnAssistantMessages = true;
38
+ }
39
+
40
+ const model: ProviderModelConfig = {
41
+ id: apiModel.id,
42
+ name: meta?.display_name ?? apiModel.id,
43
+ reasoning,
44
+ input: (meta?.capabilities.vision ? ["text", "image"] : ["text"]) as (
45
+ | "text"
46
+ | "image"
47
+ )[],
48
+ cost: {
49
+ input: meta?.pricing.input_per_million ?? 0,
50
+ output: meta?.pricing.output_per_million ?? 0,
51
+ cacheRead: meta?.pricing.cached_input_per_million ?? 0,
52
+ cacheWrite: meta?.pricing.cached_output_per_million ?? 0,
53
+ },
54
+ contextWindow: apiModel.max_model_len,
55
+ maxTokens: meta?.limits.max_output_tokens ?? 65536,
56
+ compat,
57
+ };
58
+
59
+ if (reasoning) {
60
+ model.thinkingLevelMap = override?.thinkingLevelMap ?? {
61
+ minimal: null,
62
+ low: null,
63
+ medium: "medium",
64
+ high: null,
65
+ xhigh: null,
66
+ };
67
+ }
68
+
69
+ if (override) {
70
+ return applyHiddenOverride(model, override);
71
+ }
72
+
73
+ return model;
74
+ }
75
+
76
+ function applyHiddenOverride(
77
+ model: ProviderModelConfig,
78
+ override: Partial<ProviderModelConfig>,
79
+ ): ProviderModelConfig {
80
+ const result: ProviderModelConfig = { ...model };
81
+
82
+ if (override.name !== undefined) result.name = override.name;
83
+ if (override.reasoning !== undefined) result.reasoning = override.reasoning;
84
+ if (override.input !== undefined) result.input = override.input;
85
+ if (override.thinkingLevelMap !== undefined) {
86
+ result.thinkingLevelMap = override.thinkingLevelMap;
87
+ }
88
+ if (override.contextWindow !== undefined) {
89
+ result.contextWindow = override.contextWindow;
90
+ }
91
+ if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
92
+ if (override.cost !== undefined) {
93
+ result.cost = { ...model.cost, ...override.cost };
94
+ }
95
+ if (override.compat !== undefined) {
96
+ result.compat = { ...model.compat, ...override.compat };
97
+ }
98
+
99
+ return result;
100
+ }
101
+
102
+ /**
103
+ * Load hidden models from the authenticated /v1/models endpoint.
104
+ *
105
+ * Hidden models are any models returned by the API that are not already part of
106
+ * the public hardcoded list. If the API key is missing or the request fails, an
107
+ * empty array is returned silently.
108
+ */
109
+ export async function loadHiddenModels(
110
+ authStorage: AuthStorage,
111
+ ): Promise<ProviderModelConfig[]> {
112
+ const apiKey = await getNeuralwattApiKey(authStorage);
113
+ if (!apiKey) return [];
114
+
115
+ const result = await fetchNeuralwattModels(apiKey);
116
+ if (!result.success) return [];
117
+
118
+ const publicIds = new Set(NEURALWATT_MODELS.map((model) => model.id));
119
+
120
+ return result.data
121
+ .filter(
122
+ (model) =>
123
+ !model.metadata?.deprecated && !model.metadata?.pricing.pricing_tbd,
124
+ )
125
+ .filter((model) => !publicIds.has(model.id))
126
+ .map(buildHiddenModel);
127
+ }
@@ -0,0 +1,23 @@
1
+ import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
2
+ import { buildLegacyNeuralwattModels } from "./legacy";
3
+ import { NEURALWATT_MODELS } from "./public-models";
4
+
5
+ export { loadHiddenModels } from "./hidden";
6
+ export {
7
+ buildLegacyNeuralwattModels,
8
+ LEGACY_MODEL_ALIAS_MAP,
9
+ LEGACY_NEURALWATT_MODEL_IDS,
10
+ } from "./legacy";
11
+ export { NEURALWATT_MODELS } from "./public-models";
12
+
13
+ export function getNeuralwattModels(options?: {
14
+ includeLegacyModelIds?: boolean;
15
+ }): ProviderModelConfig[] {
16
+ const models: ProviderModelConfig[] = [...NEURALWATT_MODELS];
17
+
18
+ if (options?.includeLegacyModelIds) {
19
+ models.push(...buildLegacyNeuralwattModels());
20
+ }
21
+
22
+ return models;
23
+ }
@@ -0,0 +1,37 @@
1
+ import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
2
+ import { NEURALWATT_MODELS } from "./public-models";
3
+
4
+ // Legacy model IDs that should resolve to a canonical public model.
5
+ // These are phased out over time and are only included when `includeLegacyModelIds` is enabled.
6
+ export const LEGACY_MODEL_ALIAS_MAP = {
7
+ "glm-5.1": "glm-5.2",
8
+ "glm-5.1-fast": "glm-5.2-fast",
9
+ "zai-org/GLM-5.1-FP8": "glm-5.2",
10
+ "moonshotai/Kimi-K2.6": "kimi-k2.6",
11
+ "Qwen/Qwen3.5-397B-A17B-FP8": "qwen3.5-397b",
12
+ "Qwen/Qwen3.6-35B-A3B": "qwen3.6-35b",
13
+ } as const;
14
+
15
+ export const LEGACY_NEURALWATT_MODEL_IDS = new Set<string>(
16
+ Object.keys(LEGACY_MODEL_ALIAS_MAP),
17
+ );
18
+
19
+ export function buildLegacyNeuralwattModels(): ProviderModelConfig[] {
20
+ return Object.entries(LEGACY_MODEL_ALIAS_MAP).map(
21
+ ([legacyId, canonicalId]) => {
22
+ const canonical = NEURALWATT_MODELS.find(
23
+ (model) => model.id === canonicalId,
24
+ );
25
+
26
+ if (!canonical) {
27
+ throw new Error(`Missing canonical model for legacy alias ${legacyId}`);
28
+ }
29
+
30
+ return {
31
+ ...canonical,
32
+ id: legacyId,
33
+ name: `${canonical.name} (legacy ID)`,
34
+ };
35
+ },
36
+ );
37
+ }
@@ -1,86 +1,9 @@
1
- // Hardcoded models from Neuralwatt API
2
- // Source: https://api.neuralwatt.com/v1/models
3
- // Pricing, capabilities, and limits from metadata fields in /v1/models
4
-
5
1
  import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
6
2
 
3
+ // Public models returned by https://api.neuralwatt.com/v1/models (unauthenticated view).
4
+ // Pricing, capabilities, and limits are sourced from the API metadata fields.
7
5
  export const NEURALWATT_MODELS: ProviderModelConfig[] = [
8
- // GLM-5.1 (200K vLLM deployment) - ZhipuAI
9
- // Legacy id previously aliased to glm-5.1; now serving a GLM-5.2 test build.
10
- {
11
- id: "zai-org/GLM-5.1-FP8",
12
- name: "GLM-5.2 (test)",
13
- reasoning: true,
14
- input: ["text"],
15
- cost: {
16
- input: 1.1,
17
- output: 3.6,
18
- cacheRead: 0,
19
- cacheWrite: 0,
20
- },
21
- contextWindow: 1048560,
22
- maxTokens: 65536,
23
- thinkingLevelMap: {
24
- minimal: null,
25
- low: null,
26
- medium: "medium",
27
- high: null,
28
- xhigh: null,
29
- },
30
- compat: {
31
- supportsDeveloperRole: false,
32
- maxTokensField: "max_tokens",
33
- requiresReasoningContentOnAssistantMessages: true,
34
- },
35
- },
36
- // GLM-5.1 - ZhipuAI
37
- // Backed by the 1048K GLM-5.2 deployment (GLM-5.1 redirect in effect). Deprecated.
38
- {
39
- id: "glm-5.1",
40
- name: "GLM-5.1",
41
- reasoning: true,
42
- input: ["text"],
43
- cost: {
44
- input: 1.1,
45
- output: 3.6,
46
- cacheRead: 0,
47
- cacheWrite: 0,
48
- },
49
- contextWindow: 1048560,
50
- maxTokens: 65536,
51
- thinkingLevelMap: {
52
- minimal: null,
53
- low: null,
54
- medium: "medium",
55
- high: null,
56
- xhigh: null,
57
- },
58
- compat: {
59
- supportsDeveloperRole: false,
60
- maxTokensField: "max_tokens",
61
- requiresReasoningContentOnAssistantMessages: true,
62
- },
63
- },
64
- // GLM-5.1 Fast - ZhipuAI
65
- {
66
- id: "glm-5.1-fast",
67
- name: "GLM-5.1 Fast",
68
- reasoning: false,
69
- input: ["text"],
70
- cost: {
71
- input: 1.1,
72
- output: 3.6,
73
- cacheRead: 0,
74
- cacheWrite: 0,
75
- },
76
- contextWindow: 1048560,
77
- maxTokens: 65536,
78
- compat: {
79
- supportsDeveloperRole: false,
80
- maxTokensField: "max_tokens",
81
- },
82
- },
83
- // GLM-5.2 - ZhipuAI (test canary)
6
+ // GLM-5.2 - ZhipuAI
84
7
  {
85
8
  id: "glm-5.2",
86
9
  name: "GLM-5.2",
@@ -89,14 +12,11 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
89
12
  cost: {
90
13
  input: 1.45,
91
14
  output: 4.5,
92
- cacheRead: 0,
15
+ cacheRead: 0.3625,
93
16
  cacheWrite: 0,
94
17
  },
95
18
  contextWindow: 1048560,
96
19
  maxTokens: 65536,
97
- // GLM-5.2 has two native reasoning depths (high, max) plus thinking-off.
98
- // Pi levels below high disable thinking; high -> high, xhigh -> max.
99
- // See https://portal.neuralwatt.com/docs/api/chat-completions#reasoning-effort
100
20
  thinkingLevelMap: {
101
21
  minimal: null,
102
22
  low: null,
@@ -119,7 +39,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
119
39
  cost: {
120
40
  input: 1.45,
121
41
  output: 4.5,
122
- cacheRead: 0,
42
+ cacheRead: 0.3625,
123
43
  cacheWrite: 0,
124
44
  },
125
45
  contextWindow: 1048560,
@@ -138,7 +58,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
138
58
  cost: {
139
59
  input: 0.52,
140
60
  output: 2.59,
141
- cacheRead: 0,
61
+ cacheRead: 0.13,
142
62
  cacheWrite: 0,
143
63
  },
144
64
  contextWindow: 262128,
@@ -165,7 +85,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
165
85
  cost: {
166
86
  input: 0.52,
167
87
  output: 2.59,
168
- cacheRead: 0,
88
+ cacheRead: 0.13,
169
89
  cacheWrite: 0,
170
90
  },
171
91
  contextWindow: 262128,
@@ -184,7 +104,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
184
104
  cost: {
185
105
  input: 0.69,
186
106
  output: 3.22,
187
- cacheRead: 0,
107
+ cacheRead: 0.1725,
188
108
  cacheWrite: 0,
189
109
  },
190
110
  contextWindow: 262128,
@@ -211,7 +131,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
211
131
  cost: {
212
132
  input: 0.69,
213
133
  output: 3.22,
214
- cacheRead: 0,
134
+ cacheRead: 0.1725,
215
135
  cacheWrite: 0,
216
136
  },
217
137
  contextWindow: 262128,
@@ -230,7 +150,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
230
150
  cost: {
231
151
  input: 0.69,
232
152
  output: 4.14,
233
- cacheRead: 0,
153
+ cacheRead: 0.1725,
234
154
  cacheWrite: 0,
235
155
  },
236
156
  contextWindow: 262128,
@@ -257,7 +177,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
257
177
  cost: {
258
178
  input: 0.69,
259
179
  output: 4.14,
260
- cacheRead: 0,
180
+ cacheRead: 0.1725,
261
181
  cacheWrite: 0,
262
182
  },
263
183
  contextWindow: 262128,
@@ -276,7 +196,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
276
196
  cost: {
277
197
  input: 0.29,
278
198
  output: 1.15,
279
- cacheRead: 0,
199
+ cacheRead: 0.0725,
280
200
  cacheWrite: 0,
281
201
  },
282
202
  contextWindow: 131056,
@@ -303,7 +223,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
303
223
  cost: {
304
224
  input: 0.95,
305
225
  output: 4.0,
306
- cacheRead: 0,
226
+ cacheRead: 0.2375,
307
227
  cacheWrite: 0,
308
228
  },
309
229
  contextWindow: 262128,
@@ -331,7 +251,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
331
251
  cost: {
332
252
  input: 0.29,
333
253
  output: 1.15,
334
- cacheRead: 0,
254
+ cacheRead: 0.0725,
335
255
  cacheWrite: 0,
336
256
  },
337
257
  contextWindow: 131056,
@@ -342,38 +262,3 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
342
262
  },
343
263
  },
344
264
  ];
345
-
346
- const LEGACY_MODEL_ALIAS_MAP = {
347
- "moonshotai/Kimi-K2.6": "kimi-k2.6",
348
- "Qwen/Qwen3.5-397B-A17B-FP8": "qwen3.5-397b",
349
- "Qwen/Qwen3.6-35B-A3B": "qwen3.6-35b",
350
- } as const;
351
-
352
- export const LEGACY_NEURALWATT_MODEL_IDS = new Set<string>(
353
- Object.keys(LEGACY_MODEL_ALIAS_MAP),
354
- );
355
-
356
- const LEGACY_NEURALWATT_MODELS: ProviderModelConfig[] = Object.entries(
357
- LEGACY_MODEL_ALIAS_MAP,
358
- ).map(([legacyId, canonicalId]) => {
359
- const canonical = NEURALWATT_MODELS.find((model) => model.id === canonicalId);
360
-
361
- if (!canonical) {
362
- throw new Error(`Missing canonical model for legacy alias ${legacyId}`);
363
- }
364
-
365
- return {
366
- ...canonical,
367
- id: legacyId,
368
- name: `${canonical.name} (legacy ID)`,
369
- };
370
- });
371
-
372
- export function getNeuralwattModels(options?: {
373
- includeLegacyModelIds?: boolean;
374
- }): ProviderModelConfig[] {
375
- if (options?.includeLegacyModelIds)
376
- return [...NEURALWATT_MODELS, ...LEGACY_NEURALWATT_MODELS];
377
-
378
- return NEURALWATT_MODELS;
379
- }
@@ -1,8 +1,8 @@
1
1
  import type { AuthStorage } from "@earendil-works/pi-coding-agent";
2
2
  import { getNeuralwattApiKey } from "../../lib/env";
3
+ import { fetchQuotas } from "../../lib/neuralwatt-api";
3
4
  import type { NeuralwattQuotas } from "../../types/quota-api";
4
5
  import { parseQuotaHeaders } from "../../types/quota-events";
5
- import { fetchQuotas } from "../../utils/quotas";
6
6
 
7
7
  export function buildQuotasFromHeaders(
8
8
  headers: Record<string, string>,
@@ -1,6 +1,11 @@
1
+ import type {
2
+ NeuralwattApiModel,
3
+ NeuralwattApiModelsResponse,
4
+ } from "../types/models-api";
1
5
  import type { NeuralwattQuotas } from "../types/quota-api";
2
6
  import type { QuotasResult } from "../types/quota-events";
3
7
 
8
+ const BASE_URL = "https://api.neuralwatt.com/v1";
4
9
  const FETCH_TIMEOUT_MS = 15_000;
5
10
 
6
11
  function isTimeoutReason(reason: unknown): boolean {
@@ -10,6 +15,55 @@ function isTimeoutReason(reason: unknown): boolean {
10
15
  );
11
16
  }
12
17
 
18
+ function combineSignals(signal?: AbortSignal): AbortSignal {
19
+ const signals: AbortSignal[] = [AbortSignal.timeout(FETCH_TIMEOUT_MS)];
20
+ if (signal) signals.push(signal);
21
+ return AbortSignal.any(signals);
22
+ }
23
+
24
+ async function neuralwattFetch(
25
+ path: string,
26
+ apiKey: string,
27
+ signal?: AbortSignal,
28
+ headers?: Record<string, string>,
29
+ ): Promise<Response> {
30
+ return fetch(`${BASE_URL}${path}`, {
31
+ headers: { Authorization: `Bearer ${apiKey}`, ...headers },
32
+ signal: combineSignals(signal),
33
+ });
34
+ }
35
+
36
+ export type NeuralwattModelsResult =
37
+ | { success: true; data: NeuralwattApiModel[] }
38
+ | { success: false };
39
+
40
+ export async function fetchNeuralwattModels(
41
+ apiKey: string,
42
+ signal?: AbortSignal,
43
+ ): Promise<NeuralwattModelsResult> {
44
+ if (!apiKey) {
45
+ return { success: false };
46
+ }
47
+
48
+ const combined = combineSignals(signal);
49
+
50
+ try {
51
+ const response = await neuralwattFetch("/models", apiKey, combined, {
52
+ Referer: "https://pi.dev",
53
+ "X-Title": "npm:@aliou/pi-neuralwatt",
54
+ });
55
+
56
+ if (!response.ok) {
57
+ return { success: false };
58
+ }
59
+
60
+ const data: NeuralwattApiModelsResponse = await response.json();
61
+ return { success: true, data: data.data };
62
+ } catch {
63
+ return { success: false };
64
+ }
65
+ }
66
+
13
67
  export async function fetchQuotas(
14
68
  apiKey: string,
15
69
  signal?: AbortSignal,
@@ -21,15 +75,10 @@ export async function fetchQuotas(
21
75
  };
22
76
  }
23
77
 
24
- const signals: AbortSignal[] = [AbortSignal.timeout(FETCH_TIMEOUT_MS)];
25
- if (signal) signals.push(signal);
26
- const combined = AbortSignal.any(signals);
78
+ const combined = combineSignals(signal);
27
79
 
28
80
  try {
29
- const response = await fetch("https://api.neuralwatt.com/v1/quota", {
30
- headers: { Authorization: `Bearer ${apiKey}` },
31
- signal: combined,
32
- });
81
+ const response = await neuralwattFetch("/quota", apiKey, combined);
33
82
 
34
83
  if (!response.ok) {
35
84
  let message = response.statusText;
@@ -0,0 +1,53 @@
1
+ export interface NeuralwattApiModelPricing {
2
+ input_per_million: number;
3
+ output_per_million: number;
4
+ cached_input_per_million: number | null;
5
+ cached_output_per_million: number | null;
6
+ currency: string;
7
+ pricing_tbd: boolean;
8
+ }
9
+
10
+ export interface NeuralwattApiModelCapabilities {
11
+ tools: boolean;
12
+ json_mode: boolean;
13
+ vision: boolean;
14
+ reasoning: boolean;
15
+ reasoning_effort: boolean;
16
+ streaming: boolean;
17
+ system_role: boolean;
18
+ developer_role: boolean;
19
+ }
20
+
21
+ export interface NeuralwattApiModelLimits {
22
+ max_context_length: number;
23
+ max_output_tokens: number | null;
24
+ max_images: number | null;
25
+ }
26
+
27
+ export interface NeuralwattApiModelMetadata {
28
+ display_name: string;
29
+ description: string | null;
30
+ provider: string;
31
+ huggingface_id: string | null;
32
+ pricing: NeuralwattApiModelPricing;
33
+ capabilities: NeuralwattApiModelCapabilities;
34
+ limits: NeuralwattApiModelLimits;
35
+ deprecated: boolean;
36
+ deprecated_message: string | null;
37
+ }
38
+
39
+ export interface NeuralwattApiModel {
40
+ id: string;
41
+ object: string;
42
+ created: number;
43
+ owned_by: string;
44
+ root?: string;
45
+ parent?: string | null;
46
+ max_model_len: number;
47
+ metadata?: NeuralwattApiModelMetadata;
48
+ }
49
+
50
+ export interface NeuralwattApiModelsResponse {
51
+ object: "list";
52
+ data: NeuralwattApiModel[];
53
+ }