mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
fix: align context usage with claude code
This commit is contained in:
@@ -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)', () => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
@@ -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(<MainPanel {...defaultProps} activeSession={session} getContextColor={getContextColor} />);
|
||||
|
||||
// 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
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,8 +61,8 @@ const createTestTab = (overrides: Partial<AITab> = {}): 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,
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -343,9 +343,11 @@ export function HistoryDetailModal({
|
||||
</span>
|
||||
</div>
|
||||
{(() => {
|
||||
// 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 (
|
||||
<div className="flex flex-col gap-1">
|
||||
|
||||
Reference in New Issue
Block a user