From 728ca278d440a1388b3af5ea974573c6fc703be8 Mon Sep 17 00:00:00 2001 From: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com> Date: Sat, 15 Aug 2026 13:18:30 +0800 Subject: [PATCH 1/2] Price provider-qualified Codex models --- .../Generated/CodexParserHash.generated.swift | 2 +- .../CodexBarCore/PiSessionCostScanner.swift | 4 +- .../Vendored/CostUsage/CostUsagePricing.swift | 77 +++++++++++-- .../CostUsage/CostUsagePricingKey.swift | 2 +- .../Vendored/CostUsage/CostUsageScanner.swift | 2 +- .../CodexBarTests/CostUsagePricingTests.swift | 109 ++++++++++++++++++ .../ProviderArchitectureGatekeeperTests.swift | 40 +++---- 7 files changed, 203 insertions(+), 33 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 40e7d25b1b..1e49d23941 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 = "e2899fcb0234e5c1" + static let value = "18fe6184167f05c4" } diff --git a/Sources/CodexBarCore/PiSessionCostScanner.swift b/Sources/CodexBarCore/PiSessionCostScanner.swift index 3845ae5931..72008ea6d5 100644 --- a/Sources/CodexBarCore/PiSessionCostScanner.swift +++ b/Sources/CodexBarCore/PiSessionCostScanner.swift @@ -75,7 +75,7 @@ enum PiSessionCostScanner { private static let costScale = 1_000_000_000.0 /// Bump for Pi-only cost formula changes not represented by the parser or pricing fingerprints. - private static let costFormulaVersion = 1 + private static let costFormulaVersion = 2 private static let maxLineBytes = 16 * 1024 * 1024 private static let maxSafeRoundedInt = Double(Int.max) - 1 private static let sessionStartFilenameRegex = try? NSRegularExpression( @@ -256,7 +256,7 @@ enum PiSessionCostScanner { modelsDevArtifact: modelsDevArtifact, formulaVersion: Self.costFormulaVersion, parserHash: CodexParserHash.value, - modelsDevProviderIDs: ["anthropic", "openai"])) + modelsDevProviderIDs: CostUsagePricing.codexModelsDevProviderIDs.union(["anthropic"]))) } private static func requestedWindowExpandsCache( diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift index 4c04113d89..ff6982db25 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift @@ -421,6 +421,20 @@ enum CostUsagePricing { ] private static let codexModelsDevProviderID = "openai" + /// Provider IDs emitted by Codex-compatible clients that have matching entries in models.dev. + /// + /// The route prefix is part of the model identity for local usage estimates. Keep both the + /// client-facing aliases and their models.dev provider IDs here so pricing-cache fingerprints + /// invalidate when any supported route's rates change. + static let codexModelsDevProviderIDs: Set = [ + "deepseek", + "kimi-coding", + "kimi-for-coding", + "openai", + "opencode", + "opencode-free", + "opencode-go", + ] private static let claudeModelsDevProviderID = "anthropic" static func normalizeCodexModel(_ raw: String) -> String { @@ -541,18 +555,12 @@ enum CostUsagePricing { { let key = self.normalizeCodexModel(model) guard key != self.codexUnattributedModel else { return nil } - let modelsDevLookup = self.modelsDevLookup( - providerID: self.codexModelsDevProviderID, + let modelsDevLookup = self.codexModelsDevLookup( model: model, catalog: modelsDevCatalog, cacheRoot: modelsDevCacheRoot) - ?? (model == key ? nil : self.modelsDevLookup( - providerID: self.codexModelsDevProviderID, - model: key, - catalog: modelsDevCatalog, - cacheRoot: modelsDevCacheRoot)) if let lookup = modelsDevLookup { - let bundled = self.codex[key] + let bundled = lookup.pricing.providerID == self.codexModelsDevProviderID ? self.codex[key] : nil // A missing catalog context block means models.dev has no long-context opinion, so use // the bundled tuple. Once the block exists, preserve its omissions and normal fallback // semantics instead of filling individual fields from a different pricing source. @@ -590,6 +598,59 @@ enum CostUsagePricing { return pricing } + /// Resolves the provider-qualified model IDs written by Codex-compatible clients without + /// falling back to OpenAI pricing for an unrelated route. Unqualified model IDs retain the + /// historical OpenAI behavior, including the gpt-5.6 alias lookup. + private static func codexModelsDevLookup( + model rawModel: String, + catalog: ModelsDevCatalog?, + cacheRoot: URL?) -> ModelsDevPricingLookup? + { + let trimmed = rawModel.trimmingCharacters(in: .whitespacesAndNewlines) + let candidates: [(providerID: String, modelID: String)] + if let slash = trimmed.firstIndex(of: "/") { + let routeID = String(trimmed[.. = ["openai"]) -> String + modelsDevProviderIDs: Set = CostUsagePricing.codexModelsDevProviderIDs) -> String { var parts = [ "costFormulaVersion=\(formulaVersion)", diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index c36513fe23..744f2b7288 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -2081,7 +2081,7 @@ enum CostUsageScanner { /// Bump when the report pricing formula changes. Rates are resolved when reports are read; /// this fingerprint only invalidates downstream presentation caches such as Workspaces snapshots. - private static let codexCostFormulaVersion = 2 + private static let codexCostFormulaVersion = 3 static func codexPricingKey(modelsDevArtifact: ModelsDevCacheArtifact?) -> String { CostUsagePricingKey.codex( diff --git a/Tests/CodexBarTests/CostUsagePricingTests.swift b/Tests/CodexBarTests/CostUsagePricingTests.swift index 96ce407a6a..fda9c2b3dc 100644 --- a/Tests/CodexBarTests/CostUsagePricingTests.swift +++ b/Tests/CodexBarTests/CostUsagePricingTests.swift @@ -50,6 +50,115 @@ struct CostUsagePricingTests { #expect(cost == nil) } + @Test + func `codex cost resolves OpenCodex provider qualified models`() throws { + let root = try Self.seedModelsDevCache(""" + { + "deepseek": { + "id": "deepseek", + "models": { + "deepseek-v4-flash": { + "id": "deepseek-v4-flash", + "cost": { "input": 0.14, "output": 0.28 } + } + } + }, + "kimi-for-coding": { + "id": "kimi-for-coding", + "models": { + "k3": { + "id": "k3", + "cost": { "input": 0, "output": 0 } + } + } + }, + "opencode": { + "id": "opencode", + "models": { + "deepseek-v4-flash-free": { + "id": "deepseek-v4-flash-free", + "cost": { "input": 0, "output": 0 } + } + } + }, + "opencode-go": { + "id": "opencode-go", + "models": { + "deepseek-v4-flash": { + "id": "deepseek-v4-flash", + "cost": { "input": 0.07, "output": 0.14 } + } + } + } + } + """) + + let opencodeGo = CostUsagePricing.codexCostUSD( + model: "opencode-go/deepseek-v4-flash", + inputTokens: 100, + cachedInputTokens: 10, + outputTokens: 5, + modelsDevCacheRoot: root) + let opencodeFree = CostUsagePricing.codexCostUSD( + model: "opencode-free/deepseek-v4-flash-free", + inputTokens: 100, + cachedInputTokens: 10, + outputTokens: 5, + modelsDevCacheRoot: root) + let kimi = CostUsagePricing.codexCostUSD( + model: "kimi-coding/k3", + inputTokens: 100, + cachedInputTokens: 10, + outputTokens: 5, + modelsDevCacheRoot: root) + let deepseek = CostUsagePricing.codexCostUSD( + model: "deepseek/deepseek-v4-flash", + inputTokens: 100, + cachedInputTokens: 10, + outputTokens: 5, + modelsDevCacheRoot: root) + + #expect(opencodeGo == (100.0 * 0.07e-6) + (5.0 * 0.14e-6)) + #expect(opencodeFree == 0) + #expect(kimi == 0) + #expect(deepseek == (100.0 * 0.14e-6) + (5.0 * 0.28e-6)) + } + + @Test + func `codex cost does not cross charge an unknown provider prefix`() throws { + let root = try Self.seedModelsDevCache(""" + { + "openai": { + "id": "openai", + "models": { + "deepseek-v4-flash": { + "id": "deepseek-v4-flash", + "cost": { "input": 99, "output": 199 } + } + } + }, + "unlisted-route": { + "id": "unlisted-route", + "models": { + "deepseek-v4-flash": { + "id": "deepseek-v4-flash", + "cost": { "input": 1, "output": 1 } + } + } + } + } + """) + + let cost = CostUsagePricing.codexCostUSD( + model: "unlisted-route/deepseek-v4-flash", + inputTokens: 100, + cachedInputTokens: 0, + outputTokens: 5, + modelsDevCacheRoot: root) + + #expect(cost == nil) + } + @Test func `codex cost supports gpt51 codex max`() { let cost = CostUsagePricing.codexCostUSD( diff --git a/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift b/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift index 54eeb5affa..da849b2545 100644 --- a/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift +++ b/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift @@ -3487,14 +3487,6 @@ struct ProviderArchitectureGatekeeperTests { expectedReferenceCount: 2, expectedReferenceFingerprint: ["claude@0", "codex@0"], reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), - AllowedProviderConstruct( - path: "Sources/CodexBarCore/PiSessionCostScanner.swift", - line: 259, - anchor: "modelsDevProviderIDs: [\"anthropic\", \"openai\"]))", - expectedProviderIDs: ["openai"], - expectedReferenceCount: 1, - expectedReferenceFingerprint: ["openai@0"], - reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), AllowedProviderConstruct( path: "Sources/CodexBarCore/PiSessionCostScanner.swift", line: 834, @@ -3622,13 +3614,13 @@ struct ProviderArchitectureGatekeeperTests { path: "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift", line: 423, anchor: "private static let codexModelsDevProviderID = \"openai\"", - expectedProviderIDs: ["openai"], - expectedReferenceCount: 1, - expectedReferenceFingerprint: ["openai@0"], + expectedProviderIDs: ["deepseek", "openai", "opencode"], + expectedReferenceCount: 4, + expectedReferenceFingerprint: ["openai@0", "deepseek@7", "openai@10", "opencode@11"], reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), AllowedProviderConstruct( path: "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift", - line: 437, + line: 451, anchor: "if self.codex[trimmed] != nil {", expectedProviderIDs: ["codex"], expectedReferenceCount: 2, @@ -3636,7 +3628,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), AllowedProviderConstruct( path: "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift", - line: 480, + line: 494, anchor: "if self.claude[base] != nil {", expectedProviderIDs: ["claude"], expectedReferenceCount: 1, @@ -3644,7 +3636,15 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), AllowedProviderConstruct( path: "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift", - line: 589, + line: 563, + anchor: "let bundled = lookup.pricing.providerID == self.codexModelsDevProviderID ? self.codex[key] : nil", + expectedProviderIDs: ["codex"], + expectedReferenceCount: 1, + expectedReferenceFingerprint: ["codex@0"], + reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), + AllowedProviderConstruct( + path: "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift", + line: 597, anchor: "guard let pricing = self.codex[key] else { return nil }", expectedProviderIDs: ["codex"], expectedReferenceCount: 1, @@ -3652,19 +3652,19 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), AllowedProviderConstruct( path: "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift", - line: 708, + line: 767, anchor: "guard let pricing = self.claude[key] else { return nil }", expectedProviderIDs: ["claude"], expectedReferenceCount: 1, expectedReferenceFingerprint: ["claude@0"], reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), AllowedProviderConstruct( - path: "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricingKey.swift", - line: 13, - anchor: "modelsDevProviderIDs: Set = [\"openai\"]) -> String", - expectedProviderIDs: ["openai"], + path: "Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift", + line: 621, + anchor: "providerIDs.append(\"opencode\")", + expectedProviderIDs: ["opencode"], expectedReferenceCount: 1, - expectedReferenceFingerprint: ["openai@0"], + expectedReferenceFingerprint: ["opencode@0"], reason: "This exact cost scanner dispatch selects a provider-owned transcript, cache, or pricing format."), AllowedProviderConstruct( path: "Sources/CodexBarCore/Vendored/CostUsage/ModelsDevPricing.swift", From 2371835a13d4ae88dbd6008388267ceee8ee1d9b Mon Sep 17 00:00:00 2001 From: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com> Date: Sat, 15 Aug 2026 14:42:23 +0800 Subject: [PATCH 2/2] Route qualified model pricing refreshes --- Sources/CodexBarCore/CostUsageFetcher.swift | 65 ++++++++------ .../Generated/CodexParserHash.generated.swift | 2 +- .../Vendored/CostUsage/CostUsagePricing.swift | 88 ++++++++++--------- ...UsageFetcherUnknownModelPricingTests.swift | 51 +++++++++++ .../ProviderArchitectureGatekeeperTests.swift | 30 +++---- 5 files changed, 154 insertions(+), 82 deletions(-) diff --git a/Sources/CodexBarCore/CostUsageFetcher.swift b/Sources/CodexBarCore/CostUsageFetcher.swift index e1d0e74703..eedbe993f2 100644 --- a/Sources/CodexBarCore/CostUsageFetcher.swift +++ b/Sources/CodexBarCore/CostUsageFetcher.swift @@ -616,9 +616,13 @@ public struct CostUsageFetcher: Sendable { } } - private struct UnknownPricingRefreshRequest: Sendable { + private struct ModelsDevPricingTarget: Hashable, Sendable { let providerID: String - let modelIDs: Set + let modelID: String + } + + private struct UnknownPricingRefreshRequest: Sendable { + let targets: Set let now: Date let cacheRoot: URL? let client: ModelsDevClient @@ -632,22 +636,24 @@ public struct CostUsageFetcher: Sendable { client: ModelsDevClient) -> UnknownPricingRefreshRequest? { guard provider == .codex || provider == .claude else { return nil } - let unknownModelIDs = Set(daily.data.flatMap { entry in - entry.modelBreakdowns?.compactMap { breakdown -> String? in - guard breakdown.costUSD == nil else { return nil } - if provider == .codex, - CostUsagePricing.isCodexUnattributedModel(breakdown.modelName) - { - return nil + var targets = Set() + for entry in daily.data { + for breakdown in entry.modelBreakdowns ?? [] { + guard breakdown.costUSD == nil else { continue } + if provider == .codex { + guard !CostUsagePricing.isCodexUnattributedModel(breakdown.modelName) else { continue } + for target in CostUsagePricing.codexModelsDevPricingTargets(for: breakdown.modelName) { + targets.insert(ModelsDevPricingTarget(providerID: target.providerID, modelID: target.modelID)) + } + } else { + targets.insert(ModelsDevPricingTarget(providerID: "anthropic", modelID: breakdown.modelName)) } - return breakdown.modelName - } ?? [] - }) - guard !unknownModelIDs.isEmpty else { return nil } + } + } + guard !targets.isEmpty else { return nil } return UnknownPricingRefreshRequest( - providerID: provider == .codex ? "openai" : "anthropic", - modelIDs: unknownModelIDs, + targets: targets, now: now, cacheRoot: cacheRoot, client: client) @@ -657,23 +663,30 @@ public struct CostUsageFetcher: Sendable { _ request: UnknownPricingRefreshRequest, inBackground: Bool) async -> Bool { - if inBackground { - Task.detached(priority: .utility) { - _ = await ModelsDevPricingPipeline.refreshForUnknownModelsIfNeeded( - providerID: request.providerID, - modelIDs: request.modelIDs, + func refreshTargets() async -> Bool { + let targetsByProvider = Dictionary(grouping: request.targets, by: \.providerID) + for providerID in targetsByProvider.keys.sorted() { + let modelIDs = Set(targetsByProvider[providerID, default: []].map(\.modelID)) + let outcome = await ModelsDevPricingPipeline.refreshForUnknownModelsIfNeeded( + providerID: providerID, + modelIDs: modelIDs, now: request.now, cacheRoot: request.cacheRoot, client: request.client) + if outcome == .pricingAvailable { + return true + } + } + return false + } + + if inBackground { + Task.detached(priority: .utility) { + _ = await refreshTargets() } return false } - return await ModelsDevPricingPipeline.refreshForUnknownModelsIfNeeded( - providerID: request.providerID, - modelIDs: request.modelIDs, - now: request.now, - cacheRoot: request.cacheRoot, - client: request.client) == .pricingAvailable + return await refreshTargets() } static func loadCachedCodexTokenSnapshot( diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 1e49d23941..7d75ccf273 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 = "18fe6184167f05c4" + static let value = "3c1ec2b780582978" } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift index ff6982db25..4ea8da01ee 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsagePricing.swift @@ -437,6 +437,46 @@ enum CostUsagePricing { ] private static let claudeModelsDevProviderID = "anthropic" + /// Returns the provider/model identities that may price a Codex model. Keep this mapping + /// shared by direct lookup and unknown-price refresh so a newly downloaded catalog is checked + /// under the same identity that was used to resolve the model. + static func codexModelsDevPricingTargets(for rawModel: String) -> [(providerID: String, modelID: String)] { + let trimmed = rawModel.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return [] } + if let slash = trimmed.firstIndex(of: "/") { + let routeID = String(trimmed[.. String { var trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.hasPrefix("openai/") { @@ -606,46 +646,14 @@ enum CostUsagePricing { catalog: ModelsDevCatalog?, cacheRoot: URL?) -> ModelsDevPricingLookup? { - let trimmed = rawModel.trimmingCharacters(in: .whitespacesAndNewlines) - let candidates: [(providerID: String, modelID: String)] - if let slash = trimmed.firstIndex(of: "/") { - let routeID = String(trimmed[..