Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions src/vs/platform/agentHost/node/agentHostTelemetryReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ import { AgentHostClientConnectionKind, AgentHostLaunchKind, AgentHostTransportK
export type AgentHostUserMessageSentSource = 'direct' | 'queued';

/**
* Who produced the message that started a turn. Extends the protocol's
* {@link MessageKind} with `agentMerge`: Agent Merge drives its repair turns
* with a host-generated message that carries the `systemNotification` origin,
* and reporting those under their own value keeps automated merge work
* separable from turns a person or an agent asked for.
* The message origin used for telemetry. Extends the protocol's
* {@link MessageKind} with host-owned classifications for Agent Merge repair
* turns and VS Code sessions marked ephemeral, which telemetry reports as
* `inline`.
*/
export type AgentHostMessageOriginTelemetryKind = MessageKind | 'agentMerge';
export type AgentHostMessageOriginTelemetryKind = MessageKind | 'agentMerge' | 'inline';

/** Classifies the actor that produced a turn's message for telemetry. */
export function getMessageOriginTelemetryKind(message: Message): AgentHostMessageOriginTelemetryKind {
/** Classifies a turn's message origin, including host-owned session classifications. */
export function getMessageOriginTelemetryKind(message: Message, isEphemeralSession: boolean): AgentHostMessageOriginTelemetryKind {
if (isEphemeralSession) {
return 'inline';
}
// The marker only counts on the origin the host stamps it with, so a client
// cannot dress a user message up as automated merge work.
if (message.origin.kind === MessageKind.SystemNotification && isAgentMergeMessage(message)) {
Expand Down Expand Up @@ -123,7 +125,7 @@ export type IAgentHostUserMessageSentClassification = IAgentHostCopilotSkuClassi
initiatorDevDeviceId?: { classification: 'EndUserPseudonymizedInformation'; purpose: 'BusinessInsight'; endpoint: 'SqmMachineId'; comment: 'The initiating VS Code client development device identifier.' };
agentSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The agent host session identifier.' };
source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the message was sent directly or from the queued-message flow.' };
messageOriginKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of actor that produced the message: a user, an agent (session orchestration tools such as create_session/send_message), Agent Merge, a tool, an automation, or a system notification.' };
messageOriginKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The message origin: a user, an agent (session orchestration tools such as create_session/send_message), Agent Merge, an inline session (derived from the VS Code ephemeral-session marker), a tool, an automation, or a system notification.' };
isSubagentSession: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the message was sent to a subagent session.' };
turnCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of completed turns in the session when the message was sent.' };
activeClientId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the first active client for the session, if any.' };
Expand Down Expand Up @@ -249,7 +251,7 @@ export type IAgentHostTurnCompletedClassification = IAgentHostEventClassificatio
isBYOK: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the selected model is a bring-your-own-key model, when model context is available.' };
permissionLevel: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The tool auto-approval level configured for the session at turn start (e.g. default, autoApprove, autopilot).' };
interactionMode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The agent host interaction mode configured at turn start.' };
messageOriginKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The kind of actor that started the turn: a user, an agent (session orchestration tools such as create_session/create_chat/send_message), Agent Merge, a tool, an automation, or a system notification.' };
messageOriginKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The message origin that started the turn: a user, an agent (session orchestration tools such as create_session/create_chat/send_message), Agent Merge, an inline session (derived from the VS Code ephemeral-session marker), a tool, an automation, or a system notification.' };
errorType: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The structured agent host or provider error type when the turn fails.' };
failureStage: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The bounded stage at which the agent host turn failed.' };
isMultiRoot: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the session spans more than one working directory.' };
Expand All @@ -270,6 +272,7 @@ export interface IAgentHostTurnFailedEvent extends IAgentHostEventTelemetry {
chatSessionId: string;
isSubagentSession: boolean;
turnId: string;
messageOriginKind: AgentHostMessageOriginTelemetryKind | undefined;
failureStage: AgentHostTurnFailureStage;
errorType: string;
errorName: string | undefined;
Expand All @@ -286,6 +289,7 @@ export type IAgentHostTurnFailedClassification = IAgentHostEventClassification &
chatSessionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The chat identifier within the agent host session.' };
isSubagentSession: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the failed turn belongs to a subagent session.' };
turnId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the failed turn within the agent host session.' };
messageOriginKind: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The kind of actor or host-owned session classification that started the failed turn.' };
failureStage: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The bounded stage at which the agent host turn failed.' };
errorType: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The structured agent host or provider error type.' };
errorName: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The name of the exception, when available.' };
Expand Down Expand Up @@ -406,6 +410,7 @@ export interface IAgentHostTurnHungEvent extends IAgentHostEventTelemetry {
chatSessionId: string;
isSubagentSession: boolean;
turnId: string;
messageOriginKind: AgentHostMessageOriginTelemetryKind | undefined;
hangReason: AgentHostTurnHangReason;
isExpected: boolean;
hadAnyProgress: boolean;
Expand Down Expand Up @@ -433,6 +438,7 @@ export type IAgentHostTurnHungClassification = IAgentHostEventClassification & {
chatSessionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The chat identifier within the agent host session.' };
isSubagentSession: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the hung turn belongs to a subagent session.' };
turnId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the hung turn within the agent host session.' };
messageOriginKind: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The kind of actor or host-owned session classification that started the hung turn.' };
hangReason: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The bounded state the turn was quiet in: noProgress, stalledAfterProgress, waitingOnUser, or runningTool.' };
isExpected: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the quiet period is explained by a legitimate wait (blocked on the user or running a tool) rather than an unexplained hang.' };
hadAnyProgress: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether any turn activity at all was observed before the watchdog fired.' };
Expand Down Expand Up @@ -460,6 +466,7 @@ export interface IAgentHostTurnHungReport extends IAgentHostTurnAttributedReport
provider: string;
session: string;
turnId: string;
messageOriginKind: AgentHostMessageOriginTelemetryKind | undefined;
hangReason: AgentHostTurnHangReason;
hadAnyProgress: boolean;
lastActivityKind: string;
Expand All @@ -485,6 +492,7 @@ export interface IAgentHostHungTurnCompletedEvent extends IAgentHostEventTelemet
chatSessionId: string;
isSubagentSession: boolean;
turnId: string;
messageOriginKind: AgentHostMessageOriginTelemetryKind | undefined;
hangReason: AgentHostTurnHangReason;
result: AgentHostTurnResult;
hangReportCount: number;
Expand All @@ -498,6 +506,7 @@ export type IAgentHostHungTurnCompletedClassification = IAgentHostEventClassific
chatSessionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The chat identifier within the agent host session.' };
isSubagentSession: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the recovered turn belongs to a subagent session.' };
turnId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the recovered turn within the agent host session.' };
messageOriginKind: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The kind of actor or host-owned session classification that started the recovered turn.' };
hangReason: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The most recently reported hang reason for the turn before it completed.' };
result: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether the previously hung turn eventually completed successfully, with an error, or was cancelled.' };
hangReportCount: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Number of hang reports emitted for the turn before it completed.' };
Expand All @@ -511,6 +520,7 @@ export interface IAgentHostHungTurnCompletedReport extends IAgentHostTurnAttribu
provider: string;
session: string;
turnId: string;
messageOriginKind: AgentHostMessageOriginTelemetryKind | undefined;
hangReason: AgentHostTurnHangReason;
result: AgentHostTurnResult;
hangReportCount: number;
Expand Down Expand Up @@ -886,7 +896,7 @@ export class AgentHostTelemetryReporter {
});
}

userMessageSent(provider: string, clientId: string | undefined, clientContext: IAgentHostClientTelemetryContext, session: string, turnId: string, sessionState: ISessionWithDefaultChat | undefined, source: AgentHostUserMessageSentSource, message: Message): void {
userMessageSent(provider: string, clientId: string | undefined, clientContext: IAgentHostClientTelemetryContext, session: string, turnId: string, sessionState: ISessionWithDefaultChat | undefined, source: AgentHostUserMessageSentSource, message: Message, isEphemeralSession: boolean): void {
const attachmentCount = message.attachments?.length ?? 0;
const activeClients = sessionState?.activeClients ?? [];
const sessionUri = isAhpChatChannel(session) ? parseRequiredSessionUriFromChatUri(session) : session;
Expand All @@ -901,7 +911,7 @@ export class AgentHostTelemetryReporter {
...(clientContext.devDeviceId ? { initiatorDevDeviceId: clientContext.devDeviceId } : {}),
agentSessionId: AgentSession.id(sessionUri),
source,
messageOriginKind: getMessageOriginTelemetryKind(message),
messageOriginKind: getMessageOriginTelemetryKind(message, isEphemeralSession),
isSubagentSession: isSubagentSession(sessionUri),
turnCount: sessionState?.turns.length ?? 0,
...(activeClients.length > 0 ? {
Expand All @@ -916,7 +926,7 @@ export class AgentHostTelemetryReporter {
initiatorClientType: clientContext.clientType,
conversationId: AgentSession.id(sessionUri),
turnId,
messageOriginKind: getMessageOriginTelemetryKind(message),
messageOriginKind: getMessageOriginTelemetryKind(message, isEphemeralSession),
});
}

Expand Down Expand Up @@ -1270,6 +1280,7 @@ export class AgentHostTelemetryReporter {
chatSessionId,
isSubagentSession: isSubagent,
turnId: report.turnId,
messageOriginKind: report.messageOriginKind,
failureStage: report.failure.stage,
errorType: report.failure.error.errorType,
errorName: report.failure.errorName,
Expand All @@ -1296,6 +1307,7 @@ export class AgentHostTelemetryReporter {
chatSessionId: getTelemetryChatSessionId(report.session),
isSubagentSession: isSubagentChatUri(report.session) || isSubagentSession(session),
turnId: report.turnId,
messageOriginKind: report.messageOriginKind,
hangReason: report.hangReason,
isExpected: report.hangReason === 'waitingOnUser' || report.hangReason === 'runningTool',
hadAnyProgress: report.hadAnyProgress,
Expand Down Expand Up @@ -1330,6 +1342,7 @@ export class AgentHostTelemetryReporter {
chatSessionId: getTelemetryChatSessionId(report.session),
isSubagentSession: isSubagentChatUri(report.session) || isSubagentSession(session),
turnId: report.turnId,
messageOriginKind: report.messageOriginKind,
hangReason: report.hangReason,
result: report.result,
hangReportCount: report.hangReportCount,
Expand Down
6 changes: 4 additions & 2 deletions src/vs/platform/agentHost/node/agentHostTurnStarter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ export function startTurn(accessor: ServicesAccessor, request: ITurnStartRequest
return undefined;
}

telemetryReporter.userMessageSent(agent.id, request.clientId, request.clientContext, request.chat, request.turnId, state, request.source, request.message);
const isEphemeralSession = stateManager.isEphemeralSession(request.session);
const messageOriginKind = getMessageOriginTelemetryKind(request.message, isEphemeralSession);
telemetryReporter.userMessageSent(agent.id, request.clientId, request.clientContext, request.chat, request.turnId, state, request.source, request.message, isEphemeralSession);
const { model, modelTelemetryKind, modelSelectionKind, permissionLevel, interactionMode } = getTurnTelemetryContext(agent, request.chat, createAgentChatContext(stateManager, request.session, request.chat), state, request.message.model?.id);
turnTracker.turnStarted(agent, request.chat, request.turnId, model, modelTelemetryKind, modelSelectionKind, permissionLevel, interactionMode, request.clientContext, request.clientId, undefined, undefined, getMessageOriginTelemetryKind(request.message));
turnTracker.turnStarted(agent, request.chat, request.turnId, model, modelTelemetryKind, modelSelectionKind, permissionLevel, interactionMode, request.clientContext, request.clientId, undefined, undefined, messageOriginKind);
return { agent };
}
6 changes: 6 additions & 0 deletions src/vs/platform/agentHost/node/agentHostTurnTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,10 @@ export class AgentHostTurnTracker extends Disposable {
return this._turnTimings.get(this._key(session, turnId))?.clientContext;
}

getMessageOriginKind(session: string, turnId: string): AgentHostMessageOriginTelemetryKind | undefined {
return this._turnTimings.get(this._key(session, turnId))?.messageOriginKind;
}

getInitiatorClientId(session: string, turnId: string): string | undefined {
return this._turnTimings.get(this._key(session, turnId))?.initiatorClientId;
}
Expand Down Expand Up @@ -424,6 +428,7 @@ export class AgentHostTurnTracker extends Disposable {
provider: timing.agent.id,
session: timing.session,
turnId,
messageOriginKind: timing.messageOriginKind,
hangReason: timing.lastHangReason,
result,
hangReportCount: timing.hangReportCount,
Expand Down Expand Up @@ -505,6 +510,7 @@ export class AgentHostTurnTracker extends Disposable {
provider: timing.agent.id,
session: timing.session,
turnId: timing.turnId,
messageOriginKind: timing.messageOriginKind,
hangReason,
hadAnyProgress: timing.lastActivityKind !== TURN_ACTIVITY_NONE,
lastActivityKind: timing.lastActivityKind,
Expand Down
Loading
Loading