pi-opencode-go-provider 1.0.6 → 1.0.8

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 CHANGED
@@ -68,22 +68,24 @@ pi
68
68
 
69
69
  | Model | API | Type | Context | Max Tokens | Input Cost | Output Cost |
70
70
  |-------|-----|------|---------|------------|------------|-------------|
71
- | DeepSeek V4 Flash | Completions | Text | 1.0M | 384K | $0.14 | $0.28 |
71
+ | DeepSeek V4 Flash | Completions | Text | 1.0M | 384K | $0.07 | $0.14 |
72
72
  | DeepSeek V4 Pro | Completions | Text | 1.0M | 384K | $0.43 | $0.87 |
73
73
  | GLM-5.1 | Completions | Text | 203K | 33K | $1.40 | $4.40 |
74
74
  | GLM-5.2 | Completions | Text | 1.0M | 131K | $1.40 | $4.40 |
75
- | Grok 4.5 | Completions | Text + Image | 500K | 500K | $2.00 | $6.00 |
75
+ | GPT-5.6 Luna (2x usage) | Responses | Text + Image | 1.1M | 128K | $0.10 | $0.60 |
76
+ | Grok 4.5 | Responses | Text + Image | 500K | 500K | $2.00 | $6.00 |
76
77
  | Hy3 | Completions | Text | 256K | 64K | $0.14 | $0.58 |
77
78
  | Kimi K2.6 | Completions | Text + Image | 262K | 66K | $0.95 | $4.00 |
78
79
  | Kimi K2.7 Code | Completions | Text + Image | 262K | 262K | $0.95 | $4.00 |
79
80
  | Kimi K3 (2x usage) | Completions | Text + Image | 1.0M | 131K | $3.00 | $15.00 |
80
81
  | MiMo V2.5 | Completions | Text + Image | 1.0M | 128K | $0.14 | $0.28 |
81
82
  | MiMo V2.5 Pro | Completions | Text | 1.0M | 128K | $0.43 | $0.87 |
82
- | MiniMax-M2.7 | Anthropic | Text | 205K | 131K | $0.30 | $1.20 |
83
+ | MiniMax-M2.7 | Completions | Text | 205K | 131K | $0.30 | $1.20 |
83
84
  | MiniMax-M3 | Anthropic | Text + Image | 1.0M | 131K | $0.30 | $1.20 |
84
- | Qwen3.6 Plus | Anthropic | Text + Image | 1.0M | 66K | $0.50 | $3.00 |
85
+ | Qwen3.6 Plus | Completions | Text + Image | 1.0M | 66K | $0.50 | $3.00 |
85
86
  | Qwen3.7 Max | Anthropic | Text | 1.0M | 66K | $2.50 | $7.50 |
86
87
  | Qwen3.7 Plus | Anthropic | Text + Image | 1.0M | 66K | $0.40 | $1.60 |
88
+ | Qwen3.8 Max | Anthropic | Text + Image | 1.0M | 131K | $2.00 | $6.00 |
87
89
  *Costs are per million tokens. Prices subject to change - check [opencode.ai](https://opencode.ai) for current pricing.*
88
90
 
89
91
  ## Usage
package/index.ts CHANGED
@@ -52,7 +52,9 @@ interface JsonModel {
52
52
  cacheRead: number;
53
53
  cacheWrite: number;
54
54
  };
55
- contextWindow: number;
55
+ // null between transformApiModel and mergeWithEmbedded: the live /v1/models
56
+ // API omits context data; null = "API silent, prefer curated/default".
57
+ contextWindow: number | null;
56
58
  maxTokens: number;
57
59
  compat?: Record<string, unknown>;
58
60
  }
@@ -185,7 +187,12 @@ function transformApiModel(apiModel: any): JsonModel {
185
187
  cacheRead: apiModel.cost?.cache_read || 0,
186
188
  cacheWrite: apiModel.cost?.cache_write || 0,
187
189
  },
