Skip to content
Closed
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
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 = "cdef6eb9658a43e2"
static let value = "c1352739994bce10"
}
87 changes: 27 additions & 60 deletions Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ enum CostUsagePricing {
let outputCostPerTokenAboveThreshold: Double?
let cacheReadInputCostPerTokenAboveThreshold: Double?
let cacheWriteInputCostPerTokenAboveThreshold: Double?
let priorityInputCostPerToken: Double?
let priorityOutputCostPerToken: Double?
let priorityCacheReadInputCostPerToken: Double?
let priorityCacheWriteInputCostPerToken: Double?

init(
inputCostPerToken: Double,
Expand All @@ -33,11 +29,7 @@ enum CostUsagePricing {
inputCostPerTokenAboveThreshold: Double? = nil,
outputCostPerTokenAboveThreshold: Double? = nil,
cacheReadInputCostPerTokenAboveThreshold: Double? = nil,
cacheWriteInputCostPerTokenAboveThreshold: Double? = nil,
priorityInputCostPerToken: Double? = nil,
priorityOutputCostPerToken: Double? = nil,
priorityCacheReadInputCostPerToken: Double? = nil,
priorityCacheWriteInputCostPerToken: Double? = nil)
cacheWriteInputCostPerTokenAboveThreshold: Double? = nil)
{
self.inputCostPerToken = inputCostPerToken
self.outputCostPerToken = outputCostPerToken
Expand All @@ -49,10 +41,6 @@ enum CostUsagePricing {
self.outputCostPerTokenAboveThreshold = outputCostPerTokenAboveThreshold
self.cacheReadInputCostPerTokenAboveThreshold = cacheReadInputCostPerTokenAboveThreshold
self.cacheWriteInputCostPerTokenAboveThreshold = cacheWriteInputCostPerTokenAboveThreshold
self.priorityInputCostPerToken = priorityInputCostPerToken
self.priorityOutputCostPerToken = priorityOutputCostPerToken
self.priorityCacheReadInputCostPerToken = priorityCacheReadInputCostPerToken
self.priorityCacheWriteInputCostPerToken = priorityCacheWriteInputCostPerToken
}
}

Expand Down Expand Up @@ -156,18 +144,12 @@ enum CostUsagePricing {
thresholdTokens: 272_000,
inputCostPerTokenAboveThreshold: 5e-6,
outputCostPerTokenAboveThreshold: 2.25e-5,
cacheReadInputCostPerTokenAboveThreshold: 5e-7,
priorityInputCostPerToken: 5e-6,
priorityOutputCostPerToken: 3e-5,
priorityCacheReadInputCostPerToken: 5e-7),
cacheReadInputCostPerTokenAboveThreshold: 5e-7),
"gpt-5.4-mini": CodexPricing(
inputCostPerToken: 7.5e-7,
outputCostPerToken: 4.5e-6,
cacheReadInputCostPerToken: 7.5e-8,
displayLabel: nil,
priorityInputCostPerToken: 1.5e-6,
priorityOutputCostPerToken: 9e-6,
priorityCacheReadInputCostPerToken: 1.5e-7),
displayLabel: nil),
"gpt-5.4-nano": CodexPricing(
inputCostPerToken: 2e-7,
outputCostPerToken: 1.25e-6,
Expand All @@ -186,19 +168,16 @@ enum CostUsagePricing {
thresholdTokens: 272_000,
inputCostPerTokenAboveThreshold: 1e-5,
outputCostPerTokenAboveThreshold: 4.5e-5,
cacheReadInputCostPerTokenAboveThreshold: 1e-6,
priorityInputCostPerToken: 1.25e-5,
priorityOutputCostPerToken: 7.5e-5,
priorityCacheReadInputCostPerToken: 1.25e-6),
cacheReadInputCostPerTokenAboveThreshold: 1e-6),
"gpt-5.5-pro": CodexPricing(
inputCostPerToken: 3e-5,
outputCostPerToken: 1.8e-4,
cacheReadInputCostPerToken: nil,
displayLabel: nil),
// GPT-5.6 Sol/Terra/Luna (OpenAI pricing page + model cards).
// Long context: prompts with >272K input tokens are 2x input / 1.5x output for the full
// request. Cache writes: 1.25x uncached input. Priority rates are explicit because support
// and multipliers are provider contracts, not properties that can be inferred from Standard.
// request. Cache writes: 1.25x uncached input. Priority support and multipliers are applied
// separately after Standard pricing resolves from models.dev or this bundled fallback.
"gpt-5.6-sol": CodexPricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 3e-5,
Expand All @@ -209,11 +188,7 @@ enum CostUsagePricing {
inputCostPerTokenAboveThreshold: 1e-5,
outputCostPerTokenAboveThreshold: 4.5e-5,
cacheReadInputCostPerTokenAboveThreshold: 1e-6,
cacheWriteInputCostPerTokenAboveThreshold: 1.25e-5,
priorityInputCostPerToken: 1e-5,
priorityOutputCostPerToken: 6e-5,
priorityCacheReadInputCostPerToken: 1e-6,
priorityCacheWriteInputCostPerToken: 1.25e-5),
cacheWriteInputCostPerTokenAboveThreshold: 1.25e-5),
"gpt-5.6-terra": CodexPricing(
inputCostPerToken: 2.5e-6,
outputCostPerToken: 1.5e-5,
Expand All @@ -224,11 +199,7 @@ enum CostUsagePricing {
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),
cacheWriteInputCostPerTokenAboveThreshold: 6.25e-6),
"gpt-5.6-luna": CodexPricing(
inputCostPerToken: 1e-6,
outputCostPerToken: 6e-6,
Expand All @@ -239,11 +210,7 @@ enum CostUsagePricing {
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),
cacheWriteInputCostPerTokenAboveThreshold: 2.5e-6),
]

