From dd0c05bdd422bd766bfbd4283f82de1ae1efcfd0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 18 Feb 2026 10:50:19 -0800 Subject: [PATCH] Add telemetry for stop button --- .../browser/actions/chatExecuteActions.ts | 20 +++++++++++++++++- .../chat/common/chatService/chatService.ts | 18 ++++++++++++++++ .../common/chatService/chatServiceImpl.ts | 21 +++++++++++++++++-- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 9ec8cb095eb..1fac629812d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -20,12 +20,13 @@ import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contex import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ChatContextKeys } from '../../common/actions/chatContextKeys.js'; import { IChatMode, IChatModeService } from '../../common/chatModes.js'; import { chatVariableLeader } from '../../common/requestParser/chatParserTypes.js'; -import { IChatService } from '../../common/chatService/chatService.js'; +import { ChatStopCancellationNoopClassification, ChatStopCancellationNoopEvent, ChatStopCancellationNoopEventName, IChatService } from '../../common/chatService/chatService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind, } from '../../common/constants.js'; import { ILanguageModelChatMetadata } from '../../common/languageModels.js'; import { ILanguageModelToolsService } from '../../common/tools/languageModelToolsService.js'; @@ -874,14 +875,31 @@ export class CancelAction extends Action2 { run(accessor: ServicesAccessor, ...args: unknown[]) { const context = args[0] as IChatExecuteActionContext | undefined; const widgetService = accessor.get(IChatWidgetService); + const logService = accessor.get(ILogService); + const telemetryService = accessor.get(ITelemetryService); const widget = context?.widget ?? widgetService.lastFocusedWidget; if (!widget) { + telemetryService.publicLog2(ChatStopCancellationNoopEventName, { + source: 'cancelAction', + reason: 'noWidget', + requestInProgress: 'unknown', + pendingRequests: 0, + }); + logService.info('ChatCancelAction#run: No focused chat widget was found'); return; } const chatService = accessor.get(IChatService); if (widget.viewModel) { chatService.cancelCurrentRequestForSession(widget.viewModel.sessionResource); + } else { + telemetryService.publicLog2(ChatStopCancellationNoopEventName, { + source: 'cancelAction', + reason: 'noViewModel', + requestInProgress: 'unknown', + pendingRequests: 0, + }); + logService.info('ChatCancelAction#run: Canceled chat widget has no view model'); } } } diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts index 610e43616bf..a6604f07fba 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts @@ -1455,3 +1455,21 @@ export interface IChatSessionStartOptions { canUseTools?: boolean; disableBackgroundKeepAlive?: boolean; } + +export const ChatStopCancellationNoopEventName = 'chat.stopCancellationNoop'; + +export type ChatStopCancellationNoopEvent = { + source: 'cancelAction' | 'chatService'; + reason: 'noWidget' | 'noViewModel' | 'noPendingRequest' | 'requestAlreadyCanceled' | 'requestIdUnavailable'; + requestInProgress: 'true' | 'false' | 'unknown'; + pendingRequests: number; +}; + +export type ChatStopCancellationNoopClassification = { + source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The layer where stop cancellation no-op occurred.' }; + reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The no-op reason when stop cancellation did not dispatch fully.' }; + requestInProgress: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether request-in-progress was true, false, or unknown at no-op time.' }; + pendingRequests: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of queued pending requests at no-op time when known.'; isMeasurement: true }; + owner: 'roblourens'; + comment: 'Tracks possible no-op stop cancellation paths.'; +}; diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts index f295461bad9..49070377074 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts @@ -26,6 +26,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { ILogService } from '../../../../../platform/log/common/log.js'; import { Progress } from '../../../../../platform/progress/common/progress.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; import { IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js'; @@ -38,7 +39,7 @@ import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, ICha import { ChatModelStore, IStartSessionProps } from '../model/chatModelStore.js'; import { chatAgentLeader, ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, chatSubcommandLeader, getPromptText, IParsedChatRequest } from '../requestParser/chatParserTypes.js'; import { ChatRequestParser } from '../requestParser/chatRequestParser.js'; -import { ChatMcpServersStarting, ChatRequestQueueKind, ChatSendResult, ChatSendResultQueued, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatModelReference, IChatProgress, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatSessionContext, IChatSessionStartOptions, IChatUserActionEvent, ResponseModelState } from './chatService.js'; +import { ChatMcpServersStarting, ChatRequestQueueKind, ChatSendResult, ChatSendResultQueued, ChatStopCancellationNoopClassification, ChatStopCancellationNoopEvent, ChatStopCancellationNoopEventName, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatModelReference, IChatProgress, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatSessionContext, IChatSessionStartOptions, IChatUserActionEvent, ResponseModelState } from './chatService.js'; import { ChatRequestTelemetry, ChatServiceTelemetry } from './chatServiceTelemetry.js'; import { IChatSessionsService } from '../chatSessionsService.js'; import { ChatSessionStore, IChatSessionEntryMetadata } from '../model/chatSessionStore.js'; @@ -146,6 +147,7 @@ export class ChatService extends Disposable implements IChatService { constructor( @IStorageService private readonly storageService: IStorageService, @ILogService private readonly logService: ILogService, + @ITelemetryService private readonly telemetryService: ITelemetryService, @IExtensionService private readonly extensionService: IExtensionService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -1395,7 +1397,22 @@ export class ChatService extends Disposable implements IChatService { cancelCurrentRequestForSession(sessionResource: URI): void { this.trace('cancelCurrentRequestForSession', `session: ${sessionResource}`); - this._pendingRequests.get(sessionResource)?.cancel(); + const pendingRequest = this._pendingRequests.get(sessionResource); + if (!pendingRequest) { + const model = this._sessionModels.get(sessionResource); + const requestInProgress = model?.requestInProgress.get(); + const pendingRequestsCount = model?.getPendingRequests().length ?? 0; + this.telemetryService.publicLog2(ChatStopCancellationNoopEventName, { + source: 'chatService', + reason: 'noPendingRequest', + requestInProgress: requestInProgress === undefined ? 'unknown' : requestInProgress ? 'true' : 'false', + pendingRequests: pendingRequestsCount, + }); + this.info('cancelCurrentRequestForSession', `No pending request was found for session ${sessionResource}. requestInProgress=${requestInProgress ?? 'unknown'}, pendingRequests=${pendingRequestsCount}`); + return; + } + + pendingRequest.cancel(); this._pendingRequests.deleteAndDispose(sessionResource); }