Add telemetry for stop button

This commit is contained in:
Rob Lourens
2026-02-18 10:50:19 -08:00
parent 3810f9746a
commit dd0c05bdd4
3 changed files with 56 additions and 3 deletions
@@ -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<ChatStopCancellationNoopEvent, ChatStopCancellationNoopClassification>(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<ChatStopCancellationNoopEvent, ChatStopCancellationNoopClassification>(ChatStopCancellationNoopEventName, {
source: 'cancelAction',
reason: 'noViewModel',
requestInProgress: 'unknown',
pendingRequests: 0,
});
logService.info('ChatCancelAction#run: Canceled chat widget has no view model');
}
}
}
@@ -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.';
};
@@ -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<ChatStopCancellationNoopEvent, ChatStopCancellationNoopClassification>(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);
}