diff --git a/templates/vsc/python/default-bot/.gitignore.tpl b/templates/vsc/python/default-bot/.gitignore.tpl new file mode 100644 index 00000000000..9292aa91b5a --- /dev/null +++ b/templates/vsc/python/default-bot/.gitignore.tpl @@ -0,0 +1,125 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules/ +.pnp +.pnp.js + +# testing +/coverage + +# production +/build +/dist + +# misc +.DS_Store +*.log +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.tmp + +# Runtime data +pids +logs + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.local +.env.dev +.env.staging +.env.prod + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Subscription validation event payload files +validationEvents.json + +# Local environment variables +.env.*.user + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.pytest_cache/ +.coverage +htmlcov/ +.tox/ +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.mypy_cache/ +.dmypy.json +dmypy.json + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDE +.idea/ +.vscode/settings.json +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/templates/vsc/python/default-bot/.vscode/extensions.json b/templates/vsc/python/default-bot/.vscode/extensions.json new file mode 100644 index 00000000000..10dc7e806e1 --- /dev/null +++ b/templates/vsc/python/default-bot/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension", + "ms-python.python", + "ms-python.flake8", + "ms-python.black-formatter" + ] +} diff --git a/templates/vsc/python/default-bot/.vscode/launch.json.tpl b/templates/vsc/python/default-bot/.vscode/launch.json.tpl new file mode 100644 index 00000000000..bc08f7e21e5 --- /dev/null +++ b/templates/vsc/python/default-bot/.vscode/launch.json.tpl @@ -0,0 +1,153 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Remote (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 1 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "presentation": { + "group": "3-remote", + "order": 2 + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Edge)", + "type": "msedge", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Python: Remote Attach" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch App (Chrome)", + "type": "chrome", + "request": "launch", + "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}", + "cascadeTerminateToConfigurations": [ + "Python: Remote Attach" + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Python: Remote Attach", + "type": "python", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5678 + }, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}", + "remoteRoot": "." + } + ], + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + }, + { + "name": "Launch Remote (Desktop)", + "type": "python", + "request": "launch", + "preLaunchTask": "Start App in Desktop Client (Remote)", + "presentation": { + "group": "3-remote", + "order": 3 + }, + "internalConsoleOptions": "neverOpen", + } + ], + "compounds": [ + { + "name": "Debug in Teams (Edge)", + "configurations": [ + "Launch App (Edge)", + "Python: Remote Attach" + ], + "preLaunchTask": "Start App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Chrome)", + "configurations": [ + "Launch App (Chrome)", + "Python: Remote Attach" + ], + "preLaunchTask": "Start App Locally", + "presentation": { +{{#enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} + "order": 2 + }, + "stopAll": true + }, + { + "name": "Debug in Teams (Desktop)", + "configurations": [ + "Python: Remote Attach" + ], + "preLaunchTask": "Start App in Desktop Client", + "presentation": { + "group": "2-local", + "order": 3 + }, + "stopAll": true + }, + { + "name": "Debug in Microsoft 365 Agents Playground", + "configurations": [ + "Python: Remote Attach" + ], + "preLaunchTask": "Start App in Microsoft 365 Agents Playground", + "presentation": { +{{#enableTestToolByDefault}} + "group": "1-local", +{{/enableTestToolByDefault}} +{{^enableTestToolByDefault}} + "group": "2-local", +{{/enableTestToolByDefault}} + "order": 1 + }, + "stopAll": true + } + ] +} diff --git a/templates/vsc/python/default-bot/.vscode/settings.json b/templates/vsc/python/default-bot/.vscode/settings.json new file mode 100644 index 00000000000..6a11db769d8 --- /dev/null +++ b/templates/vsc/python/default-bot/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "debug.onTaskErrors": "abort", + "json.schemas": [ + { + "fileMatch": [ + "/aad.*.json" + ], + "schema": {} + } + ], + "python.defaultInterpreterPath": "python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": true, + "python.formatting.provider": "black" +} diff --git a/templates/vsc/python/default-bot/.vscode/tasks.json b/templates/vsc/python/default-bot/.vscode/tasks.json new file mode 100644 index 00000000000..8945abd2ebb --- /dev/null +++ b/templates/vsc/python/default-bot/.vscode/tasks.json @@ -0,0 +1,245 @@ +// This file is automatically generated by Microsoft 365 Agents Toolkit. +// The teamsfx tasks defined in this file require Microsoft 365 Agents Toolkit version >= 5.0.0. +// See https://aka.ms/teamsfx-tasks for details on how to customize each task. +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start App in Microsoft 365 Agents Playground", + "dependsOn": [ + "Validate prerequisites (Microsoft 365 Agents Playground)", + "Deploy (Microsoft 365 Agents Playground)", + "Start application (Microsoft 365 Agents Playground)", + "Start Microsoft 365 Agents Playground", + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites (Microsoft 365 Agents Playground)", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "python", // Validate if Python is installed. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 5678, // Python debugger port + 56150, // Microsoft 365 Agents Playground port + ] + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy (Microsoft 365 Agents Playground)", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "playground", + } + }, + { + "label": "Start application (Microsoft 365 Agents Playground)", + "type": "shell", + "command": "python", + "args": [ + "src/app.py" + ], + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + "env": { + "PYTHONPATH": "${workspaceFolder}/src" + } + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Bot Started|Running on|Application startup complete" + } + } + }, + { + "label": "Start Microsoft 365 Agents Playground", + "type": "shell", + "command": "npm run dev:teamsfx:launch-testtool", + "isBackground": true, + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin:${env:PATH}", + "DEFAULT_CHANNEL_ID": "emulator" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/teamsapptester/node_modules/.bin;${env:PATH}" + } + } + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Listening on" + } + }, + "presentation": { + "panel": "dedicated", + "reveal": "silent" + } + }, + { + "label": "Start App Locally", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start application" + ], + "dependsOrder": "sequence" + }, + { + // Check all required prerequisites. + // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args. + "label": "Validate prerequisites", + "type": "teamsfx", + "command": "debug-check-prerequisites", + "args": { + "prerequisites": [ + "python", // Validate if Python is installed. + "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 5678 // Python debugger port + ] + } + }, + { + // Start the local tunnel service to forward public URL to local port and inspect traffic. + // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions. + "label": "Start local tunnel", + "type": "teamsfx", + "command": "debug-start-local-tunnel", + "args": { + "type": "dev-tunnel", + "ports": [ + { + "portNumber": 3978, + "protocol": "http", + "access": "public", + "writeToEnvironmentFile": { + "endpoint": "BOT_ENDPOINT", // output tunnel endpoint as BOT_ENDPOINT + "domain": "BOT_DOMAIN" // output tunnel domain as BOT_DOMAIN + } + } + ], + "env": "local" + }, + "isBackground": true, + "problemMatcher": "$teamsfx-local-tunnel-watch" + }, + { + // Create the debug resources. + // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args. + "label": "Provision", + "type": "teamsfx", + "command": "provision", + "args": { + "env": "local" + } + }, + { + // Build project. + // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args. + "label": "Deploy", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "local" + } + }, + { + "label": "Start application", + "type": "shell", + "command": "python", + "args": [ + "src/app.py" + ], + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + "env": { + "PYTHONPATH": "${workspaceFolder}/src" + } + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "Bot Started|Running on|Application startup complete" + } + } + }, + { + "label": "Start App in Desktop Client", + "dependsOn": [ + "Validate prerequisites", + "Start local tunnel", + "Provision", + "Deploy", + "Start application", + "Start desktop client" + ], + "dependsOrder": "sequence" + }, + { + "label": "Start desktop client", + "type": "teamsfx", + "command": "launch-desktop-client", + "args": { + "url": "teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true" + } + }, + { + "label": "Start App in Desktop Client (Remote)", + "type": "teamsfx", + "command": "launch-desktop-client", + "args": { + "url": "teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true" + } + } + ] +} diff --git a/templates/vsc/python/default-bot/.webappignore b/templates/vsc/python/default-bot/.webappignore new file mode 100644 index 00000000000..c96dde774e5 --- /dev/null +++ b/templates/vsc/python/default-bot/.webappignore @@ -0,0 +1,23 @@ +__pycache__/ +*.py[cod] +.Python +env/ +pip-log.txt +pip-delete-this-directory.txt +.tox +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.log +.git +.mypy_cache +.pytest_cache +.hypothesis + +.DS_Store +.vscode + +.env diff --git a/templates/vsc/python/default-bot/README.md.tpl b/templates/vsc/python/default-bot/README.md.tpl new file mode 100644 index 00000000000..07bc9b0ba93 --- /dev/null +++ b/templates/vsc/python/default-bot/README.md.tpl @@ -0,0 +1,86 @@ +# Default Bot + +This template contains a simple echo bot for Microsoft Teams using the Microsoft Agents Hosting library for Python. + +## Prerequisites + +- Python 3.8 or higher +- Microsoft Teams +- [Microsoft 365 Agents Toolkit](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension) + +## Getting Started + +1. In the Command Palette, select **Microsoft 365 Agents Toolkit: Create a new App** and choose **Default Bot** template +2. Provide your app name and save to a folder +3. Open the app folder in VS Code +4. Press **F5** to start debugging which launches your app in Microsoft Teams using Teams Toolkit + +## What's Included + +This template includes: + +- Echo bot functionality that repeats user messages with a count +- Welcome message for new conversation members +- Command handlers for bot management: + - `/reset` - Reset conversation state + - `/count` - Show current message count + - `/diag` - Show diagnostic information + - `/state` - Show current conversation state + - `/runtime` - Show runtime information +- Conversation state management +- Microsoft Agents Hosting library integration + +## Project Structure + +``` +├── .vscode/ +│ ├── launch.json # Launch configurations for debugging +│ ├── settings.json # VS Code settings +│ └── tasks.json # VS Code tasks +├── appPackage/ # Teams app manifest +│ ├── color.png # App icon (color) +│ ├── outline.png # App icon (outline) +│ └── manifest.json # Teams app manifest +├── env/ # Environment files +├── infra/ # Infrastructure files for deployment +├── src/ +│ ├── app.py # Main application entry point +│ ├── bot.py # Bot logic and handlers +│ ├── config.py # Configuration settings +│ └── requirements.txt # Python dependencies +├── .gitignore +├── .webappignore +├── README.md +└── m365agents.yml # Project configuration +``` + +## Key Features + +### Echo Functionality +The bot echoes back any message sent to it with a message counter. + +### Command Handling +Special commands are handled with dedicated handlers: +- Type `/reset` to clear conversation state +- Type `/count` to see the current message count +- Type `/diag` to view diagnostic information +- Type `/state` to see current conversation state +- Type `/runtime` to view runtime information + +### State Management +Conversation state is maintained using the Microsoft Agents Hosting library's built-in state management. + +## Customization + +You can extend this bot by: + +1. Adding new command handlers using the `@teams_bot.message()` decorator +2. Implementing custom activity handlers using `@teams_bot.activity()` +3. Adding custom state properties to the `ConversationState` class +4. Integrating with external APIs or services + +## Learn More + +- [Microsoft Agents Documentation](https://github.com/microsoft/Agents) +- [Microsoft Teams Platform Documentation](https://docs.microsoft.com/microsoftteams/platform/) +- [Microsoft 365 Agents Toolkit Documentation](https://docs.microsoft.com/microsoftteams/platform/toolkit/) diff --git a/templates/vsc/python/default-bot/appPackage/color.png b/templates/vsc/python/default-bot/appPackage/color.png new file mode 100644 index 00000000000..01aa37e347d Binary files /dev/null and b/templates/vsc/python/default-bot/appPackage/color.png differ diff --git a/templates/vsc/python/default-bot/appPackage/manifest.json.tpl b/templates/vsc/python/default-bot/appPackage/manifest.json.tpl new file mode 100644 index 00000000000..6d47ada9791 --- /dev/null +++ b/templates/vsc/python/default-bot/appPackage/manifest.json.tpl @@ -0,0 +1,60 @@ +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.22/MicrosoftTeams.schema.json", + "manifestVersion": "1.22", + "version": "1.0.0", + "id": "${{TEAMS_APP_ID}}", + "developer": { + "name": "My App, Inc.", + "websiteUrl": "https://www.example.com", + "privacyUrl": "https://www.example.com/privacy", + "termsOfUseUrl": "https://www.example.com/termofuse" + }, + "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", + "bots": [ + { + "botId": "${{CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID}}", + "scopes": [ + "personal", + "team", + "groupChat" + ], + "supportsFiles": false, + "isNotificationOnly": false, + "commandLists": [ + { + "scopes": ["personal", "team", "groupChat"], + "commands": [ + { + "title": "Hi", + "description": "Say hi to the bot." + }, + { + "title": "/reset", + "description": "Delete conversation state." + } + ] + } + ] + } + ], + "composeExtensions": [], + "configurableTabs": [], + "staticTabs": [], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} \ No newline at end of file diff --git a/templates/vsc/python/default-bot/appPackage/outline.png b/templates/vsc/python/default-bot/appPackage/outline.png new file mode 100644 index 00000000000..f7a4c864475 Binary files /dev/null and b/templates/vsc/python/default-bot/appPackage/outline.png differ diff --git a/templates/vsc/python/default-bot/env/.env.dev b/templates/vsc/python/default-bot/env/.env.dev new file mode 100644 index 00000000000..d3d50491bbe --- /dev/null +++ b/templates/vsc/python/default-bot/env/.env.dev @@ -0,0 +1,19 @@ +# This file includes environment variables that will be committed to git by default. + +# Built-in environment variables +TEAMSFX_ENV=dev +APP_NAME_SUFFIX=dev + +# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups. +AZURE_SUBSCRIPTION_ID= +AZURE_RESOURCE_GROUP_NAME= +RESOURCE_SUFFIX= + +# Generated during provision, you can also add your own variables. +TEAMS_APP_ID= +BOT_AZURE_APP_SERVICE_RESOURCE_ID= +BOT_DOMAIN= + +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID= +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET= +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID= \ No newline at end of file diff --git a/templates/vsc/python/default-bot/env/.env.local b/templates/vsc/python/default-bot/env/.env.local new file mode 100644 index 00000000000..c626139e866 --- /dev/null +++ b/templates/vsc/python/default-bot/env/.env.local @@ -0,0 +1,11 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=local +APP_NAME_SUFFIX=local + +# Generated during provision, you can also add your own variables. +CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID= +TEAMS_APP_ID= +BOT_DOMAIN= +BOT_ENDPOINT= diff --git a/templates/vsc/python/default-bot/env/.env.playground b/templates/vsc/python/default-bot/env/.env.playground new file mode 100644 index 00000000000..46329a41dff --- /dev/null +++ b/templates/vsc/python/default-bot/env/.env.playground @@ -0,0 +1,8 @@ +# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment. + +# Built-in environment variables +TEAMSFX_ENV=testtool + +# Environment variables used by Microsoft 365 Agents Playground +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.playgroundstore.json diff --git a/templates/vsc/python/default-bot/img/echo-bot.png b/templates/vsc/python/default-bot/img/echo-bot.png new file mode 100644 index 00000000000..25e2984e50f Binary files /dev/null and b/templates/vsc/python/default-bot/img/echo-bot.png differ diff --git a/templates/vsc/python/default-bot/infra/azure.bicep b/templates/vsc/python/default-bot/infra/azure.bicep new file mode 100644 index 00000000000..3173e3c9a8c --- /dev/null +++ b/templates/vsc/python/default-bot/infra/azure.bicep @@ -0,0 +1,91 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +param webAppSKU string + +@maxLength(42) +param botDisplayName string + +param serverfarmsName string = resourceBaseName +param webAppName string = resourceBaseName +param identityName string = resourceBaseName +param location string = resourceGroup().location + +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + location: location + name: identityName +} + +// Compute resources for your Web App +resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = { + kind: 'app' + location: location + name: serverfarmsName + sku: { + name: webAppSKU + } +} + +// Web App that hosts your bot +resource webApp 'Microsoft.Web/sites@2021-02-01' = { + kind: 'app' + location: location + name: webAppName + properties: { + serverFarmId: serverfarm.id + httpsOnly: true + siteConfig: { + alwaysOn: true + appSettings: [ + { + name: 'WEBSITE_RUN_FROM_PACKAGE' + value: '1' // Run Azure App Service from a package file + } + { + name: 'WEBSITE_NODE_DEFAULT_VERSION' + value: '~18' // Set NodeJS version to 18.x for your site + } + { + name: 'RUNNING_ON_AZURE' + value: '1' + } + { + name: 'clientId' + value: identity.properties.clientId + } + { + name: 'tenantId' + value: identity.properties.tenantId + } + ] + ftpsState: 'FtpsOnly' + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${identity.id}': {} + } + } +} + +// Register your web service as a bot with the Bot Framework +module azureBotRegistration './botRegistration/azurebot.bicep' = { + name: 'Azure-Bot-registration' + params: { + resourceBaseName: resourceBaseName + identityClientId: identity.properties.clientId + identityResourceId: identity.id + identityTenantId: identity.properties.tenantId + botAppDomain: webApp.properties.defaultHostName + botDisplayName: botDisplayName + } +} + +// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details. +output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id +output BOT_DOMAIN string = webApp.properties.defaultHostName +output CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID string = identity.properties.clientId +output BOT_TENANT_ID string = identity.properties.tenantId diff --git a/templates/vsc/python/default-bot/infra/azure.parameters.json.tpl b/templates/vsc/python/default-bot/infra/azure.parameters.json.tpl new file mode 100644 index 00000000000..62a64c38ff4 --- /dev/null +++ b/templates/vsc/python/default-bot/infra/azure.parameters.json.tpl @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceBaseName": { + "value": "bot${{RESOURCE_SUFFIX}}" + }, + "webAppSKU": { + "value": "B1" + }, + "botDisplayName": { + "value": "{{appName}}" + } + } +} \ No newline at end of file diff --git a/templates/vsc/python/default-bot/infra/botRegistration/azurebot.bicep b/templates/vsc/python/default-bot/infra/botRegistration/azurebot.bicep new file mode 100644 index 00000000000..a5a27b8fe43 --- /dev/null +++ b/templates/vsc/python/default-bot/infra/botRegistration/azurebot.bicep @@ -0,0 +1,42 @@ +@maxLength(20) +@minLength(4) +@description('Used to generate names for all resources in this file') +param resourceBaseName string + +@maxLength(42) +param botDisplayName string + +param botServiceName string = resourceBaseName +param botServiceSku string = 'F0' +param identityResourceId string +param identityClientId string +param identityTenantId string +param botAppDomain string + +// Register your web service as a bot with the Bot Framework +resource botService 'Microsoft.BotService/botServices@2021-03-01' = { + kind: 'azurebot' + location: 'global' + name: botServiceName + properties: { + displayName: botDisplayName + endpoint: 'https://${botAppDomain}/api/messages' + msaAppId: identityClientId + msaAppMSIResourceId: identityResourceId + msaAppTenantId:identityTenantId + msaAppType:'UserAssignedMSI' + } + sku: { + name: botServiceSku + } +} + +// Connect the bot service to Microsoft Teams +resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = { + parent: botService + location: 'global' + name: 'MsTeamsChannel' + properties: { + channelName: 'MsTeamsChannel' + } +} diff --git a/templates/vsc/python/default-bot/infra/botRegistration/readme.md b/templates/vsc/python/default-bot/infra/botRegistration/readme.md new file mode 100644 index 00000000000..d5416243cd3 --- /dev/null +++ b/templates/vsc/python/default-bot/infra/botRegistration/readme.md @@ -0,0 +1 @@ +The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again. \ No newline at end of file diff --git a/templates/vsc/python/default-bot/m365agents.local.yml.tpl b/templates/vsc/python/default-bot/m365agents.local.yml.tpl new file mode 100644 index 00000000000..7ba983ceffc --- /dev/null +++ b/templates/vsc/python/default-bot/m365agents.local.yml.tpl @@ -0,0 +1,82 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/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: v1.9 + +provision: + # Creates an app + - uses: teamsApp/create + with: + # 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 + + # Create or reuse an existing Microsoft Entra application for bot. + - uses: aadApp/create + with: + # The Microsoft Entra application's display name + name: {{appName}}${{APP_NAME_SUFFIX}} + generateClientSecret: true + signInAudience: AzureADMultipleOrgs + writeToEnvironmentFile: + # The Microsoft Entra application's client id created for bot. + clientId: CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID + # The Microsoft Entra application's client secret created for bot. + clientSecret: CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET + # The Microsoft Entra application's object id created for bot. + objectId: BOT_OBJECT_ID + + # Create or update the bot registration on dev.botframework.com + - uses: botFramework/create + with: + botId: ${{CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID}} + name: {{appName}} + messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages + description: "" + channels: + - name: msteams + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + + # Build 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 + outputFolder: ./appPackage/build + # 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 app manifest to an existing app in + # Developer Portal. + # Will use the app id in manifest file to determine which 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 + +deploy: + # Install Python dependencies + - uses: cli/runCommand + name: install dependencies + with: + command: python -m pip install -r src/requirements.txt + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs + envs: + clientId: ${{CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID}} + clientSecret: ${{CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET}} diff --git a/templates/vsc/python/default-bot/m365agents.playground.yml b/templates/vsc/python/default-bot/m365agents.playground.yml new file mode 100644 index 00000000000..071966aae6f --- /dev/null +++ b/templates/vsc/python/default-bot/m365agents.playground.yml @@ -0,0 +1,25 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/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: v1.9 + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.7 + symlinkDir: ./devTools/teamsapptester + + # Install Python dependencies + - uses: cli/runCommand + name: install dependencies + with: + command: python -m pip install -r src/requirements.txt + + # Generate runtime environment variables + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs.playground + envs: + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} diff --git a/templates/vsc/python/default-bot/m365agents.yml.tpl b/templates/vsc/python/default-bot/m365agents.yml.tpl new file mode 100644 index 00000000000..f275b610535 --- /dev/null +++ b/templates/vsc/python/default-bot/m365agents.yml.tpl @@ -0,0 +1,126 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/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: v1.9 + +environmentFolderPath: ./env + +# Triggered when 'teamsapp provision' is executed +provision: + # Creates an app + - uses: teamsApp/create + with: + # 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 + + - uses: arm/deploy # Deploy given ARM templates parallelly. + with: + # AZURE_SUBSCRIPTION_ID is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select a subscription. + # Referencing other environment variables with empty values + # will skip the subscription selection prompt. + subscriptionId: ${{AZURE_SUBSCRIPTION_ID}} + # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable, + # if its value is empty, TeamsFx will prompt you to select or create one + # resource group. + # Referencing other environment variables with empty values + # will skip the resource group selection prompt. + resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}} + templates: + - path: ./infra/azure.bicep # Relative path to this file + # Relative path to this yaml file. + # Placeholders will be replaced with corresponding environment + # variable before ARM deployment. + parameters: ./infra/azure.parameters.json + # Required when deploying ARM template + deploymentName: Create-resources-for-bot + # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you, + # will use bicep CLI in PATH if you remove this config. + bicepCliVersion: v0.9.1 + + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build 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 + outputFolder: ./appPackage/build + # 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 app manifest to an existing app in + # Developer Portal. + # Will use the app id in manifest file to determine which 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 + +# Triggered when 'teamsapp deploy' is executed +deploy: + # Install Python dependencies + - uses: cli/runCommand + name: install dependencies + with: + command: python -m pip install -r src/requirements.txt + # Deploy your application to Azure App Service using the zip deploy feature. + # For additional details, refer to https://aka.ms/zip-deploy-to-app-services. + - uses: azureAppService/zipDeploy + with: + # Deploy base folder + artifactFolder: . + # Ignore file location, leave blank will ignore nothing + ignoreFile: .webappignore + # The resource id of the cloud resource to be deployed to. + # This key will be generated by arm/deploy action automatically. + # You can replace it with your existing Azure Resource id + # or add it to your environment variable file. + resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} + +# Triggered when 'teamsapp publish' is executed +publish: + # Validate using manifest schema + - uses: teamsApp/validateManifest + with: + # Path to manifest template + manifestPath: ./appPackage/manifest.json + # Build 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 + outputFolder: ./appPackage/build + # 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 app manifest to an existing app in + # Developer Portal. + # Will use the app id in manifest file to determine which 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 diff --git a/templates/vsc/python/default-bot/package.json.tpl b/templates/vsc/python/default-bot/package.json.tpl new file mode 100644 index 00000000000..c4a791ecb20 --- /dev/null +++ b/templates/vsc/python/default-bot/package.json.tpl @@ -0,0 +1,18 @@ +{ + "name": "{{SafeProjectNameLowerCase}}", + "version": "1.0.0", + "description": "Microsoft 365 Agents Toolkit echo bot sample (Python)", + "author": "Microsoft", + "license": "MIT", + "scripts": { + "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.playground python src/app.py", + "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.playground teamsapptester start" + }, + "repository": { + "type": "git", + "url": "https://github.com" + }, + "devDependencies": { + "env-cmd": "^10.1.0" + } +} diff --git a/templates/vsc/python/default-bot/pyproject.toml b/templates/vsc/python/default-bot/pyproject.toml new file mode 100644 index 00000000000..9337634ca27 --- /dev/null +++ b/templates/vsc/python/default-bot/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "default-bot" +version = "1.0.0" +description = "Microsoft 365 Agents Toolkit echo bot sample (Python)" +authors = [{name = "Microsoft"}] +license = {text = "MIT"} +requires-python = ">=3.8" +dependencies = [ + "python-dotenv", + "aiohttp", + "microsoft-agents-hosting" +] + +[tool.black] +line-length = 100 +target-version = ['py38'] + +[tool.flake8] +max-line-length = 100 +exclude = [".git", "__pycache__", "venv", ".venv"] + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true diff --git a/templates/vsc/python/default-bot/src/app.py b/templates/vsc/python/default-bot/src/app.py new file mode 100644 index 00000000000..202df8dc5be --- /dev/null +++ b/templates/vsc/python/default-bot/src/app.py @@ -0,0 +1,52 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import logging +from os import environ +from aiohttp import web + +from microsoft_agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware, start_agent_process +from microsoft_agents.authentication.msal import MsalConnectionManager +from microsoft_agents.activity import load_configuration_from_env +from microsoft_agents.hosting.core import MemoryStorage, AgentApplication + +from bot import teams_bot, connection_manager, agents_sdk_config, adapter + +logger = logging.getLogger(__name__) + +# Create routes +routes = web.RouteTableDef() + +@routes.post("/api/messages") +async def on_messages(req: web.Request) -> web.Response: + """Handle incoming messages.""" + try: + agent: AgentApplication = req.app["agent_app"] + adapter: CloudAdapter = req.app["adapter"] + return await start_agent_process( + req, + agent, + adapter, + ) + except Exception as e: + logger.error(f"Error processing message: {e}") + return web.Response(status=500, text=str(e)) + +# Create the web application +aiohttp_web_app = web.Application(middlewares=[jwt_authorization_middleware]) +aiohttp_web_app.add_routes(routes) + +aiohttp_web_app["agent_configuration"] = connection_manager.get_default_connection_configuration() +aiohttp_web_app["agent_app"] = teams_bot +aiohttp_web_app["adapter"] = adapter + +if __name__ == "__main__": + port = int(environ.get("PORT", 3978)) + client_id = agents_sdk_config.get('client_id', 'Unknown') + debug = environ.get("DEBUG", "false").lower() == "true" + + logger.info(f"Bot Started, listening to port {port} for appId {client_id} debug {debug}") + + web.run_app(aiohttp_web_app, host="localhost", port=port) diff --git a/templates/vsc/python/default-bot/src/bot.py b/templates/vsc/python/default-bot/src/bot.py new file mode 100644 index 00000000000..d7ce2d94be4 --- /dev/null +++ b/templates/vsc/python/default-bot/src/bot.py @@ -0,0 +1,119 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import logging +import json + +from importlib.metadata import version +from os import environ, path +from dotenv import load_dotenv + +from microsoft_agents.authentication.msal import MsalConnectionManager +from microsoft_agents.hosting.core import ( + AgentApplication, + TurnState, + TurnContext, + MemoryStorage +) +from microsoft_agents.activity import ActivityTypes, load_configuration_from_env +from microsoft_agents.hosting.aiohttp import CloudAdapter + +# Load environment variables +load_dotenv(path.join(path.dirname(__file__), "../env/.env.dev")) + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Load configuration from environment +agents_sdk_config = load_configuration_from_env(environ) + +# Create storage and connection manager +storage = MemoryStorage() +connection_manager = MsalConnectionManager(**agents_sdk_config) + +# Create adapter +adapter = CloudAdapter(connection_manager=connection_manager) + +# Create the agent application +teams_bot = AgentApplication[TurnState]( + storage=storage +) + +@teams_bot.message("/reset") +async def on_reset_message(context: TurnContext, state: TurnState): + """Reset conversation state when user types /reset.""" + state.conversation.clear(context) + await context.send_activity("Ok I've deleted the current conversation state.") + +@teams_bot.message("/count") +async def on_count_message(context: TurnContext, state: TurnState): + """Show current message count when user types /count.""" + count = state.conversation.get_value("count", lambda: 0, target_cls=int) + await context.send_activity(f"The count is {count}") + +@teams_bot.message("/diag") +async def on_diag_message(context: TurnContext, state: TurnState): + """Show diagnostic information when user types /diag.""" + activity_json = json.dumps(context.activity.model_dump(by_alias=True, exclude_unset=True), indent=2, default=str) + await context.send_activity(f"```json\n{activity_json}\n```") + +@teams_bot.message("/state") +async def on_state_message(context: TurnContext, state: TurnState): + """Show current state when user types /state.""" + state_dict = { + "conversation": state.conversation.__dict__ if state.conversation else None, + "user": state.user.__dict__ if state.user else None, + "temp": state.temp.__dict__ if state.temp else None + } + state_json = json.dumps(state_dict, indent=2, default=str) + await context.send_activity(f"```json\n{state_json}\n```") + +@teams_bot.message("/runtime") +async def on_runtime_message(context: TurnContext, state: TurnState): + """Show runtime information when user types /runtime.""" + import sys + agents_version = version("microsoft-agents-hosting-core") + + runtime_info = { + "python_version": sys.version, + "agents_sdk_version": agents_version + } + runtime_json = json.dumps(runtime_info, indent=2) + await context.send_activity(f"```json\n{runtime_json}\n```") + +@teams_bot.conversation_update("membersAdded") +async def on_members_added(context: TurnContext, state: TurnState): + """Welcome new members when they're added to the conversation.""" + try: + from microsoft_agents.hosting.core import __version__ as agents_version + except ImportError: + agents_version = "unknown" + + await context.send_activity( + f"Hi there! I'm an echo bot running on Agents SDK version {agents_version} that will echo what you said to me." + ) + +@teams_bot.activity(ActivityTypes.message) +async def on_message_activity(context: TurnContext, state: TurnState): + """Handle all message activities - echo back what the user said with a count.""" + + # Increment count + count = state.conversation.get_value("count", lambda: 0, target_cls=int) + state.conversation.set_value("count", count + 1) + + # Echo back user's message with count + await context.send_activity(f"[{state.conversation.get_value('count', target_cls=int)}] you said: {context.activity.text}") + +# Additional pattern matching examples (similar to TypeScript version) +# Note: These may need to be adjusted based on the actual Python API +async def custom_message_filter(context: TurnContext) -> bool: + """Custom filter function to match message activities.""" + return context.activity.type == "message" + +@teams_bot.activity(custom_message_filter) +async def on_custom_filter_message(context: TurnContext, state: TurnState): + """Handle activities that match a custom filter function.""" + await context.send_activity(f"Matched function: {context.activity.type}") diff --git a/templates/vsc/python/default-bot/src/config.py b/templates/vsc/python/default-bot/src/config.py new file mode 100644 index 00000000000..2be9a146092 --- /dev/null +++ b/templates/vsc/python/default-bot/src/config.py @@ -0,0 +1,21 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +import os +from dotenv import load_dotenv + +load_dotenv(os.path.join(os.path.dirname(__file__), "./env/.env.dev")) + +class Config: + """Bot Configuration""" + + PORT = int(os.environ.get("PORT", 3978)) + CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID = os.environ.get("CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID", "") + BOT_PASSWORD = os.environ.get("BOT_PASSWORD", "") + BOT_TYPE = os.environ.get("BOT_TYPE", "") + BOT_TENANT_ID = os.environ.get("BOT_TENANT_ID", "") + + # Debug mode + DEBUG = os.environ.get("DEBUG", "false").lower() == "true" diff --git a/templates/vsc/python/default-bot/src/requirements.txt b/templates/vsc/python/default-bot/src/requirements.txt new file mode 100644 index 00000000000..e8f95d72f07 --- /dev/null +++ b/templates/vsc/python/default-bot/src/requirements.txt @@ -0,0 +1,4 @@ +python-dotenv +aiohttp +microsoft-agents-hosting-aiohttp +microsoft-agents-authentication-msal diff --git a/templates/vsc/python/default-bot/web.config b/templates/vsc/python/default-bot/web.config new file mode 100644 index 00000000000..fe8e093efc2 --- /dev/null +++ b/templates/vsc/python/default-bot/web.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +