From 0930f9d254654a284a163faf53166d3976cf07fc Mon Sep 17 00:00:00 2001 From: Ammar Date: Sun, 18 Jan 2026 17:59:17 -0600 Subject: [PATCH 1/2] feat: mark output reserve on auto-compact sliders --- .../ContextUsageIndicatorButton.tsx | 47 ++++++++++++++++ .../RightSidebar/ContextUsageBar.tsx | 55 ++++++++++++++++++- src/common/utils/tokens/tokenMeterUtils.ts | 3 + 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/browser/components/ContextUsageIndicatorButton.tsx b/src/browser/components/ContextUsageIndicatorButton.tsx index 65f2f46832..b8084bc04a 100644 --- a/src/browser/components/ContextUsageIndicatorButton.tsx +++ b/src/browser/components/ContextUsageIndicatorButton.tsx @@ -11,6 +11,17 @@ import { Switch } from "./ui/switch"; import { formatTokens, type TokenMeterData } from "@/common/utils/tokens/tokenMeterUtils"; import { cn } from "@/common/lib/utils"; +/** Output reserve indicator (context limit minus max output tokens) */ +const OutputReserveIndicator: React.FC<{ threshold: number }> = ({ threshold }) => { + if (threshold <= 0 || threshold >= 100) return null; + + return ( +
+ ); +}; /** Compact threshold tick mark for the button view */ const CompactThresholdIndicator: React.FC<{ threshold: number }> = ({ threshold }) => { if (threshold >= 100) return null; @@ -79,6 +90,25 @@ const AutoCompactSettings: React.FC<{ const showUsageSlider = usageConfig && data.maxTokens; const isIdleEnabled = idleConfig?.hours !== null && idleConfig?.hours !== undefined; + const outputReserveThreshold = React.useMemo(() => { + if (!data.maxTokens || !data.maxOutputTokens) return null; + if (data.maxOutputTokens <= 0 || data.maxOutputTokens >= data.maxTokens) return null; + const raw = ((data.maxTokens - data.maxOutputTokens) / data.maxTokens) * 100; + return Math.max(0, Math.min(100, raw)); + }, [data.maxTokens, data.maxOutputTokens]); + + const outputReserveTokens = + data.maxTokens && data.maxOutputTokens ? data.maxTokens - data.maxOutputTokens : null; + + const showOutputReserveIndicator = Boolean(showUsageSlider && outputReserveThreshold !== null); + const showOutputReserveWarning = Boolean( + showUsageSlider && + usageConfig && + usageConfig.threshold < 100 && + outputReserveThreshold !== null && + usageConfig.threshold > outputReserveThreshold + ); + const handleIdleToggle = (enabled: boolean) => { if (!idleConfig) return; const parsed = parseInt(idleInputValue, 10); @@ -118,9 +148,26 @@ const AutoCompactSettings: React.FC<{
+ {showOutputReserveIndicator && outputReserveThreshold !== null && ( + + )} {showUsageSlider && }
{showUsageSlider && } + {showOutputReserveIndicator && + outputReserveThreshold !== null && + outputReserveTokens !== null && ( +
+ Output reserve starts at {outputReserveThreshold.toFixed(1)}% ( + {formatTokens(outputReserveTokens)} prompt max) +
+ )} + {showOutputReserveWarning && outputReserveThreshold !== null && ( +
+ Auto-compact threshold is above the output reserve ({outputReserveThreshold.toFixed(1)} + %). Requests may hit context_exceeded before auto-compact runs. +
+ )}
{/* Idle-based auto-compact */} diff --git a/src/browser/components/RightSidebar/ContextUsageBar.tsx b/src/browser/components/RightSidebar/ContextUsageBar.tsx index 4d9fefce51..30271c972e 100644 --- a/src/browser/components/RightSidebar/ContextUsageBar.tsx +++ b/src/browser/components/RightSidebar/ContextUsageBar.tsx @@ -3,6 +3,17 @@ import { TokenMeter } from "./TokenMeter"; import { HorizontalThresholdSlider, type AutoCompactionConfig } from "./ThresholdSlider"; import { formatTokens, type TokenMeterData } from "@/common/utils/tokens/tokenMeterUtils"; +const OutputReserveIndicator: React.FC<{ threshold: number }> = ({ threshold }) => { + if (threshold <= 0 || threshold >= 100) return null; + + return ( +
+ ); +}; + interface ContextUsageBarProps { data: TokenMeterData; /** Auto-compaction settings for threshold slider */ @@ -17,8 +28,6 @@ const ContextUsageBarComponent: React.FC = ({ showTitle = true, testId, }) => { - if (data.totalTokens === 0) return null; - const totalDisplay = formatTokens(data.totalTokens); const maxDisplay = data.maxTokens ? ` / ${formatTokens(data.maxTokens)}` : ""; const percentageDisplay = data.maxTokens ? ` (${data.totalPercentage.toFixed(1)}%)` : ""; @@ -26,6 +35,29 @@ const ContextUsageBarComponent: React.FC = ({ const showWarning = !data.maxTokens; const showThresholdSlider = autoCompaction && data.maxTokens; + const outputReserveThreshold = React.useMemo(() => { + if (!data.maxTokens || !data.maxOutputTokens) return null; + if (data.maxOutputTokens <= 0 || data.maxOutputTokens >= data.maxTokens) return null; + const raw = ((data.maxTokens - data.maxOutputTokens) / data.maxTokens) * 100; + return Math.max(0, Math.min(100, raw)); + }, [data.maxTokens, data.maxOutputTokens]); + + const outputReserveTokens = + data.maxTokens && data.maxOutputTokens ? data.maxTokens - data.maxOutputTokens : null; + + const showOutputReserveIndicator = Boolean( + showThresholdSlider && outputReserveThreshold !== null + ); + const showOutputReserveWarning = Boolean( + showThresholdSlider && + autoCompaction && + autoCompaction.threshold < 100 && + outputReserveThreshold !== null && + autoCompaction.threshold > outputReserveThreshold + ); + + if (data.totalTokens === 0) return null; + return (
@@ -43,9 +75,28 @@ const ContextUsageBarComponent: React.FC = ({
+ {showOutputReserveIndicator && outputReserveThreshold !== null && ( + + )} {showThresholdSlider && }
+ {showOutputReserveIndicator && + outputReserveThreshold !== null && + outputReserveTokens !== null && ( +
+ Output reserve starts at {outputReserveThreshold.toFixed(1)}% ( + {formatTokens(outputReserveTokens)} prompt max) +
+ )} + + {showOutputReserveWarning && outputReserveThreshold !== null && ( +
+ Auto-compact threshold is above the output reserve ({outputReserveThreshold.toFixed(1)}%). + Requests may hit context_exceeded before auto-compact runs. +
+ )} + {showWarning && (
Unknown model limits - showing relative usage only diff --git a/src/common/utils/tokens/tokenMeterUtils.ts b/src/common/utils/tokens/tokenMeterUtils.ts index 5943fa99f7..ca13a61728 100644 --- a/src/common/utils/tokens/tokenMeterUtils.ts +++ b/src/common/utils/tokens/tokenMeterUtils.ts @@ -23,6 +23,7 @@ export interface TokenMeterData { segments: TokenSegment[]; totalTokens: number; maxTokens?: number; + maxOutputTokens?: number; totalPercentage: number; } @@ -65,6 +66,7 @@ export function calculateTokenMeterData( const modelStats = getModelStats(model); const maxTokens = use1M && supports1MContext(model) ? 1_000_000 : modelStats?.max_input_tokens; + const maxOutputTokens = modelStats?.max_output_tokens; // Total tokens used in the request. // For Anthropic prompt caching, cacheCreate tokens are reported separately but still @@ -96,6 +98,7 @@ export function calculateTokenMeterData( segments, totalTokens: totalUsed, maxTokens, + maxOutputTokens, totalPercentage: verticalProportions ? maxTokens ? (totalUsed / maxTokens) * 100 From a48e7bf7b4cd088d574cb2e6d14999950409e0a2 Mon Sep 17 00:00:00 2001 From: Ammar Date: Sun, 18 Jan 2026 18:06:28 -0600 Subject: [PATCH 2/2] fix: align reserve indicator with react compiler --- src/browser/components/ContextUsageIndicatorButton.tsx | 7 ++++--- src/browser/components/RightSidebar/ContextUsageBar.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/browser/components/ContextUsageIndicatorButton.tsx b/src/browser/components/ContextUsageIndicatorButton.tsx index b8084bc04a..efe283cb0d 100644 --- a/src/browser/components/ContextUsageIndicatorButton.tsx +++ b/src/browser/components/ContextUsageIndicatorButton.tsx @@ -12,7 +12,8 @@ import { formatTokens, type TokenMeterData } from "@/common/utils/tokens/tokenMe import { cn } from "@/common/lib/utils"; /** Output reserve indicator (context limit minus max output tokens) */ -const OutputReserveIndicator: React.FC<{ threshold: number }> = ({ threshold }) => { +const OutputReserveIndicator: React.FC<{ threshold: number }> = (props) => { + const threshold = props.threshold; if (threshold <= 0 || threshold >= 100) return null; return ( @@ -90,12 +91,12 @@ const AutoCompactSettings: React.FC<{ const showUsageSlider = usageConfig && data.maxTokens; const isIdleEnabled = idleConfig?.hours !== null && idleConfig?.hours !== undefined; - const outputReserveThreshold = React.useMemo(() => { + const outputReserveThreshold = (() => { if (!data.maxTokens || !data.maxOutputTokens) return null; if (data.maxOutputTokens <= 0 || data.maxOutputTokens >= data.maxTokens) return null; const raw = ((data.maxTokens - data.maxOutputTokens) / data.maxTokens) * 100; return Math.max(0, Math.min(100, raw)); - }, [data.maxTokens, data.maxOutputTokens]); + })(); const outputReserveTokens = data.maxTokens && data.maxOutputTokens ? data.maxTokens - data.maxOutputTokens : null; diff --git a/src/browser/components/RightSidebar/ContextUsageBar.tsx b/src/browser/components/RightSidebar/ContextUsageBar.tsx index 30271c972e..d25d3147b8 100644 --- a/src/browser/components/RightSidebar/ContextUsageBar.tsx +++ b/src/browser/components/RightSidebar/ContextUsageBar.tsx @@ -3,7 +3,8 @@ import { TokenMeter } from "./TokenMeter"; import { HorizontalThresholdSlider, type AutoCompactionConfig } from "./ThresholdSlider"; import { formatTokens, type TokenMeterData } from "@/common/utils/tokens/tokenMeterUtils"; -const OutputReserveIndicator: React.FC<{ threshold: number }> = ({ threshold }) => { +const OutputReserveIndicator: React.FC<{ threshold: number }> = (props) => { + const threshold = props.threshold; if (threshold <= 0 || threshold >= 100) return null; return ( @@ -35,12 +36,12 @@ const ContextUsageBarComponent: React.FC = ({ const showWarning = !data.maxTokens; const showThresholdSlider = autoCompaction && data.maxTokens; - const outputReserveThreshold = React.useMemo(() => { + const outputReserveThreshold = (() => { if (!data.maxTokens || !data.maxOutputTokens) return null; if (data.maxOutputTokens <= 0 || data.maxOutputTokens >= data.maxTokens) return null; const raw = ((data.maxTokens - data.maxOutputTokens) / data.maxTokens) * 100; return Math.max(0, Math.min(100, raw)); - }, [data.maxTokens, data.maxOutputTokens]); + })(); const outputReserveTokens = data.maxTokens && data.maxOutputTokens ? data.maxTokens - data.maxOutputTokens : null;