Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts
5319 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 { sep } from '../../../../../base/common/path.js';6import { raceCancellationError } from '../../../../../base/common/async.js';7import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';8import { Codicon } from '../../../../../base/common/codicons.js';9import { AsyncEmitter, Emitter, Event } from '../../../../../base/common/event.js';10import { combinedDisposable, Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';11import { ResourceMap } from '../../../../../base/common/map.js';12import { Schemas } from '../../../../../base/common/network.js';13import * as resources from '../../../../../base/common/resources.js';14import { ThemeIcon } from '../../../../../base/common/themables.js';15import { URI, UriComponents } from '../../../../../base/common/uri.js';16import { generateUuid } from '../../../../../base/common/uuid.js';17import { localize, localize2 } from '../../../../../nls.js';18import { Action2, IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';19import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';20import { IRelaxedExtensionDescription } from '../../../../../platform/extensions/common/extensions.js';21import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js';22import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';23import { ILabelService } from '../../../../../platform/label/common/label.js';24import { ILogService } from '../../../../../platform/log/common/log.js';25import { isDark } from '../../../../../platform/theme/common/theme.js';26import { IThemeService } from '../../../../../platform/theme/common/themeService.js';27import { IEditorService } from '../../../../services/editor/common/editorService.js';28import { IExtensionService, isProposedApiEnabled } from '../../../../services/extensions/common/extensions.js';29import { ExtensionsRegistry } from '../../../../services/extensions/common/extensionsRegistry.js';30import { ChatEditorInput } from '../widgetHosts/editor/chatEditorInput.js';31import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentService } from '../../common/participants/chatAgents.js';32import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';33import { IChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemController, IChatSessionOptionsWillNotifyExtensionEvent, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsExtensionPoint, IChatSessionsService, isSessionInProgressStatus } from '../../common/chatSessionsService.js';34import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js';35import { CHAT_CATEGORY } from '../actions/chatActions.js';36import { IChatEditorOptions } from '../widgetHosts/editor/chatEditor.js';37import { IChatModel } from '../../common/model/chatModel.js';38import { IChatService, IChatToolInvocation } from '../../common/chatService/chatService.js';39import { autorun, autorunIterableDelta, observableFromEvent, observableSignalFromEvent } from '../../../../../base/common/observable.js';40import { IChatRequestVariableEntry } from '../../common/attachments/chatVariableEntries.js';41import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';42import { IMarkdownString } from '../../../../../base/common/htmlContent.js';43import { IViewsService } from '../../../../services/views/common/viewsService.js';44import { ChatViewId } from '../chat.js';45import { ChatViewPane } from '../widgetHosts/viewPane/chatViewPane.js';46import { AgentSessionProviders, backgroundAgentDisplayName, getAgentSessionProviderName } from '../agentSessions/agentSessions.js';47import { BugIndicatingError, isCancellationError } from '../../../../../base/common/errors.js';48import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';49import { LocalChatSessionUri } from '../../common/model/chatUri.js';50import { assertNever } from '../../../../../base/common/assert.js';51import { ICommandService } from '../../../../../platform/commands/common/commands.js';52import { Target } from '../../common/promptSyntax/service/promptsService.js';5354const extensionPoint = ExtensionsRegistry.registerExtensionPoint<IChatSessionsExtensionPoint[]>({55extensionPoint: 'chatSessions',56jsonSchema: {57description: localize('chatSessionsExtPoint', 'Contributes chat session integrations to the chat widget.'),58type: 'array',59items: {60type: 'object',61additionalProperties: false,62properties: {63type: {64description: localize('chatSessionsExtPoint.chatSessionType', 'Unique identifier for the type of chat session.'),65type: 'string',66},67name: {68description: localize('chatSessionsExtPoint.name', 'Name of the dynamically registered chat participant (eg: @agent). Must not contain whitespace.'),69type: 'string',70pattern: '^[\\w-]+$'71},72displayName: {73description: localize('chatSessionsExtPoint.displayName', 'A longer name for this item which is used for display in menus.'),74type: 'string',75},76description: {77description: localize('chatSessionsExtPoint.description', 'Description of the chat session for use in menus and tooltips.'),78type: 'string'79},80when: {81description: localize('chatSessionsExtPoint.when', 'Condition which must be true to show this item.'),82type: 'string'83},84icon: {85description: localize('chatSessionsExtPoint.icon', 'Icon identifier (codicon ID) for the chat session editor tab. For example, "$(github)" or "$(cloud)".'),86anyOf: [{87type: 'string'88},89{90type: 'object',91properties: {92light: {93description: localize('icon.light', 'Icon path when a light theme is used'),94type: 'string'95},96dark: {97description: localize('icon.dark', 'Icon path when a dark theme is used'),98type: 'string'99}100}101}]102},103order: {104description: localize('chatSessionsExtPoint.order', 'Order in which this item should be displayed.'),105type: 'integer'106},107alternativeIds: {108description: localize('chatSessionsExtPoint.alternativeIds', 'Alternative identifiers for backward compatibility.'),109type: 'array',110items: {111type: 'string'112}113},114welcomeTitle: {115description: localize('chatSessionsExtPoint.welcomeTitle', 'Title text to display in the chat welcome view for this session type.'),116type: 'string'117},118welcomeMessage: {119description: localize('chatSessionsExtPoint.welcomeMessage', 'Message text (supports markdown) to display in the chat welcome view for this session type.'),120type: 'string'121},122welcomeTips: {123description: localize('chatSessionsExtPoint.welcomeTips', 'Tips text (supports markdown and theme icons) to display in the chat welcome view for this session type.'),124type: 'string'125},126inputPlaceholder: {127description: localize('chatSessionsExtPoint.inputPlaceholder', 'Placeholder text to display in the chat input box for this session type.'),128type: 'string'129},130capabilities: {131description: localize('chatSessionsExtPoint.capabilities', 'Optional capabilities for this chat session.'),132type: 'object',133additionalProperties: false,134properties: {135supportsFileAttachments: {136description: localize('chatSessionsExtPoint.supportsFileAttachments', 'Whether this chat session supports attaching files or file references.'),137type: 'boolean'138},139supportsToolAttachments: {140description: localize('chatSessionsExtPoint.supportsToolAttachments', 'Whether this chat session supports attaching tools or tool references.'),141type: 'boolean'142},143supportsMCPAttachments: {144description: localize('chatSessionsExtPoint.supportsMCPAttachments', 'Whether this chat session supports attaching MCP resources.'),145type: 'boolean'146},147supportsImageAttachments: {148description: localize('chatSessionsExtPoint.supportsImageAttachments', 'Whether this chat session supports attaching images.'),149type: 'boolean'150},151supportsSearchResultAttachments: {152description: localize('chatSessionsExtPoint.supportsSearchResultAttachments', 'Whether this chat session supports attaching search results.'),153type: 'boolean'154},155supportsInstructionAttachments: {156description: localize('chatSessionsExtPoint.supportsInstructionAttachments', 'Whether this chat session supports attaching instructions.'),157type: 'boolean'158},159supportsSourceControlAttachments: {160description: localize('chatSessionsExtPoint.supportsSourceControlAttachments', 'Whether this chat session supports attaching source control changes.'),161type: 'boolean'162},163supportsProblemAttachments: {164description: localize('chatSessionsExtPoint.supportsProblemAttachments', 'Whether this chat session supports attaching problems.'),165type: 'boolean'166},167supportsSymbolAttachments: {168description: localize('chatSessionsExtPoint.supportsSymbolAttachments', 'Whether this chat session supports attaching symbols.'),169type: 'boolean'170},171supportsPromptAttachments: {172description: localize('chatSessionsExtPoint.supportsPromptAttachments', 'Whether this chat session supports attaching prompts.'),173type: 'boolean'174}175}176},177commands: {178markdownDescription: localize('chatCommandsDescription', "Commands available for this chat session, which the user can invoke with a `/`."),179type: 'array',180items: {181additionalProperties: false,182type: 'object',183defaultSnippets: [{ body: { name: '', description: '' } }],184required: ['name'],185properties: {186name: {187description: localize('chatCommand', "A short name by which this command is referred to in the UI, e.g. `fix` or `explain` for commands that fix an issue or explain code. The name should be unique among the commands provided by this participant."),188type: 'string'189},190description: {191description: localize('chatCommandDescription', "A description of this command."),192type: 'string'193},194when: {195description: localize('chatCommandWhen', "A condition which must be true to enable this command."),196type: 'string'197},198}199}200},201canDelegate: {202description: localize('chatSessionsExtPoint.canDelegate', 'Whether delegation is supported. Default is false. Note that enabling this is experimental and may not be respected at all times.'),203type: 'boolean',204default: false205},206isReadOnly: {207description: localize('chatSessionsExtPoint.isReadOnly', 'Whether this session type is for read-only agents that do not support interactive chat. This flag is incompatible with \'canDelegate\'.'),208type: 'boolean',209default: false210},211customAgentTarget: {212description: localize('chatSessionsExtPoint.customAgentTarget', 'When set, the chat session will show a filtered mode picker that prefers custom agents whose target property matches this value. Custom agents without a target property are still shown in all session types. This enables the use of standard agent/mode with contributed sessions.'),213type: 'string'214}215},216required: ['type', 'name', 'displayName', 'description'],217}218},219activationEventsGenerator: function* (contribs) {220for (const contrib of contribs) {221yield `onChatSession:${contrib.type}`;222}223}224});225226class ContributedChatSessionData extends Disposable {227228private readonly _optionsCache: Map<string /* 'models' */, string | IChatSessionProviderOptionItem>;229public getOption(optionId: string): string | IChatSessionProviderOptionItem | undefined {230return this._optionsCache.get(optionId);231}232public setOption(optionId: string, value: string | IChatSessionProviderOptionItem): void {233this._optionsCache.set(optionId, value);234}235236constructor(237readonly session: IChatSession,238readonly chatSessionType: string,239readonly resource: URI,240readonly options: Record<string, string | IChatSessionProviderOptionItem> | undefined,241private readonly onWillDispose: (resource: URI) => void242) {243super();244245this._optionsCache = new Map<string, string | IChatSessionProviderOptionItem>();246if (options) {247for (const [key, value] of Object.entries(options)) {248this._optionsCache.set(key, value);249}250}251252this._register(this.session.onWillDispose(() => {253this.onWillDispose(this.resource);254}));255}256}257258259export class ChatSessionsService extends Disposable implements IChatSessionsService {260readonly _serviceBrand: undefined;261262private readonly _itemControllers = new Map</* type */ string, { readonly controller: IChatSessionItemController; readonly initialRefresh: Promise<void> }>();263264private readonly _contributions: Map</* type */ string, { readonly contribution: IChatSessionsExtensionPoint; readonly extension: IRelaxedExtensionDescription }> = new Map();265private readonly _contributionDisposables = this._register(new DisposableMap</* type */ string>());266267private readonly _contentProviders: Map</* scheme */ string, IChatSessionContentProvider> = new Map();268private readonly _alternativeIdMap: Map</* alternativeId */ string, /* primaryType */ string> = new Map();269private readonly _contextKeys = new Set<string>();270271private readonly _onDidChangeItemsProviders = this._register(new Emitter<{ readonly chatSessionType: string }>());272readonly onDidChangeItemsProviders = this._onDidChangeItemsProviders.event;273274private readonly _onDidChangeSessionItems = this._register(new Emitter<{ readonly chatSessionType: string }>());275readonly onDidChangeSessionItems = this._onDidChangeSessionItems.event;276277private readonly _onDidChangeAvailability = this._register(new Emitter<void>());278readonly onDidChangeAvailability: Event<void> = this._onDidChangeAvailability.event;279280private readonly _onDidChangeInProgress = this._register(new Emitter<void>());281public get onDidChangeInProgress() { return this._onDidChangeInProgress.event; }282283private readonly _onDidChangeContentProviderSchemes = this._register(new Emitter<{ readonly added: string[]; readonly removed: string[] }>());284public get onDidChangeContentProviderSchemes() { return this._onDidChangeContentProviderSchemes.event; }285private readonly _onDidChangeSessionOptions = this._register(new Emitter<URI>());286public get onDidChangeSessionOptions() { return this._onDidChangeSessionOptions.event; }287private readonly _onDidChangeOptionGroups = this._register(new Emitter<string>());288public get onDidChangeOptionGroups() { return this._onDidChangeOptionGroups.event; }289private readonly _onRequestNotifyExtension = this._register(new AsyncEmitter<IChatSessionOptionsWillNotifyExtensionEvent>());290public get onRequestNotifyExtension() { return this._onRequestNotifyExtension.event; }291292private readonly inProgressMap: Map<string, number> = new Map();293private readonly _sessionTypeOptions: Map<string, IChatSessionProviderOptionGroup[]> = new Map();294private readonly _sessionTypeIcons: Map<string, ThemeIcon | { light: URI; dark: URI }> = new Map();295private readonly _sessionTypeWelcomeTitles: Map<string, string> = new Map();296private readonly _sessionTypeWelcomeMessages: Map<string, string> = new Map();297private readonly _sessionTypeWelcomeTips: Map<string, string> = new Map();298private readonly _sessionTypeInputPlaceholders: Map<string, string> = new Map();299300private readonly _sessions = new ResourceMap<ContributedChatSessionData>();301302private readonly _hasCanDelegateProvidersKey: IContextKey<boolean>;303304constructor(305@ILogService private readonly _logService: ILogService,306@IChatAgentService private readonly _chatAgentService: IChatAgentService,307@IExtensionService private readonly _extensionService: IExtensionService,308@IContextKeyService private readonly _contextKeyService: IContextKeyService,309@IMenuService private readonly _menuService: IMenuService,310@IThemeService private readonly _themeService: IThemeService,311@ILabelService private readonly _labelService: ILabelService312) {313super();314315this._hasCanDelegateProvidersKey = ChatContextKeys.hasCanDelegateProviders.bindTo(this._contextKeyService);316317this._register(extensionPoint.setHandler(extensions => {318for (const ext of extensions) {319if (!isProposedApiEnabled(ext.description, 'chatSessionsProvider')) {320continue;321}322if (!Array.isArray(ext.value)) {323continue;324}325for (const contribution of ext.value) {326this._register(this.registerContribution(contribution, ext.description));327}328}329}));330331// Listen for context changes and re-evaluate contributions332this._register(Event.filter(this._contextKeyService.onDidChangeContext, e => e.affectsSome(this._contextKeys))(() => {333this._evaluateAvailability();334}));335336const builtinSessionProviders = [AgentSessionProviders.Local];337const contributedSessionProviders = observableFromEvent(338this.onDidChangeAvailability,339() => Array.from(this._contributions.keys()).filter(key => this._contributionDisposables.has(key) && isAgentSessionProviderType(key)) as AgentSessionProviders[],340).recomputeInitiallyAndOnChange(this._store);341342this._register(autorun(reader => {343backgroundAgentDisplayName.read(reader);344const activatedProviders = [...builtinSessionProviders, ...contributedSessionProviders.read(reader)];345for (const provider of Object.values(AgentSessionProviders)) {346if (activatedProviders.includes(provider)) {347reader.store.add(registerNewSessionInPlaceAction(provider, getAgentSessionProviderName(provider)));348}349}350}));351352this._register(this.onDidChangeSessionItems(({ chatSessionType }) => {353this.updateInProgressStatus(chatSessionType).catch(error => {354this._logService.warn(`Failed to update progress status for '${chatSessionType}':`, error);355});356}));357358this._register(this._labelService.registerFormatter({359scheme: Schemas.copilotPr,360formatting: {361label: '${authority}${path}',362separator: sep,363stripPathStartingSeparator: true,364}365}));366}367368public reportInProgress(chatSessionType: string, count: number): void {369let displayName: string | undefined;370371if (chatSessionType === AgentSessionProviders.Local) {372displayName = localize('chat.session.inProgress.local', "Local Agent");373} else if (chatSessionType === AgentSessionProviders.Background) {374displayName = localize('chat.session.inProgress.background', "Background Agent");375} else if (chatSessionType === AgentSessionProviders.Cloud) {376displayName = localize('chat.session.inProgress.cloud', "Cloud Agent");377} else {378displayName = this._contributions.get(chatSessionType)?.contribution.displayName;379}380381if (displayName) {382this.inProgressMap.set(displayName, count);383}384this._onDidChangeInProgress.fire();385}386387public getInProgress(): { displayName: string; count: number }[] {388return Array.from(this.inProgressMap.entries()).map(([displayName, count]) => ({ displayName, count }));389}390391private async updateInProgressStatus(chatSessionType: string): Promise<void> {392try {393const results = await this.getChatSessionItems([chatSessionType], CancellationToken.None);394const items = results.flatMap(r => r.items);395const inProgress = items.filter(item => item.status && isSessionInProgressStatus(item.status));396this.reportInProgress(chatSessionType, inProgress.length);397} catch (error) {398this._logService.warn(`Failed to update in-progress status for chat session type '${chatSessionType}':`, error);399}400}401402private registerContribution(contribution: IChatSessionsExtensionPoint, ext: IRelaxedExtensionDescription): IDisposable {403if (this._contributions.has(contribution.type)) {404return { dispose: () => { } };405}406407// Track context keys from the when condition408if (contribution.when) {409const whenExpr = ContextKeyExpr.deserialize(contribution.when);410if (whenExpr) {411for (const key of whenExpr.keys()) {412this._contextKeys.add(key);413}414}415}416417this._contributions.set(contribution.type, { contribution, extension: ext });418419// Register alternative IDs if provided420if (contribution.alternativeIds) {421for (const altId of contribution.alternativeIds) {422if (this._alternativeIdMap.has(altId)) {423this._logService.warn(`Alternative ID '${altId}' is already mapped to '${this._alternativeIdMap.get(altId)}'. Remapping to '${contribution.type}'.`);424}425this._alternativeIdMap.set(altId, contribution.type);426}427}428429// Store icon mapping if provided430let icon: ThemeIcon | { dark: URI; light: URI } | undefined;431432if (contribution.icon) {433// Parse icon string - support ThemeIcon format or file path from extension434if (typeof contribution.icon === 'string') {435icon = contribution.icon.startsWith('$(') && contribution.icon.endsWith(')')436? ThemeIcon.fromString(contribution.icon)437: ThemeIcon.fromId(contribution.icon);438} else {439icon = {440dark: resources.joinPath(ext.extensionLocation, contribution.icon.dark),441light: resources.joinPath(ext.extensionLocation, contribution.icon.light)442};443}444}445446if (icon) {447this._sessionTypeIcons.set(contribution.type, icon);448}449450// Store welcome title, message, tips, and input placeholder if provided451if (contribution.welcomeTitle) {452this._sessionTypeWelcomeTitles.set(contribution.type, contribution.welcomeTitle);453}454if (contribution.welcomeMessage) {455this._sessionTypeWelcomeMessages.set(contribution.type, contribution.welcomeMessage);456}457if (contribution.welcomeTips) {458this._sessionTypeWelcomeTips.set(contribution.type, contribution.welcomeTips);459}460if (contribution.inputPlaceholder) {461this._sessionTypeInputPlaceholders.set(contribution.type, contribution.inputPlaceholder);462}463464this._evaluateAvailability();465466return {467dispose: () => {468this._contributions.delete(contribution.type);469// Remove alternative ID mappings470if (contribution.alternativeIds) {471for (const altId of contribution.alternativeIds) {472if (this._alternativeIdMap.get(altId) === contribution.type) {473this._alternativeIdMap.delete(altId);474}475}476}477this._sessionTypeIcons.delete(contribution.type);478this._sessionTypeWelcomeTitles.delete(contribution.type);479this._sessionTypeWelcomeMessages.delete(contribution.type);480this._sessionTypeWelcomeTips.delete(contribution.type);481this._sessionTypeInputPlaceholders.delete(contribution.type);482this._contributionDisposables.deleteAndDispose(contribution.type);483this._updateHasCanDelegateProvidersContextKey();484}485};486}487488private _isContributionAvailable(contribution: IChatSessionsExtensionPoint): boolean {489if (!contribution.when) {490return true;491}492const whenExpr = ContextKeyExpr.deserialize(contribution.when);493return !whenExpr || this._contextKeyService.contextMatchesRules(whenExpr);494}495496/**497* Resolves a session type to its primary type, checking for alternative IDs.498* @param sessionType The session type or alternative ID to resolve499* @returns The primary session type, or undefined if not found or not available500*/501private _resolveToPrimaryType(sessionType: string): string | undefined {502// Try to find the primary type first503const contribution = this._contributions.get(sessionType)?.contribution;504if (contribution) {505// If the contribution is available, use it506if (this._isContributionAvailable(contribution)) {507return sessionType;508}509// If not available, fall through to check for alternatives510}511512// Check if this is an alternative ID, or if the primary type is not available513const primaryType = this._alternativeIdMap.get(sessionType);514if (primaryType) {515const altContribution = this._contributions.get(primaryType)?.contribution;516if (altContribution && this._isContributionAvailable(altContribution)) {517return primaryType;518}519}520521return undefined;522}523524private _registerMenuItems(contribution: IChatSessionsExtensionPoint, extensionDescription: IRelaxedExtensionDescription): IDisposable {525// If provider registers anything for the create submenu, let it fully control the creation526const contextKeyService = this._contextKeyService.createOverlay([527['chatSessionType', contribution.type]528]);529530const rawMenuActions = this._menuService.getMenuActions(MenuId.AgentSessionsCreateSubMenu, contextKeyService);531const menuActions = rawMenuActions.map(value => value[1]).flat();532533const disposables = new DisposableStore();534535// Mirror all create submenu actions into the global Chat New menu536for (let i = 0; i < menuActions.length; i++) {537const action = menuActions[i];538if (action instanceof MenuItemAction) {539// TODO: This is an odd way to do this, but the best we can do currently540if (i === 0 && !contribution.canDelegate) {541disposables.add(registerNewSessionExternalAction(contribution.type, contribution.displayName, action.item.id));542} else {543disposables.add(MenuRegistry.appendMenuItem(MenuId.ChatNewMenu, {544command: action.item,545group: '4_externally_contributed',546}));547}548}549}550return {551dispose: () => disposables.dispose()552};553}554555private _registerCommands(contribution: IChatSessionsExtensionPoint): IDisposable {556const isAvailableInSessionTypePicker = isAgentSessionProviderType(contribution.type);557558return combinedDisposable(559registerAction2(class OpenChatSessionAction extends Action2 {560constructor() {561super({562id: `workbench.action.chat.openSessionWithPrompt.${contribution.type}`,563title: localize2('interactiveSession.openSessionWithPrompt', "New {0} with Prompt", contribution.displayName),564category: CHAT_CATEGORY,565icon: Codicon.plus,566f1: false,567precondition: ChatContextKeys.enabled568});569}570571async run(accessor: ServicesAccessor, chatOptions?: { resource: UriComponents; prompt: string; attachedContext?: IChatRequestVariableEntry[] }): Promise<void> {572const chatService = accessor.get(IChatService);573const { type } = contribution;574575if (chatOptions) {576const resource = URI.revive(chatOptions.resource);577const ref = await chatService.loadSessionForResource(resource, ChatAgentLocation.Chat, CancellationToken.None);578await chatService.sendRequest(resource, chatOptions.prompt, { agentIdSilent: type, attachedContext: chatOptions.attachedContext });579ref?.dispose();580}581}582}),583// Creates a chat editor584registerAction2(class OpenNewChatSessionEditorAction extends Action2 {585constructor() {586super({587id: `workbench.action.chat.openNewSessionEditor.${contribution.type}`,588title: localize2('interactiveSession.openNewSessionEditor', "New {0}", contribution.displayName),589category: CHAT_CATEGORY,590icon: Codicon.plus,591f1: true,592precondition: ChatContextKeys.enabled,593});594}595596async run(accessor: ServicesAccessor, chatOptions?: { prompt: string; attachedContext?: IChatRequestVariableEntry[] }): Promise<void> {597const { type, displayName } = contribution;598await openChatSession(accessor, { type, displayName, position: ChatSessionPosition.Editor }, chatOptions);599}600}),601// New chat in sidebar chat (+ button)602registerAction2(class OpenNewChatSessionSidebarAction extends Action2 {603constructor() {604super({605id: `workbench.action.chat.openNewSessionSidebar.${contribution.type}`,606title: localize2('interactiveSession.openNewSessionSidebar', "New {0}", contribution.displayName),607category: CHAT_CATEGORY,608icon: Codicon.plus,609f1: false, // Hide from Command Palette610precondition: ChatContextKeys.enabled,611menu: !isAvailableInSessionTypePicker ? {612id: MenuId.ChatNewMenu,613group: '3_new_special',614} : undefined,615});616}617618async run(accessor: ServicesAccessor, chatOptions?: { prompt: string; attachedContext?: IChatRequestVariableEntry[] }): Promise<void> {619const { type, displayName } = contribution;620await openChatSession(accessor, { type, displayName, position: ChatSessionPosition.Sidebar }, chatOptions);621}622})623);624}625626private _evaluateAvailability(): void {627let hasChanges = false;628for (const { contribution, extension } of this._contributions.values()) {629const isCurrentlyRegistered = this._contributionDisposables.has(contribution.type);630const shouldBeRegistered = this._isContributionAvailable(contribution);631if (isCurrentlyRegistered && !shouldBeRegistered) {632// Disable the contribution by disposing its disposable store633this._contributionDisposables.deleteAndDispose(contribution.type);634635// Also dispose any cached sessions for this contribution636this._disposeSessionsForContribution(contribution.type);637hasChanges = true;638} else if (!isCurrentlyRegistered && shouldBeRegistered) {639// Enable the contribution by registering it640this._enableContribution(contribution, extension);641hasChanges = true;642}643}644if (hasChanges) {645this._onDidChangeAvailability.fire();646for (const chatSessionType of this._itemControllers.keys()) {647this._onDidChangeItemsProviders.fire({ chatSessionType });648}649for (const { contribution } of this._contributions.values()) {650this._onDidChangeSessionItems.fire({ chatSessionType: contribution.type });651}652}653this._updateHasCanDelegateProvidersContextKey();654}655656private _enableContribution(contribution: IChatSessionsExtensionPoint, ext: IRelaxedExtensionDescription): void {657const disposableStore = new DisposableStore();658this._contributionDisposables.set(contribution.type, disposableStore);659if (contribution.isReadOnly || contribution.canDelegate) {660disposableStore.add(this._registerAgent(contribution, ext));661disposableStore.add(this._registerCommands(contribution));662}663disposableStore.add(this._registerMenuItems(contribution, ext));664}665666private _disposeSessionsForContribution(contributionId: string): void {667// Find and dispose all sessions that belong to this contribution668const sessionsToDispose: URI[] = [];669for (const [sessionResource, sessionData] of this._sessions) {670if (sessionData.chatSessionType === contributionId) {671sessionsToDispose.push(sessionResource);672}673}674675if (sessionsToDispose.length > 0) {676this._logService.info(`Disposing ${sessionsToDispose.length} cached sessions for contribution '${contributionId}' due to when clause change`);677}678679for (const sessionKey of sessionsToDispose) {680const sessionData = this._sessions.get(sessionKey);681if (sessionData) {682sessionData.dispose(); // This will call _onWillDisposeSession and clean up683}684}685}686687private _registerAgent(contribution: IChatSessionsExtensionPoint, ext: IRelaxedExtensionDescription): IDisposable {688const { type: id, name, displayName, description } = contribution;689const storedIcon = this._sessionTypeIcons.get(id);690const icons = ThemeIcon.isThemeIcon(storedIcon)691? { themeIcon: storedIcon, icon: undefined, iconDark: undefined }692: storedIcon693? { icon: storedIcon.light, iconDark: storedIcon.dark }694: { themeIcon: Codicon.sendToRemoteAgent };695696const agentData: IChatAgentData = {697id,698name,699fullName: displayName,700description: description,701isDefault: false,702isCore: false,703isDynamic: true,704slashCommands: contribution.commands ?? [],705locations: [ChatAgentLocation.Chat],706modes: [ChatModeKind.Agent, ChatModeKind.Ask],707disambiguation: [],708metadata: {709...icons,710},711capabilities: contribution.capabilities,712canAccessPreviousChatHistory: true,713extensionId: ext.identifier,714extensionVersion: ext.version,715extensionDisplayName: ext.displayName || ext.name,716extensionPublisherId: ext.publisher,717};718719return this._chatAgentService.registerAgent(id, agentData);720}721722getAllChatSessionContributions(): IChatSessionsExtensionPoint[] {723return Array.from(this._contributions.values(), x => x.contribution)724.filter(contribution => this._isContributionAvailable(contribution));725}726727private _updateHasCanDelegateProvidersContextKey(): void {728const hasCanDelegate = this.getAllChatSessionContributions().filter(c => c.canDelegate);729const canDelegateEnabled = hasCanDelegate.length > 0;730this._logService.trace(`[ChatSessionsService] hasCanDelegateProvidersAvailable=${canDelegateEnabled} (${hasCanDelegate.map(c => c.type).join(', ')})`);731this._hasCanDelegateProvidersKey.set(canDelegateEnabled);732}733734getChatSessionContribution(chatSessionType: string): IChatSessionsExtensionPoint | undefined {735const contribution = this._contributions.get(chatSessionType)?.contribution;736if (!contribution) {737return undefined;738}739740return this._isContributionAvailable(contribution) ? contribution : undefined;741}742743async activateChatSessionItemProvider(chatViewType: string): Promise<void> {744await this.doActivateChatSessionItemController(chatViewType);745}746747private async doActivateChatSessionItemController(chatViewType: string): Promise<boolean> {748await this._extensionService.whenInstalledExtensionsRegistered();749const resolvedType = this._resolveToPrimaryType(chatViewType);750if (resolvedType) {751chatViewType = resolvedType;752}753754const contribution = this._contributions.get(chatViewType)?.contribution;755if (contribution && !this._isContributionAvailable(contribution)) {756return false;757}758759if (this._itemControllers.has(chatViewType)) {760return true;761}762763await this._extensionService.activateByEvent(`onChatSession:${chatViewType}`);764765const controller = this._itemControllers.get(chatViewType)!;766return !!controller;767}768769async canResolveChatSession(chatSessionResource: URI) {770await this._extensionService.whenInstalledExtensionsRegistered();771const resolvedType = this._resolveToPrimaryType(chatSessionResource.scheme) || chatSessionResource.scheme;772const contribution = this._contributions.get(resolvedType)?.contribution;773if (contribution && !this._isContributionAvailable(contribution)) {774return false;775}776777if (this._contentProviders.has(chatSessionResource.scheme)) {778return true;779}780781await this._extensionService.activateByEvent(`onChatSession:${chatSessionResource.scheme}`);782return this._contentProviders.has(chatSessionResource.scheme);783}784785private async tryActivateControllers(providersToResolve: readonly string[] | undefined): Promise<void> {786await Promise.all(this.getAllChatSessionContributions().map(async (contrib) => {787if (providersToResolve && !providersToResolve.includes(contrib.type)) {788return; // skip: not considered for resolving789}790791if (!await this.doActivateChatSessionItemController(contrib.type)) {792// We requested this provider but it is not available793if (providersToResolve?.includes(contrib.type)) {794this._logService.trace(`[ChatSessionsService] No enabled provider found for chat session type ${contrib.type}`);795}796}797}));798}799800public async getChatSessionItems(providersToResolve: readonly string[] | undefined, token: CancellationToken): Promise<Array<{ readonly chatSessionType: string; readonly items: readonly IChatSessionItem[] }>> {801// First, make sure contributed controller are active802await this.tryActivateControllers(providersToResolve);803804// Then actually resolve items for all active controllers805const results: Array<{ readonly chatSessionType: string; readonly items: readonly IChatSessionItem[] }> = [];806await Promise.all(Array.from(this._itemControllers).map(async ([chatSessionType, controllerEntry]) => {807const resolvedType = this._resolveToPrimaryType(chatSessionType) ?? chatSessionType;808if (providersToResolve && !providersToResolve.includes(resolvedType)) {809return; // skip: not considered for resolving810}811812try {813await controllerEntry.initialRefresh; // Ensure initial refresh is complete before accessing items814815const providerSessions = controllerEntry.controller.items;816this._logService.trace(`[ChatSessionsService] Resolved ${providerSessions.length} sessions for provider ${resolvedType}`);817results.push({ chatSessionType: resolvedType, items: providerSessions });818} catch (err) {819if (!isCancellationError(err)) {820// Log error but continue with other providers821this._logService.error(`[ChatSessionsService] Failed to resolve sessions for provider ${resolvedType}`, err);822}823}824}));825826return results;827}828829public async refreshChatSessionItems(providersToResolve: readonly string[] | undefined, token: CancellationToken): Promise<void> {830await this.tryActivateControllers(providersToResolve);831832await Promise.all(Array.from(this._itemControllers).map(async ([chatSessionType, controllerEntry]) => {833try {834await controllerEntry.controller.refresh(token);835} catch (err) {836if (!isCancellationError(err)) {837// Log error but continue with other providers838this._logService.error(`[ChatSessionsService] Failed to resolve sessions for provider ${chatSessionType}`, err);839}840}841}));842}843844registerChatSessionItemController(chatSessionType: string, controller: IChatSessionItemController): IDisposable {845const disposables = new DisposableStore();846847848// Register and trigger an initial refresh to populate the provider's items849const initialRefreshCts = disposables.add(new CancellationTokenSource());850this._itemControllers.set(chatSessionType, { controller, initialRefresh: controller.refresh(initialRefreshCts.token) });851this._onDidChangeItemsProviders.fire({ chatSessionType });852853disposables.add(controller.onDidChangeChatSessionItems(() => {854this._onDidChangeSessionItems.fire({ chatSessionType });855}));856857this.updateInProgressStatus(chatSessionType).catch(error => {858this._logService.warn(`Failed to update initial progress status for '${chatSessionType}':`, error);859});860861return {862dispose: () => {863initialRefreshCts.cancel();864disposables.dispose();865866const controller = this._itemControllers.get(chatSessionType);867if (controller) {868this._itemControllers.delete(chatSessionType);869this._onDidChangeItemsProviders.fire({ chatSessionType });870}871}872};873}874875registerChatSessionContentProvider(chatSessionType: string, provider: IChatSessionContentProvider): IDisposable {876if (this._contentProviders.has(chatSessionType)) {877throw new Error(`Content provider for ${chatSessionType} is already registered.`);878}879880this._contentProviders.set(chatSessionType, provider);881this._onDidChangeContentProviderSchemes.fire({ added: [chatSessionType], removed: [] });882883return {884dispose: () => {885this._contentProviders.delete(chatSessionType);886887this._onDidChangeContentProviderSchemes.fire({ added: [], removed: [chatSessionType] });888889// Remove all sessions that were created by this provider890for (const [key, session] of this._sessions) {891if (session.chatSessionType === chatSessionType) {892session.dispose();893this._sessions.delete(key);894}895}896}897};898}899900public registerChatModelChangeListeners(901chatService: IChatService,902chatSessionType: string,903onChange: () => void904): IDisposable {905const disposableStore = new DisposableStore();906const chatModelsICareAbout = chatService.chatModels.map(models =>907Array.from(models).filter((model: IChatModel) => model.sessionResource.scheme === chatSessionType)908);909910const listeners = new ResourceMap<IDisposable>();911const autoRunDisposable = autorunIterableDelta(912reader => chatModelsICareAbout.read(reader),913({ addedValues, removedValues }) => {914removedValues.forEach((removed) => {915const listener = listeners.get(removed.sessionResource);916if (listener) {917listeners.delete(removed.sessionResource);918listener.dispose();919}920});921addedValues.forEach((added) => {922const requestChangeListener = added.lastRequestObs.map(last => last?.response && observableSignalFromEvent('chatSessions.modelRequestChangeListener', last.response.onDidChange));923const modelChangeListener = observableSignalFromEvent('chatSessions.modelChangeListener', added.onDidChange);924listeners.set(added.sessionResource, autorun(reader => {925requestChangeListener.read(reader)?.read(reader);926modelChangeListener.read(reader);927onChange();928}));929});930}931);932disposableStore.add(toDisposable(() => {933for (const listener of listeners.values()) { listener.dispose(); }934}));935disposableStore.add(autoRunDisposable);936return disposableStore;937}938939940public getInProgressSessionDescription(chatModel: IChatModel): string | undefined {941const requests = chatModel.getRequests();942if (requests.length === 0) {943return undefined;944}945946// Get the last request to check its response status947const lastRequest = requests.at(-1);948const response = lastRequest?.response;949if (!response) {950return undefined;951}952953// If the response is complete, show Finished954if (response.isComplete) {955return undefined;956}957958// Get the response parts to find tool invocations and progress messages959const responseParts = response.response.value;960let description: string | IMarkdownString | undefined = '';961962for (let i = responseParts.length - 1; i >= 0; i--) {963const part = responseParts[i];964if (description) {965break;966}967968if (part.kind === 'confirmation' && typeof part.message === 'string') {969description = part.message;970} else if (part.kind === 'toolInvocation') {971const toolInvocation = part as IChatToolInvocation;972const state = toolInvocation.state.get();973description = toolInvocation.generatedTitle || toolInvocation.pastTenseMessage || toolInvocation.invocationMessage;974if (state.type === IChatToolInvocation.StateKind.WaitingForConfirmation) {975const confirmationTitle = state.confirmationMessages?.title;976const titleMessage = confirmationTitle && (typeof confirmationTitle === 'string'977? confirmationTitle978: confirmationTitle.value);979const descriptionValue = typeof description === 'string' ? description : description.value;980description = titleMessage ?? localize('chat.sessions.description.waitingForConfirmation', "Waiting for confirmation: {0}", descriptionValue);981}982} else if (part.kind === 'toolInvocationSerialized') {983description = part.invocationMessage;984} else if (part.kind === 'progressMessage') {985description = part.content;986} else if (part.kind === 'thinking') {987description = localize('chat.sessions.description.thinking', 'Thinking...');988}989}990991return description ? renderAsPlaintext(description, { useLinkFormatter: true }) : '';992}993994public async getOrCreateChatSession(sessionResource: URI, token: CancellationToken): Promise<IChatSession> {995const existingSessionData = this._sessions.get(sessionResource);996if (existingSessionData) {997return existingSessionData.session;998}9991000if (!(await raceCancellationError(this.canResolveChatSession(sessionResource), token))) {1001throw Error(`Can not find provider for ${sessionResource}`);1002}10031004const resolvedType = this._resolveToPrimaryType(sessionResource.scheme) || sessionResource.scheme;1005const provider = this._contentProviders.get(resolvedType);1006if (!provider) {1007throw Error(`Can not find provider for ${sessionResource}`);1008}10091010const session = await raceCancellationError(provider.provideChatSessionContent(sessionResource, token), token);1011const sessionData = new ContributedChatSessionData(session, sessionResource.scheme, sessionResource, session.options, resource => {1012sessionData.dispose();1013this._sessions.delete(resource);1014});10151016this._sessions.set(sessionResource, sessionData);10171018return session;1019}10201021public hasAnySessionOptions(sessionResource: URI): boolean {1022const session = this._sessions.get(sessionResource);1023return !!session && !!session.options && Object.keys(session.options).length > 0;1024}10251026public getSessionOption(sessionResource: URI, optionId: string): string | IChatSessionProviderOptionItem | undefined {1027const session = this._sessions.get(sessionResource);1028return session?.getOption(optionId);1029}10301031public setSessionOption(sessionResource: URI, optionId: string, value: string | IChatSessionProviderOptionItem): boolean {1032const session = this._sessions.get(sessionResource);1033return !!session?.setOption(optionId, value);1034}10351036/**1037* Store option groups for a session type1038*/1039public setOptionGroupsForSessionType(chatSessionType: string, handle: number, optionGroups?: IChatSessionProviderOptionGroup[]): void {1040if (optionGroups) {1041this._sessionTypeOptions.set(chatSessionType, optionGroups);1042} else {1043this._sessionTypeOptions.delete(chatSessionType);1044}1045this._onDidChangeOptionGroups.fire(chatSessionType);1046}10471048/**1049* Get available option groups for a session type1050*/1051public getOptionGroupsForSessionType(chatSessionType: string): IChatSessionProviderOptionGroup[] | undefined {1052return this._sessionTypeOptions.get(chatSessionType);1053}10541055/**1056* Notify extension about option changes for a session1057*/1058public async notifySessionOptionsChange(sessionResource: URI, updates: ReadonlyArray<{ optionId: string; value: string | IChatSessionProviderOptionItem }>): Promise<void> {1059if (!updates.length) {1060return;1061}1062this._logService.trace(`[ChatSessionsService] notifySessionOptionsChange: starting for ${sessionResource}, ${updates.length} update(s): [${updates.map(u => u.optionId).join(', ')}]`);1063// Fire event to notify MainThreadChatSessions (which forwards to extension host)1064// Uses fireAsync to properly await async listener work via waitUntil pattern1065await this._onRequestNotifyExtension.fireAsync({ sessionResource, updates }, CancellationToken.None);1066this._logService.trace(`[ChatSessionsService] notifySessionOptionsChange: fireAsync completed for ${sessionResource}`);1067for (const u of updates) {1068this.setSessionOption(sessionResource, u.optionId, u.value);1069}1070this._onDidChangeSessionOptions.fire(sessionResource);1071this._logService.trace(`[ChatSessionsService] notifySessionOptionsChange: finished for ${sessionResource}`);1072}10731074/**1075* Get the icon for a specific session type1076*/1077public getIconForSessionType(chatSessionType: string): ThemeIcon | URI | undefined {1078const sessionTypeIcon = this._sessionTypeIcons.get(chatSessionType);10791080if (ThemeIcon.isThemeIcon(sessionTypeIcon)) {1081return sessionTypeIcon;1082}10831084if (isDark(this._themeService.getColorTheme().type)) {1085return sessionTypeIcon?.dark;1086} else {1087return sessionTypeIcon?.light;1088}1089}10901091/**1092* Get the welcome title for a specific session type1093*/1094public getWelcomeTitleForSessionType(chatSessionType: string): string | undefined {1095return this._sessionTypeWelcomeTitles.get(chatSessionType);1096}10971098/**1099* Get the welcome message for a specific session type1100*/1101public getWelcomeMessageForSessionType(chatSessionType: string): string | undefined {1102return this._sessionTypeWelcomeMessages.get(chatSessionType);1103}11041105/**1106* Get the input placeholder for a specific session type1107*/1108public getInputPlaceholderForSessionType(chatSessionType: string): string | undefined {1109return this._sessionTypeInputPlaceholders.get(chatSessionType);1110}11111112/**1113* Get the capabilities for a specific session type1114*/1115public getCapabilitiesForSessionType(chatSessionType: string): IChatAgentAttachmentCapabilities | undefined {1116const contribution = this._contributions.get(chatSessionType)?.contribution;1117return contribution?.capabilities;1118}11191120/**1121* Get the customAgentTarget for a specific session type.1122* When set, the mode picker should show filtered custom agents matching this target.1123*/1124public getCustomAgentTargetForSessionType(chatSessionType: string): Target {1125const contribution = this._contributions.get(chatSessionType)?.contribution;1126return contribution?.customAgentTarget ?? Target.Undefined;1127}11281129public getContentProviderSchemes(): string[] {1130return Array.from(this._contentProviders.keys());1131}1132}11331134registerSingleton(IChatSessionsService, ChatSessionsService, InstantiationType.Delayed);11351136function registerNewSessionInPlaceAction(type: string, displayName: string): IDisposable {1137return registerAction2(class NewChatSessionInPlaceAction extends Action2 {1138constructor() {1139super({1140id: `workbench.action.chat.openNewChatSessionInPlace.${type}`,1141title: localize2('interactiveSession.openNewChatSessionInPlace', "New {0}", displayName),1142category: CHAT_CATEGORY,1143f1: false,1144precondition: ChatContextKeys.enabled,1145});1146}11471148// Expected args: [chatSessionPosition: 'sidebar' | 'editor']1149async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {1150if (args.length === 0) {1151throw new BugIndicatingError('Expected chat session position argument');1152}11531154const chatSessionPosition = args[0];1155if (chatSessionPosition !== ChatSessionPosition.Sidebar && chatSessionPosition !== ChatSessionPosition.Editor) {1156throw new BugIndicatingError(`Invalid chat session position argument: ${chatSessionPosition}`);1157}11581159await openChatSession(accessor, { type: type, displayName: localize('chat', "Chat"), position: chatSessionPosition, replaceEditor: true });1160}1161});1162}11631164function registerNewSessionExternalAction(type: string, displayName: string, commandId: string): IDisposable {1165return registerAction2(class NewChatSessionExternalAction extends Action2 {1166constructor() {1167super({1168id: `workbench.action.chat.openNewChatSessionExternal.${type}`,1169title: localize2('interactiveSession.openNewChatSessionExternal', "New {0}", displayName),1170category: CHAT_CATEGORY,1171f1: false,1172precondition: ChatContextKeys.enabled,1173});1174}1175async run(accessor: ServicesAccessor): Promise<void> {1176const commandService = accessor.get(ICommandService);1177await commandService.executeCommand(commandId);1178}1179});1180}11811182export enum ChatSessionPosition {1183Editor = 'editor',1184Sidebar = 'sidebar'1185}11861187type NewChatSessionSendOptions = {1188readonly prompt: string;1189readonly attachedContext?: IChatRequestVariableEntry[];1190};11911192export type NewChatSessionOpenOptions = {1193readonly type: string;1194readonly position: ChatSessionPosition;1195readonly displayName: string;1196readonly chatResource?: UriComponents;1197readonly replaceEditor?: boolean;1198};11991200async function openChatSession(accessor: ServicesAccessor, openOptions: NewChatSessionOpenOptions, chatSendOptions?: NewChatSessionSendOptions): Promise<void> {1201const viewsService = accessor.get(IViewsService);1202const chatService = accessor.get(IChatService);1203const logService = accessor.get(ILogService);1204const editorGroupService = accessor.get(IEditorGroupsService);1205const editorService = accessor.get(IEditorService);12061207// Determine resource to open1208const resource = getResourceForNewChatSession(openOptions);12091210// Open chat session1211try {1212switch (openOptions.position) {1213case ChatSessionPosition.Sidebar: {1214const view = await viewsService.openView(ChatViewId) as ChatViewPane;1215if (openOptions.type === AgentSessionProviders.Local) {1216await view.widget.clear();1217} else {1218await view.loadSession(resource);1219}1220view.focus();1221break;1222}1223case ChatSessionPosition.Editor: {1224const options: IChatEditorOptions = {1225override: ChatEditorInput.EditorID,1226pinned: true,1227title: {1228fallback: localize('chatEditorContributionName', "{0}", openOptions.displayName),1229}1230};1231if (openOptions.replaceEditor) {1232// TODO: Do not rely on active editor1233const activeEditor = editorGroupService.activeGroup.activeEditor;1234if (!activeEditor || !(activeEditor instanceof ChatEditorInput)) {1235throw new Error('No active chat editor to replace');1236}1237await editorService.replaceEditors([{ editor: activeEditor, replacement: { resource, options } }], editorGroupService.activeGroup);1238} else {1239await editorService.openEditor({ resource, options });1240}1241break;1242}1243default: assertNever(openOptions.position, `Unknown chat session position: ${openOptions.position}`);1244}1245} catch (e) {1246logService.error(`Failed to open '${openOptions.type}' chat session with openOptions: ${JSON.stringify(openOptions)}`, e);1247return;1248}12491250// Send initial prompt if provided1251if (chatSendOptions) {1252try {1253await chatService.sendRequest(resource, chatSendOptions.prompt, { agentIdSilent: openOptions.type, attachedContext: chatSendOptions.attachedContext });1254} catch (e) {1255logService.error(`Failed to send initial request to '${openOptions.type}' chat session with contextOptions: ${JSON.stringify(chatSendOptions)}`, e);1256}1257}1258}12591260export function getResourceForNewChatSession(options: NewChatSessionOpenOptions): URI {1261if (options.chatResource) {1262return URI.revive(options.chatResource);1263}12641265const isRemoteSession = options.type !== AgentSessionProviders.Local;1266if (isRemoteSession) {1267return URI.from({1268scheme: options.type,1269path: `/untitled-${generateUuid()}`,1270});1271}12721273const isEditorPosition = options.position === ChatSessionPosition.Editor;1274if (isEditorPosition) {1275return ChatEditorInput.getNewEditorUri();1276}12771278return LocalChatSessionUri.forSession(generateUuid());1279}12801281function isAgentSessionProviderType(type: string): boolean {1282return Object.values(AgentSessionProviders).includes(type as AgentSessionProviders);1283}128412851286