Default to background; mark mainagent; emit conversationId+parentRequestId on response.cancelled/error

Inverts requestKind defaulting in chatMLFetcher.fetchMany so that any
request reaching the fetcher without an explicit requestKindOptions is
classified as 'background'. Adds 'mainagent' as the explicit opt-in for
primary user-driven turns. Now:

- defaultIntentRequestHandler passes { kind: 'subagent' } for subagents
  and { kind: 'mainagent' } for normal user turns.
- inlineChatIntent (both chat2 and inline edit paths) passes
  { kind: 'mainagent' }.
- All other callers (title gen, summarization, intent detection, commit
  message gen, terminal fix gen, PR title/desc gen, rename suggestions,
  semantic search, virtualToolGrouper, etc.) automatically get
  'background' without per-call edits.

Also adds parentRequestId and conversationId to response.success,
response.cancelled, and response.error events so subagent requests can
be joined back to the main agent request, matching the
panel.request schema.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kevin Kent
2026-04-29 10:32:07 -07:00
co-authored by Copilot
parent 11bbc7a9c6
commit fc86fd72ba
5 changed files with 58 additions and 20 deletions
@@ -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
@@ -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<ChatResponses> {
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<number>,
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<Response> {
// 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,
@@ -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 } : {}),
@@ -732,7 +732,7 @@ class DefaultToolCallingLoop extends ToolCallingLoop<IDefaultToolLoopOptions> {
},
requestKindOptions: this.options.request.subAgentInvocationId
? { kind: 'subagent' }
: undefined,
: { kind: 'mainagent' },
enableRetryOnFilter: true
}, token);
}
@@ -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,