cdx-manager 0.24.8 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/VERSION +1 -1
- package/changelogs/CHANGELOGS_0_24_8.md +8 -0
- package/changelogs/CHANGELOGS_0_24_9.md +18 -0
- package/changelogs/CHANGELOGS_0_25_0.md +17 -0
- package/changelogs/CHANGELOGS_UNRELEASED.md +7 -0
- package/checksums/release-archives.json +22 -0
- package/docs/cli.md +20 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +1 -1
- package/src/cli_args.py +26 -2
- package/src/commands/launch.py +4 -4
- package/src/commands/runs.py +160 -18
- package/src/commands/status.py +49 -7
- package/src/interactive_usage.py +17 -6
- package/src/provider_background.py +8 -1
- package/src/provider_runtime.py +24 -3
- package/src/run_command.py +18 -2
- package/src/run_registry.py +13 -2
- package/src/run_usage.py +89 -24
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br clear="left"/>
|
|
6
6
|
|
|
7
|
-
[](LICENSE) ](LICENSE)  
|
|
8
8
|
|
|
9
9
|
**The right AI coding account, when you need it.** CDX Manager keeps your Codex and Claude accounts in separate profiles, shows which ones have quota, and helps you pick the next usable session from your terminal.
|
|
10
10
|
|
|
@@ -42,6 +42,33 @@ cdx work
|
|
|
42
42
|
|
|
43
43
|
CDX checks authentication before interactive launches. Handoff keeps the source transcript in place and asks the receiving agent to verify the conversation and workspace before acting. No account switch or prompt submission happens just because you run `cdx next`.
|
|
44
44
|
|
|
45
|
+
## Cache efficiency and fix rounds
|
|
46
|
+
|
|
47
|
+
`cdx history --json` records uncached input, cache reads, cache writes, output tokens, and `usage.cache_hit_ratio` beside the existing token totals. The ratio is cache reads divided by all input tokens, excluding output. Missing provider splits stay `null`, and text views show `cache n/d` instead of claiming a zero hit rate.
|
|
48
|
+
|
|
49
|
+
`cdx stats --json` adds `cache_usage` sums and an input-weighted `cache_hit_ratio` to each session, project (`--by project`), model (`--by model`), and total. `cache_usage` preserves unknown sums as `null`; existing numeric aggregate fields keep their previous behavior. Ratios include only runs with measured input and cache reads. Coverage is explicit in `measured_runs` and `measured_input_tokens`. `run_types.cold` and `run_types.continued` expose counts, sums, and ratios for comparison. Text views show values such as `cache 72 %` next to tokens.
|
|
50
|
+
|
|
51
|
+
Reuse a completed run's provider conversation for another fix round:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cdx run work --cwd /path/to/repo --prompt "Implement the change" --reasoning-effort high --json
|
|
55
|
+
cdx run --continue-run RUN_ID --cwd /path/to/repo --prompt "Fix the remaining test failure" --json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`--continue-run` resumes the exact Claude session or Codex thread and pins the previous run's recorded model and effort, even if the profile settings changed. The profile is inferred from the parent; an explicit session or provider must match. An explicit model or effort must also match. Runs and history record `parent_run_id` and `conversation_id`. Detached continuations use the cdx child supervisor with the same pins. Codex continuation usage is measured between exact transcript snapshots before and after the run, since exec resume can report conversation totals. If either boundary is unavailable, per-run usage stays unknown with `usage_attribution: "continuation_usage_unavailable"` in history instead of counting earlier turns again.
|
|
59
|
+
|
|
60
|
+
Conservative assumption: a continuation requires a finished Claude or Codex run, a locally available provider transcript, and recorded model and effort settings. Missing runs, profiles, expired sessions, and unavailable settings produce a clear error; the caller can retry without `--continue-run` for a cold run. `--failover` and `--isolate-workspace` cannot be combined with continuation. Capacity recovery and provider model fallback are disabled for continuations so they cannot silently switch model or account. Provider cache retention remains provider-controlled, so resuming does not guarantee a cache hit.
|
|
61
|
+
|
|
62
|
+
## Run costs and budgets
|
|
63
|
+
|
|
64
|
+
`cdx history --json` includes nullable `cost_usd` for each entry, estimated from its observed usage and serving model using the same table and `CDX_TOKEN_PRICES` overrides as `cdx stats`. Unknown prices or usage stay `null`.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
cdx run work --cwd /path/to/repo --prompt "Fix the tests" --budget 2.50 --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`--budget` stops a run once its measured estimated cost reaches the USD amount, including with `--detach` and `--continue-run`. A budget stop exits with code 125 and reports `error.code: "budget_exceeded"` and `error.spent_usd`. Detached callers read the final result through `cdx run-status` or `cdx run-report`. Usage arrives in provider updates, so the estimate can overshoot; unknown usage or prices cannot trigger a budget stop. See the [budget contract](docs/cli.md#run-budgets) for details.
|
|
71
|
+
|
|
45
72
|
## Codex model capacity recovery
|
|
46
73
|
|
|
47
74
|
`cdx run` (including `--detach`) automatically recovers from Codex's "Selected model is at capacity" failure. It retries the same prompt, profile, and model after 1 and 2 seconds, then tries the profile's `fallback_model` list in configured order, removing duplicates. Configure alternatives with `cdx set work --fallback-model MODEL_A,MODEL_B`.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.25.0
|
|
@@ -10,6 +10,14 @@ This patch improves recovery from Codex model capacity failures. (#67)
|
|
|
10
10
|
- Exclude disabled, logged-out, `never`, and `ask-first` profiles from automatic fallback. Limit recovery to three attempts per model and four profile switches, reporting `capacity_recovery_exhausted` when no eligible alternative remains or the budget is exhausted. (#67)
|
|
11
11
|
- Log recovery actions to stderr and structured run data, preserve failed attempt output, and report the effective model without changing profile settings. (#67)
|
|
12
12
|
|
|
13
|
+
## Token efficiency
|
|
14
|
+
|
|
15
|
+
- Record per-run cache hit ratios beside the existing cache read, cache write, uncached input, output, and total token fields. Preserve missing provider splits as `null` in headless, interactive, and background usage readers.
|
|
16
|
+
- Add nullable `cache_usage` sums, input-weighted ratios, measurement coverage, and cold versus continued comparisons to stats by session, project, and model. Display `cache 72 %` or `cache n/d` beside tokens without changing existing JSON fields.
|
|
17
|
+
- Add `cdx run --continue-run RUN_ID` for native Claude and Codex conversation resume with pinned model and reasoning effort, including detached runs. Persist parent links and exact conversation identities. Refuse missing, mismatched, expired, or unpinnable parents; prevent model/account fallback during a continuation.
|
|
18
|
+
- Measure Codex continuation usage from exact transcript boundaries so cumulative conversation totals are not billed again. Parse cache write counts when Codex reports them.
|
|
19
|
+
- Add provider parsing, JSON compatibility, aggregation, continuation validation, settings pinning, refusal, and parent-link regression tests.
|
|
20
|
+
|
|
13
21
|
## Maintenance
|
|
14
22
|
|
|
15
23
|
- Record tray companion checksums for v0.24.7. (`16938c0d22de4589f5b4757fd7ff55e848127e7d`)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# CDX Manager 0.24.9
|
|
2
|
+
|
|
3
|
+
Release date: 2026-10-30
|
|
4
|
+
|
|
5
|
+
This patch tracks cache efficiency and resumes runs with pinned settings. (#69)
|
|
6
|
+
|
|
7
|
+
## Cache efficiency and run continuation
|
|
8
|
+
|
|
9
|
+
- Record per-run cache hit ratios alongside cache read, cache write, uncached input, output, and total token counts. Preserve unknown provider splits as `null`. (#69)
|
|
10
|
+
- Add nullable `cache_usage` summaries, input-weighted cache hit ratios, measurement coverage, and cold versus continued run comparisons to stats by session, project, and model. Show cache efficiency beside token counts in history and stats. (#69)
|
|
11
|
+
- Add `cdx run --continue-run RUN_ID` to resume the exact Claude or Codex conversation from a finished run with its recorded profile, model, and reasoning effort, including detached runs. Preserve parent run links and provider conversation identities. (#69)
|
|
12
|
+
- Refuse unavailable parents, missing transcripts, mismatched profiles, and incompatible settings. Keep continuation settings pinned by disabling automatic capacity recovery and model fallback, and rejecting `--failover` and `--isolate-workspace`. (#69)
|
|
13
|
+
- Attribute Codex continuation usage to exact transcript boundaries so earlier turns are not counted again. Leave per-run usage unknown when those boundaries are unavailable, and parse cache write counts when reported. (#69)
|
|
14
|
+
|
|
15
|
+
## Maintenance
|
|
16
|
+
|
|
17
|
+
- Record tray companion checksums for v0.24.8. (`2f1bb93db481ff6f06d8c61435a228ae9de9c4be`)
|
|
18
|
+
- Record release archive checksums for v0.24.8. (`7ff1b1cc0b11016adc5c1a30135f1553af3a53d7`)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# CDX Manager 0.25.0
|
|
2
|
+
|
|
3
|
+
Release date: 2026-10-31
|
|
4
|
+
|
|
5
|
+
This release adds per-run cost estimates and enforces estimated USD budgets. (#71)
|
|
6
|
+
|
|
7
|
+
## Run costs and budgets
|
|
8
|
+
|
|
9
|
+
- Include nullable `cost_usd` estimates in history and run results, using observed usage, the serving model, cache token rates, and the same pricing table and `CDX_TOKEN_PRICES` overrides as stats. Unknown usage or prices remain `null`. (#71)
|
|
10
|
+
- Add `cdx run --budget USD` to stop a run when its measured estimated spend reaches the limit. Retries, model recovery, and account failover share the run budget; a budget stop terminates the provider process tree and prevents further recovery or failover. (#71)
|
|
11
|
+
- Support budgets for detached and continued runs. A continuation starts a fresh budget that excludes earlier turns; detached results remain available through `cdx run-status` and `cdx run-report`. (#71)
|
|
12
|
+
- Report budget stops with exit code `125`, `error.code: "budget_exceeded"`, and `error.spent_usd`; include `cost_usd` and `budget_usd` in final results. Estimates depend on reported usage and known prices, so delayed updates can cause overshoot and unknown costs cannot trigger a stop. (#71)
|
|
13
|
+
|
|
14
|
+
## Maintenance
|
|
15
|
+
|
|
16
|
+
- Record tray companion checksums for v0.24.9. (`eb4de92877c91f339426b344f9f242bdede02eec`)
|
|
17
|
+
- Record release archive checksums for v0.24.9. (`afecbfdcf4b999663cd6937955349824728f621e`)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Unreleased
|
|
2
|
+
|
|
3
|
+
## Run costs and budgets
|
|
4
|
+
|
|
5
|
+
- Add nullable `cost_usd` to JSON history entries using the same observed usage, model pricing, and price overrides as stats.
|
|
6
|
+
- Add `cdx run --budget USD` to stop on measured estimated spend with exit code 125, `budget_exceeded`, and `error.spent_usd` in the final JSON. Share the budget across recovery attempts and failover; support detached runs and fresh budgets for continued runs without charging earlier turns.
|
|
7
|
+
- Document unknown usage/prices and delayed provider reporting, including possible budget overshoot. Cover pricing, unknown costs, validation, live process termination, Claude transcript accumulation, continuation boundaries, and detached supervision in tests.
|
|
@@ -661,6 +661,28 @@
|
|
|
661
661
|
"x86_64-pc-windows-msvc": "41df35daa664802b87c052d6a9f634b240eb4e4a915868f1dd629b6734eab76a",
|
|
662
662
|
"x86_64-unknown-linux-musl": "2492e137e5c883fa16f502843a867e0325744dfc66d7d38be2c00eb362425af6"
|
|
663
663
|
}
|
|
664
|
+
},
|
|
665
|
+
"v0.24.8": {
|
|
666
|
+
"github_tarball_sha256": "4e34a7c7411cca9bfe568b07dc997c0d992d6c97806dbf091558cf2a826d6c1c",
|
|
667
|
+
"github_zip_sha256": "ec3f52aba6d645e1ab8990d763b4b761703e1e455f8bf40b58d6a47a01374d66",
|
|
668
|
+
"tray_assets": {
|
|
669
|
+
"aarch64-apple-darwin": "87a3abf4bf9f024ed1dfad74e6b2fe0734b9ae5811e4f6d93da7bdcab02fad66",
|
|
670
|
+
"aarch64-unknown-linux-musl": "32e7481d86be156e6c5e94d25473ff595e2596686c81ebdeff4cb719b993c7d2",
|
|
671
|
+
"x86_64-apple-darwin": "644a0a60b7d740c0853e426f69021cdc323b01438e0bcdf5f2792e8b9e3005c3",
|
|
672
|
+
"x86_64-pc-windows-msvc": "6df3f25ab6a9aa02a45aa6f90f95c3484553b248175394af9ecfd12668479281",
|
|
673
|
+
"x86_64-unknown-linux-musl": "ea867d352a18db59e882d6abbdbafa61b54fbec7fafa66746852696cf18cd6c5"
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
"v0.24.9": {
|
|
677
|
+
"github_tarball_sha256": "1bf646b5f6dc5b3fa9ba99a7736d62fc0170469cbb6bf6617124dfb134cbbea7",
|
|
678
|
+
"github_zip_sha256": "b921117dfd70beb48e8688d94706f755714a4eb826a94dc4b73ef250e646324e",
|
|
679
|
+
"tray_assets": {
|
|
680
|
+
"aarch64-apple-darwin": "378b8b9d85159263f9ce4a130ad0c67d5f142b43609b3719be0664c9b3125fed",
|
|
681
|
+
"aarch64-unknown-linux-musl": "49f9f5e1d0a352935a9e7c738150d36f679b22c66b389c937124e01bf6d954ac",
|
|
682
|
+
"x86_64-apple-darwin": "40a12abb38b8b4ef61c110f6426847b190cb9dd7f146d8f33f3abf0e5b3d8488",
|
|
683
|
+
"x86_64-pc-windows-msvc": "21dfebd0265bd8705d381517357cd8166cb0e2da37b27ed3e3485c892d9289ee",
|
|
684
|
+
"x86_64-unknown-linux-musl": "8477262e55f31ee2217382048b967846478a34f222beeb4242fa9c4c2942f747"
|
|
685
|
+
}
|
|
664
686
|
}
|
|
665
687
|
}
|
|
666
688
|
}
|
package/docs/cli.md
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
| `cdx agents [--max-age-hours N] [--json]` | Each session's last known agent state (working, waiting, done, failed, limit, ended), waiting first; records older than N hours (default 24) are dropped. See [Agent state](sessions-and-notifications.md#agent-state) |
|
|
57
57
|
| `cdx next [--json] [--refresh]` | Select the best next assistant using the same priority logic as `cdx status` |
|
|
58
58
|
| `cdx select --provider PROVIDER [--min-reasoning-effort minimal\|low\|medium\|high\|xhigh] [--min-power minimal\|low\|medium\|high\|xhigh] [--require-ready] [--refresh] --json` | Select a suitable session for headless automation |
|
|
59
|
-
| `cdx run [session] --cwd PATH (--prompt-file PATH\|--prompt TEXT\|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant\|code-review\|retro] [--reasoning-effort minimal\|low\|medium\|high\|xhigh] [--power minimal\|low\|medium\|high\|xhigh] [--permission MODE] [--timeout-seconds N] [--label TEXT] [--max-parallel N] [--detach] [--failover] [--refresh] --json` | Run one headless task and return a stable JSON result; `--detach` returns the `run_id` at launch without waiting, `--failover` continues the task on the next account when this one hits its rate limit (with `--detach` too: the detached cdx child applies it and the run's `occupancies` record each move), `--prompt-file -` reads the prompt from stdin, `--label` names the run in `cdx runs`, `--max-parallel N` (default `CDX_MAX_RUNS`; unset, invalid, 0 or negative means no limit) refuses to start with `too_many_runs` when N runs are already running, `--kind` (default `assistant`) labels the run record and `code-review` adds a review report to its final payload |
|
|
59
|
+
| `cdx run [session] --cwd PATH (--prompt-file PATH\|--prompt TEXT\|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant\|code-review\|retro] [--reasoning-effort minimal\|low\|medium\|high\|xhigh] [--power minimal\|low\|medium\|high\|xhigh] [--permission MODE] [--timeout-seconds N] [--budget USD] [--label TEXT] [--max-parallel N] [--continue-run RUN_ID] [--detach] [--failover] [--refresh] --json` | Run one headless task and return a stable JSON result; `--detach` returns the `run_id` at launch without waiting, `--failover` continues the task on the next account when this one hits its rate limit (with `--detach` too: the detached cdx child applies it and the run's `occupancies` record each move), `--prompt-file -` reads the prompt from stdin, `--label` names the run in `cdx runs`, `--max-parallel N` (default `CDX_MAX_RUNS`; unset, invalid, 0 or negative means no limit) refuses to start with `too_many_runs` when N runs are already running, `--kind` (default `assistant`) labels the run record and `code-review` adds a review report to its final payload |
|
|
60
60
|
| `cdx run-report <run_id> --json` | Full report, transcript metadata, and final payload for a run; `final_message` is the run's last assistant message, read from Claude's `result` envelope or Codex's last `agent_message` (null when neither exists) |
|
|
61
61
|
| `cdx run-status <run_id> --json` | Status of one run by id |
|
|
62
62
|
| `cdx run-tail <run_id> [--lines N] --json` | Last lines of a run's own output, while it is still running or after it finished |
|
|
@@ -241,6 +241,20 @@ The result includes `launcher: "cdx"`, `run_id`, selected `session`, `provider`,
|
|
|
241
241
|
|
|
242
242
|
Known headless and completed interactive token usage is persisted in launch history and can be aggregated later. Interactive records are best-effort local data: Codex reads its latest cumulative rollout snapshot, Claude reads its assistant usage records, and Ollama — which writes no transcript of its own — is launched with `--verbose` so its per-response token counts land in the terminal capture cdx already takes. Antigravity reports no usage: its history is schema-less binary protobuf and its logs carry no usage line, so those sessions show no tokens by nature rather than by omission.
|
|
243
243
|
|
|
244
|
+
`--continue-run RUN_ID` resumes the exact provider conversation from a finished Claude or Codex run, inferring its profile and pinning its recorded model and effort. Existing targets and settings may be supplied only if they match. The parent transcript must still exist. `parent_run_id` links the new run to its parent in run results, the registry, and history; `conversation_id` identifies the provider conversation. Detached continuations preserve these settings through the cdx supervisor. Codex continuation usage is measured between exact transcript snapshots before and after the run, since exec resume can report conversation totals. If either boundary is unavailable, per-run usage stays unknown with `usage_attribution: "continuation_usage_unavailable"` in history instead of counting earlier turns again. Continuation does not use automatic capacity recovery or model fallback, and cannot be combined with `--failover` or `--isolate-workspace`.
|
|
245
|
+
|
|
246
|
+
Continuation errors are `continue_run_not_found` (unknown parent), `continue_run_mismatch` (another or missing profile/provider), `continue_run_unavailable` (unfinished or unsupported run, expired transcript, or unavailable settings), and `continue_run_settings_mismatch` (changed model/effort or incompatible flags). Callers may start a normal run after a refusal by omitting `--continue-run`.
|
|
247
|
+
|
|
248
|
+
#### Run budgets
|
|
249
|
+
|
|
250
|
+
`cdx run --budget USD` accepts a finite positive amount in US dollars. It uses the same token pricing function, cache rates, and `CDX_TOKEN_PRICES` overrides as `cdx stats`. The supervisor checks reported stdout usage and the exact provider conversation transcript every 250 ms while waiting, and checks again at completion. Claude's transcript messages are summed; continuation measurements subtract the transcript snapshot taken before this run. Earlier turns do not consume the new budget. Measured costs from retries, model recovery, and account failover share the same run budget.
|
|
251
|
+
|
|
252
|
+
At or above the limit, cdx terminates the provider process tree, skips further recovery/failover, records the run as `failed`, and exits with **125**. The final JSON has `ok: false`, `exit_code: 125`, `error.source: "cdx"`, `error.code: "budget_exceeded"`, and `error.spent_usd` with the measured estimated spend across attempts. `cost_usd` repeats that amount and `budget_usd` records the requested limit. Normal results also include nullable `cost_usd` for their observed usage and `budget_usd` (`null` without the flag).
|
|
253
|
+
|
|
254
|
+
With `--detach`, the launch still exits successfully and includes `budget_usd`; cdx always uses its own child supervisor for budgeted runs, including when native background delegation is enabled. Inspect `cdx run-status RUN_ID --json` or `cdx run-report RUN_ID --json` for the child's final payload and budget error. `--continue-run` starts a fresh budget for the new run, including in detached mode.
|
|
255
|
+
|
|
256
|
+
Conservative assumption: this is a stop based on observed, priceable usage, not a prepaid billing limit. Provider updates can arrive late or only at completion, and termination can take time, so actual spend can exceed the amount. Missing usage, missing continuation boundaries, mixed/unobserved models, and unknown prices are not treated as free tokens and cannot trigger the budget; their estimated cost stays `null`. Supply a `CDX_TOKEN_PRICES` override for a model missing from the table. Existing profile-level `cdx set --budget` settings remain the provider-native Claude cap and apply independently.
|
|
257
|
+
|
|
244
258
|
Every usage record, whichever path produced it, uses one set of definitions:
|
|
245
259
|
|
|
246
260
|
| Field | Meaning |
|
|
@@ -251,15 +265,20 @@ Every usage record, whichever path produced it, uses one set of definitions:
|
|
|
251
265
|
| `cache_read_tokens` | Tokens served from it. Kept apart from cache creation because the two cost roughly 1.25x and 0.1x of uncached input, a difference no summed figure can recover. |
|
|
252
266
|
| `cached_input_tokens` | `cache_creation_tokens + cache_read_tokens`, derived. The single CACHE column in `cdx stats` shows this. |
|
|
253
267
|
| `output_tokens` | Generated tokens. |
|
|
268
|
+
| `cache_hit_ratio` | Cache reads divided by uncached input plus cache writes plus cache reads, excluding output. A fraction from 0 to 1; `null` without a reported read split or with zero input. When Codex omits writes, its reported total input is uncached input plus reads. |
|
|
254
269
|
| `reasoning_tokens` | Reasoning the provider counts separately. Codex reports one; Claude does not, because it bills thinking as output — those tokens are already inside `output_tokens`, so an empty REASON column for Claude is correct rather than missing. |
|
|
255
270
|
| `total_tokens` | `input + cache_creation + cache_read + output`. Cache is typically the bulk of real consumption, so a total that left it out would not be slightly low — it would be a different quantity. |
|
|
256
271
|
|
|
257
272
|
Cached input is counted once: it is excluded from `input_tokens` and reported in the cache fields, never in both. `null` means the provider did not report the figure, which is not the same as zero.
|
|
258
273
|
|
|
274
|
+
History and stats text show `cache 72 %` beside tokens, or `cache n/d` for unknown splits. Stats JSON adds `cache_usage` with nullable sums for `input_tokens`, `cache_creation_tokens`, `cache_read_tokens`, and `output_tokens`. Existing numeric aggregate fields are unchanged. `cache_hit_ratio` is computed from summed reads and input across measured runs, never by averaging percentages. `measured_runs`, `measured_input_tokens`, and `measured_cache_read_tokens` describe ratio coverage, excluding missing splits and zero-input runs. Each session, project, model, and total includes `run_types.cold` and `run_types.continued`, each with `runs` and the same cache summary fields. A missing `parent_run_id` counts as cold for historical compatibility.
|
|
275
|
+
|
|
259
276
|
`cdx stats` also shows a **`COST~`** column and ranks sessions by it. It weights each token class by what it costs relative to one uncached input token — cache read at the recorded model's ratio (0.1x by default), cache write 1.25x (2x for one-hour writes), and output at the recorded model's output/input ratio — and reports the result in uncached-input-equivalent tokens. These are ratios rather than prices. It is **not** a currency amount, and it is not comparable across providers whose own prices differ. Writes reported as one-hour are weighted 2x; a record without that split keeps the five-minute 1.25x rate.
|
|
260
277
|
|
|
261
278
|
Ranking on the raw total would put a session that mostly replayed a cache above one that spent far more on generation, because cache reads are typically the overwhelming majority of tokens consumed while costing a fiftieth of an output token.
|
|
262
279
|
|
|
280
|
+
`cdx history --json` includes `cost_usd` on every entry, calculated when read using the current stats price table and overrides. Missing or untrusted usage and unknown serving models/prices yield `null`, never a fabricated zero. Cache reads, writes (including one-hour TTL), and output use the same rates as stats.
|
|
281
|
+
|
|
263
282
|
The **`USD~`** column turns that into money for runs whose serving model cdx recorded. It prices each token class directly using the built-in model table. To customize prices: set `CDX_TOKEN_PRICES` to a JSON object mapping model id to dollars per million tokens to override the built-in table, whose last review date is printed with the totals. A run whose model cdx never saw stays unpriced rather than being charged at a default tier, and the totals line says how many runs were actually priced.
|
|
264
283
|
|
|
265
284
|
The table requires `input` and `output` prices in USD per million tokens and accepts an optional `cache_read` price in the same units. Without it, cache reads cost 0.1x input; cache writes assume 1.25x input. GPT-6.1 Sol and Claude Opus 5.5 use 0.05x cache reads, while Claude Fable/Mythos 5.1 use 0.025x. Both `COST~` and `USD~` honor those rates. `CDX_TOKEN_PRICES` replaces the supplied model row: a two-field override keeps the legacy 0.1x fallback; include `cache_read` to override it (including zero).
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -245,7 +245,7 @@ def _print_help(use_color=False):
|
|
|
245
245
|
f" {_style('cdx reset <name> [--yes] [--json]', '36', use_color)}",
|
|
246
246
|
f" {_style('cdx auth refresh <name|all> [--json]', '36', use_color)}",
|
|
247
247
|
f" {_style('cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json', '36', use_color)}",
|
|
248
|
-
f" {_style('cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review|retro] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] [--label TEXT] [--max-parallel N] [--detach] [--failover] [--refresh] --json', '36', use_color)}",
|
|
248
|
+
f" {_style('cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review|retro] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] [--label TEXT] [--max-parallel N] [--continue-run RUN_ID] [--detach] [--failover] [--refresh] --json', '36', use_color)}",
|
|
249
249
|
f" {_style('cdx runs [--limit N] [--since 7d|today|DATE] [--running] --json', '36', use_color)}",
|
|
250
250
|
f" {_style('cdx run-status <run_id> --json', '36', use_color)}",
|
|
251
251
|
f" {_style('cdx run-report <run_id> --json', '36', use_color)}",
|
package/src/cli_args.py
CHANGED
|
@@ -5,6 +5,7 @@ Split out of cli_commands.py: pure input-parsing layer (no command execution),
|
|
|
5
5
|
imported back there and used by the handlers.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import math
|
|
8
9
|
from datetime import datetime, timedelta
|
|
9
10
|
|
|
10
11
|
from .config import (
|
|
@@ -47,7 +48,7 @@ CAN_RESUME_USAGE = "Usage: cdx can-resume <name> [--conversation <id>] [--json]"
|
|
|
47
48
|
SESSIONS_USAGE = "Usage: cdx sessions [--all] --json"
|
|
48
49
|
SELECT_USAGE = "Usage: cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json"
|
|
49
50
|
NEXT_USAGE = "Usage: cdx next [--json] [--refresh]"
|
|
50
|
-
RUN_USAGE = "Usage: cdx run [session] (--cwd|--dir) PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review|retro] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--network-access] [--isolate-workspace] [--timeout-seconds N] [--label TEXT] [--max-parallel N] [--detach] [--failover] [--refresh] --json"
|
|
51
|
+
RUN_USAGE = "Usage: cdx run [session] (--cwd|--dir) PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review|retro] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--network-access] [--isolate-workspace] [--timeout-seconds N] [--budget USD] [--label TEXT] [--max-parallel N] [--continue-run RUN_ID] [--detach] [--failover] [--refresh] --json"
|
|
51
52
|
RUN_JSON_REQUIRED = "cdx run: --json is required."
|
|
52
53
|
RUN_TARGET_REQUIRED = "cdx run: specify a session name or --provider PROVIDER."
|
|
53
54
|
RUN_SESSION_PROVIDER_CONFLICT = "cdx run: cannot specify both a session name and --provider."
|
|
@@ -425,6 +426,17 @@ def _parse_timeout_seconds(value, usage=RUN_USAGE):
|
|
|
425
426
|
return parsed
|
|
426
427
|
|
|
427
428
|
|
|
429
|
+
def _parse_run_budget(value):
|
|
430
|
+
try:
|
|
431
|
+
amount = float(value)
|
|
432
|
+
if not math.isfinite(amount) or amount <= 0:
|
|
433
|
+
raise ValueError
|
|
434
|
+
return amount
|
|
435
|
+
except (TypeError, ValueError) as error:
|
|
436
|
+
raise CdxArgumentError("cdx run: --budget must be a finite positive USD amount.",
|
|
437
|
+
code=ARG_CODE_OUT_OF_RANGE, arguments=["--budget"]) from error
|
|
438
|
+
|
|
439
|
+
|
|
428
440
|
def _parse_run_timeout_seconds(value):
|
|
429
441
|
try:
|
|
430
442
|
return _parse_timeout_seconds(value, RUN_USAGE)
|
|
@@ -539,11 +551,16 @@ def cdx_schema():
|
|
|
539
551
|
"provider_start_failed",
|
|
540
552
|
"provider_failed",
|
|
541
553
|
"provider_timeout",
|
|
554
|
+
"budget_exceeded",
|
|
542
555
|
"run_cancelled",
|
|
543
556
|
"too_many_runs",
|
|
544
557
|
"failover_exhausted",
|
|
545
558
|
"failover_limit_reached",
|
|
546
559
|
"capacity_recovery_exhausted",
|
|
560
|
+
"continue_run_not_found",
|
|
561
|
+
"continue_run_mismatch",
|
|
562
|
+
"continue_run_unavailable",
|
|
563
|
+
"continue_run_settings_mismatch",
|
|
547
564
|
"invalid_request",
|
|
548
565
|
"cdx_error",
|
|
549
566
|
],
|
|
@@ -661,11 +678,13 @@ def _parse_run_args(args):
|
|
|
661
678
|
"--prompt": {"key": "prompt", "type": "str", "default": None},
|
|
662
679
|
"--provider": {"key": "provider", "type": "str", "default": None, "transform": _parse_run_provider},
|
|
663
680
|
"--model": {"key": "model", "type": "str", "default": None},
|
|
681
|
+
"--continue-run": {"key": "continue_run", "type": "str", "default": None},
|
|
664
682
|
"--kind": {"key": "kind", "type": "str", "default": "assistant"},
|
|
665
683
|
"--reasoning-effort": {"key": "reasoning_effort", "type": "str", "default": None},
|
|
666
684
|
"--power": {"key": "power", "type": "str", "default": None},
|
|
667
685
|
"--permission": {"key": "permission", "type": "str", "default": None, "transform": _normalize_run_permission},
|
|
668
686
|
"--timeout-seconds": {"key": "timeout_seconds", "type": "str", "default": None, "transform": _parse_run_timeout_seconds},
|
|
687
|
+
"--budget": {"key": "budget", "type": "str", "default": None, "transform": _parse_run_budget},
|
|
669
688
|
"--detach": {"key": "detach", "type": "bool", "default": False},
|
|
670
689
|
"--network-access": {"key": "network_access", "type": "bool", "default": False},
|
|
671
690
|
"--isolate-workspace": {"key": "isolate_workspace", "type": "bool", "default": False},
|
|
@@ -684,7 +703,7 @@ def _parse_run_args(args):
|
|
|
684
703
|
code=ARG_CODE_MUTUALLY_EXCLUSIVE,
|
|
685
704
|
arguments=["session", "--provider"],
|
|
686
705
|
)
|
|
687
|
-
if not name and not parsed["provider"]:
|
|
706
|
+
if not name and not parsed["provider"] and not parsed["continue_run"]:
|
|
688
707
|
raise CdxArgumentError(RUN_TARGET_REQUIRED, code=ARG_CODE_MISSING, arguments=["session", "--provider"])
|
|
689
708
|
if not parsed["cwd"]:
|
|
690
709
|
raise CdxArgumentError(RUN_CWD_REQUIRED, code=ARG_CODE_MISSING, arguments=["--cwd"])
|
|
@@ -708,6 +727,9 @@ def _parse_run_args(args):
|
|
|
708
727
|
arguments=["--kind"],
|
|
709
728
|
allowed=RUN_KIND_VALUES,
|
|
710
729
|
)
|
|
730
|
+
if parsed["continue_run"] is not None and not parsed["continue_run"].strip():
|
|
731
|
+
raise CdxArgumentError("cdx run: --continue-run requires a nonempty run id.",
|
|
732
|
+
code=ARG_CODE_INVALID_VALUE, arguments=["--continue-run"])
|
|
711
733
|
effort = _parse_run_effort(parsed["reasoning_effort"], parsed["power"])
|
|
712
734
|
return {
|
|
713
735
|
"name": name,
|
|
@@ -715,9 +737,11 @@ def _parse_run_args(args):
|
|
|
715
737
|
"cwd": parsed["cwd"],
|
|
716
738
|
"prompt_file": parsed["prompt_file"],
|
|
717
739
|
"prompt": parsed["prompt"],
|
|
740
|
+
"continue_run": parsed["continue_run"],
|
|
718
741
|
"kind": parsed["kind"],
|
|
719
742
|
"model": parsed["model"],
|
|
720
743
|
"permission": parsed["permission"],
|
|
744
|
+
"budget": parsed["budget"],
|
|
721
745
|
"timeout_seconds": parsed["timeout_seconds"],
|
|
722
746
|
"detach": parsed["detach"],
|
|
723
747
|
"network_access": parsed["network_access"],
|
package/src/commands/launch.py
CHANGED
|
@@ -34,7 +34,7 @@ from ..provider_runtime import (
|
|
|
34
34
|
with_requested_conversation,
|
|
35
35
|
)
|
|
36
36
|
from ..run_command import PROMPT_STDIN, read_run_prompt
|
|
37
|
-
from ..run_usage import estimate_cost
|
|
37
|
+
from ..run_usage import estimate_cost, format_cache_ratio
|
|
38
38
|
|
|
39
39
|
LAUNCH_USAGE = "Usage: cdx <name> [--dir PATH] [--prompt TEXT|--prompt-file PATH|--task TEXT [--end-state TEXT]] [--resume] [--on-busy fail|ask] [--json]"
|
|
40
40
|
|
|
@@ -425,9 +425,9 @@ def _goodbye_line(session, run_info, use_color):
|
|
|
425
425
|
if cost is not None:
|
|
426
426
|
parts.append(_format_usd(cost))
|
|
427
427
|
elif isinstance(usage, dict):
|
|
428
|
-
total_tokens =
|
|
428
|
+
total_tokens = usage.get("total_tokens")
|
|
429
429
|
if total_tokens:
|
|
430
|
-
parts.append(f"{_format_token_count(total_tokens)} tokens")
|
|
430
|
+
parts.append(f"{_format_token_count(total_tokens)} tokens {format_cache_ratio(usage.get('cache_hit_ratio'))}")
|
|
431
431
|
return _dim(" · ".join(parts), use_color)
|
|
432
432
|
|
|
433
433
|
|
|
@@ -482,7 +482,7 @@ def _attach_interactive_usage(session, run_info, history=None, baseline=None, un
|
|
|
482
482
|
else:
|
|
483
483
|
previous = _previous_cumulative(history, provider_transcript)
|
|
484
484
|
if previous is not None:
|
|
485
|
-
delta = usage_delta(cumulative, previous)
|
|
485
|
+
delta = usage_delta(cumulative, previous, provider=session.get("provider"))
|
|
486
486
|
if delta is not None:
|
|
487
487
|
run_info["usage"] = delta
|
|
488
488
|
# A None delta means the transcript shrank, rotated, or was replaced.
|