fix(ui): stop the context meter from counting every internal round-trip
The token breakdown of an assistant message accumulates across every API round-trip inside the turn: each tool call re-reads the whole cached prompt, so input/cache.read add up to several times the context window. Every context-usage surface summed those fields, which is why the meter could read 330% of a 1M window whose real fill was 232,872 tokens (23.3%), and why reopening an older session jumps the readout (#2562). The server reports the final round-trip's window as tokens.total (optional in the message schema; opencode 1.18.18 returns it, verified against its live /session/:id/message API). Prefer it everywhere the window fill is displayed and fall back to summing only when the server did not send it: contextTokensFromBreakdown in tokenUtils now owns that rule, and the context store extractor, sync store getter, work status panel, context sidebar, VS Code layout, mini chat, and mobile metadata all use it instead of their own inline sums. Fixes #2562
This commit is contained in:
@@ -92,6 +92,7 @@ const extractTokenBreakdown = (message: SessionMessage): TokenBreakdown => {
|
||||
}
|
||||
|
||||
const breakdown = source as {
|
||||
total?: unknown;
|
||||
input?: unknown;
|
||||
output?: unknown;
|
||||
reasoning?: unknown;
|
||||
@@ -103,6 +104,10 @@ const extractTokenBreakdown = (message: SessionMessage): TokenBreakdown => {
|
||||
const reasoning = toNonNegativeNumber(breakdown.reasoning);
|
||||
const cacheRead = toNonNegativeNumber(breakdown.cache?.read);
|
||||
const cacheWrite = toNonNegativeNumber(breakdown.cache?.write);
|
||||
// Multi-step turns accumulate the fields across API round-trips (every tool
|
||||
// call re-reads the whole cached prompt), so summing them overstates the
|
||||
// window. The server-reported total is the final round-trip's window.
|
||||
const reportedTotal = toNonNegativeNumber(breakdown.total);
|
||||
|
||||
return {
|
||||
input,
|
||||
@@ -110,7 +115,7 @@ const extractTokenBreakdown = (message: SessionMessage): TokenBreakdown => {
|
||||
reasoning,
|
||||
cacheRead,
|
||||
cacheWrite,
|
||||
total: input + output + reasoning + cacheRead + cacheWrite,
|
||||
total: reportedTotal > 0 ? reportedTotal : input + output + reasoning + cacheRead + cacheWrite,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useViewportStore } from '@/sync/viewport-store';
|
||||
import { useSessions, useDirectorySync, useSessionMessages, useSessionMessagesResolved } from '@/sync/sync-context';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { resolveGlobalSessionDirectory, useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
|
||||
import { contextTokensFromBreakdown } from '@/stores/utils/tokenUtils';
|
||||
import { ContextUsageDisplay } from '@/components/ui/ContextUsageDisplay';
|
||||
import { McpDropdown } from '@/components/mcp/McpDropdown';
|
||||
import { ArchiveAllDropdown } from '@/components/session/ArchiveAllDropdown';
|
||||
@@ -702,7 +703,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
}
|
||||
|
||||
if (!lastTokens && message.tokens) {
|
||||
const total = message.tokens.input + message.tokens.output + message.tokens.reasoning + (message.tokens.cache?.read ?? 0) + (message.tokens.cache?.write ?? 0);
|
||||
const total = contextTokensFromBreakdown(message.tokens);
|
||||
if (total > 0) {
|
||||
lastTokens = message.tokens;
|
||||
lastMessageId = (currentSessionMessages[i] as { id?: string }).id;
|
||||
@@ -730,7 +731,7 @@ const VSCodeHeader: React.FC<VSCodeHeaderProps> = ({ title, showBack, onBack, on
|
||||
}
|
||||
|
||||
const lastTokens = headerMessageSummary.lastTokens;
|
||||
const totalTokens = lastTokens.input + lastTokens.output + lastTokens.reasoning + (lastTokens.cache?.read ?? 0) + (lastTokens.cache?.write ?? 0);
|
||||
const totalTokens = contextTokensFromBreakdown(lastTokens);
|
||||
const thresholdLimit = contextLimit > 0 ? contextLimit : 200000;
|
||||
const percentage = contextLimit > 0 ? Math.round((totalTokens / contextLimit) * 100) : 0;
|
||||
const normalizedOutput = outputLimit > 0 ? Math.round((lastTokens.output / outputLimit) * 100) : undefined;
|
||||
|
||||
Reference in New Issue
Block a user