Skip to content
Draft
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
6 changes: 5 additions & 1 deletion src/vs/platform/agentHost/node/copilot/copilotAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { CopilotClient, RuntimeConnection, type CopilotClientOptions } from '@github/copilot-sdk';
import { CopilotClient, RuntimeConnection, type CopilotClientOptions, type GitHubTelemetryNotification } from '@github/copilot-sdk';
import * as fs from 'fs/promises';
import * as os from 'os';
import { CancelablePromise, createCancelablePromise, Delayer, disposableTimeout, Limiter, SequencerByKey } from '../../../../base/common/async.js';
Expand Down Expand Up @@ -60,6 +60,7 @@ import { COPILOT_BRANCH_PREFIX, ICopilotBranchNameGenerator } from './copilotBra
import { CopilotAgentSession, type CopilotSdkMode } from './copilotAgentSession.js';
import { ICopilotSessionContext, projectFromCopilotContext } from './copilotGitProject.js';
import { parsedPluginsEqual, toChildCustomizations } from './copilotPluginConverters.js';
import { CopilotGitHubTelemetryForwarder } from './copilotGitHubTelemetryForwarder.js';
import { CopilotSessionLauncher, ContextSizeConfigKey, ThinkingLevelConfigKey, getCopilotContextTier, resolveCopilotReasoningEffort, type CopilotSessionLaunchPlan, type IActiveClientSnapshot } from './copilotSessionLauncher.js';
import { ShellManager } from './copilotShellTools.js';
import { isAgentHostTelemetryService } from '../agentHostTelemetryService.js';
Expand Down Expand Up @@ -470,6 +471,7 @@ export class CopilotAgent extends Disposable implements IAgent {
private _shutdownPromise: Promise<void> | undefined;
private readonly _plugins: PluginController;
private readonly _sessionLauncher: CopilotSessionLauncher;
private readonly _gitHubTelemetryForwarder: CopilotGitHubTelemetryForwarder;
readonly onDidCustomizationsChange: Event<void>;
/** Per-session active client state for tools + plugin snapshot tracking. */
private readonly _activeClients = new ResourceMap<ActiveClient>();
Expand All @@ -495,6 +497,7 @@ export class CopilotAgent extends Disposable implements IAgent {
super();
this._plugins = this._register(this._instantiationService.createInstance(PluginController));
this._sessionLauncher = this._instantiationService.createInstance(CopilotSessionLauncher);
this._gitHubTelemetryForwarder = this._instantiationService.createInstance(CopilotGitHubTelemetryForwarder, () => this._restrictedTelemetryEnabled);
this.onDidCustomizationsChange = this._plugins.onDidChange;
// Mirror the sub-agent fan-out signals onto the first-class spawned-
// chat channel so the orchestrator manages sub-agent chats
Expand Down Expand Up @@ -1003,6 +1006,7 @@ export class CopilotAgent extends Disposable implements IAgent {
telemetry,
logLevel: copilotCliLogLevelFor(this._logService.getLevel()),
enableRemoteSessions: this._isSessionSyncEnabled(),
onGitHubTelemetry: (notification: GitHubTelemetryNotification) => this._gitHubTelemetryForwarder.forward(notification),
};
const client = this._createCopilotClient(clientOptions);
await client.start();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { GitHubTelemetryNotification } from '@github/copilot-sdk';
import { ITelemetryData, ITelemetryService } from '../../../telemetry/common/telemetry.js';

/**
* Re-emits GitHub-shaped telemetry events forwarded by the Copilot CLI runtime
* (via the SDK's `onGitHubTelemetry` connection-global callback) through VS
* Code's {@link ITelemetryService} so they land in the same first-party
* Microsoft cluster/database as the rest of the agent host's telemetry.
*
* Restricted events (`cli.restricted_telemetry`) are only forwarded when
* restricted telemetry is enabled for the current Copilot token; standard
* events always flow through.
*/
export class CopilotGitHubTelemetryForwarder {

constructor(
private readonly _isRestrictedTelemetryEnabled: () => boolean,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
) { }

forward(notification: GitHubTelemetryNotification): void {
// The runtime marks enhanced/restricted events which must only be routed
// to first-party Microsoft stores and only when the user has opted in.
if (notification.restricted && !this._isRestrictedTelemetryEnabled()) {
return;
}
Comment on lines +26 to +31

const event = notification.event;

// The event's own `properties` (strings) and `metrics` (numbers) carry the
// payload; the telemetry appender routes them to properties/measurements by
// value type. Contextual metadata is added under distinct keys.
const data: ITelemetryData = {
...event.properties,
...event.metrics,
githubSessionId: notification.sessionId,
restricted: notification.restricted,
kind: event.kind,
createdAt: event.created_at,
modelCallId: event.model_call_id,
expAssignmentContext: event.exp_assignment_context,
eventSessionId: event.session_id,
copilotTrackingId: event.copilot_tracking_id,
};

const client = event.client;
if (client) {
data.cliVersion = client.cli_version;
data.osPlatform = client.os_platform;
data.osVersion = client.os_version;
data.osArch = client.os_arch;
data.nodeVersion = client.node_version;
data.copilotPlan = client.copilot_plan;
data.clientType = client.client_type;
data.clientName = client.client_name;
data.isStaff = client.is_staff;
data.devDeviceId = client.dev_device_id;
}

if (event.features) {
for (const [key, value] of Object.entries(event.features)) {
if (value !== undefined) {
data[`feature.${key}`] = value;
}
}
}

this._telemetryService.publicLog(`copilotCli/${event.kind}`, data);
}
}
Loading