From da5c5194cf67f7d0e50af6fa317decaf49b698c4 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 10 Jul 2026 14:49:14 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Expose=20GPT-5.6=20max=20re?= =?UTF-8?q?asoning=20effort=20+=20long-context=20cache=20premium?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- packages/api/src/agents/transactions.ts | 17 ++++-- .../data-provider/src/parameterSettings.ts | 2 + packages/data-provider/src/schemas.spec.ts | 19 +++++++ packages/data-provider/src/schemas.ts | 1 + .../data-schemas/src/methods/transaction.ts | 10 +++- packages/data-schemas/src/methods/tx.spec.ts | 47 ++++++++++++++-- packages/data-schemas/src/methods/tx.ts | 53 ++++++++++++++++++- 7 files changed, 137 insertions(+), 12 deletions(-) diff --git a/packages/api/src/agents/transactions.ts b/packages/api/src/agents/transactions.ts index a9eeda1973..2a62c4a0bb 100644 --- a/packages/api/src/agents/transactions.ts +++ b/packages/api/src/agents/transactions.ts @@ -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); diff --git a/packages/data-provider/src/parameterSettings.ts b/packages/data-provider/src/parameterSettings.ts index 4f495307fd..ccf7dfc64c 100644 --- a/packages/data-provider/src/parameterSettings.ts +++ b/packages/data-provider/src/parameterSettings.ts @@ -244,6 +244,7 @@ const openAIParams: Record = { ReasoningEffort.medium, ReasoningEffort.high, ReasoningEffort.xhigh, + ReasoningEffort.max, ], enumMappings: { [ReasoningEffort.unset]: 'com_ui_auto', @@ -253,6 +254,7 @@ const openAIParams: Record = { [ReasoningEffort.medium]: 'com_ui_medium', [ReasoningEffort.high]: 'com_ui_high', [ReasoningEffort.xhigh]: 'com_ui_xhigh', + [ReasoningEffort.max]: 'com_ui_max', }, optionType: 'model', columnSpan: 4, diff --git a/packages/data-provider/src/schemas.spec.ts b/packages/data-provider/src/schemas.spec.ts index d15974f13b..d60a9c4b39 100644 --- a/packages/data-provider/src/schemas.spec.ts +++ b/packages/data-provider/src/schemas.spec.ts @@ -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(); + }); +}); diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 47e40bf589..169b5b3d69 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -202,6 +202,7 @@ export enum ReasoningEffort { medium = 'medium', high = 'high', xhigh = 'xhigh', + max = 'max', } export enum ReasoningParameterFormat { diff --git a/packages/data-schemas/src/methods/transaction.ts b/packages/data-schemas/src/methods/transaction.ts index bfd5376f71..ec39158db4 100644 --- a/packages/data-schemas/src/methods/transaction.ts +++ b/packages/data-schemas/src/methods/transaction.ts @@ -17,6 +17,7 @@ type CacheMultiplierParams = { cacheType?: 'write' | 'read'; model?: string; endpointTokenConfig?: Record>; + 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, diff --git a/packages/data-schemas/src/methods/tx.spec.ts b/packages/data-schemas/src/methods/tx.spec.ts index b70625cc74..67a28b01e8 100644 --- a/packages/data-schemas/src/methods/tx.spec.ts +++ b/packages/data-schemas/src/methods/tx.spec.ts @@ -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. diff --git a/packages/data-schemas/src/methods/tx.ts b/packages/data-schemas/src/methods/tx.ts index 8f66e9962a..fb8e199b64 100644 --- a/packages/data-schemas/src/methods/tx.ts +++ b/packages/data-schemas/src/methods/tx.ts @@ -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>; + 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>; + 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 {