diff --git a/src/__tests__/main/parsers/usage-aggregator.test.ts b/src/__tests__/main/parsers/usage-aggregator.test.ts index 7e0e0c6e..2ba0d20e 100644 --- a/src/__tests__/main/parsers/usage-aggregator.test.ts +++ b/src/__tests__/main/parsers/usage-aggregator.test.ts @@ -99,7 +99,8 @@ describe('estimateContextUsage', () => { contextWindow: 200000, }); const result = estimateContextUsage(stats, 'claude-code'); - expect(result).toBe(100); + // Output tokens excluded; 150k / 200k = 75% + expect(result).toBe(75); }); }); @@ -113,7 +114,7 @@ describe('estimateContextUsage', () => { it('should use codex default context window (200k)', () => { const stats = createStats({ contextWindow: 0 }); const result = estimateContextUsage(stats, 'codex'); - expect(result).toBe(8); + expect(result).toBe(5); }); it('should use opencode default context window (128k)', () => { diff --git a/src/__tests__/renderer/components/HistoryDetailModal.test.tsx b/src/__tests__/renderer/components/HistoryDetailModal.test.tsx index f12cfd85..eee8f8a2 100644 --- a/src/__tests__/renderer/components/HistoryDetailModal.test.tsx +++ b/src/__tests__/renderer/components/HistoryDetailModal.test.tsx @@ -642,7 +642,7 @@ describe('HistoryDetailModal', () => { usageStats: { inputTokens: 74000, outputTokens: 1000, - cacheReadInputTokens: 0, + cacheReadInputTokens: 1000, cacheCreationInputTokens: 0, contextWindow: 100000, totalCostUsd: 0, @@ -652,7 +652,7 @@ describe('HistoryDetailModal', () => { /> ); - // (74000 + 1000) / 100000 = 75% + // (74000 + 1000 cache read) / 100000 = 75% expect(screen.getByText('75%')).toBeInTheDocument(); }); diff --git a/src/__tests__/renderer/components/MainPanel.test.tsx b/src/__tests__/renderer/components/MainPanel.test.tsx index afeb7dcd..891b01dd 100644 --- a/src/__tests__/renderer/components/MainPanel.test.tsx +++ b/src/__tests__/renderer/components/MainPanel.test.tsx @@ -1794,8 +1794,8 @@ describe('MainPanel', () => { createdAt: Date.now(), usageStats: { inputTokens: 50000, - outputTokens: 25000, - cacheReadInputTokens: 0, + outputTokens: 0, + cacheReadInputTokens: 25000, cacheCreationInputTokens: 0, totalCostUsd: 0.05, contextWindow: 200000, @@ -1806,7 +1806,7 @@ describe('MainPanel', () => { render(); - // Context usage should be (50000 + 25000) / 200000 * 100 = 37.5% + // Context usage should be (50000 + 25000 cache read) / 200000 * 100 = 37.5% expect(getContextColor).toHaveBeenCalledWith(38, theme); // Rounded to 38 }); }); diff --git a/src/__tests__/renderer/components/TabSwitcherModal.test.tsx b/src/__tests__/renderer/components/TabSwitcherModal.test.tsx index 2644c8e7..daeea226 100644 --- a/src/__tests__/renderer/components/TabSwitcherModal.test.tsx +++ b/src/__tests__/renderer/components/TabSwitcherModal.test.tsx @@ -61,8 +61,8 @@ const createTestTab = (overrides: Partial = {}): AITab => ({ usageStats: { inputTokens: 1000, outputTokens: 500, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.05, contextWindow: 200000, }, @@ -104,8 +104,8 @@ describe('TabSwitcherModal', () => { usageStats: { inputTokens: 500, outputTokens: 200, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.01, contextWindow: 200000, }, @@ -132,8 +132,8 @@ describe('TabSwitcherModal', () => { usageStats: { inputTokens: 5000, outputTokens: 3500, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.10, contextWindow: 200000, }, @@ -160,8 +160,8 @@ describe('TabSwitcherModal', () => { usageStats: { inputTokens: 700, outputTokens: 300, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.02, contextWindow: 200000, }, @@ -189,8 +189,8 @@ describe('TabSwitcherModal', () => { usageStats: { inputTokens: 100, outputTokens: 50, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0, contextWindow: 200000, }, @@ -216,8 +216,8 @@ describe('TabSwitcherModal', () => { usageStats: { inputTokens: 100, outputTokens: 50, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.005, contextWindow: 200000, }, @@ -243,8 +243,8 @@ describe('TabSwitcherModal', () => { usageStats: { inputTokens: 1000, outputTokens: 500, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 1.23, contextWindow: 200000, }, @@ -501,8 +501,8 @@ describe('TabSwitcherModal', () => { usageStats: { inputTokens: 1000, outputTokens: 500, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.05, contextWindow: 0, }, @@ -527,10 +527,10 @@ describe('TabSwitcherModal', () => { it('calculates correct percentage', () => { const tab = createTestTab({ usageStats: { - inputTokens: 10000, + inputTokens: 20000, outputTokens: 10000, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.50, contextWindow: 100000, }, @@ -556,9 +556,9 @@ describe('TabSwitcherModal', () => { const tab = createTestTab({ usageStats: { inputTokens: 150000, - outputTokens: 100000, - cacheReadTokens: 0, - cacheWriteTokens: 0, + outputTokens: 0, + cacheReadInputTokens: 100000, + cacheCreationInputTokens: 0, totalCostUsd: 5.00, contextWindow: 200000, }, @@ -810,8 +810,8 @@ describe('TabSwitcherModal', () => { usageStats: { inputTokens: 10000, outputTokens: 10000, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.50, contextWindow: 100000, }, @@ -842,10 +842,10 @@ describe('TabSwitcherModal', () => { it('applies color based on percentage (success for low)', () => { const tab = createTestTab({ usageStats: { - inputTokens: 5000, + inputTokens: 10000, outputTokens: 5000, - cacheReadTokens: 0, - cacheWriteTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, totalCostUsd: 0.25, contextWindow: 200000, }, diff --git a/src/__tests__/renderer/utils/contextExtractor.test.ts b/src/__tests__/renderer/utils/contextExtractor.test.ts index 57535841..6d46e355 100644 --- a/src/__tests__/renderer/utils/contextExtractor.test.ts +++ b/src/__tests__/renderer/utils/contextExtractor.test.ts @@ -94,8 +94,8 @@ describe('extractTabContext', () => { usageStats: { inputTokens: 100, outputTokens: 200, - cacheReadTokens: 50, - cacheCreationTokens: 0, + cacheReadInputTokens: 50, + cacheCreationInputTokens: 0, costUsd: 0.01, }, }); @@ -424,15 +424,15 @@ describe('estimateTokenCount', () => { usageStats: { inputTokens: 500, outputTokens: 1000, - cacheReadTokens: 0, - cacheCreationTokens: 0, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 200, costUsd: 0.05, }, }; const tokens = estimateTokenCount(context); - expect(tokens).toBe(1500); + expect(tokens).toBe(700); // input + cacheCreation + cacheRead }); it('should estimate from log content when no usage stats', () => { @@ -614,7 +614,13 @@ describe('calculateTotalTokens', () => { name: 'Context 1', logs: [], agentType: 'claude-code', - usageStats: { inputTokens: 100, outputTokens: 200, cacheReadTokens: 0, cacheCreationTokens: 0, costUsd: 0 }, + usageStats: { + inputTokens: 100, + outputTokens: 200, + cacheReadInputTokens: 50, + cacheCreationInputTokens: 25, + costUsd: 0, + }, }, { type: 'tab', @@ -623,13 +629,20 @@ describe('calculateTotalTokens', () => { name: 'Context 2', logs: [], agentType: 'claude-code', - usageStats: { inputTokens: 300, outputTokens: 400, cacheReadTokens: 0, cacheCreationTokens: 0, costUsd: 0 }, + usageStats: { + inputTokens: 300, + outputTokens: 400, + cacheReadInputTokens: 75, + cacheCreationInputTokens: 25, + costUsd: 0, + }, }, ]; const total = calculateTotalTokens(contexts); - expect(total).toBe(1000); // (100+200) + (300+400) + // input + cacheCreation + cacheRead for each context + expect(total).toBe(575); // (100+25+50) + (300+25+75) }); }); @@ -643,7 +656,13 @@ describe('getContextSummary', () => { name: 'Context 1', logs: [createMockLog(), createMockLog()], agentType: 'claude-code', - usageStats: { inputTokens: 100, outputTokens: 100, cacheReadTokens: 0, cacheCreationTokens: 0, costUsd: 0 }, + usageStats: { + inputTokens: 100, + outputTokens: 100, + cacheReadInputTokens: 50, + cacheCreationInputTokens: 25, + costUsd: 0, + }, }, { type: 'session', @@ -652,7 +671,13 @@ describe('getContextSummary', () => { name: 'Context 2', logs: [createMockLog(), createMockLog(), createMockLog()], agentType: 'opencode', - usageStats: { inputTokens: 200, outputTokens: 200, cacheReadTokens: 0, cacheCreationTokens: 0, costUsd: 0 }, + usageStats: { + inputTokens: 200, + outputTokens: 200, + cacheReadInputTokens: 75, + cacheCreationInputTokens: 25, + costUsd: 0, + }, }, ]; @@ -660,7 +685,7 @@ describe('getContextSummary', () => { expect(summary.totalSources).toBe(2); expect(summary.totalLogs).toBe(5); - expect(summary.estimatedTokens).toBe(600); + expect(summary.estimatedTokens).toBe(475); expect(summary.byAgent['claude-code']).toBe(1); expect(summary.byAgent['opencode']).toBe(1); }); diff --git a/src/__tests__/renderer/utils/contextUsage.test.ts b/src/__tests__/renderer/utils/contextUsage.test.ts index bd78013f..f1893c4a 100644 --- a/src/__tests__/renderer/utils/contextUsage.test.ts +++ b/src/__tests__/renderer/utils/contextUsage.test.ts @@ -72,13 +72,13 @@ describe('estimateContextUsage', () => { it('should use claude default context window (200k)', () => { const stats = createStats({ contextWindow: 0 }); const result = estimateContextUsage(stats, 'claude'); - expect(result).toBe(8); + expect(result).toBe(5); }); it('should use codex default context window (200k)', () => { const stats = createStats({ contextWindow: 0 }); const result = estimateContextUsage(stats, 'codex'); - expect(result).toBe(8); + expect(result).toBe(5); }); it('should use opencode default context window (128k)', () => { @@ -91,7 +91,7 @@ describe('estimateContextUsage', () => { it('should use aider default context window (128k)', () => { const stats = createStats({ contextWindow: 0 }); const result = estimateContextUsage(stats, 'aider'); - expect(result).toBe(12); + expect(result).toBe(8); }); it('should return null for terminal agent', () => { @@ -151,7 +151,7 @@ describe('estimateContextUsage', () => { const stats = createStats({ contextWindow: -100 }); const result = estimateContextUsage(stats, 'claude-code'); // Should use fallback since contextWindow is invalid - expect(result).toBe(8); + expect(result).toBe(5); }); it('should handle undefined context window', () => { @@ -160,7 +160,7 @@ describe('estimateContextUsage', () => { stats.contextWindow = undefined; const result = estimateContextUsage(stats, 'claude-code'); // Should use fallback - expect(result).toBe(8); + expect(result).toBe(5); }); it('should handle very large token counts', () => { diff --git a/src/__tests__/web/mobile/SessionStatusBanner.test.tsx b/src/__tests__/web/mobile/SessionStatusBanner.test.tsx index 96f79e10..24a6cc1b 100644 --- a/src/__tests__/web/mobile/SessionStatusBanner.test.tsx +++ b/src/__tests__/web/mobile/SessionStatusBanner.test.tsx @@ -417,7 +417,7 @@ describe('SessionStatusBanner', () => { it('shows warning color (yellow) for usage 70-89%', () => { const usageStats = createUsageStats({ - inputTokens: 100000, + inputTokens: 160000, outputTokens: 60000, contextWindow: 200000, }); @@ -464,7 +464,7 @@ describe('SessionStatusBanner', () => { it('shows warning color at exactly 70%', () => { const usageStats = createUsageStats({ - inputTokens: 70000, + inputTokens: 140000, outputTokens: 70000, contextWindow: 200000, }); @@ -770,7 +770,7 @@ describe('SessionStatusBanner', () => { describe('ContextUsageBar component', () => { it('renders progressbar with correct ARIA attributes', () => { const usageStats = createUsageStats({ - inputTokens: 50000, + inputTokens: 100000, outputTokens: 50000, contextWindow: 200000, }); @@ -786,7 +786,7 @@ describe('SessionStatusBanner', () => { it('has accessible aria-label', () => { const usageStats = createUsageStats({ - inputTokens: 50000, + inputTokens: 100000, outputTokens: 50000, contextWindow: 200000, }); @@ -799,7 +799,7 @@ describe('SessionStatusBanner', () => { it('has descriptive title', () => { const usageStats = createUsageStats({ - inputTokens: 50000, + inputTokens: 100000, outputTokens: 50000, contextWindow: 200000, }); @@ -1285,7 +1285,7 @@ describe('SessionStatusBanner', () => { // Tokens expect(screen.getByText('7.0K')).toBeInTheDocument(); // Context usage - expect(screen.getByText('4%')).toBeInTheDocument(); + expect(screen.getByText('3%')).toBeInTheDocument(); // Last response section expect(screen.getByText(/5m ago/)).toBeInTheDocument(); }); @@ -1443,7 +1443,7 @@ describe('SessionStatusBanner', () => { expect(screen.getByText('AI')).toBeInTheDocument(); expect(screen.getByText('$2.50')).toBeInTheDocument(); expect(screen.getByText('180.0K')).toBeInTheDocument(); - expect(screen.getByText('90%')).toBeInTheDocument(); + expect(screen.getByText('75%')).toBeInTheDocument(); expect(screen.getByText('1:05')).toBeInTheDocument(); expect(screen.getByLabelText('AI is thinking')).toBeInTheDocument(); }); diff --git a/src/renderer/components/HistoryDetailModal.tsx b/src/renderer/components/HistoryDetailModal.tsx index 8d578ffe..9fed6607 100644 --- a/src/renderer/components/HistoryDetailModal.tsx +++ b/src/renderer/components/HistoryDetailModal.tsx @@ -343,9 +343,11 @@ export function HistoryDetailModal({ {(() => { - // Context usage = (input + output + cache read) / context window - // Cache read tokens represent the conversation history sent with each request - const contextTokens = entry.usageStats!.inputTokens + entry.usageStats!.outputTokens + (entry.usageStats!.cacheReadInputTokens || 0); + // Context usage = (input + cache creation + cache read) / context window + const contextTokens = + entry.usageStats!.inputTokens + + (entry.usageStats!.cacheCreationInputTokens || 0) + + (entry.usageStats!.cacheReadInputTokens || 0); const contextUsage = Math.min(100, Math.round((contextTokens / entry.usageStats!.contextWindow) * 100)); return (