mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-11 05:30:03 -05:00
Add newChatSessionItemHandler to let chat sessions create new session uris
This commit is contained in:
@@ -197,9 +197,22 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||
const contributedSession = chatSession?.contributedChatSession;
|
||||
let chatSessionContext: IChatSessionContextDto | undefined;
|
||||
if (contributedSession) {
|
||||
let chatSessionResource = contributedSession.chatSessionResource;
|
||||
let isUntitled = contributedSession.isUntitled;
|
||||
|
||||
// For new untitled sessions, invoke the controller's newChatSessionItemHandler
|
||||
// to let the extension create a proper session item before the first request.
|
||||
if (isUntitled) {
|
||||
const newItem = await this._chatSessionService.createNewChatSessionItem(contributedSession.chatSessionType, request, token);
|
||||
if (newItem) {
|
||||
chatSessionResource = newItem.resource;
|
||||
isUntitled = false;
|
||||
}
|
||||
}
|
||||
|
||||
chatSessionContext = {
|
||||
chatSessionResource: contributedSession.chatSessionResource,
|
||||
isUntitled: contributedSession.isUntitled,
|
||||
chatSessionResource,
|
||||
isUntitled,
|
||||
initialSessionOptions: contributedSession.initialSessionOptions?.map(o => ({
|
||||
optionId: o.optionId,
|
||||
value: typeof o.value === 'string' ? o.value : o.value.id,
|
||||
|
||||
@@ -347,6 +347,21 @@ class MainThreadChatSessionItemController extends Disposable implements IChatSes
|
||||
return this._proxy.$refreshChatSessionItems(this._handle, token);
|
||||
}
|
||||
|
||||
async newChatSessionItem(request: IChatAgentRequest, token: CancellationToken): Promise<IChatSessionItem | undefined> {
|
||||
const dto = await raceCancellationError(this._proxy.$newChatSessionItem(this._handle, request, token), token);
|
||||
if (!dto) {
|
||||
return undefined;
|
||||
}
|
||||
const item: IChatSessionItem = {
|
||||
...dto,
|
||||
resource: URI.revive(dto.resource),
|
||||
changes: revive(dto.changes),
|
||||
};
|
||||
this._items.set(item.resource, item);
|
||||
this._onDidChangeChatSessionItems.fire();
|
||||
return item;
|
||||
}
|
||||
|
||||
acceptChange(change: { readonly addedOrUpdated: readonly IChatSessionItem[]; readonly removed: readonly URI[] }): void {
|
||||
for (const item of change.addedOrUpdated) {
|
||||
this._items.set(item.resource, item);
|
||||
|
||||
@@ -3442,6 +3442,7 @@ export interface MainThreadChatSessionsShape extends IDisposable {
|
||||
export interface ExtHostChatSessionsShape {
|
||||
$refreshChatSessionItems(providerHandle: number, token: CancellationToken): Promise<void>;
|
||||
$onDidChangeChatSessionItemState(providerHandle: number, sessionResource: UriComponents, archived: boolean): void;
|
||||
$newChatSessionItem(controllerHandle: number, request: Dto<IChatAgentRequest>, token: CancellationToken): Promise<Dto<IChatSessionItem> | undefined>;
|
||||
|
||||
$provideChatSessionContent(providerHandle: number, sessionResource: UriComponents, token: CancellationToken): Promise<ChatSessionDto>;
|
||||
$interruptChatSessionActiveResponse(providerHandle: number, sessionResource: UriComponents, requestId: string): Promise<void>;
|
||||
|
||||
@@ -379,6 +379,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
|
||||
throw new Error('Not implemented for providers');
|
||||
},
|
||||
onDidChangeChatSessionItemState: onDidChangeChatSessionItemStateEmitter.event,
|
||||
newChatSessionItemHandler: undefined,
|
||||
dispose: () => {
|
||||
disposables.dispose();
|
||||
},
|
||||
@@ -422,6 +423,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
let isDisposed = false;
|
||||
let newChatSessionItemHandler: vscode.ChatSessionItemController['newChatSessionItemHandler'];
|
||||
const onDidChangeChatSessionItemStateEmitter = disposables.add(new Emitter<vscode.ChatSessionItem>());
|
||||
|
||||
const collection = new ChatSessionItemCollectionImpl(controllerHandle, this._proxy);
|
||||
@@ -451,6 +453,8 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
|
||||
});
|
||||
return item;
|
||||
},
|
||||
get newChatSessionItemHandler() { return newChatSessionItemHandler; },
|
||||
set newChatSessionItemHandler(handler: vscode.ChatSessionItemController['newChatSessionItemHandler']) { newChatSessionItemHandler = handler; },
|
||||
dispose: () => {
|
||||
isDisposed = true;
|
||||
disposables.dispose();
|
||||
@@ -768,6 +772,29 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
|
||||
await controllerData.controller.refreshHandler(token);
|
||||
}
|
||||
|
||||
async $newChatSessionItem(handle: number, request: IChatAgentRequest, token: CancellationToken): Promise<ReturnType<typeof typeConvert.ChatSessionItem.from> | undefined> {
|
||||
const controllerData = this._chatSessionItemControllers.get(handle);
|
||||
if (!controllerData) {
|
||||
this._logService.warn(`No controller found for handle ${handle}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const handler = controllerData.controller.newChatSessionItemHandler;
|
||||
if (!handler) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const model = await this.getModelForRequest(request, controllerData.extension);
|
||||
const chatRequest = typeConvert.ChatAgentRequest.to(request, undefined, model, [], new Map(), controllerData.extension, this._logService);
|
||||
|
||||
const item = await handler({ request: chatRequest }, token);
|
||||
if (!item) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return typeConvert.ChatSessionItem.from(item);
|
||||
}
|
||||
|
||||
$onDidChangeChatSessionItemState(controllerHandle: number, sessionResourceComponents: UriComponents, archived: boolean): void {
|
||||
const controllerData = this._chatSessionItemControllers.get(controllerHandle);
|
||||
if (!controllerData) {
|
||||
|
||||
@@ -62,6 +62,7 @@ suite('ObservableChatSession', function () {
|
||||
$disposeChatSessionContent: sinon.stub(),
|
||||
$refreshChatSessionItems: sinon.stub(),
|
||||
$onDidChangeChatSessionItemState: sinon.stub(),
|
||||
$newChatSessionItem: sinon.stub().resolves(undefined),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -359,6 +360,7 @@ suite('MainThreadChatSessions', function () {
|
||||
$disposeChatSessionContent: sinon.stub(),
|
||||
$refreshChatSessionItems: sinon.stub(),
|
||||
$onDidChangeChatSessionItemState: sinon.stub(),
|
||||
$newChatSessionItem: sinon.stub().resolves(undefined),
|
||||
};
|
||||
|
||||
const extHostContext = new class implements IExtHostContext {
|
||||
|
||||
@@ -29,7 +29,7 @@ import { IEditorService } from '../../../../services/editor/common/editorService
|
||||
import { IExtensionService, isProposedApiEnabled } from '../../../../services/extensions/common/extensions.js';
|
||||
import { ExtensionsRegistry } from '../../../../services/extensions/common/extensionsRegistry.js';
|
||||
import { ChatEditorInput } from '../widgetHosts/editor/chatEditorInput.js';
|
||||
import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentService } from '../../common/participants/chatAgents.js';
|
||||
import { IChatAgentAttachmentCapabilities, IChatAgentData, IChatAgentRequest, IChatAgentService } from '../../common/participants/chatAgents.js';
|
||||
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
|
||||
import { IChatSession, IChatSessionContentProvider, IChatSessionItem, IChatSessionItemController, IChatSessionOptionsWillNotifyExtensionEvent, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsExtensionPoint, IChatSessionsService, isSessionInProgressStatus } from '../../common/chatSessionsService.js';
|
||||
import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js';
|
||||
@@ -992,6 +992,16 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ
|
||||
return description ? renderAsPlaintext(description, { useLinkFormatter: true }) : '';
|
||||
}
|
||||
|
||||
async createNewChatSessionItem(chatSessionType: string, request: IChatAgentRequest, token: CancellationToken): Promise<IChatSessionItem | undefined> {
|
||||
const controllerData = this._itemControllers.get(chatSessionType);
|
||||
if (!controllerData) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await controllerData.initialRefresh;
|
||||
return controllerData.controller.newChatSessionItem?.(request, token);
|
||||
}
|
||||
|
||||
public async getOrCreateChatSession(sessionResource: URI, token: CancellationToken): Promise<IChatSession> {
|
||||
{
|
||||
const existingSessionData = this._sessions.get(sessionResource);
|
||||
|
||||
@@ -198,6 +198,8 @@ export interface IChatSessionItemController {
|
||||
get items(): readonly IChatSessionItem[];
|
||||
|
||||
refresh(token: CancellationToken): Promise<void>;
|
||||
|
||||
newChatSessionItem?(request: IChatAgentRequest, token: CancellationToken): Promise<IChatSessionItem | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,6 +295,12 @@ export interface IChatSessionsService {
|
||||
|
||||
registerChatModelChangeListeners(chatService: IChatService, chatSessionType: string, onChange: () => void): IDisposable;
|
||||
getInProgressSessionDescription(chatModel: IChatModel): string | undefined;
|
||||
|
||||
/**
|
||||
* Creates a new chat session item using the controller's newChatSessionItemHandler.
|
||||
* Returns undefined if the controller doesn't have a handler or if no controller is registered.
|
||||
*/
|
||||
createNewChatSessionItem(chatSessionType: string, request: IChatAgentRequest, token: CancellationToken): Promise<IChatSessionItem | undefined>;
|
||||
}
|
||||
|
||||
export function isSessionInProgressStatus(state: ChatSessionStatus): boolean {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { IDisposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { ResourceMap } from '../../../../../base/common/map.js';
|
||||
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { IChatAgentAttachmentCapabilities } from '../../common/participants/chatAgents.js';
|
||||
import { IChatAgentAttachmentCapabilities, IChatAgentRequest } from '../../common/participants/chatAgents.js';
|
||||
import { IChatModel } from '../../common/model/chatModel.js';
|
||||
import { IChatService } from '../../common/chatService/chatService.js';
|
||||
import { IChatSession, IChatSessionContentProvider, IChatSessionItemController, IChatSessionItem, IChatSessionOptionsWillNotifyExtensionEvent, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js';
|
||||
@@ -217,6 +217,10 @@ export class MockChatSessionsService implements IChatSessionsService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async createNewChatSessionItem(_chatSessionType: string, _request: IChatAgentRequest, _token: CancellationToken): Promise<IChatSessionItem | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerChatModelChangeListeners(chatService: IChatService, chatSessionType: string, onChange: () => void): IDisposable {
|
||||
// Store the emitter so tests can trigger it
|
||||
this.onChange = onChange;
|
||||
|
||||
@@ -92,6 +92,15 @@ declare module 'vscode' {
|
||||
*/
|
||||
export type ChatSessionItemControllerRefreshHandler = (token: CancellationToken) => Thenable<void>;
|
||||
|
||||
export interface ChatSessionItemControllerNewItemHandlerContext {
|
||||
readonly request: ChatRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension callback invoked when a new chat session is started.
|
||||
*/
|
||||
export type ChatSessionItemControllerNewItemHandler = (context: ChatSessionItemControllerNewItemHandlerContext, token: CancellationToken) => Thenable<ChatSessionItem>;
|
||||
|
||||
/**
|
||||
* Manages chat sessions for a specific chat session type
|
||||
*/
|
||||
@@ -120,6 +129,15 @@ declare module 'vscode' {
|
||||
*/
|
||||
readonly refreshHandler: ChatSessionItemControllerRefreshHandler;
|
||||
|
||||
/**
|
||||
* Invoked when a new chat session is started.
|
||||
*
|
||||
* This allows the controller to initialize the chat session item with information from the initial request.
|
||||
*
|
||||
* The returned chat session is added to the collection and shown in the UI.
|
||||
*/
|
||||
newChatSessionItemHandler?: ChatSessionItemControllerNewItemHandler;
|
||||
|
||||
/**
|
||||
* Fired when an item's archived state changes.
|
||||
*/
|
||||
@@ -363,6 +381,7 @@ declare module 'vscode' {
|
||||
*/
|
||||
// TODO: Should we introduce our own type for `ChatRequestHandler` since not all field apply to chat sessions?
|
||||
// TODO: Revisit this to align with code.
|
||||
// TODO: pass in options?
|
||||
readonly requestHandler: ChatRequestHandler | undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user