coding-agent-cost 0.3.0__tar.gz → 0.4.0__tar.gz
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.
- {coding_agent_cost-0.3.0/coding_agent_cost.egg-info → coding_agent_cost-0.4.0}/PKG-INFO +49 -3
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/README.md +48 -2
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/__init__.py +1 -1
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/aggregate.py +15 -1
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/cli.py +6 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/facts.py +12 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/rates.json +46 -3
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/rates.py +73 -5
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/readers/claude.py +47 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0/coding_agent_cost.egg-info}/PKG-INFO +49 -3
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/coding_agent_cost.egg-info/SOURCES.txt +1 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/pyproject.toml +1 -1
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_aggregate.py +157 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_astra_pricing.py +2 -2
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_cli.py +52 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_e0a_review3_hardening.py +3 -3
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_facts.py +41 -0
- coding_agent_cost-0.4.0/tests/test_haiku_5_5_rates.py +87 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_rates.py +200 -2
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_reader_claude.py +191 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_reader_codex.py +20 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_sonnet_5_5_rates.py +1 -1
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_version_consistency.py +8 -7
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/LICENSE +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/config.py +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/readers/__init__.py +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/readers/codex.py +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/agent_cost/renderers.py +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/coding_agent_cost.egg-info/dependency_links.txt +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/coding_agent_cost.egg-info/entry_points.txt +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/coding_agent_cost.egg-info/top_level.txt +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/setup.cfg +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_e0a_fable_5_1_and_sonnet_5_correction.py +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_measure_v1_contract.py +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_opus_5_5_rates.py +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_reader_dedup.py +0 -0
- {coding_agent_cost-0.3.0 → coding_agent_cost-0.4.0}/tests/test_reader_output_lower_bound.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-agent-cost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Estimate AI coding agent (Claude Code / Codex CLI) token usage and cost from local logs
|
|
5
5
|
Author: shiki-yusuke
|
|
6
6
|
License: MIT
|
|
@@ -244,6 +244,16 @@ network, never calls `gh`, and never resolves branches or PRs.
|
|
|
244
244
|
present in the log, it's used; otherwise the cache-write tokens are priced
|
|
245
245
|
at the 5-minute rate as an explicit **lower bound** and flagged
|
|
246
246
|
`lower_bound` rather than guessed at the (more expensive) 1-hour rate.
|
|
247
|
+
Each Claude fact also carries its request's prompt length
|
|
248
|
+
(`prompt_tokens` = `input_tokens` + `cache_read_input_tokens` +
|
|
249
|
+
`cache_creation_input_tokens`), used only to pick the tier of a model
|
|
250
|
+
whose price depends on prompt length (`claude-haiku-5-5`).
|
|
251
|
+
- **Prompt-length tiers need a known prompt length.** A fact whose prompt
|
|
252
|
+
length is unknown -- every Codex fact (a delta of cumulative totals, not
|
|
253
|
+
one request), a Claude fact from a conflicting dedup group, or a Claude
|
|
254
|
+
row whose cache-write TTL breakdown exceeds its `cache_creation_input_tokens`
|
|
255
|
+
-- is priced at the model's base (cheapest) tier and flagged
|
|
256
|
+
`lower_bound`. Models without tiers are unaffected.
|
|
247
257
|
- **Codex CLI**: rollout files record a *cumulative* token count after each
|
|
248
258
|
turn; agent-cost turns that into per-turn deltas. Under the Codex token-rate
|
|
249
259
|
tariff used here, cache writes are not added as a separate charge. The current
|
|
@@ -303,6 +313,37 @@ carries a `catalog_version`, a list of `sources` (the pricing page a rate
|
|
|
303
313
|
came from), and is validated on load (no duplicate model keys or aliases,
|
|
304
314
|
no negative rates, no overlapping periods for the same model).
|
|
305
315
|
|
|
316
|
+
A rate period can price by prompt length (catalog `schema_version` `"2"`).
|
|
317
|
+
The period's own five values are the base tier, for prompts up to the first
|
|
318
|
+
threshold; each entry in `prompt_tiers` applies to prompts *strictly over*
|
|
319
|
+
its `prompt_tokens_over`, and a request uses the largest threshold it
|
|
320
|
+
exceeds. Prompt length is the request's input + cache-read + cache-write
|
|
321
|
+
tokens, and the chosen tier prices every token kind of that request,
|
|
322
|
+
output included:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"rate_id": "claude-haiku-5-5-launch-2026-10-07",
|
|
327
|
+
"effective_from": "2026-10-07T00:00:00+00:00",
|
|
328
|
+
"effective_until": null,
|
|
329
|
+
"input_nocache": "0.10", "cache_read": "0.01", "cache_write_5m": "0.125", "cache_write_1h": "0.20", "output": "0.50",
|
|
330
|
+
"prompt_tiers": [
|
|
331
|
+
{ "prompt_tokens_over": 100000,
|
|
332
|
+
"input_nocache": "0.50", "cache_read": "0.05", "cache_write_5m": "0.625", "cache_write_1h": "1.0", "output": "2.50" }
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
In a period with `prompt_tiers`, all five values of the base and of every
|
|
338
|
+
tier must be present and non-null, thresholds must be positive integers in
|
|
339
|
+
ascending order without duplicates, and every value must be greater than or
|
|
340
|
+
equal to the same value in the previous tier (the base first). That
|
|
341
|
+
monotonicity is what makes the base tier a true lower bound for a fact whose
|
|
342
|
+
prompt length is unknown. A `schema_version` `"1"` catalog may not contain
|
|
343
|
+
`prompt_tiers`; agent-cost 0.3.0 and earlier reject a `"2"` catalog as
|
|
344
|
+
unsupported rather than mispricing it, so a custom `--rates` file that uses
|
|
345
|
+
tiers must declare `"2"`.
|
|
346
|
+
|
|
306
347
|
To use your own catalog instead of the one bundled with the package, pass
|
|
307
348
|
`--rates path/to/rates.json` to `report` or `export` -- this fully replaces
|
|
308
349
|
the bundled catalog, it does not merge with it. Inspect any catalog with:
|
|
@@ -402,10 +443,11 @@ agent-cost makes zero network calls. `agent-cost export`'s JSONL never
|
|
|
402
443
|
includes absolute file paths, rollout paths, prompt/message content, or git
|
|
403
444
|
branch names -- only the fields needed to reproduce a cost estimate:
|
|
404
445
|
`occurred_at_utc`, `agent`, `session_id`, `model_raw`, `model_key`,
|
|
405
|
-
`token_kind`, `tokens`, `mode`,
|
|
446
|
+
`token_kind`, `tokens`, `mode`, `source_quality` (a fixed-vocabulary
|
|
406
447
|
caveat about how that one fact was derived: `"ok"`, Codex's
|
|
407
448
|
`"first_event_delta"`, or Claude's `"identity_missing"` /
|
|
408
|
-
`"output_lower_bound"` -- never null)
|
|
449
|
+
`"output_lower_bound"` -- never null), and `prompt_tokens` (the request's
|
|
450
|
+
prompt length as an integer, or null when unknown).
|
|
409
451
|
|
|
410
452
|
## License
|
|
411
453
|
|
|
@@ -429,6 +471,10 @@ MIT. See [LICENSE](LICENSE).
|
|
|
429
471
|
未知のモデル・単価表にない token 種別は `unpriced` として扱い、憶測の価格を出しません。
|
|
430
472
|
- 単価表 (`agent_cost/rates.json`) は履歴型カタログで、値上げは新しい期間として追加します。
|
|
431
473
|
`--rates PATH` で別カタログに完全差し替えできます。
|
|
474
|
+
- prompt 長で単価が変わるモデル(`claude-haiku-5-5`、100,000 tokens 超で高い単価)は期間の
|
|
475
|
+
`prompt_tiers`(schema_version `"2"`)で表し、リクエストごとの prompt 長(input + cache read +
|
|
476
|
+
cache write)で tier を選びます。prompt 長が不明な fact(Codex 由来、conflicting group、usage
|
|
477
|
+
不整合)は基底 tier(最安)で **下限推計**(`lower_bound`)になります。
|
|
432
478
|
- `agent-cost measure --session-id ID [--session-id ID ...] --format json` は他プログラムから
|
|
433
479
|
subprocess で叩くための機械可読な契約です(`protocol_version: "measure/v1"`)。指定した
|
|
434
480
|
session_id が1件も見つからなくても終了コードは0(空集計として表現)、`--session-id` 未指定など
|
|
@@ -218,6 +218,16 @@ network, never calls `gh`, and never resolves branches or PRs.
|
|
|
218
218
|
present in the log, it's used; otherwise the cache-write tokens are priced
|
|
219
219
|
at the 5-minute rate as an explicit **lower bound** and flagged
|
|
220
220
|
`lower_bound` rather than guessed at the (more expensive) 1-hour rate.
|
|
221
|
+
Each Claude fact also carries its request's prompt length
|
|
222
|
+
(`prompt_tokens` = `input_tokens` + `cache_read_input_tokens` +
|
|
223
|
+
`cache_creation_input_tokens`), used only to pick the tier of a model
|
|
224
|
+
whose price depends on prompt length (`claude-haiku-5-5`).
|
|
225
|
+
- **Prompt-length tiers need a known prompt length.** A fact whose prompt
|
|
226
|
+
length is unknown -- every Codex fact (a delta of cumulative totals, not
|
|
227
|
+
one request), a Claude fact from a conflicting dedup group, or a Claude
|
|
228
|
+
row whose cache-write TTL breakdown exceeds its `cache_creation_input_tokens`
|
|
229
|
+
-- is priced at the model's base (cheapest) tier and flagged
|
|
230
|
+
`lower_bound`. Models without tiers are unaffected.
|
|
221
231
|
- **Codex CLI**: rollout files record a *cumulative* token count after each
|
|
222
232
|
turn; agent-cost turns that into per-turn deltas. Under the Codex token-rate
|
|
223
233
|
tariff used here, cache writes are not added as a separate charge. The current
|
|
@@ -277,6 +287,37 @@ carries a `catalog_version`, a list of `sources` (the pricing page a rate
|
|
|
277
287
|
came from), and is validated on load (no duplicate model keys or aliases,
|
|
278
288
|
no negative rates, no overlapping periods for the same model).
|
|
279
289
|
|
|
290
|
+
A rate period can price by prompt length (catalog `schema_version` `"2"`).
|
|
291
|
+
The period's own five values are the base tier, for prompts up to the first
|
|
292
|
+
threshold; each entry in `prompt_tiers` applies to prompts *strictly over*
|
|
293
|
+
its `prompt_tokens_over`, and a request uses the largest threshold it
|
|
294
|
+
exceeds. Prompt length is the request's input + cache-read + cache-write
|
|
295
|
+
tokens, and the chosen tier prices every token kind of that request,
|
|
296
|
+
output included:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{
|
|
300
|
+
"rate_id": "claude-haiku-5-5-launch-2026-10-07",
|
|
301
|
+
"effective_from": "2026-10-07T00:00:00+00:00",
|
|
302
|
+
"effective_until": null,
|
|
303
|
+
"input_nocache": "0.10", "cache_read": "0.01", "cache_write_5m": "0.125", "cache_write_1h": "0.20", "output": "0.50",
|
|
304
|
+
"prompt_tiers": [
|
|
305
|
+
{ "prompt_tokens_over": 100000,
|
|
306
|
+
"input_nocache": "0.50", "cache_read": "0.05", "cache_write_5m": "0.625", "cache_write_1h": "1.0", "output": "2.50" }
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
In a period with `prompt_tiers`, all five values of the base and of every
|
|
312
|
+
tier must be present and non-null, thresholds must be positive integers in
|
|
313
|
+
ascending order without duplicates, and every value must be greater than or
|
|
314
|
+
equal to the same value in the previous tier (the base first). That
|
|
315
|
+
monotonicity is what makes the base tier a true lower bound for a fact whose
|
|
316
|
+
prompt length is unknown. A `schema_version` `"1"` catalog may not contain
|
|
317
|
+
`prompt_tiers`; agent-cost 0.3.0 and earlier reject a `"2"` catalog as
|
|
318
|
+
unsupported rather than mispricing it, so a custom `--rates` file that uses
|
|
319
|
+
tiers must declare `"2"`.
|
|
320
|
+
|
|
280
321
|
To use your own catalog instead of the one bundled with the package, pass
|
|
281
322
|
`--rates path/to/rates.json` to `report` or `export` -- this fully replaces
|
|
282
323
|
the bundled catalog, it does not merge with it. Inspect any catalog with:
|
|
@@ -376,10 +417,11 @@ agent-cost makes zero network calls. `agent-cost export`'s JSONL never
|
|
|
376
417
|
includes absolute file paths, rollout paths, prompt/message content, or git
|
|
377
418
|
branch names -- only the fields needed to reproduce a cost estimate:
|
|
378
419
|
`occurred_at_utc`, `agent`, `session_id`, `model_raw`, `model_key`,
|
|
379
|
-
`token_kind`, `tokens`, `mode`,
|
|
420
|
+
`token_kind`, `tokens`, `mode`, `source_quality` (a fixed-vocabulary
|
|
380
421
|
caveat about how that one fact was derived: `"ok"`, Codex's
|
|
381
422
|
`"first_event_delta"`, or Claude's `"identity_missing"` /
|
|
382
|
-
`"output_lower_bound"` -- never null)
|
|
423
|
+
`"output_lower_bound"` -- never null), and `prompt_tokens` (the request's
|
|
424
|
+
prompt length as an integer, or null when unknown).
|
|
383
425
|
|
|
384
426
|
## License
|
|
385
427
|
|
|
@@ -403,6 +445,10 @@ MIT. See [LICENSE](LICENSE).
|
|
|
403
445
|
未知のモデル・単価表にない token 種別は `unpriced` として扱い、憶測の価格を出しません。
|
|
404
446
|
- 単価表 (`agent_cost/rates.json`) は履歴型カタログで、値上げは新しい期間として追加します。
|
|
405
447
|
`--rates PATH` で別カタログに完全差し替えできます。
|
|
448
|
+
- prompt 長で単価が変わるモデル(`claude-haiku-5-5`、100,000 tokens 超で高い単価)は期間の
|
|
449
|
+
`prompt_tiers`(schema_version `"2"`)で表し、リクエストごとの prompt 長(input + cache read +
|
|
450
|
+
cache write)で tier を選びます。prompt 長が不明な fact(Codex 由来、conflicting group、usage
|
|
451
|
+
不整合)は基底 tier(最安)で **下限推計**(`lower_bound`)になります。
|
|
406
452
|
- `agent-cost measure --session-id ID [--session-id ID ...] --format json` は他プログラムから
|
|
407
453
|
subprocess で叩くための機械可読な契約です(`protocol_version: "measure/v1"`)。指定した
|
|
408
454
|
session_id が1件も見つからなくても終了コードは0(空集計として表現)、`--session-id` 未指定など
|
|
@@ -28,6 +28,11 @@ def price_fact(catalog: RateCatalog, fact: Fact) -> Tuple[Optional[Decimal], str
|
|
|
28
28
|
never at the more expensive 1-hour rate -- and flagged accordingly.
|
|
29
29
|
An unrecognized model or an unpriced token kind for an otherwise-known
|
|
30
30
|
model both return ``(None, "unpriced", None)`` rather than a guess.
|
|
31
|
+
|
|
32
|
+
For a period with ``prompt_tiers``, the fact is priced at the tier with
|
|
33
|
+
the largest ``prompt_tokens_over`` its ``prompt_tokens`` exceeds (the
|
|
34
|
+
base values when none). A fact whose ``prompt_tokens`` is unknown is
|
|
35
|
+
priced at the base values -- the cheapest tier -- as a lower bound.
|
|
31
36
|
"""
|
|
32
37
|
resolved_key, period = catalog.rate_for(fact.model_key, fact.occurred_at_utc)
|
|
33
38
|
if resolved_key is None or period is None:
|
|
@@ -47,7 +52,16 @@ def price_fact(catalog: RateCatalog, fact: Fact) -> Tuple[Optional[Decimal], str
|
|
|
47
52
|
rate_field = "cache_write_5m"
|
|
48
53
|
lower_bound = True
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
values = period.values
|
|
56
|
+
if period.prompt_tiers:
|
|
57
|
+
if fact.prompt_tokens is None:
|
|
58
|
+
lower_bound = True
|
|
59
|
+
else:
|
|
60
|
+
for tier in period.prompt_tiers:
|
|
61
|
+
if fact.prompt_tokens > tier.prompt_tokens_over:
|
|
62
|
+
values = tier.values
|
|
63
|
+
|
|
64
|
+
rate = values.get(rate_field)
|
|
51
65
|
if rate is None:
|
|
52
66
|
return None, "unpriced", None
|
|
53
67
|
|
|
@@ -168,6 +168,7 @@ def cmd_export(args) -> int:
|
|
|
168
168
|
"tokens": f.tokens,
|
|
169
169
|
"mode": f.mode,
|
|
170
170
|
"source_quality": f.source_quality,
|
|
171
|
+
"prompt_tokens": f.prompt_tokens,
|
|
171
172
|
}
|
|
172
173
|
out.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
173
174
|
finally:
|
|
@@ -305,6 +306,11 @@ def cmd_rates_show(args) -> int:
|
|
|
305
306
|
print(f" [{period.rate_id}] {period.effective_from.isoformat()} .. {until}")
|
|
306
307
|
for field_name, value in period.values.items():
|
|
307
308
|
print(f" {field_name}: {value if value is not None else '(unpriced)'}")
|
|
309
|
+
if period.prompt_tiers:
|
|
310
|
+
print(" prompt_tiers:")
|
|
311
|
+
for tier in period.prompt_tiers:
|
|
312
|
+
values = " ".join(f"{name}={value}" for name, value in tier.values.items())
|
|
313
|
+
print(f" > {tier.prompt_tokens_over}: {values}")
|
|
308
314
|
return 0
|
|
309
315
|
|
|
310
316
|
print(f"catalog_version: {catalog.catalog_version}")
|
|
@@ -84,6 +84,12 @@ class Fact:
|
|
|
84
84
|
tokens: int
|
|
85
85
|
mode: str = "unknown"
|
|
86
86
|
source_quality: str = "ok"
|
|
87
|
+
# The prompt length of the one request that produced this fact (usage's
|
|
88
|
+
# input_tokens + cache_read_input_tokens + cache_creation_input_tokens).
|
|
89
|
+
# Used only to pick the tier of a prompt-length-tiered rate. None means
|
|
90
|
+
# unknown (a Codex fact, or a Claude fact whose usage was inconsistent or
|
|
91
|
+
# came from a conflicting dedup group).
|
|
92
|
+
prompt_tokens: Optional[int] = None
|
|
87
93
|
|
|
88
94
|
def __post_init__(self) -> None:
|
|
89
95
|
if self.agent not in AGENTS:
|
|
@@ -96,5 +102,11 @@ class Fact:
|
|
|
96
102
|
raise ValueError(f"invalid source_quality: {self.source_quality!r}")
|
|
97
103
|
if self.tokens < 0:
|
|
98
104
|
raise ValueError(f"tokens must be >= 0, got {self.tokens}")
|
|
105
|
+
if self.prompt_tokens is not None and (
|
|
106
|
+
isinstance(self.prompt_tokens, bool)
|
|
107
|
+
or not isinstance(self.prompt_tokens, int)
|
|
108
|
+
or self.prompt_tokens < 0
|
|
109
|
+
):
|
|
110
|
+
raise ValueError(f"prompt_tokens must be None or an int >= 0, got {self.prompt_tokens!r}")
|
|
99
111
|
if self.occurred_at_utc.tzinfo is None:
|
|
100
112
|
raise ValueError("occurred_at_utc must be timezone-aware")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schema_version": "
|
|
3
|
-
"catalog_version": "2026-09
|
|
2
|
+
"schema_version": "2",
|
|
3
|
+
"catalog_version": "2026-10-09",
|
|
4
4
|
"currency": "USD",
|
|
5
5
|
"unit": "per_mtok",
|
|
6
6
|
"usd_per_credit": "0.04",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"claude-fable-5-1 added 2026-09-09: rate_id claude-fable-5-1-local-first-observed-2026-09-02. effective_from (2026-09-02T01:17:55Z) is the timestamp the raw model ID was first observed across all 54 local transcript files (none found in August), not a confirmed official launch date -- if an authoritative launch date is confirmed later, add it as a separate rate_id with effective_until on this one rather than editing this period in place. Values (cache_read $0.25/MTok = 0.025x base input) differ from claude-fable-5's cache_read ($1.00/MTok = 0.1x); this is a genuine per-model price difference confirmed against the primary source (see sources below), not an inconsistency to reconcile.",
|
|
13
13
|
"claude-sonnet-5 corrected 2026-09-09: a prior period (rate_id claude-sonnet-5-standard, effective_from 2026-09-01, values 3.0/0.30/3.75/6.0/15.0) priced a previously-announced rate increase that the pricing page's claude-sonnet-5-introductory-pricing note states did not occur (\"The previously scheduled increase to $3/$15 ... on September 1, 2026 will not occur\"). That period has been replaced by claude-sonnet-5-standard-2026-09-01, which carries the same $2/$0.20/$2.50/$4/$10 values as the preceding claude-sonnet-5-launch-promo period, since the launch price is confirmed to be the ongoing price with no change at the 2026-09-01 boundary. Any measure/v1 or report output computed against the pre-correction catalog for claude-sonnet-5 sessions occurring on or after 2026-09-01 overstated cost by 1.5x for that model. To detect this class of error going forward, cross-check each priced model's rate periods against its primary pricing page's Note text on a recurring (e.g. monthly) basis for mentions of a rate-change announcement being withdrawn or not taking effect -- a period can be internally valid (schema-wise) and still price a rate that was announced but never actually charged.",
|
|
14
14
|
"claude-opus-5-5 added 2026-09-23: rate_id claude-opus-5-5-launch-2026-09-22. effective_from 2026-09-22T00:00:00Z is 00:00:00Z of the official public launch date (anthropic.com/claude-opus-5-5, 'September 22, 2026'); the launch time of day is not published, but a transcript row whose raw model ID is claude-opus-5-5 cannot predate the launch, so pricing from the start of that UTC day cannot price a pre-launch event. Values 4.0/0.20/5.0/8.0/20.0 per the pricing page: cache_read is 0.05x base input (pricing-page footnote 2), a genuine per-model difference from the standard 0.1x and from claude-fable-5-1's 0.025x. fast_multiplier 2.0 (fast mode $8/$40 per MTok on the pricing page). Claude Code's CHANGELOG entry for 2.1.280 states Opus 5.5 became the default model (see sources); rows whose raw model ID is claude-opus-5-5 would be unpriced without this entry. Claude Sonnet 5.5 and Claude Haiku 5.5 are announced to follow 'in the coming weeks' -- add them as separate entries when their model IDs and prices are published, never as aliases of the 5.x entries.",
|
|
15
|
-
"claude-sonnet-5-5 added 2026-09-29: rate_id claude-sonnet-5-5-launch-2026-09-28. effective_from 2026-09-28T00:00:00Z is 00:00:00Z of the official public launch date (anthropic.com/claude-sonnet-5-5, 'September 28, 2026'); as with claude-opus-5-5, the launch time of day is not published, but a transcript row whose raw model ID is claude-sonnet-5-5 cannot predate the launch, so pricing from the start of that UTC day cannot price a pre-launch event. Values 2.0/0.20/2.50/4.0/10.0 per the pricing page (Sonnet 5.5 row, no footnote: cache_read is the standard 0.1x, unlike claude-opus-5-5's 0.05x and claude-fable-5-1's 0.025x). Identical to claude-sonnet-5's ongoing values, but kept as a separate entry, never an alias: the pricing page lists the two rows independently and a future change to one need not apply to the other. fast_multiplier 1.0: the pricing page's fast-mode table lists only the Opus models. Claude Code's CHANGELOG entry for 2.1.284 states Sonnet 5.5 is now the default Sonnet model, so the `sonnet` alias in agent frontmatter resolves to it and rows whose raw model ID is claude-sonnet-5-5 are expected from that version onward; no such row existed in the local transcripts on 2026-09-29. Claude Haiku 5.5 is announced to follow 'in the coming weeks' -- add it as a separate entry when its model ID and prices are published."
|
|
15
|
+
"claude-sonnet-5-5 added 2026-09-29: rate_id claude-sonnet-5-5-launch-2026-09-28. effective_from 2026-09-28T00:00:00Z is 00:00:00Z of the official public launch date (anthropic.com/claude-sonnet-5-5, 'September 28, 2026'); as with claude-opus-5-5, the launch time of day is not published, but a transcript row whose raw model ID is claude-sonnet-5-5 cannot predate the launch, so pricing from the start of that UTC day cannot price a pre-launch event. Values 2.0/0.20/2.50/4.0/10.0 per the pricing page (Sonnet 5.5 row, no footnote: cache_read is the standard 0.1x, unlike claude-opus-5-5's 0.05x and claude-fable-5-1's 0.025x). Identical to claude-sonnet-5's ongoing values, but kept as a separate entry, never an alias: the pricing page lists the two rows independently and a future change to one need not apply to the other. fast_multiplier 1.0: the pricing page's fast-mode table lists only the Opus models. Claude Code's CHANGELOG entry for 2.1.284 states Sonnet 5.5 is now the default Sonnet model, so the `sonnet` alias in agent frontmatter resolves to it and rows whose raw model ID is claude-sonnet-5-5 are expected from that version onward; no such row existed in the local transcripts on 2026-09-29. Claude Haiku 5.5 is announced to follow 'in the coming weeks' -- add it as a separate entry when its model ID and prices are published.",
|
|
16
|
+
"claude-haiku-5-5 added 2026-10-09: rate_id claude-haiku-5-5-launch-2026-10-07. effective_from 2026-10-07T00:00:00Z is 00:00:00Z of the official release date (platform.claude.com/docs/en/models/haiku-5-5/overview, 'Released October 7, 2026'); as with claude-sonnet-5-5, the release time of day is not published, but a transcript row whose raw model ID is claude-haiku-5-5 cannot predate the release. Its prices are tiered by prompt length: the period's top-level values (0.10/0.01/0.125/0.20/0.50) apply to prompts up to 100,000 tokens, and prompt_tiers[0] (prompt_tokens_over 100000: 0.50/0.05/0.625/1.0/2.50) to prompts over 100,000 tokens, per the pricing page's two Haiku 5.5 rows (no footnote: cache_read is the standard 0.1x). Prompt length follows the pricing page's Long context pricing section: it counts all of a request's input tokens including cache reads and cache writes, and each request is priced on its own, so a request over the threshold pays the higher prices for every token kind, output included. A fact whose prompt length is unknown (Codex, a conflicting Claude dedup group, inconsistent usage) is priced at the top-level values as a lower_bound. A separate entry, never an alias of claude-haiku-4-5. fast_multiplier 1.0: the pricing page's fast-mode table lists only the Opus models. Claude Code's CHANGELOG entry for 2.1.293 states Haiku 5.5 is now the default Haiku model, so the `haiku` alias resolves to it from that version onward. schema_version is 2 because prompt_tiers is new: agent-cost 0.3.0 and earlier reject schema_version 2 as unsupported instead of silently pricing every claude-haiku-5-5 request at the cheaper tier, and a schema_version 1 catalog may not carry prompt_tiers."
|
|
16
17
|
],
|
|
17
18
|
"sources": [
|
|
18
19
|
{
|
|
@@ -97,6 +98,21 @@
|
|
|
97
98
|
"url": "https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md",
|
|
98
99
|
"retrieved_at": "2026-09-29",
|
|
99
100
|
"note": "Claude Code 2.1.284 entry: added Claude Sonnet 5.5 (claude-sonnet-5-5), now the default Sonnet model; the reason claude-sonnet-5-5 rows are expected in transcripts from that version"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"url": "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
104
|
+
"retrieved_at": "2026-10-09",
|
|
105
|
+
"note": "used for claude-haiku-5-5 (two Haiku 5.5 rows: prompts up to 100K tokens $0.10 / $0.125 / $0.20 / $0.01 / $0.50, prompts over 100K tokens $0.50 / $0.625 / $1 / $0.05 / $2.50; no footnote so cache_read is the standard 0.1x; fast-mode table lists Opus models only); Long context pricing section: a request's prompt length counts all of its input tokens including cache reads and cache writes, and each request is priced on its own"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"url": "https://platform.claude.com/docs/en/models/haiku-5-5/overview",
|
|
109
|
+
"retrieved_at": "2026-10-09",
|
|
110
|
+
"note": "Haiku 5.5 overview: Released October 7, 2026, model ID claude-haiku-5-5, Retirement not sooner than October 7, 2027"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"url": "https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md",
|
|
114
|
+
"retrieved_at": "2026-10-09",
|
|
115
|
+
"note": "Claude Code 2.1.293 entry: Added Claude Haiku 5.5 (claude-haiku-5-5), now the default Haiku model; 1M context, $0.10/$0.50 per MTok ($0.50/$2.50 for prompts over 100K); the reason claude-haiku-5-5 rows are expected in transcripts from that version"
|
|
100
116
|
}
|
|
101
117
|
],
|
|
102
118
|
"models": [
|
|
@@ -314,6 +330,33 @@
|
|
|
314
330
|
}
|
|
315
331
|
]
|
|
316
332
|
},
|
|
333
|
+
{
|
|
334
|
+
"model_key": "claude-haiku-5-5",
|
|
335
|
+
"aliases": [],
|
|
336
|
+
"fast_multiplier": "1.0",
|
|
337
|
+
"rates": [
|
|
338
|
+
{
|
|
339
|
+
"rate_id": "claude-haiku-5-5-launch-2026-10-07",
|
|
340
|
+
"effective_from": "2026-10-07T00:00:00+00:00",
|
|
341
|
+
"effective_until": null,
|
|
342
|
+
"input_nocache": "0.10",
|
|
343
|
+
"cache_read": "0.01",
|
|
344
|
+
"cache_write_5m": "0.125",
|
|
345
|
+
"cache_write_1h": "0.20",
|
|
346
|
+
"output": "0.50",
|
|
347
|
+
"prompt_tiers": [
|
|
348
|
+
{
|
|
349
|
+
"prompt_tokens_over": 100000,
|
|
350
|
+
"input_nocache": "0.50",
|
|
351
|
+
"cache_read": "0.05",
|
|
352
|
+
"cache_write_5m": "0.625",
|
|
353
|
+
"cache_write_1h": "1.0",
|
|
354
|
+
"output": "2.50"
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
},
|
|
317
360
|
{
|
|
318
361
|
"model_key": "gpt-5.6-sol",
|
|
319
362
|
"aliases": [],
|
|
@@ -16,7 +16,7 @@ from datetime import datetime, timezone
|
|
|
16
16
|
from decimal import Decimal, InvalidOperation
|
|
17
17
|
from importlib import resources
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Optional
|
|
19
|
+
from typing import Optional, Tuple
|
|
20
20
|
|
|
21
21
|
RATE_FIELDS = (
|
|
22
22
|
"input_nocache",
|
|
@@ -30,8 +30,11 @@ RATE_FIELDS = (
|
|
|
30
30
|
# knows how to interpret. A catalog claiming anything else is rejected
|
|
31
31
|
# outright rather than silently treated as USD-per-mtok -- a catalog in a
|
|
32
32
|
# different currency or unit priced as if it were USD-per-mtok would be
|
|
33
|
-
# wrong by a fixed, silent factor.
|
|
34
|
-
|
|
33
|
+
# wrong by a fixed, silent factor. Schema "2" adds optional per-period
|
|
34
|
+
# ``prompt_tiers``; it is a separate version (and "1" may not carry tiers)
|
|
35
|
+
# so that a reader that predates tiers rejects a tiered catalog as
|
|
36
|
+
# unsupported instead of silently pricing every request at the base tier.
|
|
37
|
+
SUPPORTED_SCHEMA_VERSIONS = ("1", "2")
|
|
35
38
|
SUPPORTED_CURRENCIES = ("USD",)
|
|
36
39
|
SUPPORTED_UNITS = ("per_mtok",)
|
|
37
40
|
|
|
@@ -40,12 +43,28 @@ class RatesValidationError(ValueError):
|
|
|
40
43
|
"""The rates catalog failed structural or business-rule validation."""
|
|
41
44
|
|
|
42
45
|
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class PromptTier:
|
|
48
|
+
"""Rate values for requests whose prompt length is strictly greater
|
|
49
|
+
than ``prompt_tokens_over``."""
|
|
50
|
+
|
|
51
|
+
prompt_tokens_over: int
|
|
52
|
+
values: dict
|
|
53
|
+
|
|
54
|
+
|
|
43
55
|
@dataclass(frozen=True)
|
|
44
56
|
class RatePeriod:
|
|
45
57
|
rate_id: str
|
|
46
58
|
effective_from: datetime
|
|
47
59
|
effective_until: Optional[datetime]
|
|
60
|
+
# Base tier: prompt length <= the first tier's threshold (or every
|
|
61
|
+
# request, when there are no tiers).
|
|
48
62
|
values: dict
|
|
63
|
+
# Ascending by threshold; a request uses the tier with the largest
|
|
64
|
+
# threshold its prompt length exceeds. Every value in a tiered period
|
|
65
|
+
# is non-null and non-decreasing tier to tier, so ``values`` is the
|
|
66
|
+
# cheapest (a lower bound) when the prompt length is unknown.
|
|
67
|
+
prompt_tiers: Tuple[PromptTier, ...] = ()
|
|
49
68
|
|
|
50
69
|
def covers(self, moment: datetime) -> bool:
|
|
51
70
|
if moment < self.effective_from:
|
|
@@ -125,7 +144,46 @@ def _parse_datetime(value, *, field_name: str) -> datetime:
|
|
|
125
144
|
return dt.astimezone(timezone.utc)
|
|
126
145
|
|
|
127
146
|
|
|
128
|
-
def
|
|
147
|
+
def _build_prompt_tiers(tiers_raw, base_values: dict, *, where: str) -> Tuple[PromptTier, ...]:
|
|
148
|
+
if not isinstance(tiers_raw, list):
|
|
149
|
+
raise RatesValidationError(f"{where}: prompt_tiers must be a list")
|
|
150
|
+
if not tiers_raw:
|
|
151
|
+
return ()
|
|
152
|
+
|
|
153
|
+
for f in RATE_FIELDS:
|
|
154
|
+
if base_values[f] is None:
|
|
155
|
+
raise RatesValidationError(f"{where}: {f} is required when prompt_tiers is present")
|
|
156
|
+
|
|
157
|
+
tiers = []
|
|
158
|
+
previous_values = base_values
|
|
159
|
+
previous_over = None
|
|
160
|
+
for idx, tier_raw in enumerate(tiers_raw):
|
|
161
|
+
tier_where = f"{where}.prompt_tiers[{idx}]"
|
|
162
|
+
if not isinstance(tier_raw, dict):
|
|
163
|
+
raise RatesValidationError(f"{tier_where}: must be an object")
|
|
164
|
+
over = tier_raw.get("prompt_tokens_over")
|
|
165
|
+
if isinstance(over, bool) or not isinstance(over, int) or over <= 0:
|
|
166
|
+
raise RatesValidationError(f"{tier_where}: prompt_tokens_over must be a positive int, got {over!r}")
|
|
167
|
+
if previous_over is not None and over <= previous_over:
|
|
168
|
+
raise RatesValidationError(
|
|
169
|
+
f"{tier_where}: prompt_tokens_over must be ascending without duplicates "
|
|
170
|
+
f"({over} after {previous_over})"
|
|
171
|
+
)
|
|
172
|
+
values = {f: _parse_decimal(tier_raw.get(f), field_name=f"{tier_where}.{f}") for f in RATE_FIELDS}
|
|
173
|
+
for f in RATE_FIELDS:
|
|
174
|
+
if values[f] is None:
|
|
175
|
+
raise RatesValidationError(f"{tier_where}: {f} is required")
|
|
176
|
+
if values[f] < previous_values[f]:
|
|
177
|
+
raise RatesValidationError(
|
|
178
|
+
f"{tier_where}: {f} {values[f]} is lower than the previous tier's {previous_values[f]}"
|
|
179
|
+
)
|
|
180
|
+
tiers.append(PromptTier(prompt_tokens_over=over, values=values))
|
|
181
|
+
previous_values = values
|
|
182
|
+
previous_over = over
|
|
183
|
+
return tuple(tiers)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _build_model_entry(raw: dict, *, schema_version: str) -> ModelEntry:
|
|
129
187
|
model_key = raw.get("model_key")
|
|
130
188
|
if not model_key or not isinstance(model_key, str):
|
|
131
189
|
raise RatesValidationError(f"model entry missing model_key: {raw!r}")
|
|
@@ -169,12 +227,22 @@ def _build_model_entry(raw: dict) -> ModelEntry:
|
|
|
169
227
|
f: _parse_decimal(rate_raw.get(f), field_name=f"{model_key}.{rate_id}.{f}")
|
|
170
228
|
for f in RATE_FIELDS
|
|
171
229
|
}
|
|
230
|
+
prompt_tiers: Tuple[PromptTier, ...] = ()
|
|
231
|
+
if "prompt_tiers" in rate_raw:
|
|
232
|
+
if schema_version == "1":
|
|
233
|
+
raise RatesValidationError(
|
|
234
|
+
f"{model_key}.{rate_id}: prompt_tiers requires schema_version \"2\""
|
|
235
|
+
)
|
|
236
|
+
prompt_tiers = _build_prompt_tiers(
|
|
237
|
+
rate_raw["prompt_tiers"], values, where=f"{model_key}.{rate_id}"
|
|
238
|
+
)
|
|
172
239
|
periods.append(
|
|
173
240
|
RatePeriod(
|
|
174
241
|
rate_id=rate_id,
|
|
175
242
|
effective_from=effective_from,
|
|
176
243
|
effective_until=effective_until,
|
|
177
244
|
values=values,
|
|
245
|
+
prompt_tiers=prompt_tiers,
|
|
178
246
|
)
|
|
179
247
|
)
|
|
180
248
|
|
|
@@ -227,7 +295,7 @@ def _validate_and_build(data: dict) -> RateCatalog:
|
|
|
227
295
|
|
|
228
296
|
models: dict = {}
|
|
229
297
|
for raw in data.get("models") or []:
|
|
230
|
-
entry = _build_model_entry(raw)
|
|
298
|
+
entry = _build_model_entry(raw, schema_version=schema_version)
|
|
231
299
|
if entry.model_key in models:
|
|
232
300
|
raise RatesValidationError(f"duplicate model_key: {entry.model_key}")
|
|
233
301
|
models[entry.model_key] = entry
|
|
@@ -127,6 +127,33 @@ def _extract_tokens(usage: dict) -> Optional[dict]:
|
|
|
127
127
|
return tokens
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
def _prompt_tokens(usage: dict) -> Optional[int]:
|
|
131
|
+
"""The request's prompt length -- ``input_tokens`` +
|
|
132
|
+
``cache_read_input_tokens`` + ``cache_creation_input_tokens`` (missing
|
|
133
|
+
counts as 0), the definition the pricing page's long-context tiers use.
|
|
134
|
+
|
|
135
|
+
Returns ``None`` when the usage is inconsistent: a ``cache_creation`` TTL
|
|
136
|
+
breakdown larger than ``cache_creation_input_tokens``, or a negative
|
|
137
|
+
field (including either TTL breakdown field). Raises
|
|
138
|
+
``TypeError``/``ValueError`` on a field that fails int conversion, which
|
|
139
|
+
the caller counts as malformed (as for ``_extract_tokens``).
|
|
140
|
+
"""
|
|
141
|
+
input_tokens = int(usage.get("input_tokens") or 0)
|
|
142
|
+
cache_read = int(usage.get("cache_read_input_tokens") or 0)
|
|
143
|
+
cache_creation_total = int(usage.get("cache_creation_input_tokens") or 0)
|
|
144
|
+
cache_creation = usage.get("cache_creation")
|
|
145
|
+
if isinstance(cache_creation, dict):
|
|
146
|
+
ephemeral_5m = int(cache_creation.get("ephemeral_5m_input_tokens") or 0)
|
|
147
|
+
ephemeral_1h = int(cache_creation.get("ephemeral_1h_input_tokens") or 0)
|
|
148
|
+
if ephemeral_5m < 0 or ephemeral_1h < 0:
|
|
149
|
+
return None
|
|
150
|
+
if ephemeral_5m + ephemeral_1h > cache_creation_total:
|
|
151
|
+
return None
|
|
152
|
+
if input_tokens < 0 or cache_read < 0 or cache_creation_total < 0:
|
|
153
|
+
return None
|
|
154
|
+
return input_tokens + cache_read + cache_creation_total
|
|
155
|
+
|
|
156
|
+
|
|
130
157
|
def _valid_id(value) -> Optional[str]:
|
|
131
158
|
"""Accept only a non-empty ``str`` as a dedup identifier.
|
|
132
159
|
|
|
@@ -476,6 +503,11 @@ def parse_session_detailed(jsonl_path: Path) -> ClaudeParseResult:
|
|
|
476
503
|
if tokens is None:
|
|
477
504
|
malformed += 1
|
|
478
505
|
continue
|
|
506
|
+
try:
|
|
507
|
+
prompt_tokens = _prompt_tokens(usage)
|
|
508
|
+
except (TypeError, ValueError):
|
|
509
|
+
malformed += 1
|
|
510
|
+
continue
|
|
479
511
|
|
|
480
512
|
model_raw = message.get("model") or "(unknown)"
|
|
481
513
|
mode = _detect_mode(usage)
|
|
@@ -491,6 +523,7 @@ def parse_session_detailed(jsonl_path: Path) -> ClaudeParseResult:
|
|
|
491
523
|
"sid": sid,
|
|
492
524
|
"stop_reason": stop_reason,
|
|
493
525
|
"tokens": tokens,
|
|
526
|
+
"prompt_tokens": prompt_tokens,
|
|
494
527
|
}
|
|
495
528
|
|
|
496
529
|
key = (msg_id, req_id) if (msg_id is not None and req_id is not None) else None
|
|
@@ -546,6 +579,8 @@ def parse_session_detailed(jsonl_path: Path) -> ClaudeParseResult:
|
|
|
546
579
|
group_conflicting = not is_streaming_progression
|
|
547
580
|
if group_conflicting:
|
|
548
581
|
conflicting_duplicate_groups += 1
|
|
582
|
+
prompt_lengths = {r["prompt_tokens"] for r in rows}
|
|
583
|
+
prompt_lengths_agree = len(prompt_lengths) == 1 and None not in prompt_lengths
|
|
549
584
|
|
|
550
585
|
if group_skipped > 0 or group_conflicting:
|
|
551
586
|
dedup_units.append(
|
|
@@ -563,6 +598,17 @@ def parse_session_detailed(jsonl_path: Path) -> ClaudeParseResult:
|
|
|
563
598
|
"mode": _resolve_mode(rows, adopted),
|
|
564
599
|
"sid": adopted["sid"],
|
|
565
600
|
"tokens": adopted["tokens"],
|
|
601
|
+
# A conflicting group's rows disagree (possibly on the input
|
|
602
|
+
# side), and a non-conflicting group's rows can still
|
|
603
|
+
# disagree on prompt length (one row's usage inconsistent,
|
|
604
|
+
# so None). Either way the adopted row's prompt length isn't
|
|
605
|
+
# a settled fact: leave it unknown (priced as a lower bound)
|
|
606
|
+
# rather than pick a tier from it.
|
|
607
|
+
"prompt_tokens": (
|
|
608
|
+
adopted["prompt_tokens"]
|
|
609
|
+
if not group_conflicting and prompt_lengths_agree
|
|
610
|
+
else None
|
|
611
|
+
),
|
|
566
612
|
}
|
|
567
613
|
source_quality = "ok"
|
|
568
614
|
output_source_quality = "ok" if _is_final(adopted) else "output_lower_bound"
|
|
@@ -580,6 +626,7 @@ def parse_session_detailed(jsonl_path: Path) -> ClaudeParseResult:
|
|
|
580
626
|
tokens=amount,
|
|
581
627
|
mode=row["mode"],
|
|
582
628
|
source_quality=output_source_quality if kind == "output" else source_quality,
|
|
629
|
+
prompt_tokens=row["prompt_tokens"],
|
|
583
630
|
)
|
|
584
631
|
)
|
|
585
632
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-agent-cost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Estimate AI coding agent (Claude Code / Codex CLI) token usage and cost from local logs
|
|
5
5
|
Author: shiki-yusuke
|
|
6
6
|
License: MIT
|
|
@@ -244,6 +244,16 @@ network, never calls `gh`, and never resolves branches or PRs.
|
|
|
244
244
|
present in the log, it's used; otherwise the cache-write tokens are priced
|
|
245
245
|
at the 5-minute rate as an explicit **lower bound** and flagged
|
|
246
246
|
`lower_bound` rather than guessed at the (more expensive) 1-hour rate.
|
|
247
|
+
Each Claude fact also carries its request's prompt length
|
|
248
|
+
(`prompt_tokens` = `input_tokens` + `cache_read_input_tokens` +
|
|
249
|
+
`cache_creation_input_tokens`), used only to pick the tier of a model
|
|
250
|
+
whose price depends on prompt length (`claude-haiku-5-5`).
|
|
251
|
+
- **Prompt-length tiers need a known prompt length.** A fact whose prompt
|
|
252
|
+
length is unknown -- every Codex fact (a delta of cumulative totals, not
|
|
253
|
+
one request), a Claude fact from a conflicting dedup group, or a Claude
|
|
254
|
+
row whose cache-write TTL breakdown exceeds its `cache_creation_input_tokens`
|
|
255
|
+
-- is priced at the model's base (cheapest) tier and flagged
|
|
256
|
+
`lower_bound`. Models without tiers are unaffected.
|
|
247
257
|
- **Codex CLI**: rollout files record a *cumulative* token count after each
|
|
248
258
|
turn; agent-cost turns that into per-turn deltas. Under the Codex token-rate
|
|
249
259
|
tariff used here, cache writes are not added as a separate charge. The current
|
|
@@ -303,6 +313,37 @@ carries a `catalog_version`, a list of `sources` (the pricing page a rate
|
|
|
303
313
|
came from), and is validated on load (no duplicate model keys or aliases,
|
|
304
314
|
no negative rates, no overlapping periods for the same model).
|
|
305
315
|
|
|
316
|
+
A rate period can price by prompt length (catalog `schema_version` `"2"`).
|
|
317
|
+
The period's own five values are the base tier, for prompts up to the first
|
|
318
|
+
threshold; each entry in `prompt_tiers` applies to prompts *strictly over*
|
|
319
|
+
its `prompt_tokens_over`, and a request uses the largest threshold it
|
|
320
|
+
exceeds. Prompt length is the request's input + cache-read + cache-write
|
|
321
|
+
tokens, and the chosen tier prices every token kind of that request,
|
|
322
|
+
output included:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"rate_id": "claude-haiku-5-5-launch-2026-10-07",
|
|
327
|
+
"effective_from": "2026-10-07T00:00:00+00:00",
|
|
328
|
+
"effective_until": null,
|
|
329
|
+
"input_nocache": "0.10", "cache_read": "0.01", "cache_write_5m": "0.125", "cache_write_1h": "0.20", "output": "0.50",
|
|
330
|
+
"prompt_tiers": [
|
|
331
|
+
{ "prompt_tokens_over": 100000,
|
|
332
|
+
"input_nocache": "0.50", "cache_read": "0.05", "cache_write_5m": "0.625", "cache_write_1h": "1.0", "output": "2.50" }
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
In a period with `prompt_tiers`, all five values of the base and of every
|
|
338
|
+
tier must be present and non-null, thresholds must be positive integers in
|
|
339
|
+
ascending order without duplicates, and every value must be greater than or
|
|
340
|
+
equal to the same value in the previous tier (the base first). That
|
|
341
|
+
monotonicity is what makes the base tier a true lower bound for a fact whose
|
|
342
|
+
prompt length is unknown. A `schema_version` `"1"` catalog may not contain
|
|
343
|
+
`prompt_tiers`; agent-cost 0.3.0 and earlier reject a `"2"` catalog as
|
|
344
|
+
unsupported rather than mispricing it, so a custom `--rates` file that uses
|
|
345
|
+
tiers must declare `"2"`.
|
|
346
|
+
|
|
306
347
|
To use your own catalog instead of the one bundled with the package, pass
|
|
307
348
|
`--rates path/to/rates.json` to `report` or `export` -- this fully replaces
|
|
308
349
|
the bundled catalog, it does not merge with it. Inspect any catalog with:
|
|
@@ -402,10 +443,11 @@ agent-cost makes zero network calls. `agent-cost export`'s JSONL never
|
|
|
402
443
|
includes absolute file paths, rollout paths, prompt/message content, or git
|
|
403
444
|
branch names -- only the fields needed to reproduce a cost estimate:
|
|
404
445
|
`occurred_at_utc`, `agent`, `session_id`, `model_raw`, `model_key`,
|
|
405
|
-
`token_kind`, `tokens`, `mode`,
|
|
446
|
+
`token_kind`, `tokens`, `mode`, `source_quality` (a fixed-vocabulary
|
|
406
447
|
caveat about how that one fact was derived: `"ok"`, Codex's
|
|
407
448
|
`"first_event_delta"`, or Claude's `"identity_missing"` /
|
|
408
|
-
`"output_lower_bound"` -- never null)
|
|
449
|
+
`"output_lower_bound"` -- never null), and `prompt_tokens` (the request's
|
|
450
|
+
prompt length as an integer, or null when unknown).
|
|
409
451
|
|
|
410
452
|
## License
|
|
411
453
|
|
|
@@ -429,6 +471,10 @@ MIT. See [LICENSE](LICENSE).
|
|
|
429
471
|
未知のモデル・単価表にない token 種別は `unpriced` として扱い、憶測の価格を出しません。
|
|
430
472
|
- 単価表 (`agent_cost/rates.json`) は履歴型カタログで、値上げは新しい期間として追加します。
|
|
431
473
|
`--rates PATH` で別カタログに完全差し替えできます。
|
|
474
|
+
- prompt 長で単価が変わるモデル(`claude-haiku-5-5`、100,000 tokens 超で高い単価)は期間の
|
|
475
|
+
`prompt_tiers`(schema_version `"2"`)で表し、リクエストごとの prompt 長(input + cache read +
|
|
476
|
+
cache write)で tier を選びます。prompt 長が不明な fact(Codex 由来、conflicting group、usage
|
|
477
|
+
不整合)は基底 tier(最安)で **下限推計**(`lower_bound`)になります。
|
|
432
478
|
- `agent-cost measure --session-id ID [--session-id ID ...] --format json` は他プログラムから
|
|
433
479
|
subprocess で叩くための機械可読な契約です(`protocol_version: "measure/v1"`)。指定した
|
|
434
480
|
session_id が1件も見つからなくても終了コードは0(空集計として表現)、`--session-id` 未指定など
|