@trazum/core 2.4.3 → 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 +35 -3
  54. package/dist/pricing.d.ts.map +1 -1
  55. package/dist/pricing.js +569 -54
  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 +24 -0
  88. package/dist/types.d.ts.map +1 -1
  89. package/dist/usage.d.ts +31 -0
  90. package/dist/usage.d.ts.map +1 -1
  91. package/dist/usage.js +79 -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 +614 -59
  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 +18 -0
  121. package/src/usage.ts +112 -10
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
  /*
@@ -52,9 +52,9 @@ export const PROVIDER_REVIEWED = Object.freeze({
52
52
  * dated snapshots (`gpt-5-2025-08-07` and the mini and nano ones), which is
53
53
  * the id a usage log usually carries, shut down on 2026-12-11 and are now
54
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) is on the same
56
- * page and not added: every one of them doubles its rates for prompts over
57
- * 272K tokens, a per-request condition no log Trazum prices passes yet.
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
58
  */
59
59
  openai: '2026-10-11',
60
60
  /*
@@ -64,24 +64,20 @@ export const PROVIDER_REVIEWED = Object.freeze({
64
64
  * for every Gemini 2.5 and newer model, so `caching` is 'automatic' across
65
65
  * the board; the minimums moved to 4,096 on 3.6 and 3.8 Flash and 2,048 on
66
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. Gemini 3.1 Pro
68
- * Preview and 2.5 Pro's prompts over 200k are priced by prompt length per
69
- * request, which nothing that prices a log passes yet, so the first is left
70
- * out and the second's tier is named in its note rather than guessed.
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.
71
70
  */
72
71
  google: '2026-10-10',
73
72
  /*
74
- * platform.kimi.ai/docs/pricing/chat (platform.moonshot.ai 301s there),
75
- * read 2026-09-12. The page prices kimi-k3, kimi-k2.6, kimi-k2.7-code and
76
- * its highspeed variant; `kimi-k2`, the one id this table carries, is no
77
- * longer on it. Its price is kept because calls in somebody's log really
78
- * happened at it, and it is not marked retired because that is recorded
79
- * from the provider's own refusal to a request, which needs a key this
80
- * repository does not hold. The newer models are not added: the page states
81
- * no context window for any of them, and a context window this table did
82
- * 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.
83
79
  */
84
- moonshot: '2026-09-12',
80
+ moonshot: '2026-10-11',
85
81
  /*
86
82
  * api-docs.deepseek.com/quick_start/pricing read 2026-10-10, and the
87
83
  * 2026-09-10 announcement (api-docs.deepseek.com/news/news260910) read
@@ -92,15 +88,14 @@ export const PROVIDER_REVIEWED = Object.freeze({
92
88
  */
93
89
  deepseek: '2026-10-10',
94
90
  /*
95
- * docs.x.ai/docs/models, read 2026-09-12. The page prices grok-4.6, 4.5,
96
- * 4.3 and the 4.20 family, each at two rates split at 200k prompt tokens;
97
- * `grok-4`, the one id this table carries, is no longer on it. Kept and not
98
- * marked retired, for the reasons given for kimi-k2 above. The newer models
99
- * are not added: this table has no way to express a price that changes at
100
- * 200k prompt tokens, and pricing a 300k-token call at the under-200k rate
101
- * would understate it by half while looking exact.
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.
102
97
  */
103
- xai: '2026-09-12',
98
+ xai: '2026-10-11',
104
99
  /*
105
100
  * `mistral.ai/pricing`, read 2026-08-28 — by a human, because the page
106
101
  * renders its table in the browser and serves a fetch only an FAQ example
@@ -179,6 +174,12 @@ export function multipliersFor(model, on) {
179
174
  multipliersMemo.set(model, result);
180
175
  return result;
181
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
+ }
182
183
  /**
183
184
  * Mistral discounts cached input by 90% and publishes nothing else about it
184
185
  * that can be read from here: not how caching starts, not a minimum, not a
@@ -187,6 +188,26 @@ export function multipliersFor(model, on) {
187
188
  * cannot be.
188
189
  */
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.';
190
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'
191
212
  + ' request settings, so 1,024 here is the nearest published floor. The 2025-08-07 snapshot shuts down on'
192
213
  + ' 2026-12-11; OpenAI names the gpt-5.6 models as replacements.';
@@ -386,18 +407,44 @@ export const MODELS = [
386
407
  capability: 'mid',
387
408
  tier: 'sonnet',
388
409
  },
