fix(cost): price deepseek- model ids from the first-party branch first

A gateway provider id outside the models.dev registry falls back to the
model-side resolution, where azure's self-named SDK package won
`deepseek-v4-pro` and its flat listing (no cache_read published) billed
cache hits as plain input at 4x the official input rate (issue #93). A
`deepseek-` model id now prices from DeepSeek's own registry branch
first — the same first-party specialness the peak/off-peak split
already grants DeepSeek — and falls back to the model-side tiers when
the first party does not list the id.

Fixes #93.
This commit is contained in:
bowenliang123 committed 2026-09-28 17:38:11 +08:00
1 parent addccdd4aa
commit ee236f21a1
2 files changed
+32 -1

No files matched your search

+13 -1
View File
@@ -226,12 +226,24 @@ function lookup(models: Record<string, PriceTriple>, model: string): PriceTriple
* exact, case-insensitive, or suffix. A provider the book does not carry
* falls back to the model-side resolution index, which names the vendor
* from data (org segment, own-vendor SDK, id prefix, lone carrier) — never
* from a per-model hardcode.
* from a per-model hardcode. The one vendor-level seam: a `deepseek-` model
* id prices from DeepSeek's own registry branch first — its listing is the
* first-party price book (cache rates included), while the model-side tiers
* can hand the same id to a self-named hosting provider whose flat listing
* bills cache hits as plain input (azure's `deepseek-v4-pro`, issue #93) —
* the same specialness the peak/off-peak split already grants DeepSeek.
*/
export function priceOf(book: ModelBook | null | undefined, provider: string, model: string): PriceTriple | null {
if (book === null || book === undefined) return null
const direct = branchOf(book.prices, modelsDevProviderOf(provider))
if (direct !== null) return lookup(direct, model)
if (model.toLowerCase().startsWith('deepseek-')) {
const firstParty = branchOf(book.prices, 'deepseek')
if (firstParty !== null) {
const official = lookup(firstParty, model)
if (official !== null) return official
}
}
return resolveRate(book.index, model)
}
+19
View File
@@ -144,6 +144,25 @@ describe('priceIndexOf / the model-side resolution tiers', () => {
assert.deepEqual(priceOf(BOOK_B, 'any-gateway', 'deepseek-v4-flash'), FLASH, 'deepseek-v4-flash names deepseek')
})
test('a deepseek- model id prices from the first-party branch first (issue #93)', () => {
// The live-registry shape: azure re-lists `deepseek-v4-pro` flat (no
// cache_read published) and its `@ai-sdk/azure` package is the only
// self-named one — DeepSeek rides `@ai-sdk/openai-compatible`, so the
// model-side tiers would hand the id to azure. The `deepseek-` spelling
// prices from DeepSeek's own listing instead.
const AZURE_FLAT = { hit: 1.74, miss: 1.74, write: 1.74, out: 3.48 }
const hostBook = bookOf({
deepseek: { 'deepseek-v4-pro': PRO },
azure: { 'deepseek-v4-pro': AZURE_FLAT, 'deepseek-v4-pro-0813': AZURE_FLAT, 'gpt-x': AZURE_FLAT },
}, { deepseek: '@ai-sdk/openai-compatible', azure: '@ai-sdk/azure' })
assert.deepEqual(priceOf(hostBook, 'hx', 'deepseek-v4-pro'), PRO, 'the id names deepseek; a self-named SDK proves hosting, not authorship')
assert.deepEqual(priceOf(hostBook, 'hx', 'DeepSeek-V4-Pro'), PRO, 'the seam is case-insensitive')
assert.deepEqual(priceOf(hostBook, 'hx', 'deepseek-v4-pro-0813'), AZURE_FLAT, 'an id the first party does not list falls back to the model-side tiers')
assert.deepEqual(priceOf(hostBook, 'hx', 'gpt-x'), AZURE_FLAT, 'a model id outside the seam resolves as before')
const hostOnly = bookOf({ azure: { 'deepseek-junk': AZURE_FLAT } }, { azure: '@ai-sdk/azure' })
assert.deepEqual(priceOf(hostOnly, 'hx', 'deepseek-junk'), AZURE_FLAT, 'no first-party branch in the book leaves the tiers in charge')
})
test('tier: a lone carrier prices without any vendor signal', () => {
const lone = bookOf({ solo: { 'weird-model-9': K3 } })
assert.deepEqual(priceOf(lone, 'unknown', 'weird-model-9'), K3)