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

162 lines
8.4 KiB
TypeScript
Raw Normal View History

2023-11-13 16:30:27 +01:00
import * as vscode from 'vscode';
2024-02-23 15:56:39 +01:00
const CAT_NAMES_COMMAND_ID = 'cat.namesInEditor';
2024-03-20 20:44:27 -03:00
const CAT_PARTICIPANT_ID = 'chat-sample.cat';
2023-11-13 16:30:27 +01:00
interface ICatChatResult extends vscode.ChatResult {
metadata: {
command: string;
}
2023-11-13 15:59:54 -06:00
}
const MODEL_SELECTOR: vscode.LanguageModelChatSelector = { vendor: 'copilot', family: 'gpt-3.5-turbo' };
2023-11-13 16:30:27 +01:00
export function activate(context: vscode.ExtensionContext) {
2024-02-22 15:51:21 +01:00
// Define a Cat chat handler.
const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise<ICatChatResult> => {
// To talk to an LLM in your subcommand handler implementation, your
2023-11-13 16:30:27 +01:00
// extension can use VS Code's `requestChatAccess` API to access the Copilot API.
2023-11-13 15:59:54 -06:00
// The GitHub Copilot Chat extension implements this provider.
if (request.command == 'teach') {
2024-02-28 18:19:09 +01:00
stream.progress('Picking the right topic to teach...');
const topic = getTopic(context.history);
2023-11-13 16:30:27 +01:00
const messages = [
vscode.LanguageModelChatMessage.User('You are a cat! Your job is to explain computer science concepts in the funny manner of a cat. Always start your response by stating what concept you are explaining. Always include code samples.'),
vscode.LanguageModelChatMessage.User(topic)
2023-11-13 16:30:27 +01:00
];
const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR);
const chatResponse = await model.sendRequest(messages, {}, token);
for await (const fragment of chatResponse.text) {
stream.markdown(fragment);
2023-11-13 16:30:27 +01:00
}
stream.button({
2024-02-23 15:56:39 +01:00
command: CAT_NAMES_COMMAND_ID,
title: vscode.l10n.t('Use Cat Names in Editor')
});
return { metadata: { command: 'teach' } };
} else if (request.command == 'play') {
2024-02-28 18:19:09 +01:00
stream.progress('Throwing away the computer science books and preparing to play with some Python code...');
2023-11-13 16:30:27 +01:00
const messages = [
vscode.LanguageModelChatMessage.User('You are a cat! Reply in the voice of a cat, using cat analogies when appropriate. Be concise to prepare for cat play time.'),
vscode.LanguageModelChatMessage.User('Give a small random python code samples (that have cat names for variables). ' + request.prompt)
2023-11-13 16:30:27 +01:00
];
const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR);
const chatResponse = await model.sendRequest(messages, {}, token);
for await (const fragment of chatResponse.text) {
stream.markdown(fragment);
2023-11-13 16:30:27 +01:00
}
return { metadata: { command: 'play' } };
2023-11-13 15:59:54 -06:00
} else {
const messages = [
vscode.LanguageModelChatMessage.User(`You are a cat! Think carefully and step by step like a cat would.
2024-05-03 16:22:40 +02:00
Your job is to explain computer science concepts in the funny manner of a cat, using cat metaphors. Always start your response by stating what concept you are explaining. Always include code samples.`),
vscode.LanguageModelChatMessage.User(request.prompt)
];
const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR);
const chatResponse = await model.sendRequest(messages, {}, token);
for await (const fragment of chatResponse.text) {
// Process the output from the language model
// Replace all python function definitions with cat sounds to make the user stop looking at the code and start playing with the cat
const catFragment = fragment.replaceAll('def', 'meow');
stream.markdown(catFragment);
}
return { metadata: { command: '' } };
}
2023-11-13 16:30:27 +01:00
};
2024-02-22 15:51:21 +01:00
// Chat participants appear as top-level options in the chat input
2023-11-13 16:30:27 +01:00
// when you type `@`, and can contribute sub-commands in the chat input
// that appear when you type `/`.
2024-03-20 20:44:27 -03:00
const cat = vscode.chat.createChatParticipant(CAT_PARTICIPANT_ID, handler);
2024-02-22 15:51:21 +01:00
cat.iconPath = vscode.Uri.joinPath(context.extensionUri, 'cat.jpeg');
cat.followupProvider = {
provideFollowups(result: ICatChatResult, context: vscode.ChatContext, token: vscode.CancellationToken) {
2024-02-15 15:47:34 +01:00
return [{
prompt: 'let us play',
label: vscode.l10n.t('Play with the cat'),
command: 'play'
} satisfies vscode.ChatFollowup];
2023-11-13 16:30:27 +01:00
}
};
context.subscriptions.push(
2024-02-22 15:51:21 +01:00
cat,
2023-11-13 16:30:27 +01:00
// Register the command handler for the /meow followup
2024-02-23 15:56:39 +01:00
vscode.commands.registerTextEditorCommand(CAT_NAMES_COMMAND_ID, async (textEditor: vscode.TextEditor) => {
// Replace all variables in active editor with cat names and words
const text = textEditor.document.getText();
const messages = [
2024-05-16 20:51:16 -07:00
vscode.LanguageModelChatMessage.User(`You are a cat! Think carefully and step by step like a cat would.
2024-05-03 16:22:40 +02:00
Your job is to replace all variable names in the following code with funny cat variable names. Be creative. IMPORTANT respond just with code. Do not use markdown!`),
2024-05-16 20:51:16 -07:00
vscode.LanguageModelChatMessage.User(text)
2024-02-23 15:56:39 +01:00
];
2024-02-28 18:19:09 +01:00
let chatResponse: vscode.LanguageModelChatResponse | undefined;
try {
const [model] = await vscode.lm.selectChatModels({ vendor: 'copilot', family: 'gpt-3.5-turbo' });
chatResponse = await model.sendRequest(messages, {}, new vscode.CancellationTokenSource().token);
2024-02-28 18:19:09 +01:00
} catch (err) {
// making the chat request might fail because
// - model does not exist
// - user consent not given
// - quote limits exceeded
if (err instanceof vscode.LanguageModelError) {
console.log(err.message, err.code, err.cause)
}
return
}
2024-02-23 15:56:39 +01:00
// Clear the editor content before inserting new content
await textEditor.edit(edit => {
const start = new vscode.Position(0, 0);
const end = new vscode.Position(textEditor.document.lineCount - 1, textEditor.document.lineAt(textEditor.document.lineCount - 1).text.length);
edit.delete(new vscode.Range(start, end));
});
// Stream the code into the editor as it is coming in from the Language Model
2024-02-28 18:19:09 +01:00
try {
for await (const fragment of chatResponse.text) {
2024-02-28 18:19:09 +01:00
await textEditor.edit(edit => {
const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1);
const position = new vscode.Position(lastLine.lineNumber, lastLine.text.length);
edit.insert(position, fragment);
});
}
} catch (err) {
// async response stream may fail, e.g network interruption or server side error
2024-02-23 15:56:39 +01:00
await textEditor.edit(edit => {
const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1);
const position = new vscode.Position(lastLine.lineNumber, lastLine.text.length);
2024-02-28 18:19:09 +01:00
edit.insert(position, (<Error>err).message);
2024-02-23 15:56:39 +01:00
});
}
2023-11-13 16:30:27 +01:00
}),
);
}
// Get a random topic that the cat has not taught in the chat history yet
function getTopic(history: ReadonlyArray<vscode.ChatRequestTurn | vscode.ChatResponseTurn>): string {
const topics = ['linked list', 'recursion', 'stack', 'queue', 'pointers'];
// Filter the chat history to get only the responses from the cat
const previousCatResponses = history.filter(h => {
2024-03-20 20:44:27 -03:00
return h instanceof vscode.ChatResponseTurn && h.participant == CAT_PARTICIPANT_ID
}) as vscode.ChatResponseTurn[];
// Filter the topics to get only the topics that have not been taught by the cat yet
const topicsNoRepetition = topics.filter(topic => {
return !previousCatResponses.some(catResponse => {
return catResponse.response.some(r => {
return r instanceof vscode.ChatResponseMarkdownPart && r.value.value.includes(topic)
});
});
});
2024-02-23 14:52:23 +01:00
return topicsNoRepetition[Math.floor(Math.random() * topicsNoRepetition.length)] || 'I have taught you everything I know. Meow!';
}
2023-11-13 16:30:27 +01:00
export function deactivate() { }