Skip to content

Commit 4b7af07

Browse files
authored
Merge pull request #7315 from uinstinct/tool-call-skip
feat: continue tool calls after rejection
2 parents e58b250 + e0e00c3 commit 4b7af07

File tree

5 files changed

+62
-3
lines changed

5 files changed

+62
-3
lines changed

core/config/sharedConfig.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const sharedConfigSchema = z
3232
displayRawMarkdown: z.boolean(),
3333
showChatScrollbar: z.boolean(),
3434
autoAcceptEditToolDiffs: z.boolean(),
35+
continueAfterToolRejection: z.boolean(),
3536

3637
// `tabAutocompleteOptions` in `ContinueConfig`
3738
useAutocompleteCache: z.boolean(),
@@ -158,6 +159,11 @@ export function modifyAnyConfigWithSharedConfig<
158159
configCopy.ui.showSessionTabs = sharedConfig.showSessionTabs;
159160
}
160161

162+
if (sharedConfig.continueAfterToolRejection !== undefined) {
163+
configCopy.ui.continueAfterToolRejection =
164+
sharedConfig.continueAfterToolRejection;
165+
}
166+
161167
configCopy.experimental = {
162168
...configCopy.experimental,
163169
};

core/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,7 @@ export interface ContinueUIConfig {
13461346
codeWrap?: boolean;
13471347
showSessionTabs?: boolean;
13481348
autoAcceptEditToolDiffs?: boolean;
1349+
continueAfterToolRejection?: boolean;
13491350
}
13501351

13511352
export interface ContextMenuConfig {

gui/src/pages/config/UserSettingsForm.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ export function UserSettingsForm() {
8282

8383
// TODO defaults are in multiple places, should be consolidated and probably not explicit here
8484
const showSessionTabs = config.ui?.showSessionTabs ?? false;
85+
const continueAfterToolRejection =
86+
config.ui?.continueAfterToolRejection ?? false;
8587
const codeWrap = config.ui?.codeWrap ?? false;
8688
const showChatScrollbar = config.ui?.showChatScrollbar ?? false;
8789
const readResponseTTS = config.experimental?.readResponseTTS ?? false;
@@ -449,6 +451,16 @@ export function UserSettingsForm() {
449451
text="@Codebase: use tool calling only"
450452
/>
451453

454+
<ToggleSwitch
455+
isToggled={continueAfterToolRejection}
456+
onToggle={() =>
457+
handleUpdate({
458+
continueAfterToolRejection: !continueAfterToolRejection,
459+
})
460+
}
461+
text="Stream after tool rejection"
462+
/>
463+
452464
{hasContinueEmail && (
453465
<ContinueFeaturesMenu
454466
enableStaticContextualization={

gui/src/redux/thunks/cancelToolCall.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
import { createAsyncThunk } from "@reduxjs/toolkit";
22
import posthog from "posthog-js";
3-
import { cancelToolCall as cancelToolCallAction } from "../slices/sessionSlice";
3+
import {
4+
cancelToolCall as cancelToolCallAction,
5+
updateToolCallOutput,
6+
} from "../slices/sessionSlice";
47
import { ThunkApiType } from "../store";
58
import { findToolCallById } from "../util";
9+
import { streamResponseAfterToolCall } from "./streamResponseAfterToolCall";
10+
11+
const DEFAULT_USER_REJECTION_MESSAGE = `The user skipped the tool call.
12+
If the tool call is optional or non-critical to the main goal, skip it and continue with the next step.
13+
If the tool call is essential, try an alternative approach.
14+
If no alternatives exist, offer to pause here.`;
615

716
export const cancelToolCallThunk = createAsyncThunk<
817
void,
918
{ toolCallId: string },
1019
ThunkApiType
1120
>("chat/cancelToolCall", async ({ toolCallId }, { dispatch, getState }) => {
1221
const state = getState();
22+
const continueAfterToolRejection =
23+
state.config.config.ui?.continueAfterToolRejection;
1324
const toolCallState = findToolCallById(state.session.history, toolCallId);
1425

1526
if (toolCallState) {
@@ -21,6 +32,26 @@ export const cancelToolCallThunk = createAsyncThunk<
2132
});
2233
}
2334

35+
if (continueAfterToolRejection) {
36+
// Update tool call output with rejection message
37+
dispatch(
38+
updateToolCallOutput({
39+
toolCallId,
40+
contextItems: [
41+
{
42+
icon: "problems",
43+
name: "Tool Call Rejected",
44+
description: "User skipped the tool call",
45+
content: DEFAULT_USER_REJECTION_MESSAGE,
46+
hidden: true,
47+
},
48+
],
49+
}),
50+
);
51+
}
52+
2453
// Dispatch the actual cancel action
2554
dispatch(cancelToolCallAction({ toolCallId }));
55+
56+
void dispatch(streamResponseAfterToolCall({ toolCallId }));
2657
});

gui/src/redux/thunks/streamResponseAfterToolCall.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { streamThunkWrapper } from "./streamThunkWrapper";
1616
*/
1717
function areAllToolsDoneStreaming(
1818
assistantMessage: ChatHistoryItemWithMessageId | undefined,
19+
continueAfterToolRejection: boolean | undefined,
1920
): boolean {
2021
// This might occur because of race conditions, if so, the tools are completed
2122
if (!assistantMessage?.toolCallStates) {
@@ -24,7 +25,10 @@ function areAllToolsDoneStreaming(
2425

2526
// Only continue if all tool calls are complete
2627
const completedToolCalls = assistantMessage.toolCallStates.filter(
27-
(tc) => tc.status === "done" || tc.status === "errored",
28+
(tc) =>
29+
tc.status === "done" ||
30+
tc.status === "errored" ||
31+
(continueAfterToolRejection && tc.status === "canceled"),
2832
);
2933

3034
return completedToolCalls.length === assistantMessage.toolCallStates.length;
@@ -71,7 +75,12 @@ export const streamResponseAfterToolCall = createAsyncThunk<
7175
item.toolCallStates?.some((tc) => tc.toolCallId === toolCallId),
7276
);
7377

74-
if (areAllToolsDoneStreaming(assistantMessage)) {
78+
if (
79+
areAllToolsDoneStreaming(
80+
assistantMessage,
81+
state.config.config.ui?.continueAfterToolRejection,
82+
)
83+
) {
7584
unwrapResult(await dispatch(streamNormalInput({})));
7685
}
7786
}),

0 commit comments

Comments
 (0)