@aliou/pi-neuralwatt 0.7.1 → 0.7.2
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
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
type NeuralwattQuotasUpdatedPayload,
|
|
22
22
|
} from "../../types/quota-events";
|
|
23
23
|
import { normalizeNeuralwattContextOverflowError } from "./context-overflow";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
getNeuralwattModels,
|
|
26
|
+
loadCachedHiddenModels,
|
|
27
|
+
loadHiddenModels,
|
|
28
|
+
writeHiddenModelsCache,
|
|
29
|
+
} from "./models";
|
|
25
30
|
import { buildQuotasFromHeaders, fetchRequestedQuotas } from "./quota-store";
|
|
26
31
|
import {
|
|
27
32
|
type NeuralwattRateLimitInfo,
|
|
@@ -72,8 +77,24 @@ export default async function (pi: ExtensionAPI) {
|
|
|
72
77
|
await configLoader.load();
|
|
73
78
|
|
|
74
79
|
let latestQuotas: NeuralwattQuotas | undefined;
|
|
80
|
+
|
|
81
|
+
// Stale-while-revalidate seed for hidden models.
|
|
82
|
+
//
|
|
83
|
+
// Hidden models are only discoverable by hitting the authenticated
|
|
84
|
+
// `/v1/models` endpoint, which we can do inside `session_start` (Pi does not
|
|
85
|
+
// expose `authStorage` to extension factories). However, Pi validates scoped
|
|
86
|
+
// models (e.g. `neuralwatt/glm-5.2-short`) during startup, *before*
|
|
87
|
+
// `session_start` fires. To avoid "No models match pattern" warnings on saved
|
|
88
|
+
// scoped models, we synchronously restore the previous session's fetch from
|
|
89
|
+
// the on-disk cache so the provider is registered with hidden models at
|
|
90
|
+
// load time. `session_start` then revalidates from the live API and writes
|
|
91
|
+
// the cache back. First run with no cache still warns once.
|
|
75
92
|
let hiddenModels: ProviderModelConfig[] = [];
|
|
93
|
+
if (configLoader.getConfig().includeHiddenModels) {
|
|
94
|
+
hiddenModels = loadCachedHiddenModels();
|
|
95
|
+
}
|
|
76
96
|
let hiddenModelsLoaded = false;
|
|
97
|
+
let hiddenModelsAbort: AbortController | undefined;
|
|
77
98
|
|
|
78
99
|
const handleSseQuota = (line: string) => {
|
|
79
100
|
const quotas = updateQuotasFromSseComment(latestQuotas, line);
|
|
@@ -85,7 +106,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
85
106
|
});
|
|
86
107
|
};
|
|
87
108
|
|
|
88
|
-
registerNeuralwattProvider(pi, handleSseQuota);
|
|
109
|
+
registerNeuralwattProvider(pi, handleSseQuota, hiddenModels);
|
|
89
110
|
|
|
90
111
|
const loadedFeatures = new Set<NeuralwattFeatureId>();
|
|
91
112
|
|
|
@@ -95,9 +116,24 @@ export default async function (pi: ExtensionAPI) {
|
|
|
95
116
|
});
|
|
96
117
|
|
|
97
118
|
pi.events.on(NEURALWATT_CONFIG_UPDATED_EVENT, () => {
|
|
119
|
+
// Toggle may have enabled hidden models since startup. Seed from the disk
|
|
120
|
+
// cache so previously discovered models are available immediately without
|
|
121
|
+
// waiting for the next session_start revalidation.
|
|
122
|
+
if (
|
|
123
|
+
configLoader.getConfig().includeHiddenModels &&
|
|
124
|
+
!hiddenModelsLoaded &&
|
|
125
|
+
hiddenModels.length === 0
|
|
126
|
+
) {
|
|
127
|
+
hiddenModels = loadCachedHiddenModels();
|
|
128
|
+
}
|
|
98
129
|
registerNeuralwattProvider(pi, handleSseQuota, hiddenModels);
|
|
99
130
|
});
|
|
100
131
|
|
|
132
|
+
pi.on("session_shutdown", () => {
|
|
133
|
+
hiddenModelsAbort?.abort();
|
|
134
|
+
hiddenModelsAbort = undefined;
|
|
135
|
+
});
|
|
136
|
+
|
|
101
137
|
let lastHeaderEmitAt = 0;
|
|
102
138
|
let quotaRequestInFlight = false;
|
|
103
139
|
|
|
@@ -203,10 +239,20 @@ export default async function (pi: ExtensionAPI) {
|
|
|
203
239
|
|
|
204
240
|
if (!hiddenModelsLoaded && configLoader.getConfig().includeHiddenModels) {
|
|
205
241
|
hiddenModelsLoaded = true;
|
|
206
|
-
|
|
242
|
+
hiddenModelsAbort?.abort();
|
|
243
|
+
hiddenModelsAbort = new AbortController();
|
|
244
|
+
const fetched = await loadHiddenModels(
|
|
245
|
+
ctx.modelRegistry.authStorage,
|
|
246
|
+
hiddenModelsAbort.signal,
|
|
247
|
+
);
|
|
248
|
+
// Persist for the next startup so scoped models resolve without warnings
|
|
249
|
+
// on Pi's subsequent launches.
|
|
207
250
|
if (fetched.length > 0) {
|
|
208
251
|
hiddenModels = fetched;
|
|
209
|
-
|
|
252
|
+
await writeHiddenModelsCache(hiddenModels);
|
|
253
|
+
if (!hiddenModelsAbort.signal.aborted) {
|
|
254
|
+
registerNeuralwattProvider(pi, handleSseQuota, hiddenModels);
|
|
255
|
+
}
|
|
210
256
|
}
|
|
211
257
|
}
|
|
212
258
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stale-while-revalidate disk cache for hidden Neuralwatt models.
|
|
9
|
+
*
|
|
10
|
+
* Hidden models are discovered by hitting the authenticated `/v1/models`
|
|
11
|
+
* endpoint, which only happens inside `session_start` (Pi does not expose
|
|
12
|
+
* `authStorage` to the extension factory). Because Pi validates scoped models
|
|
13
|
+
* during startup — before `session_start` fires — we persist the last fetch to
|
|
14
|
+
* disk so the provider can be registered with cached models instantly on the
|
|
15
|
+
* next launch. The first run with no cache still warns once; subsequent runs
|
|
16
|
+
* resolve cleanly.
|
|
17
|
+
*
|
|
18
|
+
* File shape: `{ version: 1, models: ProviderModelConfig[] }`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const CACHE_VERSION = 1;
|
|
22
|
+
const CACHE_FILENAME = "neuralwatt-hidden-models.json";
|
|
23
|
+
|
|
24
|
+
function cachePath(): string {
|
|
25
|
+
return join(getAgentDir(), "cache", CACHE_FILENAME);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface HiddenModelsCacheFile {
|
|
29
|
+
version?: unknown;
|
|
30
|
+
models?: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read cached hidden models synchronously.
|
|
35
|
+
*
|
|
36
|
+
* Designed to be called from the provider extension factory body, where Pi
|
|
37
|
+
* has not entered the event loop yet. Returns an empty array if the cache is
|
|
38
|
+
* missing, unreadable, or malformed.
|
|
39
|
+
*/
|
|
40
|
+
export function loadCachedHiddenModels(): ProviderModelConfig[] {
|
|
41
|
+
try {
|
|
42
|
+
const path = cachePath();
|
|
43
|
+
if (!existsSync(path)) return [];
|
|
44
|
+
|
|
45
|
+
const parsed: HiddenModelsCacheFile = JSON.parse(
|
|
46
|
+
readFileSync(path, "utf8"),
|
|
47
|
+
);
|
|
48
|
+
if (!Array.isArray(parsed?.models)) return [];
|
|
49
|
+
|
|
50
|
+
return parsed.models as ProviderModelConfig[];
|
|
51
|
+
} catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Persist hidden models to disk for the next startup.
|
|
58
|
+
*
|
|
59
|
+
* Called after a successful `/v1/models` fetch in `session_start`. Failures are
|
|
60
|
+
* swallowed since a missing cache only degrades to first-run behavior.
|
|
61
|
+
*/
|
|
62
|
+
export async function writeHiddenModelsCache(
|
|
63
|
+
models: ProviderModelConfig[],
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
try {
|
|
66
|
+
const path = cachePath();
|
|
67
|
+
await mkdir(dirname(path), { recursive: true });
|
|
68
|
+
await writeFile(
|
|
69
|
+
path,
|
|
70
|
+
`${JSON.stringify({ version: CACHE_VERSION, models }, null, 2)}\n`,
|
|
71
|
+
"utf8",
|
|
72
|
+
);
|
|
73
|
+
} catch {
|
|
74
|
+
// Cache writes are best-effort. A missing cache only falls back to the
|
|
75
|
+
// first-run path (next session revalidates and writes again).
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -108,11 +108,12 @@ function applyHiddenOverride(
|
|
|
108
108
|
*/
|
|
109
109
|
export async function loadHiddenModels(
|
|
110
110
|
authStorage: AuthStorage,
|
|
111
|
+
signal?: AbortSignal,
|
|
111
112
|
): Promise<ProviderModelConfig[]> {
|
|
112
113
|
const apiKey = await getNeuralwattApiKey(authStorage);
|
|
113
114
|
if (!apiKey) return [];
|
|
114
115
|
|
|
115
|
-
const result = await fetchNeuralwattModels(apiKey);
|
|
116
|
+
const result = await fetchNeuralwattModels(apiKey, signal);
|
|
116
117
|
if (!result.success) return [];
|
|
117
118
|
|
|
118
119
|
const publicIds = new Set(NEURALWATT_MODELS.map((model) => model.id));
|
|
@@ -2,6 +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 { loadCachedHiddenModels, writeHiddenModelsCache } from "./cache";
|
|
5
6
|
export { loadHiddenModels } from "./hidden";
|
|
6
7
|
export {
|
|
7
8
|
buildLegacyNeuralwattModels,
|