From 5fca862b0c7915e763abdf28ecf94cc9beb06bf0 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 13 Nov 2023 15:59:54 -0600 Subject: [PATCH 1/2] Extend chat agent sample a bit --- chat-agent-sample/src/extension.ts | 48 +++++++++++++------ .../src/vscode.proposed.chatVariables.d.ts | 35 ++++++++++++++ 2 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 chat-agent-sample/src/vscode.proposed.chatVariables.d.ts diff --git a/chat-agent-sample/src/extension.ts b/chat-agent-sample/src/extension.ts index b7405abb..56208ae0 100644 --- a/chat-agent-sample/src/extension.ts +++ b/chat-agent-sample/src/extension.ts @@ -1,26 +1,26 @@ import * as vscode from 'vscode'; -const CAT_LEARN_SYSTEM_PROMPT = 'You are a cat! Your job is to explain computer science concepts in a funny manner of a cat. Always start your response by stating what concept you are explaining.'; -const CAT_PLAY_SYSTEM_PROMPT = 'You are a cat that wants to play! Reply in a helpful way for a coder, but with the hidden meaning that all you want to do is play.'; const MEOW_COMMAND_ID = 'cat.meow'; +interface ICatChatAgentResult extends vscode.ChatAgentResult2 { + slashCommand: string; +} + export function activate(context: vscode.ExtensionContext) { - const teachResult = { /* you can return anything in your result object */ }; - const playResult = { /* you can return anything in your result object */ }; // Define a Cat chat agent handler. - const handler: vscode.ChatAgentHandler = async (request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, progress: vscode.Progress, token: vscode.CancellationToken) => { + const handler: vscode.ChatAgentHandler = async (request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, progress: vscode.Progress, token: vscode.CancellationToken): Promise => { // To talk to an LLM in your slash command handler implementation, your // extension can use VS Code's `requestChatAccess` API to access the Copilot API. - // The pre-release of the GitHub Copilot Chat extension implements this provider. + // The GitHub Copilot Chat extension implements this provider. if (request.slashCommand?.name == 'teach') { const access = await vscode.chat.requestChatAccess('copilot'); - const topics = ["linked list", "recursion", "stack", "queue", "pointers"]; + const topics = ['linked list', 'recursion', 'stack', 'queue', 'pointers']; const topic = topics[Math.floor(Math.random() * topics.length)]; const messages = [ { role: vscode.ChatMessageRole.System, - content: CAT_LEARN_SYSTEM_PROMPT + content: '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.' }, { role: vscode.ChatMessageRole.User, @@ -31,13 +31,13 @@ export function activate(context: vscode.ExtensionContext) { for await (const fragment of chatRequest.response) { progress.report({ content: fragment }); } - return teachResult; + return { slashCommand: 'teach' }; } else if (request.slashCommand?.name == 'play') { const access = await vscode.chat.requestChatAccess('copilot'); const messages = [ { role: vscode.ChatMessageRole.System, - content: CAT_PLAY_SYSTEM_PROMPT + content: 'You are a cat that wants to play! Reply in a helpful way for a coder, but with the hidden meaning that all you want to do is play.' }, { role: vscode.ChatMessageRole.User, @@ -48,8 +48,26 @@ export function activate(context: vscode.ExtensionContext) { for await (const fragment of chatRequest.response) { progress.report({ content: fragment }); } - return playResult; - } + return { slashCommand: 'play' }; + } else { + const access = await vscode.chat.requestChatAccess('copilot'); + const messages = [ + { + role: vscode.ChatMessageRole.System, + content: 'You are a cat! Reply in the voice of a cat, using cat analogies when appropriate.' + }, + { + role: vscode.ChatMessageRole.User, + content: request.prompt + } + ]; + const chatRequest = access.makeRequest(messages, {}, token); + for await (const fragment of chatRequest.response) { + progress.report({ content: fragment }); + } + + return { slashCommand: '' }; + } }; // Agents appear as top-level options in the chat input @@ -69,14 +87,14 @@ export function activate(context: vscode.ExtensionContext) { }; agent.followupProvider = { - provideFollowups(result, token) { - if (result === teachResult) { + provideFollowups(result: ICatChatAgentResult, token: vscode.CancellationToken) { + if (result.slashCommand === 'teach') { return [{ commandId: MEOW_COMMAND_ID, message: '@cat thank you', title: vscode.l10n.t('Meow!') }]; - } else if (result === playResult) { + } else if (result.slashCommand === 'play') { return [{ message: '@cat let us play', title: vscode.l10n.t('Play with the cat') diff --git a/chat-agent-sample/src/vscode.proposed.chatVariables.d.ts b/chat-agent-sample/src/vscode.proposed.chatVariables.d.ts new file mode 100644 index 00000000..6d9de44b --- /dev/null +++ b/chat-agent-sample/src/vscode.proposed.chatVariables.d.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * 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 InteractiveRequest { + variables: Record; + } + + export enum ChatVariableLevel { + Short = 1, + Medium = 2, + Full = 3 + } + + export interface ChatVariableValue { + level: ChatVariableLevel; + value: string; + description?: string; + } + + export interface ChatVariableContext { + message: string; + } + + export interface ChatVariableResolver { + resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; + } + + export namespace chat { + export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; + } +} From da68825ecf51fa0992fbb78b4ce2b9dd012e9ecd Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 17 Jan 2024 17:25:47 -0300 Subject: [PATCH 2/2] Update API --- chat-agent-sample/src/extension.ts | 4 +- .../src/vscode.proposed.chatAgents2.d.ts | 119 +++++++++++++++--- .../src/vscode.proposed.chatVariables.d.ts | 35 ------ 3 files changed, 107 insertions(+), 51 deletions(-) delete mode 100644 chat-agent-sample/src/vscode.proposed.chatVariables.d.ts diff --git a/chat-agent-sample/src/extension.ts b/chat-agent-sample/src/extension.ts index 56208ae0..4e4c94bf 100644 --- a/chat-agent-sample/src/extension.ts +++ b/chat-agent-sample/src/extension.ts @@ -13,7 +13,7 @@ export function activate(context: vscode.ExtensionContext) { // To talk to an LLM in your slash command 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.slashCommand?.name == 'teach') { + if (request.subCommand == 'teach') { const access = await vscode.chat.requestChatAccess('copilot'); const topics = ['linked list', 'recursion', 'stack', 'queue', 'pointers']; const topic = topics[Math.floor(Math.random() * topics.length)]; @@ -32,7 +32,7 @@ export function activate(context: vscode.ExtensionContext) { progress.report({ content: fragment }); } return { slashCommand: 'teach' }; - } else if (request.slashCommand?.name == 'play') { + } else if (request.subCommand == 'play') { const access = await vscode.chat.requestChatAccess('copilot'); const messages = [ { diff --git a/chat-agent-sample/src/vscode.proposed.chatAgents2.d.ts b/chat-agent-sample/src/vscode.proposed.chatAgents2.d.ts index 376ee957..bcc56550 100644 --- a/chat-agent-sample/src/vscode.proposed.chatAgents2.d.ts +++ b/chat-agent-sample/src/vscode.proposed.chatAgents2.d.ts @@ -5,11 +5,17 @@ declare module 'vscode' { + export interface ChatAgentHistoryEntry { + request: ChatAgentRequest; + response: ChatAgentContentProgress[]; + result: ChatAgentResult2; + } + export interface ChatAgentContext { /** * All of the chat messages so far in the current chat session. */ - history: ChatMessage[]; + history: ChatAgentHistoryEntry[]; } /** @@ -64,12 +70,12 @@ declare module 'vscode' { /** * Represents user feedback for a result. */ - export interface ChatAgentResult2Feedback { + export interface ChatAgentResult2Feedback { /** * This instance of ChatAgentResult2 is the same instance that was returned from the chat agent, * and it can be extended with arbitrary properties if needed. */ - readonly result: ChatAgentResult2; + readonly result: TResult; /** * The kind of feedback that was received. @@ -102,11 +108,13 @@ declare module 'vscode' { * slash command is prepended to the chat input. */ readonly shouldRepopulate?: boolean; + /** * Placeholder text to render in the chat input * when the slash command has been repopulated. * Has no effect if `shouldRepopulate` is `false`. */ + // TODO@API merge this with shouldRepopulate? so that invalid state cannot be represented? readonly followupPlaceholder?: string; } @@ -159,16 +167,16 @@ declare module 'vscode' { /** * Will be invoked once after each request to get suggested followup questions to show the user. The user can click the followup to send it to the chat. */ - export interface FollowupProvider { + export interface FollowupProvider { /** * * @param result The same instance of the result object that was returned by the chat agent, and it can be extended with arbitrary properties if needed. * @param token A cancellation token. */ - provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult; + provideFollowups(result: TResult, token: CancellationToken): ProviderResult; } - export interface ChatAgent2 { + export interface ChatAgent2 { /** * The short name by which this agent is referred to in the UI, e.g `workspace`. @@ -207,7 +215,7 @@ declare module 'vscode' { /** * This provider will be called once after each request to retrieve suggested followup questions. */ - followupProvider?: FollowupProvider; + followupProvider?: FollowupProvider; /** * When the user clicks this agent in `/help`, this text will be submitted to this slash command @@ -221,10 +229,9 @@ declare module 'vscode' { * The passed {@link ChatAgentResult2Feedback.result result} is guaranteed to be the same instance that was * previously returned from this chat agent. */ - onDidReceiveFeedback: Event; + onDidReceiveFeedback: Event>; /** - * TODO@API explain what happens wrt to history, in-flight requests etc... * Dispose this agent and free resources */ dispose(): void; @@ -240,23 +247,45 @@ declare module 'vscode' { */ prompt: string; + /** + * The ID of the chat agent to which this request was directed. + */ + agentId: string; + /** * The {@link ChatAgentSlashCommand slash command} that was selected for this request. It is guaranteed that the passed slash * command is an instance that was previously returned from the {@link ChatAgentSlashCommandProvider.provideSlashCommands slash command provider}. + * @deprecated this will be replaced by `subCommand` */ slashCommand?: ChatAgentSlashCommand; + /** + * The name of the {@link ChatAgentSlashCommand slash command} that was selected for this request. + */ + subCommand?: string; + variables: Record; } - // TODO@API should these each be prefixed ChatAgentProgress*? - export type ChatAgentProgress = + export type ChatAgentContentProgress = | ChatAgentContent - | ChatAgentTask | ChatAgentFileTree + | ChatAgentInlineContentReference + | ChatAgentTask; + + export type ChatAgentMetadataProgress = | ChatAgentUsedContext | ChatAgentContentReference - | ChatAgentInlineContentReference; + | ChatAgentProgressMessage; + + export type ChatAgentProgress = ChatAgentContentProgress | ChatAgentMetadataProgress; + + /** + * Is displayed in the UI to communicate steps of progress to the user. Should be used when the agent may be slow to respond, e.g. due to doing extra work before sending the actual request to the LLM. + */ + export interface ChatAgentProgressMessage { + message: string; + } /** * Indicates a piece of content that was used by the chat agent while processing the request. Will be displayed to the user. @@ -306,6 +335,8 @@ declare module 'vscode' { /** * A Thenable resolving to the real content. The placeholder will be replaced with this content once it's available. */ + // TODO@API Should this be an async iterable or progress instance instead + // TODO@API Should this include more inline-renderable items like `ChatAgentInlineContentReference` resolvedContent: Thenable; } @@ -331,8 +362,17 @@ declare module 'vscode' { /** * A Uri for this node, opened when it's clicked. */ + // TODO@API why label and uri. Can the former be derived from the latter? + // TODO@API don't use uri but just names? This API allows to to build nonsense trees where the data structure doesn't match the uris + // path-structure. uri: Uri; + /** + * The type of this node. Defaults to {@link FileType.Directory} if it has {@link ChatAgentFileTreeData.children children}. + */ + // TODO@API cross API usage + type?: FileType; + /** * The children of this node. */ @@ -363,6 +403,57 @@ declare module 'vscode' { * @param handler The reply-handler of the agent. * @returns A new chat agent */ - export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; + export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2; + + /** + * Register a variable which can be used in a chat request to any agent. + * @param name The name of the variable, to be used in the chat input as `#name`. + * @param description A description of the variable for the chat input suggest widget. + * @param resolver Will be called to provide the chat variable's value when it is used. + */ + export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; + } + + /** + * The detail level of this chat variable value. + */ + export enum ChatVariableLevel { + Short = 1, + Medium = 2, + Full = 3 + } + + export interface ChatVariableValue { + /** + * The detail level of this chat variable value. If possible, variable resolvers should try to offer shorter values that will consume fewer tokens in an LLM prompt. + */ + level: ChatVariableLevel; + + /** + * The variable's value, which can be included in an LLM prompt as-is, or the chat agent may decide to read the value and do something else with it. + */ + value: string | Uri; + + /** + * A description of this value, which could be provided to the LLM as a hint. + */ + description?: string; + } + + export interface ChatVariableContext { + /** + * The message entered by the user, which includes this variable. + */ + prompt: string; + } + + export interface ChatVariableResolver { + /** + * A callback to resolve the value of a chat variable. + * @param name The name of the variable. + * @param context Contextual information about this chat request. + * @param token A cancellation token. + */ + resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; } } diff --git a/chat-agent-sample/src/vscode.proposed.chatVariables.d.ts b/chat-agent-sample/src/vscode.proposed.chatVariables.d.ts deleted file mode 100644 index 6d9de44b..00000000 --- a/chat-agent-sample/src/vscode.proposed.chatVariables.d.ts +++ /dev/null @@ -1,35 +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 InteractiveRequest { - variables: Record; - } - - export enum ChatVariableLevel { - Short = 1, - Medium = 2, - Full = 3 - } - - export interface ChatVariableValue { - level: ChatVariableLevel; - value: string; - description?: string; - } - - export interface ChatVariableContext { - message: string; - } - - export interface ChatVariableResolver { - resolve(name: string, context: ChatVariableContext, token: CancellationToken): ProviderResult; - } - - export namespace chat { - export function registerVariable(name: string, description: string, resolver: ChatVariableResolver): Disposable; - } -}