@microsoft/m365-spec-parser 0.1.1-alpha.2f5decfcc.0 → 0.1.1-alpha.3bb446445.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,7 @@ var ErrorType;
20
20
  ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
21
21
  ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
22
22
  ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
23
+ ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
23
24
  ErrorType["ListFailed"] = "list-failed";
24
25
  ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
25
26
  ErrorType["FilterSpecFailed"] = "filter-spec-failed";
@@ -28,6 +29,21 @@ var ErrorType;
28
29
  ErrorType["GenerateFailed"] = "generate-failed";
29
30
  ErrorType["ValidateFailed"] = "validate-failed";
30
31
  ErrorType["GetSpecFailed"] = "get-spec-failed";
32
+ ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
33
+ ErrorType["MissingOperationId"] = "missing-operation-id";
34
+ ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
35
+ ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
36
+ ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
37
+ ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
38
+ ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
39
+ ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
40
+ ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
41
+ ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
42
+ ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
43
+ ErrorType["NoParameter"] = "no-parameter";
44
+ ErrorType["NoAPIInfo"] = "no-api-info";
45
+ ErrorType["MethodNotAllowed"] = "method-not-allowed";
46
+ ErrorType["UrlPathNotExist"] = "url-path-not-exist";
31
47
  ErrorType["Cancelled"] = "cancelled";
32
48
  ErrorType["Unknown"] = "unknown";
33
49
  })(ErrorType || (ErrorType = {}));
@@ -67,7 +83,7 @@ ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
67
83
  ConstantString.MissingOperationId = "Missing operationIds: %s.";
68
84
  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.";
69
85
  ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
70
- ConstantString.SchemaNotSupported = "'oneOf', 'anyOf', and 'not' schema are not supported: %s.";
86
+ ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
71
87
  ConstantString.UnknownSchema = "Unknown schema: %s.";
72
88
  ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
73
89
  ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
@@ -75,6 +91,7 @@ ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: pleas
75
91
  ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
76
92
  ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
77
93
  ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
94
+ ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
78
95
  ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
79
96
  ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
80
97
  ConstantString.WrappedCardVersion = "devPreview";
@@ -86,9 +103,14 @@ ConstantString.AdaptiveCardVersion = "1.5";
86
103
  ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
87
104
  ConstantString.AdaptiveCardType = "AdaptiveCard";
88
105
  ConstantString.TextBlockType = "TextBlock";
106
+ ConstantString.ImageType = "Image";
89
107
  ConstantString.ContainerType = "Container";
90
- ConstantString.RegistrationIdPostfix = "REGISTRATION_ID";
91
- ConstantString.OAuthRegistrationIdPostFix = "OAUTH_REGISTRATION_ID";
108
+ ConstantString.RegistrationIdPostfix = {
109
+ apiKey: "REGISTRATION_ID",
110
+ oauth2: "CONFIGURATION_ID",
111
+ http: "REGISTRATION_ID",
112
+ openIdConnect: "REGISTRATION_ID",
113
+ };
92
114
  ConstantString.ResponseCodeFor20X = [
93
115
  "200",
94
116
  "201",
@@ -147,9 +169,11 @@ ConstantString.ShortDescriptionMaxLens = 80;
147
169
  ConstantString.FullDescriptionMaxLens = 4000;
148
170
  ConstantString.CommandDescriptionMaxLens = 128;
149
171
  ConstantString.ParameterDescriptionMaxLens = 128;
172
+ ConstantString.ConversationStarterMaxLens = 50;
150
173
  ConstantString.CommandTitleMaxLens = 32;
151
174
  ConstantString.ParameterTitleMaxLens = 32;
152
- ConstantString.SMERequiredParamsMaxNum = 5;
175
+ ConstantString.SMERequiredParamsMaxNum = 5;
176
+ ConstantString.DefaultPluginId = "plugin_1";
153
177
 
154
178
  // Copyright (c) Microsoft Corporation.
155
179
  class SpecParserError extends Error {
@@ -172,221 +196,9 @@ class Utils {
172
196
  }
173
197
  return false;
174
198
  }
175
- static checkParameters(paramObject, isCopilot) {
176
- const paramResult = {
177
- requiredNum: 0,
178
- optionalNum: 0,
179
- isValid: true,
180
- };
181
- if (!paramObject) {
182
- return paramResult;
183
- }
184
- for (let i = 0; i < paramObject.length; i++) {
185
- const param = paramObject[i];
186
- const schema = param.schema;
187
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
188
- paramResult.isValid = false;
189
- continue;
190
- }
191
- const isRequiredWithoutDefault = param.required && schema.default === undefined;
192
- if (isCopilot) {
193
- if (isRequiredWithoutDefault) {
194
- paramResult.requiredNum = paramResult.requiredNum + 1;
195
- }
196
- else {
197
- paramResult.optionalNum = paramResult.optionalNum + 1;
198
- }
199
- continue;
200
- }
201
- if (param.in === "header" || param.in === "cookie") {
202
- if (isRequiredWithoutDefault) {
203
- paramResult.isValid = false;
204
- }
205
- continue;
206
- }
207
- if (schema.type !== "boolean" &&
208
- schema.type !== "string" &&
209
- schema.type !== "number" &&
210
- schema.type !== "integer") {
211
- if (isRequiredWithoutDefault) {
212
- paramResult.isValid = false;
213
- }
214
- continue;
215
- }
216
- if (param.in === "query" || param.in === "path") {
217
- if (isRequiredWithoutDefault) {
218
- paramResult.requiredNum = paramResult.requiredNum + 1;
219
- }
220
- else {
221
- paramResult.optionalNum = paramResult.optionalNum + 1;
222
- }
223
- }
224
- }
225
- return paramResult;
226
- }
227
- static checkPostBody(schema, isRequired = false, isCopilot = false) {
228
- var _a;
229
- const paramResult = {
230
- requiredNum: 0,
231
- optionalNum: 0,
232
- isValid: true,
233
- };
234
- if (Object.keys(schema).length === 0) {
235
- return paramResult;
236
- }
237
- const isRequiredWithoutDefault = isRequired && schema.default === undefined;
238
- if (isCopilot && this.hasNestedObjectInSchema(schema)) {
239
- paramResult.isValid = false;
240
- return paramResult;
241
- }
242
- if (schema.type === "string" ||
243
- schema.type === "integer" ||
244
- schema.type === "boolean" ||
245
- schema.type === "number") {
246
- if (isRequiredWithoutDefault) {
247
- paramResult.requiredNum = paramResult.requiredNum + 1;
248
- }
249
- else {
250
- paramResult.optionalNum = paramResult.optionalNum + 1;
251
- }
252
- }
253
- else if (schema.type === "object") {
254
- const { properties } = schema;
255
- for (const property in properties) {
256
- let isRequired = false;
257
- if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
258
- isRequired = true;
259
- }
260
- const result = Utils.checkPostBody(properties[property], isRequired, isCopilot);
261
- paramResult.requiredNum += result.requiredNum;
262
- paramResult.optionalNum += result.optionalNum;
263
- paramResult.isValid = paramResult.isValid && result.isValid;
264
- }
265
- }
266
- else {
267
- if (isRequiredWithoutDefault && !isCopilot) {
268
- paramResult.isValid = false;
269
- }
270
- }
271
- return paramResult;
272
- }
273
199
  static containMultipleMediaTypes(bodyObject) {
274
200
  return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
275
201
  }
