Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,6 @@ export default tseslint.config(
],
'verbs': [
'accept',
'archive',
'change',
'close',
'collapse',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadChatSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ export class MainThreadChatSessions extends Disposable implements MainThreadChat
));
}


$onDidChangeChatSessionItems(handle: number): void {
this._itemProvidersRegistrations.get(handle)?.onDidChangeItems.fire();
}
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 0 additions & 4 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1530,10 +1530,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'chatSessionsProvider');
return extHostChatSessions.registerChatSessionItemProvider(extension, chatSessionType, provider);
},
createChatSessionItemController: (chatSessionType: string, refreshHandler: () => Thenable<void>) => {
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);
Expand Down
278 changes: 15 additions & 263 deletions src/vs/workbench/api/common/extHostChatSessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<vscode.ChatSessionItem>();
#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<readonly [id: URI, chatSessionItem: vscode.ChatSessionItem]> {
return this.#items.entries();
}
}

// #endregion

class ExtHostChatSession {
private _stream: ChatAgentResponseStream;

Expand Down Expand Up @@ -235,20 +62,13 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
readonly extension: IExtensionDescription;
readonly disposable: DisposableStore;
}>();
private readonly _chatSessionItemControllers = new Map<number, {
readonly sessionType: string;
readonly controller: vscode.ChatSessionItemController;
readonly extension: IExtensionDescription;
readonly disposable: DisposableStore;
}>();
private _nextChatSessionItemProviderHandle = 0;
private readonly _chatSessionContentProviders = new Map<number, {
readonly provider: vscode.ChatSessionContentProvider;
readonly extension: IExtensionDescription;
readonly capabilities?: vscode.ChatSessionCapabilities;
readonly disposable: DisposableStore;
}>();
private _nextChatSessionItemControllerHandle = 0;
private _nextChatSessionItemProviderHandle = 0;
private _nextChatSessionContentProviderHandle = 0;

/**
Expand Down Expand Up @@ -320,52 +140,6 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
};
}


createChatSessionItemController(extension: IExtensionDescription, id: string, refreshHandler: () => Thenable<void>): vscode.ChatSessionItemController {
const controllerHandle = this._nextChatSessionItemControllerHandle++;
const disposables = new DisposableStore();

// TODO: Currently not hooked up
const onDidArchiveChatSessionItem = disposables.add(new Emitter<vscode.ChatSessionItem>());

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();
Expand Down Expand Up @@ -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 {
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sessionType parameter is not used in the method body and should be removed to clean up the code.

Suggested change
private convertChatSessionItem(sessionType: string, sessionContent: vscode.ChatSessionItem): IChatSessionItem {
private convertChatSessionItem(sessionType: string, sessionContent: vscode.ChatSessionItem): IChatSessionItem {
void sessionType;

Copilot uses AI. Check for mistakes.
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 :
Expand All @@ -441,35 +207,21 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
}

async $provideChatSessionItems(handle: number, token: vscode.CancellationToken): Promise<IChatSessionItem[]> {
let items: vscode.ChatSessionItem[];

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 [];
}
const entry = this._chatSessionItemProviders.get(handle);
if (!entry) {
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;
}
Expand Down
Loading
Loading