coding-agent-cost 0.5.2__tar.gz → 0.5.3__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 (41) hide show
  1. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/PKG-INFO +3 -1
  2. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/__init__.py +1 -1
  3. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/rates.json +124 -2
  4. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/coding_agent_cost.egg-info/PKG-INFO +3 -1
  5. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/coding_agent_cost.egg-info/SOURCES.txt +2 -0
  6. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/pyproject.toml +3 -1
  7. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_astra_pricing.py +2 -2
  8. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_billing_plan.py +27 -4
  9. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_cli.py +1 -1
  10. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_e0a_review3_hardening.py +3 -3
  11. coding_agent_cost-0.5.3/tests/test_fable_5_2_rates.py +83 -0
  12. coding_agent_cost-0.5.3/tests/test_gpt_6_1_rates.py +117 -0
  13. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_haiku_5_5_rates.py +1 -1
  14. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_rates.py +2 -1
  15. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_sonnet_5_5_rates.py +1 -1
  16. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_version_consistency.py +6 -6
  17. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/LICENSE +0 -0
  18. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/README.md +0 -0
  19. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/aggregate.py +0 -0
  20. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/billing_plan.py +0 -0
  21. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/cli.py +0 -0
  22. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/config.py +0 -0
  23. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/facts.py +0 -0
  24. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/rates.py +0 -0
  25. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/readers/__init__.py +0 -0
  26. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/readers/claude.py +0 -0
  27. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/readers/codex.py +0 -0
  28. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/agent_cost/renderers.py +0 -0
  29. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/coding_agent_cost.egg-info/dependency_links.txt +0 -0
  30. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/coding_agent_cost.egg-info/entry_points.txt +0 -0
  31. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/coding_agent_cost.egg-info/top_level.txt +0 -0
  32. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/setup.cfg +0 -0
  33. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_aggregate.py +0 -0
  34. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_e0a_fable_5_1_and_sonnet_5_correction.py +0 -0
  35. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_facts.py +0 -0
  36. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_measure_v1_contract.py +0 -0
  37. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_opus_5_5_rates.py +0 -0
  38. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_reader_claude.py +0 -0
  39. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_reader_codex.py +0 -0
  40. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/tests/test_reader_dedup.py +0 -0
  41. {coding_agent_cost-0.5.2 → coding_agent_cost-0.5.3}/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.5.2
3
+ Version: 0.5.3
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
@@ -17,6 +17,8 @@ Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
20
22
  Classifier: Topic :: Software Development :: Build Tools
21
23
  Classifier: Topic :: Utilities
22
24
  Requires-Python: >=3.9
@@ -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.5.2"
9
+ __version__ = "0.5.3"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": "2",
3
- "catalog_version": "2026-10-13",
3
+ "catalog_version": "2026-11-10",
4
4
  "currency": "USD",
5
5
  "unit": "per_mtok",
6
6
  "usd_per_credit": "0.04",
