@subvalue/cli 0.1.3 → 0.1.4

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 (37) hide show
  1. package/PRE_RELEASE.md +6 -6
  2. package/README.md +5 -3
  3. package/dist/PRICING.md +15 -16
  4. package/dist/packages/cli/src/index.js +109 -20
  5. package/dist/packages/cli/src/protocol.js +13 -0
  6. package/dist/packages/cli/src/runtime.js +44 -14
  7. package/dist/packages/cli/src/server.js +306 -62
  8. package/dist/packages/core/src/calendar.js +41 -18
  9. package/dist/packages/core/src/coverage.js +12 -0
  10. package/dist/packages/core/src/demo.js +61 -9
  11. package/dist/packages/core/src/metadata.js +52 -9
  12. package/dist/packages/core/src/pricing/current.js +346 -297
  13. package/dist/packages/core/src/pricing/index.js +373 -73
  14. package/dist/packages/core/src/providers/claude/index.js +93 -21
  15. package/dist/packages/core/src/providers/codex/index.js +213 -46
  16. package/dist/packages/core/src/scanner.js +226 -46
  17. package/dist/packages/core/src/security.js +127 -34
  18. package/dist/packages/core/src/storage.js +215 -47
  19. package/dist/packages/core/src/subscriptions.js +146 -21
  20. package/dist/packages/core/src/summary.js +408 -59
  21. package/dist/packages/core/src/types.js +71 -17
  22. package/dist/packages/core/src/usage-json.js +166 -47
  23. package/dist/packages/receipt/src/index.js +386 -82
  24. package/dist/packages/ui/src/brand.js +42 -27
  25. package/dist/public/app.css +2280 -61
  26. package/dist/public/apps/web/src/landing.js +200 -8
  27. package/dist/public/apps/web/src/main.js +1143 -98
  28. package/dist/public/index.html +24 -1
  29. package/dist/public/landing.css +3235 -80
  30. package/dist/public/landing.html +21 -1
  31. package/dist/public/packages/core/src/calendar.js +41 -18
  32. package/dist/public/packages/core/src/coverage.js +12 -0
  33. package/dist/public/packages/core/src/subscriptions.js +146 -21
  34. package/dist/public/packages/receipt/src/index.js +386 -82
  35. package/dist/public/packages/ui/src/brand.js +42 -27
  36. package/dist/public/privacy.css +197 -9
  37. package/package.json +1 -1
@@ -1,84 +1,384 @@
1
-
2
- import {currentPrices} from './current.js';
1
+
2
+ import { currentPrices } from './current.js';
3
3
 
4
-
5
-
6
-
7
-
8
-
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
9
37
 
