diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index 6b45f162796..a6aa6ee451c 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -159,6 +159,18 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { return; } + if ('documents' in progress) { + const usedContext = { + documents: progress.documents.map(({ uri, version, ranges }) => ({ + uri: URI.revive(uri), + version, + ranges, + })), + }; + this._activeRequestProgressCallbacks.get(id)?.(usedContext); // FIXME@ulugbekna: is this a correct thing to do? + return; + } + this._activeRequestProgressCallbacks.get(id)?.(progress); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ea5ccab44fc..289f9a81eaf 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1223,7 +1223,18 @@ export interface IChatResponseProgressFileTreeData { children?: IChatResponseProgressFileTreeData[]; } -export type IChatResponseProgressDto = { content: string | IMarkdownString } | { requestId: string } | { placeholder: string } | { treeData: IChatResponseProgressFileTreeData }; +export type IDocumentContextDto = { + uri: UriComponents; + version: number; + ranges: IRange[]; +}; + +export type IChatResponseProgressDto = + | { content: string | IMarkdownString } + | { requestId: string } + | { placeholder: string } + | { treeData: IChatResponseProgressFileTreeData } + | { documents: IDocumentContextDto[] }; export interface MainThreadChatShape extends IDisposable { $registerChatProvider(handle: number, id: string): Promise; diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index 9aa2c625cc9..265994b5f96 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -236,6 +236,14 @@ export class ExtHostChat implements ExtHostChatShape { this._proxy.$acceptResponseProgress(handle, sessionId, { content: typeof progress.content === 'string' ? progress.content : typeConvert.MarkdownString.from(progress.content) }); + } else if ('documents' in progress) { + this._proxy.$acceptResponseProgress(handle, sessionId, { + documents: progress.documents.map(d => ({ + uri: d.uri, + version: d.version, + ranges: d.ranges.map(r => typeConvert.Range.from(r)) + })) + }); } else { this._proxy.$acceptResponseProgress(handle, sessionId, progress); } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 11eefe47dea..69ecd139299 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -11,7 +11,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { ILogService } from 'vs/platform/log/common/log'; import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; export interface IChatRequestModel { readonly id: string; @@ -32,11 +32,13 @@ export type ResponsePart = resolvedContent?: Promise< string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData } >; - }; + } + | IUsedContext; export interface IResponse { readonly value: (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData)[]; onDidChangeValue: Event; + usedContext: IUsedContext | undefined; updateContent(responsePart: ResponsePart, quiet?: boolean): void; asString(): string; } @@ -114,6 +116,11 @@ export class Response implements IResponse { return this._onDidChangeValue.event; } + private _usedContext: IUsedContext | undefined; + public get usedContext(): IUsedContext | undefined { + return this._usedContext; + } + // responseParts internally tracks all the response parts, including strings which are currently resolving, so that they can be updated when they do resolve private _responseParts: InternalResponsePart[]; // responseData externally presents the response parts with consolidated contiguous strings (including strings which were previously resolving) @@ -179,6 +186,8 @@ export class Response implements IResponse { } else if (isCompleteInteractiveProgressTreeData(responsePart)) { this._responseParts.push(responsePart); this._updateRepr(quiet); + } else if ('documents' in responsePart) { + this._usedContext = responsePart; } } @@ -590,6 +599,8 @@ export class ChatModel extends Disposable implements IChatModel { request.response.updateContent(progress.content, quiet); } else if ('placeholder' in progress || isCompleteInteractiveProgressTreeData(progress)) { request.response.updateContent(progress, quiet); + } else if ('documents' in progress) { + request.response.updateContent(progress); } else { request.setProviderRequestId(progress.requestId); request.response.setProviderResponseId(progress.requestId); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index b97c1a7c995..5c71168c493 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -8,6 +8,7 @@ import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { IRange } from 'vs/editor/common/core/range'; import { ProviderResult } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatModel, ChatModel, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -51,8 +52,22 @@ export interface IChatResponseProgressFileTreeData { children?: IChatResponseProgressFileTreeData[]; } +export type IDocumentContext = { + uri: URI; + version: number; + ranges: IRange[]; +}; + +export type IUsedContext = { + documents: IDocumentContext[]; +}; + export type IChatProgress = - { content: string | IMarkdownString } | { requestId: string } | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent: Promise }; + | { content: string | IMarkdownString } + | { requestId: string } + | { treeData: IChatResponseProgressFileTreeData } + | { placeholder: string; resolvedContent: Promise } + | IUsedContext; export interface IPersistedChatState { } export interface IChatProvider { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 75bed122f24..f5fe279835f 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -440,7 +440,6 @@ export class ChatService extends Disposable implements IChatService { const resolvedCommand = typeof message === 'string' && message.startsWith('/') ? await this.handleSlashCommand(model.sessionId, message) : message; - let gotProgress = false; const requestType = typeof message === 'string' ? (message.startsWith('/') ? 'slashCommand' : 'string') : @@ -453,6 +452,7 @@ export class ChatService extends Disposable implements IChatService { } gotProgress = true; + if ('content' in progress) { this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${typeof progress.content === 'string' ? progress.content.length : progress.content.value.length} chars`); } else if ('placeholder' in progress) { @@ -460,6 +460,8 @@ export class ChatService extends Disposable implements IChatService { } else if (isCompleteInteractiveProgressTreeData(progress)) { // This isn't exposed in API this.trace('sendRequest', `Provider returned tree data for session ${model.sessionId}, ${progress.treeData.label}`); + } else if ('documents' in progress) { + this.trace('sendRequest', `Provider returned documents for session ${model.sessionId}:\n ${JSON.stringify(progress.documents, null, '\t')}`); } else { this.trace('sendRequest', `Provider returned id for session ${model.sessionId}, ${progress.requestId}`); } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index 174991827c7..5c176ddfad8 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -138,7 +138,24 @@ declare module 'vscode' { treeData: FileTreeData; } - export type InteractiveProgress = InteractiveProgressContent | InteractiveProgressId | InteractiveProgressTask | InteractiveProgressFileTree; + // FIXME@ulugbekna: my reservation with this type is that + // we already have a type called `TextDocumentContext` above passed to inline chat providers + export interface DocumentContext { + uri: Uri; + version: number; + ranges: Range[]; // @ulugbekna: we're making this an array of ranges rather than array of `DocumentContext`s, which should be less costly + } + + export interface InteractiveProgressUsedContext { + documents: DocumentContext[]; + } + + export type InteractiveProgress = + | InteractiveProgressContent + | InteractiveProgressId + | InteractiveProgressTask + | InteractiveProgressFileTree + | InteractiveProgressUsedContext; export interface InteractiveResponseCommand { commandId: string;