Path: blob/main/src/vs/sessions/contrib/agentFeedback/browser/agentFeedbackAttachment.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Disposable, DisposableMap } from '../../../../base/common/lifecycle.js';6import { Codicon } from '../../../../base/common/codicons.js';7import { basename } from '../../../../base/common/resources.js';8import { URI } from '../../../../base/common/uri.js';9import { localize } from '../../../../nls.js';10import { IAgentFeedbackService } from './agentFeedbackService.js';11import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';12import { IAgentFeedbackVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';1314export const ATTACHMENT_ID_PREFIX = 'agentFeedback:';1516/**17* Keeps the "N feedback items" attachment in the chat input in sync with the18* AgentFeedbackService. One attachment per session resource, updated reactively.19* Clears feedback after the chat prompt is sent.20*/21export class AgentFeedbackAttachmentContribution extends Disposable {2223static readonly ID = 'workbench.contrib.agentFeedbackAttachment';2425/** Track onDidAcceptInput subscriptions per widget session */26private readonly _widgetListeners = this._store.add(new DisposableMap<string>());2728constructor(29@IAgentFeedbackService private readonly _agentFeedbackService: IAgentFeedbackService,30@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,31) {32super();3334this._store.add(this._agentFeedbackService.onDidChangeFeedback(e => {35this._updateAttachment(e.sessionResource);36this._ensureAcceptListener(e.sessionResource);37}));38}3940private async _updateAttachment(sessionResource: URI): Promise<void> {41const widget = this._chatWidgetService.getWidgetBySessionResource(sessionResource);42if (!widget) {43return;44}4546const feedbackItems = this._agentFeedbackService.getFeedback(sessionResource);47const attachmentId = ATTACHMENT_ID_PREFIX + sessionResource.toString();4849if (feedbackItems.length === 0) {50widget.attachmentModel.delete(attachmentId);51return;52}5354const value = this._buildFeedbackValue(feedbackItems);5556const entry: IAgentFeedbackVariableEntry = {57kind: 'agentFeedback',58id: attachmentId,59name: feedbackItems.length === 160? localize('agentFeedback.one', "1 comment")61: localize('agentFeedback.many', "{0} comments", feedbackItems.length),62icon: Codicon.comment,63sessionResource,64feedbackItems: feedbackItems.map(f => ({65id: f.id,66text: f.text,67resourceUri: f.resourceUri,68range: f.range,69codeSelection: f.codeSelection,70diffHunks: f.diffHunks,71sourcePRReviewCommentId: f.sourcePRReviewCommentId,72})),73value,74};7576// Upsert77widget.attachmentModel.delete(attachmentId);78widget.attachmentModel.addContext(entry);79}8081/**82* Builds a rich string value for the agent feedback attachment from83* the selection and diff context already stored on each feedback item.84*/85private _buildFeedbackValue(feedbackItems: IAgentFeedbackVariableEntry['feedbackItems']): string {86const parts: string[] = ['The following comments were made on the code changes:'];87for (const item of feedbackItems) {88const fileName = basename(item.resourceUri);89const lineRef = item.range.startLineNumber === item.range.endLineNumber90? `${item.range.startLineNumber}`91: `${item.range.startLineNumber}-${item.range.endLineNumber}`;9293let part = `[${fileName}:${lineRef}]`;94if (item.sourcePRReviewCommentId) {95part += `\n(PR review comment, thread ID: ${item.sourcePRReviewCommentId} — resolve this thread when addressed)`;96}97if (item.codeSelection) {98part += `\nSelection:\n\`\`\`\n${item.codeSelection}\n\`\`\``;99}100if (item.diffHunks) {101part += `\nDiff Hunks:\n\`\`\`diff\n${item.diffHunks}\n\`\`\``;102}103part += `\nComment: ${item.text}`;104parts.push(part);105}106107return parts.join('\n\n');108}109110/**111* Ensure we listen for the chat widget's submit event so we can clear feedback after send.112*/113private _ensureAcceptListener(sessionResource: URI): void {114const key = sessionResource.toString();115if (this._widgetListeners.has(key)) {116return;117}118119const widget = this._chatWidgetService.getWidgetBySessionResource(sessionResource);120if (!widget) {121return;122}123124this._widgetListeners.set(key, widget.onDidSubmitAgent(() => {125this._agentFeedbackService.clearFeedback(sessionResource);126this._widgetListeners.deleteAndDispose(key);127}));128}129}130131132