10
-
11
-
12
- const verified='2026-10-05T00:00:00.000Z';
13
- const openai='https://developers.openai.com/api/docs/pricing',anthropic='https://platform.claude.com/docs/en/about-claude/pricing';
14
- const date=(day )=>day+'T00:00:00.000Z';
15
- const changes='https://developers.openai.com/api/docs/changelog';
16
- const claudeChanges='https://platform.claude.com/docs/en/release-notes/overview';
17
- function claude(model ,start ,input ,output ,until =null,max ,transition=false) {return {provider:'claude',model,effective_from:date(start),effective_until:until?date(until):null,input,cached_input:input/10,cache_write:null,cache_write_5m:input*1.25,cache_write_1h:input*2,output,source:anthropic,sources:[anthropic,claudeChanges],verified_at:verified,uncertain_days:[...(transition?[start]:[]),...(until?[until]:[])],max_verified_context:max};}
18
- function codex(model ,start ,input ,cached ,output ,until =null,transition=false) {return {provider:'codex',model,effective_from:date(start),effective_until:until?date(until):null,input,cached_input:cached,cache_write:input*1.25,cache_write_5m:null,cache_write_1h:null,output,source:openai,sources:[openai,changes,`https://developers.openai.com/api/docs/models/${model}`],verified_at:verified,context_threshold:272000,long_context:{input:input*2,cached_input:cached*2,cache_write:input*2.5,output:output*1.5},uncertain_days:[...(transition?[start]:[]),...(until?[until]:[])]};}
19
- const codexModels=['gpt-6-astra','gpt-6-sol','gpt-6.1-sol','gpt-6-luna','gpt-5.6-sol','gpt-5.6-terra','gpt-5.6-luna','gpt-5.3-codex'];
20
- const claudeModels=['claude-fable-5','claude-fable-5-1','claude-mythos-5','claude-mythos-5-1','claude-mythos-preview','claude-opus-5-5','claude-opus-5','claude-opus-4-8','claude-opus-4-7','claude-opus-4-6','claude-opus-4-5-20251101','claude-opus-4-1-20250805','claude-opus-4-20250514','claude-sonnet-5','claude-sonnet-5-5','claude-sonnet-4-5-20250929','claude-sonnet-4-6','claude-sonnet-4-20250514','claude-3-7-sonnet-20250219','claude-haiku-4-5-20251001'];
21
- const mappings=(models ,source )=>Object.fromEntries(models.map(canonical=>[canonical,{canonical,confidence:'HIGH' ,source,verified_at:verified}]));
38
+ const verified = '2026-10-05T00:00:00.000Z';
39
+ const openai = 'https://developers.openai.com/api/docs/pricing',
40
+ anthropic = 'https://platform.claude.com/docs/en/about-claude/pricing';
41
+ const date = (day ) => day + 'T00:00:00.000Z';
42
+ const changes = 'https://developers.openai.com/api/docs/changelog';
43
+ const claudeChanges = 'https://platform.claude.com/docs/en/release-notes/overview';
44
+ function claude(
45
+ model ,
46
+ start ,
47
+ input ,
48
+ output ,
49
+ until = null,
50
+ max ,
51
+ transition = false,
52
+ ) {
53
+ return {
54
+ provider: 'claude',
55
+ model,
56
+ effective_from: date(start),
57
+ effective_until: until ? date(until) : null,
58
+ input,
59
+ cached_input: input / 10,
60
+ cache_write: null,
61
+ cache_write_5m: input * 1.25,
62
+ cache_write_1h: input * 2,
63
+ output,
64
+ source: anthropic,
65
+ sources: [anthropic, claudeChanges],
66
+ verified_at: verified,
67
+ uncertain_days: [...(transition ? [start] : []), ...(until ? [until] : [])],
68
+ max_verified_context: max,
69
+ };
70
+ }
71
+ function codex(
72
+ model ,
73
+ start ,
74
+ input ,
75
+ cached ,
76
+ output ,
77
+ until = null,
78
+ transition = false,
79
+ ) {
80
+ return {
81
+ provider: 'codex',
82
+ model,
83
+ effective_from: date(start),
84
+ effective_until: until ? date(until) : null,
85
+ input,
86
+ cached_input: cached,
87
+ cache_write: input * 1.25,
88
+ cache_write_5m: null,
89
+ cache_write_1h: null,
90
+ output,
91
+ source: openai,
92
+ sources: [openai, changes, `https://developers.openai.com/api/docs/models/${model}`],
93
+ verified_at: verified,
94
+ context_threshold: 272000,
95
+ long_context: {
96
+ input: input * 2,
97
+ cached_input: cached * 2,
98
+ cache_write: input * 2.5,
99
+ output: output * 1.5,
100
+ },
101
+ uncertain_days: [...(transition ? [start] : []), ...(until ? [until] : [])],
102
+ };
103
+ }
104
+ const codexModels = [
105
+ 'gpt-6-astra',
106
+ 'gpt-6-sol',
107
+ 'gpt-6.1-sol',
108
+ 'gpt-6-luna',
109
+ 'gpt-5.6-sol',
110
+ 'gpt-5.6-terra',
111
+ 'gpt-5.6-luna',
112
+ 'gpt-5.3-codex',
113
+ ];
114
+ const claudeModels = [
115
+ 'claude-fable-5',
116
+ 'claude-fable-5-1',
117
+ 'claude-mythos-5',
118
+ 'claude-mythos-5-1',
119
+ 'claude-mythos-preview',
120
+ 'claude-opus-5-5',
121
+ 'claude-opus-5',
122
+ 'claude-opus-4-8',
123
+ 'claude-opus-4-7',
124
+ 'claude-opus-4-6',
125
+ 'claude-opus-4-5-20251101',
126
+ 'claude-opus-4-1-20250805',
127
+ 'claude-opus-4-20250514',
128
+ 'claude-sonnet-5',
129
+ 'claude-sonnet-5-5',
130
+ 'claude-sonnet-4-5-20250929',
131
+ 'claude-sonnet-4-6',
132
+ 'claude-sonnet-4-20250514',
133
+ 'claude-3-7-sonnet-20250219',
134
+ 'claude-haiku-4-5-20251001',
135
+ ];
136
+ const mappings = (models , source ) =>
137
+ Object.fromEntries(
138
+ models.map((canonical) => [
139
+ canonical,
140
+ { canonical, confidence: 'HIGH' , source, verified_at: verified },
141
+ ]),
142
+ );
22
143
  // A newly launched model has one documented initial rate: use it for API-equivalent
23
144
  // estimates on its launch day. Actual changes between rates remain uncertain that day.
