mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-04-27 16:55:44 +08:00
Format all ts in project
This commit is contained in:
@ -25,9 +25,9 @@ export class SampleChatModelProvider implements LanguageModelChatProvider2 {
|
||||
}
|
||||
async provideLanguageModelChatResponse(model: LanguageModelChatInformation, _messages: Array<LanguageModelChatMessage>, _options: LanguageModelChatRequestHandleOptions, progress: Progress<ChatResponseFragment2>, _token: CancellationToken): Promise<void> {
|
||||
if (model.id === "sample-dog-model") {
|
||||
progress.report({index: 0, part: new LanguageModelTextPart("Woof! This is a dog model response.") });
|
||||
progress.report({ index: 0, part: new LanguageModelTextPart("Woof! This is a dog model response.") });
|
||||
} else if (model.id === "sample-cat-model") {
|
||||
progress.report({index: 0, part: new LanguageModelTextPart("Meow! This is a cat model response.") });
|
||||
progress.report({ index: 0, part: new LanguageModelTextPart("Meow! This is a cat model response.") });
|
||||
} else {
|
||||
progress.report({ index: 0, part: new LanguageModelTextPart("Unknown model.") });
|
||||
}
|
||||
|
||||
@ -2,34 +2,34 @@ import * as vscode from 'vscode';
|
||||
import * as chatUtils from '@vscode/chat-extension-utils';
|
||||
|
||||
export function registerChatLibChatParticipant(context: vscode.ExtensionContext) {
|
||||
const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => {
|
||||
if (request.command === 'list') {
|
||||
stream.markdown(`Available tools: ${vscode.lm.tools.map(tool => tool.name).join(', ')}\n\n`);
|
||||
return;
|
||||
}
|
||||
const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => {
|
||||
if (request.command === 'list') {
|
||||
stream.markdown(`Available tools: ${vscode.lm.tools.map(tool => tool.name).join(', ')}\n\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
const tools = request.command === 'all' ?
|
||||
vscode.lm.tools :
|
||||
vscode.lm.tools.filter(tool => tool.tags.includes('chat-tools-sample'));
|
||||
const tools = request.command === 'all' ?
|
||||
vscode.lm.tools :
|
||||
vscode.lm.tools.filter(tool => tool.tags.includes('chat-tools-sample'));
|
||||
|
||||
const libResult = chatUtils.sendChatParticipantRequest(
|
||||
request,
|
||||
chatContext,
|
||||
{
|
||||
prompt: 'You are a cat! Answer as a cat.',
|
||||
responseStreamOptions: {
|
||||
stream,
|
||||
references: true,
|
||||
responseText: true
|
||||
},
|
||||
tools
|
||||
},
|
||||
token);
|
||||
const libResult = chatUtils.sendChatParticipantRequest(
|
||||
request,
|
||||
chatContext,
|
||||
{
|
||||
prompt: 'You are a cat! Answer as a cat.',
|
||||
responseStreamOptions: {
|
||||
stream,
|
||||
references: true,
|
||||
responseText: true
|
||||
},
|
||||
tools
|
||||
},
|
||||
token);
|
||||
|
||||
return await libResult.result;
|
||||
};
|
||||
return await libResult.result;
|
||||
};
|
||||
|
||||
const chatLibParticipant = vscode.chat.createChatParticipant('chat-tools-sample.catTools', handler);
|
||||
chatLibParticipant.iconPath = vscode.Uri.joinPath(context.extensionUri, 'cat.jpeg');
|
||||
context.subscriptions.push(chatLibParticipant);
|
||||
const chatLibParticipant = vscode.chat.createChatParticipant('chat-tools-sample.catTools', handler);
|
||||
chatLibParticipant.iconPath = vscode.Uri.joinPath(context.extensionUri, 'cat.jpeg');
|
||||
context.subscriptions.push(chatLibParticipant);
|
||||
}
|
||||
@ -5,11 +5,11 @@ import { registerToolUserChatParticipant } from './toolParticipant';
|
||||
import { registerChatTools } from './tools';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
registerSimpleParticipant(context);
|
||||
registerToolUserChatParticipant(context);
|
||||
registerChatLibChatParticipant(context);
|
||||
registerSimpleParticipant(context);
|
||||
registerToolUserChatParticipant(context);
|
||||
registerChatLibChatParticipant(context);
|
||||
|
||||
registerChatTools(context);
|
||||
registerChatTools(context);
|
||||
}
|
||||
|
||||
export function deactivate() { }
|
||||
|
||||
@ -6,218 +6,218 @@ const CAT_NAMES_COMMAND_ID = 'cat.namesInEditor';
|
||||
const CAT_PARTICIPANT_ID = 'chat-sample.cat';
|
||||
|
||||
interface ICatChatResult extends vscode.ChatResult {
|
||||
metadata: {
|
||||
command: string;
|
||||
}
|
||||
metadata: {
|
||||
command: string;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerSimpleParticipant(context: vscode.ExtensionContext) {
|
||||
|
||||
// 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
|
||||
// extension can use VS Code's `requestChatAccess` API to access the Copilot API.
|
||||
// The GitHub Copilot Chat extension implements this provider.
|
||||
if (request.command === 'randomTeach') {
|
||||
stream.progress('Picking the right topic to teach...');
|
||||
const topic = getTopic(context.history);
|
||||
try {
|
||||
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)
|
||||
];
|
||||
// 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
|
||||
// extension can use VS Code's `requestChatAccess` API to access the Copilot API.
|
||||
// The GitHub Copilot Chat extension implements this provider.
|
||||
if (request.command === 'randomTeach') {
|
||||
stream.progress('Picking the right topic to teach...');
|
||||
const topic = getTopic(context.history);
|
||||
try {
|
||||
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)
|
||||
];
|
||||
|
||||
const chatResponse = await request.model.sendRequest(messages, {}, token);
|
||||
for await (const fragment of chatResponse.text) {
|
||||
stream.markdown(fragment);
|
||||
}
|
||||
const chatResponse = await request.model.sendRequest(messages, {}, token);
|
||||
for await (const fragment of chatResponse.text) {
|
||||
stream.markdown(fragment);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
handleError(logger, err, stream);
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(logger, err, stream);
|
||||
}
|
||||
|
||||
stream.button({
|
||||
command: CAT_NAMES_COMMAND_ID,
|
||||
title: vscode.l10n.t('Use Cat Names in Editor')
|
||||
});
|
||||
stream.button({
|
||||
command: CAT_NAMES_COMMAND_ID,
|
||||
title: vscode.l10n.t('Use Cat Names in Editor')
|
||||
});
|
||||
|
||||
logger.logUsage('request', { kind: 'randomTeach' });
|
||||
return { metadata: { command: 'randomTeach' } };
|
||||
} else if (request.command === 'play') {
|
||||
stream.progress('Throwing away the computer science books and preparing to play with some Python code...');
|
||||
try {
|
||||
// Here's an example of how to use the prompt-tsx library to build a prompt
|
||||
const { messages } = await renderPrompt(
|
||||
PlayPrompt,
|
||||
{ userQuery: request.prompt },
|
||||
{ modelMaxPromptTokens: request.model.maxInputTokens },
|
||||
request.model);
|
||||
logger.logUsage('request', { kind: 'randomTeach' });
|
||||
return { metadata: { command: 'randomTeach' } };
|
||||
} else if (request.command === 'play') {
|
||||
stream.progress('Throwing away the computer science books and preparing to play with some Python code...');
|
||||
try {
|
||||
// Here's an example of how to use the prompt-tsx library to build a prompt
|
||||
const { messages } = await renderPrompt(
|
||||
PlayPrompt,
|
||||
{ userQuery: request.prompt },
|
||||
{ modelMaxPromptTokens: request.model.maxInputTokens },
|
||||
request.model);
|
||||
|
||||
const chatResponse = await request.model.sendRequest(messages, {}, token);
|
||||
for await (const fragment of chatResponse.text) {
|
||||
stream.markdown(fragment);
|
||||
}
|
||||
const chatResponse = await request.model.sendRequest(messages, {}, token);
|
||||
for await (const fragment of chatResponse.text) {
|
||||
stream.markdown(fragment);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
handleError(logger, err, stream);
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(logger, err, stream);
|
||||
}
|
||||
|
||||
logger.logUsage('request', { kind: 'play' });
|
||||
return { metadata: { command: 'play' } };
|
||||
} else {
|
||||
try {
|
||||
const messages = [
|
||||
vscode.LanguageModelChatMessage.User(`You are a cat! Think carefully and step by step like a cat would.
|
||||
logger.logUsage('request', { kind: 'play' });
|
||||
return { metadata: { command: 'play' } };
|
||||
} else {
|
||||
try {
|
||||
const messages = [
|
||||
vscode.LanguageModelChatMessage.User(`You are a cat! Think carefully and step by step like a cat would.
|
||||
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)
|
||||
];
|
||||
vscode.LanguageModelChatMessage.User(request.prompt)
|
||||
];
|
||||
|
||||
const chatResponse = await request.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);
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(logger, err, stream);
|
||||
}
|
||||
const chatResponse = await request.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);
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(logger, err, stream);
|
||||
}
|
||||
|
||||
logger.logUsage('request', { kind: '' });
|
||||
return { metadata: { command: '' } };
|
||||
}
|
||||
};
|
||||
logger.logUsage('request', { kind: '' });
|
||||
return { metadata: { command: '' } };
|
||||
}
|
||||
};
|
||||
|
||||
// Chat participants appear as top-level options in the chat input
|
||||
// when you type `@`, and can contribute sub-commands in the chat input
|
||||
// that appear when you type `/`.
|
||||
const cat = vscode.chat.createChatParticipant(CAT_PARTICIPANT_ID, handler);
|
||||
cat.iconPath = vscode.Uri.joinPath(context.extensionUri, 'cat.jpeg');
|
||||
cat.followupProvider = {
|
||||
provideFollowups(_result: ICatChatResult, _context: vscode.ChatContext, _token: vscode.CancellationToken) {
|
||||
return [{
|
||||
prompt: 'let us play',
|
||||
label: vscode.l10n.t('Play with the cat'),
|
||||
command: 'play'
|
||||
} satisfies vscode.ChatFollowup];
|
||||
}
|
||||
};
|
||||
// Chat participants appear as top-level options in the chat input
|
||||
// when you type `@`, and can contribute sub-commands in the chat input
|
||||
// that appear when you type `/`.
|
||||
const cat = vscode.chat.createChatParticipant(CAT_PARTICIPANT_ID, handler);
|
||||
cat.iconPath = vscode.Uri.joinPath(context.extensionUri, 'cat.jpeg');
|
||||
cat.followupProvider = {
|
||||
provideFollowups(_result: ICatChatResult, _context: vscode.ChatContext, _token: vscode.CancellationToken) {
|
||||
return [{
|
||||
prompt: 'let us play',
|
||||
label: vscode.l10n.t('Play with the cat'),
|
||||
command: 'play'
|
||||
} satisfies vscode.ChatFollowup];
|
||||
}
|
||||
};
|
||||
|
||||
const logger = vscode.env.createTelemetryLogger({
|
||||
sendEventData(eventName, data) {
|
||||
// Capture event telemetry
|
||||
console.log(`Event: ${eventName}`);
|
||||
console.log(`Data: ${JSON.stringify(data)}`);
|
||||
},
|
||||
sendErrorData(error, data) {
|
||||
// Capture error telemetry
|
||||
console.error(`Error: ${error}`);
|
||||
console.error(`Data: ${JSON.stringify(data)}`);
|
||||
}
|
||||
});
|
||||
const logger = vscode.env.createTelemetryLogger({
|
||||
sendEventData(eventName, data) {
|
||||
// Capture event telemetry
|
||||
console.log(`Event: ${eventName}`);
|
||||
console.log(`Data: ${JSON.stringify(data)}`);
|
||||
},
|
||||
sendErrorData(error, data) {
|
||||
// Capture error telemetry
|
||||
console.error(`Error: ${error}`);
|
||||
console.error(`Data: ${JSON.stringify(data)}`);
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(cat.onDidReceiveFeedback((feedback: vscode.ChatResultFeedback) => {
|
||||
// Log chat result feedback to be able to compute the success matric of the participant
|
||||
// unhelpful / totalRequests is a good success metric
|
||||
logger.logUsage('chatResultFeedback', {
|
||||
kind: feedback.kind
|
||||
});
|
||||
}));
|
||||
context.subscriptions.push(cat.onDidReceiveFeedback((feedback: vscode.ChatResultFeedback) => {
|
||||
// Log chat result feedback to be able to compute the success matric of the participant
|
||||
// unhelpful / totalRequests is a good success metric
|
||||
logger.logUsage('chatResultFeedback', {
|
||||
kind: feedback.kind
|
||||
});
|
||||
}));
|
||||
|
||||
context.subscriptions.push(
|
||||
cat,
|
||||
// Register the command handler for the /meow followup
|
||||
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();
|
||||
context.subscriptions.push(
|
||||
cat,
|
||||
// Register the command handler for the /meow followup
|
||||
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();
|
||||
|
||||
let chatResponse: vscode.LanguageModelChatResponse | undefined;
|
||||
try {
|
||||
// Use gpt-4o since it is fast and high quality.
|
||||
const [model] = await vscode.lm.selectChatModels({ vendor: 'copilot', family: 'gpt-4o' });
|
||||
if (!model) {
|
||||
console.log('Model not found. Please make sure the GitHub Copilot Chat extension is installed and enabled.');
|
||||
return;
|
||||
}
|
||||
let chatResponse: vscode.LanguageModelChatResponse | undefined;
|
||||
try {
|
||||
// Use gpt-4o since it is fast and high quality.
|
||||
const [model] = await vscode.lm.selectChatModels({ vendor: 'copilot', family: 'gpt-4o' });
|
||||
if (!model) {
|
||||
console.log('Model not found. Please make sure the GitHub Copilot Chat extension is installed and enabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = [
|
||||
vscode.LanguageModelChatMessage.User(`You are a cat! Think carefully and step by step like a cat would.
|
||||
const messages = [
|
||||
vscode.LanguageModelChatMessage.User(`You are a cat! Think carefully and step by step like a cat would.
|
||||
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!`),
|
||||
vscode.LanguageModelChatMessage.User(text)
|
||||
];
|
||||
chatResponse = await model.sendRequest(messages, {}, new vscode.CancellationTokenSource().token);
|
||||
vscode.LanguageModelChatMessage.User(text)
|
||||
];
|
||||
chatResponse = await model.sendRequest(messages, {}, new vscode.CancellationTokenSource().token);
|
||||
|
||||
} catch (err) {
|
||||
if (err instanceof vscode.LanguageModelError) {
|
||||
console.log(err.message, err.code, err.cause);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof vscode.LanguageModelError) {
|
||||
console.log(err.message, err.code, err.cause);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 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));
|
||||
});
|
||||
// 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
|
||||
try {
|
||||
for await (const fragment of chatResponse.text) {
|
||||
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
|
||||
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, (err as Error).message);
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
// Stream the code into the editor as it is coming in from the Language Model
|
||||
try {
|
||||
for await (const fragment of chatResponse.text) {
|
||||
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
|
||||
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, (err as Error).message);
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function handleError(logger: vscode.TelemetryLogger, err: any, stream: vscode.ChatResponseStream): void {
|
||||
// making the chat request might fail because
|
||||
// - model does not exist
|
||||
// - user consent not given
|
||||
// - quote limits exceeded
|
||||
logger.logError(err);
|
||||
// making the chat request might fail because
|
||||
// - model does not exist
|
||||
// - user consent not given
|
||||
// - quote limits exceeded
|
||||
logger.logError(err);
|
||||
|
||||
if (err instanceof vscode.LanguageModelError) {
|
||||
console.log(err.message, err.code, err.cause);
|
||||
if (err.cause instanceof Error && err.cause.message.includes('off_topic')) {
|
||||
stream.markdown(vscode.l10n.t('I\'m sorry, I can only explain computer science concepts.'));
|
||||
}
|
||||
} else {
|
||||
// re-throw other errors so they show up in the UI
|
||||
throw err;
|
||||
}
|
||||
if (err instanceof vscode.LanguageModelError) {
|
||||
console.log(err.message, err.code, err.cause);
|
||||
if (err.cause instanceof Error && err.cause.message.includes('off_topic')) {
|
||||
stream.markdown(vscode.l10n.t('I\'m sorry, I can only explain computer science concepts.'));
|
||||
}
|
||||
} else {
|
||||
// re-throw other errors so they show up in the UI
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
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 => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return topicsNoRepetition[Math.floor(Math.random() * topicsNoRepetition.length)] || 'I have taught you everything I know. Meow!';
|
||||
return topicsNoRepetition[Math.floor(Math.random() * topicsNoRepetition.length)] || 'I have taught you everything I know. Meow!';
|
||||
}
|
||||
|
||||
@ -3,136 +3,136 @@ import * as vscode from 'vscode';
|
||||
import { ToolCallRound, ToolResultMetadata, ToolUserPrompt } from './toolsPrompt';
|
||||
|
||||
export interface TsxToolUserMetadata {
|
||||
toolCallsMetadata: ToolCallsMetadata;
|
||||
toolCallsMetadata: ToolCallsMetadata;
|
||||
}
|
||||
|
||||
export interface ToolCallsMetadata {
|
||||
toolCallRounds: ToolCallRound[];
|
||||
toolCallResults: Record<string, vscode.LanguageModelToolResult>;
|
||||
toolCallRounds: ToolCallRound[];
|
||||
toolCallResults: Record<string, vscode.LanguageModelToolResult>;
|
||||
}
|
||||
|
||||
export function isTsxToolUserMetadata(obj: unknown): obj is TsxToolUserMetadata {
|
||||
// If you change the metadata format, you would have to make this stricter or handle old objects in old ChatRequest metadata
|
||||
return !!obj &&
|
||||
!!(obj as TsxToolUserMetadata).toolCallsMetadata &&
|
||||
Array.isArray((obj as TsxToolUserMetadata).toolCallsMetadata.toolCallRounds);
|
||||
// If you change the metadata format, you would have to make this stricter or handle old objects in old ChatRequest metadata
|
||||
return !!obj &&
|
||||
!!(obj as TsxToolUserMetadata).toolCallsMetadata &&
|
||||
Array.isArray((obj as TsxToolUserMetadata).toolCallsMetadata.toolCallRounds);
|
||||
}
|
||||
|
||||
export function registerToolUserChatParticipant(context: vscode.ExtensionContext) {
|
||||
const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => {
|
||||
if (request.command === 'list') {
|
||||
stream.markdown(`Available tools: ${vscode.lm.tools.map(tool => tool.name).join(', ')}\n\n`);
|
||||
return;
|
||||
}
|
||||
const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => {
|
||||
if (request.command === 'list') {
|
||||
stream.markdown(`Available tools: ${vscode.lm.tools.map(tool => tool.name).join(', ')}\n\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
let model = request.model;
|
||||
if (model.vendor === 'copilot' && model.family.startsWith('o1')) {
|
||||
// The o1 models do not currently support tools
|
||||
const models = await vscode.lm.selectChatModels({
|
||||
vendor: 'copilot',
|
||||
family: 'gpt-4o'
|
||||
});
|
||||
model = models[0];
|
||||
}
|
||||
let model = request.model;
|
||||
if (model.vendor === 'copilot' && model.family.startsWith('o1')) {
|
||||
// The o1 models do not currently support tools
|
||||
const models = await vscode.lm.selectChatModels({
|
||||
vendor: 'copilot',
|
||||
family: 'gpt-4o'
|
||||
});
|
||||
model = models[0];
|
||||
}
|
||||
|
||||
// Use all tools, or tools with the tags that are relevant.
|
||||
const tools = request.command === 'all' ?
|
||||
vscode.lm.tools :
|
||||
vscode.lm.tools.filter(tool => tool.tags.includes('chat-tools-sample'));
|
||||
const options: vscode.LanguageModelChatRequestOptions = {
|
||||
justification: 'To make a request to @toolsTSX',
|
||||
};
|
||||
// Use all tools, or tools with the tags that are relevant.
|
||||
const tools = request.command === 'all' ?
|
||||
vscode.lm.tools :
|
||||
vscode.lm.tools.filter(tool => tool.tags.includes('chat-tools-sample'));
|
||||
const options: vscode.LanguageModelChatRequestOptions = {
|
||||
justification: 'To make a request to @toolsTSX',
|
||||
};
|
||||
|
||||
// Render the initial prompt
|
||||
const result = await renderPrompt(
|
||||
ToolUserPrompt,
|
||||
{
|
||||
context: chatContext,
|
||||
request,
|
||||
toolCallRounds: [],
|
||||
toolCallResults: {}
|
||||
},
|
||||
{ modelMaxPromptTokens: model.maxInputTokens },
|
||||
model);
|
||||
let messages = result.messages;
|
||||
result.references.forEach(ref => {
|
||||
if (ref.anchor instanceof vscode.Uri || ref.anchor instanceof vscode.Location) {
|
||||
stream.reference(ref.anchor);
|
||||
}
|
||||
});
|
||||
// Render the initial prompt
|
||||
const result = await renderPrompt(
|
||||
ToolUserPrompt,
|
||||
{
|
||||
context: chatContext,
|
||||
request,
|
||||
toolCallRounds: [],
|
||||
toolCallResults: {}
|
||||
},
|
||||
{ modelMaxPromptTokens: model.maxInputTokens },
|
||||
model);
|
||||
let messages = result.messages;
|
||||
result.references.forEach(ref => {
|
||||
if (ref.anchor instanceof vscode.Uri || ref.anchor instanceof vscode.Location) {
|
||||
stream.reference(ref.anchor);
|
||||
}
|
||||
});
|
||||
|
||||
const toolReferences = [...request.toolReferences];
|
||||
const accumulatedToolResults: Record<string, vscode.LanguageModelToolResult> = {};
|
||||
const toolCallRounds: ToolCallRound[] = [];
|
||||
const runWithTools = async (): Promise<void> => {
|
||||
// If a toolReference is present, force the model to call that tool
|
||||
const requestedTool = toolReferences.shift();
|
||||
if (requestedTool) {
|
||||
options.toolMode = vscode.LanguageModelChatToolMode.Required;
|
||||
options.tools = vscode.lm.tools.filter(tool => tool.name === requestedTool.name);
|
||||
} else {
|
||||
options.toolMode = undefined;
|
||||
options.tools = [...tools];
|
||||
}
|
||||
const toolReferences = [...request.toolReferences];
|
||||
const accumulatedToolResults: Record<string, vscode.LanguageModelToolResult> = {};
|
||||
const toolCallRounds: ToolCallRound[] = [];
|
||||
const runWithTools = async (): Promise<void> => {
|
||||
// If a toolReference is present, force the model to call that tool
|
||||
const requestedTool = toolReferences.shift();
|
||||
if (requestedTool) {
|
||||
options.toolMode = vscode.LanguageModelChatToolMode.Required;
|
||||
options.tools = vscode.lm.tools.filter(tool => tool.name === requestedTool.name);
|
||||
} else {
|
||||
options.toolMode = undefined;
|
||||
options.tools = [...tools];
|
||||
}
|
||||
|
||||
// Send the request to the LanguageModelChat
|
||||
const response = await model.sendRequest(messages, options, token);
|
||||
// Send the request to the LanguageModelChat
|
||||
const response = await model.sendRequest(messages, options, token);
|
||||
|
||||
// Stream text output and collect tool calls from the response
|
||||
const toolCalls: vscode.LanguageModelToolCallPart[] = [];
|
||||
let responseStr = '';
|
||||
for await (const part of response.stream) {
|
||||
if (part instanceof vscode.LanguageModelTextPart) {
|
||||
stream.markdown(part.value);
|
||||
responseStr += part.value;
|
||||
} else if (part instanceof vscode.LanguageModelToolCallPart) {
|
||||
toolCalls.push(part);
|
||||
}
|
||||
}
|
||||
// Stream text output and collect tool calls from the response
|
||||
const toolCalls: vscode.LanguageModelToolCallPart[] = [];
|
||||
let responseStr = '';
|
||||
for await (const part of response.stream) {
|
||||
if (part instanceof vscode.LanguageModelTextPart) {
|
||||
stream.markdown(part.value);
|
||||
responseStr += part.value;
|
||||
} else if (part instanceof vscode.LanguageModelToolCallPart) {
|
||||
toolCalls.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
if (toolCalls.length) {
|
||||
// If the model called any tools, then we do another round- render the prompt with those tool calls (rendering the PromptElements will invoke the tools)
|
||||
// and include the tool results in the prompt for the next request.
|
||||
toolCallRounds.push({
|
||||
response: responseStr,
|
||||
toolCalls
|
||||
});
|
||||
const result = (await renderPrompt(
|
||||
ToolUserPrompt,
|
||||
{
|
||||
context: chatContext,
|
||||
request,
|
||||
toolCallRounds,
|
||||
toolCallResults: accumulatedToolResults
|
||||
},
|
||||
{ modelMaxPromptTokens: model.maxInputTokens },
|
||||
model));
|
||||
messages = result.messages;
|
||||
const toolResultMetadata = result.metadatas.getAll(ToolResultMetadata);
|
||||
if (toolResultMetadata?.length) {
|
||||
// Cache tool results for later, so they can be incorporated into later prompts without calling the tool again
|
||||
toolResultMetadata.forEach(meta => accumulatedToolResults[meta.toolCallId] = meta.result);
|
||||
}
|
||||
if (toolCalls.length) {
|
||||
// If the model called any tools, then we do another round- render the prompt with those tool calls (rendering the PromptElements will invoke the tools)
|
||||
// and include the tool results in the prompt for the next request.
|
||||
toolCallRounds.push({
|
||||
response: responseStr,
|
||||
toolCalls
|
||||
});
|
||||
const result = (await renderPrompt(
|
||||
ToolUserPrompt,
|
||||
{
|
||||
context: chatContext,
|
||||
request,
|
||||
toolCallRounds,
|
||||
toolCallResults: accumulatedToolResults
|
||||
},
|
||||
{ modelMaxPromptTokens: model.maxInputTokens },
|
||||
model));
|
||||
messages = result.messages;
|
||||
const toolResultMetadata = result.metadatas.getAll(ToolResultMetadata);
|
||||
if (toolResultMetadata?.length) {
|
||||
// Cache tool results for later, so they can be incorporated into later prompts without calling the tool again
|
||||
toolResultMetadata.forEach(meta => accumulatedToolResults[meta.toolCallId] = meta.result);
|
||||
}
|
||||
|
||||
// This loops until the model doesn't want to call any more tools, then the request is done.
|
||||
return runWithTools();
|
||||
}
|
||||
};
|
||||
// This loops until the model doesn't want to call any more tools, then the request is done.
|
||||
return runWithTools();
|
||||
}
|
||||
};
|
||||
|
||||
await runWithTools();
|
||||
await runWithTools();
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
// Return tool call metadata so it can be used in prompt history on the next request
|
||||
toolCallsMetadata: {
|
||||
toolCallResults: accumulatedToolResults,
|
||||
toolCallRounds
|
||||
}
|
||||
} satisfies TsxToolUserMetadata,
|
||||
};
|
||||
};
|
||||
return {
|
||||
metadata: {
|
||||
// Return tool call metadata so it can be used in prompt history on the next request
|
||||
toolCallsMetadata: {
|
||||
toolCallResults: accumulatedToolResults,
|
||||
toolCallRounds
|
||||
}
|
||||
} satisfies TsxToolUserMetadata,
|
||||
};
|
||||
};
|
||||
|
||||
const toolUser = vscode.chat.createChatParticipant('chat-tools-sample.tools', handler);
|
||||
toolUser.iconPath = new vscode.ThemeIcon('tools');
|
||||
context.subscriptions.push(toolUser);
|
||||
const toolUser = vscode.chat.createChatParticipant('chat-tools-sample.tools', handler);
|
||||
toolUser.iconPath = new vscode.ThemeIcon('tools');
|
||||
context.subscriptions.push(toolUser);
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
"lint-all": "tsx .scripts/run-script.ts lint",
|
||||
"install-all": "tsx .scripts/run-command.ts npm install",
|
||||
"audit-fix-all": "tsx .scripts/run-command.ts npm audit fix",
|
||||
"format-all": "tsx .scripts/format",
|
||||
"update-readme": "tsx .scripts/update-readme.ts",
|
||||
"update-lsif": "tsx .scripts/update-lsif.ts",
|
||||
"validate": "tsx .scripts/validate.ts"
|
||||
|
||||
@ -177,7 +177,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
handleTerminalLink(link: CustomTerminalLink) {
|
||||
vscode.window.showInformationMessage(`Link activated (data = ${link.data})`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user