From 67dc64868b5fd220d2cc6f658541e9952f15b6d4 Mon Sep 17 00:00:00 2001 From: amitjoshi438 <54068463+amitjoshi438@users.noreply.github.com> Date: Fri, 17 May 2024 15:23:59 +0530 Subject: [PATCH] [@powerpages] GitHub copilot auth handling for AIB and PAC (#947) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Update package.json with extension dependencies and enabled API proposals for chat participants * feat: Add PowerPagesChatParticipant for chat functionality * feat: Add logic to handle chat requests in PowerPagesChatParticipant * feat: Removed logs * TODO * feat: Add logic to handle chat requests in PowerPagesChatParticipant(correct response format) * chore: Remove unused code and update PowerPagesChatParticipant initialization * chore: Update PowerPagesChatParticipant initialization and pac integration * feat: Initialize organization details in PowerPagesChatParticipant * chore: Remove unnecessary code and update OrgChangeNotifier initialization * Fix lint warnings * feat: Refactor PowerPagesChatParticipant class The code changes refactor the PowerPagesChatParticipant class in the `PowerPagesChatParticipant.ts` file. The changes include: - Fixing a typo in the `instance` property declaration - Updating the constructor parameters to have consistent spacing - Adding a comment to handle chat requests - Removing a console.log statement - Updating the `intializeOrgDetails` method to have consistent spacing - Updating the `intializeOrgDetails` method to use destructuring assignment - Updating the `intializeOrgDetails` method to update the `orgDetails` in the global state - Removing unused code Co-authored-by: amitjoshi * Added response for error scenarios * Added & removed comments * refactor: Update PowerPagesCopilot name in package.json The code changes in the package.json file update the "name" property for the "powerpages" module to "Power Pages Copilot". This change reflects the updated name for the module. Co-authored-by: amitjoshi * Code refactoring for utils and constants * Enhanced pac auth handling * refactor: Update PowerPagesChatParticipantConstants The code changes in the `PowerPagesChatParticipantConstants.ts` file refactor the constants used in the PowerPages chat participant. The changes include: - Cleaning up the code formatting Co-authored-by: amitjoshi --------- Co-authored-by: amitjoshi Co-authored-by: tyaginidhi Co-authored-by: Nidhi Tyagi 🌟🐇🌴❄️ --- package.json | 1 + src/client/extension.ts | 6 +- src/client/lib/PacActivityBarUI.ts | 5 +- src/common/OrgChangeNotifier.ts | 11 +- src/common/OrgHandlerUtils.ts | 59 ++ src/common/Utils.ts | 4 +- .../powerpages/PowerPagesChatParticipant.ts | 124 ++- .../PowerPagesChatParticipantConstants.ts | 10 + .../PowerPagesChatParticipantTypes.ts | 4 + .../PowerPagesChatParticipantUtils.ts | 18 + .../vscode.proposed.chatParticipant.d.ts | 891 +++++++++--------- .../vscode.proposed.languageModels.d.ts | 567 +++++------ 12 files changed, 952 insertions(+), 748 deletions(-) create mode 100644 src/common/OrgHandlerUtils.ts create mode 100644 src/common/chat-participants/powerpages/PowerPagesChatParticipantConstants.ts create mode 100644 src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts diff --git a/package.json b/package.json index 10554b6a..488c55b5 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ { "id": "powerpages", "name": "powerpages", + "fullName": "Power Pages Copilot", "description": "Power Pages Copilot", "isSticky": true } diff --git a/src/client/extension.ts b/src/client/extension.ts index 2bb51c08..72927a5e 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -40,7 +40,6 @@ import { oneDSLoggerWrapper } from "../common/OneDSLoggerTelemetry/oneDSLoggerWr import { OrgChangeNotifier, orgChangeEvent } from "../common/OrgChangeNotifier"; import { ActiveOrgOutput } from "./pac/PacTypes"; import { telemetryEventNames } from "./telemetry/TelemetryEventNames"; -import { PowerPagesChatParticipant } from "../common/chat-participants/powerpages/PowerPagesChatParticipant"; let client: LanguageClient; let _context: vscode.ExtensionContext; @@ -178,9 +177,6 @@ export async function activate( // Add CRUD related callback subscription here await handleFileSystemCallbacks(_context, _telemetry); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const PowerPagesChatParticipantInstance = new PowerPagesChatParticipant(_context, _telemetry); - const cliContext = new CliAcquisitionContext(_context, _telemetry); const cli = new CliAcquisition(cliContext); const cliPath = await cli.ensureInstalled(); @@ -219,7 +215,7 @@ export async function activate( oneDSLoggerWrapper.getLogger().traceError(exceptionError.name, exceptionError.message, exceptionError, { eventName: 'VscodeDesktopUsage' }); } // Init OrgChangeNotifier instance - OrgChangeNotifier.createOrgChangeNotifierInstance(pacTerminal.getWrapper()); + OrgChangeNotifier.createOrgChangeNotifierInstance(pacTerminal.getWrapper(), _context); _telemetry.sendTelemetryEvent("PowerPagesWebsiteYmlExists"); // Capture's PowerPages Users oneDSLoggerWrapper.getLogger().traceInfo("PowerPagesWebsiteYmlExists"); diff --git a/src/client/lib/PacActivityBarUI.ts b/src/client/lib/PacActivityBarUI.ts index 7cb9bc09..cdfe443c 100644 --- a/src/client/lib/PacActivityBarUI.ts +++ b/src/client/lib/PacActivityBarUI.ts @@ -9,6 +9,7 @@ import { AuthTreeView } from './AuthPanelView'; import { EnvAndSolutionTreeView } from './EnvAndSolutionTreeView'; import { PowerPagesCopilot } from '../../common/copilot/PowerPagesCopilot'; import { ITelemetry } from '../telemetry/ITelemetry'; +import { PowerPagesChatParticipant } from '../../common/chat-participants/powerpages/PowerPagesChatParticipant'; export function RegisterPanels(pacWrapper: PacWrapper, context: vscode.ExtensionContext, telemetry: ITelemetry): vscode.Disposable[] { const authPanel = new AuthTreeView(() => pacWrapper.authList(), pacWrapper); @@ -20,11 +21,13 @@ export function RegisterPanels(pacWrapper: PacWrapper, context: vscode.Extension const copilotPanel = new PowerPagesCopilot(context.extensionUri, context, telemetry, pacWrapper); + const powerPagesChatParticipant = PowerPagesChatParticipant.getInstance(context, telemetry, pacWrapper); + vscode.window.registerWebviewViewProvider('powerpages.copilot', copilotPanel, { webviewOptions: { retainContextWhenHidden: true, }, }); - return [authPanel, envAndSolutionPanel, copilotPanel]; + return [authPanel, envAndSolutionPanel, copilotPanel, powerPagesChatParticipant]; } diff --git a/src/common/OrgChangeNotifier.ts b/src/common/OrgChangeNotifier.ts index 130b23b7..26c1cfd7 100644 --- a/src/common/OrgChangeNotifier.ts +++ b/src/common/OrgChangeNotifier.ts @@ -18,18 +18,21 @@ export class OrgChangeNotifier { private _pacWrapper: PacWrapper | undefined; private _orgDetails: ActiveOrgOutput | undefined; private static _orgChangeNotifierObj: OrgChangeNotifier | undefined; + private extensionContext: vscode.ExtensionContext; - private constructor(pacWrapper: PacWrapper) { + private constructor(pacWrapper: PacWrapper, extensionContext: vscode.ExtensionContext) { this._pacWrapper = pacWrapper; this.activeOrgDetails(); if (this._pacWrapper) { this.setupFileWatcher(); } + + this.extensionContext = extensionContext; } - public static createOrgChangeNotifierInstance(pacWrapper: PacWrapper) { + public static createOrgChangeNotifierInstance(pacWrapper: PacWrapper, extensionContext: vscode.ExtensionContext) { if (!OrgChangeNotifier._orgChangeNotifierObj) { - OrgChangeNotifier._orgChangeNotifierObj = new OrgChangeNotifier(pacWrapper); + OrgChangeNotifier._orgChangeNotifierObj = new OrgChangeNotifier(pacWrapper, extensionContext); } return OrgChangeNotifier._orgChangeNotifierObj; } @@ -53,4 +56,4 @@ export class OrgChangeNotifier { orgChangeErrorEventEmitter.fire(); } } -} \ No newline at end of file +} diff --git a/src/common/OrgHandlerUtils.ts b/src/common/OrgHandlerUtils.ts new file mode 100644 index 00000000..fad66a7d --- /dev/null +++ b/src/common/OrgHandlerUtils.ts @@ -0,0 +1,59 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { ExtensionContext } from 'vscode'; +import { ActiveOrgOutput } from '../client/pac/PacTypes'; +import { PacWrapper } from '../client/pac/PacWrapper'; +import { OrgDetails } from './chat-participants/powerpages/PowerPagesChatParticipantTypes'; +import { PAC_SUCCESS } from './copilot/constants'; +import { createAuthProfileExp } from './Utils'; + +export const ORG_DETAILS_KEY = 'orgDetails'; + +export function handleOrgChangeSuccess( + orgDetails: ActiveOrgOutput, + extensionContext: ExtensionContext +): { orgID: string, orgUrl: string } { + const orgID = orgDetails.OrgId; + const orgUrl = orgDetails.OrgUrl; + + extensionContext.globalState.update(ORG_DETAILS_KEY, { orgID, orgUrl }); + + //TODO: Handle AIB GEOs + + return { orgID, orgUrl }; +} + +export async function initializeOrgDetails( + isOrgDetailsInitialized: boolean, + extensionContext: ExtensionContext, + pacWrapper?: PacWrapper +): Promise<{ orgID?: string, orgUrl?: string }> { + if (isOrgDetailsInitialized) { + return {}; + } + + const orgDetails: OrgDetails | undefined = extensionContext.globalState.get(ORG_DETAILS_KEY); + let orgID: string | undefined; + let orgUrl: string | undefined; + + if (orgDetails && orgDetails.orgID && orgDetails.orgUrl) { + orgID = orgDetails.orgID; + orgUrl = orgDetails.orgUrl; + } else { + if (pacWrapper) { + const pacActiveOrg = await pacWrapper.activeOrg(); + if (pacActiveOrg && pacActiveOrg.Status === PAC_SUCCESS) { + const orgDetails = handleOrgChangeSuccess(pacActiveOrg.Results, extensionContext); + orgID = orgDetails.orgID; + orgUrl = orgDetails.orgUrl; + } else { + await createAuthProfileExp(pacWrapper); + } + } + } + + return { orgID, orgUrl }; +} diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 5372b60d..1251095b 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -92,7 +92,9 @@ export function showConnectedOrgMessage(environmentName: string, orgUrl: string) export async function showInputBoxAndGetOrgUrl() { return vscode.window.showInputBox({ placeHolder: vscode.l10n.t("Enter the environment URL"), - prompt: vscode.l10n.t("Active auth profile is not found or has expired. To create a new auth profile, enter the environment URL.") + prompt: vscode.l10n.t("Active auth profile is not found or has expired. To create a new auth profile, enter the environment URL."), + ignoreFocusOut: true, //Input box should not close on focus out + }); } diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index e7e6bae6..20159b15 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -8,35 +8,130 @@ import { createChatParticipant } from '../ChatParticipantUtils'; import { IPowerPagesChatResult } from './PowerPagesChatParticipantTypes'; import { ITelemetry } from '../../../client/telemetry/ITelemetry'; import TelemetryReporter from '@vscode/extension-telemetry'; - +import { sendApiRequest } from '../../copilot/IntelligenceApiService'; +import { PacWrapper } from '../../../client/pac/PacWrapper'; +import { intelligenceAPIAuthentication } from '../../AuthenticationProvider'; +import { ActiveOrgOutput } from '../../../client/pac/PacTypes'; +import { orgChangeErrorEvent, orgChangeEvent } from '../../OrgChangeNotifier'; +import { ORG_DETAILS_KEY, handleOrgChangeSuccess, initializeOrgDetails } from '../../OrgHandlerUtils'; +import { getEndpoint } from './PowerPagesChatParticipantUtils'; +import { AUTHENTICATION_FAILED_MSG, COPILOT_NOT_AVAILABLE_MSG, PAC_AUTH_NOT_FOUND, POWERPAGES_CHAT_PARTICIPANT_ID, RESPONSE_AWAITED_MSG } from './PowerPagesChatParticipantConstants'; export class PowerPagesChatParticipant { + private static instance: PowerPagesChatParticipant | null = null; private chatParticipant: vscode.ChatParticipant; private telemetry: ITelemetry; + private extensionContext: vscode.ExtensionContext; + private readonly _pacWrapper?: PacWrapper; + private isOrgDetailsInitialized = false; + private readonly _disposables: vscode.Disposable[] = []; + private cachedEndpoint: { intelligenceEndpoint: string, geoName: string } | null = null; + + private orgID: string | undefined; + private orgUrl: string | undefined; - constructor(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter,) { + private constructor(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter, pacWrapper?: PacWrapper) { - this.chatParticipant = createChatParticipant('powerpages', this.handler); + this.chatParticipant = createChatParticipant(POWERPAGES_CHAT_PARTICIPANT_ID, this.handler); //TODO: Check the icon image this.chatParticipant.iconPath = vscode.Uri.joinPath(context.extensionUri, 'src', 'common', 'chat-participants', 'powerpages', 'assets', 'copilot.png'); this.telemetry = telemetry; + + this.extensionContext = context; + + this._pacWrapper = pacWrapper; + + this._disposables.push(orgChangeEvent(async (orgDetails: ActiveOrgOutput) => { + await this.handleOrgChangeSuccess(orgDetails); + })); + + this._disposables.push(orgChangeErrorEvent(async () => { + this.extensionContext.globalState.update(ORG_DETAILS_KEY, { orgID: undefined, orgUrl: undefined}); + })); + + } + + public static getInstance(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter, pacWrapper?: PacWrapper) { + if (!PowerPagesChatParticipant.instance) { + PowerPagesChatParticipant.instance = new PowerPagesChatParticipant(context, telemetry, pacWrapper); + } + + return PowerPagesChatParticipant.instance; + } + + public dispose() { + this.chatParticipant.dispose(); } private handler: vscode.ChatRequestHandler = async ( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _request: vscode.ChatRequest, - // eslint-disable-next-line @typescript-eslint/no-unused-vars + request: vscode.ChatRequest, _context: vscode.ChatContext, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _stream: vscode.ChatResponseStream, + stream: vscode.ChatResponseStream, // eslint-disable-next-line @typescript-eslint/no-unused-vars _token: vscode.CancellationToken ): Promise => { // Handle chat requests here - // TODO: Handle authentication and org change + stream.progress(RESPONSE_AWAITED_MSG) + + await this.initializeOrgDetails(); + + if (!this.orgID) { + stream.markdown(PAC_AUTH_NOT_FOUND); + return { + metadata: { + command: '' + } + }; + } + + const intelligenceApiAuthResponse = await intelligenceAPIAuthentication(this.telemetry, '', this.orgID, true); + + if (!intelligenceApiAuthResponse) { + stream.markdown(AUTHENTICATION_FAILED_MSG); + + return { + metadata: { + command: '', + } + }; + } + + const intelligenceApiToken = intelligenceApiAuthResponse.accessToken; + + const { intelligenceEndpoint, geoName } = await getEndpoint(this.orgID, this.telemetry, this.cachedEndpoint); + + if (!intelligenceEndpoint || !geoName) { + stream.markdown(COPILOT_NOT_AVAILABLE_MSG) + + return { + metadata: { + command: '' + } + }; + } + + const userPrompt = request.prompt; + + if (!userPrompt) { + + //TODO: Show start message + + return { + metadata: { + command: '' + } + }; + } + + //TODO: Handle form and list scenarios + const llmResponse = await sendApiRequest([{ displayText: userPrompt, code: '' }], { dataverseEntity: '', entityField: '', fieldType: '' }, this.orgID, intelligenceApiToken, '', '', [], this.telemetry, intelligenceEndpoint, geoName); + + stream.markdown(llmResponse[0].displayText); + + stream.markdown('\n```typescript\n' + llmResponse[0].code + '\n```'); return { metadata: { @@ -46,4 +141,15 @@ export class PowerPagesChatParticipant { }; + private async initializeOrgDetails(): Promise { + const { orgID, orgUrl } = await initializeOrgDetails(this.isOrgDetailsInitialized, this.extensionContext, this._pacWrapper); + this.orgID = orgID; + this.orgUrl = orgUrl; + } + + private async handleOrgChangeSuccess(orgDetails: ActiveOrgOutput): Promise { + const { orgID, orgUrl } = handleOrgChangeSuccess(orgDetails, this.extensionContext); + this.orgID = orgID; + this.orgUrl = orgUrl; + } } diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantConstants.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantConstants.ts new file mode 100644 index 00000000..ad0c29c7 --- /dev/null +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantConstants.ts @@ -0,0 +1,10 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +export const POWERPAGES_CHAT_PARTICIPANT_ID = 'powerpages'; +export const RESPONSE_AWAITED_MSG = 'Working on it...' +export const AUTHENTICATION_FAILED_MSG = 'Authentication failed. Please try again.'; +export const COPILOT_NOT_AVAILABLE_MSG = 'Copilot is not available. Please contact your administrator.'; +export const PAC_AUTH_NOT_FOUND = 'Active auth profile is not found or has expired. Please try again.'; diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantTypes.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantTypes.ts index 52a0d7f8..fb537bc4 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipantTypes.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantTypes.ts @@ -10,3 +10,7 @@ export interface IPowerPagesChatResult extends vscode.ChatResult { command: string; } } +export interface OrgDetails { + orgID: string; + orgUrl: string; +} diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts new file mode 100644 index 00000000..4ec19dbb --- /dev/null +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts @@ -0,0 +1,18 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { ITelemetry } from "../../../client/telemetry/ITelemetry"; +import { getIntelligenceEndpoint } from "../../ArtemisService"; + +export async function getEndpoint( + orgID: string, + telemetry: ITelemetry, + cachedEndpoint: { intelligenceEndpoint: string; geoName: string } | null +): Promise<{ intelligenceEndpoint: string; geoName: string }> { + if (!cachedEndpoint) { + cachedEndpoint = await getIntelligenceEndpoint(orgID, telemetry, '') as { intelligenceEndpoint: string; geoName: string }; + } + return cachedEndpoint; +} diff --git a/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts b/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts index 0955b1d5..2b710033 100644 --- a/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts +++ b/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts @@ -5,449 +5,450 @@ declare module 'vscode' { - /** - * Represents a user request in chat history. - */ - export class ChatRequestTurn { - /** - * The prompt as entered by the user. - * - * Information about variables used in this request is stored in {@link ChatRequestTurn.variables}. - * - * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} - * are not part of the prompt. - */ - readonly prompt: string; - - /** - * The id of the chat participant to which this request was directed. - */ - readonly participant: string; - - /** - * The name of the {@link ChatCommand command} that was selected for this request. - */ - 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. - */ - readonly variables: ChatResolvedVariable[]; - - private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: string); - } - - /** - * Represents a chat participant's response in chat history. - */ - export class ChatResponseTurn { - /** - * The content that was received from the chat participant. Only the stream parts that represent actual content (not metadata) are represented. - */ - readonly response: ReadonlyArray; - - /** - * The result that was received from the chat participant. - */ - readonly result: ChatResult; - - /** - * The id of the chat participant that this response came from. - */ - readonly participant: string; - - /** - * The name of the command that this response came from. - */ - readonly command?: string; - - private constructor(response: ReadonlyArray, result: ChatResult, participant: string); - } - - export interface ChatContext { - /** - * All of the chat messages so far in the current chat session. - */ - readonly history: ReadonlyArray; - } - - /** - * Represents an error result from a chat request. - */ - export interface ChatErrorDetails { - /** - * An error message that is shown to the user. - */ - 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. - */ - responseIsFiltered?: boolean; - } - - /** - * The result of a chat request. - */ - export interface ChatResult { - /** - * If the request resulted in an error, this property defines the error details. - */ - errorDetails?: ChatErrorDetails; - - /** - * Arbitrary metadata for this result. Can be anything, but must be JSON-stringifyable. - */ - readonly metadata?: { readonly [key: string]: unknown }; - } - - /** - * Represents the type of user feedback received. - */ - export enum ChatResultFeedbackKind { - /** - * The user marked the result as helpful. - */ - Unhelpful = 0, - - /** - * The user marked the result as unhelpful. - */ - Helpful = 1, - } - - /** - * Represents user feedback for a result. - */ - export interface ChatResultFeedback { - /** - * The ChatResult for which the user is providing feedback. - * This object has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. - */ - readonly result: ChatResult; - - /** - * The kind of feedback that was received. - */ - readonly kind: ChatResultFeedbackKind; - } - - /** - * A followup question suggested by the participant. - */ - export interface ChatFollowup { - /** - * The message to send to the chat. - */ - prompt: string; - - /** - * A title to show the user. The prompt will be shown by default, when this is unspecified. - */ - label?: string; - - /** - * By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant by ID. - * Followups can only invoke a participant that was contributed by the same extension. - */ - participant?: string; - - /** - * By default, the followup goes to the same participant/command. But this property can be set to invoke a different command. - */ - command?: string; - } - - /** - * 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 ChatFollowupProvider { - /** - * Provide followups for the given result. - * @param result This object has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. - * @param token A cancellation token. - */ - provideFollowups(result: ChatResult, context: ChatContext, token: CancellationToken): ProviderResult; - } - - /** - * A chat request handler is a callback that will be invoked when a request is made to a chat participant. - */ - export type ChatRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult; - - /** - * A chat participant can be invoked by the user in a chat session, using the `@` prefix. When it is invoked, it handles the chat request and is solely - * responsible for providing a response to the user. A ChatParticipant is created using {@link chat.createChatParticipant}. - */ - export interface ChatParticipant { - /** - * A unique ID for this participant. - */ - readonly id: string; - - /** - * An icon for the participant shown in UI. - */ - iconPath?: Uri | { - /** - * The icon path for the light theme. - */ - light: Uri; - /** - * The icon path for the dark theme. - */ - dark: Uri; - } | ThemeIcon; - - /** - * The handler for requests to this participant. - */ - requestHandler: ChatRequestHandler; - - /** - * This provider will be called once after each request to retrieve suggested followup questions. - */ - followupProvider?: ChatFollowupProvider; - - /** - * An event that fires whenever feedback for a result is received, e.g. when a user up- or down-votes - * a result. - * - * The passed {@link ChatResultFeedback.result result} is guaranteed to be the same instance that was - * previously returned from this chat participant. - */ - onDidReceiveFeedback: Event; - - /** - * 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 { - /** - * 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 ChatRequest.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?` - /** - * The values of the variable. Can be an empty array if the variable doesn't currently have a value. - */ - readonly values: ChatVariableValue[]; - } - - export interface ChatRequest { - /** - * The prompt as entered by the user. - * - * Information about variables used in this request is stored in {@link ChatRequest.variables}. - * - * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} - * are not part of the prompt. - */ - readonly prompt: string; - - /** - * The name of the {@link ChatCommand command} that was selected for this request. - */ - 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 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 - * string-manipulation of the prompt. - */ - readonly variables: readonly ChatResolvedVariable[]; - } - - /** - * The ChatResponseStream is how a participant is able to return content to the chat view. It provides several methods for streaming different types of content - * which will be rendered in an appropriate way in the chat view. A participant can use the helper method for the type of content it wants to return, or it - * can instantiate a {@link ChatResponsePart} and use the generic {@link ChatResponseStream.push} method to return it. - */ - export interface ChatResponseStream { - /** - * Push a markdown part to this stream. Short-hand for - * `push(new ChatResponseMarkdownPart(value))`. - * - * @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; - - /** - * 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. - */ - anchor(value: Uri | Location, title?: string): ChatResponseStream; - - /** - * 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; - - /** - * 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. - */ - filetree(value: ChatResponseFileTree[], baseUri: Uri): ChatResponseStream; - - /** - * 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; - - /** - * Push a reference to this stream. Short-hand for - * `push(new ChatResponseReferencePart(value))`. - * - * *Note* that the reference is not rendered inline with the response. - * - * @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; - - /** - * Pushes a part to this stream. - * - * @param part A response part, rendered or metadata - */ - push(part: ChatResponsePart): ChatResponseStream; - } - - export class ChatResponseMarkdownPart { - value: MarkdownString; - - /** - * @param value Note: The boolean form of {@link MarkdownString.isTrusted} is NOT supported. - */ - constructor(value: string | MarkdownString); - } - - export interface ChatResponseFileTree { - name: string; - children?: ChatResponseFileTree[]; - } - - export class ChatResponseFileTreePart { - value: ChatResponseFileTree[]; - baseUri: Uri; - constructor(value: ChatResponseFileTree[], baseUri: Uri); - } - - export class ChatResponseAnchorPart { - value: Uri | Location | SymbolInformation; - title?: string; - constructor(value: Uri | Location | SymbolInformation, title?: string); - } - - export class ChatResponseProgressPart { - value: string; - constructor(value: string); - } - - export class ChatResponseReferencePart { - value: Uri | Location | { variableName: string; value?: Uri | Location }; - iconPath?: ThemeIcon; - constructor(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: ThemeIcon); - } - - export class ChatResponseCommandButtonPart { - value: Command; - constructor(value: Command); - } - - /** - * Represents the different chat response types. - */ - export type ChatResponsePart = ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart - | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; - - - export namespace chat { - /** - * Create a new {@link ChatParticipant chat participant} instance. - * - * @param id A unique identifier for the participant. - * @param handler A request handler for the participant. - * @returns A new chat participant - */ - 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; - } + /** + * Represents a user request in chat history. + */ + export class ChatRequestTurn { + /** + * The prompt as entered by the user. + * + * Information about variables used in this request is stored in {@link ChatRequestTurn.variables}. + * + * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} + * are not part of the prompt. + */ + readonly prompt: string; + + /** + * The id of the chat participant to which this request was directed. + */ + readonly participant: string; + + /** + * The name of the {@link ChatCommand command} that was selected for this request. + */ + 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. + */ + readonly variables: ChatResolvedVariable[]; + + private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: string); + } + + /** + * Represents a chat participant's response in chat history. + */ + export class ChatResponseTurn { + /** + * The content that was received from the chat participant. Only the stream parts that represent actual content (not metadata) are represented. + */ + readonly response: ReadonlyArray; + + /** + * The result that was received from the chat participant. + */ + readonly result: ChatResult; + + /** + * The id of the chat participant that this response came from. + */ + readonly participant: string; + + /** + * The name of the command that this response came from. + */ + readonly command?: string; + + private constructor(response: ReadonlyArray, result: ChatResult, participant: string); + } + + export interface ChatContext { + /** + * All of the chat messages so far in the current chat session. + */ + readonly history: ReadonlyArray; + } + + /** + * Represents an error result from a chat request. + */ + export interface ChatErrorDetails { + /** + * An error message that is shown to the user. + */ + 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. + */ + responseIsFiltered?: boolean; + } + + /** + * The result of a chat request. + */ + export interface ChatResult { + /** + * If the request resulted in an error, this property defines the error details. + */ + errorDetails?: ChatErrorDetails; + + /** + * Arbitrary metadata for this result. Can be anything, but must be JSON-stringifyable. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + readonly metadata?: { readonly [key: string]: any }; + } + + /** + * Represents the type of user feedback received. + */ + export enum ChatResultFeedbackKind { + /** + * The user marked the result as helpful. + */ + Unhelpful = 0, + + /** + * The user marked the result as unhelpful. + */ + Helpful = 1, + } + + /** + * Represents user feedback for a result. + */ + export interface ChatResultFeedback { + /** + * The ChatResult for which the user is providing feedback. + * This object has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. + */ + readonly result: ChatResult; + + /** + * The kind of feedback that was received. + */ + readonly kind: ChatResultFeedbackKind; + } + + /** + * A followup question suggested by the participant. + */ + export interface ChatFollowup { + /** + * The message to send to the chat. + */ + prompt: string; + + /** + * A title to show the user. The prompt will be shown by default, when this is unspecified. + */ + label?: string; + + /** + * By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant by ID. + * Followups can only invoke a participant that was contributed by the same extension. + */ + participant?: string; + + /** + * By default, the followup goes to the same participant/command. But this property can be set to invoke a different command. + */ + command?: string; + } + + /** + * 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 ChatFollowupProvider { + /** + * Provide followups for the given result. + * @param result This object has the same properties as the result returned from the participant callback, including `metadata`, but is not the same instance. + * @param token A cancellation token. + */ + provideFollowups(result: ChatResult, context: ChatContext, token: CancellationToken): ProviderResult; + } + + /** + * A chat request handler is a callback that will be invoked when a request is made to a chat participant. + */ + export type ChatRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult; + + /** + * A chat participant can be invoked by the user in a chat session, using the `@` prefix. When it is invoked, it handles the chat request and is solely + * responsible for providing a response to the user. A ChatParticipant is created using {@link chat.createChatParticipant}. + */ + export interface ChatParticipant { + /** + * A unique ID for this participant. + */ + readonly id: string; + + /** + * An icon for the participant shown in UI. + */ + iconPath?: Uri | { + /** + * The icon path for the light theme. + */ + light: Uri; + /** + * The icon path for the dark theme. + */ + dark: Uri; + } | ThemeIcon; + + /** + * The handler for requests to this participant. + */ + requestHandler: ChatRequestHandler; + + /** + * This provider will be called once after each request to retrieve suggested followup questions. + */ + followupProvider?: ChatFollowupProvider; + + /** + * An event that fires whenever feedback for a result is received, e.g. when a user up- or down-votes + * a result. + * + * The passed {@link ChatResultFeedback.result result} is guaranteed to be the same instance that was + * previously returned from this chat participant. + */ + onDidReceiveFeedback: Event; + + /** + * 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 { + /** + * 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 ChatRequest.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?` + /** + * The values of the variable. Can be an empty array if the variable doesn't currently have a value. + */ + readonly values: ChatVariableValue[]; + } + + export interface ChatRequest { + /** + * The prompt as entered by the user. + * + * Information about variables used in this request is stored in {@link ChatRequest.variables}. + * + * *Note* that the {@link ChatParticipant.name name} of the participant and the {@link ChatCommand.name command} + * are not part of the prompt. + */ + readonly prompt: string; + + /** + * The name of the {@link ChatCommand command} that was selected for this request. + */ + 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 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 + * string-manipulation of the prompt. + */ + readonly variables: readonly ChatResolvedVariable[]; + } + + /** + * The ChatResponseStream is how a participant is able to return content to the chat view. It provides several methods for streaming different types of content + * which will be rendered in an appropriate way in the chat view. A participant can use the helper method for the type of content it wants to return, or it + * can instantiate a {@link ChatResponsePart} and use the generic {@link ChatResponseStream.push} method to return it. + */ + export interface ChatResponseStream { + /** + * Push a markdown part to this stream. Short-hand for + * `push(new ChatResponseMarkdownPart(value))`. + * + * @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; + + /** + * 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. + */ + anchor(value: Uri | Location, title?: string): ChatResponseStream; + + /** + * 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; + + /** + * 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. + */ + filetree(value: ChatResponseFileTree[], baseUri: Uri): ChatResponseStream; + + /** + * 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; + + /** + * Push a reference to this stream. Short-hand for + * `push(new ChatResponseReferencePart(value))`. + * + * *Note* that the reference is not rendered inline with the response. + * + * @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; + + /** + * Pushes a part to this stream. + * + * @param part A response part, rendered or metadata + */ + push(part: ChatResponsePart): ChatResponseStream; + } + + export class ChatResponseMarkdownPart { + value: MarkdownString; + + /** + * @param value Note: The boolean form of {@link MarkdownString.isTrusted} is NOT supported. + */ + constructor(value: string | MarkdownString); + } + + export interface ChatResponseFileTree { + name: string; + children?: ChatResponseFileTree[]; + } + + export class ChatResponseFileTreePart { + value: ChatResponseFileTree[]; + baseUri: Uri; + constructor(value: ChatResponseFileTree[], baseUri: Uri); + } + + export class ChatResponseAnchorPart { + value: Uri | Location | SymbolInformation; + title?: string; + constructor(value: Uri | Location | SymbolInformation, title?: string); + } + + export class ChatResponseProgressPart { + value: string; + constructor(value: string); + } + + export class ChatResponseReferencePart { + value: Uri | Location | { variableName: string; value?: Uri | Location }; + iconPath?: ThemeIcon; + constructor(value: Uri | Location | { variableName: string; value?: Uri | Location }, iconPath?: ThemeIcon); + } + + export class ChatResponseCommandButtonPart { + value: Command; + constructor(value: Command); + } + + /** + * Represents the different chat response types. + */ + export type ChatResponsePart = ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart + | ChatResponseProgressPart | ChatResponseReferencePart | ChatResponseCommandButtonPart; + + + export namespace chat { + /** + * Create a new {@link ChatParticipant chat participant} instance. + * + * @param id A unique identifier for the participant. + * @param handler A request handler for the participant. + * @returns A new chat participant + */ + 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/src/common/chat-participants/vscode.proposed.languageModels.d.ts b/src/common/chat-participants/vscode.proposed.languageModels.d.ts index 6797108a..ab8bb90d 100644 --- a/src/common/chat-participants/vscode.proposed.languageModels.d.ts +++ b/src/common/chat-participants/vscode.proposed.languageModels.d.ts @@ -7,287 +7,288 @@ declare module 'vscode' { - /** - * Represents a language model response. - * - * @see {@link LanguageModelAccess.chatRequest} - */ - export interface LanguageModelChatResponse { - - /** - * 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. - */ - stream: AsyncIterable; - } - - /** - * 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 LanguageModelChatSystemMessage { - - /** - * 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 LanguageModelChatUserMessage { - - /** - * 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 LanguageModelChatAssistantMessage { - - /** - * The content of this message. - */ - content: string; - - /** - * The optional name of a user for this message. - */ - name: string | undefined; - - /** - * Create a new assistant message. - * - * @param content The content of the message. - * @param name The optional name of a user for the message. - */ - constructor(content: string, name?: string); - } - - /** - * Different types of language model messages. - */ - 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; - - /** - * The human-readable name of the language model. - */ - readonly name: string; - - /** - * The version of the language model. - */ - readonly version: string; - - /** - * The number of available tokens that can be used when sending requests - * to the language model. - * - * @see {@link lm.sendChatRequest} - */ - readonly tokens: number; - } - - /** - * An event describing the change in the set of available language models. - */ - // TODO@API use LanguageModelInformation instead of string? - export interface LanguageModelChangeEvent { - /** - * Added language models. - */ - readonly added: readonly string[]; - /** - * Removed language models. - */ - readonly removed: readonly string[]; - } - - /** - * An error type for language model specific errors. - * - * Consumers of language models should check the code property to determine specific - * failure causes, like `if(someError.code === vscode.LanguageModelError.NotFound.name) {...}` - * for the case of referring to an unknown language model. For unspecified errors the `cause`-property - * will contain the actual error. - */ - 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; - - /** - * A code that identifies this error. - * - * Possible values are names of errors, like {@linkcode LanguageModelError.NotFound NotFound}, - * or `Unknown` for unspecified errors from the language model itself. In the latter case the - * `cause`-property will contain the actual error. - */ - readonly code: string; - } - - /** - * Options for making a chat request using a language model. - * - * @see {@link lm.chatRequest} - */ - export interface LanguageModelChatRequestOptions { - - /** - * A human-readable message that explains why access to a language model is needed and what feature is enabled by it. - */ - 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. - */ - modelOptions?: { [name: string]: unknown }; - } - - /** - * Namespace for language model related functionality. - */ - export namespace lm { - - /** - * The identifiers of all language models that are currently available. - */ - export const languageModels: readonly string[]; - - /** - * An event that is fired when the set of available language models changes. - */ - 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; - } - - /** - * Represents extension specific information about the access to language models. - */ - export interface LanguageModelAccessInformation { - - /** - * An event that fires when access information changes. - */ - onDidChange: Event; - - /** - * 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. - * - * @param languageModelId A language model identifier. - * @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; - } - - export interface ExtensionContext { - - /** - * An object that keeps information about how this extension can use language models. - * - * @see {@link lm.sendChatRequest} - */ - readonly languageModelAccessInformation: LanguageModelAccessInformation; - } + /** + * Represents a language model response. + * + * @see {@link LanguageModelAccess.chatRequest} + */ + export interface LanguageModelChatResponse { + + /** + * 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. + */ + stream: AsyncIterable; + } + + /** + * 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 LanguageModelChatSystemMessage { + + /** + * 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 LanguageModelChatUserMessage { + + /** + * 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 LanguageModelChatAssistantMessage { + + /** + * The content of this message. + */ + content: string; + + /** + * The optional name of a user for this message. + */ + name: string | undefined; + + /** + * Create a new assistant message. + * + * @param content The content of the message. + * @param name The optional name of a user for the message. + */ + constructor(content: string, name?: string); + } + + /** + * Different types of language model messages. + */ + 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; + + /** + * The human-readable name of the language model. + */ + readonly name: string; + + /** + * The version of the language model. + */ + readonly version: string; + + /** + * The number of available tokens that can be used when sending requests + * to the language model. + * + * @see {@link lm.sendChatRequest} + */ + readonly tokens: number; + } + + /** + * An event describing the change in the set of available language models. + */ + // TODO@API use LanguageModelInformation instead of string? + export interface LanguageModelChangeEvent { + /** + * Added language models. + */ + readonly added: readonly string[]; + /** + * Removed language models. + */ + readonly removed: readonly string[]; + } + + /** + * An error type for language model specific errors. + * + * Consumers of language models should check the code property to determine specific + * failure causes, like `if(someError.code === vscode.LanguageModelError.NotFound.name) {...}` + * for the case of referring to an unknown language model. For unspecified errors the `cause`-property + * will contain the actual error. + */ + 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; + + /** + * A code that identifies this error. + * + * Possible values are names of errors, like {@linkcode LanguageModelError.NotFound NotFound}, + * or `Unknown` for unspecified errors from the language model itself. In the latter case the + * `cause`-property will contain the actual error. + */ + readonly code: string; + } + + /** + * Options for making a chat request using a language model. + * + * @see {@link lm.chatRequest} + */ + export interface LanguageModelChatRequestOptions { + + /** + * A human-readable message that explains why access to a language model is needed and what feature is enabled by it. + */ + 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. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + modelOptions?: { [name: string]: any }; + } + + /** + * Namespace for language model related functionality. + */ + export namespace lm { + + /** + * The identifiers of all language models that are currently available. + */ + export const languageModels: readonly string[]; + + /** + * An event that is fired when the set of available language models changes. + */ + 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; + } + + /** + * Represents extension specific information about the access to language models. + */ + export interface LanguageModelAccessInformation { + + /** + * An event that fires when access information changes. + */ + onDidChange: Event; + + /** + * 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. + * + * @param languageModelId A language model identifier. + * @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; + } + + export interface ExtensionContext { + + /** + * An object that keeps information about how this extension can use language models. + * + * @see {@link lm.sendChatRequest} + */ + readonly languageModelAccessInformation: LanguageModelAccessInformation; + } }