@@ -14,7 +14,9 @@
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
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
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.",
17
- "claude-sonnet-5-5 cache_read cut 2026-10-13: the launch period (rate_id claude-sonnet-5-5-launch-2026-09-28) now ends at 2026-10-07T00:00:00Z and a new period (rate_id claude-sonnet-5-5-cache-read-cut-2026-10-07) prices cache_read at 0.10 instead of 0.20 from that instant; input_nocache, cache_write_5m, cache_write_1h and output are unchanged (2.0/2.50/4.0/10.0). anthropic.com/claude-haiku-5-5 (dated October 7, 2026) states 'starting today, we're lowering the price of cache reads on Claude Sonnet 5.5' and 'Cache reads now cost 50% less: $0.10 per million tokens rather than $0.20'; the source gives a calendar date only, so effective_from is 00:00:00Z of that day, following the claude-sonnet-5-standard-2026-09-01 precedent. The pricing page (retrieved 2026-10-13) lists the Sonnet 5.5 row as $2 / $2.50 / $4 / $0.10 / $10 with footnote 2: cache hits and refreshes on Claude Opus 5.5 and Claude Sonnet 5.5 are 0.05x the base input price, so cache_read is now 0.05x like claude-opus-5-5. Claude Code's CHANGELOG entry for 2.1.296 (2026-10-09) updated /cost, the status line, --max-budget-usd and the SDK's cost figures to $0.10 per million Sonnet 5.5 cache-read tokens; that is the date Claude Code's own figures changed, not the API price's effective date. catalog 2026-10-09 priced claude-sonnet-5-5 cache_read at 0.20 for events from 2026-10-07 through 2026-10-13, overstating that component 2x; reports produced with it for windows touching 2026-10-07 or later should be regenerated. claude-sonnet-5 is unchanged (cache_read stays 0.20): the pricing page's footnote 3 covers its input/output prices only, and footnote 2 names Opus 5.5 and Sonnet 5.5, not Sonnet 5."
17
+ "claude-sonnet-5-5 cache_read cut 2026-10-13: the launch period (rate_id claude-sonnet-5-5-launch-2026-09-28) now ends at 2026-10-07T00:00:00Z and a new period (rate_id claude-sonnet-5-5-cache-read-cut-2026-10-07) prices cache_read at 0.10 instead of 0.20 from that instant; input_nocache, cache_write_5m, cache_write_1h and output are unchanged (2.0/2.50/4.0/10.0). anthropic.com/claude-haiku-5-5 (dated October 7, 2026) states 'starting today, we're lowering the price of cache reads on Claude Sonnet 5.5' and 'Cache reads now cost 50% less: $0.10 per million tokens rather than $0.20'; the source gives a calendar date only, so effective_from is 00:00:00Z of that day, following the claude-sonnet-5-standard-2026-09-01 precedent. The pricing page (retrieved 2026-10-13) lists the Sonnet 5.5 row as $2 / $2.50 / $4 / $0.10 / $10 with footnote 2: cache hits and refreshes on Claude Opus 5.5 and Claude Sonnet 5.5 are 0.05x the base input price, so cache_read is now 0.05x like claude-opus-5-5. Claude Code's CHANGELOG entry for 2.1.296 (2026-10-09) updated /cost, the status line, --max-budget-usd and the SDK's cost figures to $0.10 per million Sonnet 5.5 cache-read tokens; that is the date Claude Code's own figures changed, not the API price's effective date. catalog 2026-10-09 priced claude-sonnet-5-5 cache_read at 0.20 for events from 2026-10-07 through 2026-10-13, overstating that component 2x; reports produced with it for windows touching 2026-10-07 or later should be regenerated. claude-sonnet-5 is unchanged (cache_read stays 0.20): the pricing page's footnote 3 covers its input/output prices only, and footnote 2 names Opus 5.5 and Sonnet 5.5, not Sonnet 5.",
18
+ "gpt-6.1-terra, gpt-6.1-sol and codex-auto-review added 2026-11-09: standard tier, short context USD per MTok from the human-approved price table pricing-2026-11-v4 (developers.openai.com/api/docs/pricing, retrieved 2026-11-05); effective_from 2026-10-01T00:00:00Z is a placeholder before the first observed use (official launch date unconfirmed; rate_id *-launch-date-unconfirmed, following the claude-opus-5 placeholder precedent); usage from 2026-11-04 must be priced, so the retrieval date 2026-11-05 cannot be used. codex-auto-review is the model name of the Codex automatic review (guardian subagent) and has no published price; a human decision on 2026-11-06 prices it the same as gpt-6.1-terra. credits_per_mtok follow usd_per_credit=0.04. Existing entries (including gpt-5.6-terra) are unchanged.",
19
+ "claude-fable-5-2 and claude-mythos-5-2 added 2026-11-10: rate_ids claude-fable-5-2-launch-2026-11-09 / claude-mythos-5-2-launch-2026-11-09. effective_from 2026-11-09T00:00:00Z is 00:00:00Z of the official announcement date (anthropic.com/claude-fable-and-mythos-5-2, published November 9, 2026); the launch time of day is not published, but a transcript row whose raw model ID is claude-fable-5-2 or claude-mythos-5-2 cannot predate the launch. Values 8.0/0.20/10.0/16.0/40.0 per the pricing page (retrieved 2026-11-10): cache_read is 0.025x base input (footnote 1), like claude-fable-5-1. claude-mythos-5-2 (limited availability, verified organizations only) has the same prices; it is kept as a separate entry, never an alias of claude-fable-5-2. fast_multiplier 2.0 (fast mode $16/$80 per MTok on the pricing page). No prompt-length tiers (1M context at standard prices). Claude Code's CHANGELOG entry for 2.1.321 adds claude-fable-5-2 at $8/$40 with $0.20 cache reads. claude-fable-5-1 is unchanged (10.0/0.25/12.50/20.0/50.0). claude-mythos-5-1 stays absent (official release date unconfirmed), so its rows remain unpriced."
18
20
  ],
