@leo000001/opencode-quota-sidebar 4.0.9 → 4.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cost.js CHANGED
@@ -1,23 +1,28 @@
1
- import { asNumber, isRecord } from './helpers.js';
1
+ import { asNumber, isRecord } from "./helpers.js";
2
2
  export const API_COST_ENABLED_PROVIDERS = new Set([
3
- 'openai',
4
- 'anthropic',
5
- 'kimi-for-coding',
6
- 'zhipu',
7
- 'minimax-cn-coding-plan',
3
+ "openai",
4
+ "anthropic",
5
+ "kimi-for-coding",
6
+ "zhipu",
7
+ "minimax-cn-coding-plan",
8
8
  ]);
9
+ export const API_COST_RULES_VERSION = 2;
10
+ const MODEL_COST_STANDARD_UNIT = 1_000_000;
11
+ const MODEL_COST_RATE_UNIT_THRESHOLD = 0.001;
12
+ const OPENAI_FAST_COST_MULTIPLIER = 2;
13
+ const ANTHROPIC_FAST_COST_MULTIPLIER = 6;
9
14
  const MODEL_COST_RATE_ALIASES = {
10
- 'zhipuai-coding-plan:glm-5.1': ['zhipu:glm-5'],
11
- 'zhipuai-coding-plan:glm-5.1-thinking': ['zhipu:glm-5'],
12
- 'zhipu:glm-5.1': ['zhipu:glm-5'],
13
- 'zhipu:glm-5.1-thinking': ['zhipu:glm-5'],
15
+ "zhipuai-coding-plan:glm-5.1": ["zhipu:glm-5"],
16
+ "zhipuai-coding-plan:glm-5.1-thinking": ["zhipu:glm-5"],
17
+ "zhipu:glm-5.1": ["zhipu:glm-5"],
18
+ "zhipu:glm-5.1-thinking": ["zhipu:glm-5"],
14
19
  };
