pi-wafer-provider 1.1.2 → 1.1.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.
- package/AGENTS.md +1 -0
- package/README.md +8 -6
- package/deprecated-models.json +1 -0
- package/index.ts +53 -8
- package/models.json +94 -37
- package/package.json +1 -1
- package/patch.json +9 -58
- package/scripts/update-models.js +99 -26
package/AGENTS.md
CHANGED
|
@@ -7,6 +7,7 @@ The following files are **idempotent** and regenerated by `scripts/update-models
|
|
|
7
7
|
| File | Why it's auto-generated |
|
|
8
8
|
|------|------------------------|
|
|
9
9
|
| `models.json` | Built from the provider API. `update-models.js` fetches models, preserves curated data for known IDs, and writes this file. |
|
|
10
|
+
| `deprecated-models.json` | Graveyard for models the API delisted. update-models.js stamps them with deprecatedAt and pi keeps serving them for a 2-week grace period, then evicts them. Never edit by hand. |
|
|
10
11
|
| `README.md` (model table) | The table under `## Available Models` is replaced in-place by `update-models.js` after merging base models → patch → custom models. |
|
|
11
12
|
|
|
12
13
|
## Correct Files to Edit
|
package/README.md
CHANGED
|
@@ -76,12 +76,14 @@ pi
|
|
|
76
76
|
|
|
77
77
|
| Model | Type | Context | Max Output | Input Cost | Output Cost | Cached Input |
|
|
78
78
|
|-------|------|---------|------------|------------|-------------|--------------|
|
|
79
|
-
| GLM
|
|
80
|
-
| GLM
|
|
81
|
-
|
|
|
82
|
-
| Kimi
|
|
83
|
-
|
|
|
84
|
-
|
|
|
79
|
+
| GLM-5.1 | Text | 203K | 33K | $1.00 | $3.20 | $0.10 |
|
|
80
|
+
| GLM-5.2 | Text | 1M | 16K | $1.26 | $3.96 | $0.23 |
|
|
81
|
+
| GLM5.2-Fast | Text | 1M | 16K | $2.10 | $6.60 | $0.21 |
|
|
82
|
+
| Kimi-K2.6 | Text + Image | 262K | 33K | $1.14 | $4.80 | $0.19 |
|
|
83
|
+
| Kimi-K3 | Text + Image | 912K | 16K | $3.00 | $15.00 | $0.30 |
|
|
84
|
+
| Kimi-K3-Fast | Text + Image | 1M | 16K | $4.50 | $22.50 | $0.45 |
|
|
85
|
+
| MiniMax-M3 | Text + Image | 1M | 16K | $0.33 | $1.32 | $0.07 |
|
|
86
|
+
| Qwen 3.5 397B (A17B) | Text | 262K | 33K | Free | Free | Free |
|
|
85
87
|
|
|
86
88
|
*Costs are per million tokens. Prices based on official provider pricing.*
|
|
87
89
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { getAgentDir, type ExtensionAPI, type ModelRegistry } from "@earendil-wo
|
|
|
35
35
|
import modelsData from "./models.json" with { type: "json" };
|
|
36
36
|
import customModelsData from "./custom-models.json" with { type: "json" };
|
|
37
37
|
import patchData from "./patch.json" with { type: "json" };
|
|
38
|
+
import deprecatedData from "./deprecated-models.json" with { type: "json" };
|
|
38
39
|
import fs from "fs";
|
|
39
40
|
import path from "path";
|
|
40
41
|
|
|
@@ -123,7 +124,10 @@ function applyPatch(model: JsonModel, patch: PatchEntry): JsonModel {
|
|
|
123
124
|
function buildModels(base: JsonModel[], custom: JsonModel[], patch: PatchData): JsonModel[] {
|
|
124
125
|
const modelMap = new Map<string, JsonModel>();
|
|
125
126
|
|
|
126
|
-
|
|
127
|
+
// Seed with the base list plus grace-period deprecated models so patch.json
|
|
128
|
+
// entries apply to deprecated models exactly as while the model was live
|
|
129
|
+
// (withDeprecated keeps live data on id conflicts).
|
|
130
|
+
for (const model of withDeprecated(base)) {
|
|
127
131
|
modelMap.set(model.id, model);
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -185,17 +189,29 @@ interface ProviderConfig {
|
|
|
185
189
|
|
|
186
190
|
/** Transform a model from the Wafer /v1/models API. */
|
|
187
191
|
function transformApiModel(apiModel: any): JsonModel | null {
|
|
192
|
+
const details = apiModel.wafer || {};
|
|
193
|
+
const capabilities = details.capabilities || {};
|
|
194
|
+
const pricing = details.pricing || {};
|
|
195
|
+
const reasoning = capabilities.reasoning === true;
|
|
188
196
|
return {
|
|
189
197
|
id: apiModel.id,
|
|
190
|
-
name: apiModel.id,
|
|
191
|
-
reasoning
|
|
192
|
-
input: ["text"],
|
|
193
|
-
cost: {
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
name: details.display_name || apiModel.id,
|
|
199
|
+
reasoning,
|
|
200
|
+
input: capabilities.vision === true ? ["text", "image"] : ["text"],
|
|
201
|
+
cost: {
|
|
202
|
+
input: (pricing.input_cents_per_million || 0) / 100,
|
|
203
|
+
output: (pricing.output_cents_per_million || 0) / 100,
|
|
204
|
+
cacheRead: (pricing.cache_read_cents_per_million || 0) / 100,
|
|
205
|
+
cacheWrite: 0,
|
|
206
|
+
},
|
|
207
|
+
contextWindow: details.context_length || apiModel.max_model_len || 0,
|
|
208
|
+
maxTokens: details.context_length || apiModel.max_model_len || 0,
|
|
196
209
|
compat: {
|
|
210
|
+
supportsStore: false,
|
|
211
|
+
supportsDeveloperRole: false,
|
|
212
|
+
maxTokensField: "max_completion_tokens",
|
|
197
213
|
supportsZdr: apiModel.zdr_supported ?? undefined,
|
|
198
|
-
supportsReasoningEffort: true,
|
|
214
|
+
...(reasoning ? { supportsReasoningEffort: true } : {}),
|
|
199
215
|
},
|
|
200
216
|
};
|
|
201
217
|
}
|
|
@@ -275,6 +291,35 @@ function mergeWithEmbedded(liveModels: JsonModel[], embeddedModels: JsonModel[])
|
|
|
275
291
|
return result;
|
|
276
292
|
}
|
|
277
293
|
|
|
294
|
+
// Grace period for delisted models. When the provider API stops listing a
|
|
295
|
+
// model, update-models.js moves its last-known definition into
|
|
296
|
+
// deprecated-models.json (stamped with deprecatedAt) instead of dropping it.
|
|
297
|
+
// For 14 days the model keeps working here so in-flight sessions and saved
|
|
298
|
+
// model settings do not break; afterwards it is evicted permanently.
|
|
299
|
+
const DEPRECATED_MODEL_TTL_MS = 14 * 24 * 60 * 60 * 1000;
|
|
300
|
+
|
|
301
|
+
// Grace-period deprecated models with deprecation metadata stripped.
|
|
302
|
+
function activeDeprecatedModels(): JsonModel[] {
|
|
303
|
+
const now = Date.now();
|
|
304
|
+
const result: JsonModel[] = [];
|
|
305
|
+
for (const entry of Object.values(deprecatedData as Record<string, JsonModel & { deprecatedAt?: string }>)) {
|
|
306
|
+
if (!entry?.id) continue;
|
|
307
|
+
const removedAt = Date.parse(entry.deprecatedAt ?? "");
|
|
308
|
+
if (Number.isNaN(removedAt) || now - removedAt > DEPRECATED_MODEL_TTL_MS) continue;
|
|
309
|
+
const model = { ...entry } as JsonModel & { deprecatedAt?: string };
|
|
310
|
+
delete model.deprecatedAt;
|
|
311
|
+
result.push(model);
|
|
312
|
+
}
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Append grace-period deprecated models the list does not already have (live data wins).
|
|
317
|
+
function withDeprecated(models: JsonModel[]): JsonModel[] {
|
|
318
|
+
const seen = new Set(models.map((m) => m.id));
|
|
319
|
+
const extras = activeDeprecatedModels().filter((m) => !seen.has(m.id));
|
|
320
|
+
return extras.length > 0 ? [...models, ...extras] : models;
|
|
321
|
+
}
|
|
322
|
+
|
|
278
323
|
function loadStaleModels(providerId: string, embeddedModels: JsonModel[]): JsonModel[] {
|
|
279
324
|
const cached = loadCachedModels(providerId);
|
|
280
325
|
if (!cached || cached.length === 0) return embeddedModels;
|
package/models.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
[
|
|
2
2
|
{
|
|
3
3
|
"id": "GLM-5.1",
|
|
4
|
-
"name": "GLM
|
|
5
|
-
"reasoning":
|
|
4
|
+
"name": "GLM-5.1",
|
|
5
|
+
"reasoning": true,
|
|
6
6
|
"input": [
|
|
7
7
|
"text"
|
|
8
8
|
],
|
|
9
9
|
"cost": {
|
|
10
|
-
"input": 1
|
|
11
|
-
"output":
|
|
12
|
-
"cacheRead": 0.
|
|
10
|
+
"input": 1,
|
|
11
|
+
"output": 3.2,
|
|
12
|
+
"cacheRead": 0.1,
|
|
13
13
|
"cacheWrite": 0
|
|
14
14
|
},
|
|
15
15
|
"contextWindow": 202752,
|
|
@@ -17,20 +17,22 @@
|
|
|
17
17
|
"compat": {
|
|
18
18
|
"maxTokensField": "max_completion_tokens",
|
|
19
19
|
"supportsDeveloperRole": false,
|
|
20
|
-
"supportsZdr": true
|
|
20
|
+
"supportsZdr": true,
|
|
21
|
+
"supportsStore": false,
|
|
22
|
+
"supportsReasoningEffort": true
|
|
21
23
|
}
|
|
22
24
|
},
|
|
23
25
|
{
|
|
24
26
|
"id": "GLM-5.2",
|
|
25
|
-
"name": "GLM
|
|
26
|
-
"reasoning":
|
|
27
|
+
"name": "GLM-5.2",
|
|
28
|
+
"reasoning": true,
|
|
27
29
|
"input": [
|
|
28
30
|
"text"
|
|
29
31
|
],
|
|
30
32
|
"cost": {
|
|
31
|
-
"input":
|
|
32
|
-
"output":
|
|
33
|
-
"cacheRead": 0,
|
|
33
|
+
"input": 1.26,
|
|
34
|
+
"output": 3.96,
|
|
35
|
+
"cacheRead": 0.23,
|
|
34
36
|
"cacheWrite": 0
|
|
35
37
|
},
|
|
36
38
|
"contextWindow": 1048576,
|
|
@@ -39,20 +41,21 @@
|
|
|
39
41
|
"maxTokensField": "max_completion_tokens",
|
|
40
42
|
"supportsDeveloperRole": false,
|
|
41
43
|
"supportsStore": false,
|
|
42
|
-
"supportsZdr": true
|
|
44
|
+
"supportsZdr": true,
|
|
45
|
+
"supportsReasoningEffort": true
|
|
43
46
|
}
|
|
44
47
|
},
|
|
45
48
|
{
|
|
46
49
|
"id": "glm5.2-fast",
|
|
47
|
-
"name": "
|
|
48
|
-
"reasoning":
|
|
50
|
+
"name": "GLM5.2-Fast",
|
|
51
|
+
"reasoning": true,
|
|
49
52
|
"input": [
|
|
50
53
|
"text"
|
|
51
54
|
],
|
|
52
55
|
"cost": {
|
|
53
|
-
"input":
|
|
54
|
-
"output":
|
|
55
|
-
"cacheRead": 0,
|
|
56
|
+
"input": 2.1,
|
|
57
|
+
"output": 6.6,
|
|
58
|
+
"cacheRead": 0.21,
|
|
56
59
|
"cacheWrite": 0
|
|
57
60
|
},
|
|
58
61
|
"contextWindow": 1048576,
|
|
@@ -61,20 +64,22 @@
|
|
|
61
64
|
"maxTokensField": "max_completion_tokens",
|
|
62
65
|
"supportsDeveloperRole": false,
|
|
63
66
|
"supportsStore": false,
|
|
64
|
-
"supportsZdr": true
|
|
67
|
+
"supportsZdr": true,
|
|
68
|
+
"supportsReasoningEffort": true
|
|
65
69
|
}
|
|
66
70
|
},
|
|
67
71
|
{
|
|
68
72
|
"id": "Kimi-K2.6",
|
|
69
|
-
"name": "Kimi
|
|
70
|
-
"reasoning":
|
|
73
|
+
"name": "Kimi-K2.6",
|
|
74
|
+
"reasoning": true,
|
|
71
75
|
"input": [
|
|
72
|
-
"text"
|
|
76
|
+
"text",
|
|
77
|
+
"image"
|
|
73
78
|
],
|
|
74
79
|
"cost": {
|
|
75
|
-
"input": 1.
|
|
80
|
+
"input": 1.14,
|
|
76
81
|
"output": 4.8,
|
|
77
|
-
"cacheRead": 0.
|
|
82
|
+
"cacheRead": 0.19,
|
|
78
83
|
"cacheWrite": 0
|
|
79
84
|
},
|
|
80
85
|
"contextWindow": 262144,
|
|
@@ -82,20 +87,71 @@
|
|
|
82
87
|
"compat": {
|
|
83
88
|
"maxTokensField": "max_completion_tokens",
|
|
84
89
|
"supportsDeveloperRole": false,
|
|
85
|
-
"supportsZdr": false
|
|
90
|
+
"supportsZdr": false,
|
|
91
|
+
"supportsStore": false,
|
|
92
|
+
"supportsReasoningEffort": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "Kimi-K3",
|
|
97
|
+
"name": "Kimi-K3",
|
|
98
|
+
"reasoning": true,
|
|
99
|
+
"input": [
|
|
100
|
+
"text",
|
|
101
|
+
"image"
|
|
102
|
+
],
|
|
103
|
+
"cost": {
|
|
104
|
+
"input": 3,
|
|
105
|
+
"output": 15,
|
|
106
|
+
"cacheRead": 0.3,
|
|
107
|
+
"cacheWrite": 0
|
|
108
|
+
},
|
|
109
|
+
"contextWindow": 912384,
|
|
110
|
+
"maxTokens": 16384,
|
|
111
|
+
"compat": {
|
|
112
|
+
"maxTokensField": "max_completion_tokens",
|
|
113
|
+
"supportsDeveloperRole": false,
|
|
114
|
+
"supportsStore": false,
|
|
115
|
+
"supportsZdr": true,
|
|
116
|
+
"supportsReasoningEffort": true
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "kimi-k3-fast",
|
|
121
|
+
"name": "Kimi-K3-Fast",
|
|
122
|
+
"reasoning": true,
|
|
123
|
+
"input": [
|
|
124
|
+
"text",
|
|
125
|
+
"image"
|
|
126
|
+
],
|
|
127
|
+
"cost": {
|
|
128
|
+
"input": 4.5,
|
|
129
|
+
"output": 22.5,
|
|
130
|
+
"cacheRead": 0.45,
|
|
131
|
+
"cacheWrite": 0
|
|
132
|
+
},
|
|
133
|
+
"contextWindow": 1048576,
|
|
134
|
+
"maxTokens": 16384,
|
|
135
|
+
"compat": {
|
|
136
|
+
"maxTokensField": "max_completion_tokens",
|
|
137
|
+
"supportsDeveloperRole": false,
|
|
138
|
+
"supportsStore": false,
|
|
139
|
+
"supportsZdr": true,
|
|
140
|
+
"supportsReasoningEffort": true
|
|
86
141
|
}
|
|
87
142
|
},
|
|
88
143
|
{
|
|
89
144
|
"id": "MiniMax-M3",
|
|
90
|
-
"name": "MiniMax
|
|
91
|
-
"reasoning":
|
|
145
|
+
"name": "MiniMax-M3",
|
|
146
|
+
"reasoning": true,
|
|
92
147
|
"input": [
|
|
93
|
-
"text"
|
|
148
|
+
"text",
|
|
149
|
+
"image"
|
|
94
150
|
],
|
|
95
151
|
"cost": {
|
|
96
|
-
"input": 0,
|
|
97
|
-
"output":
|
|
98
|
-
"cacheRead": 0,
|
|
152
|
+
"input": 0.33,
|
|
153
|
+
"output": 1.32,
|
|
154
|
+
"cacheRead": 0.07,
|
|
99
155
|
"cacheWrite": 0
|
|
100
156
|
},
|
|
101
157
|
"contextWindow": 1048576,
|
|
@@ -104,7 +160,8 @@
|
|
|
104
160
|
"maxTokensField": "max_completion_tokens",
|
|
105
161
|
"supportsDeveloperRole": false,
|
|
106
162
|
"supportsStore": false,
|
|
107
|
-
"supportsZdr": false
|
|
163
|
+
"supportsZdr": false,
|
|
164
|
+
"supportsReasoningEffort": true
|
|
108
165
|
}
|
|
109
166
|
},
|
|
110
167
|
{
|
|
@@ -112,13 +169,12 @@
|
|
|
112
169
|
"name": "Qwen 3.5 397B (A17B)",
|
|
113
170
|
"reasoning": false,
|
|
114
171
|
"input": [
|
|
115
|
-
"text"
|
|
116
|
-
"image"
|
|
172
|
+
"text"
|
|
117
173
|
],
|
|
118
174
|
"cost": {
|
|
119
|
-
"input": 0
|
|
120
|
-
"output":
|
|
121
|
-
"cacheRead": 0
|
|
175
|
+
"input": 0,
|
|
176
|
+
"output": 0,
|
|
177
|
+
"cacheRead": 0,
|
|
122
178
|
"cacheWrite": 0
|
|
123
179
|
},
|
|
124
180
|
"contextWindow": 262144,
|
|
@@ -126,7 +182,8 @@
|
|
|
126
182
|
"compat": {
|
|
127
183
|
"maxTokensField": "max_completion_tokens",
|
|
128
184
|
"supportsDeveloperRole": false,
|
|
129
|
-
"supportsZdr": false
|
|
185
|
+
"supportsZdr": false,
|
|
186
|
+
"supportsStore": false
|
|
130
187
|
}
|
|
131
188
|
}
|
|
132
189
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-wafer-provider",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Wafer Serverless provider extension for pi - Access Qwen3.5-397B-A17B, GLM-5.1, Kimi K2.6, and DeepSeek-V4 through the Wafer Serverless API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
package/patch.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"Qwen3.5-397B-A17B": {
|
|
3
|
-
"providers": [
|
|
3
|
+
"providers": [
|
|
4
|
+
"wafer-serverless"
|
|
5
|
+
],
|
|
4
6
|
"reasoning": true,
|
|
5
7
|
"compat": {
|
|
6
8
|
"thinkingFormat": "qwen",
|
|
@@ -10,7 +12,9 @@
|
|
|
10
12
|
}
|
|
11
13
|
},
|
|
12
14
|
"GLM-5.1": {
|
|
13
|
-
"providers": [
|
|
15
|
+
"providers": [
|
|
16
|
+
"wafer-serverless"
|
|
17
|
+
],
|
|
14
18
|
"reasoning": true,
|
|
15
19
|
"compat": {
|
|
16
20
|
"thinkingFormat": "zai",
|
|
@@ -20,67 +24,14 @@
|
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
26
|
"Kimi-K2.6": {
|
|
23
|
-
"providers": [
|
|
27
|
+
"providers": [
|
|
28
|
+
"wafer-serverless"
|
|
29
|
+
],
|
|
24
30
|
"reasoning": true,
|
|
25
31
|
"compat": {
|
|
26
32
|
"maxTokensField": "max_completion_tokens",
|
|
27
33
|
"supportsDeveloperRole": false,
|
|
28
34
|
"supportsReasoningEffort": true
|
|
29
35
|
}
|
|
30
|
-
},
|
|
31
|
-
"Qwen3.6-35B-A3B": {
|
|
32
|
-
"providers": ["wafer-serverless"],
|
|
33
|
-
"reasoning": true,
|
|
34
|
-
"compat": {
|
|
35
|
-
"thinkingFormat": "qwen",
|
|
36
|
-
"maxTokensField": "max_completion_tokens",
|
|
37
|
-
"supportsDeveloperRole": false,
|
|
38
|
-
"supportsReasoningEffort": true
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
"deepseek-v4-flash": {
|
|
42
|
-
"providers": ["wafer-serverless"],
|
|
43
|
-
"reasoning": true,
|
|
44
|
-
"compat": {
|
|
45
|
-
"thinkingFormat": "deepseek",
|
|
46
|
-
"maxTokensField": "max_completion_tokens",
|
|
47
|
-
"supportsDeveloperRole": false,
|
|
48
|
-
"supportsReasoningEffort": true
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
"deepseek-v4-pro": {
|
|
52
|
-
"providers": ["wafer-serverless"],
|
|
53
|
-
"reasoning": true,
|
|
54
|
-
"contextWindow": 1000000,
|
|
55
|
-
"maxTokens": 384000,
|
|
56
|
-
"thinkingLevelMap": {
|
|
57
|
-
"minimal": null,
|
|
58
|
-
"low": null,
|
|
59
|
-
"medium": null,
|
|
60
|
-
"high": "high",
|
|
61
|
-
"max": "max"
|
|
62
|
-
},
|
|
63
|
-
"compat": {
|
|
64
|
-
"thinkingFormat": "deepseek",
|
|
65
|
-
"maxTokensField": "max_completion_tokens",
|
|
66
|
-
"supportsDeveloperRole": false,
|
|
67
|
-
"supportsReasoningEffort": true
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
"qwen3.7-max": {
|
|
71
|
-
"providers": ["wafer-serverless"],
|
|
72
|
-
"reasoning": true,
|
|
73
|
-
"cost": {
|
|
74
|
-
"input": 5.0,
|
|
75
|
-
"output": 15.0,
|
|
76
|
-
"cacheRead": 0.5,
|
|
77
|
-
"cacheWrite": 0
|
|
78
|
-
},
|
|
79
|
-
"compat": {
|
|
80
|
-
"thinkingFormat": "qwen",
|
|
81
|
-
"maxTokensField": "max_completion_tokens",
|
|
82
|
-
"supportsDeveloperRole": false,
|
|
83
|
-
"supportsReasoningEffort": true
|
|
84
|
-
}
|
|
85
36
|
}
|
|
86
37
|
}
|
package/scripts/update-models.js
CHANGED
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
* - models.json: Provider model definitions (enriched with pricing & compat)
|
|
7
7
|
* - README.md: Model table in the Available Models section
|
|
8
8
|
*
|
|
9
|
-
* The Wafer /v1/models API returns
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* existing data and only adds new models with sensible defaults.
|
|
13
|
-
* Curate models.json manually after new model discovery.
|
|
9
|
+
* The Wafer /v1/models API returns model limits plus nested capability, pricing,
|
|
10
|
+
* modality, and ZDR metadata. models.json mirrors those API-owned fields while
|
|
11
|
+
* patch.json remains the source of truth for thinking controls and corrections.
|
|
14
12
|
*
|
|
15
13
|
* patch.json is applied at runtime by the provider — not baked into models.json.
|
|
16
14
|
*
|
|
@@ -72,39 +70,51 @@ async function fetchModels() {
|
|
|
72
70
|
function transformApiModel(apiModel, existingModelsMap) {
|
|
73
71
|
const id = apiModel.id;
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
const details = apiModel.wafer || {};
|
|
74
|
+
const capabilities = details.capabilities || {};
|
|
75
|
+
const pricing = details.pricing || {};
|
|
76
|
+
const reasoning = capabilities.reasoning === true;
|
|
77
|
+
const input = capabilities.vision === true ? ['text', 'image'] : ['text'];
|
|
78
|
+
const cost = {
|
|
79
|
+
input: (pricing.input_cents_per_million || 0) / 100,
|
|
80
|
+
output: (pricing.output_cents_per_million || 0) / 100,
|
|
81
|
+
cacheRead: (pricing.cache_read_cents_per_million || 0) / 100,
|
|
82
|
+
cacheWrite: 0,
|
|
83
|
+
};
|
|
84
|
+
|
|
76
85
|
if (existingModelsMap[id]) {
|
|
77
86
|
const existing = { ...existingModelsMap[id] };
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
existing.compat
|
|
85
|
-
|
|
87
|
+
existing.name = details.display_name || existing.name;
|
|
88
|
+
existing.reasoning = reasoning;
|
|
89
|
+
existing.input = input;
|
|
90
|
+
existing.cost = cost;
|
|
91
|
+
existing.contextWindow = details.context_length || apiModel.max_model_len || existing.contextWindow;
|
|
92
|
+
existing.compat = {
|
|
93
|
+
...(existing.compat || {}),
|
|
94
|
+
supportsStore: false,
|
|
95
|
+
supportsDeveloperRole: false,
|
|
96
|
+
maxTokensField: 'max_completion_tokens',
|
|
97
|
+
supportsZdr: apiModel.zdr_supported,
|
|
98
|
+
...(reasoning ? { supportsReasoningEffort: true } : {}),
|
|
99
|
+
};
|
|
86
100
|
return existing;
|
|
87
101
|
}
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
const contextWindow = details.context_length || apiModel.max_model_len || 131072;
|
|
90
104
|
const model = {
|
|
91
105
|
id,
|
|
92
|
-
name: generateDisplayName(id),
|
|
93
|
-
reasoning
|
|
94
|
-
input
|
|
95
|
-
cost
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
cacheRead: 0,
|
|
99
|
-
cacheWrite: 0,
|
|
100
|
-
},
|
|
101
|
-
contextWindow: apiModel.max_model_len || 131072,
|
|
102
|
-
maxTokens: 16384,
|
|
106
|
+
name: details.display_name || generateDisplayName(id),
|
|
107
|
+
reasoning,
|
|
108
|
+
input,
|
|
109
|
+
cost,
|
|
110
|
+
contextWindow,
|
|
111
|
+
maxTokens: contextWindow,
|
|
103
112
|
compat: {
|
|
104
113
|
maxTokensField: 'max_completion_tokens',
|
|
105
114
|
supportsDeveloperRole: false,
|
|
106
115
|
supportsStore: false,
|
|
107
116
|
supportsZdr: apiModel.zdr_supported,
|
|
117
|
+
...(reasoning ? { supportsReasoningEffort: true } : {}),
|
|
108
118
|
},
|
|
109
119
|
};
|
|
110
120
|
|
|
@@ -226,6 +236,67 @@ function updateReadme(models) {
|
|
|
226
236
|
|
|
227
237
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
228
238
|
|
|
239
|
+
// Grace period for delisted models: update-models.js moves models the API no
|
|
240
|
+
// longer lists into deprecated-models.json (stamped with deprecatedAt) instead
|
|
241
|
+
// of dropping them; the runtime appends them back so sessions and saved model
|
|
242
|
+
// settings keep working, and after 14 days they are evicted permanently.
|
|
243
|
+
const DEPRECATED_MODEL_TTL_MS = 14 * 24 * 60 * 60 * 1000;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Reconcile deprecated-models.json against the freshly fetched model list.
|
|
247
|
+
* - in old models.json but not the API: moved into the deprecated file
|
|
248
|
+
* (deprecatedAt = now; preserved on repeat runs so the grace clock is not reset)
|
|
249
|
+
* - back in the API: resurrected (dropped from the deprecated file)
|
|
250
|
+
* - deprecatedAt older than 14 days: evicted permanently
|
|
251
|
+
* Must run BEFORE the new models.json is written; it reads the old file itself.
|
|
252
|
+
*/
|
|
253
|
+
function updateDeprecatedModels(modelsJsonPath, newModels) {
|
|
254
|
+
const deprecatedPath = path.join(path.dirname(modelsJsonPath), 'deprecated-models.json');
|
|
255
|
+
|
|
256
|
+
let oldModels = [];
|
|
257
|
+
try {
|
|
258
|
+
const parsed = JSON.parse(fs.readFileSync(modelsJsonPath, 'utf8'));
|
|
259
|
+
if (Array.isArray(parsed)) oldModels = parsed;
|
|
260
|
+
} catch { /* first run: no previous models.json */ }
|
|
261
|
+
|
|
262
|
+
let deprecated = {};
|
|
263
|
+
try {
|
|
264
|
+
const parsed = JSON.parse(fs.readFileSync(deprecatedPath, 'utf8'));
|
|
265
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) deprecated = parsed;
|
|
266
|
+
} catch { /* no graveyard yet */ }
|
|
267
|
+
|
|
268
|
+
const currentIds = new Set(newModels.map(m => m.id));
|
|
269
|
+
const now = new Date().toISOString();
|
|
270
|
+
const added = [];
|
|
271
|
+
const resurrected = [];
|
|
272
|
+
const evicted = [];
|
|
273
|
+
|
|
274
|
+
for (const old of oldModels) {
|
|
275
|
+
if (old && old.id && !currentIds.has(old.id) && !deprecated[old.id]) {
|
|
276
|
+
deprecated[old.id] = { ...old, deprecatedAt: now };
|
|
277
|
+
added.push(old.id);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (const [id, entry] of Object.entries(deprecated)) {
|
|
282
|
+
if (currentIds.has(id)) {
|
|
283
|
+
delete deprecated[id];
|
|
284
|
+
resurrected.push(id);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const removedAt = Date.parse(entry && entry.deprecatedAt ? entry.deprecatedAt : '');
|
|
288
|
+
if (Number.isNaN(removedAt) || Date.now() - removedAt > DEPRECATED_MODEL_TTL_MS) {
|
|
289
|
+
delete deprecated[id];
|
|
290
|
+
evicted.push(id);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (added.length > 0 || resurrected.length > 0 || evicted.length > 0) {
|
|
295
|
+
fs.writeFileSync(deprecatedPath, JSON.stringify(deprecated, null, 2) + '\n');
|
|
296
|
+
console.log('Updated deprecated-models.json ' + JSON.stringify({ added, resurrected, evicted }));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
229
300
|
async function main() {
|
|
230
301
|
try {
|
|
231
302
|
const apiModels = await fetchModels();
|
|
@@ -249,6 +320,8 @@ async function main() {
|
|
|
249
320
|
models.sort((a, b) => a.name.localeCompare(b.name));
|
|
250
321
|
|
|
251
322
|
// Save models.json (pure API output, no patch/custom baked in)
|
|
323
|
+
// Move delisted models to deprecated-models.json BEFORE models.json is overwritten
|
|
324
|
+
updateDeprecatedModels(MODELS_JSON_PATH, models);
|
|
252
325
|
saveJson(MODELS_JSON_PATH, models);
|
|
253
326
|
|
|
254
327
|
// Build full model list for README: base → patch → custom
|