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
22 changes: 22 additions & 0 deletions src/vs/platform/agentHost/browser/remoteAgentHostProtocolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { URI } from '../../../base/common/uri.js';
import { generateUuid } from '../../../base/common/uuid.js';
import { ILogService } from '../../log/common/log.js';
import { FileSystemProviderErrorCode, IFileService, toFileSystemProviderErrorCode } from '../../files/common/files.js';
import { IConfigurationService } from '../../configuration/common/configuration.js';
import { AgentSession, IAgentConnection, IAgentCreateSessionConfig, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult } from '../common/agentService.js';
import { AgentSubscriptionManager, type IAgentSubscription } from '../common/state/agentSubscription.js';
import { agentHostAuthority, fromAgentHostUri, toAgentHostUri } from '../common/agentHostUri.js';
Expand All @@ -32,6 +33,9 @@ import { AhpErrorCodes } from '../common/state/protocol/errors.js';
import { ContentEncoding, ResourceRequestParams, type CompletionsParams, type CompletionsResult, type CreateTerminalParams, type ResolveSessionConfigResult, type SessionConfigCompletionsResult } from '../common/state/protocol/commands.js';
import { decodeBase64, encodeBase64, VSBuffer } from '../../../base/common/buffer.js';
import { ILoadEstimator, LoadEstimator } from '../../../base/parts/ipc/common/ipc.net.js';
import { TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from '../../telemetry/common/telemetry.js';
import { getTelemetryLevel } from '../../telemetry/common/telemetryUtils.js';
import { AgentHostTelemetryLevelConfigKey, telemetryLevelToAgentHostConfigValue } from '../common/agentHostSchema.js';

const AHP_CLIENT_CONNECTION_CLOSED = -32000;

Expand Down Expand Up @@ -248,6 +252,7 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
@ILogService private readonly _logService: ILogService,
@IFileService private readonly _fileService: IFileService,
@IAgentHostPermissionService private readonly _permissionService: IAgentHostPermissionService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) {
super();
this._address = address;
Expand Down Expand Up @@ -275,6 +280,15 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
this._subscriptionManager.receiveEnvelope(envelope);
}));

this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TELEMETRY_SETTING_ID) || e.affectsConfiguration(TELEMETRY_OLD_SETTING_ID) || e.affectsConfiguration(TELEMETRY_CRASH_REPORTER_SETTING_ID)) {
if (this._state.kind !== AgentHostClientState.Connected) {
return;
}
this._updateTelemetryLevel();
}
}));

// Detect silently-dead transports — see {@link _resetLivenessTimers}.
this._resetLivenessTimers();
}
Expand Down Expand Up @@ -357,6 +371,7 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
}

this._completionTriggerCharacters = result.completionTriggerCharacters ?? [];
this._updateTelemetryLevel();
this._transitionTo({ kind: AgentHostClientState.Connected });
}

Expand Down Expand Up @@ -1119,6 +1134,13 @@ export class RemoteAgentHostProtocolClient extends Disposable implements IAgentC
return this._dispatchRequest<IRemoteAgentHostExtensionCommandMap[M]['result']>(method, params);
}

private _updateTelemetryLevel(): void {
this.dispatchAction({
type: ActionType.RootConfigChanged,
config: { [AgentHostTelemetryLevelConfigKey]: telemetryLevelToAgentHostConfigValue(getTelemetryLevel(this._configurationService)) },
}, this._clientId, 0);
}

/**
* Common path for outgoing JSON-RPC requests: gate on any in-flight
* reconnect (unless explicitly bypassed for the `reconnect` RPC itself),
Expand Down
38 changes: 38 additions & 0 deletions src/vs/platform/agentHost/common/agentHostSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { localize } from '../../../nls.js';
import { TelemetryConfiguration, TelemetryLevel } from '../../telemetry/common/telemetry.js';
import { SessionConfigKey } from './sessionConfigKeys.js';
import type { SessionConfigPropertySchema, SessionConfigSchema } from './state/protocol/commands.js';
import { JsonRpcErrorCodes, ProtocolError } from './state/sessionProtocol.js';
Expand Down Expand Up @@ -349,6 +350,43 @@ export const platformSessionSchema = createSchema({
* auto-approval. See `SessionPermissionManager` for the evaluation
* rules.
*/
export const AgentHostTelemetryLevelConfigKey = 'telemetryLevel';

