feat: Expose GPT-5.6 max reasoning effort + long-context cache premium

Folds in the two deferred Codex findings:

1. Add `max` to the OpenAI `ReasoningEffort` enum and the reasoning_effort
   parameter options/labels so GPT-5.6 (Sol/Terra/Luna) can request its
   documented highest reasoning setting. Backend passthrough and zod
   validation pick it up via the nativeEnum schema.

2. Apply the long-context (>272K input) premium to cache tokens. Adds
   `premiumCacheTokenValues` + `getPremiumCacheRate`, threads
   `inputTokenCount` into `getCacheMultiplier`, and wires it through both
   structured-spend paths. Covers the gpt-5.4/5.5/5.6 family whose cache
   write/read previously stayed at flat base rates on long-context calls.
This commit is contained in:
Danny Avila 2026-07-10 14:49:14 -04:00
parent 4a01b0e402
commit da5c5194cf
7 changed files with 137 additions and 12 deletions

View file

@ -17,6 +17,7 @@ interface GetCacheMultiplierParams {
cacheType: 'write' | 'read';
model?: string;
endpointTokenConfig?: EndpointTokenConfig;
inputTokenCount?: number;
}
export interface PricingFns {
@ -124,11 +125,19 @@ function calculateStructuredTokenValue(
inputTokenCount,
});
const writeMultiplier =
pricing.getCacheMultiplier({ cacheType: 'write', model, endpointTokenConfig }) ??
inputMultiplier;
pricing.getCacheMultiplier({
cacheType: 'write',
model,
endpointTokenConfig,
inputTokenCount,
}) ?? inputMultiplier;
const readMultiplier =
pricing.getCacheMultiplier({ cacheType: 'read', model, endpointTokenConfig }) ??
inputMultiplier;
pricing.getCacheMultiplier({
cacheType: 'read',
model,
endpointTokenConfig,
inputTokenCount,
}) ?? inputMultiplier;
const inputAbs = Math.abs(txData.inputTokens ?? 0);
const writeAbs = Math.abs(txData.writeTokens ?? 0);

View file

