@trazum/core 2.4.3 → 2.5.1

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 +30 -0
  58. package/dist/receipt.d.ts.map +1 -1
  59. package/dist/receipt.js +13 -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 +36 -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/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
  /*
@@ -54,9 +54,9 @@ export const PROVIDER_REVIEWED: Readonly<Record<string, string>> = Object.freeze
54
54
  * dated snapshots (`gpt-5-2025-08-07` and the mini and nano ones), which is
55
55
  * the id a usage log usually carries, shut down on 2026-12-11 and are now
56
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) is on the same
58
- * page and not added: every one of them doubles its rates for prompts over
59
- * 272K tokens, a per-request condition no log Trazum prices passes yet.
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
60
  */
61
61
  openai: '2026-10-11',
62
62
  /*
@@ -66,24 +66,20 @@ export const PROVIDER_REVIEWED: Readonly<Record<string, string>> = Object.freeze
66
66
  * for every Gemini 2.5 and newer model, so `caching` is 'automatic' across
67
67
  * the board; the minimums moved to 4,096 on 3.6 and 3.8 Flash and 2,048 on
68
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. Gemini 3.1 Pro
70
- * Preview and 2.5 Pro's prompts over 200k are priced by prompt length per
71
- * request, which nothing that prices a log passes yet, so the first is left
72
- * out and the second's tier is named in its note rather than guessed.
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.
73
72
  */
74
73
  google: '2026-10-10',
75
74
  /*
76
- * platform.kimi.ai/docs/pricing/chat (platform.moonshot.ai 301s there),
77
- * read 2026-09-12. The page prices kimi-k3, kimi-k2.6, kimi-k2.7-code and
78
- * its highspeed variant; `kimi-k2`, the one id this table carries, is no
79
- * longer on it. Its price is kept because calls in somebody's log really
80
- * happened at it, and it is not marked retired because that is recorded
81
- * from the provider's own refusal to a request, which needs a key this
82
- * repository does not hold. The newer models are not added: the page states
83
- * no context window for any of them, and a context window this table did
84
- * 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.
85
81
  */
86
- moonshot: '2026-09-12',
82
+ moonshot: '2026-10-11',
87
83
  /*
88
84
  * api-docs.deepseek.com/quick_start/pricing read 2026-10-10, and the
89
85
  * 2026-09-10 announcement (api-docs.deepseek.com/news/news260910) read
@@ -94,15 +90,14 @@ export const PROVIDER_REVIEWED: Readonly<Record<string, string>> = Object.freeze
94
90
  */
95
91
  deepseek: '2026-10-10',
96
92
  /*
97
- * docs.x.ai/docs/models, read 2026-09-12. The page prices grok-4.6, 4.5,
98
- * 4.3 and the 4.20 family, each at two rates split at 200k prompt tokens;
99
- * `grok-4`, the one id this table carries, is no longer on it. Kept and not
100
- * marked retired, for the reasons given for kimi-k2 above. The newer models
101
- * are not added: this table has no way to express a price that changes at
102
- * 200k prompt tokens, and pricing a 300k-token call at the under-200k rate
103
- * would understate it by half while looking exact.
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.
104
99
  */
105
- xai: '2026-09-12',
100
+ xai: '2026-10-11',
106
101
  /*
107
102
  * `mistral.ai/pricing`, read 2026-08-28 — by a human, because the page
108
103
  * renders its table in the browser and serves a fetch only an FAQ example
@@ -194,6 +189,14 @@ export function multipliersFor(model: ModelPricing, on?: Date): {
194
189
  return result;
195
190
  }
196
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
+
197
200
  /**
198
201
  * Mistral discounts cached input by 90% and publishes nothing else about it
199
202
  * that can be read from here: not how caching starts, not a minimum, not a
@@ -202,6 +205,32 @@ export function multipliersFor(model: ModelPricing, on?: Date): {
202
205
  * cannot be.
203
206
  */
204
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
+
205
234
  const OPENAI_GPT5_NOTE =
206
235
  'Caching is automatic, with no cache_control to set. OpenAI says the minimum for models before GPT-5.6 varies by'
207
236
  + ' request settings, so 1,024 here is the nearest published floor. The 2025-08-07 snapshot shuts down on'
@@ -408,18 +437,45 @@ export const MODELS: ModelPricing[] = [
408
437
  capability: 'mid',
409
438
  tier: 'sonnet',
410
439
  },
