@aliou/pi-neuralwatt 0.9.0 → 0.10.4
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.
|
@@ -3,6 +3,44 @@ import { fetchNeuralwattModels } from "../../../src/lib/neuralwatt-api";
|
|
|
3
3
|
import type { NeuralwattApiModel } from "../../../src/types/models-api";
|
|
4
4
|
import { NEURALWATT_MODELS } from "./public-models";
|
|
5
5
|
|
|
6
|
+
// Hidden aliases that work for authorized accounts but are omitted from the
|
|
7
|
+
// authenticated /v1/models response. Keep these gated by includeHiddenModels.
|
|
8
|
+
// Move an entry to public-models.ts once Neuralwatt advertises it publicly.
|
|
9
|
+
export const HIDDEN_NEURALWATT_MODELS: ProviderModelConfig[] = [
|
|
10
|
+
// DeepSeek V4 Flash Canary - early-access 284B/13B-active MoE served on B200s.
|
|
11
|
+
// Neuralwatt omits it from the authenticated catalog. Context and runtime
|
|
12
|
+
// capabilities were verified directly; pricing follows DeepSeek's upstream
|
|
13
|
+
// rates until Neuralwatt publishes model metadata.
|
|
14
|
+
{
|
|
15
|
+
id: "deepseek-v4-flash",
|
|
16
|
+
name: "DeepSeek V4 Flash (Canary)",
|
|
17
|
+
reasoning: true,
|
|
18
|
+
input: ["text", "image"],
|
|
19
|
+
cost: {
|
|
20
|
+
input: 0.14,
|
|
21
|
+
output: 0.28,
|
|
22
|
+
cacheRead: 0.0028,
|
|
23
|
+
cacheWrite: 0,
|
|
24
|
+
},
|
|
25
|
+
contextWindow: 1_000_000,
|
|
26
|
+
maxTokens: 384_000,
|
|
27
|
+
thinkingLevelMap: {
|
|
28
|
+
off: "none",
|
|
29
|
+
minimal: "low",
|
|
30
|
+
low: "low",
|
|
31
|
+
medium: "medium",
|
|
32
|
+
high: "high",
|
|
33
|
+
xhigh: null,
|
|
34
|
+
max: "max",
|
|
35
|
+
},
|
|
36
|
+
compat: {
|
|
37
|
+
supportsDeveloperRole: false,
|
|
38
|
+
maxTokensField: "max_tokens",
|
|
39
|
+
requiresReasoningContentOnAssistantMessages: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
6
44
|
// Per-ID overrides for known hidden models. The authenticated /v1/models endpoint
|
|
7
45
|
// exposes pricing and capabilities, but some Pi-specific behavior (thinking levels,
|
|
8
46
|
// compat flags) has to be supplied by hand.
|
|
@@ -2,7 +2,7 @@ import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { buildLegacyNeuralwattModels } from "./legacy";
|
|
3
3
|
import { NEURALWATT_MODELS } from "./public-models";
|
|
4
4
|
|
|
5
|
-
export { loadHiddenModels } from "./hidden";
|
|
5
|
+
export { HIDDEN_NEURALWATT_MODELS, loadHiddenModels } from "./hidden";
|
|
6
6
|
export {
|
|
7
7
|
buildLegacyNeuralwattModels,
|
|
8
8
|
LEGACY_MODEL_ALIAS_MAP,
|
|
@@ -3,7 +3,48 @@ import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
// Public models returned by https://api.neuralwatt.com/v1/models (unauthenticated view).
|
|
4
4
|
// Pricing, capabilities, and limits are sourced from the API metadata fields.
|
|
5
5
|
export const NEURALWATT_MODELS: ProviderModelConfig[] = [
|
|
6
|
+
// Gemma 4 31B - NVIDIA NVFP4 checkpoint
|
|
7
|
+
{
|
|
8
|
+
id: "nvidia/Gemma-4-31B-IT-NVFP4",
|
|
9
|
+
name: "Gemma 4 31B (NVFP4)",
|
|
10
|
+
reasoning: false,
|
|
11
|
+
input: ["text", "image"],
|
|
12
|
+
cost: {
|
|
13
|
+
input: 0.144,
|
|
14
|
+
output: 0.42,
|
|
15
|
+
cacheRead: 0.036,
|
|
16
|
+
cacheWrite: 0,
|
|
17
|
+
},
|
|
18
|
+
contextWindow: 262128,
|
|
19
|
+
maxTokens: 16384,
|
|
20
|
+
compat: {
|
|
21
|
+
supportsDeveloperRole: false,
|
|
22
|
+
maxTokensField: "max_tokens",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
// Gemma 4 31B - Google, served from NVIDIA's NVFP4 checkpoint
|
|
26
|
+
{
|
|
27
|
+
id: "gemma-4-31b",
|
|
28
|
+
name: "Gemma 4 31B",
|
|
29
|
+
reasoning: false,
|
|
30
|
+
input: ["text", "image"],
|
|
31
|
+
cost: {
|
|
32
|
+
input: 0.144,
|
|
33
|
+
output: 0.42,
|
|
34
|
+
cacheRead: 0.036,
|
|
35
|
+
cacheWrite: 0,
|
|
36
|
+
},
|
|
37
|
+
contextWindow: 262128,
|
|
38
|
+
maxTokens: 16384,
|
|
39
|
+
compat: {
|
|
40
|
+
supportsDeveloperRole: false,
|
|
41
|
+
maxTokensField: "max_tokens",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
6
44
|
// GLM-5.2 - ZhipuAI
|
|
45
|
+
// Native reasoning tiers: off (none), high, max. Exposes Pi's `max` level
|
|
46
|
+
// (introduced in Pi 0.80.6) for GLM's top tier; `xhigh` is an unsupported
|
|
47
|
+
// hole between `high` and `max`.
|
|
7
48
|
{
|
|
8
49
|
id: "glm-5.2",
|
|
9
50
|
name: "GLM-5.2",
|
|
@@ -23,7 +64,8 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
|
|
|
23
64
|
low: null,
|
|
24
65
|
medium: null,
|
|
25
66
|
high: "high",
|
|
26
|
-
xhigh:
|
|
67
|
+
xhigh: null,
|
|
68
|
+
max: "max",
|
|
27
69
|
},
|
|
28
70
|
compat: {
|
|
29
71
|
supportsDeveloperRole: false,
|
|
@@ -63,14 +105,15 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
|
|
|
63
105
|
cacheWrite: 0,
|
|
64
106
|
},
|
|
65
107
|
contextWindow: 199984,
|
|
66
|
-
maxTokens:
|
|
108
|
+
maxTokens: 32000,
|
|
67
109
|
thinkingLevelMap: {
|
|
68
110
|
off: "none",
|
|
69
111
|
minimal: null,
|
|
70
112
|
low: null,
|
|
71
113
|
medium: null,
|
|
72
114
|
high: "high",
|
|
73
|
-
xhigh:
|
|
115
|
+
xhigh: null,
|
|
116
|
+
max: "max",
|
|
74
117
|
},
|
|
75
118
|
compat: {
|
|
76
119
|
supportsDeveloperRole: false,
|
|
@@ -91,7 +134,7 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
|
|
|
91
134
|
cacheWrite: 0,
|
|
92
135
|
},
|
|
93
136
|
contextWindow: 199984,
|
|
94
|
-
maxTokens:
|
|
137
|
+
maxTokens: 32000,
|
|
95
138
|
compat: {
|
|
96
139
|
supportsDeveloperRole: false,
|
|
97
140
|
maxTokensField: "max_tokens",
|
|
@@ -283,7 +326,8 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
|
|
|
283
326
|
low: null,
|
|
284
327
|
medium: null,
|
|
285
328
|
high: "high",
|
|
286
|
-
xhigh:
|
|
329
|
+
xhigh: null,
|
|
330
|
+
max: "max",
|
|
287
331
|
},
|
|
288
332
|
compat: {
|
|
289
333
|
supportsDeveloperRole: false,
|
|
@@ -366,7 +410,8 @@ export const NEURALWATT_MODELS: ProviderModelConfig[] = [
|
|
|
366
410
|
low: null,
|
|
367
411
|
medium: null,
|
|
368
412
|
high: "high",
|
|
369
|
-
xhigh:
|
|
413
|
+
xhigh: null,
|
|
414
|
+
max: "max",
|
|
370
415
|
},
|
|
371
416
|
compat: {
|
|
372
417
|
supportsDeveloperRole: false,
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
RefreshModelsContext,
|
|
6
6
|
} from "@earendil-works/pi-ai";
|
|
7
7
|
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { loadHiddenModels } from "./hidden";
|
|
8
|
+
import { HIDDEN_NEURALWATT_MODELS, loadHiddenModels } from "./hidden";
|
|
9
9
|
import { buildLegacyNeuralwattModels } from "./legacy";
|
|
10
10
|
import { NEURALWATT_MODELS } from "./public-models";
|
|
11
11
|
|
|
@@ -67,6 +67,22 @@ function dedupeHiddenModels(
|
|
|
67
67
|
return hiddenModels.filter((model) => !baselineIds.has(model.id));
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function configuredHiddenModels(
|
|
71
|
+
discoveredModels: ProviderModelConfig[],
|
|
72
|
+
baselineModels: ProviderModelConfig[],
|
|
73
|
+
): ProviderModelConfig[] {
|
|
74
|
+
const hardcodedIds = new Set(
|
|
75
|
+
HIDDEN_NEURALWATT_MODELS.map((model) => model.id),
|
|
76
|
+
);
|
|
77
|
+
return dedupeHiddenModels(
|
|
78
|
+
[
|
|
79
|
+
...HIDDEN_NEURALWATT_MODELS,
|
|
80
|
+
...discoveredModels.filter((model) => !hardcodedIds.has(model.id)),
|
|
81
|
+
],
|
|
82
|
+
baselineModels,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
function cachedHiddenModels(
|
|
71
87
|
stored: ModelsStoreEntry | undefined,
|
|
72
88
|
): ProviderModelConfig[] {
|
|
@@ -104,7 +120,10 @@ export async function refreshNeuralwattModels(
|
|
|
104
120
|
return baseline;
|
|
105
121
|
}
|
|
106
122
|
|
|
107
|
-
const cachedHidden =
|
|
123
|
+
const cachedHidden = configuredHiddenModels(
|
|
124
|
+
cachedHiddenModels(stored),
|
|
125
|
+
baseline,
|
|
126
|
+
);
|
|
108
127
|
const cachedCatalog = [...baseline, ...cachedHidden];
|
|
109
128
|
|
|
110
129
|
if (!context.allowNetwork || context.signal?.aborted) {
|
|
@@ -124,7 +143,7 @@ export async function refreshNeuralwattModels(
|
|
|
124
143
|
throw new Error("Neuralwatt model catalog refresh failed");
|
|
125
144
|
}
|
|
126
145
|
|
|
127
|
-
const catalog = [...baseline, ...
|
|
146
|
+
const catalog = [...baseline, ...configuredHiddenModels(hidden, baseline)];
|
|
128
147
|
await persistModels(context, catalog);
|
|
129
148
|
return catalog;
|
|
130
149
|
}
|
|
@@ -5,6 +5,13 @@ import { formatKwh, formatUsd } from "../../src/utils/quota-format";
|
|
|
5
5
|
export type WarningSeverity = "warning" | "critical";
|
|
6
6
|
|
|
7
7
|
const COOLDOWN_MS = 60 * 60 * 1000; // 60 minutes
|
|
8
|
+
const LOW_PCT = 25;
|
|
9
|
+
const CRITICAL_PCT = 10;
|
|
10
|
+
|
|
11
|
+
/** Per-kWh price once a subscription's included kWh are exhausted. */
|
|
12
|
+
const OVERAGE_RATE_PER_KWH_SUBSCRIBED = 5;
|
|
13
|
+
/** Per-kWh price when there is no active subscription (no included kWh). */
|
|
14
|
+
const OVERAGE_RATE_PER_KWH_UNSUBSCRIBED = 10;
|
|
8
15
|
|
|
9
16
|
interface AlertState {
|
|
10
17
|
lastSeverity: WarningSeverity;
|
|
@@ -19,14 +26,16 @@ export function clearAlertState(): void {
|
|
|
19
26
|
alerts.clear();
|
|
20
27
|
}
|
|
21
28
|
|
|
29
|
+
function severityForPct(pct: number): WarningSeverity {
|
|
30
|
+
return pct <= CRITICAL_PCT ? "critical" : "warning";
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
function shouldNotify(key: string, severity: WarningSeverity): boolean {
|
|
23
34
|
const state = alerts.get(key);
|
|
24
35
|
if (!state) return true;
|
|
25
36
|
|
|
26
37
|
const order: WarningSeverity[] = ["warning", "critical"];
|
|
27
|
-
|
|
28
|
-
const lastIndex = order.indexOf(state.lastSeverity);
|
|
29
|
-
if (currentIndex > lastIndex) return true;
|
|
38
|
+
if (order.indexOf(severity) > order.indexOf(state.lastSeverity)) return true;
|
|
30
39
|
|
|
31
40
|
return Date.now() - state.lastNotifiedAt >= COOLDOWN_MS;
|
|
32
41
|
}
|
|
@@ -35,9 +44,121 @@ function markNotified(key: string, severity: WarningSeverity): void {
|
|
|
35
44
|
alerts.set(key, { lastSeverity: severity, lastNotifiedAt: Date.now() });
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
interface PendingWarning {
|
|
48
|
+
key: string;
|
|
49
|
+
severity: WarningSeverity;
|
|
50
|
+
message: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Subscription energy (kWh) — the primary billing pool while subscribed. */
|
|
54
|
+
function energyWarning(
|
|
55
|
+
sub: NonNullable<NeuralwattQuotas["subscription"]>,
|
|
56
|
+
): PendingWarning | undefined {
|
|
57
|
+
if (sub.kwh_included <= 0) return;
|
|
58
|
+
const pct = (sub.kwh_remaining / sub.kwh_included) * 100;
|
|
59
|
+
if (pct > LOW_PCT) return;
|
|
60
|
+
return {
|
|
61
|
+
key: "energy",
|
|
62
|
+
severity: severityForPct(pct),
|
|
63
|
+
message: `Energy: ${pct.toFixed(0)}% remaining (${formatKwh(sub.kwh_remaining)} of ${formatKwh(sub.kwh_included)})`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Balance credits (USD) — on-demand top-up pool. */
|
|
68
|
+
function creditsWarning(quotas: NeuralwattQuotas): PendingWarning | undefined {
|
|
69
|
+
const { credits_remaining_usd, total_credits_usd } = quotas.balance;
|
|
70
|
+
if (total_credits_usd <= 0) return;
|
|
71
|
+
const pct = (credits_remaining_usd / total_credits_usd) * 100;
|
|
72
|
+
if (pct > LOW_PCT) return;
|
|
73
|
+
return {
|
|
74
|
+
key: "credits",
|
|
75
|
+
severity: severityForPct(pct),
|
|
76
|
+
message: `Credits: ${pct.toFixed(0)}% remaining (${formatUsd(credits_remaining_usd)} of ${formatUsd(total_credits_usd)})`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Overage usage billed against the overage cap, derived from kWh usage. */
|
|
81
|
+
interface OverageProgress {
|
|
82
|
+
/** kWh billed at the overage rate. */
|
|
83
|
+
overageKwh: number;
|
|
84
|
+
/** Per-kWh rate applied (USD). */
|
|
85
|
+
rate: number;
|
|
86
|
+
/** Cost of the overage kWh so far (USD). */
|
|
87
|
+
costUsd: number;
|
|
88
|
+
/** Configured overage cap (USD), or 0 when none. */
|
|
89
|
+
capUsd: number;
|
|
90
|
+
/** Remaining cap headroom (USD), clamped at 0. */
|
|
91
|
+
remainingUsd: number;
|
|
92
|
+
/** Remaining cap as a percentage of the cap (0-100). 0 when no cap. */
|
|
93
|
+
pctRemaining: number;
|
|
94
|
+
/** Cap exhausted (overage cost has reached or passed the cap). */
|
|
95
|
+
exhausted: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function computeOverageProgress(
|
|
99
|
+
quotas: NeuralwattQuotas,
|
|
100
|
+
): OverageProgress {
|
|
101
|
+
const capUsd = quotas.limits.overage_limit_usd ?? 0;
|
|
102
|
+
const hasSub = quotas.subscription !== null;
|
|
103
|
+
|
|
104
|
+
// Subscribed: only kWh beyond the included quota are billed at the overage
|
|
105
|
+
// rate. Unsubscribed: every kWh is billable — the monthly usage total is the
|
|
106
|
+
// overage pool (there is no included quota to subtract from).
|
|
107
|
+
const overageKwh = hasSub
|
|
108
|
+
? Math.max(
|
|
109
|
+
0,
|
|
110
|
+
(quotas.subscription?.kwh_used ?? 0) -
|
|
111
|
+
(quotas.subscription?.kwh_included ?? 0),
|
|
112
|
+
)
|
|
113
|
+
: quotas.usage.current_month.energy_kwh;
|
|
114
|
+
|
|
115
|
+
const rate = hasSub
|
|
116
|
+
? OVERAGE_RATE_PER_KWH_SUBSCRIBED
|
|
117
|
+
: OVERAGE_RATE_PER_KWH_UNSUBSCRIBED;
|
|
118
|
+
const costUsd = overageKwh * rate;
|
|
119
|
+
const remainingUsd = Math.max(0, capUsd - costUsd);
|
|
120
|
+
const pctRemaining = capUsd > 0 ? (remainingUsd / capUsd) * 100 : 0;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
overageKwh,
|
|
124
|
+
rate,
|
|
125
|
+
costUsd,
|
|
126
|
+
capUsd,
|
|
127
|
+
remainingUsd,
|
|
128
|
+
pctRemaining,
|
|
129
|
+
exhausted: capUsd > 0 && remainingUsd <= 0,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function overageWarning(progress: OverageProgress): PendingWarning {
|
|
134
|
+
const pct = Math.max(0, Math.min(100, progress.pctRemaining));
|
|
135
|
+
return {
|
|
136
|
+
key: "overage",
|
|
137
|
+
// Entering overage is itself worth a critical alert; the % controls the
|
|
138
|
+
// message, not whether we notify.
|
|
139
|
+
severity: "critical",
|
|
140
|
+
message: `Overage cap: ${pct.toFixed(0)}% remaining (${formatUsd(progress.remainingUsd)} of ${formatUsd(progress.capUsd)}, ${formatKwh(progress.overageKwh)} over @ ${formatUsd(progress.rate)}/kWh)`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
38
144
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
145
|
+
* Warning progression mirrors Neuralwatt's billing order. Each stage uses its
|
|
146
|
+
* own alert key, so once a later stage starts the earlier one stops — the
|
|
147
|
+
* warning "moves on" instead of re-reporting a depleted pool forever.
|
|
148
|
+
*
|
|
149
|
+
* subscribed, not in overage → energy (kWh remaining of quota)
|
|
150
|
+
* subscribed, in overage, cap set → overage cap progress (credits unreachable)
|
|
151
|
+
* subscribed, in overage, cap exhausted→ balance credits
|
|
152
|
+
* subscribed, in overage, no cap → balance credits (overage draws them down)
|
|
153
|
+
* no subscription, cap set → overage cap progress (all kWh billable)
|
|
154
|
+
* no subscription, no cap → balance credits
|
|
155
|
+
*
|
|
156
|
+
* Overage cost is derived from kWh usage: subscribed pays $5/kWh for kWh
|
|
157
|
+
* beyond the included quota; unsubscribed pays $10/kWh for all usage. There is
|
|
158
|
+
* no overage-spent counter in the API, so progress is computed.
|
|
159
|
+
*
|
|
160
|
+
* Usage totals (monthly/lifetime cost in USD) are deliberately not used as a
|
|
161
|
+
* threshold basis — they are not directly tied to the subscription's kWh quota.
|
|
41
162
|
*/
|
|
42
163
|
export function checkQuotas(
|
|
43
164
|
ctx: ExtensionContext,
|
|
@@ -45,55 +166,59 @@ export function checkQuotas(
|
|
|
45
166
|
): void {
|
|
46
167
|
if (!ctx.hasUI) return;
|
|
47
168
|
|
|
48
|
-
const
|
|
49
|
-
const
|
|
169
|
+
const pending: PendingWarning[] = [];
|
|
170
|
+
const sub = quotas.subscription;
|
|
50
171
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
warnings.push(
|
|
62
|
-
`Credits: ${pct.toFixed(0)}% remaining (${formatUsd(credits_remaining_usd)} of ${formatUsd(total_credits_usd)})`,
|
|
63
|
-
);
|
|
64
|
-
}
|
|
172
|
+
if (sub?.in_overage) {
|
|
173
|
+
const cap = quotas.limits.overage_limit_usd;
|
|
174
|
+
if (cap !== null && cap > 0) {
|
|
175
|
+
const progress = computeOverageProgress(quotas);
|
|
176
|
+
if (progress.exhausted) {
|
|
177
|
+
// Cap spent — fall through to the balance credits.
|
|
178
|
+
const cw = creditsWarning(quotas);
|
|
179
|
+
if (cw) pending.push(cw);
|
|
180
|
+
} else {
|
|
181
|
+
pending.push(overageWarning(progress));
|
|
65
182
|
}
|
|
183
|
+
} else {
|
|
184
|
+
// No cap: overage spends down the balance credits directly.
|
|
185
|
+
const cw = creditsWarning(quotas);
|
|
186
|
+
if (cw) pending.push(cw);
|
|
66
187
|
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (shouldNotify(key, severity)) {
|
|
82
|
-
markNotified(key, severity);
|
|
83
|
-
const tag = in_overage ? " [OVERAGE]" : "";
|
|
84
|
-
warnings.push(
|
|
85
|
-
`Energy${tag}: ${pct.toFixed(0)}% remaining (${formatKwh(kwh_remaining)} of ${formatKwh(kwh_included)})`,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
188
|
+
} else if (sub) {
|
|
189
|
+
const ew = energyWarning(sub);
|
|
190
|
+
if (ew) pending.push(ew);
|
|
191
|
+
} else {
|
|
192
|
+
// No subscription. All kWh bill at the unsubscribed rate; warn on the
|
|
193
|
+
// overage cap when one is set, otherwise on the balance credits.
|
|
194
|
+
const cap = quotas.limits.overage_limit_usd;
|
|
195
|
+
if (cap !== null && cap > 0) {
|
|
196
|
+
const progress = computeOverageProgress(quotas);
|
|
197
|
+
if (progress.exhausted) {
|
|
198
|
+
const cw = creditsWarning(quotas);
|
|
199
|
+
if (cw) pending.push(cw);
|
|
200
|
+
} else if (progress.pctRemaining <= LOW_PCT) {
|
|
201
|
+
pending.push(overageWarning(progress));
|
|
88
202
|
}
|
|
203
|
+
} else {
|
|
204
|
+
const cw = creditsWarning(quotas);
|
|
205
|
+
if (cw) pending.push(cw);
|
|
89
206
|
}
|
|
90
207
|
}
|
|
91
208
|
|
|
92
|
-
|
|
209
|
+
const fired = pending.filter((w) => {
|
|
210
|
+
if (shouldNotify(w.key, w.severity)) {
|
|
211
|
+
markNotified(w.key, w.severity);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (fired.length === 0) return;
|
|
93
218
|
|
|
94
|
-
const hasCritical =
|
|
219
|
+
const hasCritical = fired.some((w) => w.severity === "critical");
|
|
95
220
|
ctx.ui.notify(
|
|
96
|
-
`Neuralwatt quota warning:\n${
|
|
221
|
+
`Neuralwatt quota warning:\n${fired.map((w) => ` - ${w.message}`).join("\n")}`,
|
|
97
222
|
hasCritical ? "error" : "warning",
|
|
98
223
|
);
|
|
99
224
|
}
|