static func codexBuiltInPricingFingerprint() -> String {
Expand All @@ -262,10 +229,7 @@ enum CostUsagePricing {
self.optionalPricingFingerprint(pricing.outputCostPerTokenAboveThreshold),
self.optionalPricingFingerprint(pricing.cacheReadInputCostPerTokenAboveThreshold),
self.optionalPricingFingerprint(pricing.cacheWriteInputCostPerTokenAboveThreshold),
self.optionalPricingFingerprint(pricing.priorityInputCostPerToken),
self.optionalPricingFingerprint(pricing.priorityOutputCostPerToken),
self.optionalPricingFingerprint(pricing.priorityCacheReadInputCostPerToken),
self.optionalPricingFingerprint(pricing.priorityCacheWriteInputCostPerToken),
self.optionalPricingFingerprint(self.codexPriorityMultiplier(model: model)),
].joined(separator: "|"))
}
return parts.joined(separator: "\n")
Expand Down Expand Up @@ -590,31 +554,34 @@ enum CostUsagePricing {
inputTokens: Int,
cachedInputTokens: Int = 0,
cacheWriteInputTokens: Int = 0,
outputTokens: Int) -> Double?
outputTokens: Int,
modelsDevCatalog: ModelsDevCatalog? = nil,
modelsDevCacheRoot: URL? = nil) -> Double?
{
let key = self.normalizeCodexModel(model)
guard let pricing = self.codex[key],
let priorityInputCostPerToken = pricing.priorityInputCostPerToken,
let priorityOutputCostPerToken = pricing.priorityOutputCostPerToken
else { return nil }
guard let multiplier = self.codexPriorityMultiplier(model: model) else { return nil }
// OpenAI does not support Priority processing for long-context requests. Do not combine
// the independent Standard long-context and Priority short-context rate tables.
if max(0, inputTokens) > self.codexPriorityInputTokenLimit {
return nil
}

let priorityPricing = CodexPricing(
inputCostPerToken: priorityInputCostPerToken,
outputCostPerToken: priorityOutputCostPerToken,
cacheReadInputCostPerToken: pricing.priorityCacheReadInputCostPerToken,
displayLabel: nil,
cacheWriteInputCostPerToken: pricing.priorityCacheWriteInputCostPerToken)
return self.codexCostUSD(
pricing: priorityPricing,
model: model,
inputTokens: inputTokens,
cachedInputTokens: cachedInputTokens,
outputTokens: outputTokens,
cacheWriteInputTokens: cacheWriteInputTokens,
outputTokens: outputTokens)
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot)
.map { $0 * multiplier }
}

static func codexPriorityMultiplier(model: String) -> Double? {
switch self.normalizeCodexModel(model) {
case "gpt-5.4", "gpt-5.4-mini": 2
case "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna": 2.5
default: nil
}
}

private static func codexCostUSD(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ extension CostUsageScanner {
model: pricedModel,
inputTokens: row.input,
cachedInputTokens: row.cached,
outputTokens: row.output)
outputTokens: row.output,
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot)
else { continue }
total += max(priorityCost - baseCost, 0)
seen = true
Expand Down Expand Up @@ -252,7 +254,9 @@ extension CostUsageScanner {
model: pricedModel,
inputTokens: row.input,
cachedInputTokens: row.cached,
outputTokens: row.output)
outputTokens: row.output,
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot)
{
breakdown.priorityCostUSD += max(priorityCost, baseCost ?? priorityCost)
breakdown.sawPriorityCost = true
Expand Down Expand Up @@ -542,7 +546,9 @@ extension CostUsageScanner {
model: pricedModel,
inputTokens: row.input,
cachedInputTokens: row.cached,
outputTokens: row.output)
outputTokens: row.output,
modelsDevCatalog: modelsDevCatalog,
modelsDevCacheRoot: modelsDevCacheRoot)
{
priorityCostNanos[row.day, default: [:]][row.model, default: 0] += Int64(
(max(priorityCost, baseCost ?? priorityCost) * Self.costScale).rounded())
Expand Down
57 changes: 45 additions & 12 deletions Tests/CodexBarTests/CostUsagePricingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,14 +301,17 @@ struct CostUsagePricingTests {
cachedInputTokens: 20,
outputTokens: 10)

// Priority is 2x short-context rates (Sol input $10/1M, etc.).
#expect(sol == (80.0 * 1e-5) + (20.0 * 1e-6) + (10.0 * 6e-5))
#expect(terra == (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5))
#expect(luna == (80.0 * 2e-6) + (20.0 * 2e-7) + (10.0 * 1.2e-5))
// GPT-5.6 Priority is 2.5x short-context Standard pricing.
let expectedSol = (80.0 * 1.25e-5) + (20.0 * 1.25e-6) + (10.0 * 7.5e-5)
let expectedTerra = (80.0 * 6.25e-6) + (20.0 * 6.25e-7) + (10.0 * 3.75e-5)
let expectedLuna = (80.0 * 2.5e-6) + (20.0 * 2.5e-7) + (10.0 * 1.5e-5)
#expect(abs((sol ?? 0) - expectedSol) < 1e-12)
#expect(abs((terra ?? 0) - expectedTerra) < 1e-12)
#expect(abs((luna ?? 0) - expectedLuna) < 1e-12)
}

