Outlook_Addin_LLM/node_modules/@microsoft/m365-spec-parser/dist/index.node.cjs.js

2081 lines
94 KiB
JavaScript

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var SwaggerParser = require('@apidevtools/swagger-parser');
var converter = require('swagger2openapi');
var jsyaml = require('js-yaml');
var fs = require('fs-extra');
var path = require('path');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var SwaggerParser__default = /*#__PURE__*/_interopDefaultLegacy(SwaggerParser);
var converter__default = /*#__PURE__*/_interopDefaultLegacy(converter);
var jsyaml__default = /*#__PURE__*/_interopDefaultLegacy(jsyaml);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
// Copyright (c) Microsoft Corporation.
/**
* An enum that represents the types of errors that can occur during validation.
*/
exports.ErrorType = void 0;
(function (ErrorType) {
ErrorType["SpecNotValid"] = "spec-not-valid";
ErrorType["RemoteRefNotSupported"] = "remote-ref-not-supported";
ErrorType["NoServerInformation"] = "no-server-information";
ErrorType["UrlProtocolNotSupported"] = "url-protocol-not-supported";
ErrorType["RelativeServerUrlNotSupported"] = "relative-server-url-not-supported";
ErrorType["NoSupportedApi"] = "no-supported-api";
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
ErrorType["ListFailed"] = "list-failed";
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
ErrorType["UpdateManifestFailed"] = "update-manifest-failed";
ErrorType["GenerateAdaptiveCardFailed"] = "generate-adaptive-card-failed";
ErrorType["GenerateFailed"] = "generate-failed";
ErrorType["ValidateFailed"] = "validate-failed";
ErrorType["GetSpecFailed"] = "get-spec-failed";
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
ErrorType["MissingOperationId"] = "missing-operation-id";
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
ErrorType["NoParameter"] = "no-parameter";
ErrorType["NoAPIInfo"] = "no-api-info";
ErrorType["MethodNotAllowed"] = "method-not-allowed";
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
ErrorType["Cancelled"] = "cancelled";
ErrorType["Unknown"] = "unknown";
})(exports.ErrorType || (exports.ErrorType = {}));
/**
* An enum that represents the types of warnings that can occur during validation.
*/
exports.WarningType = void 0;
(function (WarningType) {
WarningType["OperationIdMissing"] = "operationid-missing";
WarningType["GenerateCardFailed"] = "generate-card-failed";
WarningType["OperationOnlyContainsOptionalParam"] = "operation-only-contains-optional-param";
WarningType["ConvertSwaggerToOpenAPI"] = "convert-swagger-to-openapi";
WarningType["Unknown"] = "unknown";
})(exports.WarningType || (exports.WarningType = {}));
/**
* An enum that represents the validation status of an OpenAPI specification file.
*/
exports.ValidationStatus = void 0;
(function (ValidationStatus) {
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
})(exports.ValidationStatus || (exports.ValidationStatus = {}));
exports.ProjectType = void 0;
(function (ProjectType) {
ProjectType[ProjectType["Copilot"] = 0] = "Copilot";
ProjectType[ProjectType["SME"] = 1] = "SME";
ProjectType[ProjectType["TeamsAi"] = 2] = "TeamsAi";
})(exports.ProjectType || (exports.ProjectType = {}));
// Copyright (c) Microsoft Corporation.
class ConstantString {
}
ConstantString.CancelledMessage = "Operation is cancelled.";
ConstantString.NoServerInformation = "No server information is found in the OpenAPI description document.";
ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
ConstantString.MissingOperationId = "Missing operationIds: %s.";
ConstantString.NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
ConstantString.UnknownSchema = "Unknown schema: %s.";
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: please make sure that the environment variable %s is defined.";
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
ConstantString.WrappedCardVersion = "devPreview";
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
ConstantString.WrappedCardResponseLayout = "list";
ConstantString.GetMethod = "get";
ConstantString.PostMethod = "post";
ConstantString.AdaptiveCardVersion = "1.5";
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
ConstantString.AdaptiveCardType = "AdaptiveCard";
ConstantString.TextBlockType = "TextBlock";
ConstantString.ImageType = "Image";
ConstantString.ContainerType = "Container";
ConstantString.RegistrationIdPostfix = {
apiKey: "REGISTRATION_ID",
oauth2: "CONFIGURATION_ID",
http: "REGISTRATION_ID",
openIdConnect: "REGISTRATION_ID",
};
ConstantString.ResponseCodeFor20X = [
"200",
"201",
"202",
"203",
"204",
"205",
"206",
"207",
"208",
"226",
"default",
];
ConstantString.AllOperationMethods = [
"get",
"post",
"put",
"delete",
"patch",
"head",
"options",
"trace",
];
// TODO: update after investigating the usage of these constants.
ConstantString.WellknownResultNames = [
"result",
"data",
"items",
"root",
"matches",
"queries",
"list",
"output",
];
ConstantString.WellknownTitleName = ["title", "name", "summary", "caption", "subject", "label"];
ConstantString.WellknownSubtitleName = [
"subtitle",
"id",
"uid",
"description",
"desc",
"detail",
];
ConstantString.WellknownImageName = [
"image",
"icon",
"avatar",
"picture",
"photo",
"logo",
"pic",
"thumbnail",
"img",
];
ConstantString.ShortDescriptionMaxLens = 80;
ConstantString.FullDescriptionMaxLens = 4000;
ConstantString.CommandDescriptionMaxLens = 128;
ConstantString.ParameterDescriptionMaxLens = 128;
ConstantString.ConversationStarterMaxLens = 50;
ConstantString.CommandTitleMaxLens = 32;
ConstantString.ParameterTitleMaxLens = 32;
ConstantString.SMERequiredParamsMaxNum = 5;
ConstantString.DefaultPluginId = "plugin_1";
ConstantString.PluginManifestSchema = "https://aka.ms/json-schemas/copilot-extensions/v2.1/plugin.schema.json";
// Copyright (c) Microsoft Corporation.
class SpecParserError extends Error {
constructor(message, errorType) {
super(message);
this.errorType = errorType;
}
}
// Copyright (c) Microsoft Corporation.
class Utils {
static hasNestedObjectInSchema(schema) {
if (schema.type === "object") {
for (const property in schema.properties) {
const nestedSchema = schema.properties[property];
if (nestedSchema.type === "object") {
return true;
}
}
}
return false;
}
static containMultipleMediaTypes(bodyObject) {
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
}
static isBearerTokenAuth(authScheme) {
return authScheme.type === "http" && authScheme.scheme === "bearer";
}
static isAPIKeyAuth(authScheme) {
return authScheme.type === "apiKey";
}
static isOAuthWithAuthCodeFlow(authScheme) {
return !!(authScheme.type === "oauth2" &&
authScheme.flows &&
authScheme.flows.authorizationCode);
}
static getAuthArray(securities, spec) {
var _a;
const result = [];
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
if (securitiesArr && securitySchemas) {
for (let i = 0; i < securitiesArr.length; i++) {
const security = securitiesArr[i];
const authArray = [];
for (const name in security) {
const auth = securitySchemas[name];
authArray.push({
authScheme: auth,
name: name,
});
}
if (authArray.length > 0) {
result.push(authArray);
}
}
}
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
return result;
}
static getAuthInfo(spec) {
let authInfo = undefined;
for (const url in spec.paths) {
for (const method in spec.paths[url]) {
const operation = spec.paths[url][method];
const authArray = Utils.getAuthArray(operation.security, spec);
if (authArray && authArray.length > 0) {
const currentAuth = authArray[0][0];
if (!authInfo) {
authInfo = authArray[0][0];
}
else if (authInfo.name !== currentAuth.name) {
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, exports.ErrorType.MultipleAuthNotSupported);
}
}
}
}
return authInfo;
}
static updateFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
static getResponseJson(operationObject) {
var _a, _b;
let json = {};
let multipleMediaType = false;
for (const code of ConstantString.ResponseCodeFor20X) {
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
multipleMediaType = false;
json = responseObject.content["application/json"];
if (Utils.containMultipleMediaTypes(responseObject)) {
multipleMediaType = true;
json = {};
}
else {
break;
}
}
}
return { json, multipleMediaType };
}
static convertPathToCamelCase(path) {
const pathSegments = path.split(/[./{]/);
const camelCaseSegments = pathSegments.map((segment) => {
segment = segment.replace(/}/g, "");
return segment.charAt(0).toUpperCase() + segment.slice(1);
});
const camelCasePath = camelCaseSegments.join("");
return camelCasePath;
}
static getUrlProtocol(urlString) {
try {
const url = new URL(urlString);
return url.protocol;
}
catch (err) {
return undefined;
}
}
static resolveEnv(str) {
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
let matches = placeHolderReg.exec(str);
let newStr = str;
while (matches != null) {
const envVar = matches[1];
const envVal = process.env[envVar];
if (!envVal) {
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
}
else {
newStr = newStr.replace(matches[0], envVal);
}
matches = placeHolderReg.exec(str);
}
return newStr;
}
static checkServerUrl(servers) {
const errors = [];
let serverUrl;
try {
serverUrl = Utils.resolveEnv(servers[0].url);
}
catch (err) {
errors.push({
type: exports.ErrorType.ResolveServerUrlFailed,
content: err.message,
data: servers,
});
return errors;
}
const protocol = Utils.getUrlProtocol(serverUrl);
if (!protocol) {
// Relative server url is not supported
errors.push({
type: exports.ErrorType.RelativeServerUrlNotSupported,
content: ConstantString.RelativeServerUrlNotSupported,
data: servers,
});
}
else if (protocol !== "https:") {
// Http server url is not supported
const protocolString = protocol.slice(0, -1);
errors.push({
type: exports.ErrorType.UrlProtocolNotSupported,
content: Utils.format(ConstantString.UrlProtocolNotSupported, protocol.slice(0, -1)),
data: protocolString,
});
}
return errors;
}
static validateServer(spec, options) {
var _a;
const errors = [];
let hasTopLevelServers = false;
let hasPathLevelServers = false;
let hasOperationLevelServers = false;
if (spec.servers && spec.servers.length >= 1) {
hasTopLevelServers = true;
// for multiple server, we only use the first url
const serverErrors = Utils.checkServerUrl(spec.servers);
errors.push(...serverErrors);
}
const paths = spec.paths;
for (const path in paths) {
const methods = paths[path];
if ((methods === null || methods === void 0 ? void 0 : methods.servers) && methods.servers.length >= 1) {
hasPathLevelServers = true;
const serverErrors = Utils.checkServerUrl(methods.servers);
errors.push(...serverErrors);
}
for (const method in methods) {
const operationObject = methods[method];
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
hasOperationLevelServers = true;
const serverErrors = Utils.checkServerUrl(operationObject.servers);
errors.push(...serverErrors);
}
}
}
}
if (!hasTopLevelServers && !hasPathLevelServers && !hasOperationLevelServers) {
errors.push({
type: exports.ErrorType.NoServerInformation,
content: ConstantString.NoServerInformation,
});
}
return errors;
}
static isWellKnownName(name, wellknownNameList) {
for (let i = 0; i < wellknownNameList.length; i++) {
name = name.replace(/_/g, "").replace(/-/g, "");
if (name.toLowerCase().includes(wellknownNameList[i])) {
return true;
}
}
return false;
}
static generateParametersFromSchema(schema, name, allowMultipleParameters, isRequired = false) {
var _a, _b;
const requiredParams = [];
const optionalParams = [];
if (schema.type === "string" ||
schema.type === "integer" ||
schema.type === "boolean" ||
schema.type === "number") {
const parameter = {
name: name,
title: Utils.updateFirstLetter(name).slice(0, ConstantString.ParameterTitleMaxLens),
description: ((_a = schema.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
};
if (allowMultipleParameters) {
Utils.updateParameterWithInputType(schema, parameter);
}
if (isRequired && schema.default === undefined) {
parameter.isRequired = true;
requiredParams.push(parameter);
}
else {
optionalParams.push(parameter);
}
}
else if (schema.type === "object") {
const { properties } = schema;
for (const property in properties) {
let isRequired = false;
if (schema.required && ((_b = schema.required) === null || _b === void 0 ? void 0 : _b.indexOf(property)) >= 0) {
isRequired = true;
}
const [requiredP, optionalP] = Utils.generateParametersFromSchema(properties[property], property, allowMultipleParameters, isRequired);
requiredParams.push(...requiredP);
optionalParams.push(...optionalP);
}
}
return [requiredParams, optionalParams];
}
static updateParameterWithInputType(schema, param) {
if (schema.enum) {
param.inputType = "choiceset";
param.choices = [];
for (let i = 0; i < schema.enum.length; i++) {
param.choices.push({
title: schema.enum[i],
value: schema.enum[i],
});
}
}
else if (schema.type === "string") {
param.inputType = "text";
}
else if (schema.type === "integer" || schema.type === "number") {
param.inputType = "number";
}
else if (schema.type === "boolean") {
param.inputType = "toggle";
}
if (schema.default) {
param.value = schema.default;
}
}
static parseApiInfo(operationItem, options) {
var _a, _b;
const requiredParams = [];
const optionalParams = [];
const paramObject = operationItem.parameters;
if (paramObject) {
paramObject.forEach((param) => {
var _a;
const parameter = {
name: param.name,
title: Utils.updateFirstLetter(param.name).slice(0, ConstantString.ParameterTitleMaxLens),
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
};
const schema = param.schema;
if (options.allowMultipleParameters && schema) {
Utils.updateParameterWithInputType(schema, parameter);
}
if (param.in !== "header" && param.in !== "cookie") {
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
parameter.isRequired = true;
requiredParams.push(parameter);
}
else {
optionalParams.push(parameter);
}
}
});
}
if (operationItem.requestBody) {
const requestBody = operationItem.requestBody;
const requestJson = requestBody.content["application/json"];
if (Object.keys(requestJson).length !== 0) {
const schema = requestJson.schema;
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
requiredParams.push(...requiredP);
optionalParams.push(...optionalP);
}
}
const operationId = operationItem.operationId;
const parameters = [...requiredParams, ...optionalParams];
const command = {
context: ["compose"],
type: "query",
title: ((_a = operationItem.summary) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.CommandTitleMaxLens),
id: operationId,
parameters: parameters,
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
};
return command;
}
static format(str, ...args) {
let index = 0;
return str.replace(/%s/g, () => {
const arg = args[index++];
return arg !== undefined ? arg : "";
});
}
static getSafeRegistrationIdEnvName(authName) {
if (!authName) {
return "";
}
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
}
return safeRegistrationIdEnvName;
}
static getServerObject(spec, method, path) {
const pathObj = spec.paths[path];
const operationObject = pathObj[method];
const rootServer = spec.servers && spec.servers[0];
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
const operationServer = operationObject.servers && operationObject.servers[0];
const serverUrl = operationServer || methodServer || rootServer;
return serverUrl;
}
}
// Copyright (c) Microsoft Corporation.
class Validator {
listAPIs() {
var _a;
if (this.apiMap) {
return this.apiMap;
}
const paths = this.spec.paths;
const result = {};
for (const path in paths) {
const methods = paths[path];
for (const method in methods) {
const operationObject = methods[method];
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
const validateResult = this.validateAPI(method, path);
result[`${method.toUpperCase()} ${path}`] = {
operation: operationObject,
isValid: validateResult.isValid,
reason: validateResult.reason,
};
}
}
}
this.apiMap = result;
return result;
}
validateSpecVersion() {
const result = { errors: [], warnings: [] };
if (this.spec.openapi >= "3.1.0") {
result.errors.push({
type: exports.ErrorType.SpecVersionNotSupported,
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
data: this.spec.openapi,
});
}
return result;
}
validateSpecServer() {
const result = { errors: [], warnings: [] };
const serverErrors = Utils.validateServer(this.spec, this.options);
result.errors.push(...serverErrors);
return result;
}
validateSpecNoSupportAPI() {
const result = { errors: [], warnings: [] };
const apiMap = this.listAPIs();
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
if (validAPIs.length === 0) {
const data = [];
for (const key in apiMap) {
const { reason } = apiMap[key];
const apiInvalidReason = { api: key, reason: reason };
data.push(apiInvalidReason);
}
result.errors.push({
type: exports.ErrorType.NoSupportedApi,
content: ConstantString.NoSupportedApi,
data,
});
}
return result;
}
validateSpecOperationId() {
const result = { errors: [], warnings: [] };
const apiMap = this.listAPIs();
// OperationId missing
const apisMissingOperationId = [];
for (const key in apiMap) {
const { operation } = apiMap[key];
if (!operation.operationId) {
apisMissingOperationId.push(key);
}
}
if (apisMissingOperationId.length > 0) {
result.warnings.push({
type: exports.WarningType.OperationIdMissing,
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
data: apisMissingOperationId,
});
}
return result;
}
validateMethodAndPath(method, path) {
const result = { isValid: true, reason: [] };
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
result.isValid = false;
result.reason.push(exports.ErrorType.MethodNotAllowed);
return result;
}
const pathObj = this.spec.paths[path];
if (!pathObj || !pathObj[method]) {
result.isValid = false;
result.reason.push(exports.ErrorType.UrlPathNotExist);
return result;
}
return result;
}
validateResponse(method, path) {
const result = { isValid: true, reason: [] };
const operationObject = this.spec.paths[path][method];
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
if (this.options.projectType === exports.ProjectType.SME) {
// only support response body only contains “application/json” content type
if (multipleMediaType) {
result.reason.push(exports.ErrorType.ResponseContainMultipleMediaTypes);
}
else if (Object.keys(json).length === 0) {
// response body should not be empty
result.reason.push(exports.ErrorType.ResponseJsonIsEmpty);
}
}
return result;
}
validateServer(method, path) {
const result = { isValid: true, reason: [] };
const serverObj = Utils.getServerObject(this.spec, method, path);
if (!serverObj) {
// should contain server URL
result.reason.push(exports.ErrorType.NoServerInformation);
}
else {
// server url should be absolute url with https protocol
const serverValidateResult = Utils.checkServerUrl([serverObj]);
result.reason.push(...serverValidateResult.map((item) => item.type));
}
return result;
}
validateAuth(method, path) {
const pathObj = this.spec.paths[path];
const operationObject = pathObj[method];
const securities = operationObject.security;
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
if (authSchemeArray.length === 0) {
return { isValid: true, reason: [] };
}
if (this.options.allowAPIKeyAuth ||
this.options.allowOauth2 ||
this.options.allowBearerTokenAuth) {
// Currently we don't support multiple auth in one operation
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
return {
isValid: false,
reason: [exports.ErrorType.MultipleAuthNotSupported],
};
}
for (const auths of authSchemeArray) {
if (auths.length === 1) {
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
return { isValid: true, reason: [] };
}
}
}
}
return { isValid: false, reason: [exports.ErrorType.AuthTypeIsNotSupported] };
}
checkPostBodySchema(schema, isRequired = false) {
var _a;
const paramResult = {
requiredNum: 0,
optionalNum: 0,
isValid: true,
reason: [],
};
if (Object.keys(schema).length === 0) {
return paramResult;
}
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
const isCopilot = this.projectType === exports.ProjectType.Copilot;
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
paramResult.isValid = false;
paramResult.reason = [exports.ErrorType.RequestBodyContainsNestedObject];
return paramResult;
}
if (schema.type === "string" ||
schema.type === "integer" ||
schema.type === "boolean" ||
schema.type === "number") {
if (isRequiredWithoutDefault) {
paramResult.requiredNum = paramResult.requiredNum + 1;
}
else {
paramResult.optionalNum = paramResult.optionalNum + 1;
}
}
else if (schema.type === "object") {
const { properties } = schema;
for (const property in properties) {
let isRequired = false;
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
isRequired = true;
}
const result = this.checkPostBodySchema(properties[property], isRequired);
paramResult.requiredNum += result.requiredNum;
paramResult.optionalNum += result.optionalNum;
paramResult.isValid = paramResult.isValid && result.isValid;
paramResult.reason.push(...result.reason);
}
}
else {
if (isRequiredWithoutDefault && !isCopilot) {
paramResult.isValid = false;
paramResult.reason.push(exports.ErrorType.PostBodyContainsRequiredUnsupportedSchema);
}
}
return paramResult;
}
checkParamSchema(paramObject) {
const paramResult = {
requiredNum: 0,
optionalNum: 0,
isValid: true,
reason: [],
};
if (!paramObject) {
return paramResult;
}
const isCopilot = this.projectType === exports.ProjectType.Copilot;
for (let i = 0; i < paramObject.length; i++) {
const param = paramObject[i];
const schema = param.schema;
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
paramResult.isValid = false;
paramResult.reason.push(exports.ErrorType.ParamsContainsNestedObject);
continue;
}
const isRequiredWithoutDefault = param.required && schema.default === undefined;
if (isCopilot) {
if (isRequiredWithoutDefault) {
paramResult.requiredNum = paramResult.requiredNum + 1;
}
else {
paramResult.optionalNum = paramResult.optionalNum + 1;
}
continue;
}
if (param.in === "header" || param.in === "cookie") {
if (isRequiredWithoutDefault) {
paramResult.isValid = false;
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
}
continue;
}
if (schema.type !== "boolean" &&
schema.type !== "string" &&
schema.type !== "number" &&
schema.type !== "integer") {
if (isRequiredWithoutDefault) {
paramResult.isValid = false;
paramResult.reason.push(exports.ErrorType.ParamsContainRequiredUnsupportedSchema);
}
continue;
}
if (param.in === "query" || param.in === "path") {
if (isRequiredWithoutDefault) {
paramResult.requiredNum = paramResult.requiredNum + 1;
}
else {
paramResult.optionalNum = paramResult.optionalNum + 1;
}
}
}
return paramResult;
}
hasNestedObjectInSchema(schema) {
if (schema.type === "object") {
for (const property in schema.properties) {
const nestedSchema = schema.properties[property];
if (nestedSchema.type === "object") {
return true;
}
}
}
return false;
}
}
// Copyright (c) Microsoft Corporation.
class CopilotValidator extends Validator {
constructor(spec, options) {
super();
this.projectType = exports.ProjectType.Copilot;
this.options = options;
this.spec = spec;
}
validateSpec() {
const result = { errors: [], warnings: [] };
// validate spec version
let validationResult = this.validateSpecVersion();
result.errors.push(...validationResult.errors);
// validate spec server
validationResult = this.validateSpecServer();
result.errors.push(...validationResult.errors);
// validate no supported API
validationResult = this.validateSpecNoSupportAPI();
result.errors.push(...validationResult.errors);
// validate operationId missing
validationResult = this.validateSpecOperationId();
result.warnings.push(...validationResult.warnings);
return result;
}
validateAPI(method, path) {
const result = { isValid: true, reason: [] };
method = method.toLocaleLowerCase();
// validate method and path
const methodAndPathResult = this.validateMethodAndPath(method, path);
if (!methodAndPathResult.isValid) {
return methodAndPathResult;
}
const operationObject = this.spec.paths[path][method];
// validate auth
const authCheckResult = this.validateAuth(method, path);
result.reason.push(...authCheckResult.reason);
// validate operationId
if (!this.options.allowMissingId && !operationObject.operationId) {
result.reason.push(exports.ErrorType.MissingOperationId);
}
// validate server
const validateServerResult = this.validateServer(method, path);
result.reason.push(...validateServerResult.reason);
// validate response
const validateResponseResult = this.validateResponse(method, path);
result.reason.push(...validateResponseResult.reason);
// validate requestBody
const requestBody = operationObject.requestBody;
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
if (requestJsonBody) {
const requestBodySchema = requestJsonBody.schema;
if (requestBodySchema.type !== "object") {
result.reason.push(exports.ErrorType.PostBodySchemaIsNotJson);
}
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
result.reason.push(...requestBodyParamResult.reason);
}
// validate parameters
const paramObject = operationObject.parameters;
const paramResult = this.checkParamSchema(paramObject);
result.reason.push(...paramResult.reason);
if (result.reason.length > 0) {
result.isValid = false;
}
return result;
}
}
// Copyright (c) Microsoft Corporation.
class SMEValidator extends Validator {
constructor(spec, options) {
super();
this.projectType = exports.ProjectType.SME;
this.options = options;
this.spec = spec;
}
validateSpec() {
const result = { errors: [], warnings: [] };
// validate spec version
let validationResult = this.validateSpecVersion();
result.errors.push(...validationResult.errors);
// validate spec server
validationResult = this.validateSpecServer();
result.errors.push(...validationResult.errors);
// validate no supported API
validationResult = this.validateSpecNoSupportAPI();
result.errors.push(...validationResult.errors);
// validate operationId missing
if (this.options.allowMissingId) {
validationResult = this.validateSpecOperationId();
result.warnings.push(...validationResult.warnings);
}
return result;
}
validateAPI(method, path) {
const result = { isValid: true, reason: [] };
method = method.toLocaleLowerCase();
// validate method and path
const methodAndPathResult = this.validateMethodAndPath(method, path);
if (!methodAndPathResult.isValid) {
return methodAndPathResult;
}
const operationObject = this.spec.paths[path][method];
// validate auth
const authCheckResult = this.validateAuth(method, path);
result.reason.push(...authCheckResult.reason);
// validate operationId
if (!this.options.allowMissingId && !operationObject.operationId) {
result.reason.push(exports.ErrorType.MissingOperationId);
}
// validate server
const validateServerResult = this.validateServer(method, path);
result.reason.push(...validateServerResult.reason);
// validate response
const validateResponseResult = this.validateResponse(method, path);
result.reason.push(...validateResponseResult.reason);
let postBodyResult = {
requiredNum: 0,
optionalNum: 0,
isValid: true,
reason: [],
};
// validate requestBody
const requestBody = operationObject.requestBody;
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
if (Utils.containMultipleMediaTypes(requestBody)) {
result.reason.push(exports.ErrorType.PostBodyContainMultipleMediaTypes);
}
if (requestJsonBody) {
const requestBodySchema = requestJsonBody.schema;
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
result.reason.push(...postBodyResult.reason);
}
// validate parameters
const paramObject = operationObject.parameters;
const paramResult = this.checkParamSchema(paramObject);
result.reason.push(...paramResult.reason);
// validate total parameters count
if (paramResult.isValid && postBodyResult.isValid) {
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
result.reason.push(...paramCountResult.reason);
}
if (result.reason.length > 0) {
result.isValid = false;
}
return result;
}
validateParamCount(postBodyResult, paramResult) {
const result = { isValid: true, reason: [] };
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
if (totalRequiredParams > 1) {
if (!this.options.allowMultipleParameters ||
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
result.reason.push(exports.ErrorType.ExceededRequiredParamsLimit);
}
}
else if (totalParams === 0) {
result.reason.push(exports.ErrorType.NoParameter);
}
return result;
}
}
SMEValidator.SMERequiredParamsMaxNum = 5;
// Copyright (c) Microsoft Corporation.
class TeamsAIValidator extends Validator {
constructor(spec, options) {
super();
this.projectType = exports.ProjectType.TeamsAi;
this.options = options;
this.spec = spec;
}
validateSpec() {
const result = { errors: [], warnings: [] };
// validate spec server
let validationResult = this.validateSpecServer();
result.errors.push(...validationResult.errors);
// validate no supported API
validationResult = this.validateSpecNoSupportAPI();
result.errors.push(...validationResult.errors);
return result;
}
validateAPI(method, path) {
const result = { isValid: true, reason: [] };
method = method.toLocaleLowerCase();
// validate method and path
const methodAndPathResult = this.validateMethodAndPath(method, path);
if (!methodAndPathResult.isValid) {
return methodAndPathResult;
}
const operationObject = this.spec.paths[path][method];
// validate operationId
if (!this.options.allowMissingId && !operationObject.operationId) {
result.reason.push(exports.ErrorType.MissingOperationId);
}
// validate server
const validateServerResult = this.validateServer(method, path);
result.reason.push(...validateServerResult.reason);
if (result.reason.length > 0) {
result.isValid = false;
}
return result;
}
}
class ValidatorFactory {
static create(spec, options) {
var _a;
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : exports.ProjectType.SME;
switch (type) {
case exports.ProjectType.SME:
return new SMEValidator(spec, options);
case exports.ProjectType.Copilot:
return new CopilotValidator(spec, options);
case exports.ProjectType.TeamsAi:
return new TeamsAIValidator(spec, options);
default:
throw new Error(`Invalid project type: ${type}`);
}
}
}
// Copyright (c) Microsoft Corporation.
class SpecFilter {
static specFilter(filter, unResolveSpec, resolvedSpec, options) {
var _a;
try {
const newSpec = Object.assign({}, unResolveSpec);
const newPaths = {};
for (const filterItem of filter) {
const [method, path] = filterItem.split(" ");
const methodName = method.toLowerCase();
const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
if (ConstantString.AllOperationMethods.includes(methodName) &&
pathObj &&
pathObj[methodName]) {
const validator = ValidatorFactory.create(resolvedSpec, options);
const validateResult = validator.validateAPI(methodName, path);
if (!validateResult.isValid) {
continue;
}
if (!newPaths[path]) {
newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
for (const m of ConstantString.AllOperationMethods) {
delete newPaths[path][m];
}
}
newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
// Add the operationId if missing
if (!newPaths[path][methodName].operationId) {
newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
}
}
}
newSpec.paths = newPaths;
return newSpec;
}
catch (err) {
throw new SpecParserError(err.toString(), exports.ErrorType.FilterSpecFailed);
}
}
}
// Copyright (c) Microsoft Corporation.
class AdaptiveCardGenerator {
static generateAdaptiveCard(operationItem) {
try {
const { json } = Utils.getResponseJson(operationItem);
let cardBody = [];
let schema = json.schema;
let jsonPath = "$";
if (schema && Object.keys(schema).length > 0) {
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
if (jsonPath !== "$") {
schema = schema.properties[jsonPath];
}
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
}
// if no schema, try to use example value
if (cardBody.length === 0 && (json.examples || json.example)) {
cardBody = [
{
type: ConstantString.TextBlockType,
text: "${jsonStringify($root)}",
wrap: true,
},
];
}
// if no example value, use default success response
if (cardBody.length === 0) {
cardBody = [
{
type: ConstantString.TextBlockType,
text: "success",
wrap: true,
},
];
}
const fullCard = {
type: ConstantString.AdaptiveCardType,
$schema: ConstantString.AdaptiveCardSchema,
version: ConstantString.AdaptiveCardVersion,
body: cardBody,
};
return [fullCard, jsonPath];
}
catch (err) {
throw new SpecParserError(err.toString(), exports.ErrorType.GenerateAdaptiveCardFailed);
}
}
static generateCardFromResponse(schema, name, parentArrayName = "") {
if (schema.type === "array") {
// schema.items can be arbitrary object: schema { type: array, items: {} }
if (Object.keys(schema.items).length === 0) {
return [
{
type: ConstantString.TextBlockType,
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
wrap: true,
},
];
}
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
const template = {
type: ConstantString.ContainerType,
$data: name ? `\${${name}}` : "${$root}",
items: Array(),
};
template.items.push(...obj);
return [template];
}
// some schema may not contain type but contain properties
if (schema.type === "object" || (!schema.type && schema.properties)) {
const { properties } = schema;
const result = [];
for (const property in properties) {
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
result.push(...obj);
}
if (schema.additionalProperties) {
// TODO: better ways to handler warnings.
console.warn(ConstantString.AdditionalPropertiesNotSupported);
}
return result;
}
if (schema.type === "string" ||
schema.type === "integer" ||
schema.type === "boolean" ||
schema.type === "number") {
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
// string in root: "ddd"
let text = "result: ${$root}";
if (name) {
// object { id: "1" }
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
if (parentArrayName) {
// object types inside array: { tags: ["id": 1, "name": "name"] }
text = `${parentArrayName}.${text}`;
}
}
else if (parentArrayName) {
// string array: photoUrls: ["1", "2"]
text = `${parentArrayName}: ` + "${$data}";
}
return [
{
type: ConstantString.TextBlockType,
text,
wrap: true,
},
];
}
else {
if (name) {
return [
{
type: "Image",
url: `\${${name}}`,
$when: `\${${name} != null}`,
},
];
}
else {
return [
{
type: "Image",
url: "${$data}",
$when: "${$data != null}",
},
];
}
}
}
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
}
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
}
// Find the first array property in the response schema object with the well-known name
static getResponseJsonPathFromSchema(schema) {
if (schema.type === "object" || (!schema.type && schema.properties)) {
const { properties } = schema;
for (const property in properties) {
const schema = properties[property];
if (schema.type === "array" &&
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
return property;
}
}
}
return "$";
}
static isImageUrlProperty(schema, name, parentArrayName) {
const propertyName = name ? name : parentArrayName;
return (!!propertyName &&
schema.type === "string" &&
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
}
}
// Copyright (c) Microsoft Corporation.
function wrapAdaptiveCard(card, jsonPath) {
const result = {
version: ConstantString.WrappedCardVersion,
$schema: ConstantString.WrappedCardSchema,
jsonPath: jsonPath,
responseLayout: ConstantString.WrappedCardResponseLayout,
responseCardTemplate: card,
previewCardTemplate: inferPreviewCardTemplate(card),
};
return result;
}
function wrapResponseSemantics(card, jsonPath) {
const props = inferProperties(card);
const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
const result = {
data_path: dataPath,
};
if (props.title || props.subtitle || props.imageUrl) {
result.properties = {};
if (props.title) {
result.properties.title = "$." + props.title;
}
if (props.subtitle) {
result.properties.subtitle = "$." + props.subtitle;
}
if (props.imageUrl) {
result.properties.url = "$." + props.imageUrl;
}
}
result.static_template = card;
return result;
}
/**
* Infers the preview card template from an Adaptive Card and a JSON path.
* The preview card template includes a title and an optional subtitle and image.
* It populates the preview card template with the first text block that matches
* each well-known name, in the order of title, subtitle, and image.
* If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
* If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
* @param card The Adaptive Card to infer the preview card template from.
* @param jsonPath The JSON path to the root object in the card body.
* @returns The inferred preview card template.
*/
function inferPreviewCardTemplate(card) {
const result = {
title: "result",
};
const inferredProperties = inferProperties(card);
if (inferredProperties.title) {
result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
}
if (inferredProperties.subtitle) {
result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
}
if (inferredProperties.imageUrl) {
result.image = {
url: `\${${inferredProperties.imageUrl}}`,
alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
$when: `\${${inferredProperties.imageUrl} != null}`,
};
}
return result;
}
function inferProperties(card) {
var _a;
const result = {};
const nameSet = new Set();
let rootObject;
if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
rootObject = card.body[0].items;
}
else {
rootObject = card.body;
}
for (const element of rootObject) {
if (element.type === ConstantString.TextBlockType) {
const textElement = element;
const index = textElement.text.indexOf("${if(");
if (index > 0) {
const text = textElement.text.substring(index);
const match = text.match(/\${if\(([^,]+),/);
const property = match ? match[1] : "";
if (property) {
nameSet.add(property);
}
}
}
else if (element.type === ConstantString.ImageType) {
const imageElement = element;
const match = imageElement.url.match(/\${([^,]+)}/);
const property = match ? match[1] : "";
if (property) {
nameSet.add(property);
}
}
}
for (const name of nameSet) {
if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
result.title = name;
nameSet.delete(name);
}
else if (!result.subtitle &&
Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
result.subtitle = name;
nameSet.delete(name);
}
else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
result.imageUrl = name;
nameSet.delete(name);
}
}
for (const name of nameSet) {
if (!result.title) {
result.title = name;
nameSet.delete(name);
}
else if (!result.subtitle) {
result.subtitle = name;
nameSet.delete(name);
}
}
if (!result.title && result.subtitle) {
result.title = result.subtitle;
delete result.subtitle;
}
return result;
}
// Copyright (c) Microsoft Corporation.
class ManifestUpdater {
static updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo) {
return __awaiter(this, void 0, void 0, function* () {
const manifest = yield fs__default['default'].readJSON(manifestPath);
const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
manifest.copilotExtensions = manifest.copilotExtensions || {};
// Insert plugins in manifest.json if it is plugin for Copilot.
if (!options.isGptPlugin) {
manifest.copilotExtensions.plugins = [
{
file: apiPluginRelativePath,
id: ConstantString.DefaultPluginId,
},
];
ManifestUpdater.updateManifestDescription(manifest, spec);
}
const appName = this.removeEnvs(manifest.name.short);
const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
const [apiPlugin, warnings] = yield ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options);
return [manifest, apiPlugin, warnings];
});
}
static updateManifestDescription(manifest, spec) {
var _a, _b;
manifest.description = {
short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
full: (_b = ((_a = spec.info.description) !== null && _a !== void 0 ? _a : manifest.description.full)) === null || _b === void 0 ? void 0 : _b.slice(0, ConstantString.FullDescriptionMaxLens),
};
}
static checkSchema(schema, method, pathUrl) {
if (schema.type === "array") {
const items = schema.items;
ManifestUpdater.checkSchema(items, method, pathUrl);
}
else if (schema.type !== "string" &&
schema.type !== "boolean" &&
schema.type !== "integer" &&
schema.type !== "number") {
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), exports.ErrorType.UpdateManifestFailed);
}
}
static generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options) {
var _a, _b, _c, _d;
return __awaiter(this, void 0, void 0, function* () {
const warnings = [];
const functions = [];
const functionNames = [];
const conversationStarters = [];
const paths = spec.paths;
const pluginAuthObj = {
type: "None",
};
if (authInfo) {
if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
pluginAuthObj.type = "OAuthPluginVault";
}
else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
pluginAuthObj.type = "ApiKeyPluginVault";
}
if (pluginAuthObj.type !== "None") {
const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
pluginAuthObj.reference_id = `\${{${safeRegistrationIdName}}}`;
}
}
for (const pathUrl in paths) {
const pathItem = paths[pathUrl];
if (pathItem) {
const operations = pathItem;
for (const method in operations) {
if (options.allowMethods.includes(method)) {
const operationItem = operations[method];
const confirmationBodies = [];
if (operationItem) {
const operationId = operationItem.operationId;
const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
const summary = operationItem.summary;
const paramObject = operationItem.parameters;
const requestBody = operationItem.requestBody;
if (paramObject) {
for (let i = 0; i < paramObject.length; i++) {
const param = paramObject[i];
const schema = param.schema;
ManifestUpdater.checkSchema(schema, method, pathUrl);
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(param.name));
}
}
if (requestBody) {
const requestJsonBody = requestBody.content["application/json"];
const requestBodySchema = requestJsonBody.schema;
if (requestBodySchema.type === "object") {
for (const property in requestBodySchema.properties) {
const schema = requestBodySchema.properties[property];
ManifestUpdater.checkSchema(schema, method, pathUrl);
confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(property));
}
}
else {
throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), exports.ErrorType.UpdateManifestFailed);
}
}
const funcObj = {
name: operationId,
description: description,
};
if (options.allowResponseSemantics) {
try {
const { json } = Utils.getResponseJson(operationItem);
if (json.schema) {
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
card.body = card.body.slice(0, 5);
const responseSemantic = wrapResponseSemantics(card, jsonPath);
funcObj.capabilities = {
response_semantics: responseSemantic,
};
}
}
catch (err) {
warnings.push({
type: exports.WarningType.GenerateCardFailed,
content: err.toString(),
data: operationId,
});
}
}
if (options.allowConfirmation && method !== ConstantString.GetMethod) {
if (!funcObj.capabilities) {
funcObj.capabilities = {};
}
funcObj.capabilities.confirmation = {
type: "AdaptiveCard",
title: (_b = operationItem.summary) !== null && _b !== void 0 ? _b : description,
};
if (confirmationBodies.length > 0) {
funcObj.capabilities.confirmation.body = confirmationBodies.join("\n");
}
}
functions.push(funcObj);
functionNames.push(operationId);
const conversationStarterStr = (summary !== null && summary !== void 0 ? summary : description).slice(0, ConstantString.ConversationStarterMaxLens);
if (conversationStarterStr) {
conversationStarters.push(conversationStarterStr);
}
}
}
}
}
}
let apiPlugin;
if (yield fs__default['default'].pathExists(apiPluginFilePath)) {
apiPlugin = yield fs__default['default'].readJSON(apiPluginFilePath);
}
else {
apiPlugin = {
$schema: ConstantString.PluginManifestSchema,
schema_version: "v2.1",
name_for_human: "",
description_for_human: "",
namespace: "",
functions: [],
runtimes: [],
};
}
apiPlugin.functions = apiPlugin.functions || [];
for (const func of functions) {
const index = (_c = apiPlugin.functions) === null || _c === void 0 ? void 0 : _c.findIndex((f) => f.name === func.name);
if (index === -1) {
apiPlugin.functions.push(func);
}
else {
apiPlugin.functions[index] = func;
}
}
apiPlugin.runtimes = apiPlugin.runtimes || [];
const index = apiPlugin.runtimes.findIndex((runtime) => {
var _a, _b;
return runtime.spec.url === specRelativePath &&
runtime.type === "OpenApi" &&
((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
});
if (index === -1) {
apiPlugin.runtimes.push({
type: "OpenApi",
auth: pluginAuthObj,
spec: {
url: specRelativePath,
},
run_for_functions: functionNames,
});
}
else {
apiPlugin.runtimes[index].run_for_functions = functionNames;
}
if (!apiPlugin.name_for_human) {
apiPlugin.name_for_human = appName;
}
if (!apiPlugin.namespace) {
apiPlugin.namespace = ManifestUpdater.removeAllSpecialCharacters(appName);
}
if (!apiPlugin.description_for_human) {
apiPlugin.description_for_human =
(_d = spec.info.description) !== null && _d !== void 0 ? _d : "<Please add description of the plugin>";
}
if (options.allowConversationStarters && conversationStarters.length > 0) {
if (!apiPlugin.capabilities) {
apiPlugin.capabilities = {
localization: {},
};
}
if (!apiPlugin.capabilities.conversation_starters) {
apiPlugin.capabilities.conversation_starters = conversationStarters
.slice(0, 5)
.map((text) => ({ text }));
}
}
return [apiPlugin, warnings];
});
}
static updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
return __awaiter(this, void 0, void 0, function* () {
try {
const originalManifest = yield fs__default['default'].readJSON(manifestPath);
const updatedPart = {};
updatedPart.composeExtensions = [];
let warnings = [];
if (options.projectType === exports.ProjectType.SME) {
const updateResult = yield ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
const commands = updateResult[0];
warnings = updateResult[1];
const composeExtension = {
composeExtensionType: "apiBased",
apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
commands: commands,
};
if (authInfo) {
const auth = authInfo.authScheme;
const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
composeExtension.authorization = {
authType: "apiSecretServiceAuth",
apiSecretServiceAuthConfiguration: {
apiSecretRegistrationId: `\${{${safeRegistrationIdName}}}`,
},
};
}
else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
composeExtension.authorization = {
authType: "oAuth2.0",
oAuthConfiguration: {
oauthConfigurationId: `\${{${safeRegistrationIdName}}}`,
},
};
updatedPart.webApplicationInfo = {
id: "${{AAD_APP_CLIENT_ID}}",
resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
};
}
}
updatedPart.composeExtensions = [composeExtension];
}
updatedPart.description = originalManifest.description;
ManifestUpdater.updateManifestDescription(updatedPart, spec);
const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
return [updatedManifest, warnings];
}
catch (err) {
throw new SpecParserError(err.toString(), exports.ErrorType.UpdateManifestFailed);
}
});
}
static generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const paths = spec.paths;
const commands = [];
const warnings = [];
if (paths) {
for (const pathUrl in paths) {
const pathItem = paths[pathUrl];
if (pathItem) {
const operations = pathItem;
// Currently only support GET and POST method
for (const method in operations) {
if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
const operationItem = operations[method];
if (operationItem) {
const command = Utils.parseApiInfo(operationItem, options);
if (command.parameters &&
command.parameters.length >= 1 &&
command.parameters.some((param) => param.isRequired)) {
command.parameters = command.parameters.filter((param) => param.isRequired);
}
else if (command.parameters && command.parameters.length > 0) {
command.parameters = [command.parameters[0]];
warnings.push({
type: exports.WarningType.OperationOnlyContainsOptionalParam,
content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
data: command.id,
});
}
if (adaptiveCardFolder) {
const adaptiveCardPath = path__default['default'].join(adaptiveCardFolder, command.id + ".json");
command.apiResponseRenderingTemplateFile = (yield fs__default['default'].pathExists(adaptiveCardPath))
? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
: "";
}
commands.push(command);
}
}
}
}
}
}
return [commands, warnings];
});
}
static getRelativePath(from, to) {
const relativePath = path__default['default'].relative(path__default['default'].dirname(from), to);
return path__default['default'].normalize(relativePath).replace(/\\/g, "/");
}
static removeEnvs(str) {
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
const matches = placeHolderReg.exec(str);
let newStr = str;
if (matches != null) {
newStr = newStr.replace(matches[0], "");
}
return newStr;
}
static removeAllSpecialCharacters(str) {
return str.toLowerCase().replace(/[^a-z0-9]/g, "");
}
static getConfirmationBodyItem(paramName) {
return `* **${Utils.updateFirstLetter(paramName)}**: {{function.parameters.${paramName}}}`;
}
}
// Copyright (c) Microsoft Corporation.
/**
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
*/
class SpecParser {
/**
* Creates a new instance of the SpecParser class.
* @param pathOrDoc The path to the OpenAPI specification file or the OpenAPI specification object.
* @param options The options for parsing the OpenAPI specification file.
*/
constructor(pathOrDoc, options) {
this.defaultOptions = {
allowMissingId: true,
allowSwagger: true,
allowAPIKeyAuth: false,
allowBearerTokenAuth: false,
allowMultipleParameters: false,
allowOauth2: false,
allowMethods: ["get", "post"],
allowConversationStarters: false,
allowResponseSemantics: false,
allowConfirmation: false,
projectType: exports.ProjectType.SME,
isGptPlugin: false,
};
this.pathOrSpec = pathOrDoc;
this.parser = new SwaggerParser__default['default']();
this.options = Object.assign(Object.assign({}, this.defaultOptions), (options !== null && options !== void 0 ? options : {}));
}
/**
* Validates the OpenAPI specification file and returns a validation result.
*
* @returns A validation result object that contains information about any errors or warnings in the specification file.
*/
validate() {
return __awaiter(this, void 0, void 0, function* () {
try {
try {
yield this.loadSpec();
yield this.parser.validate(this.spec);
}
catch (e) {
return {
status: exports.ValidationStatus.Error,
warnings: [],
errors: [{ type: exports.ErrorType.SpecNotValid, content: e.toString() }],
};
}
const errors = [];
const warnings = [];
if (!this.options.allowSwagger && this.isSwaggerFile) {
return {
status: exports.ValidationStatus.Error,
warnings: [],
errors: [
{ type: exports.ErrorType.SwaggerNotSupported, content: ConstantString.SwaggerNotSupported },
],
};
}
// Remote reference not supported
const refPaths = this.parser.$refs.paths();
// refPaths [0] is the current spec file path
if (refPaths.length > 1) {
errors.push({
type: exports.ErrorType.RemoteRefNotSupported,
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
data: refPaths,
});
}
if (!!this.isSwaggerFile && this.options.allowSwagger) {
warnings.push({
type: exports.WarningType.ConvertSwaggerToOpenAPI,
content: ConstantString.ConvertSwaggerToOpenAPI,
});
}
const validator = this.getValidator(this.spec);
const validationResult = validator.validateSpec();
warnings.push(...validationResult.warnings);
errors.push(...validationResult.errors);
let status = exports.ValidationStatus.Valid;
if (warnings.length > 0 && errors.length === 0) {
status = exports.ValidationStatus.Warning;
}
else if (errors.length > 0) {
status = exports.ValidationStatus.Error;
}
return {
status: status,
warnings: warnings,
errors: errors,
};
}
catch (err) {
throw new SpecParserError(err.toString(), exports.ErrorType.ValidateFailed);
}
});
}
// eslint-disable-next-line @typescript-eslint/require-await
listSupportedAPIInfo() {
return __awaiter(this, void 0, void 0, function* () {
throw new Error("Method not implemented.");
});
}
/**
* Lists all the OpenAPI operations in the specification file.
* @returns A string array that represents the HTTP method and path of each operation, such as ['GET /pets/{petId}', 'GET /user/{userId}']
* according to copilot plugin spec, only list get and post method without auth
*/
list() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.loadSpec();
const spec = this.spec;
const apiMap = this.getAPIs(spec);
const result = {
APIs: [],
allAPICount: 0,
validAPICount: 0,
};
for (const apiKey in apiMap) {
const { operation, isValid, reason } = apiMap[apiKey];
const [method, path] = apiKey.split(" ");
const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
const apiResult = {
api: apiKey,
server: "",
operationId: operationId,
isValid: isValid,
reason: reason,
};
if (isValid) {
const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
if (serverObj) {
apiResult.server = Utils.resolveEnv(serverObj.url);
}
const authArray = Utils.getAuthArray(operation.security, spec);
for (const auths of authArray) {
if (auths.length === 1) {
apiResult.auth = auths[0];
break;
}
}
}
result.APIs.push(apiResult);
}
result.allAPICount = result.APIs.length;
result.validAPICount = result.APIs.filter((api) => api.isValid).length;
return result;
}
catch (err) {
if (err instanceof SpecParserError) {
throw err;
}
throw new SpecParserError(err.toString(), exports.ErrorType.ListFailed);
}
});
}
/**
* Generate specs according to the filters.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
*/
getFilteredSpecs(filter, signal) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
}
yield this.loadSpec();
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
}
const newUnResolvedSpec = SpecFilter.specFilter(filter, this.unResolveSpec, this.spec, this.options);
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
}
const newSpec = (yield this.parser.dereference(newUnResolvedSpec));
return [newUnResolvedSpec, newSpec];
}
catch (err) {
if (err instanceof SpecParserError) {
throw err;
}
throw new SpecParserError(err.toString(), exports.ErrorType.GetSpecFailed);
}
});
}
/**
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
* @param manifestPath A file path of the Teams app manifest file to update.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
* @param pluginFilePath File path of the api plugin file to generate.
*/
generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
return __awaiter(this, void 0, void 0, function* () {
const result = {
allSuccess: true,
warnings: [],
};
try {
const newSpecs = yield this.getFilteredSpecs(filter, signal);
const newUnResolvedSpec = newSpecs[0];
const newSpec = newSpecs[1];
const authInfo = Utils.getAuthInfo(newSpec);
yield this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
}
const [updatedManifest, apiPlugin, warnings] = yield ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
result.warnings.push(...warnings);
yield fs__default['default'].outputJSON(manifestPath, updatedManifest, { spaces: 4 });
yield fs__default['default'].outputJSON(pluginFilePath, apiPlugin, { spaces: 4 });
}
catch (err) {
if (err instanceof SpecParserError) {
throw err;
}
throw new SpecParserError(err.toString(), exports.ErrorType.GenerateFailed);
}
return result;
});
}
/**
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
* @param manifestPath A file path of the Teams app manifest file to update.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
*/
generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
return __awaiter(this, void 0, void 0, function* () {
const result = {
allSuccess: true,
warnings: [],
};
try {
const newSpecs = yield this.getFilteredSpecs(filter, signal);
const newUnResolvedSpec = newSpecs[0];
const newSpec = newSpecs[1];
let authInfo = undefined;
if (this.options.projectType === exports.ProjectType.SME) {
authInfo = Utils.getAuthInfo(newSpec);
}
yield this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
if (adaptiveCardFolder) {
for (const url in newSpec.paths) {
for (const method in newSpec.paths[url]) {
// paths object may contain description/summary which is not a http method, so we need to check if it is a operation object
if (this.options.allowMethods.includes(method)) {
const operation = newSpec.paths[url][method];
try {
const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operation);
const fileName = path__default['default'].join(adaptiveCardFolder, `${operation.operationId}.json`);
const wrappedCard = wrapAdaptiveCard(card, jsonPath);
yield fs__default['default'].outputJSON(fileName, wrappedCard, { spaces: 2 });
const dataFileName = path__default['default'].join(adaptiveCardFolder, `${operation.operationId}.data.json`);
yield fs__default['default'].outputJSON(dataFileName, {}, { spaces: 2 });
}
catch (err) {
result.allSuccess = false;
result.warnings.push({
type: exports.WarningType.GenerateCardFailed,
content: err.toString(),
data: operation.operationId,
});
}
}
}
}
}
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
throw new SpecParserError(ConstantString.CancelledMessage, exports.ErrorType.Cancelled);
}
const [updatedManifest, warnings] = yield ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
yield fs__default['default'].outputJSON(manifestPath, updatedManifest, { spaces: 2 });
result.warnings.push(...warnings);
}
catch (err) {
if (err instanceof SpecParserError) {
throw err;
}
throw new SpecParserError(err.toString(), exports.ErrorType.GenerateFailed);
}
return result;
});
}
loadSpec() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.spec) {
this.unResolveSpec = (yield this.parser.parse(this.pathOrSpec));
// Convert swagger 2.0 to openapi 3.0
if (!this.unResolveSpec.openapi && this.unResolveSpec.swagger === "2.0") {
const specObj = yield converter__default['default'].convert(this.unResolveSpec, {});
this.unResolveSpec = specObj.openapi;
this.isSwaggerFile = true;
}
const clonedUnResolveSpec = JSON.parse(JSON.stringify(this.unResolveSpec));
this.spec = (yield this.parser.dereference(clonedUnResolveSpec));
}
});
}
getAPIs(spec) {
const validator = this.getValidator(spec);
const apiMap = validator.listAPIs();
return apiMap;
}
getValidator(spec) {
if (this.validator) {
return this.validator;
}
const validator = ValidatorFactory.create(spec, this.options);
this.validator = validator;
return validator;
}
saveFilterSpec(outputSpecPath, unResolvedSpec) {
return __awaiter(this, void 0, void 0, function* () {
let resultStr;
if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
resultStr = jsyaml__default['default'].dump(unResolvedSpec);
}
else {
resultStr = JSON.stringify(unResolvedSpec, null, 2);
}
yield fs__default['default'].outputFile(outputSpecPath, resultStr);
});
}
}
exports.AdaptiveCardGenerator = AdaptiveCardGenerator;
exports.ConstantString = ConstantString;
exports.SpecParser = SpecParser;
exports.SpecParserError = SpecParserError;
exports.Utils = Utils;
//# sourceMappingURL=index.node.cjs.js.map