Path: blob/main/src/vs/sessions/contrib/agentHost/browser/localAgentHostSessionsProvider.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Codicon } from '../../../../base/common/codicons.js';6import { MarkdownString } from '../../../../base/common/htmlContent.js';7import { Schemas } from '../../../../base/common/network.js';8import { autorun, IObservable } from '../../../../base/common/observable.js';9import { basename, dirname } from '../../../../base/common/resources.js';10import { ThemeIcon } from '../../../../base/common/themables.js';11import { URI } from '../../../../base/common/uri.js';12import { localize } from '../../../../nls.js';13import { IAgentConnection, IAgentHostService, type IAgentSessionMetadata } from '../../../../platform/agentHost/common/agentService.js';14import type { ISessionGitState } from '../../../../platform/agentHost/common/state/sessionState.js';15import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';16import { ILabelService } from '../../../../platform/label/common/label.js';17import { IChatWidgetService } from '../../../../workbench/contrib/chat/browser/chat.js';18import { IChatService } from '../../../../workbench/contrib/chat/common/chatService/chatService.js';19import { IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';20import { ILanguageModelsService } from '../../../../workbench/contrib/chat/common/languageModels.js';21import { BaseAgentHostSessionsProvider } from './baseAgentHostSessionsProvider.js';22import { buildAgentHostSessionWorkspace, readBranchProtectionPatterns } from '../../../common/agentHostSessionWorkspace.js';23import { ISessionWorkspace, ISessionWorkspaceBrowseAction, SESSION_WORKSPACE_GROUP_LOCAL } from '../../../services/sessions/common/session.js';24import { toAgentHostUri } from '../../../../platform/agentHost/common/agentHostUri.js';25import { LOCAL_AGENT_HOST_PROVIDER_ID } from '../../../common/agentHostSessionsProvider.js';2627const LOCAL_RESOURCE_SCHEME_PREFIX = 'agent-host-';2829/**30* Local-window sessions provider backed by the in-process31* {@link IAgentHostService}. A thin subclass of32* {@link BaseAgentHostSessionsProvider} that supplies the local-only33* variation: a built-in connection that is always present, session-type34* synchronization from the local agent host's `rootState`, and a local35* file-picker browse action.36*/37export class LocalAgentHostSessionsProvider extends BaseAgentHostSessionsProvider {3839readonly id = LOCAL_AGENT_HOST_PROVIDER_ID;40readonly label: string;41readonly icon: ThemeIcon = Codicon.vm;42readonly browseActions: readonly ISessionWorkspaceBrowseAction[];43readonly supportsLocalWorkspaces = true;4445private readonly _localLabel = localize('localAgentHostSessionTypeLocation', "Local");46private readonly _localDescription = new MarkdownString(this._localLabel);4748constructor(49@IAgentHostService private readonly _agentHostService: IAgentHostService,50@IChatSessionsService chatSessionsService: IChatSessionsService,51@IChatService chatService: IChatService,52@IChatWidgetService chatWidgetService: IChatWidgetService,53@ILanguageModelsService languageModelsService: ILanguageModelsService,54@ILabelService private readonly _labelService: ILabelService,55@IConfigurationService private readonly _configurationService: IConfigurationService,56) {57super(chatSessionsService, chatService, chatWidgetService, languageModelsService);5859this.label = localize('localAgentHostLabel', "Local Agent Host");6061this.browseActions = [];6263this._attachConnectionListeners(this._agentHostService, this._store);6465const rootStateValue = this._agentHostService.rootState.value;66if (rootStateValue && !(rootStateValue instanceof Error)) {67this._syncSessionTypesFromRootState(rootStateValue);68this._syncRootConfigFromRootState(rootStateValue);69}70this._register(this._agentHostService.rootState.onDidChange(rootState => {71this._syncSessionTypesFromRootState(rootState);72this._syncRootConfigFromRootState(rootState);73}));7475// Eagerly populate the session cache once authentication has settled.76// Without this, the sidebar would only call `getSessions()` after some77// other event (e.g. a `notify/sessionAdded` after the user sends a78// message) forced a refresh. We wait for `authenticationPending` to79// settle because the underlying agent (e.g. CopilotAgent) throws80// `AHP_AUTH_REQUIRED` from `listSessions()` until its auth token is81// resolved. The `authenticationPending` observable is sticky (once82// it goes false it stays false), so this autorun fires83// `_refreshSessions()` at most once for the eager-load case.84this._register(autorun(reader => {85if (this._agentHostService.authenticationPending.read(reader)) {86return;87}88this._cacheInitialized = true;89this._refreshSessions();90}));91}9293// -- BaseAgentHostSessionsProvider hooks ---------------------------------9495protected get connection(): IAgentConnection { return this._agentHostService; }9697protected get authenticationPending(): IObservable<boolean> { return this._agentHostService.authenticationPending; }9899/**100* Local resource scheme: `agent-host-${provider}`. Must match the type101* string registered by AgentHostContribution. Distinct from the logical102* {@link ISession.sessionType}, which is the agent provider name itself103* (e.g. `copilotcli`) so the same agent shares one session type across104* local and remote hosts.105*/106protected resourceSchemeForProvider(provider: string): string {107return `${LOCAL_RESOURCE_SCHEME_PREFIX}${provider}`;108}109110protected _adapterOptions() {111return {112description: this._localDescription,113buildWorkspace: (project: IAgentSessionMetadata['project'], workingDirectory: URI | undefined, gitState: ISessionGitState | undefined) => {114const uriForDescription = project?.uri ?? workingDirectory;115const description = uriForDescription ? this._labelService.getUriLabel(dirname(uriForDescription), { relative: false }) : undefined;116const branchProtectionPatterns = readBranchProtectionPatterns(this._configurationService, workingDirectory ?? project?.uri);117return LocalAgentHostSessionsProvider.buildWorkspace(project, workingDirectory, this._localLabel, gitState, description, branchProtectionPatterns);118},119};120}121122protected _formatSessionTypeLabel(agentLabel: string): string {123// Use the unadorned agent label (e.g. "Copilot") rather than tagging it124// with `[Local]`. The session type id is shared with the extension-host125// Copilot CLI provider, so the filter menu / new-session picker entry126// covers both sets of sessions; the `[Local]` tag belongs on the127// per-session workspace label, not the type label.128return agentLabel;129}130131protected override _diffUriMapper(): (uri: URI) => URI {132return uri => toAgentHostUri(uri, 'local');133}134135// -- Workspaces ----------------------------------------------------------136137static buildWorkspace(project: IAgentSessionMetadata['project'], workingDirectory: URI | undefined, providerLabel: string, gitState: ISessionGitState | undefined, description?: string, branchProtectionPatterns?: readonly string[]): ISessionWorkspace | undefined {138return buildAgentHostSessionWorkspace(project, workingDirectory, { providerLabel, fallbackIcon: Codicon.folder, requiresWorkspaceTrust: true, description, branchProtectionPatterns, group: SESSION_WORKSPACE_GROUP_LOCAL }, gitState);139}140141resolveWorkspace(repositoryUri: URI): ISessionWorkspace | undefined {142if (repositoryUri.scheme !== Schemas.file) {143return undefined;144}145const folderName = basename(repositoryUri) || repositoryUri.path;146return {147label: `${folderName} [${this._localLabel}]`,148description: this._labelService.getUriLabel(dirname(repositoryUri), { relative: false }),149group: SESSION_WORKSPACE_GROUP_LOCAL,150icon: Codicon.folder,151repositories: [{ uri: repositoryUri, workingDirectory: undefined, detail: undefined, baseBranchName: undefined }],152requiresWorkspaceTrust: true,153};154}155}156157158