From 1c012edb7df7de45bfcc30a80cf0558cf6095053 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 20 Sep 2024 17:33:59 -0700 Subject: [PATCH] Update for tools changes --- chat-sample/package.json | 11 +++- chat-sample/src/extension.ts | 53 ++++++++++--------- chat-sample/src/play.tsx | 38 ------------- chat-sample/src/tools.tsx | 19 +++++++ ...ode.proposed.chatParticipantAdditions.d.ts | 17 +++--- chat-sample/vscode.proposed.lmTools.d.ts | 31 ++++++++--- 6 files changed, 90 insertions(+), 79 deletions(-) delete mode 100644 chat-sample/src/play.tsx create mode 100644 chat-sample/src/tools.tsx diff --git a/chat-sample/package.json b/chat-sample/package.json index 02952ae8..691458ed 100644 --- a/chat-sample/package.json +++ b/chat-sample/package.json @@ -49,7 +49,10 @@ "default": 0 } } - } + }, + "supportedContentTypes": [ + "text/plain" + ] }, { "id": "chat-sample_catVoice", @@ -58,7 +61,11 @@ "modelDescription": "Speak in a cat voice", "userDescription": "Speak in a cat voice", "icon": "$(files)", - "canBeInvokedManually": true + "canBeInvokedManually": true, + "supportedContentTypes": [ + "text/plain", + "application/vnd.codechat.prompt+json.1" + ] } ] }, diff --git a/chat-sample/src/extension.ts b/chat-sample/src/extension.ts index d9bbb22d..9a6921bc 100644 --- a/chat-sample/src/extension.ts +++ b/chat-sample/src/extension.ts @@ -1,6 +1,6 @@ -import { contentType, renderElementJSON } from '@vscode/prompt-tsx'; +import { contentType as promptTsxContentType, renderElementJSON } from '@vscode/prompt-tsx'; import * as vscode from 'vscode'; -import { CatToolPrompt } from './play'; +import { CatVoiceToolResult } from './tools'; export function activate(context: vscode.ExtensionContext) { registerChatTool(context); @@ -10,12 +10,16 @@ export function activate(context: vscode.ExtensionContext) { function registerChatTool(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.lm.registerTool('chat-sample_catVoice', { async invoke(options, token) { - return { - [contentType]: await renderElementJSON(CatToolPrompt, {}, options.tokenOptions, token), - toString() { - return 'Reply in the voice of a cat! Use cat analogies when appropriate.'; - }, - }; + 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; }, })); @@ -25,19 +29,15 @@ function registerChatTool(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.lm.registerTool('chat-sample_tabCount', { async invoke(options, token) { - return { - toString() { - const params = options.parameters as ITabCountParameters; - if (typeof params.tabGroup === 'number') { - const group = vscode.window.tabGroups.all[Math.max(params.tabGroup - 1, 0)]; - const nth = params.tabGroup === 1 ? '1st' : params.tabGroup === 2 ? '2nd' : params.tabGroup === 3 ? '3rd' : `${params.tabGroup}th`; - return `There are ${group.tabs.length} tabs open in the ${nth} tab group.`; - } else { - const group = vscode.window.tabGroups.activeTabGroup; - return `There are ${group.tabs.length} tabs open.`; - } - }, - }; + const params = options.parameters as ITabCountParameters; + if (typeof params.tabGroup === 'number') { + const group = vscode.window.tabGroups.all[Math.max(params.tabGroup - 1, 0)]; + const nth = params.tabGroup === 1 ? '1st' : params.tabGroup === 2 ? '2nd' : params.tabGroup === 3 ? '3rd' : `${params.tabGroup}th`; + return { 'text/plain': `There are ${group.tabs.length} tabs open in the ${nth} tab group.` }; + } else { + const group = vscode.window.tabGroups.activeTabGroup; + return { 'text/plain': `There are ${group.tabs.length} tabs open.` }; + } }, })); } @@ -107,28 +107,31 @@ function registerChatParticipant(context: vscode.ExtensionContext) { } stream.progress(`Calling tool: ${tool.id} with ${part.parameters}`); + // TODO support prompt-tsx here + const requestedContentType = 'text/plain'; toolCalls.push({ call: part, - result: vscode.lm.invokeTool(tool.id, { parameters: JSON.parse(part.parameters), toolInvocationToken: request.toolInvocationToken }, token), + result: vscode.lm.invokeTool(tool.id, { parameters: JSON.parse(part.parameters), toolInvocationToken: request.toolInvocationToken, requestedContentTypes: [requestedContentType] }, token), tool }); } } if (toolCalls.length) { - let assistantMsg = vscode.LanguageModelChatMessage.Assistant(''); + const assistantMsg = vscode.LanguageModelChatMessage.Assistant(''); assistantMsg.content2 = toolCalls.map(toolCall => new vscode.LanguageModelChatResponseToolCallPart(toolCall.tool.id, toolCall.call.toolCallId, toolCall.call.parameters)); messages.push(assistantMsg); for (const toolCall of toolCalls) { // NOTE that the result of calling a function is a special content type of a USER-message - let message = vscode.LanguageModelChatMessage.User(''); + const message = vscode.LanguageModelChatMessage.User(''); + message.content2 = [new vscode.LanguageModelChatMessageToolResultPart(toolCall.call.toolCallId, (await toolCall.result).toString())]; messages.push(message); } // IMPORTANT The prompt must end with a USER message (with no tool call) messages.push(vscode.LanguageModelChatMessage.User(`Above is the result of calling the functions ${toolCalls.map(call => call.tool.id).join(', ')}. The user cannot see this result, so you should explain it to the user if referencing it in your answer.`)); - + // RE-enter return runWithFunctions(); } diff --git a/chat-sample/src/play.tsx b/chat-sample/src/play.tsx deleted file mode 100644 index 0bf19902..00000000 --- a/chat-sample/src/play.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - BasePromptElementProps, - PromptElement, - PromptSizing, - TextChunk, - UserMessage -} from '@vscode/prompt-tsx'; - -export interface PromptProps extends BasePromptElementProps { - userQuery: string; -} - -export class PlayPrompt extends PromptElement { - render(state: void, sizing: PromptSizing) { - return ( - <> - - You are a cat! Reply in the voice of a cat, using cat analogies when - appropriate. Be concise to prepare for cat play time. Give a small random - python code sample (that has cat names for variables). - - {this.props.userQuery} - - ); - } -} - -export class CatToolPrompt extends PromptElement { - render(state: void, sizing: PromptSizing) { - return ( - <> - - Reply in the voice of a cat! Use cat analogies when appropriate. - - - ); - } -} diff --git a/chat-sample/src/tools.tsx b/chat-sample/src/tools.tsx new file mode 100644 index 00000000..43b39635 --- /dev/null +++ b/chat-sample/src/tools.tsx @@ -0,0 +1,19 @@ +import { + BasePromptElementProps, + PromptElement, + PromptSizing, + TextChunk, + UserMessage +} from '@vscode/prompt-tsx'; + +export class CatVoiceToolResult extends PromptElement { + render(state: void, sizing: PromptSizing) { + return ( + <> + + Reply in the voice of a cat! Use cat analogies when appropriate. + + + ); + } +} diff --git a/chat-sample/vscode.proposed.chatParticipantAdditions.d.ts b/chat-sample/vscode.proposed.chatParticipantAdditions.d.ts index 1c500f53..b6eba7e9 100644 --- a/chat-sample/vscode.proposed.chatParticipantAdditions.d.ts +++ b/chat-sample/vscode.proposed.chatParticipantAdditions.d.ts @@ -214,6 +214,8 @@ declare module 'vscode' { * The `data` for any confirmations that were rejected */ rejectedConfirmationData?: any[]; + + userSelectedModel?: LanguageModelChat; } // TODO@API fit this into the stream @@ -248,10 +250,6 @@ declare module 'vscode' { export type ChatExtendedRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult; - export interface ChatRequest { - toolInvocationToken: ChatParticipantToolToken; - } - export interface ChatResult { nextQuestion?: { prompt: string; @@ -310,7 +308,14 @@ declare module 'vscode' { codeBlockIndex: number; totalCharacters: number; newFile?: boolean; - userAction?: string; + } + + export interface ChatApplyAction { + // eslint-disable-next-line local/vscode-dts-string-type-literals + kind: 'apply'; + codeBlockIndex: number; + totalCharacters: number; + newFile?: boolean; codeMapper?: string; } @@ -345,7 +350,7 @@ declare module 'vscode' { export interface ChatUserActionEvent { readonly result: ChatResult; - readonly action: ChatCopyAction | ChatInsertAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction | ChatEditorAction; + readonly action: ChatCopyAction | ChatInsertAction | ChatApplyAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction | ChatEditorAction; } export interface ChatPromptReference { diff --git a/chat-sample/vscode.proposed.lmTools.d.ts b/chat-sample/vscode.proposed.lmTools.d.ts index e4b045a4..43749d8a 100644 --- a/chat-sample/vscode.proposed.lmTools.d.ts +++ b/chat-sample/vscode.proposed.lmTools.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 6 +// version: 7 // https://github.com/microsoft/vscode/issues/213274 declare module 'vscode' { @@ -73,14 +73,10 @@ declare module 'vscode' { export interface LanguageModelToolResult { /** - * The result can contain arbitrary representations of the content. An 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. 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. */ [contentType: string]: any; - - /** - * A string representation of the result which can be incorporated back into an LLM prompt without any special handling. - */ - toString(): string; } // Tool registration/invoking between extensions @@ -98,7 +94,6 @@ declare module 'vscode' { /** * Invoke a tool with the given parameters. - * TODO@API Could request a set of contentTypes to be returned so they don't all need to be computed? */ export function invokeTool(id: string, options: LanguageModelToolInvocationOptions, token: CancellationToken): Thenable; } @@ -106,6 +101,11 @@ declare module 'vscode' { export type ChatParticipantToolToken = unknown; 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. + */ toolInvocationToken: ChatParticipantToolToken | undefined; /** @@ -113,6 +113,11 @@ declare module 'vscode' { */ parameters: Object; + /** + * A tool invoker can request that particular content types be returned from the tool. All tools are required to support `text/plain`. + */ + requestedContentTypes: string[]; + /** * Options to hint at how many tokens the tool should return in its response. */ @@ -154,6 +159,11 @@ declare module 'vscode' { * A JSON schema for the parameters this tool accepts. */ parametersSchema?: JSONSchema; + + /** + * The list of content types that the tool has declared support for. + */ + supportedContentTypes: string[]; } export interface LanguageModelTool { @@ -188,6 +198,11 @@ declare module 'vscode' { * string-manipulation of the prompt. */ readonly toolReferences: readonly ChatLanguageModelToolReference[]; + + /** + * A token that can be passed to {@link lm.invokeTool} when invoking a tool inside the context of handling a chat request. + */ + readonly toolInvocationToken: ChatParticipantToolToken; } export interface ChatRequestTurn {