Skip to content

Commit dcc42ea

Browse files
ryoppippirzv-me
andcommitted
fix(codex): price recorded Ultrafast requests at Ultrafast rates
Codex rollouts can record `service_tier: "ultrafast"` in `thread_settings_applied` events, but ccusage left that value unclassified, so with a Standard config fallback those requests were priced at Standard rates. Recognize the recorded tier and track it in its own usage bucket, so the tier stays per request: when a thread switches back to Standard, earlier requests keep their Ultrafast pricing and token totals are unchanged. Ultrafast rates come from OpenAI's API pricing table, which publishes them for GPT-6.1 Sol and GPT-6 Astra only, at exactly 6x Standard for every input, cached input, cache write and output rate, short and long context. The embedded models.dev snapshot's GPT-6.1 Sol Ultrafast entry agrees. Models without a published Ultrafast rate keep Standard pricing. Fixes #1987 Co-authored-by: Razvan Aurariu <[email protected]>
1 parent b8ad727 commit dcc42ea

12 files changed

Lines changed: 227 additions & 40 deletions

File tree

‎docs/guide/codex/index.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ These views support `--json`, `--compact`, `--offline`, and `--speed auto|standa
5050
- **Per-model grouping** – The active `turn_context` specifies the model for newly counted usage. Replayed parent contexts in current MultiAgent V2 subagent prefixes remain inherited history and do not add model usage to the child. We aggregate tokens per day/month and per model. Sessions lacking model metadata (seen in early September 2025 builds) are skipped.
5151
- **Pricing** – Rates come from LiteLLM's pricing dataset via the shared `LiteLLMPricingFetcher`. Codex's internal review label uses the manually curated, date-based fallback timeline below and remains marked as approximate.
5252
- **Scheduled pricing** – DeepSeek V4 Flash and Pro use each event's timestamp: legacy rates apply before `2026-08-16T16:00:00Z`, and the later rates use UTC weekday peak windows of `01:00–04:00` and `06:00–10:00` (endpoints excluded). Cache creation follows the scheduled input rate.
53-
- **Speed pricing** uses `--speed auto` by default. For rollouts written by Codex CLI 0.144.0 and later, ccusage applies recorded `thread_settings_applied` tier changes chronologically. `priority` and legacy `fast` use Fast pricing, `flex` uses Flex pricing, and `default` or `standard` uses Standard pricing. Unmarked usage falls back to `config.toml` detection. Pass `--speed standard`, `--speed flex`, or `--speed fast` to override every recorded tier. Fast and Flex pricing use model-specific multipliers only when one is available; otherwise, ccusage keeps standard pricing rather than inventing a rate.
53+
- **Speed pricing** uses `--speed auto` by default. For rollouts written by Codex CLI 0.144.0 and later, ccusage applies recorded `thread_settings_applied` tier changes chronologically. `priority` and legacy `fast` use Fast pricing, `ultrafast` uses Ultrafast pricing, `flex` uses Flex pricing, and `default` or `standard` uses Standard pricing. Unmarked usage falls back to `config.toml` detection. Pass `--speed standard`, `--speed flex`, or `--speed fast` to override every recorded tier. Fast and Flex pricing use model-specific multipliers only when one is available; otherwise, ccusage keeps standard pricing rather than inventing a rate.
5454
- **Legacy fallback** – Early September 2025 logs that never recorded `turn_context` metadata are still included; the CLI assumes `gpt-5` for pricing so you can review the tokens even though the model tag is missing (the JSON output also marks these rows with `"isFallback": true`).
5555
- **Cost formula** – Non-cached input uses the standard input price; cached input uses the cache-read price (falling back to the input price when missing); and output tokens are billed at the output price. All prices are per million tokens. Reasoning tokens may be shown for reference, but they are part of the output charge and are not billed separately.
5656
- **Totals and reports** – Daily, monthly, and session views display per-model breakdowns, overall totals, and optional JSON for automation.
@@ -68,13 +68,13 @@ When Codex emits a model alias, the CLI automatically resolves it through the Li
6868

6969
## Speed Pricing
7070

71-
By default, `ccusage codex` uses `--speed auto`. Codex CLI 0.144.0 and later can persist `thread_settings_applied` events in session rollouts. ccusage associates each token event with the most recent recognized setting. `service_tier = "priority"` or legacy `"fast"` is Fast, `"flex"` is Flex, and `"default"` or `"standard"` is Standard. This supports sessions that switch modes over time instead of applying one multiplier to the entire day or session.
71+
By default, `ccusage codex` uses `--speed auto`. Codex CLI 0.144.0 and later can persist `thread_settings_applied` events in session rollouts. ccusage associates each token event with the most recent recognized setting. `service_tier = "priority"` or legacy `"fast"` is Fast, `"ultrafast"` is Ultrafast, `"flex"` is Flex, and `"default"` or `"standard"` is Standard. This supports sessions that switch modes over time instead of applying one multiplier to the entire day or session.
7272

7373
Some usage remains unclassified, including older rollouts, saved headless `codex exec --json` output, and startup usage before the first persisted settings event. For only that unclassified portion, auto mode reads `config.toml` from each `CODEX_HOME` root and uses Fast when any root has `service_tier = "priority"` or legacy `service_tier = "fast"`. If no root requests Fast and a root requests `service_tier = "flex"`, it uses Flex. Otherwise it uses Standard. An unsupported recorded tier is also left unclassified rather than inheriting a stale tier. Explicit `--speed standard`, `--speed flex`, and `--speed fast` override all recorded and fallback tiers.
7474

7575
Fallback detection reads the top-level `service_tier` and lets a named profile override it only when `profile = "name"` selects that profile in `config.toml`. Inactive profiles are ignored, and a selected profile without its own tier inherits the top-level setting. If Codex selected a profile through `--profile` or another configuration file, use an explicit `--speed` for unclassified usage because ccusage cannot infer that selection from `config.toml`.
7676

77-
Fast and Flex pricing use model-specific multipliers only when one is published. GPT-5.6 Sol, Terra, and Luna use the [documented 2× API Priority rate](https://learn.chatgpt.com/docs/agent-configuration/speed#fast-mode), and GPT-6 [Astra](https://developers.openai.com/api/docs/models/gpt-6-astra), [Sol](https://developers.openai.com/api/docs/models/gpt-6-sol), and [Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) and GPT-6.1 [Sol](https://developers.openai.com/api/docs/models/gpt-6.1-sol) and [Terra](https://developers.openai.com/api/docs/models/gpt-6.1-terra) use their documented 2× Fast rates. Models listed in OpenAI's [Flex pricing table](https://developers.openai.com/api/docs/pricing) use 0.5× Standard pricing, including dated and provider-qualified names. These are distinct from ChatGPT credit-consumption rates. ccusage's `costUSD` is an API-equivalent estimate, not a ChatGPT credit balance. When a model's Fast or Flex rate is unknown, ccusage reports standard pricing rather than assuming a multiplier, which may underestimate Fast usage or overestimate Flex usage.
77+
Fast and Flex pricing use model-specific multipliers only when one is published. GPT-5.6 Sol, Terra, and Luna use the [documented 2× API Priority rate](https://learn.chatgpt.com/docs/agent-configuration/speed#fast-mode), and GPT-6 [Astra](https://developers.openai.com/api/docs/models/gpt-6-astra), [Sol](https://developers.openai.com/api/docs/models/gpt-6-sol), and [Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) and GPT-6.1 [Sol](https://developers.openai.com/api/docs/models/gpt-6.1-sol) and [Terra](https://developers.openai.com/api/docs/models/gpt-6.1-terra) use their documented 2× Fast rates. Models listed in OpenAI's [Flex pricing table](https://developers.openai.com/api/docs/pricing) use 0.5× Standard pricing, including dated and provider-qualified names. Recorded Ultrafast usage uses the 6× Standard rates in the same table's Ultrafast section, which covers GPT-6.1 Sol and GPT-6 Astra; other models keep Standard pricing for Ultrafast requests. These are distinct from ChatGPT credit-consumption rates. ccusage's `costUSD` is an API-equivalent estimate, not a ChatGPT credit balance. When a model's Fast or Flex rate is unknown, ccusage reports standard pricing rather than assuming a multiplier, which may underestimate Fast usage or overestimate Flex usage.
7878

7979
Use [`pricingOverrides`](/guide/config-files#pricing-overrides) to configure a model's `fastMultiplier` or `flexMultiplier`.
8080

‎nix/cargo-artifacts.nix‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ let
8888
lib.optionals (lib.elem "ccusage-core" names) [
8989
(rustRoot + /crates/ccusage-core/src/fast-multiplier-overrides.json)
9090
(rustRoot + /crates/ccusage-core/src/flex-multiplier-overrides.json)
91+
(rustRoot + /crates/ccusage-core/src/ultrafast-multiplier-overrides.json)
9192
]
9293
++ lib.optionals (lib.elem "ccusage-adapter-codex" names) [
9394
(rustRoot + /adapters/codex/src/codex-auto-review-fallbacks.json)

‎package.nix‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ let
2727
|| lib.hasSuffix "/cli-commands.json" path
2828
|| lib.hasSuffix "/fast-multiplier-overrides.json" path
2929
|| lib.hasSuffix "/flex-multiplier-overrides.json" path
30+
|| lib.hasSuffix "/ultrafast-multiplier-overrides.json" path
3031
|| lib.hasSuffix "/models-dev-pricing.json" path
3132
|| lib.hasSuffix "/models-dev-catalog-rules.json" path
3233
|| lib.hasSuffix "/codex-auto-review-fallbacks.json" path;

‎rust/adapters/codex/src/aggregate.rs‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,9 @@ fn accumulate_codex_event_into_model_usage(
502502
}
503503
Some(CodexServiceTier::Fast) => Some(&mut timestamped_usage.recorded_fast_usage),
504504
Some(CodexServiceTier::Flex) => Some(&mut timestamped_usage.recorded_flex_usage),
505+
Some(CodexServiceTier::Ultrafast) => {
506+
Some(&mut timestamped_usage.recorded_ultrafast_usage)
507+
}
505508
None => None,
506509
};
507510
if let Some(recorded_usage) = recorded_usage {
@@ -514,6 +517,7 @@ fn accumulate_codex_event_into_model_usage(
514517
Some(CodexServiceTier::Standard) => Some(&mut model_usage.recorded_standard_usage),
515518
Some(CodexServiceTier::Fast) => Some(&mut model_usage.recorded_fast_usage),
516519
Some(CodexServiceTier::Flex) => Some(&mut model_usage.recorded_flex_usage),
520+
Some(CodexServiceTier::Ultrafast) => Some(&mut model_usage.recorded_ultrafast_usage),
517521
None => None,
518522
};
519523
if let Some(recorded_usage) = recorded_usage {
@@ -562,6 +566,7 @@ fn merge_recorded_codex_usage(
562566
CodexServiceTier::Standard => &mut model_usage.recorded_standard_usage,
563567
CodexServiceTier::Fast => &mut model_usage.recorded_fast_usage,
564568
CodexServiceTier::Flex => &mut model_usage.recorded_flex_usage,
569+
CodexServiceTier::Ultrafast => &mut model_usage.recorded_ultrafast_usage,
565570
};
566571
merge_codex_usage_bucket(recorded_usage, usage);
567572

@@ -574,6 +579,7 @@ fn merge_recorded_codex_usage(
574579
CodexServiceTier::Standard => &mut timestamped_usage.recorded_standard_usage,
575580
CodexServiceTier::Fast => &mut timestamped_usage.recorded_fast_usage,
576581
CodexServiceTier::Flex => &mut timestamped_usage.recorded_flex_usage,
582+
CodexServiceTier::Ultrafast => &mut timestamped_usage.recorded_ultrafast_usage,
577583
};
578584
merge_codex_usage_bucket(recorded_usage, usage);
579585
}
@@ -826,6 +832,10 @@ fn merge_codex_model_usage(target: &mut crate::CodexModelUsage, source: crate::C
826832
);
827833
merge_codex_usage_bucket(&mut target.recorded_fast_usage, source.recorded_fast_usage);
828834
merge_codex_usage_bucket(&mut target.recorded_flex_usage, source.recorded_flex_usage);
835+
merge_codex_usage_bucket(
836+
&mut target.recorded_ultrafast_usage,
837+
source.recorded_ultrafast_usage,
838+
);
829839
for (timestamp, usage) in source.timestamped_usage {
830840
let target_usage = target.timestamped_usage.entry(timestamp).or_default();
831841
merge_codex_usage_bucket(&mut target_usage.usage, usage.usage);
@@ -841,6 +851,10 @@ fn merge_codex_model_usage(target: &mut crate::CodexModelUsage, source: crate::C
841851
&mut target_usage.recorded_flex_usage,
842852
usage.recorded_flex_usage,
843853
);
854+
merge_codex_usage_bucket(
855+
&mut target_usage.recorded_ultrafast_usage,
856+
usage.recorded_ultrafast_usage,
857+
);
844858
}
845859
target.is_fallback |= source.is_fallback;
846860
}

‎rust/adapters/codex/src/lib.rs‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,75 @@ mod tests {
767767
assert!((cost - 10e-6).abs() < f64::EPSILON);
768768
}
769769

770+
#[test]
771+
fn prices_recorded_ultrafast_usage_at_published_rates() {
772+
let pricing = PricingMap::load_embedded();
773+
let ultrafast = |usage: CodexUsageBucket| CodexModelUsage {
774+
input_tokens: usage.input_tokens,
775+
cached_input_tokens: usage.cached_input_tokens,
776+
output_tokens: usage.output_tokens,
777+
total_tokens: usage.input_tokens + usage.output_tokens,
778+
long_context_input_tokens: usage.long_context_input_tokens,
779+
long_context_cached_input_tokens: usage.long_context_cached_input_tokens,
780+
long_context_output_tokens: usage.long_context_output_tokens,
781+
recorded_ultrafast_usage: usage,
782+
..CodexModelUsage::default()
783+
};
784+
let short = ultrafast(CodexUsageBucket {
785+
input_tokens: 1_200,
786+
cached_input_tokens: 200,
787+
output_tokens: 50,
788+
..CodexUsageBucket::default()
789+
});
790+
let long = ultrafast(CodexUsageBucket {
791+
input_tokens: 300_000,
792+
cached_input_tokens: 250_000,
793+
output_tokens: 1_000,
794+
long_context_input_tokens: 300_000,
795+
long_context_cached_input_tokens: 250_000,
796+
long_context_output_tokens: 1_000,
797+
..CodexUsageBucket::default()
798+
});
799+
800+
// Expected costs use OpenAI's published Ultrafast USD per 1M tokens.
801+
for (model, short_cost, long_cost) in [
802+
("gpt-6.1-sol", 0.01512, 1.59),
803+
("gpt-6-astra", 0.0762, 9.45),
804+
] {
805+
for (usage, expected) in [(&short, short_cost), (&long, long_cost)] {
806+
let cost = calculate_codex_model_cost(model, usage, &pricing, CodexSpeed::Auto);
807+
assert!((cost - expected).abs() < 1e-12, "{model}: {cost}");
808+
}
809+
}
810+
}
811+
812+
#[test]
813+
fn keeps_standard_pricing_for_ultrafast_without_a_published_rate() {
814+
let pricing = PricingMap::load_embedded();
815+
let usage = CodexModelUsage {
816+
input_tokens: 1_200,
817+
cached_input_tokens: 200,
818+
output_tokens: 50,
819+
total_tokens: 1_250,
820+
recorded_ultrafast_usage: CodexUsageBucket {
821+
input_tokens: 1_200,
822+
cached_input_tokens: 200,
823+
output_tokens: 50,
824+
..CodexUsageBucket::default()
825+
},
826+
..CodexModelUsage::default()
827+
};
828+
829+
for model in ["gpt-6-sol", "gpt-6.1-terra", "gpt-6-luna"] {
830+
let standard =
831+
calculate_codex_model_cost(model, &usage, &pricing, CodexSpeed::Standard);
832+
let auto = calculate_codex_model_cost(model, &usage, &pricing, CodexSpeed::Auto);
833+
834+
assert!(standard > 0.0, "{model} has embedded pricing");
835+
assert!((auto - standard).abs() < f64::EPSILON, "{model}");
836+
}
837+
}
838+
770839
#[test]
771840
fn prices_rollout_service_tiers_across_loading_and_report_modes() {
772841
for (model, expected_costs) in [

‎rust/adapters/codex/src/parser.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,7 @@ fn codex_service_tier(value: &str) -> Option<CodexServiceTier> {
667667
"default" | "standard" => Some(CodexServiceTier::Standard),
668668
"fast" | "priority" => Some(CodexServiceTier::Fast),
669669
"flex" => Some(CodexServiceTier::Flex),
670+
"ultrafast" => Some(CodexServiceTier::Ultrafast),
670671
_ => None,
671672
}
672673
}
@@ -1376,6 +1377,7 @@ mod tests {
13761377
("priority", Some(CodexServiceTier::Fast)),
13771378
("fast", Some(CodexServiceTier::Fast)),
13781379
("flex", Some(CodexServiceTier::Flex)),
1380+
("ultrafast", Some(CodexServiceTier::Ultrafast)),
13791381
("", None),
13801382
] {
13811383
assert_eq!(

0 commit comments

Comments
 (0)