Skip to content
7 changes: 5 additions & 2 deletions src/extension/prompt/node/chatMLFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher {

const baseTelemetry = TelemetryData.createAndMarkAsIssued({
...telemetryProperties,
headerRequestId: ourRequestId,
baseModel: chatEndpoint.model,
uiKind: ChatLocation.toString(location)
});
Expand Down Expand Up @@ -1077,6 +1078,8 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher {
});

const modelRequestId = getRequestId(connection.responseHeaders);
// Preserve ourRequestId as headerRequestId if the server didn't echo x-request-id
modelRequestId.headerRequestId = modelRequestId.headerRequestId || ourRequestId;
telemetryData.extendWithRequestId(modelRequestId);

for (const [key, value] of Object.entries(request)) {
Expand All @@ -1085,8 +1088,6 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher {
} // Skip messages (PII)
telemetryData.properties[`request.option.${key}`] = JSON.stringify(value) ?? 'undefined';
}

telemetryData.properties['headerRequestId'] = ourRequestId;
this._telemetryService.sendGHTelemetryEvent('request.sent', telemetryData.properties, telemetryData.measurements);

const requestStart = Date.now();
Expand Down Expand Up @@ -1387,6 +1388,8 @@ export class ChatMLFetcherImpl extends AbstractChatMLFetcher {
// This ID is hopefully the one the same as ourRequestId, but it is not guaranteed.
// If they are different then we will override the original one we set in telemetryData above.
const modelRequestId = getRequestId(response.headers);
// Preserve ourRequestId as headerRequestId if the server didn't echo x-request-id
modelRequestId.headerRequestId = modelRequestId.headerRequestId || ourRequestId;
telemetryData.extendWithRequestId(modelRequestId);

// TODO: Add response length (requires parsing)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export interface IExecutionSubagentToolCallingLoopOptions extends IToolCallingLo
promptText: string;
/** Optional pre-generated subagent invocation ID. If not provided, a new UUID will be generated. */
subAgentInvocationId?: string;
/** The tool_call_id from the parent agent's LLM response that triggered this subagent invocation. */
parentToolCallId?: string;
}

export class ExecutionSubagentToolCallingLoop extends ToolCallingLoop<IExecutionSubagentToolCallingLoopOptions> {
Expand Down Expand Up @@ -139,10 +141,12 @@ export class ExecutionSubagentToolCallingLoop extends ToolCallingLoop<IExecution
// This loop is inside a tool called from another request, so never user initiated
userInitiatedRequest: false,
telemetryProperties: {
requestId: this.options.subAgentInvocationId,
messageId: randomUUID(),
messageSource: 'chat.editAgent',
subType: 'subagent/execution',
conversationId: this.options.conversation.sessionId
conversationId: this.options.conversation.sessionId,
parentToolCallId: this.options.parentToolCallId,
},
}, token);
}
Expand Down
6 changes: 5 additions & 1 deletion src/extension/prompt/node/searchSubagentToolCallingLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface ISearchSubagentToolCallingLoopOptions extends IToolCallingLoopO
promptText: string;
/** Optional pre-generated subagent invocation ID. If not provided, a new UUID will be generated. */
subAgentInvocationId?: string;
/** The tool_call_id from the parent agent's LLM response that triggered this subagent invocation. */
parentToolCallId?: string;
}

export class SearchSubagentToolCallingLoop extends ToolCallingLoop<ISearchSubagentToolCallingLoopOptions> {
Expand Down Expand Up @@ -153,10 +155,12 @@ export class SearchSubagentToolCallingLoop extends ToolCallingLoop<ISearchSubage
// This loop is inside a tool called from another request, so never user initiated
userInitiatedRequest: false,
telemetryProperties: {
requestId: this.options.subAgentInvocationId,
messageId: randomUUID(),
messageSource: 'chat.editAgent',
subType: 'subagent/search',
conversationId: this.options.conversation.sessionId
conversationId: this.options.conversation.sessionId,
parentToolCallId: this.options.parentToolCallId,
},
requestKindOptions: { kind: 'subagent' }
}, token);
Expand Down
1 change: 1 addition & 0 deletions src/extension/tools/node/executionSubagentTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class ExecutionSubagentTool implements ICopilotTool<IExecutionSubagentParams> {
location: request.location,
promptText: options.input.query,
subAgentInvocationId: subAgentInvocationId,
parentToolCallId: options.chatStreamToolCallId,
});

const stream = this._inputContext?.stream && ChatResponseStreamImpl.filter(
Expand Down
1 change: 1 addition & 0 deletions src/extension/tools/node/searchSubagentTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class SearchSubagentTool implements ICopilotTool<ISearchSubagentParams> {
location: request.location,
promptText: options.input.query,
subAgentInvocationId: subAgentInvocationId,
parentToolCallId: options.chatStreamToolCallId,
});

const stream = this._inputContext?.stream && ChatResponseStreamImpl.filter(
Expand Down
4 changes: 2 additions & 2 deletions src/extension/tools/vscode-node/toolsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@ export class ToolsService extends BaseToolsService {
} catch { /* swallow serialization errors */ }
}

// For runSubagent tool, store this execute_tool span's trace context so the subagent's
// For subagent tools, store this execute_tool span's trace context so the subagent's
// invoke_agent span can be parented to THIS tool call (not the grandparent invoke_agent).
const chatStreamToolCallId = (options as { chatStreamToolCallId?: string }).chatStreamToolCallId;
const chatRequestId = (options as { chatRequestId?: string }).chatRequestId;
const subAgentInvocationId = (options as { subAgentInvocationId?: string }).subAgentInvocationId;
if (String(name) === 'runSubagent') {
if (subAgentInvocationId) {
const traceCtx = span.getSpanContext();
if (traceCtx) {
if (chatStreamToolCallId) {
Expand Down
2 changes: 2 additions & 0 deletions src/platform/networking/common/networking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ export type IChatRequestTelemetryProperties = {
subType?: string;
/** For a subagent: The request ID of the parent request that invoked this subagent. */
parentRequestId?: string;
/** For a subagent: The tool_call_id from the parent agent's LLM response that triggered this subagent invocation. */
parentToolCallId?: string;
}

export interface ICreateEndpointBodyOptions extends IMakeChatRequestOptions {
Expand Down
2 changes: 2 additions & 0 deletions src/platform/networking/node/chatStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ function sendModelCallTelemetry(telemetryService: ITelemetryService, messageData

// Send one telemetry event per chunk
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
const parentToolCallId = telemetryData.properties.parentToolCallId;
const modelCallData = TelemetryData.createAndMarkAsIssued({
modelCallId,
conversationId, // Trajectory identifier linking main and supplementary calls
Expand All @@ -402,6 +403,7 @@ function sendModelCallTelemetry(telemetryService: ITelemetryService, messageData
...(requestTurn !== undefined && { requestTurn: requestTurn.toString() }), // Add requestTurn only for input calls
...(requestOptionsId && { requestOptionsId }), // Add requestOptionsId for input calls
...(telemetryData.properties.turnIndex && { turnIndex: telemetryData.properties.turnIndex }), // Add turnIndex from original telemetryData
...(parentToolCallId && { parentToolCallId }), // Link subagent calls to parent tool invocation
}, telemetryData.measurements); // Include measurements from original telemetryData

telemetryService.sendInternalMSFTTelemetryEvent(eventName, modelCallData.properties, modelCallData.measurements);
Expand Down
Loading