349
350
constructor(
352
>
private readonly _fileService: IFileService,
353
>
private readonly _sessionDataService: ISessionDataService,
354
>
private readonly _productService: IProductService,
355
>
private readonly _gitService: IAgentHostGitService,
356
>
private readonly _checkpointService: IAgentHostCheckpointService = NULL_CHECKPOINT_SERVICE,
357
>
private readonly _rootConfigResource?: URI,
358
>
private readonly _telemetryService: ITelemetryService = NullTelemetryService,
359
>
_fileMonitorService?: IAgentHostFileMonitorService,
360
>
copilotApiService?: ICopilotApiService,
361
>
fetchFn?: typeof globalThis.fetch,
362
>
providerConfigurations: readonly IAgentCustomizationSettingsRegistration[] = [],
363
>
) {
364
>
super();
365
>
this._logService.info('AgentService initialized');
366
>
this._authService = new AgentHostAuthenticationService(_logService);
367
>
this._stateManager = this._register(new AgentHostStateManager(_logService, {
368
>
hostBuildInfo: hostBuildInfoFromProduct(this._productService),
369
>
changesetStateRetention: {
370
>
// The cache calls this lazily after construction. If a future state-manager
371
>
// initialization path registers changesets before `_changesets` is assigned,
372
>
// keep the entry pinned rather than evicting with incomplete liveness data.
373
>
canEvict: changeset => this._changesets ? this._isChangesetEvictable(changeset) : false,
374
>
},
375
>
}));
376
>
this._register(this._stateManager.onDidEmitEnvelope(e => this._onDidAction.fire(e)));
377
>
this._register(this._stateManager.onDidEmitEnvelope(e => this._trackPendingSubagentChatFromEnvelope(e)));
378
>
this._register(this._stateManager.onDidEmitNotification(e => this._onDidNotification.fire(e)));
379
>
380
>
// Build a local instantiation scope so downstream components can
381
>
// consume {@link IAgentConfigurationService} (and later {@link ILogService})
382
>
// via DI rather than being plumbed plain-class references.
383
>
const configurationService = this._register(new AgentConfigurationService(this._stateManager, this._logService, this._rootConfigResource, providerConfigurations));
384
>
this._configurationService = configurationService;
385
>
const fileMonitorService = _fileMonitorService ?? this._register(new AgentHostFileMonitorService(this._fileService, this._logService));
386
>
updateAgentHostTelemetryLevelFromConfig(this._telemetryService, this._stateManager.rootState.config?.values);
387
>
const services = new ServiceCollection(
388
>
[ILogService, this._logService],
389
>
[IAgentService, this],
390
>
[IProductService, this._productService],
391
>
[IAgentConfigurationService, configurationService],
392
>
[IAgentHostStateManager, this._stateManager],
393
>
[IAgentHostFileMonitorService, fileMonitorService],
394
>
[IAgentHostGitService, this._gitService],
395
>
[ITelemetryService, this._telemetryService],
396
>
// The outer agent-host process DI registers `ISessionDataService`,
397
>
// but this nested strict `InstantiationService` does not inherit it.
398
>
// Add it explicitly so `@ISessionDataService` injection into the
399
>
// changeset service (and any future sibling) resolves correctly.
400
>
[ISessionDataService, this._sessionDataService],
401
>
);
402
>
const instantiationService = this._register(new InstantiationService(services, /*strict*/ true));
403
>
this._gitHubEndpointService = this._register(instantiationService.createInstance(AgentHostGitHubEndpointService));
404
>
services.set(IAgentHostGitHubEndpointService, this._gitHubEndpointService);
405
>
// A GitHub Enterprise URI change repoints every agent's GitHub resource
406
>
// identity to a different authorization server, so the client must obtain a
407
>
// token for the new resource. One root-channel `auth/required` covers all
408
>
// agents (the URI is host-level config).
409
>
this._register(this._gitHubEndpointService.onDidChange(() => {
410
this._stateManager.emitAuthRequired({
411
resource: this._gitHubEndpointService.getCopilotResource().resource,
412
reason: AuthRequiredReason.Required,
413
});
415
>
const agentHostOctoKitService = instantiationService.createInstance(AgentHostOctoKitService, fetchFn);
416
>
services.set(IAgentHostOctoKitService, agentHostOctoKitService);
417
>
const effectiveCopilotApiService = copilotApiService ?? instantiationService.createInstance(CopilotApiService, fetchFn);
418
>
services.set(ICopilotApiService, effectiveCopilotApiService);
419
>
420
>
this._gitStateService = this._register(instantiationService.createInstance(AgentHostGitStateService));
421
>
services.set(IAgentHostGitStateService, this._gitStateService);
422
>
423
>
// The checkpoint service is constructed in the outer agent-host
424
>
// DI scope and passed via {@link _checkpointService}; register it
425
>
// in the inner service collection so the changeset service /
426
>
// side effects can resolve it via DI.
427
>
services.set(IAgentHostCheckpointService, this._checkpointService);
428
>
429
>
// The subscription service manages the lifecycle of changeset subscriptions. The service
430
>
// is also consulted by other services when refreshing changesets and changeset operations.
431
>
this._changesetSubscriptions = instantiationService.createInstance(AgentHostChangesetSubscriptionService);
432
>
services.set(IAgentHostChangesetSubscriptionService, this._changesetSubscriptions);
433
>
434
>
// The operation contribution service manages the lifecycle of changeset operations.
435
>
this._changesetOperationService = this._register(instantiationService.createInstance(AgentHostChangesetOperationService));
436
>
services.set(IAgentHostChangesetOperationService, this._changesetOperationService);
437
>
438
>
// The changes review service is responsible for managing review/unreview state for changeset changes.
439
>
this._reviewService = this._register(instantiationService.createInstance(AgentHostReviewService));
440
>
services.set(IAgentHostReviewService, this._reviewService);
441
>
442
>
// The changeset service is responsible for computing, publishing, and persisting changesets.
443
>
this._changesets = this._register(instantiationService.createInstance(AgentHostChangesetService));
444
>
services.set(IAgentHostChangesetService, this._changesets);
445
>
446
>
// The coordinator owns all AgentService-side orchestration of the changeset feature: lifecycle
447
>
// hooks, listSessions overlay, subscription URI routing, and the deferred-refresh state machine.
448
>
this._changesetCoordinator = this._register(instantiationService.createInstance(AgentHostChangesetCoordinator));
449
>
this._register(this._stateManager.onDidChangeSessionActiveTurn(e => this._changesetCoordinator.onSessionTurnActiveChanged(e.session, e.active)));
450
>
451
>
// Register the changeset operation contributions.
452
>
this._register(this._changesetOperationService.registerContribution(instantiationService.createInstance(AgentHostCommitOperationContribution)));
453
>
this._register(this._changesetOperationService.registerContribution(instantiationService.createInstance(AgentHostPullRequestOperationContribution)));
454
>
this._register(this._changesetOperationService.registerContribution(instantiationService.createInstance(AgentHostSyncOperationContribution)));
455
>
this._register(this._changesetOperationService.registerContribution(instantiationService.createInstance(AgentHostDiscardChangesOperationContribution)));
456
>
457
>
this._completions = this._register(instantiationService.createInstance(AgentHostCompletions));
458
>
// Built-in generic provider: completes files in the session's workspace folder.
459
>
const workspaceFiles = this._register(instantiationService.createInstance(AgentHostWorkspaceFiles));
460
>
this._register(this._completions.registerProvider(
461
>
new AgentHostFileCompletionProvider(this._stateManager, workspaceFiles),
462
>
));
463
>
// Built-in generic provider: offers the `/rename` slash command for any
464
>
// session that already has history. Execution is handled server-side in
465
>
// AgentSideEffects (redirected to a SessionTitleChanged action).
466
>
this._register(this._completions.registerProvider(
467
>
new AgentHostRenameCompletionProvider(
468
>
session => (this._stateManager.getSessionState(session)?.turns.length ?? 0) > 0,
469
>
),
470
>
));
471
>
472
>
// Terminal management — the terminal manager listens to the state
473
>
// manager's action stream and dispatches PTY output back through it.
474
>
// Created before AgentSideEffects and registered in the local scope so
475
>
// AgentSideEffects can consume it via DI (for inline `!command`
476
>
// execution).
477
>
this._terminalManager = this._register(instantiationService.createInstance(AgentHostTerminalManager));
478
>
services.set(IAgentHostTerminalManager, this._terminalManager);
479
>
480
>
this._localTurns = new AgentHostLocalTurns(this._sessionDataService, this._logService);
481
>
482
>
this._sideEffects = this._register(instantiationService.createInstance(AgentSideEffects, this._stateManager, {
483
>
getAgent: session => this._findProviderForSession(session),
484
>
sessionDataService: this._sessionDataService,
485
>
localTurns: this._localTurns,
486
>
agents: this._agents,
487
>
copilotApiService: effectiveCopilotApiService,
488
>
getGitHubCopilotToken: () => {
489
return this.getAuthToken({
490
resource: this._gitHubEndpointService.getCopilotResource().resource,