mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-06-13 07:10:26 +08:00
More tools
This commit is contained in:
@ -13,9 +13,4 @@ This sample shows
|
||||
- Run the extension in a new VS Code window
|
||||
|
||||
## TODO
|
||||
- Read files from references
|
||||
- Tools
|
||||
- Something to collect some vscode window context (tab count)
|
||||
- Something that has a side effect and confirmation- run in terminal?
|
||||
- Something that returns a large amount of data and uses Prompt-TSX and references- findFiles
|
||||
- Use prompt-tsx for main prompt
|
||||
- Use prompt-tsx for main prompt and tools
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB |
12
chat-tools-sample/package-lock.json
generated
12
chat-tools-sample/package-lock.json
generated
@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "chat-sample",
|
||||
"name": "chat-tool-sample",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "chat-sample",
|
||||
"name": "chat-tool-sample",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.5.9",
|
||||
"@types/vscode": "^1.90.0",
|
||||
"@types/vscode": "^1.94.0",
|
||||
"@vscode/prompt-tsx": "^0.2.7-alpha",
|
||||
"eslint": "^7.22.0",
|
||||
"run-script-os": "^1.1.6",
|
||||
@ -213,9 +213,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.90.0.tgz",
|
||||
"integrity": "sha512-oT+ZJL7qHS9Z8bs0+WKf/kQ27qWYR3trsXpq46YDjFqBsMLG4ygGGjPaJ2tyrH0wJzjOEmDyg9PDJBBhWg9pkQ==",
|
||||
"version": "1.94.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.94.0.tgz",
|
||||
"integrity": "sha512-UyQOIUT0pb14XSqJskYnRwD2aG0QrPVefIfrW1djR+/J4KeFQ0i1+hjZoaAmeNf3Z2jleK+R2hv+EboG/m8ruw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vscode/prompt-tsx": {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "chat-tool-sample",
|
||||
"name": "chat-tools-sample",
|
||||
"publisher": "vscode-samples",
|
||||
"displayName": "Copilot Chat Tools Sample",
|
||||
"description": "Sample chat extension that registers and uses LanguageModelTools",
|
||||
@ -32,8 +32,8 @@
|
||||
],
|
||||
"languageModelTools": [
|
||||
{
|
||||
"tags": ["editors", "chat-sample"],
|
||||
"id": "chat-sample_tabCount",
|
||||
"id": "chat-tools-sample_tabCount",
|
||||
"tags": ["editors", "chat-tools-sample"],
|
||||
"name": "tabCount",
|
||||
"displayName": "Tab Count",
|
||||
"modelDescription": "The number of active tabs in a tab group",
|
||||
@ -49,6 +49,53 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"supportedContentTypes": [
|
||||
"text/plain"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "chat-tools-sample_findFiles",
|
||||
"tags": [
|
||||
"files",
|
||||
"search",
|
||||
"chat-tools-sample"
|
||||
],
|
||||
"displayName": "Find Files",
|
||||
"modelDescription": "Search for files in the current workspace",
|
||||
"parametersSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"description": "Search for files that match this glob pattern"
|
||||
}
|
||||
},
|
||||
"required": ["pattern"]
|
||||
},
|
||||
"supportedContentTypes": [
|
||||
"text/plain"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "chat-tools-sample_runInTerminal",
|
||||
"tags": [
|
||||
"terminal",
|
||||
"chat-tools-sample"
|
||||
],
|
||||
"displayName": "Run in Terminal",
|
||||
"modelDescription": "Run a command in a terminal and return the output",
|
||||
"parametersSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The command to run"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
]
|
||||
},
|
||||
"supportedContentTypes": [
|
||||
"text/plain"
|
||||
],
|
||||
@ -64,7 +111,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.5.9",
|
||||
"@types/vscode": "^1.90.0",
|
||||
"@types/vscode": "^1.94.0",
|
||||
"@vscode/prompt-tsx": "^0.2.7-alpha",
|
||||
"eslint": "^7.22.0",
|
||||
"run-script-os": "^1.1.6",
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { contentType as promptTsxContentType, renderElementJSON } from '@vscode/prompt-tsx';
|
||||
import * as vscode from 'vscode';
|
||||
import { CatVoiceToolResult } from './tools';
|
||||
import { FindFilesTool, RunInTerminalTool, TabCountTool } from './tools';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
registerChatTool(context);
|
||||
@ -8,35 +7,9 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
function registerChatTool(context: vscode.ExtensionContext) {
|
||||
interface ITabCountParameters {
|
||||
tabGroup?: number;
|
||||
}
|
||||
|
||||
context.subscriptions.push(vscode.lm.registerTool<ITabCountParameters>('chat-sample_tabCount', {
|
||||
async invoke(options, token) {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
const params = options.parameters as ITabCountParameters;
|
||||
if (typeof params.tabGroup === 'number') {
|
||||
const group = vscode.window.tabGroups.all[Math.max(params.tabGroup - 1, 0)];
|
||||
const nth = params.tabGroup === 1 ? '1st' : params.tabGroup === 2 ? '2nd' : params.tabGroup === 3 ? '3rd' : `${params.tabGroup}th`;
|
||||
return { 'text/plain': `There are ${group.tabs.length} tabs open in the ${nth} tab group.` };
|
||||
} else {
|
||||
const group = vscode.window.tabGroups.activeTabGroup;
|
||||
return { 'text/plain': `There are ${group.tabs.length} tabs open.` };
|
||||
}
|
||||
},
|
||||
prepareToolInvocation: async (options) => {
|
||||
const confirmationMessages = {
|
||||
title: 'Count the number of open tabs',
|
||||
message: new vscode.MarkdownString(`${options.participantName} will count the number of open tabs` + (options.parameters.tabGroup !== undefined ? ` in tab group ${options.parameters.tabGroup}` : ''))
|
||||
};
|
||||
|
||||
return {
|
||||
invocationMessage: 'Counting the number of tabs',
|
||||
confirmationMessages
|
||||
}
|
||||
},
|
||||
}));
|
||||
context.subscriptions.push(vscode.lm.registerTool('chat-tools-sample_tabCount', new TabCountTool()));
|
||||
context.subscriptions.push(vscode.lm.registerTool('chat-tools-sample_findFiles', new FindFilesTool()));
|
||||
context.subscriptions.push(vscode.lm.registerTool('chat-tools-sample_runInTerminal', new RunInTerminalTool()));
|
||||
}
|
||||
|
||||
interface IToolCall {
|
||||
@ -48,6 +21,7 @@ interface IToolCall {
|
||||
const llmInstructions = `Instructions:
|
||||
- The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question.
|
||||
- If you aren't sure which tool is relevant, you can call multiple tools. You can call tools repeatedly to take actions or gather as much context as needed until you have completed the task fully. Don't give up unless you are sure the request cannot be fulfilled with the tools you have.
|
||||
- Don't make assumptions about the situation- gather context first, then perform the task or answer the question.
|
||||
- Don't ask the user for confirmation to use tools, just use them.
|
||||
- After editing a file, DO NOT show the user a codeblock with the edit or new file contents. Assume that the user can see the result.`
|
||||
|
||||
|
||||
172
chat-tools-sample/src/tools.ts
Normal file
172
chat-tools-sample/src/tools.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
interface ITabCountParameters {
|
||||
tabGroup?: number;
|
||||
}
|
||||
|
||||
export class TabCountTool implements vscode.LanguageModelTool<ITabCountParameters> {
|
||||
async invoke(
|
||||
options: vscode.LanguageModelToolInvocationOptions<ITabCountParameters>,
|
||||
token: vscode.CancellationToken
|
||||
) {
|
||||
const params = options.parameters;
|
||||
if (typeof params.tabGroup === 'number') {
|
||||
const group = vscode.window.tabGroups.all[Math.max(params.tabGroup - 1, 0)];
|
||||
const nth =
|
||||
params.tabGroup === 1
|
||||
? '1st'
|
||||
: params.tabGroup === 2
|
||||
? '2nd'
|
||||
: params.tabGroup === 3
|
||||
? '3rd'
|
||||
: `${params.tabGroup}th`;
|
||||
return {
|
||||
'text/plain': `There are ${group.tabs.length} tabs open in the ${nth} tab group.`,
|
||||
};
|
||||
} else {
|
||||
const group = vscode.window.tabGroups.activeTabGroup;
|
||||
return { 'text/plain': `There are ${group.tabs.length} tabs open.` };
|
||||
}
|
||||
}
|
||||
|
||||
async prepareToolInvocation(
|
||||
options: vscode.LanguageModelToolInvocationPrepareOptions<ITabCountParameters>,
|
||||
token: vscode.CancellationToken
|
||||
) {
|
||||
const confirmationMessages = {
|
||||
title: 'Count the number of open tabs',
|
||||
message: new vscode.MarkdownString(
|
||||
`${options.participantName} will count the number of open tabs` +
|
||||
(options.parameters.tabGroup !== undefined
|
||||
? ` in tab group ${options.parameters.tabGroup}`
|
||||
: '')
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
invocationMessage: 'Counting the number of tabs',
|
||||
confirmationMessages,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface IFindFilesParameters {
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
export class FindFilesTool implements vscode.LanguageModelTool<IFindFilesParameters> {
|
||||
async invoke(
|
||||
options: vscode.LanguageModelToolInvocationOptions<IFindFilesParameters>,
|
||||
token: vscode.CancellationToken
|
||||
) {
|
||||
const params = options.parameters as IFindFilesParameters;
|
||||
const files = await vscode.workspace.findFiles(
|
||||
params.pattern,
|
||||
'**/node_modules/**',
|
||||
undefined,
|
||||
token
|
||||
);
|
||||
|
||||
const result: vscode.LanguageModelToolResult = {};
|
||||
if (options.requestedContentTypes.includes('text/plain')) {
|
||||
const strFiles = files.map((f) => f.fsPath).join('\n');
|
||||
result[
|
||||
'text/plain'
|
||||
] = `Found ${files.length} files matching "${params.pattern}":\n${strFiles}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async prepareToolInvocation(
|
||||
options: vscode.LanguageModelToolInvocationPrepareOptions<IFindFilesParameters>,
|
||||
token: vscode.CancellationToken
|
||||
) {
|
||||
return {
|
||||
invocationMessage: `Searching workspace for "${options.parameters.pattern}"`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface IRunInTerminalParameters {
|
||||
command: string;
|
||||
}
|
||||
|
||||
async function waitForShellIntegration(
|
||||
terminal: vscode.Terminal,
|
||||
timeout: number
|
||||
): Promise<void> {
|
||||
let resolve: () => void;
|
||||
let reject: (e: Error) => void;
|
||||
let p = new Promise<void>((_resolve, _reject) => {
|
||||
resolve = _resolve;
|
||||
reject = _reject;
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => reject(new Error('Could not run terminal command: shell integration is not enabled')), timeout);
|
||||
|
||||
const listener = vscode.window.onDidChangeTerminalShellIntegration((e) => {
|
||||
if (e.terminal === terminal) {
|
||||
clearTimeout(timer);
|
||||
listener.dispose();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
await p;
|
||||
}
|
||||
|
||||
export class RunInTerminalTool
|
||||
implements vscode.LanguageModelTool<IRunInTerminalParameters>
|
||||
{
|
||||
async invoke(
|
||||
options: vscode.LanguageModelToolInvocationOptions<IRunInTerminalParameters>,
|
||||
token: vscode.CancellationToken
|
||||
) {
|
||||
const result: vscode.LanguageModelToolResult = {};
|
||||
const params = options.parameters as IRunInTerminalParameters;
|
||||
|
||||
const terminal = vscode.window.createTerminal('Language Model Tool User');
|
||||
terminal.show();
|
||||
try {
|
||||
await waitForShellIntegration(terminal, 5000);
|
||||
} catch(e) {
|
||||
if (options.requestedContentTypes.includes('text/plain')) {
|
||||
result['text/plain'] = (e as Error).message;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const execution = terminal.shellIntegration!.executeCommand(params.command);
|
||||
const terminalStream = execution.read();
|
||||
|
||||
let terminalResult = '';
|
||||
for await (const chunk of terminalStream) {
|
||||
terminalResult += chunk;
|
||||
}
|
||||
|
||||
if (options.requestedContentTypes.includes('text/plain')) {
|
||||
result['text/plain'] = terminalResult;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async prepareToolInvocation(
|
||||
options: vscode.LanguageModelToolInvocationPrepareOptions<IRunInTerminalParameters>,
|
||||
token: vscode.CancellationToken
|
||||
) {
|
||||
const confirmationMessages = {
|
||||
title: 'Run command in terminal',
|
||||
message: new vscode.MarkdownString(
|
||||
`${options.participantName} will run this command in a terminal:` +
|
||||
`\n\n\`\`\`\n${options.parameters.command}\n\`\`\`\n`
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
invocationMessage: `Running command in terminal`,
|
||||
confirmationMessages,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import {
|
||||
BasePromptElementProps,
|
||||
PromptElement,
|
||||
PromptSizing,
|
||||
TextChunk,
|
||||
UserMessage
|
||||
} from '@vscode/prompt-tsx';
|
||||
|
||||
export class CatVoiceToolResult extends PromptElement<BasePromptElementProps, void> {
|
||||
render(state: void, sizing: PromptSizing) {
|
||||
return (
|
||||
<>
|
||||
<TextChunk>
|
||||
Reply in the voice of a cat! Use cat analogies when appropriate.
|
||||
</TextChunk>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user