diff --git a/nodejs/claude/sample-agent/.gitignore b/nodejs/claude/sample-agent/.gitignore new file mode 100644 index 00000000..8aca3335 --- /dev/null +++ b/nodejs/claude/sample-agent/.gitignore @@ -0,0 +1,24 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +env/.env.sandbox +.localConfigs +.localConfigs.playground +.localConfigs +.notification.localstore.json +.notification.playgroundstore.json +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store + +# build +dist/ + +# Dev tool directories +/devTools/ diff --git a/nodejs/claude/sample-agent/.vscode/extensions.json b/nodejs/claude/sample-agent/.vscode/extensions.json new file mode 100644 index 00000000..aac0a6e3 --- /dev/null +++ b/nodejs/claude/sample-agent/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} diff --git a/nodejs/claude/sample-agent/.vscode/launch.json b/nodejs/claude/sample-agent/.vscode/launch.json new file mode 100644 index 00000000..20f088e2 --- /dev/null +++ b/nodejs/claude/sample-agent/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Local Service", + "type": "node", + "request": "attach", + "port": 9239, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Microsoft 365 Agents Playground", + "configurations": [ + "Attach to Local Service" + ], + "preLaunchTask": "Start App in Microsoft 365 Agents Playground", + "presentation": { + "group": "1-playground", + "order": 1 + }, + "stopAll": true + } + ] +} diff --git a/nodejs/claude/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 b/nodejs/claude/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 new file mode 100644 index 00000000..2fee52fa --- /dev/null +++ b/nodejs/claude/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 @@ -0,0 +1,77 @@ +$ErrorActionPreference = 'Stop' + +$workspace = Get-Location +$playgroundEnvPath = Join-Path $workspace 'env/.env.playground' +$playgroundUserEnvPath = Join-Path $workspace 'env/.env.playground.user' + +$a365Command = Get-Command a365 -ErrorAction SilentlyContinue +if (-not $a365Command) { + throw "a365 CLI is not installed or not on PATH. Install with: dotnet tool install --global Microsoft.Agents.A365.DevTools.Cli" +} + +if (-not (Test-Path $playgroundEnvPath)) { + throw "Missing env file: $playgroundEnvPath" +} + +$appIdLine = Get-Content $playgroundEnvPath | Where-Object { $_ -match '^\s*CLIENT_APP_ID\s*=\s*.+$' } | Select-Object -First 1 +if (-not $appIdLine) { + throw "CLIENT_APP_ID is required in env/.env.playground" +} + +$appId = ($appIdLine -split '=', 2)[1].Trim() +if ([string]::IsNullOrWhiteSpace($appId)) { + throw "CLIENT_APP_ID in env/.env.playground is empty" +} + +Write-Host "Running a365 develop add-permissions for app id $appId" +& a365 develop add-permissions --app-id $appId +if ($LASTEXITCODE -ne 0) { + throw "a365 develop add-permissions failed" +} + +Write-Host "Getting bearer token via a365..." +Write-Host "This may complete silently using cached credentials, or it may require interactive Windows sign-in (WAM)." +Write-Host "If interactive sign-in is required and no prompt appears, check the taskbar for a hidden sign-in window and bring it to front." +Write-Host "Running a365 develop get-token for app id $appId" +$tokenOutput = & a365 develop get-token --app-id $appId --output raw +if ($LASTEXITCODE -ne 0) { + throw "a365 develop get-token failed" +} + +$rawOutput = [string]::Join("`n", $tokenOutput) +$rawOutput = $rawOutput -replace "`r", '' + +$bearerToken = $null +$jwtRegex = '(?[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)' +$jwtMatches = [regex]::Matches($rawOutput, $jwtRegex) +if ($jwtMatches.Count -gt 0) { + $bearerToken = ($jwtMatches | ForEach-Object { $_.Groups['token'].Value } | Sort-Object Length -Descending | Select-Object -First 1).Trim() +} + +if ([string]::IsNullOrWhiteSpace($bearerToken)) { + throw "Unable to extract a bearer token from a365 develop get-token output" +} + +$userEnvLines = @() +if (Test-Path $playgroundUserEnvPath) { + $userEnvLines = Get-Content $playgroundUserEnvPath +} + +$updated = $false +for ($i = 0; $i -lt $userEnvLines.Count; $i++) { + if ($userEnvLines[$i] -match '^\s*SECRET_BEARER_TOKEN\s*=') { + $userEnvLines[$i] = "SECRET_BEARER_TOKEN=$bearerToken" + $updated = $true + break + } +} + +if (-not $updated) { + if ($userEnvLines.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($userEnvLines[$userEnvLines.Count - 1])) { + $userEnvLines += '' + } + $userEnvLines += "SECRET_BEARER_TOKEN=$bearerToken" +} + +Set-Content -Path $playgroundUserEnvPath -Value $userEnvLines -Encoding UTF8 +Write-Host 'SECRET_BEARER_TOKEN has been updated in env/.env.playground.user' diff --git a/nodejs/claude/sample-agent/.vscode/tasks.json b/nodejs/claude/sample-agent/.vscode/tasks.json new file mode 100644 index 00000000..f40092c3 --- /dev/null +++ b/nodejs/claude/sample-agent/.vscode/tasks.json @@ -0,0 +1,121 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start App in Microsoft 365 Agents Playground", + "dependsOn": [ + "Validate prerequisites (Microsoft 365 Agents Playground)", + "Refresh bearer token (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": [ + "nodejs", // Validate if Node.js is installed. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239, // app inspector port for Node.js debugger + 56150 // Microsoft 365 Agents Playground port + ] + } + }, + { + "label": "Refresh bearer token (Microsoft 365 Agents Playground)", + "type": "shell", + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "${workspaceFolder}/.vscode/scripts/refresh-bearer-token.ps1" + ], + "options": { + "cwd": "${workspaceFolder}" + } + }, + { + // 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)", + "dependsOrder": "sequence", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "playground" + } + }, + { + "label": "Start application (Microsoft 365 Agents Playground)", + "type": "shell", + "command": "npm run dev:teamsfx:playground", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "Server listening on|Bot/ME service listening at|[nodemon] app crashed" + } + } + }, + { + "label": "Start Microsoft 365 Agents Playground", + "type": "shell", + "command": "npm run dev:teamsfx:launch-playground", + "isBackground": true, + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/playground/node_modules/.bin;${env:PATH}" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/playground/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" + } + } + ] +} diff --git a/nodejs/claude/sample-agent/README.md b/nodejs/claude/sample-agent/README.md index 120a684c..e9b643fd 100644 --- a/nodejs/claude/sample-agent/README.md +++ b/nodejs/claude/sample-agent/README.md @@ -12,11 +12,17 @@ This sample uses the [Microsoft Agent 365 SDK for Node.js](https://github.com/mi For comprehensive documentation and guidance on building agents with the Microsoft Agent 365 SDK, including how to add tooling, observability, and notifications, visit the [Microsoft Agent 365 Developer Documentation](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/). ## Prerequisites - -- Node.js 18.x or higher -- Microsoft Agent 365 SDK -- Claude Agent SDK 0.1.1 or higher -- Claude API credentials +> +> To run the template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 18.x or higher +> - [Microsoft 365 Agents Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) latest version +> - Prepare your own Anthropic API credentials +> - Azure CLI signed in with `az login` + +> - Microsoft Agent 365 SDK +> - Claude Agent SDK 0.1.1 or higher +> - A365 CLI: Required for agent deployment and management. ## Working with User Identity @@ -32,6 +38,16 @@ information — always available with no API calls or token acquisition: The sample logs these fields at the start of every message turn and injects the display name into the LLM system instructions for personalized responses. +## Running the Agent in Microsoft 365 Agents Playground + +1. First, select the Microsoft 365 Agents Toolkit icon on the left in the VS Code toolbar. +1. In file *env/.env.playground.user*, fill in your Anthropic API key `SECRET_ANTHROPIC_API_KEY=`. +1. In file *env/.env.playground*, fill in your custom app registration client id `CLIENT_APP_ID`. +1. Press F5 to start debugging which launches your agent in Microsoft 365 Agents Playground using a web browser. Select `Debug in Microsoft 365 Agents Playground`. +1. You can send any message to get a response from the agent. + +**Congratulations**! You are running an agent that can now interact with users in Microsoft 365 Agents Playground. + ## Handling Agent Install and Uninstall When a user installs (hires) or uninstalls (removes) the agent, the A365 platform sends an `InstallationUpdate` activity — also referred to as the `agentInstanceCreated` event. The sample handles this in `handleInstallationUpdateActivity` ([agent.ts](src/agent.ts)): diff --git a/nodejs/claude/sample-agent/env/.env.playground b/nodejs/claude/sample-agent/env/.env.playground new file mode 100644 index 00000000..c1d20191 --- /dev/null +++ b/nodejs/claude/sample-agent/env/.env.playground @@ -0,0 +1,23 @@ +# 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=playground + +# Environment variables used by Microsoft 365 Agents Playground +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json + +# Custom app registration needed for bearer token +CLIENT_APP_ID= + +# Use Agentic Authentication rather than OBO +USE_AGENTIC_AUTH=false + +# Set service connection as default +connectionsMap__0__serviceUrl=* +connectionsMap__0__connection=service_connection + +# AgenticAuthentication Options +agentic_type=agentic +agentic_altBlueprintConnectionName=service_connection +agentic_scopes=ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default # Prod Agentic scope diff --git a/nodejs/claude/sample-agent/env/.env.playground.user b/nodejs/claude/sample-agent/env/.env.playground.user new file mode 100644 index 00000000..ac051992 --- /dev/null +++ b/nodejs/claude/sample-agent/env/.env.playground.user @@ -0,0 +1,4 @@ +# Anthropic Configuration +SECRET_ANTHROPIC_API_KEY= + +SECRET_BEARER_TOKEN= diff --git a/nodejs/claude/sample-agent/m365agents.playground.yml b/nodejs/claude/sample-agent/m365agents.playground.yml new file mode 100644 index 00000000..b9dabdef --- /dev/null +++ b/nodejs/claude/sample-agent/m365agents.playground.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/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.11 + +environmentFolderPath: ./env + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.7 + symlinkDir: ./devTools/playground + + # Run npm command + - uses: cli/runNpmCommand + with: + args: install + + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs.playground + envs: + NODE_ENV: local + PORT: 3978 + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} + ANTHROPIC_API_KEY: ${{SECRET_ANTHROPIC_API_KEY}} + BEARER_TOKEN: ${{SECRET_BEARER_TOKEN}} + USE_AGENTIC_AUTH: ${{USE_AGENTIC_AUTH}} + connectionsMap__0__serviceUrl: ${{connectionsMap__0__serviceUrl}} + connectionsMap__0__connection: ${{connectionsMap__0__connection}} + agentic_type: ${{agentic_type}} + agentic_altBlueprintConnectionName: ${{agentic_altBlueprintConnectionName}} + agentic_scopes: ${{agentic_scopes}} + connections__service_connection__settings__clientId: "" + connections__service_connection__settings__clientSecret: "" + connections__service_connection__settings__tenantId: "" diff --git a/nodejs/claude/sample-agent/m365agents.yml b/nodejs/claude/sample-agent/m365agents.yml new file mode 100644 index 00000000..50d15fc3 --- /dev/null +++ b/nodejs/claude/sample-agent/m365agents.yml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/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.11 diff --git a/nodejs/claude/sample-agent/package.json b/nodejs/claude/sample-agent/package.json index aa44ad32..c6a445c2 100644 --- a/nodejs/claude/sample-agent/package.json +++ b/nodejs/claude/sample-agent/package.json @@ -5,11 +5,13 @@ "type": "commonjs", "scripts": { "start": "node dist/index.js", - "dev": "nodemon --watch src --exec ts-node src/index.ts", + "dev": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register src/index.ts", "test-tool": "agentsplayground", "install:clean": "npm run clean && npm install", "clean": "rimraf dist node_modules package-lock.json", - "build": "tsc" + "build": "tsc", + "dev:teamsfx:playground": "env-cmd --silent -f .localConfigs.playground npm run dev", + "dev:teamsfx:launch-playground": "env-cmd --silent -f env/.env.playground agentsplayground start" }, "keywords": [], "author": "Microsoft", @@ -32,6 +34,7 @@ "@microsoft/m365agentsplayground": "^0.2.18", "@types/express": "^4.17.21", "@types/node": "^20.14.9", + "env-cmd": "^11.0.0", "nodemon": "^3.1.10", "rimraf": "^5.0.0", "ts-node": "^10.9.2", diff --git a/nodejs/langchain/sample-agent/.gitignore b/nodejs/langchain/sample-agent/.gitignore new file mode 100644 index 00000000..75a5c39d --- /dev/null +++ b/nodejs/langchain/sample-agent/.gitignore @@ -0,0 +1,24 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +env/.env.sandbox +.localConfigs +.localConfigs.playground +.localConfigs +.notification.localstore.json +.notification.playgroundstore.json +appPackage/build + +# dependencies +node_modules/ + +# misc +.env +.deployment +.DS_Store + +# build +dist/ + +# Dev tool directories +/devTools/ \ No newline at end of file diff --git a/nodejs/langchain/sample-agent/.vscode/extensions.json b/nodejs/langchain/sample-agent/.vscode/extensions.json new file mode 100644 index 00000000..1b70a393 --- /dev/null +++ b/nodejs/langchain/sample-agent/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension" + ] +} \ No newline at end of file diff --git a/nodejs/langchain/sample-agent/.vscode/launch.json b/nodejs/langchain/sample-agent/.vscode/launch.json new file mode 100644 index 00000000..6bb77973 --- /dev/null +++ b/nodejs/langchain/sample-agent/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Local Service", + "type": "node", + "request": "attach", + "port": 9239, + "restart": true, + "presentation": { + "group": "all", + "hidden": true + }, + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Microsoft 365 Agents Playground", + "configurations": [ + "Attach to Local Service" + ], + "preLaunchTask": "Start App in Microsoft 365 Agents Playground", + "presentation": { + "group": "1-playground", + "order": 1 + }, + "stopAll": true + } + ] +} \ No newline at end of file diff --git a/nodejs/langchain/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 b/nodejs/langchain/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 new file mode 100644 index 00000000..9d9a98dc --- /dev/null +++ b/nodejs/langchain/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 @@ -0,0 +1,77 @@ +$ErrorActionPreference = 'Stop' + +$workspace = Get-Location +$playgroundEnvPath = Join-Path $workspace 'env/.env.playground' +$playgroundUserEnvPath = Join-Path $workspace 'env/.env.playground.user' + +$a365Command = Get-Command a365 -ErrorAction SilentlyContinue +if (-not $a365Command) { + throw "a365 CLI is not installed or not on PATH. Install with: dotnet tool install --global Microsoft.Agents.A365.DevTools.Cli" +} + +if (-not (Test-Path $playgroundEnvPath)) { + throw "Missing env file: $playgroundEnvPath" +} + +$appIdLine = Get-Content $playgroundEnvPath | Where-Object { $_ -match '^\s*CLIENT_APP_ID\s*=\s*.+$' } | Select-Object -First 1 +if (-not $appIdLine) { + throw "CLIENT_APP_ID is required in env/.env.playground" +} + +$appId = ($appIdLine -split '=', 2)[1].Trim() +if ([string]::IsNullOrWhiteSpace($appId)) { + throw "CLIENT_APP_ID in env/.env.playground is empty" +} + +Write-Host "Running a365 develop add-permissions for app id $appId" +& a365 develop add-permissions --app-id $appId +if ($LASTEXITCODE -ne 0) { + throw "a365 develop add-permissions failed" +} + +Write-Host "Getting bearer token via a365..." +Write-Host "This may complete silently using cached credentials, or it may require interactive Windows sign-in (WAM)." +Write-Host "If interactive sign-in is required and no prompt appears, check the taskbar for a hidden sign-in window and bring it to front." +Write-Host "Running a365 develop get-token for app id $appId" +$tokenOutput = & a365 develop get-token --app-id $appId --output raw +if ($LASTEXITCODE -ne 0) { + throw "a365 develop get-token failed" +} + +$rawOutput = [string]::Join("`n", $tokenOutput) +$rawOutput = $rawOutput -replace "`r", '' + +$bearerToken = $null +$jwtRegex = '(?[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)' +$jwtMatches = [regex]::Matches($rawOutput, $jwtRegex) +if ($jwtMatches.Count -gt 0) { + $bearerToken = ($jwtMatches | ForEach-Object { $_.Groups['token'].Value } | Sort-Object Length -Descending | Select-Object -First 1).Trim() +} + +if ([string]::IsNullOrWhiteSpace($bearerToken)) { + throw "Unable to extract a bearer token from a365 develop get-token output" +} + +$userEnvLines = @() +if (Test-Path $playgroundUserEnvPath) { + $userEnvLines = Get-Content $playgroundUserEnvPath +} + +$updated = $false +for ($i = 0; $i -lt $userEnvLines.Count; $i++) { + if ($userEnvLines[$i] -match '^\s*SECRET_BEARER_TOKEN\s*=') { + $userEnvLines[$i] = "SECRET_BEARER_TOKEN=$bearerToken" + $updated = $true + break + } +} + +if (-not $updated) { + if ($userEnvLines.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($userEnvLines[$userEnvLines.Count - 1])) { + $userEnvLines += '' + } + $userEnvLines += "SECRET_BEARER_TOKEN=$bearerToken" +} + +Set-Content -Path $playgroundUserEnvPath -Value $userEnvLines -Encoding UTF8 +Write-Host 'SECRET_BEARER_TOKEN has been updated in env/.env.playground.user' \ No newline at end of file diff --git a/nodejs/langchain/sample-agent/.vscode/tasks.json b/nodejs/langchain/sample-agent/.vscode/tasks.json new file mode 100644 index 00000000..ca71683f --- /dev/null +++ b/nodejs/langchain/sample-agent/.vscode/tasks.json @@ -0,0 +1,121 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start App in Microsoft 365 Agents Playground", + "dependsOn": [ + "Validate prerequisites (Microsoft 365 Agents Playground)", + "Refresh bearer token (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": [ + "nodejs", // Validate if Node.js is installed. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 9239, // app inspector port for Node.js debugger + 56150 // Microsoft 365 Agents Playground port + ] + } + }, + { + "label": "Refresh bearer token (Microsoft 365 Agents Playground)", + "type": "shell", + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "${workspaceFolder}/.vscode/scripts/refresh-bearer-token.ps1" + ], + "options": { + "cwd": "${workspaceFolder}" + } + }, + { + // 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)", + "dependsOrder": "sequence", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "playground" + } + }, + { + "label": "Start application (Microsoft 365 Agents Playground)", + "type": "shell", + "command": "npm run dev:teamsfx:playground", + "isBackground": true, + "options": { + "cwd": "${workspaceFolder}", + }, + "problemMatcher": { + "pattern": [ + { + "regexp": "^.*$", + "file": 0, + "location": 1, + "message": 2 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "[nodemon] starting", + "endsPattern": "Server listening on|Bot/ME service listening at|[nodemon] app crashed" + } + } + }, + { + "label": "Start Microsoft 365 Agents Playground", + "type": "shell", + "command": "npm run dev:teamsfx:launch-playground", + "isBackground": true, + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/playground/node_modules/.bin;${env:PATH}" + } + }, + "windows": { + "options": { + "env": { + "PATH": "${workspaceFolder}/devTools/playground/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" + } + } + ] +} \ No newline at end of file diff --git a/nodejs/langchain/sample-agent/README.md b/nodejs/langchain/sample-agent/README.md index f1986428..cdceb31c 100644 --- a/nodejs/langchain/sample-agent/README.md +++ b/nodejs/langchain/sample-agent/README.md @@ -12,25 +12,28 @@ This sample uses the [Microsoft Agent 365 SDK for Node.js](https://github.com/mi For comprehensive documentation and guidance on building agents with the Microsoft Agent 365 SDK, including how to add tooling, observability, and notifications, visit the [Microsoft Agent 365 Developer Documentation](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/). ## Prerequisites +> +> To run the template in your local dev machine, you will need: +> +> - [Node.js](https://nodejs.org/), supported versions: 18.x or higher +> - [Microsoft 365 Agents Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) latest version +> - Prepare your own Azure/openAI API credentials +> - Azure CLI signed in with `az login` -- Node.js 18.x or higher -- Microsoft Agent 365 SDK -- LangChain 1.0.1 or higher -- Azure/OpenAI API credentials +> - Microsoft Agent 365 SDK +> - LangChain 1.0.1 or higher +> - A365 CLI: Required for agent deployment and management. -## Working with User Identity +## Running the Agent in Microsoft 365 Agents Playground -On every incoming message, the A365 platform populates `activity.from` with basic user -information — always available with no API calls or token acquisition: +1. First, select the Microsoft 365 Agents Toolkit icon on the left in the VS Code toolbar. +1. In file *env/.env.playground.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=`, and deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=` if you're using Azure OpenAI. +1. In file *env/.env.playground.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=` if you're using OpenAI. +1. In file *env/.env.playground*, fill in your custom app registration client id `CLIENT_APP_ID`. +1. Press F5 to start debugging which launches your agent in Microsoft 365 Agents Playground using a web browser. Select `Debug in Microsoft 365 Agents Playground`. +1. You can send any message to get a response from the agent. -| Field | Description | -|---|---| -| `activity.from.id` | Channel-specific user ID (e.g., `29:1AbcXyz...` in Teams) | -| `activity.from.name` | Display name as known to the channel | -| `activity.from.aadObjectId` | Azure AD Object ID — use this to call Microsoft Graph | - -The sample logs these fields at the start of every message turn and injects the display name -into the LLM system instructions for personalized responses. +**Congratulations**! You are running an agent that can now interact with users in Microsoft 365 Agents Playground. ## Handling Agent Install and Uninstall @@ -54,7 +57,6 @@ To test with Agents Playground, use **Mock an Activity → Install application** ## Running the Agent To set up and test this agent, refer to the [Configure Agent Testing](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/testing?tabs=nodejs) guide for complete instructions. - For a detailed explanation of the agent code and implementation, see the [Agent Code Walkthrough](Agent-Code-Walkthrough.md). ## Support diff --git a/nodejs/langchain/sample-agent/env/.env.playground b/nodejs/langchain/sample-agent/env/.env.playground new file mode 100644 index 00000000..6c5e027d --- /dev/null +++ b/nodejs/langchain/sample-agent/env/.env.playground @@ -0,0 +1,23 @@ +# 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=playground + +# Environment variables used by Microsoft 365 Agents Playground +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json + +# Custom app registration needed for bearer token +CLIENT_APP_ID= + +# Use Agentic Authentication rather than OBO +USE_AGENTIC_AUTH=false + +# Set service connection as default +connectionsMap__0__serviceUrl=* +connectionsMap__0__connection=service_connection + +# AgenticAuthentication Options +agentic_type=agentic +agentic_altBlueprintConnectionName=service_connection +agentic_scopes=ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default # Prod Agentic scope \ No newline at end of file diff --git a/nodejs/langchain/sample-agent/env/.env.playground.user b/nodejs/langchain/sample-agent/env/.env.playground.user new file mode 100644 index 00000000..f087929f --- /dev/null +++ b/nodejs/langchain/sample-agent/env/.env.playground.user @@ -0,0 +1,12 @@ +# LLM Configuration (choose one option) + +# Option 1: Azure OpenAI (preferred for enterprise) +SECRET_AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= + +# Option 2: OpenAI (if Azure OpenAI not configured) +# SECRET_OPENAI_API_KEY= +# OPENAI_MODEL=gpt-4o + +SECRET_BEARER_TOKEN= \ No newline at end of file diff --git a/nodejs/langchain/sample-agent/m365agents.playground.yml b/nodejs/langchain/sample-agent/m365agents.playground.yml new file mode 100644 index 00000000..a589f6cb --- /dev/null +++ b/nodejs/langchain/sample-agent/m365agents.playground.yml @@ -0,0 +1,41 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/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.11 + +environmentFolderPath: ./env + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.7 + symlinkDir: ./devTools/playground + + # Run npm command + - uses: cli/runNpmCommand + with: + args: install + + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.localConfigs.playground + envs: + NODE_ENV: local + PORT: 3978 + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + # OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + BEARER_TOKEN: ${{SECRET_BEARER_TOKEN}} + USE_AGENTIC_AUTH: ${{USE_AGENTIC_AUTH}} + connectionsMap__0__serviceUrl: ${{connectionsMap__0__serviceUrl}} + connectionsMap__0__connection: ${{connectionsMap__0__connection}} + agentic_type: ${{agentic_type}} + agentic_altBlueprintConnectionName: ${{agentic_altBlueprintConnectionName}} + agentic_scopes: ${{agentic_scopes}} + connections__service_connection__settings__clientId: "" + connections__service_connection__settings__clientSecret: "" + connections__service_connection__settings__tenantId: "" diff --git a/nodejs/langchain/sample-agent/m365agents.yml b/nodejs/langchain/sample-agent/m365agents.yml new file mode 100644 index 00000000..de22d0d0 --- /dev/null +++ b/nodejs/langchain/sample-agent/m365agents.yml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/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.11 + diff --git a/nodejs/langchain/sample-agent/package.json b/nodejs/langchain/sample-agent/package.json index 346868fc..5605fa14 100644 --- a/nodejs/langchain/sample-agent/package.json +++ b/nodejs/langchain/sample-agent/package.json @@ -6,10 +6,12 @@ "type": "commonjs", "scripts": { "start": "node dist/index.js", - "dev": "nodemon --watch src --exec ts-node src/index.ts", + "dev": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register src/index.ts", "test-tool": "agentsplayground", "eval": "node --env-file .env src/evals/index.js", - "build": "tsc" + "build": "tsc", + "dev:teamsfx:playground": "env-cmd --silent -f .localConfigs.playground npm run dev", + "dev:teamsfx:launch-playground": "env-cmd --silent -f env/.env.playground agentsplayground start" }, "keywords": [ "langchain", @@ -46,6 +48,7 @@ "@types/express": "^4.17.21", "@types/node": "^20.14.9", "nodemon": "^3.1.10", - "ts-node": "^10.9.2" + "ts-node": "^10.9.2", + "env-cmd": "^11.0.0" } } diff --git a/python/agent-framework/sample-agent/.gitignore b/python/agent-framework/sample-agent/.gitignore new file mode 100644 index 00000000..ba1c1747 --- /dev/null +++ b/python/agent-framework/sample-agent/.gitignore @@ -0,0 +1,28 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +env/.env.sandbox +.localConfigs +.localConfigs.playground +.localConfigs +.notification.localstore.json +.notification.playgroundstore.json +appPackage/build + +# Python +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +dist/ +build/ +.venv/ +venv/ + +# misc +.env +.deployment +.DS_Store + +# Dev tool directories +/devTools/ diff --git a/python/agent-framework/sample-agent/.vscode/extensions.json b/python/agent-framework/sample-agent/.vscode/extensions.json new file mode 100644 index 00000000..1f2ba3b2 --- /dev/null +++ b/python/agent-framework/sample-agent/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension", + "ms-python.python" + ] +} diff --git a/python/agent-framework/sample-agent/.vscode/launch.json b/python/agent-framework/sample-agent/.vscode/launch.json new file mode 100644 index 00000000..5b004f21 --- /dev/null +++ b/python/agent-framework/sample-agent/.vscode/launch.json @@ -0,0 +1,48 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Start Python", + "type": "debugpy", + "request": "launch", + "python": "${workspaceFolder}/.venv/Scripts/python.exe", + "program": "${workspaceFolder}/start_with_generic_host.py", + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "serverReadyAction": { + "pattern": "(started|listening|Running|Serving) (at|on) (https?://\\S+)", + "action": "startDebugging", + "name": "Start Microsoft 365 Agents Playground" + } + }, + { + "name": "Start Microsoft 365 Agents Playground", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/devTools/playground/node_modules/@microsoft/m365agentsplayground/cli.js", + "args": [ + "start" + ], + "env": { + "PATH": "${workspaceFolder}/devTools/nodejs;${env:PATH}" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Microsoft 365 Agents Playground", + "configurations": [ + "Start Python" + ], + "preLaunchTask": "Deploy (Microsoft 365 Agents Playground)", + "presentation": { + "group": "1-playground", + "order": 1 + }, + "stopAll": true + } + ] +} diff --git a/python/agent-framework/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 b/python/agent-framework/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 new file mode 100644 index 00000000..2fee52fa --- /dev/null +++ b/python/agent-framework/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 @@ -0,0 +1,77 @@ +$ErrorActionPreference = 'Stop' + +$workspace = Get-Location +$playgroundEnvPath = Join-Path $workspace 'env/.env.playground' +$playgroundUserEnvPath = Join-Path $workspace 'env/.env.playground.user' + +$a365Command = Get-Command a365 -ErrorAction SilentlyContinue +if (-not $a365Command) { + throw "a365 CLI is not installed or not on PATH. Install with: dotnet tool install --global Microsoft.Agents.A365.DevTools.Cli" +} + +if (-not (Test-Path $playgroundEnvPath)) { + throw "Missing env file: $playgroundEnvPath" +} + +$appIdLine = Get-Content $playgroundEnvPath | Where-Object { $_ -match '^\s*CLIENT_APP_ID\s*=\s*.+$' } | Select-Object -First 1 +if (-not $appIdLine) { + throw "CLIENT_APP_ID is required in env/.env.playground" +} + +$appId = ($appIdLine -split '=', 2)[1].Trim() +if ([string]::IsNullOrWhiteSpace($appId)) { + throw "CLIENT_APP_ID in env/.env.playground is empty" +} + +Write-Host "Running a365 develop add-permissions for app id $appId" +& a365 develop add-permissions --app-id $appId +if ($LASTEXITCODE -ne 0) { + throw "a365 develop add-permissions failed" +} + +Write-Host "Getting bearer token via a365..." +Write-Host "This may complete silently using cached credentials, or it may require interactive Windows sign-in (WAM)." +Write-Host "If interactive sign-in is required and no prompt appears, check the taskbar for a hidden sign-in window and bring it to front." +Write-Host "Running a365 develop get-token for app id $appId" +$tokenOutput = & a365 develop get-token --app-id $appId --output raw +if ($LASTEXITCODE -ne 0) { + throw "a365 develop get-token failed" +} + +$rawOutput = [string]::Join("`n", $tokenOutput) +$rawOutput = $rawOutput -replace "`r", '' + +$bearerToken = $null +$jwtRegex = '(?[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)' +$jwtMatches = [regex]::Matches($rawOutput, $jwtRegex) +if ($jwtMatches.Count -gt 0) { + $bearerToken = ($jwtMatches | ForEach-Object { $_.Groups['token'].Value } | Sort-Object Length -Descending | Select-Object -First 1).Trim() +} + +if ([string]::IsNullOrWhiteSpace($bearerToken)) { + throw "Unable to extract a bearer token from a365 develop get-token output" +} + +$userEnvLines = @() +if (Test-Path $playgroundUserEnvPath) { + $userEnvLines = Get-Content $playgroundUserEnvPath +} + +$updated = $false +for ($i = 0; $i -lt $userEnvLines.Count; $i++) { + if ($userEnvLines[$i] -match '^\s*SECRET_BEARER_TOKEN\s*=') { + $userEnvLines[$i] = "SECRET_BEARER_TOKEN=$bearerToken" + $updated = $true + break + } +} + +if (-not $updated) { + if ($userEnvLines.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($userEnvLines[$userEnvLines.Count - 1])) { + $userEnvLines += '' + } + $userEnvLines += "SECRET_BEARER_TOKEN=$bearerToken" +} + +Set-Content -Path $playgroundUserEnvPath -Value $userEnvLines -Encoding UTF8 +Write-Host 'SECRET_BEARER_TOKEN has been updated in env/.env.playground.user' diff --git a/python/agent-framework/sample-agent/.vscode/settings.json b/python/agent-framework/sample-agent/.vscode/settings.json new file mode 100644 index 00000000..a9dd15cd --- /dev/null +++ b/python/agent-framework/sample-agent/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe" +} diff --git a/python/agent-framework/sample-agent/.vscode/tasks.json b/python/agent-framework/sample-agent/.vscode/tasks.json new file mode 100644 index 00000000..637738e3 --- /dev/null +++ b/python/agent-framework/sample-agent/.vscode/tasks.json @@ -0,0 +1,52 @@ +{ + "version": "2.0.0", + "tasks": [ + { + // 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": [ + "nodejs", // Check if Node.js is installed and the version is >= 12. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 56150, // Microsoft 365 Agents Playground port + ] + } + }, + { + "label": "Refresh bearer token (Microsoft 365 Agents Playground)", + "type": "shell", + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "${workspaceFolder}/.vscode/scripts/refresh-bearer-token.ps1" + ], + "options": { + "cwd": "${workspaceFolder}" + } + }, + { + // 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)", + "dependsOn": [ + "Validate prerequisites (Microsoft 365 Agents Playground)", + "Refresh bearer token (Microsoft 365 Agents Playground)" + ], + "dependsOrder": "sequence", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "playground" + } + }, + ] +} diff --git a/python/agent-framework/sample-agent/README.md b/python/agent-framework/sample-agent/README.md index 62785a4f..3e8f52af 100644 --- a/python/agent-framework/sample-agent/README.md +++ b/python/agent-framework/sample-agent/README.md @@ -12,11 +12,29 @@ This sample uses the [Microsoft Agent 365 SDK for Python](https://github.com/mic For comprehensive documentation and guidance on building agents with the Microsoft Agent 365 SDK, including how to add tooling, observability, and notifications, visit the [Microsoft Agent 365 Developer Documentation](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/). ## Prerequisites - -- Python 3.x -- Microsoft Agent 365 SDK -- Agent Framework (agent-framework-azure-ai) -- Azure/OpenAI API credentials +> +> To run the template in your local dev machine, you will need: +> +> - [Python](https://www.python.org/), supported versions: 3.11 or higher +> - [Microsoft 365 Agents Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) latest version +> - Prepare your own Azure OpenAI / OpenAI API credentials +> - Azure CLI signed in with `az login` + +> - Microsoft Agent 365 SDK +> - Agent Framework (agent-framework-azure-ai) +> - A365 CLI: Required for agent deployment and management. + +## Python Environment Configuration + +Set up the Python virtual environment manually before running the agent or deploy steps: + +1. Install `uv`: + - `pip install uv` +2. Create a virtual environment: + - `uv venv` +3. Activate the virtual environment: + - Windows PowerShell: `.venv\Scripts\Activate.ps1` + - macOS/Linux: `source .venv/bin/activate` ## Working with User Identity @@ -32,6 +50,17 @@ information — always available with no API calls or token acquisition: The sample logs these fields at the start of every message turn and injects the display name into the LLM system instructions for personalized responses. +## Running the Agent in Microsoft 365 Agents Playground + +1. First, select the Microsoft 365 Agents Toolkit icon on the left in the VS Code toolbar. +1. In file *env/.env.playground.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=`, and deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=` if you're using Azure OpenAI. +1. In file *env/.env.playground.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=` if you're using OpenAI. +1. In file *env/.env.playground*, fill in your custom app registration client id `CLIENT_APP_ID`. +1. Press F5 to start debugging which launches your agent in Microsoft 365 Agents Playground using a web browser. Select `Debug in Microsoft 365 Agents Playground`. +1. You can send any message to get a response from the agent. + +**Congratulations**! You are running an agent that can now interact with users in Microsoft 365 Agents Playground. + ## Handling Agent Install and Uninstall When a user installs (hires) or uninstalls (removes) the agent, the A365 platform sends an `InstallationUpdate` activity — also referred to as the `agentInstanceCreated` event. The sample handles this in `on_installation_update` in `host_agent_server.py`: diff --git a/python/agent-framework/sample-agent/env/.env.playground b/python/agent-framework/sample-agent/env/.env.playground new file mode 100644 index 00000000..c1d20191 --- /dev/null +++ b/python/agent-framework/sample-agent/env/.env.playground @@ -0,0 +1,23 @@ +# 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=playground + +# Environment variables used by Microsoft 365 Agents Playground +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json + +# Custom app registration needed for bearer token +CLIENT_APP_ID= + +# Use Agentic Authentication rather than OBO +USE_AGENTIC_AUTH=false + +# Set service connection as default +connectionsMap__0__serviceUrl=* +connectionsMap__0__connection=service_connection + +# AgenticAuthentication Options +agentic_type=agentic +agentic_altBlueprintConnectionName=service_connection +agentic_scopes=ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default # Prod Agentic scope diff --git a/python/agent-framework/sample-agent/env/.env.playground.user b/python/agent-framework/sample-agent/env/.env.playground.user new file mode 100644 index 00000000..fbe833b4 --- /dev/null +++ b/python/agent-framework/sample-agent/env/.env.playground.user @@ -0,0 +1,13 @@ +# LLM Configuration (choose one option) + +# Option 1: Azure OpenAI (preferred for enterprise) +SECRET_AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= +AZURE_OPENAI_API_VERSION= + +# Option 2: OpenAI (if Azure OpenAI not configured) +# SECRET_OPENAI_API_KEY= +# OPENAI_MODEL=gpt-4o + +SECRET_BEARER_TOKEN= \ No newline at end of file diff --git a/python/agent-framework/sample-agent/m365agents.playground.yml b/python/agent-framework/sample-agent/m365agents.playground.yml new file mode 100644 index 00000000..a40acdb7 --- /dev/null +++ b/python/agent-framework/sample-agent/m365agents.playground.yml @@ -0,0 +1,43 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/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.11 + +environmentFolderPath: ./env + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.7 + symlinkDir: ./devTools/playground + nodejs: + symlinkDir: ./devTools/nodejs + + # Install Python dependencies + - uses: script + with: + run: uv pip install -e . + + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.env + envs: + PORT: 3978 + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + AZURE_OPENAI_API_VERSION: ${{AZURE_OPENAI_API_VERSION}} + # OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + BEARER_TOKEN: ${{SECRET_BEARER_TOKEN}} + USE_AGENTIC_AUTH: ${{USE_AGENTIC_AUTH}} + connectionsMap__0__serviceUrl: ${{connectionsMap__0__serviceUrl}} + connectionsMap__0__connection: ${{connectionsMap__0__connection}} + agentic_type: ${{agentic_type}} + agentic_altBlueprintConnectionName: ${{agentic_altBlueprintConnectionName}} + agentic_scopes: ${{agentic_scopes}} + connections__service_connection__settings__clientId: "" + connections__service_connection__settings__clientSecret: "" + connections__service_connection__settings__tenantId: "" diff --git a/python/agent-framework/sample-agent/m365agents.yml b/python/agent-framework/sample-agent/m365agents.yml new file mode 100644 index 00000000..50d15fc3 --- /dev/null +++ b/python/agent-framework/sample-agent/m365agents.yml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/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.11 diff --git a/python/openai/sample-agent/.gitignore b/python/openai/sample-agent/.gitignore new file mode 100644 index 00000000..ba1c1747 --- /dev/null +++ b/python/openai/sample-agent/.gitignore @@ -0,0 +1,28 @@ +# TeamsFx files +env/.env.*.user +env/.env.local +env/.env.sandbox +.localConfigs +.localConfigs.playground +.localConfigs +.notification.localstore.json +.notification.playgroundstore.json +appPackage/build + +# Python +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +dist/ +build/ +.venv/ +venv/ + +# misc +.env +.deployment +.DS_Store + +# Dev tool directories +/devTools/ diff --git a/python/openai/sample-agent/.vscode/extensions.json b/python/openai/sample-agent/.vscode/extensions.json new file mode 100644 index 00000000..1f2ba3b2 --- /dev/null +++ b/python/openai/sample-agent/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "TeamsDevApp.ms-teams-vscode-extension", + "ms-python.python" + ] +} diff --git a/python/openai/sample-agent/.vscode/launch.json b/python/openai/sample-agent/.vscode/launch.json new file mode 100644 index 00000000..f684a410 --- /dev/null +++ b/python/openai/sample-agent/.vscode/launch.json @@ -0,0 +1,48 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Start Python", + "type": "debugpy", + "request": "launch", + "python": "${workspaceFolder}/.venv/Scripts/python.exe", + "program": "${workspaceFolder}/start_with_generic_host.py", + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "serverReadyAction": { + "pattern": "(started|listening|Running|Serving) (at|on) (https?://\\S+)", + "action": "startDebugging", + "name": "Start Microsoft 365 Agents Playground" + } + }, + { + "name": "Start Microsoft 365 Agents Playground", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/devTools/playground/node_modules/@microsoft/m365agentsplayground/cli.js", + "args": [ + "start" + ], + "env": { + "PATH": "${workspaceFolder}/devTools/nodejs;${env:PATH}" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ], + "compounds": [ + { + "name": "Debug in Microsoft 365 Agents Playground", + "configurations": [ + "Start Python" + ], + "preLaunchTask": "Deploy (Microsoft 365 Agents Playground)", + "presentation": { + "group": "1-playground", + "order": 1 + }, + "stopAll": true + } + ] +} \ No newline at end of file diff --git a/python/openai/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 b/python/openai/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 new file mode 100644 index 00000000..2fee52fa --- /dev/null +++ b/python/openai/sample-agent/.vscode/scripts/refresh-bearer-token.ps1 @@ -0,0 +1,77 @@ +$ErrorActionPreference = 'Stop' + +$workspace = Get-Location +$playgroundEnvPath = Join-Path $workspace 'env/.env.playground' +$playgroundUserEnvPath = Join-Path $workspace 'env/.env.playground.user' + +$a365Command = Get-Command a365 -ErrorAction SilentlyContinue +if (-not $a365Command) { + throw "a365 CLI is not installed or not on PATH. Install with: dotnet tool install --global Microsoft.Agents.A365.DevTools.Cli" +} + +if (-not (Test-Path $playgroundEnvPath)) { + throw "Missing env file: $playgroundEnvPath" +} + +$appIdLine = Get-Content $playgroundEnvPath | Where-Object { $_ -match '^\s*CLIENT_APP_ID\s*=\s*.+$' } | Select-Object -First 1 +if (-not $appIdLine) { + throw "CLIENT_APP_ID is required in env/.env.playground" +} + +$appId = ($appIdLine -split '=', 2)[1].Trim() +if ([string]::IsNullOrWhiteSpace($appId)) { + throw "CLIENT_APP_ID in env/.env.playground is empty" +} + +Write-Host "Running a365 develop add-permissions for app id $appId" +& a365 develop add-permissions --app-id $appId +if ($LASTEXITCODE -ne 0) { + throw "a365 develop add-permissions failed" +} + +Write-Host "Getting bearer token via a365..." +Write-Host "This may complete silently using cached credentials, or it may require interactive Windows sign-in (WAM)." +Write-Host "If interactive sign-in is required and no prompt appears, check the taskbar for a hidden sign-in window and bring it to front." +Write-Host "Running a365 develop get-token for app id $appId" +$tokenOutput = & a365 develop get-token --app-id $appId --output raw +if ($LASTEXITCODE -ne 0) { + throw "a365 develop get-token failed" +} + +$rawOutput = [string]::Join("`n", $tokenOutput) +$rawOutput = $rawOutput -replace "`r", '' + +$bearerToken = $null +$jwtRegex = '(?[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)' +$jwtMatches = [regex]::Matches($rawOutput, $jwtRegex) +if ($jwtMatches.Count -gt 0) { + $bearerToken = ($jwtMatches | ForEach-Object { $_.Groups['token'].Value } | Sort-Object Length -Descending | Select-Object -First 1).Trim() +} + +if ([string]::IsNullOrWhiteSpace($bearerToken)) { + throw "Unable to extract a bearer token from a365 develop get-token output" +} + +$userEnvLines = @() +if (Test-Path $playgroundUserEnvPath) { + $userEnvLines = Get-Content $playgroundUserEnvPath +} + +$updated = $false +for ($i = 0; $i -lt $userEnvLines.Count; $i++) { + if ($userEnvLines[$i] -match '^\s*SECRET_BEARER_TOKEN\s*=') { + $userEnvLines[$i] = "SECRET_BEARER_TOKEN=$bearerToken" + $updated = $true + break + } +} + +if (-not $updated) { + if ($userEnvLines.Count -gt 0 -and -not [string]::IsNullOrWhiteSpace($userEnvLines[$userEnvLines.Count - 1])) { + $userEnvLines += '' + } + $userEnvLines += "SECRET_BEARER_TOKEN=$bearerToken" +} + +Set-Content -Path $playgroundUserEnvPath -Value $userEnvLines -Encoding UTF8 +Write-Host 'SECRET_BEARER_TOKEN has been updated in env/.env.playground.user' diff --git a/python/openai/sample-agent/.vscode/tasks.json b/python/openai/sample-agent/.vscode/tasks.json new file mode 100644 index 00000000..ad03a2f8 --- /dev/null +++ b/python/openai/sample-agent/.vscode/tasks.json @@ -0,0 +1,52 @@ +{ + "version": "2.0.0", + "tasks": [ + { + // 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": [ + "nodejs", // Check if Node.js is installed and the version is >= 12. + "portOccupancy" // Validate available ports to ensure those debug ones are not occupied. + ], + "portOccupancy": [ + 3978, // app service port + 56150, // Microsoft 365 Agents Playground port + ] + } + }, + { + "label": "Refresh bearer token (Microsoft 365 Agents Playground)", + "type": "shell", + "command": "powershell", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "${workspaceFolder}/.vscode/scripts/refresh-bearer-token.ps1" + ], + "options": { + "cwd": "${workspaceFolder}" + } + }, + { + // 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)", + "dependsOn": [ + "Validate prerequisites (Microsoft 365 Agents Playground)", + "Refresh bearer token (Microsoft 365 Agents Playground)" + ], + "dependsOrder": "sequence", + "type": "teamsfx", + "command": "deploy", + "args": { + "env": "playground" + } + }, + ] +} \ No newline at end of file diff --git a/python/openai/sample-agent/README.md b/python/openai/sample-agent/README.md index 49f4a142..717c0d34 100644 --- a/python/openai/sample-agent/README.md +++ b/python/openai/sample-agent/README.md @@ -12,11 +12,29 @@ This sample uses the [Microsoft Agent 365 SDK for Python](https://github.com/mic For comprehensive documentation and guidance on building agents with the Microsoft Agent 365 SDK, including how to add tooling, observability, and notifications, visit the [Microsoft Agent 365 Developer Documentation](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/). ## Prerequisites - -- Python 3.x -- Microsoft Agent 365 SDK -- OpenAI Agents SDK (openai-agents) -- Azure/OpenAI API credentials +> +> To run the template in your local dev machine, you will need: +> +> - [Python](https://www.python.org/), supported versions: 3.11 or higher +> - [Microsoft 365 Agents Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) latest version +> - Prepare your own Azure OpenAI / OpenAI API credentials +> - Azure CLI signed in with `az login` + +> - Microsoft Agent 365 SDK +> - OpenAI Agents SDK (openai-agents) +> - A365 CLI: Required for agent deployment and management. + +## Python Environment Configuration + +Set up the Python virtual environment manually before running the agent or deploy steps: + +1. Install `uv`: + - `pip install uv` +2. Create a virtual environment: + - `uv venv` +3. Activate the virtual environment: + - Windows PowerShell: `.venv\Scripts\Activate.ps1` + - macOS/Linux: `source .venv/bin/activate` ## Working with User Identity @@ -32,6 +50,17 @@ information — always available with no API calls or token acquisition: The sample logs these fields at the start of every message turn and injects the display name into the LLM system instructions for personalized responses. +## Running the Agent in Microsoft 365 Agents Playground + +1. First, select the Microsoft 365 Agents Toolkit icon on the left in the VS Code toolbar. +1. In file *env/.env.playground.user*, fill in your Azure OpenAI key `SECRET_AZURE_OPENAI_API_KEY=`, endpoint `AZURE_OPENAI_ENDPOINT=`, and deployment name `AZURE_OPENAI_DEPLOYMENT_NAME=` if you're using Azure OpenAI. +1. In file *env/.env.playground.user*, fill in your OpenAI key `SECRET_OPENAI_API_KEY=` if you're using OpenAI. +1. In file *env/.env.playground*, fill in your custom app registration client id `CLIENT_APP_ID`. +1. Press F5 to start debugging which launches your agent in Microsoft 365 Agents Playground using a web browser. Select `Debug in Microsoft 365 Agents Playground`. +1. You can send any message to get a response from the agent. + +**Congratulations**! You are running an agent that can now interact with users in Microsoft 365 Agents Playground. + ## Handling Agent Install and Uninstall When a user installs (hires) or uninstalls (removes) the agent, the A365 platform sends an `InstallationUpdate` activity — also referred to as the `agentInstanceCreated` event. The sample handles this in `on_installation_update` in `host_agent_server.py`: diff --git a/python/openai/sample-agent/env/.env.playground b/python/openai/sample-agent/env/.env.playground new file mode 100644 index 00000000..c1d20191 --- /dev/null +++ b/python/openai/sample-agent/env/.env.playground @@ -0,0 +1,23 @@ +# 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=playground + +# Environment variables used by Microsoft 365 Agents Playground +TEAMSAPPTESTER_PORT=56150 +TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json + +# Custom app registration needed for bearer token +CLIENT_APP_ID= + +# Use Agentic Authentication rather than OBO +USE_AGENTIC_AUTH=false + +# Set service connection as default +connectionsMap__0__serviceUrl=* +connectionsMap__0__connection=service_connection + +# AgenticAuthentication Options +agentic_type=agentic +agentic_altBlueprintConnectionName=service_connection +agentic_scopes=ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default # Prod Agentic scope diff --git a/python/openai/sample-agent/env/.env.playground.user b/python/openai/sample-agent/env/.env.playground.user new file mode 100644 index 00000000..8c9bdeb1 --- /dev/null +++ b/python/openai/sample-agent/env/.env.playground.user @@ -0,0 +1,12 @@ +# LLM Configuration (choose one option) + +# Option 1: Azure OpenAI (preferred for enterprise) +SECRET_AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT_NAME= + +# Option 2: OpenAI (if Azure OpenAI not configured) +# SECRET_OPENAI_API_KEY= +# OPENAI_MODEL=gpt-4o + +SECRET_BEARER_TOKEN= diff --git a/python/openai/sample-agent/m365agents.playground.yml b/python/openai/sample-agent/m365agents.playground.yml new file mode 100644 index 00000000..9b192ae7 --- /dev/null +++ b/python/openai/sample-agent/m365agents.playground.yml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/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.11 + +environmentFolderPath: ./env + +deploy: + # Install development tool(s) + - uses: devTool/install + with: + testTool: + version: ~0.2.7 + symlinkDir: ./devTools/playground + nodejs: + symlinkDir: ./devTools/nodejs + + # Install Python dependencies + - uses: script + with: + run: uv pip install -e . + + - uses: file/createOrUpdateEnvironmentFile + with: + target: ./.env + envs: + PORT: 3978 + TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}} + AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}} + AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}} + AZURE_OPENAI_DEPLOYMENT: ${{AZURE_OPENAI_DEPLOYMENT_NAME}} + # OPENAI_API_KEY: ${{SECRET_OPENAI_API_KEY}} + BEARER_TOKEN: ${{SECRET_BEARER_TOKEN}} + USE_AGENTIC_AUTH: ${{USE_AGENTIC_AUTH}} + connectionsMap__0__serviceUrl: ${{connectionsMap__0__serviceUrl}} + connectionsMap__0__connection: ${{connectionsMap__0__connection}} + agentic_type: ${{agentic_type}} + agentic_altBlueprintConnectionName: ${{agentic_altBlueprintConnectionName}} + agentic_scopes: ${{agentic_scopes}} + connections__service_connection__settings__clientId: "" + connections__service_connection__settings__clientSecret: "" + connections__service_connection__settings__tenantId: "" diff --git a/python/openai/sample-agent/m365agents.yml b/python/openai/sample-agent/m365agents.yml new file mode 100644 index 00000000..50d15fc3 --- /dev/null +++ b/python/openai/sample-agent/m365agents.yml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.11/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.11