188
- contextWindow: apiModel.limit?.context || apiModel.context_length || 131072,
190
+ // The live /v1/models endpoint exposes only {id, object, created, owned_by}
191
+ // — no `limit`, no `context_length`. A literal `|| 131072` here used to make
192
+ // every "API silent" model truthy 131072, which then clobbered the curated
193
+ // 1M/262K values in mergeWithEmbedded via the `||` truthy check. Use `null`
194
+ // so the embedded curated value is preferred when the API is silent.
195
+ contextWindow: apiModel.limit?.context || apiModel.context_length || null,
189
196
  maxTokens: apiModel.limit?.output || 0,
190
197
  };
191
198
  }
@@ -246,10 +253,20 @@ function mergeWithEmbedded(liveModels: JsonModel[], embeddedModels: JsonModel[])
246
253
  cacheRead: liveModel.cost.cacheRead || embedded.cost.cacheRead,
247
254
  cacheWrite: liveModel.cost.cacheWrite || embedded.cost.cacheWrite,
248
255
  },
249
- contextWindow: liveModel.contextWindow || embedded.contextWindow,
256
+ // Nullish coalesce: if the live API was silent (liveModel.contextWindow
257
+ // is null), fall through to the curated embedded value. The final
258
+ // `?? 131072` is the safe default for the rare case of a Custom-only
259
+ // model with neither live nor curated data. Using `||` here was the
260
+ // bug — it treated the API's 131072-fallback as authoritative and
261
+ // shadowed curated 1M/262K context windows.
262
+ contextWindow: liveModel.contextWindow ?? embedded.contextWindow ?? 131072,
250
263
  });
251
264
  } else {
252
- result.push(liveModel);
265
+ // API-only model (no curated embedded counterpart). The Live API is silent
266
+ // on contextWindow for all current opencode-go models, so liveModel.contextWindow
267
+ // is null. Apply the safe default here so downstream consumers (e.g. pi core's
268
+ // formatTokenCount → count.toString()) don't crash on null.
269
+ result.push({ ...liveModel, contextWindow: liveModel.contextWindow ?? 131072 });
253
270
  }
254
271
  }
255
272
  // Append any embedded models that the live API didn't return
@@ -346,7 +363,7 @@ export default function (pi: ExtensionAPI) {
346
363
  thinkingLevelMap: m.thinkingLevelMap,
347
364
  input: m.input,
348
365
  cost: m.cost,
349
- contextWindow: m.contextWindow,
366
+ contextWindow: m.contextWindow ?? 131072,
350
367
  maxTokens: m.maxTokens,
351
368
  compat: m.compat,
352
369
  })),
@@ -373,7 +390,7 @@ export default function (pi: ExtensionAPI) {
373
390
  thinkingLevelMap: m.thinkingLevelMap,
374
391
  input: m.input,
375
392
  cost: m.cost,
376
- contextWindow: m.contextWindow,
393
+ contextWindow: m.contextWindow ?? 131072,
377
394
  maxTokens: m.maxTokens,
378
395
  compat: m.compat,
379
396
  })),
package/models.json CHANGED
@@ -38,7 +38,7 @@
38
38
  },
39
39
  {
40
40
  "id": "deepseek-v4-flash",
41
- "name": "DeepSeek V4 Flash",
41
+ "name": "DeepSeek V4 Flash (2x usage)",
42
42
  "api": "openai-completions",
43
43
  "baseUrl": "https://opencode.ai/zen/go/v1",
44
44
  "reasoning": true,
@@ -46,9 +46,9 @@
46
46
  "text"
47
47
  ],
48
48
  "cost": {
49
- "input": 0.14,
50
- "output": 0.28,
51
- "cacheRead": 0.0028,
49
+ "input": 0.07,
50
+ "output": 0.14,
51
+ "cacheRead": 0.0014,
52
52
  "cacheWrite": 0
53
53
  },
