@aliou/pi-neuralwatt 0.13.0 → 0.14.0
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.
|
@@ -18,7 +18,8 @@ import type { NeuralwattQuotas } from "../../src/types/quota-api";
|
|
|
18
18
|
import { getNeuralwattApiKey } from "../_shared/auth";
|
|
19
19
|
import { registerNeuralwattSettings } from "./commands/settings";
|
|
20
20
|
import { normalizeNeuralwattContextOverflowError } from "./context-overflow";
|
|
21
|
-
import { getNeuralwattModels
|
|
21
|
+
import { getNeuralwattModels } from "./models";
|
|
22
|
+
import { createNeuralwattProvider } from "./provider";
|
|
22
23
|
import { buildQuotasFromHeaders, fetchRequestedQuotas } from "./quota-store";
|
|
23
24
|
import {
|
|
24
25
|
type NeuralwattRateLimitInfo,
|
|
@@ -42,24 +43,24 @@ function registerNeuralwattProvider(
|
|
|
42
43
|
): void {
|
|
43
44
|
const { provider: providerConfig } = configLoader.getConfig();
|
|
44
45
|
|
|
45
|
-
const
|
|
46
|
+
const staticModels = getNeuralwattModels({
|
|
46
47
|
includeLegacyModelIds: providerConfig.includeLegacyModelIds,
|
|
47
48
|
includeAliasedModelIds: providerConfig.includeAliasedModelIds,
|
|
48
49
|
});
|
|
49
50
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
const apiProvider = getApiProvider("openai-completions");
|
|
52
|
+
const baseStreamSimple = apiProvider?.streamSimple;
|
|
53
|
+
const streamSimple = baseStreamSimple
|
|
54
|
+
? (wrapNeuralwattStreamSimple(
|
|
55
|
+
baseStreamSimple as never,
|
|
56
|
+
onSseQuota,
|
|
57
|
+
) as never)
|
|
58
|
+
: undefined;
|
|
59
|
+
|
|
60
|
+
pi.registerProvider(
|
|
61
|
+
createNeuralwattProvider(
|
|
62
|
+
staticModels,
|
|
63
|
+
() => ({
|
|
63
64
|
includeLegacyModelIds:
|
|
64
65
|
configLoader.getConfig().provider.includeLegacyModelIds,
|
|
65
66
|
includeAliasedModelIds:
|
|
@@ -67,18 +68,9 @@ function registerNeuralwattProvider(
|
|
|
67
68
|
includeEarlyAccessModels:
|
|
68
69
|
configLoader.getConfig().provider.includeEarlyAccessModels,
|
|
69
70
|
}),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const baseStreamSimple = provider?.streamSimple;
|
|
74
|
-
if (baseStreamSimple) {
|
|
75
|
-
config.streamSimple = wrapNeuralwattStreamSimple(
|
|
76
|
-
baseStreamSimple as never,
|
|
77
|
-
onSseQuota,
|
|
78
|
-
) as never;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
pi.registerProvider("neuralwatt", config);
|
|
71
|
+
streamSimple,
|
|
72
|
+
),
|
|
73
|
+
);
|
|
82
74
|
}
|
|
83
75
|
|
|
84
76
|
export default async function (pi: ExtensionAPI) {
|
|
@@ -180,28 +180,37 @@ const FAMILIES: [NeuralwattModelFamily, NeuralwattVariantSpec[]][] = [
|
|
|
180
180
|
},
|
|
181
181
|
],
|
|
182
182
|
],
|
|
183
|
+
// The kimi-k3 endpoint rejects anything above 327,680 total tokens with
|
|
184
|
+
// `400: max_completion_tokens is too large … supports at most 327680
|
|
185
|
+
// completion tokens` (verified at runtime), even though the API advertises
|
|
186
|
+
// `max_model_len: 1048560` with a null output cap for the whole family.
|
|
187
|
+
// The -fast/-flex endpoints don't enforce any cap server-side yet (they
|
|
188
|
+
// accept max_completion_tokens beyond the advertised window), but they are
|
|
189
|
+
// the same K3 deployment and are expected to share the 327,680 limit, so
|
|
190
|
+
// all three variants are pinned to it. The drift check in models.test.ts
|
|
191
|
+
// whitelists this divergence via CONTEXT_WINDOW_OVERRIDES.
|
|
183
192
|
[
|
|
184
193
|
KIMI_K3,
|
|
185
194
|
[
|
|
186
195
|
{
|
|
187
196
|
id: "kimi-k3",
|
|
188
197
|
name: "Kimi K3",
|
|
189
|
-
contextWindow:
|
|
190
|
-
maxOutputTokens:
|
|
198
|
+
contextWindow: 327680,
|
|
199
|
+
maxOutputTokens: 327680,
|
|
191
200
|
reasoning: true,
|
|
192
201
|
},
|
|
193
202
|
{
|
|
194
203
|
id: "kimi-k3-fast",
|
|
195
204
|
name: "Kimi K3 Fast",
|
|
196
|
-
contextWindow:
|
|
197
|
-
maxOutputTokens:
|
|
205
|
+
contextWindow: 327680,
|
|
206
|
+
maxOutputTokens: 327680,
|
|
198
207
|
reasoning: false,
|
|
199
208
|
},
|
|
200
209
|
{
|
|
201
210
|
id: "kimi-k3-flex",
|
|
202
211
|
name: "Kimi K3 (flex)",
|
|
203
|
-
contextWindow:
|
|
204
|
-
maxOutputTokens:
|
|
212
|
+
contextWindow: 327680,
|
|
213
|
+
maxOutputTokens: 327680,
|
|
205
214
|
reasoning: true,
|
|
206
215
|
costMultiplier: FLEX_COST_MULTIPLIER,
|
|
207
216
|
},
|
|
@@ -5,10 +5,6 @@ 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 {
|
|
9
|
-
persistModels,
|
|
10
|
-
readStoredModels,
|
|
11
|
-
} from "../../../src/refresh-store-compat";
|
|
12
8
|
import {
|
|
13
9
|
ALIAS_NEURALWATT_MODEL_IDS,
|
|
14
10
|
buildAliasNeuralwattModels,
|
|
@@ -24,6 +20,8 @@ const PROVIDER_ID = "neuralwatt";
|
|
|
24
20
|
const BASE_URL = "https://api.neuralwatt.com/v1";
|
|
25
21
|
const API = "openai-completions" as const;
|
|
26
22
|
|
|
23
|
+
export type StoredProviderModels = readonly Model<Api>[];
|
|
24
|
+
|
|
27
25
|
export interface RefreshNeuralwattModelsOptions {
|
|
28
26
|
includeLegacyModelIds: boolean;
|
|
29
27
|
includeAliasedModelIds: boolean;
|
|
@@ -129,25 +127,27 @@ function persistCatalog(
|
|
|
129
127
|
context: RefreshModelsContext,
|
|
130
128
|
models: ProviderModelConfig[],
|
|
131
129
|
): Promise<boolean> {
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
return context.publish({
|
|
131
|
+
persist: {
|
|
132
|
+
models: models.map(toStoredModel),
|
|
133
|
+
checkedAt: Date.now(),
|
|
134
|
+
},
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
/** Refresh the complete Neuralwatt catalog
|
|
138
|
+
/** Refresh the complete Neuralwatt catalog; undefined = failed (stale store kept). */
|
|
139
139
|
export async function refreshNeuralwattModels(
|
|
140
140
|
context: RefreshModelsContext,
|
|
141
141
|
options: RefreshNeuralwattModelsOptions,
|
|
142
|
-
): Promise<ProviderModelConfig[]> {
|
|
142
|
+
): Promise<ProviderModelConfig[] | undefined> {
|
|
143
143
|
const baseline = configuredModels(
|
|
144
144
|
options.includeLegacyModelIds,
|
|
145
145
|
options.includeAliasedModelIds,
|
|
146
146
|
);
|
|
147
|
-
const stored =
|
|
147
|
+
const stored = context.stored;
|
|
148
148
|
|
|
149
149
|
if (!options.includeEarlyAccessModels) {
|
|
150
|
-
await persistCatalog(context, baseline);
|
|
150
|
+
await persistCatalog(context, baseline).catch(() => false);
|
|
151
151
|
return baseline;
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -161,29 +161,31 @@ export async function refreshNeuralwattModels(
|
|
|
161
161
|
cachedEarlyAccess,
|
|
162
162
|
);
|
|
163
163
|
|
|
164
|
-
if (!context.allowNetwork || context.signal
|
|
164
|
+
if (!context.allowNetwork || context.signal.aborted) {
|
|
165
165
|
return cachedCatalog;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
// Anonymous credential (empty or missing key): keep the public catalog and
|
|
169
|
+
// skip discovery, which requires a real key.
|
|
168
170
|
const apiKey =
|
|
169
|
-
context.credential?.type === "api_key"
|
|
171
|
+
context.credential?.type === "api_key" && context.credential.key
|
|
172
|
+
? context.credential.key
|
|
173
|
+
: undefined;
|
|
170
174
|
if (!apiKey) return cachedCatalog;
|
|
171
175
|
|
|
172
176
|
const earlyAccess = await (options.loadEarlyAccess ?? loadEarlyAccessModels)(
|
|
173
177
|
apiKey,
|
|
174
178
|
context.signal,
|
|
175
179
|
);
|
|
176
|
-
if (context.signal
|
|
177
|
-
if (!earlyAccess)
|
|
178
|
-
throw new Error("Neuralwatt model catalog refresh failed");
|
|
179
|
-
}
|
|
180
|
+
if (context.signal.aborted) return cachedCatalog;
|
|
181
|
+
if (!earlyAccess) return undefined;
|
|
180
182
|
|
|
181
183
|
const catalog = configuredModels(
|
|
182
184
|
options.includeLegacyModelIds,
|
|
183
185
|
options.includeAliasedModelIds,
|
|
184
186
|
configuredEarlyAccessModels(earlyAccess, baseline),
|
|
185
187
|
);
|
|
186
|
-
context.signal
|
|
187
|
-
await persistCatalog(context, catalog);
|
|
188
|
+
context.signal.throwIfAborted();
|
|
189
|
+
await persistCatalog(context, catalog).catch(() => false);
|
|
188
190
|
return catalog;
|
|
189
191
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Api,
|
|
3
|
+
Model,
|
|
4
|
+
Provider,
|
|
5
|
+
ProviderStreamOptions,
|
|
6
|
+
} from "@earendil-works/pi-ai";
|
|
7
|
+
import { stream, streamSimple } from "@earendil-works/pi-ai/compat";
|
|
8
|
+
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import type {
|
|
10
|
+
RefreshNeuralwattModelsOptions,
|
|
11
|
+
StoredProviderModels,
|
|
12
|
+
} from "./models/refresh";
|
|
13
|
+
import { refreshNeuralwattModels } from "./models/refresh";
|
|
14
|
+
import type { AnyStreamSimple } from "./stream-simple";
|
|
15
|
+
|
|
16
|
+
export const NEURALWATT_PROVIDER_ID = "neuralwatt";
|
|
17
|
+
export const NEURALWATT_BASE_URL = "https://api.neuralwatt.com/v1";
|
|
18
|
+
export const NEURALWATT_API_KEY_ENV = "NEURALWATT_API_KEY";
|
|
19
|
+
|
|
20
|
+
const NEURALWATT_REQUEST_HEADERS = {
|
|
21
|
+
Referer: "https://pi.dev",
|
|
22
|
+
"X-Title": "npm:@aliou/pi-neuralwatt",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const API = "openai-completions" as const;
|
|
26
|
+
|
|
27
|
+
function toProviderModels(
|
|
28
|
+
models: readonly ProviderModelConfig[],
|
|
29
|
+
): Model<Api>[] {
|
|
30
|
+
return models.map((model) => ({
|
|
31
|
+
...model,
|
|
32
|
+
api: model.api ?? API,
|
|
33
|
+
provider: NEURALWATT_PROVIDER_ID,
|
|
34
|
+
baseUrl: model.baseUrl ?? NEURALWATT_BASE_URL,
|
|
35
|
+
headers: NEURALWATT_REQUEST_HEADERS,
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createNeuralwattProvider(
|
|
40
|
+
staticModels: ProviderModelConfig[],
|
|
41
|
+
refreshOptions: () => RefreshNeuralwattModelsOptions,
|
|
42
|
+
streamSimpleOverride?: AnyStreamSimple,
|
|
43
|
+
): Provider {
|
|
44
|
+
let liveModels = toProviderModels(staticModels);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
id: NEURALWATT_PROVIDER_ID,
|
|
48
|
+
name: "Neuralwatt",
|
|
49
|
+
baseUrl: NEURALWATT_BASE_URL,
|
|
50
|
+
headers: NEURALWATT_REQUEST_HEADERS,
|
|
51
|
+
auth: {
|
|
52
|
+
apiKey: {
|
|
53
|
+
name: "Neuralwatt API key",
|
|
54
|
+
login: async (interaction) => ({
|
|
55
|
+
type: "api_key",
|
|
56
|
+
key: await interaction.prompt({
|
|
57
|
+
type: "secret",
|
|
58
|
+
message: "Enter Neuralwatt API key",
|
|
59
|
+
}),
|
|
60
|
+
}),
|
|
61
|
+
check: async ({ ctx, credential }) => {
|
|
62
|
+
if (credential?.type === "api_key" && credential.key) {
|
|
63
|
+
return { type: "api_key", source: "stored credential" };
|
|
64
|
+
}
|
|
65
|
+
if (await ctx.env(NEURALWATT_API_KEY_ENV)) {
|
|
66
|
+
return { type: "api_key", source: NEURALWATT_API_KEY_ENV };
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
},
|
|
70
|
+
resolve: async ({ ctx, credential, signal }) => {
|
|
71
|
+
signal.throwIfAborted();
|
|
72
|
+
if (credential?.type === "api_key" && credential.key) {
|
|
73
|
+
return {
|
|
74
|
+
auth: { apiKey: credential.key },
|
|
75
|
+
env: credential.env,
|
|
76
|
+
source: "stored credential",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const envKey = await ctx.env(NEURALWATT_API_KEY_ENV);
|
|
80
|
+
signal.throwIfAborted();
|
|
81
|
+
if (envKey) {
|
|
82
|
+
return { auth: { apiKey: envKey }, source: NEURALWATT_API_KEY_ENV };
|
|
83
|
+
}
|
|
84
|
+
// Resolve never fails: without a key the catalog is the hardcoded
|
|
85
|
+
// one and early-access discovery is skipped (anonymous playground
|
|
86
|
+
// traffic authenticates at stream time).
|
|
87
|
+
return { auth: { apiKey: "" }, source: "anonymous" };
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
getModels: () => liveModels,
|
|
92
|
+
refreshModels: async (context) => {
|
|
93
|
+
const refreshed = await refreshNeuralwattModels(
|
|
94
|
+
context,
|
|
95
|
+
refreshOptions(),
|
|
96
|
+
);
|
|
97
|
+
// Fresh or offline store: the refresh intentionally skipped the
|
|
98
|
+
// network; adopt the persisted catalog anyway so getModels reflects it
|
|
99
|
+
// (statics otherwise).
|
|
100
|
+
const next = refreshed ?? context.stored?.models;
|
|
101
|
+
if (!next || next.length === 0) return;
|
|
102
|
+
const adopted = toProviderModels(next as StoredProviderModels);
|
|
103
|
+
await context.publish({
|
|
104
|
+
update: () => {
|
|
105
|
+
liveModels = adopted;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
stream: (model, context, options) =>
|
|
110
|
+
stream(model, context, options as ProviderStreamOptions | undefined),
|
|
111
|
+
streamSimple: streamSimpleOverride ?? streamSimple,
|
|
112
|
+
};
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliou/pi-neuralwatt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"@aliou/pi-utils-ui": "^0.5.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-ai": ">=0.
|
|
42
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
41
|
+
"@earendil-works/pi-ai": ">=0.84.0",
|
|
42
|
+
"@earendil-works/pi-coding-agent": ">=0.84.0",
|
|
43
43
|
"@earendil-works/pi-tui": "*"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Backward-compat shim for the Pi coding-agent provider refresh context.
|
|
3
|
-
//
|
|
4
|
-
// Pi 0.84 replaced dynamic `context.store` read/write access with the
|
|
5
|
-
// read-only `context.stored` snapshot and the generation-checked
|
|
6
|
-
// `context.publish({ persist })` transaction. This module detects the
|
|
7
|
-
// available API shape at runtime so the extension works on both <0.84 (store)
|
|
8
|
-
// and >=0.84 (publish) hosts.
|
|
9
|
-
//
|
|
10
|
-
// Once the minimum supported @earendil-works/pi-coding-agent version is
|
|
11
|
-
// >=0.84, delete this file and:
|
|
12
|
-
// - replace `readStoredModels(context)` with `context.stored`
|
|
13
|
-
// - replace `persistModels(context, entry)` with
|
|
14
|
-
// `await context.publish({ persist: entry })` (skip when aborted)
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
import type {
|
|
18
|
-
ModelsStoreEntry,
|
|
19
|
-
RefreshModelsContext,
|
|
20
|
-
} from "@earendil-works/pi-ai";
|
|
21
|
-
|
|
22
|
-
type LegacyRefreshModelsContext = RefreshModelsContext & {
|
|
23
|
-
store?: {
|
|
24
|
-
read(): Promise<ModelsStoreEntry | undefined>;
|
|
25
|
-
write(entry: ModelsStoreEntry): Promise<unknown>;
|
|
26
|
-
};
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Returns the persisted catalog entry for the current provider, reading from
|
|
31
|
-
* the 0.84+ `context.stored` snapshot when available and falling back to the
|
|
32
|
-
* legacy `context.store.read()` on older hosts.
|
|
33
|
-
*/
|
|
34
|
-
export async function readStoredModels(
|
|
35
|
-
context: RefreshModelsContext,
|
|
36
|
-
): Promise<ModelsStoreEntry | undefined> {
|
|
37
|
-
if (context.stored !== undefined) return context.stored;
|
|
38
|
-
return readLegacyStore(context);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function readLegacyStore(
|
|
42
|
-
context: RefreshModelsContext,
|
|
43
|
-
): Promise<ModelsStoreEntry | undefined> {
|
|
44
|
-
const legacy = context as LegacyRefreshModelsContext;
|
|
45
|
-
return legacy.store ? legacy.store.read() : Promise.resolve(undefined);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Persists the catalog entry, publishing through
|
|
50
|
-
* `context.publish({ persist: entry })` on 0.84+ hosts and writing through
|
|
51
|
-
* the legacy `context.store.write(entry)` on older hosts.
|
|
52
|
-
*
|
|
53
|
-
* Returns true when the entry was persisted. On 0.84+ hosts a return value of
|
|
54
|
-
* false means a newer refresh superseded this publication (generation check).
|
|
55
|
-
*/
|
|
56
|
-
export async function persistModels(
|
|
57
|
-
context: RefreshModelsContext,
|
|
58
|
-
entry: ModelsStoreEntry,
|
|
59
|
-
): Promise<boolean> {
|
|
60
|
-
if (typeof context.publish === "function") {
|
|
61
|
-
return context.publish({ persist: entry });
|
|
62
|
-
}
|
|
63
|
-
const legacy = context as LegacyRefreshModelsContext;
|
|
64
|
-
if (legacy.store) {
|
|
65
|
-
await legacy.store.write(entry);
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
return false;
|
|
69
|
-
}
|