276
- /**
277
- * Checks if the given API is supported.
278
- * @param {string} method - The HTTP method of the API.
279
- * @param {string} path - The path of the API.
280
- * @param {OpenAPIV3.Document} spec - The OpenAPI specification document.
281
- * @returns {boolean} - Returns true if the API is supported, false otherwise.
282
- * @description The following APIs are supported:
283
- * 1. only support Get/Post operation without auth property
284
- * 2. parameter inside query or path only support string, number, boolean and integer
285
- * 3. parameter inside post body only support string, number, boolean, integer and object
286
- * 4. request body + required parameters <= 1
287
- * 5. response body should be “application/json” and not empty, and response code should be 20X
288
- * 6. only support request body with “application/json” content type
289
- */
290
- static isSupportedApi(method, path, spec, options) {
291
- var _a;
292
- const pathObj = spec.paths[path];
293
- method = method.toLocaleLowerCase();
294
- if (pathObj) {
295
- if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && pathObj[method]) {
296
- const securities = pathObj[method].security;
297
- const isTeamsAi = options.projectType === ProjectType.TeamsAi;
298
- const isCopilot = options.projectType === ProjectType.Copilot;
299
- // Teams AI project doesn't care about auth, it will use authProvider for user to implement
300
- if (!isTeamsAi) {
301
- const authArray = Utils.getAuthArray(securities, spec);
302
- if (!Utils.isSupportedAuth(authArray, options)) {
303
- return false;
304
- }
305
- }
306
- const operationObject = pathObj[method];
307
- if (!options.allowMissingId && !operationObject.operationId) {
308
- return false;
309
- }
310
- const paramObject = operationObject.parameters;
311
- const requestBody = operationObject.requestBody;
312
- const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
313
- if (!isTeamsAi && Utils.containMultipleMediaTypes(requestBody)) {
314
- return false;
315
- }
316
- const responseJson = Utils.getResponseJson(operationObject, isTeamsAi);
317
- if (Object.keys(responseJson).length === 0) {
318
- return false;
319
- }
320
- // Teams AI project doesn't care about request parameters/body
321
- if (isTeamsAi) {
322
- return true;
323
- }
324
- let requestBodyParamResult = {
325
- requiredNum: 0,
326
- optionalNum: 0,
327
- isValid: true,
328
- };
329
- if (requestJsonBody) {
330
- const requestBodySchema = requestJsonBody.schema;
331
- if (isCopilot && requestBodySchema.type !== "object") {
332
- return false;
333
- }
334
- requestBodyParamResult = Utils.checkPostBody(requestBodySchema, requestBody.required, isCopilot);
335
- }
336
- if (!requestBodyParamResult.isValid) {
337
- return false;
338
- }
339
- const paramResult = Utils.checkParameters(paramObject, isCopilot);
340
- if (!paramResult.isValid) {
341
- return false;
342
- }
343
- // Copilot support arbitrary parameters
344
- if (isCopilot) {
345
- return true;
346
- }
347
- if (requestBodyParamResult.requiredNum + paramResult.requiredNum > 1) {
348
- if (options.allowMultipleParameters &&
349
- requestBodyParamResult.requiredNum + paramResult.requiredNum <=
350
- ConstantString.SMERequiredParamsMaxNum) {
351
- return true;
352
- }
353
- return false;
354
- }
355
- else if (requestBodyParamResult.requiredNum +
356
- requestBodyParamResult.optionalNum +
357
- paramResult.requiredNum +
358
- paramResult.optionalNum ===
359
- 0) {
360
- return false;
361
- }
362
- else {
363
- return true;
364
- }
365
- }
366
- }
367
- return false;
368
- }
369
- static isSupportedAuth(authSchemeArray, options) {
370
- if (authSchemeArray.length === 0) {
371
- return true;
372
- }
373
- if (options.allowAPIKeyAuth || options.allowOauth2 || options.allowBearerTokenAuth) {
374
- // Currently we don't support multiple auth in one operation
375
- if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
376
- return false;
377
- }
378
- for (const auths of authSchemeArray) {
379
- if (auths.length === 1) {
380
- if ((options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
381
- (options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
382
- (options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
383
- return true;
384
- }
385
- }
386
- }
387
- }
388
- return false;
389
- }
390
202
  static isBearerTokenAuth(authScheme) {
391
203
  return authScheme.type === "http" && authScheme.scheme === "bearer";
392
204
  }
@@ -394,18 +206,18 @@ class Utils {
394
206
  return authScheme.type === "apiKey";
395
207
  }
396
208
  static isOAuthWithAuthCodeFlow(authScheme) {
397
- if (authScheme.type === "oauth2" && authScheme.flows && authScheme.flows.authorizationCode) {
398
- return true;
399
- }
400
- return false;
209
+ return !!(authScheme.type === "oauth2" &&
210
+ authScheme.flows &&
211
+ authScheme.flows.authorizationCode);
401
212
  }
402
213
  static getAuthArray(securities, spec) {
403
214
  var _a;
404
215
  const result = [];
405
216
  const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
406
- if (securities && securitySchemas) {
407
- for (let i = 0; i < securities.length; i++) {
408
- const security = securities[i];
217
+ const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
218
+ if (securitiesArr && securitySchemas) {
219
+ for (let i = 0; i < securitiesArr.length; i++) {
220
+ const security = securitiesArr[i];
409
221
  const authArray = [];
410
222
  for (const name in security) {
411
223
  const auth = securitySchemas[name];
@@ -422,17 +234,39 @@ class Utils {
422
234
  result.sort((a, b) => a[0].name.localeCompare(b[0].name));
423
235
  return result;
424
236
  }
237
+ static getAuthInfo(spec) {
238
+ let authInfo = undefined;
239
+ for (const url in spec.paths) {
240
+ for (const method in spec.paths[url]) {
241
+ const operation = spec.paths[url][method];
242
+ const authArray = Utils.getAuthArray(operation.security, spec);
243
+ if (authArray && authArray.length > 0) {
244
+ const currentAuth = authArray[0][0];
245
+ if (!authInfo) {
246
+ authInfo = authArray[0][0];
247
+ }
248
+ else if (authInfo.name !== currentAuth.name) {
249
+ throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
250
+ }
251
+ }
252
+ }
253
+ }
254
+ return authInfo;
255
+ }
425
256
  static updateFirstLetter(str) {
426
257
  return str.charAt(0).toUpperCase() + str.slice(1);
427
258
  }
428
- static getResponseJson(operationObject, isTeamsAiProject = false) {
259
+ static getResponseJson(operationObject) {
429
260
  var _a, _b;
430
261
  let json = {};
262
+ let multipleMediaType = false;
431
263
  for (const code of ConstantString.ResponseCodeFor20X) {
432
264
  const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
433
265
  if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
266
+ multipleMediaType = false;
434
267
  json = responseObject.content["application/json"];
435
- if (!isTeamsAiProject && Utils.containMultipleMediaTypes(responseObject)) {
268
+ if (Utils.containMultipleMediaTypes(responseObject)) {
269
+ multipleMediaType = true;
436
270
  json = {};
437
271
  }
438
272
  else {
@@ -440,7 +274,7 @@ class Utils {
440
274
  }
441
275
  }
442
276
  }
443
- return json;
277
+ return { json, multipleMediaType };
444
278
  }
445
279
  static convertPathToCamelCase(path) {
446
280
  const pathSegments = path.split(/[./{]/);
@@ -460,10 +294,10 @@ class Utils {
460
294
  return undefined;
461
295
  }
462
296
  }
463
- static resolveServerUrl(url) {
297
+ static resolveEnv(str) {
464
298
  const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
465
- let matches = placeHolderReg.exec(url);
466
- let newUrl = url;
299
+ let matches = placeHolderReg.exec(str);
300
+ let newStr = str;
467
301
  while (matches != null) {
468
302
  const envVar = matches[1];
469
303
  const envVal = process.env[envVar];
@@ -471,17 +305,17 @@ class Utils {
471
305
  throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
472
306
  }
473
307
  else {
474
- newUrl = newUrl.replace(matches[0], envVal);
308
+ newStr = newStr.replace(matches[0], envVal);
475
309
  }
476
- matches = placeHolderReg.exec(url);
310
+ matches = placeHolderReg.exec(str);
477
311
  }
478
- return newUrl;
312
+ return newStr;
479
313
  }
480
314
  static checkServerUrl(servers) {
481
315
  const errors = [];
482
316
  let serverUrl;
483
317
  try {
484
- serverUrl = Utils.resolveServerUrl(servers[0].url);
318
+ serverUrl = Utils.resolveEnv(servers[0].url);
485
319
  }
486
320
  catch (err) {
487
321
  errors.push({
@@ -512,6 +346,7 @@ class Utils {
512
346
  return errors;
513
347
  }
514
348
  static validateServer(spec, options) {
349
+ var _a;
515
350
  const errors = [];
516
351
  let hasTopLevelServers = false;
517
352
  let hasPathLevelServers = false;
@@ -532,7 +367,7 @@ class Utils {
532
367
  }
533
368
  for (const method in methods) {
534
369
  const operationObject = methods[method];
535
- if (Utils.isSupportedApi(method, path, spec, options)) {
370
+ if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
536
371
  if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
537
372
  hasOperationLevelServers = true;
538
373
  const serverErrors = Utils.checkServerUrl(operationObject.servers);
@@ -659,13 +494,7 @@ class Utils {
659
494
  }
660
495
  }
661
496
  const operationId = operationItem.operationId;
662
- const parameters = [];
663
- if (requiredParams.length !== 0) {
664
- parameters.push(...requiredParams);
665
- }
666
- else {
667
- parameters.push(optionalParams[0]);
668
- }
497
+ const parameters = [...requiredParams, ...optionalParams];
669
498
  const command = {
670
499
  context: ["compose"],
671
500
  type: "query",
@@ -674,360 +503,575 @@ class Utils {
674
503
  parameters: parameters,
675
504
  description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
676
505
  };
677
- let warning = undefined;
678
- if (requiredParams.length === 0 && optionalParams.length > 1) {
679
- warning = {
680
- type: WarningType.OperationOnlyContainsOptionalParam,
681
- content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, operationId),
682
- data: operationId,
683
- };
506
+ return command;
507
+ }
508
+ static format(str, ...args) {
509
+ let index = 0;
510
+ return str.replace(/%s/g, () => {
511
+ const arg = args[index++];
512
+ return arg !== undefined ? arg : "";
513
+ });
514
+ }
515
+ static getSafeRegistrationIdEnvName(authName) {
516
+ if (!authName) {
517
+ return "";
518
+ }
519
+ let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
520
+ if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
521
+ safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
684
522
  }
685
- return [command, warning];
523
+ return safeRegistrationIdEnvName;
686
524
  }
687
- static listSupportedAPIs(spec, options) {
688
- const paths = spec.paths;
525
+ static getServerObject(spec, method, path) {
526
+ const pathObj = spec.paths[path];
527
+ const operationObject = pathObj[method];
528
+ const rootServer = spec.servers && spec.servers[0];
529
+ const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
530
+ const operationServer = operationObject.servers && operationObject.servers[0];
531
+ const serverUrl = operationServer || methodServer || rootServer;
532
+ return serverUrl;
533
+ }
534
+ }
535
+
536
+ // Copyright (c) Microsoft Corporation.
537
+ class Validator {
538
+ listAPIs() {
539
+ var _a;
540
+ if (this.apiMap) {
541
+ return this.apiMap;
542
+ }
543
+ const paths = this.spec.paths;
689
544
  const result = {};
690
545
  for (const path in paths) {
691
546
  const methods = paths[path];
692
547
  for (const method in methods) {
693
- if (Utils.isSupportedApi(method, path, spec, options)) {
694
- const operationObject = methods[method];
695
- result[`${method.toUpperCase()} ${path}`] = operationObject;
548
+ const operationObject = methods[method];
549
+ if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
550
+ const validateResult = this.validateAPI(method, path);
551
+ result[`${method.toUpperCase()} ${path}`] = {
552
+ operation: operationObject,
553
+ isValid: validateResult.isValid,
554
+ reason: validateResult.reason,
555
+ };
696
556
  }
697
557
  }
698
558
  }
559
+ this.apiMap = result;
699
560
  return result;
700
561
  }
701
- static validateSpec(spec, parser, isSwaggerFile, options) {
702
- const errors = [];
703
- const warnings = [];
704
- if (isSwaggerFile) {
705
- warnings.push({
706
- type: WarningType.ConvertSwaggerToOpenAPI,
707
- content: ConstantString.ConvertSwaggerToOpenAPI,
562
+ validateSpecVersion() {
563
+ const result = { errors: [], warnings: [] };
564
+ if (this.spec.openapi >= "3.1.0") {
565
+ result.errors.push({
566
+ type: ErrorType.SpecVersionNotSupported,
567
+ content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
568
+ data: this.spec.openapi,
708
569
  });
709
570
  }
710
- // Server validation
711
- const serverErrors = Utils.validateServer(spec, options);
712
- errors.push(...serverErrors);
713
- // Remote reference not supported
714
- const refPaths = parser.$refs.paths();
715
- // refPaths [0] is the current spec file path
716
- if (refPaths.length > 1) {
717
- errors.push({
718
- type: ErrorType.RemoteRefNotSupported,
719
- content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
720
- data: refPaths,
721
- });
722
- }
723
- // No supported API
724
- const apiMap = Utils.listSupportedAPIs(spec, options);
725
- if (Object.keys(apiMap).length === 0) {
726
- errors.push({
571
+ return result;
572
+ }
573
+ validateSpecServer() {
574
+ const result = { errors: [], warnings: [] };
575
+ const serverErrors = Utils.validateServer(this.spec, this.options);
576
+ result.errors.push(...serverErrors);
577
+ return result;
578
+ }
579
+ validateSpecNoSupportAPI() {
580
+ const result = { errors: [], warnings: [] };
581
+ const apiMap = this.listAPIs();
582
+ const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
583
+ if (validAPIs.length === 0) {
584
+ const data = [];
585
+ for (const key in apiMap) {
586
+ const { reason } = apiMap[key];
587
+ const apiInvalidReason = { api: key, reason: reason };
588
+ data.push(apiInvalidReason);
589
+ }
590
+ result.errors.push({
727
591
  type: ErrorType.NoSupportedApi,
728
592
  content: ConstantString.NoSupportedApi,
593
+ data,
729
594
  });
730
595
  }
596
+ return result;
597
+ }
598
+ validateSpecOperationId() {
599
+ const result = { errors: [], warnings: [] };
600
+ const apiMap = this.listAPIs();
731
601
  // OperationId missing
732
602
  const apisMissingOperationId = [];
733
603
  for (const key in apiMap) {
734
- const pathObjectItem = apiMap[key];
735
- if (!pathObjectItem.operationId) {
604
+ const { operation } = apiMap[key];
605
+ if (!operation.operationId) {
736
606
  apisMissingOperationId.push(key);
737
607
  }
738
608
  }
739
609
  if (apisMissingOperationId.length > 0) {
740
- warnings.push({
610
+ result.warnings.push({
741
611
  type: WarningType.OperationIdMissing,
742
612
  content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
743
613
  data: apisMissingOperationId,
744
614
  });
745
615
  }
746
- let status = ValidationStatus.Valid;
747
- if (warnings.length > 0 && errors.length === 0) {
748
- status = ValidationStatus.Warning;
616
+ return result;
617
+ }
618
+ validateMethodAndPath(method, path) {
619
+ const result = { isValid: true, reason: [] };
620
+ if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
621
+ result.isValid = false;
622
+ result.reason.push(ErrorType.MethodNotAllowed);
623
+ return result;
749
624
  }
750
- else if (errors.length > 0) {
751
- status = ValidationStatus.Error;
625
+ const pathObj = this.spec.paths[path];
626
+ if (!pathObj || !pathObj[method]) {
627
+ result.isValid = false;
628
+ result.reason.push(ErrorType.UrlPathNotExist);
629
+ return result;
752
630
  }
753
- return {
754
- status,
755
- warnings,
756
- errors,
757
- };
631
+ return result;
758
632
  }
759
- static format(str, ...args) {
760
- let index = 0;
761
- return str.replace(/%s/g, () => {
762
- const arg = args[index++];
763
- return arg !== undefined ? arg : "";
764
- });
633
+ validateResponse(method, path) {
634
+ const result = { isValid: true, reason: [] };
635
+ const operationObject = this.spec.paths[path][method];
636
+ const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
637
+ if (this.options.projectType === ProjectType.SME) {
638
+ // only support response body only contains “application/json” content type
639
+ if (multipleMediaType) {
640
+ result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
641
+ }
642
+ else if (Object.keys(json).length === 0) {
643
+ // response body should not be empty
644
+ result.reason.push(ErrorType.ResponseJsonIsEmpty);
645
+ }
646
+ }
647
+ return result;
765
648
  }
766
- static getSafeRegistrationIdEnvName(authName) {
767
- if (!authName) {
768
- return "";
649
+ validateServer(method, path) {
650
+ const result = { isValid: true, reason: [] };
651
+ const serverObj = Utils.getServerObject(this.spec, method, path);
652
+ if (!serverObj) {
653
+ // should contain server URL
654
+ result.reason.push(ErrorType.NoServerInformation);
769
655
  }
770
- let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
771
- if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
772
- safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
656
+ else {
657
+ // server url should be absolute url with https protocol
658
+ const serverValidateResult = Utils.checkServerUrl([serverObj]);
659
+ result.reason.push(...serverValidateResult.map((item) => item.type));
773
660
  }
774
- return safeRegistrationIdEnvName;
661
+ return result;
775
662
  }
776
- static getAllAPICount(spec) {
777
- let count = 0;
778
- const paths = spec.paths;
779
- for (const path in paths) {
780
- const methods = paths[path];
781
- for (const method in methods) {
782
- if (ConstantString.AllOperationMethods.includes(method)) {
783
- count++;
784
- }
785
- }
663
+ validateAuth(method, path) {
664
+ const pathObj = this.spec.paths[path];
665
+ const operationObject = pathObj[method];
666
+ const securities = operationObject.security;
667
+ const authSchemeArray = Utils.getAuthArray(securities, this.spec);
668
+ if (authSchemeArray.length === 0) {
669
+ return { isValid: true, reason: [] };
786
670
  }
787
- return count;
788
- }
789
- }
790
-
791
- // Copyright (c) Microsoft Corporation.
792
- class SpecFilter {
793
- static specFilter(filter, unResolveSpec, resolvedSpec, options) {
794
- try {
795
- const newSpec = Object.assign({}, unResolveSpec);
796
- const newPaths = {};
797
- for (const filterItem of filter) {
798
- const [method, path] = filterItem.split(" ");
799
- const methodName = method.toLowerCase();
800
- if (!Utils.isSupportedApi(methodName, path, resolvedSpec, options)) {
801
- continue;
802
- }
803
- if (!newPaths[path]) {
804
- newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
805
- for (const m of ConstantString.AllOperationMethods) {
806
- delete newPaths[path][m];
671
+ if (this.options.allowAPIKeyAuth ||
672
+ this.options.allowOauth2 ||
673
+ this.options.allowBearerTokenAuth) {
674
+ // Currently we don't support multiple auth in one operation
675
+ if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
676
+ return {
677
+ isValid: false,
678
+ reason: [ErrorType.MultipleAuthNotSupported],
679
+ };
680
+ }
681
+ for (const auths of authSchemeArray) {
682
+ if (auths.length === 1) {
683
+ if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
684
+ (this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
685
+ (this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
686
+ return { isValid: true, reason: [] };
807
687
  }
808
688
  }
809
- newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
810
- // Add the operationId if missing
811
- if (!newPaths[path][methodName].operationId) {
812
- newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
813
- }
814
689
  }
815
- newSpec.paths = newPaths;
816
- return newSpec;
817
- }
818
- catch (err) {
819
- throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
820
690
  }
691
+ return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
821
692
  }
822
- }
823
-
824
- // Copyright (c) Microsoft Corporation.
825
- class ManifestUpdater {
826
- static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options) {
827
- const manifest = await fs.readJSON(manifestPath);
828
- const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
829
- manifest.plugins = [
830
- {
831
- pluginFile: apiPluginRelativePath,
832
- },
833
- ];
834
- ManifestUpdater.updateManifestDescription(manifest, spec);
835
- const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
836
- const apiPlugin = ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, options);
837
- return [manifest, apiPlugin];
838
- }
839
- static updateManifestDescription(manifest, spec) {
840
- var _a, _b;
841
- manifest.description = {
842
- short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
843
- 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),
693
+ checkPostBodySchema(schema, isRequired = false) {
694
+ var _a;
695
+ const paramResult = {
696
+ requiredNum: 0,
697
+ optionalNum: 0,
698
+ isValid: true,
699
+ reason: [],
844
700
  };
845
- }
846
- static mapOpenAPISchemaToFuncParam(schema, method, pathUrl) {
847
- let parameter;
701
+ if (Object.keys(schema).length === 0) {
702
+ return paramResult;
703
+ }
704
+ const isRequiredWithoutDefault = isRequired && schema.default === undefined;
705
+ const isCopilot = this.projectType === ProjectType.Copilot;
706
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
707
+ paramResult.isValid = false;
708
+ paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
709
+ return paramResult;
710
+ }
848
711
  if (schema.type === "string" ||
849
- schema.type === "boolean" ||
850
712
  schema.type === "integer" ||
851
- schema.type === "number" ||
852
- schema.type === "array") {
853
- parameter = schema;
713
+ schema.type === "boolean" ||
714
+ schema.type === "number") {
715
+ if (isRequiredWithoutDefault) {
716
+ paramResult.requiredNum = paramResult.requiredNum + 1;
717
+ }
718
+ else {
719
+ paramResult.optionalNum = paramResult.optionalNum + 1;
720
+ }
721
+ }
722
+ else if (schema.type === "object") {
723
+ const { properties } = schema;
724
+ for (const property in properties) {
725
+ let isRequired = false;
726
+ if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
727
+ isRequired = true;
728
+ }
729
+ const result = this.checkPostBodySchema(properties[property], isRequired);
730
+ paramResult.requiredNum += result.requiredNum;
731
+ paramResult.optionalNum += result.optionalNum;
732
+ paramResult.isValid = paramResult.isValid && result.isValid;
733
+ paramResult.reason.push(...result.reason);
734
+ }
854
735
  }
855
736
  else {
856
- throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
737
+ if (isRequiredWithoutDefault && !isCopilot) {
738
+ paramResult.isValid = false;
739
+ paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
740
+ }
857
741
  }
858
- return parameter;
742
+ return paramResult;
859
743
  }
860
- static generatePluginManifestSchema(spec, specRelativePath, options) {
861
- var _a, _b, _c;
862
- const functions = [];
863
- const functionNames = [];
864
- const paths = spec.paths;
865
- for (const pathUrl in paths) {
866
- const pathItem = paths[pathUrl];
867
- if (pathItem) {
868
- const operations = pathItem;
869
- for (const method in operations) {
870
- if (options.allowMethods.includes(method)) {
871
- const operationItem = operations[method];
872
- if (operationItem) {
873
- const operationId = operationItem.operationId;
874
- const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
875
- const paramObject = operationItem.parameters;
876
- const requestBody = operationItem.requestBody;
877
- const parameters = {
878
- type: "object",
879
- properties: {},
880
- required: [],
881
- };
882
- if (paramObject) {
883
- for (let i = 0; i < paramObject.length; i++) {
884
- const param = paramObject[i];
885
- const schema = param.schema;
886
- parameters.properties[param.name] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
887
- if (param.required) {
888
- parameters.required.push(param.name);
889
- }
890
- if (!parameters.properties[param.name].description) {
891
- parameters.properties[param.name].description = (_b = param.description) !== null && _b !== void 0 ? _b : "";
892
- }
893
- }
894
- }
895
- if (requestBody) {
896
- const requestJsonBody = requestBody.content["application/json"];
897
- const requestBodySchema = requestJsonBody.schema;
898
- if (requestBodySchema.type === "object") {
899
- if (requestBodySchema.required) {
900
- parameters.required.push(...requestBodySchema.required);
901
- }
902
- for (const property in requestBodySchema.properties) {
903
- const schema = requestBodySchema.properties[property];
904
- parameters.properties[property] = ManifestUpdater.mapOpenAPISchemaToFuncParam(schema, method, pathUrl);
905
- }
906
- }
907
- else {
908
- throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), ErrorType.UpdateManifestFailed);
909
- }
910
- }
911
- const funcObj = {
912
- name: operationId,
913
- description: description,
914
- parameters: parameters,
915
- };
916
- functions.push(funcObj);
917
- functionNames.push(operationId);
918
- }
919
- }
744
+ checkParamSchema(paramObject) {
745
+ const paramResult = {
746
+ requiredNum: 0,
747
+ optionalNum: 0,
748
+ isValid: true,
749
+ reason: [],
750
+ };
751
+ if (!paramObject) {
752
+ return paramResult;
753
+ }
754
+ const isCopilot = this.projectType === ProjectType.Copilot;
755
+ for (let i = 0; i < paramObject.length; i++) {
756
+ const param = paramObject[i];
757
+ const schema = param.schema;
758
+ if (isCopilot && this.hasNestedObjectInSchema(schema)) {
759
+ paramResult.isValid = false;
760
+ paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
761
+ continue;
762
+ }
763
+ const isRequiredWithoutDefault = param.required && schema.default === undefined;
764
+ if (isCopilot) {
765
+ if (isRequiredWithoutDefault) {
766
+ paramResult.requiredNum = paramResult.requiredNum + 1;
767
+ }
768
+ else {
769
+ paramResult.optionalNum = paramResult.optionalNum + 1;
770
+ }
771
+ continue;
772
+ }
773
+ if (param.in === "header" || param.in === "cookie") {
774
+ if (isRequiredWithoutDefault) {
775
+ paramResult.isValid = false;
776
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
777
+ }
778
+ continue;
779
+ }
780
+ if (schema.type !== "boolean" &&
781
+ schema.type !== "string" &&
782
+ schema.type !== "number" &&
783
+ schema.type !== "integer") {
784
+ if (isRequiredWithoutDefault) {
785
+ paramResult.isValid = false;
786
+ paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
787
+ }
788
+ continue;
789
+ }
790
+ if (param.in === "query" || param.in === "path") {
791
+ if (isRequiredWithoutDefault) {
792
+ paramResult.requiredNum = paramResult.requiredNum + 1;
793
+ }
794
+ else {
795
+ paramResult.optionalNum = paramResult.optionalNum + 1;
920
796
  }
921
797
  }
922
798
  }
923
- const apiPlugin = {
924
- schema_version: "v2",
925
- name_for_human: spec.info.title,
926
- description_for_human: (_c = spec.info.description) !== null && _c !== void 0 ? _c : "<Please add description of the plugin>",
927
- functions: functions,
928
- runtimes: [
929
- {
930
- type: "OpenApi",
931
- auth: {
932
- type: "none", // TODO, support auth in the future
933
- },
934
- spec: {
935
- url: specRelativePath,
936
- },
937
- run_for_functions: functionNames,
938
- },
939
- ],
940
- };
941
- return apiPlugin;
799
+ return paramResult;
942
800
  }
943
- static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
944
- try {
945
- const originalManifest = await fs.readJSON(manifestPath);
946
- const updatedPart = {};
947
- updatedPart.composeExtensions = [];
948
- let warnings = [];
949
- if (options.projectType === ProjectType.SME) {
950
- const updateResult = await ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
951
- const commands = updateResult[0];
952
- warnings = updateResult[1];
953
- const composeExtension = {
954
- composeExtensionType: "apiBased",
955
- apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
956
- commands: commands,
957
- };
958
- if (authInfo) {
959
- const auth = authInfo.authScheme;
960
- if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
961
- const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix}`);
962
- composeExtension.authorization = {
963
- authType: "apiSecretServiceAuth",
964
- apiSecretServiceAuthConfiguration: {
965
- apiSecretRegistrationId: `\${{${safeApiSecretRegistrationId}}}`,
966
- },
967
- };
968
- }
969
- else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
970
- const safeOAuth2RegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.OAuthRegistrationIdPostFix}`);
971
- composeExtension.authorization = {
972
- authType: "oAuth2.0",
973
- oAuthConfiguration: {
974
- oauthConfigurationId: `\${{${safeOAuth2RegistrationId}}}`,
975
- },
976
- };
977
- updatedPart.webApplicationInfo = {
978
- id: "${{AAD_APP_CLIENT_ID}}",
979
- resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
980
- };
981
- }
801
+ hasNestedObjectInSchema(schema) {
802
+ if (schema.type === "object") {
803
+ for (const property in schema.properties) {
804
+ const nestedSchema = schema.properties[property];
805
+ if (nestedSchema.type === "object") {
806
+ return true;
982
807
  }
983
- updatedPart.composeExtensions = [composeExtension];
984
808
  }
985
- updatedPart.description = originalManifest.description;
986
- ManifestUpdater.updateManifestDescription(updatedPart, spec);
987
- const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
988
- return [updatedManifest, warnings];
989
809
  }
990
- catch (err) {
991
- throw new SpecParserError(err.toString(), ErrorType.UpdateManifestFailed);
810
+ return false;
811
+ }
812
+ }
813
+
814
+ // Copyright (c) Microsoft Corporation.
815
+ class CopilotValidator extends Validator {
816
+ constructor(spec, options) {
817
+ super();
818
+ this.projectType = ProjectType.Copilot;
819
+ this.options = options;
820
+ this.spec = spec;
821
+ }
822
+ validateSpec() {
823
+ const result = { errors: [], warnings: [] };
824
+ // validate spec version
825
+ let validationResult = this.validateSpecVersion();
826
+ result.errors.push(...validationResult.errors);
827
+ // validate spec server
828
+ validationResult = this.validateSpecServer();
829
+ result.errors.push(...validationResult.errors);
830
+ // validate no supported API
831
+ validationResult = this.validateSpecNoSupportAPI();
832
+ result.errors.push(...validationResult.errors);
833
+ // validate operationId missing
834
+ validationResult = this.validateSpecOperationId();
835
+ result.warnings.push(...validationResult.warnings);
836
+ return result;
837
+ }
838
+ validateAPI(method, path) {
839
+ const result = { isValid: true, reason: [] };
840
+ method = method.toLocaleLowerCase();
841
+ // validate method and path
842
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
843
+ if (!methodAndPathResult.isValid) {
844
+ return methodAndPathResult;
845
+ }
846
+ const operationObject = this.spec.paths[path][method];
847
+ // validate auth
848
+ const authCheckResult = this.validateAuth(method, path);
849
+ result.reason.push(...authCheckResult.reason);
850
+ // validate operationId
851
+ if (!this.options.allowMissingId && !operationObject.operationId) {
852
+ result.reason.push(ErrorType.MissingOperationId);
853
+ }
854
+ // validate server
855
+ const validateServerResult = this.validateServer(method, path);
856
+ result.reason.push(...validateServerResult.reason);
857
+ // validate response
858
+ const validateResponseResult = this.validateResponse(method, path);
859
+ result.reason.push(...validateResponseResult.reason);
860
+ // validate requestBody
861
+ const requestBody = operationObject.requestBody;
862
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
863
+ if (requestJsonBody) {
864
+ const requestBodySchema = requestJsonBody.schema;
865
+ if (requestBodySchema.type !== "object") {
866
+ result.reason.push(ErrorType.PostBodySchemaIsNotJson);
867
+ }
868
+ const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
869
+ result.reason.push(...requestBodyParamResult.reason);
870
+ }
871
+ // validate parameters
872
+ const paramObject = operationObject.parameters;
873
+ const paramResult = this.checkParamSchema(paramObject);
874
+ result.reason.push(...paramResult.reason);
875
+ if (result.reason.length > 0) {
876
+ result.isValid = false;
992
877
  }
878
+ return result;
993
879
  }
994
- static async generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
880
+ }
881
+
882
+ // Copyright (c) Microsoft Corporation.
883
+ class SMEValidator extends Validator {
884
+ constructor(spec, options) {
885
+ super();
886
+ this.projectType = ProjectType.SME;
887
+ this.options = options;
888
+ this.spec = spec;
889
+ }
890
+ validateSpec() {
891
+ const result = { errors: [], warnings: [] };
892
+ // validate spec version
893
+ let validationResult = this.validateSpecVersion();
894
+ result.errors.push(...validationResult.errors);
895
+ // validate spec server
896
+ validationResult = this.validateSpecServer();
897
+ result.errors.push(...validationResult.errors);
898
+ // validate no supported API
899
+ validationResult = this.validateSpecNoSupportAPI();
900
+ result.errors.push(...validationResult.errors);
901
+ // validate operationId missing
902
+ if (this.options.allowMissingId) {
903
+ validationResult = this.validateSpecOperationId();
904
+ result.warnings.push(...validationResult.warnings);
905
+ }
906
+ return result;
907
+ }
908
+ validateAPI(method, path) {
909
+ const result = { isValid: true, reason: [] };
910
+ method = method.toLocaleLowerCase();
911
+ // validate method and path
912
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
913
+ if (!methodAndPathResult.isValid) {
914
+ return methodAndPathResult;
915
+ }
916
+ const operationObject = this.spec.paths[path][method];
917
+ // validate auth
918
+ const authCheckResult = this.validateAuth(method, path);
919
+ result.reason.push(...authCheckResult.reason);
920
+ // validate operationId
921
+ if (!this.options.allowMissingId && !operationObject.operationId) {
922
+ result.reason.push(ErrorType.MissingOperationId);
923
+ }
924
+ // validate server
925
+ const validateServerResult = this.validateServer(method, path);
926
+ result.reason.push(...validateServerResult.reason);
927
+ // validate response
928
+ const validateResponseResult = this.validateResponse(method, path);
929
+ result.reason.push(...validateResponseResult.reason);
930
+ let postBodyResult = {
931
+ requiredNum: 0,
932
+ optionalNum: 0,
933
+ isValid: true,
934
+ reason: [],
935
+ };
936
+ // validate requestBody
937
+ const requestBody = operationObject.requestBody;
938
+ const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
939
+ if (Utils.containMultipleMediaTypes(requestBody)) {
940
+ result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
941
+ }
942
+ if (requestJsonBody) {
943
+ const requestBodySchema = requestJsonBody.schema;
944
+ postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
945
+ result.reason.push(...postBodyResult.reason);
946
+ }
947
+ // validate parameters
948
+ const paramObject = operationObject.parameters;
949
+ const paramResult = this.checkParamSchema(paramObject);
950
+ result.reason.push(...paramResult.reason);
951
+ // validate total parameters count
952
+ if (paramResult.isValid && postBodyResult.isValid) {
953
+ const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
954
+ result.reason.push(...paramCountResult.reason);
955
+ }
956
+ if (result.reason.length > 0) {
957
+ result.isValid = false;
958
+ }
959
+ return result;
960
+ }
961
+ validateParamCount(postBodyResult, paramResult) {
962
+ const result = { isValid: true, reason: [] };
963
+ const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
964
+ const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
965
+ if (totalRequiredParams > 1) {
966
+ if (!this.options.allowMultipleParameters ||
967
+ totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
968
+ result.reason.push(ErrorType.ExceededRequiredParamsLimit);
969
+ }
970
+ }
971
+ else if (totalParams === 0) {
972
+ result.reason.push(ErrorType.NoParameter);
973
+ }
974
+ return result;
975
+ }
976
+ }
977
+ SMEValidator.SMERequiredParamsMaxNum = 5;
978
+
979
+ // Copyright (c) Microsoft Corporation.
980
+ class TeamsAIValidator extends Validator {
981
+ constructor(spec, options) {
982
+ super();
983
+ this.projectType = ProjectType.TeamsAi;
984
+ this.options = options;
985
+ this.spec = spec;
986
+ }
987
+ validateSpec() {
988
+ const result = { errors: [], warnings: [] };
989
+ // validate spec server
990
+ let validationResult = this.validateSpecServer();
991
+ result.errors.push(...validationResult.errors);
992
+ // validate no supported API
993
+ validationResult = this.validateSpecNoSupportAPI();
994
+ result.errors.push(...validationResult.errors);
995
+ return result;
996
+ }
997
+ validateAPI(method, path) {
998
+ const result = { isValid: true, reason: [] };
999
+ method = method.toLocaleLowerCase();
1000
+ // validate method and path
1001
+ const methodAndPathResult = this.validateMethodAndPath(method, path);
1002
+ if (!methodAndPathResult.isValid) {
1003
+ return methodAndPathResult;
1004
+ }
1005
+ const operationObject = this.spec.paths[path][method];
1006
+ // validate operationId
1007
+ if (!this.options.allowMissingId && !operationObject.operationId) {
1008
+ result.reason.push(ErrorType.MissingOperationId);
1009
+ }
1010
+ // validate server
1011
+ const validateServerResult = this.validateServer(method, path);
1012
+ result.reason.push(...validateServerResult.reason);
1013
+ if (result.reason.length > 0) {
1014
+ result.isValid = false;
1015
+ }
1016
+ return result;
1017
+ }
1018
+ }
1019
+
1020
+ class ValidatorFactory {
1021
+ static create(spec, options) {
995
1022
  var _a;
996
- const paths = spec.paths;
997
- const commands = [];
998
- const warnings = [];
999
- if (paths) {
1000
- for (const pathUrl in paths) {
1001
- const pathItem = paths[pathUrl];
1002
- if (pathItem) {
1003
- const operations = pathItem;
1004
- // Currently only support GET and POST method
1005
- for (const method in operations) {
1006
- if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
1007
- const operationItem = operations[method];
1008
- if (operationItem) {
1009
- const [command, warning] = Utils.parseApiInfo(operationItem, options);
1010
- if (adaptiveCardFolder) {
1011
- const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
1012
- command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
1013
- ? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
1014
- : "";
1015
- }
1016
- if (warning) {
1017
- warnings.push(warning);
1018
- }
1019
- commands.push(command);
1020
- }
1023
+ const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
1024
+ switch (type) {
1025
+ case ProjectType.SME:
1026
+ return new SMEValidator(spec, options);
1027
+ case ProjectType.Copilot:
1028
+ return new CopilotValidator(spec, options);
1029
+ case ProjectType.TeamsAi:
1030
+ return new TeamsAIValidator(spec, options);
1031
+ default:
1032
+ throw new Error(`Invalid project type: ${type}`);
1033
+ }
1034
+ }
1035
+ }
1036
+
1037
+ // Copyright (c) Microsoft Corporation.
1038
+ class SpecFilter {
1039
+ static specFilter(filter, unResolveSpec, resolvedSpec, options) {
1040
+ var _a;
1041
+ try {
1042
+ const newSpec = Object.assign({}, unResolveSpec);
1043
+ const newPaths = {};
1044
+ for (const filterItem of filter) {
1045
+ const [method, path] = filterItem.split(" ");
1046
+ const methodName = method.toLowerCase();
1047
+ const pathObj = (_a = resolvedSpec.paths) === null || _a === void 0 ? void 0 : _a[path];
1048
+ if (ConstantString.AllOperationMethods.includes(methodName) &&
1049
+ pathObj &&
1050
+ pathObj[methodName]) {
1051
+ const validator = ValidatorFactory.create(resolvedSpec, options);
1052
+ const validateResult = validator.validateAPI(methodName, path);
1053
+ if (!validateResult.isValid) {
1054
+ continue;
1055
+ }
1056
+ if (!newPaths[path]) {
1057
+ newPaths[path] = Object.assign({}, unResolveSpec.paths[path]);
1058
+ for (const m of ConstantString.AllOperationMethods) {
1059
+ delete newPaths[path][m];
1021
1060
  }
1022
1061
  }
1062
+ newPaths[path][methodName] = unResolveSpec.paths[path][methodName];
1063
+ // Add the operationId if missing
1064
+ if (!newPaths[path][methodName].operationId) {
1065
+ newPaths[path][methodName].operationId = `${methodName}${Utils.convertPathToCamelCase(path)}`;
1066
+ }
1023
1067
  }
1024
1068
  }
1069
+ newSpec.paths = newPaths;
1070
+ return newSpec;
1071
+ }
1072
+ catch (err) {
1073
+ throw new SpecParserError(err.toString(), ErrorType.FilterSpecFailed);
1025
1074
  }
1026
- return [commands, warnings];
1027
- }
1028
- static getRelativePath(from, to) {
1029
- const relativePath = path.relative(path.dirname(from), to);
1030
- return path.normalize(relativePath).replace(/\\/g, "/");
1031
1075
  }
1032
1076
  }
1033
1077
 
@@ -1035,7 +1079,7 @@ class ManifestUpdater {
1035
1079
  class AdaptiveCardGenerator {
1036
1080
  static generateAdaptiveCard(operationItem) {
1037
1081
  try {
1038
- const json = Utils.getResponseJson(operationItem);
1082
+ const { json } = Utils.getResponseJson(operationItem);
1039
1083
  let cardBody = [];
1040
1084
  let schema = json.schema;
1041
1085
  let jsonPath = "$";
@@ -1201,6 +1245,27 @@ function wrapAdaptiveCard(card, jsonPath) {
1201
1245
  };
1202
1246
  return result;
1203
1247
  }
1248
+ function wrapResponseSemantics(card, jsonPath) {
1249
+ const props = inferProperties(card);
1250
+ const dataPath = jsonPath === "$" ? "$" : "$." + jsonPath;
1251
+ const result = {
1252
+ data_path: dataPath,
1253
+ };
1254
+ if (props.title || props.subtitle || props.imageUrl) {
1255
+ result.properties = {};
1256
+ if (props.title) {
1257
+ result.properties.title = "$." + props.title;
1258
+ }
1259
+ if (props.subtitle) {
1260
+ result.properties.subtitle = "$." + props.subtitle;
1261
+ }
1262
+ if (props.imageUrl) {
1263
+ result.properties.url = "$." + props.imageUrl;
1264
+ }
1265
+ }
1266
+ result.static_template = card;
1267
+ return result;
1268
+ }
1204
1269
  /**
1205
1270
  * Infers the preview card template from an Adaptive Card and a JSON path.
1206
1271
  * The preview card template includes a title and an optional subtitle and image.
@@ -1213,11 +1278,29 @@ function wrapAdaptiveCard(card, jsonPath) {
1213
1278
  * @returns The inferred preview card template.
1214
1279
  */
1215
1280
  function inferPreviewCardTemplate(card) {
1216
- var _a;
1217
1281
  const result = {
1218
- title: "",
1282
+ title: "result",
1219
1283
  };
1220
- const textBlockElements = new Set();
1284
+ const inferredProperties = inferProperties(card);
1285
+ if (inferredProperties.title) {
1286
+ result.title = `\${if(${inferredProperties.title}, ${inferredProperties.title}, 'N/A')}`;
1287
+ }
1288
+ if (inferredProperties.subtitle) {
1289
+ result.subtitle = `\${if(${inferredProperties.subtitle}, ${inferredProperties.subtitle}, 'N/A')}`;
1290
+ }
1291
+ if (inferredProperties.imageUrl) {
1292
+ result.image = {
1293
+ url: `\${${inferredProperties.imageUrl}}`,
1294
+ alt: `\${if(${inferredProperties.imageUrl}, ${inferredProperties.imageUrl}, 'N/A')}`,
1295
+ $when: `\${${inferredProperties.imageUrl} != null}`,
1296
+ };
1297
+ }
1298
+ return result;
1299
+ }
1300
+ function inferProperties(card) {
1301
+ var _a;
1302
+ const result = {};
1303
+ const nameSet = new Set();
1221
1304
  let rootObject;
1222
1305
  if (((_a = card.body[0]) === null || _a === void 0 ? void 0 : _a.type) === ConstantString.ContainerType) {
1223
1306
  rootObject = card.body[0].items;
@@ -1230,56 +1313,372 @@ function inferPreviewCardTemplate(card) {
1230
1313
  const textElement = element;
1231
1314
  const index = textElement.text.indexOf("${if(");
1232
1315
  if (index > 0) {
1233
- textElement.text = textElement.text.substring(index);
1234
- textBlockElements.add(textElement);
1316
+ const text = textElement.text.substring(index);
1317
+ const match = text.match(/\${if\(([^,]+),/);
1318
+ const property = match ? match[1] : "";
1319
+ if (property) {
1320
+ nameSet.add(property);
1321
+ }
1322
+ }
1323
+ }
1324
+ else if (element.type === ConstantString.ImageType) {
1325
+ const imageElement = element;
1326
+ const match = imageElement.url.match(/\${([^,]+)}/);
1327
+ const property = match ? match[1] : "";
1328
+ if (property) {
1329
+ nameSet.add(property);
1235
1330
  }
1236
1331
  }
1237
1332
  }
1238
- for (const element of textBlockElements) {
1239
- const text = element.text;
1240
- if (!result.title && Utils.isWellKnownName(text, ConstantString.WellknownTitleName)) {
1241
- result.title = text;
1242
- textBlockElements.delete(element);
1333
+ for (const name of nameSet) {
1334
+ if (!result.title && Utils.isWellKnownName(name, ConstantString.WellknownTitleName)) {
1335
+ result.title = name;
1336
+ nameSet.delete(name);
1243
1337
  }
1244
1338
  else if (!result.subtitle &&
1245
- Utils.isWellKnownName(text, ConstantString.WellknownSubtitleName)) {
1246
- result.subtitle = text;
1247
- textBlockElements.delete(element);
1339
+ Utils.isWellKnownName(name, ConstantString.WellknownSubtitleName)) {
1340
+ result.subtitle = name;
1341
+ nameSet.delete(name);
1248
1342
  }
1249
- else if (!result.image && Utils.isWellKnownName(text, ConstantString.WellknownImageName)) {
1250
- const match = text.match(/\${if\(([^,]+),/);
1251
- const property = match ? match[1] : "";
1252
- if (property) {
1253
- result.image = {
1254
- url: `\${${property}}`,
1255
- alt: text,
1256
- $when: `\${${property} != null}`,
1257
- };
1258
- }
1259
- textBlockElements.delete(element);
1343
+ else if (!result.imageUrl && Utils.isWellKnownName(name, ConstantString.WellknownImageName)) {
1344
+ result.imageUrl = name;
1345
+ nameSet.delete(name);
1260
1346
  }
1261
1347
  }
1262
- for (const element of textBlockElements) {
1263
- const text = element.text;
1348
+ for (const name of nameSet) {
1264
1349
  if (!result.title) {
1265
- result.title = text;
1266
- textBlockElements.delete(element);
1350
+ result.title = name;
1351
+ nameSet.delete(name);
1267
1352
  }
1268
1353
  else if (!result.subtitle) {
1269
- result.subtitle = text;
1270
- textBlockElements.delete(element);
1354
+ result.subtitle = name;
1355
+ nameSet.delete(name);
1271
1356
  }
1272
1357
  }
1273
1358
  if (!result.title && result.subtitle) {
1274
1359
  result.title = result.subtitle;
1275
1360
  delete result.subtitle;
1276
1361
  }
1277
- if (!result.title) {
1278
- result.title = "result";
1279
- }
1280
1362
  return result;
1281
1363
  }
1282
1364
 
1365
+ // Copyright (c) Microsoft Corporation.
1366
+ class ManifestUpdater {
1367
+ static async updateManifestWithAiPlugin(manifestPath, outputSpecPath, apiPluginFilePath, spec, options, authInfo) {
1368
+ const manifest = await fs.readJSON(manifestPath);
1369
+ const apiPluginRelativePath = ManifestUpdater.getRelativePath(manifestPath, apiPluginFilePath);
1370
+ // Insert plugins in manifest.json if it is plugin for Copilot.
1371
+ if (!options.isGptPlugin) {
1372
+ manifest.plugins = [
1373
+ {
1374
+ file: apiPluginRelativePath,
1375
+ id: ConstantString.DefaultPluginId,
1376
+ },
1377
+ ];
1378
+ ManifestUpdater.updateManifestDescription(manifest, spec);
1379
+ }
1380
+ const appName = this.removeEnvs(manifest.name.short);
1381
+ const specRelativePath = ManifestUpdater.getRelativePath(manifestPath, outputSpecPath);
1382
+ const apiPlugin = await ManifestUpdater.generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options);
1383
+ return [manifest, apiPlugin];
1384
+ }
1385
+ static updateManifestDescription(manifest, spec) {
1386
+ var _a, _b;
1387
+ manifest.description = {
1388
+ short: spec.info.title.slice(0, ConstantString.ShortDescriptionMaxLens),
1389
+ 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),
1390
+ };
1391
+ }
1392
+ static checkSchema(schema, method, pathUrl) {
1393
+ if (schema.type === "array") {
1394
+ const items = schema.items;
1395
+ ManifestUpdater.checkSchema(items, method, pathUrl);
1396
+ }
1397
+ else if (schema.type !== "string" &&
1398
+ schema.type !== "boolean" &&
1399
+ schema.type !== "integer" &&
1400
+ schema.type !== "number") {
1401
+ throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(schema)), ErrorType.UpdateManifestFailed);
1402
+ }
1403
+ }
1404
+ static async generatePluginManifestSchema(spec, specRelativePath, apiPluginFilePath, appName, authInfo, options) {
1405
+ var _a, _b, _c, _d;
1406
+ const functions = [];
1407
+ const functionNames = [];
1408
+ const conversationStarters = [];
1409
+ const paths = spec.paths;
1410
+ const pluginAuthObj = {
1411
+ type: "None",
1412
+ };
1413
+ if (authInfo) {
1414
+ if (Utils.isOAuthWithAuthCodeFlow(authInfo.authScheme)) {
1415
+ pluginAuthObj.type = "OAuthPluginVault";
1416
+ }
1417
+ else if (Utils.isBearerTokenAuth(authInfo.authScheme)) {
1418
+ pluginAuthObj.type = "ApiKeyPluginVault";
1419
+ }
1420
+ if (pluginAuthObj.type !== "None") {
1421
+ const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
1422
+ pluginAuthObj.reference_id = `\${{${safeRegistrationIdName}}}`;
1423
+ }
1424
+ }
1425
+ for (const pathUrl in paths) {
1426
+ const pathItem = paths[pathUrl];
1427
+ if (pathItem) {
1428
+ const operations = pathItem;
1429
+ for (const method in operations) {
1430
+ if (options.allowMethods.includes(method)) {
1431
+ const operationItem = operations[method];
1432
+ const confirmationBodies = [];
1433
+ if (operationItem) {
1434
+ const operationId = operationItem.operationId;
1435
+ const description = (_a = operationItem.description) !== null && _a !== void 0 ? _a : "";
1436
+ const summary = operationItem.summary;
1437
+ const paramObject = operationItem.parameters;
1438
+ const requestBody = operationItem.requestBody;
1439
+ if (paramObject) {
1440
+ for (let i = 0; i < paramObject.length; i++) {
1441
+ const param = paramObject[i];
1442
+ const schema = param.schema;
1443
+ ManifestUpdater.checkSchema(schema, method, pathUrl);
1444
+ confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(param.name));
1445
+ }
1446
+ }
1447
+ if (requestBody) {
1448
+ const requestJsonBody = requestBody.content["application/json"];
1449
+ const requestBodySchema = requestJsonBody.schema;
1450
+ if (requestBodySchema.type === "object") {
1451
+ for (const property in requestBodySchema.properties) {
1452
+ const schema = requestBodySchema.properties[property];
1453
+ ManifestUpdater.checkSchema(schema, method, pathUrl);
1454
+ confirmationBodies.push(ManifestUpdater.getConfirmationBodyItem(property));
1455
+ }
1456
+ }
1457
+ else {
1458
+ throw new SpecParserError(Utils.format(ConstantString.UnsupportedSchema, method, pathUrl, JSON.stringify(requestBodySchema)), ErrorType.UpdateManifestFailed);
1459
+ }
1460
+ }
1461
+ const funcObj = {
1462
+ name: operationId,
1463
+ description: description,
1464
+ };
1465
+ if (options.allowResponseSemantics) {
1466
+ const { json } = Utils.getResponseJson(operationItem);
1467
+ if (json.schema) {
1468
+ const [card, jsonPath] = AdaptiveCardGenerator.generateAdaptiveCard(operationItem);
1469
+ const responseSemantic = wrapResponseSemantics(card, jsonPath);
1470
+ funcObj.capabilities = {
1471
+ response_semantics: responseSemantic,
1472
+ };
1473
+ }
1474
+ }
1475
+ if (options.allowConfirmation && method !== ConstantString.GetMethod) {
1476
+ if (!funcObj.capabilities) {
1477
+ funcObj.capabilities = {};
1478
+ }
1479
+ funcObj.capabilities.confirmation = {
1480
+ type: "AdaptiveCard",
1481
+ title: (_b = operationItem.summary) !== null && _b !== void 0 ? _b : description,
1482
+ };
1483
+ if (confirmationBodies.length > 0) {
1484
+ funcObj.capabilities.confirmation.body = confirmationBodies.join("\n");
1485
+ }
1486
+ }
1487
+ functions.push(funcObj);
1488
+ functionNames.push(operationId);
1489
+ const conversationStarterStr = (summary !== null && summary !== void 0 ? summary : description).slice(0, ConstantString.ConversationStarterMaxLens);
1490
+ if (conversationStarterStr) {
1491
+ conversationStarters.push(conversationStarterStr);
1492
+ }
1493
+ }
1494
+ }
1495
+ }
1496
+ }
1497
+ }
1498
+ let apiPlugin;
1499
+ if (await fs.pathExists(apiPluginFilePath)) {
1500
+ apiPlugin = await fs.readJSON(apiPluginFilePath);
1501
+ }
1502
+ else {
1503
+ apiPlugin = {
1504
+ schema_version: "v2.1",
1505
+ name_for_human: "",
1506
+ description_for_human: "",
1507
+ namespace: "",
1508
+ functions: [],
1509
+ runtimes: [],
1510
+ };
1511
+ }
1512
+ apiPlugin.functions = apiPlugin.functions || [];
1513
+ for (const func of functions) {
1514
+ const index = (_c = apiPlugin.functions) === null || _c === void 0 ? void 0 : _c.findIndex((f) => f.name === func.name);
1515
+ if (index === -1) {
1516
+ apiPlugin.functions.push(func);
1517
+ }
1518
+ else {
1519
+ apiPlugin.functions[index] = func;
1520
+ }
1521
+ }
1522
+ apiPlugin.runtimes = apiPlugin.runtimes || [];
1523
+ const index = apiPlugin.runtimes.findIndex((runtime) => {
1524
+ var _a, _b;
1525
+ return runtime.spec.url === specRelativePath &&
1526
+ runtime.type === "OpenApi" &&
1527
+ ((_b = (_a = runtime.auth) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : "None") === pluginAuthObj.type;
1528
+ });
1529
+ if (index === -1) {
1530
+ apiPlugin.runtimes.push({
1531
+ type: "OpenApi",
1532
+ auth: pluginAuthObj,
1533
+ spec: {
1534
+ url: specRelativePath,
1535
+ },
1536
+ run_for_functions: functionNames,
1537
+ });
1538
+ }
1539
+ else {
1540
+ apiPlugin.runtimes[index].run_for_functions = functionNames;
1541
+ }
1542
+ if (!apiPlugin.name_for_human) {
1543
+ apiPlugin.name_for_human = appName;
1544
+ }
1545
+ if (!apiPlugin.namespace) {
1546
+ apiPlugin.namespace = ManifestUpdater.removeAllSpecialCharacters(appName);
1547
+ }
1548
+ if (!apiPlugin.description_for_human) {
1549
+ apiPlugin.description_for_human =
1550
+ (_d = spec.info.description) !== null && _d !== void 0 ? _d : "<Please add description of the plugin>";
1551
+ }
1552
+ if (options.allowConversationStarters && conversationStarters.length > 0) {
1553
+ if (!apiPlugin.capabilities) {
1554
+ apiPlugin.capabilities = {
1555
+ localization: {},
1556
+ };
1557
+ }
1558
+ if (!apiPlugin.capabilities.conversation_starters) {
1559
+ apiPlugin.capabilities.conversation_starters = conversationStarters
1560
+ .slice(0, 5)
1561
+ .map((text) => ({ text }));
1562
+ }
1563
+ }
1564
+ return apiPlugin;
1565
+ }
1566
+ static async updateManifest(manifestPath, outputSpecPath, spec, options, adaptiveCardFolder, authInfo) {
1567
+ try {
1568
+ const originalManifest = await fs.readJSON(manifestPath);
1569
+ const updatedPart = {};
1570
+ updatedPart.composeExtensions = [];
1571
+ let warnings = [];
1572
+ if (options.projectType === ProjectType.SME) {
1573
+ const updateResult = await ManifestUpdater.generateCommands(spec, manifestPath, options, adaptiveCardFolder);
1574
+ const commands = updateResult[0];
1575
+ warnings = updateResult[1];
1576
+ const composeExtension = {
1577
+ composeExtensionType: "apiBased",
1578
+ apiSpecificationFile: ManifestUpdater.getRelativePath(manifestPath, outputSpecPath),
1579
+ commands: commands,
1580
+ };
1581
+ if (authInfo) {
1582
+ const auth = authInfo.authScheme;
1583
+ const safeRegistrationIdName = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
1584
+ if (Utils.isAPIKeyAuth(auth) || Utils.isBearerTokenAuth(auth)) {
1585
+ const safeApiSecretRegistrationId = Utils.getSafeRegistrationIdEnvName(`${authInfo.name}_${ConstantString.RegistrationIdPostfix[authInfo.authScheme.type]}`);
1586
+ composeExtension.authorization = {
1587
+ authType: "apiSecretServiceAuth",
1588
+ apiSecretServiceAuthConfiguration: {
1589
+ apiSecretRegistrationId: `\${{${safeRegistrationIdName}}}`,
1590
+ },
1591
+ };
1592
+ }
1593
+ else if (Utils.isOAuthWithAuthCodeFlow(auth)) {
1594
+ composeExtension.authorization = {
1595
+ authType: "oAuth2.0",
1596
+ oAuthConfiguration: {
1597
+ oauthConfigurationId: `\${{${safeRegistrationIdName}}}`,
1598
+ },
1599
+ };
1600
+ updatedPart.webApplicationInfo = {
1601
+ id: "${{AAD_APP_CLIENT_ID}}",
1602
+ resource: "api://${{DOMAIN}}/${{AAD_APP_CLIENT_ID}}",
1603
+ };
1604
+ }
1605
+ }
1606
+ updatedPart.composeExtensions = [composeExtension];
1607
+ }
1608
+ updatedPart.description = originalManifest.description;
1609
+ ManifestUpdater.updateManifestDescription(updatedPart, spec);
1610
+ const updatedManifest = Object.assign(Object.assign({}, originalManifest), updatedPart);
1611
+ return [updatedManifest, warnings];
1612
+ }
1613
+ catch (err) {
1614
+ throw new SpecParserError(err.toString(), ErrorType.UpdateManifestFailed);
1615
+ }
1616
+ }
1617
+ static async generateCommands(spec, manifestPath, options, adaptiveCardFolder) {
1618
+ var _a;
1619
+ const paths = spec.paths;
1620
+ const commands = [];
1621
+ const warnings = [];
1622
+ if (paths) {
1623
+ for (const pathUrl in paths) {
1624
+ const pathItem = paths[pathUrl];
1625
+ if (pathItem) {
1626
+ const operations = pathItem;
1627
+ // Currently only support GET and POST method
1628
+ for (const method in operations) {
1629
+ if ((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) {
1630
+ const operationItem = operations[method];
1631
+ if (operationItem) {
1632
+ const command = Utils.parseApiInfo(operationItem, options);
1633
+ if (command.parameters &&
1634
+ command.parameters.length >= 1 &&
1635
+ command.parameters.some((param) => param.isRequired)) {
1636
+ command.parameters = command.parameters.filter((param) => param.isRequired);
1637
+ }
1638
+ else if (command.parameters && command.parameters.length > 0) {
1639
+ command.parameters = [command.parameters[0]];
1640
+ warnings.push({
1641
+ type: WarningType.OperationOnlyContainsOptionalParam,
1642
+ content: Utils.format(ConstantString.OperationOnlyContainsOptionalParam, command.id),
1643
+ data: command.id,
1644
+ });
1645
+ }
1646
+ if (adaptiveCardFolder) {
1647
+ const adaptiveCardPath = path.join(adaptiveCardFolder, command.id + ".json");
1648
+ command.apiResponseRenderingTemplateFile = (await fs.pathExists(adaptiveCardPath))
1649
+ ? ManifestUpdater.getRelativePath(manifestPath, adaptiveCardPath)
1650
+ : "";
1651
+ }
1652
+ commands.push(command);
1653
+ }
1654
+ }
1655
+ }
1656
+ }
1657
+ }
1658
+ }
1659
+ return [commands, warnings];
1660
+ }
1661
+ static getRelativePath(from, to) {
1662
+ const relativePath = path.relative(path.dirname(from), to);
1663
+ return path.normalize(relativePath).replace(/\\/g, "/");
1664
+ }
1665
+ static removeEnvs(str) {
1666
+ const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
1667
+ const matches = placeHolderReg.exec(str);
1668
+ let newStr = str;
1669
+ if (matches != null) {
1670
+ newStr = newStr.replace(matches[0], "");
1671
+ }
1672
+ return newStr;
1673
+ }
1674
+ static removeAllSpecialCharacters(str) {
1675
+ return str.toLowerCase().replace(/[^a-z0-9]/g, "");
1676
+ }
1677
+ static getConfirmationBodyItem(paramName) {
1678
+ return `* **${Utils.updateFirstLetter(paramName)}**: {{function.parameters.${paramName}}}`;
1679
+ }
1680
+ }
1681
+
1283
1682
  // Copyright (c) Microsoft Corporation.
1284
1683
  /**
1285
1684
  * A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
@@ -1299,7 +1698,11 @@ class SpecParser {
1299
1698
  allowMultipleParameters: false,
1300
1699
  allowOauth2: false,
1301
1700
  allowMethods: ["get", "post"],
1701
+ allowConversationStarters: false,
1702
+ allowResponseSemantics: false,
1703
+ allowConfirmation: false,
1302
1704
  projectType: ProjectType.SME,
1705
+ isGptPlugin: false,
1303
1706
  };
1304
1707
  this.pathOrSpec = pathOrDoc;
1305
1708
  this.parser = new SwaggerParser();
@@ -1323,6 +1726,8 @@ class SpecParser {
1323
1726
  errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
1324
1727
  };
1325
1728
  }
1729
+ const errors = [];
1730
+ const warnings = [];
1326
1731
  if (!this.options.allowSwagger && this.isSwaggerFile) {
1327
1732
  return {
1328
1733
  status: ValidationStatus.Error,
@@ -1332,7 +1737,38 @@ class SpecParser {
1332
1737
  ],
1333
1738
  };
1334
1739
  }
1335
- return Utils.validateSpec(this.spec, this.parser, !!this.isSwaggerFile, this.options);
1740
+ // Remote reference not supported
1741
+ const refPaths = this.parser.$refs.paths();
1742
+ // refPaths [0] is the current spec file path
1743
+ if (refPaths.length > 1) {
1744
+ errors.push({
1745
+ type: ErrorType.RemoteRefNotSupported,
1746
+ content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
1747
+ data: refPaths,
1748
+ });
1749
+ }
1750
+ if (!!this.isSwaggerFile && this.options.allowSwagger) {
1751
+ warnings.push({
1752
+ type: WarningType.ConvertSwaggerToOpenAPI,
1753
+ content: ConstantString.ConvertSwaggerToOpenAPI,
1754
+ });
1755
+ }
1756
+ const validator = this.getValidator(this.spec);
1757
+ const validationResult = validator.validateSpec();
1758
+ warnings.push(...validationResult.warnings);
1759
+ errors.push(...validationResult.errors);
1760
+ let status = ValidationStatus.Valid;
1761
+ if (warnings.length > 0 && errors.length === 0) {
1762
+ status = ValidationStatus.Warning;
1763
+ }
1764
+ else if (errors.length > 0) {
1765
+ status = ValidationStatus.Error;
1766
+ }
1767
+ return {
1768
+ status: status,
1769
+ warnings: warnings,
1770
+ errors: errors,
1771
+ };
1336
1772
  }
1337
1773
  catch (err) {
1338
1774
  throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
@@ -1352,45 +1788,40 @@ class SpecParser {
1352
1788
  try {
1353
1789
  await this.loadSpec();
1354
1790
  const spec = this.spec;
1355
- const apiMap = this.getAllSupportedAPIs(spec);
1791
+ const apiMap = this.getAPIs(spec);
1356
1792
  const result = {
1357
- validAPIs: [],
1793
+ APIs: [],
1358
1794
  allAPICount: 0,
1359
1795
  validAPICount: 0,
1360
1796
  };
1361
1797
  for (const apiKey in apiMap) {
1798
+ const { operation, isValid, reason } = apiMap[apiKey];
1799
+ const [method, path] = apiKey.split(" ");
1800
+ const operationId = (_a = operation.operationId) !== null && _a !== void 0 ? _a : `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
1362
1801
  const apiResult = {
1363
- api: "",
1802
+ api: apiKey,
1364
1803
  server: "",
1365
- operationId: "",
1804
+ operationId: operationId,
1805
+ isValid: isValid,
1806
+ reason: reason,
1366
1807
  };
1367
- const [method, path] = apiKey.split(" ");
1368
- const operation = apiMap[apiKey];
1369
- const rootServer = spec.servers && spec.servers[0];
1370
- const methodServer = spec.paths[path].servers && ((_a = spec.paths[path]) === null || _a === void 0 ? void 0 : _a.servers[0]);
1371
- const operationServer = operation.servers && operation.servers[0];
1372
- const serverUrl = operationServer || methodServer || rootServer;
1373
- if (!serverUrl) {
1374
- throw new SpecParserError(ConstantString.NoServerInformation, ErrorType.NoServerInformation);
1375
- }
1376
- apiResult.server = Utils.resolveServerUrl(serverUrl.url);
1377
- let operationId = operation.operationId;
1378
- if (!operationId) {
1379
- operationId = `${method.toLowerCase()}${Utils.convertPathToCamelCase(path)}`;
1380
- }
1381
- apiResult.operationId = operationId;
1382
- const authArray = Utils.getAuthArray(operation.security, spec);
1383
- for (const auths of authArray) {
1384
- if (auths.length === 1) {
1385
- apiResult.auth = auths[0];
1386
- break;
1808
+ if (isValid) {
1809
+ const serverObj = Utils.getServerObject(spec, method.toLocaleLowerCase(), path);
1810
+ if (serverObj) {
1811
+ apiResult.server = Utils.resolveEnv(serverObj.url);
1812
+ }
1813
+ const authArray = Utils.getAuthArray(operation.security, spec);
1814
+ for (const auths of authArray) {
1815
+ if (auths.length === 1) {
1816
+ apiResult.auth = auths[0];
1817
+ break;
1818
+ }
1387
1819
  }
1388
1820
  }
1389
- apiResult.api = apiKey;
1390
- result.validAPIs.push(apiResult);
1821
+ result.APIs.push(apiResult);
1391
1822
  }
1392
- result.allAPICount = Utils.getAllAPICount(spec);
1393
- result.validAPICount = result.validAPIs.length;
1823
+ result.allAPICount = result.APIs.length;
1824
+ result.validAPICount = result.APIs.filter((api) => api.isValid).length;
1394
1825
  return result;
1395
1826
  }
1396
1827
  catch (err) {
@@ -1443,18 +1874,12 @@ class SpecParser {
1443
1874
  const newSpecs = await this.getFilteredSpecs(filter, signal);
1444
1875
  const newUnResolvedSpec = newSpecs[0];
1445
1876
  const newSpec = newSpecs[1];
1446
- let resultStr;
1447
- if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
1448
- resultStr = jsyaml.dump(newUnResolvedSpec);
1449
- }
1450
- else {
1451
- resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
1452
- }
1453
- await fs.outputFile(outputSpecPath, resultStr);
1877
+ const authInfo = Utils.getAuthInfo(newSpec);
1878
+ await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
1454
1879
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1455
1880
  throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
1456
1881
  }
1457
- const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options);
1882
+ const [updatedManifest, apiPlugin] = await ManifestUpdater.updateManifestWithAiPlugin(manifestPath, outputSpecPath, pluginFilePath, newSpec, this.options, authInfo);
1458
1883
  await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
1459
1884
  await fs.outputJSON(pluginFilePath, apiPlugin, { spaces: 2 });
1460
1885
  }
@@ -1482,32 +1907,11 @@ class SpecParser {
1482
1907
  const newSpecs = await this.getFilteredSpecs(filter, signal);
1483
1908
  const newUnResolvedSpec = newSpecs[0];
1484
1909
  const newSpec = newSpecs[1];
1485
- const authSet = new Set();
1486
- let hasMultipleAuth = false;
1487
- for (const url in newSpec.paths) {
1488
- for (const method in newSpec.paths[url]) {
1489
- const operation = newSpec.paths[url][method];
1490
- const authArray = Utils.getAuthArray(operation.security, newSpec);
1491
- if (authArray && authArray.length > 0) {
1492
- authSet.add(authArray[0][0]);
1493
- if (authSet.size > 1) {
1494
- hasMultipleAuth = true;
1495
- break;
1496
- }
1497
- }
1498
- }
1499
- }
1500
- if (hasMultipleAuth && this.options.projectType !== ProjectType.TeamsAi) {
1501
- throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
1502
- }
1503
- let resultStr;
1504
- if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
1505
- resultStr = jsyaml.dump(newUnResolvedSpec);
1506
- }
1507
- else {
1508
- resultStr = JSON.stringify(newUnResolvedSpec, null, 2);
1910
+ let authInfo = undefined;
1911
+ if (this.options.projectType === ProjectType.SME) {
1912
+ authInfo = Utils.getAuthInfo(newSpec);
1509
1913
  }
1510
- await fs.outputFile(outputSpecPath, resultStr);
1914
+ await this.saveFilterSpec(outputSpecPath, newUnResolvedSpec);
1511
1915
  if (adaptiveCardFolder) {
1512
1916
  for (const url in newSpec.paths) {
1513
1917
  for (const method in newSpec.paths[url]) {
@@ -1537,7 +1941,6 @@ class SpecParser {
1537
1941
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
1538
1942
  throw new SpecParserError(ConstantString.CancelledMessage, ErrorType.Cancelled);
1539
1943
  }
1540
- const authInfo = Array.from(authSet)[0];
1541
1944
  const [updatedManifest, warnings] = await ManifestUpdater.updateManifest(manifestPath, outputSpecPath, newSpec, this.options, adaptiveCardFolder, authInfo);
1542
1945
  await fs.outputJSON(manifestPath, updatedManifest, { spaces: 2 });
1543
1946
  result.warnings.push(...warnings);
@@ -1563,13 +1966,28 @@ class SpecParser {
1563
1966
  this.spec = (await this.parser.dereference(clonedUnResolveSpec));
1564
1967
  }
1565
1968
  }
1566
- getAllSupportedAPIs(spec) {
1567
- if (this.apiMap !== undefined) {
1568
- return this.apiMap;
1969
+ getAPIs(spec) {
1970
+ const validator = this.getValidator(spec);
1971
+ const apiMap = validator.listAPIs();
1972
+ return apiMap;
1973
+ }
1974
+ getValidator(spec) {
1975
+ if (this.validator) {
1976
+ return this.validator;
1569
1977
  }
1570
- const result = Utils.listSupportedAPIs(spec, this.options);
1571
- this.apiMap = result;
1572
- return result;
1978
+ const validator = ValidatorFactory.create(spec, this.options);
1979
+ this.validator = validator;
1980
+ return validator;
1981
+ }
1982
+ async saveFilterSpec(outputSpecPath, unResolvedSpec) {
1983
+ let resultStr;
1984
+ if (outputSpecPath.endsWith(".yaml") || outputSpecPath.endsWith(".yml")) {
1985
+ resultStr = jsyaml.dump(unResolvedSpec);
1986
+ }
1987
+ else {
1988
+ resultStr = JSON.stringify(unResolvedSpec, null, 2);
1989
+ }
1990
+ await fs.outputFile(outputSpecPath, resultStr);
1573
1991
  }
1574
1992
  }
1575
1993