Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackAttachment.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Disposable, DisposableMap } from '../../../../base/common/lifecycle.js';
7
import { Codicon } from '../../../../base/common/codicons.js';
8
import { basename } from '../../../../base/common/resources.js';
9
import { URI } from '../../../../base/common/uri.js';
10
import { localize } from '../../../../nls.js';
11
import { IAgentFeedbackService } from './agentFeedbackService.js';
12
import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';
13
import { IAgentFeedbackVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';
14
15
export const ATTACHMENT_ID_PREFIX = 'agentFeedback:';
16
17
/**
18
* Keeps the "N feedback items" attachment in the chat input in sync with the
19
* AgentFeedbackService. One attachment per session resource, updated reactively.
20
* Clears feedback after the chat prompt is sent.
21
*/
22
export class AgentFeedbackAttachmentContribution extends Disposable {
23
24
static readonly ID = 'workbench.contrib.agentFeedbackAttachment';
25
26
/** Track onDidAcceptInput subscriptions per widget session */
27
private readonly _widgetListeners = this._store.add(new DisposableMap<string>());
28
29
constructor(
30
@IAgentFeedbackService private readonly _agentFeedbackService: IAgentFeedbackService,
31
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
32
) {
33
super();
34
35
this._store.add(this._agentFeedbackService.onDidChangeFeedback(e => {
36
this._updateAttachment(e.sessionResource);
37
this._ensureAcceptListener(e.sessionResource);
38
}));
39
}
40
41
private async _updateAttachment(sessionResource: URI): Promise<void> {
42
const widget = this._chatWidgetService.getWidgetBySessionResource(sessionResource);
43
if (!widget) {
44
return;
45
}
46
47
const feedbackItems = this._agentFeedbackService.getFeedback(sessionResource);
48
const attachmentId = ATTACHMENT_ID_PREFIX + sessionResource.toString();
49
50
if (feedbackItems.length === 0) {
51
widget.attachmentModel.delete(attachmentId);
52
return;
53
}
54
55
const value = this._buildFeedbackValue(feedbackItems);
56
57
const entry: IAgentFeedbackVariableEntry = {
58
kind: 'agentFeedback',
59
id: attachmentId,
60
name: feedbackItems.length === 1
61
? localize('agentFeedback.one', "1 comment")
62
: localize('agentFeedback.many', "{0} comments", feedbackItems.length),
63
icon: Codicon.comment,
64
sessionResource,
65
feedbackItems: feedbackItems.map(f => ({
66
id: f.id,
67
text: f.text,
68
resourceUri: f.resourceUri,
69
range: f.range,
70
codeSelection: f.codeSelection,
71
diffHunks: f.diffHunks,
72
sourcePRReviewCommentId: f.sourcePRReviewCommentId,
73
})),
74
value,
75
};
76
77
// Upsert
78
widget.attachmentModel.delete(attachmentId);
79
widget.attachmentModel.addContext(entry);
80
}
81
82
/**
83
* Builds a rich string value for the agent feedback attachment from
84
* the selection and diff context already stored on each feedback item.
85
*/
86
private _buildFeedbackValue(feedbackItems: IAgentFeedbackVariableEntry['feedbackItems']): string {
87
const parts: string[] = ['The following comments were made on the code changes:'];
88
for (const item of feedbackItems) {
89
const fileName = basename(item.resourceUri);
90
const lineRef = item.range.startLineNumber === item.range.endLineNumber
91
? `${item.range.startLineNumber}`
92
: `${item.range.startLineNumber}-${item.range.endLineNumber}`;
93
94
let part = `[${fileName}:${lineRef}]`;
95
if (item.sourcePRReviewCommentId) {
96
part += `\n(PR review comment, thread ID: ${item.sourcePRReviewCommentId} — resolve this thread when addressed)`;
97
}
98
if (item.codeSelection) {
99
part += `\nSelection:\n\`\`\`\n${item.codeSelection}\n\`\`\``;
100
}
101
if (item.diffHunks) {
102
part += `\nDiff Hunks:\n\`\`\`diff\n${item.diffHunks}\n\`\`\``;
103
}
104
part += `\nComment: ${item.text}`;
105
parts.push(part);
106
}
107
108
return parts.join('\n\n');
109
}
110
111
/**
112
* Ensure we listen for the chat widget's submit event so we can clear feedback after send.
113
*/
114
private _ensureAcceptListener(sessionResource: URI): void {
115
const key = sessionResource.toString();
116
if (this._widgetListeners.has(key)) {
117
return;
118
}
119
120
const widget = this._chatWidgetService.getWidgetBySessionResource(sessionResource);
121
if (!widget) {
122
return;
123
}
124
125
this._widgetListeners.set(key, widget.onDidSubmitAgent(() => {
126
this._agentFeedbackService.clearFeedback(sessionResource);
127
this._widgetListeners.deleteAndDispose(key);
128
}));
129
}
130
}
131
132