Skip to content

Commit 4c44946

Browse files
committed
feat: storing usage object for chat thread instead of for each assistant message & showing suggestion to open new chat, if prompt tokens are exceeding 30k limit boundary
1 parent 973a3cf commit 4c44946

File tree

13 files changed

+110
-71
lines changed

13 files changed

+110
-71
lines changed

refact-agent/gui/src/app/middleware.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
restoreChat,
1212
newIntegrationChat,
1313
chatResponse,
14+
setThreadUsage,
1415
} from "../features/Chat/Thread";
1516
import { statisticsApi } from "../services/refact/statistics";
1617
import { integrationsApi } from "../services/refact/integrations";
@@ -40,6 +41,7 @@ import {
4041
updateAgentUsage,
4142
updateMaxAgentUsageAmount,
4243
} from "../features/AgentUsage/agentUsageSlice";
44+
import { isChatResponseChoice } from "../services/refact";
4345

4446
const AUTH_ERROR_MESSAGE =
4547
"There is an issue with your API key. Check out your API Key or re-login";
@@ -107,6 +109,13 @@ startListening({
107109
const maxFreeAgentUsage = getMaxFreeAgentUsage(payload);
108110
dispatch(updateMaxAgentUsageAmount(maxFreeAgentUsage));
109111
}
112+
113+
if (isChatResponseChoice(payload)) {
114+
const { usage } = payload;
115+
if (usage) {
116+
dispatch(setThreadUsage({ chatId: payload.id, usage }));
117+
}
118+
}
110119
},
111120
});
112121

refact-agent/gui/src/components/ChatContent/AssistantInput.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,21 @@ import React, { useCallback } from "react";
22
import { Markdown } from "../Markdown";
33

44
import { Container, Box } from "@radix-ui/themes";
5-
import { ToolCall, Usage } from "../../services/refact";
5+
import { ToolCall } from "../../services/refact";
66
import { ToolContent } from "./ToolsContent";
77
import { fallbackCopying } from "../../utils/fallbackCopying";
88
import { telemetryApi } from "../../services/refact/telemetry";
99
import { LikeButton } from "./LikeButton";
10-
import { UsageCounter } from "./UsageCounter";
1110

1211
type ChatInputProps = {
1312
message: string | null;
1413
toolCalls?: ToolCall[] | null;
15-
usage?: Usage | null;
1614
isLast?: boolean;
1715
};
1816

1917
export const AssistantInput: React.FC<ChatInputProps> = ({
2018
message,
2119
toolCalls,
22-
usage,
2320
isLast,
2421
}) => {
2522
const [sendTelemetryEvent] =
@@ -70,7 +67,6 @@ export const AssistantInput: React.FC<ChatInputProps> = ({
7067
</Box>
7168
)}
7269
{toolCalls && <ToolContent toolCalls={toolCalls} />}
73-
{usage && <UsageCounter usage={usage} />}
7470
{isLast && <LikeButton />}
7571
</Container>
7672
);

refact-agent/gui/src/components/ChatContent/ChatContent.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
selectIsWaiting,
2525
selectMessages,
2626
selectThread,
27+
selectThreadUsage,
2728
} from "../../features/Chat/Thread/selectors";
2829
import { takeWhile } from "../../utils";
2930
import { GroupedDiffs } from "./DiffContent";
@@ -32,6 +33,7 @@ import { popBackTo } from "../../features/Pages/pagesSlice";
3233
import { ChatLinks, UncommittedChangesWarning } from "../ChatLinks";
3334
import { telemetryApi } from "../../services/refact/telemetry";
3435
import { PlaceHolderText } from "./PlaceHolderText";
36+
import { UsageCounter } from "./UsageCounter";
3537