export function telemetryLevelToAgentHostConfigValue(telemetryLevel: TelemetryLevel): TelemetryConfiguration {
switch (telemetryLevel) {
case TelemetryLevel.NONE:
return TelemetryConfiguration.OFF;
case TelemetryLevel.CRASH:
return TelemetryConfiguration.CRASH;
case TelemetryLevel.ERROR:
return TelemetryConfiguration.ERROR;
case TelemetryLevel.USAGE:
return TelemetryConfiguration.ON;
}
}

export function agentHostConfigValueToTelemetryLevel(value: unknown): TelemetryLevel | undefined {
switch (value) {
case TelemetryConfiguration.OFF:
return TelemetryLevel.NONE;
case TelemetryConfiguration.CRASH:
return TelemetryLevel.CRASH;
case TelemetryConfiguration.ERROR:
return TelemetryLevel.ERROR;
case TelemetryConfiguration.ON:
return TelemetryLevel.USAGE;
default:
return undefined;
}
}

export const platformRootSchema = createSchema({
[SessionConfigKey.Permissions]: permissionsProperty,
[AgentHostTelemetryLevelConfigKey]: schemaProperty<TelemetryConfiguration>({
type: 'string',
title: localize('agentHost.config.telemetryLevel.title', "Telemetry Level"),
description: localize('agentHost.config.telemetryLevel.description', "Most restrictive telemetry level requested by connected clients."),
enum: [TelemetryConfiguration.ON, TelemetryConfiguration.ERROR, TelemetryConfiguration.CRASH, TelemetryConfiguration.OFF],
default: TelemetryConfiguration.ON,
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ import { AhpJsonlLogger, getAhpLogByteLength } from '../common/ahpJsonlLogger.js
import { wrapAgentServiceWithAhpLogging } from './localAhpJsonlLogging.js';
import { AgentSubscriptionManager, type IAgentSubscription } from '../common/state/agentSubscription.js';
import type { CompletionsParams, CompletionsResult, CreateTerminalParams, ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../common/state/protocol/commands.js';
import type { ActionEnvelope, INotification, IRootConfigChangedAction, SessionAction, TerminalAction } from '../common/state/sessionActions.js';
import { ActionType, type ActionEnvelope, type INotification, type IRootConfigChangedAction, type SessionAction, type TerminalAction } from '../common/state/sessionActions.js';
import type { ResourceCopyParams, ResourceCopyResult, ResourceDeleteParams, ResourceDeleteResult, ResourceListResult, ResourceMoveParams, ResourceMoveResult, ResourceReadResult, ResourceWriteParams, ResourceWriteResult, IStateSnapshot } from '../common/state/sessionProtocol.js';
import { StateComponents, ROOT_STATE_URI, type RootState } from '../common/state/sessionState.js';
import { revive } from '../../../base/common/marshalling.js';
import { URI } from '../../../base/common/uri.js';
import { IFileService } from '../../files/common/files.js';
import { AGENT_HOST_CLIENT_RESOURCE_CHANNEL, AgentHostClientResourceChannel } from '../common/agentHostClientResourceChannel.js';
import { TELEMETRY_CRASH_REPORTER_SETTING_ID, TELEMETRY_OLD_SETTING_ID, TELEMETRY_SETTING_ID } from '../../telemetry/common/telemetry.js';
import { getTelemetryLevel } from '../../telemetry/common/telemetryUtils.js';
import { AgentHostTelemetryLevelConfigKey, telemetryLevelToAgentHostConfigValue } from '../common/agentHostSchema.js';

/**
* Renderer-side implementation of {@link IAgentHostService} that connects
Expand Down Expand Up @@ -76,7 +79,7 @@ export class LocalAgentHostServiceClient extends Disposable implements IAgentHos

constructor(
@ILogService private readonly _logService: ILogService,
@IConfigurationService configurationService: IConfigurationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IFileService private readonly _fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IInstantiationService instantiationService: IInstantiationService,
Expand All @@ -92,7 +95,7 @@ export class LocalAgentHostServiceClient extends Disposable implements IAgentHos
// Optionally wrap the proxy with a logging layer that synthesizes JSON-RPC
// frames for every request/response/notification on the in-process MessagePort
// channel, mirroring the AHP transport JSONL logs produced by remote agent hosts.
this._ahpLogger = configurationService.getValue<boolean>(AgentHostAhpJsonlLoggingSettingId)
this._ahpLogger = this._configurationService.getValue<boolean>(AgentHostAhpJsonlLoggingSettingId)
? this._register(instantiationService.createInstance(AhpJsonlLogger, {
logsHome: environmentService.logsHome,
connectionId: this.clientId,
Expand All @@ -113,7 +116,13 @@ export class LocalAgentHostServiceClient extends Disposable implements IAgentHos
resource => this.unsubscribe(resource),
));

if (configurationService.getValue<boolean>(AgentHostEnabledSettingId)) {
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TELEMETRY_SETTING_ID) || e.affectsConfiguration(TELEMETRY_OLD_SETTING_ID) || e.affectsConfiguration(TELEMETRY_CRASH_REPORTER_SETTING_ID)) {
this._updateTelemetryLevel();
}
}));

if (this._configurationService.getValue<boolean>(AgentHostEnabledSettingId)) {
this._connect();
}
}
Expand All @@ -133,6 +142,7 @@ export class LocalAgentHostServiceClient extends Disposable implements IAgentHos
// AgentHostClientFileSystemProvider that calls back through this channel.
client.registerChannel(AGENT_HOST_CLIENT_RESOURCE_CHANNEL, new AgentHostClientResourceChannel(this._fileService, this._ahpLogger));
this._clientEventually.complete(client);
this._updateTelemetryLevel();

store.add(this._proxy.onDidAction(e => {
const revived = revive(e) as ActionEnvelope;
Expand Down Expand Up @@ -161,6 +171,13 @@ export class LocalAgentHostServiceClient extends Disposable implements IAgentHos
});
}

private _updateTelemetryLevel(): void {
this.dispatchAction({
type: ActionType.RootConfigChanged,
config: { [AgentHostTelemetryLevelConfigKey]: telemetryLevelToAgentHostConfigValue(getTelemetryLevel(this._configurationService)) },
}, this.clientId, 0);
}

// ---- IAgentService forwarding (no await needed, delayed channel handles queuing) ----

authenticate(params: AuthenticateParams): Promise<AuthenticateResult> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,20 @@ export class ElectronAgentHostStarter extends Disposable implements IAgentHostSt
dbSpanExporterEnabled: this._configurationService.getValue<boolean>(AgentHostOTelDbSpanExporterEnabledSettingId),
}, process.env);

const args = [
'--logsPath', this._environmentMainService.logsHome.with({ scheme: Schemas.file }).fsPath,
'--user-data-dir', this._environmentMainService.userDataPath,
];
if (this._environmentMainService.disableTelemetry) {
args.push('--disable-telemetry');
}

this.utilityProcess.start({
type: 'agentHost',
name: 'agent-host',
entryPoint: 'vs/platform/agentHost/node/agentHostMain',
execArgv,
args: [
'--logsPath', this._environmentMainService.logsHome.with({ scheme: Schemas.file }).fsPath,
'--user-data-dir', this._environmentMainService.userDataPath,
],
args,
env: {
...deepClone(process.env),
...shellEnv,
Expand Down
13 changes: 10 additions & 3 deletions src/vs/platform/agentHost/node/agentHostMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,20 @@ import { AgentPluginManager } from './agentPluginManager.js';
import { AgentHostGitService, IAgentHostGitService } from './agentHostGitService.js';
import { registerPendingEditContentProvider } from './copilot/pendingEditContentStore.js';
import { join } from '../../../base/common/path.js';
import { createAgentHostTelemetryService } from './agentHostTelemetryService.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';

// Entry point for the agent host utility process.
// Sets up IPC, logging, and registers agent providers (Copilot).
// When VSCODE_AGENT_HOST_PORT or VSCODE_AGENT_HOST_SOCKET_PATH env vars
// are set, also starts a WebSocket server for external clients.

startAgentHost();
void startAgentHost().catch(err => {
console.error(err);
process.exit(1);
});

function startAgentHost(): void {
async function startAgentHost(): Promise<void> {
// Setup RPC - supports both Electron utility process and Node child process
let server: ChildProcessServer<string> | UtilityProcessServer;
if (isUtilityProcess(process)) {
Expand Down Expand Up @@ -100,6 +105,7 @@ function startAgentHost(): void {
// Session data service
const sessionDataService = new SessionDataService(URI.file(environmentService.userDataPath), fileService, logService);
const rootConfigResource = joinPath(environmentService.appSettingsHome, 'globalStorage', 'agent-host-config.json');
const telemetryService = await createAgentHostTelemetryService({ environmentService, productService, fileService, loggerService, logService, disposables });

// Create the real service implementation that lives in this process
let agentService: AgentService;
Expand All @@ -113,6 +119,7 @@ function startAgentHost(): void {
diServices.set(IFileService, fileService);
diServices.set(ISessionDataService, sessionDataService);
diServices.set(IProductService, productService);
diServices.set(ITelemetryService, telemetryService);
instantiationService = new InstantiationService(diServices);
const gitService = instantiationService.createInstance(AgentHostGitService);
diServices.set(IAgentHostGitService, gitService);
Expand All @@ -124,7 +131,7 @@ function startAgentHost(): void {
diServices.set(IClaudeAgentSdkService, claudeAgentSdkService);
const agentHostOTelService = disposables.add(instantiationService.createInstance(AgentHostOTelService));
diServices.set(IAgentHostOTelService, agentHostOTelService);
agentService = new AgentService(logService, fileService, sessionDataService, productService, gitService, rootConfigResource);
agentService = new AgentService(logService, fileService, sessionDataService, productService, gitService, rootConfigResource, telemetryService);
const pluginManager = new AgentPluginManager(URI.file(environmentService.userDataPath), fileService, logService);
diServices.set(IAgentPluginManager, pluginManager);
const diffComputeService = disposables.add(new NodeWorkerDiffComputeService(logService));
Expand Down
6 changes: 5 additions & 1 deletion src/vs/platform/agentHost/node/agentHostServerMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import { AgentPluginManager } from './agentPluginManager.js';
import { IAgentPluginManager } from '../common/agentPluginManager.js';
import { registerPendingEditContentProvider } from './copilot/pendingEditContentStore.js';
import { AgentHostGitService, IAgentHostGitService } from './agentHostGitService.js';
import { createAgentHostTelemetryService } from './agentHostTelemetryService.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';

/** Log to stderr so messages appear in the terminal alongside the process. */
function log(msg: string): void {
Expand Down Expand Up @@ -186,17 +188,19 @@ async function main(): Promise<void> {
// `createInstance` (it needs IFileService + INativeEnvironmentService).
// The git service is shared by AgentService (for diff computation +
// showBlob) and the production agent registration path.
const telemetryService = await createAgentHostTelemetryService({ environmentService, productService, fileService, loggerService, logService, disposables, disableTelemetry: options.quiet });
const diServices = new ServiceCollection();
diServices.set(IProductService, productService);
diServices.set(INativeEnvironmentService, environmentService);
diServices.set(ILogService, logService);
diServices.set(IFileService, fileService);
diServices.set(ISessionDataService, sessionDataService);
diServices.set(ITelemetryService, telemetryService);
const instantiationService = new InstantiationService(diServices);
const gitService = instantiationService.createInstance(AgentHostGitService);

// Create the agent service (owns AgentHostStateManager + AgentSideEffects internally)
const agentService = new AgentService(logService, fileService, sessionDataService, productService, gitService, rootConfigResource);
const agentService = new AgentService(logService, fileService, sessionDataService, productService, gitService, rootConfigResource, telemetryService);
disposables.add(agentService);

// Register agents
Expand Down
4 changes: 3 additions & 1 deletion src/vs/platform/agentHost/node/agentHostStateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { Emitter, Event } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { equals } from '../../../base/common/objects.js';
import { ILogService } from '../../log/common/log.js';
import { TelemetryLevel } from '../../telemetry/common/telemetry.js';
import { ActionType, NotificationType, ActionEnvelope, ActionOrigin, INotification, IRootConfigChangedAction, SessionAction, RootAction, StateAction, TerminalAction, isRootAction, isSessionAction } from '../common/state/sessionActions.js';
import type { IStateSnapshot } from '../common/state/sessionProtocol.js';
import { rootReducer, sessionReducer } from '../common/state/sessionReducers.js';
import { createRootState, createSessionState, SessionLifecycle, type RootState, type SessionMeta, type SessionState, type SessionSummary, type Turn, type URI, ROOT_STATE_URI } from '../common/state/sessionState.js';
import { IPermissionsValue, platformRootSchema } from '../common/agentHostSchema.js';
import { AgentHostTelemetryLevelConfigKey, IPermissionsValue, platformRootSchema, telemetryLevelToAgentHostConfigValue } from '../common/agentHostSchema.js';
import { SessionConfigKey } from '../common/sessionConfigKeys.js';

/**
Expand Down Expand Up @@ -67,6 +68,7 @@ export class AgentHostStateManager extends Disposable {
schema: platformRootSchema.toProtocol(),
values: platformRootSchema.validateOrDefault({}, {
[SessionConfigKey.Permissions]: { allow: [], deny: [] } satisfies IPermissionsValue,
[AgentHostTelemetryLevelConfigKey]: telemetryLevelToAgentHostConfigValue(TelemetryLevel.USAGE),
}),
},
};
Expand Down
Loading
Loading