@subvalue/cli 0.1.2 → 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.
- package/PRE_RELEASE.md +6 -6
- package/README.md +7 -5
- package/dist/PRICING.md +15 -16
- package/dist/packages/cli/src/index.js +109 -20
- package/dist/packages/cli/src/protocol.js +13 -0
- package/dist/packages/cli/src/runtime.js +44 -14
- package/dist/packages/cli/src/server.js +306 -62
- package/dist/packages/core/src/calendar.js +47 -0
- package/dist/packages/core/src/coverage.js +12 -0
- package/dist/packages/core/src/demo.js +61 -9
- package/dist/packages/core/src/metadata.js +52 -9
- package/dist/packages/core/src/pricing/current.js +346 -297
- package/dist/packages/core/src/pricing/index.js +373 -73
- package/dist/packages/core/src/providers/claude/index.js +93 -21
- package/dist/packages/core/src/providers/codex/index.js +213 -46
- package/dist/packages/core/src/scanner.js +226 -46
- package/dist/packages/core/src/security.js +127 -34
- package/dist/packages/core/src/storage.js +215 -47
- package/dist/packages/core/src/subscriptions.js +146 -21
- package/dist/packages/core/src/summary.js +409 -51
- package/dist/packages/core/src/types.js +71 -17
- package/dist/packages/core/src/usage-json.js +166 -47
- package/dist/packages/receipt/src/index.js +387 -80
- package/dist/packages/ui/src/brand.js +48 -3
- package/dist/public/app.css +2280 -56
- package/dist/public/apps/web/src/landing.js +200 -8
- package/dist/public/apps/web/src/main.js +1143 -63
- package/dist/public/index.html +24 -1
- package/dist/public/landing.css +3235 -70
- package/dist/public/landing.html +21 -1
- package/dist/public/logo/wordmark-monochrome.svg +1 -0
- package/dist/public/logo/wordmark.svg +1 -0
- package/dist/public/packages/core/src/calendar.js +47 -0
- package/dist/public/packages/core/src/coverage.js +12 -0
- package/dist/public/packages/core/src/subscriptions.js +146 -21
- package/dist/public/packages/receipt/src/index.js +387 -80
- package/dist/public/packages/ui/src/brand.js +48 -3
- package/dist/public/privacy.css +197 -6
- package/dist/public/privacy.html +1 -1
- 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
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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({
|
|
53
|
-
|
|
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']={
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
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'){
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 {
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
}
|