Skip to content

Commit a0dbf40

Browse files
committed
feat(chat): append tool call accepted message to chat when the user accepts the diff.
1 parent f1102ea commit a0dbf40

File tree

5 files changed

+95
-7
lines changed

5 files changed

+95
-7
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import { ToolCommand } from "../../../services/refact/tools";
3434
import { scanFoDuplicatesWith, takeFromEndWhile } from "../../../utils";
3535
import { debugApp } from "../../../debugConfig";
36+
import { ideToolCallResponse } from "../../../hooks";
3637

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

@@ -141,6 +142,10 @@ export const fixBrokenToolMessages = createAction<PayloadWithId>(
141142
"chatThread/fixBrokenToolMessages",
142143
);
143144

145+
export const upsertToolCall = createAction<
146+
Parameters<typeof ideToolCallResponse>[0]
147+
>("chatThread/upsertToolCall");
148+
144149
// TODO: This is the circular dep when imported from hooks :/
145150
const createAppAsyncThunk = createAsyncThunk.withTypes<{
146151
state: RootState;

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

Lines changed: 66 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,17 @@ 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,
4246
isToolCallMessage,
47+
isToolMessage,
48+
ToolMessage,
4349
validateToolCall,
4450
} from "../../../services/refact";
4551

@@ -331,4 +337,63 @@ export const chatReducer = createReducer(initialState, (builder) => {
331337
const newMessage = { ...lastMessage, tool_calls: validToolCalls };
332338
state.thread.messages = [...messages, newMessage];
333339
});
340+
341+
builder.addCase(upsertToolCall, (state, action) => {
342+
// if (action.payload.toolCallId !== state.thread.id && !(action.payload.chatId in state.cache)) return state;
343+
if (action.payload.chatId === state.thread.id) {
344+
maybeAppendToolCallResultFromIdeToMessages(
345+
state.thread.messages,
346+
action.payload.toolCallId,
347+
action.payload.accepted,
348+
);
349+
} else if (action.payload.chatId in state.cache) {
350+
const thread = state.cache[action.payload.chatId];
351+
maybeAppendToolCallResultFromIdeToMessages(
352+
thread.messages,
353+
action.payload.toolCallId,
354+
action.payload.accepted,
355+
);
356+
}
357+
});
334358
});
359+
360+
export function maybeAppendToolCallResultFromIdeToMessages(
361+
messages: Draft<ChatMessages>,
362+
toolCallId: string,
363+
accepted: boolean | "indeterminate",
364+
) {
365+
const hasDiff = messages.find(
366+
(d) => isDiffMessage(d) && d.tool_call_id === toolCallId,
367+
);
368+
if (hasDiff) return;
369+
370+
const message = messageForToolCall(accepted);
371+
372+
const hasToolCall = messages.find(
373+
(d) => isToolMessage(d) && d.content.tool_call_id === toolCallId,
374+
);
375+
376+
if (hasToolCall) return;
377+
378+
const assistantMessageIndex = messages.findIndex((message) => {
379+
if (!isAssistantMessage(message)) return false;
380+
return message.tool_calls?.find((toolCall) => toolCall.id === toolCallId);
381+
});
382+
383+
if (assistantMessageIndex === -1) return;
384+
const toolMessage: ToolMessage = {
385+
role: "tool",
386+
content: {
387+
content: message,
388+
tool_call_id: toolCallId,
389+
},
390+
};
391+
392+
messages.splice(assistantMessageIndex + 1, 0, toolMessage);
393+
}
394+
395+
function messageForToolCall(accepted: boolean | "indeterminate") {
396+
if (accepted === false) return "The user rejected the changes.";
397+
if (accepted === true) return "The user accepted the changes.";
398+
return "The user may have made modifications to changes.";
399+
}

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

Lines changed: 15 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,
@@ -19,6 +20,7 @@ import {
1920
isUserMessage,
2021
} from "../../services/refact";
2122
import { AppDispatch, RootState } from "../../app/store";
23+
import { ideToolCallResponse } from "../../hooks/useEventBusForIDE";
2224

2325
export type ChatHistoryItem = ChatThread & {
2426
createdAt: string;
@@ -141,6 +143,18 @@ export const historySlice = createSlice({
141143
clearHistory: () => {
142144
return {};
143145
},
146+
147+
upsertToolCallIntoHistory: (
148+
state,
149+
action: PayloadAction<Parameters<typeof ideToolCallResponse>[0]>,
150+
) => {
151+
if (!(action.payload.chatId in state)) return;
152+
maybeAppendToolCallResultFromIdeToMessages(
153+
state[action.payload.chatId].messages,
154+
action.payload.toolCallId,
155+
action.payload.accepted,
156+
);
157+
},
144158
},
145159
selectors: {
146160
getChatById: (state, id: string): ChatHistoryItem | null => {
@@ -163,6 +177,7 @@ export const {
163177
setTitleGenerationCompletionForChat,
164178
updateChatTitleById,
165179
clearHistory,
180+
upsertToolCallIntoHistory,
166181
} = historySlice.actions;
167182
export const { getChatById, getHistory } = historySlice.selectors;
168183

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import { updateConfig } from "../features/Config/configSlice";
66
import { setFileInfo } from "../features/Chat/activeFile";
77
import { setSelectedSnippet } from "../features/Chat/selectedSnippet";
88
import { setCurrentProjectInfo } from "../features/Chat/currentProject";
9-
import { newChatAction } from "../features/Chat/Thread/actions";
9+
import { newChatAction, upsertToolCall } from "../features/Chat/Thread/actions";
1010
import {
1111
isPageInHistory,
1212
push,
1313
selectPages,
1414
} from "../features/Pages/pagesSlice";
1515
import { ideToolCallResponse } from "./useEventBusForIDE";
16+
import { upsertToolCallIntoHistory } from "../features/History/historySlice";
1617

1718
export function useEventBusForApp() {
1819
const config = useConfig();
@@ -45,9 +46,11 @@ export function useEventBusForApp() {
4546
}
4647

4748
if (ideToolCallResponse.match(event.data)) {
48-
console.log("IDE TOOL EDIT RESPONSE");
49-
console.log(event.data);
50-
// TODO: handle the edits
49+
const actions = [
50+
upsertToolCall(event.data.payload),
51+
upsertToolCallIntoHistory(event.data.payload),
52+
];
53+
actions.forEach((action) => dispatch(action));
5154
}
5255

5356
// TODO: ideToolEditResponse.

refact-agent/gui/src/services/refact/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ export interface MultiModalToolResult extends BaseToolResult {
8080

8181
export type ToolResult = SingleModelToolResult | MultiModalToolResult;
8282

83-
type MultiModalToolContent = {
83+
export type MultiModalToolContent = {
8484
m_type: string; // "image/*" | "text" ... maybe narrow this?
8585
m_content: string; // base64 if image,
8686
};
8787

88-
function isMultiModalToolContent(
88+
export function isMultiModalToolContent(
8989
content: unknown,
9090
): content is MultiModalToolContent {
9191
if (!content) return false;

0 commit comments

Comments
 (0)