Path: blob/main/src/vs/sessions/contrib/chat/browser/agentHost/agentHostPermissionPickerDelegate.ts
13405 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 { Disposable, DisposableMap } from '../../../../../base/common/lifecycle.js';6import { derived, IObservable, IReader, observableSignal } from '../../../../../base/common/observable.js';7import { KNOWN_AUTO_APPROVE_VALUES, SessionConfigKey } from '../../../../../platform/agentHost/common/sessionConfigKeys.js';8import { SessionConfigPropertySchema } from '../../../../../platform/agentHost/common/state/protocol/commands.js';9import { ChatPermissionLevel, isChatPermissionLevel } from '../../../../../workbench/contrib/chat/common/constants.js';10import { IPermissionPickerDelegate } from '../../../../contrib/copilotChatSessions/browser/permissionPicker.js';11import { IAgentHostSessionsProvider, isAgentHostProvider } from '../../../../common/agentHostSessionsProvider.js';12import { ISessionsProvider } from '../../../../services/sessions/common/sessionsProvider.js';13import { ISessionsProvidersService } from '../../../../services/sessions/browser/sessionsProvidersService.js';14import { ISessionsManagementService } from '../../../../services/sessions/common/sessionsManagement.js';1516const REQUIRED_AUTO_APPROVE_VALUE = 'default';17const REQUIRED_MODE_VALUE = 'interactive';1819/**20* Returns `true` when an `autoApprove` session-config property uses the21* shape the unified permission picker expects: a string enum that is a22* subset of `default | autoApprove | autopilot` and contains at least23* `default`.24*25* Callers use this to decide whether to render the unified26* {@link PermissionPicker} (with its built-in warning dialogs, autopilot27* gating, and policy enforcement) or fall back to the generic per-property28* picker.29*/30export function isWellKnownAutoApproveSchema(schema: SessionConfigPropertySchema): boolean {31if (schema.type !== 'string' || !Array.isArray(schema.enum) || schema.enum.length === 0) {32return false;33}34if (!schema.enum.includes(REQUIRED_AUTO_APPROVE_VALUE)) {35return false;36}37return schema.enum.every(value => KNOWN_AUTO_APPROVE_VALUES.has(value));38}3940/**41* {@link IPermissionPickerDelegate} backed by the active session's AHP42* `autoApprove` config property.43*44* - `currentPermissionLevel` derives from the active session's45* `provider.getSessionConfig(...).values.autoApprove`, recomputed when the46* active session changes or when any agent-host provider fires47* `onDidChangeSessionConfig`.48* - `setPermissionLevel(level)` calls `provider.setSessionConfigValue(sessionId,49* 'autoApprove', level)` for the active session's provider.50* - `isApplicable` is `true` only when the active session's `autoApprove`51* schema matches the well-known shape, so the picker hides itself for52* non-conforming agents (which fall back to the generic per-property53* picker) and when no agent-host session is active.54*/55export class AgentHostPermissionPickerDelegate extends Disposable implements IPermissionPickerDelegate {5657/** Fires every time any agent-host provider's session config changes. */58private readonly _configChangedSignal = observableSignal('agentHostPermissionPicker.configChanged');59private readonly _providerSubscriptions = this._register(new DisposableMap<string>());6061readonly currentPermissionLevel: IObservable<ChatPermissionLevel>;62readonly isApplicable: IObservable<boolean>;6364constructor(65@ISessionsManagementService private readonly _sessionsManagementService: ISessionsManagementService,66@ISessionsProvidersService private readonly _sessionsProvidersService: ISessionsProvidersService,67) {68super();6970this._watchProviders(this._sessionsProvidersService.getProviders());71this._register(this._sessionsProvidersService.onDidChangeProviders(e => {72for (const provider of e.removed) {73this._providerSubscriptions.deleteAndDispose(provider.id);74}75this._watchProviders(e.added);76this._configChangedSignal.trigger(undefined);77}));7879this.currentPermissionLevel = derived(this, reader => this._readLevel(reader));80this.isApplicable = derived(this, reader => this._readIsWellKnown(reader));81}8283setPermissionLevel(level: ChatPermissionLevel): void {84const session = this._sessionsManagementService.activeSession.get();85if (!session) {86return;87}88const provider = this._getProvider(session.providerId);89if (!provider) {90return;91}92provider.setSessionConfigValue(session.sessionId, SessionConfigKey.AutoApprove, level)93.catch(() => { /* best-effort */ });94}9596private _readLevel(reader: IReader): ChatPermissionLevel {97this._configChangedSignal.read(reader);98const session = this._sessionsManagementService.activeSession.read(reader);99if (!session) {100return ChatPermissionLevel.Default;101}102const provider = this._getProvider(session.providerId);103if (!provider) {104return ChatPermissionLevel.Default;105}106const value = provider.getSessionConfig(session.sessionId)?.values[SessionConfigKey.AutoApprove];107return isChatPermissionLevel(value) ? value : ChatPermissionLevel.Default;108}109110private _readIsWellKnown(reader: IReader): boolean {111this._configChangedSignal.read(reader);112const session = this._sessionsManagementService.activeSession.read(reader);113if (!session) {114return false;115}116const provider = this._getProvider(session.providerId);117if (!provider) {118return false;119}120const schema = provider.getSessionConfig(session.sessionId)?.schema.properties[SessionConfigKey.AutoApprove];121return !!schema && isWellKnownAutoApproveSchema(schema);122}123124private _getProvider(providerId: string): IAgentHostSessionsProvider | undefined {125const provider = this._sessionsProvidersService.getProvider(providerId);126return provider && isAgentHostProvider(provider) ? provider : undefined;127}128129private _watchProviders(providers: readonly ISessionsProvider[]): void {130for (const provider of providers) {131if (!isAgentHostProvider(provider) || this._providerSubscriptions.has(provider.id)) {132continue;133}134this._providerSubscriptions.set(provider.id, provider.onDidChangeSessionConfig(() => {135this._configChangedSignal.trigger(undefined);136}));137}138}139}140141/**142* Returns `true` when a `mode` session-config property uses the shape the143* dedicated agent-host mode picker expects: a string enum that contains144* at least `interactive`.145*146* Callers use this to decide whether to render the dedicated mode picker147* (with mode-specific icons and behavior) or fall back to the generic148* per-property picker.149*/150export function isWellKnownModeSchema(schema: SessionConfigPropertySchema): boolean {151if (schema.type !== 'string' || !Array.isArray(schema.enum) || schema.enum.length === 0) {152return false;153}154if (!schema.enum.includes(REQUIRED_MODE_VALUE)) {155return false;156}157return true;158}159160161162