Skip to content

fix: 🐛 Fix the cancel button #5662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

chezsmithy
Copy link
Contributor

@chezsmithy chezsmithy commented May 14, 2025

Description

Updated clearLastEmptyResponse to handle cleanup of dangling thinking, and assistant messages. Also look for dangling tools use and tool result messages that might cause an error with certain APIs like AWS bedrock.

Clearly note that the request has been cancelled to the LLM and stop processing.

Cancel running terminal commands by killing them.

This should resolve any issues where dangling thinking or tools use messages are left behind when the cancel button is used breaking the ability to continue the session.

TODO: I discovered one edge case that I could use help fixing. When a file is edited there is a small window between when the edit is started and the editor displays the edits, Accept and Reject Buttons where the request can be cancelled. In this case the cancel button can leave things in an odd state with code still highlighted and the Accept / Reject buttons left behind. I wasn't sure how to check if we are in that state, and fully cancel the Edit Session. Effectively, I would like to perform he same operations as clicking the Reject Button. (Remove decorations, and cancel the edit session). I believe @RomneyDa's change to make the apply cancellable should solve for this.

Checklist

  • I've read the contributing guide
  • The relevant docs, if any, have been updated or created
  • The relevant tests, if any, have been updated or created

Screenshots

[ For visual changes, include screenshots. Screen recordings are particularly helpful, and appreciated! ]

Testing instructions

Enable Thinking
In Agent Mode:

  1. Ask to update test.js and add a power function.
  2. When thinking is displayed hit cancel.
  3. Observe that thinking is removed, and you are retunred to the previous command.

In Agent Mode:

  1. Ask to update test.js and add a power function.
  2. Once thinking ends, and the LLM is starting to stream the next steps hit cancel.
  3. Observe the edit step is stopped, and the thinking modes are cancelled.

In Agent Mode:

  1. start a long running terminal command.
  2. Hit cancel.
  3. See the terminal command is stopped.

Summary by mrge

Cancelling a user request now removes all history after the last user message, cleaning up any leftover thinking or tool messages.

  • Bug Fixes
    • Fixed an issue where cancelling could leave dangling messages or incomplete tool states in the UI.

@chezsmithy chezsmithy requested a review from a team as a code owner May 14, 2025 07:26
@chezsmithy chezsmithy requested review from RomneyDa and removed request for a team May 14, 2025 07:26
Copy link

netlify bot commented May 14, 2025

Deploy Preview for continuedev ready!

Name Link
🔨 Latest commit 4d7243c
🔍 Latest deploy log https://app.netlify.com/projects/continuedev/deploys/682c0eaa81c5110008b9d6ec
😎 Deploy Preview https://deploy-preview-5662--continuedev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@dosubot dosubot bot added the size:S This PR changes 10-29 lines, ignoring generated files. label May 14, 2025
state.mainEditorContentTrigger =
state.history[state.history.length - 2].editorState;
state.history = state.history.slice(0, -2);
// TODO is this logic correct for tool use conversations?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO was on the mark. I've followed up with an implementation that follows this line of thinking and peformed extensive testing to validate it.

Copy link
Collaborator

@RomneyDa RomneyDa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chezsmithy this will be a great problem to solve, has been on my mind recently.

The main issue and reason I hadn't approached this yet is that (correct me if I'm wrong) if the user cancels streaming in Agent mode with this implementation after several turns of the conversation, there will be no recent user messages and it will essentially truncate to the beginning of the convo since the pattern is
user -> assistant + calls -> tool -> assistant + calls -> tool -> assistant + calls etc.

In this case we also can't just truncate back to the last tool message because (if I remember right) e.g. Anthropic won't accept the handshake user -> assistant + calls -> tool (= user in anthropic/gemini) -> user because of sequential user messages

Would love ideas/brainstorming on how to solve this!

@github-project-automation github-project-automation bot moved this from Todo to In Progress in Issues and PRs May 14, 2025
@chezsmithy
Copy link
Contributor Author

@RomneyDa I ran a number of scenarios last night, and I agree with your assessment. There really isn't a good way to roll back to a point in time and keep all the call data balanced to avoid failures with the next API call.

I've seen other tools handle this differently with checkpoints that allow you to restore artifact state but do not modify the history and do not allow a cancel option.

