From 5ca3fc90087a2052396ee698895f3aa5b4d77277 Mon Sep 17 00:00:00 2001 From: Paul Holstein <44263169+holstein13@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:49:05 -0500 Subject: [PATCH] fix(review): address CodeRabbit and Gemini review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Design doc: update "No UI changes" to reflect cost disclaimer - Plan doc: fix MD001 heading level jump (### after #) - pricing.ts: rename to PRICING_MAP, add isLiteLLMPricing type guard, pre-compute LOWERCASE_KEY_MAP for O(1) case-insensitive lookups - SessionContextHeader: simplify cost disclaimer JSX into single span - Tests: fix no-empty-function lint (() => {} → () => undefined) Co-Authored-By: Claude Opus 4.6 --- ...026-02-23-unify-cost-calculation-design.md | 2 +- .../2026-02-23-unify-cost-calculation.md | 2 + .../components/SessionContextHeader.tsx | 32 ++++++++-------- src/shared/utils/pricing.ts | 38 ++++++++++--------- test/main/utils/costCalculation.test.ts | 2 +- test/shared/utils/pricing.test.ts | 2 +- 6 files changed, 42 insertions(+), 36 deletions(-) diff --git a/docs/plans/2026-02-23-unify-cost-calculation-design.md b/docs/plans/2026-02-23-unify-cost-calculation-design.md index 76a91505..38380aa6 100644 --- a/docs/plans/2026-02-23-unify-cost-calculation-design.md +++ b/docs/plans/2026-02-23-unify-cost-calculation-design.md @@ -62,7 +62,7 @@ Create a single shared pricing module that both processes import. - 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 +- Small UI change: Visible Context header adds a "parent only · view full cost" action when available ## Out of Scope diff --git a/docs/plans/2026-02-23-unify-cost-calculation.md b/docs/plans/2026-02-23-unify-cost-calculation.md index ead3814b..aa6dfc43 100644 --- a/docs/plans/2026-02-23-unify-cost-calculation.md +++ b/docs/plans/2026-02-23-unify-cost-calculation.md @@ -10,6 +10,8 @@ --- +## Tasks + ### Task 1: Create the shared pricing module with tests **Files:** diff --git a/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx b/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx index ef69d539..0c5cd0dd 100644 --- a/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx +++ b/src/renderer/components/chat/SessionContextPanel/components/SessionContextHeader.tsx @@ -134,22 +134,22 @@ export const SessionContextHeader = ({ {formatCostUsd(sessionMetrics.costUsd)} - (parent only - {onViewReport ? ( - - · - - ) - - ) : ( - ) - )} + + {' (parent only'} + {onViewReport && ( + <> + {' · '} + + + )} + {')'} + )} diff --git a/src/shared/utils/pricing.ts b/src/shared/utils/pricing.ts index 0e3b800a..28800696 100644 --- a/src/shared/utils/pricing.ts +++ b/src/shared/utils/pricing.ts @@ -22,19 +22,28 @@ export interface DisplayPricing { const TIER_THRESHOLD = 200_000; -const pricingMap = pricingData as Record; +const PRICING_MAP = pricingData as Record; -function tryGetPricing(key: string): LiteLLMPricing | null { - const entry = pricingMap[key]; - if ( - entry && +// Pre-compute lowercase key map for O(1) case-insensitive lookups +const LOWERCASE_KEY_MAP = new Map(); +for (const key of Object.keys(PRICING_MAP)) { + if (!LOWERCASE_KEY_MAP.has(key.toLowerCase())) { + LOWERCASE_KEY_MAP.set(key.toLowerCase(), key); + } +} + +function isLiteLLMPricing(entry: unknown): entry is LiteLLMPricing { + return ( + !!entry && typeof entry === 'object' && 'input_cost_per_token' in entry && 'output_cost_per_token' in entry - ) { - return entry as LiteLLMPricing; - } - return null; + ); +} + +function tryGetPricing(key: string): LiteLLMPricing | null { + const entry = PRICING_MAP[key]; + return isLiteLLMPricing(entry) ? entry : null; } export function getPricing(modelName: string): LiteLLMPricing | null { @@ -42,14 +51,9 @@ export function getPricing(modelName: string): LiteLLMPricing | null { if (exact) return exact; const lowerName = modelName.toLowerCase(); - const lower = tryGetPricing(lowerName); - if (lower) return lower; - - for (const key of Object.keys(pricingMap)) { - if (key.toLowerCase() === lowerName) { - const match = tryGetPricing(key); - if (match) return match; - } + const originalKey = LOWERCASE_KEY_MAP.get(lowerName); + if (originalKey) { + return tryGetPricing(originalKey); } return null; diff --git a/test/main/utils/costCalculation.test.ts b/test/main/utils/costCalculation.test.ts index 1843daea..2ea481a3 100644 --- a/test/main/utils/costCalculation.test.ts +++ b/test/main/utils/costCalculation.test.ts @@ -84,7 +84,7 @@ describe('Cost Calculation', () => { }); it('should return 0 cost when model pricing not found', () => { - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); const messages: ParsedMessage[] = [ { type: 'assistant', diff --git a/test/shared/utils/pricing.test.ts b/test/shared/utils/pricing.test.ts index 7b872cb7..f8a807b9 100644 --- a/test/shared/utils/pricing.test.ts +++ b/test/shared/utils/pricing.test.ts @@ -55,7 +55,7 @@ describe('Shared Pricing Module', () => { }); it('should return 0 for unknown models', () => { - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); const cost = calculateMessageCost('unknown-model', 1000, 500, 0, 0); expect(cost).toBe(0); expect(warnSpy).toHaveBeenCalledWith(