Skip to content

Commit aa40f1e

Browse files
0xSMWsteipete
andauthored
Add GPT-5.6 Sol/Terra/Luna Codex pricing (#2023)
* Add GPT-5.6 Sol/Terra/Luna Codex pricing Local cost scanning already counts gpt-5.6-* tokens but left day totals understated without rates. Bundle preview pricing so Sol/Terra/Luna resolve without models.dev. * Price GPT-5.6 long-context, Priority, cache-write, and alias Address review: official long-context (>272K) and Priority rates, 1.25x cache-write billing for Pi disjoint writes, and route gpt-5.6 to Sol. * Invalidate Pi cost cache and price Priority cache writes Bump Pi session cost cache to v5 so GPT-5.6 rows reprice with 1.25x cache-write rates, and apply the same multiplier on Priority input. * Fall back bundled long-context rates for models.dev gaps When models.dev has a GPT-5.6 hit without context_over_200k fields, still apply bundled above-threshold input/output/cache-read rates with the 272K threshold so long prompts are not under-billed at short rates. * fix: harden GPT-5.6 cost pricing Co-authored-by: Stephen <stephen@klu.ai> * Use released GPT-5.6 IDs and fictitious date suffixes in tests Match AGENTS model-name policy: price coverage uses official unsuffixed IDs; dated normalization uses explicitly fictitious 2099-01-01 suffixes. * test: lock GPT-5.6 Priority boundary --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent 5bee017 commit aa40f1e

10 files changed

Lines changed: 838 additions & 67 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66
- Agent Sessions: list and focus live local or SSH-discovered Codex and Claude Code sessions from the menu and CLI.
77
- Kimi K2: add a Usage Dashboard shortcut to the human-facing legacy credits page. Thanks @joeVenner!
8+
- Codex: add GPT-5.6 Sol, Terra, and Luna pricing, including long-context, Priority, cache-write, alias, and automatic Pi cache repricing when rates change (#2023). Thanks @0xSMW!
89
- Codex: add a provider option to hide Spark quota rows without hiding credits or other extra usage (#2013). Thanks @intellectronica!
910
- Wayfinder: add opt-in local gateway health, routing, savings, and latency usage with configurable loopback URL support. Thanks @tcballard!
1011
- Claude CLI: surface model-scoped weekly limits alongside all-model usage without duplicating matching web limits. Thanks @janpollak!
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand.
22

33
enum CodexParserHash {
4-
static let value = "3edc97b8139ae59e"
4+
static let value = "7985c9336eb88ef7"
55
}

Sources/CodexBarCore/PiSessionCostCache.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import Foundation
22

33
enum PiSessionCostCacheIO {
4-
private static let artifactVersion = 4
4+
/// Artifact schema version. Pricing changes are tracked separately by `pricingKey`.
5+
private static let artifactVersion = 5
56

67
private static func defaultCacheRoot() -> URL {
78
let root = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
@@ -51,10 +52,11 @@ struct PiSessionCostCache: Codable {
5152
var lastScanUnixMs: Int64 = 0
5253
var scanSinceKey: String?
5354
var scanUntilKey: String?
55+
var pricingKey: String?
5456
var daysByProvider: [String: [String: [String: PiPackedUsage]]] = [:]
5557
var files: [String: PiSessionFileUsage] = [:]
5658

57-
init(version: Int = 4) {
59+
init(version: Int = 5) {
5860
self.version = version
5961
}
6062
}

Sources/CodexBarCore/PiSessionCostScanner.swift

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ enum PiSessionCostScanner {
4949
private struct ModelsDevPricingContext {
5050
let catalog: ModelsDevCatalog?
5151
let cacheRoot: URL?
52+
let pricingKey: String
5253
}
5354

5455
private struct ScanContext {
@@ -59,6 +60,8 @@ enum PiSessionCostScanner {
5960
}
6061

6162
private static let costScale = 1_000_000_000.0
63+
/// Bump for Pi-only cost formula changes not represented by the parser or pricing fingerprints.
64+
private static let costFormulaVersion = 1
6265
private static let maxLineBytes = 16 * 1024 * 1024
6366
private static let maxSafeRoundedInt = Double(Int.max) - 1
6467
private static let sessionStartFilenameRegex = try? NSRegularExpression(
@@ -98,12 +101,12 @@ enum PiSessionCostScanner {
98101
var cache = PiSessionCostCacheIO.load(cacheRoot: options.cacheRoot)
99102
let nowMs = Int64(now.timeIntervalSince1970 * 1000)
100103
let refreshMs = Int64(max(0, options.refreshMinIntervalSeconds) * 1000)
101-
let pricingContext = ModelsDevPricingContext(
102-
catalog: CostUsagePricing.modelsDevCatalog(now: now, cacheRoot: options.cacheRoot),
103-
cacheRoot: options.cacheRoot)
104+
let pricingContext = self.pricingContext(now: now, cacheRoot: options.cacheRoot)
104105
let windowExpanded = self.requestedWindowExpandsCache(range: range, cache: cache)
106+
let pricingChanged = cache.pricingKey != pricingContext.pricingKey
105107
let shouldRefresh = options.forceRescan
106108
|| windowExpanded
109+
|| pricingChanged
107110
|| refreshMs == 0
108111
|| cache.lastScanUnixMs == 0
109112
|| nowMs - cache.lastScanUnixMs > refreshMs
@@ -121,7 +124,7 @@ enum PiSessionCostScanner {
121124
cache: &cache,
122125
context: ScanContext(
123126
range: range,
124-
forceRescan: options.forceRescan || windowExpanded,
127+
forceRescan: options.forceRescan || windowExpanded || pricingChanged,
125128
pricingContext: pricingContext,
126129
checkCancellation: checkCancellation))
127130
}
@@ -139,6 +142,7 @@ enum PiSessionCostScanner {
139142

140143
cache.scanSinceKey = range.scanSinceKey
141144
cache.scanUntilKey = range.scanUntilKey
145+
cache.pricingKey = pricingContext.pricingKey
142146
cache.lastScanUnixMs = nowMs
143147
try checkCancellation?()
144148
PiSessionCostCacheIO.save(cache: cache, cacheRoot: options.cacheRoot)
@@ -185,9 +189,8 @@ enum PiSessionCostScanner {
185189
guard !cache.daysByProvider.isEmpty else { return nil }
186190
guard !self.requestedWindowExpandsCache(range: range, cache: cache) else { return nil }
187191

188-
let pricingContext = ModelsDevPricingContext(
189-
catalog: CostUsagePricing.modelsDevCatalog(now: now, cacheRoot: cacheRoot),
190-
cacheRoot: cacheRoot)
192+
let pricingContext = self.pricingContext(now: now, cacheRoot: cacheRoot)
193+
guard cache.pricingKey == pricingContext.pricingKey else { return nil }
191194
let report = self.buildReport(
192195
provider: provider,
193196
cache: cache,
@@ -200,6 +203,17 @@ enum PiSessionCostScanner {
200203
return CachedDailyReportResult(report: report, lastScanAt: lastScanAt)
201204
}
202205

206+
private static func pricingContext(now: Date, cacheRoot: URL?) -> ModelsDevPricingContext {
207+
let modelsDevArtifact = ModelsDevCache.load(now: now, cacheRoot: cacheRoot).artifact
208+
return ModelsDevPricingContext(
209+
catalog: modelsDevArtifact?.catalog,
210+
cacheRoot: cacheRoot,
211+
pricingKey: CostUsagePricingKey.codex(
212+
modelsDevArtifact: modelsDevArtifact,
213+
formulaVersion: Self.costFormulaVersion,
214+
parserHash: CodexParserHash.value))
215+
}
216+
203217
private static func requestedWindowExpandsCache(
204218
range: CostUsageScanner.CostUsageDayRange,
205219
cache: PiSessionCostCache) -> Bool
@@ -645,12 +659,14 @@ enum PiSessionCostScanner {
645659
switch provider {
646660
case .codex:
647661
// Pi records input, cache reads, and cache writes as disjoint counts. Codex pricing
648-
// expects cached input to be a subset of total input, so reconstruct that total here.
662+
// expects cached/write tokens to be subsets of total input, so reconstruct that total
663+
// here and pass writes separately (1.25x input for GPT-5.6 when rates are known).
649664
CostUsagePricing.codexCostUSD(
650665
model: modelName,
651666
inputTokens: usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens,
652667
cachedInputTokens: usage.cacheReadTokens,
653668
outputTokens: usage.outputTokens,
669+
cacheWriteInputTokens: usage.cacheWriteTokens,
654670
modelsDevCatalog: pricingContext?.catalog,
655671
modelsDevCacheRoot: pricingContext?.cacheRoot)
656672
case .claude:

0 commit comments

Comments
 (0)