context-mode 1.0.166 → 1.0.167

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 (52) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  5. package/.openclaw-plugin/package.json +1 -1
  6. package/README.md +6 -4
  7. package/build/adapters/codex/usage.d.ts +107 -0
  8. package/build/adapters/codex/usage.js +227 -0
  9. package/build/adapters/gemini-cli/hooks.d.ts +7 -1
  10. package/build/adapters/gemini-cli/hooks.js +9 -1
  11. package/build/adapters/gemini-cli/index.js +11 -0
  12. package/build/adapters/kimi/paths.d.ts +20 -0
  13. package/build/adapters/kimi/paths.js +41 -1
  14. package/build/adapters/kimi/usage.d.ts +82 -0
  15. package/build/adapters/kimi/usage.js +217 -0
  16. package/build/adapters/omp/plugin.d.ts +6 -0
  17. package/build/adapters/omp/plugin.js +87 -2
  18. package/build/adapters/omp/usage.d.ts +49 -0
  19. package/build/adapters/omp/usage.js +110 -0
  20. package/build/adapters/openclaw/plugin.d.ts +10 -0
  21. package/build/adapters/openclaw/plugin.js +57 -0
  22. package/build/adapters/openclaw/usage.d.ts +34 -0
  23. package/build/adapters/openclaw/usage.js +52 -0
  24. package/build/adapters/opencode/plugin.d.ts +17 -0
  25. package/build/adapters/opencode/plugin.js +40 -1
  26. package/build/adapters/pi/extension.js +34 -1
  27. package/build/adapters/qwen-code/index.js +23 -1
  28. package/build/adapters/qwen-code/usage.d.ts +90 -0
  29. package/build/adapters/qwen-code/usage.js +222 -0
  30. package/build/session/db.d.ts +11 -0
  31. package/build/session/db.js +33 -0
  32. package/build/session/extract.d.ts +208 -0
  33. package/build/session/extract.js +670 -43
  34. package/build/session/model-prices.json +429 -0
  35. package/build/session/pricing.d.ts +64 -0
  36. package/build/session/pricing.js +151 -0
  37. package/cli.bundle.mjs +62 -62
  38. package/configs/antigravity-cli/plugin.json +1 -1
  39. package/configs/copilot-cli/.github/plugin/plugin.json +1 -1
  40. package/configs/gemini-cli/settings.json +11 -0
  41. package/hooks/codex/stop.mjs +91 -4
  42. package/hooks/gemini-cli/aftermodel.mjs +70 -0
  43. package/hooks/kimi/stop.mjs +74 -3
  44. package/hooks/qwen-code/platform.mjs +1 -0
  45. package/hooks/qwen-code/stop.mjs +168 -0
  46. package/hooks/session-db.bundle.mjs +7 -7
  47. package/hooks/session-extract.bundle.mjs +3 -2
  48. package/hooks/session-loaders.mjs +9 -1
  49. package/hooks/stop.mjs +35 -2
  50. package/openclaw.plugin.json +1 -1
  51. package/package.json +1 -1
  52. package/server.bundle.mjs +90 -90
