diff --git a/frontend/src/components/StatusBar.jsx b/frontend/src/components/StatusBar.jsx
index 1132069..8007de2 100644
--- a/frontend/src/components/StatusBar.jsx
+++ b/frontend/src/components/StatusBar.jsx
@@ -22,6 +22,7 @@ export function StatusBar({ sessionStats, isProcessing, connected, permissionMod
cacheReadTokens = 0,
cacheCreationTokens = 0,
numTurns = 0,
+ contextWindow = 200000,
isCompacting = false,
} = sessionStats || {};
@@ -29,10 +30,12 @@ export function StatusBar({ sessionStats, isProcessing, connected, permissionMod
const currentMode = PERMISSION_MODES.find(m => m.value === permissionMode) || PERMISSION_MODES[0];
const ModeIcon = currentMode.icon;
- // Calculate total tokens and estimate context usage
- const totalTokens = inputTokens + outputTokens;
- // Claude has ~200k context, but we show relative usage
- const contextPercent = Math.min(100, (inputTokens / 200000) * 100);
+ // Calculate context usage
+ // inputTokens from modelUsage represents tokens in current context (including cache reads)
+ // contextWindow is the model's max context size (e.g., 200000 for opus)
+ const contextUsed = inputTokens + outputTokens;
+ const contextPercent = contextWindow > 0 ? Math.min(100, (contextUsed / contextWindow) * 100) : 0;
+ const contextRemaining = Math.max(0, 100 - contextPercent);
// Format cost
const formatCost = (cost) => {
@@ -137,7 +140,7 @@ export function StatusBar({ sessionStats, isProcessing, connected, permissionMod
{/* Right side: Token usage */}
{/* Token counts */}
- {totalTokens > 0 && (
+ {contextUsed > 0 && (
In:
@@ -156,15 +159,18 @@ export function StatusBar({ sessionStats, isProcessing, connected, permissionMod
)}
- {/* Context status - simple text based on remaining context */}
+ {/* Context status - shows remaining context percentage */}
Context:
- {inputTokens > 0 ? (
+ {contextUsed > 0 ? (
= 95 ? 'text-red-400 font-medium' :
- contextPercent >= 85 ? 'text-yellow-400' : 'text-green-400'
+ contextRemaining <= 5 ? 'text-red-400 font-medium animate-pulse' :
+ contextRemaining <= 15 ? 'text-red-400' :
+ contextRemaining <= 30 ? 'text-yellow-400' : 'text-green-400'
}`}>
- {contextPercent >= 85 ? `${(100 - contextPercent).toFixed(0)}% left` : 'ok'}
+ {contextRemaining <= 30
+ ? `${contextRemaining.toFixed(0)}% left`
+ : `${contextPercent.toFixed(0)}% used`}
) : (
ok
diff --git a/frontend/src/hooks/useClaudeSession.js b/frontend/src/hooks/useClaudeSession.js
index 33d072c..59095d6 100644
--- a/frontend/src/hooks/useClaudeSession.js
+++ b/frontend/src/hooks/useClaudeSession.js
@@ -32,6 +32,7 @@ export function useClaudeSession() {
cacheReadTokens: 0,
cacheCreationTokens: 0,
numTurns: 0,
+ contextWindow: 200000, // Default, updated from modelUsage
isCompacting: false,
});
@@ -361,14 +362,29 @@ export function useClaudeSession() {
setIsProcessing(false);
// Update session stats from result event
- if (event.usage || event.total_cost_usd !== undefined) {
+ // Claude sends detailed modelUsage with per-model stats including contextWindow
+ // usage contains: input_tokens (new), cache_read_input_tokens, cache_creation_input_tokens, output_tokens
+ if (event.usage || event.modelUsage || event.total_cost_usd !== undefined) {
+ // Get the primary model's usage (usually claude-opus or claude-sonnet)
+ const modelUsage = event.modelUsage || {};
+ const primaryModel = Object.keys(modelUsage).find(k => k.includes('opus') || k.includes('sonnet'))
+ || Object.keys(modelUsage)[0];
+ const primaryUsage = primaryModel ? modelUsage[primaryModel] : null;
+
+ // Calculate effective input tokens (what's actually in context)
+ // This is: new input + cache read (cache creation doesn't count towards context limit)
+ const effectiveInput = (event.usage?.input_tokens || 0) +
+ (event.usage?.cache_read_input_tokens || 0);
+
setSessionStats(prev => ({
- totalCost: event.total_cost_usd || prev.totalCost,
- inputTokens: event.usage?.input_tokens || prev.inputTokens,
- outputTokens: event.usage?.output_tokens || prev.outputTokens,
- cacheReadTokens: event.usage?.cache_read_input_tokens || prev.cacheReadTokens,
- cacheCreationTokens: event.usage?.cache_creation_input_tokens || prev.cacheCreationTokens,
- numTurns: event.num_turns || prev.numTurns,
+ totalCost: event.total_cost_usd ?? prev.totalCost,
+ // Use modelUsage for accurate per-turn counts, fallback to usage
+ inputTokens: primaryUsage?.inputTokens ?? effectiveInput ?? prev.inputTokens,
+ outputTokens: primaryUsage?.outputTokens ?? event.usage?.output_tokens ?? prev.outputTokens,
+ cacheReadTokens: primaryUsage?.cacheReadInputTokens ?? event.usage?.cache_read_input_tokens ?? prev.cacheReadTokens,
+ cacheCreationTokens: primaryUsage?.cacheCreationInputTokens ?? event.usage?.cache_creation_input_tokens ?? prev.cacheCreationTokens,
+ numTurns: event.num_turns ?? prev.numTurns,
+ contextWindow: primaryUsage?.contextWindow ?? prev.contextWindow,
isCompacting: false,
}));
}