coding-agent-cost 0.2.0__tar.gz → 0.2.2__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.
Files changed (35) hide show
  1. {coding_agent_cost-0.2.0/coding_agent_cost.egg-info → coding_agent_cost-0.2.2}/PKG-INFO +5 -5
  2. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/README.md +4 -4
  3. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/__init__.py +1 -1
  4. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/rates.json +76 -5
  5. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2/coding_agent_cost.egg-info}/PKG-INFO +5 -5
  6. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/coding_agent_cost.egg-info/SOURCES.txt +2 -0
  7. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/pyproject.toml +1 -1
  8. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_astra_pricing.py +2 -2
  9. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_e0a_review3_hardening.py +3 -3
  10. coding_agent_cost-0.2.2/tests/test_opus_5_5_rates.py +74 -0
  11. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_rates.py +1 -1
  12. coding_agent_cost-0.2.2/tests/test_sonnet_5_5_rates.py +69 -0
  13. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_version_consistency.py +4 -4
  14. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/LICENSE +0 -0
  15. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/aggregate.py +0 -0
  16. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/cli.py +0 -0
  17. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/config.py +0 -0
  18. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/facts.py +0 -0
  19. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/rates.py +0 -0
  20. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/readers/__init__.py +0 -0
  21. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/readers/claude.py +0 -0
  22. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/readers/codex.py +0 -0
  23. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/agent_cost/renderers.py +0 -0
  24. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/coding_agent_cost.egg-info/dependency_links.txt +0 -0
  25. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/coding_agent_cost.egg-info/entry_points.txt +0 -0
  26. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/coding_agent_cost.egg-info/top_level.txt +0 -0
  27. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/setup.cfg +0 -0
  28. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_aggregate.py +0 -0
  29. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_cli.py +0 -0
  30. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_e0a_fable_5_1_and_sonnet_5_correction.py +0 -0
  31. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_facts.py +0 -0
  32. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_measure_v1_contract.py +0 -0
  33. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_reader_claude.py +0 -0
  34. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_reader_codex.py +0 -0
  35. {coding_agent_cost-0.2.0 → coding_agent_cost-0.2.2}/tests/test_reader_dedup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-agent-cost