Typically the loss of context from the last user message seems to be pretty short. The biggest loss can be tool outcome state. Perhaps there is a way to summarize those lost outcomes and inject that into the next user message, or maybe provide that as an option for the user? (E.g You are restarting a cancelled flow, would you like for me to include a summary of your cancelled tool calls into the next request?).

@chezsmithy
Copy link
Contributor Author

Another thought.

Maybe we have adaptive cancel.

If you cancel a thinking call or LLM request after a user message, it rolls to the last user message.

If you cancel during a tools call it only cancels the call but doesn't roll back. Perhaps if a tools call has occurred since the last user message we never allow roll back, only cancelling the next tool call.

@chezsmithy chezsmithy marked this pull request as draft May 15, 2025 20:04
Copy link


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@chezsmithy chezsmithy changed the title fix: 🐛 Remove all history when a user request is cancelled fix: 🐛 Fix the cancel button May 20, 2025
Copy link
Collaborator

@RomneyDa RomneyDa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chezsmithy really appreciate the thorough testing here.
I think despite the solid PR description, I may have lost the core idea being implemented here, not easily grasping the exact scope/consequence of these changes.

Could you write up a quick re-outline of the exact approach/changes again?

@@ -362,6 +369,7 @@ export interface AssistantChatMessage {
role: "assistant";
content: MessageContent;
toolCalls?: ToolCallDelta[];
toolCallId?: string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toolCallId shouldn't be on user chat message since one user message can have multiple tool calls, should already be within each toolcalldelta

@@ -96,7 +96,7 @@ export function LumpToolbar() {
<StopButton
className="text-gray-400"
onClick={() => {
dispatch(cancelStream());
dispatch(cancelButton());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

semantic nitpick, thinking cancelStream makes more sense as the action performed, and since dispatched on keyboard events not just button click. also reduces file changes

role: "tool",
content: "Tool use was cancelled.",
toolCallId: toolCallId
} as ToolResultChatMessageWithId,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we avoid a new one-off message type and use the existing ChatHistoryItemWithMessageId type?

await dispatch(setInactive());
await dispatch(abortStream());
await dispatch(clearLastEmptyResponse());
await dispatch(streamAssistantMessage({ content: "The request has been cancelled." }));
Copy link
Collaborator

@RomneyDa RomneyDa May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In chat mode this should just terminate, shouldn't stream an assistant response

});

// Add handlers for clearLastEmptyResponseThunk
builder.addCase(clearLastEmptyResponse.fulfilled, (state, action) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no action can remove

},

// Add handlers for cancelToolCallAndAddResult
builder.addCase(cancelToolCallAndAddResult.fulfilled, (state, action) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be moved to the thunk so that it's all in one place? I'm not super familiar with the builder paradigm, could you explain the advantage of it vs dispatching a new message/tool call status from the thunk? Stylistic opinion but feels a bit disjointed. Testability related?

@chezsmithy
Copy link
Contributor Author

@RomneyDa I want to solve for the following:

Today using AWS bedrock as a back end hitting the cancel button result in a non presumable session about 70% of the time. The user has to start a new session to recover loosing all context.

  • Cancelled thinking removes orphaned tags that break the AWS converse API.
  • Cancelled tool calls always have an added tool result to not break the AWS converse API and the LLM is fully aware of tool status.
  • Cancelled assistant messages that might be truncated are removed.
  • Cancelling tools like terminal or apply leave you in an expected consistent state.
  • The LLM is aware that cancellation was triggered so it stops and asks for further instructions before proceeding with any tasks.

Where I am at:

  • thinking and assistant messages are cleaned up.
  • orphaned tool call and results are handled in the history. However, I've noted a few edge cases where I still get orphaned results breaking the ability to resume when I hit the cancel button right at the start of tool calls.
  • Cancelling the terminal is wired up but the UI needs work.
  • Writing the assistant message at the end of the cancellation can end with some overwrite in the UI.
  • Cancelling the apply/edit tool needs your fixes to allow cancellation to arrive at a consistent state.
  • Should solve for rejecting all edits on cancel.

Hope this helps? - S.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size:S This PR changes 10-29 lines, ignoring generated files.
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

2 participants