Skip to content

Commit b48fec6

Browse files
authored
feat(code): add context usage indicator (#1282)
tracks context usage now, and adds a small indicator in the prompt input box hidden til > 40%, color-coded green -> yellow -> red ![Screenshot 2026-03-17 at 10.04.12 AM.png](https://app.graphite.com/user-attachments/assets/bf068537-4896-4c5b-9594-b71bbb653627.png) this is mostly because i want to add a "clear context" checkbox to plan approval requests next :)
1 parent 53ed7f4 commit b48fec6

7 files changed

Lines changed: 92 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Text, Tooltip } from "@radix-ui/themes";
2+
import { useContextUsageForTask } from "@renderer/features/sessions/hooks/useSession";
3+
4+
const CONTEXT_WARNING_THRESHOLD_PCT = 40;
5+
6+
interface ContextUsageIndicatorProps {
7+
taskId?: string;
8+
}
9+
10+
export function ContextUsageIndicator({ taskId }: ContextUsageIndicatorProps) {
11+
const contextUsage = useContextUsageForTask(taskId);
12+
if (!contextUsage || contextUsage.size <= 0) return null;
13+
14+
const percent = Math.round((contextUsage.used / contextUsage.size) * 100);
15+
16+
if (percent < CONTEXT_WARNING_THRESHOLD_PCT) return null;
17+
18+
return (
19+
<Tooltip
20+
content={`Context: ${percent}% used (${Math.round(contextUsage.used / 1000)}k / ${Math.round(contextUsage.size / 1000)}k tokens)`}
21+
>
22+
<Text
23+
size="1"
24+
style={{
25+
color: getContextColor(percent),
26+
fontFamily: "var(--font-mono)",
27+
padding: "4px 10px",
28+
cursor: "default",
29+
}}
30+
>
31+
{percent}%
32+
</Text>
33+
</Tooltip>
34+
);
35+
}
36+
37+
function getContextColor(percent: number): string {
38+
if (percent >= 80) return "var(--red-9)";
39+
if (percent >= 50) return "var(--yellow-11)";
40+
return "var(--green-9)";
41+
}

apps/code/src/renderer/features/message-editor/components/EditorToolbar.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Paperclip } from "@phosphor-icons/react";
33
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
44
import { useRef } from "react";
55
import type { FileAttachment } from "../utils/content";
6+
import { ContextUsageIndicator } from "./ContextUsageIndicator";
67

78
interface EditorToolbarProps {
89
disabled?: boolean;
@@ -69,6 +70,7 @@ export function EditorToolbar({
6970
{!hideSelectors && (
7071
<ModelSelector taskId={taskId} adapter={adapter} disabled={disabled} />
7172
)}
73+
<ContextUsageIndicator taskId={taskId} />
7274
</Flex>
7375
);
7476
}

apps/code/src/renderer/features/sessions/hooks/useSession.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,25 @@ export const useThoughtLevelConfigOptionForTask = (
147147
return useConfigOptionForTask(taskId, "thought_level");
148148
};
149149

150+
/** Get context window usage for a task (used / size) */
151+
export const useContextUsageForTask = (
152+
taskId: string | undefined,
153+
): { used: number; size: number } | undefined => {
154+
return useSessionStore((s) => {
155+
if (!taskId) return undefined;
156+
const taskRunId = s.taskIdIndex[taskId];
157+
if (!taskRunId) return undefined;
158+
const session = s.sessions[taskRunId];
159+
if (
160+
session?.contextUsed === undefined ||
161+
session?.contextSize === undefined
162+
) {
163+
return undefined;
164+
}
165+
return { used: session.contextUsed, size: session.contextSize };
166+
});
167+
};
168+
150169
/** Get the adapter type for a task */
151170
export const useAdapterForTask = (
152171
taskId: string | undefined,

apps/code/src/renderer/features/sessions/service/service.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,23 @@ export class SessionService {
911911
setPersistedConfigOptions(taskRunId, configOptions);
912912
log.info("Session config options updated", { taskRunId });
913913
}
914+
915+
// Handle context usage updates
916+
if (params?.update?.sessionUpdate === "usage_update") {
917+
const update = params.update as {
918+
used?: number;
919+
size?: number;
920+
};
921+
if (
922+
typeof update.used === "number" &&
923+
typeof update.size === "number"
924+
) {
925+
sessionStoreSetters.updateSession(taskRunId, {
926+
contextUsed: update.used,
927+
contextSize: update.size,
928+
});
929+
}
930+
}
914931
}
915932

916933
// Handle _posthog/sdk_session notifications for adapter info

apps/code/src/renderer/features/sessions/stores/sessionStore.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export interface AgentSession {
7373
/** Number of session/prompt events to skip from polled logs (set during resume) */
7474
skipPolledPromptCount?: number;
7575
optimisticItems: OptimisticItem[];
76+
/** Context window tokens used (from usage_update) */
77+
contextUsed?: number;
78+
/** Context window total size in tokens (from usage_update) */
79+
contextSize?: number;
7680
}
7781

7882
// --- Config Option Helpers ---

packages/agent/src/adapters/claude/claude-agent.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
346346
? Math.min(...contextWindows)
347347
: getDefaultContextWindow(this.session.modelId ?? "");
348348

349+
this.session.contextSize = contextWindowSize;
350+
if (lastAssistantTotalUsage !== null) {
351+
this.session.contextUsed = lastAssistantTotalUsage;
352+
}
353+
349354
// Send usage_update notification
350355
if (lastAssistantTotalUsage !== null) {
351356
await this.client.sessionUpdate({

packages/agent/src/adapters/claude/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ export type Session = BaseSession & {
5353
effort?: EffortLevel;
5454
configOptions: SessionConfigOption[];
5555
accumulatedUsage: AccumulatedUsage;
56+
/** Latest context window usage (total tokens from last assistant message) */
57+
contextUsed?: number;
58+
/** Context window size in tokens */
59+
contextSize?: number;
5660
promptRunning: boolean;
5761
pendingMessages: Map<string, PendingMessage>;
5862
nextPendingOrder: number;

0 commit comments

Comments
 (0)