Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -387,4 +387,8 @@ mochawesome-report
# metrics
.metrics

package-lock.json
package-lock.json

# add claude related ignores
.claude/
CLAUDE.md
1 change: 1 addition & 0 deletions packages/cli/src/commands/models/listTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function listAllCapabilities(): OptionItem[] {
VSCapabilityOptions.empty(),
VSCapabilityOptions.declarativeAgent(),
TeamsAgentCapabilityOptions.basicChatbot(),
TeamsAgentCapabilityOptions.collaboratorAgent(),
TeamsAgentCapabilityOptions.customCopilotRag(),
// TeamsAgentCapabilityOptions.aiAgent(),
VSCapabilityOptions.weatherAgentBot(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,5 +235,11 @@ export const teamsAgentsAndAppsTemplates: Template[] = [
language: "typescript",
description: "",
},
{
id: "teams-collaborator-agent-csharp",
name: TemplateNames.TeamsCollaboratorAgent,
language: "csharp",
description: "",
},
...teamsOtherTemplates,
];
2 changes: 2 additions & 0 deletions packages/fx-core/src/question/scaffold/vs/createRootNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export function scaffoldQuestionForVS(): IQTreeNode {
VSCapabilityOptions.empty(),
VSCapabilityOptions.declarativeAgent(),
TeamsAgentCapabilityOptions.basicChatbot(),
TeamsAgentCapabilityOptions.collaboratorAgent(),
TeamsAgentCapabilityOptions.customCopilotRag(),
// TeamsAgentCapabilityOptions.aiAgent(),
VSCapabilityOptions.weatherAgentBot(),
Expand All @@ -123,6 +124,7 @@ export function scaffoldQuestionForVS(): IQTreeNode {
llmServiceNode({
enum: [
TeamsAgentCapabilityOptions.basicChatbot().id,
TeamsAgentCapabilityOptions.collaboratorAgent().id,
TeamsAgentCapabilityOptions.customCopilotRag().id,
// TeamsAgentCapabilityOptions.aiAgent().id,
VSCapabilityOptions.weatherAgentBot().id,
Expand Down
192 changes: 192 additions & 0 deletions packages/tests/src/e2e/vs/TeamsCollaboratorAgent.dotnet.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/**
* @author Quke <[email protected]>
*/

import { describe } from "mocha";
import * as path from "path";

import { it } from "@microsoft/extra-shot-mocha";

import MockAzureAccountProvider from "@microsoft/m365agentstoolkit-cli/src/commonlib/azureLoginUserPassword";
import { AzureScopes, environmentNameManager } from "@microsoft/teamsfx-core";
import { assert } from "chai";
import fs from "fs-extra";
import { CliHelper } from "../../commonlib/cliHelper";
import { EnvConstants } from "../../commonlib/constants";
import {
getResourceGroupNameFromResourceId,
getSiteNameFromResourceId,
getWebappSettings,
} from "../../commonlib/utilities";
import { Capability } from "../../utils/constants";
import {
cleanUpLocalProject,
createResourceGroup,
deleteResourceGroupByName,
getSubscriptionId,
getTestFolder,
getUniqueAppName,
readContextMultiEnvV3,
} from "../commonUtils";
import {
deleteAadAppByClientId,
deleteBot,
deleteTeamsApp,
getAadAppByClientId,
getBot,
getTeamsApp,
} from "../debug/utility";

describe("Teams Collaborator Agent for csharp version", function () {
const testFolder = getTestFolder();
const subscription = getSubscriptionId();
const appName = getUniqueAppName();
const resourceGroupName = `${appName}-rg`;
const projectPath = path.resolve(testFolder, appName);
const envName = environmentNameManager.getDefaultEnvName();

after(async () => {
// clean up
let context = await readContextMultiEnvV3(projectPath, "local");
if (context?.TEAMS_APP_ID) {
await deleteTeamsApp(context.TEAMS_APP_ID);
}
if (context?.BOT_ID) {
await deleteBot(context.BOT_ID);
await deleteAadAppByClientId(context.BOT_ID);
}

context = await readContextMultiEnvV3(projectPath, "dev");
if (context?.TEAMS_APP_ID) {
await deleteTeamsApp(context.TEAMS_APP_ID);
}
await deleteResourceGroupByName(resourceGroupName);
await cleanUpLocalProject(projectPath);
});

it(
"csharp template",
{
testPlanCaseId: 35527255,
author: "[email protected]",
},
async function () {
// Scaffold
const myRecordAzOpenAI: Record<string, string> = {};
myRecordAzOpenAI["programming-language"] = "csharp";
myRecordAzOpenAI["azure-openai-key"] = "fake";
myRecordAzOpenAI["azure-openai-deployment-name"] = "fake";
myRecordAzOpenAI["azure-openai-endpoint"] = "https://test.com";
const options = Object.entries(myRecordAzOpenAI)
.map(([key, value]) => "--" + key + " " + value)
.join(" ");
const env = Object.assign({}, process.env);
env["TEAMSFX_CLI_DOTNET"] = "true";
await CliHelper.createProjectWithCapability(
appName,
testFolder,
Capability.TeamsCollaboratorAgent,
env,
options
);

// Validate Scaffold
const indexFile = path.join(testFolder, appName, "Program.cs");
fs.access(indexFile, fs.constants.F_OK, (err) => {
assert.notExists(err, "Program.cs should exist");
});

// Local Debug (Provision)
await CliHelper.provisionProject(projectPath, "", "local", {
...process.env,
BOT_DOMAIN: "test.ngrok.io",
BOT_ENDPOINT: "https://test.ngrok.io",
});
console.log(`[Successfully] provision for ${projectPath}`);

let context = await readContextMultiEnvV3(projectPath, "local");
assert.isDefined(context, "local env file should exist");

// validate aad
assert.isUndefined(context.AAD_APP_OBJECT_ID, "AAD should not exist");

// validate teams app
assert.isDefined(context.TEAMS_APP_ID, "teams app id should be defined");
const teamsApp = await getTeamsApp(context.TEAMS_APP_ID);
assert.equal(teamsApp?.teamsAppId, context.TEAMS_APP_ID);

// validate bot
assert.isDefined(context.BOT_ID);
assert.isNotEmpty(context.BOT_ID);
const aadApp = await getAadAppByClientId(context.BOT_ID);
assert.isDefined(aadApp);
assert.equal(aadApp?.appId, context.BOT_ID);
const bot = await getBot(context.BOT_ID);
assert.equal(bot?.botId, context.BOT_ID);
assert.equal(
bot?.messagingEndpoint,
"https://test.ngrok.io/api/messages"
);

// Remote Provision
const result = await createResourceGroup(resourceGroupName, "westus");
assert.isTrue(
result,
`failed to create resource group: ${resourceGroupName}`
);

await CliHelper.provisionProject(projectPath, "", "dev", {
...process.env,
AZURE_RESOURCE_GROUP_NAME: resourceGroupName,
});

context = await readContextMultiEnvV3(projectPath, envName);
assert.exists(context, "env file should exist");

// validate teams app
assert.isDefined(context.TEAMS_APP_ID);
const remoteTeamsApp = await getTeamsApp(context.TEAMS_APP_ID);
assert.equal(remoteTeamsApp?.teamsAppId, context.TEAMS_APP_ID);

const appServiceResourceId =
context[EnvConstants.BOT_AZURE_APP_SERVICE_RESOURCE_ID];
assert.exists(
appServiceResourceId,
"Azure App Service resource ID should exist"
);

const tokenProvider = MockAzureAccountProvider;
const tokenCredential = await tokenProvider.getIdentityCredentialAsync();
const token = (await tokenCredential?.getToken(AzureScopes))?.token;
assert.exists(token);

const response = await getWebappSettings(
subscription,
getResourceGroupNameFromResourceId(appServiceResourceId),
getSiteNameFromResourceId(appServiceResourceId),
token as string
);
assert.exists(response, "Web app settings should exist");
assert.equal(
response["WEBSITE_RUN_FROM_PACKAGE"],
"1",
"Run from package should be 1"
);
assert.equal(
response["RUNNING_ON_AZURE"],
"1",
"Running on azure should be 1"
);

// Remote Deploy
await CliHelper.deployAll(projectPath);

// Validate Deploy
context = await readContextMultiEnvV3(projectPath, envName);
assert.exists(context, "env file should exist");
}
);
});
30 changes: 30 additions & 0 deletions templates/vs/csharp/teams-collaborator-agent/.gitignore.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# TeamsFx files
build
appPackage/build
env/.env.*.user
env/.env.local
appsettings.Development.json
appsettings.Playground.json
.deployment

# User-specific files
*.user

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Notification local store
.notification.localstore.json
.notification.playgroundstore.json

# devTools
devTools/
Loading
Loading