@trazum/core 2.4.2 → 2.5.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.
Files changed (121) hide show
  1. package/dist/advisories.d.ts.map +1 -1
  2. package/dist/advisories.js +17 -10
  3. package/dist/advisories.js.map +1 -1
  4. package/dist/answer.d.ts.map +1 -1
  5. package/dist/answer.js +2 -1
  6. package/dist/answer.js.map +1 -1
  7. package/dist/anthropic-usage.d.ts +6 -0
  8. package/dist/anthropic-usage.d.ts.map +1 -1
  9. package/dist/anthropic-usage.js +1 -0
  10. package/dist/anthropic-usage.js.map +1 -1
  11. package/dist/billing.d.ts +20 -0
  12. package/dist/billing.d.ts.map +1 -0
  13. package/dist/billing.js +32 -0
  14. package/dist/billing.js.map +1 -0
  15. package/dist/budget.d.ts.map +1 -1
  16. package/dist/budget.js +18 -8
  17. package/dist/budget.js.map +1 -1
  18. package/dist/connector.d.ts +20 -1
  19. package/dist/connector.d.ts.map +1 -1
  20. package/dist/connector.js +42 -11
  21. package/dist/connector.js.map +1 -1
  22. package/dist/conversation.d.ts.map +1 -1
  23. package/dist/conversation.js +4 -2
  24. package/dist/conversation.js.map +1 -1
  25. package/dist/gateway.d.ts.map +1 -1
  26. package/dist/gateway.js +5 -3
  27. package/dist/gateway.js.map +1 -1
  28. package/dist/guard.d.ts.map +1 -1
  29. package/dist/guard.js +7 -5
  30. package/dist/guard.js.map +1 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/input-shape.d.ts.map +1 -1
  34. package/dist/input-shape.js +4 -2
  35. package/dist/input-shape.js.map +1 -1
  36. package/dist/ladder.d.ts.map +1 -1
  37. package/dist/ladder.js +2 -1
  38. package/dist/ladder.js.map +1 -1
  39. package/dist/levers.d.ts.map +1 -1
  40. package/dist/levers.js +5 -2
  41. package/dist/levers.js.map +1 -1
  42. package/dist/openai-usage.d.ts +18 -0
  43. package/dist/openai-usage.d.ts.map +1 -1
  44. package/dist/openai-usage.js +26 -1
  45. package/dist/openai-usage.js.map +1 -1
  46. package/dist/openrouter-activity.d.ts +6 -0
  47. package/dist/openrouter-activity.d.ts.map +1 -1
  48. package/dist/openrouter-activity.js +7 -0
  49. package/dist/openrouter-activity.js.map +1 -1
  50. package/dist/output-shape.d.ts.map +1 -1
  51. package/dist/output-shape.js +2 -1
  52. package/dist/output-shape.js.map +1 -1
  53. package/dist/pricing.d.ts +36 -4
  54. package/dist/pricing.d.ts.map +1 -1
  55. package/dist/pricing.js +804 -91
  56. package/dist/pricing.js.map +1 -1
  57. package/dist/receipt.d.ts +17 -0
  58. package/dist/receipt.d.ts.map +1 -1
  59. package/dist/receipt.js +10 -0
  60. package/dist/receipt.js.map +1 -1
  61. package/dist/repeats.d.ts.map +1 -1
  62. package/dist/repeats.js +4 -2
  63. package/dist/repeats.js.map +1 -1
  64. package/dist/reprice.d.ts +18 -2
  65. package/dist/reprice.d.ts.map +1 -1
  66. package/dist/reprice.js +34 -12
  67. package/dist/reprice.js.map +1 -1
  68. package/dist/rollup.d.ts.map +1 -1
  69. package/dist/rollup.js +3 -0
  70. package/dist/rollup.js.map +1 -1
  71. package/dist/savings.d.ts.map +1 -1
  72. package/dist/savings.js +10 -3
  73. package/dist/savings.js.map +1 -1
  74. package/dist/session-cost.d.ts.map +1 -1
  75. package/dist/session-cost.js +5 -2
  76. package/dist/session-cost.js.map +1 -1
  77. package/dist/session-ledger.d.ts.map +1 -1
  78. package/dist/session-ledger.js +12 -7
  79. package/dist/session-ledger.js.map +1 -1
  80. package/dist/truncation-retry.d.ts.map +1 -1
  81. package/dist/truncation-retry.js +4 -2
  82. package/dist/truncation-retry.js.map +1 -1
  83. package/dist/ttl-fit.d.ts +8 -0
  84. package/dist/ttl-fit.d.ts.map +1 -1
  85. package/dist/ttl-fit.js +25 -14
  86. package/dist/ttl-fit.js.map +1 -1
  87. package/dist/types.d.ts +51 -0
  88. package/dist/types.d.ts.map +1 -1
  89. package/dist/usage.d.ts +36 -1
  90. package/dist/usage.d.ts.map +1 -1
  91. package/dist/usage.js +91 -10
  92. package/dist/usage.js.map +1 -1
  93. package/package.json +1 -1
  94. package/src/advisories.ts +18 -10
  95. package/src/answer.ts +6 -1
  96. package/src/anthropic-usage.ts +7 -0
  97. package/src/billing.ts +37 -0
  98. package/src/budget.ts +18 -8
  99. package/src/connector.ts +58 -10
  100. package/src/conversation.ts +4 -2
  101. package/src/gateway.ts +5 -3
  102. package/src/guard.ts +7 -5
  103. package/src/index.ts +1 -1
  104. package/src/input-shape.ts +4 -2
  105. package/src/ladder.ts +2 -1
  106. package/src/levers.ts +5 -2
  107. package/src/openai-usage.ts +44 -2
  108. package/src/openrouter-activity.ts +13 -0
  109. package/src/output-shape.ts +2 -1
  110. package/src/pricing.ts +854 -96
  111. package/src/receipt.ts +23 -1
  112. package/src/repeats.ts +4 -2
  113. package/src/reprice.ts +57 -16
  114. package/src/rollup.ts +3 -0
  115. package/src/savings.ts +12 -5
  116. package/src/session-cost.ts +5 -2
  117. package/src/session-ledger.ts +18 -9
  118. package/src/truncation-retry.ts +4 -2
  119. package/src/ttl-fit.ts +39 -15
  120. package/src/types.ts +46 -0
  121. package/src/usage.ts +129 -11
