Skip to content

Commit

Permalink
Expand to EU and AU geos for copilot (#781)
Browse files Browse the repository at this point in the history
* Expand to Eu, AU, and UK geos for copilot

* cleanup and update for comments

* Enable copilot in AU and EU only
  • Loading branch information
tyaginidhi authored Feb 12, 2024
1 parent 5f9b4e6 commit 09dfb54
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 98 deletions.
4 changes: 2 additions & 2 deletions src/common/ArtemisService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import fetch, { RequestInit } from "node-fetch";
import { COPILOT_UNAVAILABLE, US_GEO } from "./copilot/constants";
import { COPILOT_UNAVAILABLE, SUPPORTED_GEO } from "./copilot/constants";
import { ITelemetry } from "../client/telemetry/ITelemetry";
import { sendTelemetryEvent } from "./copilot/telemetry/copilotTelemetry";
import { CopilotArtemisFailureEvent, CopilotArtemisSuccessEvent } from "./copilot/telemetry/telemetryConstants";
Expand All @@ -20,7 +20,7 @@ export async function getIntelligenceEndpoint(orgId: string, telemetry: ITelemet
const { geoName, environment, clusterNumber } = artemisResponse[0];
sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId });

if (geoName !== US_GEO) {
if (!SUPPORTED_GEO.includes(geoName)) {
return { intelligenceEndpoint: COPILOT_UNAVAILABLE, geoName: geoName };
}

Expand Down
202 changes: 107 additions & 95 deletions src/common/copilot/IntelligenceApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import fetch, { RequestInit } from "node-fetch";
import { INAPPROPRIATE_CONTENT, INPUT_CONTENT_FILTERED, INVALID_INFERENCE_INPUT, InvalidResponse, MalaciousScenerioResponse, NetworkError, PROMPT_LIMIT_EXCEEDED, PromptLimitExceededResponse, RELEVANCY_CHECK_FAILED, RateLimitingResponse, UnauthorizedResponse, UserPrompt } from "./constants";
import { GEO_REQUIRES_CROSS_GEO_OPTIONS, INAPPROPRIATE_CONTENT, INPUT_CONTENT_FILTERED, INVALID_INFERENCE_INPUT, InvalidResponse, MalaciousScenerioResponse, NetworkError, PROMPT_LIMIT_EXCEEDED, PromptLimitExceededResponse, RELEVANCY_CHECK_FAILED, RateLimitingResponse, UnauthorizedResponse, UserPrompt } from "./constants";
import { IActiveFileParams } from "./model";
import { sendTelemetryEvent } from "./telemetry/copilotTelemetry";
import { ITelemetry } from "../../client/telemetry/ITelemetry";
Expand All @@ -15,108 +15,120 @@ import { EXTENSION_NAME } from "../../client/constants";
const clientType = EXTENSION_NAME + '-' + getExtensionType();
const clientVersion = getExtensionVersion();

export async function sendApiRequest(userPrompt: UserPrompt[], activeFileParams: IActiveFileParams, orgID: string, apiToken: string, sessionID: string, entityName: string, entityColumns: string[], telemetry: ITelemetry, aibEndpoint: string | null) {

if (!aibEndpoint) {
return NetworkError;
}

const requestBody = {
"question": userPrompt[0].displayText,
"top": 1,
"context": {
"sessionId": sessionID,
"scenario": "PowerPagesProDev",
"subScenario": "PowerPagesProDevGeneric",
"version": "V1",
"information": {
"dataverseEntity": activeFileParams.dataverseEntity,
"entityField": activeFileParams.entityField,
"fieldType": activeFileParams.fieldType,
"activeFileContent": userPrompt[0].code, //Active file content (selected code)
"targetEntity": entityName,
"targetColumns": entityColumns,
"clientType": clientType,
"clientVersion": clientVersion,
}
}
};

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 requestInit: RequestInit = {
method: "POST",
headers: {
'Content-Type': "application/json",
Authorization: `Bearer ${apiToken}`,
},
body: JSON.stringify(requestBody),
}

try {
const startTime = performance.now();
if (!aibEndpoint) {
return NetworkError;
}

const response = await fetch(aibEndpoint, {
...requestInit
});
let requestBody = {
"question": userPrompt[0].displayText,
"top": 1,
"context": {
"sessionId": sessionID,
"scenario": "PowerPagesProDev",
"subScenario": "PowerPagesProDevGeneric",
"version": "V1",
"information": {
"dataverseEntity": activeFileParams.dataverseEntity,
"entityField": activeFileParams.entityField,
"fieldType": activeFileParams.fieldType,
"activeFileContent": userPrompt[0].code, //Active file content (selected code)
"targetEntity": entityName,
"targetColumns": entityColumns,
"clientType": clientType,
"clientVersion": clientVersion,
}
},
"crossGeoOptions": {
"enableCrossGeoCall": false
}
};

const endTime = performance.now();
if (geoName && GEO_REQUIRES_CROSS_GEO_OPTIONS.includes(geoName)) {
requestBody = {
...requestBody,
"crossGeoOptions": {
"enableCrossGeoCall": true
}
}
}

const responseTime = endTime - startTime;

if (response.ok) {
try {
const jsonResponse = await response.json();
const requestInit: RequestInit = {
method: "POST",
headers: {
'Content-Type': "application/json",
Authorization: `Bearer ${apiToken}`,
},
body: JSON.stringify(requestBody),
}

if (jsonResponse.operationStatus === 'Success') {
sendTelemetryEvent(telemetry, { eventName: CopilotResponseSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgId: orgID });
if (jsonResponse.additionalData && Array.isArray(jsonResponse.additionalData) && jsonResponse.additionalData.length > 0) {
const additionalData = jsonResponse.additionalData[0];
if (additionalData.properties && additionalData.properties.response) {
const responseMessage = additionalData.properties.response;
return responseMessage;
try {
const startTime = performance.now();

const response = await fetch(aibEndpoint, {
...requestInit
});

const endTime = performance.now();

const responseTime = endTime - startTime;

if (response.ok) {
try {
const jsonResponse = await response.json();

if (jsonResponse.operationStatus === 'Success') {
sendTelemetryEvent(telemetry, { eventName: CopilotResponseSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgId: orgID });
if (jsonResponse.additionalData && Array.isArray(jsonResponse.additionalData) && jsonResponse.additionalData.length > 0) {
const additionalData = jsonResponse.additionalData[0];
if (additionalData.properties && additionalData.properties.response) {
const responseMessage = additionalData.properties.response;
return responseMessage;
}
}
}
else {
const errorMessage = jsonResponse.error.messages[0]; //Error from AIB with status code 200
return [{ displayText: errorMessage, code: '' }];
}
throw new Error("Invalid response format");
} catch (error) {
sendTelemetryEvent(telemetry, { eventName: CopilotResponseOkFailureEvent, copilotSessionId: sessionID, error: error as Error, durationInMills: responseTime, orgId: orgID });
return InvalidResponse;
}
} else {
try {
const errorResponse = await response.json();
const errorCode = errorResponse.error && errorResponse.error.code;
const errorMessage = errorResponse.error && errorResponse.error.messages[0];

const responseError = new Error(errorMessage);
sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEventWithMessage, copilotSessionId: sessionID, responseStatus: String(response.status), error: responseError, durationInMills: responseTime, orgId: orgID });

if (response.status === 429) {
return RateLimitingResponse
}
else if (response.status === 401) {
return UnauthorizedResponse;
}
else if (errorCode === RELEVANCY_CHECK_FAILED || errorCode === INAPPROPRIATE_CONTENT || errorCode === INPUT_CONTENT_FILTERED) {
return MalaciousScenerioResponse;
} else if (errorCode === PROMPT_LIMIT_EXCEEDED || errorCode === INVALID_INFERENCE_INPUT) {
return PromptLimitExceededResponse;
}
else if (errorMessage) {
return InvalidResponse;
}
} catch (error) {
sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, responseStatus: String(response.status), error: error as Error, durationInMills: responseTime, orgId: orgID });
return InvalidResponse;
}
}
}
else {
const errorMessage = jsonResponse.error.messages[0]; //Error from AIB with status code 200
return [{ displayText: errorMessage, code: '' }];
}
throw new Error("Invalid response format");
} catch (error) {
sendTelemetryEvent(telemetry, { eventName: CopilotResponseOkFailureEvent, copilotSessionId: sessionID, error: error as Error, durationInMills: responseTime, orgId: orgID });
return InvalidResponse;
}
} else {
try {
const errorResponse = await response.json();
const errorCode = errorResponse.error && errorResponse.error.code;
const errorMessage = errorResponse.error && errorResponse.error.messages[0];

const responseError = new Error(errorMessage);
sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEventWithMessage, copilotSessionId: sessionID, responseStatus: String(response.status), error: responseError, durationInMills: responseTime, orgId: orgID });

if (response.status === 429) {
return RateLimitingResponse
}
else if (response.status === 401) {
return UnauthorizedResponse;
}
else if (errorCode === RELEVANCY_CHECK_FAILED || errorCode === INAPPROPRIATE_CONTENT || errorCode === INPUT_CONTENT_FILTERED) {
return MalaciousScenerioResponse;
} else if (errorCode === PROMPT_LIMIT_EXCEEDED || errorCode === INVALID_INFERENCE_INPUT) {
return PromptLimitExceededResponse;
}
else if (errorMessage) {
return InvalidResponse;
}
} catch (error) {
sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, responseStatus: String(response.status), error: error as Error, durationInMills: responseTime, orgId: orgID });
return InvalidResponse;
}
} catch (error) {
sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, error: error as Error, orgId: orgID });
return NetworkError;
}
} catch (error) {
sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, error: error as Error, orgId: orgID });
return NetworkError;
}

}
2 changes: 1 addition & 1 deletion src/common/copilot/PowerPagesCopilot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {

entityColumns = await getEntityColumns(entityName, activeOrgUrl, dataverseToken, telemetry, sessionID);
}
return sendApiRequest(data, activeFileParams, orgID, intelligenceApiToken, sessionID, entityName, entityColumns, telemetry, this.aibEndpoint);
return sendApiRequest(data, activeFileParams, orgID, intelligenceApiToken, sessionID, entityName, entityColumns, telemetry, this.aibEndpoint, this.geoName);
})
.then(apiResponse => {
this.sendMessageToWebview({ type: 'apiResponse', value: apiResponse });
Expand Down
2 changes: 2 additions & 0 deletions src/common/copilot/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const US_GEO = 'us';
export const AUSTRALIA_GEO = 'au';
export const EUROPE_GEO = 'eu';
export const UK_GEO = 'uk';
export const SUPPORTED_GEO = [US_GEO, AUSTRALIA_GEO, EUROPE_GEO]; // UK, India rollout post IAPI changes
export const GEO_REQUIRES_CROSS_GEO_OPTIONS = [EUROPE_GEO]; // Per CAPI guidelines - remains EUDB scope only
export const COPILOT_UNAVAILABLE = 'copilotunavailable';
export const AUTH_CREATE_MESSAGE = vscode.l10n.t('Creating new Auth Profile');
export const AUTH_CREATE_FAILED = vscode.l10n.t("Error creating auth profile for org")
Expand Down

0 comments on commit 09dfb54

Please sign in to comment.