From cfec264062a99a4ee247bff5a1cda4360bce9301 Mon Sep 17 00:00:00 2001 From: Isidor Nikolic Date: Wed, 15 May 2024 23:36:25 +0200 Subject: [PATCH] update sample to latest api (#1021) * update sample to latest api * Fix family * Minor cleanup * Add fullname --------- Co-authored-by: Rob Lourens --- chat-sample/package-lock.json | 2 +- chat-sample/package.json | 3 +- chat-sample/src/extension.ts | 60 ++-- .../src/vscode.proposed.chatParticipant.d.ts | 222 ++++++++----- .../vscode.proposed.chatVariableResolver.d.ts | 52 --- .../src/vscode.proposed.languageModels.d.ts | 309 ++++++++++-------- 6 files changed, 335 insertions(+), 313 deletions(-) delete mode 100644 chat-sample/src/vscode.proposed.chatVariableResolver.d.ts diff --git a/chat-sample/package-lock.json b/chat-sample/package-lock.json index 8c54fdb1..aa526fcd 100644 --- a/chat-sample/package-lock.json +++ b/chat-sample/package-lock.json @@ -15,7 +15,7 @@ "typescript": "^4.0.3" }, "engines": { - "vscode": "^1.86.0" + "vscode": "^1.90.0" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/chat-sample/package.json b/chat-sample/package.json index d096a2ed..d6d461a8 100644 --- a/chat-sample/package.json +++ b/chat-sample/package.json @@ -9,7 +9,7 @@ }, "version": "0.1.0", "engines": { - "vscode": "^1.88.0" + "vscode": "^1.90.0" }, "extensionDependencies": [ "github.copilot-chat" @@ -27,6 +27,7 @@ "chatParticipants": [ { "id": "chat-sample.cat", + "fullName": "Cat", "name": "cat", "description": "Meow! What can I teach you?", "isSticky": true, diff --git a/chat-sample/src/extension.ts b/chat-sample/src/extension.ts index d98aa070..32f65e21 100644 --- a/chat-sample/src/extension.ts +++ b/chat-sample/src/extension.ts @@ -9,7 +9,7 @@ interface ICatChatResult extends vscode.ChatResult { } } -const LANGUAGE_MODEL_ID = 'copilot-gpt-3.5-turbo'; // Use faster model. Alternative is 'copilot-gpt-4', which is slower but more powerful +const MODEL_SELECTOR: vscode.LanguageModelChatSelector = { vendor: 'copilot', family: 'gpt-3.5-turbo' }; export function activate(context: vscode.ExtensionContext) { @@ -22,11 +22,12 @@ export function activate(context: vscode.ExtensionContext) { stream.progress('Picking the right topic to teach...'); const topic = getTopic(context.history); const messages = [ - new vscode.LanguageModelChatUserMessage('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.'), - new vscode.LanguageModelChatUserMessage(topic) + 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 vscode.lm.sendChatRequest(LANGUAGE_MODEL_ID, messages, {}, token); - for await (const fragment of chatResponse.stream) { + const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR); + const chatResponse = await model.sendRequest(messages, {}, token); + for await (const fragment of chatResponse.text) { stream.markdown(fragment); } @@ -39,22 +40,24 @@ export function activate(context: vscode.ExtensionContext) { } else if (request.command == 'play') { stream.progress('Throwing away the computer science books and preparing to play with some Python code...'); const messages = [ - new vscode.LanguageModelChatUserMessage('You are a cat! Reply in the voice of a cat, using cat analogies when appropriate. Be concise to prepare for cat play time.'), - new vscode.LanguageModelChatUserMessage('Give a small random python code samples (that have cat names for variables). ' + request.prompt) + vscode.LanguageModelChatMessage.User('You are a cat! Reply in the voice of a cat, using cat analogies when appropriate. Be concise to prepare for cat play time.'), + vscode.LanguageModelChatMessage.User('Give a small random python code samples (that have cat names for variables). ' + request.prompt) ]; - const chatResponse = await vscode.lm.sendChatRequest(LANGUAGE_MODEL_ID, messages, {}, token); - for await (const fragment of chatResponse.stream) { + const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR); + const chatResponse = await model.sendRequest(messages, {}, token); + for await (const fragment of chatResponse.text) { stream.markdown(fragment); } return { metadata: { command: 'play' } }; } else { const messages = [ - new vscode.LanguageModelChatUserMessage(`You are a cat! Think carefully and step by step like a cat would. + 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.`), - new vscode.LanguageModelChatUserMessage(request.prompt) + vscode.LanguageModelChatMessage.User(request.prompt) ]; - const chatResponse = await vscode.lm.sendChatRequest(LANGUAGE_MODEL_ID, messages, {}, token); - for await (const fragment of chatResponse.stream) { + const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR); + const chatResponse = await 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'); @@ -80,28 +83,6 @@ export function activate(context: vscode.ExtensionContext) { } }; - vscode.chat.registerChatVariableResolver('cat_context', 'Describes the state of mind and version of the cat', { - resolve: (name, context, token) => { - if (name == 'cat_context') { - const mood = Math.random() > 0.5 ? 'happy' : 'grumpy'; - return [ - { - level: vscode.ChatVariableLevel.Short, - value: 'version 1.3 ' + mood - }, - { - level: vscode.ChatVariableLevel.Medium, - value: 'I am a playful cat, version 1.3, and I am ' + mood - }, - { - level: vscode.ChatVariableLevel.Full, - value: 'I am a playful cat, version 1.3, this version prefer to explain everything using mouse and tail metaphores. I am ' + mood - } - ] - } - } - }); - context.subscriptions.push( cat, // Register the command handler for the /meow followup @@ -109,14 +90,15 @@ export function activate(context: vscode.ExtensionContext) { // Replace all variables in active editor with cat names and words const text = textEditor.document.getText(); const messages = [ - new vscode.LanguageModelChatUserMessage(`You are a cat! Think carefully and step by step like a cat would. + new vscode.LanguageModelChatMessage(vscode.LanguageModelChatMessageRole.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!`), - new vscode.LanguageModelChatUserMessage(text) + new vscode.LanguageModelChatMessage(vscode.LanguageModelChatMessageRole.User, text) ]; let chatResponse: vscode.LanguageModelChatResponse | undefined; try { - chatResponse = await vscode.lm.sendChatRequest(LANGUAGE_MODEL_ID, messages, {}, new vscode.CancellationTokenSource().token); + const [model] = await vscode.lm.selectChatModels({ vendor: 'copilot', family: 'gpt-3.5-turbo' }); + chatResponse = await model.sendRequest(messages, {}, new vscode.CancellationTokenSource().token); } catch (err) { // making the chat request might fail because @@ -138,7 +120,7 @@ export function activate(context: vscode.ExtensionContext) { // Stream the code into the editor as it is coming in from the Language Model try { - for await (const fragment of chatResponse.stream) { + 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); diff --git a/chat-sample/src/vscode.proposed.chatParticipant.d.ts b/chat-sample/src/vscode.proposed.chatParticipant.d.ts index d22f0b8e..9ace1a09 100644 --- a/chat-sample/src/vscode.proposed.chatParticipant.d.ts +++ b/chat-sample/src/vscode.proposed.chatParticipant.d.ts @@ -12,7 +12,7 @@ declare module 'vscode' { /** * The prompt as entered by the user. * - * Information about variables used in this request is stored in {@link ChatRequestTurn.variables}. + * Information about references used in this request is stored in {@link ChatRequestTurn.references}. * * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. @@ -30,12 +30,14 @@ declare module 'vscode' { readonly command?: string; /** - * The variables that were referenced in this message. - * TODO@API ensure that this will be compatible with future changes to chat variables. + * The references that were used in this message. */ - readonly variables: ChatResolvedVariable[]; + readonly references: ChatPromptReference[]; - private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: string); + /** + * @hidden + */ + private constructor(prompt: string, command: string | undefined, references: ChatPromptReference[], participant: string); } /** @@ -62,12 +64,18 @@ declare module 'vscode' { */ readonly command?: string; + /** + * @hidden + */ private constructor(response: ReadonlyArray, result: ChatResult, participant: string); } + /** + * Extra context passed to a participant. + */ export interface ChatContext { /** - * All of the chat messages so far in the current chat session. + * All of the chat messages so far in the current chat session. Currently, only chat messages for the current participant are included. */ readonly history: ReadonlyArray; } @@ -81,15 +89,6 @@ declare module 'vscode' { */ message: string; - /** - * If partial markdown content was sent over the {@link ChatRequestHandler handler}'s response stream before the response terminated, then this flag - * can be set to true and it will be rendered with incomplete markdown features patched up. - * - * For example, if the response terminated after sending part of a triple-backtick code block, then the editor will - * render it as a complete code block. - */ - responseIsIncomplete?: boolean; - /** * If set to true, the response will be partly blurred out. */ @@ -229,43 +228,41 @@ declare module 'vscode' { onDidReceiveFeedback: Event; /** - * Dispose this participant and free resources + * Dispose this participant and free resources. */ 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 ChatResolvedVariable { + export interface ChatPromptReference { /** - * The name of the variable. - * - * *Note* that the name doesn't include the leading `#`-character, - * e.g `selection` for `#selection`. + * A unique identifier for this kind of reference. */ - readonly name: string; + readonly id: string; /** - * The start and end index of the variable in the {@link ChatRequest.prompt prompt}. + * 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. */ readonly range?: [start: number, end: number]; - // TODO@API decouple of resolve API, use `value: string | Uri | (maybe) unknown?` /** - * The values of the variable. Can be an empty array if the variable doesn't currently have a value. + * A description of this value that could be used in an LLM prompt. */ - readonly values: ChatVariableValue[]; + readonly modelDescription?: string; + + /** + * The value of this reference. The `string | Uri | Location` types are used today, but this could expand in the future. + */ + readonly value: string | Uri | Location | unknown; } export interface ChatRequest { /** * The prompt as entered by the user. * - * Information about variables used in this request is stored in {@link ChatRequest.variables}. + * Information about references used in this request is stored in {@link ChatRequest.references}. * * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} * are not part of the prompt. @@ -277,17 +274,16 @@ declare module 'vscode' { */ readonly command: string | undefined; - /** - * The list of variables and their values that are referenced in the prompt. + * The list of references 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 participant - * to further modify the prompt, for instance by inlining variable values or creating links to - * headings which contain the resolved values. Variables 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 + * *Note* that 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. */ - readonly variables: readonly ChatResolvedVariable[]; + readonly references: readonly ChatPromptReference[]; } /** @@ -302,48 +298,43 @@ declare module 'vscode' { * * @see {@link ChatResponseStream.push} * @param value A markdown string or a string that should be interpreted as markdown. The boolean form of {@link MarkdownString.isTrusted} is NOT supported. - * @returns This stream. */ - markdown(value: string | MarkdownString): ChatResponseStream; + markdown(value: string | MarkdownString): void; /** * Push an anchor part to this stream. Short-hand for * `push(new ChatResponseAnchorPart(value, title))`. * An anchor is an inline reference to some type of resource. * - * @param value A uri or location - * @param title An optional title that is rendered with value - * @returns This stream. + * @param value A uri, location, or symbol information. + * @param title An optional title that is rendered with value. */ - anchor(value: Uri | Location, title?: string): ChatResponseStream; + anchor(value: Uri | Location, title?: string): void; /** * Push a command button part to this stream. Short-hand for * `push(new ChatResponseCommandButtonPart(value, title))`. * * @param command A Command that will be executed when the button is clicked. - * @returns This stream. */ - button(command: Command): ChatResponseStream; + button(command: Command): void; /** * Push a filetree part to this stream. Short-hand for * `push(new ChatResponseFileTreePart(value))`. * * @param value File tree data. - * @param baseUri The base uri to which this file tree is relative to. - * @returns This stream. + * @param baseUri The base uri to which this file tree is relative. */ - filetree(value: ChatResponseFileTree[], baseUri: Uri): ChatResponseStream; + filetree(value: ChatResponseFileTree[], baseUri: Uri): void; /** * Push a progress part to this stream. Short-hand for * `push(new ChatResponseProgressPart(value))`. * * @param value A progress message - * @returns This stream. */ - progress(value: string): ChatResponseStream; + progress(value: string): void; /** * Push a reference to this stream. Short-hand for @@ -353,57 +344,144 @@ declare module 'vscode' { * * @param value A uri or location * @param iconPath Icon for the reference shown in UI - * @returns This stream. */ - reference(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: ThemeIcon): ChatResponseStream; + reference(value: Uri | Location, iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }): void; /** * Pushes a part to this stream. * * @param part A response part, rendered or metadata */ - push(part: ChatResponsePart): ChatResponseStream; + push(part: ChatResponsePart): void; } + /** + * Represents a part of a chat response that is formatted as Markdown. + */ export class ChatResponseMarkdownPart { + /** + * A markdown string or a string that should be interpreted as markdown. + */ value: MarkdownString; /** - * @param value Note: The boolean form of {@link MarkdownString.isTrusted} is NOT supported. + * Create a new ChatResponseMarkdownPart. + * + * @param value A markdown string or a string that should be interpreted as markdown. The boolean form of {@link MarkdownString.isTrusted} is NOT supported. */ constructor(value: string | MarkdownString); } + /** + * Represents a file tree structure in a chat response. + */ export interface ChatResponseFileTree { + /** + * The name of the file or directory. + */ name: string; + + /** + * An array of child file trees, if the current file tree is a directory. + */ children?: ChatResponseFileTree[]; } + /** + * Represents a part of a chat response that is a file tree. + */ export class ChatResponseFileTreePart { + /** + * File tree data. + */ value: ChatResponseFileTree[]; + + /** + * The base uri to which this file tree is relative + */ baseUri: Uri; + + /** + * Create a new ChatResponseFileTreePart. + * @param value File tree data. + * @param baseUri The base uri to which this file tree is relative. + */ constructor(value: ChatResponseFileTree[], baseUri: Uri); } + /** + * Represents a part of a chat response that is an anchor, that is rendered as a link to a target. + */ export class ChatResponseAnchorPart { - value: Uri | Location | SymbolInformation; + /** + * The target of this anchor. + */ + value: Uri | Location; + + /** + * An optional title that is rendered with value. + */ title?: string; - constructor(value: Uri | Location | SymbolInformation, title?: string); + + /** + * Create a new ChatResponseAnchorPart. + * @param value A uri or location. + * @param title An optional title that is rendered with value. + */ + constructor(value: Uri | Location, title?: string); } + /** + * Represents a part of a chat response that is a progress message. + */ export class ChatResponseProgressPart { + /** + * The progress message + */ value: string; + + /** + * Create a new ChatResponseProgressPart. + * @param value A progress message + */ constructor(value: string); } + /** + * Represents a part of a chat response that is a reference, rendered separately from the content. + */ export class ChatResponseReferencePart { - value: Uri | Location | { variableName: string; value?: Uri | Location }; - iconPath?: ThemeIcon; - constructor(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: ThemeIcon); + /** + * The reference target. + */ + value: Uri | Location; + + /** + * The icon for the reference. + */ + iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }; + + /** + * Create a new ChatResponseReferencePart. + * @param value A uri or location + * @param iconPath Icon for the reference shown in UI + */ + constructor(value: Uri | Location, iconPath?: Uri | ThemeIcon | { light: Uri; dark: Uri }); } + /** + * Represents a part of a chat response that is a button that executes a command. + */ export class ChatResponseCommandButtonPart { + /** + * The command that will be executed when the button is clicked. + */ value: Command; + + /** + * Create a new ChatResponseCommandButtonPart. + * @param value A Command that will be executed when the button is clicked. + */ constructor(value: Command); } @@ -424,30 +502,4 @@ declare module 'vscode' { */ export function createChatParticipant(id: string, handler: ChatRequestHandler): ChatParticipant; } - - /** - * 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 participant 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; - } } diff --git a/chat-sample/src/vscode.proposed.chatVariableResolver.d.ts b/chat-sample/src/vscode.proposed.chatVariableResolver.d.ts deleted file mode 100644 index 32f081ec..00000000 --- a/chat-sample/src/vscode.proposed.chatVariableResolver.d.ts +++ /dev/null @@ -1,52 +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 namespace chat { - - /** - * Register a variable which can be used in a chat request to any participant. - * @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 registerChatVariableResolver(name: string, description: string, resolver: ChatVariableResolver): Disposable; - } - - 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 participant 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-sample/src/vscode.proposed.languageModels.d.ts b/chat-sample/src/vscode.proposed.languageModels.d.ts index 73f4d954..2024eb9f 100644 --- a/chat-sample/src/vscode.proposed.languageModels.d.ts +++ b/chat-sample/src/vscode.proposed.languageModels.d.ts @@ -5,49 +5,48 @@ declare module 'vscode' { + // https://github.com/microsoft/vscode/issues/206265 + /** - * Represents a language model response. - * - * @see {@link LanguageModelAccess.chatRequest} + * Represents the role of a chat message. This is either the user or the assistant. */ - export interface LanguageModelChatResponse { + export enum LanguageModelChatMessageRole { + /** + * The user role, e.g the human interacting with a language model. + */ + User = 1, /** - * An async iterable that is a stream of text chunks forming the overall response. - * - * *Note* that this stream will error when during data receiving an error occurrs. + * The assistant role, e.g. the language model generating responses. */ - stream: AsyncIterable; + Assistant = 2 } /** - * 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. + * Represents a message in a chat. Can assume different roles, like user or assistant. */ - export class LanguageModelChatSystemMessage { + export class LanguageModelChatMessage { /** - * The content of this message. - */ - content: string; - - /** - * Create a new system message. + * Utility to 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); - } + static User(content: string, name?: string): LanguageModelChatMessage; - /** - * A language model message that represents a user message. - */ - export class LanguageModelChatUserMessage { + /** + * Utility to create a new assistant message. + * + * @param content The content of the message. + * @param name The optional name of a user for the message. + */ + static Assistant(content: string, name?: string): LanguageModelChatMessage; + + /** + * The role of this message. + */ + role: LanguageModelChatMessageRole; /** * The content of this message. @@ -62,83 +61,149 @@ declare module 'vscode' { /** * Create a new user message. * + * @param role The role of the message. * @param content The content of the message. * @param name The optional name of a user for the message. */ - constructor(content: string, name?: string); + constructor(role: LanguageModelChatMessageRole, 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 LanguageModelChatAssistantMessage { + * Represents a language model response. + * + * @see {@link LanguageModelAccess.chatRequest} + */ + export interface LanguageModelChatResponse { /** - * The content of this message. - */ - content: string; - - /** - * The optional name of a user for this message. - */ - name: string | undefined; - - /** - * Create a new assistant message. + * An async iterable that is a stream of text chunks forming the overall response. * - * @param content The content of the message. - * @param name The optional name of a user for the message. + * *Note* that this stream will error when during data receiving an error occurs. Consumers of + * the stream should handle the errors accordingly. + * + * @example + * ```ts + * try { + * // consume stream + * for await (const chunk of response.stream) { + * console.log(chunk); + * } + * + * } catch(e) { + * // stream ended with an error + * console.error(e); + * } + * ``` + * + * To cancel the stream, the consumer can {@link CancellationTokenSource.cancel cancel} the token that was used to make the request + * or break from the for-loop. */ - constructor(content: string, name?: string); + text: AsyncIterable; } /** - * Different types of language model messages. + * Represents a language model for making chat requests. + * + * @see {@link lm.selectChatModels} */ - export type LanguageModelChatMessage = LanguageModelChatSystemMessage | LanguageModelChatUserMessage | LanguageModelChatAssistantMessage; - - /** - * Represents information about a registered language model. - */ - export interface LanguageModelInformation { - /** - * The identifier of the language model. - */ - readonly id: string; + export interface LanguageModelChat { /** - * The human-readable name of the language model. + * Human-readable name of the language model. */ readonly name: string; /** - * The version of the language model. + * Opaque identifier of the language model. + */ + readonly id: string; + + /** + * A well-know identifier of the vendor of the language model, a sample is `copilot`, but + * values are defined by extensions contributing chat models and need to be looked up with them. + */ + readonly vendor: string; + + /** + * Opaque family-name of the language model. Values might be `gpt-3.5-turbo`, `gpt4`, `phi2`, or `llama` + * but they are defined by extensions contributing languages and subject to change. + */ + readonly family: string; + + /** + * Opaque version string of the model. This is defined by the extension contributing the language model + * and subject to change. */ readonly version: string; /** - * The number of available tokens that can be used when sending requests - * to the language model. - * - * @see {@link lm.sendChatRequest} + * The maximum number of tokens that can be sent to the model in a single request. */ - readonly tokens: number; + readonly maxInputTokens: number; + + /** + * Make a chat request using a language model. + * + * *Note* that language model use may be subject to access restrictions and user consent. Calling this function + * for the first time (for a extension) will show a consent dialog to the user and because of that this function + * must _only be called in response to a user action!_ Extension can use {@link LanguageModelAccessInformation.canSendRequest} + * to check if they have the necessary permissions to make a request. + * + * This function will return a rejected promise if making a request to the language model is not + * possible. Reasons for this can be: + * + * - user consent not given, see {@link LanguageModelError.NoPermissions `NoPermissions`} + * - model does not exist anymore, see {@link LanguageModelError.NotFound `NotFound`} + * - quota limits exceeded, see {@link LanguageModelError.Blocked `Blocked`} + * - other issues in which case extension must check {@link LanguageModelError.cause `LanguageModelError.cause`} + * + * @param messages An array of message instances. + * @param options Options that control the request. + * @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one. + * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when the request couldn't be made. + */ + sendRequest(messages: LanguageModelChatMessage[], options?: LanguageModelChatRequestOptions, token?: CancellationToken): Thenable; + + /** + * Count the number of tokens in a message using the model specific tokenizer-logic. + + * @param text A string or a message instance. + * @param token Optional cancellation token. See {@link CancellationTokenSource} for how to create one. + * @returns A thenable that resolves to the number of tokens. + */ + countTokens(text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable; } /** - * An event describing the change in the set of available language models. + * Describes how to select language models for chat requests. + * + * @see {@link lm.selectChatModels} */ - // TODO@API use LanguageModelInformation instead of string? - export interface LanguageModelChangeEvent { + export interface LanguageModelChatSelector { + /** - * Added language models. + * A vendor of language models. + * @see {@link LanguageModelChat.vendor} */ - readonly added: readonly string[]; + vendor?: string; + /** - * Removed language models. + * A family of language models. + * @see {@link LanguageModelChat.family} */ - readonly removed: readonly string[]; + family?: string; + + /** + * The version of a language model. + * @see {@link LanguageModelChat.version} + */ + version?: string; + + /** + * The identifier of a language model. + * @see {@link LanguageModelChat.id} + */ + id?: string; } /** @@ -151,17 +216,22 @@ declare module 'vscode' { */ export class LanguageModelError extends Error { - /** - * The language model does not exist. - */ - static NotFound(message?: string): LanguageModelError; - /** * The requestor does not have permissions to use this * language model */ static NoPermissions(message?: string): LanguageModelError; + /** + * The requestor is blocked from using this language model. + */ + static Blocked(message?: string): LanguageModelError; + + /** + * The language model does not exist. + */ + static NotFound(message?: string): LanguageModelError; + /** * A code that identifies this error. * @@ -175,7 +245,7 @@ declare module 'vscode' { /** * Options for making a chat request using a language model. * - * @see {@link lm.chatRequest} + * @see {@link LanguageModelChat.sendRequest} */ export interface LanguageModelChatRequestOptions { @@ -184,12 +254,6 @@ declare module 'vscode' { */ justification?: string; - /** - * Do not show the consent UI if the user has not yet granted access to the language model but fail the request instead. - */ - // TODO@API Revisit this, how do you do the first request? - silent?: boolean; - /** * A set of options that control the behavior of the language model. These options are specific to the language model * and need to be lookup in the respective documentation. @@ -203,58 +267,33 @@ declare module 'vscode' { export namespace lm { /** - * The identifiers of all language models that are currently available. + * An event that is fired when the set of available chat models changes. */ - export const languageModels: readonly string[]; + export const onDidChangeChatModels: Event; /** - * An event that is fired when the set of available language models changes. + * Select chat models by a {@link LanguageModelChatSelector selector}. This can yield in multiple or no chat models and + * extensions must handle these cases, esp. when no chat model exists, gracefully. + * + * ```ts + * + * const models = await vscode.lm.selectChatModels({family: 'gpt-3.5-turbo'})!; + * if (models.length > 0) { + * const [first] = models; + * const response = await first.sendRequest(...) + * // ... + * } else { + * // NO chat models available + * } + * ``` + * + * *Note* that extensions can hold-on to the results returned by this function and use them later. However, when the + * {@link onDidChangeChatModels}-event is fired the list of chat models might have changed and extensions should re-query. + * + * @param selector A chat model selector. When omitted all chat models are returned. + * @returns An array of chat models, can be empty! */ - export const onDidChangeLanguageModels: Event; - - /** - * Retrieve information about a language model. - * - * @param languageModel A language model identifier. - * @returns A {@link LanguageModelInformation} instance or `undefined` if the language model does not exist. - */ - export function getLanguageModelInformation(languageModel: string): LanguageModelInformation | undefined; - - /** - * Make a chat request using a language model. - * - * - *Note 1:* language model use may be subject to access restrictions and user consent. - * - * - *Note 2:* language models are contributed by other extensions and as they evolve and change, - * the set of available language models may change over time. Therefore it is strongly recommend to check - * {@link languageModels} for aviailable values and handle missing language models gracefully. - * - * This function will return a rejected promise if making a request to the language model is not - * possible. Reasons for this can be: - * - * - user consent not given, see {@link LanguageModelError.NoPermissions `NoPermissions`} - * - model does not exist, see {@link LanguageModelError.NotFound `NotFound`} - * - quota limits exceeded, see {@link LanguageModelError.cause `LanguageModelError.cause`} - * - * @param languageModel A language model identifier. - * @param messages An array of message instances. - * @param options Options that control the request. - * @param token A cancellation token which controls the request. See {@link CancellationTokenSource} for how to create one. - * @returns A thenable that resolves to a {@link LanguageModelChatResponse}. The promise will reject when the request couldn't be made. - */ - export function sendChatRequest(languageModel: string, messages: LanguageModelChatMessage[], options: LanguageModelChatRequestOptions, token: CancellationToken): Thenable; - - /** - * Uses the language model specific tokenzier and computes the length in token of a given message. - * - * *Note* that this function will throw when the language model does not exist. - * - * @param languageModel A language model identifier. - * @param text A string or a message instance. - * @param token Optional cancellation token. - * @returns A thenable that resolves to the length of the message in tokens. - */ - export function computeTokenLength(languageModel: string, text: string | LanguageModelChatMessage, token?: CancellationToken): Thenable; + export function selectChatModels(selector?: LanguageModelChatSelector): Thenable; } /** @@ -270,13 +309,13 @@ declare module 'vscode' { /** * Checks if a request can be made to a language model. * - * *Note* that calling this function will not trigger a consent UI but just checks. + * *Note* that calling this function will not trigger a consent UI but just checks for a persisted state. * - * @param languageModelId A language model identifier. + * @param chat A language model chat object. * @return `true` if a request can be made, `false` if not, `undefined` if the language * model does not exist or consent hasn't been asked for. */ - canSendRequest(languageModelId: string): boolean | undefined; + canSendRequest(chat: LanguageModelChat): boolean | undefined; } export interface ExtensionContext {