@trazum/core 1.81.0 → 1.83.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 (84) hide show
  1. package/README.md +34 -18
  2. package/dist/advisories.d.ts +33 -1
  3. package/dist/advisories.d.ts.map +1 -1
  4. package/dist/advisories.js +130 -20
  5. package/dist/advisories.js.map +1 -1
  6. package/dist/band.d.ts +131 -0
  7. package/dist/band.d.ts.map +1 -0
  8. package/dist/band.js +210 -0
  9. package/dist/band.js.map +1 -0
  10. package/dist/conform.d.ts +1 -1
  11. package/dist/conform.d.ts.map +1 -1
  12. package/dist/conform.js +84 -1
  13. package/dist/conform.js.map +1 -1
  14. package/dist/helicone.d.ts +92 -0
  15. package/dist/helicone.d.ts.map +1 -0
  16. package/dist/helicone.js +194 -0
  17. package/dist/helicone.js.map +1 -0
  18. package/dist/i18n/en.d.ts.map +1 -1
  19. package/dist/i18n/en.js +16 -3
  20. package/dist/i18n/en.js.map +1 -1
  21. package/dist/i18n/es.d.ts.map +1 -1
  22. package/dist/i18n/es.js +17 -3
  23. package/dist/i18n/es.js.map +1 -1
  24. package/dist/i18n/types.d.ts +60 -1
  25. package/dist/i18n/types.d.ts.map +1 -1
  26. package/dist/index.d.ts +16 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +9 -3
  29. package/dist/index.js.map +1 -1
  30. package/dist/langsmith.d.ts +104 -0
  31. package/dist/langsmith.d.ts.map +1 -0
  32. package/dist/langsmith.js +270 -0
  33. package/dist/langsmith.js.map +1 -0
  34. package/dist/levers.d.ts +1 -1
  35. package/dist/levers.js +2 -2
  36. package/dist/levers.js.map +1 -1
  37. package/dist/litellm.d.ts +98 -0
  38. package/dist/litellm.d.ts.map +1 -0
  39. package/dist/litellm.js +207 -0
  40. package/dist/litellm.js.map +1 -0
  41. package/dist/measured-profile.d.ts +4 -0
  42. package/dist/measured-profile.d.ts.map +1 -1
  43. package/dist/measured-profile.js +22 -1
  44. package/dist/measured-profile.js.map +1 -1
  45. package/dist/pricing.d.ts +98 -3
  46. package/dist/pricing.d.ts.map +1 -1
  47. package/dist/pricing.js +393 -35
  48. package/dist/pricing.js.map +1 -1
  49. package/dist/receipt.d.ts +253 -0
  50. package/dist/receipt.d.ts.map +1 -0
  51. package/dist/receipt.js +111 -0
  52. package/dist/receipt.js.map +1 -0
  53. package/dist/schemas.d.ts.map +1 -1
  54. package/dist/schemas.js +36 -0
  55. package/dist/schemas.js.map +1 -1
  56. package/dist/tokenizer.d.ts +45 -49
  57. package/dist/tokenizer.d.ts.map +1 -1
  58. package/dist/tokenizer.js +100 -51
  59. package/dist/tokenizer.js.map +1 -1
  60. package/dist/types.d.ts +108 -1
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/verdict-bridge.d.ts +113 -0
  63. package/dist/verdict-bridge.d.ts.map +1 -0
  64. package/dist/verdict-bridge.js +127 -0
  65. package/dist/verdict-bridge.js.map +1 -0
  66. package/package.json +1 -1
  67. package/src/advisories.ts +166 -19
  68. package/src/band.ts +213 -0
  69. package/src/conform.ts +100 -1
  70. package/src/helicone.ts +227 -0
  71. package/src/i18n/en.ts +19 -3
  72. package/src/i18n/es.ts +20 -3
  73. package/src/i18n/types.ts +63 -1
  74. package/src/index.ts +31 -2
  75. package/src/langsmith.ts +315 -0
  76. package/src/levers.ts +3 -3
  77. package/src/litellm.ts +243 -0
  78. package/src/measured-profile.ts +26 -2
  79. package/src/pricing.ts +430 -30
  80. package/src/receipt.ts +351 -0
  81. package/src/schemas.ts +36 -0
  82. package/src/tokenizer.ts +101 -51
  83. package/src/types.ts +104 -0
  84. package/src/verdict-bridge.ts +200 -0
