86 lines
2.6 KiB
TypeScript
86 lines
2.6 KiB
TypeScript
|
|
import { describe, it, expect } from 'vitest';
|
||
|
|
import { estimateCostUsd, PRICING } from './pricing';
|
||
|
|
import { HAIKU, SONNET, OPUS } from './models';
|
||
|
|
|
||
|
|
describe('PRICING', () => {
|
||
|
|
it('has rows for all three analyzer models', () => {
|
||
|
|
expect(PRICING[HAIKU]).toBeDefined();
|
||
|
|
expect(PRICING[SONNET]).toBeDefined();
|
||
|
|
expect(PRICING[OPUS]).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('output is more expensive than input on every model', () => {
|
||
|
|
for (const m of [HAIKU, SONNET, OPUS] as const) {
|
||
|
|
expect(PRICING[m].output).toBeGreaterThan(PRICING[m].input);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('cache_read is much cheaper than uncached input', () => {
|
||
|
|
for (const m of [HAIKU, SONNET, OPUS] as const) {
|
||
|
|
expect(PRICING[m].cacheRead).toBeLessThan(PRICING[m].input);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('estimateCostUsd', () => {
|
||
|
|
it('charges Haiku $1/1M input + $5/1M output', () => {
|
||
|
|
// 1,000,000 input + 1,000,000 output = $1 + $5 = $6
|
||
|
|
expect(
|
||
|
|
estimateCostUsd(HAIKU, { input_tokens: 1_000_000, output_tokens: 1_000_000 })
|
||
|
|
).toBe(6);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('charges Sonnet $3/1M input + $15/1M output', () => {
|
||
|
|
expect(
|
||
|
|
estimateCostUsd(SONNET, { input_tokens: 1_000_000, output_tokens: 1_000_000 })
|
||
|
|
).toBe(18);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('charges Opus $5/1M input + $25/1M output', () => {
|
||
|
|
expect(
|
||
|
|
estimateCostUsd(OPUS, { input_tokens: 1_000_000, output_tokens: 1_000_000 })
|
||
|
|
).toBe(30);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('charges cache_read at the discounted rate', () => {
|
||
|
|
// 1M cache_read at Haiku's $0.10 = $0.10
|
||
|
|
const cost = estimateCostUsd(HAIKU, {
|
||
|
|
input_tokens: 0,
|
||
|
|
output_tokens: 0,
|
||
|
|
cache_read_input_tokens: 1_000_000,
|
||
|
|
});
|
||
|
|
expect(cost).toBeCloseTo(0.1, 4);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('charges cache_creation at the 1.25x premium', () => {
|
||
|
|
// 1M cache_write at Haiku's $1.25 = $1.25
|
||
|
|
const cost = estimateCostUsd(HAIKU, {
|
||
|
|
input_tokens: 0,
|
||
|
|
output_tokens: 0,
|
||
|
|
cache_creation_input_tokens: 1_000_000,
|
||
|
|
});
|
||
|
|
expect(cost).toBeCloseTo(1.25, 4);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rounds to 4 decimals (matches DB column)', () => {
|
||
|
|
const cost = estimateCostUsd(HAIKU, { input_tokens: 1, output_tokens: 1 });
|
||
|
|
// 1 input @ $1/M = $0.000001 ≈ rounds to 0.0000
|
||
|
|
expect(cost.toString().split('.')[1]?.length ?? 0).toBeLessThanOrEqual(4);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('returns 0 for zero usage', () => {
|
||
|
|
expect(
|
||
|
|
estimateCostUsd(OPUS, { input_tokens: 0, output_tokens: 0 })
|
||
|
|
).toBe(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('produces a realistic ticket-analysis cost for a typical Stage 1 call', () => {
|
||
|
|
// ~10K input tokens, ~500 output tokens on Haiku ≈ $0.013
|
||
|
|
const cost = estimateCostUsd(HAIKU, {
|
||
|
|
input_tokens: 10_000,
|
||
|
|
output_tokens: 500,
|
||
|
|
});
|
||
|
|
expect(cost).toBeCloseTo(0.0125, 4);
|
||
|
|
});
|
||
|
|
});
|