54
54
  "contextWindow": 1000000,
@@ -182,6 +182,25 @@
182
182
  "contextWindow": 1000000,
183
183
  "maxTokens": 384000
184
184
  },
185
+ {
186
+ "id": "qwen3.8-max",
187
+ "name": "Qwen3.8 Max",
188
+ "api": "anthropic-messages",
189
+ "baseUrl": "https://opencode.ai/zen/go",
190
+ "reasoning": true,
191
+ "input": [
192
+ "text",
193
+ "image"
194
+ ],
195
+ "cost": {
196
+ "input": 2,
197
+ "output": 6,
198
+ "cacheRead": 0.25,
199
+ "cacheWrite": 2.5
200
+ },
201
+ "contextWindow": 1000000,
202
+ "maxTokens": 131072
203
+ },
185
204
  {
186
205
  "id": "mimo-v2.5",
187
206
  "name": "MiMo V2.5",
@@ -201,6 +220,25 @@
201
220
  "contextWindow": 1000000,
202
221
  "maxTokens": 128000
203
222
  },
223
+ {
224
+ "id": "gpt-5.6-luna",
225
+ "name": "GPT-5.6 Luna (2x usage)",
226
+ "api": "openai-completions",
227
+ "baseUrl": "https://opencode.ai/zen/go/v1",
228
+ "reasoning": true,
229
+ "input": [
230
+ "text",
231
+ "image"
232
+ ],
233
+ "cost": {
234
+ "input": 0.1,
235
+ "output": 0.6,
236
+ "cacheRead": 0.01,
237
+ "cacheWrite": 0.125
238
+ },
239
+ "contextWindow": 1050000,
240
+ "maxTokens": 128000
241
+ },
204
242
  {
205
243
  "id": "grok-4.5",
206
244
  "name": "Grok 4.5",
@@ -241,7 +279,7 @@
241
279
  },