3638
export type ChatContentProps = {
3739
onRetry: (index: number, question: UserMessage["content"]) => void;
@@ -47,6 +49,7 @@ export const ChatContent: React.FC<ChatContentProps> = ({
4749
const messages = useAppSelector(selectMessages);
4850
const isStreaming = useAppSelector(selectIsStreaming);
4951
const thread = useAppSelector(selectThread);
52+
const threadUsage = useAppSelector(selectThreadUsage);
5053
const isConfig = thread.mode === "CONFIGURE";
5154
const isWaiting = useAppSelector(selectIsWaiting);
5255
const [sendTelemetryEvent] =
@@ -113,6 +116,7 @@ export const ChatContent: React.FC<ChatContentProps> = ({
113116
{messages.length === 0 && <PlaceHolderText />}
114117
{renderMessages(messages, onRetryWrapper)}
115118
<UncommittedChangesWarning />
119+
{threadUsage && <UsageCounter usage={threadUsage} />}
116120

117121
<Container py="4">
118122
<Spinner spinning={isWaiting} />
@@ -189,7 +193,6 @@ function renderMessages(
189193
key={key}
190194
message={head.content}
191195
toolCalls={head.tool_calls}
192-
usage={head.usage}
193196
isLast={isLast}
194197
/>,
195198
];

refact-agent/gui/src/components/ChatContent/UsageCounter/UsageCounter.tsx

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ArrowDownIcon, ArrowUpIcon } from "@radix-ui/react-icons";
44
import { ScrollArea } from "../../ScrollArea";
55
import { Usage } from "../../../services/refact";
66
import styles from "./UsageCounter.module.css";
7+
import { calculateInputTokens } from "./UsageCounter.utils";
78

89
type UsageCounterProps = {
910
usage: Usage;
@@ -17,19 +18,25 @@ function formatNumber(num: number): string {
1718
: num.toString();
1819
}
1920

20-
const calculateTokens = (usage: Usage, keys: (keyof Usage)[]): number =>
21-
keys.reduce((acc, key) => {
22-
const value = usage[key];
23-
return acc + (typeof value === "number" ? value : 0);
24-
}, 0);
21+
const TokenDisplay: React.FC<{ label: string; value: number }> = ({
22+
label,
23+
value,
24+
}) => (
25+
<Flex align="center" justify="between" width="100%">
26+
<Text size="1" weight="bold">
27+
{label}
28+
</Text>
29+
<Text size="1">{value}</Text>
30+
</Flex>
31+
);
2532

2633
export const UsageCounter: React.FC<UsageCounterProps> = ({ usage }) => {
27-
const inputTokens = calculateTokens(usage, [
34+
const inputTokens = calculateInputTokens(usage, [
2835
"prompt_tokens",
2936
"cache_creation_input_tokens",
3037
"cache_read_input_tokens",
3138
]);
32-
const outputTokens = calculateTokens(usage, ["completion_tokens"]);
39+
const outputTokens = calculateInputTokens(usage, ["completion_tokens"]);
3340

3441
return (
3542
<HoverCard.Root>
@@ -59,43 +66,28 @@ export const UsageCounter: React.FC<UsageCounterProps> = ({ usage }) => {
5966
<Text size="2" mb="2">
6067
Tokens spent per message:
6168
</Text>
62-
<Flex align="center" justify="between" width="100%">
63-
<Text size="1" weight="bold">
64-
Input tokens (in total):{" "}
65-
</Text>
66-
<Text size="1">{inputTokens}</Text>
67-
</Flex>
69+
<TokenDisplay
70+
label="Input tokens (in total):"
71+
value={inputTokens}
72+
/>
6873
{usage.cache_read_input_tokens && (
69-
<Flex align="center" justify="between" width="100%">
70-
<Text size="1" weight="bold">
71-
Cache read input tokens:{" "}
72-
</Text>
73-
<Text size="1">{usage.cache_read_input_tokens}</Text>
74-
</Flex>
74+
<TokenDisplay
75+
label="Cache read input tokens:"
76+
value={usage.cache_read_input_tokens}
77+
/>
7578
)}
7679
{usage.cache_creation_input_tokens && (
77-
<Flex align="center" justify="between" width="100%">
78-
<Text size="1" weight="bold">
79-
Cache creation input tokens:{" "}
80-
</Text>
81-
<Text size="1">{usage.cache_creation_input_tokens}</Text>
82-
</Flex>
80+
<TokenDisplay
81+
label="Cache creation input tokens:"
82+
value={usage.cache_creation_input_tokens}
83+
/>
8384
)}
84-
<Flex align="center" justify="between" width="100%">
85-
<Text size="1" weight="bold">
86-
Completion tokens:{" "}
87-
</Text>
88-
<Text size="1">{outputTokens}</Text>
89-
</Flex>
85+
<TokenDisplay label="Completion tokens:" value={outputTokens} />
9086
{usage.completion_tokens_details && (
91-
<Flex align="center" justify="between" width="100%">
92-
<Text size="1" weight="bold">
93-
Reasoning tokens:{" "}
94-
</Text>
95-
<Text size="1">
96-
{usage.completion_tokens_details.reasoning_tokens}
97-
</Text>
98-
</Flex>
87+
<TokenDisplay
88+
label="Reasoning tokens:"
89+
value={usage.completion_tokens_details.reasoning_tokens}
90+
/>
9991
)}
10092
</Flex>
10193
</HoverCard.Content>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Usage } from "../../../services/refact";
2+
3+
export const calculateInputTokens = (
4+
usage: Usage,
5+
keys: (keyof Usage)[],
6+
): number =>
7+
keys.reduce((acc, key) => {
8+
const value = usage[key];
9+
return acc + (typeof value === "number" ? value : 0);
10+
}, 0);

refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export const SuggestNewChat = ({
9696
usage limits faster.
9797
</Text>
9898
<Flex align="center" gap="3" flexShrink="0">
99-
<Link size="1" onClick={onCreateNewChat}>
99+
<Link size="1" onClick={onCreateNewChat} color="indigo">
100100
Start a new chat
101101
</Link>
102102
<IconButton

refact-agent/gui/src/features/Chat/Thread/actions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
LspChatMode,
99
PayloadWithChatAndMessageId,
1010
PayloadWithChatAndBoolean,
11+
PayloadWithChatAndUsage,
1112
} from "./types";
1213
import {
1314
isAssistantDelta,
@@ -62,6 +63,10 @@ export const setIsNewChatSuggested = createAction<PayloadWithChatAndBoolean>(
6263
"chatThread/setIsNewChatSuggested",
6364
);
6465

66+
export const setThreadUsage = createAction<PayloadWithChatAndUsage>(
67+
"chatThread/setThreadUsage",
68+
);
69+
6570
export const setIsNewChatSuggestionRejected =
6671
createAction<PayloadWithChatAndBoolean>(
6772
"chatThread/setIsNewChatSuggestionRejected",

refact-agent/gui/src/features/Chat/Thread/reducer.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ import {
3535
fixBrokenToolMessages,
3636
setIsNewChatSuggested,
3737
setIsNewChatSuggestionRejected,
38+
setThreadUsage,
3839
} from "./actions";
3940
import { formatChatResponse } from "./utils";
4041
import {
4142
DEFAULT_MAX_NEW_TOKENS,
4243
isToolCallMessage,
4344
validateToolCall,
4445
} from "../../../services/refact";
46+
import { calculateInputTokens } from "../../../components/ChatContent/UsageCounter/UsageCounter.utils";
47+
48+
const RECOMMENDED_MAXIMUM_PROMPT_TOKENS_AMOUNT = 30000;
4549

4650
const createChatThread = (
4751
tool_use: ToolUse,
@@ -218,6 +222,31 @@ export const chatReducer = createReducer(initialState, (builder) => {
218222
};
219223
});
220224

225+
builder.addCase(setThreadUsage, (state, action) => {
226+
if (state.thread.id !== action.payload.chatId) return state;
227+
228+
const { usage } = action.payload;
229+
state.thread.usage = usage;
230+
231+
const inputTokensAmount = calculateInputTokens(usage, [
232+
"prompt_tokens",
233+
"cache_creation_input_tokens",
234+
"cache_read_input_tokens",
235+
]);
236+
237+
if (inputTokensAmount >= RECOMMENDED_MAXIMUM_PROMPT_TOKENS_AMOUNT) {
238+
const { wasSuggested, wasRejectedByUser } =
239+
state.thread.new_chat_suggested;
240+
241+
state.thread.new_chat_suggested = {
242+
wasSuggested: wasSuggested || !wasSuggested,
243+
wasRejectedByUser: wasRejectedByUser
244+
? !wasRejectedByUser
245+
: wasRejectedByUser,
246+
};
247+
}
248+
});
249+
221250
builder.addCase(setEnabledCheckpoints, (state, action) => {
222251
state.checkpoints_enabled = action.payload;
223252
});

refact-agent/gui/src/features/Chat/Thread/selectors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const selectCheckpointsEnabled = (state: RootState) =>
1818

1919
export const selectThreadNewChatSuggested = (state: RootState) =>
2020
state.chat.thread.new_chat_suggested;
21+
export const selectThreadUsage = (state: RootState) => state.chat.thread.usage;
2122
export const selectIsWaiting = (state: RootState) =>
2223
state.chat.waiting_for_response;
2324
export const selectIsStreaming = (state: RootState) => state.chat.streaming;

refact-agent/gui/src/features/Chat/Thread/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Usage } from "../../../services/refact";
12
import { SystemPrompts } from "../../../services/refact/prompts";
23
import { ChatMessages } from "../../../services/refact/types";
34
import { parseOrElse } from "../../../utils/parseOrElse";
@@ -23,6 +24,7 @@ export type ChatThread = {
2324
project_name?: string;
2425
last_user_message_id?: string;
2526
new_chat_suggested: SuggestedChat;
27+
usage?: Usage;
2628
};
2729

2830
export type SuggestedChat = {
@@ -50,6 +52,7 @@ export type Chat = {
5052
export type PayloadWithId = { id: string };
5153
export type PayloadWithChatAndMessageId = { chatId: string; messageId: string };
5254
export type PayloadWithChatAndBoolean = { chatId: string; value: boolean };
55+
export type PayloadWithChatAndUsage = { chatId: string; usage: Usage };
5356
export type PayloadWithIdAndTitle = {
5457
title: string;
5558
isTitleGenerated: boolean;

0 commit comments

Comments
 (0)