Skip to content

Commit 685f095

Browse files
committed
fix(patch): continue if accepted or allow clicked, stop if rejected of stop clicked.
1 parent 1ef2ea5 commit 685f095

File tree

7 files changed

+168
-14
lines changed

7 files changed

+168
-14
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
newIntegrationChat,
1313
chatResponse,
1414
setIsWaitingForResponse,
15+
upsertToolCall,
1516
} from "../features/Chat/Thread";
1617
import { statisticsApi } from "../services/refact/statistics";
1718
import { integrationsApi } from "../services/refact/integrations";
@@ -46,6 +47,7 @@ import {
4647
updateMaxAgentUsageAmount,
4748
} from "../features/AgentUsage/agentUsageSlice";
4849
import { ideToolCallResponse } from "../hooks/useEventBusForIDE";
50+
import { upsertToolCallIntoHistory } from "../features/History/historySlice";
4951

5052
const AUTH_ERROR_MESSAGE =
5153
"There is an issue with your API key. Check out your API Key or re-login";
@@ -509,6 +511,11 @@ startListening({
509511
effect: (action, listenerApi) => {
510512
const state = listenerApi.getState();
511513

514+
if (action.payload.accepted === false) {
515+
listenerApi.dispatch(upsertToolCallIntoHistory(action.payload));
516+
listenerApi.dispatch(upsertToolCall(action.payload));
517+
}
518+
512519
listenerApi.dispatch(updateConfirmationAfterIdeToolUse(action.payload));
513520

514521
const pauseReasons = state.confirmation.pauseReasons.filter(

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from "react";
1+
import React, { useCallback, useMemo } from "react";
22
import {
33
PATCH_LIKE_FUNCTIONS,
44
useAppDispatch,
@@ -88,6 +88,10 @@ export const ToolConfirmation: React.FC<ToolConfirmationProps> = ({
8888
confirmToolUsage();
8989
};
9090

91+
const handleReject = useCallback(() => {
92+
rejectToolUsage(toolCallIds);
93+
}, [rejectToolUsage, toolCallIds]);
94+
9195
const message = getConfirmationMessage(
9296
commands,
9397
rules,
@@ -101,7 +105,7 @@ export const ToolConfirmation: React.FC<ToolConfirmationProps> = ({
101105
return (
102106
<PatchConfirmation
103107
handleAllowForThisChat={handleAllowForThisChat}
104-
rejectToolUsage={rejectToolUsage}
108+
rejectToolUsage={handleReject}
105109
confirmToolUsage={confirmToolUsage}
106110
/>
107111
);
@@ -167,7 +171,7 @@ export const ToolConfirmation: React.FC<ToolConfirmationProps> = ({
167171
color="red"
168172
variant="surface"
169173
size="1"
170-
onClick={rejectToolUsage}
174+
onClick={handleReject}
171175
>
172176
Stop
173177
</Button>

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { ToolCommand } from "../../../services/refact/tools";
3434
import { scanFoDuplicatesWith, takeFromEndWhile } from "../../../utils";
3535
import { debugApp } from "../../../debugConfig";
3636
import { ChatHistoryItem } from "../../History/historySlice";
37+
import { ideToolCallResponse } from "../../../hooks/useEventBusForIDE";
3738

3839
export const newChatAction = createAction("chatThread/new");
3940

@@ -144,6 +145,10 @@ export const fixBrokenToolMessages = createAction<PayloadWithId>(
144145
"chatThread/fixBrokenToolMessages",
145146
);
146147

148+
export const upsertToolCall = createAction<
149+
Parameters<typeof ideToolCallResponse>[0] & { replaceOnly?: boolean }
150+
>("chatThread/upsertToolCall");
151+
147152
// TODO: This is the circular dep when imported from hooks :/
148153
const createAppAsyncThunk = createAsyncThunk.withTypes<{
149154
state: RootState;

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

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createReducer } from "@reduxjs/toolkit";
1+
import { createReducer, Draft } from "@reduxjs/toolkit";
22
import {
33
Chat,
44
ChatThread,
@@ -35,11 +35,19 @@ import {
3535
fixBrokenToolMessages,
3636
setIsNewChatSuggested,
3737
setIsNewChatSuggestionRejected,
38+
upsertToolCall,
3839
} from "./actions";
3940
import { formatChatResponse } from "./utils";
4041
import {
42+
ChatMessages,
4143
DEFAULT_MAX_NEW_TOKENS,
44+
isAssistantMessage,
45+
isDiffMessage,
46+
isMultiModalToolResult,
4247
isToolCallMessage,
48+
isToolMessage,
49+
ToolCall,
50+
ToolMessage,
4351
validateToolCall,
4452
} from "../../../services/refact";
4553

@@ -334,4 +342,100 @@ export const chatReducer = createReducer(initialState, (builder) => {
334342
const newMessage = { ...lastMessage, tool_calls: validToolCalls };
335343
state.thread.messages = [...messages, newMessage];
336344
});
345+
346+
builder.addCase(upsertToolCall, (state, action) => {
347+
// if (action.payload.toolCallId !== state.thread.id && !(action.payload.chatId in state.cache)) return state;
348+
if (action.payload.chatId === state.thread.id) {
349+
maybeAppendToolCallResultFromIdeToMessages(
350+
state.thread.messages,
351+
action.payload.toolCallId,
352+
action.payload.accepted,
353+
);
354+
} else if (action.payload.chatId in state.cache) {
355+
const thread = state.cache[action.payload.chatId];
356+
maybeAppendToolCallResultFromIdeToMessages(
357+
thread.messages,
358+
action.payload.toolCallId,
359+
action.payload.accepted,
360+
action.payload.replaceOnly,
361+
);
362+
}
363+
});
337364
});
365+
366+
export function maybeAppendToolCallResultFromIdeToMessages(
367+
messages: Draft<ChatMessages>,
368+
toolCallId: string,
369+
accepted: boolean | "indeterminate",
370+
replaceOnly = false,
371+
) {
372+
const hasDiff = messages.find(
373+
(d) => isDiffMessage(d) && d.tool_call_id === toolCallId,
374+
);
375+
if (hasDiff) return;
376+
377+
const maybeToolResult = messages.find(
378+
(d) => isToolMessage(d) && d.content.tool_call_id === toolCallId,
379+
);
380+
381+
const toolCalls = messages.reduce<ToolCall[]>((acc, message) => {
382+
if (!isAssistantMessage(message)) return acc;
383+
if (!message.tool_calls) return acc;
384+
return acc.concat(message.tool_calls);
385+
}, []);
386+
387+
const maybeToolCall = toolCalls.find(
388+
(toolCall) => toolCall.id === toolCallId,
389+
);
390+
391+
const message = messageForToolCall(accepted, maybeToolCall);
392+
393+
if (replaceOnly && !maybeToolResult) return;
394+
395+
if (
396+
maybeToolResult &&
397+
isToolMessage(maybeToolResult) &&
398+
typeof maybeToolResult.content.content === "string"
399+
) {
400+
maybeToolResult.content.content = message;
401+
return;
402+
} else if (
403+
maybeToolResult &&
404+
isToolMessage(maybeToolResult) &&
405+
isMultiModalToolResult(maybeToolResult.content)
406+
) {
407+
maybeToolResult.content.content.push({
408+
m_type: "text",
409+
m_content: message,
410+
});
411+
return;
412+
}
413+
414+
const assistantMessageIndex = messages.findIndex((message) => {
415+
if (!isAssistantMessage(message)) return false;
416+
return message.tool_calls?.find((toolCall) => toolCall.id === toolCallId);
417+
});
418+
419+
if (assistantMessageIndex === -1) return;
420+
const toolMessage: ToolMessage = {
421+
role: "tool",
422+
content: {
423+
content: message,
424+
tool_call_id: toolCallId,
425+
},
426+
};
427+
428+
messages.splice(assistantMessageIndex + 1, 0, toolMessage);
429+
}
430+
431+
function messageForToolCall(
432+
accepted: boolean | "indeterminate",
433+
toolCall?: ToolCall,
434+
) {
435+
if (accepted === false && toolCall?.function.name) {
436+
`Whoops the user didn't like the command ${toolCall.function.name}. Stop and ask for correction from the user.`;
437+
}
438+
if (accepted === false) return "The user rejected the changes.";
439+
if (accepted === true) return "The user accepted the changes.";
440+
return "The user may have made modifications to changes.";
441+
}

refact-agent/gui/src/features/History/historySlice.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
chatGenerateTitleThunk,
1010
ChatThread,
1111
doneStreaming,
12+
maybeAppendToolCallResultFromIdeToMessages,
1213
removeChatFromCache,
1314
restoreChat,
1415
setChatMode,
@@ -20,6 +21,7 @@ import {
2021
isUserMessage,
2122
} from "../../services/refact";
2223
import { AppDispatch, RootState } from "../../app/store";
24+
import { ideToolCallResponse } from "../../hooks/useEventBusForIDE";
2325

2426
export type ChatHistoryItem = Omit<ChatThread, "new_chat_suggested"> & {
2527
createdAt: string;
@@ -143,6 +145,23 @@ export const historySlice = createSlice({
143145
clearHistory: () => {
144146
return {};
145147
},
148+
149+
upsertToolCallIntoHistory: (
150+
state,
151+
action: PayloadAction<
152+
Parameters<typeof ideToolCallResponse>[0] & {
153+
replaceOnly?: boolean;
154+
}
155+
>,
156+
) => {
157+
if (!(action.payload.chatId in state)) return;
158+
maybeAppendToolCallResultFromIdeToMessages(
159+
state[action.payload.chatId].messages,
160+
action.payload.toolCallId,
161+
action.payload.accepted,
162+
action.payload.replaceOnly,
163+
);
164+
},
146165
},
147166
selectors: {
148167
getChatById: (state, id: string): ChatHistoryItem | null => {
@@ -165,6 +184,7 @@ export const {
165184
setTitleGenerationCompletionForChat,
166185
updateChatTitleById,
167186
clearHistory,
187+
upsertToolCallIntoHistory,
168188
} = historySlice.actions;
169189
export const { getChatById, getHistory } = historySlice.selectors;
170190

refact-agent/gui/src/hooks/useEventBusForIDE.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const ideToolCall = createAction<{
5555

5656
export const ideToolCallResponse = createAction<{
5757
toolCallId: string;
58+
chatId: string;
5859
accepted: boolean | "indeterminate";
5960
}>("ide/toolEditResponse");
6061

refact-agent/gui/src/hooks/useSendChatRequest.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ import {
5050
setChatMode,
5151
setIsWaitingForResponse,
5252
setLastUserMessageId,
53+
upsertToolCall,
5354
} from "../features/Chat";
5455

5556
import { v4 as uuidv4 } from "uuid";
57+
import { upsertToolCallIntoHistory } from "../features/History/historySlice";
5658

5759
type SubmitHandlerParams =
5860
| {
@@ -318,16 +320,27 @@ export const useSendChatRequest = () => {
318320
dispatch(setIsWaitingForResponse(false));
319321
}, [abort, dispatch]);
320322

321-
const rejectToolUsage = useCallback(() => {
322-
abort();
323-
dispatch(
324-
clearPauseReasonsAndHandleToolsStatus({
325-
wasInteracted: true,
326-
confirmationStatus: false,
327-
}),
328-
);
329-
dispatch(setIsWaitingForResponse(false));
330-
}, [abort, dispatch]);
323+
const rejectToolUsage = useCallback(
324+
(toolCallIds: string[]) => {
325+
abort();
326+
327+
toolCallIds.forEach((toolCallId) => {
328+
dispatch(
329+
upsertToolCallIntoHistory({ toolCallId, chatId, accepted: false }),
330+
);
331+
dispatch(upsertToolCall({ toolCallId, chatId, accepted: false }));
332+
});
333+
334+
dispatch(
335+
clearPauseReasonsAndHandleToolsStatus({
336+
wasInteracted: true,
337+
confirmationStatus: false,
338+
}),
339+
);
340+
dispatch(setIsWaitingForResponse(false));
341+
},
342+
[abort, chatId, dispatch],
343+
);
331344

332345
const retryFromIndex = useCallback(
333346
(index: number, question: UserMessage["content"]) => {

0 commit comments

Comments
 (0)