389
- /*
390
- * Claude Haiku 5.5 is not here, on purpose, and its calls are an unpriced
391
- * gap that names it. The pricing page (read 2026-10-10) prices it by prompt
392
- * length: $0.10/$0.50 up to 100,000 tokens and $0.50/$2.50 above, where the
393
- * length counts cache reads and writes and each request is priced on its
394
- * own. A tier can say that, but nothing that prices a usage log passes one
395
- * request's prompt length to `effectivePricing`: records are summed per
396
- * workload, model and day before they are priced. Added today, every Haiku
397
- * 5.5 call would fall to the undecided ceiling, five times the price most of
398
- * them paid. It goes in with the change that prices tiered models per
399
- * request, not before.
400
- */
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
+ },
401
448
  {
402
449
  id: 'claude-haiku-4-5',
403
450
  // The dated form is the canonical API id and is what a real usage log
@@ -425,6 +472,188 @@ export const MODELS = [
425
472
  // guide says the minimum "varies by request settings", so it is the nearest
426
473
  // published floor, not a promise, and each row's note says so.
427
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
+ },
656
+ // ------------------------------------------------------------------------
428
657
  {
429
658
  id: 'gpt-5',
430
659
  aliases: ['gpt-5-2025-08-07'],
@@ -571,9 +800,116 @@ export const MODELS = [
571
800
  notes: 'Implicit caching is automatic above 4,096 tokens. Explicit caching is also billed for storage:'
572
801
  + ' $0.50 per 1M tokens per hour through 2026-12-31, $1.00 after.',
573
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
+ },
574
840
  // ------------------------------------------------------------------------
575
841
  // Moonshot
576
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
+ },
577
913
  {
578
914
  id: 'kimi-k2',
579
915
  provider: 'moonshot',
@@ -581,13 +917,22 @@ export const MODELS = [
581
917
  inputPerMTok: 0.6,
582
918
  outputPerMTok: 2.5,
583
919
  contextWindow: 256_000,
584
- cacheMinTokens: 1024,
920
+ cacheMinTokens: null,
585
921
  caching: 'automatic',
586
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,
587
932
  capability: 'mid',
588
933
  tier: 'sonnet',
589
- notes: 'No batch API: the batch advisory stays quiet rather than offering a discount you cannot buy.'
590
- + ' 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}`,
591
936
  },
592
937
  // ------------------------------------------------------------------------
593
938
  // DeepSeek
@@ -729,19 +1074,174 @@ export const MODELS = [
729
1074
  // ------------------------------------------------------------------------
730
1075
  // xAI
731
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
+ },
732
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
+ */
733
1221
  id: 'grok-4',
1222
+ aliases: ['grok-4-0709'],
734
1223
  provider: 'xai',
735
1224
  displayName: 'Grok 4',
736
- inputPerMTok: 3,
737
- outputPerMTok: 15,
1225
+ inputPerMTok: 1.25,
1226
+ outputPerMTok: 2.5,
738
1227
  contextWindow: 256_000,
739
- cacheMinTokens: 1024,
1228
+ cacheMinTokens: null,
740
1229
  caching: 'automatic',
741
- 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,
742
1241
  capability: 'large',
743
1242
  tier: 'opus',
744
- 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}`,
745
1245
  },
746
1246
  // ------------------------------------------------------------------------
747
1247
  // Mistral
@@ -993,16 +1493,20 @@ function cache(model) {
993
1493
  * because "this does not apply" and "nobody said" lead to different reports and
994
1494
  * collapsing them is the oldest mistake in this repository.
995
1495
  */
996
- function holds(when, on, inputTokens) {
1496
+ function holds(when, on, size) {
997
1497
  if (when.kind === 'utc-hours') {
998
1498
  const day = on.getUTCDay();
999
1499
  if (when.weekdaysOnly && (day === 0 || day === 6))
1000
1500
  return false;
1001
1501
  return when.hours.includes(on.getUTCHours());
1002
1502
  }
1003
- if (inputTokens === undefined)
1004
- return null;
1005
- 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;
1006
1510
  }
1007
1511
  /**
1008
1512
  * What one call is billed at, and what decided it.
@@ -1039,7 +1543,7 @@ export function effectivePricing(model, on = new Date(), context = {}) {
1039
1543
  if (model.tiers !== undefined && model.tiers.length > 0) {
1040
1544
  const undecided = [];
1041
1545
  for (const tier of model.tiers) {
1042
- const verdict = holds(tier.when, on, context.inputTokens);
1546
+ const verdict = holds(tier.when, on, context);
1043
1547
  if (verdict === true) {
1044
1548
  return {
1045
1549
  inputPerMTok: tier.inputPerMTok,
@@ -1059,6 +1563,11 @@ export function effectivePricing(model, on = new Date(), context = {}) {
1059
1563
  }
1060
1564
  /* The dearest candidate, so the figure is a ceiling rather than a wish. */
1061
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 };
1062
1571
  const ceiling = dearest.inputPerMTok > model.inputPerMTok
1063
1572
  ? {
1064
1573
  inputPerMTok: dearest.inputPerMTok,
@@ -1077,7 +1586,10 @@ export function effectivePricing(model, on = new Date(), context = {}) {
1077
1586
  tier: {
1078
1587
  applied: ceiling.applied,
1079
1588
  decided: false,
1080
- 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,
1081
1593
  },
1082
1594
  };
1083
1595
  }
@@ -1090,6 +1602,9 @@ function describe(when) {
1090
1602
  if (when.kind === 'input-tokens-above') {
1091
1603
  return `prompts over ${when.tokens.toLocaleString('en-US')} input tokens`;
1092
1604
  }
1605
+ if (when.kind === 'input-tokens-at-least') {
1606
+ return `prompts of ${when.tokens.toLocaleString('en-US')} input tokens or more`;
1607
+ }
1093
1608
  const windows = [];
1094
1609
  let start = null;
1095
1610
  const sorted = [...when.hours].sort((a, b) => a - b);