mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 12:19:20 +00:00
Add decorations for variables in rendered chat requests (#193745)
* Use parsed chat request in model and renderer * Fix using variables with the chat parser * Test snapshot * Add test * Update test snapshots
This commit is contained in:
@@ -62,8 +62,8 @@ import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/brows
|
|||||||
import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions';
|
import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions';
|
||||||
import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat';
|
import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat';
|
||||||
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
|
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups';
|
||||||
|
import { convertParsedRequestToMarkdown, walkTreeAndAnnotateResourceLinks } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer';
|
||||||
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
|
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
|
||||||
import { fixVariableReferences, walkTreeAndAnnotateResourceLinks } from 'vs/workbench/contrib/chat/browser/chatVariableReferenceRenderer';
|
|
||||||
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||||
import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
import { IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
@@ -314,7 +314,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
|||||||
} else if (isResponseVM(element)) {
|
} else if (isResponseVM(element)) {
|
||||||
this.basicRenderElement(element.response.value, element, index, templateData);
|
this.basicRenderElement(element.response.value, element, index, templateData);
|
||||||
} else if (isRequestVM(element)) {
|
} else if (isRequestVM(element)) {
|
||||||
this.basicRenderElement([new MarkdownString(element.messageText)], element, index, templateData);
|
const markdown = 'kind' in element.message ?
|
||||||
|
element.message.message :
|
||||||
|
convertParsedRequestToMarkdown(element.message);
|
||||||
|
this.basicRenderElement([new MarkdownString(markdown)], element, index, templateData);
|
||||||
} else {
|
} else {
|
||||||
this.renderWelcomeMessage(element, templateData);
|
this.renderWelcomeMessage(element, templateData);
|
||||||
}
|
}
|
||||||
@@ -608,11 +611,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
|||||||
});
|
});
|
||||||
|
|
||||||
const codeblocks: IChatCodeBlockInfo[] = [];
|
const codeblocks: IChatCodeBlockInfo[] = [];
|
||||||
|
|
||||||
if (isRequestVM(element)) {
|
|
||||||
markdown = fixVariableReferences(markdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = this.renderer.render(markdown, {
|
const result = this.renderer.render(markdown, {
|
||||||
fillInIncompleteTokens,
|
fillInIncompleteTokens,
|
||||||
codeBlockRendererSync: (languageId, text) => {
|
codeBlockRendererSync: (languageId, text) => {
|
||||||
|
|||||||
@@ -4,27 +4,33 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as dom from 'vs/base/browser/dom';
|
import * as dom from 'vs/base/browser/dom';
|
||||||
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
|
import { IParsedChatRequest, ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
|
|
||||||
const variableRefUrlPrefix = 'http://vscodeVar_';
|
const variableRefUrl = 'http://_vscodeDecoration_';
|
||||||
|
|
||||||
export function fixVariableReferences(markdown: IMarkdownString): IMarkdownString {
|
export function convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string {
|
||||||
const fixedMarkdownSource = markdown.value.replace(/\]\(values:(.*)/g, `](${variableRefUrlPrefix}_$1`);
|
let result = '';
|
||||||
return new MarkdownString(fixedMarkdownSource, { isTrusted: markdown.isTrusted, supportThemeIcons: markdown.supportThemeIcons, supportHtml: markdown.supportHtml });
|
for (const part of parsedRequest.parts) {
|
||||||
|
if (part instanceof ChatRequestTextPart) {
|
||||||
|
result += part.text;
|
||||||
|
} else {
|
||||||
|
result += `[${part.text}](${variableRefUrl})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function walkTreeAndAnnotateResourceLinks(element: HTMLElement): void {
|
export function walkTreeAndAnnotateResourceLinks(element: HTMLElement): void {
|
||||||
element.querySelectorAll('a').forEach(a => {
|
element.querySelectorAll('a').forEach(a => {
|
||||||
const href = a.getAttribute('data-href');
|
const href = a.getAttribute('data-href');
|
||||||
if (href) {
|
if (href) {
|
||||||
if (href.startsWith(variableRefUrlPrefix)) {
|
if (href.startsWith(variableRefUrl)) {
|
||||||
a.parentElement!.replaceChild(
|
a.parentElement!.replaceChild(
|
||||||
renderResourceWidget(a.textContent!),
|
renderResourceWidget(a.textContent!),
|
||||||
a);
|
a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
walkTreeAndAnnotateResourceLinks(a as HTMLElement);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ import { IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat
|
|||||||
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
||||||
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
|
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
|
||||||
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
|
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
|
|
||||||
export class QuickChatService extends Disposable implements IQuickChatService {
|
export class QuickChatService extends Disposable implements IQuickChatService {
|
||||||
@@ -271,7 +272,7 @@ class QuickChat extends Disposable {
|
|||||||
for (const request of this.model.getRequests()) {
|
for (const request of this.model.getRequests()) {
|
||||||
if (request.response?.response.value || request.response?.errorDetails) {
|
if (request.response?.response.value || request.response?.errorDetails) {
|
||||||
this.chatService.addCompleteRequest(widget.viewModel.sessionId,
|
this.chatService.addCompleteRequest(widget.viewModel.sessionId,
|
||||||
request.message as string,
|
request.message as IParsedChatRequest,
|
||||||
{
|
{
|
||||||
message: request.response.response.value,
|
message: request.response.response.value,
|
||||||
errorDetails: request.response.errorDetails,
|
errorDetails: request.response.errorDetails,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
|
|||||||
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
|
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
|
||||||
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors';
|
import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors';
|
||||||
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart } from '../../common/chatRequestParser';
|
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||||
import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
@@ -127,7 +127,7 @@ class InputEditorDecorations extends Disposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(viewModel.sessionId, inputValue);
|
const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(viewModel.sessionId, inputValue)).parts;
|
||||||
|
|
||||||
let placeholderDecoration: IDecorationOptions[] | undefined;
|
let placeholderDecoration: IDecorationOptions[] | undefined;
|
||||||
const agentPart = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
|
const agentPart = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
|
||||||
@@ -252,7 +252,7 @@ class SlashCommandCompletions extends Disposable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
|
const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts;
|
||||||
const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart);
|
const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart);
|
||||||
if (usedAgent) {
|
if (usedAgent) {
|
||||||
// No (classic) global slash commands when an agent is used
|
// No (classic) global slash commands when an agent is used
|
||||||
@@ -303,7 +303,7 @@ class AgentCompletions extends Disposable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
|
const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts;
|
||||||
const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart);
|
const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart);
|
||||||
if (usedAgent && !Range.containsPosition(usedAgent.editorRange, position)) {
|
if (usedAgent && !Range.containsPosition(usedAgent.editorRange, position)) {
|
||||||
// Only one agent allowed
|
// Only one agent allowed
|
||||||
@@ -340,7 +340,7 @@ class AgentCompletions extends Disposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue());
|
const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts;
|
||||||
const usedAgent = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
|
const usedAgent = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
|
||||||
if (!usedAgent) {
|
if (!usedAgent) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
|||||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
|
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
|
|
||||||
export interface IChatRequestModel {
|
export interface IChatRequestModel {
|
||||||
@@ -19,7 +20,7 @@ export interface IChatRequestModel {
|
|||||||
readonly username: string;
|
readonly username: string;
|
||||||
readonly avatarIconUri?: URI;
|
readonly avatarIconUri?: URI;
|
||||||
readonly session: IChatModel;
|
readonly session: IChatModel;
|
||||||
readonly message: string | IChatReplyFollowup;
|
readonly message: IParsedChatRequest | IChatReplyFollowup;
|
||||||
readonly response: IChatResponseModel | undefined;
|
readonly response: IChatResponseModel | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ export class ChatRequestModel implements IChatRequestModel {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly session: ChatModel,
|
public readonly session: ChatModel,
|
||||||
public readonly message: string | IChatReplyFollowup,
|
public readonly message: IParsedChatRequest | IChatReplyFollowup,
|
||||||
private _providerRequestId?: string) {
|
private _providerRequestId?: string) {
|
||||||
this._id = 'request_' + ChatRequestModel.nextId++;
|
this._id = 'request_' + ChatRequestModel.nextId++;
|
||||||
}
|
}
|
||||||
@@ -457,8 +458,9 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get title(): string {
|
get title(): string {
|
||||||
const firstRequestMessage = this._requests[0]?.message;
|
// const firstRequestMessage = this._requests[0]?.message;
|
||||||
const message = typeof firstRequestMessage === 'string' ? firstRequestMessage : firstRequestMessage?.message ?? '';
|
// const message = typeof firstRequestMessage === 'string' ? firstRequestMessage : firstRequestMessage?.message ?? '';
|
||||||
|
const message = '';
|
||||||
return message.split('\n')[0].substring(0, 50);
|
return message.split('\n')[0].substring(0, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,7 +468,6 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||||||
public readonly providerId: string,
|
public readonly providerId: string,
|
||||||
private readonly initialData: ISerializableChatData | IExportableChatData | undefined,
|
private readonly initialData: ISerializableChatData | IExportableChatData | undefined,
|
||||||
@ILogService private readonly logService: ILogService,
|
@ILogService private readonly logService: ILogService,
|
||||||
@IChatAgentService private readonly chatAgentService: IChatAgentService,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -492,14 +493,15 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||||||
this._welcomeMessage = new ChatWelcomeMessageModel(this, content);
|
this._welcomeMessage = new ChatWelcomeMessageModel(this, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
return requests.map((raw: ISerializableChatRequestData) => {
|
return [];
|
||||||
const request = new ChatRequestModel(this, raw.message, raw.providerRequestId);
|
// return requests.map((raw: ISerializableChatRequestData) => {
|
||||||
if (raw.response || raw.responseErrorDetails) {
|
// const request = new ChatRequestModel(this, raw.message, raw.providerRequestId);
|
||||||
const agent = raw.agent && this.chatAgentService.getAgents().find(a => a.id === raw.agent!.id); // TODO do something reasonable if this agent has disappeared since the last session
|
// if (raw.response || raw.responseErrorDetails) {
|
||||||
request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups);
|
// const agent = raw.agent && this.chatAgentService.getAgents().find(a => a.id === raw.agent!.id); // TODO do something reasonable if this agent has disappeared since the last session
|
||||||
}
|
// request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups);
|
||||||
return request;
|
// }
|
||||||
});
|
// return request;
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
startReinitialize(): void {
|
startReinitialize(): void {
|
||||||
@@ -543,7 +545,7 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||||||
return this._requests;
|
return this._requests;
|
||||||
}
|
}
|
||||||
|
|
||||||
addRequest(message: string | IChatReplyFollowup, chatAgent?: IChatAgentData): ChatRequestModel {
|
addRequest(message: IParsedChatRequest | IChatReplyFollowup, chatAgent?: IChatAgentData): ChatRequestModel {
|
||||||
if (!this._session) {
|
if (!this._session) {
|
||||||
throw new Error('addRequest: No session');
|
throw new Error('addRequest: No session');
|
||||||
}
|
}
|
||||||
@@ -649,7 +651,7 @@ export class ChatModel extends Disposable implements IChatModel {
|
|||||||
requests: this._requests.map((r): ISerializableChatRequestData => {
|
requests: this._requests.map((r): ISerializableChatRequestData => {
|
||||||
return {
|
return {
|
||||||
providerRequestId: r.providerRequestId,
|
providerRequestId: r.providerRequestId,
|
||||||
message: typeof r.message === 'string' ? r.message : r.message.message,
|
message: typeof r.message === 'string' ? r.message : '',
|
||||||
response: r.response ? r.response.response.value : undefined,
|
response: r.response ? r.response.response.value : undefined,
|
||||||
responseErrorDetails: r.response?.errorDetails,
|
responseErrorDetails: r.response?.errorDetails,
|
||||||
followups: r.response?.followups,
|
followups: r.response?.followups,
|
||||||
|
|||||||
73
src/vs/workbench/contrib/chat/common/chatParserTypes.ts
Normal file
73
src/vs/workbench/contrib/chat/common/chatParserTypes.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||||
|
import { IRange } from 'vs/editor/common/core/range';
|
||||||
|
import { IChatAgentData, IChatAgentCommand } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
|
import { ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
|
|
||||||
|
// These are in a separate file to avoid circular dependencies with the dependencies of the parser
|
||||||
|
|
||||||
|
export interface IParsedChatRequest {
|
||||||
|
readonly parts: ReadonlyArray<IParsedChatRequestPart>;
|
||||||
|
readonly text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IParsedChatRequestPart {
|
||||||
|
readonly range: OffsetRange;
|
||||||
|
readonly editorRange: IRange;
|
||||||
|
readonly text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO rename to tokens
|
||||||
|
|
||||||
|
export class ChatRequestTextPart implements IParsedChatRequestPart {
|
||||||
|
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An invocation of a static variable that can be resolved by the variable service
|
||||||
|
*/
|
||||||
|
export class ChatRequestVariablePart implements IParsedChatRequestPart {
|
||||||
|
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly variableName: string, readonly variableArg: string) { }
|
||||||
|
|
||||||
|
get text(): string {
|
||||||
|
const argPart = this.variableArg ? `:${this.variableArg}` : '';
|
||||||
|
return `@${this.variableName}${argPart}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An invocation of an agent that can be resolved by the agent service
|
||||||
|
*/
|
||||||
|
export class ChatRequestAgentPart implements IParsedChatRequestPart {
|
||||||
|
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { }
|
||||||
|
|
||||||
|
get text(): string {
|
||||||
|
return `@${this.agent.id}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An invocation of an agent's subcommand
|
||||||
|
*/
|
||||||
|
export class ChatRequestAgentSubcommandPart implements IParsedChatRequestPart {
|
||||||
|
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly command: IChatAgentCommand) { }
|
||||||
|
|
||||||
|
get text(): string {
|
||||||
|
return `/${this.command.name}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An invocation of a standalone slash command
|
||||||
|
*/
|
||||||
|
export class ChatRequestSlashCommandPart implements IParsedChatRequestPart {
|
||||||
|
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly slashCommand: ISlashCommand) { }
|
||||||
|
|
||||||
|
get text(): string {
|
||||||
|
return `/${this.slashCommand.command}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,13 +6,14 @@
|
|||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
|
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
|
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
|
|
||||||
const variableOrAgentReg = /^@([\w_\-]+)(:\d+)?(?=(\s|$))/i; // An @-variable with an optional numeric : arg (@response:2)
|
const variableOrAgentReg = /^@([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // An @-variable with an optional numeric : arg (@response:2)
|
||||||
const slashReg = /\/([\w_-]+)(?=(\s|$))/i; // A / command
|
const slashReg = /\/([\w_-]+)(?=(\s|$|\b))/i; // A / command
|
||||||
|
|
||||||
export class ChatRequestParser {
|
export class ChatRequestParser {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -21,7 +22,7 @@ export class ChatRequestParser {
|
|||||||
@IChatService private readonly chatService: IChatService,
|
@IChatService private readonly chatService: IChatService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async parseChatRequest(sessionId: string, message: string): Promise<IParsedChatRequestPart[]> {
|
async parseChatRequest(sessionId: string, message: string): Promise<IParsedChatRequest> {
|
||||||
const parts: IParsedChatRequestPart[] = [];
|
const parts: IParsedChatRequestPart[] = [];
|
||||||
|
|
||||||
let lineNumber = 1;
|
let lineNumber = 1;
|
||||||
@@ -68,7 +69,10 @@ export class ChatRequestParser {
|
|||||||
new Range(lastPart?.editorRange.endLineNumber ?? 1, lastPart?.editorRange.endColumn ?? 1, lineNumber, column),
|
new Range(lastPart?.editorRange.endLineNumber ?? 1, lastPart?.editorRange.endColumn ?? 1, lineNumber, column),
|
||||||
message.slice(lastPartEnd, message.length)));
|
message.slice(lastPartEnd, message.length)));
|
||||||
|
|
||||||
return parts;
|
return {
|
||||||
|
parts,
|
||||||
|
text: message,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryToParseVariableOrAgent(message: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestAgentPart | ChatRequestVariablePart | undefined {
|
private tryToParseVariableOrAgent(message: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestAgentPart | ChatRequestVariablePart | undefined {
|
||||||
@@ -131,40 +135,3 @@ export class ChatRequestParser {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IParsedChatRequestPart {
|
|
||||||
readonly range: OffsetRange;
|
|
||||||
readonly editorRange: IRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChatRequestTextPart implements IParsedChatRequestPart {
|
|
||||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string) { }
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* An invocation of a static variable that can be resolved by the variable service
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class ChatRequestVariablePart implements IParsedChatRequestPart {
|
|
||||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly variableName: string, readonly variableArg: string) { }
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* An invocation of an agent that can be resolved by the agent service
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class ChatRequestAgentPart implements IParsedChatRequestPart {
|
|
||||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { }
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* An invocation of an agent's subcommand
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class ChatRequestAgentSubcommandPart implements IParsedChatRequestPart {
|
|
||||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly command: IChatAgentCommand) { }
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* An invocation of a standalone slash command
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class ChatRequestSlashCommandPart implements IParsedChatRequestPart {
|
|
||||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly slashCommand: ISlashCommand) { }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { URI } from 'vs/base/common/uri';
|
|||||||
import { ProviderResult } from 'vs/editor/common/languages';
|
import { ProviderResult } from 'vs/editor/common/languages';
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IChatModel, ChatModel, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { IChatModel, ChatModel, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
|
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
|
|
||||||
export interface IChat {
|
export interface IChat {
|
||||||
@@ -227,7 +228,7 @@ export interface IChatService {
|
|||||||
getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]>;
|
getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]>;
|
||||||
clearSession(sessionId: string): void;
|
clearSession(sessionId: string): void;
|
||||||
addRequest(context: any): void;
|
addRequest(context: any): void;
|
||||||
addCompleteRequest(sessionId: string, message: string, response: IChatCompleteResponse): void;
|
addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, response: IChatCompleteResponse): void;
|
||||||
sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void;
|
sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void;
|
||||||
getHistory(): IChatDetail[];
|
getHistory(): IChatDetail[];
|
||||||
removeHistoryEntry(sessionId: string): void;
|
removeHistoryEntry(sessionId: string): void;
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ import { Progress } from 'vs/platform/progress/common/progress';
|
|||||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||||
import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||||
import { ChatModel, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData, isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { ChatModel, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData, isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
|
import { ChatRequestAgentPart, ChatRequestSlashCommandPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider';
|
import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider';
|
||||||
|
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||||
import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatRequest, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatRequest, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
||||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
@@ -431,19 +433,21 @@ export class ChatService extends Disposable implements IChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This method is only returning whether the request was accepted - don't block on the actual request
|
// This method is only returning whether the request was accepted - don't block on the actual request
|
||||||
return { responseCompletePromise: this._sendRequestAsync(model, provider, request, usedSlashCommand) };
|
return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request, usedSlashCommand) };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _sendRequestAsync(model: ChatModel, provider: IChatProvider, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise<void> {
|
private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise<void> {
|
||||||
const resolvedAgent = typeof message === 'string' ? this.resolveAgent(message) : undefined;
|
const parsedRequest = typeof message === 'string' ?
|
||||||
|
await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) :
|
||||||
|
message; // Handle the followup type along with the response
|
||||||
|
|
||||||
let request: ChatRequestModel;
|
let request: ChatRequestModel;
|
||||||
|
const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart);
|
||||||
const resolvedCommand = typeof message === 'string' && message.startsWith('/') ? await this.handleSlashCommand(model.sessionId, message) : message;
|
const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart);
|
||||||
|
|
||||||
|
|
||||||
let gotProgress = false;
|
let gotProgress = false;
|
||||||
const requestType = typeof message === 'string' ?
|
const requestType = typeof message === 'string' ?
|
||||||
(message.startsWith('/') ? 'slashCommand' : 'string') :
|
commandPart ? 'slashCommand' : 'string' :
|
||||||
'followup';
|
'followup';
|
||||||
|
|
||||||
const rawResponsePromise = createCancelablePromise<void>(async token => {
|
const rawResponsePromise = createCancelablePromise<void>(async token => {
|
||||||
@@ -491,8 +495,8 @@ export class ChatService extends Disposable implements IChatService {
|
|||||||
let rawResponse: IChatResponse | null | undefined;
|
let rawResponse: IChatResponse | null | undefined;
|
||||||
let slashCommandFollowups: IChatFollowup[] | void = [];
|
let slashCommandFollowups: IChatFollowup[] | void = [];
|
||||||
|
|
||||||
if (typeof message === 'string' && resolvedAgent) {
|
if (typeof message === 'string' && agentPart) {
|
||||||
request = model.addRequest(message);
|
request = model.addRequest(parsedRequest);
|
||||||
const history: IChatMessage[] = [];
|
const history: IChatMessage[] = [];
|
||||||
for (const request of model.getRequests()) {
|
for (const request of model.getRequests()) {
|
||||||
if (typeof request.message !== 'string' || !request.response) {
|
if (typeof request.message !== 'string' || !request.response) {
|
||||||
@@ -503,15 +507,15 @@ export class ChatService extends Disposable implements IChatService {
|
|||||||
history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value });
|
history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const agentResult = await this.chatAgentService.invokeAgent(resolvedAgent.id, message.substring(resolvedAgent.id.length + 1).trimStart(), new Progress<IChatSlashFragment>(p => {
|
const agentResult = await this.chatAgentService.invokeAgent(agentPart.agent.id, message.substring(agentPart.agent.id.length + 1).trimStart(), new Progress<IChatSlashFragment>(p => {
|
||||||
const { content } = p;
|
const { content } = p;
|
||||||
const data = isCompleteInteractiveProgressTreeData(content) ? content : { content };
|
const data = isCompleteInteractiveProgressTreeData(content) ? content : { content };
|
||||||
progressCallback(data);
|
progressCallback(data);
|
||||||
}), history, token);
|
}), history, token);
|
||||||
slashCommandFollowups = agentResult?.followUp;
|
slashCommandFollowups = agentResult?.followUp;
|
||||||
rawResponse = { session: model.session! };
|
rawResponse = { session: model.session! };
|
||||||
} else if ((typeof resolvedCommand === 'string' && typeof message === 'string' && this.chatSlashCommandService.hasCommand(resolvedCommand))) {
|
} else if (commandPart && typeof message === 'string' && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) {
|
||||||
request = model.addRequest(message);
|
request = model.addRequest(parsedRequest);
|
||||||
// contributed slash commands
|
// contributed slash commands
|
||||||
// TODO: spell this out in the UI
|
// TODO: spell this out in the UI
|
||||||
const history: IChatMessage[] = [];
|
const history: IChatMessage[] = [];
|
||||||
@@ -524,7 +528,7 @@ export class ChatService extends Disposable implements IChatService {
|
|||||||
history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value });
|
history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const commandResult = await this.chatSlashCommandService.executeCommand(resolvedCommand, message.substring(resolvedCommand.length + 1).trimStart(), new Progress<IChatSlashFragment>(p => {
|
const commandResult = await this.chatSlashCommandService.executeCommand(commandPart.slashCommand.command, message.substring(commandPart.slashCommand.command.length + 1).trimStart(), new Progress<IChatSlashFragment>(p => {
|
||||||
const { content } = p;
|
const { content } = p;
|
||||||
const data = isCompleteInteractiveProgressTreeData(content) ? content : { content };
|
const data = isCompleteInteractiveProgressTreeData(content) ? content : { content };
|
||||||
progressCallback(data);
|
progressCallback(data);
|
||||||
@@ -533,19 +537,18 @@ export class ChatService extends Disposable implements IChatService {
|
|||||||
rawResponse = { session: model.session! };
|
rawResponse = { session: model.session! };
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
request = model.addRequest(parsedRequest);
|
||||||
const requestProps: IChatRequest = {
|
const requestProps: IChatRequest = {
|
||||||
session: model.session!,
|
session: model.session!,
|
||||||
message: resolvedCommand,
|
message,
|
||||||
variables: {}
|
variables: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof requestProps.message === 'string') {
|
if ('parts' in parsedRequest) {
|
||||||
const varResult = await this.chatVariablesService.resolveVariables(requestProps.message, model, token);
|
const varResult = await this.chatVariablesService.resolveVariables(parsedRequest, model, token);
|
||||||
requestProps.variables = varResult.variables;
|
requestProps.variables = varResult.variables;
|
||||||
requestProps.message = varResult.prompt;
|
requestProps.message = varResult.prompt;
|
||||||
}
|
}
|
||||||
request = model.addRequest(requestProps.message);
|
|
||||||
|
|
||||||
rawResponse = await provider.provideReply(requestProps, progressCallback, token);
|
rawResponse = await provider.provideReply(requestProps, progressCallback, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,26 +616,6 @@ export class ChatService extends Disposable implements IChatService {
|
|||||||
provider.removeRequest?.(model.session!, requestId);
|
provider.removeRequest?.(model.session!, requestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleSlashCommand(sessionId: string, command: string): Promise<string> {
|
|
||||||
const slashCommands = await this.getSlashCommands(sessionId, CancellationToken.None);
|
|
||||||
for (const slashCommand of slashCommands ?? []) {
|
|
||||||
if (command.startsWith(`/${slashCommand.command}`) && this.chatSlashCommandService.hasCommand(slashCommand.command)) {
|
|
||||||
return slashCommand.command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveAgent(prompt: string): IChatAgentData | undefined {
|
|
||||||
prompt = prompt.trim();
|
|
||||||
const agents = this.chatAgentService.getAgents();
|
|
||||||
if (!prompt.startsWith('@')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return agents.find(a => prompt.match(new RegExp(`@${a.id}($|\\s)`)));
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]> {
|
async getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]> {
|
||||||
const model = this._sessionModels.get(sessionId);
|
const model = this._sessionModels.get(sessionId);
|
||||||
if (!model) {
|
if (!model) {
|
||||||
@@ -707,7 +690,7 @@ export class ChatService extends Disposable implements IChatService {
|
|||||||
return Array.from(this._providers.keys());
|
return Array.from(this._providers.keys());
|
||||||
}
|
}
|
||||||
|
|
||||||
async addCompleteRequest(sessionId: string, message: string, response: IChatCompleteResponse): Promise<void> {
|
async addCompleteRequest(sessionId: string, message: string | IParsedChatRequest, response: IChatCompleteResponse): Promise<void> {
|
||||||
this.trace('addCompleteRequest', `message: ${message}`);
|
this.trace('addCompleteRequest', `message: ${message}`);
|
||||||
|
|
||||||
const model = this._sessionModels.get(sessionId);
|
const model = this._sessionModels.get(sessionId);
|
||||||
@@ -716,7 +699,8 @@ export class ChatService extends Disposable implements IChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await model.waitForInitialization();
|
await model.waitForInitialization();
|
||||||
const request = model.addRequest(message, undefined);
|
const parsedRequest = typeof message === 'string' ? await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : message;
|
||||||
|
const request = model.addRequest(parsedRequest);
|
||||||
if (typeof response.message === 'string') {
|
if (typeof response.message === 'string') {
|
||||||
model.acceptResponseProgress(request, { content: response.message });
|
model.acceptResponseProgress(request, { content: response.message });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Iterable } from 'vs/base/common/iterator';
|
|||||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
|
import { ChatRequestVariablePart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
|
|
||||||
export interface IChatVariableData {
|
export interface IChatVariableData {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -39,7 +40,7 @@ export interface IChatVariablesService {
|
|||||||
/**
|
/**
|
||||||
* Resolves all variables that occur in `prompt`
|
* Resolves all variables that occur in `prompt`
|
||||||
*/
|
*/
|
||||||
resolveVariables(prompt: string, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult>;
|
resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IChatData {
|
interface IChatData {
|
||||||
@@ -60,40 +61,29 @@ export class ChatVariablesService implements IChatVariablesService {
|
|||||||
constructor() {
|
constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveVariables(prompt: string, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult> {
|
async resolveVariables(prompt: IParsedChatRequest, model: IChatModel, token: CancellationToken): Promise<IChatVariableResolveResult> {
|
||||||
const resolvedVariables: Record<string, IChatRequestVariableValue[]> = {};
|
const resolvedVariables: Record<string, IChatRequestVariableValue[]> = {};
|
||||||
const jobs: Promise<any>[] = [];
|
const jobs: Promise<any>[] = [];
|
||||||
|
|
||||||
// TODO have a separate parser that is also used for decorations
|
|
||||||
const regex = /(^|\s)@(\w+)(:\w+)?(?=\s|$|\b)/ig;
|
|
||||||
|
|
||||||
let lastMatch = 0;
|
|
||||||
const parsedPrompt: string[] = [];
|
const parsedPrompt: string[] = [];
|
||||||
let match: RegExpMatchArray | null;
|
prompt.parts
|
||||||
while (match = regex.exec(prompt)) {
|
.forEach((varPart, i) => {
|
||||||
const [fullMatch, leading, varName, arg] = match;
|
if (varPart instanceof ChatRequestVariablePart) {
|
||||||
const data = this._resolver.get(varName.toLowerCase());
|
const data = this._resolver.get(varPart.variableName.toLowerCase());
|
||||||
if (data) {
|
if (data) {
|
||||||
if (!arg || data.data.canTakeArgument) {
|
jobs.push(data.resolver(prompt.text, varPart.variableArg, model, token).then(value => {
|
||||||
parsedPrompt.push(prompt.substring(lastMatch, match.index!));
|
if (value) {
|
||||||
parsedPrompt.push('');
|
resolvedVariables[varPart.variableName] = value;
|
||||||
lastMatch = match.index! + fullMatch.length;
|
parsedPrompt[i] = `[@${varPart.variableName}](values:${varPart.variableName})`;
|
||||||
const varIndex = parsedPrompt.length - 1;
|
} else {
|
||||||
const argWithoutColon = arg?.slice(1);
|
parsedPrompt[i] = varPart.text;
|
||||||
const fullVarName = varName + (arg ?? '');
|
}
|
||||||
jobs.push(data.resolver(prompt, argWithoutColon, model, token).then(value => {
|
}).catch(onUnexpectedExternalError));
|
||||||
if (value) {
|
}
|
||||||
resolvedVariables[fullVarName] = value;
|
} else {
|
||||||
parsedPrompt[varIndex] = `${leading}[@${fullVarName}](values:${fullVarName})`;
|
parsedPrompt[i] = varPart.text;
|
||||||
} else {
|
|
||||||
parsedPrompt[varIndex] = fullMatch;
|
|
||||||
}
|
|
||||||
}).catch(onUnexpectedExternalError));
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
parsedPrompt.push(prompt.substring(lastMatch));
|
|
||||||
|
|
||||||
await Promise.allSettled(jobs);
|
await Promise.allSettled(jobs);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { localize } from 'vs/nls';
|
|||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
import { IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse, Response } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse, Response } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
|
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
|
import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ export interface IChatRequestViewModel {
|
|||||||
readonly dataId: string;
|
readonly dataId: string;
|
||||||
readonly username: string;
|
readonly username: string;
|
||||||
readonly avatarIconUri?: URI;
|
readonly avatarIconUri?: URI;
|
||||||
readonly message: string | IChatReplyFollowup;
|
readonly message: IParsedChatRequest | IChatReplyFollowup;
|
||||||
readonly messageText: string;
|
readonly messageText: string;
|
||||||
currentRenderedHeight: number | undefined;
|
currentRenderedHeight: number | undefined;
|
||||||
}
|
}
|
||||||
@@ -215,7 +216,7 @@ export class ChatRequestViewModel implements IChatRequestViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get messageText() {
|
get messageText() {
|
||||||
return typeof this.message === 'string' ? this.message : this.message.message;
|
return 'kind' in this.message ? this.message.message : this.message.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentRenderedHeight: number | undefined;
|
currentRenderedHeight: number | undefined;
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 0,
|
|
||||||
endExclusive: 6
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 1,
|
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 7
|
|
||||||
},
|
|
||||||
agent: {
|
|
||||||
id: "agent",
|
|
||||||
metadata: {
|
|
||||||
description: "",
|
|
||||||
subCommands: [ { name: "subCommand" } ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 6,
|
|
||||||
endExclusive: 18
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 7,
|
|
||||||
endLineNumber: 2,
|
|
||||||
endColumn: 4
|
|
||||||
},
|
|
||||||
text: " Please \ndo "
|
|
||||||
},
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 18,
|
|
||||||
endExclusive: 29
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 2,
|
|
||||||
startColumn: 4,
|
|
||||||
endLineNumber: 2,
|
|
||||||
endColumn: 15
|
|
||||||
},
|
|
||||||
command: { name: "subCommand" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 29,
|
|
||||||
endExclusive: 63
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 2,
|
|
||||||
startColumn: 15,
|
|
||||||
endLineNumber: 3,
|
|
||||||
endColumn: 18
|
|
||||||
},
|
|
||||||
text: " with @selection\nand @debugConsole"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 0,
|
|
||||||
endExclusive: 21
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 1,
|
|
||||||
endLineNumber: 3,
|
|
||||||
endColumn: 7
|
|
||||||
},
|
|
||||||
text: "line 1\nline 2\r\nline 3"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,73 +1,76 @@
|
|||||||
[
|
{
|
||||||
{
|
parts: [
|
||||||
range: {
|
{
|
||||||
start: 0,
|
range: {
|
||||||
endExclusive: 10
|
start: 0,
|
||||||
|
endExclusive: 10
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 11
|
||||||
|
},
|
||||||
|
text: "Hello Mr. "
|
||||||
},
|
},
|
||||||
editorRange: {
|
{
|
||||||
startLineNumber: 1,
|
range: {
|
||||||
startColumn: 1,
|
start: 10,
|
||||||
endLineNumber: 1,
|
endExclusive: 16
|
||||||
endColumn: 11
|
},
|
||||||
},
|
editorRange: {
|
||||||
text: "Hello Mr. "
|
startLineNumber: 1,
|
||||||
},
|
startColumn: 11,
|
||||||
{
|
endLineNumber: 1,
|
||||||
range: {
|
endColumn: 17
|
||||||
start: 10,
|
},
|
||||||
endExclusive: 16
|
agent: {
|
||||||
},
|
id: "agent",
|
||||||
editorRange: {
|
metadata: {
|
||||||
startLineNumber: 1,
|
description: "",
|
||||||
startColumn: 11,
|
subCommands: [ { name: "subCommand" } ]
|
||||||
endLineNumber: 1,
|
}
|
||||||
endColumn: 17
|
|
||||||
},
|
|
||||||
agent: {
|
|
||||||
id: "agent",
|
|
||||||
metadata: {
|
|
||||||
description: "",
|
|
||||||
subCommands: [ { name: "subCommand" } ]
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 16,
|
||||||
|
endExclusive: 17
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 17,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 18
|
||||||
|
},
|
||||||
|
text: " "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 17,
|
||||||
|
endExclusive: 28
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 18,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 29
|
||||||
|
},
|
||||||
|
command: { name: "subCommand" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 28,
|
||||||
|
endExclusive: 35
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 29,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 36
|
||||||
|
},
|
||||||
|
text: " thanks"
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
text: "Hello Mr. @agent /subCommand thanks"
|
||||||
range: {
|
}
|
||||||
start: 16,
|
|
||||||
endExclusive: 17
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 17,
|
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 18
|
|
||||||
},
|
|
||||||
text: " "
|
|
||||||
},
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 17,
|
|
||||||
endExclusive: 28
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 18,
|
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 29
|
|
||||||
},
|
|
||||||
command: { name: "subCommand" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 28,
|
|
||||||
endExclusive: 35
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 29,
|
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 36
|
|
||||||
},
|
|
||||||
text: " thanks"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 0,
|
||||||
|
endExclusive: 14
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 15
|
||||||
|
},
|
||||||
|
text: "Are you there "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 14,
|
||||||
|
endExclusive: 20
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 15,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 21
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
id: "agent",
|
||||||
|
metadata: {
|
||||||
|
description: "",
|
||||||
|
subCommands: [ { name: "subCommand" } ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 20,
|
||||||
|
endExclusive: 21
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 21,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 22
|
||||||
|
},
|
||||||
|
text: "?"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
text: "Are you there @agent?"
|
||||||
|
}
|
||||||
@@ -1,60 +1,63 @@
|
|||||||
[
|
{
|
||||||
{
|
parts: [
|
||||||
range: {
|
{
|
||||||
start: 0,
|
range: {
|
||||||
endExclusive: 6
|
start: 0,
|
||||||
},
|
endExclusive: 6
|
||||||
editorRange: {
|
},
|
||||||
startLineNumber: 1,
|
editorRange: {
|
||||||
startColumn: 1,
|
startLineNumber: 1,
|
||||||
endLineNumber: 1,
|
startColumn: 1,
|
||||||
endColumn: 7
|
endLineNumber: 1,
|
||||||
},
|
endColumn: 7
|
||||||
agent: {
|
},
|
||||||
id: "agent",
|
agent: {
|
||||||
metadata: {
|
id: "agent",
|
||||||
description: "",
|
metadata: {
|
||||||
subCommands: [ { name: "subCommand" } ]
|
description: "",
|
||||||
|
subCommands: [ { name: "subCommand" } ]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 6,
|
||||||
|
endExclusive: 17
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 7,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 18
|
||||||
|
},
|
||||||
|
text: " Please do "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 17,
|
||||||
|
endExclusive: 28
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 18,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 29
|
||||||
|
},
|
||||||
|
command: { name: "subCommand" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 28,
|
||||||
|
endExclusive: 35
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 29,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 36
|
||||||
|
},
|
||||||
|
text: " thanks"
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
text: "@agent Please do /subCommand thanks"
|
||||||
range: {
|
}
|
||||||
start: 6,
|
|
||||||
endExclusive: 17
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 7,
|
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 18
|
|
||||||
},
|
|
||||||
text: " Please do "
|
|
||||||
},
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 17,
|
|
||||||
endExclusive: 28
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 18,
|
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 29
|
|
||||||
},
|
|
||||||
command: { name: "subCommand" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 28,
|
|
||||||
endExclusive: 35
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 29,
|
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 36
|
|
||||||
},
|
|
||||||
text: " thanks"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 0,
|
||||||
|
endExclusive: 6
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 7
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
id: "agent",
|
||||||
|
metadata: {
|
||||||
|
description: "",
|
||||||
|
subCommands: [ { name: "subCommand" } ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 6,
|
||||||
|
endExclusive: 18
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 7,
|
||||||
|
endLineNumber: 2,
|
||||||
|
endColumn: 4
|
||||||
|
},
|
||||||
|
text: " Please \ndo "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 18,
|
||||||
|
endExclusive: 29
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 2,
|
||||||
|
startColumn: 4,
|
||||||
|
endLineNumber: 2,
|
||||||
|
endColumn: 15
|
||||||
|
},
|
||||||
|
command: { name: "subCommand" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 29,
|
||||||
|
endExclusive: 63
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 2,
|
||||||
|
startColumn: 15,
|
||||||
|
endLineNumber: 3,
|
||||||
|
endColumn: 18
|
||||||
|
},
|
||||||
|
text: " with @selection\nand @debugConsole"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
text: "@agent Please \ndo /subCommand with @selection\nand @debugConsole"
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
[
|
{
|
||||||
{
|
parts: [
|
||||||
range: {
|
{
|
||||||
start: 0,
|
range: {
|
||||||
endExclusive: 13
|
start: 0,
|
||||||
},
|
endExclusive: 13
|
||||||
editorRange: {
|
},
|
||||||
startLineNumber: 1,
|
editorRange: {
|
||||||
startColumn: 1,
|
startLineNumber: 1,
|
||||||
endLineNumber: 1,
|
startColumn: 1,
|
||||||
endColumn: 14
|
endLineNumber: 1,
|
||||||
},
|
endColumn: 14
|
||||||
text: "/explain this"
|
},
|
||||||
}
|
text: "/explain this"
|
||||||
]
|
}
|
||||||
|
],
|
||||||
|
text: "/explain this"
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
[
|
{
|
||||||
{
|
parts: [
|
||||||
range: {
|
{
|
||||||
start: 0,
|
range: {
|
||||||
endExclusive: 26
|
start: 0,
|
||||||
},
|
endExclusive: 26
|
||||||
editorRange: {
|
},
|
||||||
startLineNumber: 1,
|
editorRange: {
|
||||||
startColumn: 1,
|
startLineNumber: 1,
|
||||||
endLineNumber: 1,
|
startColumn: 1,
|
||||||
endColumn: 27
|
endLineNumber: 1,
|
||||||
},
|
endColumn: 27
|
||||||
text: "What does @selection mean?"
|
},
|
||||||
}
|
text: "What does @selection mean?"
|
||||||
]
|
}
|
||||||
|
],
|
||||||
|
text: "What does @selection mean?"
|
||||||
|
}
|
||||||
@@ -1,28 +1,31 @@
|
|||||||
[
|
{
|
||||||
{
|
parts: [
|
||||||
range: {
|
{
|
||||||
start: 0,
|
range: {
|
||||||
endExclusive: 4
|
start: 0,
|
||||||
|
endExclusive: 4
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 5
|
||||||
|
},
|
||||||
|
slashCommand: { command: "fix" }
|
||||||
},
|
},
|
||||||
editorRange: {
|
{
|
||||||
startLineNumber: 1,
|
range: {
|
||||||
startColumn: 1,
|
start: 4,
|
||||||
endLineNumber: 1,
|
endExclusive: 9
|
||||||
endColumn: 5
|
},
|
||||||
},
|
editorRange: {
|
||||||
slashCommand: { command: "fix" }
|
startLineNumber: 1,
|
||||||
},
|
startColumn: 5,
|
||||||
{
|
endLineNumber: 1,
|
||||||
range: {
|
endColumn: 10
|
||||||
start: 4,
|
},
|
||||||
endExclusive: 9
|
text: " /fix"
|
||||||
},
|
}
|
||||||
editorRange: {
|
],
|
||||||
startLineNumber: 1,
|
text: "/fix /fix"
|
||||||
startColumn: 5,
|
}
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 10
|
|
||||||
},
|
|
||||||
text: " /fix"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
[
|
{
|
||||||
{
|
parts: [
|
||||||
range: {
|
{
|
||||||
start: 0,
|
range: {
|
||||||
endExclusive: 4
|
start: 0,
|
||||||
},
|
endExclusive: 4
|
||||||
editorRange: {
|
},
|
||||||
startLineNumber: 1,
|
editorRange: {
|
||||||
startColumn: 1,
|
startLineNumber: 1,
|
||||||
endLineNumber: 1,
|
startColumn: 1,
|
||||||
endColumn: 5
|
endLineNumber: 1,
|
||||||
},
|
endColumn: 5
|
||||||
text: "test"
|
},
|
||||||
}
|
text: "test"
|
||||||
]
|
}
|
||||||
|
],
|
||||||
|
text: "test"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 0,
|
||||||
|
endExclusive: 21
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 3,
|
||||||
|
endColumn: 7
|
||||||
|
},
|
||||||
|
text: "line 1\nline 2\r\nline 3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
text: "line 1\nline 2\r\nline 3"
|
||||||
|
}
|
||||||
@@ -1,28 +1,31 @@
|
|||||||
[
|
{
|
||||||
{
|
parts: [
|
||||||
range: {
|
{
|
||||||
start: 0,
|
range: {
|
||||||
endExclusive: 4
|
start: 0,
|
||||||
|
endExclusive: 4
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 5
|
||||||
|
},
|
||||||
|
slashCommand: { command: "fix" }
|
||||||
},
|
},
|
||||||
editorRange: {
|
{
|
||||||
startLineNumber: 1,
|
range: {
|
||||||
startColumn: 1,
|
start: 4,
|
||||||
endLineNumber: 1,
|
endExclusive: 9
|
||||||
endColumn: 5
|
},
|
||||||
},
|
editorRange: {
|
||||||
slashCommand: { command: "fix" }
|
startLineNumber: 1,
|
||||||
},
|
startColumn: 5,
|
||||||
{
|
endLineNumber: 1,
|
||||||
range: {
|
endColumn: 10
|
||||||
start: 4,
|
},
|
||||||
endExclusive: 9
|
text: " this"
|
||||||
},
|
}
|
||||||
editorRange: {
|
],
|
||||||
startLineNumber: 1,
|
text: "/fix this"
|
||||||
startColumn: 5,
|
}
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 10
|
|
||||||
},
|
|
||||||
text: " this"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 0,
|
||||||
|
endExclusive: 8
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 9
|
||||||
|
},
|
||||||
|
text: "What is "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 8,
|
||||||
|
endExclusive: 18
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 9,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 19
|
||||||
|
},
|
||||||
|
variableName: "selection",
|
||||||
|
variableArg: ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 18,
|
||||||
|
endExclusive: 19
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 19,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 20
|
||||||
|
},
|
||||||
|
text: "?"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
text: "What is @selection?"
|
||||||
|
}
|
||||||
@@ -1,42 +1,45 @@
|
|||||||
[
|
{
|
||||||
{
|
parts: [
|
||||||
range: {
|
{
|
||||||
start: 0,
|
range: {
|
||||||
endExclusive: 10
|
start: 0,
|
||||||
|
endExclusive: 10
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 11
|
||||||
|
},
|
||||||
|
text: "What does "
|
||||||
},
|
},
|
||||||
editorRange: {
|
{
|
||||||
startLineNumber: 1,
|
range: {
|
||||||
startColumn: 1,
|
start: 10,
|
||||||
endLineNumber: 1,
|
endExclusive: 20
|
||||||
endColumn: 11
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 11,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 21
|
||||||
|
},
|
||||||
|
variableName: "selection",
|
||||||
|
variableArg: ""
|
||||||
},
|
},
|
||||||
text: "What does "
|
{
|
||||||
},
|
range: {
|
||||||
{
|
start: 20,
|
||||||
range: {
|
endExclusive: 26
|
||||||
start: 10,
|
},
|
||||||
endExclusive: 20
|
editorRange: {
|
||||||
},
|
startLineNumber: 1,
|
||||||
editorRange: {
|
startColumn: 21,
|
||||||
startLineNumber: 1,
|
endLineNumber: 1,
|
||||||
startColumn: 11,
|
endColumn: 27
|
||||||
endLineNumber: 1,
|
},
|
||||||
endColumn: 21
|
text: " mean?"
|
||||||
},
|
}
|
||||||
variableName: "selection",
|
],
|
||||||
variableArg: ""
|
text: "What does @selection mean?"
|
||||||
},
|
}
|
||||||
{
|
|
||||||
range: {
|
|
||||||
start: 20,
|
|
||||||
endExclusive: 26
|
|
||||||
},
|
|
||||||
editorRange: {
|
|
||||||
startLineNumber: 1,
|
|
||||||
startColumn: 21,
|
|
||||||
endLineNumber: 1,
|
|
||||||
endColumn: 27
|
|
||||||
},
|
|
||||||
text: " mean?"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -36,7 +36,7 @@ suite('ChatRequestParser', () => {
|
|||||||
await assertSnapshot(result);
|
await assertSnapshot(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('_plain text with newlines', async () => {
|
test('plain text with newlines', async () => {
|
||||||
parser = instantiationService.createInstance(ChatRequestParser);
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
const text = 'line 1\nline 2\r\nline 3';
|
const text = 'line 1\nline 2\r\nline 3';
|
||||||
const result = await parser.parseChatRequest('1', text);
|
const result = await parser.parseChatRequest('1', text);
|
||||||
@@ -87,6 +87,17 @@ suite('ChatRequestParser', () => {
|
|||||||
await assertSnapshot(result);
|
await assertSnapshot(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('variable with question mark', async () => {
|
||||||
|
const variablesService = mockObject<IChatVariablesService>()({});
|
||||||
|
variablesService.hasVariable.returns(true);
|
||||||
|
instantiationService.stub(IChatVariablesService, variablesService as any);
|
||||||
|
|
||||||
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
const text = 'What is @selection?';
|
||||||
|
const result = await parser.parseChatRequest('1', text);
|
||||||
|
await assertSnapshot(result);
|
||||||
|
});
|
||||||
|
|
||||||
test('invalid variables', async () => {
|
test('invalid variables', async () => {
|
||||||
const variablesService = mockObject<IChatVariablesService>()({});
|
const variablesService = mockObject<IChatVariablesService>()({});
|
||||||
variablesService.hasVariable.returns(false);
|
variablesService.hasVariable.returns(false);
|
||||||
@@ -108,6 +119,16 @@ suite('ChatRequestParser', () => {
|
|||||||
await assertSnapshot(result);
|
await assertSnapshot(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('agent with question mark', async () => {
|
||||||
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
|
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
|
||||||
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
const result = await parser.parseChatRequest('1', 'Are you there @agent?');
|
||||||
|
await assertSnapshot(result);
|
||||||
|
});
|
||||||
|
|
||||||
test('agent not first', async () => {
|
test('agent not first', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
|
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
|
||||||
@@ -118,7 +139,7 @@ suite('ChatRequestParser', () => {
|
|||||||
await assertSnapshot(result);
|
await assertSnapshot(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('_agents and variables and multiline', async () => {
|
test('agents and variables and multiline', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
|
agentsService.getAgent.returns(<IChatAgentData>{ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } });
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|||||||
@@ -94,11 +94,11 @@ suite('Chat', () => {
|
|||||||
|
|
||||||
const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None));
|
const session1 = testDisposables.add(testService.startSession('provider1', CancellationToken.None));
|
||||||
await session1.waitForInitialization();
|
await session1.waitForInitialization();
|
||||||
session1!.addRequest('request 1');
|
session1!.addRequest({ parts: [], text: 'request 1' });
|
||||||
|
|
||||||
const session2 = testDisposables.add(testService.startSession('provider2', CancellationToken.None));
|
const session2 = testDisposables.add(testService.startSession('provider2', CancellationToken.None));
|
||||||
await session2.waitForInitialization();
|
await session2.waitForInitialization();
|
||||||
session2!.addRequest('request 2');
|
session2!.addRequest({ parts: [], text: 'request 2' });
|
||||||
|
|
||||||
assert.strictEqual(provider1.lastInitialState, undefined);
|
assert.strictEqual(provider1.lastInitialState, undefined);
|
||||||
assert.strictEqual(provider2.lastInitialState, undefined);
|
assert.strictEqual(provider2.lastInitialState, undefined);
|
||||||
|
|||||||
@@ -6,58 +6,78 @@
|
|||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
||||||
import { ChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||||
|
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
|
||||||
|
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||||
|
import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
|
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||||
|
import { ChatVariablesService, IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
|
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
|
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||||
|
|
||||||
suite('ChatVariables', function () {
|
suite('ChatVariables', function () {
|
||||||
|
|
||||||
let service: ChatVariablesService;
|
let service: ChatVariablesService;
|
||||||
|
let instantiationService: TestInstantiationService;
|
||||||
|
const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();
|
||||||
|
|
||||||
setup(function () {
|
setup(function () {
|
||||||
service = new ChatVariablesService();
|
service = new ChatVariablesService();
|
||||||
|
instantiationService = testDisposables.add(new TestInstantiationService());
|
||||||
|
instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService()));
|
||||||
|
instantiationService.stub(ILogService, new NullLogService());
|
||||||
|
instantiationService.stub(IExtensionService, new TestExtensionService());
|
||||||
|
instantiationService.stub(IChatVariablesService, service);
|
||||||
|
instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));
|
||||||
});
|
});
|
||||||
|
|
||||||
ensureNoDisposablesAreLeakedInTestSuite();
|
|
||||||
|
|
||||||
test('ChatVariables - resolveVariables', async function () {
|
test('ChatVariables - resolveVariables', async function () {
|
||||||
|
|
||||||
const v1 = service.registerVariable({ name: 'foo', description: 'bar' }, async () => ([{ level: 'full', value: 'farboo' }]));
|
const v1 = service.registerVariable({ name: 'foo', description: 'bar' }, async () => ([{ level: 'full', value: 'farboo' }]));
|
||||||
const v2 = service.registerVariable({ name: 'far', description: 'boo' }, async () => ([{ level: 'full', value: 'farboo' }]));
|
const v2 = service.registerVariable({ name: 'far', description: 'boo' }, async () => ([{ level: 'full', value: 'farboo' }]));
|
||||||
|
|
||||||
|
const parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
|
||||||
|
const resolveVariables = async (text: string) => {
|
||||||
|
const result = await parser.parseChatRequest('1', text);
|
||||||
|
return await service.resolveVariables(result, null!, CancellationToken.None);
|
||||||
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
const data = await service.resolveVariables('Hello @foo and@far', null!, CancellationToken.None);
|
const data = await resolveVariables('Hello @foo and@far');
|
||||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||||
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo) and@far');
|
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo) and@far');
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const data = await service.resolveVariables('@foo Hello', null!, CancellationToken.None);
|
const data = await resolveVariables('@foo Hello');
|
||||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||||
assert.strictEqual(data.prompt, '[@foo](values:foo) Hello');
|
assert.strictEqual(data.prompt, '[@foo](values:foo) Hello');
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const data = await service.resolveVariables('Hello @foo', null!, CancellationToken.None);
|
const data = await resolveVariables('Hello @foo');
|
||||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const data = await service.resolveVariables('Hello @foo?', null!, CancellationToken.None);
|
const data = await resolveVariables('Hello @foo?');
|
||||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||||
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo)?');
|
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo)?');
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const data = await service.resolveVariables('Hello @foo and@far @foo', null!, CancellationToken.None);
|
const data = await resolveVariables('Hello @foo and@far @foo');
|
||||||
assert.strictEqual(Object.keys(data.variables).length, 1);
|
assert.strictEqual(Object.keys(data.variables).length, 1);
|
||||||
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
assert.deepEqual(Object.keys(data.variables).sort(), ['foo']);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const data = await service.resolveVariables('Hello @foo and @far @foo', null!, CancellationToken.None);
|
const data = await resolveVariables('Hello @foo and @far @foo');
|
||||||
assert.strictEqual(Object.keys(data.variables).length, 2);
|
assert.strictEqual(Object.keys(data.variables).length, 2);
|
||||||
assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']);
|
assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const data = await service.resolveVariables('Hello @foo and @far @foo @unknown', null!, CancellationToken.None);
|
const data = await resolveVariables('Hello @foo and @far @foo @unknown');
|
||||||
assert.strictEqual(Object.keys(data.variables).length, 2);
|
assert.strictEqual(Object.keys(data.variables).length, 2);
|
||||||
assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']);
|
assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']);
|
||||||
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo) and [@far](values:far) [@foo](values:foo) @unknown');
|
assert.strictEqual(data.prompt, 'Hello [@foo](values:foo) and [@far](values:far) [@foo](values:foo) @unknown');
|
||||||
|
|||||||
Reference in New Issue
Block a user