3
- Version: 0.2.0
3
+ Version: 0.2.2
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
@@ -73,7 +73,8 @@ agent-cost report
73
73
  The PyPI distribution is named [`coding-agent-cost`](https://pypi.org/project/coding-agent-cost/),
74
74
  while the command remains `agent-cost` and the import remains `agent_cost`. The shorter PyPI
75
75
  name is unavailable because of PyPI's similarity rule; this project is not affiliated with the
76
- unrelated `agentcost` distribution.
76
+ unrelated `agentcost` distribution. Releases are published from a `v*` tag by GitHub Actions
77
+ through PyPI's Trusted Publisher; see [docs/release.md](docs/release.md).
77
78
 
78
79
  ## Synthetic output example
79
80
 
@@ -266,8 +267,7 @@ network, never calls `gh`, and never resolves branches or PRs.
266
267
  in the report's `data_quality` block instead of being silently dropped or
267
268
  clamped to zero.
268
269
  - **Known catalog gaps**, tracked in `agent_cost/rates.json`'s `notes`:
269
- `claude-opus-5`'s launch date could not be confirmed from an authoritative
270
- source, so its rate period's `effective_from` is a placeholder. `gpt-5.6`
270
+ `gpt-5.6`
271
271
  (Sol/Terra/Luna) credits could not be confirmed from the primary source
272
272
  (`help.openai.com`'s Codex rate card returns HTTP 403 to automated
273
273
  fetches); the values in the catalog come from several independent
@@ -342,7 +342,7 @@ agent-cost measure --session-id <id> [--session-id <id> ...] \
342
342
  ```json
343
343
  {
344
344
  "protocol_version": "measure/v1",
345
- "producer_version": "0.2.0",
345
+ "producer_version": "0.2.2",
346
346
  "accounting_basis": "agent-cost-raw-total/v2",
347
347
  "generated_at": "...",
348
348
  "window": { "since": "...", "until": null },
@@ -47,7 +47,8 @@ agent-cost report
47
47
  The PyPI distribution is named [`coding-agent-cost`](https://pypi.org/project/coding-agent-cost/),
48
48
  while the command remains `agent-cost` and the import remains `agent_cost`. The shorter PyPI
49
49
  name is unavailable because of PyPI's similarity rule; this project is not affiliated with the
50
- unrelated `agentcost` distribution.
50
+ unrelated `agentcost` distribution. Releases are published from a `v*` tag by GitHub Actions
51
+ through PyPI's Trusted Publisher; see [docs/release.md](docs/release.md).
51
52
 
52
53
  ## Synthetic output example
53
54
 
@@ -240,8 +241,7 @@ network, never calls `gh`, and never resolves branches or PRs.
240
241
  in the report's `data_quality` block instead of being silently dropped or
241
242
  clamped to zero.
242
243
  - **Known catalog gaps**, tracked in `agent_cost/rates.json`'s `notes`:
243
- `claude-opus-5`'s launch date could not be confirmed from an authoritative
244
- source, so its rate period's `effective_from` is a placeholder. `gpt-5.6`
244
+ `gpt-5.6`
245
245
  (Sol/Terra/Luna) credits could not be confirmed from the primary source
246
246
  (`help.openai.com`'s Codex rate card returns HTTP 403 to automated
247
247
  fetches); the values in the catalog come from several independent
@@ -316,7 +316,7 @@ agent-cost measure --session-id <id> [--session-id <id> ...] \
316
316
  ```json
317
317
  {
318
318
  "protocol_version": "measure/v1",
319
- "producer_version": "0.2.0",
319
+ "producer_version": "0.2.2",
320
320
  "accounting_basis": "agent-cost-raw-total/v2",
321
321
  "generated_at": "...",
322
322
  "window": { "since": "...", "until": null },
@@ -6,4 +6,4 @@ against a versioned rate catalog. Everything happens locally; there are no
6
6
  network calls.
7
7
  """
8
8
 
9
- __version__ = "0.2.0"
9
+ __version__ = "0.2.2"
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "schema_version": "1",
3
- "catalog_version": "2026-09-09",
3
+ "catalog_version": "2026-09-29",
4
4
  "currency": "USD",
5
5
  "unit": "per_mtok",
6
6
  "usd_per_credit": "0.04",
7
7
  "notes": [
8
8
  "All Claude rates are standard (non-batch, non-priority) API prices. Batch API is roughly 50% off, but agent-cost's logs have no signal for whether a given request went through Batch, so batch usage is priced at standard rates and will overstate cost for anyone using Batch.",
9
- "claude-opus-5's launch date could not be confirmed from an authoritative source as of this catalog_version; its rate period's effective_from (2026-07-01) is a placeholder -- update it once confirmed.",
9
+ "claude-opus-5 effective_from corrected 2026-09-23: the 2026-07-01 placeholder (rate_id claude-opus-5-launch-date-unconfirmed) is replaced by the official launch date 2026-07-24 (anthropic.com/news/claude-opus-5, dated 'Jul 24, 2026'), rate_id claude-opus-5-launch-2026-07-24 -- the period is replaced rather than edited in place, following the claude-sonnet-5-standard -> claude-sonnet-5-standard-2026-09-01 correction precedent (rate_id is not consumed by any reader or report field; reports pin catalog_version + sha256). The source gives a calendar date only, so effective_from is 00:00:00Z of that day: a transcript row carrying this model ID cannot predate the public launch, so nothing on the launch day is mispriced by the earlier-of-day boundary. The earliest claude-opus-5 row across local transcripts is 2026-07-28T18:22:52Z, so no locally observed event moves from priced to unpriced; events between 2026-07-01 and 2026-07-23 that were priced under the placeholder are now unpriced, which is the correct treatment for a model that did not exist yet.",
10
10
  "gpt-5.6 (sol/terra/luna) credits could not be confirmed from the primary source (help.openai.com's Codex rate card returned HTTP 403 to automated fetches). The values here were cross-checked across several independent secondary sources that agreed with each other and are internally consistent (Sol's $5/$30 per-MTok API price x 25 = 125/750 credits, matching usd_per_credit=0.04); see the gpt-5.6-* sources entries below. Treat effective_from (2026-07-01) as a placeholder and re-verify against the primary rate card when it becomes reachable.",
11
11
  "gpt-6-astra: Codex standard token-based rates only; not API pricing, legacy message metering, or actual contractual charges. Confirmed 2026-09-06 JST (2026-09-05T17:18:23Z). effective_from is this observation cutoff, NOT an official launch or price-start time; earlier events remain unpriced. No Codex long-context surcharge or cache-write charge is applied. Fast multiplier 2.5 applies to explicit priority request settings with matching owned turn/model context; explicit default uses Standard. Missing or ambiguous Astra modes remain unpriced. These settings do not confirm the backend processing tier. See docs/astra-pricing.md for compatibility limits and the separate GPT-5.6 discrepancy report.",
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
- "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."
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
+ "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."
14
16
  ],
15
17
  "sources": [
16
18
  {
@@ -60,6 +62,41 @@
60
62
  "url": "https://platform.claude.com/docs/en/build-with-claude/prompt-caching",
61
63
  "retrieved_at": "2026-09-09",
62
64
  "note": "cache write TTL multipliers (5m = 1.25x, 1h = 2x), cross-checked against the pricing page cache columns"
65
+ },
66
+ {
67
+ "url": "https://platform.claude.com/docs/en/about-claude/pricing",
68
+ "retrieved_at": "2026-09-23",
69
+ "note": "used for claude-opus-5-5 (Opus 5.5 row: $4 / $5 / $8 / $0.20 / $20, footnote 2 cache_read 0.05x, fast mode $8/$40); fetched via WebFetch, no local snapshot"
70
+ },
71
+ {
72
+ "url": "https://www.anthropic.com/claude-opus-5-5",
73
+ "retrieved_at": "2026-09-23",
74
+ "note": "Opus 5.5 announcement: public launch date September 22, 2026, model ID claude-opus-5-5, Sonnet 5.5 / Haiku 5.5 to follow"
75
+ },
76
+ {
77
+ "url": "https://www.anthropic.com/news/claude-opus-5",
78
+ "retrieved_at": "2026-09-23",
79
+ "note": "Opus 5 announcement dated Jul 24, 2026; used to replace the claude-opus-5 effective_from placeholder"
80
+ },
81
+ {
82
+ "url": "https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md",
83
+ "retrieved_at": "2026-09-23",
84
+ "note": "Claude Code 2.1.280 entry: Claude Opus 5.5 became the default model (1M context, $4/$20 per MTok, $0.20/MTok cache reads); the reason claude-opus-5-5 rows are expected in transcripts from that version"
85
+ },
86
+ {
87
+ "url": "https://platform.claude.com/docs/en/about-claude/pricing",
88
+ "retrieved_at": "2026-09-29",
89
+ "note": "used for claude-sonnet-5-5 (Sonnet 5.5 row: $2 / $2.50 / $4 / $0.20 / $10, no footnote so cache_read is the standard 0.1x; fast-mode table lists Opus models only); fetched via WebFetch, no local snapshot"
90
+ },
91
+ {
92
+ "url": "https://www.anthropic.com/claude-sonnet-5-5",
93
+ "retrieved_at": "2026-09-29",
94
+ "note": "Sonnet 5.5 announcement: public launch date September 28, 2026, model ID claude-sonnet-5-5, $2/$10 per MTok, Haiku 5.5 to follow in the coming weeks"
95
+ },
96
+ {
97
+ "url": "https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md",
98
+ "retrieved_at": "2026-09-29",
99
+ "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"
63
100
  }
64
101
  ],
65
102
  "models": [
@@ -137,8 +174,8 @@
137
174
  "fast_multiplier": "2.0",
138
175
  "rates": [
139
176
  {
140
- "rate_id": "claude-opus-5-launch-date-unconfirmed",
141
- "effective_from": "2026-07-01T00:00:00+00:00",
177
+ "rate_id": "claude-opus-5-launch-2026-07-24",
178
+ "effective_from": "2026-07-24T00:00:00+00:00",
142
179
  "effective_until": null,
143
180
  "input_nocache": "5.0",
144
181
  "cache_read": "0.50",
@@ -148,6 +185,23 @@
148
185
  }
149
186
  ]
150
187
  },
188
+ {
189
+ "model_key": "claude-opus-5-5",
190
+ "aliases": [],
191
+ "fast_multiplier": "2.0",
192
+ "rates": [
193
+ {
194
+ "rate_id": "claude-opus-5-5-launch-2026-09-22",
195
+ "effective_from": "2026-09-22T00:00:00+00:00",
196
+ "effective_until": null,
197
+ "input_nocache": "4.0",
198
+ "cache_read": "0.20",
199
+ "cache_write_5m": "5.0",
200
+ "cache_write_1h": "8.0",
201
+ "output": "20.0"
202
+ }
203
+ ]
204
+ },
151
205
  {
152
206
  "model_key": "claude-opus-4-7",
153
207
  "aliases": [],
@@ -226,6 +280,23 @@
226
280
  }
227
281
  ]
228
282
  },
283
+ {
284
+ "model_key": "claude-sonnet-5-5",
285
+ "aliases": [],
286
+ "fast_multiplier": "1.0",
287
+ "rates": [
288
+ {
289
+ "rate_id": "claude-sonnet-5-5-launch-2026-09-28",
290
+ "effective_from": "2026-09-28T00:00:00+00:00",
291
+ "effective_until": null,
292
+ "input_nocache": "2.0",
293
+ "cache_read": "0.20",
294
+ "cache_write_5m": "2.50",
295
+ "cache_write_1h": "4.0",
296
+ "output": "10.0"
297
+ }
298
+ ]
299
+ },
229
300
  {
230
301
  "model_key": "claude-haiku-4-5",
231
302
  "aliases": [],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-agent-cost
3
- Version: 0.2.0
3
+ Version: 0.2.2
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
@@ -73,7 +73,8 @@ agent-cost report
73
73
  The PyPI distribution is named [`coding-agent-cost`](https://pypi.org/project/coding-agent-cost/),
74
74
  while the command remains `agent-cost` and the import remains `agent_cost`. The shorter PyPI
75
75
  name is unavailable because of PyPI's similarity rule; this project is not affiliated with the
76
- unrelated `agentcost` distribution.
76
+ unrelated `agentcost` distribution. Releases are published from a `v*` tag by GitHub Actions
77
+ through PyPI's Trusted Publisher; see [docs/release.md](docs/release.md).
77
78
 
78
79
  ## Synthetic output example
79
80
 
@@ -266,8 +267,7 @@ network, never calls `gh`, and never resolves branches or PRs.
266
267
  in the report's `data_quality` block instead of being silently dropped or
267
268
  clamped to zero.
268
269
  - **Known catalog gaps**, tracked in `agent_cost/rates.json`'s `notes`:
269
- `claude-opus-5`'s launch date could not be confirmed from an authoritative
270
- source, so its rate period's `effective_from` is a placeholder. `gpt-5.6`
270
+ `gpt-5.6`
271
271
  (Sol/Terra/Luna) credits could not be confirmed from the primary source
272
272
  (`help.openai.com`'s Codex rate card returns HTTP 403 to automated
273
273
  fetches); the values in the catalog come from several independent
@@ -342,7 +342,7 @@ agent-cost measure --session-id <id> [--session-id <id> ...] \
342
342
  ```json
343
343
  {
344
344
  "protocol_version": "measure/v1",
345
- "producer_version": "0.2.0",
345
+ "producer_version": "0.2.2",
346
346
  "accounting_basis": "agent-cost-raw-total/v2",
347
347
  "generated_at": "...",
348
348
  "window": { "since": "...", "until": null },
@@ -24,8 +24,10 @@ tests/test_e0a_fable_5_1_and_sonnet_5_correction.py
24
24
  tests/test_e0a_review3_hardening.py
25
25
  tests/test_facts.py
26
26
  tests/test_measure_v1_contract.py
27
+ tests/test_opus_5_5_rates.py
27
28
  tests/test_rates.py
28
29
  tests/test_reader_claude.py
29
30
  tests/test_reader_codex.py
30
31
  tests/test_reader_dedup.py
32
+ tests/test_sonnet_5_5_rates.py
31
33
  tests/test_version_consistency.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "coding-agent-cost"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Estimate AI coding agent (Claude Code / Codex CLI) token usage and cost from local logs"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -45,7 +45,7 @@ def test_standard_fast_and_unknown_mode_prices(kind, usd, credits, mode, multipl
45
45
 
46
46
  def test_catalog_observation_boundary_is_not_backdated():
47
47
  catalog = load_rates()
48
- assert catalog.catalog_version == "2026-09-09"
48
+ assert catalog.catalog_version == "2026-09-29"
49
49
  entry = catalog.models["gpt-6-astra"]
50
50
  assert entry.aliases == ()
51
51
  assert entry.rates[0].effective_from == CUTOFF
@@ -149,7 +149,7 @@ def test_measure_reads_isolated_synthetic_astra_with_existing_contract(tmp_path,
149
149
  assert cli.main(["measure", "--session-id", "synthetic-astra", "--agent", "codex"]) == 0
150
150
  report = json.loads(capsys.readouterr().out)
151
151
  assert report["protocol_version"] == "measure/v1"
152
- assert report["rates"]["catalog_version"] == "2026-09-09"
152
+ assert report["rates"]["catalog_version"] == "2026-09-29"
153
153
  assert len(report["rates"]["sha256"]) == 64
154
154
  assert report["sessions"]["synthetic-astra"]["totals"]["estimated_cost_usd"] == 122
155
155
  assert report["total"]["totals"]["credits"] == 3050
@@ -19,7 +19,7 @@ architect レビュー (3 巡目) と builder レビューで、既存テスト
19
19
  かつ確定金額が計上されない。境界ちょうどは priced になる。
20
20
 
21
21
  条件D (PR レビュー3点目・追加分): catalog は
22
- catalog_version == "2026-09-09" であり、sources の中に url
22
+ catalog_version == "2026-09-29"(rates.json の現行値)であり、sources の中に url
23
23
  "https://platform.claude.com/docs/en/about-claude/pricing" の
24
24
  entry があって、その note に
25
25
  "sha256=d79ad28567196bd55dd50e2fd89341b9da9774a45c1d02fcf387078507fd15e0"
@@ -224,7 +224,7 @@ def test_condition_c_claude_fable_5_1_at_effective_from_boundary_is_priced_contr
224
224
 
225
225
 
226
226
  def test_condition_d_catalog_version_and_pricing_source_sha256_note():
227
- """条件D: catalog_version == "2026-09-09" であり、sources に url
227
+ """条件D: catalog_version == "2026-09-29"(rates.json の現行値)であり、sources に url
228
228
  "https://platform.claude.com/docs/en/about-claude/pricing" の entry が
229
229
  あって、その note に指定の sha256 文字列を含むこと。
230
230
 
@@ -233,7 +233,7 @@ def test_condition_d_catalog_version_and_pricing_source_sha256_note():
233
233
  ダイジェストを貼り付けてしまう。
234
234
  """
235
235
  catalog = load_rates()
236
- assert catalog.catalog_version == "2026-09-09"
236
+ assert catalog.catalog_version == "2026-09-29"
237
237
 
238
238
  matches = [
239
239
  s
@@ -0,0 +1,74 @@
1
+ """Acceptance tests for the claude-opus-5-5 rate entry and the claude-opus-5
2
+ effective_from correction (catalog_version 2026-09-23).
3
+
4
+ Primary sources: platform.claude.com/docs/en/about-claude/pricing (Opus 5.5
5
+ row + footnote 2), anthropic.com/claude-opus-5-5 (launch 2026-09-22),
6
+ anthropic.com/news/claude-opus-5 (launch 2026-07-24).
7
+ """
8
+
9
+ from datetime import datetime, timezone
10
+ from decimal import Decimal
11
+
12
+ from agent_cost.rates import load_rates
13
+
14
+ UTC = timezone.utc
15
+
16
+
17
+ def dt(s):
18
+ return datetime.fromisoformat(s).replace(tzinfo=UTC)
19
+
20
+
21
+ def test_claude_opus_5_5_resolves_with_pricing_page_values_on_or_after_launch():
22
+ catalog = load_rates()
23
+ for occurred_at in ("2026-09-22T00:00:00+00:00", "2026-12-01T00:00:00+00:00"):
24
+ resolved, period = catalog.rate_for("claude-opus-5-5", dt(occurred_at))
25
+ assert resolved == "claude-opus-5-5"
26
+ assert period is not None
27
+ assert period.rate_id == "claude-opus-5-5-launch-2026-09-22"
28
+ assert period.values["input_nocache"] == Decimal("4.0")
29
+ assert period.values["cache_read"] == Decimal("0.20")
30
+ assert period.values["cache_write_5m"] == Decimal("5.0")
31
+ assert period.values["cache_write_1h"] == Decimal("8.0")
32
+ assert period.values["output"] == Decimal("20.0")
33
+
34
+
35
+ def test_claude_opus_5_5_is_unpriced_before_launch_date():
36
+ catalog = load_rates()
37
+ resolved, before = catalog.rate_for("claude-opus-5-5", dt("2026-09-21T23:59:59+00:00"))
38
+ assert resolved == "claude-opus-5-5"
39
+ assert before is None
40
+
41
+
42
+ def test_claude_opus_5_5_cache_read_is_0_05x_base_input_not_standard_0_1x():
43
+ # Footnote 2 on the pricing page: Opus 5.5 cache hits are 0.05x base input.
44
+ catalog = load_rates()
45
+ _, period = catalog.rate_for("claude-opus-5-5", dt("2026-10-01T00:00:00+00:00"))
46
+ assert period.values["cache_read"] == period.values["input_nocache"] * Decimal("0.05")
47
+ assert period.values["cache_read"] != period.values["input_nocache"] * Decimal("0.1")
48
+
49
+
50
+ def test_claude_opus_5_5_fast_multiplier_2x():
51
+ catalog = load_rates()
52
+ assert catalog.models["claude-opus-5-5"].fast_multiplier == Decimal("2.0")
53
+
54
+
55
+ def test_claude_opus_5_5_is_not_an_alias_of_claude_opus_5():
56
+ catalog = load_rates()
57
+ assert "claude-opus-5-5" not in catalog.models["claude-opus-5"].aliases
58
+ assert catalog.models["claude-opus-5-5"].aliases == ()
59
+
60
+
61
+ def test_claude_opus_5_effective_from_is_the_official_launch_date():
62
+ catalog = load_rates()
63
+ resolved, before = catalog.rate_for("claude-opus-5", dt("2026-07-23T23:59:59+00:00"))
64
+ assert resolved == "claude-opus-5"
65
+ assert before is None
66
+ _, on_launch = catalog.rate_for("claude-opus-5", dt("2026-07-24T00:00:00+00:00"))
67
+ assert on_launch is not None
68
+ assert on_launch.rate_id == "claude-opus-5-launch-2026-07-24"
69
+ assert on_launch.values["input_nocache"] == Decimal("5.0")
70
+ assert on_launch.values["cache_read"] == Decimal("0.50")
71
+ assert on_launch.values["cache_write_5m"] == Decimal("6.25")
72
+ assert on_launch.values["cache_write_1h"] == Decimal("10.0")
73
+ assert on_launch.values["output"] == Decimal("25.0")
74
+ assert on_launch.effective_until is None
@@ -88,7 +88,7 @@ def test_claude_opus_5_is_priced_with_fast_multiplier_2x():
88
88
  catalog = load_rates()
89
89
  entry = catalog.models["claude-opus-5"]
90
90
  assert entry.fast_multiplier == Decimal("2.0")
91
- _, period = catalog.rate_for("claude-opus-5", dt("2026-07-15T00:00:00+00:00"))
91
+ _, period = catalog.rate_for("claude-opus-5", dt("2026-08-01T00:00:00+00:00"))
92
92
  assert period.values["input_nocache"] == Decimal("5.0")
93
93
  assert period.values["output"] == Decimal("25.0")
94
94
 
@@ -0,0 +1,69 @@
1
+ """Acceptance tests for the claude-sonnet-5-5 rate entry (catalog_version 2026-09-29).
2
+
3
+ Primary sources: platform.claude.com/docs/en/about-claude/pricing (Sonnet 5.5
4
+ row, no cache_read footnote), anthropic.com/claude-sonnet-5-5 (launch
5
+ 2026-09-28), Claude Code CHANGELOG 2.1.284 (Sonnet 5.5 is the default Sonnet).
6
+ """
7
+
8
+ from datetime import datetime, timezone
9
+ from decimal import Decimal
10
+
11
+ from agent_cost.rates import load_rates
12
+
13
+ UTC = timezone.utc
14
+
15
+
16
+ def dt(s):
17
+ return datetime.fromisoformat(s).replace(tzinfo=UTC)
18
+
19
+
20
+ def test_claude_sonnet_5_5_resolves_with_pricing_page_values_on_or_after_launch():
21
+ catalog = load_rates()
22
+ for occurred_at in ("2026-09-28T00:00:00+00:00", "2026-12-01T00:00:00+00:00"):
23
+ resolved, period = catalog.rate_for("claude-sonnet-5-5", dt(occurred_at))
24
+ assert resolved == "claude-sonnet-5-5"
25
+ assert period is not None
26
+ assert period.rate_id == "claude-sonnet-5-5-launch-2026-09-28"
27
+ assert period.values["input_nocache"] == Decimal("2.0")
28
+ assert period.values["cache_read"] == Decimal("0.20")
29
+ assert period.values["cache_write_5m"] == Decimal("2.50")
30
+ assert period.values["cache_write_1h"] == Decimal("4.0")
31
+ assert period.values["output"] == Decimal("10.0")
32
+
33
+
34
+ def test_claude_sonnet_5_5_is_unpriced_before_launch_date():
35
+ catalog = load_rates()
36
+ resolved, before = catalog.rate_for("claude-sonnet-5-5", dt("2026-09-27T23:59:59+00:00"))
37
+ assert resolved == "claude-sonnet-5-5"
38
+ assert before is None
39
+
40
+
41
+ def test_claude_sonnet_5_5_is_its_own_entry_not_an_alias_of_sonnet_5():
42
+ # Same values as claude-sonnet-5 today, but the pricing page lists the rows
43
+ # independently; a future change to one must not silently apply to the other.
44
+ catalog = load_rates()
45
+ assert "claude-sonnet-5-5" in catalog.models
46
+ assert catalog.models["claude-sonnet-5-5"].aliases == ()
47
+ assert "claude-sonnet-5-5" not in catalog.models["claude-sonnet-5"].aliases
48
+ _, s5 = catalog.rate_for("claude-sonnet-5", dt("2026-09-28T00:00:00+00:00"))
49
+ _, s55 = catalog.rate_for("claude-sonnet-5-5", dt("2026-09-28T00:00:00+00:00"))
50
+ assert s5.rate_id != s55.rate_id
51
+ assert s5.values == s55.values
52
+
53
+
54
+ def test_claude_sonnet_5_5_cache_read_is_standard_multiplier_and_no_fast_mode():
55
+ catalog = load_rates()
56
+ entry = catalog.models["claude-sonnet-5-5"]
57
+ assert entry.fast_multiplier == Decimal("1.0")
58
+ period = entry.rates[0]
59
+ assert period.values["cache_read"] == period.values["input_nocache"] * Decimal("0.1")
60
+ assert period.values["cache_write_5m"] == period.values["input_nocache"] * Decimal("1.25")
61
+ assert period.values["cache_write_1h"] == period.values["input_nocache"] * Decimal("2")
62
+
63
+
64
+ def test_catalog_version_and_sources_record_the_sonnet_5_5_primary_sources():
65
+ catalog = load_rates()
66
+ assert catalog.catalog_version == "2026-09-29"
67
+ urls = {(s.get("url"), s.get("retrieved_at")) for s in catalog.sources}
68
+ assert ("https://platform.claude.com/docs/en/about-claude/pricing", "2026-09-29") in urls
69
+ assert ("https://www.anthropic.com/claude-sonnet-5-5", "2026-09-29") in urls
@@ -1,6 +1,6 @@
1
1
  """Spec: pyproject.toml's [project].version and agent_cost.__version__ must
2
- agree, and both must read "0.2.0" for this change (E0-a3 bumps the package
3
- version alongside the measure/v1 data_quality additions). A broken
2
+ agree, and both must read "0.2.2" for this change (the Sonnet 5.5 catalog-only release bumps
3
+ the package version, as the Opus 5.5 catalog release did for 0.2.1). A broken
4
4
  implementation would bump one file but not the other, or forget the bump
5
5
  entirely.
6
6
 
@@ -29,5 +29,5 @@ def test_pyproject_version_matches_package_version():
29
29
 
30
30
 
31
31
  def test_version_is_0_2_0():
32
- assert agent_cost.__version__ == "0.2.0"
33
- assert _pyproject_version() == "0.2.0"
32
+ assert agent_cost.__version__ == "0.2.2"
33
+ assert _pyproject_version() == "0.2.2"