Adopt changes to chat message shapes (#957)

This commit is contained in:
Rob Lourens
2024-02-14 20:59:00 +00:00
committed by GitHub
parent 2eb7bad119
commit ce58e8c282
4 changed files with 165 additions and 151 deletions

View File

@ -13,7 +13,7 @@ const LANGUAGE_MODEL_ID = 'copilot-gpt-4';
export function activate(context: vscode.ExtensionContext) {
// Define a Cat chat agent handler.
const handler: vscode.ChatAgentHandler = async (request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, stream: vscode.ChatAgentResponseStream, token: vscode.CancellationToken): Promise<ICatChatAgentResult> => {
const handler: vscode.ChatAgentRequestHandler = async (request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, stream: vscode.ChatAgentResponseStream, token: vscode.CancellationToken): Promise<ICatChatAgentResult> => {
// 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.
@ -22,14 +22,8 @@ export function activate(context: vscode.ExtensionContext) {
const topics = ['linked list', 'recursion', 'stack', 'queue', 'pointers'];
const topic = topics[Math.floor(Math.random() * topics.length)];
const messages = [
{
role: vscode.ChatMessageRole.System,
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,
content: topic
},
new vscode.LanguageModelSystemMessage('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.'),
new vscode.LanguageModelUserMessage(topic)
];
const chatRequest = access.makeChatRequest(messages, {}, token);
for await (const fragment of chatRequest.stream) {
@ -45,14 +39,8 @@ export function activate(context: vscode.ExtensionContext) {
} else if (request.command == 'play') {
const access = await vscode.chat.requestLanguageModelAccess(LANGUAGE_MODEL_ID);
const messages = [
{
role: vscode.ChatMessageRole.System,
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,
content: request.prompt
}
new vscode.LanguageModelSystemMessage('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.'),
new vscode.LanguageModelUserMessage(request.prompt)
];
const chatRequest = access.makeChatRequest(messages, {}, token);
for await (const fragment of chatRequest.stream) {
@ -62,14 +50,8 @@ export function activate(context: vscode.ExtensionContext) {
} else {
const access = await vscode.chat.requestLanguageModelAccess(LANGUAGE_MODEL_ID);
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
}
new vscode.LanguageModelSystemMessage('You are a cat! Reply in the voice of a cat, using cat analogies when appropriate.'),
new vscode.LanguageModelUserMessage(request.prompt)
];
const chatRequest = access.makeChatRequest(messages, {}, token);
for await (const fragment of chatRequest.stream) {

View File

@ -1,25 +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' {
// ChatML
export enum ChatMessageRole {
System = 0,
User = 1,
Assistant = 2,
Function = 3,
}
// ChatML
export class ChatMessage {
role: ChatMessageRole;
content: string;
name?: string;
constructor(role: ChatMessageRole, content: string);
}
}

View File

@ -5,33 +5,13 @@
declare module 'vscode' {
/**
* One request/response pair in chat history.
*/
export interface ChatAgentHistoryEntry {
/**
* The request that was sent to the chat agent.
*/
request: ChatAgentRequest;
/**
* The content that was received from the chat agent. Only the progress parts that represent actual content (not metadata) are represented.
*/
response: ReadonlyArray<ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>;
/**
* The result that was received from the chat agent.
*/
result: ChatAgentResult2;
}
// TODO@API name: Turn?
export class ChatAgentRequestTurn {
/**
* The prompt as entered by the user.
*
* Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}.
* Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}.
*
* *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command}
* are not part of the prompt.
@ -41,7 +21,8 @@ declare module 'vscode' {
/**
* The ID of the chat agent to which this request was directed.
*/
readonly agentId: string;
// TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name)
readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string };
/**
* The name of the {@link ChatAgentCommand command} that was selected for this request.
@ -49,12 +30,11 @@ declare module 'vscode' {
readonly command: string | undefined;
/**
*
* The variables that were referenced in this message.
*/
// TODO@API is this needed?
readonly variables: ChatAgentResolvedVariable[];
private constructor(prompt: string, agentId: string, command: string | undefined, variables: ChatAgentResolvedVariable[],);
private constructor(prompt: string, command: string | undefined, variables: ChatAgentResolvedVariable[], agent: { extensionId: string; agentId: string });
}
// TODO@API name: Turn?
@ -70,24 +50,17 @@ declare module 'vscode' {
*/
readonly result: ChatAgentResult2;
readonly agentId: string;
// TODO@API NAME: agentId shouldbe agentName or just agent (because it is ChatAgent#name)
readonly agent: { readonly extensionId: string; readonly agent: string; readonly agentId: string };
private constructor(response: ReadonlyArray<ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>, result: ChatAgentResult2, agentId: string);
private constructor(response: ReadonlyArray<ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>, result: ChatAgentResult2, agentId: { extensionId: string; agentId: string });
}
export interface ChatAgentContext {
/**
* @deprecated
*/
history: ChatAgentHistoryEntry[];
// location:
/**
* All of the chat messages so far in the current chat session.
*/
// TODO@API name: histroy
readonly history2: ReadonlyArray<ChatAgentRequestTurn | ChatAgentResponseTurn>;
readonly history: ReadonlyArray<ChatAgentRequestTurn | ChatAgentResponseTurn>;
}
/**
@ -180,26 +153,17 @@ declare module 'vscode' {
readonly sampleRequest?: string;
/**
* Whether executing the command puts the
* chat into a persistent mode, where the
* command is prepended to the chat input.
* Whether executing the command puts the chat into a persistent mode, where the command is automatically added to the chat input for the next message.
* If this is not set, the chat input will fall back to the agent after submitting this command.
*/
readonly shouldRepopulate?: boolean;
/**
* Placeholder text to render in the chat input
* when the 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;
readonly isSticky?: boolean;
}
export interface ChatAgentCommandProvider {
/**
* Returns a list of commands that its agent is capable of handling. A command
* can be selected by the user and will then be passed to the {@link ChatAgentHandler handler}
* can be selected by the user and will then be passed to the {@link ChatAgentRequestHandler handler}
* via the {@link ChatAgentRequest.command command} property.
*
*
@ -207,6 +171,7 @@ declare module 'vscode' {
* @returns A list of commands. The lack of a result can be signaled by returning `undefined`, `null`, or
* an empty array.
*/
// TODO@API Q: should we provide the current history or last results for extra context?
provideCommands(token: CancellationToken): ProviderResult<ChatAgentCommand[]>;
}
@ -216,16 +181,19 @@ declare module 'vscode' {
export interface ChatAgentFollowup {
/**
* The message to send to the chat.
* TODO@API is it ok for variables to resolved from the text of this prompt, using the `#` syntax?
*/
prompt: string;
/**
* By default, the followup goes to the same agent/subCommand. But these properties can be set to override that.
* By default, the followup goes to the same agent/command. But this property can be set to invoke a different agent.
* TODO@API do extensions need to specify the extensionID of the agent here as well?
*/
agentId?: string;
subCommand?: string;
/**
* By default, the followup goes to the same agent/command. But this property can be set to invoke a different command.
*/
command?: string;
/**
* A tooltip to show when hovering over the followup.
@ -235,6 +203,7 @@ declare module 'vscode' {
/**
* A title to show the user, when it is different than the message.
*/
// TODO@API title vs tooltip?
title?: string;
}
@ -250,6 +219,12 @@ declare module 'vscode' {
provideFollowups(result: ChatAgentResult2, token: CancellationToken): ProviderResult<ChatAgentFollowup[]>;
}
/**
* A chat request handler is a callback that will be invoked when a request is made to a chat agent.
*/
export type ChatAgentRequestHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult<ChatAgentResult2>;
export interface ChatAgent2 {
/**
@ -281,6 +256,11 @@ declare module 'vscode' {
dark: Uri;
} | ThemeIcon;
/**
* The handler for requests to this agent.
*/
requestHandler: ChatAgentRequestHandler;
/**
* This provider will be called to retrieve the agent's commands.
*/
@ -291,16 +271,6 @@ declare module 'vscode' {
*/
followupProvider?: ChatAgentFollowupProvider;
// TODO@API
// notify(request: ChatResponsePart, reference: string): boolean;
// BETTER
// requestResponseStream(callback: (stream: ChatAgentResponseStream) => void, why?: string): void;
// TODO@API
// clear NEVER happens
// onDidClearResult(value: TResult): void;
/**
* When the user clicks this agent in `/help`, this text will be submitted to this command
*/
@ -321,49 +291,59 @@ declare module 'vscode' {
dispose(): void;
}
/**
* A resolved variable value is a name-value pair as well as the range in the prompt where a variable was used.
*/
export interface ChatAgentResolvedVariable {
name: string;
range: [start: number, end: number];
values: ChatVariableValue[];
/**
* The name of the variable.
*
* *Note* that the name doesn't include the leading `#`-character,
* e.g `selection` for `#selection`.
*/
readonly name: string;
/**
* The start and end index of the variable in the {@link ChatAgentRequest.prompt prompt}.
*
* *Note* that the indices take the leading `#`-character into account which means they can
* used to modify the prompt as-is.
*/
readonly range: [start: number, end: number];
// TODO@API decouple of resolve API, use `value: string | Uri | (maybe) unknown?`
readonly values: ChatVariableValue[];
}
export interface ChatAgentRequest {
/**
* The prompt entered by the user. The {@link ChatAgent2.name name} of the agent or the {@link ChatAgentCommand.name command}
* are not part of the prompt.
*
* @see {@link ChatAgentRequest.command}
*/
prompt: string;
/**
* The prompt as entered by the user.
*
* Information about variables used in this request are is stored in {@link ChatAgentRequest.variables2}.
* Information about variables used in this request are is stored in {@link ChatAgentRequest.variables}.
*
* *Note* that the {@link ChatAgent2.name name} of the agent and the {@link ChatAgentCommand.name command}
* are not part of the prompt.
*/
prompt2: string;
/**
* The ID of the chat agent to which this request was directed.
*/
agentId: string;
readonly prompt: string;
/**
* The name of the {@link ChatAgentCommand command} that was selected for this request.
*/
command?: string;
/** @deprecated */
variables: Record<string, ChatVariableValue[]>;
readonly command: string | undefined;
/**
* The list of variables and their values that are referenced in the prompt.
*
* *Note* that the prompt contains varibale references as authored and that it is up to the agent
* to further modify the prompt, for instance by inlining variable values or creating links to
* headings which contain the resolved values. vvariables are sorted in reverse by their range
* in the prompt. That means the last variable in the prompt is the first in this list. This simplifies
* string-manipulation of the prompt.
*/
variables2: ChatAgentResolvedVariable[];
// TODO@API Q? are there implicit variables that are not part of the prompt?
readonly variables: readonly ChatAgentResolvedVariable[];
}
export interface ChatAgentResponseStream {
@ -376,6 +356,7 @@ declare module 'vscode' {
* @param value A plain text value.
* @returns This stream.
*/
// TODO@API remove?
text(value: string): ChatAgentResponseStream;
/**
@ -386,6 +367,7 @@ declare module 'vscode' {
* @param value A markdown string or a string that should be interpreted as markdown.
* @returns This stream.
*/
// TODO@API NAME: content
markdown(value: string | MarkdownString): ChatAgentResponseStream;
/**
@ -450,11 +432,6 @@ declare module 'vscode' {
push(part: ChatResponsePart): ChatAgentResponseStream;
}
// TODO@API
// support ChatResponseCommandPart
// support ChatResponseTextEditPart
// support ChatResponseCodeReferencePart
// TODO@API should the name suffix differentiate between rendered items (XYZPart)
// and metadata like XYZItem
export class ChatResponseTextPart {
@ -486,6 +463,7 @@ declare module 'vscode' {
export class ChatResponseProgressPart {
value: string;
// TODO@API inline
constructor(value: string);
}
@ -499,24 +477,24 @@ declare module 'vscode' {
constructor(value: Command);
}
/**
* Represents the different chat response types.
*/
export type ChatResponsePart = ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart
| ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart;
// TODO@API Remove a different type of `request` so that they can
// evolve at their own pace
export type ChatAgentHandler = (request: ChatAgentRequest, context: ChatAgentContext, response: ChatAgentResponseStream, token: CancellationToken) => ProviderResult<ChatAgentResult2>;
export namespace chat {
/**
* Create a new {@link ChatAgent2 chat agent} instance.
*
* @param name Short name by which this agent is referred to in the UI
* @param handler The reply-handler of the agent.
* @param name Short name by which the agent is referred to in the UI. The name must be unique for the extension
* contributing the agent but can collide with names from other extensions.
* @param handler A request handler for the agent.
* @returns A new chat agent
*/
export function createChatAgent(name: string, handler: ChatAgentHandler): ChatAgent2;
export function createChatAgent(name: string, handler: ChatAgentRequestHandler): ChatAgent2;
/**
* Register a variable which can be used in a chat request to any agent.

View File

@ -5,18 +5,96 @@
declare module 'vscode' {
/**
* Represents a language model response.
*
* @see {@link LanguageModelAccess.makeChatRequest}
*/
export interface LanguageModelResponse {
/**
* The overall result of the request which represents failure or success
* but _not_ the actual response or responses
* but. The concrete value is not specified and depends on the selected language model.
*
* *Note* that the actual response represented by the {@link LanguageModelResponse.stream `stream`}-property
*/
// TODO@API define this type!
result: Thenable<unknown>;
/**
* An async iterable that is a stream of text chunks forming the overall response.
*/
stream: AsyncIterable<string>;
}
/**
* A language model message that represents a system message.
*
* System messages provide instructions to the language model that define the context in
* which user messages are interpreted.
*
* *Note* that a language model may choose to add additional system messages to the ones
* provided by extensions.
*/
export class LanguageModelSystemMessage {
/**
* The content of this message.
*/
content: string;
/**
* Create a new system message.
*
* @param content The content of the message.
*/
constructor(content: string);
}
/**
* A language model message that represents a user message.
*/
export class LanguageModelUserMessage {
/**
* The content of this message.
*/
content: string;
/**
* The optional name of a user for this message.
*/
name: string | undefined;
/**
* Create a new user message.
*
* @param content The content of the message.
* @param name The optional name of a user for the message.
*/
constructor(content: string, name?: string);
}
/**
* A language model message that represents an assistant message, usually in response to a user message
* or as a sample response/reply-pair.
*/
export class LanguageModelAssistantMessage {
/**
* The content of this message.
*/
content: string;
/**
* Create a new assistant message.
*
* @param content The content of the message.
*/
constructor(content: string);
}
export type LanguageModelMessage = LanguageModelSystemMessage | LanguageModelUserMessage | LanguageModelAssistantMessage;
/**
* Represents access to using a language model. Access can be revoked at any time and extension
* must check if the access is {@link LanguageModelAccess.isRevoked still valid} before using it.
@ -31,6 +109,7 @@ declare module 'vscode' {
/**
* An event that is fired when the access the language model has has been revoked or re-granted.
*/
// TODO@API NAME?
readonly onDidChangeAccess: Event<void>;
/**
@ -49,7 +128,7 @@ declare module 'vscode' {
* @param messages
* @param options
*/
makeChatRequest(messages: ChatMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse;
makeChatRequest(messages: LanguageModelMessage[], options: { [name: string]: any }, token: CancellationToken): LanguageModelResponse;
}
export interface LanguageModelAccessOptions {
@ -73,7 +152,7 @@ declare module 'vscode' {
readonly removed: readonly string[];
}
//@API DEFINE the namespace for this: env, lm, ai?
//@API DEFINE the namespace for this: lm (languageModels), copilot, ai, env,?
export namespace chat {
/**