@aliou/pi-neuralwatt 0.7.6 → 0.8.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 +4 -2
- package/{src/extensions → extensions}/command-quotas/command.ts +2 -2
- package/extensions/command-quotas/components/progress-bar.ts +38 -0
- package/{src/extensions → extensions}/command-quotas/components/quota-tabs.ts +4 -4
- package/{src/extensions → extensions}/command-quotas/components/quotas-display.ts +1 -1
- package/{src/extensions → extensions}/command-quotas/index.ts +3 -3
- package/extensions/provider/commands/settings/index.ts +246 -0
- package/{src/extensions → extensions}/provider/index.ts +36 -25
- package/{src/extensions → extensions}/provider/models/hidden.ts +3 -3
- package/{src/extensions → extensions}/provider/quota-store.ts +4 -4
- package/{src/extensions → extensions}/provider/sse-quotas.ts +1 -1
- package/{src/extensions → extensions}/quota-warnings/index.ts +11 -20
- package/{src/extensions → extensions}/quota-warnings/notifier.ts +9 -8
- package/{src/extensions → extensions}/sub-bar-integration/index.ts +9 -10
- package/{src/extensions → extensions}/sub-bar-integration/snapshot.ts +3 -3
- package/package.json +20 -14
- package/schema.json +50 -6
- package/src/config/defaults.ts +17 -0
- package/src/config/index.ts +9 -0
- package/src/config/loader.ts +74 -0
- package/src/config/migration/01-disable-legacy-model-ids-by-default.ts +44 -0
- package/src/config/migration/02-flat-to-nested-config.ts +149 -0
- package/src/config/migration/index.ts +16 -0
- package/src/config/types.ts +77 -0
- package/src/{types/quota-events.ts → events.ts} +25 -13
- package/src/lib/neuralwatt-api.ts +1 -1
- package/src/types/quota-result.ts +12 -0
- package/src/utils/quota-bar.ts +0 -38
- package/src/config.ts +0 -210
- /package/{src/lib/env.ts → extensions/_shared/auth.ts} +0 -0
- /package/{src/extensions → extensions}/provider/context-overflow.ts +0 -0
- /package/{src/extensions → extensions}/provider/models/cache.ts +0 -0
- /package/{src/extensions → extensions}/provider/models/index.ts +0 -0
- /package/{src/extensions → extensions}/provider/models/legacy.ts +0 -0
- /package/{src/extensions → extensions}/provider/models/public-models.ts +0 -0
- /package/{src/extensions → extensions}/provider/rate-limit-error.ts +0 -0
- /package/{src/extensions → extensions}/provider/stream-simple.ts +0 -0
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ pi install npm:@aliou/pi-neuralwatt
|
|
|
36
36
|
pi install git:github.com/aliou/pi-neuralwatt
|
|
37
37
|
|
|
38
38
|
# Local development
|
|
39
|
-
pi -e ./
|
|
39
|
+
pi -e ./extensions/provider/index.ts
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Usage
|
|
@@ -80,9 +80,11 @@ Configure features with `/neuralwatt:settings`:
|
|
|
80
80
|
|
|
81
81
|
The provider itself cannot be disabled — it is always loaded.
|
|
82
82
|
|
|
83
|
+
Configuration uses nested per-feature sections. Existing flat config files are migrated automatically, with a backup written next to the migrated config.
|
|
84
|
+
|
|
83
85
|
## Adding or Updating Models
|
|
84
86
|
|
|
85
|
-
Models are hardcoded in `
|
|
87
|
+
Models are hardcoded in `extensions/provider/models/public-models.ts` and validated against the live API. To update:
|
|
86
88
|
|
|
87
89
|
1. Run `pnpm test` — it fetches `/v1/models` and compares against hardcoded definitions
|
|
88
90
|
2. Fix any discrepancies (missing models, changed context windows)
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
type ExtensionAPI,
|
|
4
4
|
getAgentDir,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { fetchQuotas } from "../../src/lib/neuralwatt-api";
|
|
7
|
+
import { getNeuralwattApiKey } from "../_shared/auth";
|
|
8
8
|
import { QuotasComponent } from "./components/quotas-display";
|
|
9
9
|
|
|
10
10
|
function missingAuthMessage(): string {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Severity } from "../../../src/utils/quota-bar";
|
|
3
|
+
|
|
4
|
+
export type BarStyle = "filled-used" | "filled-remaining";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Render a progress bar.
|
|
8
|
+
*
|
|
9
|
+
* filled-used: filled region = used portion (colored by severity of remaining%)
|
|
10
|
+
* filled-remaining: filled region = remaining portion (colored by severity of remaining%)
|
|
11
|
+
*/
|
|
12
|
+
export function renderProgressBar(
|
|
13
|
+
percent: number,
|
|
14
|
+
width: number,
|
|
15
|
+
theme: Theme,
|
|
16
|
+
severity: Severity,
|
|
17
|
+
style: BarStyle = "filled-remaining",
|
|
18
|
+
): string {
|
|
19
|
+
const clamped = Math.max(0, Math.min(100, Math.round(percent)));
|
|
20
|
+
const filledCount = Math.round((clamped / 100) * width);
|
|
21
|
+
|
|
22
|
+
const parts: string[] = [];
|
|
23
|
+
for (let idx = 0; idx < width; idx++) {
|
|
24
|
+
const isFilled = idx < filledCount;
|
|
25
|
+
if (style === "filled-used") {
|
|
26
|
+
// filled = used (severity color), empty = remaining (dim)
|
|
27
|
+
parts.push(
|
|
28
|
+
isFilled ? theme.fg(severity, "\u2593") : theme.fg("success", "\u2591"),
|
|
29
|
+
);
|
|
30
|
+
} else {
|
|
31
|
+
// filled = remaining (severity color), empty = used (dim)
|
|
32
|
+
parts.push(
|
|
33
|
+
isFilled ? theme.fg(severity, "\u2588") : theme.fg("dim", "\u2591"),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return parts.join("");
|
|
38
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import type { NeuralwattQuotas } from "../../../types/quota-api";
|
|
3
|
+
import type { NeuralwattQuotas } from "../../../src/types/quota-api";
|
|
4
4
|
import {
|
|
5
5
|
percentCreditsRemaining,
|
|
6
6
|
percentEnergyRemaining,
|
|
7
|
-
renderProgressBar,
|
|
8
7
|
severityFromPercent,
|
|
9
|
-
} from "../../../utils/quota-bar";
|
|
8
|
+
} from "../../../src/utils/quota-bar";
|
|
10
9
|
import {
|
|
11
10
|
formatKwh,
|
|
12
11
|
formatTokens,
|
|
13
12
|
formatUsd,
|
|
14
|
-
} from "../../../utils/quota-format";
|
|
13
|
+
} from "../../../src/utils/quota-format";
|
|
14
|
+
import { renderProgressBar } from "./progress-bar";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Subscription tab — plan details, energy quota, billing period.
|
|
@@ -2,7 +2,7 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
4
4
|
import { Loader, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
5
|
-
import type { NeuralwattQuotas } from "../../../types/quota-api";
|
|
5
|
+
import type { NeuralwattQuotas } from "../../../src/types/quota-api";
|
|
6
6
|
import {
|
|
7
7
|
renderCreditsTab,
|
|
8
8
|
renderSubscriptionTab,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { configLoader } from "../../src/config";
|
|
2
3
|
import {
|
|
3
|
-
configLoader,
|
|
4
4
|
NEURALWATT_EXTENSIONS_REGISTER_EVENT,
|
|
5
5
|
NEURALWATT_EXTENSIONS_REQUEST_EVENT,
|
|
6
|
-
} from "../../
|
|
6
|
+
} from "../../src/events";
|
|
7
7
|
import { registerQuotasCommand } from "./command";
|
|
8
8
|
|
|
9
9
|
export default async function (pi: ExtensionAPI) {
|
|
@@ -11,7 +11,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
11
11
|
|
|
12
12
|
const config = configLoader.getConfig();
|
|
13
13
|
|
|
14
|
-
if (config.quotaCommand) {
|
|
14
|
+
if (config.quotaCommand.enabled) {
|
|
15
15
|
registerQuotasCommand(pi);
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import {
|
|
2
|
+
registerSettingsCommand,
|
|
3
|
+
type SettingsSection,
|
|
4
|
+
} from "@aliou/pi-utils-settings";
|
|
5
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
7
|
+
import {
|
|
8
|
+
configLoader,
|
|
9
|
+
type NeuralwattConfig,
|
|
10
|
+
type NeuralwattRawConfig,
|
|
11
|
+
type ResolvedNeuralwattConfig,
|
|
12
|
+
} from "../../../../src/config";
|
|
13
|
+
import {
|
|
14
|
+
NEURALWATT_CONFIG_UPDATED_EVENT,
|
|
15
|
+
type NeuralwattFeatureId,
|
|
16
|
+
} from "../../../../src/events";
|
|
17
|
+
|
|
18
|
+
export interface RegisterNeuralwattSettingsOptions {
|
|
19
|
+
getLoadedFeatures: () => Set<NeuralwattFeatureId>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function emitConfigUpdated(pi: ExtensionAPI): void {
|
|
23
|
+
pi.events.emit(NEURALWATT_CONFIG_UPDATED_EVENT, {
|
|
24
|
+
config: configLoader.getConfig(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function featureRow(
|
|
29
|
+
id: NeuralwattFeatureId,
|
|
30
|
+
label: string,
|
|
31
|
+
description: string,
|
|
32
|
+
configValue: boolean,
|
|
33
|
+
isLoaded: boolean,
|
|
34
|
+
): SettingItem {
|
|
35
|
+
if (isLoaded) {
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
label,
|
|
39
|
+
description,
|
|
40
|
+
currentValue: configValue ? "enabled" : "disabled",
|
|
41
|
+
values: ["enabled", "disabled"],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
label,
|
|
47
|
+
description: `${description} (Not loaded by Pi)`,
|
|
48
|
+
currentValue: "unavailable",
|
|
49
|
+
values: [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function optionalFeatureValue(value: unknown): boolean | undefined {
|
|
54
|
+
if (typeof value === "boolean") return value;
|
|
55
|
+
if (value && typeof value === "object") {
|
|
56
|
+
const enabled = (value as { enabled?: boolean }).enabled;
|
|
57
|
+
if (typeof enabled === "boolean") return enabled;
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function featureValue(value: unknown, fallback: boolean): boolean {
|
|
63
|
+
return optionalFeatureValue(value) ?? fallback;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function toNestedConfig(config: NeuralwattRawConfig): NeuralwattConfig {
|
|
67
|
+
const provider = "provider" in config ? config.provider : undefined;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
provider: {
|
|
71
|
+
...(provider ?? {}),
|
|
72
|
+
includeLegacyModelIds:
|
|
73
|
+
provider?.includeLegacyModelIds ??
|
|
74
|
+
("includeLegacyModelIds" in config
|
|
75
|
+
? config.includeLegacyModelIds
|
|
76
|
+
: undefined),
|
|
77
|
+
includeHiddenModels:
|
|
78
|
+
provider?.includeHiddenModels ??
|
|
79
|
+
("includeHiddenModels" in config
|
|
80
|
+
? config.includeHiddenModels
|
|
81
|
+
: undefined),
|
|
82
|
+
},
|
|
83
|
+
quotaCommand: {
|
|
84
|
+
...(typeof config.quotaCommand === "object" ? config.quotaCommand : {}),
|
|
85
|
+
enabled: optionalFeatureValue(config.quotaCommand),
|
|
86
|
+
},
|
|
87
|
+
quotaWarnings: {
|
|
88
|
+
...(typeof config.quotaWarnings === "object" ? config.quotaWarnings : {}),
|
|
89
|
+
enabled: optionalFeatureValue(config.quotaWarnings),
|
|
90
|
+
},
|
|
91
|
+
subBarIntegration: {
|
|
92
|
+
...(typeof config.subBarIntegration === "object"
|
|
93
|
+
? config.subBarIntegration
|
|
94
|
+
: {}),
|
|
95
|
+
enabled: optionalFeatureValue(config.subBarIntegration),
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function registerNeuralwattSettings(
|
|
101
|
+
pi: ExtensionAPI,
|
|
102
|
+
options: RegisterNeuralwattSettingsOptions,
|
|
103
|
+
): void {
|
|
104
|
+
const { getLoadedFeatures } = options;
|
|
105
|
+
|
|
106
|
+
registerSettingsCommand<NeuralwattRawConfig, ResolvedNeuralwattConfig>(pi, {
|
|
107
|
+
commandName: "neuralwatt:settings",
|
|
108
|
+
title: "Neuralwatt Settings",
|
|
109
|
+
configStore: configLoader,
|
|
110
|
+
buildSections: (tabConfig, resolved): SettingsSection[] => {
|
|
111
|
+
const loaded = getLoadedFeatures();
|
|
112
|
+
return [
|
|
113
|
+
{
|
|
114
|
+
label: "Features",
|
|
115
|
+
items: [
|
|
116
|
+
featureRow(
|
|
117
|
+
"quotaCommand",
|
|
118
|
+
"Quota command",
|
|
119
|
+
"Toggle the /neuralwatt:quota command, showing your API usage at a glance",
|
|
120
|
+
featureValue(
|
|
121
|
+
tabConfig?.quotaCommand,
|
|
122
|
+
resolved.quotaCommand.enabled,
|
|
123
|
+
),
|
|
124
|
+
loaded.has("quotaCommand"),
|
|
125
|
+
),
|
|
126
|
+
featureRow(
|
|
127
|
+
"quotaWarnings",
|
|
128
|
+
"Quota warnings",
|
|
129
|
+
"Toggle notifications when credits or energy are running low",
|
|
130
|
+
featureValue(
|
|
131
|
+
tabConfig?.quotaWarnings,
|
|
132
|
+
resolved.quotaWarnings.enabled,
|
|
133
|
+
),
|
|
134
|
+
loaded.has("quotaWarnings"),
|
|
135
|
+
),
|
|
136
|
+
featureRow(
|
|
137
|
+
"subBarIntegration",
|
|
138
|
+
"Sub-bar integration",
|
|
139
|
+
"Toggle integration with the status bar and sub-core",
|
|
140
|
+
featureValue(
|
|
141
|
+
tabConfig?.subBarIntegration,
|
|
142
|
+
resolved.subBarIntegration.enabled,
|
|
143
|
+
),
|
|
144
|
+
loaded.has("subBarIntegration"),
|
|
145
|
+
),
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
label: "Other settings",
|
|
150
|
+
items: [
|
|
151
|
+
{
|
|
152
|
+
id: "includeLegacyModelIds",
|
|
153
|
+
label: "Legacy model IDs",
|
|
154
|
+
description:
|
|
155
|
+
"Include deprecated Neuralwatt model IDs as aliases in the model picker",
|
|
156
|
+
currentValue:
|
|
157
|
+
((tabConfig &&
|
|
158
|
+
"provider" in tabConfig &&
|
|
159
|
+
tabConfig.provider?.includeLegacyModelIds) ??
|
|
160
|
+
(tabConfig &&
|
|
161
|
+
"includeLegacyModelIds" in tabConfig &&
|
|
162
|
+
tabConfig.includeLegacyModelIds) ??
|
|
163
|
+
resolved.provider.includeLegacyModelIds)
|
|
164
|
+
? "include"
|
|
165
|
+
: "ignore",
|
|
166
|
+
values: ["include", "ignore"],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "includeHiddenModels",
|
|
170
|
+
label: "Hidden models",
|
|
171
|
+
description:
|
|
172
|
+
"Include Neuralwatt models that are accessible via API key but not advertised in the public model list",
|
|
173
|
+
currentValue:
|
|
174
|
+
((tabConfig &&
|
|
175
|
+
"provider" in tabConfig &&
|
|
176
|
+
tabConfig.provider?.includeHiddenModels) ??
|
|
177
|
+
(tabConfig &&
|
|
178
|
+
"includeHiddenModels" in tabConfig &&
|
|
179
|
+
tabConfig.includeHiddenModels) ??
|
|
180
|
+
resolved.provider.includeHiddenModels)
|
|
181
|
+
? "include"
|
|
182
|
+
: "ignore",
|
|
183
|
+
values: ["include", "ignore"],
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
},
|
|
189
|
+
onSettingChange: (id, newValue, config) => {
|
|
190
|
+
// Non-feature toggles are handled first so they are not blocked by the
|
|
191
|
+
// loaded-features guard (they are managed directly by the provider).
|
|
192
|
+
if (id === "includeLegacyModelIds") {
|
|
193
|
+
const nestedConfig = toNestedConfig(config);
|
|
194
|
+
return {
|
|
195
|
+
...nestedConfig,
|
|
196
|
+
provider: {
|
|
197
|
+
...nestedConfig.provider,
|
|
198
|
+
includeLegacyModelIds: newValue === "include",
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (id === "includeHiddenModels") {
|
|
204
|
+
const nestedConfig = toNestedConfig(config);
|
|
205
|
+
return {
|
|
206
|
+
...nestedConfig,
|
|
207
|
+
provider: {
|
|
208
|
+
...nestedConfig.provider,
|
|
209
|
+
includeHiddenModels: newValue === "include",
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!getLoadedFeatures().has(id as NeuralwattFeatureId)) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const enabled = newValue === "enabled";
|
|
219
|
+
switch (id) {
|
|
220
|
+
case "quotaCommand":
|
|
221
|
+
return {
|
|
222
|
+
...toNestedConfig(config),
|
|
223
|
+
quotaCommand: { ...toNestedConfig(config).quotaCommand, enabled },
|
|
224
|
+
};
|
|
225
|
+
case "quotaWarnings":
|
|
226
|
+
return {
|
|
227
|
+
...toNestedConfig(config),
|
|
228
|
+
quotaWarnings: { ...toNestedConfig(config).quotaWarnings, enabled },
|
|
229
|
+
};
|
|
230
|
+
case "subBarIntegration":
|
|
231
|
+
return {
|
|
232
|
+
...toNestedConfig(config),
|
|
233
|
+
subBarIntegration: {
|
|
234
|
+
...toNestedConfig(config).subBarIntegration,
|
|
235
|
+
enabled,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
default:
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
onSave: async () => {
|
|
243
|
+
emitConfigUpdated(pi);
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
import { getApiProvider } from "@earendil-works/pi-ai";
|
|
1
|
+
import { getApiProvider } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
import type {
|
|
3
3
|
ExtensionAPI,
|
|
4
4
|
ProviderModelConfig,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { configLoader } from "../../src/config";
|
|
6
7
|
import {
|
|
7
|
-
configLoader,
|
|
8
|
-
emitConfigUpdated,
|
|
9
8
|
NEURALWATT_CONFIG_UPDATED_EVENT,
|
|
10
9
|
NEURALWATT_EXTENSIONS_REGISTER_EVENT,
|
|
11
10
|
NEURALWATT_EXTENSIONS_REQUEST_EVENT,
|
|
12
|
-
type NeuralwattFeatureId,
|
|
13
|
-
registerNeuralwattSettings,
|
|
14
|
-
} from "../../config";
|
|
15
|
-
import { getNeuralwattApiKey } from "../../lib/env";
|
|
16
|
-
import { fetchQuotas } from "../../lib/neuralwatt-api";
|
|
17
|
-
import type { NeuralwattQuotas } from "../../types/quota-api";
|
|
18
|
-
import {
|
|
19
11
|
NEURALWATT_QUOTAS_REQUEST_EVENT,
|
|
20
12
|
NEURALWATT_QUOTAS_UPDATED_EVENT,
|
|
13
|
+
type NeuralwattFeatureId,
|
|
21
14
|
type NeuralwattQuotasUpdatedPayload,
|
|
22
|
-
} from "../../
|
|
15
|
+
} from "../../src/events";
|
|
16
|
+
import { fetchQuotas } from "../../src/lib/neuralwatt-api";
|
|
17
|
+
import type { NeuralwattQuotas } from "../../src/types/quota-api";
|
|
18
|
+
import { getNeuralwattApiKey } from "../_shared/auth";
|
|
19
|
+
import { registerNeuralwattSettings } from "./commands/settings";
|
|
23
20
|
import { normalizeNeuralwattContextOverflowError } from "./context-overflow";
|
|
24
21
|
import {
|
|
25
22
|
getNeuralwattModels,
|
|
@@ -38,16 +35,23 @@ import { wrapNeuralwattStreamSimple } from "./stream-simple";
|
|
|
38
35
|
|
|
39
36
|
const HEADER_EMIT_THROTTLE_MS = 5_000;
|
|
40
37
|
|
|
38
|
+
function emitConfigUpdated(pi: ExtensionAPI): void {
|
|
39
|
+
pi.events.emit(NEURALWATT_CONFIG_UPDATED_EVENT, {
|
|
40
|
+
config: configLoader.getConfig(),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
41
44
|
function registerNeuralwattProvider(
|
|
42
45
|
pi: ExtensionAPI,
|
|
43
46
|
onSseQuota: (line: string) => void,
|
|
44
47
|
hiddenModels: ProviderModelConfig[] = [],
|
|
45
48
|
): void {
|
|
46
|
-
const {
|
|
47
|
-
configLoader.getConfig();
|
|
49
|
+
const { provider: providerConfig } = configLoader.getConfig();
|
|
48
50
|
|
|
49
|
-
const publicModels = getNeuralwattModels({
|
|
50
|
-
|
|
51
|
+
const publicModels = getNeuralwattModels({
|
|
52
|
+
includeLegacyModelIds: providerConfig.includeLegacyModelIds,
|
|
53
|
+
});
|
|
54
|
+
const resolvedHiddenModels = providerConfig.includeHiddenModels
|
|
51
55
|
? dedupeHiddenModels(hiddenModels, publicModels)
|
|
52
56
|
: [];
|
|
53
57
|
|
|
@@ -109,20 +113,23 @@ export default async function (pi: ExtensionAPI) {
|
|
|
109
113
|
// load time. `session_start` then revalidates from the live API and writes
|
|
110
114
|
// the cache back. First run with no cache still warns once.
|
|
111
115
|
let hiddenModels: ProviderModelConfig[] = [];
|
|
112
|
-
if (configLoader.getConfig().includeHiddenModels) {
|
|
116
|
+
if (configLoader.getConfig().provider.includeHiddenModels) {
|
|
113
117
|
hiddenModels = loadCachedHiddenModels();
|
|
114
118
|
}
|
|
115
119
|
let hiddenModelsLoaded = false;
|
|
116
120
|
let hiddenModelsAbort: AbortController | undefined;
|
|
117
121
|
|
|
122
|
+
let lastSseEmitAt = 0;
|
|
123
|
+
|
|
118
124
|
const handleSseQuota = (line: string) => {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
if (now - lastSseEmitAt < HEADER_EMIT_THROTTLE_MS) return;
|
|
127
|
+
|
|
119
128
|
const quotas = updateQuotasFromSseComment(latestQuotas, line);
|
|
120
129
|
if (!quotas || quotas === latestQuotas) return;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
source: "sse",
|
|
125
|
-
});
|
|
130
|
+
|
|
131
|
+
lastSseEmitAt = now;
|
|
132
|
+
emitQuotas(quotas, "sse");
|
|
126
133
|
};
|
|
127
134
|
|
|
128
135
|
registerNeuralwattProvider(pi, handleSseQuota, hiddenModels);
|
|
@@ -139,7 +146,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
139
146
|
// cache so previously discovered models are available immediately without
|
|
140
147
|
// waiting for the next session_start revalidation.
|
|
141
148
|
if (
|
|
142
|
-
configLoader.getConfig().includeHiddenModels &&
|
|
149
|
+
configLoader.getConfig().provider.includeHiddenModels &&
|
|
143
150
|
!hiddenModelsLoaded &&
|
|
144
151
|
hiddenModels.length === 0
|
|
145
152
|
) {
|
|
@@ -257,15 +264,19 @@ export default async function (pi: ExtensionAPI) {
|
|
|
257
264
|
|
|
258
265
|
pi.on("session_start", async (_event, ctx) => {
|
|
259
266
|
pendingRateLimitInfo = undefined;
|
|
260
|
-
|
|
261
|
-
|
|
267
|
+
const messages = [...new Set(configLoader.drainMessages())];
|
|
268
|
+
if (messages.length > 0) {
|
|
269
|
+
ctx.ui.notify(messages.join("\n"), "info");
|
|
262
270
|
}
|
|
263
271
|
|
|
264
272
|
loadedFeatures.clear();
|
|
265
273
|
pi.events.emit(NEURALWATT_EXTENSIONS_REQUEST_EVENT, undefined);
|
|
266
274
|
emitConfigUpdated(pi);
|
|
267
275
|
|
|
268
|
-
if (
|
|
276
|
+
if (
|
|
277
|
+
!hiddenModelsLoaded &&
|
|
278
|
+
configLoader.getConfig().provider.includeHiddenModels
|
|
279
|
+
) {
|
|
269
280
|
hiddenModelsLoaded = true;
|
|
270
281
|
hiddenModelsAbort?.abort();
|
|
271
282
|
hiddenModelsAbort = new AbortController();
|
|
@@ -2,9 +2,9 @@ import type {
|
|
|
2
2
|
AuthStorage,
|
|
3
3
|
ProviderModelConfig,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
5
|
+
import { fetchNeuralwattModels } from "../../../src/lib/neuralwatt-api";
|
|
6
|
+
import type { NeuralwattApiModel } from "../../../src/types/models-api";
|
|
7
|
+
import { getNeuralwattApiKey } from "../../_shared/auth";
|
|
8
8
|
import { NEURALWATT_MODELS } from "./public-models";
|
|
9
9
|
|
|
10
10
|
// Per-ID overrides for known hidden models. The authenticated /v1/models endpoint
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { AuthStorage } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
import { fetchQuotas } from "../../lib/neuralwatt-api";
|
|
4
|
-
import type { NeuralwattQuotas } from "../../types/quota-api";
|
|
5
|
-
import {
|
|
2
|
+
import { parseQuotaHeaders } from "../../src/events";
|
|
3
|
+
import { fetchQuotas } from "../../src/lib/neuralwatt-api";
|
|
4
|
+
import type { NeuralwattQuotas } from "../../src/types/quota-api";
|
|
5
|
+
import { getNeuralwattApiKey } from "../_shared/auth";
|
|
6
6
|
|
|
7
7
|
export function buildQuotasFromHeaders(
|
|
8
8
|
headers: Record<string, string>,
|
|
@@ -2,29 +2,27 @@ import type {
|
|
|
2
2
|
ExtensionAPI,
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { configLoader } from "../../src/config";
|
|
5
6
|
import {
|
|
6
|
-
configLoader,
|
|
7
7
|
NEURALWATT_CONFIG_UPDATED_EVENT,
|
|
8
8
|
NEURALWATT_EXTENSIONS_REGISTER_EVENT,
|
|
9
9
|
NEURALWATT_EXTENSIONS_REQUEST_EVENT,
|
|
10
|
-
type NeuralwattConfigUpdatedPayload,
|
|
11
|
-
} from "../../config";
|
|
12
|
-
import {
|
|
13
10
|
NEURALWATT_QUOTAS_UPDATED_EVENT,
|
|
11
|
+
type NeuralwattConfigUpdatedPayload,
|
|
14
12
|
type NeuralwattQuotasUpdatedPayload,
|
|
15
|
-
} from "../../
|
|
13
|
+
} from "../../src/events";
|
|
16
14
|
import { checkQuotas, clearAlertState } from "./notifier";
|
|
17
15
|
|
|
18
16
|
export default async function (pi: ExtensionAPI) {
|
|
19
17
|
await configLoader.load();
|
|
20
18
|
|
|
21
|
-
let enabled = configLoader.getConfig().quotaWarnings;
|
|
22
|
-
let currentProvider: string | undefined;
|
|
19
|
+
let enabled = configLoader.getConfig().quotaWarnings.enabled;
|
|
23
20
|
let currentContext: ExtensionContext | undefined;
|
|
24
21
|
|
|
25
22
|
// Listen for config changes at runtime
|
|
26
23
|
pi.events.on(NEURALWATT_CONFIG_UPDATED_EVENT, (data: unknown) => {
|
|
27
|
-
enabled = (data as NeuralwattConfigUpdatedPayload).config.quotaWarnings
|
|
24
|
+
enabled = (data as NeuralwattConfigUpdatedPayload).config.quotaWarnings
|
|
25
|
+
.enabled;
|
|
28
26
|
|
|
29
27
|
if (!enabled) {
|
|
30
28
|
clearAlertState();
|
|
@@ -34,36 +32,29 @@ export default async function (pi: ExtensionAPI) {
|
|
|
34
32
|
pi.events.on(NEURALWATT_QUOTAS_UPDATED_EVENT, (data: unknown) => {
|
|
35
33
|
if (!enabled) return;
|
|
36
34
|
if (!data || typeof data !== "object") return;
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
if (!currentContext) return;
|
|
36
|
+
if (currentContext.model?.provider !== "neuralwatt") return;
|
|
37
|
+
|
|
38
|
+
const { quotas } = data as NeuralwattQuotasUpdatedPayload;
|
|
39
|
+
checkQuotas(currentContext, quotas);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
pi.on("session_start", async (_event, ctx) => {
|
|
43
43
|
currentContext = ctx;
|
|
44
|
-
currentProvider = ctx.model?.provider;
|
|
45
44
|
if (ctx.model?.provider !== "neuralwatt") return;
|
|
46
45
|
clearAlertState();
|
|
47
46
|
});
|
|
48
47
|
|
|
49
48
|
pi.on("model_select", (_event, ctx) => {
|
|
50
49
|
currentContext = ctx;
|
|
51
|
-
currentProvider = ctx.model?.provider;
|
|
52
|
-
if (ctx.model?.provider !== "neuralwatt") {
|
|
53
|
-
clearAlertState();
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
clearAlertState();
|
|
57
50
|
});
|
|
58
51
|
|
|
59
52
|
pi.on("session_before_switch", (_event, ctx) => {
|
|
60
53
|
currentContext = ctx;
|
|
61
|
-
currentProvider = ctx.model?.provider;
|
|
62
54
|
});
|
|
63
55
|
|
|
64
56
|
pi.on("session_shutdown", () => {
|
|
65
57
|
currentContext = undefined;
|
|
66
|
-
currentProvider = undefined;
|
|
67
58
|
clearAlertState();
|
|
68
59
|
});
|
|
69
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { NeuralwattQuotas } from "../../types/quota-api";
|
|
3
|
-
import { formatKwh, formatUsd } from "../../utils/quota-format";
|
|
2
|
+
import type { NeuralwattQuotas } from "../../src/types/quota-api";
|
|
3
|
+
import { formatKwh, formatUsd } from "../../src/utils/quota-format";
|
|
4
4
|
|
|
5
5
|
export type WarningSeverity = "warning" | "critical";
|
|
6
6
|
|
|
@@ -11,6 +11,8 @@ interface AlertState {
|
|
|
11
11
|
lastNotifiedAt: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// Module-level state so cooldowns survive across invocations of checkQuotas()
|
|
15
|
+
// within the same Pi runtime.
|
|
14
16
|
const alerts = new Map<string, AlertState>();
|
|
15
17
|
|
|
16
18
|
export function clearAlertState(): void {
|
|
@@ -22,9 +24,9 @@ function shouldNotify(key: string, severity: WarningSeverity): boolean {
|
|
|
22
24
|
if (!state) return true;
|
|
23
25
|
|
|
24
26
|
const order: WarningSeverity[] = ["warning", "critical"];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (
|
|
27
|
+
const currentIndex = order.indexOf(severity);
|
|
28
|
+
const lastIndex = order.indexOf(state.lastSeverity);
|
|
29
|
+
if (currentIndex > lastIndex) return true;
|
|
28
30
|
|
|
29
31
|
return Date.now() - state.lastNotifiedAt >= COOLDOWN_MS;
|
|
30
32
|
}
|
|
@@ -40,7 +42,6 @@ function markNotified(key: string, severity: WarningSeverity): void {
|
|
|
40
42
|
export function checkQuotas(
|
|
41
43
|
ctx: ExtensionContext,
|
|
42
44
|
quotas: NeuralwattQuotas,
|
|
43
|
-
skipAlreadyWarned: boolean,
|
|
44
45
|
): void {
|
|
45
46
|
if (!ctx.hasUI) return;
|
|
46
47
|
|
|
@@ -55,7 +56,7 @@ export function checkQuotas(
|
|
|
55
56
|
if (pct <= 25) {
|
|
56
57
|
const severity: WarningSeverity = pct <= 10 ? "critical" : "warning";
|
|
57
58
|
const key = "credits";
|
|
58
|
-
if (
|
|
59
|
+
if (shouldNotify(key, severity)) {
|
|
59
60
|
markNotified(key, severity);
|
|
60
61
|
warnings.push(
|
|
61
62
|
`Credits: ${pct.toFixed(0)}% remaining (${formatUsd(credits_remaining_usd)} of ${formatUsd(total_credits_usd)})`,
|
|
@@ -77,7 +78,7 @@ export function checkQuotas(
|
|
|
77
78
|
? "critical"
|
|
78
79
|
: "warning";
|
|
79
80
|
const key = "energy";
|
|
80
|
-
if (
|
|
81
|
+
if (shouldNotify(key, severity)) {
|
|
81
82
|
markNotified(key, severity);
|
|
82
83
|
const tag = in_overage ? " [OVERAGE]" : "";
|
|
83
84
|
warnings.push(
|