19
21
  "sources": [
20
22
  {
@@ -124,6 +126,20 @@
124
126
  "url": "https://www.anthropic.com/claude-haiku-5-5",
125
127
  "retrieved_at": "2026-10-13",
126
128
  "note": "Haiku 5.5 announcement dated October 7, 2026, 'Further updates': starting that day, Claude Sonnet 5.5 cache reads cost $0.10 per million tokens rather than $0.20; the effective date of the claude-sonnet-5-5 cache_read cut"
129
+ },
130
+ {
131
+ "url": "https://developers.openai.com/api/docs/pricing",
132
+ "retrieved_at": "2026-11-05"
133
+ },
134
+ {
135
+ "url": "https://www.anthropic.com/claude-fable-and-mythos-5-2",
136
+ "retrieved_at": "2026-11-10",
137
+ "note": "Fable 5.2 / Mythos 5.2 announcement published November 9, 2026; the effective date of claude-fable-5-2 and claude-mythos-5-2"
138
+ },
139
+ {
140
+ "url": "https://platform.claude.com/docs/en/about-claude/pricing",
141
+ "retrieved_at": "2026-11-10",
142
+ "note": "Fable 5.2 / Mythos 5.2 rows: $8 input, $10 5m cache write, $16 1h cache write, $0.20 cache hits (footnote 1: 0.025x), $40 output; fast mode $16/$80"
127
143
  }
128
144
  ],
129
145
  "models": [
@@ -161,6 +177,23 @@
161
177
  }
162
178
  ]
163
179
  },
180
+ {
181
+ "model_key": "claude-fable-5-2",
182
+ "aliases": [],
183
+ "fast_multiplier": "2.0",
184
+ "rates": [
185
+ {
186
+ "rate_id": "claude-fable-5-2-launch-2026-11-09",
187
+ "effective_from": "2026-11-09T00:00:00+00:00",
188
+ "effective_until": null,
189
+ "input_nocache": "8.0",
190
+ "cache_read": "0.20",
191
+ "cache_write_5m": "10.0",
192
+ "cache_write_1h": "16.0",
193
+ "output": "40.0"
194
+ }
195
+ ]
196
+ },
164
197
  {
165
198
  "model_key": "claude-mythos-5",
166
199
  "aliases": [],
@@ -178,6 +211,23 @@
178
211
  }
179
212
  ]
180
213
  },
214
+ {
215
+ "model_key": "claude-mythos-5-2",
216
+ "aliases": [],
217
+ "fast_multiplier": "2.0",
218
+ "rates": [
219
+ {
220
+ "rate_id": "claude-mythos-5-2-launch-2026-11-09",
221
+ "effective_from": "2026-11-09T00:00:00+00:00",
222
+ "effective_until": null,
223
+ "input_nocache": "8.0",
224
+ "cache_read": "0.20",
225
+ "cache_write_5m": "10.0",
226
+ "cache_write_1h": "16.0",
227
+ "output": "40.0"
228
+ }
229
+ ]
230
+ },
181
231
  {
182
232
  "model_key": "claude-opus-4-8",
183
233
  "aliases": [],
@@ -689,6 +739,78 @@
689
739
  "output": "50.0"
690
740
  }
691
741
  ]