24
- export const catalog ={version:'2026-10-05.2',mappings:{codex:{...mappings(codexModels,changes),...Object.fromEntries(currentPrices.map(p=>[p.model,{canonical:p.model,confidence:'HIGH' ,source:p.source,verified_at:verified}]))},claude:mappings(claudeModels,claudeChanges)},prices:[
25
- codex('gpt-5.6-sol','2026-07-09',5,.5,30,'2026-08-21'),
26
- codex('gpt-5.6-sol','2026-08-21',4,.4,20,'2026-11-22',true), // Promotion confirmed at least through Nov 21. No invented post-promotion rate.
27
- codex('gpt-5.6-luna','2026-07-09',1,.1,6,'2026-07-30'),codex('gpt-5.6-luna','2026-07-30',.2,.02,1.2,null,true),
28
- codex('gpt-5.6-terra','2026-07-09',2.5,.25,15,'2026-07-30'),codex('gpt-5.6-terra','2026-07-30',2,.2,12,null,true),
29
- codex('gpt-6-astra','2026-09-03',10,1,50),codex('gpt-6-sol','2026-09-22',2,.2,10),codex('gpt-6-luna','2026-09-22',.1,.01,.5),codex('gpt-6.1-sol','2026-09-29',2,.1,10),
30
- {provider:'codex',model:'gpt-5.3-codex',effective_from:verified,effective_until:null,input:1.75,cached_input:.175,cache_write:null,cache_write_5m:null,cache_write_1h:null,output:14,source:openai,verified_at:verified},
31
- ...currentPrices,
32
- claude('claude-fable-5','2026-06-09',10,50,'2026-06-12'),claude('claude-fable-5','2026-07-01',10,50),claude('claude-opus-5','2026-07-24',5,25),claude('claude-opus-4-8','2026-05-28',5,25),claude('claude-opus-4-7','2026-04-16',5,25),
33
- // Long-context premium removal is dated March 13; preceding >200k requests
34
- // stay unavailable until their historical premium can be verified.
35
- claude('claude-opus-4-6','2026-02-05',5,25,'2026-03-13',200000),claude('claude-opus-4-6','2026-03-13',5,25,null,undefined,true),
36
- claude('claude-opus-4-5-20251101','2025-11-24',5,25,null,200000),claude('claude-sonnet-4-5-20250929','2025-09-29',3,15,null,200000),
37
- claude('claude-sonnet-4-6','2026-02-17',3,15,'2026-03-13',200000),claude('claude-sonnet-4-6','2026-03-13',3,15,null,undefined,true),
38
- claude('claude-sonnet-5','2026-06-30',2,10),claude('claude-sonnet-5-5','2026-09-28',2,10),
39
- {...claude('claude-opus-5-5','2026-09-22',4,20),cached_input:.2},
40
- {...claude('claude-fable-5-1','2026-09-01',10,50),cached_input:.25},
41
- {...claude('claude-mythos-5-1','2026-09-01',10,50),cached_input:.25},
42
- claude('claude-mythos-5','2026-06-09',10,50,'2026-06-12'),claude('claude-mythos-5','2026-07-01',10,50),
43
- claude('claude-haiku-4-5-20251001','2025-10-15',1,5,null,200000),
44
- {...claude('claude-opus-4-1-20250805','2025-08-05',15,75,'2026-08-05',200000),sources:[anthropic,'https://www.anthropic.com/news/claude-opus-4-1','https://platform.claude.com/docs/en/about-claude/model-deprecations']},
45
- {...claude('claude-opus-4-20250514','2025-05-22',15,75,'2026-06-15',200000),sources:[anthropic,'https://www.anthropic.com/news/claude-4','https://platform.claude.com/docs/en/about-claude/model-deprecations']},
46
- {...claude('claude-sonnet-4-20250514','2025-05-22',3,15,'2026-06-15',200000),sources:[anthropic,'https://www.anthropic.com/news/claude-4','https://platform.claude.com/docs/en/about-claude/model-deprecations']},
47
- {...claude('claude-3-7-sonnet-20250219','2025-02-24',3,15,'2026-02-19',200000),sources:[anthropic,'https://www.anthropic.com/news/claude-3-7-sonnet','https://platform.claude.com/docs/en/about-claude/model-deprecations']},
48
- // Published input/output tariff; cache rates and earlier billing start unverified.
49
- {provider:'claude',model:'claude-mythos-preview',effective_from:verified,effective_until:null,input:25,cached_input:null,cache_write:null,cache_write_5m:null,cache_write_1h:null,output:125,source:'https://www.anthropic.com/glasswing',verified_at:verified}
50
- ]};
145
+ export const catalog = {
146
+ version: '2026-10-05.2',
147
+ mappings: {
148
+ codex: {
149
+ ...mappings(codexModels, changes),
150
+ ...Object.fromEntries(
151
+ currentPrices.map((p) => [
152
+ p.model,
153
+ {
154
+ canonical: p.model,
155
+ confidence: 'HIGH' ,
156
+ source: p.source,
157
+ verified_at: verified,
158
+ },
159
+ ]),
160
+ ),
161
+ },
162
+ claude: mappings(claudeModels, claudeChanges),
163
+ },
164
+ prices: [
165
+ codex('gpt-5.6-sol', '2026-07-09', 5, 0.5, 30, '2026-08-21'),
166
+ codex('gpt-5.6-sol', '2026-08-21', 4, 0.4, 20, '2026-11-22', true), // Promotion confirmed at least through Nov 21. No invented post-promotion rate.
167
+ codex('gpt-5.6-luna', '2026-07-09', 1, 0.1, 6, '2026-07-30'),
168
+ codex('gpt-5.6-luna', '2026-07-30', 0.2, 0.02, 1.2, null, true),
169
+ codex('gpt-5.6-terra', '2026-07-09', 2.5, 0.25, 15, '2026-07-30'),
170
+ codex('gpt-5.6-terra', '2026-07-30', 2, 0.2, 12, null, true),
171
+ codex('gpt-6-astra', '2026-09-03', 10, 1, 50),
172
+ codex('gpt-6-sol', '2026-09-22', 2, 0.2, 10),
173
+ codex('gpt-6-luna', '2026-09-22', 0.1, 0.01, 0.5),
174
+ codex('gpt-6.1-sol', '2026-09-29', 2, 0.1, 10),
175
+ {
176
+ provider: 'codex',
177
+ model: 'gpt-5.3-codex',
178
+ effective_from: verified,
179
+ effective_until: null,
180
+ input: 1.75,
181
+ cached_input: 0.175,
182
+ cache_write: null,
183
+ cache_write_5m: null,
184
+ cache_write_1h: null,
185
+ output: 14,
186
+ source: openai,
187
+ verified_at: verified,
188
+ },
189
+ ...currentPrices,
190
+ claude('claude-fable-5', '2026-06-09', 10, 50, '2026-06-12'),
191
+ claude('claude-fable-5', '2026-07-01', 10, 50),
192
+ claude('claude-opus-5', '2026-07-24', 5, 25),
193
+ claude('claude-opus-4-8', '2026-05-28', 5, 25),
194
+ claude('claude-opus-4-7', '2026-04-16', 5, 25),
195
+ // Long-context premium removal is dated March 13; preceding >200k requests
196
+ // stay unavailable until their historical premium can be verified.
197
+ claude('claude-opus-4-6', '2026-02-05', 5, 25, '2026-03-13', 200000),
198
+ claude('claude-opus-4-6', '2026-03-13', 5, 25, null, undefined, true),
199
+ claude('claude-opus-4-5-20251101', '2025-11-24', 5, 25, null, 200000),
200
+ claude('claude-sonnet-4-5-20250929', '2025-09-29', 3, 15, null, 200000),
201
+ claude('claude-sonnet-4-6', '2026-02-17', 3, 15, '2026-03-13', 200000),
202
+ claude('claude-sonnet-4-6', '2026-03-13', 3, 15, null, undefined, true),
203
+ claude('claude-sonnet-5', '2026-06-30', 2, 10),
204
+ claude('claude-sonnet-5-5', '2026-09-28', 2, 10),
205
+ { ...claude('claude-opus-5-5', '2026-09-22', 4, 20), cached_input: 0.2 },
206
+ { ...claude('claude-fable-5-1', '2026-09-01', 10, 50), cached_input: 0.25 },
207
+ { ...claude('claude-mythos-5-1', '2026-09-01', 10, 50), cached_input: 0.25 },
208
+ claude('claude-mythos-5', '2026-06-09', 10, 50, '2026-06-12'),
209
+ claude('claude-mythos-5', '2026-07-01', 10, 50),
210
+ claude('claude-haiku-4-5-20251001', '2025-10-15', 1, 5, null, 200000),
211
+ {
212
+ ...claude('claude-opus-4-1-20250805', '2025-08-05', 15, 75, '2026-08-05', 200000),
213
+ sources: [
214
+ anthropic,
215
+ 'https://www.anthropic.com/news/claude-opus-4-1',
216
+ 'https://platform.claude.com/docs/en/about-claude/model-deprecations',
217
+ ],
218
+ },
219
+ {
220
+ ...claude('claude-opus-4-20250514', '2025-05-22', 15, 75, '2026-06-15', 200000),
221
+ sources: [
222
+ anthropic,
223
+ 'https://www.anthropic.com/news/claude-4',
224
+ 'https://platform.claude.com/docs/en/about-claude/model-deprecations',
225
+ ],
226
+ },
227
+ {
228
+ ...claude('claude-sonnet-4-20250514', '2025-05-22', 3, 15, '2026-06-15', 200000),
229
+ sources: [
230
+ anthropic,
231
+ 'https://www.anthropic.com/news/claude-4',
232
+ 'https://platform.claude.com/docs/en/about-claude/model-deprecations',
233
+ ],
234
+ },
235
+ {
236
+ ...claude('claude-3-7-sonnet-20250219', '2025-02-24', 3, 15, '2026-02-19', 200000),
237
+ sources: [
238
+ anthropic,
239
+ 'https://www.anthropic.com/news/claude-3-7-sonnet',
240
+ 'https://platform.claude.com/docs/en/about-claude/model-deprecations',
241
+ ],
242
+ },
243
+ // Published input/output tariff; cache rates and earlier billing start unverified.
244
+ {
245
+ provider: 'claude',
246
+ model: 'claude-mythos-preview',
247
+ effective_from: verified,
248
+ effective_until: null,
249
+ input: 25,
250
+ cached_input: null,
251
+ cache_write: null,
252
+ cache_write_5m: null,
253
+ cache_write_1h: null,
254
+ output: 125,
255
+ source: 'https://www.anthropic.com/glasswing',
256
+ verified_at: verified,
257
+ },
258
+ ],
259
+ };
51
260
  // Explicit officially documented aliases only. No prefix matching or internal-model guesses.