@Test
func `codex priority cost uses explicit cache write rates`() {
func `codex priority cost multiplies standard cache write rates`() {
let sol = CostUsagePricing.codexPriorityCostUSD(
model: "gpt-5.6-sol",
inputTokens: 100,
Expand All @@ -334,13 +337,43 @@ struct CostUsagePricingTests {
cacheWriteInputTokens: 20,
outputTokens: 5)

#expect(sol == (70.0 * 1e-5) + (10.0 * 1e-6) + (20.0 * 1.25e-5) + (5.0 * 6e-5))
#expect(terra == (70.0 * 5e-6) + (10.0 * 5e-7) + (20.0 * 6.25e-6) + (5.0 * 3e-5))
#expect(luna == (70.0 * 2e-6) + (10.0 * 2e-7) + (20.0 * 2.5e-6) + (5.0 * 1.2e-5))
// A model without an explicit Priority cache-write price keeps the legacy input-rate fold.
#expect(
modelWithoutCacheWriteSupport ==
(90.0 * 1.25e-5) + (10.0 * 1.25e-6) + (5.0 * 7.5e-5))
let expectedSol = ((70.0 * 5e-6) + (10.0 * 5e-7) + (20.0 * 6.25e-6) + (5.0 * 3e-5)) * 2.5
let expectedTerra = ((70.0 * 2.5e-6) + (10.0 * 2.5e-7) + (20.0 * 3.125e-6) + (5.0 * 1.5e-5)) * 2.5
let expectedLuna = ((70.0 * 1e-6) + (10.0 * 1e-7) + (20.0 * 1.25e-6) + (5.0 * 6e-6)) * 2.5
#expect(abs((sol ?? 0) - expectedSol) < 1e-12)
#expect(abs((terra ?? 0) - expectedTerra) < 1e-12)
#expect(abs((luna ?? 0) - expectedLuna) < 1e-12)
// A legacy model without a Standard cache-write price folds writes into uncached input.
let expectedLegacy = (90.0 * 1.25e-5) + (10.0 * 1.25e-6) + (5.0 * 7.5e-5)
#expect(abs((modelWithoutCacheWriteSupport ?? 0) - expectedLegacy) < 1e-12)
}

@Test
func `codex priority cost multiplies models dev standard pricing`() throws {
let root = try Self.seedModelsDevCache("""
{
"openai": {
"id": "openai",
"models": {
"gpt-5.6-sol": {
"id": "gpt-5.6-sol",
"cost": { "input": 7, "output": 31, "cache_read": 1, "cache_write": 8 }
}
}
}
}
""")

let cost = CostUsagePricing.codexPriorityCostUSD(
model: "gpt-5.6-sol",
inputTokens: 100,
cachedInputTokens: 10,
cacheWriteInputTokens: 20,
outputTokens: 5,
modelsDevCacheRoot: root)

// Standard is $0.000815 for this worked token mix; GPT-5.6 Priority is 2.5x.
#expect(cost == 0.0020375)
}

@Test
Expand Down
51 changes: 51 additions & 0 deletions Tests/CodexBarTests/CostUsageScannerPriorityTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,57 @@ struct CostUsageScannerPriorityTests {
#expect(breakdown.priorityTokens == 110)
}

@Test
func `codex daily report applies priority multiplier to models dev pricing`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }

let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let catalog = try JSONDecoder().decode(ModelsDevCatalog.self, from: Data("""
{
"openai": {
"id": "openai",
"models": {
"gpt-5.5": {
"id": "gpt-5.5",
"cost": { "input": 7, "output": 31, "cache_read": 1 }
}
}
}
}
""".utf8))
#expect(ModelsDevCache.save(catalog: catalog, fetchedAt: day, cacheRoot: env.cacheRoot))

let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))

let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1)

var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)

// models.dev Standard is $0.00089 for this token mix; GPT-5.5 Priority is 2.5x.
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.priorityCostUSD ?? 0) - 0.002225) < 1e-12)
}

@Test
func `codex daily report keeps cached priority surcharge without live sqlite metadata`() throws {
let env = try CostUsageTestEnvironment()
Expand Down