coding-agent-cost 0.3.0__tar.gz → 0.5.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.
Files changed (39) hide show
  1. {coding_agent_cost-0.3.0/coding_agent_cost.egg-info → coding_agent_cost-0.5.0}/PKG-INFO +160 -3
  2. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/README.md +159 -2
  3. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/__init__.py +1 -1
  4. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/aggregate.py +15 -1
  5. coding_agent_cost-0.5.0/agent_cost/billing_plan.py +654 -0
  6. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/cli.py +86 -2
  7. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/facts.py +12 -0
  8. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/rates.json +46 -3
  9. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/rates.py +73 -5
  10. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/readers/claude.py +47 -0
  11. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/renderers.py +60 -0
  12. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0/coding_agent_cost.egg-info}/PKG-INFO +160 -3
  13. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/coding_agent_cost.egg-info/SOURCES.txt +3 -0
  14. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/pyproject.toml +1 -1
  15. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_aggregate.py +157 -0
  16. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_astra_pricing.py +2 -2
  17. coding_agent_cost-0.5.0/tests/test_billing_plan.py +1360 -0
  18. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_cli.py +411 -1
  19. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_e0a_review3_hardening.py +3 -3
  20. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_facts.py +41 -0
  21. coding_agent_cost-0.5.0/tests/test_haiku_5_5_rates.py +87 -0
  22. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_rates.py +200 -2
  23. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_reader_claude.py +191 -0
  24. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_reader_codex.py +20 -0
  25. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_sonnet_5_5_rates.py +1 -1
  26. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_version_consistency.py +8 -7
  27. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/LICENSE +0 -0
  28. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/config.py +0 -0
  29. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/readers/__init__.py +0 -0
  30. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/agent_cost/readers/codex.py +0 -0
  31. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/coding_agent_cost.egg-info/dependency_links.txt +0 -0
  32. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/coding_agent_cost.egg-info/entry_points.txt +0 -0
  33. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/coding_agent_cost.egg-info/top_level.txt +0 -0
  34. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/setup.cfg +0 -0
  35. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_e0a_fable_5_1_and_sonnet_5_correction.py +0 -0
  36. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_measure_v1_contract.py +0 -0
  37. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_opus_5_5_rates.py +0 -0
  38. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.0}/tests/test_reader_dedup.py +0 -0
  39. {coding_agent_cost-0.3.0 → coding_agent_cost-0.5.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.0
3
+ Version: 0.5.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
@@ -200,6 +200,114 @@ If another program wants to parse agent-cost's output for one or more
200
200
  specific session ids, see `agent-cost measure` below rather than
201
201
  scraping `report`.
202
202
 
203
+ ## Internal billing plan (optional, confidential)
204
+
205
+ Some organizations bill Claude seats internally with their own rules -- a
206
+ fixed subscription per billing window, a usage allowance measured in
207
+ list-price dollars, and a discounted rate above it. `report --billing-plan
208
+ PATH` applies such a plan to the Claude usage agent-cost already prices at
209
+ the public catalog. Nothing changes without the flag, and there is no
210
+ default path or environment variable that turns it on.
211
+
212
+ ```bash
213
+ agent-cost report --billing-plan ~/private/billing-plan.json \
214
+ --since 2030-01-01T00:00:00+00:00 --until 2030-02-01T00:00:00+00:00 --format json
215
+ ```
216
+
217
+ A plan (`plan_schema_version` `"1"`; every value below is made up):
218
+
219
+ ```json
220
+ {
221
+ "plan_schema_version": "1",
222
+ "plan_id": "bp-example",
223
+ "nonce": "0123456789abcdef0123456789abcdef",
224
+ "applies_to": {"agent": "claude", "scope": "seat"},
225
+ "basis": "agent-cost-list-price",
226
+ "periods": [
227
+ {"period_id": "p1", "effective_from": "2030-01-01T00:00:00+00:00",
228
+ "window_subscription_usd": "3", "allowance_usd": null, "overage": null},
229
+ {"period_id": "p2", "effective_from": "2030-01-11T00:00:00+00:00",
230
+ "window_subscription_usd": "10", "allowance_usd": "50",
231
+ "overage": {"type": "charge_multiplier", "value": "0.5"}},
232
+ {"period_id": "end", "effective_from": "2030-02-01T00:00:00+00:00", "terminates": true}
233
+ ]
234
+ }
235
+ ```
236
+
237
+ - `periods` is a list of finite billing windows: window *i* is
238
+ `[effective_from_i, effective_from_{i+1})`, and the last entry must be
239
+ `{period_id, effective_from, terminates: true}` so that no window is
240
+ open-ended. Write one window per billing month (or per contract change).
241
+ `effective_from` needs a UTC offset and no sub-second part.
242
+ - `window_subscription_usd` is the fixed amount billed for that window, as
243
+ you write it -- agent-cost never prorates it.
244
+ - `allowance_usd` is the window's usage allowance in list-price dollars.
245
+ `null` means the window has no overage concept at all (it is not a zero
246
+ allowance), and then `overage` must be `null` too.
247
+ - `overage` prices `max(0, list_cost - allowance)`:
248
+ `{"type": "charge_multiplier", "value": "0.5"}` charges overage × value
249
+ (`0 <= value <= 1`), and `{"type": "block_discount", "block_usd": "5",
250
+ "discount_usd": "1"}` charges overage minus `floor(overage / block_usd) ×
251
+ discount_usd` (`0 < discount_usd <= block_usd`).
252
+ - Amounts are decimal strings only (no JSON numbers), at most 28
253
+ significant digits. `plan_id` is `[A-Za-z0-9_-]{1,32}`, `period_id`
254
+ `[A-Za-z0-9_-]{1,16}`, `nonce` 32 lowercase hex characters (e.g.
255
+ `python3 -c 'import secrets; print(secrets.token_hex(16))'`). Unknown or
256
+ duplicate keys are errors. v1 only accepts `scope: "seat"`: an allowance
257
+ shared by a department or org cannot be computed from one person's logs.
258
+
259
+ The JSON output gains an `internal_billing` block (existing keys are
260
+ unchanged), and the table gains a section headed `Internal billing
261
+ (CONFIDENTIAL -- do not share)`. For each window that intersects
262
+ `[since, until)` it shows `list_cost_usd` (Claude facts in the window,
263
+ priced at the packaged catalog), `allowance_usd`, `overage_usd`,
264
+ `overage_cost_usd`, `window_subscription_usd`, `internal_cost_usd` --
265
+ all as 4-decimal strings -- and four separate status fields:
266
+
267
+ - `query_coverage`: `full` when `--since`/`--until` cover the whole
268
+ window, otherwise `partial`. It says nothing about whether the logs
269
+ themselves are complete (deleted transcripts, other machines, API or web
270
+ usage are never seen; see `data_quality` at the top level as well).
271
+ - `window_state`: `closed` once the window has ended (`until <=
272
+ generated_at`), otherwise `open`.
273
+ - `list_cost_pricing`: `priced`, `lower_bound` (some facts were unpriced
274
+ or only priced as a lower bound, or an output fact's tokens are only a
275
+ lower bound -- `source_quality: "output_lower_bound"`) or
276
+ `no_usage_observed`.
277
+ - `internal_cost_certainty`: `estimate` only when the window is fully
278
+ queried, closed and `priced`. Otherwise `lower_bound` for windows with no
279
+ overage or `charge_multiplier` (more usage can only raise the amount), and
280
+ `indeterminate` for `block_discount` (more usage can cross a block
281
+ boundary and lower it). This describes what can be derived from the
282
+ observed list cost -- it is not a statement that the number matches an
283
+ actual invoice.
284
+
285
+ `window_subscription_usd` in each row is the plan's input echoed back, not
286
+ an amount allocated to your query. `internal_cost_usd` is the cost of the
287
+ whole billing window computed from the usage visible in this report -- it
288
+ is not the cost of the `[since, until)` range, so do not sum windows to get
289
+ one. `plan_coverage: partial` with `uncovered` ranges flags the parts of
290
+ `[since, until)` that no window covers (before the first window or after
291
+ `terminates`); usage there is not billed.
292
+
293
+ `measure` has no billing plan option on purpose: a per-session internal
294
+ cost would mean allocating a window-level charge across sessions.
295
+ `--billing-plan` cannot be combined with `--rates` (the basis must be the
296
+ packaged public catalog), `--format csv`, an `--agent` list without
297
+ `claude`, or a missing `--since`/`--until`; any of those, an unreadable or
298
+ invalid plan, or a calculation that would need rounding exits 2 with
299
+ nothing on stdout. `AGENT_COST_NOW` (an ISO 8601 instant with a UTC offset)
300
+ pins `generated_at`, and with it `window_state`, for reproducible runs.
301
+
302
+ **Keep the plan and its output private.** The plan must be a regular file
303
+ owned by you with mode `0600` (or stricter), reached without any symlink in
304
+ its path (on macOS `/tmp` and `/var` are symlinks -- keep it under your
305
+ home directory), and not inside a git repository or worktree. Error messages
306
+ never print plan values or the path. Output produced with the plan is
307
+ confidential: agent-cost has no redaction, so mind your terminal scrollback,
308
+ shell history, and the permissions of any file you redirect it to, and keep
309
+ it out of shared logs, CI, artifacts and ledgers.
310
+
203
311
  ## What this measures, and what it doesn't
204
312
 
205
313
  agent-cost only reads data that is already on disk. It never talks to the
@@ -244,6 +352,16 @@ network, never calls `gh`, and never resolves branches or PRs.
244
352
  present in the log, it's used; otherwise the cache-write tokens are priced
245
353
  at the 5-minute rate as an explicit **lower bound** and flagged
246
354
  `lower_bound` rather than guessed at the (more expensive) 1-hour rate.
355
+ Each Claude fact also carries its request's prompt length
356
+ (`prompt_tokens` = `input_tokens` + `cache_read_input_tokens` +
357
+ `cache_creation_input_tokens`), used only to pick the tier of a model
358
+ whose price depends on prompt length (`claude-haiku-5-5`).
359
+ - **Prompt-length tiers need a known prompt length.** A fact whose prompt
360
+ length is unknown -- every Codex fact (a delta of cumulative totals, not
361
+ one request), a Claude fact from a conflicting dedup group, or a Claude
362
+ row whose cache-write TTL breakdown exceeds its `cache_creation_input_tokens`
363
+ -- is priced at the model's base (cheapest) tier and flagged
364
+ `lower_bound`. Models without tiers are unaffected.
247
365
  - **Codex CLI**: rollout files record a *cumulative* token count after each
248
366
  turn; agent-cost turns that into per-turn deltas. Under the Codex token-rate
249
367
  tariff used here, cache writes are not added as a separate charge. The current
@@ -303,6 +421,37 @@ carries a `catalog_version`, a list of `sources` (the pricing page a rate
303
421
  came from), and is validated on load (no duplicate model keys or aliases,
304
422
  no negative rates, no overlapping periods for the same model).
305
423
 
424
+ A rate period can price by prompt length (catalog `schema_version` `"2"`).
425
+ The period's own five values are the base tier, for prompts up to the first
426
+ threshold; each entry in `prompt_tiers` applies to prompts *strictly over*
427
+ its `prompt_tokens_over`, and a request uses the largest threshold it
428
+ exceeds. Prompt length is the request's input + cache-read + cache-write
429
+ tokens, and the chosen tier prices every token kind of that request,
430
+ output included:
431
+
432
+ ```json
433
+ {
434
+ "rate_id": "claude-haiku-5-5-launch-2026-10-07",
435
+ "effective_from": "2026-10-07T00:00:00+00:00",
436
+ "effective_until": null,
437
+ "input_nocache": "0.10", "cache_read": "0.01", "cache_write_5m": "0.125", "cache_write_1h": "0.20", "output": "0.50",
438
+ "prompt_tiers": [
439
+ { "prompt_tokens_over": 100000,
440
+ "input_nocache": "0.50", "cache_read": "0.05", "cache_write_5m": "0.625", "cache_write_1h": "1.0", "output": "2.50" }
441
+ ]
442
+ }
443
+ ```
444
+
445
+ In a period with `prompt_tiers`, all five values of the base and of every
446
+ tier must be present and non-null, thresholds must be positive integers in
447
+ ascending order without duplicates, and every value must be greater than or
448
+ equal to the same value in the previous tier (the base first). That
449
+ monotonicity is what makes the base tier a true lower bound for a fact whose
450
+ prompt length is unknown. A `schema_version` `"1"` catalog may not contain
451
+ `prompt_tiers`; agent-cost 0.3.0 and earlier reject a `"2"` catalog as
452
+ unsupported rather than mispricing it, so a custom `--rates` file that uses
453
+ tiers must declare `"2"`.
454
+
306
455
  To use your own catalog instead of the one bundled with the package, pass
307
456
  `--rates path/to/rates.json` to `report` or `export` -- this fully replaces
308
457
  the bundled catalog, it does not merge with it. Inspect any catalog with:
@@ -402,10 +551,11 @@ agent-cost makes zero network calls. `agent-cost export`'s JSONL never
402
551
  includes absolute file paths, rollout paths, prompt/message content, or git
403
552
  branch names -- only the fields needed to reproduce a cost estimate:
404
553
  `occurred_at_utc`, `agent`, `session_id`, `model_raw`, `model_key`,
405
- `token_kind`, `tokens`, `mode`, and `source_quality` (a fixed-vocabulary
554
+ `token_kind`, `tokens`, `mode`, `source_quality` (a fixed-vocabulary
406
555
  caveat about how that one fact was derived: `"ok"`, Codex's
407
556
  `"first_event_delta"`, or Claude's `"identity_missing"` /
408
- `"output_lower_bound"` -- never null).
557
+ `"output_lower_bound"` -- never null), and `prompt_tokens` (the request's
558
+ prompt length as an integer, or null when unknown).
409
559
 
410
560
  ## License
411
561
 
@@ -429,10 +579,17 @@ MIT. See [LICENSE](LICENSE).
429
579
  未知のモデル・単価表にない token 種別は `unpriced` として扱い、憶測の価格を出しません。
430
580
  - 単価表 (`agent_cost/rates.json`) は履歴型カタログで、値上げは新しい期間として追加します。
431
581
  `--rates PATH` で別カタログに完全差し替えできます。
582
+ - prompt 長で単価が変わるモデル(`claude-haiku-5-5`、100,000 tokens 超で高い単価)は期間の
583
+ `prompt_tiers`(schema_version `"2"`)で表し、リクエストごとの prompt 長(input + cache read +
584
+ cache write)で tier を選びます。prompt 長が不明な fact(Codex 由来、conflicting group、usage
585
+ 不整合)は基底 tier(最安)で **下限推計**(`lower_bound`)になります。
432
586
  - `agent-cost measure --session-id ID [--session-id ID ...] --format json` は他プログラムから
433
587
  subprocess で叩くための機械可読な契約です(`protocol_version: "measure/v1"`)。指定した
434
588
  session_id が1件も見つからなくても終了コードは0(空集計として表現)、`--session-id` 未指定など
435
589
  の入力エラーのみ終了コード2です。
590
+ - `report --billing-plan PATH`(任意・機密)は、ローカルの課金プラン(窓ごとの定額・利用枠・超過規則)を
591
+ 公開単価で集計した Claude の list_cost に当てて `internal_billing` を出します。フラグ無しの出力は不変です。
592
+ - plan ファイルは 0600・symlink 不可・git repo 外に置き、出力は機密成果物として扱います(`measure` には按分になるため無し)。
436
593
  - 破損したログ行、読めなくなったファイル、Codex の累積カウンタが逆行するケースなどは、すべて
437
594
  `data_quality` に件数として記録し、黙って丸めたり捨てたりしません。
438
595
  - Codex の `output` は `output_tokens` のみです。実 rollout データを突合した結果
@@ -174,6 +174,114 @@ If another program wants to parse agent-cost's output for one or more
174
174
  specific session ids, see `agent-cost measure` below rather than
175
175
  scraping `report`.
176
176
 
177
+ ## Internal billing plan (optional, confidential)
178
+
179
+ Some organizations bill Claude seats internally with their own rules -- a
180
+ fixed subscription per billing window, a usage allowance measured in
181
+ list-price dollars, and a discounted rate above it. `report --billing-plan
182
+ PATH` applies such a plan to the Claude usage agent-cost already prices at
183
+ the public catalog. Nothing changes without the flag, and there is no
184
+ default path or environment variable that turns it on.
185
+
186
+ ```bash
187
+ agent-cost report --billing-plan ~/private/billing-plan.json \
188
+ --since 2030-01-01T00:00:00+00:00 --until 2030-02-01T00:00:00+00:00 --format json
189
+ ```
190
+
191
+ A plan (`plan_schema_version` `"1"`; every value below is made up):
192
+
193
+ ```json
194
+ {
195
+ "plan_schema_version": "1",
196
+ "plan_id": "bp-example",
197
+ "nonce": "0123456789abcdef0123456789abcdef",
198
+ "applies_to": {"agent": "claude", "scope": "seat"},
199
+ "basis": "agent-cost-list-price",
200
+ "periods": [
201
+ {"period_id": "p1", "effective_from": "2030-01-01T00:00:00+00:00",
202
+ "window_subscription_usd": "3", "allowance_usd": null, "overage": null},
203
+ {"period_id": "p2", "effective_from": "2030-01-11T00:00:00+00:00",
204
+ "window_subscription_usd": "10", "allowance_usd": "50",
205
+ "overage": {"type": "charge_multiplier", "value": "0.5"}},
206
+ {"period_id": "end", "effective_from": "2030-02-01T00:00:00+00:00", "terminates": true}
207
+ ]
208
+ }
209
+ ```
210
+
211
+ - `periods` is a list of finite billing windows: window *i* is
212
+ `[effective_from_i, effective_from_{i+1})`, and the last entry must be
213
+ `{period_id, effective_from, terminates: true}` so that no window is
214
+ open-ended. Write one window per billing month (or per contract change).
215
+ `effective_from` needs a UTC offset and no sub-second part.
216
+ - `window_subscription_usd` is the fixed amount billed for that window, as
217
+ you write it -- agent-cost never prorates it.
218
+ - `allowance_usd` is the window's usage allowance in list-price dollars.
219
+ `null` means the window has no overage concept at all (it is not a zero
220
+ allowance), and then `overage` must be `null` too.
221
+ - `overage` prices `max(0, list_cost - allowance)`:
222
+ `{"type": "charge_multiplier", "value": "0.5"}` charges overage × value
223
+ (`0 <= value <= 1`), and `{"type": "block_discount", "block_usd": "5",
224
+ "discount_usd": "1"}` charges overage minus `floor(overage / block_usd) ×
225
+ discount_usd` (`0 < discount_usd <= block_usd`).
226
+ - Amounts are decimal strings only (no JSON numbers), at most 28
227
+ significant digits. `plan_id` is `[A-Za-z0-9_-]{1,32}`, `period_id`
228
+ `[A-Za-z0-9_-]{1,16}`, `nonce` 32 lowercase hex characters (e.g.
229
+ `python3 -c 'import secrets; print(secrets.token_hex(16))'`). Unknown or
230
+ duplicate keys are errors. v1 only accepts `scope: "seat"`: an allowance
231
+ shared by a department or org cannot be computed from one person's logs.
232
+
233
+ The JSON output gains an `internal_billing` block (existing keys are
234
+ unchanged), and the table gains a section headed `Internal billing
235
+ (CONFIDENTIAL -- do not share)`. For each window that intersects
236
+ `[since, until)` it shows `list_cost_usd` (Claude facts in the window,
237
+ priced at the packaged catalog), `allowance_usd`, `overage_usd`,
238
+ `overage_cost_usd`, `window_subscription_usd`, `internal_cost_usd` --
239
+ all as 4-decimal strings -- and four separate status fields:
240
+
241
+ - `query_coverage`: `full` when `--since`/`--until` cover the whole
242
+ window, otherwise `partial`. It says nothing about whether the logs
243
+ themselves are complete (deleted transcripts, other machines, API or web
244
+ usage are never seen; see `data_quality` at the top level as well).
245
+ - `window_state`: `closed` once the window has ended (`until <=
246
+ generated_at`), otherwise `open`.
247
+ - `list_cost_pricing`: `priced`, `lower_bound` (some facts were unpriced
248
+ or only priced as a lower bound, or an output fact's tokens are only a
249
+ lower bound -- `source_quality: "output_lower_bound"`) or
250
+ `no_usage_observed`.
251
+ - `internal_cost_certainty`: `estimate` only when the window is fully
252
+ queried, closed and `priced`. Otherwise `lower_bound` for windows with no
253
+ overage or `charge_multiplier` (more usage can only raise the amount), and
254
+ `indeterminate` for `block_discount` (more usage can cross a block
255
+ boundary and lower it). This describes what can be derived from the
256
+ observed list cost -- it is not a statement that the number matches an
257
+ actual invoice.
258
+
259
+ `window_subscription_usd` in each row is the plan's input echoed back, not
260
+ an amount allocated to your query. `internal_cost_usd` is the cost of the
261
+ whole billing window computed from the usage visible in this report -- it
262
+ is not the cost of the `[since, until)` range, so do not sum windows to get
263
+ one. `plan_coverage: partial` with `uncovered` ranges flags the parts of
264
+ `[since, until)` that no window covers (before the first window or after
265
+ `terminates`); usage there is not billed.
266
+
267
+ `measure` has no billing plan option on purpose: a per-session internal
268
+ cost would mean allocating a window-level charge across sessions.
269
+ `--billing-plan` cannot be combined with `--rates` (the basis must be the
270
+ packaged public catalog), `--format csv`, an `--agent` list without
271
+ `claude`, or a missing `--since`/`--until`; any of those, an unreadable or
272
+ invalid plan, or a calculation that would need rounding exits 2 with
273
+ nothing on stdout. `AGENT_COST_NOW` (an ISO 8601 instant with a UTC offset)
274
+ pins `generated_at`, and with it `window_state`, for reproducible runs.
275
+
276
+ **Keep the plan and its output private.** The plan must be a regular file
277
+ owned by you with mode `0600` (or stricter), reached without any symlink in
278
+ its path (on macOS `/tmp` and `/var` are symlinks -- keep it under your
279
+ home directory), and not inside a git repository or worktree. Error messages
280
+ never print plan values or the path. Output produced with the plan is
281
+ confidential: agent-cost has no redaction, so mind your terminal scrollback,
282
+ shell history, and the permissions of any file you redirect it to, and keep
283
+ it out of shared logs, CI, artifacts and ledgers.
284
+
177
285
  ## What this measures, and what it doesn't
178
286
 
179
287
  agent-cost only reads data that is already on disk. It never talks to the
@@ -218,6 +326,16 @@ network, never calls `gh`, and never resolves branches or PRs.
218
326
  present in the log, it's used; otherwise the cache-write tokens are priced
219
327
  at the 5-minute rate as an explicit **lower bound** and flagged
220
328
  `lower_bound` rather than guessed at the (more expensive) 1-hour rate.
329
+ Each Claude fact also carries its request's prompt length
330
+ (`prompt_tokens` = `input_tokens` + `cache_read_input_tokens` +
331
+ `cache_creation_input_tokens`), used only to pick the tier of a model
332
+ whose price depends on prompt length (`claude-haiku-5-5`).
333
+ - **Prompt-length tiers need a known prompt length.** A fact whose prompt
334
+ length is unknown -- every Codex fact (a delta of cumulative totals, not
335
+ one request), a Claude fact from a conflicting dedup group, or a Claude
336
+ row whose cache-write TTL breakdown exceeds its `cache_creation_input_tokens`
337
+ -- is priced at the model's base (cheapest) tier and flagged
338
+ `lower_bound`. Models without tiers are unaffected.
221
339
  - **Codex CLI**: rollout files record a *cumulative* token count after each
222
340
  turn; agent-cost turns that into per-turn deltas. Under the Codex token-rate
223
341
  tariff used here, cache writes are not added as a separate charge. The current
@@ -277,6 +395,37 @@ carries a `catalog_version`, a list of `sources` (the pricing page a rate
277
395
  came from), and is validated on load (no duplicate model keys or aliases,
278
396
  no negative rates, no overlapping periods for the same model).
279
397
 
398
+ A rate period can price by prompt length (catalog `schema_version` `"2"`).
399
+ The period's own five values are the base tier, for prompts up to the first
400
+ threshold; each entry in `prompt_tiers` applies to prompts *strictly over*
401
+ its `prompt_tokens_over`, and a request uses the largest threshold it
402
+ exceeds. Prompt length is the request's input + cache-read + cache-write
403
+ tokens, and the chosen tier prices every token kind of that request,
404
+ output included:
405
+
406
+ ```json
407
+ {
408
+ "rate_id": "claude-haiku-5-5-launch-2026-10-07",
409
+ "effective_from": "2026-10-07T00:00:00+00:00",
410
+ "effective_until": null,
411
+ "input_nocache": "0.10", "cache_read": "0.01", "cache_write_5m": "0.125", "cache_write_1h": "0.20", "output": "0.50",
412
+ "prompt_tiers": [
413
+ { "prompt_tokens_over": 100000,
414
+ "input_nocache": "0.50", "cache_read": "0.05", "cache_write_5m": "0.625", "cache_write_1h": "1.0", "output": "2.50" }
415
+ ]
416
+ }
417
+ ```
418
+
419
+ In a period with `prompt_tiers`, all five values of the base and of every
420
+ tier must be present and non-null, thresholds must be positive integers in
421
+ ascending order without duplicates, and every value must be greater than or
422
+ equal to the same value in the previous tier (the base first). That
423
+ monotonicity is what makes the base tier a true lower bound for a fact whose
424
+ prompt length is unknown. A `schema_version` `"1"` catalog may not contain
425
+ `prompt_tiers`; agent-cost 0.3.0 and earlier reject a `"2"` catalog as
426
+ unsupported rather than mispricing it, so a custom `--rates` file that uses
427
+ tiers must declare `"2"`.
428
+
280
429
  To use your own catalog instead of the one bundled with the package, pass
281
430
  `--rates path/to/rates.json` to `report` or `export` -- this fully replaces
282
431
  the bundled catalog, it does not merge with it. Inspect any catalog with:
@@ -376,10 +525,11 @@ agent-cost makes zero network calls. `agent-cost export`'s JSONL never
376
525
  includes absolute file paths, rollout paths, prompt/message content, or git
377
526
  branch names -- only the fields needed to reproduce a cost estimate:
378
527
  `occurred_at_utc`, `agent`, `session_id`, `model_raw`, `model_key`,
379
- `token_kind`, `tokens`, `mode`, and `source_quality` (a fixed-vocabulary
528
+ `token_kind`, `tokens`, `mode`, `source_quality` (a fixed-vocabulary
380
529
  caveat about how that one fact was derived: `"ok"`, Codex's
381
530
  `"first_event_delta"`, or Claude's `"identity_missing"` /
382
- `"output_lower_bound"` -- never null).
531
+ `"output_lower_bound"` -- never null), and `prompt_tokens` (the request's
532
+ prompt length as an integer, or null when unknown).
383
533
 
384
534
  ## License
385
535
 
@@ -403,10 +553,17 @@ MIT. See [LICENSE](LICENSE).
403
553
  未知のモデル・単価表にない token 種別は `unpriced` として扱い、憶測の価格を出しません。
404
554
  - 単価表 (`agent_cost/rates.json`) は履歴型カタログで、値上げは新しい期間として追加します。
405
555
  `--rates PATH` で別カタログに完全差し替えできます。
556
+ - prompt 長で単価が変わるモデル(`claude-haiku-5-5`、100,000 tokens 超で高い単価)は期間の
557
+ `prompt_tiers`(schema_version `"2"`)で表し、リクエストごとの prompt 長(input + cache read +
558
+ cache write)で tier を選びます。prompt 長が不明な fact(Codex 由来、conflicting group、usage
559
+ 不整合)は基底 tier(最安)で **下限推計**(`lower_bound`)になります。
406
560
  - `agent-cost measure --session-id ID [--session-id ID ...] --format json` は他プログラムから
407
561
  subprocess で叩くための機械可読な契約です(`protocol_version: "measure/v1"`)。指定した
408
562
  session_id が1件も見つからなくても終了コードは0(空集計として表現)、`--session-id` 未指定など
409
563
  の入力エラーのみ終了コード2です。
564
+ - `report --billing-plan PATH`(任意・機密)は、ローカルの課金プラン(窓ごとの定額・利用枠・超過規則)を
565
+ 公開単価で集計した Claude の list_cost に当てて `internal_billing` を出します。フラグ無しの出力は不変です。
566
+ - plan ファイルは 0600・symlink 不可・git repo 外に置き、出力は機密成果物として扱います(`measure` には按分になるため無し)。
410
567
  - 破損したログ行、読めなくなったファイル、Codex の累積カウンタが逆行するケースなどは、すべて
411
568
  `data_quality` に件数として記録し、黙って丸めたり捨てたりしません。
412
569
  - Codex の `output` は `output_tokens` のみです。実 rollout データを突合した結果
@@ -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.3.0"
9
+ __version__ = "0.5.0"
@@ -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
- rate = period.values.get(rate_field)
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