742
+ },
743
+ {
744
+ "model_key": "gpt-6.1-terra",
745
+ "aliases": [],
746
+ "fast_multiplier": "1.0",
747
+ "credits_per_mtok": {
748
+ "input_nocache": "25.0",
749
+ "cache_read": "1.25",
750
+ "cache_write_5m": null,
751
+ "cache_write_1h": null,
752
+ "output": "125.0"
753
+ },
754
+ "rates": [
755
+ {
756
+ "rate_id": "gpt-6.1-terra-launch-date-unconfirmed",
757
+ "effective_from": "2026-10-01T00:00:00+00:00",
758
+ "effective_until": null,
759
+ "input_nocache": "1.00",
760
+ "cache_read": "0.05",
761
+ "cache_write_5m": null,
762
+ "cache_write_1h": null,
763
+ "output": "5.00"
764
+ }
765
+ ]
766
+ },
767
+ {
768
+ "model_key": "gpt-6.1-sol",
769
+ "aliases": [],
770
+ "fast_multiplier": "1.0",
771
+ "credits_per_mtok": {
772
+ "input_nocache": "50.0",
773
+ "cache_read": "2.5",
774
+ "cache_write_5m": null,
775
+ "cache_write_1h": null,
776
+ "output": "250.0"
777
+ },
778
+ "rates": [
779
+ {
780
+ "rate_id": "gpt-6.1-sol-launch-date-unconfirmed",
781
+ "effective_from": "2026-10-01T00:00:00+00:00",
782
+ "effective_until": null,
783
+ "input_nocache": "2.00",
784
+ "cache_read": "0.10",
785
+ "cache_write_5m": null,
786
+ "cache_write_1h": null,
787
+ "output": "10.00"
788
+ }
789
+ ]
790
+ },
791
+ {
792
+ "model_key": "codex-auto-review",
793
+ "aliases": [],
794
+ "fast_multiplier": "1.0",
795
+ "credits_per_mtok": {
796
+ "input_nocache": "25.0",
797
+ "cache_read": "1.25",
798
+ "cache_write_5m": null,
799
+ "cache_write_1h": null,
800
+ "output": "125.0"
801
+ },
802
+ "rates": [
803
+ {
804
+ "rate_id": "codex-auto-review-2026-11-terra-equivalent",
805
+ "effective_from": "2026-10-01T00:00:00+00:00",
806
+ "effective_until": null,
807
+ "input_nocache": "1.00",
808
+ "cache_read": "0.05",
809
+ "cache_write_5m": null,
810
+ "cache_write_1h": null,
811
+ "output": "5.00"
812
+ }
813
+ ]
692
814
  }
693
815
  ]
694
816
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coding-agent-cost
3
- Version: 0.5.2
3
+ Version: 0.5.3
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
@@ -17,6 +17,8 @@ Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
20
22
  Classifier: Topic :: Software Development :: Build Tools
21
23
  Classifier: Topic :: Utilities
22
24
  Requires-Python: >=3.9
@@ -24,7 +24,9 @@ tests/test_billing_plan.py
24
24
  tests/test_cli.py
25
25
  tests/test_e0a_fable_5_1_and_sonnet_5_correction.py
26
26
  tests/test_e0a_review3_hardening.py
27
+ tests/test_fable_5_2_rates.py
27
28
  tests/test_facts.py
29
+ tests/test_gpt_6_1_rates.py
28
30
  tests/test_haiku_5_5_rates.py
29
31
  tests/test_measure_v1_contract.py
30
32
  tests/test_opus_5_5_rates.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "coding-agent-cost"
7
- version = "0.5.2"
7
+ version = "0.5.3"
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" }
@@ -31,6 +31,8 @@ classifiers = [
31
31
  "Programming Language :: Python :: 3.10",
32
32
  "Programming Language :: Python :: 3.11",
33
33
  "Programming Language :: Python :: 3.12",
34
+ "Programming Language :: Python :: 3.13",
35
+ "Programming Language :: Python :: 3.14",
34
36
  "Topic :: Software Development :: Build Tools",
35
37
  "Topic :: Utilities",
36
38
  ]
@@ -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-10-13"
48
+ assert catalog.catalog_version == "2026-11-10"
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-10-13"
152
+ assert report["rates"]["catalog_version"] == "2026-11-10"
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
@@ -13,6 +13,8 @@ import copy
13
13
  import hashlib
14
14
  import json
15
15
  import os
16
+
17
+ _REAL_FSTAT = os.fstat
16
18
  import traceback
17
19
  from datetime import datetime, timezone
18
20
  from decimal import Decimal
@@ -583,11 +585,24 @@ def test_realpath_oserror_is_billing_plan_error(tmp_path, monkeypatch):
583
585
  _assert_no_leak(info.value, path)
584
586
 
585
587
 
588
+ def _is_fd_of(fd, path):
589
+ """True when fd refers to path. Keeps fakes scoped to the plan file: pytest's
590
+ capture machinery calls os.fstat/os.read/os.close on its own fds (pytest 9.2+)."""
591
+ try:
592
+ st = _REAL_FSTAT(fd)
593
+ except OSError:
594
+ return False
595
+ target = os.stat(path)
596
+ return (st.st_dev, st.st_ino) == (target.st_dev, target.st_ino)
597
+
598
+
586
599
  def test_fstat_oserror_is_billing_plan_error(tmp_path, monkeypatch):
587
600
  path = _write_plan(tmp_path, _base_plan())
588
601
 
