diff --git a/chat-sample/.gitignore b/chat-sample/.gitignore deleted file mode 100644 index 0b60dfa1..00000000 --- a/chat-sample/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -out -dist -node_modules -.vscode-test/ -*.vsix diff --git a/chat-sample/.vscode/launch.json b/chat-sample/.vscode/launch.json deleted file mode 100644 index 9bc4c02c..00000000 --- a/chat-sample/.vscode/launch.json +++ /dev/null @@ -1,35 +0,0 @@ -// A launch configuration that compiles the extension and then opens it inside a new window -// Use IntelliSense to learn about possible attributes. -// Hover to view descriptions of existing attributes. -// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js", - "${workspaceFolder}/dist/**/*.js" - ], - "preLaunchTask": "tasks: watch-tests" - } - ] -} diff --git a/chat-sample/.vscode/tasks.json b/chat-sample/.vscode/tasks.json deleted file mode 100644 index 0dc11430..00000000 --- a/chat-sample/.vscode/tasks.json +++ /dev/null @@ -1,40 +0,0 @@ -// See https://go.microsoft.com/fwlink/?LinkId=733558 -// for the documentation about the tasks.json format -{ - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never", - "group": "watchers" - }, - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "type": "npm", - "script": "watch-tests", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never", - "group": "watchers" - }, - "group": "build" - }, - { - "label": "tasks: watch-tests", - "dependsOn": [ - "npm: watch", - "npm: watch-tests" - ], - "problemMatcher": [] - } - ] -} diff --git a/chat-sample/.vscodeignore b/chat-sample/.vscodeignore deleted file mode 100644 index c6136798..00000000 --- a/chat-sample/.vscodeignore +++ /dev/null @@ -1,13 +0,0 @@ -.vscode/** -.vscode-test/** -out/** -node_modules/** -src/** -.gitignore -.yarnrc -webpack.config.js -vsc-extension-quickstart.md -**/tsconfig.json -**/.eslintrc.json -**/*.map -**/*.ts diff --git a/chat-sample/README.md b/chat-sample/README.md deleted file mode 100644 index 32fe5c65..00000000 --- a/chat-sample/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Chat Example - -This sample shows - -- How to contribute a chat participant. -- How to use the chatRequestAccess API to request access to the chat. -- How to respond with follow-ups. - -![demo](./demo.png) - -## Running the Sample - -- Run `npm install` in terminal to install dependencies -- Run the `Run Extension` target in the Debug View. This will: - - Start a task `npm: watch` to compile the code - - Run the extension in a new VS Code window diff --git a/chat-sample/demo.png b/chat-sample/demo.png deleted file mode 100644 index c7eb9852..00000000 Binary files a/chat-sample/demo.png and /dev/null differ diff --git a/chat-sample/vscode.proposed.chatParticipantAdditions.d.ts b/chat-sample/vscode.proposed.chatParticipantAdditions.d.ts deleted file mode 100644 index b6eba7e9..00000000 --- a/chat-sample/vscode.proposed.chatParticipantAdditions.d.ts +++ /dev/null @@ -1,366 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - export interface ChatParticipant { - onDidPerformAction: Event; - } - - /** - * Now only used for the "intent detection" API below - */ - export interface ChatCommand { - readonly name: string; - readonly description: string; - } - - export class ChatResponseDetectedParticipantPart { - participant: string; - // TODO@API validate this against statically-declared slash commands? - command?: ChatCommand; - constructor(participant: string, command?: ChatCommand); - } - - export interface ChatVulnerability { - title: string; - description: string; - // id: string; // Later we will need to be able to link these across multiple content chunks. - } - - export class ChatResponseMarkdownWithVulnerabilitiesPart { - value: MarkdownString; - vulnerabilities: ChatVulnerability[]; - constructor(value: string | MarkdownString, vulnerabilities: ChatVulnerability[]); - } - - export class ChatResponseCodeblockUriPart { - value: Uri; - constructor(value: Uri); - } - - /** - * Displays a {@link Command command} as a button in the chat response. - */ - export interface ChatCommandButton { - command: Command; - } - - export interface ChatDocumentContext { - uri: Uri; - version: number; - ranges: Range[]; - } - - export class ChatResponseTextEditPart { - uri: Uri; - edits: TextEdit[]; - constructor(uri: Uri, edits: TextEdit | TextEdit[]); - } - - export class ChatResponseConfirmationPart { - title: string; - message: string; - data: any; - buttons?: string[]; - constructor(title: string, message: string, data: any, buttons?: string[]); - } - - export class ChatResponseCodeCitationPart { - value: Uri; - license: string; - snippet: string; - constructor(value: Uri, license: string, snippet: string); - } - - export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseDetectedParticipantPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart; - - export class ChatResponseWarningPart { - value: MarkdownString; - constructor(value: string | MarkdownString); - } - - export class ChatResponseProgressPart2 extends ChatResponseProgressPart { - value: string; - task?: (progress: Progress) => Thenable; - constructor(value: string, task?: (progress: Progress) => Thenable); - } - - export class ChatResponseReferencePart2 { - /** - * The reference target. - */ - value: Uri | Location | { variableName: string; value?: Uri | Location } | string; - - /** - * The icon for the reference. - */ - iconPath?: Uri | ThemeIcon | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - }; - options?: { status?: { description: string; kind: ChatResponseReferencePartStatusKind } }; - - /** - * Create a new ChatResponseReferencePart. - * @param value A uri or location - * @param iconPath Icon for the reference shown in UI - */ - constructor(value: Uri | Location | { variableName: string; value?: Uri | Location } | string, iconPath?: Uri | ThemeIcon | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - }, options?: { status?: { description: string; kind: ChatResponseReferencePartStatusKind } }); - } - - export class ChatResponseMovePart { - - readonly uri: Uri; - readonly range: Range; - - constructor(uri: Uri, range: Range); - } - - // Extended to add `SymbolInformation`. Would also be added to `constructor`. - export interface ChatResponseAnchorPart { - /** - * The target of this anchor. - * - * If this is a {@linkcode Uri} or {@linkcode Location}, this is rendered as a normal link. - * - * If this is a {@linkcode SymbolInformation}, this is rendered as a symbol link. - */ - value2: Uri | Location | SymbolInformation; - } - - export interface ChatResponseStream { - - /** - * Push a progress part to this stream. Short-hand for - * `push(new ChatResponseProgressPart(value))`. - * - * @param value A progress message - * @param task If provided, a task to run while the progress is displayed. When the Thenable resolves, the progress will be marked complete in the UI, and the progress message will be updated to the resolved string if one is specified. - * @returns This stream. - */ - progress(value: string, task?: (progress: Progress) => Thenable): void; - - textEdit(target: Uri, edits: TextEdit | TextEdit[]): void; - markdownWithVulnerabilities(value: string | MarkdownString, vulnerabilities: ChatVulnerability[]): void; - codeblockUri(uri: Uri): void; - detectedParticipant(participant: string, command?: ChatCommand): void; - push(part: ChatResponsePart | ChatResponseTextEditPart | ChatResponseDetectedParticipantPart | ChatResponseWarningPart | ChatResponseProgressPart2): void; - - /** - * Show an inline message in the chat view asking the user to confirm an action. - * Multiple confirmations may be shown per response. The UI might show "Accept All" / "Reject All" actions. - * @param title The title of the confirmation entry - * @param message An extra message to display to the user - * @param data An arbitrary JSON-stringifiable object that will be included in the ChatRequest when - * the confirmation is accepted or rejected - * TODO@API should this be MarkdownString? - * TODO@API should actually be a more generic function that takes an array of buttons - */ - confirmation(title: string, message: string, data: any, buttons?: string[]): void; - - /** - * Push a warning to this stream. Short-hand for - * `push(new ChatResponseWarningPart(message))`. - * - * @param message A warning message - * @returns This stream. - */ - warning(message: string | MarkdownString): void; - - reference(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }): void; - - reference2(value: Uri | Location | string | { variableName: string; value?: Uri | Location }, iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }, options?: { status?: { description: string; kind: ChatResponseReferencePartStatusKind } }): void; - - codeCitation(value: Uri, license: string, snippet: string): void; - - push(part: ExtendedChatResponsePart): void; - } - - export enum ChatResponseReferencePartStatusKind { - Complete = 1, - Partial = 2, - Omitted = 3 - } - - /** - * Does this piggy-back on the existing ChatRequest, or is it a different type of request entirely? - * Does it show up in history? - */ - export interface ChatRequest { - /** - * The `data` for any confirmations that were accepted - */ - acceptedConfirmationData?: any[]; - - /** - * The `data` for any confirmations that were rejected - */ - rejectedConfirmationData?: any[]; - - userSelectedModel?: LanguageModelChat; - } - - // TODO@API fit this into the stream - export interface ChatUsedContext { - documents: ChatDocumentContext[]; - } - - export interface ChatParticipant { - /** - * Provide a set of variables that can only be used with this participant. - */ - participantVariableProvider?: { provider: ChatParticipantCompletionItemProvider; triggerCharacters: string[] }; - } - - export interface ChatParticipantCompletionItemProvider { - provideCompletionItems(query: string, token: CancellationToken): ProviderResult; - } - - export class ChatCompletionItem { - id: string; - label: string | CompletionItemLabel; - values: ChatVariableValue[]; - fullName?: string; - icon?: ThemeIcon; - insertText?: string; - detail?: string; - documentation?: string | MarkdownString; - command?: Command; - - constructor(id: string, label: string | CompletionItemLabel, values: ChatVariableValue[]); - } - - export type ChatExtendedRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult; - - export interface ChatResult { - nextQuestion?: { - prompt: string; - participant?: string; - command?: string; - }; - } - - export namespace chat { - /** - * Create a chat participant with the extended progress type - */ - export function createChatParticipant(id: string, handler: ChatExtendedRequestHandler): ChatParticipant; - - export function registerChatParticipantDetectionProvider(participantDetectionProvider: ChatParticipantDetectionProvider): Disposable; - } - - export interface ChatParticipantMetadata { - participant: string; - command?: string; - disambiguation: { category: string; description: string; examples: string[] }[]; - } - - export interface ChatParticipantDetectionResult { - participant: string; - command?: string; - } - - export interface ChatParticipantDetectionProvider { - provideParticipantDetection(chatRequest: ChatRequest, context: ChatContext, options: { participants?: ChatParticipantMetadata[]; location: ChatLocation }, token: CancellationToken): ProviderResult; - } - - /* - * User action events - */ - - export enum ChatCopyKind { - // Keyboard shortcut or context menu - Action = 1, - Toolbar = 2 - } - - export interface ChatCopyAction { - // eslint-disable-next-line local/vscode-dts-string-type-literals - kind: 'copy'; - codeBlockIndex: number; - copyKind: ChatCopyKind; - copiedCharacters: number; - totalCharacters: number; - copiedText: string; - } - - export interface ChatInsertAction { - // eslint-disable-next-line local/vscode-dts-string-type-literals - kind: 'insert'; - codeBlockIndex: number; - totalCharacters: number; - newFile?: boolean; - } - - export interface ChatApplyAction { - // eslint-disable-next-line local/vscode-dts-string-type-literals - kind: 'apply'; - codeBlockIndex: number; - totalCharacters: number; - newFile?: boolean; - codeMapper?: string; - } - - export interface ChatTerminalAction { - // eslint-disable-next-line local/vscode-dts-string-type-literals - kind: 'runInTerminal'; - codeBlockIndex: number; - languageId?: string; - } - - export interface ChatCommandAction { - // eslint-disable-next-line local/vscode-dts-string-type-literals - kind: 'command'; - commandButton: ChatCommandButton; - } - - export interface ChatFollowupAction { - // eslint-disable-next-line local/vscode-dts-string-type-literals - kind: 'followUp'; - followup: ChatFollowup; - } - - export interface ChatBugReportAction { - // eslint-disable-next-line local/vscode-dts-string-type-literals - kind: 'bug'; - } - - export interface ChatEditorAction { - kind: 'editor'; - accepted: boolean; - } - - export interface ChatUserActionEvent { - readonly result: ChatResult; - readonly action: ChatCopyAction | ChatInsertAction | ChatApplyAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction | ChatEditorAction; - } - - export interface ChatPromptReference { - /** - * TODO Needed for now to drive the variableName-type reference, but probably both of these should go away in the future. - */ - readonly name: string; - } - - export interface ChatResultFeedback { - readonly unhelpfulReason?: string; - } -} diff --git a/chat-sample/.eslintrc.json b/chat-tools-sample/.eslintrc.json similarity index 100% rename from chat-sample/.eslintrc.json rename to chat-tools-sample/.eslintrc.json diff --git a/chat-tools-sample/.vscode/launch.json b/chat-tools-sample/.vscode/launch.json new file mode 100644 index 00000000..c3e647c7 --- /dev/null +++ b/chat-tools-sample/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js", + "${workspaceFolder}/dist/**/*.js" + ], + "preLaunchTask": "tasks: watch-tests" + } + ] +} \ No newline at end of file diff --git a/chat-tools-sample/.vscode/tasks.json b/chat-tools-sample/.vscode/tasks.json new file mode 100644 index 00000000..ee70aab3 --- /dev/null +++ b/chat-tools-sample/.vscode/tasks.json @@ -0,0 +1,38 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "watchers" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "npm", + "script": "watch-tests", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "watchers" + }, + "group": "build" + }, + { + "label": "tasks: watch-tests", + "dependsOn": [ + "npm: watch", + "npm: watch-tests" + ], + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/chat-tools-sample/README.md b/chat-tools-sample/README.md new file mode 100644 index 00000000..8c2a122b --- /dev/null +++ b/chat-tools-sample/README.md @@ -0,0 +1,21 @@ +# Chat Tools Example + +This sample shows + +- How to create LanguageModelTools. +- How to write a participant that calls tools via a LanguageModelChat. + +## Running the Sample + +- Run `npm install` in terminal to install dependencies +- Run the `Run Extension` target in the Debug View. This will: + - Start a task `npm: watch` to compile the code + - 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 \ No newline at end of file diff --git a/chat-sample/cat.jpeg b/chat-tools-sample/cat.jpeg similarity index 100% rename from chat-sample/cat.jpeg rename to chat-tools-sample/cat.jpeg diff --git a/chat-sample/package-lock.json b/chat-tools-sample/package-lock.json similarity index 100% rename from chat-sample/package-lock.json rename to chat-tools-sample/package-lock.json diff --git a/chat-sample/package.json b/chat-tools-sample/package.json similarity index 72% rename from chat-sample/package.json rename to chat-tools-sample/package.json index fb3a97d5..2fff36bf 100644 --- a/chat-sample/package.json +++ b/chat-tools-sample/package.json @@ -1,8 +1,8 @@ { - "name": "chat-sample", + "name": "chat-tool-sample", "publisher": "vscode-samples", - "displayName": "Copilot Chat Sample", - "description": "Sample chat extension, a trusty cat tutor that will can teach you computer science topics.", + "displayName": "Copilot Chat Tools Sample", + "description": "Sample chat extension that registers and uses LanguageModelTools", "repository": { "type": "git", "url": "https://github.com/Microsoft/vscode-extension-samples" @@ -17,14 +17,12 @@ ], "activationEvents": [], "enabledApiProposals": [ - "chatVariableResolver", - "chatParticipantAdditions", "lmTools" ], "contributes": { "chatParticipants": [ { - "id": "chat-sample.tools", + "id": "chat-tools-sample.tools", "fullName": "Tool User", "name": "tools", "description": "I use tools", @@ -34,6 +32,7 @@ ], "languageModelTools": [ { + "tags": ["editors", "chat-sample"], "id": "chat-sample_tabCount", "name": "tabCount", "displayName": "Tab Count", @@ -54,19 +53,6 @@ "text/plain" ], "requiresConfirmation": true - }, - { - "id": "chat-sample_catVoice", - "name": "catVoice", - "displayName": "Cat Voice", - "modelDescription": "Speak in a cat voice", - "userDescription": "Speak in a cat voice", - "icon": "$(files)", - "canBeInvokedManually": true, - "supportedContentTypes": [ - "text/plain", - "application/vnd.codechat.prompt+json.1" - ] } ] }, diff --git a/chat-sample/src/extension.ts b/chat-tools-sample/src/extension.ts similarity index 57% rename from chat-sample/src/extension.ts rename to chat-tools-sample/src/extension.ts index bd492d40..67e7b24a 100644 --- a/chat-sample/src/extension.ts +++ b/chat-tools-sample/src/extension.ts @@ -8,27 +8,11 @@ export function activate(context: vscode.ExtensionContext) { } function registerChatTool(context: vscode.ExtensionContext) { - context.subscriptions.push(vscode.lm.registerTool('chat-sample_catVoice', { - async invoke(options, token) { - const result: vscode.LanguageModelToolResult = {}; - if (options.requestedContentTypes.includes(promptTsxContentType)) { - result[promptTsxContentType] = await renderElementJSON(CatVoiceToolResult, {}, options.tokenOptions, token); - } - - if (options.requestedContentTypes.includes('text/plain')) { - result['text/plain'] = 'Reply in the voice of a cat! Use cat analogies when appropriate.'; - } - - return result; - }, - provideToolInvocationMessage: async () => 'Speaking in the voice of a cat', - })); - interface ITabCountParameters { tabGroup?: number; } - context.subscriptions.push(vscode.lm.registerTool('chat-sample_tabCount', { + context.subscriptions.push(vscode.lm.registerTool('chat-sample_tabCount', { async invoke(options, token) { await new Promise(resolve => setTimeout(resolve, 5000)); const params = options.parameters as ITabCountParameters; @@ -41,14 +25,17 @@ function registerChatTool(context: vscode.ExtensionContext) { return { 'text/plain': `There are ${group.tabs.length} tabs open.` }; } }, - provideToolInvocationMessage: async () => 'Counting the number of tabs', - provideToolConfirmationMessages: async (options) => { - const params = options.parameters as ITabCountParameters; - return { - title: 'Count the number of open tabs', - message: new vscode.MarkdownString(`${options.participantName} will count the number of open tabs` + (params.tabGroup !== undefined ? ` in tab group ${params.tabGroup}` : '')) + 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 + } + }, })); } @@ -58,6 +45,12 @@ interface IToolCall { result: Thenable; } +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 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.` + function registerChatParticipant(context: vscode.ExtensionContext) { const handler: vscode.ChatRequestHandler = async (request: vscode.ChatRequest, chatContext: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) => { const models = await vscode.lm.selectChatModels({ @@ -66,12 +59,12 @@ function registerChatParticipant(context: vscode.ExtensionContext) { }); const model = models[0]; - // stream.markdown(`Available tools: ${vscode.lm.tools.map(tool => tool.id).join(', ')}\n\n`); + stream.markdown(`Available tools: ${vscode.lm.tools.map(tool => tool.id).join(', ')}\n\n`); const allTools = vscode.lm.tools.map((tool): vscode.LanguageModelChatTool => { return { name: tool.id, - description: tool.modelDescription, + description: tool.description, parametersSchema: tool.parametersSchema ?? {} }; }); @@ -81,9 +74,14 @@ function registerChatParticipant(context: vscode.ExtensionContext) { }; 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.`), - vscode.LanguageModelChatMessage.User(request.prompt), + vscode.LanguageModelChatMessage.User(llmInstructions), ]; + messages.push(...await getHistoryMessages(chatContext)); + if (request.references.length) { + messages.push(vscode.LanguageModelChatMessage.User(await getContextMessage(request.references))); + } + messages.push(vscode.LanguageModelChatMessage.User(request.prompt)); + const toolReferences = [...request.toolReferences]; const runWithFunctions = async (): Promise => { const requestedTool = toolReferences.shift(); @@ -134,7 +132,7 @@ function registerChatParticipant(context: vscode.ExtensionContext) { // NOTE that the result of calling a function is a special content type of a USER-message const message = vscode.LanguageModelChatMessage.User(''); - message.content2 = [new vscode.LanguageModelChatMessageToolResultPart(toolCall.call.toolCallId, (await toolCall.result)['text/plain'])]; + message.content2 = [new vscode.LanguageModelChatMessageToolResultPart(toolCall.call.toolCallId, (await toolCall.result)['text/plain']!)]; messages.push(message); } @@ -149,10 +147,57 @@ function registerChatParticipant(context: vscode.ExtensionContext) { await runWithFunctions(); }; - const toolUser = vscode.chat.createChatParticipant('chat-sample.tools', handler); + const toolUser = vscode.chat.createChatParticipant('chat-tools-sample.tools', handler); toolUser.iconPath = new vscode.ThemeIcon('tools'); context.subscriptions.push(toolUser); } +async function getContextMessage(references: ReadonlyArray): Promise { + const contextParts = (await Promise.all(references.map(async ref => { + if (ref.value instanceof vscode.Uri) { + const fileContents = (await vscode.workspace.fs.readFile(ref.value)).toString(); + return `${ref.value.fsPath}:\n\`\`\`\n${fileContents}\n\`\`\``; + } else if (ref.value instanceof vscode.Location) { + const rangeText = (await vscode.workspace.openTextDocument(ref.value.uri)).getText(ref.value.range); + return `${ref.value.uri.fsPath}:${ref.value.range.start.line + 1}-${ref.value.range.end.line + 1}\n\`\`\`${rangeText}\`\`\``; + } else if (typeof ref.value === 'string') { + return ref.value; + } + return null; + }))).filter(part => part !== null) as string[]; + + const context = contextParts + .map(part => `\n${part}\n`) + .join('\n'); + return `The user has provided these references:\n${context}`; +} + +async function getHistoryMessages(context: vscode.ChatContext): Promise { + const messages: vscode.LanguageModelChatMessage[] = []; + for (const message of context.history) { + if (message instanceof vscode.ChatRequestTurn) { + if (message.references.length) { + messages.push(vscode.LanguageModelChatMessage.User(await getContextMessage(message.references))); + } + messages.push(vscode.LanguageModelChatMessage.User(message.prompt)); + } else if (message instanceof vscode.ChatResponseTurn) { + const strResponse = message.response.map(part => { + if (part instanceof vscode.ChatResponseMarkdownPart) { + return part.value.value; + } else if (part instanceof vscode.ChatResponseAnchorPart) { + if (part.value instanceof vscode.Location) { + return ` ${part.value.uri.fsPath}:${part.value.range.start.line}-${part.value.range.end.line} `; + } else if (part.value instanceof vscode.Uri) { + return ` ${part.value.fsPath} `; + } + } + }).join(''); + messages.push(vscode.LanguageModelChatMessage.Assistant(strResponse)); + } + } + + return messages; +} + export function deactivate() { } diff --git a/chat-sample/src/tools.tsx b/chat-tools-sample/src/tools.tsx similarity index 100% rename from chat-sample/src/tools.tsx rename to chat-tools-sample/src/tools.tsx diff --git a/chat-sample/tsconfig.json b/chat-tools-sample/tsconfig.json similarity index 100% rename from chat-sample/tsconfig.json rename to chat-tools-sample/tsconfig.json diff --git a/chat-sample/vscode.proposed.lmTools.d.ts b/chat-tools-sample/vscode.proposed.lmTools.d.ts similarity index 50% rename from chat-sample/vscode.proposed.lmTools.d.ts rename to chat-tools-sample/vscode.proposed.lmTools.d.ts index 89175362..5f12e3df 100644 --- a/chat-sample/vscode.proposed.lmTools.d.ts +++ b/chat-tools-sample/vscode.proposed.lmTools.d.ts @@ -12,7 +12,7 @@ declare module 'vscode' { // API -> LM: an tool/function that is available to the language model export interface LanguageModelChatTool { - // TODO@API should use "id" here to match vscode tools, or keep name to match OpenAI? + // TODO@API should use "id" here to match vscode tools, or keep name to match OpenAI? Align everything. name: string; description: string; parametersSchema?: JSONSchema; @@ -71,21 +71,32 @@ declare module 'vscode' { content2: (string | LanguageModelChatMessageToolResultPart | LanguageModelChatResponseToolCallPart)[]; } + // Tool registration/invoking between extensions + + /** + * A result returned from a tool invocation. + */ export interface LanguageModelToolResult { /** - * The result can contain arbitrary representations of the content. Use {@link LanguageModelToolInvocationOptions.requested} to request particular types. - * `text/plain` is required to be supported by all tools. Another example might be a `PromptElementJSON` from `@vscode/prompt-tsx`, using the `contentType` exported by that library. + * The result can contain arbitrary representations of the content. A tool user can set + * {@link LanguageModelToolInvocationOptions.requested} to request particular types, and a tool implementation should only + * compute the types that were requested. `text/plain` is required to be supported by all tools. Another example might be + * a `PromptElementJSON` from `@vscode/prompt-tsx`, using the `contentType` exported by that library. */ [contentType: string]: any; - } - // Tool registration/invoking between extensions + /** + * A string representation of the result. + */ + 'text/plain'?: string; + } export namespace lm { /** - * Register a LanguageModelTool. The tool must also be registered in the package.json `languageModelTools` contribution point. + * Register a LanguageModelTool. The tool must also be registered in the package.json `languageModelTools` contribution + * point. A registered tool is available in the {@link lm.tools} list for any extension to invoke. */ - export function registerTool(id: string, tool: LanguageModelTool): Disposable; + export function registerTool(id: string, tool: LanguageModelTool): Disposable; /** * A list of all available tools. @@ -95,26 +106,35 @@ declare module 'vscode' { /** * Invoke a tool with the given parameters. */ - export function invokeTool(id: string, options: LanguageModelToolInvocationOptions, token: CancellationToken): Thenable; + export function invokeTool(id: string, options: LanguageModelToolInvocationOptions, token: CancellationToken): Thenable; } + /** + * A token that can be passed to {@link lm.invokeTool} when invoking a tool inside the context of handling a chat request. + */ export type ChatParticipantToolToken = unknown; - export interface LanguageModelToolInvocationOptions { + /** + * Options provided for tool invocation. + */ + export interface LanguageModelToolInvocationOptions { /** - * When this tool is being invoked within the context of a chat request, this token should be passed from {@link ChatRequest.toolInvocationToken}. - * In that case, a progress bar will be automatically shown for the tool invocation in the chat response view. If the tool is being invoked - * outside of a chat request, `undefined` should be passed instead. + * When this tool is being invoked within the context of a chat request, this token should be passed from + * {@link ChatRequest.toolInvocationToken}. In that case, a progress bar will be automatically shown for the tool + * invocation in the chat response view, and if the tool requires user confirmation, it will show up inline in the chat + * view. If the tool is being invoked outside of a chat request, `undefined` should be passed instead. */ toolInvocationToken: ChatParticipantToolToken | undefined; /** - * Parameters with which to invoke the tool. + * The parameters with which to invoke the tool. The parameters must match the schema defined in + * {@link LanguageModelToolDescription.parametersSchema} */ - parameters: Object; + parameters: T; /** - * A tool invoker can request that particular content types be returned from the tool. All tools are required to support `text/plain`. + * A tool user can request that particular content types be returned from the tool, depending on what the tool user + * supports. All tools are required to support `text/plain`. See {@link LanguageModelToolResult}. */ requestedContentTypes: string[]; @@ -137,59 +157,118 @@ declare module 'vscode' { }; } - export type JSONSchema = object; + /** + * Represents a JSON Schema. + * TODO@API - is this worth it? + */ + export type JSONSchema = Object; + /** + * A description of an available tool. + */ export interface LanguageModelToolDescription { /** * A unique identifier for the tool. */ - id: string; + readonly id: string; /** * A human-readable name for this tool that may be used to describe it in the UI. + * TODO@API keep? */ - displayName: string | undefined; + readonly displayName: string | undefined; /** * A description of this tool that may be passed to a language model. */ - modelDescription: string; + readonly description: string; /** * A JSON schema for the parameters this tool accepts. */ - parametersSchema?: JSONSchema; + readonly parametersSchema?: JSONSchema; /** - * The list of content types that the tool has declared support for. + * The list of content types that the tool has declared support for. See {@link LanguageModelToolResult}. */ - supportedContentTypes: string[]; - } - - export interface LanguageModelToolProvideConfirmationMessageOptions { - participantName: string; - parameters: any; + readonly supportedContentTypes: string[]; + + /** + * A set of tags, declared by the tool, that roughly describe the tool's capabilities. A tool user may use these to filter + * the set of tools to just ones that are relevant for the task at hand. + */ + readonly tags: string[]; } + /** + * Messages shown in the chat view when a tool needs confirmation from the user to run. These messages will be shown with + * buttons that say Continue and Cancel. + */ export interface LanguageModelToolConfirmationMessages { + /** + * The title of the confirmation message. + */ title: string; + + /** + * The body of the confirmation message. This should be phrased as an action of the participant that is invoking the tool + * from {@link LanguageModelToolInvocationPrepareOptions.participantName}. An example of a good message would be + * `${participantName} will run the command ${echo 'hello world'} in the terminal.` + * TODO@API keep this? + */ message: string | MarkdownString; } - export interface LanguageModelTool { - invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken): ProviderResult; + /** + * Options for {@link LanguageModelTool.prepareToolInvocation}. + */ + export interface LanguageModelToolInvocationPrepareOptions { + /** + * The name of the participant invoking the tool. + * TODO@API keep this? + */ + participantName: string; /** - * This can be implemented to customize the message shown to the user when a tool requires confirmation. + * The parameters that the tool is being invoked with. */ - provideToolConfirmationMessages?(options: LanguageModelToolProvideConfirmationMessageOptions, token: CancellationToken): Thenable; - - /** - * This message will be shown with the progress notification when the tool is invoked in a chat session. - */ - provideToolInvocationMessage?(parameters: any, token: CancellationToken): Thenable; + parameters: T; } + /** + * A tool that can be invoked by a call to a {@link LanguageModelChat}. + */ + export interface LanguageModelTool { + /** + * Invoke the tool with the given parameters and return a result. + */ + invoke(options: LanguageModelToolInvocationOptions, token: CancellationToken): ProviderResult; + + /** + * Called once before a tool is invoked. May be implemented to customize the progress message that appears while the tool + * is running, and the messages that appear when the tool needs confirmation. + */ + prepareToolInvocation?(options: LanguageModelToolInvocationPrepareOptions, token: CancellationToken): ProviderResult; + } + + /** + * The result of a call to {@link LanguageModelTool.prepareToolInvocation}. + */ + export interface PreparedToolInvocation { + /** + * A customized progress message to show while the tool runs. + */ + invocationMessage?: string; + + /** + * Customized messages to show when asking for user confirmation to run the tool. + */ + confirmationMessages?: LanguageModelToolConfirmationMessages; + } + + /** + * A reference to a tool attached to a user's request. + */ export interface ChatLanguageModelToolReference { /** * The tool's ID. Refers to a tool listed in {@link lm.tools}. @@ -197,10 +276,11 @@ declare module 'vscode' { readonly id: string; /** - * The start and end index of the reference in the {@link ChatRequest.prompt prompt}. When undefined, the reference was not part of the prompt text. + * The start and end index of the reference in the {@link ChatRequest.prompt prompt}. When undefined, the reference was + * not part of the prompt text. * - * *Note* that the indices take the leading `#`-character into account which means they can - * used to modify the prompt as-is. + * *Note* that the indices take the leading `#`-character into account which means they can be used to modify the prompt + * as-is. */ readonly range?: [start: number, end: number]; } @@ -209,12 +289,11 @@ declare module 'vscode' { /** * The list of tools that the user attached to their request. * - * *Note* that if tools are referenced in the text of the prompt, using `#`, the prompt contains - * references as authored and that it is up to the participant - * to further modify the prompt, for instance by inlining reference values or creating links to - * headings which contain the resolved values. References are sorted in reverse by their range - * in the prompt. That means the last reference in the prompt is the first in this list. This simplifies - * string-manipulation of the prompt. + * *Note* that if tools are referenced in the text of the prompt, using `#`, the prompt contains references as authored + * and it is up to the participant to further modify the prompt, for instance by inlining reference values or + * creating links to headings which contain the resolved values. References are sorted in reverse by their range in the + * prompt. That means the last reference in the prompt is the first in this list. This simplifies string-manipulation of + * the prompt. */ readonly toolReferences: readonly ChatLanguageModelToolReference[];