Files
gbrain/test/eval-takes-quality-pricing.test.ts
T
9a0bae8d62 v0.42.25.0 fix(pricing): unify chat-model pricing into one canonical source; add Opus 4.8 (#1819) (#1827)
* fix(pricing): unify chat-model pricing into one canonical source; add Opus 4.8 (#1819)

Single canonical CANONICAL_PRICING table (src/core/model-pricing.ts) with
canonicalLookup; ANTHROPIC_PRICING and takes-quality MODEL_PRICING become
derived views. cost-tracker, cross-modal runner, skillopt preflight, brainstorm
orchestrator, and brain-score all source from it. Adds Opus 4.8 ($5/$25) so
--max-cost-usd and the dream-cycle budget meter enforce on 4.8 runs; fixes a
stale Opus 4.7 $15/$75 in the takes-quality gate and reconciles Gemini 2.0 Flash
to $0.10/$0.40. Because every table derives from canonical, cross-table price
drift is structurally impossible.

* chore: bump version and changelog (v0.42.25.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: document canonical chat-pricing table for v0.42.25.0

Add KEY_FILES.md entries for src/core/model-pricing.ts (canonical
CANONICAL_PRICING + canonicalLookup) and refresh the now-derived
anthropic-pricing.ts + takes-quality-eval/pricing.ts entries to
current-state. Add the "one canonical chat-pricing table" cross-cutting
invariant to CLAUDE.md. Fix the stale model-price snapshot pointer in
SEARCH_MODE_METHODOLOGY.md (anthropic-pricing.ts -> model-pricing.ts).
Regenerate llms-full.txt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 23:00:11 -07:00

79 lines
2.8 KiB
TypeScript

/**
* takes-quality-eval/pricing — fail-closed lookup tests (codex review #4).
*/
import { describe, test, expect } from 'bun:test';
import {
getPricing,
estimateCost,
PricingNotFoundError,
MODEL_PRICING,
} from '../src/core/takes-quality-eval/pricing.ts';
describe('getPricing — fail-closed contract', () => {
test('returns pricing for the default 3-model panel', () => {
expect(getPricing('openai:gpt-4o')).toBeDefined();
expect(getPricing('anthropic:claude-opus-4-7')).toBeDefined();
expect(getPricing('google:gemini-1.5-pro')).toBeDefined();
});
test('throws PricingNotFoundError on unknown model', () => {
expect(() => getPricing('unknown:gpt-99')).toThrow(PricingNotFoundError);
});
test('opus 4.7 priced at $5/$25 (regression: was a stale $15/$75) — gbrain#1819', () => {
const p = getPricing('anthropic:claude-opus-4-7');
expect(p.input_per_1m).toBeCloseTo(5.0, 5);
expect(p.output_per_1m).toBeCloseTo(25.0, 5);
});
test('opus 4.8 is supported and priced $5/$25 — gbrain#1819', () => {
const p = getPricing('anthropic:claude-opus-4-8');
expect(p.input_per_1m).toBeCloseTo(5.0, 5);
expect(p.output_per_1m).toBeCloseTo(25.0, 5);
});
test('error message names the model AND points to the file', () => {
try {
getPricing('foo:bar');
throw new Error('expected throw');
} catch (e) {
expect(e).toBeInstanceOf(PricingNotFoundError);
expect((e as Error).message).toContain('foo:bar');
expect((e as Error).message).toContain('src/core/takes-quality-eval/pricing.ts');
expect((e as Error).message).toContain('--budget-usd 0');
}
});
});
describe('estimateCost', () => {
test('returns USD cost for known model', () => {
// openai:gpt-4o = $2.50/1M in + $10.00/1M out
// 1M in + 1M out = $12.50
const c = estimateCost('openai:gpt-4o', 1_000_000, 1_000_000);
expect(c).toBeCloseTo(12.5, 5);
});
test('zero tokens → zero cost', () => {
expect(estimateCost('openai:gpt-4o', 0, 0)).toBe(0);
});
test('throws on unknown model (matches getPricing)', () => {
expect(() => estimateCost('unknown:foo', 100, 100)).toThrow(PricingNotFoundError);
});
});
describe('MODEL_PRICING table', () => {
test('every entry has finite positive rates', () => {
for (const [model, p] of Object.entries(MODEL_PRICING)) {
expect(Number.isFinite(p.input_per_1m)).toBe(true);
expect(Number.isFinite(p.output_per_1m)).toBe(true);
expect(p.input_per_1m).toBeGreaterThan(0);
expect(p.output_per_1m).toBeGreaterThan(0);
// Output is typically more expensive than input; canary on weird drift.
expect(p.output_per_1m).toBeGreaterThanOrEqual(p.input_per_1m);
// Sanity guard against an accidental integer column (model id has the colon).
expect(model).toContain(':');
}
});
});