diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index e6f04e259e8..ef21a3d4026 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -31,7 +31,7 @@ import { reviewEdits } from '../../../inlineChat/browser/inlineChatController.js import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from '../../../terminal/browser/terminal.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { ChatCopyKind, IChatService } from '../../common/chatService.js'; -import { IChatResponseViewModel, isResponseVM } from '../../common/chatViewModel.js'; +import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; import { ChatAgentLocation } from '../../common/constants.js'; import { IChatCodeBlockContextProviderService, IChatWidgetService } from '../chat.js'; import { DefaultChatTextEditor, ICodeBlockActionContext, ICodeCompareBlockActionContext } from '../codeBlockPart.js'; @@ -152,6 +152,8 @@ export function registerChatCodeBlockActions() { if (isResponseVM(context.element)) { const chatService = accessor.get(IChatService); + const requestId = context.element.requestId; + const request = context.element.session.getItems().find(item => item.id === requestId && isRequestVM(item)) as IChatRequestViewModel | undefined; chatService.notifyUserAction({ agentId: context.element.agent?.id, command: context.element.slashCommand?.name, @@ -165,6 +167,10 @@ export function registerChatCodeBlockActions() { copiedCharacters: context.code.length, totalCharacters: context.code.length, copiedText: context.code, + copiedLines: context.code.split('\n').length, + languageId: context.languageId, + totalLines: context.code.split('\n').length, + modelId: request?.modelId ?? '' } }); } @@ -197,7 +203,9 @@ export function registerChatCodeBlockActions() { // Report copy to extensions const chatService = accessor.get(IChatService); const element = context.element as IChatResponseViewModel | undefined; - if (element) { + if (isResponseVM(element)) { + const requestId = element.requestId; + const request = element.session.getItems().find(item => item.id === requestId && isRequestVM(item)) as IChatRequestViewModel | undefined; chatService.notifyUserAction({ agentId: element.agent?.id, command: element.slashCommand?.name, @@ -211,6 +219,10 @@ export function registerChatCodeBlockActions() { copiedText, copiedCharacters: copiedText.length, totalCharacters, + languageId: context.languageId, + totalLines: context.code.split('\n').length, + copiedLines: copiedText.split('\n').length, + modelId: request?.modelId ?? '' } }); } @@ -336,6 +348,8 @@ export function registerChatCodeBlockActions() { editorService.openEditor({ contents: context.code, languageId: context.languageId, resource: undefined } satisfies IUntitledTextResourceEditorInput); if (isResponseVM(context.element)) { + const requestId = context.element.requestId; + const request = context.element.session.getItems().find(item => item.id === requestId && isRequestVM(item)) as IChatRequestViewModel | undefined; chatService.notifyUserAction({ agentId: context.element.agent?.id, command: context.element.slashCommand?.name, @@ -346,7 +360,10 @@ export function registerChatCodeBlockActions() { kind: 'insert', codeBlockIndex: context.codeBlockIndex, totalCharacters: context.code.length, - newFile: true + newFile: true, + totalLines: context.code.split('\n').length, + languageId: context.languageId, + modelId: request?.modelId ?? '' } }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts b/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts index 80758dba416..b8d78eff163 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts @@ -30,7 +30,7 @@ import { IActiveNotebookEditor, INotebookEditor } from '../../../notebook/browse import { CellKind, ICellEditOperation, NOTEBOOK_EDITOR_ID } from '../../../notebook/common/notebookCommon.js'; import { ICodeMapperCodeBlock, ICodeMapperRequest, ICodeMapperResponse, ICodeMapperService } from '../../common/chatCodeMapperService.js'; import { ChatUserAction, IChatService } from '../../common/chatService.js'; -import { isResponseVM } from '../../common/chatViewModel.js'; +import { IChatRequestViewModel, isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; import { ICodeBlockActionContext } from '../codeBlockPart.js'; import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; @@ -61,11 +61,19 @@ export class InsertCodeBlockOperation { this.notify(localize('insertCodeBlock.noActiveEditor', "To insert the code block, open a code editor or notebook editor and set the cursor at the location where to insert the code block.")); } } - notifyUserAction(this.chatService, context, { - kind: 'insert', - codeBlockIndex: context.codeBlockIndex, - totalCharacters: context.code.length - }); + + if (isResponseVM(context.element)) { + const requestId = context.element.requestId; + const request = context.element.session.getItems().find(item => item.id === requestId && isRequestVM(item)) as IChatRequestViewModel | undefined; + notifyUserAction(this.chatService, context, { + kind: 'insert', + codeBlockIndex: context.codeBlockIndex, + totalCharacters: context.code.length, + totalLines: context.code.split('\n').length, + languageId: context.languageId, + modelId: request?.modelId ?? '', + }); + } } private async handleNotebookEditor(notebookEditor: IActiveNotebookEditor, codeBlockContext: ICodeBlockActionContext): Promise { @@ -159,13 +167,21 @@ export class ApplyCodeBlockOperation { this.notify(localize('applyCodeBlock.noActiveEditor', "To apply this code block, open a code or notebook editor.")); } } - notifyUserAction(this.chatService, context, { - kind: 'apply', - codeBlockIndex: context.codeBlockIndex, - totalCharacters: context.code.length, - codeMapper: result?.codeMapper, - editsProposed: !!result?.editsProposed - }); + + if (isResponseVM(context.element)) { + const requestId = context.element.requestId; + const request = context.element.session.getItems().find(item => item.id === requestId && isRequestVM(item)) as IChatRequestViewModel | undefined; + notifyUserAction(this.chatService, context, { + kind: 'apply', + codeBlockIndex: context.codeBlockIndex, + totalCharacters: context.code.length, + codeMapper: result?.codeMapper, + editsProposed: !!result?.editsProposed, + totalLines: context.code.split('\n').length, + modelId: request?.modelId ?? '', + languageId: context.languageId, + }); + } } private async evaluateURIToUse(resource: URI | undefined, activeEditorControl: IActiveCodeEditor | undefined): Promise { diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 67c8c42b4d3..7d6c6783385 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -63,6 +63,7 @@ export interface IChatRequestModel { readonly editedFileEvents?: IChatAgentEditedFileEvent[]; shouldBeRemovedOnSend: IChatRequestDisablement | undefined; shouldBeBlocked: boolean; + readonly modelId?: string; } export interface IChatTextEditGroupState { @@ -959,6 +960,7 @@ export interface ISerializableChatRequestData { timestamp?: number; confirmation?: string; editedFileEvents?: IChatAgentEditedFileEvent[]; + modelId?: string; } export interface IExportableChatData { @@ -1347,6 +1349,7 @@ export class ChatModel extends Disposable implements IChatModel { restoredId: raw.requestId, confirmation: raw.confirmation, editedFileEvents: raw.editedFileEvents, + modelId: raw.modelId, }); request.shouldBeRemovedOnSend = raw.isHidden ? { requestId: raw.requestId } : raw.shouldBeRemovedOnSend; if (raw.response || raw.result || (raw as any).responseErrorDetails) { @@ -1691,6 +1694,7 @@ export class ChatModel extends Disposable implements IChatModel { timestamp: r.timestamp, confirmation: r.confirmation, editedFileEvents: r.editedFileEvents, + modelId: r.modelId, }; }), }; diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index b28f8de13fd..22f86179733 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -369,12 +369,19 @@ export interface IChatCopyAction { copiedCharacters: number; totalCharacters: number; copiedText: string; + totalLines: number; + copiedLines: number; + modelId: string; + languageId?: string; } export interface IChatInsertAction { kind: 'insert'; codeBlockIndex: number; totalCharacters: number; + totalLines: number; + languageId?: string; + modelId: string; newFile?: boolean; } @@ -382,6 +389,9 @@ export interface IChatApplyAction { kind: 'apply'; codeBlockIndex: number; totalCharacters: number; + totalLines: number; + languageId?: string; + modelId: string; newFile?: boolean; codeMapper?: string; editsProposed: boolean; diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 9e16743b96b..7e8a95d2f9f 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -93,6 +93,7 @@ export interface IChatRequestViewModel { readonly slashCommand: IChatAgentCommand | undefined; readonly agentOrSlashCommandDetected: boolean; readonly shouldBeBlocked?: boolean; + readonly modelId?: string; } export interface IChatResponseMarkdownRenderData { @@ -185,6 +186,7 @@ export interface IChatLiveUpdateData { export interface IChatResponseViewModel { readonly model: IChatResponseModel; readonly id: string; + readonly session: IChatViewModel; readonly sessionId: string; /** This ID updates every time the underlying data changes */ readonly dataId: string; @@ -442,6 +444,10 @@ export class ChatRequestViewModel implements IChatRequestViewModel { currentRenderedHeight: number | undefined; + get modelId() { + return this._model.modelId; + } + constructor( private readonly _model: IChatRequestModel, ) { } @@ -569,7 +575,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi } get isLast(): boolean { - return this._chatViewModel.getItems().at(-1) === this; + return this.session.getItems().at(-1) === this; } renderData: IChatResponseRenderData | undefined = undefined; @@ -608,7 +614,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi constructor( private readonly _model: IChatResponseModel, - private readonly _chatViewModel: IChatViewModel, + public readonly session: IChatViewModel, @ILogService private readonly logService: ILogService, @IChatAgentNameService private readonly chatAgentNameService: IChatAgentNameService, ) { diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap index 954291a13fb..e1128cdbe6a 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize.0.snap @@ -102,7 +102,8 @@ codeCitations: [ ], timestamp: undefined, confirmation: undefined, - editedFileEvents: undefined + editedFileEvents: undefined, + modelId: undefined } ] } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap index eac347edaac..c98aa1128f3 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap @@ -86,7 +86,8 @@ codeCitations: [ ], timestamp: undefined, confirmation: undefined, - editedFileEvents: undefined + editedFileEvents: undefined, + modelId: undefined } ] } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap index 6c53d43dcbf..0da7b9fc014 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_serialize.1.snap @@ -109,7 +109,8 @@ codeCitations: [ ], timestamp: undefined, confirmation: undefined, - editedFileEvents: undefined + editedFileEvents: undefined, + modelId: undefined }, { requestId: undefined, @@ -164,7 +165,8 @@ codeCitations: [ ], timestamp: undefined, confirmation: undefined, - editedFileEvents: undefined + editedFileEvents: undefined, + modelId: undefined } ] } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap index f6a351338de..664ca7e4c7a 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_sendRequest_fails.0.snap @@ -86,7 +86,8 @@ codeCitations: [ ], timestamp: undefined, confirmation: undefined, - editedFileEvents: undefined + editedFileEvents: undefined, + modelId: undefined } ] } \ No newline at end of file diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index 59a9d28699c..369d340afe5 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -334,6 +334,10 @@ declare module 'vscode' { copiedCharacters: number; totalCharacters: number; copiedText: string; + totalLines: number; + copiedLines: number; + modelId: string; + languageId?: string; } export interface ChatInsertAction { @@ -341,6 +345,9 @@ declare module 'vscode' { kind: 'insert'; codeBlockIndex: number; totalCharacters: number; + totalLines: number; + languageId?: string; + modelId: string; newFile?: boolean; } @@ -349,6 +356,9 @@ declare module 'vscode' { kind: 'apply'; codeBlockIndex: number; totalCharacters: number; + totalLines: number; + languageId?: string; + modelId: string; newFile?: boolean; codeMapper?: string; }