@aliou/pi-neuralwatt 0.2.0 → 0.4.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/README.md +1 -1
- package/package.json +10 -10
- package/src/config.ts +2 -2
- package/src/extensions/command-quotas/command.ts +4 -1
- package/src/extensions/command-quotas/components/quota-tabs.ts +2 -2
- package/src/extensions/command-quotas/components/quotas-display.ts +4 -4
- package/src/extensions/command-quotas/index.ts +1 -1
- package/src/extensions/provider/context-overflow.ts +31 -0
- package/src/extensions/provider/index.ts +43 -78
- package/src/extensions/provider/models.test.ts +14 -6
- package/src/extensions/provider/models.ts +35 -24
- package/src/extensions/provider/provider-payload.ts +12 -0
- package/src/extensions/provider/quota-store.ts +57 -0
- package/src/extensions/quota-warnings/index.ts +1 -1
- package/src/extensions/quota-warnings/notifier.ts +1 -1
- package/src/extensions/sub-bar-integration/index.ts +1 -1
- package/src/lib/env.ts +1 -1
- package/src/lib/fetch-models.ts +21 -15
- package/src/utils/is-offline.test.ts +60 -0
- package/src/utils/is-offline.ts +4 -0
- package/src/utils/quota-bar.ts +1 -1
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliou/pi-neuralwatt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"./src/extensions/quota-warnings/index.ts",
|
|
21
21
|
"./src/extensions/sub-bar-integration/index.ts"
|
|
22
22
|
],
|
|
23
|
-
"video": "https://assets.aliou.me/
|
|
23
|
+
"video": "https://assets.aliou.me/github/aliou/pi-neuralwatt/demo.mp4"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@aliou/pi-utils-settings": "^0.
|
|
35
|
-
"@aliou/pi-utils-ui": "^0.
|
|
34
|
+
"@aliou/pi-utils-settings": "^0.15.0",
|
|
35
|
+
"@aliou/pi-utils-ui": "^0.4.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@
|
|
39
|
-
"@
|
|
38
|
+
"@earendil-works/pi-coding-agent": "0.74.0",
|
|
39
|
+
"@earendil-works/pi-tui": "0.74.0",
|
|
40
40
|
"@sinclair/typebox": ">=0.34.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@aliou/biome-plugins": "^0.8.1",
|
|
44
44
|
"@biomejs/biome": "^2.4.12",
|
|
45
45
|
"@changesets/cli": "^2.27.11",
|
|
46
|
-
"@
|
|
47
|
-
"@
|
|
46
|
+
"@earendil-works/pi-coding-agent": "0.74.0",
|
|
47
|
+
"@earendil-works/pi-tui": "0.74.0",
|
|
48
48
|
"@types/node": "^25.0.10",
|
|
49
49
|
"husky": "^9.1.7",
|
|
50
50
|
"ts-json-schema-generator": "^2.4.0",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"vitest": "^4.0.18"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
|
-
"@
|
|
55
|
+
"@earendil-works/pi-coding-agent": {
|
|
56
56
|
"optional": true
|
|
57
57
|
},
|
|
58
|
-
"@
|
|
58
|
+
"@earendil-works/pi-tui": {
|
|
59
59
|
"optional": true
|
|
60
60
|
},
|
|
61
61
|
"@sinclair/typebox": {
|
package/src/config.ts
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
registerSettingsCommand,
|
|
4
4
|
type SettingsSection,
|
|
5
5
|
} from "@aliou/pi-utils-settings";
|
|
6
|
-
import type { ExtensionAPI } from "@
|
|
7
|
-
import type { SettingItem } from "@
|
|
6
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
8
8
|
|
|
9
9
|
export type NeuralwattFeatureId =
|
|
10
10
|
| "quotaCommand"
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type ExtensionAPI,
|
|
4
|
+
getAgentDir,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
3
6
|
import { getNeuralwattApiKey } from "../../lib/env";
|
|
4
7
|
import { fetchQuotas } from "../../utils/quotas";
|
|
5
8
|
import { QuotasComponent } from "./components/quotas-display";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Theme } from "@
|
|
2
|
-
import { truncateToWidth } from "@
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { NeuralwattQuotas } from "../../../types/quota-api";
|
|
4
4
|
import {
|
|
5
5
|
percentCreditsRemaining,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Theme } from "@
|
|
2
|
-
import { DynamicBorder } from "@
|
|
3
|
-
import type { Component, TUI } from "@
|
|
4
|
-
import { Loader, matchesKey, truncateToWidth } from "@
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
4
|
+
import { Loader, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
5
5
|
import type { NeuralwattQuotas } from "../../../types/quota-api";
|
|
6
6
|
import {
|
|
7
7
|
renderCreditsTab,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
interface AssistantErrorLike {
|
|
2
|
+
role: string;
|
|
3
|
+
stopReason?: string;
|
|
4
|
+
provider?: string;
|
|
5
|
+
errorMessage?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const NEURALWATT_CONTEXT_OVERFLOW_PATTERN =
|
|
9
|
+
/request exceeds model'?s maximum context length/i;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Normalize Neuralwatt context overflow errors so Pi's native overflow
|
|
13
|
+
* compaction path can detect them and perform compact-and-retry.
|
|
14
|
+
*/
|
|
15
|
+
export function normalizeNeuralwattContextOverflowError<
|
|
16
|
+
TMessage extends AssistantErrorLike,
|
|
17
|
+
>(message: TMessage, currentProvider?: string): TMessage | undefined {
|
|
18
|
+
if (message.role !== "assistant") return;
|
|
19
|
+
if (message.stopReason !== "error") return;
|
|
20
|
+
if (message.provider !== "neuralwatt" && currentProvider !== "neuralwatt")
|
|
21
|
+
return;
|
|
22
|
+
|
|
23
|
+
const errorMessage = message.errorMessage ?? "";
|
|
24
|
+
if (errorMessage.includes("context_length_exceeded")) return;
|
|
25
|
+
if (!NEURALWATT_CONTEXT_OVERFLOW_PATTERN.test(errorMessage)) return;
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
...message,
|
|
29
|
+
errorMessage: `context_length_exceeded: ${errorMessage}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
3
|
configLoader,
|
|
4
4
|
emitConfigUpdated,
|
|
@@ -14,22 +14,16 @@ import {
|
|
|
14
14
|
NEURALWATT_QUOTAS_REQUEST_EVENT,
|
|
15
15
|
NEURALWATT_QUOTAS_UPDATED_EVENT,
|
|
16
16
|
type NeuralwattQuotasUpdatedPayload,
|
|
17
|
-
parseQuotaHeaders,
|
|
18
17
|
} from "../../types/quota-events";
|
|
18
|
+
import { isOffline } from "../../utils/is-offline";
|
|
19
19
|
import { fetchQuotas } from "../../utils/quotas";
|
|
20
|
+
import { normalizeNeuralwattContextOverflowError } from "./context-overflow";
|
|
20
21
|
import type { NeuralwattModelConfig } from "./models";
|
|
21
22
|
import { NEURALWATT_MODELS_CACHE } from "./models";
|
|
23
|
+
import { buildModelsPayload } from "./provider-payload";
|
|
24
|
+
import { buildQuotasFromHeaders, fetchRequestedQuotas } from "./quota-store";
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
return models.map(({ fast: _fast, ...model }) => ({
|
|
25
|
-
...model,
|
|
26
|
-
compat: {
|
|
27
|
-
supportsDeveloperRole: false,
|
|
28
|
-
maxTokensField: "max_tokens",
|
|
29
|
-
...model.compat,
|
|
30
|
-
},
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
26
|
+
const HEADER_EMIT_THROTTLE_MS = 5_000;
|
|
33
27
|
|
|
34
28
|
function registerNeuralwattProvider(
|
|
35
29
|
pi: ExtensionAPI,
|
|
@@ -54,17 +48,15 @@ export default async function (pi: ExtensionAPI) {
|
|
|
54
48
|
// Register with hardcoded cache immediately so models are available on startup
|
|
55
49
|
registerNeuralwattProvider(pi, NEURALWATT_MODELS_CACHE);
|
|
56
50
|
|
|
57
|
-
// Track which feature extensions loaded
|
|
58
51
|
const loadedFeatures = new Set<NeuralwattFeatureId>();
|
|
59
52
|
|
|
60
|
-
// Register settings
|
|
53
|
+
// Register settings in the provider so it is always available.
|
|
61
54
|
registerNeuralwattSettings(pi, {
|
|
62
55
|
getLoadedFeatures: () => loadedFeatures,
|
|
63
56
|
});
|
|
64
57
|
|
|
65
|
-
// --- Quota store (event-based) ---
|
|
66
58
|
let lastHeaderEmitAt = 0;
|
|
67
|
-
|
|
59
|
+
let quotaRequestInFlight = false;
|
|
68
60
|
|
|
69
61
|
function emitQuotas(
|
|
70
62
|
quotas: NeuralwattQuotas,
|
|
@@ -77,94 +69,67 @@ export default async function (pi: ExtensionAPI) {
|
|
|
77
69
|
pi.events.emit(NEURALWATT_QUOTAS_UPDATED_EVENT, { quotas, source });
|
|
78
70
|
}
|
|
79
71
|
|
|
80
|
-
|
|
72
|
+
pi.on("message_end", (event, ctx) => {
|
|
73
|
+
const message = normalizeNeuralwattContextOverflowError(
|
|
74
|
+
event.message,
|
|
75
|
+
ctx.model?.provider,
|
|
76
|
+
);
|
|
77
|
+
if (!message) return;
|
|
78
|
+
return { message };
|
|
79
|
+
});
|
|
80
|
+
|
|
81
81
|
pi.on("after_provider_response", (event, ctx) => {
|
|
82
82
|
if (ctx.model?.provider !== "neuralwatt") return;
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
85
|
-
|
|
86
|
-
const quotas: NeuralwattQuotas = {
|
|
87
|
-
snapshot_at: new Date().toISOString(),
|
|
88
|
-
balance: {
|
|
89
|
-
credits_remaining_usd: headerQuotas.allowanceRemainingUsd,
|
|
90
|
-
total_credits_usd: 0,
|
|
91
|
-
credits_used_usd: 0,
|
|
92
|
-
accounting_method: "token",
|
|
93
|
-
},
|
|
94
|
-
usage: {
|
|
95
|
-
lifetime: { cost_usd: 0, requests: 0, tokens: 0, energy_kwh: 0 },
|
|
96
|
-
current_month: { cost_usd: 0, requests: 0, tokens: 0, energy_kwh: 0 },
|
|
97
|
-
},
|
|
98
|
-
limits: { overage_limit_usd: null, rate_limit_tier: "standard" },
|
|
99
|
-
subscription:
|
|
100
|
-
headerQuotas.subscriptionPlan !== "none" &&
|
|
101
|
-
headerQuotas.energyRemaining !== undefined
|
|
102
|
-
? {
|
|
103
|
-
plan: headerQuotas.subscriptionPlan,
|
|
104
|
-
status: "active",
|
|
105
|
-
billing_interval: "month",
|
|
106
|
-
current_period_start: "",
|
|
107
|
-
current_period_end: "",
|
|
108
|
-
auto_renew: false,
|
|
109
|
-
kwh_included: headerQuotas.energyIncluded ?? 0,
|
|
110
|
-
kwh_used: headerQuotas.energyUsed ?? 0,
|
|
111
|
-
kwh_remaining: headerQuotas.energyRemaining,
|
|
112
|
-
in_overage: false,
|
|
113
|
-
}
|
|
114
|
-
: null,
|
|
115
|
-
key: { name: "", allowance: null },
|
|
116
|
-
};
|
|
117
|
-
|
|
83
|
+
const quotas = buildQuotasFromHeaders(event.headers);
|
|
84
|
+
if (!quotas) return;
|
|
118
85
|
emitQuotas(quotas, "header");
|
|
119
86
|
});
|
|
120
87
|
|
|
121
|
-
// Respond to quota requests from other extensions
|
|
122
|
-
let quotaRequestInFlight = false;
|
|
123
88
|
pi.events.on(NEURALWATT_QUOTAS_REQUEST_EVENT, async (data: unknown) => {
|
|
124
89
|
if (quotaRequestInFlight) return;
|
|
125
90
|
quotaRequestInFlight = true;
|
|
126
91
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (result.success) emitQuotas(result.data.quotas, "api");
|
|
92
|
+
const quotas = await fetchRequestedQuotas(data);
|
|
93
|
+
if (quotas)
|
|
94
|
+
pi.events.emit(NEURALWATT_QUOTAS_UPDATED_EVENT, {
|
|
95
|
+
quotas,
|
|
96
|
+
source: "api",
|
|
97
|
+
});
|
|
134
98
|
} finally {
|
|
135
99
|
quotaRequestInFlight = false;
|
|
136
100
|
}
|
|
137
101
|
});
|
|
138
102
|
|
|
139
|
-
// Collect which feature extensions are loaded
|
|
140
103
|
pi.events.on(NEURALWATT_EXTENSIONS_REGISTER_EVENT, (data: unknown) => {
|
|
141
104
|
const { feature } = data as { feature: NeuralwattFeatureId };
|
|
142
105
|
loadedFeatures.add(feature);
|
|
143
106
|
});
|
|
144
107
|
|
|
145
|
-
// On session start: fetch live models, request extensions, emit config, fetch quotas
|
|
146
108
|
pi.on("session_start", async (_event, ctx) => {
|
|
147
109
|
loadedFeatures.clear();
|
|
148
110
|
pi.events.emit(NEURALWATT_EXTENSIONS_REQUEST_EVENT, undefined);
|
|
149
111
|
emitConfigUpdated(pi);
|
|
150
112
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const parts: string[] = [];
|
|
160
|
-
if (added.length > 0) parts.push(`${added.length} new`);
|
|
161
|
-
if (removed.length > 0) parts.push(`${removed.length} removed`);
|
|
162
|
-
ctx.ui.notify(
|
|
163
|
-
`Neuralwatt models updated (${parts.join(", ")})`,
|
|
164
|
-
"info",
|
|
113
|
+
if (!isOffline()) {
|
|
114
|
+
const result = await fetchModels();
|
|
115
|
+
if (result.success) {
|
|
116
|
+
const cacheIds = new Set(NEURALWATT_MODELS_CACHE.map((m) => m.id));
|
|
117
|
+
const liveIds = new Set(result.models.map((m) => m.id));
|
|
118
|
+
const added = result.models.filter((m) => !cacheIds.has(m.id));
|
|
119
|
+
const removed = NEURALWATT_MODELS_CACHE.filter(
|
|
120
|
+
(m) => !liveIds.has(m.id),
|
|
165
121
|
);
|
|
122
|
+
if (added.length > 0 || removed.length > 0) {
|
|
123
|
+
const parts: string[] = [];
|
|
124
|
+
if (added.length > 0) parts.push(`${added.length} new`);
|
|
125
|
+
if (removed.length > 0) parts.push(`${removed.length} removed`);
|
|
126
|
+
ctx.ui.notify(
|
|
127
|
+
`Neuralwatt models updated (${parts.join(", ")})`,
|
|
128
|
+
"info",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
registerNeuralwattProvider(pi, result.models);
|
|
166
132
|
}
|
|
167
|
-
registerNeuralwattProvider(pi, result.models);
|
|
168
133
|
}
|
|
169
134
|
|
|
170
135
|
if (ctx.model?.provider !== "neuralwatt") return;
|
|
@@ -207,9 +207,13 @@ describe("Neuralwatt models", () => {
|
|
|
207
207
|
expect(result.input).toEqual(["text"]);
|
|
208
208
|
expect(result.cost.input).toBe(0.03);
|
|
209
209
|
expect(result.cost.output).toBe(0.16);
|
|
210
|
-
expect(
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
expect(result.thinkingLevelMap).toEqual({
|
|
211
|
+
minimal: "low",
|
|
212
|
+
low: "low",
|
|
213
|
+
medium: "medium",
|
|
214
|
+
high: "high",
|
|
215
|
+
xhigh: null,
|
|
216
|
+
});
|
|
213
217
|
expect(result.fast).toBeUndefined();
|
|
214
218
|
});
|
|
215
219
|
|
|
@@ -308,9 +312,13 @@ describe("Neuralwatt models", () => {
|
|
|
308
312
|
const result = mapApiModel(visionModel);
|
|
309
313
|
expect(result.input).toEqual(["text", "image"]);
|
|
310
314
|
expect(result.reasoning).toBe(true);
|
|
311
|
-
expect(
|
|
312
|
-
|
|
313
|
-
|
|
315
|
+
expect(result.thinkingLevelMap).toEqual({
|
|
316
|
+
minimal: null,
|
|
317
|
+
low: null,
|
|
318
|
+
medium: "medium",
|
|
319
|
+
high: null,
|
|
320
|
+
xhigh: null,
|
|
321
|
+
});
|
|
314
322
|
});
|
|
315
323
|
|
|
316
324
|
it("should use defaults when metadata is missing", () => {
|
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
// Pricing: https://portal.neuralwatt.com/pricing
|
|
4
4
|
// max_model_len from /v1/models, pricing from /pricing page
|
|
5
5
|
|
|
6
|
-
import type { ProviderModelConfig } from "@
|
|
6
|
+
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
|
|
8
8
|
export interface NeuralwattModelConfig extends ProviderModelConfig {
|
|
9
9
|
/** Fast variant of a parent model (e.g. "glm-5-fast" is the fast variant of "zai-org/GLM-5.1-FP8"). */
|
|
10
10
|
fast?: boolean;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
minimal:
|
|
15
|
-
low:
|
|
13
|
+
const NEURALWATT_BINARY_THINKING_LEVEL_MAP = {
|
|
14
|
+
minimal: null,
|
|
15
|
+
low: null,
|
|
16
16
|
medium: "medium",
|
|
17
|
-
high:
|
|
18
|
-
xhigh:
|
|
17
|
+
high: null,
|
|
18
|
+
xhigh: null,
|
|
19
19
|
} as const;
|
|
20
20
|
|
|
21
21
|
/** Hardcoded model cache. Used as a fallback on startup before live models are fetched.
|
|
@@ -34,7 +34,7 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
34
34
|
cacheRead: 0,
|
|
35
35
|
cacheWrite: 0,
|
|
36
36
|
},
|
|
37
|
-
contextWindow:
|
|
37
|
+
contextWindow: 262128,
|
|
38
38
|
maxTokens: 32768,
|
|
39
39
|
compat: {
|
|
40
40
|
supportsDeveloperRole: false,
|
|
@@ -54,7 +54,7 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
54
54
|
cacheRead: 0,
|
|
55
55
|
cacheWrite: 0,
|
|
56
56
|
},
|
|
57
|
-
contextWindow:
|
|
57
|
+
contextWindow: 202736,
|
|
58
58
|
maxTokens: 32768,
|
|
59
59
|
compat: {
|
|
60
60
|
supportsDeveloperRole: false,
|
|
@@ -73,8 +73,9 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
73
73
|
cacheRead: 0,
|
|
74
74
|
cacheWrite: 0,
|
|
75
75
|
},
|
|
76
|
-
contextWindow:
|
|
76
|
+
contextWindow: 202736,
|
|
77
77
|
maxTokens: 32768,
|
|
78
|
+
thinkingLevelMap: NEURALWATT_BINARY_THINKING_LEVEL_MAP,
|
|
78
79
|
compat: {
|
|
79
80
|
supportsDeveloperRole: false,
|
|
80
81
|
maxTokensField: "max_tokens",
|
|
@@ -93,7 +94,7 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
93
94
|
cacheRead: 0,
|
|
94
95
|
cacheWrite: 0,
|
|
95
96
|
},
|
|
96
|
-
contextWindow:
|
|
97
|
+
contextWindow: 202736,
|
|
97
98
|
maxTokens: 32768,
|
|
98
99
|
compat: {
|
|
99
100
|
supportsDeveloperRole: false,
|
|
@@ -112,12 +113,17 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
112
113
|
cacheRead: 0,
|
|
113
114
|
cacheWrite: 0,
|
|
114
115
|
},
|
|
115
|
-
contextWindow:
|
|
116
|
+
contextWindow: 16368,
|
|
116
117
|
maxTokens: 4096,
|
|
118
|
+
thinkingLevelMap: {
|
|
119
|
+
minimal: "low",
|
|
120
|
+
low: "low",
|
|
121
|
+
medium: "medium",
|
|
122
|
+
high: "high",
|
|
123
|
+
xhigh: null,
|
|
124
|
+
},
|
|
117
125
|
compat: {
|
|
118
126
|
supportsDeveloperRole: false,
|
|
119
|
-
supportsReasoningEffort: true,
|
|
120
|
-
reasoningEffortMap: NEURALWATT_REASONING_EFFORT_MAP,
|
|
121
127
|
maxTokensField: "max_tokens",
|
|
122
128
|
},
|
|
123
129
|
},
|
|
@@ -133,8 +139,9 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
133
139
|
cacheRead: 0,
|
|
134
140
|
cacheWrite: 0,
|
|
135
141
|
},
|
|
136
|
-
contextWindow:
|
|
142
|
+
contextWindow: 262128,
|
|
137
143
|
maxTokens: 65536,
|
|
144
|
+
thinkingLevelMap: NEURALWATT_BINARY_THINKING_LEVEL_MAP,
|
|
138
145
|
compat: {
|
|
139
146
|
supportsDeveloperRole: false,
|
|
140
147
|
maxTokensField: "max_tokens",
|
|
@@ -153,7 +160,7 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
153
160
|
cacheRead: 0,
|
|
154
161
|
cacheWrite: 0,
|
|
155
162
|
},
|
|
156
|
-
contextWindow:
|
|
163
|
+
contextWindow: 262128,
|
|
157
164
|
maxTokens: 65536,
|
|
158
165
|
compat: {
|
|
159
166
|
supportsDeveloperRole: false,
|
|
@@ -172,8 +179,9 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
172
179
|
cacheRead: 0,
|
|
173
180
|
cacheWrite: 0,
|
|
174
181
|
},
|
|
175
|
-
contextWindow:
|
|
182
|
+
contextWindow: 262128,
|
|
176
183
|
maxTokens: 65536,
|
|
184
|
+
thinkingLevelMap: NEURALWATT_BINARY_THINKING_LEVEL_MAP,
|
|
177
185
|
compat: {
|
|
178
186
|
supportsDeveloperRole: false,
|
|
179
187
|
maxTokensField: "max_tokens",
|
|
@@ -192,7 +200,7 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
192
200
|
cacheRead: 0,
|
|
193
201
|
cacheWrite: 0,
|
|
194
202
|
},
|
|
195
|
-
contextWindow:
|
|
203
|
+
contextWindow: 262128,
|
|
196
204
|
maxTokens: 65536,
|
|
197
205
|
compat: {
|
|
198
206
|
supportsDeveloperRole: false,
|
|
@@ -211,8 +219,9 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
211
219
|
cacheRead: 0,
|
|
212
220
|
cacheWrite: 0,
|
|
213
221
|
},
|
|
214
|
-
contextWindow:
|
|
222
|
+
contextWindow: 196592,
|
|
215
223
|
maxTokens: 65536,
|
|
224
|
+
thinkingLevelMap: NEURALWATT_BINARY_THINKING_LEVEL_MAP,
|
|
216
225
|
compat: {
|
|
217
226
|
supportsDeveloperRole: false,
|
|
218
227
|
maxTokensField: "max_tokens",
|
|
@@ -230,8 +239,9 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
230
239
|
cacheRead: 0,
|
|
231
240
|
cacheWrite: 0,
|
|
232
241
|
},
|
|
233
|
-
contextWindow:
|
|
242
|
+
contextWindow: 262128,
|
|
234
243
|
maxTokens: 65536,
|
|
244
|
+
thinkingLevelMap: NEURALWATT_BINARY_THINKING_LEVEL_MAP,
|
|
235
245
|
compat: {
|
|
236
246
|
supportsDeveloperRole: false,
|
|
237
247
|
maxTokensField: "max_tokens",
|
|
@@ -250,7 +260,7 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
250
260
|
cacheRead: 0,
|
|
251
261
|
cacheWrite: 0,
|
|
252
262
|
},
|
|
253
|
-
contextWindow:
|
|
263
|
+
contextWindow: 262128,
|
|
254
264
|
maxTokens: 65536,
|
|
255
265
|
compat: {
|
|
256
266
|
supportsDeveloperRole: false,
|
|
@@ -262,15 +272,16 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
262
272
|
id: "Qwen/Qwen3.6-35B-A3B",
|
|
263
273
|
name: "Qwen3.6 35B",
|
|
264
274
|
reasoning: true,
|
|
265
|
-
input: ["text"],
|
|
275
|
+
input: ["text", "image"],
|
|
266
276
|
cost: {
|
|
267
277
|
input: 0.05,
|
|
268
278
|
output: 0.1,
|
|
269
279
|
cacheRead: 0,
|
|
270
280
|
cacheWrite: 0,
|
|
271
281
|
},
|
|
272
|
-
contextWindow:
|
|
282
|
+
contextWindow: 131056,
|
|
273
283
|
maxTokens: 32768,
|
|
284
|
+
thinkingLevelMap: NEURALWATT_BINARY_THINKING_LEVEL_MAP,
|
|
274
285
|
compat: {
|
|
275
286
|
supportsDeveloperRole: false,
|
|
276
287
|
maxTokensField: "max_tokens",
|
|
@@ -282,14 +293,14 @@ export const NEURALWATT_MODELS_CACHE: NeuralwattModelConfig[] = [
|
|
|
282
293
|
name: "Qwen3.6 35B Fast",
|
|
283
294
|
reasoning: false,
|
|
284
295
|
fast: true,
|
|
285
|
-
input: ["text"],
|
|
296
|
+
input: ["text", "image"],
|
|
286
297
|
cost: {
|
|
287
298
|
input: 0.05,
|
|
288
299
|
output: 0.1,
|
|
289
300
|
cacheRead: 0,
|
|
290
301
|
cacheWrite: 0,
|
|
291
302
|
},
|
|
292
|
-
contextWindow:
|
|
303
|
+
contextWindow: 131056,
|
|
293
304
|
maxTokens: 32768,
|
|
294
305
|
compat: {
|
|
295
306
|
supportsDeveloperRole: false,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NeuralwattModelConfig } from "./models";
|
|
2
|
+
|
|
3
|
+
export function buildModelsPayload(models: NeuralwattModelConfig[]) {
|
|
4
|
+
return models.map(({ fast: _fast, ...model }) => ({
|
|
5
|
+
...model,
|
|
6
|
+
compat: {
|
|
7
|
+
supportsDeveloperRole: false,
|
|
8
|
+
maxTokensField: "max_tokens" as const,
|
|
9
|
+
...model.compat,
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { AuthStorage } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getNeuralwattApiKey } from "../../lib/env";
|
|
3
|
+
import type { NeuralwattQuotas } from "../../types/quota-api";
|
|
4
|
+
import { parseQuotaHeaders } from "../../types/quota-events";
|
|
5
|
+
import { fetchQuotas } from "../../utils/quotas";
|
|
6
|
+
|
|
7
|
+
export function buildQuotasFromHeaders(
|
|
8
|
+
headers: Record<string, string>,
|
|
9
|
+
): NeuralwattQuotas | undefined {
|
|
10
|
+
const headerQuotas = parseQuotaHeaders(headers);
|
|
11
|
+
if (!headerQuotas) return;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
snapshot_at: new Date().toISOString(),
|
|
15
|
+
balance: {
|
|
16
|
+
credits_remaining_usd: headerQuotas.allowanceRemainingUsd,
|
|
17
|
+
total_credits_usd: 0,
|
|
18
|
+
credits_used_usd: 0,
|
|
19
|
+
accounting_method: "token",
|
|
20
|
+
},
|
|
21
|
+
usage: {
|
|
22
|
+
lifetime: { cost_usd: 0, requests: 0, tokens: 0, energy_kwh: 0 },
|
|
23
|
+
current_month: { cost_usd: 0, requests: 0, tokens: 0, energy_kwh: 0 },
|
|
24
|
+
},
|
|
25
|
+
limits: { overage_limit_usd: null, rate_limit_tier: "standard" },
|
|
26
|
+
subscription:
|
|
27
|
+
headerQuotas.subscriptionPlan !== "none" &&
|
|
28
|
+
headerQuotas.energyRemaining !== undefined
|
|
29
|
+
? {
|
|
30
|
+
plan: headerQuotas.subscriptionPlan,
|
|
31
|
+
status: "active",
|
|
32
|
+
billing_interval: "month",
|
|
33
|
+
current_period_start: "",
|
|
34
|
+
current_period_end: "",
|
|
35
|
+
auto_renew: false,
|
|
36
|
+
kwh_included: headerQuotas.energyIncluded ?? 0,
|
|
37
|
+
kwh_used: headerQuotas.energyUsed ?? 0,
|
|
38
|
+
kwh_remaining: headerQuotas.energyRemaining,
|
|
39
|
+
in_overage: false,
|
|
40
|
+
}
|
|
41
|
+
: null,
|
|
42
|
+
key: { name: "", allowance: null },
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function fetchRequestedQuotas(
|
|
47
|
+
data: unknown,
|
|
48
|
+
): Promise<NeuralwattQuotas | undefined> {
|
|
49
|
+
if (!data || typeof data !== "object") return;
|
|
50
|
+
const { authStorage } = data as { authStorage?: AuthStorage };
|
|
51
|
+
if (!authStorage) return;
|
|
52
|
+
const apiKey = await getNeuralwattApiKey(authStorage);
|
|
53
|
+
if (!apiKey) return;
|
|
54
|
+
const result = await fetchQuotas(apiKey);
|
|
55
|
+
if (!result.success) return;
|
|
56
|
+
return result.data.quotas;
|
|
57
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { NeuralwattQuotas } from "../../types/quota-api";
|
|
3
3
|
import { formatKwh, formatUsd } from "../../utils/quota-format";
|
|
4
4
|
|
package/src/lib/env.ts
CHANGED
package/src/lib/fetch-models.ts
CHANGED
|
@@ -2,6 +2,22 @@ import type { NeuralwattModelConfig } from "../extensions/provider/models";
|
|
|
2
2
|
|
|
3
3
|
const FETCH_TIMEOUT_MS = 15_000;
|
|
4
4
|
|
|
5
|
+
const NEURALWATT_BINARY_THINKING_LEVEL_MAP = {
|
|
6
|
+
minimal: null,
|
|
7
|
+
low: null,
|
|
8
|
+
medium: "medium",
|
|
9
|
+
high: null,
|
|
10
|
+
xhigh: null,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
const GPT_OSS_THINKING_LEVEL_MAP = {
|
|
14
|
+
minimal: "low",
|
|
15
|
+
low: "low",
|
|
16
|
+
medium: "medium",
|
|
17
|
+
high: "high",
|
|
18
|
+
xhigh: null,
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
5
21
|
export interface ApiModelMetadata {
|
|
6
22
|
display_name: string;
|
|
7
23
|
description: string | null;
|
|
@@ -50,14 +66,6 @@ export interface ApiResponse {
|
|
|
50
66
|
data: ApiModel[];
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
const NEURALWATT_REASONING_EFFORT_MAP = {
|
|
54
|
-
minimal: "low",
|
|
55
|
-
low: "low",
|
|
56
|
-
medium: "medium",
|
|
57
|
-
high: "high",
|
|
58
|
-
xhigh: "high",
|
|
59
|
-
} as const;
|
|
60
|
-
|
|
61
69
|
/** Identify fast variants by their owned_by field or naming convention. */
|
|
62
70
|
function isFastModel(model: ApiModel): boolean {
|
|
63
71
|
if (model.owned_by === "neuralwatt") return true;
|
|
@@ -98,13 +106,11 @@ export function mapApiModel(model: ApiModel): NeuralwattModelConfig {
|
|
|
98
106
|
result.maxTokens = meta.limits.max_output_tokens;
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
reasoningEffortMap: NEURALWATT_REASONING_EFFORT_MAP,
|
|
107
|
-
};
|
|
109
|
+
if (result.reasoning) {
|
|
110
|
+
result.thinkingLevelMap =
|
|
111
|
+
model.id === "openai/gpt-oss-20b"
|
|
112
|
+
? GPT_OSS_THINKING_LEVEL_MAP
|
|
113
|
+
: NEURALWATT_BINARY_THINKING_LEVEL_MAP;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
return result;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isOffline } from "./is-offline";
|
|
3
|
+
|
|
4
|
+
describe("isOffline", () => {
|
|
5
|
+
it("returns true when PI_OFFLINE is 1", () => {
|
|
6
|
+
const original = process.env.PI_OFFLINE;
|
|
7
|
+
process.env.PI_OFFLINE = "1";
|
|
8
|
+
expect(isOffline()).toBe(true);
|
|
9
|
+
process.env.PI_OFFLINE = original;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns true when PI_OFFLINE is true", () => {
|
|
13
|
+
const original = process.env.PI_OFFLINE;
|
|
14
|
+
process.env.PI_OFFLINE = "true";
|
|
15
|
+
expect(isOffline()).toBe(true);
|
|
16
|
+
process.env.PI_OFFLINE = original;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns true when PI_OFFLINE is yes", () => {
|
|
20
|
+
const original = process.env.PI_OFFLINE;
|
|
21
|
+
process.env.PI_OFFLINE = "yes";
|
|
22
|
+
expect(isOffline()).toBe(true);
|
|
23
|
+
process.env.PI_OFFLINE = original;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns false when PI_OFFLINE is unset", () => {
|
|
27
|
+
const original = process.env.PI_OFFLINE;
|
|
28
|
+
delete process.env.PI_OFFLINE;
|
|
29
|
+
expect(isOffline()).toBe(false);
|
|
30
|
+
process.env.PI_OFFLINE = original;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns false when PI_OFFLINE is 0", () => {
|
|
34
|
+
const original = process.env.PI_OFFLINE;
|
|
35
|
+
process.env.PI_OFFLINE = "0";
|
|
36
|
+
expect(isOffline()).toBe(false);
|
|
37
|
+
process.env.PI_OFFLINE = original;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns false when PI_OFFLINE is false", () => {
|
|
41
|
+
const original = process.env.PI_OFFLINE;
|
|
42
|
+
process.env.PI_OFFLINE = "false";
|
|
43
|
+
expect(isOffline()).toBe(false);
|
|
44
|
+
process.env.PI_OFFLINE = original;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns false when PI_OFFLINE is no", () => {
|
|
48
|
+
const original = process.env.PI_OFFLINE;
|
|
49
|
+
process.env.PI_OFFLINE = "no";
|
|
50
|
+
expect(isOffline()).toBe(false);
|
|
51
|
+
process.env.PI_OFFLINE = original;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns false for other values", () => {
|
|
55
|
+
const original = process.env.PI_OFFLINE;
|
|
56
|
+
process.env.PI_OFFLINE = "maybe";
|
|
57
|
+
expect(isOffline()).toBe(false);
|
|
58
|
+
process.env.PI_OFFLINE = original;
|
|
59
|
+
});
|
|
60
|
+
});
|
package/src/utils/quota-bar.ts
CHANGED