@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/src/pricing.ts CHANGED
@@ -32,9 +32,9 @@ import type { ModelPricing, PricingTier, TierCondition } from './types.js';
32
32
  export const PROVIDER_REVIEWED: Readonly<Record<string, string>> = Object.freeze({
33
33
  /*
34
34
  * platform.claude.com/docs/en/about-claude/pricing, the models overview and
35
- * each model's own page, read 2026-10-10. Every existing row matched; five
36
- * models were added and one (Claude Haiku 5.5) deliberately was not, see the
37
- * comment above its would-be place in MODELS.
35
+ * each model's own page, read 2026-10-10. Every existing row matched and
36
+ * five models were added. Claude Haiku 5.5 waited for per-request pricing,
37
+ * because it bills by prompt length, and was added with it on 2026-10-11.
38
38
  */
39
39
  anthropic: '2026-10-10',
40
40
  /*
@@ -48,34 +48,56 @@ export const PROVIDER_REVIEWED: Readonly<Record<string, string>> = Object.freeze
48
48
  * and priority. The standard one is the catalogue's, identified by the other
49
49
  * three being its multiples (0.5x, 0.5x, 2x) rather than by trusting the
50
50
  * order they appear in.
51
+ *
52
+ * Re-read 2026-10-11 with the prompt-caching guide, the GPT-5 model page and
53
+ * the deprecations page. GPT-5, mini and nano matched to the cent. Their
54
+ * dated snapshots (`gpt-5-2025-08-07` and the mini and nano ones), which is
55
+ * the id a usage log usually carries, shut down on 2026-12-11 and are now
56
+ * declared as aliases. The current generation (gpt-6-astra, gpt-6.1-sol,
57
+ * gpt-6-sol, gpt-6-luna and gpt-5.6 sol, terra and luna) waited for
58
+ * per-request pricing, since each doubles its rates for prompts over 272K
59
+ * tokens, and was added with it the same day, from each model's own page.
60
+ */
61
+ openai: '2026-10-11',
62
+ /*
63
+ * ai.google.dev/gemini-api/docs/pricing, docs/caching and the model pages,
64
+ * read 2026-10-10. Every price matched; what changed is how caching starts
65
+ * and what it costs on the old models. Implicit caching is on by default
66
+ * for every Gemini 2.5 and newer model, so `caching` is 'automatic' across
67
+ * the board; the minimums moved to 4,096 on 3.6 and 3.8 Flash and 2,048 on
68
+ * 2.5 Flash; and the 2.5 models read the cache at 10% of input today, not
69
+ * the 25% this table carried. Gemini 3.8 Flash was added, and Gemini 3.1
70
+ * Pro Preview on 2026-10-11 with per-request pricing, which its prompts
71
+ * over 200k need. 2.5 Pro's own long-context rate is named in its note.
51
72
  */
52
- openai: '2026-08-31',
53
- /* ai.google.dev/gemini-api/docs/pricing, read 2026-08-28. */
54
- google: '2026-08-28',
73
+ google: '2026-10-10',
55
74
  /*
56
- * platform.kimi.ai/docs/pricing/chat (platform.moonshot.ai 301s there),
57
- * read 2026-09-12. The page prices kimi-k3, kimi-k2.6, kimi-k2.7-code and
58
- * its highspeed variant; `kimi-k2`, the one id this table carries, is no
59
- * longer on it. Its price is kept because calls in somebody's log really
60
- * happened at it, and it is not marked retired because that is recorded
61
- * from the provider's own refusal to a request, which needs a key this
62
- * repository does not hold. The newer models are not added: the page states
63
- * no context window for any of them, and a context window this table did
64
- * not read is a number it would be inventing.
75
+ * platform.kimi.ai/docs/pricing/chat, models and pricing/batch, read
76
+ * 2026-10-11 in their Markdown form; the HTML renders its tables in the
77
+ * browser, which is why the review of 2026-09-12 found no context window
78
+ * and added nothing. Kimi K3, K2.7 Code (and its high-speed variant) and
79
+ * K2.6 are priced now. `kimi-k2` was discontinued on 2026-05-25 by the
80
+ * provider's own notice, so it stays priced and is no longer recommended.
65
81
  */
66
- moonshot: '2026-09-12',
67
- /* api-docs.deepseek.com/quick_start/pricing, read 2026-08-28. */
68
- deepseek: '2026-08-28',
82
+ moonshot: '2026-10-11',
69
83
  /*
70
- * docs.x.ai/docs/models, read 2026-09-12. The page prices grok-4.6, 4.5,
71
- * 4.3 and the 4.20 family, each at two rates split at 200k prompt tokens;
72
- * `grok-4`, the one id this table carries, is no longer on it. Kept and not
73
- * marked retired, for the reasons given for kimi-k2 above. The newer models
74
- * are not added: this table has no way to express a price that changes at
75
- * 200k prompt tokens, and pricing a 300k-token call at the under-200k rate
76
- * would understate it by half while looking exact.
84
+ * api-docs.deepseek.com/quick_start/pricing read 2026-10-10, and the
85
+ * 2026-09-10 announcement (api-docs.deepseek.com/news/news260910) read
86
+ * 2026-10-11 for the instant: "New pricing takes effect at 04:00 UTC on
87
+ * Sept 10, 2026." V4 Flash was retired that day and its id now bills at
88
+ * the new deepseek-flash price, so the old price lives in its `history`.
89
+ * V4 Pro matched the page unchanged.
77
90
  */
78
- xai: '2026-09-12',
91
+ deepseek: '2026-10-10',
92
+ /*
93
+ * docs.x.ai/developers/models, each model's page and the 2026-05-15
94
+ * retirement notice, read 2026-10-11 in their Markdown form. Grok 4.7, 4.6,
95
+ * 4.5, 4.3, the 4.20 family and Build 0.1 are priced, each with the rate a
96
+ * request pays once its prompt reaches 200k tokens, which per-request
97
+ * pricing can now express. `grok-4` was retired on 2026-05-15 and its calls
98
+ * have billed at Grok 4.3's prices since, so it carries both periods.
99
+ */
100
+ xai: '2026-10-11',
79
101
  /*
80
102
  * `mistral.ai/pricing`, read 2026-08-28 — by a human, because the page
81
103
  * renders its table in the browser and serves a fetch only an FAQ example
@@ -86,7 +108,18 @@ export const PROVIDER_REVIEWED: Readonly<Record<string, string>> = Object.freeze
86
108
  * servable is not the same as checking, and moving the date then would have
87
109
  * said it was.
88
110
  */
89
- mistral: '2026-08-28',
111
+ /*
112
+ * Re-read 2026-10-11 from mistral.ai/cloud/inference/ and the model cards
113
+ * at mistral.ai/news/mistral-3/, /mistral-small-4/ and
114
+ * /vibe-remote-agents-mistral-medium-3-5/, which carry the prices in their
115
+ * markup (`data-prices`) and bind the `-latest` ids. Every price matched.
116
+ * What changed is caching: the inference page offers "Cached input tokens
117
+ * -90% on input token", and every one of the three cards marks its input
118
+ * price as carrying the cache discount. This table said Mistral had no
119
+ * prompt caching at all. docs.mistral.ai, where a minimum or a mechanism
120
+ * would be, is not reachable from here, so neither is claimed.
121
+ */
122
+ mistral: '2026-10-11',
90
123
  });
91
124
 
