Files
vscode-extension-samples/chat-sample/src/extension.ts

129 lines
5.3 KiB
TypeScript
Raw Normal View History

2024-08-26 16:36:17 -07:00
import { contentType, renderElementJSON } from '@vscode/prompt-tsx';
2023-11-13 16:30:27 +01:00
import * as vscode from 'vscode';
2024-08-26 16:36:17 -07:00
import { CatToolPrompt } from './play';
2023-11-13 16:30:27 +01:00
export function activate(context: vscode.ExtensionContext) {
2024-08-05 15:34:56 -07:00
context.subscriptions.push(registerChatTool());
context.subscriptions.push(registerChatParticipant());
}
function registerChatTool() {
2024-08-26 16:36:17 -07:00
vscode.lm.registerTool('chat-sample_catVoice', {
async invoke(parameters, token) {
return {
[contentType]: await renderElementJSON(CatToolPrompt, {}, parameters.tokenOptions, token),
toString() {
return 'Reply in the voice of a cat! Use cat analogies when appropriate.';
},
};
},
});
2024-08-13 15:24:22 -07:00
return vscode.lm.registerTool('chat-sample_tabCount', {
2024-08-05 15:34:56 -07:00
async invoke(parameters, token) {
return {
toString() {
const activeTabCount = vscode.window.tabGroups.activeTabGroup.tabs.length;
return `There are ${activeTabCount} tabs open.`;
},
};
},
});
}
2023-11-13 16:30:27 +01:00
2024-08-05 15:34:56 -07:00
function registerChatParticipant() {
2024-06-06 15:59:02 -07:00
const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => {
const models = await vscode.lm.selectChatModels({
vendor: 'copilot',
2024-08-05 15:34:56 -07:00
family: 'gpt-4o'
2024-06-06 15:59:02 -07:00
});
2024-08-05 15:34:56 -07:00
const model = models[0];
2024-08-13 15:24:22 -07:00
stream.markdown(`Available tools: ${vscode.lm.tools.map(tool => tool.id).join(', ')}\n\n`);
2024-06-06 15:59:02 -07:00
2024-08-24 17:00:00 -07:00
const allTools = vscode.lm.tools.map((tool): vscode.LanguageModelChatTool => {
2024-08-06 15:44:45 -07:00
return {
2024-08-13 15:24:22 -07:00
name: tool.id,
description: tool.modelDescription,
2024-08-06 15:44:45 -07:00
parametersSchema: tool.parametersSchema ?? {}
};
});
2024-06-06 15:59:02 -07:00
const options: vscode.LanguageModelChatRequestOptions = {
justification: 'Just because!',
2024-08-05 15:34:56 -07:00
};
2024-06-06 15:59:02 -07:00
const messages = [
vscode.LanguageModelChatMessage.User(`There is a selection of tools that may give helpful context to answer the user's query. If you aren't sure which tool is relevant, you can call multiple tools.`),
2024-08-05 15:34:56 -07:00
vscode.LanguageModelChatMessage.User(request.prompt),
];
2024-08-13 15:24:22 -07:00
const toolReferences = [...request.toolReferences];
2024-06-06 15:59:02 -07:00
const runWithFunctions = async () => {
2024-08-13 15:24:22 -07:00
const requestedTool = toolReferences.shift();
2024-08-06 15:44:45 -07:00
if (requestedTool) {
2024-08-13 15:24:22 -07:00
options.toolChoice = requestedTool.id;
options.tools = allTools.filter(tool => tool.name === requestedTool.id);
2024-08-06 15:44:45 -07:00
} else {
options.toolChoice = undefined;
options.tools = allTools;
}
2024-06-06 15:59:02 -07:00
let didReceiveFunctionUse = false;
2024-08-05 15:34:56 -07:00
const response = await model.sendRequest(messages, options, token);
2024-06-06 15:59:02 -07:00
for await (const part of response.stream) {
if (part instanceof vscode.LanguageModelChatResponseTextPart) {
2024-08-05 15:34:56 -07:00
stream.markdown(part.value);
2024-08-24 17:00:00 -07:00
} else if (part instanceof vscode.LanguageModelChatResponseToolCallPart) {
2024-08-13 15:24:22 -07:00
const tool = vscode.lm.tools.find(tool => tool.id === part.name);
2024-06-06 15:59:02 -07:00
if (!tool) {
// BAD tool choice?
continue;
}
2024-08-05 15:34:56 -07:00
let parameters: any;
try {
parameters = JSON.parse(part.parameters);
} catch (err) {
throw new Error(`Got invalid tool use parameters: "${part.parameters}". (${(err as Error).message})`);
}
2024-08-13 15:24:22 -07:00
stream.progress(`Calling tool: ${tool.id} with ${part.parameters}`);
2024-08-24 17:00:00 -07:00
const result = await vscode.lm.invokeTool(tool.id, { parameters: JSON.parse(part.parameters) }, token);
2024-06-06 15:59:02 -07:00
2024-08-24 17:00:00 -07:00
let assistantMsg = vscode.LanguageModelChatMessage.Assistant('');
assistantMsg.content2 = [new vscode.LanguageModelChatResponseToolCallPart(tool.id, part.toolCallId, part.parameters)];
messages.push(assistantMsg);
2024-06-06 15:59:02 -07:00
// NOTE that the result of calling a function is a special content type of a USER-message
let message = vscode.LanguageModelChatMessage.User('');
2024-08-24 17:00:00 -07:00
message.content2 = [new vscode.LanguageModelChatMessageToolResultPart(part.toolCallId, result.toString())];
2024-08-05 15:34:56 -07:00
messages.push(message);
2024-06-06 15:59:02 -07:00
// IMPORTANT
// IMPORTANT working around CAPI always wanting to end with a `User`-message
// IMPORTANT
2024-08-13 15:24:22 -07:00
messages.push(vscode.LanguageModelChatMessage.User(`Above is the result of calling the function ${tool.id}. The user cannot see this result, so you should explain it to the user if referencing it in your answer.`));
2024-06-06 15:59:02 -07:00
didReceiveFunctionUse = true;
}
}
2024-06-06 15:59:02 -07:00
if (didReceiveFunctionUse) {
// RE-enter
return runWithFunctions();
}
};
2024-08-05 15:34:56 -07:00
await runWithFunctions();
2023-11-13 16:30:27 +01:00
};
2024-08-05 15:34:56 -07:00
const toolUser = vscode.chat.createChatParticipant('chat-sample.tools', handler);
toolUser.iconPath = new vscode.ThemeIcon('tools');
2024-08-05 15:34:56 -07:00
return toolUser;
2023-11-13 16:30:27 +01:00
}
2023-11-13 16:30:27 +01:00
export function deactivate() { }