589
602
  def failing_fstat(fd):
590
- raise _oserror_on(path)
603
+ if _is_fd_of(fd, path):
604
+ raise _oserror_on(path)
605
+ return _REAL_FSTAT(fd)
591
606
 
592
607
  monkeypatch.setattr(bp.os, "fstat", failing_fstat)
593
608
  with pytest.raises(bp.BillingPlanError) as info:
@@ -598,8 +613,12 @@ def test_fstat_oserror_is_billing_plan_error(tmp_path, monkeypatch):
598
613
  def test_read_oserror_is_billing_plan_error(tmp_path, monkeypatch):
599
614
  path = _write_plan(tmp_path, _base_plan())
600
615
 
616
+ real_read = os.read
617
+
601
618
  def failing_read(fd, n):
602
- raise _oserror_on(path)
619
+ if _is_fd_of(fd, path):
620
+ raise _oserror_on(path)
621
+ return real_read(fd, n)
603
622
 
604
623
  monkeypatch.setattr(bp.os, "read", failing_read)
605
624
  with pytest.raises(bp.BillingPlanError) as info:
@@ -612,8 +631,10 @@ def test_close_oserror_is_billing_plan_error(tmp_path, monkeypatch):
612
631
  real_close = os.close
613
632
 
614
633
  def failing_close(fd):
634
+ mine = _is_fd_of(fd, path)
615
635
  real_close(fd)
616
- raise _oserror_on(path)
636
+ if mine:
637
+ raise _oserror_on(path)
617
638
 
618
639
  monkeypatch.setattr(bp.os, "close", failing_close)
619
640
  with pytest.raises(bp.BillingPlanError) as info:
@@ -626,8 +647,10 @@ def test_close_oserror_does_not_replace_an_earlier_error(tmp_path, monkeypatch):
626
647
  real_close = os.close
627
648
 
628
649
  def failing_close(fd):
650
+ mine = _is_fd_of(fd, path)
629
651
  real_close(fd)
630
- raise _oserror_on(path)
652
+ if mine:
653
+ raise _oserror_on(path)
631
654
 
632
655
  monkeypatch.setattr(bp.os, "close", failing_close)
633
656
  with pytest.raises(bp.BillingPlanError) as info:
@@ -886,7 +886,7 @@ def test_rates_show_model_without_tiers_prints_no_prompt_tiers(capsys):
886
886
  def test_rates_validate_packaged_catalog_with_prompt_tiers_exits_zero(capsys):
887
887
  rc = cli.main(["rates", "validate"])
888
888
  assert rc == 0
889
- assert "catalog_version=2026-10-13" in capsys.readouterr().out
889
+ assert "catalog_version=2026-11-10" in capsys.readouterr().out
890
890
 
891
891
 
892
892
  # ---------------------------------------------------------------------------
@@ -19,7 +19,7 @@ architect レビュー (3 巡目) と builder レビューで、既存テスト
19
19
  かつ確定金額が計上されない。境界ちょうどは priced になる。
20
20
 
21
21
  条件D (PR レビュー3点目・追加分): catalog は
