Skip to content

Fix apply cancellation and make slash commands cancelable #5907

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

Merged
merged 4 commits into from
Jun 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function slashFromCustomCommand(
config,
selectedCode,
fetch,
abortController,
}) {
// Render prompt template
let renderedPrompt: string;
Expand Down Expand Up @@ -63,7 +64,7 @@ export function slashFromCustomCommand(

for await (const chunk of llm.streamChat(
messages,
new AbortController().signal,
abortController.signal,
completionOptions,
)) {
yield renderChatMessage(chunk);
Expand Down
4 changes: 2 additions & 2 deletions core/commands/slash/commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { renderChatMessage } from "../../util/messageContent.js";
const CommitMessageCommand: SlashCommand = {
name: "commit",
description: "Generate a commit message for current changes",
run: async function* ({ ide, llm, params }) {
run: async function* ({ ide, llm, params, abortController }) {
const includeUnstaged = params?.includeUnstaged ?? false;
const diff = await ide.getDiff(includeUnstaged);

Expand All @@ -16,7 +16,7 @@ const CommitMessageCommand: SlashCommand = {
const prompt = `${diff.join("\n")}\n\nGenerate a commit message for the above set of changes. First, give a single sentence, no more than 80 characters. Then, after 2 line breaks, give a list of no more than 5 short bullet points, each no more than 40 characters. Output nothing except for the commit message, and don't surround it in quotes.`;
for await (const chunk of llm.streamChat(
[{ role: "user", content: prompt }],
new AbortController().signal,
abortController.signal,
)) {
yield renderChatMessage(chunk);
}
Expand Down
4 changes: 2 additions & 2 deletions core/commands/slash/draftIssue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Body:\n\n`;
const DraftIssueCommand: SlashCommand = {
name: "issue",
description: "Draft a GitHub issue",
run: async function* ({ input, llm, history, params }) {
run: async function* ({ input, llm, history, params, abortController }) {
if (params?.repositoryUrl === undefined) {
yield "This command requires a repository URL to be set in the config file.";
return;
Expand All @@ -46,7 +46,7 @@ const DraftIssueCommand: SlashCommand = {

for await (const chunk of llm.streamChat(
messages,
new AbortController().signal,
abortController.signal,
)) {
body += chunk.content;
yield renderChatMessage(chunk);
Expand Down
2 changes: 1 addition & 1 deletion core/commands/slash/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function constructMcpSlashCommand(

for await (const chunk of context.llm.streamChat(
messages,
new AbortController().signal,
context.abortController.signal,
context.completionOptions,
)) {
yield renderChatMessage(chunk);
Expand Down
4 changes: 2 additions & 2 deletions core/commands/slash/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ const MAX_EXPLORE_DEPTH = 2;
const OnboardSlashCommand: SlashCommand = {
name: "onboard",
description: "Familiarize yourself with the codebase",
run: async function* ({ llm, ide }) {
run: async function* ({ llm, ide, abortController }) {
const [workspaceDir] = await ide.getWorkspaceDirs();

const context = await gatherProjectContext(workspaceDir, ide);
const prompt = createOnboardingPrompt(context);

for await (const chunk of llm.streamChat(
[{ role: "user", content: prompt }],
new AbortController().signal,
abortController.signal,
)) {
yield renderChatMessage(chunk);
}
Expand Down
4 changes: 2 additions & 2 deletions core/commands/slash/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ function getLastUserHistory(history: ChatMessage[]): string {
const ReviewMessageCommand: SlashCommand = {
name: "review",
description: "Review code and give feedback",
run: async function* ({ llm, history }) {
run: async function* ({ llm, history, abortController }) {
const reviewText = getLastUserHistory(history).replace("\\review", "");

const content = `${prompt} \r\n ${reviewText}`;

for await (const chunk of llm.streamChat(
[{ role: "user", content: content }],
new AbortController().signal,
abortController.signal,
)) {
yield renderChatMessage(chunk);
}
Expand Down
13 changes: 9 additions & 4 deletions core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
import { createNewWorkspaceBlockFile } from "./config/workspace/workspaceBlocks";
import { MCPManagerSingleton } from "./context/mcp/MCPManagerSingleton";
import { setMdmLicenseKey } from "./control-plane/mdm/mdm";
import { ApplyAbortManager } from "./edit/applyAbortManager";
import { streamDiffLines } from "./edit/streamDiffLines";
import { shouldIgnore } from "./indexing/shouldIgnore";
import { walkDirCache } from "./indexing/walkDir";
Expand All @@ -67,7 +68,6 @@ import { llmStreamChat } from "./llm/streamChat";
import type { FromCoreProtocol, ToCoreProtocol } from "./protocol";
import { OnboardingModes } from "./protocol/core";
import type { IMessenger, Message } from "./protocol/messenger";
import { StreamAbortManager } from "./util/abortManager";
import { getUriPathBasename } from "./util/uri";

const hasRulesFiles = (uris: string[]): boolean => {
Expand Down Expand Up @@ -512,6 +512,11 @@ export class Core {
throw new Error("No model selected");
}

const abortManager = ApplyAbortManager.getInstance();
const abortController = abortManager.get(
data.fileUri ?? "current-file-stream",
); // not super important since currently cancelling apply will cancel all streams it's one file at a time

return streamDiffLines({
highlighted: data.highlighted,
prefix: data.prefix,
Expand All @@ -525,13 +530,13 @@ export class Core {
language: data.language,
onlyOneInsertion: false,
overridePrompt: undefined,
abortControllerId: data.fileUri ?? "current-file-stream", // not super important since currently cancelling apply will cancel all streams it's one file at a time
abortController,
});
});

on("cancelApply", async (msg) => {
const abortManager = StreamAbortManager.getInstance();
abortManager.clear();
const abortManager = ApplyAbortManager.getInstance();
abortManager.clear(); // for now abort all streams
});

on("onboarding/complete", this.handleCompleteOnboarding.bind(this));
Expand Down
12 changes: 6 additions & 6 deletions core/util/abortManager.ts → core/edit/applyAbortManager.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
export class StreamAbortManager {
private static instance: StreamAbortManager;
export class ApplyAbortManager {
private static instance: ApplyAbortManager;
private controllers: Map<string, AbortController>;

private constructor() {
this.controllers = new Map();
}

public static getInstance(): StreamAbortManager {
if (!StreamAbortManager.instance) {
StreamAbortManager.instance = new StreamAbortManager();
public static getInstance(): ApplyAbortManager {
if (!ApplyAbortManager.instance) {
ApplyAbortManager.instance = new ApplyAbortManager();
}
return StreamAbortManager.instance;
return ApplyAbortManager.instance;
}

public get(id: string): AbortController {
Expand Down
9 changes: 8 additions & 1 deletion core/edit/lazy/applyCodeBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export async function applyCodeBlock(
newLazyFile: string,
filename: string,
llm: ILLM,
abortController: AbortController,
): Promise<{
isInstantApply: boolean;
diffLinesGenerator: AsyncGenerator<DiffLine>;
Expand Down Expand Up @@ -51,6 +52,12 @@ export async function applyCodeBlock(

return {
isInstantApply: false,
diffLinesGenerator: streamLazyApply(oldFile, filename, newLazyFile, llm),
diffLinesGenerator: streamLazyApply(
oldFile,
filename,
newLazyFile,
llm,
abortController,
),
};
}
3 changes: 2 additions & 1 deletion core/edit/lazy/replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export async function* getReplacementWithLlm(
linesBefore: string[],
linesAfter: string[],
llm: ILLM,
abortController: AbortController,
): AsyncGenerator<string> {
const userPrompt = dedent`
ORIGINAL CODE:
Expand Down Expand Up @@ -88,7 +89,7 @@ export async function* getReplacementWithLlm(
{ role: "user", content: userPrompt },
{ role: "assistant", content: assistantPrompt },
],
new AbortController().signal,
abortController.signal,
);

let lines = streamLines(completion);
Expand Down
7 changes: 3 additions & 4 deletions core/edit/lazy/streamLazyApply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@ export async function* streamLazyApply(
filename: string,
newCode: string,
llm: ILLM,
abortController: AbortController,
): AsyncGenerator<DiffLine> {
const promptFactory = lazyApplyPromptForModel(llm.model, llm.providerName);
if (!promptFactory) {
throw new Error(`Lazy apply not supported for model ${llm.model}`);
}

const promptMessages = promptFactory(oldCode, filename, newCode);
const lazyCompletion = llm.streamChat(
promptMessages,
new AbortController().signal,
);
const lazyCompletion = llm.streamChat(promptMessages, abortController.signal);

// Do find and replace over the lazy edit response
async function* replacementFunction(
Expand All @@ -39,6 +37,7 @@ export async function* streamLazyApply(
linesBefore,
linesAfter,
llm,
abortController,
)) {
yield line;
}
Expand Down
7 changes: 2 additions & 5 deletions core/edit/streamDiffLines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { streamDiff } from "../diff/streamDiff";
import { streamLines } from "../diff/util";
import { getSystemMessageWithRules } from "../llm/rules/getSystemMessageWithRules";
import { gptEditPrompt } from "../llm/templates/edit";
import { StreamAbortManager } from "../util/abortManager";
import { findLast } from "../util/findLast";
import { Telemetry } from "../util/posthog";
import { recursiveStream } from "./recursiveStream";
Expand Down Expand Up @@ -64,7 +63,7 @@ export async function* streamDiffLines({
highlighted,
suffix,
llm,
abortControllerId,
abortController,
input,
language,
onlyOneInsertion,
Expand All @@ -75,15 +74,13 @@ export async function* streamDiffLines({
highlighted: string;
suffix: string;
llm: ILLM;
abortControllerId: string;
abortController: AbortController;
input: string;
language: string | undefined;
onlyOneInsertion: boolean;
overridePrompt: ChatMessage[] | undefined;
rulesToInclude: RuleWithSource[] | undefined;
}): AsyncGenerator<DiffLine> {
const abortManager = StreamAbortManager.getInstance();
const abortController = abortManager.get(abortControllerId);
void Telemetry.capture(
"inlineEdit",
{
Expand Down
3 changes: 2 additions & 1 deletion core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,7 @@ export interface ContinueSDK {
config: ContinueConfig;
fetch: FetchFunction;
completionOptions?: LLMFullCompletionOptions;
abortController: AbortController;
}

export interface SlashCommand {
Expand Down Expand Up @@ -1280,7 +1281,7 @@ export interface HighlightedCodePayload {
}

export interface AcceptOrRejectDiffPayload {
filepath: string;
filepath?: string;
streamId?: string;
}

Expand Down
1 change: 1 addition & 0 deletions core/llm/streamChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export async function* llmStreamChat(
config.requestOptions,
),
completionOptions,
abortController,
});
let next = await gen.next();
while (!next.done) {
Expand Down
2 changes: 1 addition & 1 deletion core/promptFiles/v1/slashCommandFromPromptFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function slashCommandFromPromptFileV1(

for await (const chunk of context.llm.streamChat(
messages,
new AbortController().signal,
context.abortController.signal,
context.completionOptions,
)) {
yield renderChatMessage(chunk);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ data class StreamDiffLinesPayload(
)

data class AcceptOrRejectDiffPayload(
val filepath: String,
val filepath: String? = null,
val streamId: String? = null
)

Expand Down
8 changes: 7 additions & 1 deletion extensions/vscode/src/apply/ApplyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { applyCodeBlock } from "core/edit/lazy/applyCodeBlock";
import { getUriPathBasename } from "core/util/uri";
import * as vscode from "vscode";

import { ApplyAbortManager } from "core/edit/applyAbortManager";
import { VerticalDiffManager } from "../diff/vertical/manager";
import { VsCodeIde } from "../VsCodeIde";
import { VsCodeWebviewProtocol } from "../webviewProtocol";
Expand Down Expand Up @@ -116,11 +117,16 @@ export class ApplyManager {
return;
}

const fileUri = editor.document.uri.toString();
const abortManager = ApplyAbortManager.getInstance();
const abortController = abortManager.get(fileUri);

const { isInstantApply, diffLinesGenerator } = await applyCodeBlock(
editor.document.getText(),
text,
getUriPathBasename(editor.document.uri.toString()),
getUriPathBasename(fileUri),
llm,
abortController,
);

if (isInstantApply) {
Expand Down
Loading
Loading