@ -244,6 +244,7 @@ const openAIParams: Record<string, SettingDefinition> = {
ReasoningEffort.medium,
ReasoningEffort.high,
ReasoningEffort.xhigh,
ReasoningEffort.max,
],
enumMappings: {
[ReasoningEffort.unset]: 'com_ui_auto',
@ -253,6 +254,7 @@ const openAIParams: Record<string, SettingDefinition> = {
[ReasoningEffort.medium]: 'com_ui_medium',
[ReasoningEffort.high]: 'com_ui_high',
[ReasoningEffort.xhigh]: 'com_ui_xhigh',
[ReasoningEffort.max]: 'com_ui_max',
},
optionType: 'model',
columnSpan: 4,

View file

@ -1,9 +1,11 @@
import {
AnthropicEffort,
ReasoningEffort,
googleSettings,
anthropicSettings,
compactGoogleSchema,
eAnthropicEffortSchema,
eReasoningEffortSchema,
} from './schemas';
describe('anthropicSettings', () => {
@ -567,3 +569,20 @@ describe('AnthropicEffort', () => {
expect(() => eAnthropicEffortSchema.parse('ultra')).toThrow();
});
});
describe('ReasoningEffort', () => {
it('exposes max as the highest OpenAI reasoning effort, after xhigh', () => {
expect(ReasoningEffort.max).toBe('max');
const keys = Object.keys(ReasoningEffort);
expect(keys.indexOf('max')).toBeGreaterThan(keys.indexOf('xhigh'));
});
it('accepts max through the zod schema', () => {
expect(eReasoningEffortSchema.parse('max')).toBe('max');
expect(eReasoningEffortSchema.parse(ReasoningEffort.max)).toBe('max');
});
it('still rejects unknown effort values', () => {
expect(() => eReasoningEffortSchema.parse('ultra')).toThrow();
});
});

View file

@ -202,6 +202,7 @@ export enum ReasoningEffort {
medium = 'medium',
high = 'high',
xhigh = 'xhigh',
max = 'max',
}
export enum ReasoningParameterFormat {

View file

@ -17,6 +17,7 @@ type CacheMultiplierParams = {
cacheType?: 'write' | 'read';
model?: string;
endpointTokenConfig?: Record<string, Record<string, number>>;
inputTokenCount?: number;
};
/** Fields read/written by the internal token value calculators */
@ -142,10 +143,15 @@ export function createTransactionMethods(
cacheType: 'write',
model,
endpointTokenConfig: etConfig,
inputTokenCount,
}) ?? inputMultiplier;
const readMultiplier =
txMethods.getCacheMultiplier({ cacheType: 'read', model, endpointTokenConfig: etConfig }) ??
inputMultiplier;
txMethods.getCacheMultiplier({
cacheType: 'read',
model,
endpointTokenConfig: etConfig,
inputTokenCount,
}) ?? inputMultiplier;
txn.rateDetail = {
input: inputMultiplier,

View file

@ -5,6 +5,7 @@ import {
tokenValues,
cacheTokenValues,
premiumTokenValues,
premiumCacheTokenValues,
defaultRate,
} from './tx';
import { matchModelName, findMatchingPattern } from './test-helpers';
@ -464,10 +465,8 @@ describe('getMultiplier', () => {
});
it('should bill gpt-5.6 cache writes at the documented 1.25x input surcharge', () => {
const cacheWrites = { 'gpt-5.6': 6.25, 'gpt-5.6-terra': 3.125, 'gpt-5.6-luna': 1.25 };
for (const model of ['gpt-5.6', 'gpt-5.6-terra', 'gpt-5.6-luna'] as const) {
expect(cacheTokenValues[model].write).toBe(cacheWrites[model]);
expect(cacheTokenValues[model].write).toBe(tokenValues[model].prompt * 1.25);
for (const model of ['gpt-5.6', 'gpt-5.6-terra', 'gpt-5.6-luna']) {
expect(cacheTokenValues[model].write).toBeCloseTo(tokenValues[model].prompt * 1.25);
}
});
@ -2739,5 +2738,45 @@ describe('GPT-5.6 Long-Context Premium Pricing', () => {
});
});
describe('Long-Context Premium Cache Pricing', () => {
const premiumCacheModels = Object.keys(premiumCacheTokenValues);
it('should scale cache write/read by the same long-context multiplier as input', () => {
for (const model of premiumCacheModels) {
const inputRatio = premiumTokenValues[model].prompt / tokenValues[model].prompt;
expect(premiumCacheTokenValues[model].write).toBeCloseTo(
cacheTokenValues[model].write * inputRatio,
);
expect(premiumCacheTokenValues[model].read).toBeCloseTo(
cacheTokenValues[model].read * inputRatio,
);
}
});
it('should return premium cache rates above threshold and standard rates at/below', () => {
for (const model of premiumCacheModels) {
const { threshold } = premiumCacheTokenValues[model];
for (const cacheType of ['write', 'read'] as const) {
expect(getCacheMultiplier({ model, cacheType, inputTokenCount: threshold + 1 })).toBe(
premiumCacheTokenValues[model][cacheType],
);
expect(getCacheMultiplier({ model, cacheType, inputTokenCount: threshold })).toBe(
cacheTokenValues[model][cacheType],
);
expect(getCacheMultiplier({ model, cacheType })).toBe(cacheTokenValues[model][cacheType]);
}
}
});
it('should not apply premium cache rates to models without a premium cache entry', () => {
for (const model of ['gpt-5-mini', 'gpt-5.4-mini', 'gpt-4o']) {
expect(premiumCacheTokenValues[model]).toBeUndefined();
expect(getCacheMultiplier({ model, cacheType: 'read', inputTokenCount: 5_000_000 })).toBe(
getCacheMultiplier({ model, cacheType: 'read' }),
);
}
});
});
// Cross-package sync validation tests (tokens.ts ↔ tx.ts) moved to
// packages/api tests since they require maxTokensMap from @librechat/api.

View file

@ -387,6 +387,23 @@ export const premiumTokenValues: Record<
'gpt-5.6-luna': { threshold: 272000, prompt: 2, completion: 9 },
};
/**
* Premium (tiered) cache pricing for models whose cache rates change once the
* prompt crosses the long-context threshold. Cache write/read scale by the same
* multiplier the long-context tier applies to input (e.g. 2x for the gpt-5.x
* family), so these mirror `premiumTokenValues` on the cache dimension.
*/
export const premiumCacheTokenValues: Record<
string,
{ threshold: number; write: number; read: number }
> = {
'gpt-5.4': { threshold: 272000, write: 5, read: 0.5 },
'gpt-5.5': { threshold: 272000, write: 10, read: 1 },
'gpt-5.6': { threshold: 272000, write: 12.5, read: 1 },
'gpt-5.6-terra': { threshold: 272000, write: 6.25, read: 0.5 },
'gpt-5.6-luna': { threshold: 272000, write: 2.5, read: 0.2 },
};
export function createTxMethods(
_mongoose: typeof import('mongoose'),
txDeps: TxDeps,
@ -433,12 +450,14 @@ export function createTxMethods(
model,
endpoint,
endpointTokenConfig,
inputTokenCount,
}: {
valueKey?: string;
cacheType?: 'write' | 'read';
model?: string;
endpoint?: string;
endpointTokenConfig?: Record<string, Record<string, number>>;
inputTokenCount?: number | null;
}) => number | null;
defaultRate: number;
cacheTokenValues: Record<
@ -561,8 +580,28 @@ export function createTxMethods(
return tokenValues[valueKey]?.[tokenType] ?? defaultRate;
}
/**
* Checks if premium (tiered) cache pricing applies and returns the premium rate.
*/
function getPremiumCacheRate(
valueKey: string,
cacheType: 'write' | 'read',
inputTokenCount?: number | null,
): number | null {
if (inputTokenCount == null) {
return null;
}
const premiumEntry = premiumCacheTokenValues[valueKey];
if (!premiumEntry || inputTokenCount <= premiumEntry.threshold) {
return null;
}
return premiumEntry[cacheType] ?? null;
}
/**
* Retrieves the cache multiplier for a given value key and token type.
* When `inputTokenCount` crosses a model's long-context threshold, the
* premium cache rate applies instead of the standard one.
*/
function getCacheMultiplier({
valueKey,
@ -570,12 +609,14 @@ export function createTxMethods(
model,
endpoint,
endpointTokenConfig,
inputTokenCount,
}: {
valueKey?: string;
cacheType?: 'write' | 'read';
model?: string;
endpoint?: string;
endpointTokenConfig?: Record<string, Record<string, number>>;
inputTokenCount?: number | null;
}): number | null {
if (endpointTokenConfig && model) {
const modelConfig = endpointTokenConfig[model];
@ -587,7 +628,11 @@ export function createTxMethods(
}
if (valueKey && cacheType) {
return cacheTokenValues[valueKey]?.[cacheType] ?? null;
return (
getPremiumCacheRate(valueKey, cacheType, inputTokenCount) ??
cacheTokenValues[valueKey]?.[cacheType] ??
null
);
}
if (!cacheType || !model) {
@ -599,7 +644,11 @@ export function createTxMethods(
return null;
}
return cacheTokenValues[valueKey]?.[cacheType] ?? null;
return (
getPremiumCacheRate(valueKey, cacheType, inputTokenCount) ??
cacheTokenValues[valueKey]?.[cacheType] ??
null
);
}
return {