pi-coralbricks-provider 1.0.26 → 1.0.28

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # 🪸 pi-coralbricks-provider
4
4
 
5
- **GLM 5.3 & DeepSeek V4.1 Flash (FP4 variants included) through [Coral Bricks](https://www.coralbricks.ai)**
5
+ **GLM 5.3 & DeepSeek V4.1 Flash through [Coral Bricks](https://www.coralbricks.ai)**
6
6
 
7
7
  _A [pi](https://github.com/earendil-works/pi-coding-agent) provider extension for Coral's OpenAI-compatible inference gateway — up to **1M context** on open models._
8
8
 
@@ -22,8 +22,8 @@ Run `bun run test:pi` for offline manifest, catalog, lifecycle and streaming che
22
22
 
23
23
  ## Features
24
24
 
25
- - **4 reasoning models** from Coral's live catalog — GLM 5.3 and its FP4 variant, DeepSeek V4.1 Flash and its FP4 variant
26
- - **1M token context** on GLM and DeepSeek, with vision (image input) on DeepSeek V4.1 Flash and its FP4 variant
25
+ - **2 reasoning models** from Coral's live catalog — GLM 5.3 and DeepSeek V4.1 Flash (the `-fp4` twin slugs Coral still serves are hidden as duplicates)
26
+ - **1M token context** on GLM and DeepSeek, with vision (image input) on DeepSeek V4.1 Flash
27
27
  - **OpenAI-compatible API** — standard `/v1/chat/completions`, streaming, and tool calling
28
28
  - **Per-family thinking levels** — zai-style `thinking` control for GLM (including a *real* off switch), `reasoning_effort` for DeepSeek V4.1 Flash
29
29
  - **Accurate cost tracking** — input, cache-write and output rates mirror Coral's [published pricing](https://www.coralbricks.ai/pricing), and cached reads are **$0 on every model**
@@ -85,10 +85,8 @@ pi
85
85
 
86
86
  | Model | Context | Vision | Reasoning | Input $/M | Cache Read $/M | Cache Write $/M | Output $/M |
87
87
  |-------|---------|--------|-----------|-----------|-----------------|------------------|------------|
88
- | DeepSeek V4.1 Flash | 1.0M | ✅ | ✅ | $0.30 | — | $0.09 | $1.20 |
89
- | DeepSeek V4.1 Flash FP4 | 1.0M | ✅ | ✅ | $0.30 | — | $0.09 | $1.20 |
88
+ | DeepSeek V4.1 Flash | 1.0M | ✅ | ✅ | $0.01 | — | $0.09 | $1.20 |
90
89
  | GLM 5.3 | 1.0M | ❌ | ✅ | $1.12 | — | $1.68 | $4.40 |
91
- | GLM 5.3 FP4 | 1.0M | ❌ | ✅ | $1.12 | — | $1.68 | $4.40 |
92
90
 
93
91
  *Costs are per million tokens. Cache Read shows — because Coral bills cached input at **$0** on every model. Prompt tokens Coral has not cached yet are billed once at the Cache Write rate, in place of the Input rate. Prices subject to change — check [Coral's live catalog](https://www.coralbricks.ai/api/public/models).*
94
92
 
@@ -188,7 +186,7 @@ Coral's gateway follows the OpenAI Chat Completions API:
188
186
 
189
187
  ### Patch Overrides & Custom Models
190
188
 
191
- - **`patch.json`** — per-model overrides applied on top of `models.json` (reasoning flags, pricing corrections, compat settings, thinking level maps). Currently one entry per live model: GLM 5.3 and its FP4 twin (reasoning, thinking level map, zai-style compat settings) and DeepSeek V4.1 Flash and its FP4 twin (reasoning, image input, thinking level map, `openai`-style compat settings).
189
+ - **`patch.json`** — per-model overrides applied on top of `models.json` (reasoning flags, pricing corrections, compat settings, thinking level maps, `"hidden"`). One entry per model Coral serves: GLM 5.3 and DeepSeek V4.1 Flash carry the reasoning and compat settings, and their `-fp4` twins set `"hidden": true` so `/model` and the table above list each model once.
192
190
  - **`custom-models.json`** — full model definitions for models Coral doesn't list. Merged after patch.
193
191
 
194
192
  Merge order: `[live|cache|embedded] → patch.json → custom-models.json`
package/index.ts CHANGED
@@ -73,10 +73,13 @@ interface JsonModel {
73
73
  supportsStrictMode?: boolean;
74
74
  requiresReasoningContentOnAssistantMessages?: boolean;
75
75
  };
76
+ /** Curation-only (patch.json): keep a served model out of the catalog. */
77
+ hidden?: boolean;
76
78
  }
77
79
 
78
80
  interface PatchEntry {
79
81
  name?: string;
82
+ hidden?: boolean;
80
83
  reasoning?: boolean;
81
84
  input?: ("text" | "image")[];
82
85
  cost?: {
@@ -106,6 +109,7 @@ function applyPatch(model: JsonModel, patch: PatchEntry): JsonModel {
106
109
  };
107
110
 
108
111
  if (patch.name !== undefined) result.name = patch.name;
112
+ if (patch.hidden !== undefined) result.hidden = patch.hidden;
109
113
  if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
110
114
  if (patch.input !== undefined) result.input = patch.input;
111
115
  if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
@@ -166,7 +170,11 @@ function buildModels(base: JsonModel[], custom: JsonModel[], patch: PatchData):
166
170
  }
167
171
  }
168
172
 
169
- return Array.from(modelMap.values());
173
+ // Hidden models stay out of the registered catalog — and therefore out of
174
+ // `/model` — while `models.json` keeps mirroring what Coral serves for the key.
175
+ return Array.from(modelMap.values())
176
+ .filter((m) => !m.hidden)
177
+ .map(({ hidden: _hidden, ...model }) => model);
170
178
  }
171
179
 
172
180
  // Stale-While-Revalidate Model Sync
@@ -189,6 +197,17 @@ function toNumber(value: unknown): number {
189
197
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
190
198
  }
191
199
 
200
+ /**
201
+ * Full price of a written prompt token. Since 2026-10-08 Coral states the cache write ON TOP
202
+ * of input (/v1/models cache_write_per_m, catalog cacheWritePerM), and a written token is
203
+ * billed input + that. pi prices a written token at cacheWrite alone, so add the two. 0 when
204
+ * the row carries no cache-write rate. Rounded so 1.12 + 0.56 is 1.68, not 1.6800000000000002.
205
+ */
206
+ function writtenTokenCost(input: number, cacheWriteOnTop: unknown): number {
207
+ if (typeof cacheWriteOnTop !== "number" || !Number.isFinite(cacheWriteOnTop)) return 0;
208
+ return Math.round((input + cacheWriteOnTop) * 1e6) / 1e6;
209
+ }
210
+
192
211
  function displayName(id: string): string {
193
212
  return id
194
213
  .split(/[-_/]/)
@@ -225,8 +244,9 @@ function baseCompat() {
225
244
  /**
226
245
  * Transform a Coral /v1/models row. Coral reports per-million USD pricing
227
246
  * (input_per_m / output_per_m / cached_input_per_m — cached reads are $0 on
228
- * every model — and cache_write_per_m, the rate for prompt tokens written to
229
- * the cache), context_length, and supports_image_input.
247
+ * every model — and cache_write_per_m, the cache write ON TOP of input for a
248
+ * prompt token written to the cache), context_length, and supports_image_input.
249
+ * pi's cacheWrite is the full price of a written token: input + cache_write_per_m.
230
250
  */
231
251
  function transformApiModel(apiModel: any): JsonModel | null {
232
252
  if (!apiModel?.id) return null;
@@ -240,7 +260,7 @@ function transformApiModel(apiModel: any): JsonModel | null {
240
260
  input: toNumber(pricing.input_per_m),
241
261
  output: toNumber(pricing.output_per_m),
242
262
  cacheRead: toNumber(pricing.cached_input_per_m),
243
- cacheWrite: toNumber(pricing.cache_write_per_m),
263
+ cacheWrite: writtenTokenCost(toNumber(pricing.input_per_m), pricing.cache_write_per_m),
244
264
  },
245
265
  contextWindow: parseContextWindow(apiModel.context_length),
246
266
  maxTokens: DEFAULT_MAX_TOKENS[apiModel.id] ?? FALLBACK_MAX_TOKENS,
@@ -253,10 +273,10 @@ function transformApiModel(apiModel: any): JsonModel | null {
253
273
  * Transform a row from the public model catalog
254
274
  * (https://www.coralbricks.ai/api/public/models) — the no-auth mirror of the
255
275
  * gateway control plane. Fields: slug, name, contextWindow ("1M"),
256
- * inputPerM / outputPerM. Coral's own prices are authoritative here; the
257
- * parity/field vendor comparison fields are ignored. The catalog carries no
258
- * cache-write rate, so cacheWrite stays 0 and mergeWithEmbedded keeps the
259
- * embedded rate.
276
+ * inputPerM / outputPerM, and since 2026-10-08 cacheWritePerM (the cache write on
277
+ * top of input). Coral's own prices are authoritative here; the parity/field vendor
278
+ * comparison fields are ignored. A row without cacheWritePerM leaves cacheWrite 0 so
279
+ * mergeWithEmbedded keeps the embedded rate.
260
280
  */
261
281
  function transformCatalogModel(entry: any): JsonModel | null {
262
282
  if (!entry?.slug) return null;
@@ -269,7 +289,7 @@ function transformCatalogModel(entry: any): JsonModel | null {
269
289
  input: toNumber(entry.inputPerM),
270
290
  output: toNumber(entry.outputPerM),
271
291
  cacheRead: 0,
272
- cacheWrite: 0,
292
+ cacheWrite: writtenTokenCost(toNumber(entry.inputPerM), entry.cacheWritePerM),
273
293
  },
274
294
  contextWindow: parseContextWindow(entry.contextWindow),
275
295
  maxTokens: DEFAULT_MAX_TOKENS[entry.slug] ?? FALLBACK_MAX_TOKENS,
package/models.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "image"
9
9
  ],
10
10
  "cost": {
11
- "input": 0.3,
11
+ "input": 0.01,
12
12
  "output": 1.2,
13
13
  "cacheRead": 0,
14
14
  "cacheWrite": 0.09
@@ -30,7 +30,7 @@
30
30
  "image"
31
31
  ],
32
32
  "cost": {
33
- "input": 0.3,
33
+ "input": 0.01,
34
34
  "output": 1.2,
35
35
  "cacheRead": 0,
36
36
  "cacheWrite": 0.09
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-coralbricks-provider",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "CoralBricks provider extension for pi - Access GLM 5.3 and DeepSeek V4.1 Flash (FP4 variants included) through the Coral Inference API",
5
5
  "author": "monotykamary",
6
6
  "homepage": "https://github.com/monotykamary/pi-coralbricks-provider#readme",
package/patch.json CHANGED
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "deepseek-v4.1-flash-fast-fp4": {
10
10
  "name": "DeepSeek V4.1 Flash FP4",
11
+ "hidden": true,
11
12
  "reasoning": true,
12
13
  "input": ["text", "image"],
13
14
  "thinkingLevelMap": { "off": "none", "minimal": null, "low": "low", "medium": null, "high": "high", "xhigh": null, "max": "max" },
@@ -23,6 +24,7 @@
23
24
  },
24
25
  "glm-5.3-fp4": {
25
26
  "name": "GLM 5.3 FP4",
27
+ "hidden": true,
26
28
  "reasoning": true,
27
29
  "thinkingLevelMap": { "off": "none", "minimal": null, "low": "low", "medium": null, "high": "high", "xhigh": null, "max": "max" },
28
30
  "maxTokens": 131072,
@@ -166,6 +166,13 @@ function saveJson(filePath, data) {
166
166
  console.log(`✓ Saved ${path.basename(filePath)}`);
167
167
  }
168
168
 
169
+ // Coral states the cache write ON TOP of input (2026-10-08); pi's cacheWrite is the full
170
+ // price of a written token, input + it. 0 when the row has no cache-write rate.
171
+ function writtenTokenCost(input, cacheWriteOnTop) {
172
+ if (typeof cacheWriteOnTop !== 'number' || !Number.isFinite(cacheWriteOnTop)) return 0;
173
+ return Math.round((input + cacheWriteOnTop) * 1e6) / 1e6;
174
+ }
175
+
169
176
  function toNumber(value) {
170
177
  return typeof value === 'number' && Number.isFinite(value) ? value : 0;
171
178
  }
@@ -274,7 +281,9 @@ function transformApiModel(apiModel, existingModelsMap) {
274
281
  if (typeof pricing.input_per_m === 'number') existing.cost.input = pricing.input_per_m;
275
282
  if (typeof pricing.output_per_m === 'number') existing.cost.output = pricing.output_per_m;
276
283
  existing.cost.cacheRead = toNumber(pricing.cached_input_per_m);
277
- if (typeof pricing.cache_write_per_m === 'number') existing.cost.cacheWrite = pricing.cache_write_per_m;
284
+ if (typeof pricing.cache_write_per_m === 'number') {
285
+ existing.cost.cacheWrite = writtenTokenCost(existing.cost.input, pricing.cache_write_per_m);
286
+ }
278
287
  existing.input = input;
279
288
  return existing;
280
289
  }
@@ -289,7 +298,7 @@ function transformApiModel(apiModel, existingModelsMap) {
289
298
  input: toNumber(pricing.input_per_m),
290
299
  output: toNumber(pricing.output_per_m),
291
300
  cacheRead: toNumber(pricing.cached_input_per_m),
292
- cacheWrite: toNumber(pricing.cache_write_per_m),
301
+ cacheWrite: writtenTokenCost(toNumber(pricing.input_per_m), pricing.cache_write_per_m),
293
302
  },
294
303
  contextWindow,
295
304
  maxTokens,
@@ -311,6 +320,9 @@ function transformCatalogModel(entry, existingModelsMap) {
311
320
  if (contextWindow) existing.contextWindow = contextWindow;
312
321
  if (typeof entry.inputPerM === 'number') existing.cost.input = entry.inputPerM;
313
322
  if (typeof entry.outputPerM === 'number') existing.cost.output = entry.outputPerM;
323
+ if (typeof entry.cacheWritePerM === 'number') {
324
+ existing.cost.cacheWrite = writtenTokenCost(existing.cost.input, entry.cacheWritePerM);
325
+ }
314
326
  return existing;
315
327
  }
316
328
 
@@ -323,7 +335,7 @@ function transformCatalogModel(entry, existingModelsMap) {
323
335
  input: toNumber(entry.inputPerM),
324
336
  output: toNumber(entry.outputPerM),
325
337
  cacheRead: 0,
326
- cacheWrite: 0,
338
+ cacheWrite: writtenTokenCost(toNumber(entry.inputPerM), entry.cacheWritePerM),
327
339
  },
328
340
  contextWindow,
329
341
  maxTokens,
@@ -347,6 +359,7 @@ function applyPatch(model, patch) {
347
359
  thinkingLevelMap: model.thinkingLevelMap ? { ...model.thinkingLevelMap } : undefined,
348
360
  };
349
361
  if (patch.name !== undefined) result.name = patch.name;
362
+ if (patch.hidden !== undefined) result.hidden = patch.hidden;
350
363
  if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
351
364
  if (patch.input !== undefined) result.input = patch.input;
352
365
  if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
@@ -400,7 +413,11 @@ function buildModels(baseModels, customModels, patchData) {
400
413
  modelMap.set(model.id, model);
401
414
  }
402
415
  }
403
- return Array.from(modelMap.values());
416
+ // Mirrors index.ts buildModels: hidden models are served by Coral but kept out
417
+ // of the catalog the provider offers, so the README table skips them too.
418
+ return Array.from(modelMap.values())
419
+ .filter((m) => !m.hidden)
420
+ .map(({ hidden: _hidden, ...model }) => model);
404
421
  }
405
422
 
406
423
  // README generation