Files
Mert Koseoglu 99659f20b1 chore(pricing): add Opus 4.8 + sync ctx-stats to current Anthropic rates
Two related pricing updates landing together:

1. extractAgentUsage MODEL_PRICING — add claude-opus-4-8 entry at the
   same standard rate as 4.7 ($5/$25/$6.25/$0.50 input/output/cache_write/
   cache_read). Fast-mode variants intentionally not mapped — they ship
   as separate model ids and would dilute standard-tier dashboards.

   Bonus: resolveModelKey gains prefix-match so date-suffixed model ids
   (claude-haiku-4-5-20251001) resolve via the longest matching base
   key instead of falling to the default Sonnet rate.

2. ctx-stats analytics.ts — Opus rate stale since the v1.0.103 era
   ($15/1M was Opus 4, not 4.7/4.8). Updates:
     - pricePerToken() fallback   15 → 5 per 1M
     - OPUS_INPUT_PRICE_PER_TOKEN 15 → 5 per 1M
     - renderCostExample comment  Opus 4 $15 → Opus 4.7/4.8 $5
     - Haiku math constant        0.8 → 1.0 (Haiku 4.5 ships at $1/1M)
     - hero label                 "Opus 4 tokens" → "Opus 4.7 tokens"

   The renderer was reporting a 3× inflated savings number for every
   ctx-stats / formatReport call — share-copy that promised $1,399 of
   Opus tokens saved is actually $466 at current rates.

src/session/extract.ts:1340-1376    MODEL_PRICING + Opus 4.8 + prefix match
src/session/analytics.ts:1864       Math constants block (comment refresh)
src/session/analytics.ts:1905       Haiku 0.8 → 1.0
src/session/analytics.ts:1926       Renderer hero label refresh
src/session/analytics.ts:2389       pricePerToken comment refresh
src/session/analytics.ts:2402-2411  pricePerToken value 15 → 5
src/session/analytics.ts:2423       OPUS_INPUT_PRICE_PER_TOKEN 15 → 5

tests/session/extract-agent-usage.test.ts +2 tracers (Opus 4.8 parity,
  haiku-date-suffix prefix-match) — 19/19 GREEN
tests/analytics/price-per-token.test.ts (5 assertions updated) — 6/6 GREEN
tests/session/stats-output-format.test.ts (2 assertions updated) — 9/9 GREEN

Verification source: WebSearch against platform.claude.com,
cloudzero.com, finout.io — all confirm 2026-06 standard rates above.
2026-06-02 19:01:46 +03:00

74 lines
2.9 KiB
TypeScript

/**
* PR #741 follow-up — pricePerToken() must NOT freeze at module load.
*
* The original 1dba9b3 commit shipped:
* export const PRICE_PER_TOKEN = resolvePricePerToken();
*
* Pi sets PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN AFTER the MCP server
* has been imported (the bridge spawns the server child, then the
* child reads its own env on every render). A module-load-time const
* captures process.env at import and freezes to the Opus fallback
* because the env var is unset at import time. The PR body advertised
* env-driven dynamic pricing that silently did not work.
*
* The fix converts the export to a function so it re-reads the env on
* every call. These tests pin that behavior.
*/
import { describe, it, expect, afterEach } from "vitest";
import {
pricePerToken,
tokensToUsd,
OPUS_INPUT_PRICE_PER_TOKEN,
} from "../../src/session/analytics.js";
const ENV_NAME = "PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN";
afterEach(() => {
delete process.env[ENV_NAME];
});
describe("pricePerToken() — dynamic env resolution (PR #741 follow-up)", () => {
it("returns the Opus 4.7/4.8 input fallback ($5/1M) when env is unset", () => {
delete process.env[ENV_NAME];
expect(pricePerToken()).toBeCloseTo(5 / 1_000_000, 15);
});
it("reads PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN when set AFTER module load", () => {
// This is the key invariant: import happened above (env unset), but
// a later setter must be honored on the next call. A frozen const
// would still report the Opus fallback here.
process.env[ENV_NAME] = "0.000003"; // Sonnet 4.6 rate, $3/1M
expect(pricePerToken()).toBeCloseTo(0.000003, 12);
});
it("switches back to the fallback when env is cleared mid-run", () => {
process.env[ENV_NAME] = "0.000001"; // Haiku 4.5, $1.00/1M
expect(pricePerToken()).toBeCloseTo(0.000001, 12);
delete process.env[ENV_NAME];
expect(pricePerToken()).toBeCloseTo(5 / 1_000_000, 15);
});
it("ignores empty / non-numeric / non-positive env values and falls back", () => {
for (const bad of ["", " ", "abc", "0", "-1", "Infinity", "NaN"]) {
process.env[ENV_NAME] = bad;
expect(pricePerToken()).toBeCloseTo(5 / 1_000_000, 15);
}
});
it("tokensToUsd() picks up the dynamic rate on every call", () => {
process.env[ENV_NAME] = "0.000001"; // $1/1M — easy to verify
expect(tokensToUsd(1_000_000)).toBe("$1.00");
process.env[ENV_NAME] = "0.000010"; // $10/1M
expect(tokensToUsd(1_000_000)).toBe("$10.00");
});
it("OPUS_INPUT_PRICE_PER_TOKEN back-compat alias resolves to $5/1M (Opus 4.7/4.8)", () => {
// Third-party consumers that still import the original const name
// get the fallback literal. Pricing dedup invariant (PR #401 P1.1)
// preserved. Updated 2026-06: Opus 4.7/4.8 ship at $5/1M, not the
// earlier Opus 4 $15/1M rate.
expect(OPUS_INPUT_PRICE_PER_TOKEN).toBeCloseTo(5 / 1_000_000, 15);
});
});