92
125
  /**
@@ -132,12 +165,17 @@ const multipliersMemo = new WeakMap<
132
165
  { cacheWrite5m: number; cacheWrite1h: number; cacheRead: number; batch: number | null }
133
166
  >();
134
167
 
135
- export function multipliersFor(model: ModelPricing): {
168
+ export function multipliersFor(model: ModelPricing, on?: Date): {
136
169
  cacheWrite5m: number;
137
170
  cacheWrite1h: number;
138
171
  cacheRead: number;
139
172
  batch: number | null;
140
173
  } {
174
+ // A dated question about a repriced model asks the period in force then.
175
+ if (on !== undefined && model.history !== undefined) {
176
+ const period = pricedAt(model, on);
177
+ if (period !== model) return multipliersFor(period);
178
+ }
141
179
  const cached = multipliersMemo.get(model);
142
180
  if (cached !== undefined) return cached;
143
181
  const m = model.multipliers ?? {};
@@ -151,6 +189,57 @@ export function multipliersFor(model: ModelPricing): {
151
189
  return result;
152
190
  }
153
191
 
192
+ /** Five minutes and an hour: Anthropic's two cache entries, and the default. */
193
+ const ANTHROPIC_TTL = Object.freeze({ writeMs: 5 * 60_000, longerMs: 60 * 60_000 });
194
+
195
+ /** How long a model's cache entries live. See `ModelPricing.cacheTtl`. */
196
+ export function cacheTtlOf(model: ModelPricing): { writeMs: number; longerMs: number | null } {
197
+ return model.cacheTtl ?? ANTHROPIC_TTL;
198
+ }
199
+
200
+ /**
201
+ * Mistral discounts cached input by 90% and publishes nothing else about it
202
+ * that can be read from here: not how caching starts, not a minimum, not a
203
+ * write price. Its usage reports no write tokens, so the write ratio of 1 is
204
+ * never multiplied by anything; it is there so the default (Anthropic's 1.25)
205
+ * cannot be.
206
+ */
207
+ /** What the GPT-5 family's three rows share, from OpenAI's guide and deprecations page, read 2026-10-11. */
208
+ /** xAI's long-context band: a request whose prompt reaches 200,000 tokens, at the published higher rates. */
209
+ const GROK_LONG = (inputPerMTok: number, outputPerMTok: number): PricingTier => ({
210
+ id: 'from-200k',
211
+ when: { kind: 'input-tokens-at-least', tokens: 200_000 },
212
+ inputPerMTok,
213
+ outputPerMTok,
214
+ });
215
+
216
+ /**
217
+ * Said on every row that caches but whose provider publishes no minimum
218
+ * prefix. The bill still prices its cache reads and writes, which are
219
+ * published; what goes quiet is the advice that needs the minimum to promise
220
+ * a saving, and the reader is told why rather than handed a number from
221
+ * nowhere. The catalogue tests hold a null minimum on a caching model to
222
+ * carrying this sentence, so a forgotten minimum cannot pass as a deliberate one.
223
+ */
224
+ const NO_PUBLISHED_CACHE_MINIMUM =
225
+ 'Caches automatically, but no minimum prefix length is published, so no caching advice is given for it.';
226
+
227
+ /** OpenAI's GPT-5.6 and later: one cache entry, living at least 30 minutes. */
228
+ const OPENAI_CACHE_TTL = Object.freeze({ writeMs: 30 * 60_000, longerMs: null });
229
+
230
+ const OPENAI_CURRENT_NOTE =
231
+ 'Priced per request by prompt length: over 272,000 input tokens, 2x input and cache and 1.5x output for'
232
+ + ' the whole request. Cache writes cost 1.25x input and an entry lives at least 30 minutes.';
233
+
234
+ const OPENAI_GPT5_NOTE =
235
+ 'Caching is automatic, with no cache_control to set. OpenAI says the minimum for models before GPT-5.6 varies by'
236
+ + ' request settings, so 1,024 here is the nearest published floor. The 2025-08-07 snapshot shuts down on'
237
+ + ' 2026-12-11; OpenAI names the gpt-5.6 models as replacements.';
238
+
239
+ const MISTRAL_CACHE_NOTE =
240
+ 'Cached input costs 10% of the input price (mistral.ai/cloud/inference, read 2026-10-11). How caching starts and'
241
+ + ' its minimum are not published anywhere Trazum can read, so no caching advice is given for it.';
242
+
154
243
  export const MODELS: ModelPricing[] = [
155
244
  {
156
245
  /*
@@ -348,18 +437,45 @@ export const MODELS: ModelPricing[] = [
348
437
  capability: 'mid',
349
438
  tier: 'sonnet',
350
439
  },
351
- /*
352
- * Claude Haiku 5.5 is not here, on purpose, and its calls are an unpriced
353
- * gap that names it. The pricing page (read 2026-10-10) prices it by prompt
354
- * length: $0.10/$0.50 up to 100,000 tokens and $0.50/$2.50 above, where the
355
- * length counts cache reads and writes and each request is priced on its
356
- * own. A tier can say that, but nothing that prices a usage log passes one
357
- * request's prompt length to `effectivePricing`: records are summed per
358
- * workload, model and day before they are priced. Added today, every Haiku
359
- * 5.5 call would fall to the undecided ceiling, five times the price most of
360
- * them paid. It goes in with the change that prices tiered models per
361
- * request, not before.
362
- */
440
+ {
441
+ /*
442
+ * The pricing page, the models overview and the prompt-caching page, read
443
+ * 2026-10-10. Priced by prompt length, per request: $0.10/$0.50 for a
444
+ * prompt up to 100,000 tokens and $0.50/$2.50 over it, where the length
445
+ * counts every input token the request carried, cache reads and writes
446
+ * included, and "each request is priced on its own". Caching is the same
447
+ * ratio in both bands (hits $0.01 and $0.05, writes $0.125/$0.20 and
448
+ * $0.625/$1), and so is batch ($0.05/$0.25, $0.25/$1.25). 1M context,
449
+ * 128K output, a 512-token cache minimum.
450
+ *
451
+ * It waited for the change that prices a log per request. A record that
452
+ * is one call carries its own prompt; a row out of a usage report sums
453
+ * requests, and is billed at the over-100,000 rate unless its whole
454
+ * prompt total is under the line, with the cheaper figure printed beside
455
+ * it. See `PromptSize`.
456
+ */
457
+ id: 'claude-haiku-5-5',
458
+ provider: 'anthropic',
459
+ displayName: 'Claude Haiku 5.5',
460
+ inputPerMTok: 0.1,
461
+ outputPerMTok: 0.5,
462
+ contextWindow: 1_000_000,
463
+ cacheMinTokens: 512,
464
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2, batch: 0.5 },
465
+ tiers: [
466
+ {
467
+ id: 'over-100k',
468
+ when: { kind: 'input-tokens-above', tokens: 100_000 },
469
+ inputPerMTok: 0.5,
470
+ outputPerMTok: 2.5,
471
+ },
472
+ ],
473
+ capability: 'small',
474
+ tier: 'haiku',
475
+ notes:
476
+ 'Priced per request by prompt length: $0.10/$0.50 up to 100,000 input tokens, cache reads'
477
+ + ' and writes included, and $0.50/$2.50 over it.',
478
+ },
363
479
  {
364
480
  id: 'claude-haiku-4-5',
365
481
  // The dated form is the canonical API id and is what a real usage log
@@ -380,13 +496,200 @@ export const MODELS: ModelPricing[] = [
380
496
  // ------------------------------------------------------------------------
381
497
  // OpenAI
382
498
  //
383
- // Caching is AUTOMATIC above 1,024 tokens — there is no cache_control to set,
384
- // and a cached read costs about half the input price rather than a tenth. The
385
- // reordering advice is identical (a prefix is a prefix), the marker advice is
386
- // not, which is why `caching` and `multipliers` exist at all.
499
+ // Caching is AUTOMATIC, with no cache_control to set, and a cached read costs
500
+ // a tenth of input on these three ($0.125 against $1.25). The reordering
501
+ // advice is identical (a prefix is a prefix), the marker advice is not, which
502
+ // is why `caching` and `multipliers` exist at all. The 1,024 minimum is the
503
+ // one OpenAI publishes for GPT-5.6 and later; for these older models its
504
+ // guide says the minimum "varies by request settings", so it is the nearest
505
+ // published floor, not a promise, and each row's note says so.
506
+ // ------------------------------------------------------------------------
507
+ // GPT-5.6 and later: OpenAI's current generation.
508
+ //
509
+ // The pricing page and each model's page, read 2026-10-10 and 2026-10-11,
510
+ // and the prompt-caching guide. Three things set them apart from GPT-5:
511
+ //
512
+ // - **A long-context band, per request.** "Prompts with more than 272K input
513
+ // tokens are priced at 2x input and cache rates and 1.5x output for the
514
+ // full request." 272,000, the GPT-5 era's own input limit; a tier.
515
+ // - **Cache writes are billed**, at 1.25x the uncached input rate, and the
516
+ // usage object reports them (`cache_write_tokens`). Reads are 10% of input,
517
+ // 5% on GPT-6.1 Sol.
518
+ // - **An entry lives at least 30 minutes** after its last write or read, and
519
+ // there is no longer one to buy: `cacheTtl`.
520
+ //
521
+ // Caching is implicit by default (a breakpoint at the end of the latest
522
+ // eligible message) from a 1,024-token minimum. Batch and Flex are half
523
+ // price; Fast, Ultrafast and regional processing are other service tiers
524
+ // and are not priced here. Every model: 1,050,000 context, 922,000 input at
525
+ // most, 128,000 output.
526
+ // ------------------------------------------------------------------------
527
+ {
528
+ id: 'gpt-6-astra',
529
+ provider: 'openai',
530
+ displayName: 'GPT-6 Astra',
531
+ inputPerMTok: 10,
532
+ outputPerMTok: 50,
533
+ contextWindow: 1_050_000,
534
+ cacheMinTokens: 1024,
535
+ caching: 'automatic',
536
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
537
+ cacheTtl: OPENAI_CACHE_TTL,
538
+ tiers: [
539
+ {
540
+ id: 'over-272k',
541
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
542
+ inputPerMTok: 20,
543
+ outputPerMTok: 75,
544
+ },
545
+ ],
546
+ capability: 'frontier',
547
+ tier: 'frontier',
548
+ notes: OPENAI_CURRENT_NOTE,
549
+ },
550
+ {
551
+ id: 'gpt-6.1-sol',
552
+ provider: 'openai',
553
+ displayName: 'GPT-6.1 Sol',
554
+ inputPerMTok: 2,
555
+ outputPerMTok: 10,
556
+ contextWindow: 1_050_000,
557
+ cacheMinTokens: 1024,
558
+ caching: 'automatic',
559
+ multipliers: { cacheRead: 0.05, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
560
+ cacheTtl: OPENAI_CACHE_TTL,
561
+ tiers: [
562
+ {
563
+ id: 'over-272k',
564
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
565
+ inputPerMTok: 4,
566
+ outputPerMTok: 15,
567
+ },
568
+ ],
569
+ capability: 'large',
570
+ tier: 'opus',
571
+ notes: OPENAI_CURRENT_NOTE,
572
+ },
573
+ {
574
+ id: 'gpt-6-sol',
575
+ provider: 'openai',
576
+ displayName: 'GPT-6 Sol',
577
+ inputPerMTok: 2,
578
+ outputPerMTok: 10,
579
+ contextWindow: 1_050_000,
580
+ cacheMinTokens: 1024,
581
+ caching: 'automatic',
582
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
583
+ cacheTtl: OPENAI_CACHE_TTL,
584
+ tiers: [
585
+ {
586
+ id: 'over-272k',
587
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
588
+ inputPerMTok: 4,
589
+ outputPerMTok: 15,
590
+ },
591
+ ],
592
+ capability: 'large',
593
+ tier: 'opus',
594
+ notes: OPENAI_CURRENT_NOTE,
595
+ },
596
+ {
597
+ id: 'gpt-6-luna',
598
+ provider: 'openai',
599
+ displayName: 'GPT-6 Luna',
600
+ inputPerMTok: 0.1,
601
+ outputPerMTok: 0.5,
602
+ contextWindow: 1_050_000,
603
+ cacheMinTokens: 1024,
604
+ caching: 'automatic',
605
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
606
+ cacheTtl: OPENAI_CACHE_TTL,
607
+ tiers: [
608
+ {
609
+ id: 'over-272k',
610
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
611
+ inputPerMTok: 0.2,
612
+ outputPerMTok: 0.75,
613
+ },
614
+ ],
615
+ capability: 'small',
616
+ tier: 'haiku',
617
+ notes: OPENAI_CURRENT_NOTE,
618
+ },
619
+ {
620
+ id: 'gpt-5.6-sol',
621
+ provider: 'openai',
622
+ displayName: 'GPT-5.6 Sol',
623
+ inputPerMTok: 4,
624
+ outputPerMTok: 20,
625
+ contextWindow: 1_050_000,
626
+ cacheMinTokens: 1024,
627
+ caching: 'automatic',
628
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
629
+ cacheTtl: OPENAI_CACHE_TTL,
630
+ tiers: [
631
+ {
632
+ id: 'over-272k',
633
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
634
+ inputPerMTok: 8,
635
+ outputPerMTok: 30,
636
+ },
637
+ ],
638
+ capability: 'large',
639
+ tier: 'opus',
640
+ notes: OPENAI_CURRENT_NOTE,
641
+ },
642
+ {
643
+ id: 'gpt-5.6-terra',
644
+ provider: 'openai',
645
+ displayName: 'GPT-5.6 Terra',
646
+ inputPerMTok: 2,
647
+ outputPerMTok: 12,
648
+ contextWindow: 1_050_000,
649
+ cacheMinTokens: 1024,
650
+ caching: 'automatic',
651
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
652
+ cacheTtl: OPENAI_CACHE_TTL,
653
+ tiers: [
654
+ {
655
+ id: 'over-272k',
656
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
657
+ inputPerMTok: 4,
658
+ outputPerMTok: 18,
659
+ },
660
+ ],
661
+ capability: 'mid',
662
+ tier: 'sonnet',
663
+ notes: OPENAI_CURRENT_NOTE,
664
+ },
665
+ {
666
+ id: 'gpt-5.6-luna',
667
+ provider: 'openai',
668
+ displayName: 'GPT-5.6 Luna',
669
+ inputPerMTok: 0.2,
670
+ outputPerMTok: 1.2,
671
+ contextWindow: 1_050_000,
672
+ cacheMinTokens: 1024,
673
+ caching: 'automatic',
674
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 1.25, batch: 0.5 },
675
+ cacheTtl: OPENAI_CACHE_TTL,
676
+ tiers: [
677
+ {
678
+ id: 'over-272k',
679
+ when: { kind: 'input-tokens-above', tokens: 272_000 },
680
+ inputPerMTok: 0.4,
681
+ outputPerMTok: 1.8,
682
+ },
683
+ ],
684
+ capability: 'small',
685
+ tier: 'haiku',
686
+ notes: OPENAI_CURRENT_NOTE,
687
+ },
688
+
387
689
  // ------------------------------------------------------------------------
388
690
  {
389
691
  id: 'gpt-5',
692
+ aliases: ['gpt-5-2025-08-07'],
390
693
  provider: 'openai',
391
694
  displayName: 'GPT-5',
392
695
  inputPerMTok: 1.25,
@@ -397,10 +700,11 @@ export const MODELS: ModelPricing[] = [
397
700
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
398
701
  capability: 'frontier',
399
702
  tier: 'frontier',
400
- notes: 'Caching is automatic above 1,024 tokens; there is no cache_control to set.',
703
+ notes: OPENAI_GPT5_NOTE,
401
704
  },
402
705
  {
403
706
  id: 'gpt-5-mini',
707
+ aliases: ['gpt-5-mini-2025-08-07'],
404
708
  provider: 'openai',
405
709
  displayName: 'GPT-5 mini',
406
710
  inputPerMTok: 0.25,
@@ -411,9 +715,11 @@ export const MODELS: ModelPricing[] = [
411
715
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
412
716
  capability: 'mid',
413
717
  tier: 'sonnet',
718
+ notes: OPENAI_GPT5_NOTE,
414
719
  },
415
720
  {
416
721
  id: 'gpt-5-nano',
722
+ aliases: ['gpt-5-nano-2025-08-07'],
417
723
  provider: 'openai',
418
724
  displayName: 'GPT-5 nano',
419
725
  inputPerMTok: 0.05,
@@ -424,6 +730,7 @@ export const MODELS: ModelPricing[] = [
424
730
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
425
731
  capability: 'small',
426
732
  tier: 'haiku',
733
+ notes: OPENAI_GPT5_NOTE,
427
734
  },
428
735
 
429
736
  // ------------------------------------------------------------------------
@@ -437,8 +744,9 @@ export const MODELS: ModelPricing[] = [
437
744
  outputPerMTok: 10,
438
745
  contextWindow: 1_048_576,
439
746
  cacheMinTokens: 2048,
440
- caching: 'explicit',
441
- multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
747
+ caching: 'automatic',
748
+ /* $0.125 cached against $1.25 input for prompts up to 200k, read 2026-10-10. */
749
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
442
750
  /* Refused on 2026-08-28: still in `GET /v1beta/models`, and a 404 to a request. */
443
751
  retired: {
444
752
  on: '2026-08-28',
@@ -447,7 +755,10 @@ export const MODELS: ModelPricing[] = [
447
755
  },
448
756
  capability: 'large',
449
757
  tier: 'opus',
450
- notes: 'Context caching is billed for storage per hour as well as per read.',
758
+ notes:
759
+ 'Prompts over 200k tokens are billed $2.50 in and $15.00 out; this row prices every prompt at the up-to-200k rate,'
760
+ + ' because no log Trazum prices says how long each prompt was. Implicit caching is automatic; explicit caching is'
761
+ + ' also billed for storage per hour.',
451
762
  },
452
763
  {
453
764
  id: 'gemini-2.5-flash',
@@ -456,9 +767,10 @@ export const MODELS: ModelPricing[] = [
456
767
  inputPerMTok: 0.3,
457
768
  outputPerMTok: 2.5,
458
769
  contextWindow: 1_048_576,
459
- cacheMinTokens: 1024,
460
- caching: 'explicit',
461
- multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
770
+ cacheMinTokens: 2048,
771
+ caching: 'automatic',
772
+ /* $0.03 cached against $0.30 input (text), read 2026-10-10. */
773
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
462
774
  /* Refused on 2026-08-28: still in `GET /v1beta/models`, and a 404 to a request. */
463
775
  retired: {
464
776
  on: '2026-08-28',
@@ -471,9 +783,10 @@ export const MODELS: ModelPricing[] = [
471
783
 
472
784
  {
473
785
  /*
474
- * Google's own error message names this as the replacement for the two
475
- * retired 2.5 models, and ai.google.dev/gemini-api/docs/pricing carries the
476
- * rates, read 2026-08-28.
786
+ * Google's own error message names this as the replacement for the retired
787
+ * 2.5 Flash (the 2.5 Pro refusal names 3.1 Pro Preview), and
788
+ * ai.google.dev/gemini-api/docs/pricing carries the rates, read 2026-08-28
789
+ * and again on 2026-10-10.
477
790
  *
478
791
  * The introductory price has a published end date and a published successor,
479
792
  * which is exactly what `promo` is for and is the shape the catalogue nearly
@@ -487,8 +800,8 @@ export const MODELS: ModelPricing[] = [
487
800
  outputPerMTok: 7.5,
488
801
  promo: { inputPerMTok: 0.75, outputPerMTok: 3.75, until: '2026-12-31' },
489
802
  contextWindow: 1_048_576,
490
- cacheMinTokens: 1024,
491
- caching: 'explicit',
803
+ cacheMinTokens: 4096,
804
+ caching: 'automatic',
492
805
  /*
493
806
  * $0.075 against $0.75 inside the introductory window, and $0.15 against
494
807
  * $1.50 after it: Google scales the cache price with the headline one, so
@@ -499,13 +812,147 @@ export const MODELS: ModelPricing[] = [
499
812
  capability: 'mid',
500
813
  tier: 'sonnet',
501
814
  notes:
502
- 'Context caching is billed for storage per hour as well as per read:'
815
+ 'Implicit caching is automatic above 4,096 tokens. Explicit caching is also billed for storage:'
816
+ + ' $0.50 per 1M tokens per hour through 2026-12-31, $1.00 after.',
817
+ },
818
+ {
819
+ /*
820
+ * Gemini 3.8 Flash, from ai.google.dev/gemini-api/docs/pricing, its model
821
+ * page and docs/caching, read 2026-10-10. The same rates and the same
822
+ * introductory window as 3.6 Flash, published as one table each: $0.75
823
+ * and $3.75 through 2026-12-31, $1.50 and $7.50 from 2027-01-01, and the
824
+ * cache price scales with them ($0.075, then $0.15).
825
+ */
826
+ id: 'gemini-3.8-flash',
827
+ provider: 'google',
828
+ displayName: 'Gemini 3.8 Flash',
829
+ inputPerMTok: 1.5,
830
+ outputPerMTok: 7.5,
831
+ promo: { inputPerMTok: 0.75, outputPerMTok: 3.75, until: '2026-12-31' },
832
+ contextWindow: 1_048_576,
833
+ cacheMinTokens: 4096,
834
+ caching: 'automatic',
835
+ /* $0.075 / $0.75 = 0.1 (and $0.15 / $1.50); batch $0.375 / $0.75 = 0.5 (and $1.875 / $3.75). */
836
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
837
+ capability: 'mid',
838
+ tier: 'sonnet',
839
+ notes:
840
+ 'Implicit caching is automatic above 4,096 tokens. Explicit caching is also billed for storage:'
503
841
  + ' $0.50 per 1M tokens per hour through 2026-12-31, $1.00 after.',
504
842
  },
843
+ {
844
+ /*
845
+ * The pricing page, the model page and the caching page, read 2026-10-10
846
+ * and 2026-10-11. The model Google's own refusal of 2.5 Pro names as its
847
+ * replacement, and the second model here priced by prompt length: $2/$12
848
+ * for a prompt up to 200,000 tokens and $4/$18 over it, the cache at
849
+ * $0.20 and $0.40 (10% in both bands), batch $1/$6 and $2/$9 (half in
850
+ * both). 1,048,576 tokens in, 65,536 out, implicit caching from 4,096.
851
+ * The `-customtools` endpoint is "same as Gemini 3.1 Pro Preview pricing".
852
+ *
853
+ * A preview, and priced because a log carries it: Google's 2.5 Pro
854
+ * refusal sends every caller here.
855
+ */
856
+ id: 'gemini-3.1-pro-preview',
857
+ aliases: ['gemini-3.1-pro-preview-customtools'],
858
+ provider: 'google',
859
+ displayName: 'Gemini 3.1 Pro Preview',
860
+ inputPerMTok: 2,
861
+ outputPerMTok: 12,
862
+ contextWindow: 1_048_576,
863
+ cacheMinTokens: 4096,
864
+ caching: 'automatic',
865
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
866
+ tiers: [
867
+ {
868
+ id: 'over-200k',
869
+ when: { kind: 'input-tokens-above', tokens: 200_000 },
870
+ inputPerMTok: 4,
871
+ outputPerMTok: 18,
872
+ },
873
+ ],
874
+ capability: 'large',
875
+ tier: 'opus',
876
+ notes:
877
+ 'Priced per request by prompt length: $2/$12 up to 200,000 input tokens and $4/$18 over it.'
878
+ + ' Implicit caching is automatic above 4,096 tokens; explicit caching is also billed for storage,'
879
+ + ' $4.50 per 1M tokens per hour.',
880
+ },
505
881
 
506
882
  // ------------------------------------------------------------------------
507
883
  // Moonshot
508
884
  // ------------------------------------------------------------------------
885
+ /*
886
+ * platform.kimi.ai/docs/pricing/chat, models and pricing/batch, read in
887
+ * their Markdown form on 2026-10-11 (the HTML renders its tables in the
888
+ * browser, which is why the September review saw no context windows). K3
889
+ * bills cache writes as their own line, by TTL, like Anthropic: $3.00 for a
890
+ * 5-minute entry and $6.00 for an hour against a $3.00 input, and reads at
891
+ * $0.30. The K2 series bills no write, and reads at its own hit price. Batch
892
+ * is 60% of standard on K2.7 Code and K2.6 and not offered on K3 or the
893
+ * high-speed K2.7. No cache minimum is published for any of them.
894
+ */
895
+ {
896
+ id: 'kimi-k3',
897
+ provider: 'moonshot',
898
+ displayName: 'Kimi K3',
899
+ inputPerMTok: 3,
900
+ outputPerMTok: 15,
901
+ contextWindow: 1_048_576,
902
+ cacheMinTokens: null,
903
+ caching: 'automatic',
904
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 2, batch: null },
905
+ capability: 'frontier',
906
+ tier: 'frontier',
907
+ notes:
908
+ 'Cache writes are billed as their own line: 1x input for a 5-minute entry, 2x for an hour.'
909
+ + ` No batch API for this model. ${NO_PUBLISHED_CACHE_MINIMUM}`,
910
+ },
911
+ {
912
+ id: 'kimi-k2.7-code',
913
+ provider: 'moonshot',
914
+ displayName: 'Kimi K2.7 Code',
915
+ inputPerMTok: 0.95,
916
+ outputPerMTok: 4,
917
+ contextWindow: 262_144,
918
+ cacheMinTokens: null,
919
+ caching: 'automatic',
920
+ /* $0.19 on a hit against $0.95; batch $0.57 / $2.40, 60%. */
921
+ multipliers: { cacheRead: 0.2, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.6 },
922
+ capability: 'mid',
923
+ tier: 'sonnet',
924
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
925
+ },
926
+ {
927
+ id: 'kimi-k2.7-code-highspeed',
928
+ provider: 'moonshot',
929
+ displayName: 'Kimi K2.7 Code (high speed)',
930
+ inputPerMTok: 1.9,
931
+ outputPerMTok: 8,
932
+ contextWindow: 262_144,
933
+ cacheMinTokens: null,
934
+ caching: 'automatic',
935
+ /* $0.38 on a hit against $1.90; no batch API for this variant. */
936
+ multipliers: { cacheRead: 0.2, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
937
+ capability: 'mid',
938
+ tier: 'sonnet',
939
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
940
+ },
941
+ {
942
+ id: 'kimi-k2.6',
943
+ provider: 'moonshot',
944
+ displayName: 'Kimi K2.6',
945
+ inputPerMTok: 0.95,
946
+ outputPerMTok: 4,
947
+ contextWindow: 262_144,
948
+ cacheMinTokens: null,
949
+ caching: 'automatic',
950
+ /* $0.16 on a hit against $0.95, written as the ratio so the product is the page's figure. */
951
+ multipliers: { cacheRead: 0.16 / 0.95, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.6 },
952
+ capability: 'mid',
953
+ tier: 'sonnet',
954
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
955
+ },
509
956
  {
510
957
  id: 'kimi-k2',
511
958
  provider: 'moonshot',
@@ -513,14 +960,23 @@ export const MODELS: ModelPricing[] = [
513
960
  inputPerMTok: 0.6,
514
961
  outputPerMTok: 2.5,
515
962
  contextWindow: 256_000,
516
- cacheMinTokens: 1024,
963
+ cacheMinTokens: null,
517
964
  caching: 'automatic',
518
965
  multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
966
+ /*
967
+ * Discontinued: "The kimi-k2 series models were officially discontinued
968
+ * on May 25, 2026" (the provider's model list, read 2026-10-11). Kept at
969
+ * its last published price, because calls in somebody's log really
970
+ * happened at it, and never offered as somewhere to move. Not `retired`,
971
+ * which records the provider refusing a request, and no key was here to
972
+ * send one.
973
+ */
974
+ recommendable: false,
519
975
  capability: 'mid',
520
976
  tier: 'sonnet',
521
977
  notes:
522
- 'No batch API: the batch advisory stays quiet rather than offering a discount you cannot buy.'
523
- + ' 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.',
978
+ 'Discontinued by Moonshot on 2026-05-25; priced at the last rate it published. No batch API.'
979
+ + ` ${NO_PUBLISHED_CACHE_MINIMUM}`,
524
980
  },
525
981
 
526
982
  // ------------------------------------------------------------------------
@@ -547,34 +1003,97 @@ export const MODELS: ModelPricing[] = [
547
1003
  },
548
1004
  {
549
1005
  /*
550
- * DeepSeek V4 Flash, from api-docs.deepseek.com/quick_start/pricing, read
551
- * 2026-08-28. It replaced `deepseek-v3`, which the API now refuses.
1006
+ * DeepSeek V4 Flash, retired on 2026-09-10. The pricing page (read
1007
+ * 2026-10-10) says the id "is still accepted ... served by the
1008
+ * DeepSeek-V4.1-Flash model and billed at the Flash price", so from
1009
+ * 04:00 UTC that day it carries deepseek-flash's rates. Before that
1010
+ * instant it billed at its own, read off the same page on 2026-08-28:
1011
+ * $0.22/$0.66 off-peak, double at peak, $0.007 a cache hit.
552
1012
  *
553
1013
  * The base rate is off-peak because off-peak is the common case: peak is
554
1014
  * 01:00-04:00 and 06:00-10:00 UTC on weekdays, which is 35 hours of 168.
555
- * The peak tier is exactly double, published as such.
1015
+ * The peak tier is exactly double, published as such. Clock pricing began
1016
+ * at 16:00 UTC on 2026-08-16; the flat price before it is on no page this
1017
+ * table can read, so calls before then are priced as the first period.
1018
+ *
1019
+ * Not recommendable: a legacy id DeepSeek routes "temporarily". Without
1020
+ * this, a tie on price would offer it ahead of deepseek-flash.
556
1021
  */
557
1022
  id: 'deepseek-v4-flash',
558
1023
  provider: 'deepseek',
559
1024
  displayName: 'DeepSeek V4 Flash',
560
- inputPerMTok: 0.22,
561
- outputPerMTok: 0.66,
1025
+ recommendable: false,
1026
+ inputPerMTok: 0.15,
1027
+ outputPerMTok: 0.6,
562
1028
  tiers: [
563
1029
  {
564
1030
  id: 'peak',
565
1031
  when: { kind: 'utc-hours', hours: [1, 2, 3, 6, 7, 8, 9], weekdaysOnly: true },
566
- inputPerMTok: 0.44,
567
- outputPerMTok: 1.32,
1032
+ inputPerMTok: 0.3,
1033
+ outputPerMTok: 1.2,
1034
+ },
1035
+ ],
1036
+ history: [
1037
+ {
1038
+ until: '2026-09-10T04:00:00Z',
1039
+ inputPerMTok: 0.22,
1040
+ outputPerMTok: 0.66,
1041
+ tiers: [
1042
+ {
1043
+ id: 'peak',
1044
+ when: { kind: 'utc-hours', hours: [1, 2, 3, 6, 7, 8, 9], weekdaysOnly: true },
1045
+ inputPerMTok: 0.44,
1046
+ outputPerMTok: 1.32,
1047
+ },
1048
+ ],
1049
+ /* $0.007 cache hit against $0.22 input off-peak. */
1050
+ multipliers: { cacheRead: 0.0318, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
568
1051
  },
569
1052
  ],
570
1053
  contextWindow: 1_000_000,
571
1054
  cacheMinTokens: 1024,
572
1055
  caching: 'automatic',
573
- /* $0.007 cache hit against $0.22 input off-peak. */
574
- multipliers: { cacheRead: 0.0318, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1056
+ /* $0.003 cache hit against $0.15 input off-peak, $0.006 against $0.30 at peak. */
1057
+ multipliers: { cacheRead: 0.02, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
575
1058
  capability: 'mid',
576
1059
  tier: 'sonnet',
577
- notes: 'Maximum output 384K tokens.',
1060
+ notes:
1061
+ 'Retired 2026-09-10: the id is still accepted, served by V4.1 Flash and billed at the deepseek-flash price.'
1062
+ + ' Maximum output 384K tokens. Peak excludes Chinese public holidays, which this table cannot express.',
1063
+ },
1064
+ {
1065
+ /*
1066
+ * DeepSeek V4.1 Flash, from api-docs.deepseek.com/quick_start/pricing,
1067
+ * read 2026-10-10. Released 2026-09-10; its price took effect at 04:00
1068
+ * UTC that day. Off-peak is the base and peak is exactly double, as
1069
+ * published; the cache hit is 2% of input at both ($0.003 and $0.006).
1070
+ *
1071
+ * The cache minimum is carried from deepseek-v4-flash: DeepSeek's pages
1072
+ * publish none for this generation, and 1,024 is the figure the earlier
1073
+ * row was reviewed with.
1074
+ */
1075
+ id: 'deepseek-flash',
1076
+ provider: 'deepseek',
1077
+ displayName: 'DeepSeek V4.1 Flash',
1078
+ inputPerMTok: 0.15,
1079
+ outputPerMTok: 0.6,
1080
+ tiers: [
1081
+ {
1082
+ id: 'peak',
1083
+ when: { kind: 'utc-hours', hours: [1, 2, 3, 6, 7, 8, 9], weekdaysOnly: true },
1084
+ inputPerMTok: 0.3,
1085
+ outputPerMTok: 1.2,
1086
+ },
1087
+ ],
1088
+ contextWindow: 1_000_000,
1089
+ cacheMinTokens: 1024,
1090
+ caching: 'automatic',
1091
+ multipliers: { cacheRead: 0.02, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1092
+ capability: 'mid',
1093
+ tier: 'sonnet',
1094
+ notes:
1095
+ 'Maximum output 384K tokens. Peak excludes Chinese public holidays, which this table cannot express:'
1096
+ + ' on a weekday holiday it charges peak where DeepSeek charges off-peak.',
578
1097
  },
579
1098
  {
580
1099
  /* Same page, same clock. Pro is three times Flash at both ends. */
@@ -604,20 +1123,175 @@ export const MODELS: ModelPricing[] = [
604
1123
  // ------------------------------------------------------------------------
605
1124
  // xAI
606
1125
  // ------------------------------------------------------------------------
1126
+ /*
1127
+ * docs.x.ai/developers/models and each model's page, read in their
1128
+ * Markdown form on 2026-10-11. Every text model has two rates: "requests
1129
+ * whose prompt reaches 200k tokens are billed at the higher rate for all
1130
+ * tokens in the request", which is a size tier at 200,000 inclusive and is
1131
+ * priced per request. Cached input scales with the band; writes are not
1132
+ * billed; batch is a 20% discount where the model's page offers it at all.
1133
+ * No cache minimum is published. Aliases are the stable family names the
1134
+ * pages list, never the `-latest` names, which move.
1135
+ */
1136
+ {
1137
+ id: 'grok-4.7',
1138
+ provider: 'xai',
1139
+ displayName: 'Grok 4.7',
1140
+ inputPerMTok: 2,
1141
+ outputPerMTok: 6,
1142
+ contextWindow: 500_000,
1143
+ cacheMinTokens: null,
1144
+ caching: 'automatic',
1145
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1146
+ tiers: [GROK_LONG(4, 12)],
1147
+ capability: 'frontier',
1148
+ tier: 'frontier',
1149
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1150
+ },
1151
+ {
1152
+ id: 'grok-4.6',
1153
+ provider: 'xai',
1154
+ displayName: 'Grok 4.6',
1155
+ inputPerMTok: 2,
1156
+ outputPerMTok: 6,
1157
+ contextWindow: 500_000,
1158
+ cacheMinTokens: null,
1159
+ caching: 'automatic',
1160
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1161
+ tiers: [GROK_LONG(4, 12)],
1162
+ capability: 'large',
1163
+ tier: 'opus',
1164
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1165
+ },
1166
+ {
1167
+ id: 'grok-4.5',
1168
+ provider: 'xai',
1169
+ displayName: 'Grok 4.5',
1170
+ inputPerMTok: 2,
1171
+ outputPerMTok: 6,
1172
+ contextWindow: 500_000,
1173
+ cacheMinTokens: null,
1174
+ caching: 'automatic',
1175
+ /* $0.30 cached against $2.00. */
1176
+ multipliers: { cacheRead: 0.15, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1177
+ tiers: [GROK_LONG(4, 12)],
1178
+ capability: 'large',
1179
+ tier: 'opus',
1180
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1181
+ },
1182
+ {
1183
+ id: 'grok-4.3',
1184
+ provider: 'xai',
1185
+ displayName: 'Grok 4.3',
1186
+ inputPerMTok: 1.25,
1187
+ outputPerMTok: 2.5,
1188
+ contextWindow: 1_000_000,
1189
+ cacheMinTokens: null,
1190
+ caching: 'automatic',
1191
+ /* $0.20 cached against $1.25; batch 20% off. */
1192
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.8 },
1193
+ tiers: [GROK_LONG(2.5, 5)],
1194
+ capability: 'mid',
1195
+ tier: 'sonnet',
1196
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1197
+ },
607
1198
  {
1199
+ id: 'grok-4.20-0309-reasoning',
1200
+ aliases: ['grok-4.20', 'grok-4.20-reasoning', 'grok-4.20-0309'],
1201
+ provider: 'xai',
1202
+ displayName: 'Grok 4.20 (reasoning)',
1203
+ inputPerMTok: 1.25,
1204
+ outputPerMTok: 2.5,
1205
+ contextWindow: 1_000_000,
1206
+ cacheMinTokens: null,
1207
+ caching: 'automatic',
1208
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.8 },
1209
+ tiers: [GROK_LONG(2.5, 5)],
1210
+ capability: 'mid',
1211
+ tier: 'sonnet',
1212
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1213
+ },
1214
+ {
1215
+ id: 'grok-4.20-0309-non-reasoning',
1216
+ aliases: ['grok-4.20-non-reasoning'],
1217
+ provider: 'xai',
1218
+ displayName: 'Grok 4.20 (non-reasoning)',
1219
+ inputPerMTok: 1.25,
1220
+ outputPerMTok: 2.5,
1221
+ contextWindow: 1_000_000,
1222
+ cacheMinTokens: null,
1223
+ caching: 'automatic',
1224
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.8 },
1225
+ tiers: [GROK_LONG(2.5, 5)],
1226
+ capability: 'mid',
1227
+ tier: 'sonnet',
1228
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1229
+ },
1230
+ {
1231
+ id: 'grok-4.20-multi-agent-0309',
1232
+ aliases: ['grok-4.20-multi-agent'],
1233
+ provider: 'xai',
1234
+ displayName: 'Grok 4.20 Multi-Agent',
1235
+ inputPerMTok: 1.25,
1236
+ outputPerMTok: 2.5,
1237
+ contextWindow: 1_000_000,
1238
+ cacheMinTokens: null,
1239
+ caching: 'automatic',
1240
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.8 },
1241
+ tiers: [GROK_LONG(2.5, 5)],
1242
+ capability: 'mid',
1243
+ tier: 'sonnet',
1244
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1245
+ },
1246
+ {
1247
+ id: 'grok-build-0.1',
1248
+ provider: 'xai',
1249
+ displayName: 'Grok Build 0.1',
1250
+ inputPerMTok: 1,
1251
+ outputPerMTok: 2,
1252
+ contextWindow: 256_000,
1253
+ cacheMinTokens: null,
1254
+ caching: 'automatic',
1255
+ multipliers: { cacheRead: 0.2, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1256
+ tiers: [GROK_LONG(2, 4)],
1257
+ capability: 'mid',
1258
+ tier: 'sonnet',
1259
+ notes: NO_PUBLISHED_CACHE_MINIMUM,
1260
+ },
1261
+ {
1262
+ /*
1263
+ * Retired on 2026-05-15 at 12:00 PT (19:00 UTC), and billed since at
1264
+ * Grok 4.3's prices: "requests to the retired model slugs above will
1265
+ * automatically redirect to grok-4.3 ... you will be billed at grok-4.3
1266
+ * pricing" (xAI's retirement notice, read 2026-10-11). So the id has two
1267
+ * periods, and a log that spans the date is billed both ways, at the
1268
+ * instant xAI gave. `grok-4-0709` is the dated id the notice names.
1269
+ */
608
1270
  id: 'grok-4',
1271
+ aliases: ['grok-4-0709'],
609
1272
  provider: 'xai',
610
1273
  displayName: 'Grok 4',
611
- inputPerMTok: 3,
612
- outputPerMTok: 15,
1274
+ inputPerMTok: 1.25,
1275
+ outputPerMTok: 2.5,
613
1276
  contextWindow: 256_000,
614
- cacheMinTokens: 1024,
1277
+ cacheMinTokens: null,
615
1278
  caching: 'automatic',
616
- multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1279
+ multipliers: { cacheRead: 0.16, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1280
+ tiers: [GROK_LONG(2.5, 5)],
1281
+ history: [
1282
+ {
1283
+ until: '2026-05-15T19:00:00Z',
1284
+ inputPerMTok: 3,
1285
+ outputPerMTok: 15,
1286
+ multipliers: { cacheRead: 0.25, cacheWrite5m: 1, cacheWrite1h: 1, batch: null },
1287
+ },
1288
+ ],
1289
+ recommendable: false,
617
1290
  capability: 'large',
618
1291
  tier: 'opus',
619
1292
  notes:
620
- '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.',
1293
+ 'Retired by xAI on 2026-05-15 and redirected to Grok 4.3, whose price it bills at from then;'
1294
+ + ` $3/$15 before. ${NO_PUBLISHED_CACHE_MINIMUM}`,
621
1295
  },
622
1296
 
623
1297
  // ------------------------------------------------------------------------
@@ -667,12 +1341,12 @@ export const MODELS: ModelPricing[] = [
667
1341
  inputPerMTok: 0.5,
668
1342
  outputPerMTok: 1.5,
669
1343
  contextWindow: 262_144,
670
- cacheMinTokens: 0,
671
- caching: 'none',
672
- multipliers: { batch: 0.5 },
1344
+ cacheMinTokens: null,
1345
+ caching: 'unknown',
1346
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
673
1347
  capability: 'large',
674
1348
  tier: 'opus',
675
- notes: 'No prompt caching: reordering still helps readability but saves nothing here.',
1349
+ notes: MISTRAL_CACHE_NOTE,
676
1350
  },
677
1351
  {
678
1352
  /*
@@ -695,11 +1369,12 @@ export const MODELS: ModelPricing[] = [
695
1369
  inputPerMTok: 1.5,
696
1370
  outputPerMTok: 7.5,
697
1371
  contextWindow: 262_144,
698
- cacheMinTokens: 0,
699
- caching: 'none',
700
- multipliers: { batch: 0.5 },
1372
+ cacheMinTokens: null,
1373
+ caching: 'unknown',
1374
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
701
1375
  capability: 'mid',
702
1376
  tier: 'sonnet',
1377
+ notes: MISTRAL_CACHE_NOTE,
703
1378
  },
704
1379
  {
705
1380
  /* Same page, same day. `mistral-small-2603` is what the API reports. */
@@ -710,11 +1385,12 @@ export const MODELS: ModelPricing[] = [
710
1385
  inputPerMTok: 0.15,
711
1386
  outputPerMTok: 0.6,
712
1387
  contextWindow: 262_144,
713
- cacheMinTokens: 0,
714
- caching: 'none',
715
- multipliers: { batch: 0.5 },
1388
+ cacheMinTokens: null,
1389
+ caching: 'unknown',
1390
+ multipliers: { cacheRead: 0.1, cacheWrite5m: 1, cacheWrite1h: 1, batch: 0.5 },
716
1391
  capability: 'small',
717
1392
  tier: 'haiku',
1393
+ notes: MISTRAL_CACHE_NOTE,
718
1394
  },
719
1395
  ];
720
1396
 
@@ -871,7 +1547,38 @@ interface Rates {
871
1547
  inputPerMTok: number;
872
1548
  outputPerMTok: number;
873
1549
  promoApplied: boolean;
874
- tier: { applied: string | null; decided: boolean; because: string } | null;
1550
+ tier: {
1551
+ applied: string | null;
1552
+ decided: boolean;
1553
+ because: string;
1554
+ /**
1555
+ * The cheapest rate the undecided condition allows, present only when it
1556
+ * was not decided. The figure above is the ceiling and stays the one
1557
+ * billed; this is the other end, so a report can say how wide the
1558
+ * uncertainty is instead of only that there is some.
1559
+ */
1560
+ floor?: { inputPerMTok: number; outputPerMTok: number };
1561
+ } | null;
1562
+ }
1563
+
1564
+ /**
1565
+ * What is known about the size of the prompt being priced.
1566
+ *
1567
+ * `inputTokens` is one request's prompt, every input token it carried, cache
1568
+ * reads and writes included, and decides a size tier outright.
1569
+ *
1570
+ * `inputTokensAtMost` is a bound, for a row that sums requests: a usage
1571
+ * report's hourly or daily bucket, an export's per-day line. No single
1572
+ * request in it can be larger than the whole row, so a row whose total is at
1573
+ * or under a threshold proves every request in it was under, and that decides
1574
+ * the tier. A row over the threshold proves nothing about any one request,
1575
+ * and the tier stays undecided. The bound is never treated as a size: a day of
1576
+ * 2,000 short prompts sums past 100,000 tokens without a single request
1577
+ * getting near it.
1578
+ */
1579
+ export interface PromptSize {
1580
+ inputTokens?: number;
1581
+ inputTokensAtMost?: number;
875
1582
  }
876
1583
 
877
1584
  const pricingMemo = new WeakMap<
@@ -879,6 +1586,40 @@ const pricingMemo = new WeakMap<
879
1586
  { untilMs: number | null; promo: Rates | null; base: Rates }
880
1587
  >();
881
1588
 
1589
+ /**
1590
+ * The row as it stood at `on`: itself when the current price applies, or a
1591
+ * frozen copy carrying the period's rates, tiers and ratios. Built once per
1592
+ * period, so a log of a million calls does not build a million rows.
1593
+ */
1594
+ const periodMemo = new WeakMap<ModelPricing, { untilMs: number; row: ModelPricing }[]>();
1595
+
1596
+ function pricedAt(model: ModelPricing, on: Date): ModelPricing {
1597
+ const history = model.history;
1598
+ if (history === undefined || history.length === 0) return model;
1599
+ let periods = periodMemo.get(model);
1600
+ if (periods === undefined) {
1601
+ periods = history.map((period) => {
1602
+ const { history: _history, promo: _promo, tiers: _tiers, multipliers: _multipliers, ...rest } = model;
1603
+ const row: ModelPricing = Object.freeze({
1604
+ ...rest,
1605
+ inputPerMTok: period.inputPerMTok,
1606
+ outputPerMTok: period.outputPerMTok,
1607
+ ...(period.tiers !== undefined ? { tiers: period.tiers } : {}),
1608
+ ...(period.multipliers !== undefined
1609
+ ? { multipliers: period.multipliers }
1610
+ : model.multipliers !== undefined
1611
+ ? { multipliers: model.multipliers }
1612
+ : {}),
1613
+ });
1614
+ return { untilMs: new Date(period.until).getTime(), row };
1615
+ });
1616
+ periodMemo.set(model, periods);
1617
+ }
1618
+ const at = on.getTime();
1619
+ for (const period of periods) if (at < period.untilMs) return period.row;
1620
+ return model;
1621
+ }
1622
+
882
1623
  /** The two unconditional answers for one model, parsed once. */
883
1624
  function cache(model: ModelPricing): {
884
1625
  untilMs: number | null;
@@ -913,18 +1654,18 @@ function cache(model: ModelPricing): {
913
1654
  * because "this does not apply" and "nobody said" lead to different reports and
914
1655
  * collapsing them is the oldest mistake in this repository.
915
1656
  */
916
- function holds(
917
- when: TierCondition,
918
- on: Date,
919
- inputTokens: number | undefined,
920
- ): boolean | null {
1657
+ function holds(when: TierCondition, on: Date, size: PromptSize): boolean | null {
921
1658
  if (when.kind === 'utc-hours') {
922
1659
  const day = on.getUTCDay();
923
1660
  if (when.weekdaysOnly && (day === 0 || day === 6)) return false;
924
1661
  return when.hours.includes(on.getUTCHours());
925
1662
  }
926
- if (inputTokens === undefined) return null;
927
- return inputTokens > when.tokens;
1663
+ const over = (tokens: number): boolean =>
1664
+ when.kind === 'input-tokens-at-least' ? tokens >= when.tokens : tokens > when.tokens;
1665
+ if (size.inputTokens !== undefined) return over(size.inputTokens);
1666
+ // A whole row under the line puts every request in it under the line.
1667
+ if (size.inputTokensAtMost !== undefined && !over(size.inputTokensAtMost)) return false;
1668
+ return null;
928
1669
  }
929
1670
 
930
1671
  /**
@@ -946,8 +1687,13 @@ function holds(
946
1687
  export function effectivePricing(
947
1688
  model: ModelPricing,
948
1689
  on: Date = new Date(),
949
- context: { inputTokens?: number } = {},
1690
+ context: PromptSize = {},
950
1691
  ): Rates {
1692
+ /* A call before a published price change is priced as it was billed then. */
1693
+ if (model.history !== undefined) {
1694
+ const period = pricedAt(model, on);
1695
+ if (period !== model) return effectivePricing(period, on, context);
1696
+ }
951
1697
  const memo = pricingMemo.get(model) ?? cache(model);
952
1698
 
953
1699
  /**
@@ -961,7 +1707,7 @@ export function effectivePricing(
961
1707
  if (model.tiers !== undefined && model.tiers.length > 0) {
962
1708
  const undecided: PricingTier[] = [];
963
1709
  for (const tier of model.tiers) {
964
- const verdict = holds(tier.when, on, context.inputTokens);
1710
+ const verdict = holds(tier.when, on, context);
965
1711
  if (verdict === true) {
966
1712
  return {
967
1713
  inputPerMTok: tier.inputPerMTok,
@@ -984,6 +1730,11 @@ export function effectivePricing(
984
1730
  const dearest = undecided.reduce((worst, tier) =>
985
1731
  tier.inputPerMTok > worst.inputPerMTok ? tier : worst,
986
1732
  );
1733
+ /*
1734
+ * And the cheapest, which is the base: every undecided condition might not
1735
+ * hold, and a tier is never below the base (`pricing-tiers.test.js`).
1736
+ */
1737
+ const floor = { inputPerMTok: model.inputPerMTok, outputPerMTok: model.outputPerMTok };
987
1738
  const ceiling =
988
1739
  dearest.inputPerMTok > model.inputPerMTok
989
1740
  ? {
@@ -1003,7 +1754,11 @@ export function effectivePricing(
1003
1754
  tier: {
1004
1755
  applied: ceiling.applied,
1005
1756
  decided: false,
1006
- because: `${describe(dearest.when)}, and the token count was not given`,
1757
+ because:
1758
+ context.inputTokensAtMost !== undefined
1759
+ ? `${describe(dearest.when)}, and this row sums requests without giving any one prompt's length`
1760
+ : `${describe(dearest.when)}, and the token count was not given`,
1761
+ floor,
1007
1762
  },
1008
1763
  };
1009
1764
  }
@@ -1017,6 +1772,9 @@ function describe(when: TierCondition): string {
1017
1772
  if (when.kind === 'input-tokens-above') {
1018
1773
  return `prompts over ${when.tokens.toLocaleString('en-US')} input tokens`;
1019
1774
  }
1775
+ if (when.kind === 'input-tokens-at-least') {
1776
+ return `prompts of ${when.tokens.toLocaleString('en-US')} input tokens or more`;
1777
+ }
1020
1778
  const windows = [];
1021
1779
  let start = null;
1022
1780
  const sorted = [...when.hours].sort((a, b) => a - b);