diff --git a/chat-sample/src/chatUtilsSample.ts b/chat-sample/src/chatUtilsSample.ts index a24a8e489b..a76b000a5e 100644 --- a/chat-sample/src/chatUtilsSample.ts +++ b/chat-sample/src/chatUtilsSample.ts @@ -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); } \ No newline at end of file diff --git a/chat-sample/src/extension.ts b/chat-sample/src/extension.ts index 87df3088ea..667068cddc 100644 --- a/chat-sample/src/extension.ts +++ b/chat-sample/src/extension.ts @@ -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() { } diff --git a/chat-sample/src/simple.ts b/chat-sample/src/simple.ts index db3caac697..580407cf04 100644 --- a/chat-sample/src/simple.ts +++ b/chat-sample/src/simple.ts @@ -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 => { - // 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); - } - - } catch (err) { - handleError(logger, err, stream); - } - - 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); - - const chatResponse = await request.model.sendRequest(messages, {}, token); - for await (const fragment of chatResponse.text) { - stream.markdown(fragment); - } - - } 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. + // Define a Cat chat handler. + const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken): Promise => { + // 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); + } + + } catch (err) { + handleError(logger, err, stream); + } + + 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); + + const chatResponse = await request.model.sendRequest(messages, {}, token); + for await (const fragment of chatResponse.text) { + stream.markdown(fragment); + } + + } 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. 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 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: '' } }; - } - }; - - // 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)}`); - } - }); - - 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(); - - 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. + 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); + } + + 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]; + } + }; + + 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, + // 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; + } + + 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); - - } 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)); - }); - - // 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); - }); - } - }), - ); + 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; + } + + // 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); + }); + } + }), + ); } // 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); - - 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; - } + // 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; + } } // Get a random topic that the cat has not taught in the chat history yet function getTopic(history: ReadonlyArray): 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); - }); - }); - }); - - return topicsNoRepetition[Math.floor(Math.random() * topicsNoRepetition.length)] || 'I have taught you everything I know. Meow!'; + 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!'; } diff --git a/chat-sample/src/toolParticipant.ts b/chat-sample/src/toolParticipant.ts index 39d41e366a..36d8068ef7 100644 --- a/chat-sample/src/toolParticipant.ts +++ b/chat-sample/src/toolParticipant.ts @@ -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; + toolCallRounds: ToolCallRound[]; + toolCallResults: Record; } 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 = {}; - const toolCallRounds: ToolCallRound[] = []; - const runWithTools = async (): Promise => { - // 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 = {}; + const toolCallRounds: ToolCallRound[] = []; + const runWithTools = async (): Promise => { + // 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); } \ No newline at end of file diff --git a/package.json b/package.json index aaec174162..23ad13ec94 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/terminal-sample/src/extension.ts b/terminal-sample/src/extension.ts index adfd45081b..539cc7e323 100644 --- a/terminal-sample/src/extension.ts +++ b/terminal-sample/src/extension.ts @@ -177,7 +177,7 @@ export function activate(context: vscode.ExtensionContext) { } ]; } - + handleTerminalLink(link: CustomTerminalLink) { vscode.window.showInformationMessage(`Link activated (data = ${link.data})`); }