411
- /*
412
- * Claude Haiku 5.5 is not here, on purpose, and its calls are an unpriced
413
- * gap that names it. The pricing page (read 2026-10-10) prices it by prompt
414
- * length: $0.10/$0.50 up to 100,000 tokens and $0.50/$2.50 above, where the
415
- * length counts cache reads and writes and each request is priced on its
416
- * own. A tier can say that, but nothing that prices a usage log passes one
417
- * request's prompt length to `effectivePricing`: records are summed per
418
- * workload, model and day before they are priced. Added today, every Haiku
419
- * 5.5 call would fall to the undecided ceiling, five times the price most of
420
- * them paid. It goes in with the change that prices tiered models per
421
- * request, not before.
422
- */
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
+ },
423
479
  {
424
480
  id: 'claude-haiku-4-5',
425
481
  // The dated form is the canonical API id and is what a real usage log
@@ -447,6 +503,189 @@ export const MODELS: ModelPricing[] = [
447
503
  // one OpenAI publishes for GPT-5.6 and later; for these older models its
448
504
  // guide says the minimum "varies by request settings", so it is the nearest
449
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
+
450
689
  // ------------------------------------------------------------------------
451
690
  {
452
691
  id: 'gpt-5',
@@ -601,10 +840,119 @@ export const MODELS: ModelPricing[] = [
601
840
  'Implicit caching is automatic above 4,096 tokens. Explicit caching is also billed for storage:'
602
841
  + ' $0.50 per 1M tokens per hour through 2026-12-31, $1.00 after.',
603
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
+ },
604
881
 
605
882
  // ------------------------------------------------------------------------
606
883
  // Moonshot
607
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
+ },
608
956
  {
609
957
  id: 'kimi-k2',
610
958
  provider: 'moonshot',
@@ -612,14 +960,23 @@ export const MODELS: ModelPricing[] = [
612
960
  inputPerMTok: 0.6,
613
961
  outputPerMTok: 2.5,
614
962
  contextWindow: 256_000,
615
- cacheMinTokens: 1024,
963
+ cacheMinTokens: null,
616
964
  caching: 'automatic',
617
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,
618
975
  capability: 'mid',
619
976
  tier: 'sonnet',
620
977
  notes:
621
- 'No batch API: the batch advisory stays quiet rather than offering a discount you cannot buy.'
622
- + ' 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}`,
623
980
  },
624
981
 
625
982
  // ------------------------------------------------------------------------
@@ -766,20 +1123,175 @@ export const MODELS: ModelPricing[] = [
766
1123
  // ------------------------------------------------------------------------
767
1124
  // xAI
768
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
+ },
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
+ },
769
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
+ */
770
1270
  id: 'grok-4',
1271
+ aliases: ['grok-4-0709'],
771
1272
  provider: 'xai',
772
1273
  displayName: 'Grok 4',
773
- inputPerMTok: 3,
774
- outputPerMTok: 15,
1274
+ inputPerMTok: 1.25,
1275
+ outputPerMTok: 2.5,
775
1276
  contextWindow: 256_000,
776
- cacheMinTokens: 1024,
1277
+ cacheMinTokens: null,
777
1278
  caching: 'automatic',
778
- 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,
779
1290
  capability: 'large',
780
1291
  tier: 'opus',
781
1292
  notes:
782
- '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}`,
783
1295
  },
784
1296
 
785
1297
  // ------------------------------------------------------------------------
@@ -1035,7 +1547,38 @@ interface Rates {
1035
1547
  inputPerMTok: number;
1036
1548
  outputPerMTok: number;
1037
1549
  promoApplied: boolean;
1038
- 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;
1039
1582
  }
1040
1583
 
1041
1584
  const pricingMemo = new WeakMap<
@@ -1111,18 +1654,18 @@ function cache(model: ModelPricing): {
1111
1654
  * because "this does not apply" and "nobody said" lead to different reports and
1112
1655
  * collapsing them is the oldest mistake in this repository.
1113
1656
  */
1114
- function holds(
1115
- when: TierCondition,
1116
- on: Date,
1117
- inputTokens: number | undefined,
1118
- ): boolean | null {
1657
+ function holds(when: TierCondition, on: Date, size: PromptSize): boolean | null {
1119
1658
  if (when.kind === 'utc-hours') {
1120
1659
  const day = on.getUTCDay();
1121
1660
  if (when.weekdaysOnly && (day === 0 || day === 6)) return false;
1122
1661
  return when.hours.includes(on.getUTCHours());
1123
1662
  }
1124
- if (inputTokens === undefined) return null;
1125
- 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;
1126
1669
  }
1127
1670
 
1128
1671
  /**
@@ -1144,7 +1687,7 @@ function holds(
1144
1687
  export function effectivePricing(
1145
1688
  model: ModelPricing,
1146
1689
  on: Date = new Date(),
1147
- context: { inputTokens?: number } = {},
1690
+ context: PromptSize = {},
1148
1691
  ): Rates {
1149
1692
  /* A call before a published price change is priced as it was billed then. */
1150
1693
  if (model.history !== undefined) {
@@ -1164,7 +1707,7 @@ export function effectivePricing(
1164
1707
  if (model.tiers !== undefined && model.tiers.length > 0) {
1165
1708
  const undecided: PricingTier[] = [];
1166
1709
  for (const tier of model.tiers) {
1167
- const verdict = holds(tier.when, on, context.inputTokens);
1710
+ const verdict = holds(tier.when, on, context);
1168
1711
  if (verdict === true) {
1169
1712
  return {
1170
1713
  inputPerMTok: tier.inputPerMTok,
@@ -1187,6 +1730,11 @@ export function effectivePricing(
1187
1730
  const dearest = undecided.reduce((worst, tier) =>
1188
1731
  tier.inputPerMTok > worst.inputPerMTok ? tier : worst,
1189
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 };
1190
1738
  const ceiling =
1191
1739
  dearest.inputPerMTok > model.inputPerMTok
1192
1740
  ? {
@@ -1206,7 +1754,11 @@ export function effectivePricing(
1206
1754
  tier: {
1207
1755
  applied: ceiling.applied,
1208
1756
  decided: false,
1209
- 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,
1210
1762
  },
1211
1763
  };
1212
1764
  }
@@ -1220,6 +1772,9 @@ function describe(when: TierCondition): string {
1220
1772
  if (when.kind === 'input-tokens-above') {
1221
1773
  return `prompts over ${when.tokens.toLocaleString('en-US')} input tokens`;
1222
1774
  }
1775
+ if (when.kind === 'input-tokens-at-least') {
1776
+ return `prompts of ${when.tokens.toLocaleString('en-US')} input tokens or more`;
1777
+ }
1223
1778
  const windows = [];
1224
1779
  let start = null;
1225
1780
  const sorted = [...when.hours].sort((a, b) => a - b);