Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Text, Tooltip } from "@radix-ui/themes";
import { useContextUsageForTask } from "@renderer/features/sessions/hooks/useSession";

const CONTEXT_WARNING_THRESHOLD_PCT = 40;

interface ContextUsageIndicatorProps {
taskId?: string;
}

export function ContextUsageIndicator({ taskId }: ContextUsageIndicatorProps) {
const contextUsage = useContextUsageForTask(taskId);
if (!contextUsage || contextUsage.size <= 0) return null;

const percent = Math.round((contextUsage.used / contextUsage.size) * 100);

if (percent < CONTEXT_WARNING_THRESHOLD_PCT) return null;

return (
<Tooltip
content={`Context: ${percent}% used (${Math.round(contextUsage.used / 1000)}k / ${Math.round(contextUsage.size / 1000)}k tokens)`}
>
<Text
size="1"
style={{
color: getContextColor(percent),
fontFamily: "var(--font-mono)",
padding: "4px 10px",
cursor: "default",
}}
>
{percent}%
</Text>
</Tooltip>
);
}

function getContextColor(percent: number): string {
if (percent >= 80) return "var(--red-9)";
if (percent >= 50) return "var(--yellow-11)";
return "var(--green-9)";
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Paperclip } from "@phosphor-icons/react";
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
import { useRef } from "react";
import type { FileAttachment } from "../utils/content";
import { ContextUsageIndicator } from "./ContextUsageIndicator";

interface EditorToolbarProps {
disabled?: boolean;
Expand Down Expand Up @@ -69,6 +70,7 @@ export function EditorToolbar({
{!hideSelectors && (
<ModelSelector taskId={taskId} adapter={adapter} disabled={disabled} />
)}
<ContextUsageIndicator taskId={taskId} />
</Flex>
);
}
19 changes: 19 additions & 0 deletions apps/code/src/renderer/features/sessions/hooks/useSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,25 @@ export const useThoughtLevelConfigOptionForTask = (
return useConfigOptionForTask(taskId, "thought_level");
};

/** Get context window usage for a task (used / size) */
export const useContextUsageForTask = (
taskId: string | undefined,
): { used: number; size: number } | undefined => {
return useSessionStore((s) => {
if (!taskId) return undefined;
const taskRunId = s.taskIdIndex[taskId];
if (!taskRunId) return undefined;
const session = s.sessions[taskRunId];
if (
session?.contextUsed === undefined ||
session?.contextSize === undefined
) {
return undefined;
}
return { used: session.contextUsed, size: session.contextSize };
});
};

/** Get the adapter type for a task */
export const useAdapterForTask = (
taskId: string | undefined,
Expand Down
17 changes: 17 additions & 0 deletions apps/code/src/renderer/features/sessions/service/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,23 @@ export class SessionService {
setPersistedConfigOptions(taskRunId, configOptions);
log.info("Session config options updated", { taskRunId });
}

// Handle context usage updates
if (params?.update?.sessionUpdate === "usage_update") {
const update = params.update as {
used?: number;
size?: number;
};
if (
typeof update.used === "number" &&
typeof update.size === "number"
) {
sessionStoreSetters.updateSession(taskRunId, {
contextUsed: update.used,
contextSize: update.size,
});
}
}
}

// Handle _posthog/sdk_session notifications for adapter info
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export interface AgentSession {
/** Number of session/prompt events to skip from polled logs (set during resume) */
skipPolledPromptCount?: number;
optimisticItems: OptimisticItem[];
/** Context window tokens used (from usage_update) */
contextUsed?: number;
/** Context window total size in tokens (from usage_update) */
contextSize?: number;
}

// --- Config Option Helpers ---
Expand Down
5 changes: 5 additions & 0 deletions packages/agent/src/adapters/claude/claude-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,11 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
? Math.min(...contextWindows)
: getDefaultContextWindow(this.session.modelId ?? "");

this.session.contextSize = contextWindowSize;
if (lastAssistantTotalUsage !== null) {
this.session.contextUsed = lastAssistantTotalUsage;
}

// Send usage_update notification
if (lastAssistantTotalUsage !== null) {
await this.client.sessionUpdate({
Expand Down
4 changes: 4 additions & 0 deletions packages/agent/src/adapters/claude/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export type Session = BaseSession & {
effort?: EffortLevel;
configOptions: SessionConfigOption[];
accumulatedUsage: AccumulatedUsage;
/** Latest context window usage (total tokens from last assistant message) */
contextUsed?: number;
/** Context window size in tokens */
contextSize?: number;
promptRunning: boolean;
pendingMessages: Map<string, PendingMessage>;
nextPendingOrder: number;
Expand Down
Loading