Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/PROVIDERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ The shared refresh interval controls automatic provider polling. `0` / Manual di

Custom pricing overlays are exact-match overrides used only where the local spend contract has matching provider/model token evidence. Explicit zero rates mean free; omitted rate fields stay unknown. The Usage & Spend surface keeps provenance/coverage visible, preserves cost-only model rows when token coverage is partial, and can Copy JSON or save the same JSON contract through the native file picker.

### Codex model pricing

Codex usage is priced from the bundled OpenAI rate table first and the models.dev catalog second. OpenAI's [Daybreak aliases](https://developers.openai.com/api/docs/pricing) resolve like the unsuffixed `gpt-5.6` alias: `gpt-daybreak-blue-latest` prices as `gpt-5.6-sol` and `gpt-daybreak-red-latest` as `gpt-5.6-cyber`. Recorded model names stay unchanged. `gpt-5.6-cyber` and `gpt-5.5-cyber` use the published Cyber rates of $12.50 input, $1.25 cached input and $75 output per 1M tokens. GPT-5.4, GPT-5.5 and GPT-5.6 bill the whole request at their long-context rates once input passes 272K tokens.

Dated Codex usage keeps the prior GPT-5.6 Sol rates ($5 input, $30 output per 1M tokens) before **2026-08-21**, the repricing date in the [OpenAI changelog](https://developers.openai.com/api/docs/changelog). Current and undated usage use the current $4/$20 rates. Terra and Luna keep their separate 2026-07-30 cutoff. Windows compares calendar days rather than event instants: the Codex cost scanners and the workspace index use their local day keys, and Pi session rows use the UTC date of their timestamp. Custom pricing overlays keep precedence.

### OpenCode, Codex quota, and local cost boundaries

OpenCode-held OpenAI/Codex OAuth can be reused for **remote Codex account quota** only when the Codex provider's `External OAuth sources` setting is explicitly enabled. Native Codex credentials still take precedence, an explicit `CODEX_HOME` stays isolated, and external credentials remain read-only. This does **not** import ordinary OpenCode sessions into Codex token or spend totals. OpenCode Go's local SQLite reader remains scoped to its own `opencode-go` assistant records; OpenAI API-platform usage is a separate provider.
Expand Down
17 changes: 13 additions & 4 deletions rust/src/codex_costs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,12 +653,21 @@ mod tests {

#[test]
fn test_codex_pricing_uses_gpt55_standard_short_context_rates() {
let cost = codex_cost_usd("gpt-5.5", 1_000_000, 400_000, 1_000_000);
let cost = codex_cost_usd("gpt-5.5", 200_000, 80_000, 100_000);

// GPT-5.5 standard short-context pricing:
// 600k non-cached input at $5/M, 400k cached input at $0.50/M,
// and 1M output at $30/M.
assert!((cost - 33.20).abs() < 0.01);
// 120k non-cached input at $5/M, 80k cached input at $0.50/M,
// and 100k output at $30/M.
assert!((cost - 3.64).abs() < 1e-9);
}

#[test]
fn test_codex_pricing_bills_whole_gpt55_request_at_long_context_rates() {
let cost = codex_cost_usd("gpt-5.5", 1_000_000, 400_000, 1_000_000);

// Above 272K input the whole request bills at $10/M input,
// $1/M cached input and $45/M output.
assert!((cost - 51.40).abs() < 1e-9);
}

#[test]
Expand Down
27 changes: 25 additions & 2 deletions rust/src/codex_workspaces/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ fn index_one_file(path: &Path, range: &CostUsageDayRange) -> Option<ParsedFile>
day_entry.1 = day_entry.1.saturating_add(cached);
day_entry.2 = day_entry.2.saturating_add(output);

match CostUsagePricing::codex_cost_usd(&model, input, cached, output) {
match codex_cost_on_day(&record.day_key, &model, input, cached, output) {
Some(usd) => cost.known_usd += usd,
None => cost.unknown_tokens = cost.unknown_tokens.saturating_add(tokens),
}
Expand Down Expand Up @@ -526,14 +526,23 @@ fn merge_daily(
let tokens = input.saturating_add(*output);
acc.total_tokens = acc.total_tokens.saturating_add(tokens);
acc.cached_input_tokens = acc.cached_input_tokens.saturating_add(*cached);
match CostUsagePricing::codex_cost_usd(model, *input, *cached, *output) {
match codex_cost_on_day(day, model, *input, *cached, *output) {
Some(usd) => acc.known_usd += usd,
None => acc.unknown_tokens = acc.unknown_tokens.saturating_add(tokens),
}
}
}
}

/// Prices at the usage day's rates, like the cost scanners, so usage from
/// before a model's repricing keeps the rates it was billed at.
fn codex_cost_on_day(day: &str, model: &str, input: u64, cached: u64, output: u64) -> Option<f64> {
match CostUsageDayRange::parse_day_key(day) {
Some(day) => CostUsagePricing::codex_cost_usd_at_date(model, input, cached, output, day),
None => CostUsagePricing::codex_cost_usd(model, input, cached, output),
}
}

fn list_session_files(root: &Path, range: &CostUsageDayRange) -> Vec<PathBuf> {
if !root.exists() {
return Vec::new();
Expand Down Expand Up @@ -875,6 +884,20 @@ mod tests {
);
}

#[test]
fn daily_costs_use_each_day_rates() {
let sol = |day: &str| {
let models = HashMap::from([("gpt-5.6-sol".to_string(), (100, 10, 5))]);
let mut daily = HashMap::new();
merge_daily(&mut daily, &HashMap::from([(day.to_string(), models)]));
daily[day].known_usd
};
let historical = 90.0 * 5e-6 + 10.0 * 5e-7 + 5.0 * 3e-5;
let current = 90.0 * 4e-6 + 10.0 * 4e-7 + 5.0 * 2e-5;
assert!((sol("2026-08-20") - historical).abs() < 1e-12);
assert!((sol("2026-08-21") - current).abs() < 1e-12);
}

#[test]
fn foreign_user_version_is_rejected() {
let tmp = TempDir::new().unwrap();
Expand Down
Loading