@@ -0,0 +1,429 @@
1
+ {
2
+ "claude-opus-4-8": {
3
+ "input_per_mtok": 5,
4
+ "output_per_mtok": 25,
5
+ "cache_read_per_mtok": 0.5,
6
+ "cache_write_per_mtok": 6.25,
7
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
8
+ },
9
+ "claude-opus-4-7": {
10
+ "input_per_mtok": 5,
11
+ "output_per_mtok": 25,
12
+ "cache_read_per_mtok": 0.5,
13
+ "cache_write_per_mtok": 6.25,
14
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
15
+ },
16
+ "claude-opus-4-6": {
17
+ "input_per_mtok": 5,
18
+ "output_per_mtok": 25,
19
+ "cache_read_per_mtok": 0.5,
20
+ "cache_write_per_mtok": 6.25,
21
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
22
+ },
23
+ "claude-opus-4-5": {
24
+ "input_per_mtok": 5,
25
+ "output_per_mtok": 25,
26
+ "cache_read_per_mtok": 0.5,
27
+ "cache_write_per_mtok": 6.25,
28
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
29
+ },
30
+ "claude-sonnet-4-6": {
31
+ "input_per_mtok": 3,
32
+ "output_per_mtok": 15,
33
+ "cache_read_per_mtok": 0.3,
34
+ "cache_write_per_mtok": 3.75,
35
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
36
+ },
37
+ "claude-sonnet-4-5": {
38
+ "input_per_mtok": 3,
39
+ "output_per_mtok": 15,
40
+ "cache_read_per_mtok": 0.3,
41
+ "cache_write_per_mtok": 3.75,
42
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
43
+ },
44
+ "claude-haiku-4-5": {
45
+ "input_per_mtok": 1,
46
+ "output_per_mtok": 5,
47
+ "cache_read_per_mtok": 0.1,
48
+ "cache_write_per_mtok": 1.25,
49
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
50
+ },
51
+ "claude-3-7-sonnet": {
52
+ "input_per_mtok": 3,
53
+ "output_per_mtok": 15,
54
+ "cache_read_per_mtok": 0.3,
55
+ "cache_write_per_mtok": 3.75,
56
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
57
+ },
58
+ "claude-3-5-haiku": {
59
+ "input_per_mtok": 0.8,
60
+ "output_per_mtok": 4,
61
+ "cache_read_per_mtok": 0.08,
62
+ "cache_write_per_mtok": 1,
63
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
64
+ },
65
+ "claude-fable-5": {
66
+ "input_per_mtok": 10,
67
+ "output_per_mtok": 50,
68
+ "cache_read_per_mtok": 1,
69
+ "cache_write_per_mtok": 12.5,
70
+ "source": "https://platform.claude.com/docs/en/about-claude/pricing"
71
+ },
72
+ "gpt-5": {
73
+ "input_per_mtok": 1.25,
74
+ "output_per_mtok": 10,
75
+ "cache_read_per_mtok": 0.125,
76
+ "cache_write_per_mtok": null,
77
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
78
+ },
79
+ "gpt-5-mini": {
80
+ "input_per_mtok": 0.25,
81
+ "output_per_mtok": 2,
82
+ "cache_read_per_mtok": 0.025,
83
+ "cache_write_per_mtok": null,
84
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
85
+ },
86
+ "gpt-5-nano": {
87
+ "input_per_mtok": 0.05,
88
+ "output_per_mtok": 0.4,
89
+ "cache_read_per_mtok": 0.005,
90
+ "cache_write_per_mtok": null,
91
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
92
+ },
93
+ "gpt-5-codex": {
94
+ "input_per_mtok": 1.25,
95
+ "output_per_mtok": 10,
96
+ "cache_read_per_mtok": 0.125,
97
+ "cache_write_per_mtok": null,
98
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
99
+ },
100
+ "gpt-4.1": {
101
+ "input_per_mtok": 2,
102
+ "output_per_mtok": 8,
103
+ "cache_read_per_mtok": 0.5,
104
+ "cache_write_per_mtok": null,
105
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
106
+ },
107
+ "gpt-4.1-mini": {
108
+ "input_per_mtok": 0.4,
109
+ "output_per_mtok": 1.6,
110
+ "cache_read_per_mtok": 0.1,
111
+ "cache_write_per_mtok": null,
112
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
113
+ },
114
+ "gpt-4.1-nano": {
115
+ "input_per_mtok": 0.1,
116
+ "output_per_mtok": 0.4,
117
+ "cache_read_per_mtok": 0.025,
118
+ "cache_write_per_mtok": null,
119
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
120
+ },
121
+ "gpt-4o": {
122
+ "input_per_mtok": 2.5,
123
+ "output_per_mtok": 10,
124
+ "cache_read_per_mtok": 1.25,
125
+ "cache_write_per_mtok": null,
126
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
127
+ },
128
+ "gpt-4o-mini": {
129
+ "input_per_mtok": 0.15,
130
+ "output_per_mtok": 0.6,
131
+ "cache_read_per_mtok": 0.075,
132
+ "cache_write_per_mtok": null,
133
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
134
+ },
135
+ "o3": {
136
+ "input_per_mtok": 2,
137
+ "output_per_mtok": 8,
138
+ "cache_read_per_mtok": 0.5,
139
+ "cache_write_per_mtok": null,
140
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
141
+ },
142
+ "o4-mini": {
143
+ "input_per_mtok": 1.1,
144
+ "output_per_mtok": 4.4,
145
+ "cache_read_per_mtok": 0.275,
146
+ "cache_write_per_mtok": null,
147
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
148
+ },
149
+ "o3-mini": {
150
+ "input_per_mtok": 1.1,
151
+ "output_per_mtok": 4.4,
152
+ "cache_read_per_mtok": 0.55,
153
+ "cache_write_per_mtok": null,
154
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
155
+ },
156
+ "codex-mini-latest": {
157
+ "input_per_mtok": 1.5,
158
+ "output_per_mtok": 6,
159
+ "cache_read_per_mtok": 0.375,
160
+ "cache_write_per_mtok": null,
161
+ "source": "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
162
+ },
163
+ "gemini-2.5-pro": {
164
+ "input_per_mtok": 1.25,
165
+ "output_per_mtok": 10,
166
+ "cache_read_per_mtok": 0.125,
167
+ "cache_write_per_mtok": null,
168
+ "source": "https://ai.google.dev/gemini-api/docs/pricing"
169
+ },
170
+ "gemini-2.5-flash": {
171
+ "input_per_mtok": 0.3,
172
+ "output_per_mtok": 2.5,
173
+ "cache_read_per_mtok": 0.03,
174
+ "cache_write_per_mtok": null,
175
+ "source": "https://ai.google.dev/gemini-api/docs/pricing"
176
+ },
177
+ "gemini-2.5-flash-lite": {
178
+ "input_per_mtok": 0.1,
179
+ "output_per_mtok": 0.4,
180
+ "cache_read_per_mtok": 0.01,
181
+ "cache_write_per_mtok": null,
182
+ "source": "https://ai.google.dev/gemini-api/docs/pricing"
183
+ },
184
+ "gemini-2.0-flash": {
185
+ "input_per_mtok": 0.1,
186
+ "output_per_mtok": 0.4,
187
+ "cache_read_per_mtok": 0.025,
188
+ "cache_write_per_mtok": null,
189
+ "source": "https://ai.google.dev/gemini-api/docs/pricing"
190
+ },
191
+ "gemini-2.0-flash-lite": {
192
+ "input_per_mtok": 0.075,
193
+ "output_per_mtok": 0.3,
194
+ "cache_read_per_mtok": null,
195
+ "cache_write_per_mtok": null,
196
+ "source": "https://ai.google.dev/gemini-api/docs/pricing"
197
+ },
198
+ "gemini-3-pro-preview": {
199
+ "input_per_mtok": 2,
200
+ "output_per_mtok": 12,
201
+ "cache_read_per_mtok": 0.2,
202
+ "cache_write_per_mtok": null,
203
+ "source": "https://ai.google.dev/gemini-api/docs/pricing"
204
+ },
205
+ "gemini-3-flash-preview": {
206
+ "input_per_mtok": 0.5,
207
+ "output_per_mtok": 3,
208
+ "cache_read_per_mtok": 0.05,
209
+ "cache_write_per_mtok": null,
210
+ "source": "https://ai.google.dev/gemini-api/docs/pricing"
211
+ },
212
+ "qwen3-coder": {
213
+ "input_per_mtok": 1,
214
+ "output_per_mtok": 5,
215
+ "cache_read_per_mtok": null,
216
+ "cache_write_per_mtok": null,
217
+ "source": "https://www.alibabacloud.com/help/en/model-studio/models"
218
+ },
219
+ "qwen-max": {
220
+ "input_per_mtok": 1.6,
221
+ "output_per_mtok": 6.4,
222
+ "cache_read_per_mtok": null,
223
+ "cache_write_per_mtok": null,
224
+ "source": "https://www.alibabacloud.com/help/en/model-studio/models"
225
+ },
226
+ "qwen-plus": {
227
+ "input_per_mtok": 0.4,
228
+ "output_per_mtok": 1.2,
229
+ "cache_read_per_mtok": null,
230
+ "cache_write_per_mtok": null,
231
+ "source": "https://www.alibabacloud.com/help/en/model-studio/models"
232
+ },
233
+ "qwen-turbo": {
234
+ "input_per_mtok": 0.05,
235
+ "output_per_mtok": 0.2,
236
+ "cache_read_per_mtok": null,
237
+ "cache_write_per_mtok": null,
238
+ "source": "https://www.alibabacloud.com/help/en/model-studio/models"
239
+ },
240
+ "qwen3-max": {
241
+ "input_per_mtok": 1.2,
242
+ "output_per_mtok": 6,
243
+ "cache_read_per_mtok": null,
244
+ "cache_write_per_mtok": null,
245
+ "source": "https://www.alibabacloud.com/help/en/model-studio/models"
246
+ },
247
+ "kimi-k2": {
248
+ "input_per_mtok": 0.6,
249
+ "output_per_mtok": 2.5,
250
+ "cache_read_per_mtok": 0.15,
251
+ "cache_write_per_mtok": null,
252
+ "source": "https://platform.moonshot.ai/docs/pricing/chat"
253
+ },
254
+ "kimi-k2-turbo": {
255
+ "input_per_mtok": 1.15,
256
+ "output_per_mtok": 8,
257
+ "cache_read_per_mtok": 0.15,
258
+ "cache_write_per_mtok": null,
259
+ "source": "https://platform.moonshot.ai/docs/pricing/chat"
260
+ },
261
+ "moonshot-v1-8k": {
262
+ "input_per_mtok": 0.2,
263
+ "output_per_mtok": 2,
264
+ "cache_read_per_mtok": null,
265
+ "cache_write_per_mtok": null,
266
+ "source": "https://platform.moonshot.ai/docs/pricing"
267
+ },
268
+ "moonshot-v1-32k": {
269
+ "input_per_mtok": 1,
270
+ "output_per_mtok": 3,
271
+ "cache_read_per_mtok": null,
272
+ "cache_write_per_mtok": null,
273
+ "source": "https://platform.moonshot.ai/docs/pricing"
274
+ },
275
+ "moonshot-v1-128k": {
276
+ "input_per_mtok": 2,
277
+ "output_per_mtok": 5,
278
+ "cache_read_per_mtok": null,
279
+ "cache_write_per_mtok": null,
280
+ "source": "https://platform.moonshot.ai/docs/pricing"
281
+ },
282
+ "deepseek-v3": {
283
+ "input_per_mtok": 0.27,
284
+ "output_per_mtok": 1.1,
285
+ "cache_read_per_mtok": 0.07,
286
+ "cache_write_per_mtok": 0,
287
+ "source": "https://api-docs.deepseek.com/quick_start/pricing"
288
+ },
289
+ "deepseek-r1": {
290
+ "input_per_mtok": 0.55,
291
+ "output_per_mtok": 2.19,
292
+ "cache_read_per_mtok": null,
293
+ "cache_write_per_mtok": null,
294
+ "source": "https://api-docs.deepseek.com/quick_start/pricing"
295
+ },
296
+ "deepseek-chat": {
297
+ "input_per_mtok": 0.14,
298
+ "output_per_mtok": 0.28,
299
+ "cache_read_per_mtok": 0.0028,
300
+ "cache_write_per_mtok": null,
301
+ "source": "https://api-docs.deepseek.com/quick_start/pricing"
302
+ },
303
+ "deepseek-reasoner": {
304
+ "input_per_mtok": 0.14,
305
+ "output_per_mtok": 0.28,
306
+ "cache_read_per_mtok": 0.0028,
307
+ "cache_write_per_mtok": null,
308
+ "source": "https://api-docs.deepseek.com/quick_start/pricing"
309
+ },
310
+ "glm-4.6": {
311
+ "input_per_mtok": 0.6,
312
+ "output_per_mtok": 2.2,
313
+ "cache_read_per_mtok": 0.11,
314
+ "cache_write_per_mtok": null,
315
+ "source": "https://docs.z.ai/guides/overview/pricing"
316
+ },
317
+ "glm-4-air": {
318
+ "input_per_mtok": 0.2,
319
+ "output_per_mtok": 1.1,
320
+ "cache_read_per_mtok": 0.03,
321
+ "cache_write_per_mtok": null,
322
+ "source": "https://docs.z.ai/guides/overview/pricing"
323
+ },
324
+ "grok-4": {
325
+ "input_per_mtok": 3,
326
+ "output_per_mtok": 15,
327
+ "cache_read_per_mtok": null,
328
+ "cache_write_per_mtok": null,
329
+ "source": "https://docs.x.ai/docs/pricing"
330
+ },
331
+ "grok-3": {
332
+ "input_per_mtok": 3,
333
+ "output_per_mtok": 15,
334
+ "cache_read_per_mtok": 0.75,
335
+ "cache_write_per_mtok": null,
336
+ "source": "https://docs.x.ai/docs/pricing"
337
+ },
338
+ "grok-code-fast-1": {
339
+ "input_per_mtok": 0.2,
340
+ "output_per_mtok": 1.5,
341
+ "cache_read_per_mtok": 0.02,
342
+ "cache_write_per_mtok": null,
343
+ "source": "https://docs.x.ai/docs/pricing"
344
+ },
345
+ "grok-2": {
346
+ "input_per_mtok": 2,
347
+ "output_per_mtok": 10,
348
+ "cache_read_per_mtok": null,
349
+ "cache_write_per_mtok": null,
350
+ "source": "https://docs.x.ai/docs/pricing"
351
+ },
352
+ "mistral-large-latest": {
353
+ "input_per_mtok": 0.5,
354
+ "output_per_mtok": 1.5,
355
+ "cache_read_per_mtok": null,
356
+ "cache_write_per_mtok": null,
357
+ "source": "https://mistral.ai/pricing"
358
+ },
359
+ "codestral-latest": {
360
+ "input_per_mtok": 0.3,
361
+ "output_per_mtok": 0.9,
362
+ "cache_read_per_mtok": null,
363
+ "cache_write_per_mtok": null,
364
+ "source": "https://mistral.ai/pricing"
365
+ },
366
+ "devstral": {
367
+ "input_per_mtok": 0.4,
368
+ "output_per_mtok": 2,
369
+ "cache_read_per_mtok": null,
370
+ "cache_write_per_mtok": null,
371
+ "source": "https://mistral.ai/pricing"
372
+ },
373
+ "mistral-medium": {
374
+ "input_per_mtok": 0.4,
375
+ "output_per_mtok": 2,
376
+ "cache_read_per_mtok": null,
377
+ "cache_write_per_mtok": null,
378
+ "source": "https://mistral.ai/pricing"
379
+ },
380
+ "llama-4-maverick": {
381
+ "input_per_mtok": 0.27,
382
+ "output_per_mtok": 0.85,
383
+ "cache_read_per_mtok": null,
384
+ "cache_write_per_mtok": null,
385
+ "source": "https://www.together.ai/pricing"
386
+ },
387
+ "llama-4-scout": {
388
+ "input_per_mtok": 0.08,
389
+ "output_per_mtok": 0.3,
390
+ "cache_read_per_mtok": null,
391
+ "cache_write_per_mtok": null,
392
+ "source": "https://www.together.ai/pricing"
393
+ },
394
+ "llama-3.3-70b": {
395
+ "input_per_mtok": 0.88,
396
+ "output_per_mtok": 0.88,
397
+ "cache_read_per_mtok": null,
398
+ "cache_write_per_mtok": null,
399
+ "source": "https://www.together.ai/pricing"
400
+ },
401
+ "command-a": {
402
+ "input_per_mtok": 2.5,
403
+ "output_per_mtok": 10,
404
+ "cache_read_per_mtok": null,
405
+ "cache_write_per_mtok": null,
406
+ "source": "https://cohere.com/pricing"
407
+ },
408
+ "command-r-plus": {
409
+ "input_per_mtok": 2.5,
410
+ "output_per_mtok": 10,
411
+ "cache_read_per_mtok": null,
412
+ "cache_write_per_mtok": null,
413
+ "source": "https://cohere.com/pricing"
414
+ },
415
+ "amazon-nova-pro": {
416
+ "input_per_mtok": 0.8,
417
+ "output_per_mtok": 3.2,
418
+ "cache_read_per_mtok": null,
419
+ "cache_write_per_mtok": null,
420
+ "source": "https://aws.amazon.com/bedrock/pricing/"
421
+ },
422
+ "amazon-nova-lite": {
423
+ "input_per_mtok": 0.06,
424
+ "output_per_mtok": 0.24,
425
+ "cache_read_per_mtok": null,
426
+ "cache_write_per_mtok": null,
427
+ "source": "https://aws.amazon.com/bedrock/pricing/"
428
+ }
429
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Pricing catalog — single source of truth for per-model USD cost.
3
+ *
4
+ * Deep module, tiny interface. At load it reads one curated multi-vendor JSON
5
+ * (src/session/model-prices.json) into a Map<modelId, Price> in per-Mtok units,
6
+ * then exposes three pure-ish functions:
7
+ *
8
+ * lookupPrice(modelId) → Price | null
9
+ * computeCostUsd(modelId, tokens) → number | null
10
+ * nativeOrComputed(id, t, native) → number | null
11
+ *
12
+ * WHY THIS EXISTS — the bug it kills:
13
+ * The old table in src/session/extract.ts hardcoded ~5 Claude rows plus a
14
+ * `default` row, and any unmatched id (every OpenAI / Gemini / Qwen / DeepSeek
15
+ * / Grok model) silently inherited Claude-Sonnet pricing. Non-Claude turns were
16
+ * therefore mispriced. Here each model is priced from ITS OWN curated row, and
17
+ * an unknown id resolves to `null` (no price) instead of a wrong Claude rate.
18
+ *
19
+ * The large litellm catalog (~1.5MB, ~2900 models) is NOT bundled — it lives at
20
+ * tools/pricing/litellm-catalog.json as the dev-only refresh base for this
21
+ * curated JSON.
22
+ *
23
+ * The curated JSON is small (~13KB, 61 models) and esbuild inlines it into the
24
+ * hook/server bundles at build time (no runtime fs read, no external file).
25
+ */
26
+ /** Per-Mtok price for one model. Any of the four rates may be null ("unknown"). */
27
+ export interface Price {
28
+ input_per_mtok: number | null;
29
+ output_per_mtok: number | null;
30
+ cache_read_per_mtok: number | null;
31
+ cache_write_per_mtok: number | null;
32
+ }
33
+ /** Token counts for one turn. All fields optional; absent ⇒ treated as 0. */
34
+ export interface TokenCounts {
35
+ input_tokens?: number;
36
+ output_tokens?: number;
37
+ cache_read_tokens?: number;
38
+ cache_creation_tokens?: number;
39
+ }
40
+ /**
41
+ * Resolve a model id to its curated Price, or null on miss.
42
+ * Strategy: exact, then normalized (trim+lowercase), then provider-stripped
43
+ * normalized. Misses return null so the caller can decide (warn / fall back).
44
+ */
45
+ export declare function lookupPrice(modelId: string): Price | null;
46
+ /**
47
+ * Σ tokens × per-Mtok price / 1e6 over the four buckets. Returns null when no
48
+ * price is found OR every token count is zero/absent (so dashboards never show
49
+ * a misleading "$0.00 for nothing" row). On a price miss, warns exactly once
50
+ * with the unmatched id so the curated catalog can be extended.
51
+ *
52
+ * Bucket → price mapping:
53
+ * input_tokens → input_per_mtok
54
+ * output_tokens → output_per_mtok (null ⇒ input rate)
55
+ * cache_read_tokens → cache_read_per_mtok (null ⇒ input rate)
56
+ * cache_creation_tokens → cache_write_per_mtok (null ⇒ input rate)
57
+ */
58
+ export declare function computeCostUsd(modelId: string, t: TokenCounts): number | null;
59
+ /**
60
+ * Prefer a provider-supplied native cost when present, else compute from the
61
+ * catalog. A native cost of exactly 0 is a real value (free tier) and passes
62
+ * through — only null/undefined defers to computeCostUsd.
63
+ */
64
+ export declare function nativeOrComputed(modelId: string, t: TokenCounts, nativeCostUsd?: number | null): number | null;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Pricing catalog — single source of truth for per-model USD cost.
3
+ *
4
+ * Deep module, tiny interface. At load it reads one curated multi-vendor JSON
5
+ * (src/session/model-prices.json) into a Map<modelId, Price> in per-Mtok units,
6
+ * then exposes three pure-ish functions:
7
+ *
8
+ * lookupPrice(modelId) → Price | null
9
+ * computeCostUsd(modelId, tokens) → number | null
10
+ * nativeOrComputed(id, t, native) → number | null
11
+ *
12
+ * WHY THIS EXISTS — the bug it kills:
13
+ * The old table in src/session/extract.ts hardcoded ~5 Claude rows plus a
14
+ * `default` row, and any unmatched id (every OpenAI / Gemini / Qwen / DeepSeek
15
+ * / Grok model) silently inherited Claude-Sonnet pricing. Non-Claude turns were
16
+ * therefore mispriced. Here each model is priced from ITS OWN curated row, and
17
+ * an unknown id resolves to `null` (no price) instead of a wrong Claude rate.
18
+ *
19
+ * The large litellm catalog (~1.5MB, ~2900 models) is NOT bundled — it lives at
20
+ * tools/pricing/litellm-catalog.json as the dev-only refresh base for this
21
+ * curated JSON.
22
+ *
23
+ * The curated JSON is small (~13KB, 61 models) and esbuild inlines it into the
24
+ * hook/server bundles at build time (no runtime fs read, no external file).
25
+ */
26
+ import catalog from "./model-prices.json" with { type: "json" };
27
+ /**
28
+ * Read the curated JSON into one Map. A row with a null *input* price is
29
+ * unusable for cost (the primary bucket has no rate) and is dropped at load so
30
+ * lookupPrice returns null for it — matching "null-priced entries → no price".
31
+ * (The two null-input ids are already pruned from the JSON itself; this guard
32
+ * keeps the loader robust if one is ever re-added.)
33
+ */
34
+ function buildCatalog() {
35
+ const map = new Map();
36
+ const src = catalog;
37
+ for (const id of Object.keys(src)) {
38
+ const row = src[id];
39
+ if (row == null || typeof row !== "object")
40
+ continue;
41
+ // No input rate ⇒ no usable price for this model.
42
+ if (typeof row.input_per_mtok !== "number")
43
+ continue;
44
+ map.set(id, {
45
+ input_per_mtok: row.input_per_mtok,
46
+ output_per_mtok: typeof row.output_per_mtok === "number" ? row.output_per_mtok : null,
47
+ cache_read_per_mtok: typeof row.cache_read_per_mtok === "number" ? row.cache_read_per_mtok : null,
48
+ cache_write_per_mtok: typeof row.cache_write_per_mtok === "number" ? row.cache_write_per_mtok : null,
49
+ });
50
+ }
51
+ return map;
52
+ }
53
+ const CATALOG = buildCatalog();
54
+ /**
55
+ * Strip a single leading `provider/` segment, char-algorithmically (NO regex).
56
+ * Walks to the first '/'; everything after it is the bare model id. Only the
57
+ * FIRST segment is stripped — `openai/gpt-5` → `gpt-5`, `a/b/c` → `b/c` — so a
58
+ * model id that legitimately contains a slash keeps its remaining segments.
59
+ * Returns null when there is no '/' (caller already tried the raw form).
60
+ */
61
+ function stripProviderPrefix(id) {
62
+ for (let i = 0; i < id.length; i++) {
63
+ if (id.charCodeAt(i) === 47 /* '/' */) {
64
+ // Guard against a leading or trailing slash producing an empty segment.
65
+ if (i === 0 || i === id.length - 1)
66
+ return null;
67
+ return id.slice(i + 1);
68
+ }
69
+ }
70
+ return null;
71
+ }
72
+ /** trim + lowercase, char-safe (String.prototype.trim/toLowerCase, no regex). */
73
+ function normalize(id) {
74
+ return id.trim().toLowerCase();
75
+ }
76
+ /**
77
+ * Resolve a model id to its curated Price, or null on miss.
78
+ * Strategy: exact, then normalized (trim+lowercase), then provider-stripped
79
+ * normalized. Misses return null so the caller can decide (warn / fall back).
80
+ */
81
+ export function lookupPrice(modelId) {
82
+ if (typeof modelId !== "string" || modelId.length === 0)
83
+ return null;
84
+ // 1. Exact — fastest path, covers ids already in canonical form.
85
+ const exact = CATALOG.get(modelId);
86
+ if (exact)
87
+ return exact;
88
+ // 2. Normalized — trimmed + lowercased.
89
+ const norm = normalize(modelId);
90
+ const byNorm = CATALOG.get(norm);
91
+ if (byNorm)
92
+ return byNorm;
93
+ // 3. Provider-stripped (pi / openclaw / opencode report `provider/model`).
94
+ const bare = stripProviderPrefix(norm);
95
+ if (bare) {
96
+ const byBare = CATALOG.get(bare);
97
+ if (byBare)
98
+ return byBare;
99
+ }
100
+ return null;
101
+ }
102
+ /** Price one token bucket. A null bucket rate falls back to the input rate. */
103
+ function bucketCost(tokens, rate, inputRate) {
104
+ if (tokens <= 0)
105
+ return 0;
106
+ const effective = typeof rate === "number" ? rate : inputRate;
107
+ return tokens * effective;
108
+ }
109
+ /**
110
+ * Σ tokens × per-Mtok price / 1e6 over the four buckets. Returns null when no
111
+ * price is found OR every token count is zero/absent (so dashboards never show
112
+ * a misleading "$0.00 for nothing" row). On a price miss, warns exactly once
113
+ * with the unmatched id so the curated catalog can be extended.
114
+ *
115
+ * Bucket → price mapping:
116
+ * input_tokens → input_per_mtok
117
+ * output_tokens → output_per_mtok (null ⇒ input rate)
118
+ * cache_read_tokens → cache_read_per_mtok (null ⇒ input rate)
119
+ * cache_creation_tokens → cache_write_per_mtok (null ⇒ input rate)
120
+ */
121
+ export function computeCostUsd(modelId, t) {
122
+ const input = typeof t.input_tokens === "number" ? t.input_tokens : 0;
123
+ const output = typeof t.output_tokens === "number" ? t.output_tokens : 0;
124
+ const cacheRead = typeof t.cache_read_tokens === "number" ? t.cache_read_tokens : 0;
125
+ const cacheCreate = typeof t.cache_creation_tokens === "number" ? t.cache_creation_tokens : 0;
126
+ // All buckets empty ⇒ nothing to price, regardless of model.
127
+ if (input <= 0 && output <= 0 && cacheRead <= 0 && cacheCreate <= 0)
128
+ return null;
129
+ const price = lookupPrice(modelId);
130
+ if (!price || typeof price.input_per_mtok !== "number") {
131
+ // Unknown model — emit one line so the id can be added to the catalog.
132
+ console.warn(`[pricing] no curated price for model id: ${modelId}`);
133
+ return null;
134
+ }
135
+ const inputRate = price.input_per_mtok;
136
+ const microDollars = bucketCost(input, inputRate, inputRate) +
137
+ bucketCost(output, price.output_per_mtok, inputRate) +
138
+ bucketCost(cacheRead, price.cache_read_per_mtok, inputRate) +
139
+ bucketCost(cacheCreate, price.cache_write_per_mtok, inputRate);
140
+ return microDollars / 1_000_000;
141
+ }
142
+ /**
143
+ * Prefer a provider-supplied native cost when present, else compute from the
144
+ * catalog. A native cost of exactly 0 is a real value (free tier) and passes
145
+ * through — only null/undefined defers to computeCostUsd.
146
+ */
147
+ export function nativeOrComputed(modelId, t, nativeCostUsd) {
148
+ if (typeof nativeCostUsd === "number")
149
+ return nativeCostUsd;
150
+ return computeCostUsd(modelId, t);
151
+ }