package/dist/pricing.js CHANGED
@@ -30,9 +30,9 @@
30
30
  export const PROVIDER_REVIEWED = Object.freeze({
31
31
  /*
32
32
  * platform.claude.com/docs/en/about-claude/pricing, the models overview and
33
- * each model's own page, read 2026-10-10. Every existing row matched; five
34
- * models were added and one (Claude Haiku 5.5) deliberately was not, see the
35
- * comment above its would-be place in MODELS.
33
+ * each model's own page, read 2026-10-10. Every existing row matched and
34
+ * five models were added. Claude Haiku 5.5 waited for per-request pricing,
35
+ * because it bills by prompt length, and was added with it on 2026-10-11.
36
36
  */
37
37
  anthropic: '2026-10-10',
38
38
  /*
@@ -46,34 +46,56 @@ export const PROVIDER_REVIEWED = Object.freeze({
46
46
  * and priority. The standard one is the catalogue's, identified by the other
47
47
  * three being its multiples (0.5x, 0.5x, 2x) rather than by trusting the
48
48
  * order they appear in.
49
+ *
50
+ * Re-read 2026-10-11 with the prompt-caching guide, the GPT-5 model page and
51
+ * the deprecations page. GPT-5, mini and nano matched to the cent. Their
52
+ * dated snapshots (`gpt-5-2025-08-07` and the mini and nano ones), which is
53
+ * the id a usage log usually carries, shut down on 2026-12-11 and are now
54
+ * declared as aliases. The current generation (gpt-6-astra, gpt-6.1-sol,
55
+ * gpt-6-sol, gpt-6-luna and gpt-5.6 sol, terra and luna) waited for
56
+ * per-request pricing, since each doubles its rates for prompts over 272K
57
+ * tokens, and was added with it the same day, from each model's own page.
58
+ */
59
+ openai: '2026-10-11',
60
+ /*
61
+ * ai.google.dev/gemini-api/docs/pricing, docs/caching and the model pages,
62
+ * read 2026-10-10. Every price matched; what changed is how caching starts
63
+ * and what it costs on the old models. Implicit caching is on by default
64
+ * for every Gemini 2.5 and newer model, so `caching` is 'automatic' across
65
+ * the board; the minimums moved to 4,096 on 3.6 and 3.8 Flash and 2,048 on
66
+ * 2.5 Flash; and the 2.5 models read the cache at 10% of input today, not
67
+ * the 25% this table carried. Gemini 3.8 Flash was added, and Gemini 3.1
68
+ * Pro Preview on 2026-10-11 with per-request pricing, which its prompts
69
+ * over 200k need. 2.5 Pro's own long-context rate is named in its note.
49
70
  */
50
- openai: '2026-08-31',
51
- /* ai.google.dev/gemini-api/docs/pricing, read 2026-08-28. */
52
- google: '2026-08-28',
71
+ google: '2026-10-10',
53
72
  /*
54
- * platform.kimi.ai/docs/pricing/chat (platform.moonshot.ai 301s there),
55
- * read 2026-09-12. The page prices kimi-k3, kimi-k2.6, kimi-k2.7-code and
56
- * its highspeed variant; `kimi-k2`, the one id this table carries, is no
57
- * longer on it. Its price is kept because calls in somebody's log really
58
- * happened at it, and it is not marked retired because that is recorded
59
- * from the provider's own refusal to a request, which needs a key this
60
- * repository does not hold. The newer models are not added: the page states
61
- * no context window for any of them, and a context window this table did
62
- * not read is a number it would be inventing.
73
+ * platform.kimi.ai/docs/pricing/chat, models and pricing/batch, read
74
+ * 2026-10-11 in their Markdown form; the HTML renders its tables in the
75
+ * browser, which is why the review of 2026-09-12 found no context window
76
+ * and added nothing. Kimi K3, K2.7 Code (and its high-speed variant) and
77
+ * K2.6 are priced now. `kimi-k2` was discontinued on 2026-05-25 by the
78
+ * provider's own notice, so it stays priced and is no longer recommended.
63
79
  */
64
- moonshot: '2026-09-12',
65
- /* api-docs.deepseek.com/quick_start/pricing, read 2026-08-28. */
66
- deepseek: '2026-08-28',
80
+ moonshot: '2026-10-11',
67
81
  /*
68
- * docs.x.ai/docs/models, read 2026-09-12. The page prices grok-4.6, 4.5,
69
- * 4.3 and the 4.20 family, each at two rates split at 200k prompt tokens;
70
- * `grok-4`, the one id this table carries, is no longer on it. Kept and not
71
- * marked retired, for the reasons given for kimi-k2 above. The newer models
72
- * are not added: this table has no way to express a price that changes at
73
- * 200k prompt tokens, and pricing a 300k-token call at the under-200k rate
74
- * would understate it by half while looking exact.
82
+ * api-docs.deepseek.com/quick_start/pricing read 2026-10-10, and the
83
+ * 2026-09-10 announcement (api-docs.deepseek.com/news/news260910) read
84
+ * 2026-10-11 for the instant: "New pricing takes effect at 04:00 UTC on
85
+ * Sept 10, 2026." V4 Flash was retired that day and its id now bills at
86
+ * the new deepseek-flash price, so the old price lives in its `history`.
87
+ * V4 Pro matched the page unchanged.
75
88
  */
76
- xai: '2026-09-12',
89
+ deepseek: '2026-10-10',
90
+ /*
91
+ * docs.x.ai/developers/models, each model's page and the 2026-05-15
92
+ * retirement notice, read 2026-10-11 in their Markdown form. Grok 4.7, 4.6,
93
+ * 4.5, 4.3, the 4.20 family and Build 0.1 are priced, each with the rate a
94
+ * request pays once its prompt reaches 200k tokens, which per-request
95
+ * pricing can now express. `grok-4` was retired on 2026-05-15 and its calls
96
+ * have billed at Grok 4.3's prices since, so it carries both periods.
97
+ */
98
+ xai: '2026-10-11',
77
99
  /*
78
100
  * `mistral.ai/pricing`, read 2026-08-28 — by a human, because the page
79
101
  * renders its table in the browser and serves a fetch only an FAQ example
@@ -84,7 +106,18 @@ export const PROVIDER_REVIEWED = Object.freeze({
84
106
  * servable is not the same as checking, and moving the date then would have
85
107
  * said it was.
86
108
  */
87
- mistral: '2026-08-28',
109
+ /*
110
+ * Re-read 2026-10-11 from mistral.ai/cloud/inference/ and the model cards
111
+ * at mistral.ai/news/mistral-3/, /mistral-small-4/ and
112
+ * /vibe-remote-agents-mistral-medium-3-5/, which carry the prices in their
113
+ * markup (`data-prices`) and bind the `-latest` ids. Every price matched.
114
+ * What changed is caching: the inference page offers "Cached input tokens
115
+ * -90% on input token", and every one of the three cards marks its input
116
+ * price as carrying the cache discount. This table said Mistral had no
117
+ * prompt caching at all. docs.mistral.ai, where a minimum or a mechanism
118
+ * would be, is not reachable from here, so neither is claimed.
119
+ */
120
+ mistral: '2026-10-11',
88
121
  });
89
122
  /**
90
123
  * The catalogue's age is its oldest provider's, because a reader asking how
@@ -121,7 +154,13 @@ export const COST_MULTIPLIERS = {
121
154
  */
122
155
  /** Memoised for the same reason as `effectivePricing`: once per record. */
123
156
  const multipliersMemo = new WeakMap();
