Files
orca/src/main/codex-usage/codex-model-pricing.ts
T
Rongzheng LiuandRongzheng Liu b2270cd184 fix(usage): include GPT-6.1 Sol in Codex cost estimates (#29707)
Price gpt-6.1-sol (and its effort/suffix variants) at its own base and
long-context rates instead of leaving it unpriced.

Co-authored-by: Rongzheng Liu <tianlrz@users.noreply.github.com>
2026-10-26 15:11:00 -07:00

205 lines
6.4 KiB
TypeScript

type CodexTokenRates = { input: number; cachedInput: number; output: number }
export type CodexModelPricing = CodexTokenRates & {
/** Long-context rates apply to the entire request, including cached input and output. */
longContext?: CodexTokenRates
}
export const LONG_CONTEXT_THRESHOLD_TOKENS = 272_000
export const MODEL_PRICING: Record<string, CodexModelPricing> = {
'gpt-5': { input: 1.25, cachedInput: 0.125, output: 10 },
'gpt-5.1': { input: 1.25, cachedInput: 0.125, output: 10 },
'gpt-5.1-codex': { input: 1.25, cachedInput: 0.125, output: 10 },
'gpt-5.1-codex-max': { input: 1.25, cachedInput: 0.125, output: 10 },
'gpt-5.2': { input: 1.75, cachedInput: 0.175, output: 14 },
'gpt-5.2-pro': { input: 21, cachedInput: 21, output: 168 },
'gpt-5.2-codex': { input: 1.75, cachedInput: 0.175, output: 14 },
'gpt-5.3': { input: 1.75, cachedInput: 0.175, output: 14 },
'gpt-5.3-codex': { input: 1.75, cachedInput: 0.175, output: 14 },
'gpt-5.3-codex-spark': { input: 1.75, cachedInput: 0.175, output: 14 },
'gpt-5.4-mini': { input: 0.75, cachedInput: 0.075, output: 4.5 },
'gpt-5.4-nano': { input: 0.2, cachedInput: 0.02, output: 1.25 },
'gpt-5.4-pro': {
input: 30,
cachedInput: 30,
output: 180,
longContext: { input: 60, cachedInput: 60, output: 270 }
},
'gpt-5.4': {
input: 2.5,
cachedInput: 0.25,
output: 15,
longContext: { input: 5, cachedInput: 0.5, output: 22.5 }
},
'gpt-5.5-pro': {
input: 30,
cachedInput: 30,
output: 180,
longContext: { input: 60, cachedInput: 60, output: 270 }
},
'gpt-5.5': {
input: 5,
cachedInput: 0.5,
output: 30,
longContext: { input: 10, cachedInput: 1, output: 45 }
},
// Promotional Sol rates are listed through at least 2026-11-21.
'gpt-5.6-sol': {
input: 4,
cachedInput: 0.4,
output: 20,
longContext: { input: 8, cachedInput: 0.8, output: 30 }
},
'gpt-5.6-terra': {
input: 2,
cachedInput: 0.2,
output: 12,
longContext: { input: 4, cachedInput: 0.4, output: 18 }
},
'gpt-5.6-luna': {
input: 0.2,
cachedInput: 0.02,
output: 1.2,
longContext: { input: 0.4, cachedInput: 0.04, output: 1.8 }
},
'gpt-6-astra': {
input: 10,
cachedInput: 1,
output: 50,
longContext: { input: 20, cachedInput: 2, output: 75 }
},
'gpt-6-sol': {
input: 2,
cachedInput: 0.2,
output: 10,
longContext: { input: 4, cachedInput: 0.4, output: 15 }
},
'gpt-6.1-sol': {
input: 2,
cachedInput: 0.1,
output: 10,
longContext: { input: 4, cachedInput: 0.2, output: 15 }
},
'gpt-6-luna': {
input: 0.1,
cachedInput: 0.01,
output: 0.5,
longContext: { input: 0.2, cachedInput: 0.02, output: 0.75 }
}
}
const REASONING_TIER_SUFFIXES = ['minimal', 'low', 'medium', 'high', 'xhigh', 'auto', 'none']
// Preserve model names ending in "-max"; only strip max/ultra inside parentheses.
const PARENTHESIZED_REASONING_TIERS = [...REASONING_TIER_SUFFIXES, 'max', 'ultra']
function stripParenthesizedReasoningTier(model: string): string | null {
const match = model.match(/^(.*)\(([^()]*)\)$/)
if (!match) {
return model
}
const tier = match[2].trim().toLowerCase()
if (!PARENTHESIZED_REASONING_TIERS.includes(tier)) {
return null
}
return match[1]
}
function stripDashReasoningTiers(model: string): string {
let current = model
for (let index = 0; index < 4; index++) {
const suffix = REASONING_TIER_SUFFIXES.find((tier) => current.endsWith(`-${tier}`))
if (!suffix) {
return current
}
current = current.slice(0, -suffix.length - 1)
}
return current
}
export function normalizeModelForPricing(model: string | null): string | null {
if (!model) {
return null
}
const lower = stripParenthesizedReasoningTier(model.toLowerCase().trim())
if (!lower) {
return null
}
const normalized = stripDashReasoningTiers(lower)
if (normalized === 'gpt-5' || normalized === 'gpt-5-codex') {
return 'gpt-5'
}
if (normalized === 'gpt-5.1-codex-max' || normalized.startsWith('gpt-5.1-codex-max-')) {
return 'gpt-5.1-codex-max'
}
if (normalized === 'gpt-5.1-codex' || normalized.startsWith('gpt-5.1-codex-')) {
return 'gpt-5.1-codex'
}
if (normalized === 'gpt-5.1' || normalized.startsWith('gpt-5.1-')) {
return 'gpt-5.1'
}
if (normalized === 'gpt-5.2-pro' || normalized.startsWith('gpt-5.2-pro-')) {
return 'gpt-5.2-pro'
}
if (normalized === 'gpt-5.2-codex' || normalized.startsWith('gpt-5.2-codex-')) {
return 'gpt-5.2-codex'
}
if (normalized === 'gpt-5.2' || normalized.startsWith('gpt-5.2-')) {
return 'gpt-5.2'
}
if (normalized === 'gpt-5.3-codex-spark' || normalized.startsWith('gpt-5.3-codex-spark-')) {
return 'gpt-5.3-codex-spark'
}
if (normalized === 'gpt-5.3-codex' || normalized.startsWith('gpt-5.3-codex-')) {
return 'gpt-5.3-codex'
}
if (normalized === 'gpt-5.3' || normalized.startsWith('gpt-5.3-')) {
return 'gpt-5.3'
}
if (normalized === 'gpt-5.4-mini' || normalized.startsWith('gpt-5.4-mini-')) {
return 'gpt-5.4-mini'
}
if (normalized === 'gpt-5.4-nano' || normalized.startsWith('gpt-5.4-nano-')) {
return 'gpt-5.4-nano'
}
if (normalized === 'gpt-5.4-pro' || normalized.startsWith('gpt-5.4-pro-')) {
return 'gpt-5.4-pro'
}
if (normalized === 'gpt-5.4' || normalized.startsWith('gpt-5.4-')) {
return 'gpt-5.4'
}
if (normalized === 'gpt-5.5-pro' || normalized.startsWith('gpt-5.5-pro-')) {
return 'gpt-5.5-pro'
}
if (normalized === 'gpt-5.5' || normalized.startsWith('gpt-5.5-')) {
return 'gpt-5.5'
}
if (normalized === 'gpt-5.6-sol' || normalized.startsWith('gpt-5.6-sol-')) {
return 'gpt-5.6-sol'
}
if (normalized === 'gpt-5.6-terra' || normalized.startsWith('gpt-5.6-terra-')) {
return 'gpt-5.6-terra'
}
if (normalized === 'gpt-5.6-luna' || normalized.startsWith('gpt-5.6-luna-')) {
return 'gpt-5.6-luna'
}
if (normalized === 'gpt-6-astra' || normalized.startsWith('gpt-6-astra-')) {
return 'gpt-6-astra'
}
if (normalized === 'gpt-6-sol' || normalized.startsWith('gpt-6-sol-')) {
return 'gpt-6-sol'
}
if (normalized === 'gpt-6.1-sol' || normalized.startsWith('gpt-6.1-sol-')) {
return 'gpt-6.1-sol'
}
if (normalized === 'gpt-6-luna' || normalized.startsWith('gpt-6-luna-')) {
return 'gpt-6-luna'
}
// Exact alias match avoids assigning Sol rates to future cheaper variants.
if (normalized === 'gpt-5.6') {
return 'gpt-5.6-sol'
}
return null
}