Shared pricing module replacing dual cost calculation systems (jsonl.ts hardcoded + sessionAnalyzer.ts LiteLLM) with a single src/shared/utils/pricing.ts used by both main and renderer processes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.3 KiB
3.3 KiB
Unify Cost Calculation — Design Document
Date: 2026-02-23
Branch: feat/unify-cost-calculation
Related: PR #60 (Session Analysis Report), PR #65 (Cost Calculation Metric), Issue #72 (Plan Usage Tracking)
Problem
Cost calculation exists in two places with different pricing data and logic:
- Main process (
src/main/utils/jsonl.ts): Uses LiteLLM-sourcedpricing.json(206 models, tiered 200k-token pricing). PopulatesSessionMetrics.costUsdfor the chat UI. - Renderer (
src/renderer/utils/sessionAnalyzer.ts): Uses a hardcoded 6-model pricing table with no tiered pricing. Generates per-model cost breakdown for the Session Report.
The two systems can produce different cost numbers for the same session and will drift further as models change.
Solution
Create a single shared pricing module that both processes import.
New Module: src/shared/utils/pricing.ts
Exports:
| Export | Description |
|---|---|
ModelPricing |
Interface for per-model rates (input, output, cache read, cache creation, plus tiered variants) |
getPricing(modelName: string): ModelPricing | null |
Model lookup: exact match, lowercase, case-insensitive scan |
calculateTieredCost(tokens, baseRate, tieredRate?): number |
Applies 200k-token tier threshold |
calculateMessageCost(model, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens): number |
Computes cost for a single API call |
Pricing data: Static import pricingData from '../../../resources/pricing.json' with resolveJsonModule: true. Replaces fs.readFileSync runtime loading.
Consumer Changes
src/main/utils/jsonl.ts:
- Remove:
ModelPricinginterface,loadPricingData(),calculateTieredCost(),getPricing(),fs/pathimports - Keep:
calculateMetrics()function - Change: inline cost loop body → call
calculateMessageCost()from@shared/utils/pricing
src/renderer/utils/sessionAnalyzer.ts:
- Remove:
MODEL_PRICINGtable (~40 lines),DEFAULT_PRICING, localgetPricing(), localcostUsd() - Change: calls at lines 476 and 900 →
calculateMessageCost()from@shared/utils/pricing
Tests:
test/main/utils/costCalculation.test.ts→ update to test shared module functionstest/renderer/utils/sessionAnalyzer.test.ts→ mock@shared/utils/pricinginstead of local functions- New
test/shared/utils/pricing.test.tsfor the shared module
Pricing JSON Import Strategy
pricing.jsonstays inresources/for Electron'sextraResourcespackaging- Both Vite (renderer) and electron-vite (main) resolve the JSON import at compile time
- Remove the
fs.readFileSyncdev/prod path logic fromjsonl.ts
Fallback Behavior
getPricing()returnsnullfor unknown modelscalculateMessageCost()returns0for unknown models (matches currentjsonl.tsbehavior)- Session analyzer callers can apply a default if needed
What Changes for Users
- Report costs become more accurate (tiered pricing, 206 models instead of 6)
- Cost numbers between chat view and Session Report now agree exactly
- No UI changes — same components, same layout
Out of Scope
- Plan usage tracking (see Issue #72 — pending community feedback)
- New UI surfaces for cost display
- Changes to the
costFormatting.tsshared utility