Skip to content

Commit 968c20b

Browse files
committed
WIP: Handling context better
1 parent dba2150 commit 968c20b

File tree

3 files changed

+90
-18
lines changed

3 files changed

+90
-18
lines changed

apps/desktop/src/components/BranchList.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@
145145
commitQuery.result
146146
)}
147147
>
148-
{#snippet children([localAndRemoteCommits, upstreamOnlyCommits, branchDetails, commit])}
148+
{#snippet children([localAndRemoteCommits, _upstreamOnlyCommits, branchDetails, commit])}
149+
{@const upstreamOnlyCommits = []}
149150
{@const firstBranch = i === 0}
150151
{@const lastBranch = i === branches.length - 1}
151152
{@const iconName = getIconFromCommitState(commit?.id, commit?.state)}

apps/desktop/src/components/codegen/CodegenPage.svelte

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
ContextMenu,
6565
ContextMenuItem,
6666
ContextMenuSection,
67+
DropdownButton,
6768
EmptyStatePlaceholder,
6869
Modal
6970
} from '@gitbutler/ui';
@@ -221,7 +222,7 @@
221222
return;
222223
}
223224
224-
if (prompt.startsWith('/')) {
225+
if (prompt.startsWith('/') && !prompt.startsWith('/compact')) {
225226
chipToasts.warning('Slash commands are not yet supported');
226227
setPrompt('');
227228
return;
@@ -374,6 +375,35 @@
374375
clearContextModal?.show();
375376
}
376377
378+
async function compactContext() {
379+
if (!selectedBranch) return;
380+
381+
// Await analytics data before sending message
382+
const analyticsProperties = await codegenAnalytics.getCodegenProperties({
383+
projectId,
384+
stackId: selectedBranch.stackId,
385+
message: prompt,
386+
thinkingLevel: selectedThinkingLevel,
387+
model: selectedModel
388+
});
389+
390+
await sendClaudeMessage(
391+
{
392+
projectId,
393+
stackId: selectedBranch.stackId,
394+
message: prompt,
395+
thinkingLevel: selectedThinkingLevel,
396+
model: selectedModel,
397+
permissionMode: selectedPermissionMode,
398+
disabledMcpServers: uiState.lane(selectedBranch.stackId).disabledMcpServers.current,
399+
addDirs: laneState?.addedDirs.current || []
400+
},
401+
{ properties: analyticsProperties }
402+
);
403+
}
404+
405+
let selectedContextAction = $state<'clear' | 'compact'>('compact');
406+
377407
async function performClearContextAndRules() {
378408
if (!selectedBranch) return;
379409
@@ -564,6 +594,8 @@
564594
</Button>
565595
{/snippet}
566596
{#snippet contextActions()}
597+
{@const stats = usageStats(events)}
598+
<Badge>Context utilization {(stats.contextUtilization * 100).toFixed(0)}%</Badge>
567599
<Button
568600
kind="outline"
569601
icon="mcp"
@@ -575,15 +607,31 @@
575607
<Badge kind="soft">{enabledMcpServers}</Badge>
576608
{/snippet}
577609
</Button>
578-
<Button
610+
<DropdownButton
579611
disabled={!hasRulesToClear || formattedMessages.length === 0}
580612
kind="outline"
581613
style="warning"
582-
icon="clear-small"
583-
onclick={clearContextAndRules}
614+
menuSide="top"
615+
autoClose={true}
616+
onclick={() =>
617+
selectedContextAction === 'compact' ? compactContext() : clearContextAndRules()}
584618
>
585-
Clear context
586-
</Button>
619+
{selectedContextAction === 'compact' ? 'Compact context' : 'Clear context'}
620+
{#snippet contextMenuSlot()}
621+
<ContextMenuSection>
622+
<ContextMenuItem
623+
label="Compact context"
624+
icon="clear-small"
625+
onclick={() => (selectedContextAction = 'compact')}
626+
/>
627+
<ContextMenuItem
628+
label="Clear context"
629+
icon="clear-small"
630+
onclick={() => (selectedContextAction = 'clear')}
631+
/>
632+
</ContextMenuSection>
633+
{/snippet}
634+
</DropdownButton>
587635
{/snippet}
588636
{#snippet messages()}
589637
{#if currentStatus(events, isStackActive) === 'running' && lastUserMessageSent}

apps/desktop/src/lib/codegen/messages.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -250,21 +250,24 @@ const pricing = [
250250
input: 15,
251251
output: 75,
252252
writeCache: 18.75,
253-
readCache: 1.5
253+
readCache: 1.5,
254+
context: 200_000
254255
},
255256
{
256257
name: 'claude-sonnet',
257258
input: 3,
258259
output: 6,
259260
writeCache: 3.75,
260-
readCache: 0.3
261+
readCache: 0.3,
262+
context: 200_000
261263
},
262264
{
263265
name: 'claude-haiku',
264266
input: 0.8,
265267
output: 4,
266268
writeCache: 1,
267-
readCache: 0.08
269+
readCache: 0.08,
270+
context: 200_000
268271
}
269272
] as const;
270273

@@ -283,26 +286,46 @@ const webRequestCost = 10;
283286
export function usageStats(events: ClaudeMessage[]): {
284287
tokens: number;
285288
cost: number;
289+
/** Percentage (0 to 1) of how full the context is */
290+
contextUtilization: number;
286291
} {
287292
let tokens = 0;
288-
let cost = 0;
289-
const usedIds = new Set();
293+
let contextUtilization = 0;
294+
let lastAssistantMessage;
295+
290296
for (let i = events.length - 1; i >= 0; i--) {
291297
const event = events[i]!;
292298
if (event.content.type !== 'claudeOutput') continue;
293299
const message = event.content.subject;
294300
if (message.type !== 'assistant') continue;
295-
const usage = message.message.usage;
301+
lastAssistantMessage = message;
302+
break;
303+
}
296304

297-
if (usedIds.has(message.message.id)) {
298-
continue;
299-
}
300-
usedIds.add(message.message.id);
305+
if (lastAssistantMessage) {
306+
const usage = lastAssistantMessage.message.usage;
307+
tokens += usage.cache_read_input_tokens ?? 0;
301308
tokens += usage.input_tokens;
302309
tokens += usage.output_tokens;
310+
const modelPricing = findModelPricing(lastAssistantMessage.message.model);
311+
if (modelPricing) {
312+
contextUtilization = tokens / modelPricing.context;
313+
}
314+
}
303315

316+
let cost = 0;
317+
const usedIds = new Set();
318+
319+
for (let i = events.length - 1; i >= 0; i--) {
320+
const event = events[i]!;
321+
if (event.content.type !== 'claudeOutput') continue;
322+
const message = event.content.subject;
323+
if (message.type !== 'assistant') continue;
324+
if (usedIds.has(message.message.id)) continue;
325+
usedIds.add(message.message.id);
304326
const modelPricing = findModelPricing(message.message.model);
305327
if (!modelPricing) continue;
328+
const usage = message.message.usage;
306329

307330
cost += (usage.input_tokens * modelPricing.input) / 1_000_000;
308331
cost += (usage.output_tokens * modelPricing.output) / 1_000_000;
@@ -311,7 +334,7 @@ export function usageStats(events: ClaudeMessage[]): {
311334
cost += ((usage.server_tool_use?.web_search_requests || 0) * webRequestCost) / 1_000;
312335
}
313336

314-
return { tokens, cost };
337+
return { tokens, cost, contextUtilization };
315338
}
316339

317340
function findModelPricing(name: string) {

0 commit comments

Comments
 (0)