mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 00:48:18 +00:00
Adversarial review: survived a hostile reviewer plus two independent refuters, each told to assume the PR was broken and to default to refuting when uncertain. 19 of 62 PRs cleared that bar. voyage-4-large was billed at the voyage-3-large rate — $0.18 against a published $0.12 — so every cost estimate using it was wrong by 50%. Corrected in the canonical table only, per CLAUDE.md's rule that every other pricing table is a derived view, and the drift guard passes. Rate checked against the live vendor page. Verified before merge: the PR's own tests fail when the production change is reverted; typecheck clean; MERGEABLE/CLEAN with 22/22 checks on the current base after batches 1-3 landed. Known gap, recorded rather than hidden: this PR previously failed the JSONB parity guard on a 32-commit-stale base. I rebased it onto current master and re-ran rather than accepting 'flaky' — the guard passes on the real base, 22/22 green.
104 lines
3.7 KiB
TypeScript
104 lines
3.7 KiB
TypeScript
/**
|
||
* Pricing table contract — Voyage + ZeroEntropy coverage gate.
|
||
*
|
||
* The post-upgrade reembed cost prompt in `gbrain upgrade` falls back to
|
||
* "estimate unavailable" on unknown providers, which is fine for safety
|
||
* but bad UX if the provider IS in the recipe registry. These tests pin
|
||
* the providers that v0.35.x officially supports as first-class.
|
||
*/
|
||
import { describe, test, expect } from 'bun:test';
|
||
import {
|
||
EMBEDDING_PRICING,
|
||
lookupEmbeddingPrice,
|
||
estimateCostFromChars,
|
||
} from '../src/core/embedding-pricing.ts';
|
||
|
||
describe('lookupEmbeddingPrice — first-class providers', () => {
|
||
test('OpenAI text-embedding-3-large at $0.13/MTok', () => {
|
||
const r = lookupEmbeddingPrice('openai:text-embedding-3-large');
|
||
expect(r.kind).toBe('known');
|
||
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.13);
|
||
});
|
||
|
||
test('Voyage voyage-3-large at $0.18/MTok', () => {
|
||
const r = lookupEmbeddingPrice('voyage:voyage-3-large');
|
||
expect(r.kind).toBe('known');
|
||
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.18);
|
||
});
|
||
|
||
// Voyage v4 family, verified against docs.voyageai.com/docs/pricing 2026-07-28.
|
||
test.each([
|
||
['voyage:voyage-4-large', 0.12],
|
||
['voyage:voyage-4', 0.06],
|
||
['voyage:voyage-4-lite', 0.02],
|
||
])('Voyage %s at $%d/MTok', (model, expected) => {
|
||
const r = lookupEmbeddingPrice(model);
|
||
expect(r.kind).toBe('known');
|
||
if (r.kind === 'known') expect(r.pricePerMTok).toBe(expected);
|
||
});
|
||
|
||
// voyage-4-nano is the open-weight variant with no hosted rate published;
|
||
// it must stay unpriced so callers say "estimate unavailable" (see table comment).
|
||
test('voyage-4-nano is deliberately unpriced', () => {
|
||
expect(lookupEmbeddingPrice('voyage:voyage-4-nano').kind).toBe('unknown');
|
||
});
|
||
|
||
test('ZeroEntropy zembed-1 at $0.05/MTok (v0.35.1.0+)', () => {
|
||
const r = lookupEmbeddingPrice('zeroentropyai:zembed-1');
|
||
expect(r.kind).toBe('known');
|
||
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.05);
|
||
});
|
||
});
|
||
|
||
describe('lookupEmbeddingPrice — fall-through behavior', () => {
|
||
test('returns unknown for bogus provider', () => {
|
||
const r = lookupEmbeddingPrice('madeup:model-9000');
|
||
expect(r.kind).toBe('unknown');
|
||
if (r.kind === 'unknown') {
|
||
expect(r.provider).toBe('madeup');
|
||
expect(r.model).toBe('model-9000');
|
||
}
|
||
});
|
||
|
||
test('bare model strings default to openai', () => {
|
||
const r = lookupEmbeddingPrice('text-embedding-3-small');
|
||
expect(r.kind).toBe('known');
|
||
if (r.kind === 'known') expect(r.key).toBe('openai:text-embedding-3-small');
|
||
});
|
||
|
||
test('provider name is case-insensitive', () => {
|
||
const r = lookupEmbeddingPrice('ZeroEntropyAI:zembed-1');
|
||
expect(r.kind).toBe('known');
|
||
if (r.kind === 'known') expect(r.pricePerMTok).toBe(0.05);
|
||
});
|
||
});
|
||
|
||
describe('EMBEDDING_PRICING — table integrity', () => {
|
||
test('all entries have pricePerMTok as a non-negative finite number', () => {
|
||
for (const [key, val] of Object.entries(EMBEDDING_PRICING)) {
|
||
expect(Number.isFinite(val.pricePerMTok)).toBe(true);
|
||
expect(val.pricePerMTok).toBeGreaterThanOrEqual(0);
|
||
expect(key).toContain(':');
|
||
}
|
||
});
|
||
|
||
test('keys use lowercase provider names', () => {
|
||
for (const key of Object.keys(EMBEDDING_PRICING)) {
|
||
const provider = key.split(':')[0];
|
||
expect(provider).toBe(provider.toLowerCase());
|
||
}
|
||
});
|
||
});
|
||
|
||
describe('estimateCostFromChars', () => {
|
||
test('returns 0 for 0 chars', () => {
|
||
expect(estimateCostFromChars(0, 0.13)).toBe(0);
|
||
});
|
||
|
||
test('100M chars @ $0.13/MTok ≈ $3.71 (100M / 3.5 ≈ 28.57M tokens × 0.13)', () => {
|
||
const c = estimateCostFromChars(100_000_000, 0.13);
|
||
expect(c).toBeGreaterThan(3.7);
|
||
expect(c).toBeLessThan(3.8);
|
||
});
|
||
});
|