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
18 changes: 15 additions & 3 deletions Sources/CodexBarCore/CostUsageModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ enum CostUsageDateParser {
{
return d
}
if let d = self.dateFormatter(key: self.dayFormatterKey, format: "yyyy-MM-dd").date(from: trimmed) {
if let d = self.parseDay(trimmed, timeZone: .current) {
return d
}
if let d = self.dateFormatter(key: self.monthDayYearFormatterKey, format: "MMM d, yyyy")
Expand All @@ -1004,6 +1004,15 @@ enum CostUsageDateParser {
return nil
}

static func parseDay(_ text: String?, timeZone: TimeZone) -> Date? {
guard let text, !text.isEmpty else { return nil }
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
return self.dateFormatter(
key: self.dayFormatterKey,
format: "yyyy-MM-dd",
timeZone: timeZone).date(from: trimmed)
}

static func parseMonth(_ text: String?) -> Date? {
guard let text, !text.isEmpty else { return nil }
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
Expand Down Expand Up @@ -1035,9 +1044,12 @@ enum CostUsageDateParser {
return formatter
}

private static func dateFormatter(key: String, format: String) -> DateFormatter {
private static func dateFormatter(
key: String,
format: String,
timeZone: TimeZone = .current) -> DateFormatter
{
let threadDict = Thread.current.threadDictionary
let timeZone = TimeZone.current
let cacheKey = "\(key).\(timeZone.identifier)"
if let cached = threadDict[cacheKey] as? DateFormatter {
return cached
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand.

enum CodexParserHash {
static let value = "3aa49b47f4b78e13"
static let value = "5f28546cd6929ff0"
}
1 change: 1 addition & 0 deletions Sources/CodexBarCore/PiSessionCostScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@ enum PiSessionCostScanner {
cachedInputTokens: usage.cacheReadTokens,
outputTokens: usage.outputTokens,
cacheWriteInputTokens: usage.cacheWriteTokens,
pricingDate: pricingDate,
modelsDevCatalog: pricingContext?.catalog,
modelsDevCacheRoot: pricingContext?.cacheRoot)
case .claude:
Expand Down
167 changes: 119 additions & 48 deletions Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import Foundation

private struct PricingHistoryPeriod<Pricing: Sendable>: Sendable {
let before: Date
let models: [String: Pricing]
}

private func effectivePricing<Pricing: Sendable>(
for model: String,
on date: Date?,
history: [PricingHistoryPeriod<Pricing>],
current: Pricing? = nil) -> Pricing?
{
guard let date, history.contains(where: { $0.models[model] != nil }) else { return nil }
for period in history where date < period.before {
if let pricing = period.models[model] {
return pricing
}
}
return current
}

enum CostUsagePricing {
private static let codexPriorityInputTokenLimit = 272_000
static let codexUnattributedModel = "unknown"

struct CodexPricing {
struct CodexPricing: Sendable {
let inputCostPerToken: Double
let outputCostPerToken: Double
let cacheReadInputCostPerToken: Double?
Expand Down Expand Up @@ -56,7 +76,7 @@ enum CostUsagePricing {
}
}

struct ClaudePricing {
struct ClaudePricing: Sendable {
let inputCostPerToken: Double
let outputCostPerToken: Double
let cacheCreationInputCostPerToken: Double
Expand Down Expand Up @@ -215,31 +235,31 @@ enum CostUsagePricing {
priorityCacheReadInputCostPerToken: 1e-6,
priorityCacheWriteInputCostPerToken: 1.25e-5),
"gpt-5.6-terra": CodexPricing(
inputCostPerToken: 2.5e-6,
outputCostPerToken: 1.5e-5,
cacheReadInputCostPerToken: 2.5e-7,
inputCostPerToken: 2e-6,
outputCostPerToken: 1.2e-5,
cacheReadInputCostPerToken: 2e-7,
Comment on lines +238 to +240

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve old rates before the July 30 cutover

For bundled-fallback pricing of Terra or Luna usage dated before 2026-07-30, these replacements retroactively apply the new rates. codexCostUSD accepts no pricing date, and callers such as codexRowsCostUSD and codexRowCostBreakdown do not pass row.day; the changed pricing fingerprint also forces cached history to be rescanned. Consequently, pre-cutover Terra costs are reduced by 20% and Luna costs by 80%, corrupting historical totals. Keep the old and new tuples behind an effective-date cutoff and price each row using its recorded date.

Useful? React with 👍 / 👎.

displayLabel: nil,
cacheWriteInputCostPerToken: 3.125e-6,
cacheWriteInputCostPerToken: 2.5e-6,
thresholdTokens: 272_000,
inputCostPerTokenAboveThreshold: 5e-6,
outputCostPerTokenAboveThreshold: 2.25e-5,
cacheReadInputCostPerTokenAboveThreshold: 5e-7,
cacheWriteInputCostPerTokenAboveThreshold: 6.25e-6,
inputCostPerTokenAboveThreshold: 4e-6,
outputCostPerTokenAboveThreshold: 1.8e-5,
cacheReadInputCostPerTokenAboveThreshold: 4e-7,
cacheWriteInputCostPerTokenAboveThreshold: 5e-6,
priorityInputCostPerToken: 5e-6,
priorityOutputCostPerToken: 3e-5,
priorityCacheReadInputCostPerToken: 5e-7,
priorityCacheWriteInputCostPerToken: 6.25e-6),
"gpt-5.6-luna": CodexPricing(
inputCostPerToken: 1e-6,
outputCostPerToken: 6e-6,
cacheReadInputCostPerToken: 1e-7,
inputCostPerToken: 2e-7,
outputCostPerToken: 1.2e-6,
cacheReadInputCostPerToken: 2e-8,
displayLabel: nil,
cacheWriteInputCostPerToken: 1.25e-6,
cacheWriteInputCostPerToken: 2.5e-7,
thresholdTokens: 272_000,
inputCostPerTokenAboveThreshold: 2e-6,
outputCostPerTokenAboveThreshold: 9e-6,
cacheReadInputCostPerTokenAboveThreshold: 2e-7,
cacheWriteInputCostPerTokenAboveThreshold: 2.5e-6,
inputCostPerTokenAboveThreshold: 4e-7,
outputCostPerTokenAboveThreshold: 1.8e-6,
cacheReadInputCostPerTokenAboveThreshold: 4e-8,
cacheWriteInputCostPerTokenAboveThreshold: 5e-7,
priorityInputCostPerToken: 2e-6,
priorityOutputCostPerToken: 1.2e-5,
priorityCacheReadInputCostPerToken: 2e-7,
Expand Down Expand Up @@ -429,30 +449,6 @@ enum CostUsagePricing {
cacheReadInputCostPerTokenAboveThreshold: 6e-7),
]

private static let claudeFullContextStandardPricingCutoff = Date(timeIntervalSince1970: 1_773_360_000)
private static let claudeHistoricalLongContext: [String: ClaudePricing] = [
"claude-opus-4-6": ClaudePricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 2.5e-5,
cacheCreationInputCostPerToken: 6.25e-6,
cacheReadInputCostPerToken: 5e-7,
thresholdTokens: 200_000,
inputCostPerTokenAboveThreshold: 1e-5,
outputCostPerTokenAboveThreshold: 3.75e-5,
cacheCreationInputCostPerTokenAboveThreshold: 1.25e-5,
cacheReadInputCostPerTokenAboveThreshold: 1e-6),
"claude-sonnet-4-6": ClaudePricing(
inputCostPerToken: 3e-6,
outputCostPerToken: 1.5e-5,
cacheCreationInputCostPerToken: 3.75e-6,
cacheReadInputCostPerToken: 3e-7,
thresholdTokens: 200_000,
inputCostPerTokenAboveThreshold: 6e-6,
outputCostPerTokenAboveThreshold: 2.25e-5,
cacheCreationInputCostPerTokenAboveThreshold: 7.5e-6,
cacheReadInputCostPerTokenAboveThreshold: 6e-7),
]

private static let codexModelsDevProviderID = "openai"
private static let claudeModelsDevProviderID = "anthropic"

Expand Down Expand Up @@ -524,11 +520,24 @@ enum CostUsagePricing {
cachedInputTokens: Int,
outputTokens: Int,
cacheWriteInputTokens: Int = 0,
pricingDate: Date? = nil,
modelsDevCatalog: ModelsDevCatalog? = nil,
modelsDevCacheRoot: URL? = nil) -> Double?
{
let key = self.normalizeCodexModel(model)
guard key != self.codexUnattributedModel else { return nil }
if let historicalPricing = effectivePricing(
for: key,
on: pricingDate,
history: self.codexPricingHistory)
{
return self.codexCostUSD(
pricing: historicalPricing,
inputTokens: inputTokens,
cachedInputTokens: cachedInputTokens,
cacheWriteInputTokens: cacheWriteInputTokens,
outputTokens: outputTokens)
}
let modelsDevLookup = self.modelsDevLookup(
providerID: self.codexModelsDevProviderID,
model: model,
Expand Down Expand Up @@ -712,14 +721,14 @@ enum CostUsagePricing {
cacheCreation1h: cacheCreationInputTokens1h,
output: outputTokens)
let key = self.normalizeClaudeModel(model)
if let pricingDate,
let historicalPricing = self.claudeHistoricalLongContext[key],
let currentPricing = self.claude[key]
if let datedPricing = effectivePricing(
for: key,
on: pricingDate,
history: self.claudePricingHistory,
current: self.claude[key])
{
return self.claudeCostUSD(
pricing: pricingDate < self.claudeFullContextStandardPricingCutoff
? historicalPricing
: currentPricing,
pricing: datedPricing,
tokens: tokens)
}
if let lookup = self.modelsDevLookup(
Expand Down Expand Up @@ -809,3 +818,65 @@ enum CostUsagePricing {
cacheRoot: cacheRoot)
}
}

extension CostUsagePricing {
private static let claudePricingHistory = [PricingHistoryPeriod(
before: Date(timeIntervalSince1970: 1_773_360_000),
models: [
"claude-opus-4-6": ClaudePricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 2.5e-5,
cacheCreationInputCostPerToken: 6.25e-6,
cacheReadInputCostPerToken: 5e-7,
thresholdTokens: 200_000,
inputCostPerTokenAboveThreshold: 1e-5,
outputCostPerTokenAboveThreshold: 3.75e-5,
cacheCreationInputCostPerTokenAboveThreshold: 1.25e-5,
cacheReadInputCostPerTokenAboveThreshold: 1e-6),
"claude-sonnet-4-6": ClaudePricing(
inputCostPerToken: 3e-6,
outputCostPerToken: 1.5e-5,
cacheCreationInputCostPerToken: 3.75e-6,
cacheReadInputCostPerToken: 3e-7,
thresholdTokens: 200_000,
inputCostPerTokenAboveThreshold: 6e-6,
outputCostPerTokenAboveThreshold: 2.25e-5,
cacheCreationInputCostPerTokenAboveThreshold: 7.5e-6,
cacheReadInputCostPerTokenAboveThreshold: 6e-7),
])]

private static let codexPricingHistory = [PricingHistoryPeriod(
before: Date(timeIntervalSince1970: 1_785_369_600),
models: [
"gpt-5.6-terra": CodexPricing(
inputCostPerToken: 2.5e-6,
outputCostPerToken: 1.5e-5,
cacheReadInputCostPerToken: 2.5e-7,
displayLabel: nil,
cacheWriteInputCostPerToken: 3.125e-6,
thresholdTokens: 272_000,
inputCostPerTokenAboveThreshold: 5e-6,
outputCostPerTokenAboveThreshold: 2.25e-5,
cacheReadInputCostPerTokenAboveThreshold: 5e-7,
cacheWriteInputCostPerTokenAboveThreshold: 6.25e-6,
priorityInputCostPerToken: 5e-6,
priorityOutputCostPerToken: 3e-5,
priorityCacheReadInputCostPerToken: 5e-7,
priorityCacheWriteInputCostPerToken: 6.25e-6),
"gpt-5.6-luna": CodexPricing(
inputCostPerToken: 1e-6,
outputCostPerToken: 6e-6,
cacheReadInputCostPerToken: 1e-7,
displayLabel: nil,
cacheWriteInputCostPerToken: 1.25e-6,
thresholdTokens: 272_000,
inputCostPerTokenAboveThreshold: 2e-6,
outputCostPerTokenAboveThreshold: 9e-6,
cacheReadInputCostPerTokenAboveThreshold: 2e-7,
cacheWriteInputCostPerTokenAboveThreshold: 2.5e-6,
priorityInputCostPerToken: 2e-6,
priorityOutputCostPerToken: 1.2e-5,
priorityCacheReadInputCostPerToken: 2e-7,
priorityCacheWriteInputCostPerToken: 2.5e-6),
])]
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ extension CostUsageScanner {
inputTokens: row.input,
cachedInputTokens: row.cached,
outputTokens: row.output,
pricingDate: CostUsageDateParser.parseDay(row.day, timeZone: .gmt),
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot)
else { continue }
Expand All @@ -164,6 +165,7 @@ extension CostUsageScanner {
inputTokens: row.input,
cachedInputTokens: row.cached,
outputTokens: row.output,
pricingDate: CostUsageDateParser.parseDay(row.day, timeZone: .gmt),
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot),
let priorityCost = CostUsagePricing.codexPriorityCostUSD(
Expand Down Expand Up @@ -250,6 +252,7 @@ extension CostUsageScanner {
inputTokens: row.input,
cachedInputTokens: row.cached,
outputTokens: row.output,
pricingDate: CostUsageDateParser.parseDay(row.day, timeZone: .gmt),
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot)
if isPriority, let priorityCost = CostUsagePricing.codexPriorityCostUSD(
Expand Down Expand Up @@ -461,6 +464,7 @@ extension CostUsageScanner {
inputTokens: row.input,
cachedInputTokens: row.cached,
outputTokens: row.output,
pricingDate: CostUsageDateParser.parseDay(row.day, timeZone: .gmt),
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot)
let exactCost: Double? = if priorityMetadata != nil,
Expand Down Expand Up @@ -604,6 +608,7 @@ extension CostUsageScanner {
inputTokens: row.input,
cachedInputTokens: row.cached,
outputTokens: row.output,
pricingDate: CostUsageDateParser.parseDay(row.day, timeZone: .gmt),
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot)

Expand Down Expand Up @@ -1565,6 +1570,7 @@ extension CostUsageScanner {
inputTokens: input,
cachedInputTokens: cached,
outputTokens: output,
pricingDate: CostUsageDateParser.parseDay(day, timeZone: .gmt),
modelsDevCatalog: catalogResolver.load(modelsDevCatalogLoader),
modelsDevCacheRoot: modelsDevCacheRoot)
if splitTotalCost == nil,
Expand Down
Loading