mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* 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>
79 lines
2.8 KiB
TypeScript
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(':');
|
|
}
|
|
});
|
|
});
|