15
20
  function moonshotCanonicalModelID(modelID) {
16
- const stripped = modelID.replace(/^moonshotai[/:]/i, '');
21
+ const stripped = modelID.replace(/^moonshotai[/:]/i, "");
17
22
  switch (stripped) {
18
- case 'k2p5':
19
- case 'kimi-k2-5':
20
- return 'kimi-k2.5';
23
+ case "k2p5":
24
+ case "kimi-k2-5":
25
+ return "kimi-k2.5";
21
26
  default:
22
27
  return stripped;
23
28
  }
@@ -30,7 +35,7 @@ function moonshotModelAliases(modelID, options) {
30
35
  if (!aliases.includes(value))
31
36
  aliases.push(value);
32
37
  };
33
- const stripped = modelID.replace(/^moonshotai[/:]/i, '');
38
+ const stripped = modelID.replace(/^moonshotai[/:]/i, "");
34
39
  const canonical = moonshotCanonicalModelID(modelID);
35
40
  if (!options?.canonicalProviderKeys)
36
41
  push(modelID);
@@ -49,11 +54,11 @@ function minimaxModelAliases(modelID) {
49
54
  };
50
55
  push(modelID);
51
56
  push(modelID.toLowerCase());
52
- const stripped = modelID.replace(/^(?:minimax|minimax-cn-coding-plan)[/:]/i, '');
57
+ const stripped = modelID.replace(/^(?:minimax|minimax-cn-coding-plan)[/:]/i, "");
53
58
  push(stripped);
54
59
  push(stripped.toLowerCase());
55
60
  if (/^minimax-/i.test(stripped)) {
56
- const suffix = stripped.slice('minimax-'.length);
61
+ const suffix = stripped.slice("minimax-".length);
57
62
  push(`MiniMax-${suffix}`);
58
63
  push(`minimax-${suffix.toLowerCase()}`);
59
64
  }
@@ -77,19 +82,19 @@ function zhipuModelAliases(modelID) {
77
82
  push(modelID);
78
83
  for (let index = 0; index < queue.length; index++) {
79
84
  const stem = queue[index];
80
- const withoutProviderPrefix = stem.replace(/^(?:zhipu|z-ai|bigmodel|zhipuai-coding-plan)[/:]/, '');
85
+ const withoutProviderPrefix = stem.replace(/^(?:zhipu|z-ai|bigmodel|zhipuai-coding-plan)[/:]/, "");
81
86
  push(withoutProviderPrefix);
82
87
  push(`zhipu/${withoutProviderPrefix}`);
83
- const withoutBillingSuffix = withoutProviderPrefix.replace(/-billing$/, '');
88
+ const withoutBillingSuffix = withoutProviderPrefix.replace(/-billing$/, "");
84
89
  push(withoutBillingSuffix);
85
90
  push(`zhipu/${withoutBillingSuffix}`);
86
- const withoutThinkingSuffix = withoutBillingSuffix.replace(/-thinking$/, '');
91
+ const withoutThinkingSuffix = withoutBillingSuffix.replace(/-thinking$/, "");
87
92
  push(withoutThinkingSuffix);
88
93
  push(`zhipu/${withoutThinkingSuffix}`);
89
- const dotted = withoutThinkingSuffix.replace(/(\d)-(\d)(?=-|$)/g, '$1.$2');
94
+ const dotted = withoutThinkingSuffix.replace(/(\d)-(\d)(?=-|$)/g, "$1.$2");
90
95
  push(dotted);
91
96
  push(`zhipu/${dotted}`);
92
- const hyphenated = withoutThinkingSuffix.replace(/(\d)\.(\d)(?=-|$)/g, '$1-$2');
97
+ const hyphenated = withoutThinkingSuffix.replace(/(\d)\.(\d)(?=-|$)/g, "$1-$2");
93
98
  push(hyphenated);
94
99
  push(`zhipu/${hyphenated}`);
95
100
  }
@@ -110,71 +115,92 @@ function anthropicModelAliases(modelID) {
110
115
  for (let index = 0; index < queue.length; index++) {
111
116
  const stem = queue[index];
112
117
  const withoutProviderPrefix = stem
113
- .replace(/^(?:[a-z]+\.)*anthropic\./, '')
114
- .replace(/^anthropic[/.]/, '');
118
+ .replace(/^(?:[a-z]+\.)*anthropic\./, "")
119
+ .replace(/^anthropic[/.]/, "");
115
120
  push(withoutProviderPrefix);
116
121
  push(`anthropic/${withoutProviderPrefix}`);
117
- const withoutVersionSuffix = withoutProviderPrefix.replace(/-v\d+(?::\d+)?$/, '');
122
+ const withoutVersionSuffix = withoutProviderPrefix.replace(/-v\d+(?::\d+)?$/, "");
118
123
  push(withoutVersionSuffix);
119
124
  push(`anthropic/${withoutVersionSuffix}`);
120
- const atDate = withoutVersionSuffix.replace(/@(\d{8})$/, '-$1');
125
+ const atDate = withoutVersionSuffix.replace(/@(\d{8})$/, "-$1");
121
126
  push(atDate);
122
127
  push(`anthropic/${atDate}`);
123
- const withAtDate = withoutVersionSuffix.replace(/-(\d{8})$/, '@$1');
128
+ const withAtDate = withoutVersionSuffix.replace(/-(\d{8})$/, "@$1");
124
129
  push(withAtDate);
125
130
  push(`anthropic/${withAtDate}`);
126
- const withoutThinkingSuffix = withoutVersionSuffix.replace(/-thinking$/, '');
131
+ const withoutThinkingSuffix = withoutVersionSuffix.replace(/-thinking$/, "");
127
132
  push(withoutThinkingSuffix);
128
133
  push(`anthropic/${withoutThinkingSuffix}`);
129
- const withoutLatestSuffix = withoutThinkingSuffix.replace(/-latest$/, '');
134
+ const withoutLatestSuffix = withoutThinkingSuffix.replace(/-latest$/, "");
130
135
  push(withoutLatestSuffix);
131
136
  push(`anthropic/${withoutLatestSuffix}`);
132
137
  const withoutDateSuffix = withoutLatestSuffix
133
- .replace(/-\d{8}$/, '')
134
- .replace(/@\d{8}$/, '');
138
+ .replace(/-\d{8}$/, "")
139
+ .replace(/@\d{8}$/, "");
135
140
  push(withoutDateSuffix);
136
141
  push(`anthropic/${withoutDateSuffix}`);
137
- const dotted = withoutDateSuffix.replace(/(\d)-(\d)(?=-|$)/g, '$1.$2');
142
+ const dotted = withoutDateSuffix.replace(/(\d)-(\d)(?=-|$)/g, "$1.$2");
138
143
  push(dotted);
139
144
  push(`anthropic/${dotted}`);
140
- const hyphenated = withoutDateSuffix.replace(/(\d)\.(\d)(?=-|$)/g, '$1-$2');
145
+ const hyphenated = withoutDateSuffix.replace(/(\d)\.(\d)(?=-|$)/g, "$1-$2");
141
146
  push(hyphenated);
142
147
  push(`anthropic/${hyphenated}`);
143
148
  }
144
149
  return aliases;
145
150
  }
146
151
  function normalizeKnownProviderID(providerID) {
147
- if (providerID.startsWith('github-copilot'))
148
- return 'github-copilot';
152
+ if (providerID.toLowerCase().startsWith("github-copilot")) {
153
+ return "github-copilot";
154
+ }
149
155
  return providerID;
150
156
  }
157
+ function isOpenAICompatibleProviderID(providerID) {
158
+ return (providerID === "openai" ||
159
+ providerID.startsWith("openai-") ||
160
+ providerID.endsWith("-openai") ||
161
+ providerID.startsWith("openai/") ||
162
+ providerID.endsWith("/openai") ||
163
+ providerID.includes(".openai.") ||
164
+ providerID.endsWith("-oai"));
165
+ }
166
+ function isAnthropicCompatibleProviderID(providerID) {
167
+ return (providerID === "anthropic" ||
168
+ providerID === "claude" ||
169
+ providerID.startsWith("anthropic-") ||
170
+ providerID.endsWith("-anthropic") ||
171
+ providerID.startsWith("anthropic/") ||
172
+ providerID.endsWith("/anthropic") ||
173
+ providerID.includes(".anthropic.") ||
174
+ providerID.startsWith("claude-") ||
175
+ providerID.endsWith("-claude"));
176
+ }
151
177
  function isCanonicalZhipuProviderID(providerID) {
152
- return (providerID === 'zhipu' ||
153
- providerID === 'bigmodel' ||
154
- providerID === 'z-ai' ||
155
- providerID === 'zhipuai-coding-plan');
178
+ return (providerID === "zhipu" ||
179
+ providerID === "bigmodel" ||
180
+ providerID === "z-ai" ||
181
+ providerID === "zhipuai-coding-plan");
156
182
  }
157
183
  function isCanonicalMiniMaxProviderID(providerID) {
158
- return providerID === 'minimax' || providerID === 'minimax-cn-coding-plan';
184
+ return providerID === "minimax" || providerID === "minimax-cn-coding-plan";
159
185
  }
160
186
  export function canonicalPricingProviderID(providerID) {
161
187
  const normalized = normalizeKnownProviderID(providerID);
162
188
  const lowered = normalized.toLowerCase();
163
189
  if (isCanonicalMiniMaxProviderID(lowered)) {
164
- return 'minimax';
190
+ return "minimax";
165
191
  }
166
192
  if (isCanonicalZhipuProviderID(lowered)) {
167
- return 'zhipu';
193
+ return "zhipu";
168
194
  }
169
- if (lowered === 'kimi-for-coding')
170
- return 'moonshotai';
171
- if (lowered.includes('anthropic') || lowered.includes('claude')) {
172
- return 'anthropic';
195
+ if (lowered === "kimi-for-coding")
196
+ return "moonshotai";
197
+ if (isAnthropicCompatibleProviderID(lowered)) {
198
+ return "anthropic";
173
199
  }
174
- if (lowered.includes('openai') || lowered.endsWith('-oai'))
175
- return 'openai';
176
- if (lowered.includes('copilot'))
177
- return 'github-copilot';
200
+ if (isOpenAICompatibleProviderID(lowered))
201
+ return "openai";
202
+ if (lowered.includes("copilot"))
203
+ return "github-copilot";
178
204
  return normalized;
179
205
  }
180
206
  export function canonicalApiCostProviderID(providerID) {
@@ -182,17 +208,17 @@ export function canonicalApiCostProviderID(providerID) {
182
208
  const lowered = normalized.toLowerCase();
183
209
  if (API_COST_ENABLED_PROVIDERS.has(lowered))
184
210
  return lowered;
185
- if (lowered === 'minimax')
186
- return 'minimax-cn-coding-plan';
187
- if (lowered.includes('copilot'))
188
- return 'github-copilot';
189
- if (lowered.includes('openai') || lowered.endsWith('-oai'))
190
- return 'openai';
191
- if (lowered.includes('anthropic') || lowered.includes('claude')) {
192
- return 'anthropic';
211
+ if (lowered === "minimax")
212
+ return "minimax-cn-coding-plan";
213
+ if (lowered.includes("copilot"))
214
+ return "github-copilot";
215
+ if (isOpenAICompatibleProviderID(lowered))
216
+ return "openai";
217
+ if (isAnthropicCompatibleProviderID(lowered)) {
218
+ return "anthropic";
193
219
  }
194
220
  if (isCanonicalZhipuProviderID(lowered)) {
195
- return 'zhipu';
221
+ return "zhipu";
196
222
  }
197
223
  return normalized;
198
224
  }
@@ -251,325 +277,325 @@ function minimaxPricing(input, output, cacheRead, cacheWrite) {
251
277
  }
252
278
  const BUNDLED_CANONICAL_PRICE_ENTRIES = [
253
279
  {
254
- provider: 'openai',
280
+ provider: "openai",
255
281
  // OpenCode commonly reports the flagship alias as `gpt-5`; keep a bundled
256
282
  // fallback so API-equivalent cost still renders when runtime metadata omits
257
283
  // OpenAI pricing on a given client or subscription path.
258
- model: 'gpt-5',
284
+ model: "gpt-5",
259
285
  rates: openaiPricing(2.5, 15, 0.25),
260
- source: 'official-doc',
261
- sourceURL: 'https://openai.com/api/pricing/',
286
+ source: "official-doc",
287
+ sourceURL: "https://openai.com/api/pricing/",
262
288
  },
263
289
  {
264
- provider: 'openai',
265
- model: 'gpt-5.3',
290
+ provider: "openai",
291
+ model: "gpt-5.3",
266
292
  rates: openaiPricing(1.75, 14, 0.175),
267
- source: 'official-doc',
268
- sourceURL: 'https://developers.openai.com/api/docs/models/gpt-5.3-chat-latest',
293
+ source: "official-doc",
294
+ sourceURL: "https://developers.openai.com/api/docs/models/gpt-5.3-chat-latest",
269
295
  },
270
296
  {
271
- provider: 'openai',
272
- model: 'gpt-5.3-chat-latest',
297
+ provider: "openai",
298
+ model: "gpt-5.3-chat-latest",
273
299
  rates: openaiPricing(1.75, 14, 0.175),
274
- source: 'official-doc',
275
- sourceURL: 'https://developers.openai.com/api/docs/models/gpt-5.3-chat-latest',
300
+ source: "official-doc",
301
+ sourceURL: "https://developers.openai.com/api/docs/models/gpt-5.3-chat-latest",
276
302
  },
277
303
  {
278
- provider: 'openai',
279
- model: 'gpt-5.3-codex',
304
+ provider: "openai",
305
+ model: "gpt-5.3-codex",
280
306
  rates: openaiPricing(1.75, 14, 0.175),
281
- source: 'official-doc',
282
- sourceURL: 'https://developers.openai.com/api/docs/models/gpt-5.3-codex',
307
+ source: "official-doc",
308
+ sourceURL: "https://developers.openai.com/api/docs/models/gpt-5.3-codex",
283
309
  },
284
310
  {
285
- provider: 'openai',
286
- model: 'gpt-5.2',
311
+ provider: "openai",
312
+ model: "gpt-5.2",
287
313
  rates: openaiPricing(1.75, 14, 0.175),
288
- source: 'official-doc',
289
- sourceURL: 'https://developers.openai.com/api/docs/models/gpt-5.2',
314
+ source: "official-doc",
315
+ sourceURL: "https://developers.openai.com/api/docs/models/gpt-5.2",
290
316
  },
291
317
  {
292
- provider: 'openai',
293
- model: 'gpt-5.2-chat-latest',
318
+ provider: "openai",
319
+ model: "gpt-5.2-chat-latest",
294
320
  rates: openaiPricing(1.75, 14, 0.175),
295
- source: 'official-doc',
296
- sourceURL: 'https://developers.openai.com/api/docs/models/gpt-5.2-chat-latest',
321
+ source: "official-doc",
322
+ sourceURL: "https://developers.openai.com/api/docs/models/gpt-5.2-chat-latest",
297
323
  },
298
324
  {
299
- provider: 'openai',
300
- model: 'gpt-5.2-pro',
325
+ provider: "openai",
326
+ model: "gpt-5.2-pro",
301
327
  rates: openaiPricing(21, 168, 0),
302
- source: 'official-doc',
303
- sourceURL: 'https://openai.com/index/introducing-gpt-5-2/',
328
+ source: "official-doc",
329
+ sourceURL: "https://openai.com/index/introducing-gpt-5-2/",
304
330
  },
305
331
  {
306
- provider: 'openai',
307
- model: 'gpt-5.4',
332
+ provider: "openai",
333
+ model: "gpt-5.4",
308
334
  rates: openaiPricing(2.5, 15, 0.25),
309
- source: 'official-doc',
310
- sourceURL: 'https://openai.com/api/pricing/',
335
+ source: "official-doc",
336
+ sourceURL: "https://openai.com/api/pricing/",
311
337
  },
312
338
  {
313
- provider: 'openai',
314
- model: 'gpt-5-mini',
339
+ provider: "openai",
340
+ model: "gpt-5-mini",
315
341
  rates: openaiPricing(0.75, 4.5, 0.075),
316
- source: 'official-doc',
317
- sourceURL: 'https://openai.com/api/pricing/',
342
+ source: "official-doc",
343
+ sourceURL: "https://openai.com/api/pricing/",
318
344
  },
319
345
  {
320
- provider: 'openai',
321
- model: 'gpt-5.4-mini',
346
+ provider: "openai",
347
+ model: "gpt-5.4-mini",
322
348
  rates: openaiPricing(0.75, 4.5, 0.075),
323
- source: 'official-doc',
324
- sourceURL: 'https://openai.com/api/pricing/',
349
+ source: "official-doc",
350
+ sourceURL: "https://openai.com/api/pricing/",
325
351
  },
326
352
  {
327
- provider: 'openai',
328
- model: 'gpt-5-nano',
353
+ provider: "openai",
354
+ model: "gpt-5-nano",
329
355
  rates: openaiPricing(0.2, 1.25, 0.02),
330
- source: 'official-doc',
331
- sourceURL: 'https://openai.com/api/pricing/',
356
+ source: "official-doc",
357
+ sourceURL: "https://openai.com/api/pricing/",
332
358
  },
333
359
  {
334
- provider: 'openai',
335
- model: 'gpt-5.4-nano',
360
+ provider: "openai",
361
+ model: "gpt-5.4-nano",
336
362
  rates: openaiPricing(0.2, 1.25, 0.02),
337
- source: 'official-doc',
338
- sourceURL: 'https://openai.com/api/pricing/',
363
+ source: "official-doc",
364
+ sourceURL: "https://openai.com/api/pricing/",
339
365
  },
340
366
  {
341
- provider: 'anthropic',
342
- model: 'claude-opus-4-6',
367
+ provider: "anthropic",
368
+ model: "claude-opus-4-6",
343
369
  rates: anthropicPricing(5, 25),
344
- source: 'official-doc',
345
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
370
+ source: "official-doc",
371
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
346
372
  },
347
373
  {
348
- provider: 'anthropic',
349
- model: 'claude-opus-4-5',
374
+ provider: "anthropic",
375
+ model: "claude-opus-4-5",
350
376
  rates: anthropicPricing(5, 25),
351
- source: 'official-doc',
352
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
377
+ source: "official-doc",
378
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
353
379
  },
354
380
  {
355
- provider: 'anthropic',
356
- model: 'claude-opus-4-1',
381
+ provider: "anthropic",
382
+ model: "claude-opus-4-1",
357
383
  rates: anthropicPricing(15, 75),
358
- source: 'official-doc',
359
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
384
+ source: "official-doc",
385
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
360
386
  },
361
387
  {
362
- provider: 'anthropic',
363
- model: 'claude-opus-4',
388
+ provider: "anthropic",
389
+ model: "claude-opus-4",
364
390
  rates: anthropicPricing(15, 75),
365
- source: 'official-doc',
366
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
391
+ source: "official-doc",
392
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
367
393
  },
368
394
  {
369
- provider: 'anthropic',
370
- model: 'claude-sonnet-4-6',
395
+ provider: "anthropic",
396
+ model: "claude-sonnet-4-6",
371
397
  rates: anthropicPricing(3, 15),
372
- source: 'official-doc',
373
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
398
+ source: "official-doc",
399
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
374
400
  },
375
401
  {
376
- provider: 'anthropic',
377
- model: 'claude-sonnet-4-5',
402
+ provider: "anthropic",
403
+ model: "claude-sonnet-4-5",
378
404
  rates: anthropicPricing(3, 15, {
379
405
  longContextInput: 6,
380
406
  longContextOutput: 22.5,
381
407
  }),
382
- source: 'official-doc',
383
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
408
+ source: "official-doc",
409
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
384
410
  },
385
411
  {
386
- provider: 'anthropic',
387
- model: 'claude-sonnet-4',
412
+ provider: "anthropic",
413
+ model: "claude-sonnet-4",
388
414
  rates: anthropicPricing(3, 15, {
389
415
  longContextInput: 6,
390
416
  longContextOutput: 22.5,
391
417
  }),
392
- source: 'official-doc',
393
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
418
+ source: "official-doc",
419
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
394
420
  },
395
421
  {
396
- provider: 'anthropic',
397
- model: 'claude-3-7-sonnet',
422
+ provider: "anthropic",
423
+ model: "claude-3-7-sonnet",
398
424
  rates: anthropicPricing(3, 15),
399
- source: 'official-doc',
400
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
425
+ source: "official-doc",
426
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
401
427
  },
402
428
  {
403
- provider: 'anthropic',
404
- model: 'claude-3-5-sonnet',
429
+ provider: "anthropic",
430
+ model: "claude-3-5-sonnet",
405
431
  rates: anthropicPricing(3, 15),
406
- source: 'official-doc',
407
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
432
+ source: "official-doc",
433
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
408
434
  },
409
435
  {
410
- provider: 'anthropic',
411
- model: 'claude-haiku-4-5',
436
+ provider: "anthropic",
437
+ model: "claude-haiku-4-5",
412
438
  rates: anthropicPricing(1, 5),
413
- source: 'official-doc',
414
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
439
+ source: "official-doc",
440
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
415
441
  },
416
442
  {
417
- provider: 'anthropic',
418
- model: 'claude-3-5-haiku',
443
+ provider: "anthropic",
444
+ model: "claude-3-5-haiku",
419
445
  rates: anthropicPricing(0.8, 4),
420
- source: 'official-doc',
421
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
446
+ source: "official-doc",
447
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
422
448
  },
423
449
  {
424
- provider: 'anthropic',
425
- model: 'claude-3-opus',
450
+ provider: "anthropic",
451
+ model: "claude-3-opus",
426
452
  rates: anthropicPricing(15, 75),
427
- source: 'official-doc',
428
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
453
+ source: "official-doc",
454
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
429
455
  },
430
456
  {
431
- provider: 'anthropic',
432
- model: 'claude-3-haiku',
457
+ provider: "anthropic",
458
+ model: "claude-3-haiku",
433
459
  rates: anthropicPricing(0.25, 1.25),
434
- source: 'official-doc',
435
- sourceURL: 'https://docs.anthropic.com/en/docs/about-claude/pricing',
460
+ source: "official-doc",
461
+ sourceURL: "https://docs.anthropic.com/en/docs/about-claude/pricing",
436
462
  },
437
463
  {
438
- provider: 'zhipu',
439
- model: 'glm-5',
464
+ provider: "zhipu",
465
+ model: "glm-5",
440
466
  rates: zhipuPricing(1, 3.2, 0.2),
441
- source: 'official-doc',
442
- sourceURL: 'https://docs.z.ai/guides/overview/pricing',
467
+ source: "official-doc",
468
+ sourceURL: "https://docs.z.ai/guides/overview/pricing",
443
469
  },
444
470
  {
445
- provider: 'zhipu',
446
- model: 'glm-4.7',
471
+ provider: "zhipu",
472
+ model: "glm-4.7",
447
473
  rates: zhipuPricing(0.6, 2.2, 0.11),
448
- source: 'official-doc',
449
- sourceURL: 'https://docs.z.ai/guides/overview/pricing',
474
+ source: "official-doc",
475
+ sourceURL: "https://docs.z.ai/guides/overview/pricing",
450
476
  },
451
477
  {
452
- provider: 'zhipu',
453
- model: 'glm-4.6',
478
+ provider: "zhipu",
479
+ model: "glm-4.6",
454
480
  rates: zhipuPricing(0.6, 2.2, 0.11),
455
- source: 'official-doc',
456
- sourceURL: 'https://docs.z.ai/guides/overview/pricing',
481
+ source: "official-doc",
482
+ sourceURL: "https://docs.z.ai/guides/overview/pricing",
457
483
  },
458
484
  {
459
- provider: 'zhipu',
460
- model: 'glm-4.6v',
485
+ provider: "zhipu",
486
+ model: "glm-4.6v",
461
487
  rates: zhipuPricing(0.3, 0.9, 0.05),
462
- source: 'official-doc',
463
- sourceURL: 'https://docs.z.ai/guides/overview/pricing',
488
+ source: "official-doc",
489
+ sourceURL: "https://docs.z.ai/guides/overview/pricing",
464
490
  },
465
491
  {
466
- provider: 'zhipu',
467
- model: 'glm-4.5',
492
+ provider: "zhipu",
493
+ model: "glm-4.5",
468
494
  rates: zhipuPricing(0.6, 2.2, 0.11),
469
- source: 'official-doc',
470
- sourceURL: 'https://docs.z.ai/guides/overview/pricing',
495
+ source: "official-doc",
496
+ sourceURL: "https://docs.z.ai/guides/overview/pricing",
471
497
  },
472
498
  {
473
- provider: 'zhipu',
474
- model: 'glm-4.5-air',
499
+ provider: "zhipu",
500
+ model: "glm-4.5-air",
475
501
  rates: zhipuPricing(0.2, 1.1, 0.03),
476
- source: 'official-doc',
477
- sourceURL: 'https://docs.z.ai/guides/overview/pricing',
502
+ source: "official-doc",
503
+ sourceURL: "https://docs.z.ai/guides/overview/pricing",
478
504
  },
479
505
  {
480
- provider: 'zhipu',
481
- model: 'glm-4.5v',
506
+ provider: "zhipu",
507
+ model: "glm-4.5v",
482
508
  rates: zhipuPricing(0.6, 1.8, 0.11),
483
- source: 'official-doc',
484
- sourceURL: 'https://docs.z.ai/guides/overview/pricing',
509
+ source: "official-doc",
510
+ sourceURL: "https://docs.z.ai/guides/overview/pricing",
485
511
  },
486
512
  {
487
- provider: 'moonshotai',
488
- model: 'kimi-k2.5',
513
+ provider: "moonshotai",
514
+ model: "kimi-k2.5",
489
515
  rates: moonshotPricing(0.6, 3, 0.1),
490
- source: 'official-doc',
491
- sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
516
+ source: "official-doc",
517
+ sourceURL: "https://platform.moonshot.ai/docs/pricing/chat",
492
518
  },
493
519
  {
494
- provider: 'moonshotai',
495
- model: 'kimi-k2-thinking',
520
+ provider: "moonshotai",
521
+ model: "kimi-k2-thinking",
496
522
  rates: moonshotPricing(0.6, 2.5, 0.15),
497
- source: 'official-doc',
498
- sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
523
+ source: "official-doc",
524
+ sourceURL: "https://platform.moonshot.ai/docs/pricing/chat",
499
525
  },
500
526
  {
501
- provider: 'moonshotai',
502
- model: 'kimi-k2-0711-preview',
527
+ provider: "moonshotai",
528
+ model: "kimi-k2-0711-preview",
503
529
  rates: moonshotPricing(0.6, 2.5, 0.15),
504
- source: 'official-doc',
505
- sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
530
+ source: "official-doc",
531
+ sourceURL: "https://platform.moonshot.ai/docs/pricing/chat",
506
532
  },
507
533
  {
508
- provider: 'moonshotai',
509
- model: 'kimi-k2-0905-preview',
534
+ provider: "moonshotai",
535
+ model: "kimi-k2-0905-preview",
510
536
  rates: moonshotPricing(0.6, 2.5, 0.15),
511
- source: 'official-doc',
512
- sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
537
+ source: "official-doc",
538
+ sourceURL: "https://platform.moonshot.ai/docs/pricing/chat",
513
539
  },
514
540
  {
515
- provider: 'moonshotai',
516
- model: 'kimi-k2-turbo-preview',
541
+ provider: "moonshotai",
542
+ model: "kimi-k2-turbo-preview",
517
543
  rates: moonshotPricing(2.4, 10, 0.6),
518
- source: 'official-doc',
519
- sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
544
+ source: "official-doc",
545
+ sourceURL: "https://platform.moonshot.ai/docs/pricing/chat",
520
546
  },
521
547
  {
522
- provider: 'moonshotai',
523
- model: 'kimi-k2-thinking-turbo',
548
+ provider: "moonshotai",
549
+ model: "kimi-k2-thinking-turbo",
524
550
  rates: moonshotPricing(1.15, 8, 0.15),
525
- source: 'official-doc',
526
- sourceURL: 'https://platform.moonshot.ai/docs/pricing/chat',
551
+ source: "official-doc",
552
+ sourceURL: "https://platform.moonshot.ai/docs/pricing/chat",
527
553
  },
528
554
  {
529
- provider: 'minimax',
530
- model: 'MiniMax-M2.7',
555
+ provider: "minimax",
556
+ model: "MiniMax-M2.7",
531
557
  // OpenCode sources provider pricing from models.dev. The bundled MiniMax
532
558
  // fallback mirrors those USD-denominated entries rather than the CN RMB
533
559
  // docs so API-equivalent cost stays on the same currency basis as the rest
534
560
  // of the sidebar/report output.
535
561
  rates: minimaxPricing(0.3, 1.2, 0.06, 0.375),
536
- source: 'runtime',
537
- sourceURL: 'https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.7.toml',
562
+ source: "runtime",
563
+ sourceURL: "https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.7.toml",
538
564
  },
539
565
  {
540
- provider: 'minimax',
541
- model: 'MiniMax-M2.7-highspeed',
566
+ provider: "minimax",
567
+ model: "MiniMax-M2.7-highspeed",
542
568
  rates: minimaxPricing(0.6, 2.4, 0.06, 0.375),
543
- source: 'runtime',
544
- sourceURL: 'https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.7-highspeed.toml',
569
+ source: "runtime",
570
+ sourceURL: "https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.7-highspeed.toml",
545
571
  },
546
572
  {
547
- provider: 'minimax',
548
- model: 'MiniMax-M2.5',
573
+ provider: "minimax",
574
+ model: "MiniMax-M2.5",
549
575
  rates: minimaxPricing(0.3, 1.2, 0.03, 0.375),
550
- source: 'runtime',
551
- sourceURL: 'https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.5.toml',
576
+ source: "runtime",
577
+ sourceURL: "https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.5.toml",
552
578
  },
553
579
  {
554
- provider: 'minimax',
555
- model: 'MiniMax-M2.5-highspeed',
580
+ provider: "minimax",
581
+ model: "MiniMax-M2.5-highspeed",
556
582
  rates: minimaxPricing(0.6, 2.4, 0.06, 0.375),
557
- source: 'runtime',
558
- sourceURL: 'https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.5-highspeed.toml',
583
+ source: "runtime",
584
+ sourceURL: "https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.5-highspeed.toml",
559
585
  },
560
586
  {
561
- provider: 'minimax',
562
- model: 'MiniMax-M2.1',
587
+ provider: "minimax",
588
+ model: "MiniMax-M2.1",
563
589
  rates: minimaxPricing(0.3, 1.2, 0.03, 0.375),
564
- source: 'runtime',
565
- sourceURL: 'https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.1.toml',
590
+ source: "runtime",
591
+ sourceURL: "https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.1.toml",
566
592
  },
567
593
  {
568
- provider: 'minimax',
569
- model: 'MiniMax-M2',
594
+ provider: "minimax",
595
+ model: "MiniMax-M2",
570
596
  rates: minimaxPricing(0.3, 1.2, 0, 0),
571
- source: 'runtime',
572
- sourceURL: 'https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.toml',
597
+ source: "runtime",
598
+ sourceURL: "https://github.com/anomalyco/models.dev/blob/dev/providers/minimax/models/MiniMax-M2.toml",
573
599
  },
574
600
  ];
575
601
  export function modelCostKey(providerID, modelID) {
@@ -582,13 +608,13 @@ export function modelCostLookupKeys(providerID, modelID) {
582
608
  if (!keys.includes(key))
583
609
  keys.push(key);
584
610
  };
585
- const modelIDsFor = (options) => canonicalProviderID === 'anthropic'
611
+ const modelIDsFor = (options) => canonicalProviderID === "anthropic"
586
612
  ? anthropicModelAliases(modelID)
587
- : canonicalProviderID === 'zhipu'
613
+ : canonicalProviderID === "zhipu"
588
614
  ? zhipuModelAliases(modelID)
589
- : canonicalProviderID === 'moonshotai'
615
+ : canonicalProviderID === "moonshotai"
590
616
  ? moonshotModelAliases(modelID, options)
591
- : canonicalProviderID === 'minimax'
617
+ : canonicalProviderID === "minimax"
592
618
  ? minimaxModelAliases(modelID)
593
619
  : [modelID];
594
620
  for (const candidateModelID of modelIDsFor()) {
@@ -608,6 +634,190 @@ export function modelCostLookupKeys(providerID, modelID) {
608
634
  }
609
635
  return keys;
610
636
  }
637
+ function setLookupRates(map, providerID, modelID, rates, modelKey) {
638
+ const providerPrefix = `${providerID}:`;
639
+ for (const key of modelCostLookupKeys(providerID, modelID)) {
640
+ if (!key.startsWith(providerPrefix))
641
+ continue;
642
+ map[key] = rates;
643
+ }
644
+ if (modelKey && modelKey !== modelID) {
645
+ for (const key of modelCostLookupKeys(providerID, modelKey)) {
646
+ if (!key.startsWith(providerPrefix))
647
+ continue;
648
+ map[key] = rates;
649
+ }
650
+ }
651
+ }
652
+ function multiplyRates(rates, multiplier) {
653
+ return {
654
+ input: rates.input * multiplier,
655
+ output: rates.output * multiplier,
656
+ cacheRead: rates.cacheRead * multiplier,
657
+ cacheWrite: rates.cacheWrite * multiplier,
658
+ // Fast/tier-derived pricing intentionally does not inherit the base model's
659
+ // long-context tier. Those variants need their own explicit long-context
660
+ // schedule instead of multiplying a different billing rule implicitly.
661
+ contextOver200k: undefined,
662
+ };
663
+ }
664
+ function apiBaseModelID(model) {
665
+ return isRecord(model.api) && typeof model.api.id === "string"
666
+ ? model.api.id
667
+ : undefined;
668
+ }
669
+ function headerValue(model, key) {
670
+ if (!isRecord(model.headers))
671
+ return undefined;
672
+ const exact = model.headers[key];
673
+ if (typeof exact === "string")
674
+ return exact;
675
+ const lowerKey = key.toLowerCase();
676
+ for (const [headerKey, value] of Object.entries(model.headers)) {
677
+ if (headerKey.toLowerCase() !== lowerKey)
678
+ continue;
679
+ if (typeof value === "string")
680
+ return value;
681
+ }
682
+ return undefined;
683
+ }
684
+ function heuristicFastBaseModelID(model) {
685
+ const canonicalProviderID = canonicalPricingProviderID(model.providerID);
686
+ const candidates = [model.modelID, model.modelKey].filter((value) => Boolean(value));
687
+ for (const candidate of candidates) {
688
+ if (canonicalProviderID === "openai") {
689
+ const base = candidate.replace(/(?:[-/:](?:fast|priority))$/i, "");
690
+ if (base !== candidate)
691
+ return base;
692
+ }
693
+ if (canonicalProviderID === "anthropic") {
694
+ const base = candidate.replace(/-fast$/i, "");
695
+ if (base !== candidate)
696
+ return base;
697
+ }
698
+ }
699
+ return undefined;
700
+ }
701
+ function resolveSourceBaseRates(explicitRates, providerID, modelID) {
702
+ return modelCostLookupKeys(providerID, modelID)
703
+ .map((key) => explicitRates[key])
704
+ .find(Boolean);
705
+ }
706
+ function isStructuredOpenAIFastModel(model) {
707
+ return (canonicalPricingProviderID(model.providerID) === "openai" &&
708
+ isRecord(model.options) &&
709
+ model.options.serviceTier === "priority");
710
+ }
711
+ function isStructuredAnthropicFastModel(model) {
712
+ return (canonicalPricingProviderID(model.providerID) === "anthropic" &&
713
+ ((isRecord(model.options) && model.options.speed === "fast") ||
714
+ /\bfast\b/i.test(headerValue(model, "anthropic-beta") || "")));
715
+ }
716
+ export function derivedTierBaseModelID(model) {
717
+ const canonicalProviderID = canonicalPricingProviderID(model.providerID);
718
+ if (canonicalProviderID === "openai") {
719
+ return ((isStructuredOpenAIFastModel(model)
720
+ ? apiBaseModelID(model)
721
+ : undefined) || heuristicFastBaseModelID(model));
722
+ }
723
+ if (canonicalProviderID === "anthropic") {
724
+ return ((isStructuredAnthropicFastModel(model)
725
+ ? apiBaseModelID(model)
726
+ : undefined) || heuristicFastBaseModelID(model));
727
+ }
728
+ return undefined;
729
+ }
730
+ function derivedTierRatesForModel(model, explicitRates) {
731
+ const canonicalProviderID = canonicalPricingProviderID(model.providerID);
732
+ const baseModelID = derivedTierBaseModelID(model);
733
+ if (!baseModelID)
734
+ return undefined;
735
+ const baseRates = resolveSourceBaseRates(explicitRates, model.providerID, baseModelID);
736
+ if (!baseRates)
737
+ return undefined;
738
+ if (canonicalProviderID === "openai") {
739
+ return multiplyRates(baseRates, OPENAI_FAST_COST_MULTIPLIER);
740
+ }
741
+ if (canonicalProviderID === "anthropic") {
742
+ return multiplyRates(baseRates, ANTHROPIC_FAST_COST_MULTIPLIER);
743
+ }
744
+ return undefined;
745
+ }
746
+ export function explicitModelCostMap(models) {
747
+ const explicitRates = {};
748
+ for (const model of models) {
749
+ const rates = parseModelCostRates(model.cost);
750
+ if (!rates)
751
+ continue;
752
+ setLookupRates(explicitRates, model.providerID, model.modelID, rates, model.modelKey);
753
+ }
754
+ return explicitRates;
755
+ }
756
+ function hasExplicitModelCost(model, explicitRates) {
757
+ return [model.modelID, model.modelKey]
758
+ .filter((value) => Boolean(value))
759
+ .some((candidate) => modelCostLookupKeys(model.providerID, candidate).some((key) => Boolean(explicitRates[key])));
760
+ }
761
+ export function applyDerivedTierRatesFromSource(baseMap, metadataModels, sourceRates, options) {
762
+ const nextMap = { ...baseMap };
763
+ for (const model of metadataModels) {
764
+ if (options?.skipExplicitRates &&
765
+ hasExplicitModelCost(model, options.skipExplicitRates)) {
766
+ continue;
767
+ }
768
+ const derivedRates = derivedTierRatesForModel(model, sourceRates);
769
+ if (!derivedRates)
770
+ continue;
771
+ setLookupRates(nextMap, model.providerID, model.modelID, derivedRates, model.modelKey);
772
+ }
773
+ return nextMap;
774
+ }
775
+ export function applyExplicitRatesFromSource(baseMap, metadataModels, sourceRates, options) {
776
+ const nextMap = { ...baseMap };
777
+ for (const model of metadataModels) {
778
+ if (options?.skipExplicitRates &&
779
+ hasExplicitModelCost(model, options.skipExplicitRates)) {
780
+ continue;
781
+ }
782
+ const explicitRates = [model.modelID, model.modelKey]
783
+ .filter((value) => Boolean(value))
784
+ .flatMap((candidate) => modelCostLookupKeys(model.providerID, candidate))
785
+ .map((key) => sourceRates[key])
786
+ .find(Boolean);
787
+ if (!explicitRates)
788
+ continue;
789
+ setLookupRates(nextMap, model.providerID, model.modelID, explicitRates, model.modelKey);
790
+ }
791
+ return nextMap;
792
+ }
793
+ export function mergeModelCostSource(baseMap, models) {
794
+ const nextMap = { ...baseMap };
795
+ const explicitRates = explicitModelCostMap(models);
796
+ const explicitEntries = [];
797
+ for (const model of models) {
798
+ const rates = modelCostLookupKeys(model.providerID, model.modelID)
799
+ .map((key) => explicitRates[key])
800
+ .find(Boolean);
801
+ if (!rates)
802
+ continue;
803
+ explicitEntries.push({
804
+ providerID: model.providerID,
805
+ modelID: model.modelID,
806
+ modelKey: model.modelKey,
807
+ rates,
808
+ });
809
+ }
810
+ for (const model of models) {
811
+ const derivedRates = derivedTierRatesForModel(model, explicitRates);
812
+ if (!derivedRates)
813
+ continue;
814
+ setLookupRates(nextMap, model.providerID, model.modelID, derivedRates, model.modelKey);
815
+ }
816
+ for (const entry of explicitEntries) {
817
+ setLookupRates(nextMap, entry.providerID, entry.modelID, entry.rates, entry.modelKey);
818
+ }
819
+ return nextMap;
820
+ }
611
821
  function createBundledModelCostMap() {
612
822
  const map = {};
613
823
  for (const entry of BUNDLED_CANONICAL_PRICE_ENTRIES) {
@@ -632,18 +842,48 @@ export function getBundledCanonicalPriceEntries() {
632
842
  },
633
843
  }));
634
844
  }
845
+ function normalizeRateToPerMillion(input) {
846
+ if (!Number.isFinite(input) || input <= 0)
847
+ return 0;
848
+ return input > MODEL_COST_RATE_UNIT_THRESHOLD
849
+ ? input
850
+ : input * MODEL_COST_STANDARD_UNIT;
851
+ }
852
+ export function normalizeModelCostRates(rates) {
853
+ return {
854
+ input: normalizeRateToPerMillion(rates.input),
855
+ output: normalizeRateToPerMillion(rates.output),
856
+ cacheRead: normalizeRateToPerMillion(rates.cacheRead),
857
+ cacheWrite: normalizeRateToPerMillion(rates.cacheWrite),
858
+ contextOver200k: rates.contextOver200k
859
+ ? {
860
+ input: normalizeRateToPerMillion(rates.contextOver200k.input),
861
+ output: normalizeRateToPerMillion(rates.contextOver200k.output),
862
+ cacheRead: normalizeRateToPerMillion(rates.contextOver200k.cacheRead),
863
+ cacheWrite: normalizeRateToPerMillion(rates.contextOver200k.cacheWrite),
864
+ }
865
+ : undefined,
866
+ };
867
+ }
635
868
  export function parseModelCostRates(value) {
636
869
  if (!isRecord(value))
637
870
  return undefined;
638
871
  const readRate = (input) => {
639
- if (typeof input === 'number')
640
- return input;
641
- if (typeof input === 'string') {
872
+ if (typeof input === "number")
873
+ return normalizeRateToPerMillion(input);
874
+ if (typeof input === "string") {
642
875
  const parsed = Number(input);
643
- return Number.isFinite(parsed) ? parsed : 0;
876
+ return Number.isFinite(parsed) ? normalizeRateToPerMillion(parsed) : 0;
644
877
  }
645
878
  if (isRecord(input)) {
646
- return asNumber(input.usd, asNumber(input.value, asNumber(input.per_1m, asNumber(input.per1m, asNumber(input.per_token, asNumber(input.perToken, 0))))));
879
+ const perMillion = asNumber(input.per_1m) ?? asNumber(input.per1m) ?? undefined;
880
+ if (perMillion !== undefined)
881
+ return Math.max(0, perMillion);
882
+ const perToken = asNumber(input.per_token) ?? asNumber(input.perToken) ?? undefined;
883
+ if (perToken !== undefined) {
884
+ return Math.max(0, perToken) * MODEL_COST_STANDARD_UNIT;
885
+ }
886
+ return normalizeRateToPerMillion(asNumber(input.usd, asNumber(input.value, 0)));
647
887
  }
648
888
  return 0;
649
889
  };
@@ -676,30 +916,20 @@ export function parseModelCostRates(value) {
676
916
  contextOver200k: hasContextTier ? contextOver200k : undefined,
677
917
  };
678
918
  }
679
- const MODEL_COST_DIVISOR_PER_TOKEN = 1;
680
- const MODEL_COST_DIVISOR_PER_MILLION = 1_000_000;
681
- export function guessModelCostDivisor(rates) {
682
- // OpenCode provider pricing units can differ:
683
- // - some providers expose USD per token (e.g. 0.0000025)
684
- // - others expose USD per 1M tokens (e.g. 2.5)
685
- // Heuristic: treat values > 0.001 as "per 1M".
686
- const maxRate = Math.max(rates.input, rates.output, rates.cacheRead, rates.cacheWrite);
687
- return maxRate > 0.001
688
- ? MODEL_COST_DIVISOR_PER_MILLION
689
- : MODEL_COST_DIVISOR_PER_TOKEN;
690
- }
691
919
  export function cacheCoverageModeFromRates(rates) {
692
920
  if (!rates)
693
- return 'none';
921
+ return "none";
694
922
  if (rates.cacheWrite > 0)
695
- return 'read-write';
923
+ return "read-write";
696
924
  if (rates.cacheRead > 0)
697
- return 'read-only';
698
- return 'none';
925
+ return "read-only";
926
+ return "none";
699
927
  }
700
928
  export function calcEquivalentApiCostForMessage(message, rates) {
701
- const effectiveRates = message.tokens.input + message.tokens.cache.read > 200_000 &&
702
- rates.contextOver200k
929
+ const effectiveRates =
930
+ // Long-context tiering intentionally keys off live input tokens only.
931
+ // Cached read tokens do not promote the request into the >200k tier.
932
+ message.tokens.input > 200_000 && rates.contextOver200k
703
933
  ? rates.contextOver200k
704
934
  : rates;
705
935
  // For providers that expose reasoning tokens separately, they are still
@@ -710,7 +940,6 @@ export function calcEquivalentApiCostForMessage(message, rates) {
710
940
  billedOutput * effectiveRates.output +
711
941
  message.tokens.cache.read * effectiveRates.cacheRead +
712
942
  message.tokens.cache.write * effectiveRates.cacheWrite;
713
- const divisor = guessModelCostDivisor(effectiveRates);
714
- const normalized = rawCost / divisor;
943
+ const normalized = rawCost / MODEL_COST_STANDARD_UNIT;
715
944
  return Number.isFinite(normalized) && normalized > 0 ? normalized : 0;
716
945
  }