52
- for(const [raw,canonical] of Object.entries({'claude-opus-4-5':'claude-opus-4-5-20251101','claude-sonnet-4-5':'claude-sonnet-4-5-20250929','claude-haiku-4-5':'claude-haiku-4-5-20251001'}))catalog.mappings.claude[raw]={canonical,confidence:'HIGH',source:'https://platform.claude.com/docs/en/about-claude/models/model-ids-and-versions',verified_at:verified};
53
- catalog.mappings.codex['gpt-5.6']={canonical:'gpt-5.6-sol',confidence:'HIGH',source:changes,verified_at:verified};
261
+ for (const [raw, canonical] of Object.entries({
262
+ 'claude-opus-4-5': 'claude-opus-4-5-20251101',
263
+ 'claude-sonnet-4-5': 'claude-sonnet-4-5-20250929',
264
+ 'claude-haiku-4-5': 'claude-haiku-4-5-20251001',
265
+ }))
266
+ catalog.mappings.claude[raw] = {
267
+ canonical,
268
+ confidence: 'HIGH',
269
+ source: 'https://platform.claude.com/docs/en/about-claude/models/model-ids-and-versions',
270
+ verified_at: verified,
271
+ };
272
+ catalog.mappings.codex['gpt-5.6'] = {
273
+ canonical: 'gpt-5.6-sol',
274
+ confidence: 'HIGH',
275
+ source: changes,
276
+ verified_at: verified,
277
+ };
54
278
  // The retired Haiku tariff is still published, but its historical reduction date
