From bbcdafb73f40fc31e60c51aa885634866cefe047 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 15:51:32 -0400 Subject: [PATCH 1/3] Centralize Codex priority pricing --- .../Vendored/CostUsage/CostUsagePricing.swift | 87 ++++++------------- .../CostUsageScanner+CacheHelpers.swift | 12 ++- .../CodexBarTests/CostUsagePricingTests.swift | 57 +++++++++--- .../CostUsageScannerPriorityTests.swift | 51 +++++++++++ 4 files changed, 132 insertions(+), 75 deletions(-) diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift index 3727bf2ce2..5ac243282e 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift @@ -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, @@ -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 @@ -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 } } @@ -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, @@ -186,10 +168,7 @@ 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, @@ -197,8 +176,8 @@ enum CostUsagePricing { 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, @@ -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, @@ -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, @@ -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 { @@ -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") @@ -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( diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift index bb8736d9cd..5cfbf0585b 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift @@ -170,7 +170,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 @@ -256,7 +258,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 @@ -621,7 +625,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()) diff --git a/Tests/CodexBarTests/CostUsagePricingTests.swift b/Tests/CodexBarTests/CostUsagePricingTests.swift index 7c5340418c..9adbb68211 100644 --- a/Tests/CodexBarTests/CostUsagePricingTests.swift +++ b/Tests/CodexBarTests/CostUsagePricingTests.swift @@ -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, @@ -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 diff --git a/Tests/CodexBarTests/CostUsageScannerPriorityTests.swift b/Tests/CodexBarTests/CostUsageScannerPriorityTests.swift index 2db80641cd..9eecdea746 100644 --- a/Tests/CodexBarTests/CostUsageScannerPriorityTests.swift +++ b/Tests/CodexBarTests/CostUsageScannerPriorityTests.swift @@ -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() From 4e1b59695fbe23560a0a2fad602b2b4061fcbc06 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 3 Aug 2026 15:39:10 -0700 Subject: [PATCH 2/3] fix: define Codex Fast USD pricing --- CHANGELOG.md | 1 + .../Generated/CodexParserHash.generated.swift | 2 +- .../Vendored/CostUsage/CostUsagePricing.swift | 55 +++++++++------- .../CostUsageScanner+CacheHelpers.swift | 10 ++- .../CodexBarTests/CostUsagePricingTests.swift | 66 ++++++++++--------- .../CostUsageScannerPriorityTests.swift | 18 ++--- 6 files changed, 81 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0cf7d0bb0..ef86b86601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Changed - Menu: move each usage window's used percentage and reset time into its title row, with all pace detail on one line (#2182). Thanks @jack24254029! +- Codex: define Fast cost as estimated API Fast USD, resolve it models.dev-first with model-specific API ratios, and refresh GPT-5.6 Terra/Luna fallback rates (refs #2175). Thanks @iam-brain! ### Fixed - Sync: propagate provider configuration edits made by the CLI or directly in `config.json` to the iCloud fleet without echoing remotely applied writes. diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index e8904b9067..7401a1244a 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "a72389ecaa16bc9a" + static let value = "843ca061c36bbea1" } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift index 5ac243282e..1bd0c5e624 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift @@ -176,7 +176,7 @@ enum CostUsagePricing { 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 support and multipliers are applied + // request. Cache writes: 1.25x uncached input. API Fast 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, @@ -190,31 +190,34 @@ enum CostUsagePricing { cacheReadInputCostPerTokenAboveThreshold: 1e-6, cacheWriteInputCostPerTokenAboveThreshold: 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, 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), "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), ] static func codexBuiltInPricingFingerprint() -> String { - var parts = ["priorityInputTokenLimit=\(self.codexPriorityInputTokenLimit)"] + var parts = [ + "priorityInputTokenLimit=\(self.codexPriorityInputTokenLimit)", + "fastPricingDefinition=api-fast-usd-v1", + ] for model in self.codex.keys.sorted() { guard let pricing = self.codex[model] else { continue } parts.append([ @@ -229,7 +232,7 @@ enum CostUsagePricing { self.optionalPricingFingerprint(pricing.outputCostPerTokenAboveThreshold), self.optionalPricingFingerprint(pricing.cacheReadInputCostPerTokenAboveThreshold), self.optionalPricingFingerprint(pricing.cacheWriteInputCostPerTokenAboveThreshold), - self.optionalPricingFingerprint(self.codexPriorityMultiplier(model: model)), + self.optionalPricingFingerprint(self.codexAPIFastMultiplier(model: model)), ].joined(separator: "|")) } return parts.joined(separator: "\n") @@ -558,9 +561,9 @@ enum CostUsagePricing { modelsDevCatalog: ModelsDevCatalog? = nil, modelsDevCacheRoot: URL? = nil) -> Double? { - 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. + guard let multiplier = self.codexAPIFastMultiplier(model: model) else { return nil } + // OpenAI does not support API Fast processing for long-context requests. Do not combine + // the independent Standard long-context and Fast short-context rate tables. if max(0, inputTokens) > self.codexPriorityInputTokenLimit { return nil } @@ -576,10 +579,12 @@ enum CostUsagePricing { .map { $0 * multiplier } } - static func codexPriorityMultiplier(model: String) -> Double? { + /// Current public API Fast rates normalized against Standard API pricing. These are deliberately + /// distinct from ChatGPT/Codex Fast credit multipliers, which do not represent a USD charge. + static func codexAPIFastMultiplier(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 + case "gpt-5.4", "gpt-5.4-mini", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna": 2 + case "gpt-5.5": 2.5 default: nil } } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift index 5cfbf0585b..3820a109f8 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift @@ -185,11 +185,7 @@ extension CostUsageScanner { priorityMetadata: CodexPriorityTurnMetadata) -> String { guard let model = priorityMetadata.model, - CostUsagePricing.codexPriorityCostUSD( - model: model, - inputTokens: row.input, - cachedInputTokens: row.cached, - outputTokens: row.output) != nil + CostUsagePricing.codexAPIFastMultiplier(model: model) != nil else { return row.model } return model } @@ -482,7 +478,9 @@ extension CostUsageScanner { model: pricedModel, inputTokens: row.input, cachedInputTokens: row.cached, - outputTokens: row.output) + outputTokens: row.output, + modelsDevCatalog: modelsDevCatalog, + modelsDevCacheRoot: modelsDevCacheRoot) { max(priorityCost, baseCost ?? priorityCost) } else { diff --git a/Tests/CodexBarTests/CostUsagePricingTests.swift b/Tests/CodexBarTests/CostUsagePricingTests.swift index 9adbb68211..3865be8d05 100644 --- a/Tests/CodexBarTests/CostUsagePricingTests.swift +++ b/Tests/CodexBarTests/CostUsagePricingTests.swift @@ -133,11 +133,11 @@ struct CostUsagePricingTests { outputTokens: 5, modelsDevCacheRoot: root) - // Rates per token: Sol $5/$30 per 1M, Terra $2.50/$15, Luna $1/$6; + // Rates per token: Sol $5/$30 per 1M, Terra $2/$12, Luna $0.20/$1.20; // cache read is 10% of input. Non-cached input is 90 tokens. #expect(sol == (90.0 * 5e-6) + (10.0 * 5e-7) + (5.0 * 3e-5)) - #expect(terra == (90.0 * 2.5e-6) + (10.0 * 2.5e-7) + (5.0 * 1.5e-5)) - #expect(luna == (90.0 * 1e-6) + (10.0 * 1e-7) + (5.0 * 6e-6)) + #expect(terra == (90.0 * 2e-6) + (10.0 * 2e-7) + (5.0 * 1.2e-5)) + #expect(luna == (90.0 * 2e-7) + (10.0 * 2e-8) + (5.0 * 1.2e-6)) // Unsuffixed gpt-5.6 alias routes to Sol. #expect(alias == sol) } @@ -235,6 +235,13 @@ struct CostUsagePricingTests { #expect(withoutKey != withEmptyKey) } + @Test + func `codex pricing fingerprint records API fast USD definition`() { + let fingerprint = CostUsagePricing.codexBuiltInPricingFingerprint() + + #expect(fingerprint.contains("fastPricingDefinition=api-fast-usd-v1")) + } + @Test func `codex cost applies gpt56 long context rates`() throws { let root = try Self.cacheRoot() @@ -263,8 +270,8 @@ struct CostUsagePricingTests { // Long-context (>272K) rates apply to the entire request. Total input contains 10 cached, // 20 cache-write, and 271,971 ordinary input tokens. #expect(sol == (271_971.0 * 1e-5) + (10.0 * 1e-6) + (20.0 * 1.25e-5) + (10.0 * 4.5e-5)) - #expect(terra == (271_971.0 * 5e-6) + (10.0 * 5e-7) + (20.0 * 6.25e-6) + (10.0 * 2.25e-5)) - #expect(luna == (271_971.0 * 2e-6) + (10.0 * 2e-7) + (20.0 * 2.5e-6) + (10.0 * 9e-6)) + #expect(terra == (271_971.0 * 4e-6) + (10.0 * 4e-7) + (20.0 * 5e-6) + (10.0 * 1.8e-5)) + #expect(luna == (271_971.0 * 4e-7) + (10.0 * 4e-8) + (20.0 * 5e-7) + (10.0 * 1.8e-6)) } @Test @@ -284,27 +291,27 @@ struct CostUsagePricingTests { } @Test - func `codex priority cost supports gpt56 tiers`() { + func `codex API fast cost matches brief gpt56 scenarios`() { let sol = CostUsagePricing.codexPriorityCostUSD( model: "gpt-5.6-sol", - inputTokens: 100, - cachedInputTokens: 20, - outputTokens: 10) + inputTokens: 100_000, + cachedInputTokens: 20000, + outputTokens: 20000) let terra = CostUsagePricing.codexPriorityCostUSD( model: "gpt-5.6-terra", - inputTokens: 100, - cachedInputTokens: 20, - outputTokens: 10) + inputTokens: 100_000, + cachedInputTokens: 20000, + outputTokens: 20000) let luna = CostUsagePricing.codexPriorityCostUSD( model: "gpt-5.6-luna", - inputTokens: 100, - cachedInputTokens: 20, - outputTokens: 10) - - // 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) + inputTokens: 100_000, + cachedInputTokens: 20000, + outputTokens: 20000) + + // Public API Fast rates are 2x Standard for GPT-5.6. + let expectedSol = 2.02 + let expectedTerra = 0.808 + let expectedLuna = 0.0808 #expect(abs((sol ?? 0) - expectedSol) < 1e-12) #expect(abs((terra ?? 0) - expectedTerra) < 1e-12) #expect(abs((luna ?? 0) - expectedLuna) < 1e-12) @@ -337,9 +344,9 @@ struct CostUsagePricingTests { cacheWriteInputTokens: 20, outputTokens: 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 + let expectedSol = ((70.0 * 5e-6) + (10.0 * 5e-7) + (20.0 * 6.25e-6) + (5.0 * 3e-5)) * 2 + let expectedTerra = ((70.0 * 2e-6) + (10.0 * 2e-7) + (20.0 * 2.5e-6) + (5.0 * 1.2e-5)) * 2 + let expectedLuna = ((70.0 * 2e-7) + (10.0 * 2e-8) + (20.0 * 2.5e-7) + (5.0 * 1.2e-6)) * 2 #expect(abs((sol ?? 0) - expectedSol) < 1e-12) #expect(abs((terra ?? 0) - expectedTerra) < 1e-12) #expect(abs((luna ?? 0) - expectedLuna) < 1e-12) @@ -357,7 +364,7 @@ struct CostUsagePricingTests { "models": { "gpt-5.6-sol": { "id": "gpt-5.6-sol", - "cost": { "input": 7, "output": 31, "cache_read": 1, "cache_write": 8 } + "cost": { "input": 5, "output": 30, "cache_read": 0.5, "cache_write": 6.25 } } } } @@ -366,14 +373,13 @@ struct CostUsagePricingTests { let cost = CostUsagePricing.codexPriorityCostUSD( model: "gpt-5.6-sol", - inputTokens: 100, - cachedInputTokens: 10, - cacheWriteInputTokens: 20, - outputTokens: 5, + inputTokens: 100_000, + cachedInputTokens: 20000, + outputTokens: 20000, modelsDevCacheRoot: root) - // Standard is $0.000815 for this worked token mix; GPT-5.6 Priority is 2.5x. - #expect(cost == 0.0020375) + // The brief's Standard total is $1.01; API Fast is 2x for GPT-5.6. + #expect(abs((cost ?? 0) - 2.02) < 1e-12) } @Test diff --git a/Tests/CodexBarTests/CostUsageScannerPriorityTests.swift b/Tests/CodexBarTests/CostUsageScannerPriorityTests.swift index 9eecdea746..f95da684ec 100644 --- a/Tests/CodexBarTests/CostUsageScannerPriorityTests.swift +++ b/Tests/CodexBarTests/CostUsageScannerPriorityTests.swift @@ -53,7 +53,7 @@ struct CostUsageScannerPriorityTests { } @Test - func `codex daily report applies priority multiplier to models dev pricing`() throws { + func `codex daily report applies API fast pricing from brief`() throws { let env = try CostUsageTestEnvironment() defer { env.cleanup() } @@ -63,9 +63,9 @@ struct CostUsageScannerPriorityTests { "openai": { "id": "openai", "models": { - "gpt-5.5": { - "id": "gpt-5.5", - "cost": { "input": 7, "output": 31, "cache_read": 1 } + "gpt-5.6-sol": { + "id": "gpt-5.6-sol", + "cost": { "input": 5, "output": 30, "cache_read": 0.5 } } } } @@ -76,15 +76,15 @@ struct CostUsageScannerPriorityTests { 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": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.6-sol"]], ["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]], - self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10), + self.tokenCount(timestamp: iso1, input: 100_000, cached: 20000, output: 20000), ] _ = 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) + try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1, model: "gpt-5.6-sol") var options = CostUsageScanner.Options( codexSessionsRoot: env.codexSessionsRoot, @@ -98,9 +98,9 @@ struct CostUsageScannerPriorityTests { now: day, options: options) - // models.dev Standard is $0.00089 for this token mix; GPT-5.5 Priority is 2.5x. + // The brief's models.dev Standard total is $1.01; API Fast is 2x for GPT-5.6. let breakdown = try #require(report.data.first?.modelBreakdowns?.first) - #expect(abs((breakdown.priorityCostUSD ?? 0) - 0.002225) < 1e-12) + #expect(abs((breakdown.priorityCostUSD ?? 0) - 2.02) < 1e-12) } @Test From ca5358b7f568e6d6e3d91c9aa4536a5b489b025f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 3 Aug 2026 16:02:38 -0700 Subject: [PATCH 3/3] test: split pricing expectation math for CI type-checker Co-Authored-By: Claude Fable 5 --- .../CodexBarTests/CostUsagePricingTests.swift | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Tests/CodexBarTests/CostUsagePricingTests.swift b/Tests/CodexBarTests/CostUsagePricingTests.swift index 3865be8d05..934a171af7 100644 --- a/Tests/CodexBarTests/CostUsagePricingTests.swift +++ b/Tests/CodexBarTests/CostUsagePricingTests.swift @@ -344,14 +344,29 @@ struct CostUsagePricingTests { cacheWriteInputTokens: 20, outputTokens: 5) - let expectedSol = ((70.0 * 5e-6) + (10.0 * 5e-7) + (20.0 * 6.25e-6) + (5.0 * 3e-5)) * 2 - let expectedTerra = ((70.0 * 2e-6) + (10.0 * 2e-7) + (20.0 * 2.5e-6) + (5.0 * 1.2e-5)) * 2 - let expectedLuna = ((70.0 * 2e-7) + (10.0 * 2e-8) + (20.0 * 2.5e-7) + (5.0 * 1.2e-6)) * 2 + let solInput = 70.0 * 5e-6 + let solCached = 10.0 * 5e-7 + let solWrite = 20.0 * 6.25e-6 + let solOutput = 5.0 * 3e-5 + let expectedSol: Double = (solInput + solCached + solWrite + solOutput) * 2 + let terraInput = 70.0 * 2e-6 + let terraCached = 10.0 * 2e-7 + let terraWrite = 20.0 * 2.5e-6 + let terraOutput = 5.0 * 1.2e-5 + let expectedTerra: Double = (terraInput + terraCached + terraWrite + terraOutput) * 2 + let lunaInput = 70.0 * 2e-7 + let lunaCached = 10.0 * 2e-8 + let lunaWrite = 20.0 * 2.5e-7 + let lunaOutput = 5.0 * 1.2e-6 + let expectedLuna: Double = (lunaInput + lunaCached + lunaWrite + lunaOutput) * 2 #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) + let legacyInput = 90.0 * 1.25e-5 + let legacyCached = 10.0 * 1.25e-6 + let legacyOutput = 5.0 * 7.5e-5 + let expectedLegacy: Double = legacyInput + legacyCached + legacyOutput #expect(abs((modelWithoutCacheWriteSupport ?? 0) - expectedLegacy) < 1e-12) }