mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-08 15:55:15 +01:00
Chat request removes words that start with / (#248795)
This commit is contained in:
committed by
GitHub
parent
ed48873ba2
commit
0c82ecfe3c
@@ -16,7 +16,7 @@ import { IPromptsService } from './promptSyntax/service/types.js';
|
||||
|
||||
const agentReg = /^@([\w_\-\.]+)(?=(\s|$|\b))/i; // An @-agent
|
||||
const variableReg = /^#([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // A #-variable with an optional numeric : arg (@response:2)
|
||||
const slashReg = /\/([\w_\-\.:]+)(?=(\s|$|\b))/i; // A / command
|
||||
const slashReg = /^\/([\w_\-\.:]+)(?=(\s|$|\b))/i; // A / command
|
||||
|
||||
export interface IChatParserContext {
|
||||
/** Used only as a disambiguator, when the query references an agent that has a duplicate with the same name. */
|
||||
@@ -169,8 +169,16 @@ export class ChatRequestParser {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.some(p => p instanceof ChatRequestSlashCommandPart)) {
|
||||
// Only one slash command allowed
|
||||
if (parts.some(p => !(p instanceof ChatRequestAgentPart) && !(p instanceof ChatRequestTextPart && p.text.trim() === ''))) {
|
||||
// no other part than agent or non-whitespace text allowed: that also means no other slash command
|
||||
return;
|
||||
}
|
||||
|
||||
// only whitespace after the last part
|
||||
const previousPart = parts.at(-1);
|
||||
const previousPartEnd = previousPart?.range.endExclusive ?? 0;
|
||||
const textSincePreviousPart = fullMessage.slice(previousPartEnd, offset);
|
||||
if (textSincePreviousPart.trim() !== '') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -180,18 +188,6 @@ export class ChatRequestParser {
|
||||
|
||||
const usedAgent = parts.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart);
|
||||
if (usedAgent) {
|
||||
// The slash command must come immediately after the agent
|
||||
if (parts.some(p => (p instanceof ChatRequestTextPart && p.text.trim() !== '') || !(p instanceof ChatRequestAgentPart) && !(p instanceof ChatRequestTextPart))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousPart = parts.at(-1);
|
||||
const previousPartEnd = previousPart?.range.endExclusive ?? 0;
|
||||
const textSincePreviousPart = fullMessage.slice(previousPartEnd, offset);
|
||||
if (textSincePreviousPart.trim() !== '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const subCommand = usedAgent.agent.slashCommands.find(c => c.name === command);
|
||||
if (subCommand) {
|
||||
// Valid agent subcommand
|
||||
|
||||
@@ -144,7 +144,7 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
}
|
||||
|
||||
public asPromptSlashCommand(command: string): IChatPromptSlashCommand | undefined {
|
||||
if (command.match(/^[\w_\-\.]+/)) {
|
||||
if (command.match(/^[\w_\-\.]+$/)) {
|
||||
return { command, detail: localize('prompt.file.detail', 'Prompt file: {0}', command) };
|
||||
}
|
||||
return undefined;
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
text: " ",
|
||||
kind: "text"
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 4,
|
||||
endExclusive: 11
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 5,
|
||||
endLineNumber: 1,
|
||||
endColumn: 12
|
||||
},
|
||||
slashPromptCommand: { command: "prompt" },
|
||||
kind: "prompt"
|
||||
}
|
||||
],
|
||||
text: " /prompt"
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 41
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 42
|
||||
},
|
||||
text: "/ route and the request of /search-option",
|
||||
kind: "text"
|
||||
}
|
||||
],
|
||||
text: "/ route and the request of /search-option"
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 52
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 53
|
||||
},
|
||||
text: "handle the / route and the request of /search-option",
|
||||
kind: "text"
|
||||
}
|
||||
],
|
||||
text: "handle the / route and the request of /search-option"
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 4
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 5
|
||||
},
|
||||
text: " ",
|
||||
kind: "text"
|
||||
},
|
||||
{
|
||||
range: {
|
||||
start: 4,
|
||||
endExclusive: 8
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 5,
|
||||
endLineNumber: 1,
|
||||
endColumn: 9
|
||||
},
|
||||
slashCommand: { command: "fix" },
|
||||
kind: "slash"
|
||||
}
|
||||
],
|
||||
text: " /fix"
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 10
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 11
|
||||
},
|
||||
text: "Hello /fix",
|
||||
kind: "text"
|
||||
}
|
||||
],
|
||||
text: "Hello /fix"
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
parts: [
|
||||
{
|
||||
range: {
|
||||
start: 0,
|
||||
endExclusive: 65
|
||||
},
|
||||
editorRange: {
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 66
|
||||
},
|
||||
text: "can we add a new file for an Express router to handle the / route",
|
||||
kind: "text"
|
||||
}
|
||||
],
|
||||
text: "can we add a new file for an Express router to handle the / route"
|
||||
}
|
||||
@@ -61,6 +61,13 @@ suite('ChatRequestParser', () => {
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('slash in text', async () => {
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const text = 'can we add a new file for an Express router to handle the / route';
|
||||
const result = parser.parseChatRequest('1', text);
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('slash command', async () => {
|
||||
const slashCommandService = mockObject<IChatSlashCommandService>()({});
|
||||
slashCommandService.getCommands.returns([{ command: 'fix' }]);
|
||||
@@ -94,6 +101,89 @@ suite('ChatRequestParser', () => {
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('slash command not first', async () => {
|
||||
const slashCommandService = mockObject<IChatSlashCommandService>()({});
|
||||
slashCommandService.getCommands.returns([{ command: 'fix' }]);
|
||||
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
|
||||
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const text = 'Hello /fix';
|
||||
const result = parser.parseChatRequest('1', text);
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('slash command after whitespace', async () => {
|
||||
const slashCommandService = mockObject<IChatSlashCommandService>()({});
|
||||
slashCommandService.getCommands.returns([{ command: 'fix' }]);
|
||||
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
|
||||
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const text = ' /fix';
|
||||
const result = parser.parseChatRequest('1', text);
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('prompt slash command', async () => {
|
||||
const slashCommandService = mockObject<IChatSlashCommandService>()({});
|
||||
slashCommandService.getCommands.returns([{ command: 'fix' }]);
|
||||
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
|
||||
|
||||
const promptSlashCommandService = mockObject<IPromptsService>()({});
|
||||
promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {
|
||||
if (command.match(/^[\w_\-\.]+$/)) {
|
||||
return { command };
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
instantiationService.stub(IPromptsService, promptSlashCommandService as any);
|
||||
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const text = ' /prompt';
|
||||
const result = parser.parseChatRequest('1', text);
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('prompt slash command after text', async () => {
|
||||
const slashCommandService = mockObject<IChatSlashCommandService>()({});
|
||||
slashCommandService.getCommands.returns([{ command: 'fix' }]);
|
||||
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
|
||||
|
||||
const promptSlashCommandService = mockObject<IPromptsService>()({});
|
||||
promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {
|
||||
if (command.match(/^[\w_\-\.]+$/)) {
|
||||
return { command };
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
instantiationService.stub(IPromptsService, promptSlashCommandService as any);
|
||||
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const text = 'handle the / route and the request of /search-option';
|
||||
const result = parser.parseChatRequest('1', text);
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
test('prompt slash command after slash', async () => {
|
||||
const slashCommandService = mockObject<IChatSlashCommandService>()({});
|
||||
slashCommandService.getCommands.returns([{ command: 'fix' }]);
|
||||
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
|
||||
|
||||
const promptSlashCommandService = mockObject<IPromptsService>()({});
|
||||
promptSlashCommandService.asPromptSlashCommand.callsFake((command: string) => {
|
||||
if (command.match(/^[\w_\-\.]+$/)) {
|
||||
return { command };
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
instantiationService.stub(IPromptsService, promptSlashCommandService as any);
|
||||
|
||||
parser = instantiationService.createInstance(ChatRequestParser);
|
||||
const text = '/ route and the request of /search-option';
|
||||
const result = parser.parseChatRequest('1', text);
|
||||
await assertSnapshot(result);
|
||||
});
|
||||
|
||||
|
||||
// test('variables', async () => {
|
||||
// varService.hasVariable.returns(true);
|
||||
// varService.getVariable.returns({ id: 'copilot.selection' });
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { ITextModel } from '../../../../../editor/common/model.js';
|
||||
import { PROMPT_FILE_EXTENSION } from '../../../../../platform/prompts/common/constants.js';
|
||||
import { TextModelPromptParser } from '../../common/promptSyntax/parsers/textModelPromptParser.js';
|
||||
import { IChatPromptSlashCommand, IMetadata, IPromptPath, IPromptsService, TCombinedToolsMetadata, TPromptsType } from '../../common/promptSyntax/service/types.js';
|
||||
|
||||
@@ -27,13 +26,7 @@ export class MockPromptsService implements IPromptsService {
|
||||
getSourceFolders(_type: TPromptsType): readonly IPromptPath[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public asPromptSlashCommand(name: string): IChatPromptSlashCommand | undefined {
|
||||
if (name.endsWith(PROMPT_FILE_EXTENSION)) {
|
||||
const command = `prompt:${name.substring(0, -PROMPT_FILE_EXTENSION.length)}`;
|
||||
return {
|
||||
command, detail: name,
|
||||
};
|
||||
}
|
||||
public asPromptSlashCommand(command: string): IChatPromptSlashCommand | undefined {
|
||||
return undefined;
|
||||
}
|
||||
resolvePromptSlashCommand(_data: IChatPromptSlashCommand): Promise<IPromptPath | undefined> {
|
||||
|
||||
Reference in New Issue
Block a user