diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index efdbe9232d7..28526e5ed17 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -9,13 +9,14 @@ import { revive } from '../../../base/common/marshalling.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { ILogService } from '../../../platform/log/common/log.js'; import { IChatProgress } from '../../contrib/chat/common/chatService.js'; -import { ChatSession, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; +import { ChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService } from '../../contrib/chat/common/chatSessionsService.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { ExtHostContext, MainContext, MainThreadChatSessionsShape } from '../common/extHost.protocol.js'; @extHostNamedCustomer(MainContext.MainThreadChatSessions) export class MainThreadChatSessions extends Disposable implements MainThreadChatSessionsShape { - private readonly _registrations = this._register(new DisposableMap()); + private readonly _itemProvidersRegistrations = this._register(new DisposableMap()); + private readonly _contentProvidersRegisterations = this._register(new DisposableMap()); constructor( private readonly _extHostContext: IExtHostContext, @@ -29,10 +30,9 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat // Register the provider handle - this tracks that a provider exists const provider: IChatSessionItemProvider = { chatSessionType, - provideChatSessionItems: (token) => this._provideChatSessionItems(handle, token), - provideChatSessionContent: (id, token) => this._provideChatSessionContent(handle, id, token) + provideChatSessionItems: (token) => this._provideChatSessionItems(handle, token) }; - this._registrations.set(handle, this._chatSessionsService.registerChatSessionItemProvider(handle, provider)); + this._itemProvidersRegistrations.set(handle, this._chatSessionsService.registerChatSessionItemProvider(handle, provider)); } private async _provideChatSessionItems(handle: number, token: CancellationToken): Promise { @@ -77,9 +77,21 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat } $unregisterChatSessionItemProvider(handle: number): void { - this._registrations.deleteAndDispose(handle); + this._itemProvidersRegistrations.deleteAndDispose(handle); } + $registerChatSessionContentProvider(handle: number, chatSessionType: string): void { + const provider: IChatSessionContentProvider = { + chatSessionType, + provideChatSessionContent: (id, token) => this._provideChatSessionContent(handle, id, token) + }; + + this._contentProvidersRegisterations.set(handle, this._chatSessionsService.registerChatSessionContentProvider(handle, provider)); + } + + $unregisterChatSessionContentProvider(handle: number): void { + this._contentProvidersRegisterations.deleteAndDispose(handle); + } private _reviveIconPath( iconPath: UriComponents | { light: UriComponents; dark: UriComponents } | { id: string; color?: { id: string } | undefined }) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 58b30f699e3..fa44e13a8d5 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1509,6 +1509,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatSessionsProvider'); return extHostChatSessions.registerChatSessionItemProvider(chatSessionType, provider); }, + registerChatSessionContentProvider(chatSessionType: string, provider: vscode.ChatSessionContentProvider) { + checkProposedApiEnabled(extension, 'chatSessionsProvider'); + return extHostChatSessions.registerChatSessionContentProvider(chatSessionType, provider); + } }; // namespace: lm diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index cecb03e3fc7..fc95ecf4a0e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -3117,6 +3117,8 @@ export interface ChatSessionDto { export interface MainThreadChatSessionsShape extends IDisposable { $registerChatSessionItemProvider(handle: number, chatSessionType: string): void; $unregisterChatSessionItemProvider(handle: number): void; + $registerChatSessionContentProvider(handle: number, chatSessionType: string): void; + $unregisterChatSessionContentProvider(handle: number): void; } export interface ExtHostChatSessionsShape { diff --git a/src/vs/workbench/api/common/extHostChatSessions.ts b/src/vs/workbench/api/common/extHostChatSessions.ts index 2beba56f11e..0ebeb2783bf 100644 --- a/src/vs/workbench/api/common/extHostChatSessions.ts +++ b/src/vs/workbench/api/common/extHostChatSessions.ts @@ -20,8 +20,10 @@ import { coalesce } from '../../../base/common/arrays.js'; export class ExtHostChatSessions extends Disposable implements ExtHostChatSessionsShape { private readonly _proxy: Proxied; - private readonly _statusProviders = new Map(); - private _nextHandle = 0; + private readonly _chatSessionItemProviders = new Map(); + private readonly _chatSessionContentProviders = new Map(); + private _nextChatSessionItemProviderHandle = 0; + private _nextChatSessionContentProviderHandle = 0; private _sessionMap: Map = new Map(); private static _sessionHandlePool = 0; @@ -52,23 +54,39 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio } registerChatSessionItemProvider(chatSessionType: string, provider: vscode.ChatSessionItemProvider): vscode.Disposable { - const handle = this._nextHandle++; + const handle = this._nextChatSessionItemProviderHandle++; const disposables = new DisposableStore(); - this._statusProviders.set(handle, { provider, disposable: disposables }); + this._chatSessionItemProviders.set(handle, { provider, disposable: disposables }); this._proxy.$registerChatSessionItemProvider(handle, chatSessionType); return { dispose: () => { - this._statusProviders.delete(handle); + this._chatSessionItemProviders.delete(handle); disposables.dispose(); this._proxy.$unregisterChatSessionItemProvider(handle); } }; } + registerChatSessionContentProvider(chatSessionType: string, provider: vscode.ChatSessionContentProvider) { + const handle = this._nextChatSessionContentProviderHandle++; + const disposables = new DisposableStore(); + + this._chatSessionContentProviders.set(handle, { provider, disposable: disposables }); + this._proxy.$registerChatSessionContentProvider(handle, chatSessionType); + + return { + dispose: () => { + this._chatSessionContentProviders.delete(handle); + disposables.dispose(); + this._proxy.$unregisterChatSessionContentProvider(handle); + } + }; + } + async $provideChatSessionItems(handle: number, token: vscode.CancellationToken): Promise { - const entry = this._statusProviders.get(handle); + const entry = this._chatSessionItemProviders.get(handle); if (!entry) { this._logService.error(`No provider registered for handle ${handle}`); return []; @@ -97,7 +115,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio } async $provideChatSessionContent(handle: number, id: string, token: CancellationToken): Promise { - const provider = this._statusProviders.get(handle)?.provider; + const provider = this._chatSessionContentProviders.get(handle)?.provider; if (!provider) { throw new Error(`No provider for handle ${handle}`); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 8e8d64c9492..c6886f3243b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -806,7 +806,7 @@ export function registerChatActions() { // TODO: This is a temporary change that will be replaced by opening a new chat instance const codingAgentItem = item as ICodingAgentPickerItem; if (codingAgentItem.session) { - await this.showChatSessionInEditor(codingAgentItem.session.provider, codingAgentItem.session.session, editorService, chatWidgetService); + await this.showChatSessionInEditor(chatSessionsService, codingAgentItem.session.provider, codingAgentItem.session.session, editorService, chatWidgetService); } } @@ -869,14 +869,14 @@ export function registerChatActions() { } } - private async showChatSessionInEditor(provider: IChatSessionItemProvider, session: IChatSessionItem, editorService: IEditorService, chatWidgetService: IChatWidgetService) { + private async showChatSessionInEditor(chatSessionService: IChatSessionsService, provider: IChatSessionItemProvider, session: IChatSessionItem, editorService: IEditorService, chatWidgetService: IChatWidgetService) { // Open the chat editor await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: {} satisfies IChatEditorOptions }); - const content = await provider.provideChatSessionContent(session.id, CancellationToken.None); + const content = await chatSessionService.provideChatSessionContent(provider.chatSessionType, session.id, CancellationToken.None); // Wait a bit for the editor to be ready, then get the widget setTimeout(() => { diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index f9413968ea6..10e2be51de7 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -32,21 +32,28 @@ export interface ChatSession { export interface IChatSessionItemProvider { readonly chatSessionType: string; provideChatSessionItems(token: CancellationToken): Promise; +} + +export interface IChatSessionContentProvider { + readonly chatSessionType: string; provideChatSessionContent(id: string, token: CancellationToken): Promise; } export interface IChatSessionsService { readonly _serviceBrand: undefined; registerChatSessionItemProvider(handle: number, provider: IChatSessionItemProvider): IDisposable; + registerChatSessionContentProvider(handle: number, provider: IChatSessionContentProvider): IDisposable; hasChatSessionItemProviders: boolean; provideChatSessionItems(token: CancellationToken): Promise<{ provider: IChatSessionItemProvider; session: IChatSessionItem }[]>; + provideChatSessionContent(chatSessionType: string, id: string, token: CancellationToken): Promise; } export const IChatSessionsService = createDecorator('chatSessionsService'); export class ChatSessionsService extends Disposable implements IChatSessionsService { readonly _serviceBrand: undefined; - private _providers: Map = new Map(); + private _itemsProviders: Map = new Map(); + private _contentProviders: Map = new Map(); constructor( @ILogService private readonly _logService: ILogService, @@ -58,7 +65,7 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ const results: { provider: IChatSessionItemProvider; session: IChatSessionItem }[] = []; // Iterate through all registered providers and collect their results - for (const [handle, provider] of this._providers) { + for (const [handle, provider] of this._itemsProviders) { try { if (provider.provideChatSessionItems) { const sessions = await provider.provideChatSessionItems(token); @@ -76,16 +83,35 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ } public registerChatSessionItemProvider(handle: number, provider: IChatSessionItemProvider): IDisposable { - this._providers.set(handle, provider); + this._itemsProviders.set(handle, provider); return { dispose: () => { - this._providers.delete(handle); + this._itemsProviders.delete(handle); } }; } + registerChatSessionContentProvider(handle: number, provider: IChatSessionContentProvider): IDisposable { + this._contentProviders.set(handle, provider); + return { + dispose: () => { + this._contentProviders.delete(handle); + } + }; + } + + public async provideChatSessionContent(chatSessionType: string, id: string, token: CancellationToken): Promise { + for (const provider of this._contentProviders.values()) { + if (provider.chatSessionType === chatSessionType) { + return provider.provideChatSessionContent(id, token); + } + } + + throw new Error(`No chat session content provider found for type: ${chatSessionType}`); + } + public get hasChatSessionItemProviders(): boolean { - return this._providers.size > 0; + return this._itemsProviders.size > 0; } } diff --git a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts index 0f6dfc8d903..ee800d6c6da 100644 --- a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts @@ -22,8 +22,6 @@ declare module 'vscode' { * Provides a list of chat sessions. */ provideChatSessionItems(token: CancellationToken): ProviderResult; - - provideChatSessionContent(id: string, token: CancellationToken): Thenable; } export interface ChatSessionItem { @@ -98,7 +96,23 @@ declare module 'vscode' { readonly requestHandler: ChatRequestHandler | undefined; } + export interface ChatSessionContentProvider { + /** + * Resolves a chat session into a full `ChatSession` object. + * + * @param uri The URI of the chat session to open. Uris as structured as `vscode-chat-session:/id` + * @param token A cancellation token that can be used to cancel the operation. + */ + provideChatSessionContent(id: string, token: CancellationToken): Thenable; + } + export namespace chat { export function registerChatSessionItemProvider(chatSessionType: string, provider: ChatSessionItemProvider): Disposable; + + /** + * @param chatSessionType A unique identifier for the chat session type. This is used to differentiate between different chat session providers. + */ + export function registerChatSessionContentProvider(chatSessionType: string, provider: ChatSessionContentProvider): Disposable; + } }