diff --git a/extensions/copilot/src/extension/inlineChat/node/inlineChatIntent.ts b/extensions/copilot/src/extension/inlineChat/node/inlineChatIntent.ts index 0ac62436d76..f52e6094e73 100644 --- a/extensions/copilot/src/extension/inlineChat/node/inlineChatIntent.ts +++ b/extensions/copilot/src/extension/inlineChat/node/inlineChatIntent.ts @@ -468,6 +468,7 @@ class InlineChatEditToolsStrategy implements IInlineChatEditStrategy { conversationId: telemetry.sessionId, messageSource: this._intent.id }, + requestKindOptions: { kind: 'mainagent' }, finishedCb: async (_text, _index, delta) => { telemetry.markReceivedToken(); @@ -667,6 +668,7 @@ class InlineChatEditHeuristicStrategy implements IInlineChatEditStrategy { conversationId: telemetry.sessionId, messageSource: this._intent.id }, + requestKindOptions: { kind: 'mainagent' }, requestOptions: { stream: true, prediction diff --git a/extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts b/extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts index 86c75b170ef..1bb90a20019 100644 --- a/extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts +++ b/extensions/copilot/src/extension/prompt/node/chatMLFetcher.ts @@ -21,7 +21,7 @@ import { getResponsesApiCompactionThresholdFromBody, OpenAIResponsesProcessor, r import { collectSingleLineErrorMessage, ILogService } from '../../../platform/log/common/logService'; import { FinishedCallback, getRequestId, IResponseDelta, OptionalChatRequestParams, RequestId } from '../../../platform/networking/common/fetch'; import { FetcherId, IFetcherService, Response } from '../../../platform/networking/common/fetcherService'; -import { IBackgroundRequestOptions, IChatEndpoint, IEndpointBody, ISubagentRequestOptions, postRequest, stringifyUrlOrRequestMetadata } from '../../../platform/networking/common/networking'; +import { IChatEndpoint, IEndpointBody, IRequestKindOptions, postRequest, stringifyUrlOrRequestMetadata } from '../../../platform/networking/common/networking'; import { CAPIChatMessage, ChatCompletion, FilterReason, FinishedCompletionReason, rawMessageToCAPI } from '../../../platform/networking/common/openai'; import { sendEngineMessagesTelemetry } from '../../../platform/networking/node/chatStream'; import { CAPIWebSocketErrorEvent, IChatWebSocketManager, isCAPIWebSocketError } from '../../../platform/networking/node/chatWebSocketManager'; @@ -137,6 +137,13 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { */ public async fetchMany(opts: IFetchMLOptions, token: CancellationToken): Promise { let { debugName, endpoint: chatEndpoint, finishedCb, location, messages, requestOptions, source, telemetryProperties, userInitiatedRequest, requestKindOptions, conversationId, turnId, useWebSocket, ignoreStatefulMarker } = opts; + // Default any unmarked request to 'background'. Primary user turns + // (defaultIntentRequestHandler, inlineChatIntent, copilotcli sessions, etc.) + // explicitly opt in to 'mainagent'. This way new utility callers get classified + // correctly without each one having to remember to opt in. + if (!requestKindOptions) { + requestKindOptions = { kind: 'background' }; + } if (useWebSocket && this._consecutiveWebSocketRetryFallbacks >= ChatMLFetcherImpl._maxConsecutiveWebSocketFallbacks) { this._logService.debug(`[ChatWebSocketManager] Disabling WebSocket for request due to ${this._consecutiveWebSocketRetryFallbacks} consecutive WebSocket failures with successful HTTP fallback.`); useWebSocket = false; @@ -470,7 +477,9 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { apiType: chatEndpoint.apiType, transport, requestKindOptions, + conversationId: telemetryProperties.conversationId ?? conversationId, associatedRequestId: telemetryProperties.associatedRequestId, + parentRequestId: telemetryProperties.parentRequestId, retryAfterError: telemetryProperties.retryAfterError, retryAfterErrorGitHubRequestId: telemetryProperties.retryAfterErrorGitHubRequestId, connectivityTestError: telemetryProperties.connectivityTestError, @@ -618,7 +627,9 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { apiType: chatEndpoint.apiType, transport, requestKindOptions, + conversationId: telemetryProperties.conversationId ?? conversationId, associatedRequestId: telemetryProperties.associatedRequestId, + parentRequestId: telemetryProperties.parentRequestId, retryAfterError: telemetryProperties.retryAfterError, retryAfterErrorGitHubRequestId: telemetryProperties.retryAfterErrorGitHubRequestId, connectivityTestError: telemetryProperties.connectivityTestError, @@ -853,7 +864,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { telemetryProperties?: TelemetryProperties | undefined, useFetcher?: FetcherId, canRetryOnce?: boolean, - requestKindOptions?: IBackgroundRequestOptions | ISubagentRequestOptions, + requestKindOptions?: IRequestKindOptions, summarizedAtRoundId?: string, ): Promise<{ result: ChatResults | ChatRequestFailed | ChatRequestCanceled; fetcher?: FetcherId; bytesReceived?: number; statusCode?: number; suspendEventSeen?: boolean; resumeEventSeen?: boolean; otelSpan?: ISpanHandle }> { const isPowerSaveBlockerEnabled = this._configurationService.getExperimentBasedConfig(ConfigKey.TeamInternal.ChatRequestPowerSaveBlocker, this._experimentationService); @@ -930,7 +941,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { telemetryProperties?: TelemetryProperties | undefined, useFetcher?: FetcherId, canRetryOnce?: boolean, - requestKindOptions?: IBackgroundRequestOptions | ISubagentRequestOptions, + requestKindOptions?: IRequestKindOptions, summarizedAtRoundId?: string, ): Promise<{ result: ChatResults | ChatRequestFailed | ChatRequestCanceled; fetcher?: FetcherId; bytesReceived?: number; statusCode?: number; otelSpan?: ISpanHandle }> { @@ -1061,7 +1072,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { countTokens: () => Promise, userInitiatedRequest: boolean | undefined, telemetryProperties: TelemetryProperties | undefined, - requestKindOptions: IBackgroundRequestOptions | ISubagentRequestOptions | undefined, + requestKindOptions: IRequestKindOptions | undefined, summarizedAtRoundId: string | undefined, ): Promise<{ result: ChatResults | ChatRequestFailed | ChatRequestCanceled }> { const intent = locationToIntent(location); @@ -1243,7 +1254,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { telemetryProperties: TelemetryProperties | undefined, useFetcher: FetcherId | undefined, canRetryOnce: boolean | undefined, - requestKindOptions: IBackgroundRequestOptions | ISubagentRequestOptions | undefined, + requestKindOptions: IRequestKindOptions | undefined, ): Promise<{ result: ChatResults | ChatRequestFailed | ChatRequestCanceled; fetcher?: FetcherId; bytesReceived?: number; statusCode?: number }> { // Generate unique ID to link input and output messages const modelCallId = generateUuid(); @@ -1357,7 +1368,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { telemetryProperties?: TelemetryProperties, useFetcher?: FetcherId, canRetryOnce?: boolean, - requestKindOptions?: IBackgroundRequestOptions | ISubagentRequestOptions, + requestKindOptions?: IRequestKindOptions, ): Promise { // If request contains an image, we include this header. @@ -1728,7 +1739,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher { baseTelemetry: TelemetryData, chatEndpointInfo: IChatEndpoint, userInitiatedRequest: boolean | undefined, - requestKindOptions: IBackgroundRequestOptions | ISubagentRequestOptions | undefined, + requestKindOptions: IRequestKindOptions | undefined, transport: string, fetcher: FetcherId | undefined, bytesReceived: number | undefined, diff --git a/extensions/copilot/src/extension/prompt/node/chatMLFetcherTelemetry.ts b/extensions/copilot/src/extension/prompt/node/chatMLFetcherTelemetry.ts index 502a43d09c9..8d21aaca0c5 100644 --- a/extensions/copilot/src/extension/prompt/node/chatMLFetcherTelemetry.ts +++ b/extensions/copilot/src/extension/prompt/node/chatMLFetcherTelemetry.ts @@ -6,7 +6,7 @@ import { ChatFetchError } from '../../../platform/chat/common/commonTypes'; import { isAutoModel } from '../../../platform/endpoint/node/autoChatEndpoint'; import { FetcherId } from '../../../platform/networking/common/fetcherService'; -import { IBackgroundRequestOptions, IChatEndpoint, IChatRequestTelemetryProperties, IEndpointBody, ISubagentRequestOptions } from '../../../platform/networking/common/networking'; +import { IChatEndpoint, IChatRequestTelemetryProperties, IEndpointBody, IRequestKindOptions } from '../../../platform/networking/common/networking'; import { ChatCompletion } from '../../../platform/networking/common/openai'; import { ITelemetryService } from '../../../platform/telemetry/common/telemetry'; import { TelemetryData } from '../../../platform/telemetry/common/telemetryData'; @@ -16,7 +16,7 @@ export interface IChatMLFetcherSuccessfulData { chatCompletion: ChatCompletion; baseTelemetry: TelemetryData; userInitiatedRequest: boolean | undefined; - requestKindOptions: IBackgroundRequestOptions | ISubagentRequestOptions | undefined; + requestKindOptions: IRequestKindOptions | undefined; chatEndpointInfo: IChatEndpoint | undefined; requestBody: IEndpointBody; maxResponseTokens: number; @@ -37,8 +37,10 @@ export interface IChatMLFetcherCancellationProperties { model: string; apiType: string | undefined; transport: string; - requestKindOptions: IBackgroundRequestOptions | ISubagentRequestOptions | undefined; + requestKindOptions: IRequestKindOptions | undefined; + conversationId?: string; associatedRequestId?: string; + parentRequestId?: string; retryAfterError?: string; retryAfterErrorGitHubRequestId?: string; connectivityTestError?: string; @@ -73,7 +75,7 @@ export interface IChatMLFetcherErrorData { timeToFirstToken: number; isVisionRequest: boolean; transport: string; - requestKindOptions: IBackgroundRequestOptions | ISubagentRequestOptions | undefined; + requestKindOptions: IRequestKindOptions | undefined; fetcher: FetcherId | undefined; bytesReceived: number | undefined; issuedTime: number; @@ -113,7 +115,7 @@ export class ChatMLFetcherTelemetrySender { "filterReason": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Reason for why a response was filtered" }, "source": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Source of the initial request" }, "initiatorType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Whether the request was initiated by a user or an agent" }, - "requestKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The kind of request: 'background', 'subagent', or 'normal'" }, + "requestKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The kind of request: 'background', 'subagent', or 'mainagent'" }, "model": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Model selection for the response" }, "modelInvoked": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Actual model invoked for the response" }, "apiType": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "API type for the response- chat completions or responses" }, @@ -121,6 +123,7 @@ export class ChatMLFetcherTelemetrySender { "requestId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Id of the current turn request" }, "gitHubRequestId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "GitHub request id if available" }, "associatedRequestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Another request ID that this request is associated with (eg, the originating request of a summarization request)." }, + "parentRequestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "For a subagent: the request id of the main agent request that invoked this subagent." }, "reasoningEffort": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Reasoning effort level" }, "reasoningSummary": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Reasoning summary level" }, "fetcher": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The fetcher used for the request" }, @@ -157,7 +160,7 @@ export class ChatMLFetcherTelemetrySender { filterReason: chatCompletion.filterReason, source: baseTelemetry?.properties.messageSource ?? 'unknown', initiatorType: userInitiatedRequest ? 'user' : 'agent', - requestKind: requestKindOptions?.kind ?? 'normal', + requestKind: requestKindOptions?.kind ?? 'mainagent', conversationId: baseTelemetry?.properties.conversationId, model: chatEndpointInfo?.model, modelInvoked: chatCompletion.model, @@ -165,6 +168,7 @@ export class ChatMLFetcherTelemetrySender { requestId: chatCompletion.requestId.headerRequestId, gitHubRequestId: chatCompletion.requestId.gitHubRequestId, associatedRequestId: baseTelemetry?.properties.associatedRequestId, + parentRequestId: baseTelemetry?.properties.parentRequestId, reasoningEffort: requestBody.reasoning?.effort ?? requestBody.output_config?.effort, reasoningSummary: requestBody.reasoning?.summary, ...(fetcher ? { fetcher } : {}), @@ -207,7 +211,9 @@ export class ChatMLFetcherTelemetrySender { apiType, transport, requestKindOptions, + conversationId, associatedRequestId, + parentRequestId, retryAfterError, retryAfterErrorGitHubRequestId, connectivityTestError, @@ -238,9 +244,11 @@ export class ChatMLFetcherTelemetrySender { "model": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Model selection for the response" }, "apiType": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "API type for the response- chat completions or responses" }, "source": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Source for why the request was made" }, - "requestKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The kind of request: 'background', 'subagent', or 'normal'" }, + "requestKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The kind of request: 'background', 'subagent', or 'mainagent'" }, + "conversationId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Id for the current chat conversation." }, "requestId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Id of the request" }, "associatedRequestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Another request ID that this request is associated with (eg, the originating request of a summarization request)." }, + "parentRequestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "For a subagent: the request id of the main agent request that invoked this subagent." }, "fetcher": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The fetcher used for the request" }, "transport": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The transport used for the request (http or websocket)" }, "totalTokenMax": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Maximum total token window", "isMeasurement": true }, @@ -269,8 +277,10 @@ export class ChatMLFetcherTelemetrySender { source, requestId, model, - requestKind: requestKindOptions?.kind ?? 'normal', + requestKind: requestKindOptions?.kind ?? 'mainagent', + conversationId, associatedRequestId, + parentRequestId, ...(fetcher ? { fetcher } : {}), transport, ...(retryAfterError ? { retryAfterError } : {}), @@ -326,10 +336,12 @@ export class ChatMLFetcherTelemetrySender { "model": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Model selection for the response" }, "apiType": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "API type for the response- chat completions or responses" }, "source": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Source for why the request was made" }, - "requestKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The kind of request: 'background', 'subagent', or 'normal'" }, + "requestKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The kind of request: 'background', 'subagent', or 'mainagent'" }, + "conversationId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Id for the current chat conversation." }, "requestId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Id of the request" }, "gitHubRequestId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "GitHub request id if available" }, "associatedRequestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Another request ID that this request is associated with (eg, the originating request of a summarization request)." }, + "parentRequestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "For a subagent: the request id of the main agent request that invoked this subagent." }, "reasoningEffort": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Reasoning effort level" }, "reasoningSummary": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Reasoning summary level" }, "fetcher": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "The fetcher used for the request" }, @@ -359,7 +371,8 @@ export class ChatMLFetcherTelemetrySender { type: processed.type, reason: processed.reasonDetail || processed.reason, source: telemetryProperties?.messageSource ?? 'unknown', - requestKind: requestKindOptions?.kind ?? 'normal', + requestKind: requestKindOptions?.kind ?? 'mainagent', + conversationId: telemetryProperties?.conversationId, requestId: processed.requestId, gitHubRequestId: processed.serverRequestId, model: chatEndpointInfo.model, @@ -369,6 +382,7 @@ export class ChatMLFetcherTelemetrySender { ...(fetcher ? { fetcher } : {}), transport, associatedRequestId: telemetryProperties?.associatedRequestId, + parentRequestId: telemetryProperties?.parentRequestId, ...(telemetryProperties?.retryAfterError ? { retryAfterError: telemetryProperties.retryAfterError } : {}), ...(telemetryProperties?.retryAfterErrorGitHubRequestId ? { retryAfterErrorGitHubRequestId: telemetryProperties.retryAfterErrorGitHubRequestId } : {}), ...(telemetryProperties?.connectivityTestError ? { connectivityTestError: telemetryProperties.connectivityTestError } : {}), diff --git a/extensions/copilot/src/extension/prompt/node/defaultIntentRequestHandler.ts b/extensions/copilot/src/extension/prompt/node/defaultIntentRequestHandler.ts index 8bb998886c1..68ae6c2be42 100644 --- a/extensions/copilot/src/extension/prompt/node/defaultIntentRequestHandler.ts +++ b/extensions/copilot/src/extension/prompt/node/defaultIntentRequestHandler.ts @@ -732,7 +732,7 @@ class DefaultToolCallingLoop extends ToolCallingLoop { }, requestKindOptions: this.options.request.subAgentInvocationId ? { kind: 'subagent' } - : undefined, + : { kind: 'mainagent' }, enableRetryOnFilter: true }, token); } diff --git a/extensions/copilot/src/platform/networking/common/networking.ts b/extensions/copilot/src/platform/networking/common/networking.ts index 22368c55f68..0c830c17847 100644 --- a/extensions/copilot/src/platform/networking/common/networking.ts +++ b/extensions/copilot/src/platform/networking/common/networking.ts @@ -211,7 +211,7 @@ export interface IMakeChatRequestOptions { * Options for the kind of request being made (e.g. subagent). Controls the X-Interaction-Type header. * See notes on each interface. */ - requestKindOptions?: IBackgroundRequestOptions | ISubagentRequestOptions; + requestKindOptions?: IRequestKindOptions; } export type IChatRequestTelemetryProperties = { @@ -351,7 +351,7 @@ export interface INetworkRequestOptions { readonly useFetcher?: FetcherId; readonly canRetryOnce?: boolean; readonly location?: ChatLocation; - readonly requestKindOptions?: IBackgroundRequestOptions | ISubagentRequestOptions; + readonly requestKindOptions?: IRequestKindOptions; } /** @@ -368,6 +368,17 @@ export interface ISubagentRequestOptions { readonly kind: 'subagent'; } +/** + * A normal request is a primary user-initiated chat turn (e.g. driven by the + * default intent request handler or inline chat). Callers explicitly opt in to + * 'mainagent' so the chat ML fetcher can default unmarked requests to 'background'. + */ +export interface IMainAgentRequestOptions { + readonly kind: 'mainagent'; +} + +export type IRequestKindOptions = IBackgroundRequestOptions | ISubagentRequestOptions | IMainAgentRequestOptions; + function networkRequest( accessor: ServicesAccessor, options: INetworkRequestOptions,