55
279
  // is not established. Recognize the exact identity without backdating $0.80/$4.
56
- catalog.mappings.claude['claude-3-5-haiku-20241022']={canonical:'claude-3-5-haiku-20241022',confidence:'HIGH',source:'https://platform.claude.com/docs/en/about-claude/model-deprecations',verified_at:verified};
57
-
58
- export function priceRecord(r ,c =catalog) {
59
- const mapped=r.model_raw?c.mappings[r.provider][r.model_raw]:undefined;const mapping=typeof mapped==='object'?mapped:undefined;const canonical=typeof mapped==='string'?mapped:mapping?.canonical??null;const fail=(reason ) =>({usd:null,canonical,reason,version:null,mapping});
60
- if(!canonical)return fail('unresolved-model');if(!r.timestamp)return fail('timestamp-missing');
61
- const candidates=c.prices.filter(p=>p.provider===r.provider&&p.model===canonical&&r.timestamp >=p.effective_from&&(!p.effective_until||r.timestamp <p.effective_until));
62
- if(candidates.length!==1)return fail(candidates.length?'ambiguous-price-version':'historical-price-unavailable');let p=candidates[0];
63
- if(p.uncertain_days?.includes(r.timestamp.slice(0,10)))return fail('price-transition-day-uncertain');
64
- if(r.quality==='INCOMPLETE'||r.unallocated_total_tokens!==null)return fail('incomplete-token-components');
65
- if(r.input_tokens===null||r.cached_input_tokens===null||r.output_tokens===null||r.cache_creation_tokens===null)return fail('token-components-missing');
66
- if(r.service_tier&&!['standard','default'].includes(r.service_tier))return fail('service-tier-unavailable');
67
- if(r.speed&&r.speed!=='standard')return fail('speed-pricing-unavailable');
68
- if((r.web_search_requests??0)>0||(r.web_fetch_requests??0)>0)return fail('tool-pricing-unavailable');
69
- const context=r.provider==='claude'?r.input_tokens+r.cached_input_tokens+r.cache_creation_tokens:r.input_tokens;
70
- if(p.max_verified_context!==undefined&&context>p.max_verified_context)return fail('historical-context-price-unavailable');
71
- if(p.long_context){if(p.context_threshold==null)return fail('context-band-unverified');if(r.notes.includes('last-delta-disagreement'))return fail('request-context-ambiguous');if(context>p.context_threshold)p={...p,...p.long_context};}
72
- const cached=r.cached_input_tokens;if(cached>0&&p.cached_input===null)return fail('cache-read-price-unavailable');
73
- let input=r.input_tokens;
280
+ catalog.mappings.claude['claude-3-5-haiku-20241022'] = {
281
+ canonical: 'claude-3-5-haiku-20241022',
282
+ confidence: 'HIGH',
283
+ source: 'https://platform.claude.com/docs/en/about-claude/model-deprecations',
284
+ verified_at: verified,
285
+ };
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+ export function priceRecord(r , c = catalog) {
296
+ const mapped = r.model_raw ? c.mappings[r.provider][r.model_raw] : undefined;
297
+ const mapping = typeof mapped === 'object' ? mapped : undefined;
298
+ const canonical = typeof mapped === 'string' ? mapped : (mapping?.canonical ?? null);
299
+ const fail = (reason ) => ({
300
+ usd: null,
301
+ canonical,
302
+ reason,
303
+ version: null,
304
+ mapping,
305
+ });
306
+ if (!canonical) return fail('unresolved-model');
307
+ if (!r.timestamp) return fail('timestamp-missing');
308
+ const candidates = c.prices.filter(
309
+ (p) =>
310
+ p.provider === r.provider &&
311
+ p.model === canonical &&
312
+ r.timestamp >= p.effective_from &&
313
+ (!p.effective_until || r.timestamp < p.effective_until),
314
+ );
315
+ if (candidates.length !== 1)
316
+ return fail(candidates.length ? 'ambiguous-price-version' : 'historical-price-unavailable');
317
+ let p = candidates[0];
318
+ if (p.uncertain_days?.includes(r.timestamp.slice(0, 10)))
319
+ return fail('price-transition-day-uncertain');
320
+ if (r.quality === 'INCOMPLETE' || r.unallocated_total_tokens !== null)
321
+ return fail('incomplete-token-components');
322
+ if (
323
+ r.input_tokens === null ||
324
+ r.cached_input_tokens === null ||
325
+ r.output_tokens === null ||
326
+ r.cache_creation_tokens === null
327
+ )
328
+ return fail('token-components-missing');
329
+ if (r.service_tier && !['standard', 'default'].includes(r.service_tier))
330
+ return fail('service-tier-unavailable');
331
+ if (r.speed && r.speed !== 'standard') return fail('speed-pricing-unavailable');
332
+ if ((r.web_search_requests ?? 0) > 0 || (r.web_fetch_requests ?? 0) > 0)
333
+ return fail('tool-pricing-unavailable');
334
+ const context =
335
+ r.provider === 'claude'
336
+ ? r.input_tokens + r.cached_input_tokens + r.cache_creation_tokens
337
+ : r.input_tokens;
338
+ if (p.max_verified_context !== undefined && context > p.max_verified_context)
339
+ return fail('historical-context-price-unavailable');
340
+ if (p.long_context) {
341
+ if (p.context_threshold == null) return fail('context-band-unverified');
342
+ if (r.notes.includes('last-delta-disagreement')) return fail('request-context-ambiguous');
343
+ if (context > p.context_threshold) p = { ...p, ...p.long_context };
344
+ }
345
+ const cached = r.cached_input_tokens;
346
+ if (cached > 0 && p.cached_input === null) return fail('cache-read-price-unavailable');
347
+ let input = r.input_tokens;
74
348
  // OpenAI reports cache reads and writes as mutually exclusive input subsets.
75
349
  // A cache write replaces ordinary input pricing; it is not an additive fee.
76
- if(r.provider==='codex'){input-=cached+r.cache_creation_tokens;if(input<0)return fail('invalid-cache-subset');}
77
- let usd=(input*p.input+cached*(p.cached_input??0)+r.output_tokens*p.output)/1e6;
78
- if(r.cache_creation_tokens>0){if(r.provider==='claude'){
79
- if(r.cache_creation_5m_tokens===null||r.cache_creation_1h_tokens===null||r.cache_creation_5m_tokens+r.cache_creation_1h_tokens!==r.cache_creation_tokens||p.cache_write_5m===null||p.cache_write_1h===null)return fail('cache-duration-price-unavailable');
80
- usd+=(r.cache_creation_5m_tokens*p.cache_write_5m+r.cache_creation_1h_tokens*p.cache_write_1h)/1e6;
81
- }else{if(p.cache_write===null)return fail('cache-write-price-unavailable');usd+=r.cache_creation_tokens*p.cache_write/1e6;}}
350
+ if (r.provider === 'codex') {
351
+ input -= cached + r.cache_creation_tokens;
352
+ if (input < 0) return fail('invalid-cache-subset');
353
+ }
354
+ let usd = (input * p.input + cached * (p.cached_input ?? 0) + r.output_tokens * p.output) / 1e6;
355
+ if (r.cache_creation_tokens > 0) {
356
+ if (r.provider === 'claude') {
357
+ if (
358
+ r.cache_creation_5m_tokens === null ||
359
+ r.cache_creation_1h_tokens === null ||
360
+ r.cache_creation_5m_tokens + r.cache_creation_1h_tokens !== r.cache_creation_tokens ||
361
+ p.cache_write_5m === null ||
362
+ p.cache_write_1h === null
363
+ )
364
+ return fail('cache-duration-price-unavailable');
365
+ usd +=
366
+ (r.cache_creation_5m_tokens * p.cache_write_5m +
367
+ r.cache_creation_1h_tokens * p.cache_write_1h) /
368
+ 1e6;
369
+ } else {
370
+ if (p.cache_write === null) return fail('cache-write-price-unavailable');
371
+ usd += (r.cache_creation_tokens * p.cache_write) / 1e6;
372
+ }
373
+ }
82
374
  // Reasoning is an output breakdown; never charge it a second time.
83
- return {usd,canonical,reason:null,version:c.version,mapping,source:p.source,effectiveFrom:p.effective_from};
375
+ return {
376
+ usd,
377
+ canonical,
378
+ reason: null,
379
+ version: c.version,
380
+ mapping,
381
+ source: p.source,
382
+ effectiveFrom: p.effective_from,
383
+ };
84
384
  }
@@ -1,24 +1,96 @@
1
- import {baseRecord,hash,identifier,modelId,timestamp,token,track} from '../../metadata.js';
2
- import {emptyDiagnostics, } from '../../types.js';
3
-
4
- export const claudeState=() =>({diagnostics:emptyDiagnostics()});
5
- export function parseClaude(o ,s ,ref ,lineNumber ) {
6
- if(!o||o.type!=='assistant')return null;const m=o.message,u=m?.usage;
7
- if(!u||typeof u!=='object')return null;
8
- const msg=identifier(m.id),req=identifier(o.requestId),sid=identifier(o.sessionId);
1
+ import {
2
+ baseRecord,
3
+ hash,
4
+ identifier,
5
+ isObject,
6
+ modelId,
7
+ timestamp,
8
+ token,
9
+ track,
10
+ } from '../../metadata.js';
11
+ import { emptyDiagnostics, } from '../../types.js';
12
+
13
+
14
+
15
+ export const claudeState = () => ({ diagnostics: emptyDiagnostics() });
16
+ export function parseClaude(
17
+ event ,
18
+ state ,
19
+ sourceReference ,
20
+ lineNumber ,
21
+ ) {
22
+ if (!isObject(event) || event.type !== 'assistant' || !isObject(event.message)) return null;
23
+ const message = event.message,
24
+ usage = message.usage;
25
+ if (!isObject(usage)) return null;
26
+ const cacheCreation = isObject(usage.cache_creation) ? usage.cache_creation : null;
27
+ const serverToolUse = isObject(usage.server_tool_use) ? usage.server_tool_use : null;
28
+ const messageId = identifier(message.id),
29
+ requestId = identifier(event.requestId),
30
+ sessionId = identifier(event.sessionId);
9
31
  // Response blocks repeat the whole message usage. Never sum usage.iterations.
10
- const event=hash(sid,msg??req??[ref,lineNumber],msg?null:req);
11
- const r=baseRecord('claude',ref,event);r.timestamp=timestamp(o.timestamp);r.session_id=sid;r.request_id=req;r.source_schema='claude.message.usage.v1';r.model_raw=modelId(m.model);
12
- if(typeof o.cwd==='string')r.project_identifier_hash=hash('project',o.cwd);
13
- r.input_tokens=token(u.input_tokens);r.cached_input_tokens=token(u.cache_read_input_tokens);r.cache_creation_tokens=token(u.cache_creation_input_tokens);r.output_tokens=token(u.output_tokens);
14
- r.cache_creation_5m_tokens=token(u.cache_creation?.ephemeral_5m_input_tokens);r.cache_creation_1h_tokens=token(u.cache_creation?.ephemeral_1h_input_tokens);
15
- r.service_tier=modelId(u.service_tier);r.speed=modelId(u.speed);r.web_search_requests=token(u.server_tool_use?.web_search_requests);r.web_fetch_requests=token(u.server_tool_use?.web_fetch_requests);
16
- if([r.input_tokens,r.cached_input_tokens,r.cache_creation_tokens,r.output_tokens].every(n=>n!==null))r.total_tokens=r.input_tokens +r.cached_input_tokens +r.cache_creation_tokens +r.output_tokens ;
17
- if(!msg||!req){r.quality='MEDIUM';r.notes.push('weak-request-identity');}
18
- if([r.input_tokens,r.cached_input_tokens,r.cache_creation_tokens,r.output_tokens].some(n=>n===null)||!r.timestamp||!r.model_raw)r.quality='INCOMPLETE';
19
- if(r.cache_creation_tokens!==null&&r.cache_creation_tokens>0){
20
- if(r.cache_creation_5m_tokens===null||r.cache_creation_1h_tokens===null){r.quality='INCOMPLETE';r.notes.push('cache-duration-missing');}
21
- else if(r.cache_creation_5m_tokens+r.cache_creation_1h_tokens!==r.cache_creation_tokens){r.quality='INCOMPLETE';r.notes.push('cache-duration-disagreement');}
32
+ const sourceEventId = hash(
33
+ sessionId,
34
+ messageId ?? requestId ?? [sourceReference, lineNumber],
35
+ messageId ? null : requestId,
36
+ );
37
+ const record = baseRecord('claude', sourceReference, sourceEventId);
38
+ record.timestamp = timestamp(event.timestamp);
39
+ record.session_id = sessionId;
40
+ record.request_id = requestId;
41
+ record.source_schema = 'claude.message.usage.v1';
42
+ record.model_raw = modelId(message.model);
43
+ if (typeof event.cwd === 'string') record.project_identifier_hash = hash('project', event.cwd);
44
+ record.input_tokens = token(usage.input_tokens);
45
+ record.cached_input_tokens = token(usage.cache_read_input_tokens);
46
+ record.cache_creation_tokens = token(usage.cache_creation_input_tokens);
47
+ record.output_tokens = token(usage.output_tokens);
48
+ record.cache_creation_5m_tokens = token(cacheCreation?.ephemeral_5m_input_tokens);
49
+ record.cache_creation_1h_tokens = token(cacheCreation?.ephemeral_1h_input_tokens);
50
+ record.service_tier = modelId(usage.service_tier);
51
+ record.speed = modelId(usage.speed);
52
+ record.web_search_requests = token(serverToolUse?.web_search_requests);
53
+ record.web_fetch_requests = token(serverToolUse?.web_fetch_requests);
54
+ if (
55
+ [
56
+ record.input_tokens,
57
+ record.cached_input_tokens,
58
+ record.cache_creation_tokens,
59
+ record.output_tokens,
60
+ ].every((n) => n !== null)
61
+ )
62
+ record.total_tokens =
63
+ record.input_tokens +
64
+ record.cached_input_tokens +
65
+ record.cache_creation_tokens +
66
+ record.output_tokens ;
67
+ if (!messageId || !requestId) {
68
+ record.quality = 'MEDIUM';
69
+ record.notes.push('weak-request-identity');
22
70
  }
23
- track(s.diagnostics,r);return r;
71
+ if (
72
+ [
73
+ record.input_tokens,
74
+ record.cached_input_tokens,
75
+ record.cache_creation_tokens,
76
+ record.output_tokens,
77
+ ].some((n) => n === null) ||
78
+ !record.timestamp ||
79
+ !record.model_raw
80
+ )
81
+ record.quality = 'INCOMPLETE';
82
+ if (record.cache_creation_tokens !== null && record.cache_creation_tokens > 0) {
83
+ if (record.cache_creation_5m_tokens === null || record.cache_creation_1h_tokens === null) {
84
+ record.quality = 'INCOMPLETE';
85
+ record.notes.push('cache-duration-missing');
86
+ } else if (
87
+ record.cache_creation_5m_tokens + record.cache_creation_1h_tokens !==
88
+ record.cache_creation_tokens
89
+ ) {
90
+ record.quality = 'INCOMPLETE';
91
+ record.notes.push('cache-duration-disagreement');
92
+ }
93
+ }
94
+ track(state.diagnostics, record);
95
+ return record;
24
96
  }