124
- export function multipliersFor(model) {
157
+ export function multipliersFor(model, on) {
158
+ // A dated question about a repriced model asks the period in force then.
159
+ if (on !== undefined && model.history !== undefined) {
160
+ const period = pricedAt(model, on);
161
+ if (period !== model)
162
+ return multipliersFor(period);
163
+ }
125
164
  const cached = multipliersMemo.get(model);
126
165
  if (cached !== undefined)
127
166
  return cached;
@@ -135,6 +174,45 @@ export function multipliersFor(model) {
135
174
  multipliersMemo.set(model, result);
136
175
  return result;
137
176
  }
177
+ /** Five minutes and an hour: Anthropic's two cache entries, and the default. */
178
+ const ANTHROPIC_TTL = Object.freeze({ writeMs: 5 * 60_000, longerMs: 60 * 60_000 });
179
+ /** How long a model's cache entries live. See `ModelPricing.cacheTtl`. */
180
+ export function cacheTtlOf(model) {
181
+ return model.cacheTtl ?? ANTHROPIC_TTL;
182
+ }
183
+ /**
184
+ * Mistral discounts cached input by 90% and publishes nothing else about it
185
+ * that can be read from here: not how caching starts, not a minimum, not a
186
+ * write price. Its usage reports no write tokens, so the write ratio of 1 is
187
+ * never multiplied by anything; it is there so the default (Anthropic's 1.25)
188
+ * cannot be.
189
+ */
190
+ /** What the GPT-5 family's three rows share, from OpenAI's guide and deprecations page, read 2026-10-11. */
191
+ /** xAI's long-context band: a request whose prompt reaches 200,000 tokens, at the published higher rates. */
192
+ const GROK_LONG = (inputPerMTok, outputPerMTok) => ({
193
+ id: 'from-200k',
194
+ when: { kind: 'input-tokens-at-least', tokens: 200_000 },
195
+ inputPerMTok,
196
+ outputPerMTok,
197
+ });
198
+ /**
199
+ * Said on every row that caches but whose provider publishes no minimum
200
+ * prefix. The bill still prices its cache reads and writes, which are
201
+ * published; what goes quiet is the advice that needs the minimum to promise
202
+ * a saving, and the reader is told why rather than handed a number from
203
+ * nowhere. The catalogue tests hold a null minimum on a caching model to
204
+ * carrying this sentence, so a forgotten minimum cannot pass as a deliberate one.
205
+ */
206
+ const NO_PUBLISHED_CACHE_MINIMUM = 'Caches automatically, but no minimum prefix length is published, so no caching advice is given for it.';
207
+ /** OpenAI's GPT-5.6 and later: one cache entry, living at least 30 minutes. */
208
+ const OPENAI_CACHE_TTL = Object.freeze({ writeMs: 30 * 60_000, longerMs: null });
209
+ const OPENAI_CURRENT_NOTE = 'Priced per request by prompt length: over 272,000 input tokens, 2x input and cache and 1.5x output for'
210
+ + ' the whole request. Cache writes cost 1.25x input and an entry lives at least 30 minutes.';
211
+ const OPENAI_GPT5_NOTE = 'Caching is automatic, with no cache_control to set. OpenAI says the minimum for models before GPT-5.6 varies by'
212
+ + ' request settings, so 1,024 here is the nearest published floor. The 2025-08-07 snapshot shuts down on'
213
+ + ' 2026-12-11; OpenAI names the gpt-5.6 models as replacements.';
214
+ const MISTRAL_CACHE_NOTE = 'Cached input costs 10% of the input price (mistral.ai/cloud/inference, read 2026-10-11). How caching starts and'
215
+ + ' its minimum are not published anywhere Trazum can read, so no caching advice is given for it.';
138
216
  export const MODELS = [
139
217
  {
140
218
  /*
@@ -329,18 +407,44 @@ export const MODELS = [
329
407
  capability: 'mid',
330
408
  tier: 'sonnet',
331
409
  },
332
- /*
333
- * Claude Haiku 5.5 is not here, on purpose, and its calls are an unpriced
334
- * gap that names it. The pricing page (read 2026-10-10) prices it by prompt
335
- * length: $0.10/$0.50 up to 100,000 tokens and $0.50/$2.50 above, where the
336
- * length counts cache reads and writes and each request is priced on its
337
- * own. A tier can say that, but nothing that prices a usage log passes one
338
- * request's prompt length to `effectivePricing`: records are summed per
339
- * workload, model and day before they are priced. Added today, every Haiku
340
- * 5.5 call would fall to the undecided ceiling, five times the price most of
341
- * them paid. It goes in with the change that prices tiered models per
342
- * request, not before.
343
- */
410
+ {
411
+ /*
412
+ * The pricing page, the models overview and the prompt-caching page, read
413
+ * 2026-10-10. Priced by prompt length, per request: $0.10/$0.50 for a
414
+ * prompt up to 100,000 tokens and $0.50/$2.50 over it, where the length
415
+ * counts every input token the request carried, cache reads and writes
416
+ * included, and "each request is priced on its own". Caching is the same
417
+ * ratio in both bands (hits $0.01 and $0.05, writes $0.125/$0.20 and
418
+ * $0.625/$1), and so is batch ($0.05/$0.25, $0.25/$1.25). 1M context,
419
+ * 128K output, a 512-token cache minimum.
420
+ *
421
+ * It waited for the change that prices a log per request. A record that
422
+ * is one call carries its own prompt; a row out of a usage report sums
423
+ * requests, and is billed at the over-100,000 rate unless its whole
424
+ * prompt total is under the line, with the cheaper figure printed beside
425
+ * it. See `PromptSize`.
426
+ */
427
+ id: 'claude-haiku-5-5',
428
+ provider: 'anthropic',
429
+ displayName: 'Claude Haiku 5.5',
430
+ inputPerMTok: 0.1,
431
+ outputPerMTok: 0.5,
432
+ contextWindow: 1_000_000,
433
+ cacheMinTokens: 512,
434
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2, batch: 0.5 },
435
+ tiers: [
436
+ {
437
+ id: 'over-100k',
438
+ when: { kind: 'input-tokens-above', tokens: 100_000 },
439
+ inputPerMTok: 0.5,
440
+ outputPerMTok: 2.5,
441
+ },
442
+ ],
443
+ capability: 'small',
444
+ tier: 'haiku',
445
+ notes: 'Priced per request by prompt length: $0.10/$0.50 up to 100,000 input tokens, cache reads'
446
+ + ' and writes included, and $0.50/$2.50 over it.',
447
+ },
344
448
  {
345
449
  id: 'claude-haiku-4-5',
346
450
  // The dated form is the canonical API id and is what a real usage log
@@ -360,13 +464,199 @@ export const MODELS = [
360
464
  // ------------------------------------------------------------------------
361
465
  // OpenAI
362
466
  //
363
- // Caching is AUTOMATIC above 1,024 tokens — there is no cache_control to set,
364
- // and a cached read costs about half the input price rather than a tenth. The
365
- // reordering advice is identical (a prefix is a prefix), the marker advice is
366
- // not, which is why `caching` and `multipliers` exist at all.
467
+ // Caching is AUTOMATIC, with no cache_control to set, and a cached read costs
468
+ // a tenth of input on these three ($0.125 against $1.25). The reordering
469
+ // advice is identical (a prefix is a prefix), the marker advice is not, which
470
+ // is why `caching` and `multipliers` exist at all. The 1,024 minimum is the
471
+ // one OpenAI publishes for GPT-5.6 and later; for these older models its
472
+ // guide says the minimum "varies by request settings", so it is the nearest
473
+ // published floor, not a promise, and each row's note says so.
474
+ // ------------------------------------------------------------------------
475
+ // GPT-5.6 and later: OpenAI's current generation.
476
+ //
477
+ // The pricing page and each model's page, read 2026-10-10 and 2026-10-11,
478
+ // and the prompt-caching guide. Three things set them apart from GPT-5:
479
+ //
480
+ // - **A long-context band, per request.** "Prompts with more than 272K input
481
+ // tokens are priced at 2x input and cache rates and 1.5x output for the
482
+ // full request." 272,000, the GPT-5 era's own input limit; a tier.
483
+ // - **Cache writes are billed**, at 1.25x the uncached input rate, and the
484
+ // usage object reports them (`cache_write_tokens`). Reads are 10% of input,
485
+ // 5% on GPT-6.1 Sol.
486
+ // - **An entry lives at least 30 minutes** after its last write or read, and
487
+ // there is no longer one to buy: `cacheTtl`.
488
+ //
489
+ // Caching is implicit by default (a breakpoint at the end of the latest
490
+ // eligible message) from a 1,024-token minimum. Batch and Flex are half
491
+ // price; Fast, Ultrafast and regional processing are other service tiers
492
+ // and are not priced here. Every model: 1,050,000 context, 922,000 input at
493
+ // most, 128,000 output.
494
+ // ------------------------------------------------------------------------
495
+ {
496
+ id: 'gpt-6-astra',
497
+ provider: 'openai',
498
+ displayName: 'GPT-6 Astra',
499
+ inputPerMTok: 10,
500
+ outputPerMTok: 50,
501
+ contextWindow: 1_050_000,
502
+ cacheMinTokens: 1024,
503
+ caching: 'automatic',
504
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
505
+ cacheTtl: OPENAI_CACHE_TTL,
506
+ tiers: [
507
+ {
508
+ id: 'over-272k',
509
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
510
+ inputPerMTok: 20,
511
+ outputPerMTok: 75,
512
+ },
513
+ ],
514
+ capability: 'frontier',
515
+ tier: 'frontier',
516
+ notes: OPENAI_CURRENT_NOTE,
517
+ },
518
+ {
519
+ id: 'gpt-6.1-sol',
520
+ provider: 'openai',
521
+ displayName: 'GPT-6.1 Sol',
522
+ inputPerMTok: 2,
523
+ outputPerMTok: 10,
524
+ contextWindow: 1_050_000,
525
+ cacheMinTokens: 1024,
526
+ caching: 'automatic',
527
+ multipliers: { cacheRead: 0.05, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
528
+ cacheTtl: OPENAI_CACHE_TTL,
529
+ tiers: [
530
+ {
531
+ id: 'over-272k',
532
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
533
+ inputPerMTok: 4,
534
+ outputPerMTok: 15,
535
+ },
536
+ ],
537
+ capability: 'large',
538
+ tier: 'opus',
539
+ notes: OPENAI_CURRENT_NOTE,
540
+ },
541
+ {
542
+ id: 'gpt-6-sol',
543
+ provider: 'openai',
544
+ displayName: 'GPT-6 Sol',
545
+ inputPerMTok: 2,
546
+ outputPerMTok: 10,
547
+ contextWindow: 1_050_000,
548
+ cacheMinTokens: 1024,
549
+ caching: 'automatic',
550
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
551
+ cacheTtl: OPENAI_CACHE_TTL,
552
+ tiers: [
553
+ {
554
+ id: 'over-272k',
555
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
556
+ inputPerMTok: 4,
557
+ outputPerMTok: 15,
558
+ },
559
+ ],
560
+ capability: 'large',
561
+ tier: 'opus',
562
+ notes: OPENAI_CURRENT_NOTE,
563
+ },
564
+ {
565
+ id: 'gpt-6-luna',
566
+ provider: 'openai',
567
+ displayName: 'GPT-6 Luna',
568
+ inputPerMTok: 0.1,
569
+ outputPerMTok: 0.5,
570
+ contextWindow: 1_050_000,
571
+ cacheMinTokens: 1024,
572
+ caching: 'automatic',
573
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
574
+ cacheTtl: OPENAI_CACHE_TTL,
575
+ tiers: [
576
+ {
577
+ id: 'over-272k',
578
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
579
+ inputPerMTok: 0.2,
580
+ outputPerMTok: 0.75,
581
+ },
582
+ ],
583
+ capability: 'small',
584
+ tier: 'haiku',
585
+ notes: OPENAI_CURRENT_NOTE,
586
+ },
587
+ {
588
+ id: 'gpt-5.6-sol',
589
+ provider: 'openai',
590
+ displayName: 'GPT-5.6 Sol',
591
+ inputPerMTok: 4,
592
+ outputPerMTok: 20,
593
+ contextWindow: 1_050_000,
594
+ cacheMinTokens: 1024,
595
+ caching: 'automatic',
596
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
597
+ cacheTtl: OPENAI_CACHE_TTL,
598
+ tiers: [
599
+ {
600
+ id: 'over-272k',
601
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
602
+ inputPerMTok: 8,
603
+ outputPerMTok: 30,
604
+ },
605
+ ],
606
+ capability: 'large',
607
+ tier: 'opus',
608
+ notes: OPENAI_CURRENT_NOTE,
609
+ },
610
+ {
611
+ id: 'gpt-5.6-terra',
612
+ provider: 'openai',
613
+ displayName: 'GPT-5.6 Terra',
614
+ inputPerMTok: 2,
615
+ outputPerMTok: 12,
616
+ contextWindow: 1_050_000,
617
+ cacheMinTokens: 1024,
618
+ caching: 'automatic',
619
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
620
+ cacheTtl: OPENAI_CACHE_TTL,
621
+ tiers: [
622
+ {
623
+ id: 'over-272k',
624
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
625
+ inputPerMTok: 4,
626
+ outputPerMTok: 18,
627
+ },
628
+ ],
629
+ capability: 'mid',
630
+ tier: 'sonnet',
631
+ notes: OPENAI_CURRENT_NOTE,
632
+ },
633
+ {
634
+ id: 'gpt-5.6-luna',
635
+ provider: 'openai',
636
+ displayName: 'GPT-5.6 Luna',
637
+ inputPerMTok: 0.2,
638
+ outputPerMTok: 1.2,
639
+ contextWindow: 1_050_000,
640
+ cacheMinTokens: 1024,
641
+ caching: 'automatic',
642
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
643
+ cacheTtl: OPENAI_CACHE_TTL,
644
+ tiers: [
645
+ {
646
+ id: 'over-272k',
647
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
648
+ inputPerMTok: 0.4,
649
+ outputPerMTok: 1.8,
650
+ },
651
+ ],
652
+ capability: 'small',
653
+ tier: 'haiku',
654
+ notes: OPENAI_CURRENT_NOTE,
655
+ },
367
656
  // ------------------------------------------------------------------------
368
657
  {
369
658
  id: 'gpt-5',
659
+ aliases: ['gpt-5-2025-08-07'],
370
660
  provider: 'openai',
371
661
  displayName: 'GPT-5',
372
662
  inputPerMTok: 1.25,
@@ -377,10 +667,11 @@ export const MODELS = [
377
667
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
378
668
  capability: 'frontier',
379
669
  tier: 'frontier',
380
- notes: 'Caching is automatic above 1,024 tokens; there is no cache_control to set.',
670
+ notes: OPENAI_GPT5_NOTE,
381
671
  },
382
672
  {
383
673
  id: 'gpt-5-mini',
674
+ aliases: ['gpt-5-mini-2025-08-07'],
384
675
  provider: 'openai',
385
676
  displayName: 'GPT-5 mini',
386
677
  inputPerMTok: 0.25,
@@ -391,9 +682,11 @@ export const MODELS = [
391
682
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
392
683
  capability: 'mid',
393
684
  tier: 'sonnet',
685
+ notes: OPENAI_GPT5_NOTE,
394
686
  },
395
687
  {
396
688
  id: 'gpt-5-nano',
689
+ aliases: ['gpt-5-nano-2025-08-07'],
397
690
  provider: 'openai',
398
691
  displayName: 'GPT-5 nano',
399
692
  inputPerMTok: 0.05,
@@ -404,6 +697,7 @@ export const MODELS = [
404
697
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
405
698
  capability: 'small',
406
699
  tier: 'haiku',
700
+ notes: OPENAI_GPT5_NOTE,
407
701
  },
408
702
  // ------------------------------------------------------------------------
409
703
  // Google
@@ -416,8 +710,9 @@ export const MODELS = [
416
710
  outputPerMTok: 10,
417
711
  contextWindow: 1_048_576,
418
712
  cacheMinTokens: 2048,
419
- caching: 'explicit',
420
- multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
713
+ caching: 'automatic',
714
+ /* $0.125 cached against $1.25 input for prompts up to 200k, read 2026-10-10. */
715
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
421
716
  /* Refused on 2026-08-28: still in `GET /v1beta/models`, and a 404 to a request. */
422
717
  retired: {
423
718
  on: '2026-08-28',
@@ -425,7 +720,9 @@ export const MODELS = [
425
720
  },
426
721
  capability: 'large',
427
722
  tier: 'opus',
428
- notes: 'Context caching is billed for storage per hour as well as per read.',
723
+ notes: 'Prompts over 200k tokens are billed $2.50 in and $15.00 out; this row prices every prompt at the up-to-200k rate,'
724
+ + ' because no log Trazum prices says how long each prompt was. Implicit caching is automatic; explicit caching is'
725
+ + ' also billed for storage per hour.',
429
726
  },
430
727
  {
431
728
  id: 'gemini-2.5-flash',
@@ -434,9 +731,10 @@ export const MODELS = [
434
731
  inputPerMTok: 0.3,
435
732
  outputPerMTok: 2.5,
436
733
  contextWindow: 1_048_576,
437
- cacheMinTokens: 1024,
438
- caching: 'explicit',
439
- multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
734
+ cacheMinTokens: 2048,
735
+ caching: 'automatic',
736
+ /* $0.03 cached against $0.30 input (text), read 2026-10-10. */
737
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
440
738
  /* Refused on 2026-08-28: still in `GET /v1beta/models`, and a 404 to a request. */
441
739
  retired: {
442
740
  on: '2026-08-28',
@@ -447,9 +745,10 @@ export const MODELS = [
447
745
  },
448
746
  {
449
747
  /*
450
- * Google's own error message names this as the replacement for the two
451
- * retired 2.5 models, and ai.google.dev/gemini-api/docs/pricing carries the
452
- * rates, read 2026-08-28.
748
+ * Google's own error message names this as the replacement for the retired
749
+ * 2.5 Flash (the 2.5 Pro refusal names 3.1 Pro Preview), and
750
+ * ai.google.dev/gemini-api/docs/pricing carries the rates, read 2026-08-28
751
+ * and again on 2026-10-10.
453
752
  *
454
753
  * The introductory price has a published end date and a published successor,
455
754
  * which is exactly what `promo` is for and is the shape the catalogue nearly
@@ -463,8 +762,8 @@ export const MODELS = [
463
762
  outputPerMTok: 7.5,
464
763
  promo: { inputPerMTok: 0.75, outputPerMTok: 3.75, until: '2026-12-31' },
465
764
  contextWindow: 1_048_576,
466
- cacheMinTokens: 1024,
467
- caching: 'explicit',
765
+ cacheMinTokens: 4096,
766
+ caching: 'automatic',
468
767
  /*
469
768
  * $0.075 against $0.75 inside the introductory window, and $0.15 against
470
769
  * $1.50 after it: Google scales the cache price with the headline one, so
@@ -474,12 +773,143 @@ export const MODELS = [
474
773
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
475
774
  capability: 'mid',
476
775
  tier: 'sonnet',
477
- notes: 'Context caching is billed for storage per hour as well as per read:'
776
+ notes: 'Implicit caching is automatic above 4,096 tokens. Explicit caching is also billed for storage:'
478
777
  + ' $0.50 per 1M tokens per hour through 2026-12-31, $1.00 after.',
479
778
  },
779
+ {
780
+ /*
781
+ * Gemini 3.8 Flash, from ai.google.dev/gemini-api/docs/pricing, its model
782
+ * page and docs/caching, read 2026-10-10. The same rates and the same
783
+ * introductory window as 3.6 Flash, published as one table each: $0.75
784
+ * and $3.75 through 2026-12-31, $1.50 and $7.50 from 2027-01-01, and the
785
+ * cache price scales with them ($0.075, then $0.15).
786
+ */
787
+ id: 'gemini-3.8-flash',
788
+ provider: 'google',
789
+ displayName: 'Gemini 3.8 Flash',
790
+ inputPerMTok: 1.5,
791
+ outputPerMTok: 7.5,
792
+ promo: { inputPerMTok: 0.75, outputPerMTok: 3.75, until: '2026-12-31' },
793
+ contextWindow: 1_048_576,
794
+ cacheMinTokens: 4096,
795
+ caching: 'automatic',
796
+ /* $0.075 / $0.75 = 0.1 (and $0.15 / $1.50); batch $0.375 / $0.75 = 0.5 (and $1.875 / $3.75). */
797
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
798
+ capability: 'mid',
799
+ tier: 'sonnet',
800
+ notes: 'Implicit caching is automatic above 4,096 tokens. Explicit caching is also billed for storage:'
801
+ + ' $0.50 per 1M tokens per hour through 2026-12-31, $1.00 after.',
802
+ },
803
+ {
804
+ /*
805
+ * The pricing page, the model page and the caching page, read 2026-10-10
806
+ * and 2026-10-11. The model Google's own refusal of 2.5 Pro names as its
807
+ * replacement, and the second model here priced by prompt length: $2/$12
808
+ * for a prompt up to 200,000 tokens and $4/$18 over it, the cache at
809
+ * $0.20 and $0.40 (10% in both bands), batch $1/$6 and $2/$9 (half in
810
+ * both). 1,048,576 tokens in, 65,536 out, implicit caching from 4,096.
811
+ * The `-customtools` endpoint is "same as Gemini 3.1 Pro Preview pricing".
812
+ *
813
+ * A preview, and priced because a log carries it: Google's 2.5 Pro
814
+ * refusal sends every caller here.
815
+ */
816
+ id: 'gemini-3.1-pro-preview',
817
+ aliases: ['gemini-3.1-pro-preview-customtools'],
818
+ provider: 'google',
819
+ displayName: 'Gemini 3.1 Pro Preview',
820
+ inputPerMTok: 2,
821
+ outputPerMTok: 12,
822
+ contextWindow: 1_048_576,
823
+ cacheMinTokens: 4096,
824
+ caching: 'automatic',
825
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
826
+ tiers: [
827
+ {
828
+ id: 'over-200k',
829
+ when: { kind: 'input-tokens-above', tokens: 200_000 },
830
+ inputPerMTok: 4,
831
+ outputPerMTok: 18,
832
+ },
833
+ ],
834
+ capability: 'large',
835
+ tier: 'opus',
836
+ notes: 'Priced per request by prompt length: $2/$12 up to 200,000 input tokens and $4/$18 over it.'
837
+ + ' Implicit caching is automatic above 4,096 tokens; explicit caching is also billed for storage,'
838
+ + ' $4.50 per 1M tokens per hour.',
839
+ },
480
840
  // ------------------------------------------------------------------------
481
841
  // Moonshot
482
842
  // ------------------------------------------------------------------------
843
+ /*
844
+ * platform.kimi.ai/docs/pricing/chat, models and pricing/batch, read in
845
+ * their Markdown form on 2026-10-11 (the HTML renders its tables in the
846
+ * browser, which is why the September review saw no context windows). K3
847
+ * bills cache writes as their own line, by TTL, like Anthropic: $3.00 for a
848
+ * 5-minute entry and $6.00 for an hour against a $3.00 input, and reads at
849
+ * $0.30. The K2 series bills no write, and reads at its own hit price. Batch
850
+ * is 60% of standard on K2.7 Code and K2.6 and not offered on K3 or the
851
+ * high-speed K2.7. No cache minimum is published for any of them.
852
+ */
853
+ {
854
+ id: 'kimi-k3',
855
+ provider: 'moonshot',
856
+ displayName: 'Kimi K3',
857
+ inputPerMTok: 3,
858
+ outputPerMTok: 15,
859
+ contextWindow: 1_048_576,
860
+ cacheMinTokens: null,
861
+ caching: 'automatic',
862
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 2, batch: null },
863
+ capability: 'frontier',
864
+ tier: 'frontier',
865
+ notes: 'Cache writes are billed as their own line: 1x input for a 5-minute entry, 2x for an hour.'
866
+ + ` No batch API for this model. ${NO_PUBLISHED_CACHE_MINIMUM}`,
867
+ },
868
+ {
869
+ id: 'kimi-k2.7-code',
870
+ provider: 'moonshot',
871
+ displayName: 'Kimi K2.7 Code',
872
+ inputPerMTok: 0.95,
873
+ outputPerMTok: 4,
874
+ contextWindow: 262_144,
875
+ cacheMinTokens: null,
876
+ caching: 'automatic',
877
+ /* $0.19 on a hit against $0.95; batch $0.57 / $2.40, 60%. */
878
+ multipliers: { cacheRead: 0.2, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.6 },
879
+ capability: 'mid',
880
+ tier: 'sonnet',
881
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
882
+ },
883
+ {
884
+ id: 'kimi-k2.7-code-highspeed',
885
+ provider: 'moonshot',
886
+ displayName: 'Kimi K2.7 Code (high speed)',
887
+ inputPerMTok: 1.9,
888
+ outputPerMTok: 8,
889
+ contextWindow: 262_144,
890
+ cacheMinTokens: null,
891
+ caching: 'automatic',
892
+ /* $0.38 on a hit against $1.90; no batch API for this variant. */
893
+ multipliers: { cacheRead: 0.2, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
894
+ capability: 'mid',
895
+ tier: 'sonnet',
896
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
897
+ },
898
+ {
899
+ id: 'kimi-k2.6',
900
+ provider: 'moonshot',
901
+ displayName: 'Kimi K2.6',
902
+ inputPerMTok: 0.95,
903
+ outputPerMTok: 4,
904
+ contextWindow: 262_144,
905
+ cacheMinTokens: null,
906
+ caching: 'automatic',
907
+ /* $0.16 on a hit against $0.95, written as the ratio so the product is the page's figure. */
908
+ multipliers: { cacheRead: 0.16 / 0.95, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.6 },
909
+ capability: 'mid',
910
+ tier: 'sonnet',
911
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
912
+ },
483
913
  {
484
914
  id: 'kimi-k2',
485
915
  provider: 'moonshot',
@@ -487,13 +917,22 @@ export const MODELS = [
487
917
  inputPerMTok: 0.6,
488
918
  outputPerMTok: 2.5,
489
919
  contextWindow: 256_000,
490
- cacheMinTokens: 1024,
920
+ cacheMinTokens: null,
491
921
  caching: 'automatic',
492
922
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
923
+ /*
924
+ * Discontinued: "The kimi-k2 series models were officially discontinued
925
+ * on May 25, 2026" (the provider's model list, read 2026-10-11). Kept at
926
+ * its last published price, because calls in somebody's log really
927
+ * happened at it, and never offered as somewhere to move. Not `retired`,
928
+ * which records the provider refusing a request, and no key was here to
929
+ * send one.
930
+ */
931
+ recommendable: false,
493
932
  capability: 'mid',
494
933
  tier: 'sonnet',
495
- notes: 'No batch API: the batch advisory stays quiet rather than offering a discount you cannot buy.'
496
- + ' Not on the provider\'s pricing page as of 2026-09-12, which lists kimi-k3 and kimi-k2.6 instead; the price is the last one published for it.',
934
+ notes: 'Discontinued by Moonshot on 2026-05-25; priced at the last rate it published. No batch API.'
935
+ + ` ${NO_PUBLISHED_CACHE_MINIMUM}`,
497
936
  },
498
937
  // ------------------------------------------------------------------------
499
938
  // DeepSeek
@@ -518,34 +957,95 @@ export const MODELS = [
518
957
  },
519
958
  {
520
959
  /*
521
- * DeepSeek V4 Flash, from api-docs.deepseek.com/quick_start/pricing, read
522
- * 2026-08-28. It replaced `deepseek-v3`, which the API now refuses.
960
+ * DeepSeek V4 Flash, retired on 2026-09-10. The pricing page (read
961
+ * 2026-10-10) says the id "is still accepted ... served by the
962
+ * DeepSeek-V4.1-Flash model and billed at the Flash price", so from
963
+ * 04:00 UTC that day it carries deepseek-flash's rates. Before that
964
+ * instant it billed at its own, read off the same page on 2026-08-28:
965
+ * $0.22/$0.66 off-peak, double at peak, $0.007 a cache hit.
523
966
  *
524
967
  * The base rate is off-peak because off-peak is the common case: peak is
525
968
  * 01:00-04:00 and 06:00-10:00 UTC on weekdays, which is 35 hours of 168.
526
- * The peak tier is exactly double, published as such.
969
+ * The peak tier is exactly double, published as such. Clock pricing began
970
+ * at 16:00 UTC on 2026-08-16; the flat price before it is on no page this
971
+ * table can read, so calls before then are priced as the first period.
972
+ *
973
+ * Not recommendable: a legacy id DeepSeek routes "temporarily". Without
974
+ * this, a tie on price would offer it ahead of deepseek-flash.
527
975
  */
528
976
  id: 'deepseek-v4-flash',
529
977
  provider: 'deepseek',
530
978
  displayName: 'DeepSeek V4 Flash',
531
- inputPerMTok: 0.22,
532
- outputPerMTok: 0.66,
979
+ recommendable: false,
980
+ inputPerMTok: 0.15,
981
+ outputPerMTok: 0.6,
533
982
  tiers: [
534
983
  {
535
984
  id: 'peak',
536
985
  when: { kind: 'utc-hours', hours: [1, 2, 3, 6, 7, 8, 9], weekdaysOnly: true },
537
- inputPerMTok: 0.44,
538
- outputPerMTok: 1.32,
986
+ inputPerMTok: 0.3,
987
+ outputPerMTok: 1.2,
988
+ },
989
+ ],
990
+ history: [
991
+ {
992
+ until: '2026-09-10T04:00:00Z',
993
+ inputPerMTok: 0.22,
994
+ outputPerMTok: 0.66,
995
+ tiers: [
996
+ {
997
+ id: 'peak',
998
+ when: { kind: 'utc-hours', hours: [1, 2, 3, 6, 7, 8, 9], weekdaysOnly: true },
999
+ inputPerMTok: 0.44,
1000
+ outputPerMTok: 1.32,
1001
+ },
1002
+ ],
1003
+ /* $0.007 cache hit against $0.22 input off-peak. */
1004
+ multipliers: { cacheRead: 0.0318, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
539
1005
  },
540
1006
  ],
541
1007
  contextWindow: 1_000_000,
542
1008
  cacheMinTokens: 1024,
543
1009
  caching: 'automatic',
544
- /* $0.007 cache hit against $0.22 input off-peak. */
545
- multipliers: { cacheRead: 0.0318, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1010
+ /* $0.003 cache hit against $0.15 input off-peak, $0.006 against $0.30 at peak. */
1011
+ multipliers: { cacheRead: 0.02, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
546
1012
  capability: 'mid',
547
1013
  tier: 'sonnet',
548
- notes: 'Maximum output 384K tokens.',
1014
+ notes: 'Retired 2026-09-10: the id is still accepted, served by V4.1 Flash and billed at the deepseek-flash price.'
1015
+ + ' Maximum output 384K tokens. Peak excludes Chinese public holidays, which this table cannot express.',
1016
+ },
1017
+ {
1018
+ /*
1019
+ * DeepSeek V4.1 Flash, from api-docs.deepseek.com/quick_start/pricing,
1020
+ * read 2026-10-10. Released 2026-09-10; its price took effect at 04:00
1021
+ * UTC that day. Off-peak is the base and peak is exactly double, as
1022
+ * published; the cache hit is 2% of input at both ($0.003 and $0.006).
1023
+ *
1024
+ * The cache minimum is carried from deepseek-v4-flash: DeepSeek's pages
1025
+ * publish none for this generation, and 1,024 is the figure the earlier
1026
+ * row was reviewed with.
1027
+ */
1028
+ id: 'deepseek-flash',
1029
+ provider: 'deepseek',
1030
+ displayName: 'DeepSeek V4.1 Flash',
1031
+ inputPerMTok: 0.15,
1032
+ outputPerMTok: 0.6,
1033
+ tiers: [
1034
+ {
1035
+ id: 'peak',
1036
+ when: { kind: 'utc-hours', hours: [1, 2, 3, 6, 7, 8, 9], weekdaysOnly: true },
1037
+ inputPerMTok: 0.3,
1038
+ outputPerMTok: 1.2,
1039
+ },
1040
+ ],
1041
+ contextWindow: 1_000_000,
1042
+ cacheMinTokens: 1024,
1043
+ caching: 'automatic',
1044
+ multipliers: { cacheRead: 0.02, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1045
+ capability: 'mid',
1046
+ tier: 'sonnet',
1047
+ notes: 'Maximum output 384K tokens. Peak excludes Chinese public holidays, which this table cannot express:'
1048
+ + ' on a weekday holiday it charges peak where DeepSeek charges off-peak.',
549
1049
  },
550
1050
  {
551
1051
  /* Same page, same clock. Pro is three times Flash at both ends. */
@@ -574,19 +1074,174 @@ export const MODELS = [
574
1074
  // ------------------------------------------------------------------------
575
1075
  // xAI
576
1076
  // ------------------------------------------------------------------------
1077
+ /*
1078
+ * docs.x.ai/developers/models and each model's page, read in their
1079
+ * Markdown form on 2026-10-11. Every text model has two rates: "requests
1080
+ * whose prompt reaches 200k tokens are billed at the higher rate for all
1081
+ * tokens in the request", which is a size tier at 200,000 inclusive and is
1082
+ * priced per request. Cached input scales with the band; writes are not
1083
+ * billed; batch is a 20% discount where the model's page offers it at all.
1084
+ * No cache minimum is published. Aliases are the stable family names the
1085
+ * pages list, never the `-latest` names, which move.
1086
+ */
1087
+ {
1088
+ id: 'grok-4.7',
1089
+ provider: 'xai',
1090
+ displayName: 'Grok 4.7',
1091
+ inputPerMTok: 2,
1092
+ outputPerMTok: 6,
1093
+ contextWindow: 500_000,
1094
+ cacheMinTokens: null,
1095
+ caching: 'automatic',
1096
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1097
+ tiers: [GROK_LONG(4, 12)],
1098
+ capability: 'frontier',
1099
+ tier: 'frontier',
1100
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1101
+ },
1102
+ {
1103
+ id: 'grok-4.6',
1104
+ provider: 'xai',
1105
+ displayName: 'Grok 4.6',
1106
+ inputPerMTok: 2,
1107
+ outputPerMTok: 6,
1108
+ contextWindow: 500_000,
1109
+ cacheMinTokens: null,
1110
+ caching: 'automatic',
1111
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1112
+ tiers: [GROK_LONG(4, 12)],
1113
+ capability: 'large',
1114
+ tier: 'opus',
1115
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1116
+ },
1117
+ {
1118
+ id: 'grok-4.5',
1119
+ provider: 'xai',
1120
+ displayName: 'Grok 4.5',
1121
+ inputPerMTok: 2,
1122
+ outputPerMTok: 6,
1123
+ contextWindow: 500_000,
1124
+ cacheMinTokens: null,
1125
+ caching: 'automatic',
1126
+ /* $0.30 cached against $2.00. */
1127
+ multipliers: { cacheRead: 0.15, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1128
+ tiers: [GROK_LONG(4, 12)],
1129
+ capability: 'large',
1130
+ tier: 'opus',
1131
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1132
+ },
1133
+ {
1134
+ id: 'grok-4.3',
1135
+ provider: 'xai',
1136
+ displayName: 'Grok 4.3',
1137
+ inputPerMTok: 1.25,
1138
+ outputPerMTok: 2.5,
1139
+ contextWindow: 1_000_000,
1140
+ cacheMinTokens: null,
1141
+ caching: 'automatic',
1142
+ /* $0.20 cached against $1.25; batch 20% off. */
1143
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.8 },
1144
+ tiers: [GROK_LONG(2.5, 5)],
1145
+ capability: 'mid',
1146
+ tier: 'sonnet',
1147
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1148
+ },
1149
+ {
1150
+ id: 'grok-4.20-0309-reasoning',
1151
+ aliases: ['grok-4.20', 'grok-4.20-reasoning', 'grok-4.20-0309'],
1152
+ provider: 'xai',
1153
+ displayName: 'Grok 4.20 (reasoning)',
1154
+ inputPerMTok: 1.25,
1155
+ outputPerMTok: 2.5,
1156
+ contextWindow: 1_000_000,
1157
+ cacheMinTokens: null,
1158
+ caching: 'automatic',
1159
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.8 },
1160
+ tiers: [GROK_LONG(2.5, 5)],
1161
+ capability: 'mid',
1162
+ tier: 'sonnet',
1163
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1164
+ },
577
1165
  {
1166
+ id: 'grok-4.20-0309-non-reasoning',
1167
+ aliases: ['grok-4.20-non-reasoning'],
1168
+ provider: 'xai',
1169
+ displayName: 'Grok 4.20 (non-reasoning)',
1170
+ inputPerMTok: 1.25,
1171
+ outputPerMTok: 2.5,
1172
+ contextWindow: 1_000_000,
1173
+ cacheMinTokens: null,
1174
+ caching: 'automatic',
1175
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.8 },
1176
+ tiers: [GROK_LONG(2.5, 5)],
1177
+ capability: 'mid',
1178
+ tier: 'sonnet',
1179
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1180
+ },
1181
+ {
1182
+ id: 'grok-4.20-multi-agent-0309',
1183
+ aliases: ['grok-4.20-multi-agent'],
1184
+ provider: 'xai',
1185
+ displayName: 'Grok 4.20 Multi-Agent',
1186
+ inputPerMTok: 1.25,
1187
+ outputPerMTok: 2.5,
1188
+ contextWindow: 1_000_000,
1189
+ cacheMinTokens: null,
1190
+ caching: 'automatic',
1191
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.8 },
1192
+ tiers: [GROK_LONG(2.5, 5)],
1193
+ capability: 'mid',
1194
+ tier: 'sonnet',
1195
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1196
+ },
1197
+ {
1198
+ id: 'grok-build-0.1',
1199
+ provider: 'xai',
1200
+ displayName: 'Grok Build 0.1',
1201
+ inputPerMTok: 1,
1202
+ outputPerMTok: 2,
1203
+ contextWindow: 256_000,
1204
+ cacheMinTokens: null,
1205
+ caching: 'automatic',
1206
+ multipliers: { cacheRead: 0.2, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1207
+ tiers: [GROK_LONG(2, 4)],
1208
+ capability: 'mid',
1209
+ tier: 'sonnet',
1210
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1211
+ },
1212
+ {
1213
+ /*
1214
+ * Retired on 2026-05-15 at 12:00 PT (19:00 UTC), and billed since at
1215
+ * Grok 4.3's prices: "requests to the retired model slugs above will
1216
+ * automatically redirect to grok-4.3 ... you will be billed at grok-4.3
1217
+ * pricing" (xAI's retirement notice, read 2026-10-11). So the id has two
1218
+ * periods, and a log that spans the date is billed both ways, at the
1219
+ * instant xAI gave. `grok-4-0709` is the dated id the notice names.
1220
+ */
578
1221
  id: 'grok-4',
1222
+ aliases: ['grok-4-0709'],
579
1223
  provider: 'xai',
580
1224
  displayName: 'Grok 4',
581
- inputPerMTok: 3,
582
- outputPerMTok: 15,
1225
+ inputPerMTok: 1.25,
1226
+ outputPerMTok: 2.5,
583
1227
  contextWindow: 256_000,
584
- cacheMinTokens: 1024,
1228
+ cacheMinTokens: null,
585
1229
  caching: 'automatic',
586
- multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1230
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1231
+ tiers: [GROK_LONG(2.5, 5)],
1232
+ history: [
1233
+ {
1234
+ until: '2026-05-15T19:00:00Z',
1235
+ inputPerMTok: 3,
1236
+ outputPerMTok: 15,
1237
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1238
+ },
1239
+ ],
1240
+ recommendable: false,
587
1241
  capability: 'large',
588
1242
  tier: 'opus',
589
- notes: 'Not on the provider\'s model page as of 2026-09-12, which lists grok-4.6 and later instead; the price is the last one published for it.',
1243
+ notes: 'Retired by xAI on 2026-05-15 and redirected to Grok 4.3, whose price it bills at from then;'
1244
+ + ` $3/$15 before. ${NO_PUBLISHED_CACHE_MINIMUM}`,
590
1245
  },
591
1246
  // ------------------------------------------------------------------------
592
1247
  // Mistral
@@ -634,12 +1289,12 @@ export const MODELS = [
634
1289
  inputPerMTok: 0.5,
635
1290
  outputPerMTok: 1.5,
636
1291
  contextWindow: 262_144,
637
- cacheMinTokens: 0,
638
- caching: 'none',
639
- multipliers: { batch: 0.5 },
1292
+ cacheMinTokens: null,
1293
+ caching: 'unknown',
1294
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
640
1295
  capability: 'large',
641
1296
  tier: 'opus',
642
- notes: 'No prompt caching: reordering still helps readability but saves nothing here.',
1297
+ notes: MISTRAL_CACHE_NOTE,
643
1298
  },
644
1299
  {
645
1300
  /*
@@ -662,11 +1317,12 @@ export const MODELS = [
662
1317
  inputPerMTok: 1.5,
663
1318
  outputPerMTok: 7.5,
664
1319
  contextWindow: 262_144,
665
- cacheMinTokens: 0,
666
- caching: 'none',
667
- multipliers: { batch: 0.5 },
1320
+ cacheMinTokens: null,
1321
+ caching: 'unknown',
1322
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
668
1323
  capability: 'mid',
669
1324
  tier: 'sonnet',
1325
+ notes: MISTRAL_CACHE_NOTE,
670
1326
  },
671
1327
  {
672
1328
  /* Same page, same day. `mistral-small-2603` is what the API reports. */
@@ -677,11 +1333,12 @@ export const MODELS = [
677
1333
  inputPerMTok: 0.15,
678
1334
  outputPerMTok: 0.6,
679
1335
  contextWindow: 262_144,
680
- cacheMinTokens: 0,
681
- caching: 'none',
682
- multipliers: { batch: 0.5 },
1336
+ cacheMinTokens: null,
1337
+ caching: 'unknown',
1338
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
683
1339
  capability: 'small',
684
1340
  tier: 'haiku',
1341
+ notes: MISTRAL_CACHE_NOTE,
685
1342
  },
686
1343
  ];
687
1344
  export const DEFAULT_MODEL = 'claude-opus-5';
@@ -772,6 +1429,41 @@ export function listModels() {
772
1429
  return [...MODELS];
773
1430
  }
774
1431
  const pricingMemo = new WeakMap();
1432
+ /**
1433
+ * The row as it stood at `on`: itself when the current price applies, or a
1434
+ * frozen copy carrying the period's rates, tiers and ratios. Built once per
1435
+ * period, so a log of a million calls does not build a million rows.
1436
+ */
1437
+ const periodMemo = new WeakMap();
1438
+ function pricedAt(model, on) {
1439
+ const history = model.history;
1440
+ if (history === undefined || history.length === 0)
1441
+ return model;
1442
+ let periods = periodMemo.get(model);
1443
+ if (periods === undefined) {
1444
+ periods = history.map((period) => {
1445
+ const { history: _history, promo: _promo, tiers: _tiers, multipliers: _multipliers, ...rest } = model;
1446
+ const row = Object.freeze({
1447
+ ...rest,
1448
+ inputPerMTok: period.inputPerMTok,
1449
+ outputPerMTok: period.outputPerMTok,
1450
+ ...(period.tiers !== undefined ? { tiers: period.tiers } : {}),
1451
+ ...(period.multipliers !== undefined
1452
+ ? { multipliers: period.multipliers }
1453
+ : model.multipliers !== undefined
1454
+ ? { multipliers: model.multipliers }
1455
+ : {}),
1456
+ });
1457
+ return { untilMs: new Date(period.until).getTime(), row };
1458
+ });
1459
+ periodMemo.set(model, periods);
1460
+ }
1461
+ const at = on.getTime();
1462
+ for (const period of periods)
1463
+ if (at < period.untilMs)
1464
+ return period.row;
1465
+ return model;
1466
+ }
775
1467
  /** The two unconditional answers for one model, parsed once. */
776
1468
  function cache(model) {
777
1469
  const entry = {
@@ -801,16 +1493,20 @@ function cache(model) {
801
1493
  * because "this does not apply" and "nobody said" lead to different reports and
802
1494
  * collapsing them is the oldest mistake in this repository.
803
1495
  */
804
- function holds(when, on, inputTokens) {
1496
+ function holds(when, on, size) {
805
1497
  if (when.kind === 'utc-hours') {
806
1498
  const day = on.getUTCDay();
807
1499
  if (when.weekdaysOnly && (day === 0 || day === 6))
808
1500
  return false;
809
1501
  return when.hours.includes(on.getUTCHours());
810
1502
  }
811
- if (inputTokens === undefined)
812
- return null;
813
- return inputTokens > when.tokens;
1503
+ const over = (tokens) => when.kind === 'input-tokens-at-least' ? tokens >= when.tokens : tokens > when.tokens;
1504
+ if (size.inputTokens !== undefined)
1505
+ return over(size.inputTokens);
1506
+ // A whole row under the line puts every request in it under the line.
1507
+ if (size.inputTokensAtMost !== undefined && !over(size.inputTokensAtMost))
1508
+ return false;
1509
+ return null;
814
1510
  }
815
1511
  /**
816
1512
  * What one call is billed at, and what decided it.
@@ -829,6 +1525,12 @@ function holds(when, on, inputTokens) {
829
1525
  * `pricing.test.js` fails a tiered model whose report drops it.
830
1526
  */
831
1527
  export function effectivePricing(model, on = new Date(), context = {}) {
1528
+ /* A call before a published price change is priced as it was billed then. */
1529
+ if (model.history !== undefined) {
1530
+ const period = pricedAt(model, on);
1531
+ if (period !== model)
1532
+ return effectivePricing(period, on, context);
1533
+ }
832
1534
  const memo = pricingMemo.get(model) ?? cache(model);
833
1535
  /**
834
1536
  * Tiers first, and a promotion never runs on top of one.
@@ -841,7 +1543,7 @@ export function effectivePricing(model, on = new Date(), context = {}) {
841
1543
  if (model.tiers !== undefined && model.tiers.length > 0) {
842
1544
  const undecided = [];
843
1545
  for (const tier of model.tiers) {
844
- const verdict = holds(tier.when, on, context.inputTokens);
1546
+ const verdict = holds(tier.when, on, context);
845
1547
  if (verdict === true) {
846
1548
  return {
847
1549
  inputPerMTok: tier.inputPerMTok,
@@ -861,6 +1563,11 @@ export function effectivePricing(model, on = new Date(), context = {}) {
861
1563
  }
862
1564
  /* The dearest candidate, so the figure is a ceiling rather than a wish. */
863
1565
  const dearest = undecided.reduce((worst, tier) => tier.inputPerMTok > worst.inputPerMTok ? tier : worst);
1566
+ /*
1567
+ * And the cheapest, which is the base: every undecided condition might not
1568
+ * hold, and a tier is never below the base (`pricing-tiers.test.js`).
1569
+ */
1570
+ const floor = { inputPerMTok: model.inputPerMTok, outputPerMTok: model.outputPerMTok };
864
1571
  const ceiling = dearest.inputPerMTok > model.inputPerMTok
865
1572
  ? {
866
1573
  inputPerMTok: dearest.inputPerMTok,
@@ -879,7 +1586,10 @@ export function effectivePricing(model, on = new Date(), context = {}) {
879
1586
  tier: {
880
1587
  applied: ceiling.applied,
881
1588
  decided: false,
882
- because: `${describe(dearest.when)}, and the token count was not given`,
1589
+ because: context.inputTokensAtMost !== undefined
1590
+ ? `${describe(dearest.when)}, and this row sums requests without giving any one prompt's length`
1591
+ : `${describe(dearest.when)}, and the token count was not given`,
1592
+ floor,
883
1593
  },
884
1594
  };
885
1595
  }
@@ -892,6 +1602,9 @@ function describe(when) {
892
1602
  if (when.kind === 'input-tokens-above') {
893
1603
  return `prompts over ${when.tokens.toLocaleString('en-US')} input tokens`;
894
1604
  }
1605
+ if (when.kind === 'input-tokens-at-least') {
1606
+ return `prompts of ${when.tokens.toLocaleString('en-US')} input tokens or more`;
1607
+ }
895
1608
  const windows = [];
896
1609
  let start = null;
897
1610
  const sorted = [...when.hours].sort((a, b) => a - b);