242
280
  {
243
281
  "id": "kimi-k3",
244
- "name": "Kimi K3 (2x usage)",
282
+ "name": "Kimi K3",
245
283
  "api": "openai-completions",
246
284
  "baseUrl": "https://opencode.ai/zen/go/v1",
247
285
  "reasoning": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-opencode-go-provider",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Opencode Go provider extension for pi - Fast, efficient GLM, Kimi, and MiniMax models through the opencode.ai API",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/patch.json CHANGED
@@ -1,17 +1,176 @@
1
1
  {
2
+ "glm-5.1": {
3
+ "compat": {
4
+ "supportsStore": false,
5
+ "supportsDeveloperRole": false,
6
+ "maxTokensField": "max_tokens"
7
+ }
8
+ },
2
9
  "deepseek-v4-flash": {
3
- "compat": { "requiresReasoningContentOnAssistantMessages": true, "thinkingFormat": "deepseek" },
4
- "thinkingLevelMap": { "minimal": null, "low": null, "medium": null, "high": "high", "max": "max" }
10
+ "name": "DeepSeek V4 Flash",
11
+ "thinkingLevelMap": {
12
+ "minimal": null,
13
+ "low": null,
14
+ "medium": null,
15
+ "high": "high",
16
+ "max": "max"
17
+ },
18
+ "compat": {
19
+ "supportsStore": false,
20
+ "supportsDeveloperRole": false,
21
+ "maxTokensField": "max_tokens",
22
+ "requiresReasoningContentOnAssistantMessages": true,
23
+ "thinkingFormat": "deepseek"
24
+ }
5
25
  },
6
- "deepseek-v4-pro": {
7
- "compat": { "requiresReasoningContentOnAssistantMessages": true, "thinkingFormat": "deepseek" },
8
- "thinkingLevelMap": { "minimal": null, "low": null, "medium": null, "high": "high", "max": "max" }
26
+ "minimax-m2.7": {
27
+ "api": "openai-completions",
28
+ "baseUrl": "https://opencode.ai/zen/go/v1",
29
+ "compat": {
30
+ "supportsStore": false,
31
+ "supportsDeveloperRole": false,
32
+ "maxTokensField": "max_tokens"
33
+ }
34
+ },
35
+ "glm-5.2": {
36
+ "thinkingLevelMap": {
37
+ "off": null,
38
+ "minimal": null,
39
+ "low": null,
40
+ "medium": null,
41
+ "high": "high",
42
+ "xhigh": null,
43
+ "max": "max"
44
+ },
45
+ "compat": {
46
+ "supportsStore": false,
47
+ "supportsDeveloperRole": false,
48
+ "maxTokensField": "max_tokens"
49
+ }
9
50
  },
10
51
  "kimi-k2.6": {
11
- "compat": { "thinkingFormat": "deepseek", "supportsReasoningEffort": false },
12
- "thinkingLevelMap": { "minimal": null, "low": null, "medium": null }
52
+ "thinkingLevelMap": {
53
+ "minimal": null,
54
+ "low": null,
55
+ "medium": null
56
+ },
57
+ "compat": {
58
+ "supportsStore": false,
59
+ "supportsDeveloperRole": false,
60
+ "thinkingFormat": "deepseek",
61
+ "supportsReasoningEffort": false,
62
+ "maxTokensField": "max_tokens",
63
+ "supportsLongCacheRetention": false
64
+ }
65
+ },
66
+ "hy3": {
67
+ "thinkingLevelMap": {
68
+ "off": "none",
69
+ "minimal": null,
70
+ "low": "low",
71
+ "medium": null,
72
+ "high": "high",
73
+ "xhigh": null,
74
+ "max": null
75
+ },
76
+ "compat": {
77
+ "supportsStore": false,
78
+ "supportsDeveloperRole": false,
79
+ "maxTokensField": "max_tokens"
80
+ }
81
+ },
82
+ "deepseek-v4-pro": {
83
+ "thinkingLevelMap": {
84
+ "minimal": null,
85
+ "low": null,
86
+ "medium": null,
87
+ "high": "high",
88
+ "max": "max"
89
+ },
90
+ "compat": {
91
+ "supportsStore": false,
92
+ "supportsDeveloperRole": false,
93
+ "maxTokensField": "max_tokens",
94
+ "requiresReasoningContentOnAssistantMessages": true,
95
+ "thinkingFormat": "deepseek"
96
+ }
97
+ },
98
+ "mimo-v2.5": {
99
+ "compat": {
100
+ "supportsStore": false,
101
+ "supportsDeveloperRole": false,
102
+ "maxTokensField": "max_tokens"
103
+ }
104
+ },
105
+ "grok-4.5": {
106
+ "api": "openai-responses",
107
+ "thinkingLevelMap": {
108
+ "off": null,
109
+ "minimal": null,
110
+ "low": "low",
111
+ "medium": "medium",
112
+ "high": "high",
113
+ "xhigh": null,
114
+ "max": null
115
+ },
116
+ "compat": {
117
+ "sessionAffinityFormat": "openai-nosession"
118
+ }
119
+ },
120
+ "kimi-k2.7-code": {
121
+ "compat": {
122
+ "supportsStore": false,
123
+ "supportsDeveloperRole": false,
124
+ "maxTokensField": "max_tokens"
125
+ }
126
+ },
127
+ "kimi-k3": {
128
+ "name": "Kimi K3 (2x usage)",
129
+ "thinkingLevelMap": {
130
+ "off": null,
131
+ "minimal": null,
132
+ "low": null,
133
+ "medium": null,
134
+ "high": null,
135
+ "xhigh": null,
136
+ "max": "max"
137
+ },
138
+ "compat": {
139
+ "supportsStore": false,
140
+ "supportsDeveloperRole": false,
141
+ "maxTokensField": "max_tokens"
142
+ }
143
+ },
144
+ "mimo-v2.5-pro": {
145
+ "compat": {
146
+ "supportsStore": false,
147
+ "supportsDeveloperRole": false,
148
+ "maxTokensField": "max_tokens"
149
+ }
150
+ },
151
+ "gpt-5.6-luna": {
152
+ "api": "openai-responses",
153
+ "thinkingLevelMap": {
154
+ "off": null,
155
+ "minimal": null,
156
+ "low": "low",
157
+ "medium": "medium",
158
+ "high": "high",
159
+ "xhigh": "xhigh",
160
+ "max": "max"
161
+ },
162
+ "compat": {
163
+ "sessionAffinityFormat": "openai-nosession"
164
+ }
13
165
  },
14
166
  "qwen3.6-plus": {
15
- "compat": { "thinkingFormat": "qwen" }
167
+ "api": "openai-completions",
168
+ "baseUrl": "https://opencode.ai/zen/go/v1",
169
+ "compat": {
170
+ "supportsStore": false,
171
+ "supportsDeveloperRole": false,
172
+ "thinkingFormat": "qwen",
173
+ "maxTokensField": "max_tokens"
174
+ }
16
175
  }
17
176
  }
@@ -101,6 +101,46 @@ function convertModel(model) {
101
101
  };
102
102
  }
103
103
 
104
+ // Load a layered JSON model file.
105
+ function loadJson(fileName) {
106
+ try {
107
+ return JSON.parse(fs.readFileSync(path.join(process.cwd(), fileName), 'utf8'));
108
+ } catch {
109
+ return fileName === 'patch.json' ? {} : [];
110
+ }
111
+ }
112
+
113
+ // Deep-merge patch overrides into a model for README documentation. The
114
+ // generated models.json remains API-derived; only the displayed model map uses patches.
115
+ function applyPatch(model, patch) {
116
+ if (!patch) return model;
117
+ const result = { ...model };
118
+ for (const [key, value] of Object.entries(patch)) {
119
+ if (key === 'cost' || key === 'compat' || key === 'thinkingLevelMap') {
120
+ result[key] = { ...(result[key] || {}), ...value };
121
+ } else {
122
+ result[key] = value;
123
+ }
124
+ }
125
+ if (!result.reasoning) {
126
+ delete result.thinkingLevelMap;
127
+ if (result.compat?.thinkingFormat) delete result.compat.thinkingFormat;
128
+ }
129
+ return result;
130
+ }
131
+
132
+ // Merge API models, patches, and custom models in the same order as index.ts.
133
+ function buildModels(baseModels, customModels, patch) {
134
+ const byId = new Map(baseModels.map(model => [model.id, model]));
135
+ for (const [id, entry] of Object.entries(patch)) {
136
+ if (byId.has(id)) byId.set(id, applyPatch(byId.get(id), entry));
137
+ }
138
+ for (const model of customModels) {
139
+ byId.set(model.id, applyPatch(model, patch[model.id]));
140
+ }
141
+ return Array.from(byId.values());
142
+ }
143
+
104
144
  // Generate README model table row
105
145
  function generateReadmeRow(model) {
106
146
  const cost = model.cost || {};
@@ -238,8 +278,10 @@ async function main() {
238
278
  fs.writeFileSync(modelsPath, JSON.stringify(models, null, 2) + '\n');
239
279
  console.log(` Saved ${models.length} models to models.json`);
240
280
 
241
- // Update README
242
- updateReadme(models);
281
+ // Apply layered overrides for documentation: models.json → patch.json → custom-models.json.
282
+ const patch = loadJson('patch.json');
283
+ const customModels = Array.isArray(loadJson('custom-models.json')) ? loadJson('custom-models.json') : [];
284
+ updateReadme(buildModels(models, customModels, patch));
243
285
 
244
286
  console.log('\nDone!');
245
287
  } catch (error) {