@aliou/pi-neuralwatt 0.12.1 → 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.
- package/extensions/provider/index.ts +19 -27
- package/extensions/provider/models/build.ts +65 -12
- package/extensions/provider/models/early-access.ts +16 -14
- package/extensions/provider/models/public-models.ts +51 -78
- package/extensions/provider/models/refresh.ts +21 -19
- package/extensions/provider/provider.ts +113 -0
- package/package.json +3 -3
- package/src/types/models-api.ts +41 -0
- package/src/refresh-store-compat.ts +0 -69
|
@@ -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) {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type {
|
|
3
|
+
NeuralwattApiModelReasoning,
|
|
4
|
+
NeuralwattReasoningEffort,
|
|
5
|
+
} from "../../../src/types/models-api";
|
|
2
6
|
|
|
3
7
|
export type ThinkingLevelMap = NonNullable<
|
|
4
8
|
ProviderModelConfig["thinkingLevelMap"]
|
|
@@ -26,8 +30,11 @@ export interface NeuralwattCost {
|
|
|
26
30
|
export interface NeuralwattModelFamily {
|
|
27
31
|
cost: NeuralwattCost;
|
|
28
32
|
vision: boolean;
|
|
29
|
-
/**
|
|
30
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Reasoning contract snapshot from `/v1/models` for reasoning variants.
|
|
35
|
+
* `buildThinkingLevelMap` turns it into the Pi thinking level map.
|
|
36
|
+
*/
|
|
37
|
+
reasoningMetadata?: NeuralwattReasoningMapSource;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
export interface NeuralwattVariantSpec {
|
|
@@ -48,7 +55,54 @@ export interface NeuralwattVariantSpec {
|
|
|
48
55
|
*/
|
|
49
56
|
costMultiplier?: number;
|
|
50
57
|
vision?: boolean;
|
|
51
|
-
|
|
58
|
+
/** Override the family reasoning contract for this variant. */
|
|
59
|
+
reasoningMetadata?: NeuralwattReasoningMapSource;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Subset of the API reasoning block needed to build the Pi thinking level map.
|
|
64
|
+
* Kept narrow so public snapshots stay small and offline-friendly.
|
|
65
|
+
*/
|
|
66
|
+
export type NeuralwattReasoningMapSource = Pick<
|
|
67
|
+
NeuralwattApiModelReasoning,
|
|
68
|
+
"supported_efforts" | "mandatory"
|
|
69
|
+
>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build the Pi thinking level map from the Neuralwatt reasoning contract.
|
|
73
|
+
*
|
|
74
|
+
* Pure identity mapping: a Pi level is enabled iff it appears in
|
|
75
|
+
* `supported_efforts` (mapped to its own name), `null` otherwise. `off` maps to
|
|
76
|
+
* `"none"` when the model permits disabling reasoning (`!mandatory` and
|
|
77
|
+
* `"none"` is supported).
|
|
78
|
+
*
|
|
79
|
+
* When the reasoning block is missing (e.g. Kimi K2.7 Code, whose API metadata
|
|
80
|
+
* exposes none), falls back to a conservative `high`-only map with `off: null`,
|
|
81
|
+
* matching the upstream binary thinking toggle.
|
|
82
|
+
*
|
|
83
|
+
* `default_effort` and `effort_aliases` are deliberately ignored: Pi has no
|
|
84
|
+
* default-reasoning field, and we expose native supported efforts rather than
|
|
85
|
+
* aliasing unsupported ones.
|
|
86
|
+
*/
|
|
87
|
+
export function buildThinkingLevelMap(
|
|
88
|
+
reasoning: NeuralwattReasoningMapSource | undefined,
|
|
89
|
+
): ThinkingLevelMap {
|
|
90
|
+
// Conservative fallback for models whose API metadata has no reasoning
|
|
91
|
+
// block. Expose one known-good level and forbid disabling reasoning.
|
|
92
|
+
const supported = new Set<NeuralwattReasoningEffort>(
|
|
93
|
+
reasoning?.supported_efforts ?? ["high"],
|
|
94
|
+
);
|
|
95
|
+
const mandatory = reasoning?.mandatory ?? true;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
off: !mandatory && supported.has("none") ? "none" : null,
|
|
99
|
+
minimal: supported.has("minimal") ? "minimal" : null,
|
|
100
|
+
low: supported.has("low") ? "low" : null,
|
|
101
|
+
medium: supported.has("medium") ? "medium" : null,
|
|
102
|
+
high: supported.has("high") ? "high" : null,
|
|
103
|
+
xhigh: supported.has("xhigh") ? "xhigh" : null,
|
|
104
|
+
max: supported.has("max") ? "max" : null,
|
|
105
|
+
};
|
|
52
106
|
}
|
|
53
107
|
|
|
54
108
|
/**
|
|
@@ -97,15 +151,14 @@ export function buildNeuralwattModel(
|
|
|
97
151
|
};
|
|
98
152
|
|
|
99
153
|
if (variant.reasoning) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
model.thinkingLevelMap = { ...thinkingLevelMap };
|
|
154
|
+
// Clone so variants never share a family map instance. The map is derived
|
|
155
|
+
// from the API reasoning contract; missing metadata falls back to a
|
|
156
|
+
// high-only map rather than throwing.
|
|
157
|
+
model.thinkingLevelMap = {
|
|
158
|
+
...buildThinkingLevelMap(
|
|
159
|
+
variant.reasoningMetadata ?? family.reasoningMetadata,
|
|
160
|
+
),
|
|
161
|
+
};
|
|
109
162
|
}
|
|
110
163
|
|
|
111
164
|
return model;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { fetchNeuralwattModels } from "../../../src/lib/neuralwatt-api";
|
|
3
3
|
import type { NeuralwattApiModel } from "../../../src/types/models-api";
|
|
4
|
-
import { resolveMaxTokens } from "./build";
|
|
4
|
+
import { buildThinkingLevelMap, resolveMaxTokens } from "./build";
|
|
5
5
|
import { NEURALWATT_MODELS } from "./public-models";
|
|
6
6
|
|
|
7
7
|
// Pre-release models. Neuralwatt ships these to authorized accounts before they
|
|
@@ -13,10 +13,16 @@ export const EARLY_ACCESS_NEURALWATT_MODELS: ProviderModelConfig[] = [];
|
|
|
13
13
|
|
|
14
14
|
// Per-ID overrides for known early-access models. The authenticated /v1/models
|
|
15
15
|
// endpoint exposes pricing and capabilities, but some Pi-specific behavior
|
|
16
|
-
// (
|
|
16
|
+
// (compat flags, context window, max tokens) has to be supplied by hand.
|
|
17
|
+
// Reasoning config is always derived from the endpoint's `reasoning` block via
|
|
18
|
+
// `buildThinkingLevelMap`, so `reasoning` and `thinkingLevelMap` are not part
|
|
19
|
+
// of the override shape.
|
|
17
20
|
// Models that have since gone public now live in public-models.ts.
|
|
18
21
|
const EARLY_ACCESS_MODEL_OVERRIDES: Partial<
|
|
19
|
-
Record<
|
|
22
|
+
Record<
|
|
23
|
+
string,
|
|
24
|
+
Omit<Partial<ProviderModelConfig>, "reasoning" | "thinkingLevelMap">
|
|
25
|
+
>
|
|
20
26
|
> = {};
|
|
21
27
|
|
|
22
28
|
function buildEarlyAccessModel(
|
|
@@ -57,13 +63,10 @@ function buildEarlyAccessModel(
|
|
|
57
63
|
};
|
|
58
64
|
|
|
59
65
|
if (reasoning) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
high: null,
|
|
65
|
-
xhigh: null,
|
|
66
|
-
};
|
|
66
|
+
// Reasoning levels come straight from the endpoint's `reasoning` block:
|
|
67
|
+
// `supported_efforts` (identity) and `mandatory` (gates `off`). A missing
|
|
68
|
+
// block falls back to a high-only map inside `buildThinkingLevelMap`.
|
|
69
|
+
model.thinkingLevelMap = buildThinkingLevelMap(meta?.reasoning);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
if (override) {
|
|
@@ -80,11 +83,7 @@ function applyEarlyAccessOverride(
|
|
|
80
83
|
const result: ProviderModelConfig = { ...model };
|
|
81
84
|
|
|
82
85
|
if (override.name !== undefined) result.name = override.name;
|
|
83
|
-
if (override.reasoning !== undefined) result.reasoning = override.reasoning;
|
|
84
86
|
if (override.input !== undefined) result.input = override.input;
|
|
85
|
-
if (override.thinkingLevelMap !== undefined) {
|
|
86
|
-
result.thinkingLevelMap = override.thinkingLevelMap;
|
|
87
|
-
}
|
|
88
87
|
if (override.contextWindow !== undefined) {
|
|
89
88
|
result.contextWindow = override.contextWindow;
|
|
90
89
|
}
|
|
@@ -96,6 +95,9 @@ function applyEarlyAccessOverride(
|
|
|
96
95
|
result.compat = { ...model.compat, ...override.compat };
|
|
97
96
|
}
|
|
98
97
|
|
|
98
|
+
// `reasoning` and `thinkingLevelMap` are intentionally not overridable:
|
|
99
|
+
// reasoning config is derived from the endpoint's `reasoning` block.
|
|
100
|
+
|
|
99
101
|
return result;
|
|
100
102
|
}
|
|
101
103
|
|
|
@@ -4,119 +4,83 @@ import {
|
|
|
4
4
|
FLEX_COST_MULTIPLIER,
|
|
5
5
|
type NeuralwattModelFamily,
|
|
6
6
|
type NeuralwattVariantSpec,
|
|
7
|
-
type ThinkingLevelMap,
|
|
8
7
|
} from "./build";
|
|
9
8
|
|
|
10
9
|
// Public models returned by https://api.neuralwatt.com/v1/models.
|
|
11
10
|
// Pricing, capabilities, and limits are sourced from the API metadata fields;
|
|
12
11
|
// `maxTokens` is `metadata.limits.max_output_tokens ?? max_model_len`.
|
|
13
12
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
// GLM natively supports `high` and `max` reasoning efforts. `xhigh` is an
|
|
19
|
-
// unsupported hole between them. Pi added the `max` level in 0.80.6.
|
|
20
|
-
const GLM_THINKING: ThinkingLevelMap = {
|
|
21
|
-
off: "none",
|
|
22
|
-
minimal: null,
|
|
23
|
-
low: null,
|
|
24
|
-
medium: null,
|
|
25
|
-
high: "high",
|
|
26
|
-
xhigh: null,
|
|
27
|
-
max: "max",
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// Binary thinking control (Kimi K2.x, Qwen3.x): no graded `reasoning_effort`
|
|
31
|
-
// upstream, only a thinking on/off toggle. Expose a single known-good Pi
|
|
32
|
-
// level; "high" stands in for standard full thinking.
|
|
33
|
-
const BINARY_THINKING: ThinkingLevelMap = {
|
|
34
|
-
minimal: null,
|
|
35
|
-
low: null,
|
|
36
|
-
medium: null,
|
|
37
|
-
high: "high",
|
|
38
|
-
xhigh: null,
|
|
39
|
-
};
|
|
13
|
+
// Each reasoning family snapshots its `reasoning.supported_efforts` +
|
|
14
|
+
// `reasoning.mandatory` from the API; `buildThinkingLevelMap` turns that into
|
|
15
|
+
// the Pi thinking level map by identity (no aliasing). See `models.test.ts`
|
|
16
|
+
// for the drift check against the live catalog.
|
|
40
17
|
|
|
18
|
+
// DeepSeek V4 Flash: efforts max/high/none, not mandatory.
|
|
19
|
+
// https://api-docs.deepseek.com/guides/thinking_mode/
|
|
41
20
|
const DEEPSEEK_V4_FLASH: NeuralwattModelFamily = {
|
|
42
21
|
cost: { input: 0.14, output: 0.28, cacheRead: 0.028 },
|
|
43
22
|
vision: false,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// https://api-docs.deepseek.com/guides/thinking_mode/
|
|
48
|
-
thinkingLevelMap: {
|
|
49
|
-
off: "none",
|
|
50
|
-
minimal: null,
|
|
51
|
-
low: "low",
|
|
52
|
-
medium: null,
|
|
53
|
-
high: "high",
|
|
54
|
-
xhigh: null,
|
|
55
|
-
max: "max",
|
|
23
|
+
reasoningMetadata: {
|
|
24
|
+
supported_efforts: ["max", "high", "none"],
|
|
25
|
+
mandatory: false,
|
|
56
26
|
},
|
|
57
27
|
};
|
|
58
28
|
|
|
59
29
|
// Google, served from NVIDIA's NVFP4 checkpoint. Gemma 4's chat template
|
|
60
|
-
// takes a boolean rather than an effort level, so
|
|
61
|
-
//
|
|
30
|
+
// takes a boolean rather than an effort level, so the API only advertises
|
|
31
|
+
// `max` and `none`; every non-`none` request resolves to `max` upstream.
|
|
62
32
|
// It does not reason by default (`default_enabled: false`), but the model
|
|
63
33
|
// can produce reasoning traces when asked. See
|
|
64
34
|
// https://portal.neuralwatt.com/docs/api/chat-completions#reasoning-effort
|
|
65
|
-
const GEMMA_4_THINKING: ThinkingLevelMap = {
|
|
66
|
-
off: "none",
|
|
67
|
-
minimal: null,
|
|
68
|
-
low: null,
|
|
69
|
-
medium: null,
|
|
70
|
-
high: null,
|
|
71
|
-
xhigh: null,
|
|
72
|
-
max: "max",
|
|
73
|
-
};
|
|
74
|
-
|
|
75
35
|
const GEMMA_4: NeuralwattModelFamily = {
|
|
76
36
|
cost: { input: 0.144, output: 0.42, cacheRead: 0.0144 },
|
|
77
37
|
vision: true,
|
|
78
|
-
|
|
38
|
+
reasoningMetadata: {
|
|
39
|
+
supported_efforts: ["max", "none"],
|
|
40
|
+
mandatory: false,
|
|
41
|
+
},
|
|
79
42
|
};
|
|
80
43
|
|
|
81
|
-
// ZhipuAI.
|
|
44
|
+
// ZhipuAI. GLM-5.2 natively supports `high` and `max` reasoning efforts;
|
|
45
|
+
// `xhigh` is an unsupported hole between them. Pi's `max` level (0.80.6) maps
|
|
46
|
+
// to GLM's top tier.
|
|
82
47
|
const GLM_5_2: NeuralwattModelFamily = {
|
|
83
48
|
cost: { input: 1.45, output: 4.5, cacheRead: 0.145 },
|
|
84
49
|
vision: false,
|
|
85
|
-
|
|
50
|
+
reasoningMetadata: {
|
|
51
|
+
supported_efforts: ["max", "high", "none"],
|
|
52
|
+
mandatory: false,
|
|
53
|
+
},
|
|
86
54
|
};
|
|
87
55
|
|
|
88
|
-
// MoonshotAI. K3
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// (default "max"). There is no "medium" tier upstream, so Pi's low/high/max
|
|
92
|
-
// map directly and `off`, `minimal`, `medium`, and `xhigh` are unsupported
|
|
93
|
-
// holes. The `-fast` endpoint is a shorthand to set thinking to off.
|
|
56
|
+
// MoonshotAI. K3 supports reasoning efforts low/high/max (default max) and
|
|
57
|
+
// can be turned off (`mandatory: false`). The `-fast` endpoint is a shorthand
|
|
58
|
+
// to set thinking to off.
|
|
94
59
|
const KIMI_K3: NeuralwattModelFamily = {
|
|
95
60
|
cost: { input: 3, output: 15, cacheRead: 0.3 },
|
|
96
61
|
vision: true,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
low: "low",
|
|
101
|
-
medium: null,
|
|
102
|
-
high: "high",
|
|
103
|
-
xhigh: null,
|
|
104
|
-
max: "max",
|
|
62
|
+
reasoningMetadata: {
|
|
63
|
+
supported_efforts: ["max", "high", "low", "none"],
|
|
64
|
+
mandatory: false,
|
|
105
65
|
},
|
|
106
66
|
};
|
|
107
67
|
|
|
108
|
-
// MoonshotAI.
|
|
68
|
+
// MoonshotAI. The K2.7 Code API exposes no `reasoning` block, so this family
|
|
69
|
+
// omits `reasoningMetadata`; `buildThinkingLevelMap` falls back to a high-only
|
|
70
|
+
// map with `off: null`, matching the upstream binary thinking toggle.
|
|
109
71
|
const KIMI_K2_7_CODE: NeuralwattModelFamily = {
|
|
110
72
|
cost: { input: 0.95, output: 4.0, cacheRead: 0.095 },
|
|
111
73
|
vision: true,
|
|
112
|
-
thinkingLevelMap: { off: null, ...BINARY_THINKING },
|
|
113
74
|
};
|
|
114
75
|
|
|
115
|
-
// Qwen.
|
|
76
|
+
// Qwen. Qwen3.6 35B only advertises `high` and `none`.
|
|
116
77
|
const QWEN_3_6_35B: NeuralwattModelFamily = {
|
|
117
78
|
cost: { input: 0.29, output: 1.15, cacheRead: 0.029 },
|
|
118
79
|
vision: true,
|
|
119
|
-
|
|
80
|
+
reasoningMetadata: {
|
|
81
|
+
supported_efforts: ["high", "none"],
|
|
82
|
+
mandatory: false,
|
|
83
|
+
},
|
|
120
84
|
};
|
|
121
85
|
|
|
122
86
|
const FAMILIES: [NeuralwattModelFamily, NeuralwattVariantSpec[]][] = [
|
|
@@ -216,28 +180,37 @@ const FAMILIES: [NeuralwattModelFamily, NeuralwattVariantSpec[]][] = [
|
|
|
216
180
|
},
|
|
217
181
|
],
|
|
218
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.
|
|
219
192
|
[
|
|
220
193
|
KIMI_K3,
|
|
221
194
|
[
|
|
222
195
|
{
|
|
223
196
|
id: "kimi-k3",
|
|
224
197
|
name: "Kimi K3",
|
|
225
|
-
contextWindow:
|
|
226
|
-
maxOutputTokens:
|
|
198
|
+
contextWindow: 327680,
|
|
199
|
+
maxOutputTokens: 327680,
|
|
227
200
|
reasoning: true,
|
|
228
201
|
},
|
|
229
202
|
{
|
|
230
203
|
id: "kimi-k3-fast",
|
|
231
204
|
name: "Kimi K3 Fast",
|
|
232
|
-
contextWindow:
|
|
233
|
-
maxOutputTokens:
|
|
205
|
+
contextWindow: 327680,
|
|
206
|
+
maxOutputTokens: 327680,
|
|
234
207
|
reasoning: false,
|
|
235
208
|
},
|
|
236
209
|
{
|
|
237
210
|
id: "kimi-k3-flex",
|
|
238
211
|
name: "Kimi K3 (flex)",
|
|
239
|
-
contextWindow:
|
|
240
|
-
maxOutputTokens:
|
|
212
|
+
contextWindow: 327680,
|
|
213
|
+
maxOutputTokens: 327680,
|
|
241
214
|
reasoning: true,
|
|
242
215
|
costMultiplier: FLEX_COST_MULTIPLIER,
|
|
243
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": {
|
package/src/types/models-api.ts
CHANGED
|
@@ -18,6 +18,46 @@ export interface NeuralwattApiModelCapabilities {
|
|
|
18
18
|
developer_role: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Reasoning effort values Neuralwatt accepts on the wire. Mirrors Pi's
|
|
23
|
+
* `ModelThinkingLevel` (minus `off`, which the API spells `"none"`).
|
|
24
|
+
*/
|
|
25
|
+
export type NeuralwattReasoningEffort =
|
|
26
|
+
| "none"
|
|
27
|
+
| "minimal"
|
|
28
|
+
| "low"
|
|
29
|
+
| "medium"
|
|
30
|
+
| "high"
|
|
31
|
+
| "xhigh"
|
|
32
|
+
| "max";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Per-model reasoning contract from `/v1/models`.
|
|
36
|
+
*
|
|
37
|
+
* `supported_efforts` is authoritative for which Pi thinking levels to expose:
|
|
38
|
+
* the Pi map is built by identity (a level is enabled iff it appears here),
|
|
39
|
+
* see `buildThinkingLevelMap` in `extensions/provider/models/build.ts`.
|
|
40
|
+
* `default_effort` and `effort_aliases` are typed for fidelity but are not
|
|
41
|
+
* consumed — Pi has no default-reasoning field and we expose native efforts
|
|
42
|
+
* rather than aliasing unsupported ones.
|
|
43
|
+
*/
|
|
44
|
+
export interface NeuralwattApiModelReasoning {
|
|
45
|
+
/** Whether the model reasons by default. */
|
|
46
|
+
default_enabled: boolean;
|
|
47
|
+
/** Whether reasoning cannot be turned off. Forces `off: null` in the map. */
|
|
48
|
+
mandatory: boolean;
|
|
49
|
+
/** Efforts the model truly supports; drives the Pi thinking level map. */
|
|
50
|
+
supported_efforts: NeuralwattReasoningEffort[];
|
|
51
|
+
/** Efforts the API accepts but aliases onto a supported one. Not consumed. */
|
|
52
|
+
accepted_efforts?: NeuralwattReasoningEffort[];
|
|
53
|
+
/** Server-side default. Not consumed; Pi has no default-reasoning field. */
|
|
54
|
+
default_effort: NeuralwattReasoningEffort;
|
|
55
|
+
/** Wire-level aliases from accepted to supported efforts. Not consumed. */
|
|
56
|
+
effort_aliases?: Partial<
|
|
57
|
+
Record<NeuralwattReasoningEffort, NeuralwattReasoningEffort>
|
|
58
|
+
>;
|
|
59
|
+
}
|
|
60
|
+
|
|
21
61
|
export interface NeuralwattApiModelLimits {
|
|
22
62
|
max_context_length: number;
|
|
23
63
|
max_output_tokens: number | null;
|
|
@@ -31,6 +71,7 @@ export interface NeuralwattApiModelMetadata {
|
|
|
31
71
|
huggingface_id: string | null;
|
|
32
72
|
pricing: NeuralwattApiModelPricing;
|
|
33
73
|
capabilities: NeuralwattApiModelCapabilities;
|
|
74
|
+
reasoning?: NeuralwattApiModelReasoning;
|
|
34
75
|
limits: NeuralwattApiModelLimits;
|
|
35
76
|
deprecated: boolean;
|
|
36
77
|
deprecated_message: string | null;
|
|
@@ -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
|
-
}
|