Files
2026-07-13 12:22:33 +08:00

812 lines
36 KiB
Swift

import Foundation
enum CostUsagePricing {
private static let codexPriorityInputTokenLimit = 272_000
static let codexUnattributedModel = "unknown"
struct CodexPricing {
let inputCostPerToken: Double
let outputCostPerToken: Double
let cacheReadInputCostPerToken: Double?
/// Optional cache-write (cache creation) rate. When nil, write tokens are billed at the
/// uncached input rate (legacy Codex folding behavior).
let cacheWriteInputCostPerToken: Double?
let displayLabel: String?
let thresholdTokens: Int?
let inputCostPerTokenAboveThreshold: Double?
let outputCostPerTokenAboveThreshold: Double?
let cacheReadInputCostPerTokenAboveThreshold: Double?
let cacheWriteInputCostPerTokenAboveThreshold: Double?
let priorityInputCostPerToken: Double?
let priorityOutputCostPerToken: Double?
let priorityCacheReadInputCostPerToken: Double?
let priorityCacheWriteInputCostPerToken: Double?
init(
inputCostPerToken: Double,
outputCostPerToken: Double,
cacheReadInputCostPerToken: Double?,
displayLabel: String?,
cacheWriteInputCostPerToken: Double? = nil,
thresholdTokens: Int? = nil,
inputCostPerTokenAboveThreshold: Double? = nil,
outputCostPerTokenAboveThreshold: Double? = nil,
cacheReadInputCostPerTokenAboveThreshold: Double? = nil,
cacheWriteInputCostPerTokenAboveThreshold: Double? = nil,
priorityInputCostPerToken: Double? = nil,
priorityOutputCostPerToken: Double? = nil,
priorityCacheReadInputCostPerToken: Double? = nil,
priorityCacheWriteInputCostPerToken: Double? = nil)
{
self.inputCostPerToken = inputCostPerToken
self.outputCostPerToken = outputCostPerToken
self.cacheReadInputCostPerToken = cacheReadInputCostPerToken
self.cacheWriteInputCostPerToken = cacheWriteInputCostPerToken
self.displayLabel = displayLabel
self.thresholdTokens = thresholdTokens
self.inputCostPerTokenAboveThreshold = inputCostPerTokenAboveThreshold
self.outputCostPerTokenAboveThreshold = outputCostPerTokenAboveThreshold
self.cacheReadInputCostPerTokenAboveThreshold = cacheReadInputCostPerTokenAboveThreshold
self.cacheWriteInputCostPerTokenAboveThreshold = cacheWriteInputCostPerTokenAboveThreshold
self.priorityInputCostPerToken = priorityInputCostPerToken
self.priorityOutputCostPerToken = priorityOutputCostPerToken
self.priorityCacheReadInputCostPerToken = priorityCacheReadInputCostPerToken
self.priorityCacheWriteInputCostPerToken = priorityCacheWriteInputCostPerToken
}
}
struct ClaudePricing {
let inputCostPerToken: Double
let outputCostPerToken: Double
let cacheCreationInputCostPerToken: Double
let cacheReadInputCostPerToken: Double
let thresholdTokens: Int?
let inputCostPerTokenAboveThreshold: Double?
let outputCostPerTokenAboveThreshold: Double?
let cacheCreationInputCostPerTokenAboveThreshold: Double?
let cacheReadInputCostPerTokenAboveThreshold: Double?
}
private struct ClaudeCostTokens {
let input: Int
let cacheRead: Int
let cacheCreation: Int
let cacheCreation1h: Int
let output: Int
}
private static let codex: [String: CodexPricing] = [
"gpt-5": CodexPricing(
inputCostPerToken: 1.25e-6,
outputCostPerToken: 1e-5,
cacheReadInputCostPerToken: 1.25e-7,
displayLabel: nil),
"gpt-5-codex": CodexPricing(
inputCostPerToken: 1.25e-6,
outputCostPerToken: 1e-5,
cacheReadInputCostPerToken: 1.25e-7,
displayLabel: nil),
"gpt-5-mini": CodexPricing(
inputCostPerToken: 2.5e-7,
outputCostPerToken: 2e-6,
cacheReadInputCostPerToken: 2.5e-8,
displayLabel: nil),
"gpt-5-nano": CodexPricing(
inputCostPerToken: 5e-8,
outputCostPerToken: 4e-7,
cacheReadInputCostPerToken: 5e-9,
displayLabel: nil),
"gpt-5-pro": CodexPricing(
inputCostPerToken: 1.5e-5,
outputCostPerToken: 1.2e-4,
cacheReadInputCostPerToken: nil,
displayLabel: nil),
"gpt-5.1": CodexPricing(
inputCostPerToken: 1.25e-6,
outputCostPerToken: 1e-5,
cacheReadInputCostPerToken: 1.25e-7,
displayLabel: nil),
"gpt-5.1-codex": CodexPricing(
inputCostPerToken: 1.25e-6,
outputCostPerToken: 1e-5,
cacheReadInputCostPerToken: 1.25e-7,
displayLabel: nil),
"gpt-5.1-codex-max": CodexPricing(
inputCostPerToken: 1.25e-6,
outputCostPerToken: 1e-5,
cacheReadInputCostPerToken: 1.25e-7,
displayLabel: nil),
"gpt-5.1-codex-mini": CodexPricing(
inputCostPerToken: 2.5e-7,
outputCostPerToken: 2e-6,
cacheReadInputCostPerToken: 2.5e-8,
displayLabel: nil),
"gpt-5.2": CodexPricing(
inputCostPerToken: 1.75e-6,
outputCostPerToken: 1.4e-5,
cacheReadInputCostPerToken: 1.75e-7,
displayLabel: nil),
"gpt-5.2-codex": CodexPricing(
inputCostPerToken: 1.75e-6,
outputCostPerToken: 1.4e-5,
cacheReadInputCostPerToken: 1.75e-7,
displayLabel: nil),
"gpt-5.2-pro": CodexPricing(
inputCostPerToken: 2.1e-5,
outputCostPerToken: 1.68e-4,
cacheReadInputCostPerToken: nil,
displayLabel: nil),
"gpt-5.3-codex": CodexPricing(
inputCostPerToken: 1.75e-6,
outputCostPerToken: 1.4e-5,
cacheReadInputCostPerToken: 1.75e-7,
displayLabel: nil),
"gpt-5.3-codex-spark": CodexPricing(
inputCostPerToken: 0,
outputCostPerToken: 0,
cacheReadInputCostPerToken: 0,
displayLabel: "Research Preview"),
"gpt-5.4": CodexPricing(
inputCostPerToken: 2.5e-6,
outputCostPerToken: 1.5e-5,
cacheReadInputCostPerToken: 2.5e-7,
displayLabel: nil,
thresholdTokens: 272_000,
inputCostPerTokenAboveThreshold: 5e-6,
outputCostPerTokenAboveThreshold: 2.25e-5,
cacheReadInputCostPerTokenAboveThreshold: 5e-7,
priorityInputCostPerToken: 5e-6,
priorityOutputCostPerToken: 3e-5,
priorityCacheReadInputCostPerToken: 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),
"gpt-5.4-nano": CodexPricing(
inputCostPerToken: 2e-7,
outputCostPerToken: 1.25e-6,
cacheReadInputCostPerToken: 2e-8,
displayLabel: nil),
"gpt-5.4-pro": CodexPricing(
inputCostPerToken: 3e-5,
outputCostPerToken: 1.8e-4,
cacheReadInputCostPerToken: nil,
displayLabel: nil),
"gpt-5.5": CodexPricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 3e-5,
cacheReadInputCostPerToken: 5e-7,
displayLabel: nil,
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),
"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.
"gpt-5.6-sol": CodexPricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 3e-5,
cacheReadInputCostPerToken: 5e-7,
displayLabel: nil,
cacheWriteInputCostPerToken: 6.25e-6,
thresholdTokens: 272_000,
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),
"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),
]
static func codexBuiltInPricingFingerprint() -> String {
var parts = ["priorityInputTokenLimit=\(self.codexPriorityInputTokenLimit)"]
for model in self.codex.keys.sorted() {
guard let pricing = self.codex[model] else { continue }
parts.append([
"model=\(model)",
self.optionalPricingFingerprint(pricing.inputCostPerToken),
self.optionalPricingFingerprint(pricing.outputCostPerToken),
self.optionalPricingFingerprint(pricing.cacheReadInputCostPerToken),
self.optionalPricingFingerprint(pricing.cacheWriteInputCostPerToken),
pricing.displayLabel ?? "nil",
pricing.thresholdTokens.map(String.init) ?? "nil",
self.optionalPricingFingerprint(pricing.inputCostPerTokenAboveThreshold),
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),
].joined(separator: "|"))
}
return parts.joined(separator: "\n")
}
private static func optionalPricingFingerprint(_ value: Double?) -> String {
guard let value else { return "nil" }
return String(format: "%.17g", value)
}
private static let claude: [String: ClaudePricing] = [
"claude-fable-5": ClaudePricing(
inputCostPerToken: 1e-5,
outputCostPerToken: 5e-5,
cacheCreationInputCostPerToken: 1.25e-5,
cacheReadInputCostPerToken: 1e-6,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-haiku-4-5-20251001": ClaudePricing(
inputCostPerToken: 1e-6,
outputCostPerToken: 5e-6,
cacheCreationInputCostPerToken: 1.25e-6,
cacheReadInputCostPerToken: 1e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-haiku-4-5": ClaudePricing(
inputCostPerToken: 1e-6,
outputCostPerToken: 5e-6,
cacheCreationInputCostPerToken: 1.25e-6,
cacheReadInputCostPerToken: 1e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-opus-4-5-20251101": ClaudePricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 2.5e-5,
cacheCreationInputCostPerToken: 6.25e-6,
cacheReadInputCostPerToken: 5e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-opus-4-5": ClaudePricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 2.5e-5,
cacheCreationInputCostPerToken: 6.25e-6,
cacheReadInputCostPerToken: 5e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-opus-4-6-20260205": ClaudePricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 2.5e-5,
cacheCreationInputCostPerToken: 6.25e-6,
cacheReadInputCostPerToken: 5e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-opus-4-6": ClaudePricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 2.5e-5,
cacheCreationInputCostPerToken: 6.25e-6,
cacheReadInputCostPerToken: 5e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-opus-4-7": ClaudePricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 2.5e-5,
cacheCreationInputCostPerToken: 6.25e-6,
cacheReadInputCostPerToken: 5e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-opus-4-8": ClaudePricing(
inputCostPerToken: 5e-6,
outputCostPerToken: 2.5e-5,
cacheCreationInputCostPerToken: 6.25e-6,
cacheReadInputCostPerToken: 5e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-sonnet-4-5": 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),
"claude-sonnet-4-6": ClaudePricing(
inputCostPerToken: 3e-6,
outputCostPerToken: 1.5e-5,
cacheCreationInputCostPerToken: 3.75e-6,
cacheReadInputCostPerToken: 3e-7,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-sonnet-4-5-20250929": 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),
"claude-opus-4-20250514": ClaudePricing(
inputCostPerToken: 1.5e-5,
outputCostPerToken: 7.5e-5,
cacheCreationInputCostPerToken: 1.875e-5,
cacheReadInputCostPerToken: 1.5e-6,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-opus-4-1": ClaudePricing(
inputCostPerToken: 1.5e-5,
outputCostPerToken: 7.5e-5,
cacheCreationInputCostPerToken: 1.875e-5,
cacheReadInputCostPerToken: 1.5e-6,
thresholdTokens: nil,
inputCostPerTokenAboveThreshold: nil,
outputCostPerTokenAboveThreshold: nil,
cacheCreationInputCostPerTokenAboveThreshold: nil,
cacheReadInputCostPerTokenAboveThreshold: nil),
"claude-sonnet-4-20250514": 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 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"
static func normalizeCodexModel(_ raw: String) -> String {
var trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.hasPrefix("openai/") {
trimmed = String(trimmed.dropFirst("openai/".count))
}
// OpenAI routes the unsuffixed gpt-5.6 alias to Sol.
if trimmed == "gpt-5.6" {
return "gpt-5.6-sol"
}
if self.codex[trimmed] != nil {
return trimmed
}
if let datedSuffix = trimmed.range(of: #"-\d{4}-\d{2}-\d{2}$"#, options: .regularExpression) {
let base = String(trimmed[..<datedSuffix.lowerBound])
if self.codex[base] != nil {
return base
}
}
return trimmed
}
static func isCodexUnattributedModel(_ raw: String) -> Bool {
self.normalizeCodexModel(raw) == self.codexUnattributedModel
}
static func codexDisplayLabel(model: String) -> String? {
let key = self.normalizeCodexModel(model)
return self.codex[key]?.displayLabel
}
static func normalizeClaudeModel(_ raw: String) -> String {
var trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.hasPrefix("anthropic.") {
trimmed = String(trimmed.dropFirst("anthropic.".count))
}
if let lastDot = trimmed.lastIndex(of: "."),
trimmed.contains("claude-")
{
let tail = String(trimmed[trimmed.index(after: lastDot)...])
if tail.hasPrefix("claude-") {
trimmed = tail
}
}
if let vRange = trimmed.range(of: #"-v\d+:\d+$"#, options: .regularExpression) {
trimmed.removeSubrange(vRange)
}
if let baseRange = trimmed.range(of: #"-\d{8}$"#, options: .regularExpression) {
let base = String(trimmed[..<baseRange.lowerBound])
if self.claude[base] != nil {
return base
}
}
return trimmed
}
static func codexCostUSD(
model: String,
inputTokens: Int,
cachedInputTokens: Int,
outputTokens: Int,
cacheWriteInputTokens: Int = 0,
modelsDevCatalog: ModelsDevCatalog? = nil,
modelsDevCacheRoot: URL? = nil) -> Double?
{
let key = self.normalizeCodexModel(model)
guard key != self.codexUnattributedModel else { return nil }
let modelsDevLookup = self.modelsDevLookup(
providerID: self.codexModelsDevProviderID,
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]
// 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.
let bundledLongContext = lookup.pricing.thresholdTokens == nil ? bundled : nil
let cacheReadAboveThreshold = lookup.pricing.cacheReadInputCostPerTokenAboveThreshold
?? (lookup.pricing.thresholdTokens != nil
? lookup.pricing.cacheReadInputCostPerToken
?? lookup.pricing.inputCostPerTokenAboveThreshold
?? lookup.pricing.inputCostPerToken
: bundledLongContext?.cacheReadInputCostPerTokenAboveThreshold)
let cacheWriteAboveThreshold = lookup.pricing.cacheCreationInputCostPerTokenAboveThreshold
?? (lookup.pricing.thresholdTokens != nil
? lookup.pricing.cacheCreationInputCostPerToken
?? lookup.pricing.inputCostPerTokenAboveThreshold
?? lookup.pricing.inputCostPerToken
: bundledLongContext?.cacheWriteInputCostPerTokenAboveThreshold)
return self.codexCostUSD(
pricing: lookup.pricing,
thresholdTokens: bundled?.thresholdTokens ?? lookup.pricing.thresholdTokens,
inputCostPerTokenAboveThreshold: lookup.pricing.inputCostPerTokenAboveThreshold
?? bundledLongContext?.inputCostPerTokenAboveThreshold,
outputCostPerTokenAboveThreshold: lookup.pricing.outputCostPerTokenAboveThreshold
?? bundledLongContext?.outputCostPerTokenAboveThreshold,
cacheReadInputCostPerToken: lookup.pricing.cacheReadInputCostPerToken
?? bundled?.cacheReadInputCostPerToken,
cacheReadInputCostPerTokenAboveThreshold: cacheReadAboveThreshold,
cacheWriteInputCostPerToken: lookup.pricing.cacheCreationInputCostPerToken
?? bundled?.cacheWriteInputCostPerToken,
cacheWriteInputCostPerTokenAboveThreshold: cacheWriteAboveThreshold,
inputTokens: inputTokens,
cachedInputTokens: cachedInputTokens,
cacheWriteInputTokens: cacheWriteInputTokens,
outputTokens: outputTokens)
}
guard let pricing = self.codex[key] else { return nil }
return self.codexCostUSD(
pricing: pricing,
inputTokens: inputTokens,
cachedInputTokens: cachedInputTokens,
cacheWriteInputTokens: cacheWriteInputTokens,
outputTokens: outputTokens)
}
static func codexPriorityCostUSD(
model: String,
inputTokens: Int,
cachedInputTokens: Int = 0,
cacheWriteInputTokens: Int = 0,
outputTokens: Int) -> Double?
{
let key = self.normalizeCodexModel(model)
guard let pricing = self.codex[key],
let priorityInputCostPerToken = pricing.priorityInputCostPerToken,
let priorityOutputCostPerToken = pricing.priorityOutputCostPerToken
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,
inputTokens: inputTokens,
cachedInputTokens: cachedInputTokens,
cacheWriteInputTokens: cacheWriteInputTokens,
outputTokens: outputTokens)
}
private static func codexCostUSD(
pricing: CodexPricing,
inputTokens: Int,
cachedInputTokens: Int,
cacheWriteInputTokens: Int = 0,
outputTokens: Int) -> Double
{
// Codex/OpenAI reports `input_tokens` as the total prompt size, with cached reads as a
// SUBSET of it. Cache writes (when tracked separately, e.g. Pi) are also a subset of the
// non-cached remainder. Clamp so tokens are never invented or double-billed.
let totalInput = max(0, inputTokens)
let cached = min(max(0, cachedInputTokens), totalInput)
let remainingAfterCache = totalInput - cached
let cacheWrite = min(max(0, cacheWriteInputTokens), remainingAfterCache)
let nonCached = remainingAfterCache - cacheWrite
let cachedRate = pricing.cacheReadInputCostPerToken ?? pricing.inputCostPerToken
let usesLongContextRates = pricing.thresholdTokens.map { totalInput > $0 } ?? false
let inputRate = usesLongContextRates
? pricing.inputCostPerTokenAboveThreshold ?? pricing.inputCostPerToken
: pricing.inputCostPerToken
let cachedInputRate = usesLongContextRates
? pricing.cacheReadInputCostPerTokenAboveThreshold ?? pricing.cacheReadInputCostPerToken ?? inputRate
: cachedRate
let cacheWriteRate = usesLongContextRates
? pricing.cacheWriteInputCostPerTokenAboveThreshold
?? pricing.cacheWriteInputCostPerToken
?? inputRate
: pricing.cacheWriteInputCostPerToken ?? inputRate
let outputRate = usesLongContextRates
? pricing.outputCostPerTokenAboveThreshold ?? pricing.outputCostPerToken
: pricing.outputCostPerToken
return (Double(nonCached) * inputRate)
+ (Double(cached) * cachedInputRate)
+ (Double(cacheWrite) * cacheWriteRate)
+ (Double(max(0, outputTokens)) * outputRate)
}
private static func codexCostUSD(
pricing: ModelsDevPricingInfo,
thresholdTokens: Int? = nil,
inputCostPerTokenAboveThreshold: Double? = nil,
outputCostPerTokenAboveThreshold: Double? = nil,
cacheReadInputCostPerToken: Double? = nil,
cacheReadInputCostPerTokenAboveThreshold: Double? = nil,
cacheWriteInputCostPerToken: Double? = nil,
cacheWriteInputCostPerTokenAboveThreshold: Double? = nil,
inputTokens: Int,
cachedInputTokens: Int,
cacheWriteInputTokens: Int = 0,
outputTokens: Int) -> Double
{
self.codexCostUSD(
pricing: CodexPricing(
inputCostPerToken: pricing.inputCostPerToken,
outputCostPerToken: pricing.outputCostPerToken,
cacheReadInputCostPerToken: cacheReadInputCostPerToken
?? pricing.cacheReadInputCostPerToken,
displayLabel: nil,
cacheWriteInputCostPerToken: cacheWriteInputCostPerToken
?? pricing.cacheCreationInputCostPerToken,
thresholdTokens: thresholdTokens ?? pricing.thresholdTokens,
inputCostPerTokenAboveThreshold: inputCostPerTokenAboveThreshold
?? pricing.inputCostPerTokenAboveThreshold,
outputCostPerTokenAboveThreshold: outputCostPerTokenAboveThreshold
?? pricing.outputCostPerTokenAboveThreshold,
cacheReadInputCostPerTokenAboveThreshold: cacheReadInputCostPerTokenAboveThreshold
?? pricing.cacheReadInputCostPerTokenAboveThreshold,
cacheWriteInputCostPerTokenAboveThreshold: cacheWriteInputCostPerTokenAboveThreshold
?? pricing.cacheCreationInputCostPerTokenAboveThreshold),
inputTokens: inputTokens,
cachedInputTokens: cachedInputTokens,
cacheWriteInputTokens: cacheWriteInputTokens,
outputTokens: outputTokens)
}
static func claudeCostUSD(
model: String,
inputTokens: Int,
cacheReadInputTokens: Int,
cacheCreationInputTokens: Int,
cacheCreationInputTokens1h: Int = 0,
outputTokens: Int,
pricingDate: Date? = nil,
modelsDevCatalog: ModelsDevCatalog? = nil,
modelsDevCacheRoot: URL? = nil) -> Double?
{
let tokens = ClaudeCostTokens(
input: inputTokens,
cacheRead: cacheReadInputTokens,
cacheCreation: cacheCreationInputTokens,
cacheCreation1h: cacheCreationInputTokens1h,
output: outputTokens)
let key = self.normalizeClaudeModel(model)
if let pricingDate,
let historicalPricing = self.claudeHistoricalLongContext[key],
let currentPricing = self.claude[key]
{
return self.claudeCostUSD(
pricing: pricingDate < self.claudeFullContextStandardPricingCutoff
? historicalPricing
: currentPricing,
tokens: tokens)
}
if let lookup = self.modelsDevLookup(
providerID: self.claudeModelsDevProviderID,
model: model,
catalog: modelsDevCatalog,
cacheRoot: modelsDevCacheRoot)
{
return self.claudeCostUSD(
pricing: lookup.pricing,
tokens: tokens)
}
guard let pricing = self.claude[key] else { return nil }
return self.claudeCostUSD(
pricing: pricing,
tokens: tokens)
}
private static func claudeCostUSD(
pricing: ClaudePricing,
tokens: ClaudeCostTokens) -> Double
{
let input = max(0, tokens.input)
let cacheRead = max(0, tokens.cacheRead)
let cacheCreationTotal = max(0, tokens.cacheCreation)
let cacheCreation1h = min(max(0, tokens.cacheCreation1h), cacheCreationTotal)
let cacheCreation5m = cacheCreationTotal - cacheCreation1h
let usesLongContextRates = pricing.thresholdTokens.map {
input + cacheRead + cacheCreationTotal > $0
} ?? false
let inputRate = usesLongContextRates
? pricing.inputCostPerTokenAboveThreshold ?? pricing.inputCostPerToken
: pricing.inputCostPerToken
let cacheReadRate = usesLongContextRates
? pricing.cacheReadInputCostPerTokenAboveThreshold ?? pricing.cacheReadInputCostPerToken
: pricing.cacheReadInputCostPerToken
let cacheCreation5mRate = usesLongContextRates
? pricing.cacheCreationInputCostPerTokenAboveThreshold ?? pricing.cacheCreationInputCostPerToken
: pricing.cacheCreationInputCostPerToken
let outputRate = usesLongContextRates
? pricing.outputCostPerTokenAboveThreshold ?? pricing.outputCostPerToken
: pricing.outputCostPerToken
return Double(input) * inputRate
+ Double(cacheRead) * cacheReadRate
+ Double(cacheCreation5m) * cacheCreation5mRate
+ Double(cacheCreation1h) * inputRate * 2
+ Double(max(0, tokens.output)) * outputRate
}
private static func claudeCostUSD(
pricing: ModelsDevPricingInfo,
tokens: ClaudeCostTokens) -> Double
{
self.claudeCostUSD(
pricing: ClaudePricing(
inputCostPerToken: pricing.inputCostPerToken,
outputCostPerToken: pricing.outputCostPerToken,
cacheCreationInputCostPerToken: pricing.cacheCreationInputCostPerToken ?? pricing.inputCostPerToken,
cacheReadInputCostPerToken: pricing.cacheReadInputCostPerToken ?? pricing.inputCostPerToken,
thresholdTokens: pricing.thresholdTokens,
inputCostPerTokenAboveThreshold: pricing.inputCostPerTokenAboveThreshold,
outputCostPerTokenAboveThreshold: pricing.outputCostPerTokenAboveThreshold,
cacheCreationInputCostPerTokenAboveThreshold: pricing.cacheCreationInputCostPerTokenAboveThreshold,
cacheReadInputCostPerTokenAboveThreshold: pricing.cacheReadInputCostPerTokenAboveThreshold),
tokens: tokens)
}
static func modelsDevCatalog(now: Date = Date(), cacheRoot: URL? = nil) -> ModelsDevCatalog? {
ModelsDevCache.load(now: now, cacheRoot: cacheRoot).artifact?.catalog
}
private static func modelsDevLookup(
providerID: String,
model: String,
catalog: ModelsDevCatalog?,
cacheRoot: URL?) -> ModelsDevPricingLookup?
{
if let catalog {
return catalog.pricing(providerID: providerID, modelID: model)
}
return ModelsDevPricingPipeline.lookup(
providerID: providerID,
modelID: model,
cacheRoot: cacheRoot)
}
}