From 7944c2d537cda3d9df3282067379b4d1be0a2498 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Wed, 1 May 2024 16:28:14 +0530 Subject: [PATCH 01/10] chore: Update package.json with extension dependencies and enabled API proposals for chat participants --- package.json | 18 +- .../vscode.proposed.chatParticipant.d.ts | 455 ++++++++++++++++++ .../vscode.proposed.chatVariableResolver.d.ts | 58 +++ .../vscode.proposed.languageModels.d.ts | 293 +++++++++++ 4 files changed, 823 insertions(+), 1 deletion(-) create mode 100644 src/common/chat-participants/vscode.proposed.chatParticipant.d.ts create mode 100644 src/common/chat-participants/vscode.proposed.chatVariableResolver.d.ts create mode 100644 src/common/chat-participants/vscode.proposed.languageModels.d.ts diff --git a/package.json b/package.json index 2d287df8..eb806df2 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,14 @@ "onDebug", "onFileSystem:powerplatform-vfs" ], + "extensionDependencies": [ + "github.copilot-chat" + ], + "enabledApiProposals": [ + "chatParticipant", + "chatVariableResolver", + "languageModels" + ], "capabilities": { "untrustedWorkspaces": { "supported": "limited", @@ -112,6 +120,14 @@ } }, "contributes": { + "chatParticipants": [ + { + "id": "powerpages", + "name": "powerpages", + "description": "Power Pages Copilot", + "isSticky": true + } + ], "problemMatchers": [ { "name": "pcf-scripts-build", @@ -1124,4 +1140,4 @@ "bufferutil": "^4.0.6", "utf-8-validate": "^5.0.9" } -} \ No newline at end of file +} diff --git a/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts b/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts new file mode 100644 index 00000000..56586206 --- /dev/null +++ b/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts @@ -0,0 +1,455 @@ +/* + * 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' { + + /** + * 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]: 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.chatVariableResolver.d.ts b/src/common/chat-participants/vscode.proposed.chatVariableResolver.d.ts new file mode 100644 index 00000000..94a415c9 --- /dev/null +++ b/src/common/chat-participants/vscode.proposed.chatVariableResolver.d.ts @@ -0,0 +1,58 @@ +/* + * 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; + } + + // TODO@API align with ChatRequest + export interface ChatVariableContext { + /** + * The message entered by the user, which includes this variable. + */ + // TODO@API AS-IS, variables as types, agent/commands stripped + prompt: string; + + // readonly variables: readonly ChatResolvedVariable[]; + } + + 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/src/common/chat-participants/vscode.proposed.languageModels.d.ts b/src/common/chat-participants/vscode.proposed.languageModels.d.ts new file mode 100644 index 00000000..8d4b2885 --- /dev/null +++ b/src/common/chat-participants/vscode.proposed.languageModels.d.ts @@ -0,0 +1,293 @@ +/* + * 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' { + + /** + * 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]: 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; + } +} From b49bccc2ee0ace2425d22c2a332c842ada10d369 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Wed, 1 May 2024 18:17:33 +0530 Subject: [PATCH 02/10] feat: Add PowerPagesChatParticipant for chat functionality --- src/client/extension.ts | 4 ++ .../chat-participants/ChatParticipantUtils.ts | 10 +++ .../powerpages/PowerPagesChatParticipant.ts | 61 +++++++++++++++++++ .../PowerPagesChatParticipantTypes.ts | 12 ++++ .../powerpages/assets/copilot.png | 3 + 5 files changed, 90 insertions(+) create mode 100644 src/common/chat-participants/ChatParticipantUtils.ts create mode 100644 src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts create mode 100644 src/common/chat-participants/powerpages/PowerPagesChatParticipantTypes.ts create mode 100644 src/common/chat-participants/powerpages/assets/copilot.png diff --git a/src/client/extension.ts b/src/client/extension.ts index acd0745c..56cc1a77 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -40,6 +40,7 @@ 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; @@ -177,6 +178,9 @@ 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(); diff --git a/src/common/chat-participants/ChatParticipantUtils.ts b/src/common/chat-participants/ChatParticipantUtils.ts new file mode 100644 index 00000000..6b75cbcf --- /dev/null +++ b/src/common/chat-participants/ChatParticipantUtils.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. + */ + +import * as vscode from 'vscode'; + +export function createChatParticipant(participantId: string, handler: vscode.ChatRequestHandler): vscode.ChatParticipant { + return vscode.chat.createChatParticipant(participantId, handler); +} diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts new file mode 100644 index 00000000..0be0788a --- /dev/null +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import * as vscode from 'vscode'; +import { createChatParticipant } from '../ChatParticipantUtils'; +import { IPowerPagesChatResult } from './PowerPagesChatParticipantTypes'; +import { intelligenceAPIAuthentication } from '../../../web/client/common/authenticationProvider'; +import { ITelemetry } from '../../../client/telemetry/ITelemetry'; +import TelemetryReporter from '@vscode/extension-telemetry'; + + +export class PowerPagesChatParticipant { + private chatParticipant: vscode.ChatParticipant; + private telemetry: ITelemetry; + + constructor(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter,) { + console.log("PowerPagesChatParticipant constructor"); + + this.chatParticipant = createChatParticipant('powerpages', this.handler); + + this.chatParticipant.iconPath = vscode.Uri.joinPath(context.extensionUri, 'src', 'common', 'chat-participants', 'powerpages', 'assets', 'copilot.png'); + + this.telemetry = telemetry; + + } + + private handler: vscode.ChatRequestHandler = async ( + request: vscode.ChatRequest, + context: vscode.ChatContext, + stream: vscode.ChatResponseStream, + token: vscode.CancellationToken + ): Promise => { + // Handle chat requests here + + const aibAuth = await intelligenceAPIAuthentication(this.telemetry, 'sessionID', '9ba620dc-4b37-430e-b779-2f9a7e7a52a6', true) + + if(aibAuth.accessToken) { + + console.log(request.command); + console.log(context); + stream.markdown('Hello from PowerPagesChatParticipant'); + console.log(token); + + console.log("PowerPagesChatParticipant handler"); + + } else { + // User is not signed in + stream.markdown("Please sign in to use Power Pages") + } + + return { + metadata: { + command: 'powerpages' + } + }; + + }; + +} diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantTypes.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantTypes.ts new file mode 100644 index 00000000..52a0d7f8 --- /dev/null +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantTypes.ts @@ -0,0 +1,12 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import * as vscode from 'vscode'; + +export interface IPowerPagesChatResult extends vscode.ChatResult { + metadata: { + command: string; + } +} diff --git a/src/common/chat-participants/powerpages/assets/copilot.png b/src/common/chat-participants/powerpages/assets/copilot.png new file mode 100644 index 00000000..7227475e --- /dev/null +++ b/src/common/chat-participants/powerpages/assets/copilot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04c5ac68d0e81d4fc7c4d8b6064732ce9499748390ab1982a2c4fd6d8baed764 +size 905 From 989334bc25dcc42e7b631c798188e898695ddd92 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Thu, 2 May 2024 17:17:52 +0530 Subject: [PATCH 03/10] feat: Add logic to handle chat requests in PowerPagesChatParticipant --- .../powerpages/PowerPagesChatParticipant.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index 0be0788a..78fd0608 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -34,16 +34,18 @@ export class PowerPagesChatParticipant { ): Promise => { // Handle chat requests here + // TODO: Remove hardcoded values const aibAuth = await intelligenceAPIAuthentication(this.telemetry, 'sessionID', '9ba620dc-4b37-430e-b779-2f9a7e7a52a6', true) if(aibAuth.accessToken) { - - console.log(request.command); - console.log(context); - stream.markdown('Hello from PowerPagesChatParticipant'); - console.log(token); - console.log("PowerPagesChatParticipant handler"); + // User is signed in + stream.markdown("Welcome to Power Pages! 🚀") + + //TODO: Add logic to handle the request + console.log(request) + console.log(context) + console.log(token) } else { // User is not signed in From ff8886cc3fdd9398307d029d84816e728f1afffe Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 3 May 2024 16:00:38 +0530 Subject: [PATCH 04/10] feat: Removed logs --- .../powerpages/PowerPagesChatParticipant.ts | 30 ++++--------------- .../vscode.proposed.chatParticipant.d.ts | 2 -- .../vscode.proposed.chatVariableResolver.d.ts | 2 -- 3 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index 78fd0608..654add4e 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -6,7 +6,6 @@ import * as vscode from 'vscode'; import { createChatParticipant } from '../ChatParticipantUtils'; import { IPowerPagesChatResult } from './PowerPagesChatParticipantTypes'; -import { intelligenceAPIAuthentication } from '../../../web/client/common/authenticationProvider'; import { ITelemetry } from '../../../client/telemetry/ITelemetry'; import TelemetryReporter from '@vscode/extension-telemetry'; @@ -16,7 +15,6 @@ export class PowerPagesChatParticipant { private telemetry: ITelemetry; constructor(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter,) { - console.log("PowerPagesChatParticipant constructor"); this.chatParticipant = createChatParticipant('powerpages', this.handler); @@ -27,34 +25,18 @@ export class PowerPagesChatParticipant { } private handler: vscode.ChatRequestHandler = async ( - request: vscode.ChatRequest, - context: vscode.ChatContext, - stream: vscode.ChatResponseStream, - token: vscode.CancellationToken + _request: vscode.ChatRequest, + _context: vscode.ChatContext, + _stream: vscode.ChatResponseStream, + _token: vscode.CancellationToken ): Promise => { // Handle chat requests here - // TODO: Remove hardcoded values - const aibAuth = await intelligenceAPIAuthentication(this.telemetry, 'sessionID', '9ba620dc-4b37-430e-b779-2f9a7e7a52a6', true) - - if(aibAuth.accessToken) { - - // User is signed in - stream.markdown("Welcome to Power Pages! 🚀") - - //TODO: Add logic to handle the request - console.log(request) - console.log(context) - console.log(token) - - } else { - // User is not signed in - stream.markdown("Please sign in to use Power Pages") - } + // TODO: Handle authentication and org change return { metadata: { - command: 'powerpages' + command: '' } }; diff --git a/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts b/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts index 56586206..bb278a7b 100644 --- a/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts +++ b/src/common/chat-participants/vscode.proposed.chatParticipant.d.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ - - declare module 'vscode' { /** diff --git a/src/common/chat-participants/vscode.proposed.chatVariableResolver.d.ts b/src/common/chat-participants/vscode.proposed.chatVariableResolver.d.ts index 94a415c9..3ffdf9c1 100644 --- a/src/common/chat-participants/vscode.proposed.chatVariableResolver.d.ts +++ b/src/common/chat-participants/vscode.proposed.chatVariableResolver.d.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ - - declare module 'vscode' { export namespace chat { From 945d5c13390455088ff569ce7b46d781d8c31e24 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 3 May 2024 16:01:56 +0530 Subject: [PATCH 05/10] TODO --- .../chat-participants/powerpages/PowerPagesChatParticipant.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index 654add4e..943c67f5 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -18,6 +18,7 @@ export class PowerPagesChatParticipant { this.chatParticipant = createChatParticipant('powerpages', 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; From 5357ac8e6910acf2b74540382c5fb76878ae87c7 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 3 May 2024 17:29:00 +0530 Subject: [PATCH 06/10] feat: Add logic to handle chat requests in PowerPagesChatParticipant(correct response format) --- src/client/extension.ts | 9 ++- src/common/OrgChangeNotifier.ts | 14 +++- .../powerpages/PowerPagesChatParticipant.ts | 76 ++++++++++++++++++- 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 56cc1a77..16bc471c 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -99,6 +99,13 @@ export async function activate( vscode.workspace.onDidOpenTextDocument(didOpenTextDocument); vscode.workspace.textDocuments.forEach(didOpenTextDocument); + /** + * Required for calling Power Intelligence Service + */ + if(_context.globalState.get('orgID')) { + _context.globalState.update('orgID', ''); //TODO: Check for current active org + } + // portal web view panel _context.subscriptions.push( vscode.commands.registerCommand( @@ -219,7 +226,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/common/OrgChangeNotifier.ts b/src/common/OrgChangeNotifier.ts index 130b23b7..5f09a7fc 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; } @@ -48,9 +51,12 @@ export class OrgChangeNotifier { const pacActiveOrg = await this._pacWrapper?.activeOrg(); if (pacActiveOrg && pacActiveOrg.Status === PAC_SUCCESS) { this._orgDetails = pacActiveOrg.Results; + + await this.extensionContext.globalState.update('orgID', this._orgDetails.OrgId); + orgChangeEventEmitter.fire(this._orgDetails); } else { orgChangeErrorEventEmitter.fire(); } } -} \ No newline at end of file +} diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index 943c67f5..576b6d17 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -8,11 +8,15 @@ import { createChatParticipant } from '../ChatParticipantUtils'; import { IPowerPagesChatResult } from './PowerPagesChatParticipantTypes'; import { ITelemetry } from '../../../client/telemetry/ITelemetry'; import TelemetryReporter from '@vscode/extension-telemetry'; +import { getIntelligenceEndpoint } from '../../ArtemisService'; +import { intelligenceAPIAuthentication } from '../../../web/client/common/authenticationProvider'; +import { sendApiRequest } from '../../copilot/IntelligenceApiService'; export class PowerPagesChatParticipant { private chatParticipant: vscode.ChatParticipant; private telemetry: ITelemetry; + private extensionContext: vscode.ExtensionContext; constructor(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter,) { @@ -23,18 +27,86 @@ export class PowerPagesChatParticipant { this.telemetry = telemetry; + this.extensionContext = context; } private handler: vscode.ChatRequestHandler = async ( - _request: vscode.ChatRequest, + request: vscode.ChatRequest, _context: vscode.ChatContext, - _stream: vscode.ChatResponseStream, + stream: vscode.ChatResponseStream, _token: vscode.CancellationToken ): Promise => { // Handle chat requests here + stream.progress('Working on it...') + + let orgID = this.extensionContext.globalState.get('orgID', ''); + + orgID = 'eff535da-5f08-ef11-9f88-00224820c64c'; + + if (!orgID) { + // TODO: Auth Create Experience + + + return { + metadata: { + command: '' + } + }; + } + + const intelligenceApiAuthResponse = await intelligenceAPIAuthentication(this.telemetry, '', orgID, true); + + if (!intelligenceApiAuthResponse) { + + //TODO: Handle auth error and provide a way to re-authenticate + + return { + metadata: { + command: '' + } + }; + } + + const intelligenceApiToken = intelligenceApiAuthResponse.accessToken; + + const { intelligenceEndpoint, geoName } = await getIntelligenceEndpoint(orgID, this.telemetry, ''); //TODO: Optimize to avoid multiple calls to get intelligence endpoint + + if (!intelligenceEndpoint || !geoName) { + //TODO: Handle error + + return { + metadata: { + command: '' + } + }; + } + + const userPrompt = request.prompt; + + //TODO: Handle form and list scenarios + + if (!userPrompt) { + + //TODO: Show start message + + return { + metadata: { + command: '' + } + }; + } + + // export async function sendApiRequest(userPrompt: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, apiToken: string, sessionID: string, entityName: string, entityColumns: string[], telemetry: ITelemetry, aibEndpoint: string | null, geoName: string | null) {} + const llmResponse = await sendApiRequest([{displayText: userPrompt, code: ''}], {dataverseEntity:'', entityField: '', fieldType: ''}, orgID, intelligenceApiToken, '', '', [], this.telemetry, intelligenceEndpoint, geoName); + + stream.markdown(llmResponse[0].displayText); + + stream.markdown('\n```typescript\n' + llmResponse[0].code + '\n```'); // TODO: Handle authentication and org change + console.log(_token) + return { metadata: { command: '' From 663df06ab5bbd3ca3434c02b3893d2520d2ab3a9 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Mon, 6 May 2024 14:48:07 +0530 Subject: [PATCH 07/10] chore: Remove unused code and update PowerPagesChatParticipant initialization --- src/client/extension.ts | 11 ----------- src/client/lib/PacActivityBarUI.ts | 5 ++++- .../powerpages/PowerPagesChatParticipant.ts | 19 ++++++++++++++++++- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 16bc471c..b38c23fd 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; @@ -99,13 +98,6 @@ export async function activate( vscode.workspace.onDidOpenTextDocument(didOpenTextDocument); vscode.workspace.textDocuments.forEach(didOpenTextDocument); - /** - * Required for calling Power Intelligence Service - */ - if(_context.globalState.get('orgID')) { - _context.globalState.update('orgID', ''); //TODO: Check for current active org - } - // portal web view panel _context.subscriptions.push( vscode.commands.registerCommand( @@ -185,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(); 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/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index 576b6d17..8130152c 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -11,14 +11,17 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import { getIntelligenceEndpoint } from '../../ArtemisService'; import { intelligenceAPIAuthentication } from '../../../web/client/common/authenticationProvider'; import { sendApiRequest } from '../../copilot/IntelligenceApiService'; +import { PacWrapper } from '../../../client/pac/PacWrapper'; export class PowerPagesChatParticipant { + private static instance : PowerPagesChatParticipant | null = null; private chatParticipant: vscode.ChatParticipant; private telemetry: ITelemetry; private extensionContext: vscode.ExtensionContext; + private readonly _pacWrapper?: PacWrapper; - constructor(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter,) { + private constructor(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter, pacWrapper?: PacWrapper) { this.chatParticipant = createChatParticipant('powerpages', this.handler); @@ -28,6 +31,20 @@ export class PowerPagesChatParticipant { this.telemetry = telemetry; this.extensionContext = context; + + this._pacWrapper = pacWrapper; + } + + 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 ( From 844da4f731fe71d3f7913a7ed42fb7aeaa69411c Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Mon, 6 May 2024 17:28:30 +0530 Subject: [PATCH 08/10] chore: Update PowerPagesChatParticipant initialization and pac integration --- .../powerpages/PowerPagesChatParticipant.ts | 82 ++++++++++++++++--- 1 file changed, 71 insertions(+), 11 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index 8130152c..0863323b 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -9,10 +9,18 @@ import { IPowerPagesChatResult } from './PowerPagesChatParticipantTypes'; import { ITelemetry } from '../../../client/telemetry/ITelemetry'; import TelemetryReporter from '@vscode/extension-telemetry'; import { getIntelligenceEndpoint } from '../../ArtemisService'; -import { intelligenceAPIAuthentication } from '../../../web/client/common/authenticationProvider'; import { sendApiRequest } from '../../copilot/IntelligenceApiService'; import { PacWrapper } from '../../../client/pac/PacWrapper'; - +import { PAC_SUCCESS } from '../../copilot/constants'; +import { createAuthProfileExp } from '../../Utils'; +import { intelligenceAPIAuthentication } from '../../AuthenticationProvider'; +import { ActiveOrgOutput } from '../../../client/pac/PacTypes'; +import { orgChangeErrorEvent, orgChangeEvent } from '../../OrgChangeNotifier'; + +export interface OrgDetails { + orgID: string; + orgUrl: string; +} export class PowerPagesChatParticipant { private static instance : PowerPagesChatParticipant | null = null; @@ -20,6 +28,12 @@ export class PowerPagesChatParticipant { 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; private constructor(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter, pacWrapper?: PacWrapper) { @@ -33,6 +47,15 @@ export class PowerPagesChatParticipant { this.extensionContext = context; this._pacWrapper = pacWrapper; + + this._disposables.push(orgChangeEvent(async (orgDetails: ActiveOrgOutput) => { + await this.handleOrgChangeSuccess(orgDetails); + })); + + this._disposables.push(orgChangeErrorEvent(async () => { + await createAuthProfileExp(this._pacWrapper); + })); + } public static getInstance(context: vscode.ExtensionContext, telemetry: ITelemetry | TelemetryReporter, pacWrapper?: PacWrapper) { @@ -57,14 +80,11 @@ export class PowerPagesChatParticipant { stream.progress('Working on it...') - let orgID = this.extensionContext.globalState.get('orgID', ''); - - orgID = 'eff535da-5f08-ef11-9f88-00224820c64c'; + this.intializeOrgDetails(); - if (!orgID) { + if (!this.orgID) { // TODO: Auth Create Experience - - + await createAuthProfileExp(this._pacWrapper); return { metadata: { command: '' @@ -72,7 +92,7 @@ export class PowerPagesChatParticipant { }; } - const intelligenceApiAuthResponse = await intelligenceAPIAuthentication(this.telemetry, '', orgID, true); + const intelligenceApiAuthResponse = await intelligenceAPIAuthentication(this.telemetry, '', this.orgID, true); if (!intelligenceApiAuthResponse) { @@ -87,7 +107,7 @@ export class PowerPagesChatParticipant { const intelligenceApiToken = intelligenceApiAuthResponse.accessToken; - const { intelligenceEndpoint, geoName } = await getIntelligenceEndpoint(orgID, this.telemetry, ''); //TODO: Optimize to avoid multiple calls to get intelligence endpoint + const { intelligenceEndpoint, geoName } = await this.getEndpoint(this.orgID, this.telemetry); if (!intelligenceEndpoint || !geoName) { //TODO: Handle error @@ -115,7 +135,7 @@ export class PowerPagesChatParticipant { } // export async function sendApiRequest(userPrompt: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, apiToken: string, sessionID: string, entityName: string, entityColumns: string[], telemetry: ITelemetry, aibEndpoint: string | null, geoName: string | null) {} - const llmResponse = await sendApiRequest([{displayText: userPrompt, code: ''}], {dataverseEntity:'', entityField: '', fieldType: ''}, orgID, intelligenceApiToken, '', '', [], this.telemetry, intelligenceEndpoint, geoName); + const llmResponse = await sendApiRequest([{displayText: userPrompt, code: ''}], {dataverseEntity:'', entityField: '', fieldType: ''}, this.orgID, intelligenceApiToken, '', '', [], this.telemetry, intelligenceEndpoint, geoName); stream.markdown(llmResponse[0].displayText); @@ -132,4 +152,44 @@ export class PowerPagesChatParticipant { }; + private async intializeOrgDetails(): Promise { + + if(this.isOrgDetailsInitialized) { + return; + } + + this.isOrgDetailsInitialized = true; + + const orgDetails:OrgDetails | undefined = this.extensionContext.globalState.get('orgDetails'); + + if(orgDetails) { + this.orgID = orgDetails.orgID; + this.orgUrl = orgDetails.orgUrl; + } else { + if (this._pacWrapper) { + const pacActiveOrg = await this._pacWrapper.activeOrg(); + if (pacActiveOrg && pacActiveOrg.Status === PAC_SUCCESS) { + this.handleOrgChangeSuccess(pacActiveOrg.Results); + } else { + await createAuthProfileExp(this._pacWrapper); + } + } + } + } + + private async handleOrgChangeSuccess(orgDetails: ActiveOrgOutput) { + this.orgID = orgDetails.OrgId; + this.orgUrl = orgDetails.OrgUrl + + this.extensionContext.globalState.update('orgDetails', {orgID: this.orgID, orgUrl: this.orgUrl}); + + //TODO: Handle AIB GEOs + } + + async getEndpoint(orgID: string, telemetry: any) { + if (!this.cachedEndpoint) { + this.cachedEndpoint = await getIntelligenceEndpoint(orgID, telemetry, '') as { intelligenceEndpoint: string; geoName: string }; + } + return this.cachedEndpoint; + } } From 280c6efb92fcc090e062b61ce99f6892fc28a452 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 7 May 2024 12:45:10 +0530 Subject: [PATCH 09/10] feat: Initialize organization details in PowerPagesChatParticipant --- .../chat-participants/powerpages/PowerPagesChatParticipant.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index 0863323b..58639671 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -80,7 +80,7 @@ export class PowerPagesChatParticipant { stream.progress('Working on it...') - this.intializeOrgDetails(); + await this.intializeOrgDetails(); if (!this.orgID) { // TODO: Auth Create Experience @@ -100,7 +100,7 @@ export class PowerPagesChatParticipant { return { metadata: { - command: '' + command: '', } }; } From 5abd6baec701000227d201db5e139d05c3b6a4f6 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 7 May 2024 13:24:07 +0530 Subject: [PATCH 10/10] @powerpages aib endpoint handling --- .../powerpages/PowerPagesChatParticipant.ts | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index 58639671..3c80a62e 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -11,7 +11,7 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import { getIntelligenceEndpoint } from '../../ArtemisService'; import { sendApiRequest } from '../../copilot/IntelligenceApiService'; import { PacWrapper } from '../../../client/pac/PacWrapper'; -import { PAC_SUCCESS } from '../../copilot/constants'; +import { COPILOT_UNAVAILABLE, PAC_SUCCESS } from '../../copilot/constants'; import { createAuthProfileExp } from '../../Utils'; import { intelligenceAPIAuthentication } from '../../AuthenticationProvider'; import { ActiveOrgOutput } from '../../../client/pac/PacTypes'; @@ -109,14 +109,10 @@ export class PowerPagesChatParticipant { const { intelligenceEndpoint, geoName } = await this.getEndpoint(this.orgID, this.telemetry); - if (!intelligenceEndpoint || !geoName) { - //TODO: Handle error + const endpointAvailabilityResult = this.handleEndpointAvailability(intelligenceEndpoint, geoName); - return { - metadata: { - command: '' - } - }; + if(endpointAvailabilityResult !== '') { + return endpointAvailabilityResult; } const userPrompt = request.prompt; @@ -184,12 +180,35 @@ export class PowerPagesChatParticipant { this.extensionContext.globalState.update('orgDetails', {orgID: this.orgID, orgUrl: this.orgUrl}); //TODO: Handle AIB GEOs + + this.cachedEndpoint = null; } - async getEndpoint(orgID: string, telemetry: any) { + async getEndpoint(orgID: string, telemetry: ITelemetry) { if (!this.cachedEndpoint) { this.cachedEndpoint = await getIntelligenceEndpoint(orgID, telemetry, '') as { intelligenceEndpoint: string; geoName: string }; } return this.cachedEndpoint; } + + handleEndpointAvailability(intelligenceEndpoint: string, geoName: string) { + if (!intelligenceEndpoint || !geoName) { + return { + metadata: { + command: '' + } + }; + } else if(intelligenceEndpoint === COPILOT_UNAVAILABLE) { + return { + metadata: { + command: '' + } + }; + } + + //TODO: Handle ECS unavailable scenario + + return '' //TODO return type + } + }