10BC0 fix(codex): fallback when direct model pricing is zero (#854) · ryoppippi/ccusage@ed46544 · GitHub
[go: up one dir, main page]

Skip to content

Commit ed46544

Browse files
authored
fix(codex): fallback when direct model pricing is zero (#854)
1 parent 0034ee1 commit ed46544

File tree

1 file changed

+65
-8
lines changed

1 file changed

+65
-8
lines changed

apps/codex/src/pricing.ts

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,18 @@ import { prefetchCodexPricing } from './_macro.ts' with { type: 'macro' };
77
import { logger } from './logger.ts';
88

99
const CODEX_PROVIDER_PREFIXES = ['openai/', 'azure/', 'openrouter/openai/'];
10-
const CODEX_MODEL_ALIASES_MAP = new Map<string, string>([['gpt-5-codex', 'gpt-5']]);
10+
const CODEX_MODEL_ALIASES_MAP = new Map<string, string>([
11+
['gpt-5-codex', 'gpt-5'],
12+
['gpt-5.3-codex', 'gpt-5.2-codex'],
13+
]);
14+
15+
function hasNonZeroTokenPricing(pricing: LiteLLMModelPricing): boolean {
16+
return (
17+
(pricing.input_cost_per_token ?? 0) > 0 ||
18+
(pricing.output_cost_per_token ?? 0) > 0 ||
19+
(pricing.cache_read_input_token_cost ?? 0) > 0
20+
);
21+
}
1122

1223
function toPerMillion(value: number | undefined, fallback?: number): number {
1324
const perToken = value ?? fallback ?? 0;
@@ -44,13 +55,13 @@ export class CodexPricingSource implements PricingSource, Disposable {
4455
}
4556

4657
let pricing = directLookup.value;
47-
if (pricing == null) {
48-
const alias = CODEX_MODEL_ALIASES_MAP.get(model);
49-
if (alias != null) {
50-
const aliasLookup = await this.fetcher.getModelPricing(alias);
51-
if (Result.isFailure(aliasLookup)) {
52-
throw aliasLookup.error;
53-
}
58+
const alias = CODEX_MODEL_ALIASES_MAP.get(model);
59+
if (alias != null && (pricing == null || !hasNonZeroTokenPricing(pricing))) {
60+
const aliasLookup = await this.fetcher.getModelPricing(alias);
61+
if (Result.isFailure(aliasLookup)) {
62+
throw aliasLookup.error;
63+
}
64+
if (aliasLookup.value != null && hasNonZeroTokenPricing(aliasLo 10BC0 okup.value)) {
5465
pricing = aliasLookup.value;
5566
}
5667
}
@@ -89,5 +100,51 @@ if (import.meta.vitest != null) {
89100
expect(pricing.outputCostPerMToken).toBeCloseTo(10);
90101
expect(pricing.cachedInputCostPerMToken).toBeCloseTo(0.125);
91102
});
103+
104+
it('falls back to alias pricing when direct model pricing is all zeros', async () => {
105+
using source = new CodexPricingSource({
106+
offline: true,
107+
offlineLoader: async () => ({
108+
'gpt-5.3-codex': {
109+
input_cost_per_token: 0,
110+
output_cost_per_token: 0,
111+
cache_read_input_token_cost: 0,
112+
},
113+
'gpt-5.2-codex': {
114+
input_cost_per_token: 1.75e-6,
115+
output_cost_per_token: 1.4e-5,
116+
cache_read_input_token_cost: 1.75e-7,
117+
},
118+
}),
119+
});
120+
121+
const pricing = await source.getPricing('gpt-5.3-codex');
122+
expect(pricing.inputCostPerMToken).toBeCloseTo(1.75);
123+
expect(pricing.outputCostPerMToken).toBeCloseTo(14);
124+
expect(pricing.cachedInputCostPerMToken).toBeCloseTo(0.175);
125+
});
126+
127+
it('prefers direct pricing when non-zero pricing is available', async () => {
128+
using source = new CodexPricingSource({
129+
offline: true,
130+
offlineLoader: async () => ({
131+
'gpt-5.3-codex': {
132+
input_cost_per_token: 1.9e-6,
133+
output_cost_per_token: 1.5e-5,
134+
cache_read_input_token_cost: 1.9e-7,
135+
},
136+
'gpt-5.2-codex': {
137+
input_cost_per_token: 1.75e-6,
138+
output_cost_per_token: 1.4e-5,
139+
cache_read_input_token_cost: 1.75e-7,
140+
},
141+
}),
142+
});
143+
144+
const pricing = await source.getPricing('gpt-5.3-codex');
145+
expect(pricing.inputCostPerMToken).toBeCloseTo(1.9);
146+
expect(pricing.outputCostPerMToken).toBeCloseTo(15);
147+
expect(pricing.cachedInputCostPerMToken).toBeCloseTo(0.19);
148+
});
92149
});
93150
}

0 commit comments

Comments
 (0)
0