From 4098fd46db8f0469308e2fdb893bab7a94332c96 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 14:31:10 +0100 Subject: [PATCH 01/13] un-disable input when limit reached. --- refact-agent/gui/src/components/ChatForm/ChatForm.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/refact-agent/gui/src/components/ChatForm/ChatForm.tsx b/refact-agent/gui/src/components/ChatForm/ChatForm.tsx index e30d60f9c..023371392 100644 --- a/refact-agent/gui/src/components/ChatForm/ChatForm.tsx +++ b/refact-agent/gui/src/components/ChatForm/ChatForm.tsx @@ -104,7 +104,7 @@ export const ChatForm: React.FC = ({ if (limitReached) { dispatch( setInformation( - `Token Limit reached, ${tokens} out of ${limit} used. To continue click the compress button or start a new chat.`, + `Token Limit reached, ${tokens} out of ${limit} used. Consider clicking the compress button to reduce the size.`, ), ); } @@ -131,7 +131,6 @@ export const ChatForm: React.FC = ({ const disableSend = useMemo(() => { // TODO: if interrupting chat some errors can occur if (allDisabled) return true; - if (limitReached) return true; // if ( // currentThreadMaximumContextTokens && // currentThreadUsage?.prompt_tokens && @@ -143,7 +142,6 @@ export const ChatForm: React.FC = ({ return isWaiting || isStreaming || !isOnline || preventSend; }, [ allDisabled, - limitReached, messages.length, isWaiting, isStreaming, @@ -385,7 +383,6 @@ export const ChatForm: React.FC = ({ data-testid="chat-form-textarea" required={true} // disabled={isStreaming} - disabled={limitReached} {...props} autoFocus={autoFocus} style={{ boxShadow: "none", outline: "none" }} From acb420a844940ed927b7ac42b2684a3861474808 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 14:32:21 +0100 Subject: [PATCH 02/13] chore: add `compression_strength` to tool messages --- refact-agent/gui/src/features/Chat/Thread/utils.ts | 5 ++++- refact-agent/gui/src/services/refact/types.ts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/refact-agent/gui/src/features/Chat/Thread/utils.ts b/refact-agent/gui/src/features/Chat/Thread/utils.ts index f6a741f47..520aa3321 100644 --- a/refact-agent/gui/src/features/Chat/Thread/utils.ts +++ b/refact-agent/gui/src/features/Chat/Thread/utils.ts @@ -182,7 +182,8 @@ export function formatChatResponse( } if (isToolResponse(response)) { - const { tool_call_id, content, finish_reason } = response; + const { tool_call_id, content, finish_reason, compression_strength } = + response; const filteredMessages = finishToolCallInMessages(messages, tool_call_id); const toolResult: ToolResult = typeof content === "string" @@ -190,11 +191,13 @@ export function formatChatResponse( tool_call_id, content, finish_reason, + compression_strength, } : { tool_call_id, content, finish_reason, + compression_strength, }; return [...filteredMessages, { role: response.role, content: toolResult }]; diff --git a/refact-agent/gui/src/services/refact/types.ts b/refact-agent/gui/src/services/refact/types.ts index 0fafc5b1a..eef22c1a4 100644 --- a/refact-agent/gui/src/services/refact/types.ts +++ b/refact-agent/gui/src/services/refact/types.ts @@ -70,6 +70,7 @@ export interface BaseToolResult { tool_call_id: string; finish_reason?: string; // "call_failed" | "call_worked"; content: ToolContent; + compression_strength?: CompressionStrength; } export interface SingleModelToolResult extends BaseToolResult { From 8307cbb53c85a674019cf6f243bbb9bb7a24e5ff Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 14:32:44 +0100 Subject: [PATCH 03/13] add paused state to thread --- .../gui/src/features/Chat/Thread/actions.ts | 4 ++++ .../gui/src/features/Chat/Thread/reducer.ts | 7 +++++++ .../gui/src/features/Chat/Thread/selectors.ts | 17 ++++++++++++++--- .../gui/src/features/Chat/Thread/types.ts | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/refact-agent/gui/src/features/Chat/Thread/actions.ts b/refact-agent/gui/src/features/Chat/Thread/actions.ts index c10b0460f..e0a433159 100644 --- a/refact-agent/gui/src/features/Chat/Thread/actions.ts +++ b/refact-agent/gui/src/features/Chat/Thread/actions.ts @@ -162,6 +162,10 @@ export const setIncreaseMaxTokens = createAction( "chatThread/setIncreaseMaxTokens", ); +export const setThreadPaused = createAction( + "chatThread/setThreadPaused", +); + // TODO: This is the circular dep when imported from hooks :/ const createAppAsyncThunk = createAsyncThunk.withTypes<{ state: RootState; diff --git a/refact-agent/gui/src/features/Chat/Thread/reducer.ts b/refact-agent/gui/src/features/Chat/Thread/reducer.ts index 87066d125..70accf78f 100644 --- a/refact-agent/gui/src/features/Chat/Thread/reducer.ts +++ b/refact-agent/gui/src/features/Chat/Thread/reducer.ts @@ -39,6 +39,7 @@ import { setIsNewChatSuggestionRejected, upsertToolCall, setIncreaseMaxTokens, + setThreadPaused, } from "./actions"; import { formatChatResponse } from "./utils"; import { @@ -78,6 +79,7 @@ const createChatThread = ( boost_reasoning: false, automatic_patch: false, increase_max_tokens: false, + paused: false, }; return chat; }; @@ -275,6 +277,7 @@ export const chatReducer = createReducer(initialState, (builder) => { state.streaming = true; state.thread.read = false; state.prevent_send = false; + state.thread.paused = false; }); builder.addCase(removeChatFromCache, (state, action) => { @@ -422,6 +425,10 @@ export const chatReducer = createReducer(initialState, (builder) => { state.thread.increase_max_tokens = action.payload; }); + builder.addCase(setThreadPaused, (state, action) => { + state.thread.paused = action.payload; + }); + builder.addMatcher( capsApi.endpoints.getCaps.matchFulfilled, (state, action) => { diff --git a/refact-agent/gui/src/features/Chat/Thread/selectors.ts b/refact-agent/gui/src/features/Chat/Thread/selectors.ts index 56393d114..ea3acdb70 100644 --- a/refact-agent/gui/src/features/Chat/Thread/selectors.ts +++ b/refact-agent/gui/src/features/Chat/Thread/selectors.ts @@ -1,6 +1,7 @@ import { RootState } from "../../../app/store"; import { createSelector } from "@reduxjs/toolkit"; import { + CompressionStrength, isToolMessage, isUserMessage, UserMessage, @@ -77,14 +78,24 @@ export const selectThreadMode = createSelector( export const selectLastSentCompression = createSelector( selectMessages, (messages) => { - const lastUserMessage = messages.reduce( + const lastCompression = messages.reduce( (acc, message) => { - if (isUserMessage(message)) return message; + if (isUserMessage(message) && message.compression_strength) { + return message.compression_strength; + } + if (isToolMessage(message) && message.content.compression_strength) { + return message.content.compression_strength; + } return acc; }, null, ); - return lastUserMessage?.compression_strength ?? null; + return lastCompression; }, ); + +export const selectThreadPaused = createSelector( + selectThread, + (thread) => thread.paused ?? false, +); diff --git a/refact-agent/gui/src/features/Chat/Thread/types.ts b/refact-agent/gui/src/features/Chat/Thread/types.ts index 6a038aacf..7c907148b 100644 --- a/refact-agent/gui/src/features/Chat/Thread/types.ts +++ b/refact-agent/gui/src/features/Chat/Thread/types.ts @@ -29,6 +29,7 @@ export type ChatThread = { currentMaximumContextTokens?: number; currentMessageContextTokens?: number; increase_max_tokens?: boolean; + paused?: boolean; }; export type SuggestedChat = { From fb16a6501a858d5c3a4a533bd6a97a442a5dfb89 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 14:35:43 +0100 Subject: [PATCH 04/13] add hook to pause auto send based on compression. --- .../gui/src/features/Chat/Thread/selectors.ts | 1 - refact-agent/gui/src/hooks/index.ts | 1 + .../gui/src/hooks/useCompressionStop.ts | 25 +++++++++++++++++++ .../gui/src/hooks/useSendChatRequest.ts | 8 +++--- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 refact-agent/gui/src/hooks/useCompressionStop.ts diff --git a/refact-agent/gui/src/features/Chat/Thread/selectors.ts b/refact-agent/gui/src/features/Chat/Thread/selectors.ts index ea3acdb70..b928478dc 100644 --- a/refact-agent/gui/src/features/Chat/Thread/selectors.ts +++ b/refact-agent/gui/src/features/Chat/Thread/selectors.ts @@ -4,7 +4,6 @@ import { CompressionStrength, isToolMessage, isUserMessage, - UserMessage, } from "../../../services/refact/types"; export const selectThread = (state: RootState) => state.chat.thread; diff --git a/refact-agent/gui/src/hooks/index.ts b/refact-agent/gui/src/hooks/index.ts index 3ffb2015a..d693e0d57 100644 --- a/refact-agent/gui/src/hooks/index.ts +++ b/refact-agent/gui/src/hooks/index.ts @@ -35,3 +35,4 @@ export * from "./useCompressChat"; export * from "./useAutoFocusOnce"; export * from "./useHideScroll"; export * from "./useTotalTokenUsage"; +export * from "./useCompressionStop"; diff --git a/refact-agent/gui/src/hooks/useCompressionStop.ts b/refact-agent/gui/src/hooks/useCompressionStop.ts new file mode 100644 index 000000000..6a20479c5 --- /dev/null +++ b/refact-agent/gui/src/hooks/useCompressionStop.ts @@ -0,0 +1,25 @@ +import { useEffect, useCallback } from "react"; +import { useAppSelector } from "./useAppSelector"; +import { useAppDispatch } from "./useAppDispatch"; +import { + selectLastSentCompression, + selectThreadPaused, + setThreadPaused, +} from "../features/Chat"; + +export function useLastSentCompressionStop() { + const dispatch = useAppDispatch(); + const lastSentCompression = useAppSelector(selectLastSentCompression); + const stopped = useAppSelector(selectThreadPaused); + useEffect(() => { + if (lastSentCompression && lastSentCompression !== "absent") { + dispatch(setThreadPaused(true)); + } + }, [dispatch, lastSentCompression]); + + const resume = useCallback(() => { + dispatch(setThreadPaused(false)); + }, [dispatch]); + + return { stopped, resume, strength: lastSentCompression }; +} diff --git a/refact-agent/gui/src/hooks/useSendChatRequest.ts b/refact-agent/gui/src/hooks/useSendChatRequest.ts index c515ddd4f..05d74e4c4 100644 --- a/refact-agent/gui/src/hooks/useSendChatRequest.ts +++ b/refact-agent/gui/src/hooks/useSendChatRequest.ts @@ -56,7 +56,7 @@ import { import { v4 as uuidv4 } from "uuid"; import { upsertToolCallIntoHistory } from "../features/History/historySlice"; -import { useTotalTokenUsage } from "./useTotalTokenUsage"; +import { useLastSentCompressionStop } from "./useCompressionStop"; type SubmitHandlerParams = | { @@ -356,7 +356,7 @@ export function useAutoSend() { const sendImmediately = useAppSelector(selectSendImmediately); const wasInteracted = useAppSelector(getToolsInteractionStatus); // shows if tool confirmation popup was interacted by user const areToolsConfirmed = useAppSelector(getToolsConfirmationStatus); - const { limitReached } = useTotalTokenUsage(); + const compressionStop = useLastSentCompressionStop(); const { sendMessages, abort, messagesWithSystemPrompt } = useSendChatRequest(); // TODO: make a selector for this, or show tool formation @@ -381,7 +381,7 @@ export function useAutoSend() { const lastMessage = currentMessages.slice(-1)[0]; // here ish if ( - !limitReached && + !compressionStop.stopped && isAssistantMessage(lastMessage) && lastMessage.tool_calls && lastMessage.tool_calls.length > 0 @@ -418,6 +418,6 @@ export function useAutoSend() { isIntegration, thread.mode, thread, - limitReached, + compressionStop.stopped, ]); } From 5da62085d3cae2c10354e093f530e775844b73b3 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 15:33:20 +0100 Subject: [PATCH 05/13] ui: let the user know that their chat is being compressed. --- refact-agent/gui/src/components/Chat/Chat.tsx | 13 ++++------ .../gui/src/components/ChatForm/ChatForm.tsx | 16 ++----------- .../UsageCounter/useUsageCounter.ts | 24 ++++++++++--------- .../gui/src/features/Chat/Thread/selectors.ts | 1 + .../gui/src/hooks/useTotalTokenUsage.ts | 1 + 5 files changed, 21 insertions(+), 34 deletions(-) diff --git a/refact-agent/gui/src/components/Chat/Chat.tsx b/refact-agent/gui/src/components/Chat/Chat.tsx index 6941246b1..ea8140585 100644 --- a/refact-agent/gui/src/components/Chat/Chat.tsx +++ b/refact-agent/gui/src/components/Chat/Chat.tsx @@ -10,6 +10,7 @@ import { useGetCapsQuery, useCapsForToolUse, useAgentUsage, + useLastSentCompressionStop, } from "../../hooks"; import { type Config } from "../../features/Config/configSlice"; import { @@ -19,7 +20,6 @@ import { selectChatId, selectMessages, getSelectedToolUse, - selectThreadNewChatSuggested, } from "../../features/Chat/Thread"; import { ThreadHistoryButton } from "../Buttons"; import { push } from "../../features/Pages/pagesSlice"; @@ -53,8 +53,8 @@ export const Chat: React.FC = ({ const { submit, abort, retryFromIndex } = useSendChatRequest(); const chatToolUse = useAppSelector(getSelectedToolUse); - - const threadNewChatSuggested = useAppSelector(selectThreadNewChatSuggested); + const compressionStop = useLastSentCompressionStop(); + // const threadNewChatSuggested = useAppSelector(selectThreadNewChatSuggested); const messages = useAppSelector(selectMessages); const capsForToolUse = useCapsForToolUse(); const { disableInput } = useAgentUsage(); @@ -103,12 +103,7 @@ export const Chat: React.FC = ({ {shouldCheckpointsPopupBeShown && } - + {!isStreaming && preventSend && unCalledTools && ( diff --git a/refact-agent/gui/src/components/ChatForm/ChatForm.tsx b/refact-agent/gui/src/components/ChatForm/ChatForm.tsx index 023371392..b2e1c8c83 100644 --- a/refact-agent/gui/src/components/ChatForm/ChatForm.tsx +++ b/refact-agent/gui/src/components/ChatForm/ChatForm.tsx @@ -19,7 +19,7 @@ import { useSendChatRequest, useCompressChat, useAutoFocusOnce, - useTotalTokenUsage, + // useTotalTokenUsage, } from "../../hooks"; import { ErrorCallout, Callout } from "../Callout"; import { ComboBox } from "../ComboBox"; @@ -35,7 +35,7 @@ import { useInputValue } from "./useInputValue"; import { clearInformation, getInformationMessage, - setInformation, + // setInformation, } from "../../features/Errors/informationSlice"; import { InformationCallout } from "../Callout/Callout"; import { ToolConfirmation } from "./ToolConfirmation"; @@ -98,18 +98,6 @@ export const ChatForm: React.FC = ({ const { compressChat, compressChatRequest } = useCompressChat(); const autoFocus = useAutoFocusOnce(); - const { limitReached, tokens, limit } = useTotalTokenUsage(); - - useEffect(() => { - if (limitReached) { - dispatch( - setInformation( - `Token Limit reached, ${tokens} out of ${limit} used. Consider clicking the compress button to reduce the size.`, - ), - ); - } - }, [tokens, limit, limitReached, dispatch]); - const shouldAgentCapabilitiesBeShown = useMemo(() => { return threadToolUse === "agent"; }, [threadToolUse]); diff --git a/refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts b/refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts index e322ed93a..9d2c4ec6a 100644 --- a/refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts +++ b/refact-agent/gui/src/components/UsageCounter/useUsageCounter.ts @@ -3,9 +3,9 @@ import { selectIsStreaming, selectIsWaiting, selectMessages, - selectLastSentCompression, + // selectLastSentCompression, } from "../../features/Chat"; -import { useAppSelector } from "../../hooks"; +import { useAppSelector, useLastSentCompressionStop } from "../../hooks"; import { calculateUsageInputTokens, mergeUsages, @@ -15,7 +15,7 @@ import { isAssistantMessage } from "../../services/refact"; export function useUsageCounter() { const isStreaming = useAppSelector(selectIsStreaming); const isWaiting = useAppSelector(selectIsWaiting); - const lastSentCompression = useAppSelector(selectLastSentCompression); + const compressionStop = useLastSentCompressionStop(); const messages = useAppSelector(selectMessages); const assistantMessages = messages.filter(isAssistantMessage); const usages = assistantMessages.map((msg) => msg.usage); @@ -33,21 +33,23 @@ export function useUsageCounter() { }, [currentThreadUsage]); const isOverflown = useMemo(() => { - if (lastSentCompression === "low") return true; - if (lastSentCompression === "medium") return true; - if (lastSentCompression === "high") return true; + if (!compressionStop.stopped) return false; + if (compressionStop.strength === "low") return true; + if (compressionStop.strength === "medium") return true; + if (compressionStop.strength === "high") return true; return false; - }, [lastSentCompression]); + }, [compressionStop.stopped, compressionStop.strength]); const isWarning = useMemo(() => { - if (lastSentCompression === "medium") return true; - if (lastSentCompression === "high") return true; + if (!compressionStop.stopped) return false; + if (compressionStop.strength === "medium") return true; + if (compressionStop.strength === "high") return true; return false; - }, [lastSentCompression]); + }, [compressionStop.stopped, compressionStop.strength]); const shouldShow = useMemo(() => { return messages.length > 0 && !isStreaming && !isWaiting; - }, [messages, isStreaming, isWaiting]); + }, [messages.length, isStreaming, isWaiting]); return { shouldShow, diff --git a/refact-agent/gui/src/features/Chat/Thread/selectors.ts b/refact-agent/gui/src/features/Chat/Thread/selectors.ts index b928478dc..40a77b724 100644 --- a/refact-agent/gui/src/features/Chat/Thread/selectors.ts +++ b/refact-agent/gui/src/features/Chat/Thread/selectors.ts @@ -23,6 +23,7 @@ export const selectCheckpointsEnabled = (state: RootState) => export const selectThreadBoostReasoning = (state: RootState) => state.chat.thread.boost_reasoning; +// TODO: this should be safe to remove export const selectThreadNewChatSuggested = (state: RootState) => state.chat.thread.new_chat_suggested; export const selectThreadMaximumTokens = (state: RootState) => diff --git a/refact-agent/gui/src/hooks/useTotalTokenUsage.ts b/refact-agent/gui/src/hooks/useTotalTokenUsage.ts index 5c2a3f7e2..4469dd7e0 100644 --- a/refact-agent/gui/src/hooks/useTotalTokenUsage.ts +++ b/refact-agent/gui/src/hooks/useTotalTokenUsage.ts @@ -9,6 +9,7 @@ import { } from "../utils/calculateUsageInputTokens"; const TOKEN_LIMIT = 200_000; +// TODO: maybe remove this export function useTotalTokenUsage() { const messages = useAppSelector(selectMessages); From fc779f154116cc62318fcd8e8e740043325a7b21 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 15:38:28 +0100 Subject: [PATCH 06/13] fix: linter issues after removing `limitReached` information call out. --- refact-agent/gui/src/components/ChatForm/ChatForm.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/refact-agent/gui/src/components/ChatForm/ChatForm.tsx b/refact-agent/gui/src/components/ChatForm/ChatForm.tsx index b2e1c8c83..eb689f8c6 100644 --- a/refact-agent/gui/src/components/ChatForm/ChatForm.tsx +++ b/refact-agent/gui/src/components/ChatForm/ChatForm.tsx @@ -307,11 +307,7 @@ export const ChatForm: React.FC = ({ if (information) { return ( - + {information} ); From 83c13a62d71588c82e815f6ce597e00368c76039 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 15:51:04 +0100 Subject: [PATCH 07/13] fix: also use `/links` to decided if a new chat should be suggested. --- refact-agent/gui/src/components/Chat/Chat.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/refact-agent/gui/src/components/Chat/Chat.tsx b/refact-agent/gui/src/components/Chat/Chat.tsx index ea8140585..5b181e2bf 100644 --- a/refact-agent/gui/src/components/Chat/Chat.tsx +++ b/refact-agent/gui/src/components/Chat/Chat.tsx @@ -20,6 +20,7 @@ import { selectChatId, selectMessages, getSelectedToolUse, + selectThreadNewChatSuggested, } from "../../features/Chat/Thread"; import { ThreadHistoryButton } from "../Buttons"; import { push } from "../../features/Pages/pagesSlice"; @@ -54,7 +55,7 @@ export const Chat: React.FC = ({ const chatToolUse = useAppSelector(getSelectedToolUse); const compressionStop = useLastSentCompressionStop(); - // const threadNewChatSuggested = useAppSelector(selectThreadNewChatSuggested); + const threadNewChatSuggested = useAppSelector(selectThreadNewChatSuggested); const messages = useAppSelector(selectMessages); const capsForToolUse = useCapsForToolUse(); const { disableInput } = useAgentUsage(); @@ -103,7 +104,13 @@ export const Chat: React.FC = ({ {shouldCheckpointsPopupBeShown && } - + {!isStreaming && preventSend && unCalledTools && ( From 266b3001d001e2701ddefffb27580d9380b3a69a Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 15:53:48 +0100 Subject: [PATCH 08/13] refactor: remove `useTotalTokenUsage` hook. --- refact-agent/gui/src/hooks/index.ts | 1 - .../gui/src/hooks/useTotalTokenUsage.ts | 48 ------------------- 2 files changed, 49 deletions(-) delete mode 100644 refact-agent/gui/src/hooks/useTotalTokenUsage.ts diff --git a/refact-agent/gui/src/hooks/index.ts b/refact-agent/gui/src/hooks/index.ts index d693e0d57..6dfb21e34 100644 --- a/refact-agent/gui/src/hooks/index.ts +++ b/refact-agent/gui/src/hooks/index.ts @@ -34,5 +34,4 @@ export * from "./useResizeObserver"; export * from "./useCompressChat"; export * from "./useAutoFocusOnce"; export * from "./useHideScroll"; -export * from "./useTotalTokenUsage"; export * from "./useCompressionStop"; diff --git a/refact-agent/gui/src/hooks/useTotalTokenUsage.ts b/refact-agent/gui/src/hooks/useTotalTokenUsage.ts deleted file mode 100644 index 4469dd7e0..000000000 --- a/refact-agent/gui/src/hooks/useTotalTokenUsage.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useMemo } from "react"; -import { useAppSelector } from "./useAppSelector"; -import { isAssistantMessage } from "../events"; -import { selectMessages } from "../features/Chat"; -import { type Usage } from "../services/refact/chat"; -import { - calculateUsageInputTokens, - mergeUsages, -} from "../utils/calculateUsageInputTokens"; - -const TOKEN_LIMIT = 200_000; -// TODO: maybe remove this -export function useTotalTokenUsage() { - const messages = useAppSelector(selectMessages); - - const summedUsages = useMemo(() => { - const usages = messages.reduce((acc, message) => { - if (isAssistantMessage(message) && message.usage) { - return [...acc, message.usage]; - } - return acc; - }, []); - return mergeUsages(usages); - }, [messages]); - - const tokens = useMemo(() => { - if (!summedUsages) return 0; - return calculateUsageInputTokens({ - keys: [ - "prompt_tokens", - "cache_creation_input_tokens", - "cache_read_input_tokens", - ], - usage: summedUsages, - }); - }, [summedUsages]); - - const limitReached = useMemo(() => { - return tokens >= TOKEN_LIMIT; - }, [tokens]); - - return { - summedUsages, - tokens, - limitReached, - limit: TOKEN_LIMIT, - }; -} From b1d8946e83b4485996a0b31a2f0667b41d699b06 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 15:56:39 +0100 Subject: [PATCH 09/13] add comments about `newChatSuggested`. --- refact-agent/gui/src/features/Chat/Thread/actions.ts | 1 + refact-agent/gui/src/features/Chat/Thread/selectors.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/refact-agent/gui/src/features/Chat/Thread/actions.ts b/refact-agent/gui/src/features/Chat/Thread/actions.ts index e0a433159..bbc08eca7 100644 --- a/refact-agent/gui/src/features/Chat/Thread/actions.ts +++ b/refact-agent/gui/src/features/Chat/Thread/actions.ts @@ -63,6 +63,7 @@ export const setLastUserMessageId = createAction( "chatThread/setLastUserMessageId", ); +// TBD: only used when `/links` suggests a new chat. export const setIsNewChatSuggested = createAction( "chatThread/setIsNewChatSuggested", ); diff --git a/refact-agent/gui/src/features/Chat/Thread/selectors.ts b/refact-agent/gui/src/features/Chat/Thread/selectors.ts index 40a77b724..563ba6102 100644 --- a/refact-agent/gui/src/features/Chat/Thread/selectors.ts +++ b/refact-agent/gui/src/features/Chat/Thread/selectors.ts @@ -23,7 +23,7 @@ export const selectCheckpointsEnabled = (state: RootState) => export const selectThreadBoostReasoning = (state: RootState) => state.chat.thread.boost_reasoning; -// TODO: this should be safe to remove +// TBD: only used when `/links` suggests a new chat. export const selectThreadNewChatSuggested = (state: RootState) => state.chat.thread.new_chat_suggested; export const selectThreadMaximumTokens = (state: RootState) => From f0e5a08a74049f417d8a8745a028a0c2ff1529f5 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 16:14:35 +0100 Subject: [PATCH 10/13] pause and unpaused using newChatSuggested. --- .../src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx | 3 ++- refact-agent/gui/src/features/Chat/Thread/reducer.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx b/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx index 72776e0c4..ce64870de 100644 --- a/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx +++ b/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx @@ -8,6 +8,7 @@ import { useAppDispatch, useAppSelector, useCompressChat, + useLastSentCompressionStop, } from "../../../hooks"; import { popBackTo, push } from "../../../features/Pages/pagesSlice"; import { telemetryApi } from "../../../services/refact"; @@ -41,7 +42,6 @@ export const SuggestNewChat = ({ const [isAnimating, setIsAnimating] = useState(false); const { compressChat, compressChatRequest } = useCompressChat(); - const lastSentCompression = useAppSelector(selectLastSentCompression); useEffect(() => { if (shouldBeVisible) { setIsRendered(true); @@ -64,6 +64,7 @@ export const SuggestNewChat = ({ const handleClose = () => { dispatch(setIsNewChatSuggestionRejected({ chatId, value: true })); + void sendTelemetryEvent({ scope: `dismissedNewChatSuggestionWarning`, success: true, diff --git a/refact-agent/gui/src/features/Chat/Thread/reducer.ts b/refact-agent/gui/src/features/Chat/Thread/reducer.ts index 70accf78f..d0692fd28 100644 --- a/refact-agent/gui/src/features/Chat/Thread/reducer.ts +++ b/refact-agent/gui/src/features/Chat/Thread/reducer.ts @@ -243,6 +243,7 @@ export const chatReducer = createReducer(initialState, (builder) => { builder.addCase(setIsNewChatSuggested, (state, action) => { if (state.thread.id !== action.payload.chatId) return state; + state.thread.paused = true; state.thread.new_chat_suggested = { wasSuggested: action.payload.value, }; @@ -250,6 +251,7 @@ export const chatReducer = createReducer(initialState, (builder) => { builder.addCase(setIsNewChatSuggestionRejected, (state, action) => { if (state.thread.id !== action.payload.chatId) return state; + state.thread.paused = false; state.thread.new_chat_suggested = { ...state.thread.new_chat_suggested, wasRejectedByUser: action.payload.value, From e2040d72eafd6b4c24918d793e632a24cbbef5a3 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 16:17:43 +0100 Subject: [PATCH 11/13] fix(NewChatSuggested): use a hook to get the compression strength. --- .../SuggestNewChat/SuggestNewChat.tsx | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx b/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx index ce64870de..359f78de6 100644 --- a/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx +++ b/refact-agent/gui/src/components/ChatForm/SuggestNewChat/SuggestNewChat.tsx @@ -15,7 +15,6 @@ import { telemetryApi } from "../../../services/refact"; import { newChatAction, selectChatId, - selectLastSentCompression, setIsNewChatSuggestionRejected, } from "../../../features/Chat"; @@ -41,6 +40,7 @@ export const SuggestNewChat = ({ const [isRendered, setIsRendered] = useState(shouldBeVisible); const [isAnimating, setIsAnimating] = useState(false); const { compressChat, compressChatRequest } = useCompressChat(); + const lastSentCompression = useLastSentCompressionStop(); useEffect(() => { if (shouldBeVisible) { @@ -119,27 +119,28 @@ export const SuggestNewChat = ({ Start a new chat - {lastSentCompression && lastSentCompression !== "absent" && ( - { - if (compressChatRequest.isLoading) return; - void compressChat(); - }} - color="indigo" - asChild - > - { + if (compressChatRequest.isLoading) return; + void compressChat(); + }} + color="indigo" + asChild > - - Compress and open in a new chat. - - - )} + + + Compress and open in a new chat. + + + )} Date: Wed, 26 Mar 2025 16:50:15 +0100 Subject: [PATCH 12/13] feat: add second condition for pausing the chat. --- .../gui/src/hooks/useCompressionStop.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/refact-agent/gui/src/hooks/useCompressionStop.ts b/refact-agent/gui/src/hooks/useCompressionStop.ts index 6a20479c5..53063e184 100644 --- a/refact-agent/gui/src/hooks/useCompressionStop.ts +++ b/refact-agent/gui/src/hooks/useCompressionStop.ts @@ -1,21 +1,36 @@ -import { useEffect, useCallback } from "react"; +import { useEffect, useCallback, useMemo } from "react"; import { useAppSelector } from "./useAppSelector"; import { useAppDispatch } from "./useAppDispatch"; import { selectLastSentCompression, + selectMessages, selectThreadPaused, setThreadPaused, } from "../features/Chat"; +import { takeFromEndWhile } from "../utils"; +import { isUserMessage } from "../events"; export function useLastSentCompressionStop() { const dispatch = useAppDispatch(); const lastSentCompression = useAppSelector(selectLastSentCompression); + const messages = useAppSelector(selectMessages); const stopped = useAppSelector(selectThreadPaused); useEffect(() => { - if (lastSentCompression && lastSentCompression !== "absent") { + if (lastSentCompression && lastSentCompression !== "absent" && !stopped) { dispatch(setThreadPaused(true)); } - }, [dispatch, lastSentCompression]); + }, [dispatch, lastSentCompression, stopped]); + + const messagesFromLastUserMessage = useMemo(() => { + return takeFromEndWhile(messages, (message) => !isUserMessage(message)) + .length; + }, [messages]); + + useEffect(() => { + if (messagesFromLastUserMessage >= 40 && !stopped) { + dispatch(setThreadPaused(true)); + } + }, [dispatch, messagesFromLastUserMessage, stopped]); const resume = useCallback(() => { dispatch(setThreadPaused(false)); From b66ca8c067bf843946bc7b375e7b18764584cf98 Mon Sep 17 00:00:00 2001 From: Marc McIntosh Date: Wed, 26 Mar 2025 16:52:11 +0100 Subject: [PATCH 13/13] case: it might be posable for many messages with out compression. --- refact-agent/gui/src/hooks/useCompressionStop.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/refact-agent/gui/src/hooks/useCompressionStop.ts b/refact-agent/gui/src/hooks/useCompressionStop.ts index 53063e184..aad6687be 100644 --- a/refact-agent/gui/src/hooks/useCompressionStop.ts +++ b/refact-agent/gui/src/hooks/useCompressionStop.ts @@ -27,10 +27,15 @@ export function useLastSentCompressionStop() { }, [messages]); useEffect(() => { - if (messagesFromLastUserMessage >= 40 && !stopped) { + if ( + lastSentCompression && + lastSentCompression !== "absent" && + messagesFromLastUserMessage >= 40 && + !stopped + ) { dispatch(setThreadPaused(true)); } - }, [dispatch, messagesFromLastUserMessage, stopped]); + }, [dispatch, lastSentCompression, messagesFromLastUserMessage, stopped]); const resume = useCallback(() => { dispatch(setThreadPaused(false));