22
- catalog_version == "2026-10-13"(rates.json の現行値)であり、sources の中に url
22
+ catalog_version == "2026-11-10"(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-10-13"(rates.json の現行値)であり、sources に url
227
+ """条件D: catalog_version == "2026-11-10"(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-10-13"
236
+ assert catalog.catalog_version == "2026-11-10"
237
237
 
238
238
  matches = [
239
239
  s
@@ -0,0 +1,83 @@
1
+ """Acceptance tests for claude-fable-5-2 / claude-mythos-5-2 (catalog_version 2026-11-10).
2
+
3
+ Source: platform.claude.com/docs/en/about-claude/pricing (retrieved 2026-11-10) and the
4
+ announcement anthropic.com/claude-fable-and-mythos-5-2 (published 2026-11-09).
5
+ """
6
+
7
+ from datetime import datetime
8
+ from decimal import Decimal
9
+
10
+ from agent_cost.rates import load_rates
11
+
12
+ USD = {
13
+ "input_nocache": "8.0",
14
+ "cache_read": "0.20",
15
+ "cache_write_5m": "10.0",
16
+ "cache_write_1h": "16.0",
17
+ "output": "40.0",
18
+ }
19
+ MODELS = ("claude-fable-5-2", "claude-mythos-5-2")
20
+
21
+
22
+ def dt(s):
23
+ return datetime.fromisoformat(s)
24
+
25
+
26
+ def test_fable_and_mythos_5_2_resolve_with_pricing_page_values_from_launch():
27
+ catalog = load_rates()
28
+ for model in MODELS:
29
+ for occurred_at in ("2026-11-09T00:00:00+00:00", "2026-11-10T03:00:00+00:00", "2027-06-01T00:00:00+00:00"):
30
+ resolved, period = catalog.rate_for(model, dt(occurred_at))
31
+ assert resolved == model
32
+ assert period is not None, (model, occurred_at)
33
+ assert period.rate_id == f"{model}-launch-2026-11-09"
34
+ assert period.effective_until is None
35
+ assert period.prompt_tiers == ()
36
+ for kind, value in USD.items():
37
+ assert period.values[kind] == Decimal(value), (model, kind)
38
+
39
+
40
+ def test_fable_and_mythos_5_2_are_unpriced_before_launch():
41
+ catalog = load_rates()
42
+ for model in MODELS:
43
+ resolved, period = catalog.rate_for(model, dt("2026-11-08T23:59:59+00:00"))
44
+ assert resolved == model
45
+ assert period is None
46
+
47
+
48
+ def test_cache_read_is_0_025x_and_fast_mode_is_2x():
49
+ catalog = load_rates()
50
+ for model in MODELS:
51
+ entry = catalog.models[model]
52
+ assert entry.aliases == ()
53
+ assert entry.fast_multiplier == Decimal("2.0") # fast mode $16 / $80
54
+ assert len(entry.rates) == 1
55
+ _, period = catalog.rate_for(model, dt("2026-11-10T00:00:00+00:00"))
56
+ assert period.values["cache_read"] == period.values["input_nocache"] * Decimal("0.025")
57
+
58
+
59
+ def test_separate_entries_not_aliases():
60
+ catalog = load_rates()
61
+ for model in MODELS:
62
+ for other in catalog.models.values():
63
+ assert model not in other.aliases
64
+
65
+
66
+ def test_fable_5_1_is_unchanged_and_mythos_5_1_stays_unpriced():
67
+ catalog = load_rates()
68
+ _, period = catalog.rate_for("claude-fable-5-1", dt("2026-11-10T00:00:00+00:00"))
69
+ assert period.rate_id == "claude-fable-5-1-local-first-observed-2026-09-02"
70
+ assert period.values["input_nocache"] == Decimal("10.0")
71
+ assert period.values["cache_read"] == Decimal("0.25")
72
+ assert period.values["output"] == Decimal("50.0")
73
+ assert "claude-mythos-5-1" not in catalog.models
74
+ _, period = catalog.rate_for("claude-mythos-5-1", dt("2026-11-10T00:00:00+00:00"))
75
+ assert period is None
76
+
77
+
78
+ def test_catalog_version_and_sources_record_fable_5_2():
79
+ catalog = load_rates()
80
+ assert catalog.catalog_version == "2026-11-10"
81
+ urls = [(s.get("url"), s.get("retrieved_at")) for s in catalog.sources]
82
+ assert ("https://www.anthropic.com/claude-fable-and-mythos-5-2", "2026-11-10") in urls
83
+ assert ("https://platform.claude.com/docs/en/about-claude/pricing", "2026-11-10") in urls
@@ -0,0 +1,117 @@
1
+ """Acceptance tests for gpt-6.1-terra / gpt-6.1-sol / codex-auto-review (catalog_version 2026-11-09).
2
+
3
+ Source: human-approved price table pricing-2026-11-v4 (developers.openai.com/api/docs/pricing,
4
+ retrieved 2026-11-05; standard tier, short context). codex-auto-review (Codex automatic
5
+ review / guardian subagent) has no published price; priced as gpt-6.1-terra by a human
6
+ decision on 2026-11-06.
7
+ """
8
+
9
+ import json
10
+ from datetime import datetime, timezone
11
+ from decimal import Decimal
12
+ from pathlib import Path
13
+
14
+ import agent_cost
15
+ from agent_cost.rates import load_rates
16
+
17
+ UTC = timezone.utc
18
+
19
+ EXPECTED = {
20
+ "gpt-6.1-terra": (
21
+ "gpt-6.1-terra-launch-date-unconfirmed",
22
+ {"input_nocache": "1.00", "cache_read": "0.05", "output": "5.00"},
23
+ {"input_nocache": "25.0", "cache_read": "1.25", "output": "125.0"},
24
+ ),
25
+ "gpt-6.1-sol": (
26
+ "gpt-6.1-sol-launch-date-unconfirmed",
27
+ {"input_nocache": "2.00", "cache_read": "0.10", "output": "10.00"},
28
+ {"input_nocache": "50.0", "cache_read": "2.5", "output": "250.0"},
29
+ ),
30
+ "codex-auto-review": (
31
+ "codex-auto-review-2026-11-terra-equivalent",
32
+ {"input_nocache": "1.00", "cache_read": "0.05", "output": "5.00"},
33
+ {"input_nocache": "25.0", "cache_read": "1.25", "output": "125.0"},
34
+ ),
35
+ }
36
+
37
+
38
+ def dt(s):
39
+ return datetime.fromisoformat(s)
40
+
41
+
42
+ def test_gpt_6_1_models_resolve_with_pricing_page_values_from_effective_from():
43
+ catalog = load_rates()
44
+ for model, (rate_id, usd, _credits) in EXPECTED.items():
45
+ for occurred_at in ("2026-10-01T00:00:00+00:00", "2026-11-04T07:49:42+00:00", "2027-01-01T00:00:00+00:00"):
46
+ resolved, period = catalog.rate_for(model, dt(occurred_at))
47
+ assert resolved == model
48
+ assert period is not None, (model, occurred_at)
49
+ assert period.rate_id == rate_id
50
+ assert period.effective_until is None
51
+ for kind, value in usd.items():
52
+ assert period.values[kind] == Decimal(value), (model, kind)
53
+ assert period.values["cache_write_5m"] is None
54
+ assert period.values["cache_write_1h"] is None
55
+ assert period.prompt_tiers == ()
56
+
57
+
58
+ def test_gpt_6_1_models_are_unpriced_before_effective_from():
59
+ catalog = load_rates()
60
+ for model in EXPECTED:
61
+ resolved, period = catalog.rate_for(model, dt("2026-09-30T23:59:59+00:00"))
62
+ assert resolved == model
63
+ assert period is None
64
+
65
+
66
+ def test_gpt_6_1_models_credits_follow_usd_per_credit_and_entry_shape():
67
+ catalog = load_rates()
68
+ usd_per_credit = Decimal(catalog.usd_per_credit)
69
+ for model, (_rate_id, usd, credits) in EXPECTED.items():
70
+ entry = catalog.models[model]
71
+ assert entry.aliases == ()
72
+ assert entry.fast_multiplier == Decimal("1.0")
73
+ assert entry.credits_per_mtok["cache_write_5m"] is None
74
+ assert entry.credits_per_mtok["cache_write_1h"] is None
75
+ for kind, value in credits.items():
76
+ assert entry.credits_per_mtok[kind] == Decimal(value), (model, kind)
77
+ assert entry.credits_per_mtok[kind] * usd_per_credit == Decimal(usd[kind])
78
+
79
+
80
+ def test_codex_auto_review_is_priced_like_gpt_6_1_terra_but_is_its_own_entry():
81
+ catalog = load_rates()
82
+ at = dt("2026-11-06T00:00:00+00:00")
83
+ _, terra = catalog.rate_for("gpt-6.1-terra", at)
84
+ _, review = catalog.rate_for("codex-auto-review", at)
85
+ assert review.values == terra.values
86
+ assert review.rate_id != terra.rate_id
87
+ assert "codex-auto-review" not in catalog.models["gpt-6.1-terra"].aliases
88
+
89
+
90
+ def test_unknown_gpt_6_1_luna_stays_unpriced():
91
+ catalog = load_rates()
92
+ assert "gpt-6.1-luna" not in catalog.models
93
+ resolved, period = catalog.rate_for("gpt-6.1-luna", dt("2026-11-06T00:00:00+00:00"))
94
+ assert period is None
95
+
96
+
97
+ def test_existing_gpt_5_6_terra_values_are_unchanged():
98
+ catalog = load_rates()
99
+ entry = catalog.models["gpt-5.6-terra"]
100
+ assert len(entry.rates) == 1
101
+ _, period = catalog.rate_for("gpt-5.6-terra", dt("2026-11-06T00:00:00+00:00"))
102
+ assert period.rate_id == "gpt-5.6-terra-2026-07-secondary-sourced"
103
+ assert period.values["input_nocache"] == Decimal("2.50")
104
+ assert period.values["cache_read"] == Decimal("0.25")
105
+ assert period.values["output"] == Decimal("15.0")
106
+ assert entry.credits_per_mtok["input_nocache"] == Decimal("62.5")
107
+ assert entry.credits_per_mtok["cache_read"] == Decimal("6.25")
108
+ assert entry.credits_per_mtok["output"] == Decimal("375.0")
109
+
110
+
111
+ def test_catalog_version_and_source_record_the_gpt_6_1_pricing_page():
112
+ catalog = load_rates()
113
+ assert catalog.catalog_version >= "2026-11-09"
114
+ urls = [(s.get("url"), s.get("retrieved_at")) for s in catalog.sources]
115
+ assert urls.count(("https://developers.openai.com/api/docs/pricing", "2026-11-05")) == 1
116
+ raw = json.loads((Path(agent_cost.__file__).parent / "rates.json").read_text(encoding="utf-8"))
117
+ assert any("codex-auto-review" in n and "2026-11-06" in n for n in raw["notes"])
@@ -79,7 +79,7 @@ def test_claude_haiku_5_5_cache_multipliers_are_standard_in_every_tier_and_no_fa
79
79
 
80
80
  def test_catalog_version_schema_version_and_sources_record_the_haiku_5_5_primary_sources():
81
81
  catalog = load_rates()
82
- assert catalog.catalog_version == "2026-10-13"
82
+ assert catalog.catalog_version == "2026-11-10"
83
83
  assert catalog.schema_version == "2"
84
84
  urls = {(s.get("url"), s.get("retrieved_at")) for s in catalog.sources}
85
85
  assert ("https://platform.claude.com/docs/en/about-claude/pricing", "2026-10-09") in urls
@@ -527,6 +527,7 @@ def test_packaged_catalog_existing_models_are_unchanged():
527
527
  rows are never changed in place; a price change adds a new period.
528
528
  """
529
529
  raw = json.loads(resources.files("agent_cost").joinpath("rates.json").read_bytes())
530
- existing = [m for m in raw["models"] if m["model_key"] != "claude-haiku-5-5"]
530
+ added_after = {"claude-haiku-5-5", "gpt-6.1-terra", "gpt-6.1-sol", "codex-auto-review", "claude-fable-5-2", "claude-mythos-5-2"}
531
+ existing = [m for m in raw["models"] if m["model_key"] not in added_after]
531
532
  digest = hashlib.sha256(json.dumps(existing, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
532
533
  assert digest == _EXISTING_MODELS_SHA256
@@ -112,7 +112,7 @@ def test_claude_sonnet_5_5_cache_read_multiplier_per_period_and_no_fast_mode():
112
112
 
113
113
  def test_catalog_version_and_sources_record_the_sonnet_5_5_primary_sources():
114
114
  catalog = load_rates()
115
- assert catalog.catalog_version == "2026-10-13"
115
+ assert catalog.catalog_version == "2026-11-10"
116
116
  urls = {(s.get("url"), s.get("retrieved_at")) for s in catalog.sources}
117
117
  assert ("https://platform.claude.com/docs/en/about-claude/pricing", "2026-09-29") in urls
118
118
  assert ("https://www.anthropic.com/claude-sonnet-5-5", "2026-09-29") in urls
@@ -1,8 +1,8 @@
1
1
  """Spec: pyproject.toml's [project].version and agent_cost.__version__ must
2
- agree, and both must read "0.5.2" for this change (the packaged catalog
3
- lowers claude-sonnet-5-5 cache_read to $0.10 from 2026-10-07 -- a price
4
- correction with no code or schema change, so it bumps the patch version). A
5
- broken
2
+ agree, and both must read "0.5.3" for this change (the packaged catalog adds
3
+ claude-fable-5-2, claude-mythos-5-2, gpt-6.1-terra, gpt-6.1-sol and
4
+ codex-auto-review -- catalog entries only, with no code or schema change, so
5
+ it bumps the patch version). A broken
6
6
  implementation would bump one file but not the other, or forget the bump
7
7
  entirely.
8
8
 
@@ -31,5 +31,5 @@ def test_pyproject_version_matches_package_version():
31
31
 
32
32
 
33
33
  def test_version_is_0_5_2():
34
- assert agent_cost.__version__ == "0.5.2"
35
- assert _pyproject_version() == "0.5.2"
34
+ assert agent_cost.__version__ == "0.5.3"
35
+ assert _pyproject_version() == "0.5.3"