@@ -0,0 +1,207 @@
1
+ /**
2
+ * LiteLLM's spend log, read as a usage log.
3
+ *
4
+ * `from-claude-code` proved the pattern and `from-otel` generalised it: a pure
5
+ * converter turns one tool's export into Trazum's usage-log records, and every
6
+ * door prices it from there. LiteLLM is the gateway a great many teams already
7
+ * put in front of every provider, so its `LiteLLM_SpendLogs` table is the one
8
+ * export most likely to already exist on somebody's disk.
9
+ *
10
+ * **The format is derived, not guessed.** Every field below is read off
11
+ * `litellm/proxy/schema.prisma` in BerriAI/litellm, model `LiteLLM_SpendLogs`.
12
+ * A converter written from memory of an API is a converter that silently
13
+ * mis-reads somebody's bill, and this project has one rule above all others
14
+ * about numbers it cannot justify.
15
+ *
16
+ * ## What maps
17
+ *
18
+ * | LiteLLM | Trazum |
19
+ * | --- | --- |
20
+ * | `model` | `model` |
21
+ * | `prompt_tokens` | `usage.input_tokens` |
22
+ * | `completion_tokens` | `usage.output_tokens` |
23
+ * | `startTime` | `ts` |
24
+ * | `session_id` | `session` |
25
+ * | `request_tags[0]`, else `metadata.tags[0]`, else `model_group` | `label` |
26
+ *
27
+ * ## What deliberately does not cross
28
+ *
29
+ * The row carries `messages` and `response` — the prompt itself and the
30
+ * completion — plus `api_key` (hashed, still credential-shaped),
31
+ * `requester_ip_address`, `user` and `end_user`. **None of it is read.** The
32
+ * converter names the fields it takes and takes nothing else, and a fixture
33
+ * plants a marker in each of them and greps the whole output.
34
+ *
35
+ * ## What it refuses to invent
36
+ *
37
+ * `cache_hit` is a flag and `cache_key` an identifier; neither is a token
38
+ * count. A converted record therefore carries no cache split at all, and the
39
+ * caching questions come back `cannot-tell` rather than answered from a
40
+ * guess — the same refusal as inventing a price. `cacheFlagged` counts the
41
+ * rows that said "hit" so the operator can see the gap is real rather than
42
+ * empty.
43
+ *
44
+ * `spend` is LiteLLM's own priced figure and is **never** merged into
45
+ * Trazum's catalogue-priced total. It is a second measurement of the same
46
+ * calls, kept apart the way the store's provider-billed standing is kept
47
+ * apart from the log's, and returned on its own so a caller who wants to
48
+ * compare the two can, deliberately.
49
+ */
50
+ const asObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value)
51
+ ? value
52
+ : null;
53
+ const tryParse = (text) => {
54
+ try {
55
+ return JSON.parse(text);
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ };
61
+ const asCount = (value) => {
62
+ const n = typeof value === 'string' ? Number(value) : value;
63
+ return typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : undefined;
64
+ };
65
+ /**
66
+ * The first tag LiteLLM carries for a row, from the two places it puts them.
67
+ *
68
+ * `request_tags` is the documented column and `metadata.tags` is where the
69
+ * proxy writes them when the caller passes them in the request body. First
70
+ * only: a workload has one name in a bill, and joining several tags into one
71
+ * label would invent a workload that does not exist.
72
+ */
73
+ function labelOf(row) {
74
+ const fromColumn = Array.isArray(row.request_tags) ? row.request_tags : null;
75
+ if (fromColumn !== null && typeof fromColumn[0] === 'string' && fromColumn[0] !== '') {
76
+ return fromColumn[0];
77
+ }
78
+ const metadata = asObject(row.metadata);
79
+ const fromMetadata = metadata !== null && Array.isArray(metadata.tags) ? metadata.tags : null;
80
+ if (fromMetadata !== null && typeof fromMetadata[0] === 'string' && fromMetadata[0] !== '') {
81
+ return fromMetadata[0];
82
+ }
83
+ // The public model name a proxy route is known by. A weaker label than a
84
+ // tag — it groups by route rather than by workload — and better than none.
85
+ if (typeof row.model_group === 'string' && row.model_group !== '')
86
+ return row.model_group;
87
+ return undefined;
88
+ }
89
+ /** `startTime` as an ISO instant, or undefined when it is not a time. */
90
+ function timeOf(row) {
91
+ const raw = row.startTime ?? row.start_time;
92
+ if (typeof raw !== 'string' && typeof raw !== 'number')
93
+ return undefined;
94
+ const ms = typeof raw === 'number' ? raw : Date.parse(raw);
95
+ if (!Number.isFinite(ms))
96
+ return undefined;
97
+ return new Date(ms).toISOString();
98
+ }
99
+ /**
100
+ * Convert a LiteLLM spend-log export.
101
+ *
102
+ * Accepts a JSON array of rows, a single row, `{ data: [...] }` as the proxy's
103
+ * own endpoints return it, or newline-delimited rows — so a `psql --json`
104
+ * dump, an API response and a streamed capture all work. Pure over its input.
105
+ */
106
+ export function litellmRecords(text) {
107
+ const out = {
108
+ records: [],
109
+ rows: 0,
110
+ unnamedModel: 0,
111
+ noTokens: 0,
112
+ cacheFlagged: 0,
113
+ reportedSpendUsd: null,
114
+ unparseable: 0,
115
+ };
116
+ const rows = [];
117
+ const whole = tryParse(text);
118
+ if (whole !== undefined) {
119
+ if (Array.isArray(whole))
120
+ rows.push(...whole);
121
+ else {
122
+ const doc = asObject(whole);
123
+ // `{ data: [...] }` is what the proxy's spend endpoints return.
124
+ if (doc !== null && Array.isArray(doc.data))
125
+ rows.push(...doc.data);
126
+ else if (doc !== null)
127
+ rows.push(doc);
128
+ }
129
+ }
130
+ else {
131
+ for (const line of text.split('\n')) {
132
+ if (line.trim() === '')
133
+ continue;
134
+ const parsed = tryParse(line);
135
+ if (parsed === undefined)
136
+ out.unparseable += 1;
137
+ else if (Array.isArray(parsed))
138
+ rows.push(...parsed);
139
+ else
140
+ rows.push(parsed);
141
+ }
142
+ }
143
+ let spend = 0;
144
+ let sawSpend = false;
145
+ for (const raw of rows) {
146
+ const row = asObject(raw);
147
+ if (row === null)
148
+ continue;
149
+ // A spend log row is one that carries at least one of the two token
150
+ // columns. Anything else in the same file is not a call and is skipped
151
+ // rather than converted into a record of zero.
152
+ const input = asCount(row.prompt_tokens);
153
+ const output = asCount(row.completion_tokens);
154
+ if (input === undefined && output === undefined)
155
+ continue;
156
+ out.rows += 1;
157
+ if (input === 0 && output === 0)
158
+ out.noTokens += 1;
159
+ if (row.cache_hit === true || row.cache_hit === 'true' || row.cache_hit === 'True') {
160
+ out.cacheFlagged += 1;
161
+ }
162
+ const reported = asCount(row.spend);
163
+ if (reported !== undefined) {
164
+ spend += reported;
165
+ sawSpend = true;
166
+ }
167
+ const model = typeof row.model === 'string' ? row.model.trim() : '';
168
+ if (model === '') {
169
+ // Counted, never guessed at. `model_group` is a route name, not a model,
170
+ // and pricing a call by the route it took would be a figure attributed
171
+ // to something it does not describe.
172
+ out.unnamedModel += 1;
173
+ continue;
174
+ }
175
+ const ts = timeOf(row);
176
+ const label = labelOf(row);
177
+ const session = typeof row.session_id === 'string' && row.session_id !== ''
178
+ ? row.session_id
179
+ : undefined;
180
+ out.records.push({
181
+ model,
182
+ ...(ts !== undefined ? { ts } : {}),
183
+ ...(label !== undefined ? { label } : {}),
184
+ ...(session !== undefined ? { session } : {}),
185
+ usage: { input_tokens: input ?? 0, output_tokens: output ?? 0 },
186
+ });
187
+ }
188
+ out.reportedSpendUsd = sawSpend ? spend : null;
189
+ return out;
190
+ }
191
+ /**
192
+ * Whether a file looks like a LiteLLM spend log, by shape.
193
+ *
194
+ * Deliberately narrow: `request_id` beside one of the two token columns, and
195
+ * a field no other export in this project carries. A looser test would claim
196
+ * an OpenAI usage response, whose rows also have `prompt_tokens`, and refuse
197
+ * it in this file's words instead of letting the code that can read it try.
198
+ */
199
+ export function looksLikeLiteLlm(text, prefixBytes = 8192) {
200
+ const head = text.slice(0, prefixBytes);
201
+ if (!head.includes('prompt_tokens') && !head.includes('completion_tokens'))
202
+ return false;
203
+ return (head.includes('"request_id"') ||
204
+ head.includes('"custom_llm_provider"') ||
205
+ head.includes('"model_group"'));
206
+ }
207
+ //# sourceMappingURL=litellm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"litellm.js","sourceRoot":"","sources":["../src/litellm.ts"],"names":[],"mappings":"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"}
@@ -68,6 +68,10 @@ export interface MeasuredUsage {
68
68
  * call is the same size. It is handed over as the better of two
69
69
  * approximations — a measured token share beats a typed call share — and
70
70
  * named honestly here so no rendering can call it something it is not.
71
+ *
72
+ * The denominator is every input token, **writes included**. Leaving writes
73
+ * out made a constantly-rewritten cache report a high hit rate, which is the
74
+ * one workload that most needs to be told otherwise.
71
75
  */
72
76
  cacheReadShare: number;
73
77
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"measured-profile.d.ts","sourceRoot":"","sources":["../src/measured-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,iFAAiF;AACjF,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,OAAO,EAAE,YAAY,CAAC;IACtB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;;OAKG;IACH,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpD;;;;;;;;;OASG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,aAAa,GAAG,IAAI,CAuDtB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,yEAAyE;IACzE,oBAAoB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9D,2EAA2E;IAC3E,oBAAoB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7D;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,aAAa,CAsBf"}
1
+ {"version":3,"file":"measured-profile.d.ts","sourceRoot":"","sources":["../src/measured-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,iFAAiF;AACjF,eAAO,MAAM,aAAa,KAAK,CAAC;AAEhC,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,OAAO,EAAE,YAAY,CAAC;IACtB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;;OAKG;IACH,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpD;;;;;;;;;;;;;OAaG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,aAAa,GAAG,IAAI,CA2EtB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,yEAAyE;IACzE,oBAAoB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9D,2EAA2E;IAC3E,oBAAoB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7D;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,aAAa,CAsBf"}
@@ -55,6 +55,7 @@ export function measuredUsage(report, label, options = {}) {
55
55
  const outputTokens = slices.reduce((sum, row) => sum + row.breakdown.outputTokens, 0);
56
56
  const inputTokens = slices.reduce((sum, row) => sum + row.breakdown.inputTokens, 0);
57
57
  const cacheReadTokens = slices.reduce((sum, row) => sum + row.breakdown.cacheReadTokens, 0);
58
+ const cacheWriteTokens = slices.reduce((sum, row) => sum + row.breakdown.cacheWriteTokens, 0);
58
59
  // The model carrying the most spend. Ties break on the larger call count, so
59
60
  // the answer is stable rather than dependent on map ordering.
60
61
  const ranked = [...slices].sort((a, b) => b.breakdown.totalUsd - a.breakdown.totalUsd || b.breakdown.calls - a.breakdown.calls);
@@ -69,7 +70,27 @@ export function measuredUsage(report, label, options = {}) {
69
70
  const scaled = spanDays !== null && spanDays >= MIN_SCALE_DAYS
70
71
  ? { fromDays: spanDays, factor: SCALE_TO_DAYS / spanDays }
71
72
  : null;
72
- const readShare = inputTokens + cacheReadTokens > 0 ? cacheReadTokens / (inputTokens + cacheReadTokens) : 0;
73
+ /**
74
+ * Every input token in the denominator, writes included.
75
+ *
76
+ * This was `reads / (input + reads)`, which leaves cache *writes* out of the
77
+ * total it is a share of. A workload that rewrites its prefix on every call
78
+ * and reads it back rarely then reports a **high** hit rate: with 100 read
79
+ * tokens, no plain input and 10,000 written, the old expression answers 100%
80
+ * where the truth is 1%.
81
+ *
82
+ * That is the worst direction for this number to be wrong in. It is handed
83
+ * to `optimize` as `cacheHitRate`, which decides whether caching is paying
84
+ * off — so the shape that is burning money on writes was the shape most
85
+ * likely to be told its cache was working perfectly.
86
+ *
87
+ * Writes are input tokens: they are billed at the input rate times the write
88
+ * multiplier, they arrive in the same `usage` block, and any accounting of
89
+ * "what share of the input came from cache" that omits them is answering a
90
+ * narrower question than the one it is named for.
91
+ */
92
+ const allInputTokens = inputTokens + cacheReadTokens + cacheWriteTokens;
93
+ const readShare = allInputTokens > 0 ? cacheReadTokens / allInputTokens : 0;
73
94
  return {
74
95
  profile: {
75
96
  model: chosen.model,
@@ -1 +1 @@
1
- {"version":3,"file":"measured-profile.js","sourceRoot":"","sources":["../src/measured-profile.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"measured-profile.js","sourceRoot":"","sources":["../src/measured-profile.ts"],"names":[],"mappings":"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"}
package/dist/pricing.d.ts CHANGED
@@ -7,7 +7,33 @@ import type { ModelPricing } from './types.js';
7
7
  * Amazon Bedrock and Vertex AI pricing is set by each partner and is NOT the
8
8
  * pricing in this table.
9
9
  */
10
- export declare const PRICING_LAST_REVIEWED = "2026-06-24";
10
+ /**
11
+ * When each provider's prices were last checked against that provider's own
12
+ * published table.
13
+ *
14
+ * One date for the whole catalogue was the wrong shape, and it cost a real
15
+ * error. Seven providers publish independently and are checked independently;
16
+ * a single date forces a partial review to either overstate itself by moving
17
+ * the date for everyone, or throw itself away by leaving it. What happened is
18
+ * the second: this constant was written on 2026-08-04 already reading
19
+ * 2026-06-24, and never moved again.
20
+ *
21
+ * Meanwhile Anthropic cancelled the increase that was going to take Sonnet 5
22
+ * from its 2/10 introductory price to 3/15 on 2026-09-01, and made 2/10
23
+ * standard. The catalogue still carried the promotion with its end date, so on
24
+ * 2026-09-01 every Sonnet 5 figure this tool printed would have risen 50% with
25
+ * no code change and no way for a reader to tell. A price nobody charges is
26
+ * the one error this product cannot make, and it was four days out on a timer.
27
+ *
28
+ * So: a date per provider, checked against that provider's own page, and the
29
+ * catalogue's headline date derived as the oldest of them rather than typed.
30
+ */
31
+ export declare const PROVIDER_REVIEWED: Readonly<Record<string, string>>;
32
+ /**
33
+ * The catalogue's age is its oldest provider's, because a reader asking how
34
+ * old this table is wants the answer for the worst part of it, not the best.
35
+ */
36
+ export declare const PRICING_LAST_REVIEWED: string;
11
37
  /** Cost multipliers relative to the input price. */
12
38
  /**
13
39
  * The defaults, which are Anthropic's numbers.
@@ -75,6 +101,25 @@ export interface PricingCatalogue {
75
101
  export declare function indexModels(models: ModelPricing[]): Map<string, ModelPricing>;
76
102
  /** The prices compiled into this release. */
77
103
  export declare const BUNDLED_CATALOGUE: PricingCatalogue;
104
+ /**
105
+ * Whether this model may be offered to a reader as something to move to.
106
+ *
107
+ * One home for a two-part rule that used to be written in four places as one
108
+ * part. `recommendable: false` — a private programme, a waitlist — was honoured
109
+ * at every one of them. `retired` is the stronger statement: the provider
110
+ * answered a real request for the id with an error, so a switch to it is not a
111
+ * worse recommendation, it is one that cannot be carried out at all.
112
+ *
113
+ * Written as a function rather than as a second `&&` at four call sites,
114
+ * because the fifth call site is always the one that gets written with only the
115
+ * first half. `pricing.test.js` fails a source file that filters on
116
+ * `recommendable` without coming through here, and that guard was proved by
117
+ * planting exactly that filter.
118
+ *
119
+ * Pricing is a separate question and stays answered: a retired model keeps its
120
+ * price, because a log full of its calls records money that was really spent.
121
+ */
122
+ export declare function isOffered(model: ModelPricing): boolean;
78
123
  /** Looks a model up in a specific catalogue. */
79
124
  export declare function modelFrom(catalogue: PricingCatalogue, id: string): ModelPricing;
80
125
  /**
@@ -93,11 +138,46 @@ export declare function cheapestOfTierIn(catalogue: PricingCatalogue, tier: Mode
93
138
  provider?: string): ModelPricing;
94
139
  export declare function getModel(id: string): ModelPricing;
95
140
  export declare function listModels(): ModelPricing[];
96
- export declare function effectivePricing(model: ModelPricing, on?: Date): {
141
+ /** Effective price on a given date, applying any live promotion. */
142
+ /**
143
+ * Memoised per model, because this runs once per record when a usage log is
144
+ * priced: a 200k-line profile called it 200k times, and the `new Date(...)`
145
+ * parse of the promo's end date alone was 0.7 of the 2.4 seconds the whole
146
+ * profile took — measured with --cpu-prof, not guessed. The cache keys on
147
+ * the model object (a WeakMap, so an overlay's models are collected with
148
+ * the overlay) and stores the parsed end-of-promo instant plus the two
149
+ * possible answers; `on` still decides which answer applies on every call,
150
+ * so behaviour is unchanged to the millisecond.
151
+ */
152
+ interface Rates {
97
153
  inputPerMTok: number;
98
154
  outputPerMTok: number;
99
155
  promoApplied: boolean;
100
- };
156
+ tier: {
157
+ applied: string | null;
158
+ decided: boolean;
159
+ because: string;
160
+ } | null;
161
+ }
162
+ /**
163
+ * What one call is billed at, and what decided it.
164
+ *
165
+ * `tier` is the field this grew for, and it carries three states rather than
166
+ * two. A model with no tiers gets `null`. A model whose condition could be
167
+ * evaluated gets the tier that applied, or the base rate with `applied: null`
168
+ * and `decided: true`. A model whose condition could **not** be evaluated —
169
+ * a size tier priced without a token count — gets `decided: false`, the
170
+ * **dearer** of the candidate rates, and `because` naming what would settle it.
171
+ *
172
+ * The dearer one is not a shrug. A cost figure that is a ceiling can prove
173
+ * "under budget" and can never surprise a bill; a floor chosen for looking
174
+ * better is the flattering direction this product spends its time refusing.
175
+ * The reader still has to be told, which is what `decided: false` is for, and
176
+ * `pricing.test.js` fails a tiered model whose report drops it.
177
+ */
178
+ export declare function effectivePricing(model: ModelPricing, on?: Date, context?: {
179
+ inputTokens?: number;
180
+ }): Rates;
101
181
  /** Cheapest model of each capability tier, for recommendations. */
102
182
  export declare function cheapestOfTier(tier: ModelPricing['tier']): ModelPricing;
103
183
  /**
@@ -116,5 +196,20 @@ export declare function cheapestOfTier(tier: ModelPricing['tier']): ModelPricing
116
196
  * a date — an overlay supplies this string, and a wrong one should read as
117
197
  * unknown rather than as a confident number computed from `NaN`.
118
198
  */
199
+ /**
200
+ * The age at which this product stops treating its own price table as current.
201
+ *
202
+ * Three surfaces warn a reader that the figures above may be off — `profile`,
203
+ * the MCP report, the browser's bill — and each of them had typed `45` into
204
+ * its own comparison, with four locale sentences stating it again in prose.
205
+ * Seven copies of one claim, and nothing that would notice when they
206
+ * disagreed; the sentence a reader sees names the number, so a drift between
207
+ * them would have been a surface telling the reader one threshold and applying
208
+ * another.
209
+ *
210
+ * The value is what those surfaces already published, not a new judgement.
211
+ */
212
+ export declare const STALE_PRICING_DAYS = 45;
119
213
  export declare function reviewAgeDays(lastReviewed: string, now: Date): number | null;
214
+ export {};
120
215
  //# sourceMappingURL=pricing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD,oDAAoD;AACpD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB;IAC3B,uCAAuC;aACvC,YAAY,EAAE,IAAI;IAClB,qCAAqC;aACrC,YAAY,EAAE,CAAG;IACjB,2CAA2C;aAC3C,SAAS,EAAE,GAAG;IACd,mDAAmD;aACnD,KAAK,EAAE,GAAG;CACF,CAAC;AAeX,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAYA;AAED,eAAO,MAAM,MAAM,EAAE,YAAY,EAqQhC,CAAC;AAEF,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChC,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,0EAA0E;IAC1E,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAK7E;AAiBD,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,EAAE,gBAA+D,CAAC;AAEhG,gDAAgD;AAChD,wBAAgB,SAAS,CAAC,SAAS,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,YAAY,CAQ/E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,gBAAgB,EAC3B,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,EAC1B,EAAE,GAAE,IAAiB;AACrB;;;;;GAKG;AACH,QAAQ,CAAC,EAAE,MAAM,GAChB,YAAY,CAcd;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,CAEjD;AAED,wBAAgB,UAAU,IAAI,YAAY,EAAE,CAE3C;AAsBD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,YAAY,EACnB,EAAE,GAAE,IAAiB,GACpB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAuBxE;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,YAAY,CAKvE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAU5E"}
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}