Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand to EU and AU geos for copilot #781

Merged
merged 11 commits into from
Feb 12, 2024
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
Loading