diff --git a/packages/api/review/teamsfx-api.api.md b/packages/api/review/teamsfx-api.api.md index 684b32c9a6..483c35996b 100644 --- a/packages/api/review/teamsfx-api.api.md +++ b/packages/api/review/teamsfx-api.api.md @@ -15,15 +15,16 @@ import { TokenCredential } from '@azure/core-auth'; // @public (undocumented) export interface ApiOperation { // (undocumented) - authName?: string; + data: { + serverUrl: string; + authName?: string; + }; // (undocumented) groupName: string; // (undocumented) id: string; // (undocumented) label: string; - // (undocumented) - serverUrl: string; } // @public (undocumented) diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 9bde1eaa14..9934bf704c 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -145,8 +145,10 @@ export interface ApiOperation { id: string; label: string; groupName: string; - serverUrl: string; - authName?: string; + data: { + serverUrl: string; + authName?: string; + }; } export interface Warning { diff --git a/packages/fx-core/src/component/generator/copilotPlugin/generator.ts b/packages/fx-core/src/component/generator/copilotPlugin/generator.ts index 02befdfa67..cf708c1b17 100644 --- a/packages/fx-core/src/component/generator/copilotPlugin/generator.ts +++ b/packages/fx-core/src/component/generator/copilotPlugin/generator.ts @@ -19,6 +19,7 @@ import { ResponseTemplatesFolderName, AppPackageFolderName, Warning, + ApiOperation, } from "@microsoft/teamsfx-api"; import { Generator } from "../generator"; import path from "path"; @@ -41,7 +42,6 @@ import { ProgrammingLanguage } from "../../../question/create"; import * as fs from "fs-extra"; import { assembleError } from "../../../error"; import { - ConstantString, SpecParserError, SpecParser, ValidationStatus, @@ -49,9 +49,12 @@ import { } from "../../../common/spec-parser"; import * as util from "util"; import { isValidHttpUrl } from "../../../question/util"; +import { isApiKeyEnabled } from "../../../common/featureFlags"; -const fromApiSpeccomponentName = "copilot-plugin-existing-api"; +const fromApiSpecComponentName = "copilot-plugin-existing-api"; const fromApiSpecTemplateName = "copilot-plugin-existing-api"; +const fromApiSpecWithApiKeyComponentName = "copilot-plugin-existing-api-api-key"; +const fromApiSpecWithApiKeyTemplateName = "copilot-plugin-existing-api-api-key"; const fromOpenAIPlugincomponentName = "copilot-plugin-from-oai-plugin"; const fromOpenAIPluginTemplateName = "copilot-plugin-from-oai-plugin"; const apiSpecFolderName = "apiSpecificationFiles"; @@ -70,9 +73,9 @@ export class CopilotPluginGenerator { @hooks([ ActionExecutionMW({ enableTelemetry: true, - telemetryComponentName: fromApiSpeccomponentName, + telemetryComponentName: fromApiSpecComponentName, telemetryEventName: TelemetryEvents.Generate, - errorSource: fromApiSpeccomponentName, + errorSource: fromApiSpecComponentName, }), ]) public static async generateFromApiSpec( @@ -80,12 +83,15 @@ export class CopilotPluginGenerator { inputs: Inputs, destinationPath: string ): Promise> { + const hasAuth = (inputs[QuestionNames.ApiOperation] as ApiOperation[]).find( + (api) => !!api.data.authName + ); return await this.generateForME( context, inputs, destinationPath, - fromApiSpecTemplateName, - fromApiSpeccomponentName + hasAuth ? fromApiSpecWithApiKeyTemplateName : fromApiSpecTemplateName, + hasAuth ? fromApiSpecWithApiKeyComponentName : fromApiSpecComponentName ); } @@ -124,7 +130,8 @@ export class CopilotPluginGenerator { const safeProjectNameFromVS = language === "csharp" ? inputs[QuestionNames.SafeProjectName] : undefined; context.templateVariables = Generator.getDefaultVariables(appName, safeProjectNameFromVS); - const filters = inputs[QuestionNames.ApiOperation] as string[]; + const apiOperations = inputs[QuestionNames.ApiOperation] as ApiOperation[]; + const filters = apiOperations.map((api) => api.id); // download template const templateRes = await Generator.generateTemplate( context, @@ -141,7 +148,8 @@ export class CopilotPluginGenerator { }); // validate API spec - const specParser = new SpecParser(url); + const allowAPIKeyAuth = isApiKeyEnabled(); + const specParser = new SpecParser(url, { allowAPIKeyAuth }); const validationRes = await specParser.validate(); const warnings = validationRes.warnings; const operationIdWarning = warnings.find((w) => w.type === WarningType.OperationIdMissing); diff --git a/packages/fx-core/src/component/generator/copilotPlugin/helper.ts b/packages/fx-core/src/component/generator/copilotPlugin/helper.ts index a5db44b164..a6e554512c 100644 --- a/packages/fx-core/src/component/generator/copilotPlugin/helper.ts +++ b/packages/fx-core/src/component/generator/copilotPlugin/helper.ts @@ -44,6 +44,7 @@ import { EOL } from "os"; import { SummaryConstant } from "../../configManager/constant"; import { manifestUtils } from "../../driver/teamsApp/utils/ManifestUtils"; import path from "path"; +import { isApiKeyEnabled } from "../../../common/featureFlags"; const manifestFilePath = "/.well-known/ai-plugin.json"; const componentName = "OpenAIPluginManifestHelper"; @@ -152,7 +153,8 @@ export async function listOperations( } try { - const specParser = new SpecParser(apiSpecUrl!); + const allowAPIKeyAuth = isApiKeyEnabled(); + const specParser = new SpecParser(apiSpecUrl as string, { allowAPIKeyAuth }); const validationRes = await specParser.validate(); validationRes.errors = formatValidationErrors(validationRes.errors); @@ -219,11 +221,13 @@ function sortOperations(operations: ListAPIResult[]): ApiOperation[] { id: operation.api, label: operation.api, groupName: arr[0], - serverUrl: operation.server, + data: { + serverUrl: operation.server, + }, }; if (operation.auth) { - result.authName = operation.auth.name; + result.data.authName = operation.auth.name; } operationsWithSeparator.push(result); } @@ -576,13 +580,13 @@ function formatValidationErrorContent(error: ApiSpecErrorResult): string { try { switch (error.type) { case ErrorType.SpecNotValid: { - let content = error.content; + let content: string = error.content; if (error.content.startsWith("ResolverError: Error downloading")) { content = error.content .split("\n") .map((o) => o.trim()) .join(". "); - content = content + ". " + getLocalizedString("core.common.ErrorFetchApiSpec"); + content = `${content}. ${getLocalizedString("core.common.ErrorFetchApiSpec")}`; } return content; } diff --git a/packages/fx-core/src/core/FxCore.ts b/packages/fx-core/src/core/FxCore.ts index a3466a34ec..eeb8bcd397 100644 --- a/packages/fx-core/src/core/FxCore.ts +++ b/packages/fx-core/src/core/FxCore.ts @@ -113,6 +113,7 @@ import { } from "./middleware/utils/v3MigrationUtils"; import { CoreTelemetryComponentName, CoreTelemetryEvent, CoreTelemetryProperty } from "./telemetry"; import { CoreHookContext, PreProvisionResForVS, VersionCheckRes } from "./types"; +import { isApiKeyEnabled } from "../common/featureFlags"; import "../component/feature/sso"; export type CoreCallbackFunc = (name: string, err?: FxError, data?: any) => void | Promise; @@ -1162,7 +1163,7 @@ export class FxCore { const outputAPISpecPath = path.join(path.dirname(manifestPath), apiSpecificationFile!); // Merge exisiting operations in manifest.json - const specParser = new SpecParser(url); + const specParser = new SpecParser(url, { allowAPIKeyAuth: isApiKeyEnabled() }); const existingOperationIds = manifestUtils.getOperationIds(manifestRes.value); const operationMaps = await specParser.listOperationMap(); diff --git a/packages/fx-core/src/question/create.ts b/packages/fx-core/src/question/create.ts index 1eeb82ff8e..5ad87fa8f8 100644 --- a/packages/fx-core/src/question/create.ts +++ b/packages/fx-core/src/question/create.ts @@ -1551,6 +1551,7 @@ export function apiOperationQuestion(includeExistingAPIs = true): MultiSelectQue return { type: "multiSelect", name: QuestionNames.ApiOperation, + returnObject: true, title: getLocalizedString("core.createProjectQuestion.apiSpec.operation.title"), cliDescription: "Select Operation(s) Teams Can Interact with.", cliShortName: "o", @@ -1575,10 +1576,10 @@ export function apiOperationQuestion(includeExistingAPIs = true): MultiSelectQue for (const inputItem of input) { const operation = operations.find((op) => op.id === inputItem); if (operation) { - if (operation.authName) { - authNames.add(operation.authName); + if (operation.data.authName) { + authNames.add(operation.data.authName); } - serverUrls.add(operation.serverUrl); + serverUrls.add(operation.data.serverUrl); } } diff --git a/packages/fx-core/tests/component/generator/copilotPluginGenerator.test.ts b/packages/fx-core/tests/component/generator/copilotPluginGenerator.test.ts index 94f860c110..0e0a637f4f 100644 --- a/packages/fx-core/tests/component/generator/copilotPluginGenerator.test.ts +++ b/packages/fx-core/tests/component/generator/copilotPluginGenerator.test.ts @@ -103,6 +103,16 @@ describe("copilotPluginGenerator", function () { platform: Platform.VSCode, projectPath: "path", [QuestionNames.ApiSpecLocation]: "https://test.com", + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox @@ -131,7 +141,16 @@ describe("copilotPluginGenerator", function () { projectPath: "path", [QuestionNames.ProgrammingLanguage]: ProgrammingLanguage.CSharp, [QuestionNames.ApiSpecLocation]: "https://test.com", - [QuestionNames.ApiOperation]: ["operation1"], + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox.stub(SpecParser.prototype, "validate").resolves({ @@ -183,7 +202,16 @@ describe("copilotPluginGenerator", function () { projectPath: "path", [QuestionNames.ProgrammingLanguage]: ProgrammingLanguage.CSharp, [QuestionNames.ApiSpecLocation]: "https://test.com", - [QuestionNames.ApiOperation]: ["operation1"], + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox.stub(SpecParser.prototype, "validate").resolves({ @@ -214,6 +242,16 @@ describe("copilotPluginGenerator", function () { projectPath: "path", [QuestionNames.ProgrammingLanguage]: ProgrammingLanguage.CSharp, [QuestionNames.ApiSpecLocation]: "https://test.com", + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox.stub(SpecParser.prototype, "validate").resolves({ @@ -240,6 +278,16 @@ describe("copilotPluginGenerator", function () { platform: Platform.VSCode, projectPath: "path", openAIPluginManifest: openAIPluginManifest, + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox @@ -274,6 +322,16 @@ describe("copilotPluginGenerator", function () { platform: Platform.VSCode, projectPath: "path", openAIPluginManifest: openAIPluginManifest, + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox @@ -308,6 +366,16 @@ describe("copilotPluginGenerator", function () { platform: Platform.VSCode, projectPath: "path", [QuestionNames.ApiSpecLocation]: "https://test.com", + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox.stub(SpecParser.prototype, "generate").resolves(); @@ -325,6 +393,16 @@ describe("copilotPluginGenerator", function () { platform: Platform.VSCode, projectPath: "path", [QuestionNames.ApiSpecLocation]: "https://test.com", + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox.stub(SpecParser.prototype, "validate").resolves({ @@ -350,6 +428,16 @@ describe("copilotPluginGenerator", function () { platform: Platform.VSCode, projectPath: "path", [QuestionNames.ApiSpecLocation]: "https://test.com", + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox @@ -377,6 +465,16 @@ describe("copilotPluginGenerator", function () { platform: Platform.VSCode, projectPath: "path", [QuestionNames.ApiSpecLocation]: "https://test.com", + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox.stub(Generator, "generateTemplate").throws(new Error("test")); @@ -391,6 +489,16 @@ describe("copilotPluginGenerator", function () { platform: Platform.VSCode, projectPath: "path", [QuestionNames.ApiSpecLocation]: "https://test.com", + [QuestionNames.ApiOperation]: [ + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], }; const context = createContextV3(); sandbox diff --git a/packages/fx-core/tests/core/FxCore.test.ts b/packages/fx-core/tests/core/FxCore.test.ts index 9dd80f9b17..bd2c92723d 100644 --- a/packages/fx-core/tests/core/FxCore.test.ts +++ b/packages/fx-core/tests/core/FxCore.test.ts @@ -1486,7 +1486,16 @@ describe("copilotPlugin", async () => { platform: Platform.VSCode, [QuestionNames.Folder]: os.tmpdir(), [QuestionNames.ApiSpecLocation]: "test.json", - [QuestionNames.ApiOperation]: ["GET /user/{userId}"], + [QuestionNames.ApiOperation]: [ + { + id: "GET /user/{userId}", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], [QuestionNames.ManifestPath]: "manifest.json", projectPath: path.join(os.tmpdir(), appName), }; @@ -1521,7 +1530,16 @@ describe("copilotPlugin", async () => { platform: Platform.VSCode, [QuestionNames.Folder]: os.tmpdir(), [QuestionNames.ApiSpecLocation]: "test.json", - [QuestionNames.ApiOperation]: ["GET /user/{userId}"], + [QuestionNames.ApiOperation]: [ + { + id: "GET /user/{userId}", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], [QuestionNames.ManifestPath]: "manifest.json", projectPath: path.join(os.tmpdir(), appName), }; @@ -1565,7 +1583,16 @@ describe("copilotPlugin", async () => { platform: Platform.VSCode, [QuestionNames.Folder]: os.tmpdir(), [QuestionNames.ApiSpecLocation]: "test.json", - [QuestionNames.ApiOperation]: ["testOperation"], + [QuestionNames.ApiOperation]: [ + { + id: "testOperation", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], projectPath: path.join(os.tmpdir(), appName), }; const core = new FxCore(tools); @@ -1590,7 +1617,16 @@ describe("copilotPlugin", async () => { platform: Platform.VSCode, [QuestionNames.Folder]: os.tmpdir(), [QuestionNames.ApiSpecLocation]: "test.json", - [QuestionNames.ApiOperation]: ["testOperation"], + [QuestionNames.ApiOperation]: [ + { + id: "testOperation", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], [QuestionNames.ManifestPath]: "manifest.json", projectPath: path.join(os.tmpdir(), appName), }; @@ -1617,7 +1653,16 @@ describe("copilotPlugin", async () => { platform: Platform.VSCode, [QuestionNames.Folder]: os.tmpdir(), [QuestionNames.ApiSpecLocation]: "test.json", - [QuestionNames.ApiOperation]: ["testOperation"], + [QuestionNames.ApiOperation]: [ + { + id: "testOperation", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], [QuestionNames.ManifestPath]: "manifest.json", projectPath: path.join(os.tmpdir(), appName), }; @@ -1660,8 +1705,18 @@ describe("copilotPlugin", async () => { shouldLogWarning: true, }; const expectedResult = [ - { id: "operation1", label: "operation1", groupName: "1", serverUrl: "https://server1" }, - { id: "operation2", label: "operation2", groupName: "2", serverUrl: "https://server2" }, + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { serverUrl: "https://server1" }, + }, + { + id: "operation2", + label: "operation2", + groupName: "2", + data: { serverUrl: "https://server2" }, + }, ]; sinon.stub(CopilotPluginHelper, "listOperations").returns(Promise.resolve(ok(expectedResult))); const result = await core.copilotPluginListOperations(inputs as any); diff --git a/packages/fx-core/tests/question/create.test.ts b/packages/fx-core/tests/question/create.test.ts index f6e26d757b..8c54b9aef1 100644 --- a/packages/fx-core/tests/question/create.test.ts +++ b/packages/fx-core/tests/question/create.test.ts @@ -1018,8 +1018,22 @@ describe("scaffold question", () => { platform: Platform.VSCode, [QuestionNames.ApiSpecLocation]: "apispec", supportedApisFromApiSpec: [ - { id: "operation1", label: "operation1", groupName: "1" }, - { id: "operation2", label: "operation2", groupName: "2" }, + { + id: "operation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + { + id: "operation2", + label: "operation2", + groupName: "2", + data: { + serverUrl: "https://server1", + }, + }, ], }; @@ -1040,13 +1054,17 @@ describe("scaffold question", () => { id: "operation1", label: "operation1", groupName: "1", - serverUrl: "https://server1", + data: { + serverUrl: "https://server1", + }, }, { id: "operation2", label: "operation2", groupName: "2", - serverUrl: "https://server1", + data: { + serverUrl: "https://server1", + }, }, ], }; @@ -1067,15 +1085,19 @@ describe("scaffold question", () => { id: "operation1", label: "operation1", groupName: "1", - authName: "auth1", - serverUrl: "https://server1", + data: { + authName: "auth1", + serverUrl: "https://server1", + }, }, { id: "operation2", label: "operation2", groupName: "2", - authName: "auth1", - serverUrl: "https://server1", + data: { + authName: "auth1", + serverUrl: "https://server1", + }, }, ], }; @@ -1096,15 +1118,19 @@ describe("scaffold question", () => { id: "operation1", label: "operation1", groupName: "1", - authName: "auth1", - serverUrl: "https://server1", + data: { + authName: "auth1", + serverUrl: "https://server1", + }, }, { id: "operation2", label: "operation2", groupName: "2", - authName: "auth1", - serverUrl: "https://server2", + data: { + authName: "auth1", + serverUrl: "https://server2", + }, }, ], }; @@ -1131,22 +1157,28 @@ describe("scaffold question", () => { id: "operation1", label: "operation1-label", groupName: "1", - authName: "auth1", - serverUrl: "https://server1", + data: { + authName: "auth1", + serverUrl: "https://server1", + }, }, { id: "operation2", label: "operation2-label", groupName: "2", - authName: "auth2", - serverUrl: "https://server1", + data: { + authName: "auth2", + serverUrl: "https://server1", + }, }, { id: "operation3", label: "operation3-label", groupName: "1", - authName: "auth3", - serverUrl: "https://server1", + data: { + authName: "auth3", + serverUrl: "https://server1", + }, }, ], }; @@ -1227,14 +1259,18 @@ describe("scaffold question", () => { id: "get operation1", label: "get operation1", groupName: "GET", - authName: "api_key", - serverUrl: "https://server", + data: { + authName: "api_key", + serverUrl: "https://server", + }, }, { id: "get operation2", label: "get operation2", groupName: "GET", - serverUrl: "https://server2", + data: { + serverUrl: "https://server2", + }, }, ]); assert.isUndefined(res); @@ -1270,14 +1306,18 @@ describe("scaffold question", () => { id: "get operation1", label: "get operation1", groupName: "GET", - authName: "api_key", - serverUrl: "https://server", + data: { + authName: "api_key", + serverUrl: "https://server", + }, }, { id: "get operation2", label: "get operation2", groupName: "GET", - serverUrl: "https://server2", + data: { + serverUrl: "https://server2", + }, }, ]); assert.isUndefined(res); @@ -1440,7 +1480,9 @@ describe("scaffold question", () => { id: "GET /store/order", label: "GET /store/order", groupName: "GET", - serverUrl: "https://server2", + data: { + serverUrl: "https://server2", + }, }, ]); assert.isUndefined(res); diff --git a/packages/fx-core/tests/question/question.test.ts b/packages/fx-core/tests/question/question.test.ts index 22ce0acb67..415e801696 100644 --- a/packages/fx-core/tests/question/question.test.ts +++ b/packages/fx-core/tests/question/question.test.ts @@ -867,7 +867,19 @@ describe("copilotPluginQuestions", async () => { result: ["https://example.json"], }); } else if (question.name == QuestionNames.ApiOperation) { - return ok({ type: "success", result: ["testOperation1"] }); + return ok({ + type: "success", + result: [ + { + id: "testOperation1", + label: "operation1", + groupName: "1", + data: { + serverUrl: "https://server1", + }, + }, + ], + }); } return ok({ type: "success", result: undefined }); }; diff --git a/templates/common/copilot-plugin-existing-api-api-key/.gitignore b/templates/common/copilot-plugin-existing-api-api-key/.gitignore new file mode 100644 index 0000000000..e567799519 --- /dev/null +++ b/templates/common/copilot-plugin-existing-api-api-key/.gitignore @@ -0,0 +1,13 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +.localConfigs +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store diff --git a/templates/common/copilot-plugin-existing-api-api-key/.vscode/extensions.json b/templates/common/copilot-plugin-existing-api-api-key/.vscode/extensions.json new file mode 100644 index 0000000000..aac0a6e347 --- /dev/null +++ b/templates/common/copilot-plugin-existing-api-api-key/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} diff --git a/templates/common/copilot-plugin-existing-api-api-key/.vscode/launch.json b/templates/common/copilot-plugin-existing-api-api-key/.vscode/launch.json new file mode 100644 index 0000000000..b96979920b --- /dev/null +++ b/templates/common/copilot-plugin-existing-api-api-key/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Preview in Teams (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com?${account-hint}", + "presentation": { + "group": "remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Preview in Teams (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com?${account-hint}", + "presentation": { + "group": "remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + } + ] +} diff --git a/templates/common/copilot-plugin-existing-api-api-key/.vscode/settings.json b/templates/common/copilot-plugin-existing-api-api-key/.vscode/settings.json new file mode 100644 index 0000000000..4299620253 --- /dev/null +++ b/templates/common/copilot-plugin-existing-api-api-key/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ] +} diff --git a/templates/common/copilot-plugin-existing-api-api-key/README.md b/templates/common/copilot-plugin-existing-api-api-key/README.md new file mode 100644 index 0000000000..74a7e3a6d7 --- /dev/null +++ b/templates/common/copilot-plugin-existing-api-api-key/README.md @@ -0,0 +1,42 @@ +# Overview of Custom Search Results app template + +## Build a message extension from OpenAPI description document + +This app template allows Teams to interact directly with third-party data, apps, and services, enhancing its capabilities and broadening its range of capabilities. It allows Teams to: + +- Retrieve real-time information, for example, latest news coverage on a product launch. +- Retrieve knowledge-based information, for example, my team’s design files in Figma. + +## Get started with the template + +> **Prerequisites** +> +> To run this app template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 16, 18 +> - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts). +> - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teamsfx-cli) + +1. First, select the Teams Toolkit icon on the left in the VS Code toolbar. +2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already. +3. Create Teams app by clicking `Provision` in "Lifecycle" section. +4. Select `Preview in Teams (Edge)` or `Preview in Teams (Chrome)` from the launch configuration dropdown. +5. When Teams launches in the browser, you can navigate to a chat message and [trigger your search commands from compose message area](https://learn.microsoft.com/microsoftteams/platform/messaging-extensions/what-are-messaging-extensions?tabs=dotnet#search-commands). + +## What's included in the template + +| Folder | Contents | +| ------------ | -------------------------------------------- | +| `.vscode` | VSCode files for debugging | +| `appPackage` | Templates for the Teams application manifest, the API specification and response templates for API responses | +| `env` | Environment files | + +The following are Teams Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Teams Toolkit works. + +| File | Contents | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `teamsapp.yml` | This is the main Teams Toolkit project file. The project file defines two primary things: Properties and configuration Stage definitions. | + +## Addition information and references + +- [Extend Teams platform with APIs](https://aka.ms/teamsfx-api-plugin) diff --git a/templates/common/copilot-plugin-existing-api-api-key/appPackage/color.png b/templates/common/copilot-plugin-existing-api-api-key/appPackage/color.png new file mode 100644 index 0000000000..f27ccf2036 Binary files /dev/null and b/templates/common/copilot-plugin-existing-api-api-key/appPackage/color.png differ diff --git a/templates/common/copilot-plugin-existing-api-api-key/appPackage/manifest.json.tpl b/templates/common/copilot-plugin-existing-api-api-key/appPackage/manifest.json.tpl new file mode 100644 index 0000000000..e4751b3e60 --- /dev/null +++ b/templates/common/copilot-plugin-existing-api-api-key/appPackage/manifest.json.tpl @@ -0,0 +1,32 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.schema.json", + "manifestVersion": "devPreview", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "packageName": "com.microsoft.teams.extension", + "developer": { + "name": "Teams App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/termofuse", + "termsOfUseUrl": "https://www.example.com/privacy" + }, + "icons": { + "color": "color.png", + "outline": "outline.png" + }, + "name": { + "short": "{{appName}}${{APP_NAME_SUFFIX}}", + "full": "Full name for {{appName}}" + }, + "description": { + "short": "Short description for {{appName}}", + "full": "Full description for {{appName}}" + }, + "accentColor": "#FFFFFF", + "composeExtensions": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} \ No newline at end of file diff --git a/templates/common/copilot-plugin-existing-api-api-key/appPackage/outline.png b/templates/common/copilot-plugin-existing-api-api-key/appPackage/outline.png new file mode 100644 index 0000000000..e8cb4b6ba4 Binary files /dev/null and b/templates/common/copilot-plugin-existing-api-api-key/appPackage/outline.png differ diff --git a/templates/common/copilot-plugin-existing-api-api-key/env/.env.dev b/templates/common/copilot-plugin-existing-api-api-key/env/.env.dev new file mode 100644 index 0000000000..c53ffe21ce --- /dev/null +++ b/templates/common/copilot-plugin-existing-api-api-key/env/.env.dev @@ -0,0 +1,8 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Generated during provision, you can also add your own variables. +TEAMS_APP_ID= \ No newline at end of file diff --git a/templates/common/copilot-plugin-existing-api-api-key/teamsapp.yml.tpl b/templates/common/copilot-plugin-existing-api-api-key/teamsapp.yml.tpl new file mode 100644 index 0000000000..8c23305069 --- /dev/null +++ b/templates/common/copilot-plugin-existing-api-api-key/teamsapp.yml.tpl @@ -0,0 +1,90 @@ +# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.0.0/yaml.schema.json +# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file +# Visit https://aka.ms/teamsfx-actions for details on actions +version: 1.0.0 + +environmentFolderPath: ./env + +# Triggered when 'teamsfx provision' is executed +provision: + # Creates a Teams app + - uses: teamsApp/create + with: + # Teams app name + name: {{appName}}${{APP_NAME_SUFFIX}} + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + teamsAppId: TEAMS_APP_ID + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Extend your Teams app to Outlook and the Microsoft 365 app + - uses: teamsApp/extendToM365 + with: + # Relative path to the build app package. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + titleId: M365_TITLE_ID + appId: M365_APP_ID + +# Triggered when 'teamsfx publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build Teams app package with latest env value + - uses: teamsApp/zipAppPackage + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json + # Validate app package using validation rules + - uses: teamsApp/validateAppPackage + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Apply the Teams app manifest to an existing Teams app in + # Teams Developer Portal. + # Will use the app id in manifest file to determine which Teams app to update. + - uses: teamsApp/update + with: + # Relative path to this file. This is the path for built zip file. + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Publish the app to + # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps) + # for review and approval + - uses: teamsApp/publishAppPackage + with: + appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip + # Write the information of created resources into environment file for + # the specified environment variable(s). + writeToEnvironmentFile: + publishedAppId: TEAMS_APP_PUBLISHED_APP_ID