fix: model cost/info matches first substring key (gpt-4o-mini billed as gpt-4o) (#1439)
* fix: match model name to the longest known key, not the first substring * test: model key matching prefers the longest specific key
This commit is contained in:
@@ -8,6 +8,7 @@ import { providerLogo } from './providers.js';
|
||||
import settingsModule from './settings.js';
|
||||
import spinnerModule from './spinner.js';
|
||||
import { bindMenuDismiss } from './escMenuStack.js';
|
||||
import { matchModelKey } from './model/matchKey.js';
|
||||
|
||||
const SEARCH_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>';
|
||||
const REPORT_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><line x1="10" y1="9" x2="8" y2="9"/></svg>';
|
||||
@@ -532,11 +533,8 @@ export function modelColor(name) {
|
||||
/** Look up model info (pricing + context) by substring match */
|
||||
export function getModelInfo(modelName) {
|
||||
if (!modelName) return null;
|
||||
const name = modelName.toLowerCase();
|
||||
for (const [key, info] of Object.entries(MODEL_INFO)) {
|
||||
if (name.includes(key)) return { key, ...info };
|
||||
}
|
||||
return null;
|
||||
const key = matchModelKey(modelName, Object.keys(MODEL_INFO));
|
||||
return key ? { key, ...MODEL_INFO[key] } : null;
|
||||
}
|
||||
|
||||
function _fmtCtx(n) {
|
||||
@@ -634,13 +632,10 @@ export function applyModelColor(roleEl, modelName) {
|
||||
|
||||
export function getModelCost(modelName, inputTokens, outputTokens) {
|
||||
if (!modelName) return null;
|
||||
const name = modelName.toLowerCase();
|
||||
for (const [key, price] of Object.entries(MODEL_PRICING)) {
|
||||
if (name.includes(key)) {
|
||||
return (inputTokens * price.input + outputTokens * price.output) / 1_000_000;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
const key = matchModelKey(modelName, Object.keys(MODEL_PRICING));
|
||||
if (!key) return null;
|
||||
const price = MODEL_PRICING[key];
|
||||
return (inputTokens * price.input + outputTokens * price.output) / 1_000_000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
19
static/js/model/matchKey.js
Normal file
19
static/js/model/matchKey.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// static/js/model/matchKey.js
|
||||
//
|
||||
// Pure helper for matching a model name against a set of known keys. No DOM —
|
||||
// safe to import anywhere and to unit-test under node.
|
||||
|
||||
// Return the most specific (longest) key that is a substring of `name`, or null.
|
||||
// Returning the first match instead made "gpt-4o-mini" match the shorter
|
||||
// "gpt-4o" key — billing it at gpt-4o rates (~16x) and showing the wrong
|
||||
// context window.
|
||||
export function matchModelKey(name, keys) {
|
||||
const n = (name || '').toLowerCase();
|
||||
let best = null;
|
||||
for (const key of keys) {
|
||||
if (n.includes(key) && (best === null || key.length > best.length)) {
|
||||
best = key;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
Reference in New Issue
Block a user