Skip to content

Commit 044b747

Browse files
authored
Append file changes in response stream (#7404)
* Append file changes in response stream * 💄
1 parent 015e26c commit 044b747

File tree

2 files changed

+117
-11
lines changed

2 files changed

+117
-11
lines changed

src/@types/vscode.proposed.chatParticipantAdditions.d.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,49 @@ declare module 'vscode' {
107107
constructor(toolName: string, toolCallId: string, isError?: boolean);
108108
}
109109

110-
export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatPrepareToolInvocationPart;
110+
/**
111+
* Represents a single file diff entry in a multi diff view.
112+
*/
113+
export interface ChatResponseDiffEntry {
114+
/**
115+
* The original file URI (undefined for new files).
116+
*/
117+
originalUri?: Uri;
118+
119+
/**
120+
* The modified file URI (undefined for deleted files).
121+
*/
122+
modifiedUri?: Uri;
123+
124+
/**
125+
* Optional URI to navigate to when clicking on the file.
126+
*/
127+
goToFileUri?: Uri;
128+
}
129+
130+
/**
131+
* Represents a part of a chat response that shows multiple file diffs.
132+
*/
133+
export class ChatResponseMultiDiffPart {
134+
/**
135+
* Array of file diff entries to display.
136+
*/
137+
value: ChatResponseDiffEntry[];
138+
139+
/**
140+
* The title for the multi diff editor.
141+
*/
142+
title: string;
111143

144+
/**
145+
* Create a new ChatResponseMultiDiffPart.
146+
* @param value Array of file diff entries.
147+
* @param title The title for the multi diff editor.
148+
*/
149+
constructor(value: ChatResponseDiffEntry[], title: string);
150+
}
151+
152+
export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseNotebookEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart | ChatResponseExtensionsPart | ChatResponsePullRequestPart | ChatPrepareToolInvocationPart | ChatToolInvocationPart | ChatResponseMultiDiffPart;
112153
export class ChatResponseWarningPart {
113154
value: MarkdownString;
114155
constructor(value: string | MarkdownString);
@@ -194,6 +235,15 @@ declare module 'vscode' {
194235
constructor(extensions: string[]);
195236
}
196237

238+
export class ChatResponsePullRequestPart {
239+
readonly uri: Uri;
240+
readonly linkTag: string;
241+
readonly title: string;
242+
readonly description: string;
243+
readonly author: string;
244+
constructor(uri: Uri, title: string, description: string, author: string, linkTag: string);
245+
}
246+
197247
export interface ChatResponseStream {
198248

199249
/**
@@ -374,6 +424,10 @@ declare module 'vscode' {
374424
participant?: string;
375425
command?: string;
376426
};
427+
/**
428+
* An optional detail string that will be rendered at the end of the response in certain UI contexts.
429+
*/
430+
details?: string;
377431
}
378432

379433
export namespace chat {
@@ -468,6 +522,15 @@ declare module 'vscode' {
468522
outcome: ChatEditingSessionActionOutcome;
469523
}
470524

525+
export interface ChatEditingHunkAction {
526+
// eslint-disable-next-line local/vscode-dts-string-type-literals
527+
kind: 'chatEditingHunkAction';
528+
uri: Uri;
529+
lineCount: number;
530+
outcome: ChatEditingSessionActionOutcome;
531+
hasRemainingEdits: boolean;
532+
}
533+
471534
export enum ChatEditingSessionActionOutcome {
472535
Accepted = 1,
473536
Rejected = 2,
@@ -476,7 +539,7 @@ declare module 'vscode' {
476539

477540
export interface ChatUserActionEvent {
478541
readonly result: ChatResult;
479-
readonly action: ChatCopyAction | ChatInsertAction | ChatApplyAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction | ChatEditorAction | ChatEditingSessionAction;
542+
readonly action: ChatCopyAction | ChatInsertAction | ChatApplyAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction | ChatEditorAction | ChatEditingSessionAction | ChatEditingHunkAction;
480543
}
481544

482545
export interface ChatPromptReference {

src/github/copilotRemoteAgent.ts

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import vscode, { ThemeIcon } from 'vscode';
6+
import vscode from 'vscode';
77
import { parseSessionLogs, parseToolCallDetails } from '../../common/sessionParsing';
88
import { Repository } from '../api/api';
99
import { COPILOT_LOGINS, copilotEventToStatus, CopilotPRStatus, mostRecentCopilotEvent } from '../common/copilot';
@@ -782,7 +782,7 @@ export class CopilotRemoteAgentManager extends Disposable {
782782
}
783783
return [];
784784
}
785-
785+
786786
private extractPromptFromEvent(event: TimelineEvent): string {
787787
let body = '';
788788
if (event.event === EventType.Commented) {
@@ -876,7 +876,7 @@ export class CopilotRemoteAgentManager extends Disposable {
876876
history.push(sessionRequest);
877877

878878
// Create response turn
879-
const responseHistory = await this.createResponseTurn(logs, session);
879+
const responseHistory = await this.createResponseTurn(pullRequest, logs, session);
880880
if (responseHistory) {
881881
history.push(responseHistory);
882882
}
@@ -1019,9 +1019,9 @@ export class CopilotRemoteAgentManager extends Disposable {
10191019
});
10201020
}
10211021

1022-
private async createResponseTurn(logs: string, session: SessionInfo): Promise<vscode.ChatResponseTurn2 | undefined> {
1022+
private async createResponseTurn(pullRequest: PullRequestModel, logs: string, session: SessionInfo): Promise<vscode.ChatResponseTurn2 | undefined> {
10231023
if (logs.trim().length > 0) {
1024-
return await this.parseSessionLogsIntoResponseTurn(logs, session);
1024+
return await this.parseSessionLogsIntoResponseTurn(pullRequest, logs, session);
10251025
} else if (session.state === 'in_progress') {
10261026
// For in-progress sessions without logs, create a placeholder response
10271027
const placeholderParts = [new vscode.ChatResponseMarkdownPart('Session is initializing...')];
@@ -1135,7 +1135,12 @@ export class CopilotRemoteAgentManager extends Disposable {
11351135
const pollingInterval = 3000; // 3 seconds
11361136

11371137
return new Promise<void>((resolve, reject) => {
1138-
const complete = () => {
1138+
const complete = async () => {
1139+
const multiDiffPart = await this.getFileChangesMultiDiffPart(pullRequest);
1140+
if (multiDiffPart) {
1141+
stream.push(multiDiffPart);
1142+
}
1143+
11391144
stream.push(new vscode.ChatResponseCommandButtonPart({
11401145
title: vscode.l10n.t('Open Changes'),
11411146
command: 'pr.openChanges',
@@ -1218,6 +1223,37 @@ export class CopilotRemoteAgentManager extends Disposable {
12181223
});
12191224
}
12201225

1226+
private async getFileChangesMultiDiffPart(pullRequest: PullRequestModel): Promise<vscode.ChatResponseMultiDiffPart | undefined> {
1227+
try {
1228+
const repoInfo = await this.repoInfo();
1229+
if (!repoInfo) {
1230+
return undefined;
1231+
}
1232+
1233+
const { fm: folderManager } = repoInfo;
1234+
const changeModels = await PullRequestModel.getChangeModels(folderManager, pullRequest);
1235+
1236+
if (changeModels.length === 0) {
1237+
return undefined;
1238+
}
1239+
1240+
const diffEntries: vscode.ChatResponseDiffEntry[] = [];
1241+
for (const changeModel of changeModels) {
1242+
diffEntries.push({
1243+
originalUri: changeModel.parentFilePath,
1244+
modifiedUri: changeModel.filePath,
1245+
goToFileUri: changeModel.filePath
1246+
});
1247+
}
1248+
1249+
const title = `Changes in Pull Request #${pullRequest.number}`;
1250+
return new vscode.ChatResponseMultiDiffPart(diffEntries, title);
1251+
} catch (error) {
1252+
Logger.error(`Failed to get file changes multi diff part: ${error}`, CopilotRemoteAgentManager.ID);
1253+
return undefined;
1254+
}
1255+
}
1256+
12211257
private findPullRequestById(id: number): PullRequestModel | undefined {
12221258
for (const folderManager of this.repositoriesManager.folderManagers) {
12231259
for (const githubRepo of folderManager.gitHubRepositories) {
@@ -1273,10 +1309,10 @@ export class CopilotRemoteAgentManager extends Disposable {
12731309
return toolPart;
12741310
}
12751311

1276-
private async parseSessionLogsIntoResponseTurn(logs: string, _session: SessionInfo): Promise<vscode.ChatResponseTurn2 | undefined> {
1312+
private async parseSessionLogsIntoResponseTurn(pullRequest: PullRequestModel, logs: string, session: SessionInfo): Promise<vscode.ChatResponseTurn2 | undefined> {
12771313
try {
12781314
const logChunks = parseSessionLogs(logs);
1279-
const responseParts: Array<vscode.ChatResponseMarkdownPart | vscode.ChatToolInvocationPart> = [];
1315+
const responseParts: Array<vscode.ChatResponseMarkdownPart | vscode.ChatToolInvocationPart | vscode.ChatResponseMultiDiffPart> = [];
12801316
let currentResponseContent = '';
12811317

12821318
for (const chunk of logChunks) {
@@ -1312,6 +1348,13 @@ export class CopilotRemoteAgentManager extends Disposable {
13121348
responseParts.push(new vscode.ChatResponseMarkdownPart(currentResponseContent.trim()));
13131349
}
13141350

1351+
if (session.state === 'completed') {
1352+
const fileChangesPart = await this.getFileChangesMultiDiffPart(pullRequest);
1353+
if (fileChangesPart) {
1354+
responseParts.push(fileChangesPart);
1355+
}
1356+
}
1357+
13151358
if (responseParts.length > 0) {
13161359
const responseResult: vscode.ChatResult = {};
13171360
return new vscode.ChatResponseTurn2(responseParts, responseResult, 'copilot-swe-agent');
@@ -1427,7 +1470,7 @@ export class CopilotRemoteAgentManager extends Disposable {
14271470
const isDark = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark ||
14281471
vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.HighContrast;
14291472
const themeKind = isDark ? 'dark' : 'light';
1430-
1473+
14311474
switch (status) {
14321475
case CopilotPRStatus.Completed:
14331476
return DataUri.copilotSuccessAsImageDataURI(

0 commit comments

Comments
 (0)