mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
support handoff property in modes (#271710)
* support handoff property in modes * allow empty prompt * Update src/vs/workbench/contrib/chat/common/promptSyntax/languageProviders/promptValidator.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/contrib/chat/common/promptSyntax/service/newPromptsParser.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix nls key --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
56850a2386
commit
0df032fb9f
@@ -17,6 +17,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
|
||||
import { IChatAgentService } from './chatAgents.js';
|
||||
import { ChatContextKeys } from './chatContextKeys.js';
|
||||
import { ChatModeKind } from './constants.js';
|
||||
import { IHandOff } from './promptSyntax/service/newPromptsParser.js';
|
||||
import { ICustomChatMode, IPromptsService } from './promptSyntax/service/promptsService.js';
|
||||
|
||||
export const IChatModeService = createDecorator<IChatModeService>('chatModeService');
|
||||
@@ -99,6 +100,7 @@ export class ChatModeService extends Disposable implements IChatModeService {
|
||||
tools: cachedMode.customTools,
|
||||
model: cachedMode.model,
|
||||
modeInstructions: cachedMode.modeInstructions ?? { content: cachedMode.body ?? '', toolReferences: [] },
|
||||
handOffs: cachedMode.handOffs
|
||||
};
|
||||
const instance = new CustomChatMode(customChatMode);
|
||||
this._customModeInstances.set(uri.toString(), instance);
|
||||
@@ -202,6 +204,7 @@ export interface IChatModeData {
|
||||
readonly model?: string;
|
||||
readonly modeInstructions?: IChatModeInstructions;
|
||||
readonly body?: string; /* deprecated */
|
||||
readonly handOffs?: readonly IHandOff[];
|
||||
readonly uri?: URI;
|
||||
}
|
||||
|
||||
@@ -213,6 +216,7 @@ export interface IChatMode {
|
||||
readonly isBuiltin: boolean;
|
||||
readonly kind: ChatModeKind;
|
||||
readonly customTools?: IObservable<readonly string[] | undefined>;
|
||||
readonly handOffs?: IObservable<readonly IHandOff[] | undefined>;
|
||||
readonly model?: IObservable<string | undefined>;
|
||||
readonly modeInstructions?: IObservable<IChatModeInstructions>;
|
||||
readonly uri?: IObservable<URI>;
|
||||
@@ -242,6 +246,7 @@ function isCachedChatModeData(data: unknown): data is IChatModeData {
|
||||
(mode.customTools === undefined || Array.isArray(mode.customTools)) &&
|
||||
(mode.modeInstructions === undefined || (typeof mode.modeInstructions === 'object' && mode.modeInstructions !== null)) &&
|
||||
(mode.model === undefined || typeof mode.model === 'string') &&
|
||||
(mode.handOffs === undefined || Array.isArray(mode.handOffs)) &&
|
||||
(mode.uri === undefined || (typeof mode.uri === 'object' && mode.uri !== null));
|
||||
}
|
||||
|
||||
@@ -251,6 +256,7 @@ export class CustomChatMode implements IChatMode {
|
||||
private readonly _modeInstructions: ISettableObservable<IChatModeInstructions>;
|
||||
private readonly _uriObservable: ISettableObservable<URI>;
|
||||
private readonly _modelObservable: ISettableObservable<string | undefined>;
|
||||
private readonly _handoffsObservable: ISettableObservable<readonly IHandOff[] | undefined>;
|
||||
|
||||
public readonly id: string;
|
||||
public readonly name: string;
|
||||
@@ -283,6 +289,10 @@ export class CustomChatMode implements IChatMode {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
get handOffs(): IObservable<readonly IHandOff[] | undefined> {
|
||||
return this._handoffsObservable;
|
||||
}
|
||||
|
||||
public readonly kind = ChatModeKind.Agent;
|
||||
|
||||
constructor(
|
||||
@@ -295,6 +305,7 @@ export class CustomChatMode implements IChatMode {
|
||||
this._modelObservable = observableValue('model', customChatMode.model);
|
||||
this._modeInstructions = observableValue('_modeInstructions', customChatMode.modeInstructions);
|
||||
this._uriObservable = observableValue('uri', customChatMode.uri);
|
||||
this._handoffsObservable = observableValue('handoffs', customChatMode.handOffs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,7 +331,8 @@ export class CustomChatMode implements IChatMode {
|
||||
customTools: this.customTools.get(),
|
||||
model: this.model.get(),
|
||||
modeInstructions: this.modeInstructions.get(),
|
||||
uri: this.uri.get()
|
||||
uri: this.uri.get(),
|
||||
handOffs: this.handOffs.get()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+18
-1
@@ -17,6 +17,7 @@ import { IPromptsService } from '../service/promptsService.js';
|
||||
import { Iterable } from '../../../../../../base/common/iterator.js';
|
||||
import { PromptHeader } from '../service/newPromptsParser.js';
|
||||
import { getValidAttributeNames } from './promptValidator.js';
|
||||
import { localize } from '../../../../../../nls.js';
|
||||
|
||||
export class PromptHeaderAutocompletion implements CompletionItemProvider {
|
||||
/**
|
||||
@@ -140,7 +141,6 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const bracketIndex = lineContent.indexOf('[');
|
||||
if (bracketIndex !== -1 && bracketIndex <= position.column - 1) {
|
||||
// if the property is already inside a bracket, we don't provide value completions
|
||||
@@ -158,6 +158,22 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider {
|
||||
};
|
||||
suggestions.push(item);
|
||||
}
|
||||
if (property === 'handoffs' && (promptType === PromptsType.mode)) {
|
||||
const value = [
|
||||
'',
|
||||
' - label: Start Implementation',
|
||||
' agent: agent',
|
||||
' prompt: Implement the plan',
|
||||
' send: true'
|
||||
].join('\n');
|
||||
const item: CompletionItem = {
|
||||
label: localize('promptHeaderAutocompletion.handoffsExample', "Handoff Example"),
|
||||
kind: CompletionItemKind.Value,
|
||||
insertText: whilespaceAfterColon === 0 ? ` ${value}` : value,
|
||||
range: new Range(position.lineNumber, colonPosition.column + whilespaceAfterColon + 1, position.lineNumber, model.getLineMaxColumn(position.lineNumber)),
|
||||
};
|
||||
suggestions.push(item);
|
||||
}
|
||||
return { suggestions };
|
||||
}
|
||||
|
||||
@@ -193,6 +209,7 @@ export class PromptHeaderAutocompletion implements CompletionItemProvider {
|
||||
if (property === 'model' && (promptType === PromptsType.prompt || promptType === PromptsType.mode)) {
|
||||
return this.getModelNames(promptType === PromptsType.mode);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
+50
-1
@@ -140,6 +140,7 @@ export class PromptValidator {
|
||||
case PromptsType.mode:
|
||||
this.validateTools(attributes, ChatModeKind.Agent, report);
|
||||
this.validateModel(attributes, ChatModeKind.Agent, report);
|
||||
this.validateHandoffs(attributes, report);
|
||||
break;
|
||||
|
||||
}
|
||||
@@ -306,12 +307,60 @@ export class PromptValidator {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private validateHandoffs(attributes: IHeaderAttribute[], report: (markers: IMarkerData) => void): undefined {
|
||||
const attribute = attributes.find(attr => attr.key === 'handoffs');
|
||||
if (!attribute) {
|
||||
return;
|
||||
}
|
||||
if (attribute.value.type !== 'array') {
|
||||
report(toMarker(localize('promptValidator.handoffsMustBeArray', "The 'handoffs' attribute must be an array."), attribute.value.range, MarkerSeverity.Error));
|
||||
return;
|
||||
}
|
||||
for (const item of attribute.value.items) {
|
||||
if (item.type !== 'object') {
|
||||
report(toMarker(localize('promptValidator.eachHandoffMustBeObject', "Each handoff in the 'handoffs' attribute must be an object with 'label', 'agent', 'prompt' and optional 'send'."), item.range, MarkerSeverity.Error));
|
||||
continue;
|
||||
}
|
||||
const required = new Set(['label', 'agent', 'prompt']);
|
||||
for (const prop of item.properties) {
|
||||
switch (prop.key.value) {
|
||||
case 'label':
|
||||
if (prop.value.type !== 'string' || prop.value.value.trim().length === 0) {
|
||||
report(toMarker(localize('promptValidator.handoffLabelMustBeNonEmptyString', "The 'label' property in a handoff must be a non-empty string."), prop.value.range, MarkerSeverity.Error));
|
||||
}
|
||||
break;
|
||||
case 'agent':
|
||||
if (prop.value.type !== 'string' || prop.value.value.trim().length === 0) {
|
||||
report(toMarker(localize('promptValidator.handoffAgentMustBeNonEmptyString', "The 'agent' property in a handoff must be a non-empty string."), prop.value.range, MarkerSeverity.Error));
|
||||
}
|
||||
break;
|
||||
case 'prompt':
|
||||
if (prop.value.type !== 'string') {
|
||||
report(toMarker(localize('promptValidator.handoffPromptMustBeString', "The 'prompt' property in a handoff must be a string."), prop.value.range, MarkerSeverity.Error));
|
||||
}
|
||||
break;
|
||||
case 'send':
|
||||
if (prop.value.type !== 'boolean') {
|
||||
report(toMarker(localize('promptValidator.handoffSendMustBeBoolean', "The 'send' property in a handoff must be a boolean."), prop.value.range, MarkerSeverity.Error));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
report(toMarker(localize('promptValidator.unknownHandoffProperty', "Unknown property '{0}' in handoff object. Supported properties are 'label', 'agent', 'prompt' and optional 'send'.", prop.key.value), prop.value.range, MarkerSeverity.Warning));
|
||||
}
|
||||
required.delete(prop.key.value);
|
||||
}
|
||||
if (required.size > 0) {
|
||||
report(toMarker(localize('promptValidator.missingHandoffProperties', "Missing required properties {0} in handoff object.", Array.from(required).map(s => `'${s}'`).join(', ')), item.range, MarkerSeverity.Error));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validAttributeNames = {
|
||||
[PromptsType.prompt]: ['description', 'model', 'tools', 'mode'],
|
||||
[PromptsType.instructions]: ['description', 'applyTo', 'excludeAgent'],
|
||||
[PromptsType.mode]: ['description', 'model', 'tools', 'advancedOptions']
|
||||
[PromptsType.mode]: ['description', 'model', 'tools', 'advancedOptions', 'handoffs']
|
||||
};
|
||||
const validAttributeNamesNoExperimental = {
|
||||
[PromptsType.prompt]: validAttributeNames[PromptsType.prompt].filter(name => !isExperimentalAttribute(name)),
|
||||
|
||||
@@ -180,8 +180,44 @@ export class PromptHeader {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get handOffs(): IHandOff[] | undefined {
|
||||
const handoffsAttribute = this._parsedHeader.attributes.find(attr => attr.key === 'handoffs');
|
||||
if (!handoffsAttribute) {
|
||||
return undefined;
|
||||
}
|
||||
if (handoffsAttribute.value.type === 'array') {
|
||||
// Array format: list of objects: { agent, label, prompt, send? }
|
||||
const handoffs: IHandOff[] = [];
|
||||
for (const item of handoffsAttribute.value.items) {
|
||||
if (item.type === 'object') {
|
||||
let agent: string | undefined;
|
||||
let label: string | undefined;
|
||||
let prompt: string | undefined;
|
||||
let send: boolean | undefined;
|
||||
for (const prop of item.properties) {
|
||||
if (prop.key.value === 'agent' && prop.value.type === 'string') {
|
||||
agent = prop.value.value;
|
||||
} else if (prop.key.value === 'label' && prop.value.type === 'string') {
|
||||
label = prop.value.value;
|
||||
} else if (prop.key.value === 'prompt' && prop.value.type === 'string') {
|
||||
prompt = prop.value.value;
|
||||
} else if (prop.key.value === 'send' && prop.value.type === 'boolean') {
|
||||
send = prop.value.value;
|
||||
}
|
||||
}
|
||||
if (agent && label && prompt !== undefined) {
|
||||
handoffs.push({ agent, label, prompt, send });
|
||||
}
|
||||
}
|
||||
}
|
||||
return handoffs;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IHandOff { readonly agent: string; readonly label: string; readonly prompt: string; readonly send?: boolean }
|
||||
|
||||
export interface IHeaderAttribute {
|
||||
readonly range: Range;
|
||||
readonly key: string;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js
|
||||
import { PromptsType } from '../promptTypes.js';
|
||||
import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IChatModeInstructions } from '../../chatModes.js';
|
||||
import { ParsedPromptFile } from './newPromptsParser.js';
|
||||
import { IHandOff, ParsedPromptFile } from './newPromptsParser.js';
|
||||
import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js';
|
||||
|
||||
/**
|
||||
@@ -106,6 +106,11 @@ export interface ICustomChatMode {
|
||||
* Contents of the custom chat mode file body and other mode instructions.
|
||||
*/
|
||||
readonly modeInstructions: IChatModeInstructions;
|
||||
|
||||
/**
|
||||
* Hand-offs defined in the custom chat mode file.
|
||||
*/
|
||||
readonly handOffs?: readonly IHandOff[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -282,8 +282,8 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
if (!ast.header) {
|
||||
return { uri, name, modeInstructions };
|
||||
}
|
||||
const { description, model, tools } = ast.header;
|
||||
return { uri, name, description, model, tools, modeInstructions };
|
||||
const { description, model, tools, handOffs } = ast.header;
|
||||
return { uri, name, description, model, tools, handOffs, modeInstructions };
|
||||
|
||||
})
|
||||
);
|
||||
|
||||
@@ -192,6 +192,47 @@ suite('PromptValidator', () => {
|
||||
assert.strictEqual(markers[0].severity, MarkerSeverity.Warning);
|
||||
assert.ok(markers[0].message.startsWith(`Attribute 'applyTo' is not supported in mode files.`));
|
||||
});
|
||||
|
||||
test('tools with invalid handoffs', async () => {
|
||||
{
|
||||
const content = [
|
||||
'---',
|
||||
'description: "Test"',
|
||||
`handoffs: next`,
|
||||
'---',
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.mode);
|
||||
assert.strictEqual(markers.length, 1);
|
||||
assert.deepStrictEqual(markers.map(m => m.message), [`The 'handoffs' attribute must be an array.`]);
|
||||
}
|
||||
{
|
||||
const content = [
|
||||
'---',
|
||||
'description: "Test"',
|
||||
`handoffs:`,
|
||||
` - label: '123'`,
|
||||
'---',
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.mode);
|
||||
assert.strictEqual(markers.length, 1);
|
||||
assert.deepStrictEqual(markers.map(m => m.message), [`Missing required properties 'agent', 'prompt' in handoff object.`]);
|
||||
}
|
||||
{
|
||||
const content = [
|
||||
'---',
|
||||
'description: "Test"',
|
||||
`handoffs:`,
|
||||
` - label: '123'`,
|
||||
` agent: ''`,
|
||||
` prompt: ''`,
|
||||
` send: true`,
|
||||
'---',
|
||||
].join('\n');
|
||||
const markers = await validate(content, PromptsType.mode);
|
||||
assert.strictEqual(markers.length, 1);
|
||||
assert.deepStrictEqual(markers.map(m => m.message), [`The 'agent' property in a handoff must be a non-empty string.`]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('instructions', () => {
|
||||
|
||||
@@ -130,6 +130,7 @@ suite('ChatModeService', () => {
|
||||
assert.strictEqual(testMode.kind, ChatModeKind.Agent);
|
||||
assert.deepStrictEqual(testMode.customTools?.get(), customMode.tools);
|
||||
assert.deepStrictEqual(testMode.modeInstructions?.get(), customMode.modeInstructions);
|
||||
assert.deepStrictEqual(testMode.handOffs?.get(), customMode.handOffs);
|
||||
assert.strictEqual(testMode.uri?.get().toString(), customMode.uri.toString());
|
||||
});
|
||||
|
||||
|
||||
+60
@@ -57,6 +57,66 @@ suite('NewPromptsParser', () => {
|
||||
assert.deepEqual(result.header.tools, ['tool1', 'tool2']);
|
||||
});
|
||||
|
||||
test('mode with handoff', async () => {
|
||||
const uri = URI.parse('file:///test/chatmode.md');
|
||||
const content = [
|
||||
/* 01 */'---',
|
||||
/* 02 */`description: "Agent test"`,
|
||||
/* 03 */'model: GPT 4.1',
|
||||
/* 04 */'handoffs:',
|
||||
/* 05 */' - label: "Implement"',
|
||||
/* 06 */' agent: Default',
|
||||
/* 07 */' prompt: "Implement the plan"',
|
||||
/* 08 */' send: false',
|
||||
/* 09 */' - label: "Save"',
|
||||
/* 10 */' agent: Default',
|
||||
/* 11 */' prompt: "Save the plan to a file"',
|
||||
/* 12 */' send: true',
|
||||
/* 13 */'---',
|
||||
].join('\n');
|
||||
const result = new NewPromptsParser().parse(uri, content);
|
||||
assert.deepEqual(result.uri, uri);
|
||||
assert.ok(result.header);
|
||||
assert.deepEqual(result.header.range, { startLineNumber: 2, startColumn: 1, endLineNumber: 13, endColumn: 1 });
|
||||
assert.deepEqual(result.header.attributes, [
|
||||
{ key: 'description', range: new Range(2, 1, 2, 26), value: { type: 'string', value: 'Agent test', range: new Range(2, 14, 2, 26) } },
|
||||
{ key: 'model', range: new Range(3, 1, 3, 15), value: { type: 'string', value: 'GPT 4.1', range: new Range(3, 8, 3, 15) } },
|
||||
{
|
||||
key: 'handoffs', range: new Range(4, 1, 12, 15), value: {
|
||||
type: 'array',
|
||||
range: new Range(5, 3, 12, 15),
|
||||
items: [
|
||||
{
|
||||
type: 'object', range: new Range(5, 5, 8, 16),
|
||||
properties: [
|
||||
{ key: { type: 'string', value: 'label', range: new Range(5, 5, 5, 10) }, value: { type: 'string', value: 'Implement', range: new Range(5, 12, 5, 23) } },
|
||||
{ key: { type: 'string', value: 'agent', range: new Range(6, 5, 6, 10) }, value: { type: 'string', value: 'Default', range: new Range(6, 12, 6, 19) } },
|
||||
{ key: { type: 'string', value: 'prompt', range: new Range(7, 5, 7, 11) }, value: { type: 'string', value: 'Implement the plan', range: new Range(7, 13, 7, 33) } },
|
||||
{ key: { type: 'string', value: 'send', range: new Range(8, 5, 8, 9) }, value: { type: 'boolean', value: false, range: new Range(8, 11, 8, 16) } },
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'object', range: new Range(9, 5, 12, 15),
|
||||
properties: [
|
||||
{ key: { type: 'string', value: 'label', range: new Range(9, 5, 9, 10) }, value: { type: 'string', value: 'Save', range: new Range(9, 12, 9, 18) } },
|
||||
{ key: { type: 'string', value: 'agent', range: new Range(10, 5, 10, 10) }, value: { type: 'string', value: 'Default', range: new Range(10, 12, 10, 19) } },
|
||||
{ key: { type: 'string', value: 'prompt', range: new Range(11, 5, 11, 11) }, value: { type: 'string', value: 'Save the plan to a file', range: new Range(11, 13, 11, 38) } },
|
||||
{ key: { type: 'string', value: 'send', range: new Range(12, 5, 12, 9) }, value: { type: 'boolean', value: true, range: new Range(12, 11, 12, 15) } },
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
]);
|
||||
assert.deepEqual(result.header.description, 'Agent test');
|
||||
assert.deepEqual(result.header.model, 'GPT 4.1');
|
||||
assert.ok(result.header.handOffs);
|
||||
assert.deepEqual(result.header.handOffs, [
|
||||
{ label: 'Implement', agent: 'Default', prompt: 'Implement the plan', send: false },
|
||||
{ label: 'Save', agent: 'Default', prompt: 'Save the plan to a file', send: true }
|
||||
]);
|
||||
});
|
||||
|
||||
test('instructions', async () => {
|
||||
const uri = URI.parse('file:///test/prompt1.md');
|
||||
const content = [
|
||||
|
||||
+60
-20
@@ -731,26 +731,65 @@ suite('PromptsService', () => {
|
||||
});
|
||||
|
||||
|
||||
test('header with handOffs', async () => {
|
||||
const rootFolderName = 'custom-modes-with-handoffs';
|
||||
const rootFolder = `/${rootFolderName}`;
|
||||
const rootFolderUri = URI.file(rootFolder);
|
||||
|
||||
workspaceContextService.setWorkspace(testWorkspace(rootFolderUri));
|
||||
|
||||
await (instaService.createInstance(MockFilesystem,
|
||||
[{
|
||||
name: rootFolderName,
|
||||
children: [
|
||||
{
|
||||
name: '.github/chatmodes',
|
||||
children: [
|
||||
{
|
||||
name: 'mode1.chatmode.md',
|
||||
contents: [
|
||||
'---',
|
||||
'description: \'Mode file 1.\'',
|
||||
'handoffs: [ { agent: "Edit", label: "Do it", prompt: "Do it now" } ]',
|
||||
'---',
|
||||
],
|
||||
}
|
||||
],
|
||||
|
||||
},
|
||||
],
|
||||
}])).mock();
|
||||
|
||||
const result = (await service.getCustomChatModes(CancellationToken.None)).map(mode => ({ ...mode, uri: URI.from(mode.uri) }));
|
||||
const expected: ICustomChatMode[] = [
|
||||
{
|
||||
name: 'mode1',
|
||||
description: 'Mode file 1.',
|
||||
handOffs: [{ agent: 'Edit', label: 'Do it', prompt: 'Do it now', send: undefined }],
|
||||
modeInstructions: {
|
||||
content: '',
|
||||
toolReferences: [],
|
||||
metadata: undefined
|
||||
},
|
||||
model: undefined,
|
||||
tools: undefined,
|
||||
uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.chatmode.md'),
|
||||
},
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
result,
|
||||
expected,
|
||||
'Must get custom chat modes.',
|
||||
);
|
||||
});
|
||||
|
||||
test('body with tool references', async () => {
|
||||
const rootFolderName = 'custom-modes';
|
||||
const rootFolder = `/${rootFolderName}`;
|
||||
const rootFolderUri = URI.file(rootFolder);
|
||||
|
||||
sinon.stub(service, 'listPromptFiles')
|
||||
.returns(Promise.resolve([
|
||||
// local instructions
|
||||
{
|
||||
uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.instructions.md'),
|
||||
storage: PromptsStorage.local,
|
||||
type: PromptsType.mode,
|
||||
},
|
||||
{
|
||||
uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.instructions.md'),
|
||||
storage: PromptsStorage.local,
|
||||
type: PromptsType.instructions,
|
||||
},
|
||||
|
||||
]));
|
||||
workspaceContextService.setWorkspace(testWorkspace(rootFolderUri));
|
||||
|
||||
// mock current workspace file structure
|
||||
await (instaService.createInstance(MockFilesystem,
|
||||
@@ -761,7 +800,7 @@ suite('PromptsService', () => {
|
||||
name: '.github/chatmodes',
|
||||
children: [
|
||||
{
|
||||
name: 'mode1.instructions.md',
|
||||
name: 'mode1.chatmode.md',
|
||||
contents: [
|
||||
'---',
|
||||
'description: \'Mode file 1.\'',
|
||||
@@ -771,7 +810,7 @@ suite('PromptsService', () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'mode2.instructions.md',
|
||||
name: 'mode2.chatmode.md',
|
||||
contents: [
|
||||
'First use #tool2\nThen use #tool1',
|
||||
],
|
||||
@@ -782,7 +821,7 @@ suite('PromptsService', () => {
|
||||
],
|
||||
}])).mock();
|
||||
|
||||
const result = await service.getCustomChatModes(CancellationToken.None);
|
||||
const result = (await service.getCustomChatModes(CancellationToken.None)).map(mode => ({ ...mode, uri: URI.from(mode.uri) }));
|
||||
const expected: ICustomChatMode[] = [
|
||||
{
|
||||
name: 'mode1',
|
||||
@@ -793,8 +832,9 @@ suite('PromptsService', () => {
|
||||
toolReferences: [{ name: 'tool1', range: { start: 11, endExclusive: 17 } }],
|
||||
metadata: undefined
|
||||
},
|
||||
handOffs: undefined,
|
||||
model: undefined,
|
||||
uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.instructions.md'),
|
||||
uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode1.chatmode.md'),
|
||||
},
|
||||
{
|
||||
name: 'mode2',
|
||||
@@ -806,7 +846,7 @@ suite('PromptsService', () => {
|
||||
],
|
||||
metadata: undefined
|
||||
},
|
||||
uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.instructions.md'),
|
||||
uri: URI.joinPath(rootFolderUri, '.github/chatmodes/mode2.chatmode.md'),
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user