From 17523c000eef5c2197a369b1dda37574b9c63217 Mon Sep 17 00:00:00 2001 From: Alex Ross <38270282+alexr00@users.noreply.github.com> Date: Wed, 14 Jan 2026 11:18:42 +0100 Subject: [PATCH] Revert recent merges affecting chat session functionality (#287734) * Revert "Merge pull request #287668 from mjbvz/dev/mjbvz/eventual-sparrow" This reverts commit 81f7af4b9f20f2746d58e2ca28e4f686856df81b, reversing changes made to 85a14f966c3ad1b7dbe5c1275a235a57fac1b5d7. * Revert "Merge pull request #286642 from microsoft/dev/mjbvz/chat-session-item-controller" This reverts commit b39ecc3960ec91b2ce85518d44e3e8a323b7f4a6, reversing changes made to 45aced59351aa73fd1bc002f64ab4a0c13ba68a9. --- eslint.config.js | 1 - .../common/extensionsApiProposals.ts | 2 +- .../api/browser/mainThreadChatSessions.ts | 2 +- .../workbench/api/common/extHost.api.impl.ts | 4 - .../api/common/extHostChatSessions.ts | 278 +----------------- .../agentSessions/agentSessionsModel.ts | 52 ++-- .../agentSessions/agentSessionsPicker.ts | 2 +- .../agentSessions/agentSessionsViewer.ts | 13 +- .../chat/common/chatService/chatService.ts | 20 +- .../common/chatService/chatServiceImpl.ts | 12 +- .../chat/common/chatSessionsService.ts | 8 +- .../contrib/chat/common/model/chatModel.ts | 10 +- .../chat/common/model/chatSessionStore.ts | 7 +- .../agentSessionViewModel.test.ts | 52 ++-- .../agentSessionsDataSource.test.ts | 9 +- .../localAgentSessionsProvider.test.ts | 55 ++-- .../chat/test/common/model/mockChatModel.ts | 3 +- .../vscode.proposed.chatSessionsProvider.d.ts | 144 +-------- 18 files changed, 116 insertions(+), 558 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index b245f9466ac..37fb7fe63bf 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -899,7 +899,6 @@ export default tseslint.config( ], 'verbs': [ 'accept', - 'archive', 'change', 'close', 'collapse', diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 069ec076c42..2a0fdff9f24 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -69,7 +69,7 @@ const _allApiProposals = { }, chatSessionsProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts', - version: 4 + version: 3 }, chatStatusItem: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatStatusItem.d.ts', diff --git a/src/vs/workbench/api/browser/mainThreadChatSessions.ts b/src/vs/workbench/api/browser/mainThreadChatSessions.ts index 38de78caf4a..6a18a39b05f 100644 --- a/src/vs/workbench/api/browser/mainThreadChatSessions.ts +++ b/src/vs/workbench/api/browser/mainThreadChatSessions.ts @@ -382,6 +382,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat )); } + $onDidChangeChatSessionItems(handle: number): void { this._itemProvidersRegistrations.get(handle)?.onDidChangeItems.fire(); } @@ -490,7 +491,6 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat resource: uri, iconPath: session.iconPath, tooltip: session.tooltip ? this._reviveTooltip(session.tooltip) : undefined, - archived: session.archived, } satisfies IChatSessionItem; })); } catch (error) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 02eeb78c937..a77a0079ee0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1530,10 +1530,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatSessionsProvider'); return extHostChatSessions.registerChatSessionItemProvider(extension, chatSessionType, provider); }, - createChatSessionItemController: (chatSessionType: string, refreshHandler: () => Thenable) => { - checkProposedApiEnabled(extension, 'chatSessionsProvider'); - return extHostChatSessions.createChatSessionItemController(extension, chatSessionType, refreshHandler); - }, registerChatSessionContentProvider(scheme: string, provider: vscode.ChatSessionContentProvider, chatParticipant: vscode.ChatParticipant, capabilities?: vscode.ChatSessionCapabilities) { checkProposedApiEnabled(extension, 'chatSessionsProvider'); return extHostChatSessions.registerChatSessionContentProvider(extension, scheme, chatParticipant, provider, capabilities); diff --git a/src/vs/workbench/api/common/extHostChatSessions.ts b/src/vs/workbench/api/common/extHostChatSessions.ts index c4d34921e45..bc7366256c1 100644 --- a/src/vs/workbench/api/common/extHostChatSessions.ts +++ b/src/vs/workbench/api/common/extHostChatSessions.ts @@ -2,14 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* eslint-disable local/code-no-native-private */ import type * as vscode from 'vscode'; import { coalesce } from '../../../base/common/arrays.js'; import { CancellationToken, CancellationTokenSource } from '../../../base/common/cancellation.js'; import { CancellationError } from '../../../base/common/errors.js'; -import { Emitter } from '../../../base/common/event.js'; -import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../base/common/map.js'; import { MarshalledId } from '../../../base/common/marshallingIds.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; @@ -31,177 +29,6 @@ import { basename } from '../../../base/common/resources.js'; import { Diagnostic } from './extHostTypeConverters.js'; import { SymbolKind, SymbolKinds } from '../../../editor/common/languages.js'; -type ChatSessionTiming = vscode.ChatSessionItem['timing']; - -// #region Chat Session Item Controller - -class ChatSessionItemImpl implements vscode.ChatSessionItem { - #label: string; - #iconPath?: vscode.IconPath; - #description?: string | vscode.MarkdownString; - #badge?: string | vscode.MarkdownString; - #status?: vscode.ChatSessionStatus; - #archived?: boolean; - #tooltip?: string | vscode.MarkdownString; - #timing?: ChatSessionTiming; - #changes?: readonly vscode.ChatSessionChangedFile[] | { files: number; insertions: number; deletions: number }; - #onChanged: () => void; - - readonly resource: vscode.Uri; - - constructor(resource: vscode.Uri, label: string, onChanged: () => void) { - this.resource = resource; - this.#label = label; - this.#onChanged = onChanged; - } - - get label(): string { - return this.#label; - } - - set label(value: string) { - if (this.#label !== value) { - this.#label = value; - this.#onChanged(); - } - } - - get iconPath(): vscode.IconPath | undefined { - return this.#iconPath; - } - - set iconPath(value: vscode.IconPath | undefined) { - if (this.#iconPath !== value) { - this.#iconPath = value; - this.#onChanged(); - } - } - - get description(): string | vscode.MarkdownString | undefined { - return this.#description; - } - - set description(value: string | vscode.MarkdownString | undefined) { - if (this.#description !== value) { - this.#description = value; - this.#onChanged(); - } - } - - get badge(): string | vscode.MarkdownString | undefined { - return this.#badge; - } - - set badge(value: string | vscode.MarkdownString | undefined) { - if (this.#badge !== value) { - this.#badge = value; - this.#onChanged(); - } - } - - get status(): vscode.ChatSessionStatus | undefined { - return this.#status; - } - - set status(value: vscode.ChatSessionStatus | undefined) { - if (this.#status !== value) { - this.#status = value; - this.#onChanged(); - } - } - - get archived(): boolean | undefined { - return this.#archived; - } - - set archived(value: boolean | undefined) { - if (this.#archived !== value) { - this.#archived = value; - this.#onChanged(); - } - } - - get tooltip(): string | vscode.MarkdownString | undefined { - return this.#tooltip; - } - - set tooltip(value: string | vscode.MarkdownString | undefined) { - if (this.#tooltip !== value) { - this.#tooltip = value; - this.#onChanged(); - } - } - - get timing(): ChatSessionTiming | undefined { - return this.#timing; - } - - set timing(value: ChatSessionTiming | undefined) { - if (this.#timing !== value) { - this.#timing = value; - this.#onChanged(); - } - } - - get changes(): readonly vscode.ChatSessionChangedFile[] | { files: number; insertions: number; deletions: number } | undefined { - return this.#changes; - } - - set changes(value: readonly vscode.ChatSessionChangedFile[] | { files: number; insertions: number; deletions: number } | undefined) { - if (this.#changes !== value) { - this.#changes = value; - this.#onChanged(); - } - } -} - -class ChatSessionItemCollectionImpl implements vscode.ChatSessionItemCollection { - readonly #items = new ResourceMap(); - #onItemsChanged: () => void; - - constructor(onItemsChanged: () => void) { - this.#onItemsChanged = onItemsChanged; - } - - get size(): number { - return this.#items.size; - } - - replace(items: readonly vscode.ChatSessionItem[]): void { - this.#items.clear(); - for (const item of items) { - this.#items.set(item.resource, item); - } - this.#onItemsChanged(); - } - - forEach(callback: (item: vscode.ChatSessionItem, collection: vscode.ChatSessionItemCollection) => unknown, thisArg?: any): void { - for (const [_, item] of this.#items) { - callback.call(thisArg, item, this); - } - } - - add(item: vscode.ChatSessionItem): void { - this.#items.set(item.resource, item); - this.#onItemsChanged(); - } - - delete(resource: vscode.Uri): void { - this.#items.delete(resource); - this.#onItemsChanged(); - } - - get(resource: vscode.Uri): vscode.ChatSessionItem | undefined { - return this.#items.get(resource); - } - - [Symbol.iterator](): Iterator { - return this.#items.entries(); - } -} - -// #endregion - class ExtHostChatSession { private _stream: ChatAgentResponseStream; @@ -235,20 +62,13 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio readonly extension: IExtensionDescription; readonly disposable: DisposableStore; }>(); - private readonly _chatSessionItemControllers = new Map(); - private _nextChatSessionItemProviderHandle = 0; private readonly _chatSessionContentProviders = new Map(); - private _nextChatSessionItemControllerHandle = 0; + private _nextChatSessionItemProviderHandle = 0; private _nextChatSessionContentProviderHandle = 0; /** @@ -320,52 +140,6 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio }; } - - createChatSessionItemController(extension: IExtensionDescription, id: string, refreshHandler: () => Thenable): vscode.ChatSessionItemController { - const controllerHandle = this._nextChatSessionItemControllerHandle++; - const disposables = new DisposableStore(); - - // TODO: Currently not hooked up - const onDidArchiveChatSessionItem = disposables.add(new Emitter()); - - const collection = new ChatSessionItemCollectionImpl(() => { - this._proxy.$onDidChangeChatSessionItems(controllerHandle); - }); - - let isDisposed = false; - - const controller: vscode.ChatSessionItemController = { - id, - refreshHandler, - items: collection, - onDidArchiveChatSessionItem: onDidArchiveChatSessionItem.event, - createChatSessionItem: (resource: vscode.Uri, label: string) => { - if (isDisposed) { - throw new Error('ChatSessionItemController has been disposed'); - } - - return new ChatSessionItemImpl(resource, label, () => { - // TODO: Optimize to only update the specific item - this._proxy.$onDidChangeChatSessionItems(controllerHandle); - }); - }, - dispose: () => { - isDisposed = true; - disposables.dispose(); - }, - }; - - this._chatSessionItemControllers.set(controllerHandle, { controller, extension, disposable: disposables, sessionType: id }); - this._proxy.$registerChatSessionItemProvider(controllerHandle, id); - - disposables.add(toDisposable(() => { - this._chatSessionItemControllers.delete(controllerHandle); - this._proxy.$unregisterChatSessionItemProvider(controllerHandle); - })); - - return controller; - } - registerChatSessionContentProvider(extension: IExtensionDescription, chatSessionScheme: string, chatParticipant: vscode.ChatParticipant, provider: vscode.ChatSessionContentProvider, capabilities?: vscode.ChatSessionCapabilities): vscode.Disposable { const handle = this._nextChatSessionContentProviderHandle++; const disposables = new DisposableStore(); @@ -410,25 +184,17 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio } } - private convertChatSessionItem(sessionContent: vscode.ChatSessionItem): IChatSessionItem { - // Support both new (created, lastRequestStarted, lastRequestEnded) and old (startTime, endTime) timing properties - const timing = sessionContent.timing; - const created = timing?.created ?? timing?.startTime ?? 0; - const lastRequestStarted = timing?.lastRequestStarted ?? timing?.startTime; - const lastRequestEnded = timing?.lastRequestEnded ?? timing?.endTime; - + private convertChatSessionItem(sessionType: string, sessionContent: vscode.ChatSessionItem): IChatSessionItem { return { resource: sessionContent.resource, label: sessionContent.label, description: sessionContent.description ? typeConvert.MarkdownString.from(sessionContent.description) : undefined, badge: sessionContent.badge ? typeConvert.MarkdownString.from(sessionContent.badge) : undefined, status: this.convertChatSessionStatus(sessionContent.status), - archived: sessionContent.archived, tooltip: typeConvert.MarkdownString.fromStrict(sessionContent.tooltip), timing: { - created, - lastRequestStarted, - lastRequestEnded, + startTime: sessionContent.timing?.startTime ?? 0, + endTime: sessionContent.timing?.endTime }, changes: sessionContent.changes instanceof Array ? sessionContent.changes : @@ -441,35 +207,21 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio } async $provideChatSessionItems(handle: number, token: vscode.CancellationToken): Promise { - let items: vscode.ChatSessionItem[]; + const entry = this._chatSessionItemProviders.get(handle); + if (!entry) { + this._logService.error(`No provider registered for handle ${handle}`); + return []; + } - const controller = this._chatSessionItemControllers.get(handle); - if (controller) { - // Call the refresh handler to populate items - await controller.controller.refreshHandler(); - if (token.isCancellationRequested) { - return []; - } - - items = Array.from(controller.controller.items, x => x[1]); - } else { - - const itemProvider = this._chatSessionItemProviders.get(handle); - if (!itemProvider) { - this._logService.error(`No provider registered for handle ${handle}`); - return []; - } - - items = await itemProvider.provider.provideChatSessionItems(token) ?? []; - if (token.isCancellationRequested) { - return []; - } + const sessions = await entry.provider.provideChatSessionItems(token); + if (!sessions) { + return []; } const response: IChatSessionItem[] = []; - for (const sessionContent of items) { + for (const sessionContent of sessions) { this._sessionItems.set(sessionContent.resource, sessionContent); - response.push(this.convertChatSessionItem(sessionContent)); + response.push(this.convertChatSessionItem(entry.sessionType, sessionContent)); } return response; } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel.ts index 73776e50163..b579321fec1 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsModel.ts @@ -359,24 +359,19 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode ? { files: changes.files, insertions: changes.insertions, deletions: changes.deletions } : changes; - // Times: it is important to always provide timing information to track + // Times: it is important to always provide a start and end time to track // unread/read state for example. // If somehow the provider does not provide any, fallback to last known - let created = session.timing.created; - let lastRequestStarted = session.timing.lastRequestStarted; - let lastRequestEnded = session.timing.lastRequestEnded; - if (!created || !lastRequestEnded) { + let startTime = session.timing.startTime; + let endTime = session.timing.endTime; + if (!startTime || !endTime) { const existing = this._sessions.get(session.resource); - if (!created && existing?.timing.created) { - created = existing.timing.created; + if (!startTime && existing?.timing.startTime) { + startTime = existing.timing.startTime; } - if (!lastRequestEnded && existing?.timing.lastRequestEnded) { - lastRequestEnded = existing.timing.lastRequestEnded; - } - - if (!lastRequestStarted && existing?.timing.lastRequestStarted) { - lastRequestStarted = existing.timing.lastRequestStarted; + if (!endTime && existing?.timing.endTime) { + endTime = existing.timing.endTime; } } @@ -391,13 +386,7 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode tooltip: session.tooltip, status, archived: session.archived, - timing: { - created, - lastRequestStarted, - lastRequestEnded, - inProgressTime, - finishedOrFailedTime - }, + timing: { startTime, endTime, inProgressTime, finishedOrFailedTime }, changes: normalizedChanges, })); } @@ -465,7 +454,7 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode private isRead(session: IInternalAgentSessionData): boolean { const readDate = this.sessionStates.get(session.resource)?.read; - return (readDate ?? AgentSessionsModel.READ_STATE_INITIAL_DATE) >= (session.timing.lastRequestEnded ?? session.timing.lastRequestStarted ?? session.timing.created); + return (readDate ?? AgentSessionsModel.READ_STATE_INITIAL_DATE) >= (session.timing.endTime ?? session.timing.startTime); } private setRead(session: IInternalAgentSessionData, read: boolean): void { @@ -484,7 +473,7 @@ export class AgentSessionsModel extends Disposable implements IAgentSessionsMode //#region Sessions Cache -interface ISerializedAgentSession { +interface ISerializedAgentSession extends Omit { readonly providerType: string; readonly providerLabel: string; @@ -503,11 +492,7 @@ interface ISerializedAgentSession { readonly archived: boolean | undefined; readonly timing: { - readonly created: number; - readonly lastRequestStarted?: number; - readonly lastRequestEnded?: number; - // Old format for backward compatibility when reading - readonly startTime?: number; + readonly startTime: number; readonly endTime?: number; }; @@ -550,9 +535,8 @@ class AgentSessionsCache { archived: session.archived, timing: { - created: session.timing.created, - lastRequestStarted: session.timing.lastRequestStarted, - lastRequestEnded: session.timing.lastRequestEnded, + startTime: session.timing.startTime, + endTime: session.timing.endTime, }, changes: session.changes, @@ -569,7 +553,7 @@ class AgentSessionsCache { try { const cached = JSON.parse(sessionsCache) as ISerializedAgentSession[]; - return cached.map((session): IInternalAgentSessionData => ({ + return cached.map(session => ({ providerType: session.providerType, providerLabel: session.providerLabel, @@ -585,10 +569,8 @@ class AgentSessionsCache { archived: session.archived, timing: { - // Support loading both new and old cache formats - created: session.timing.created ?? session.timing.startTime ?? 0, - lastRequestStarted: session.timing.lastRequestStarted ?? session.timing.startTime, - lastRequestEnded: session.timing.lastRequestEnded ?? session.timing.endTime, + startTime: session.timing.startTime, + endTime: session.timing.endTime, }, changes: Array.isArray(session.changes) ? session.changes.map((change: IChatSessionFileChange) => ({ diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsPicker.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsPicker.ts index ba5bfac455d..cd91ba6fbdb 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsPicker.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsPicker.ts @@ -44,7 +44,7 @@ export const deleteButton: IQuickInputButton = { export function getSessionDescription(session: IAgentSession): string { const descriptionText = typeof session.description === 'string' ? session.description : session.description ? renderAsPlaintext(session.description) : undefined; - const timeAgo = fromNow(session.timing.lastRequestEnded ?? session.timing.lastRequestStarted ?? session.timing.created); + const timeAgo = fromNow(session.timing.endTime || session.timing.startTime); const descriptionParts = [descriptionText, session.providerLabel, timeAgo].filter(part => !!part); return descriptionParts.join(' • '); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts index 17c8d9f3a5a..f3d3e6e29cd 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts @@ -323,7 +323,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer= startOfToday) { todaySessions.push(session); } else if (sessionTime >= startOfYesterday) { @@ -827,9 +826,7 @@ export class AgentSessionsSorter implements ITreeSorter { } //Sort by end or start time (most recent first) - const timeA = sessionA.timing.lastRequestEnded ?? sessionA.timing.lastRequestStarted ?? sessionA.timing.created; - const timeB = sessionB.timing.lastRequestEnded ?? sessionB.timing.lastRequestStarted ?? sessionB.timing.created; - return timeB - timeA; + return (sessionB.timing.endTime || sessionB.timing.startTime) - (sessionA.timing.endTime || sessionA.timing.startTime); } } diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts index 6bd8e4f9060..b4f75cc832f 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts @@ -941,24 +941,8 @@ export interface IChatSessionStats { } export interface IChatSessionTiming { - /** - * Timestamp when the session was created in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - */ - created: number; - - /** - * Timestamp when the most recent request started in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - * - * Should be undefined if no requests have been made yet. - */ - lastRequestStarted: number | undefined; - - /** - * Timestamp when the most recent request completed in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - * - * Should be undefined if the most recent request is still in progress or if no requests have been made yet. - */ - lastRequestEnded: number | undefined; + startTime: number; + endTime?: number; } export const enum ResponseModelState { diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts index 97c2637ef07..e515c29b76d 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts @@ -377,11 +377,7 @@ export class ChatService extends Disposable implements IChatService { ...entry, sessionResource, // TODO@roblourens- missing for old data- normalize inside the store - timing: entry.timing ?? { - created: entry.lastMessageDate, - lastRequestStarted: undefined, - lastRequestEnded: entry.lastMessageDate, - }, + timing: entry.timing ?? { startTime: entry.lastMessageDate }, isActive: this._sessionModels.has(sessionResource), // TODO@roblourens- missing for old data- normalize inside the store lastResponseState: entry.lastResponseState ?? ResponseModelState.Complete, @@ -397,11 +393,7 @@ export class ChatService extends Disposable implements IChatService { ...metadata, sessionResource, // TODO@roblourens- missing for old data- normalize inside the store - timing: metadata.timing ?? { - created: metadata.lastMessageDate, - lastRequestStarted: undefined, - lastRequestEnded: metadata.lastMessageDate, - }, + timing: metadata.timing ?? { startTime: metadata.lastMessageDate }, isActive: this._sessionModels.has(sessionResource), // TODO@roblourens- missing for old data- normalize inside the store lastResponseState: metadata.lastResponseState ?? ResponseModelState.Complete, diff --git a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts index 94126a5ffcf..76a9b348698 100644 --- a/src/vs/workbench/contrib/chat/common/chatSessionsService.ts +++ b/src/vs/workbench/contrib/chat/common/chatSessionsService.ts @@ -14,7 +14,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from './participants/chatAgents.js'; import { IChatEditingSession } from './editing/chatEditingService.js'; import { IChatModel, IChatRequestVariableData, ISerializableChatModelInputState } from './model/chatModel.js'; -import { IChatProgress, IChatService, IChatSessionTiming } from './chatService/chatService.js'; +import { IChatProgress, IChatService } from './chatService/chatService.js'; export const enum ChatSessionStatus { Failed = 0, @@ -73,7 +73,6 @@ export interface IChatSessionsExtensionPoint { readonly commands?: IChatSessionCommandContribution[]; readonly canDelegate?: boolean; } - export interface IChatSessionItem { resource: URI; label: string; @@ -82,7 +81,10 @@ export interface IChatSessionItem { description?: string | IMarkdownString; status?: ChatSessionStatus; tooltip?: string | IMarkdownString; - timing: IChatSessionTiming; + timing: { + startTime: number; + endTime?: number; + }; changes?: { files: number; insertions: number; diff --git a/src/vs/workbench/contrib/chat/common/model/chatModel.ts b/src/vs/workbench/contrib/chat/common/model/chatModel.ts index eb25096e3c9..6115e54dbbe 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatModel.ts @@ -1801,14 +1801,10 @@ export class ChatModel extends Disposable implements IChatModel { } get timing(): IChatSessionTiming { - const lastRequest = this._requests.at(-1); - const lastResponse = lastRequest?.response; - const lastRequestStarted = lastRequest?.timestamp; - const lastRequestEnded = lastResponse?.completedAt ?? lastResponse?.timestamp; + const lastResponse = this._requests.at(-1)?.response; return { - created: this._timestamp, - lastRequestStarted, - lastRequestEnded, + startTime: this._timestamp, + endTime: lastResponse?.completedAt ?? lastResponse?.timestamp }; } diff --git a/src/vs/workbench/contrib/chat/common/model/chatSessionStore.ts b/src/vs/workbench/contrib/chat/common/model/chatSessionStore.ts index 1465a8d5c54..63ac4c99c21 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatSessionStore.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatSessionStore.ts @@ -665,13 +665,12 @@ async function getSessionMetadata(session: ChatModel | ISerializableChatData): P session.lastMessageDate : session.requests.at(-1)?.timestamp ?? session.creationDate; - const timing: IChatSessionTiming = session instanceof ChatModel ? + const timing = session instanceof ChatModel ? session.timing : // session is only ISerializableChatData in the old pre-fs storage data migration scenario { - created: session.creationDate, - lastRequestStarted: session.requests.at(-1)?.timestamp, - lastRequestEnded: lastMessageDate, + startTime: session.creationDate, + endTime: lastMessageDate }; return { diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionViewModel.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionViewModel.test.ts index bacf032abd9..114f666d135 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionViewModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionViewModel.test.ts @@ -176,8 +176,8 @@ suite('Agent Sessions', () => { test('should handle session with all properties', async () => { return runWithFakedTimers({}, async () => { - const created = Date.now(); - const lastRequestEnded = created + 1000; + const startTime = Date.now(); + const endTime = startTime + 1000; const provider: IChatSessionItemProvider = { chatSessionType: 'test-type', @@ -190,8 +190,8 @@ suite('Agent Sessions', () => { status: ChatSessionStatus.Completed, tooltip: 'Session tooltip', iconPath: ThemeIcon.fromId('check'), - timing: { created, lastRequestStarted: created, lastRequestEnded }, - changes: { files: 1, insertions: 10, deletions: 5 } + timing: { startTime, endTime }, + changes: { files: 1, insertions: 10, deletions: 5, details: [] } } ] }; @@ -210,8 +210,8 @@ suite('Agent Sessions', () => { assert.strictEqual(session.description.value, '**Bold** description'); } assert.strictEqual(session.status, ChatSessionStatus.Completed); - assert.strictEqual(session.timing.created, created); - assert.strictEqual(session.timing.lastRequestEnded, lastRequestEnded); + assert.strictEqual(session.timing.startTime, startTime); + assert.strictEqual(session.timing.endTime, endTime); assert.deepStrictEqual(session.changes, { files: 1, insertions: 10, deletions: 5 }); }); }); @@ -1521,10 +1521,9 @@ suite('Agent Sessions', () => { test('should consider sessions before initial date as read by default', async () => { return runWithFakedTimers({}, async () => { // Session with timing before the READ_STATE_INITIAL_DATE (December 8, 2025) - const oldSessionTiming: IChatSessionItem['timing'] = { - created: Date.UTC(2025, 10 /* November */, 1), - lastRequestStarted: Date.UTC(2025, 10 /* November */, 1), - lastRequestEnded: Date.UTC(2025, 10 /* November */, 2), + const oldSessionTiming = { + startTime: Date.UTC(2025, 10 /* November */, 1), + endTime: Date.UTC(2025, 10 /* November */, 2), }; const provider: IChatSessionItemProvider = { @@ -1553,10 +1552,9 @@ suite('Agent Sessions', () => { test('should consider sessions after initial date as unread by default', async () => { return runWithFakedTimers({}, async () => { // Session with timing after the READ_STATE_INITIAL_DATE (December 8, 2025) - const newSessionTiming: IChatSessionItem['timing'] = { - created: Date.UTC(2025, 11 /* December */, 10), - lastRequestStarted: Date.UTC(2025, 11 /* December */, 10), - lastRequestEnded: Date.UTC(2025, 11 /* December */, 11), + const newSessionTiming = { + startTime: Date.UTC(2025, 11 /* December */, 10), + endTime: Date.UTC(2025, 11 /* December */, 11), }; const provider: IChatSessionItemProvider = { @@ -1585,10 +1583,9 @@ suite('Agent Sessions', () => { test('should use endTime for read state comparison when available', async () => { return runWithFakedTimers({}, async () => { // Session with startTime before initial date but endTime after - const sessionTiming: IChatSessionItem['timing'] = { - created: Date.UTC(2025, 10 /* November */, 1), - lastRequestStarted: Date.UTC(2025, 10 /* November */, 1), - lastRequestEnded: Date.UTC(2025, 11 /* December */, 10), + const sessionTiming = { + startTime: Date.UTC(2025, 10 /* November */, 1), + endTime: Date.UTC(2025, 11 /* December */, 10), }; const provider: IChatSessionItemProvider = { @@ -1609,7 +1606,7 @@ suite('Agent Sessions', () => { await viewModel.resolve(undefined); const session = viewModel.sessions[0]; - // Should use lastRequestEnded (December 10) which is after the initial date + // Should use endTime (December 10) which is after the initial date assert.strictEqual(session.isRead(), false); }); }); @@ -1617,10 +1614,8 @@ suite('Agent Sessions', () => { test('should use startTime for read state comparison when endTime is not available', async () => { return runWithFakedTimers({}, async () => { // Session with only startTime before initial date - const sessionTiming: IChatSessionItem['timing'] = { - created: Date.UTC(2025, 10 /* November */, 1), - lastRequestStarted: Date.UTC(2025, 10 /* November */, 1), - lastRequestEnded: undefined, + const sessionTiming = { + startTime: Date.UTC(2025, 10 /* November */, 1), }; const provider: IChatSessionItemProvider = { @@ -2059,15 +2054,8 @@ function makeSimpleSessionItem(id: string, overrides?: Partial }; } -function makeNewSessionTiming(options?: { - created?: number; - lastRequestStarted?: number | undefined; - lastRequestEnded?: number | undefined; -}): IChatSessionItem['timing'] { - const now = Date.now(); +function makeNewSessionTiming(): IChatSessionItem['timing'] { return { - created: options?.created ?? now, - lastRequestStarted: options?.lastRequestStarted, - lastRequestEnded: options?.lastRequestEnded, + startTime: Date.now(), }; } diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts index d551277757b..f29f8f83327 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts @@ -36,9 +36,8 @@ suite('AgentSessionsDataSource', () => { label: `Session ${overrides.id ?? 'default'}`, icon: Codicon.terminal, timing: { - created: overrides.startTime ?? now, - lastRequestEnded: undefined, - lastRequestStarted: undefined, + startTime: overrides.startTime ?? now, + endTime: overrides.endTime ?? now, }, isArchived: () => overrides.isArchived ?? false, setArchived: () => { }, @@ -74,8 +73,8 @@ suite('AgentSessionsDataSource', () => { return { compare: (a, b) => { // Sort by end time, most recent first - const aTime = a.timing.lastRequestEnded ?? a.timing.lastRequestStarted ?? a.timing.created; - const bTime = b.timing.lastRequestEnded ?? b.timing.lastRequestStarted ?? b.timing.created; + const aTime = a.timing.endTime || a.timing.startTime; + const bTime = b.timing.endTime || b.timing.startTime; return bTime - aTime; } }; diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/localAgentSessionsProvider.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/localAgentSessionsProvider.test.ts index 8b88eae7f82..7be0701efe2 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessions/localAgentSessionsProvider.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/localAgentSessionsProvider.test.ts @@ -18,24 +18,11 @@ import { LocalAgentsSessionsProvider } from '../../../browser/agentSessions/loca import { ModifiedFileEntryState } from '../../../common/editing/chatEditingService.js'; import { IChatModel, IChatRequestModel, IChatResponseModel } from '../../../common/model/chatModel.js'; import { IChatDetail, IChatService, IChatSessionStartOptions, ResponseModelState } from '../../../common/chatService/chatService.js'; -import { ChatSessionStatus, IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; +import { ChatSessionStatus, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js'; import { LocalChatSessionUri } from '../../../common/model/chatUri.js'; import { ChatAgentLocation } from '../../../common/constants.js'; import { MockChatSessionsService } from '../../common/mockChatSessionsService.js'; -function createTestTiming(options?: { - created?: number; - lastRequestStarted?: number | undefined; - lastRequestEnded?: number | undefined; -}): IChatSessionItem['timing'] { - const now = Date.now(); - return { - created: options?.created ?? now, - lastRequestStarted: options?.lastRequestStarted, - lastRequestEnded: options?.lastRequestEnded, - }; -} - class MockChatService implements IChatService { private readonly _chatModels: ISettableObservable> = observableValue('chatModels', []); readonly chatModels = this._chatModels; @@ -332,7 +319,7 @@ suite('LocalAgentsSessionsProvider', () => { title: 'Test Session', lastMessageDate: Date.now(), isActive: true, - timing: createTestTiming(), + timing: { startTime: 0, endTime: 1 }, lastResponseState: ResponseModelState.Complete }]); @@ -356,7 +343,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now() - 10000, isActive: false, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming() + timing: { startTime: 0, endTime: 1 } }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); @@ -382,7 +369,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming() + timing: { startTime: 0, endTime: 1 } }]); mockChatService.setHistorySessionItems([{ sessionResource, @@ -390,7 +377,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now() - 10000, isActive: false, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming() + timing: { startTime: 0, endTime: 1 } }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); @@ -418,7 +405,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming() + timing: { startTime: 0, endTime: 1 } }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); @@ -448,7 +435,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming(), + timing: { startTime: 0, endTime: 1 }, }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); @@ -477,7 +464,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming(), + timing: { startTime: 0, endTime: 1 }, }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); @@ -506,7 +493,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming(), + timing: { startTime: 0, endTime: 1 }, }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); @@ -550,7 +537,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming(), + timing: { startTime: 0, endTime: 1 }, stats: { added: 30, removed: 8, @@ -595,7 +582,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming() + timing: { startTime: 0, endTime: 1 } }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); @@ -606,7 +593,7 @@ suite('LocalAgentsSessionsProvider', () => { }); suite('Session Timing', () => { - test('should use model timestamp for created when model exists', async () => { + test('should use model timestamp for startTime when model exists', async () => { return runWithFakedTimers({}, async () => { const provider = createProvider(); @@ -625,16 +612,16 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming({ created: modelTimestamp }) + timing: { startTime: modelTimestamp } }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); assert.strictEqual(sessions.length, 1); - assert.strictEqual(sessions[0].timing.created, modelTimestamp); + assert.strictEqual(sessions[0].timing.startTime, modelTimestamp); }); }); - test('should use lastMessageDate for created when model does not exist', async () => { + test('should use lastMessageDate for startTime when model does not exist', async () => { return runWithFakedTimers({}, async () => { const provider = createProvider(); @@ -648,16 +635,16 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate, isActive: false, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming({ created: lastMessageDate }) + timing: { startTime: lastMessageDate } }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); assert.strictEqual(sessions.length, 1); - assert.strictEqual(sessions[0].timing.created, lastMessageDate); + assert.strictEqual(sessions[0].timing.startTime, lastMessageDate); }); }); - test('should set lastRequestEnded from last response completedAt', async () => { + test('should set endTime from last response completedAt', async () => { return runWithFakedTimers({}, async () => { const provider = createProvider(); @@ -677,12 +664,12 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming({ lastRequestEnded: completedAt }) + timing: { startTime: 0, endTime: completedAt } }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); assert.strictEqual(sessions.length, 1); - assert.strictEqual(sessions[0].timing.lastRequestEnded, completedAt); + assert.strictEqual(sessions[0].timing.endTime, completedAt); }); }); }); @@ -705,7 +692,7 @@ suite('LocalAgentsSessionsProvider', () => { lastMessageDate: Date.now(), isActive: true, lastResponseState: ResponseModelState.Complete, - timing: createTestTiming() + timing: { startTime: 0, endTime: 1 } }]); const sessions = await provider.provideChatSessionItems(CancellationToken.None); diff --git a/src/vs/workbench/contrib/chat/test/common/model/mockChatModel.ts b/src/vs/workbench/contrib/chat/test/common/model/mockChatModel.ts index 026c88b2fa5..4cced4a16c4 100644 --- a/src/vs/workbench/contrib/chat/test/common/model/mockChatModel.ts +++ b/src/vs/workbench/contrib/chat/test/common/model/mockChatModel.ts @@ -10,14 +10,13 @@ import { URI } from '../../../../../../base/common/uri.js'; import { IChatEditingSession } from '../../../common/editing/chatEditingService.js'; import { IChatChangeEvent, IChatModel, IChatRequestModel, IChatRequestNeedsInputInfo, IExportableChatData, IExportableRepoData, IInputModel, ISerializableChatData } from '../../../common/model/chatModel.js'; import { ChatAgentLocation } from '../../../common/constants.js'; -import { IChatSessionTiming } from '../../../common/chatService/chatService.js'; export class MockChatModel extends Disposable implements IChatModel { readonly onDidDispose = this._register(new Emitter()).event; readonly onDidChange = this._register(new Emitter()).event; sessionId = ''; readonly timestamp = 0; - readonly timing: IChatSessionTiming = { created: Date.now(), lastRequestStarted: undefined, lastRequestEnded: undefined }; + readonly timing = { startTime: 0 }; readonly initialLocation = ChatAgentLocation.Chat; readonly title = ''; readonly hasCustomTitle = false; diff --git a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts index c1cbdf9c715..ac6ade0f413 100644 --- a/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 4 +// version: 3 declare module 'vscode' { /** @@ -26,25 +26,6 @@ declare module 'vscode' { InProgress = 2 } - export namespace chat { - /** - * Registers a new {@link ChatSessionItemProvider chat session item provider}. - * - * To use this, also make sure to also add `chatSessions` contribution in the `package.json`. - * - * @param chatSessionType The type of chat session the provider is for. - * @param provider The provider to register. - * - * @returns A disposable that unregisters the provider when disposed. - */ - export function registerChatSessionItemProvider(chatSessionType: string, provider: ChatSessionItemProvider): Disposable; - - /** - * Creates a new {@link ChatSessionItemController chat session item controller} with the given unique identifier. - */ - export function createChatSessionItemController(id: string, refreshHandler: () => Thenable): ChatSessionItemController; - } - /** * Provides a list of information about chat sessions. */ @@ -71,86 +52,6 @@ declare module 'vscode' { // #endregion } - /** - * Provides a list of information about chat sessions. - */ - export interface ChatSessionItemController { - readonly id: string; - - /** - * Unregisters the controller, disposing of its associated chat session items. - */ - dispose(): void; - - /** - * Managed collection of chat session items - */ - readonly items: ChatSessionItemCollection; - - /** - * Creates a new managed chat session item that be added to the collection. - */ - createChatSessionItem(resource: Uri, label: string): ChatSessionItem; - - /** - * Handler called to refresh the collection of chat session items. - * - * This is also called on first load to get the initial set of items. - */ - refreshHandler: () => Thenable; - - /** - * Fired when an item is archived by the editor - * - * TODO: expose archive state on the item too? - */ - readonly onDidArchiveChatSessionItem: Event; - } - - /** - * A collection of chat session items. It provides operations for managing and iterating over the items. - */ - export interface ChatSessionItemCollection extends Iterable { - /** - * Gets the number of items in the collection. - */ - readonly size: number; - - /** - * Replaces the items stored by the collection. - * @param items Items to store. - */ - replace(items: readonly ChatSessionItem[]): void; - - /** - * Iterate over each entry in this collection. - * - * @param callback Function to execute for each entry. - * @param thisArg The `this` context used when invoking the handler function. - */ - forEach(callback: (item: ChatSessionItem, collection: ChatSessionItemCollection) => unknown, thisArg?: any): void; - - /** - * Adds the chat session item to the collection. If an item with the same resource URI already - * exists, it'll be replaced. - * @param item Item to add. - */ - add(item: ChatSessionItem): void; - - /** - * Removes a single chat session item from the collection. - * @param resource Item resource to delete. - */ - delete(resource: Uri): void; - - /** - * Efficiently gets a chat session item by resource, if it exists, in the collection. - * @param resource Item resource to get. - * @returns The found item or undefined if it does not exist. - */ - get(resource: Uri): ChatSessionItem | undefined; - } - export interface ChatSessionItem { /** * The resource associated with the chat session. @@ -190,42 +91,15 @@ declare module 'vscode' { tooltip?: string | MarkdownString; /** - * Whether the chat session has been archived. - */ - archived?: boolean; - - /** - * Timing information for the chat session + * The times at which session started and ended */ timing?: { - /** - * Timestamp when the session was created in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - */ - created: number; - - /** - * Timestamp when the most recent request started in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - * - * Should be undefined if no requests have been made yet. - */ - lastRequestStarted?: number; - - /** - * Timestamp when the most recent request completed in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - * - * Should be undefined if the most recent request is still in progress or if no requests have been made yet. - */ - lastRequestEnded?: number; - /** * Session start timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - * @deprecated Use `created` and `lastRequestStarted` instead. */ - startTime?: number; - + startTime: number; /** * Session end timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. - * @deprecated Use `lastRequestEnded` instead. */ endTime?: number; }; @@ -394,6 +268,18 @@ declare module 'vscode' { } export namespace chat { + /** + * Registers a new {@link ChatSessionItemProvider chat session item provider}. + * + * To use this, also make sure to also add `chatSessions` contribution in the `package.json`. + * + * @param chatSessionType The type of chat session the provider is for. + * @param provider The provider to register. + * + * @returns A disposable that unregisters the provider when disposed. + */ + export function registerChatSessionItemProvider(chatSessionType: string, provider: ChatSessionItemProvider): Disposable; + /** * Registers a new {@link ChatSessionContentProvider chat session content provider}. *