Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ProxyAgent-CSharp/.github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# GitHub Copilot Instructions for Azure AI Foundry Agent for M365
# GitHub Copilot Instructions for Microsoft Foundry Agent for M365

## Project Overview
This is a proxy solution that connects Azure AI Foundry agents to Microsoft 365 Copilot and Teams using the Microsoft 365 Agents Toolkit.
This is a proxy solution that connects Microsoft Foundry agents to Microsoft 365 Copilot and Teams using the Microsoft 365 Agents Toolkit.

## Technology Stack
- **.NET 9** - Bot application runtime
- **M365 Agent SDK** - Microsoft 365 Agent SDK
- **Microsoft 365 Agents Toolkit** - Formerly Teams Toolkit
- **Azure AI Foundry Agent SDK** - For agent integration
- **Microsoft Foundry Agent SDK** - For agent integration
- **Bicep** - Infrastructure as Code
- **Managed Identity** - For production authentication (no secrets)

## Architecture Patterns
- Use the proxy pattern to route messages between M365 Copilot and Azure AI Foundry
- Use the proxy pattern to route messages between M365 Copilot and Microsoft Foundry
- Bot Service acts as the messaging endpoint
- Managed Identity for authentication in production
- Client Secret + Single Tenant for local development
Expand Down
8 changes: 6 additions & 2 deletions ProxyAgent-CSharp/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
"name": "Launch in Teams (Edge)",
"type": "msedge",
"request": "launch",
"url": "https://teams.microsoft.com/l/app/${TEAMS_APP_ID}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}",
"url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{local:TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}",
"presentation": {
"group": "Teams",
"hidden": true,
"order": 1
},
"internalConsoleOptions": "neverOpen"
Expand All @@ -16,9 +17,10 @@
"name": "Launch in Teams (Chrome)",
"type": "chrome",
"request": "launch",
"url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&appTenantId=${{TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}",
"url": "https://teams.microsoft.com/l/app/${{{local:TEAMS_APP_ID}}}?installAppPackage=true&webjoin=true&appTenantId=${{local:TEAMS_APP_TENANT_ID}}&login_hint=${{TEAMSFX_M365_USER_NAME}}",
"presentation": {
"group": "Teams",
"hidden": true,
"order": 2
},
"internalConsoleOptions": "neverOpen"
Expand All @@ -30,6 +32,7 @@
"url": "https://m365.cloud.microsoft/chat/entity1-d870f6cd-4aa5-4d42-9626-ab690c041429/${local:agent-hint}?auth=2&$login_hint=${TEAMSFX_M365_USER_NAME}&developerMode=Basic",
"presentation": {
"group": "Copilot",
"hidden": true,
"order": 3
},
"internalConsoleOptions": "neverOpen"
Expand All @@ -41,6 +44,7 @@
"url": "https://m365.cloud.microsoft/chat/entity1-d870f6cd-4aa5-4d42-9626-ab690c041429/${{local:agent-hint}}?auth=2&${{TEAMSFX_M365_USER_NAME}}&developerMode=Basic",
"presentation": {
"group": "Copilot",
"hidden": true,
"order": 4
},
"internalConsoleOptions": "neverOpen"
Expand Down
24 changes: 12 additions & 12 deletions ProxyAgent-CSharp/AzureAgentToM365ATK/Agents/AzureAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

// Use this flag to enable the no SSO mode, which allows the agent to run without user authentication.
// We will then use DefaultAzureCredential (set via 'az login') to authenticate the agent in the Azure AI Foundry project.
// We will then use DefaultAzureCredential (set via 'az login') to authenticate the agent in the Microsoft Foundry project.
// #define DISABLE_SSO

#if DISABLE_SSO
Expand All @@ -25,7 +25,7 @@ namespace AzureAgentToM365ATK.Agent;

public class AzureAgent : AgentApplication
{
// This is a cache to store the agent model for the Azure AI Foundry agent as this object uses private serializer and virtual objects and is expensive to create.
// This is a cache to store the agent model for the Microsoft Foundry agent as this object uses private serializer and virtual objects and is expensive to create.
// This cache will store the returned model by agent ID. if you need to change the agent model you would need to clear this cache.
private static ConcurrentDictionary<string, Response<PersistentAgent>> _agentModelCache = new();

Expand All @@ -35,29 +35,29 @@ public class AzureAgent : AgentApplication
public AzureAgent(AgentApplicationOptions options, IConfiguration configuration) : base(options)
{

// TO DO: get the connection string of your Azure AI Foundry project in the portal
// TO DO: get the connection string of your Microsoft Foundry project in the portal
this._connectionStringForAgent = configuration["AIServices:AzureAIFoundryProjectEndpoint"];
if (string.IsNullOrEmpty(_connectionStringForAgent))
{
throw new InvalidOperationException("AzureAIFoundryProjectEndpoint is not configured.");
}

// TO DO: Get the assistant ID in the Azure AI Foundry project portal for your agent
// TO DO: Get the assistant ID in the Microsoft Foundry project portal for your agent
this._agentId = configuration["AIServices:AgentID"];
if (string.IsNullOrEmpty(this._agentId))
{
throw new InvalidOperationException("AgentID is not configured.");
}

// Setup Agent with Route handlers to manage connecting and responding from the Azure AI Foundry agent
// Setup Agent with Route handlers to manage connecting and responding from the Microsoft Foundry agent

// This is handling the sign out event, which will clear the user authorization token.
OnMessage("--signout", HandleSignOutAsync);

// This is handling the clearing of the agent model cache without needing to restart the agent.
OnMessage("--clearcache", HandleClearingModelCacheAsync);

// This is handling the message activity, which will send the user message to the Azure AI Foundry agent.
// This is handling the message activity, which will send the user message to the Microsoft Foundry agent.
// we are also indicating which auth profile we want to have available for this handler.
#if DISABLE_SSO
OnActivity(ActivityTypes.Message, SendMessageToAzureAgent);
Expand Down Expand Up @@ -94,7 +94,7 @@ private async Task HandleSignOutAsync(ITurnContext turnContext, ITurnState turnS
}

/// <summary>
/// This method sends the user message ( just text in this example ) to the Azure AI Foundry agent and streams the response back to the user.
/// This method sends the user message ( just text in this example ) to the Microsoft Foundry agent and streams the response back to the user.
/// </summary>
/// <param name="turnContext"></param>
/// <param name="turnState"></param>
Expand All @@ -108,14 +108,14 @@ protected async Task SendMessageToAzureAgent(ITurnContext turnContext, ITurnStat
// Start a Streaming Process to let clients that support streaming know that we are processing the request.
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Just a moment please..", cancellationToken).ConfigureAwait(false);

// Set up the PersistentAgentsClient to communicate with the Azure AI Foundry agent.
// Set up the PersistentAgentsClient to communicate with the Microsoft Foundry agent.

#if DISABLE_SSO
PersistentAgentsClient _aiProjectClient = new PersistentAgentsClient(this._connectionStringForAgent, new DefaultAzureCredential());
#else
// This is a helper class to generate an OBO User Token for the Azure AI Foundry agent from the current user authorization.
// This is a helper class to generate an OBO User Token for the Microsoft Foundry agent from the current user authorization.
PersistentAgentsClient _aiProjectClient = new PersistentAgentsClient(this._connectionStringForAgent,
// This is a helper class to generate an OBO User Token for the Azure AI Foundry agent from the current user authorization.
// This is a helper class to generate an OBO User Token for the Microsoft Foundry agent from the current user authorization.
new UserAuthorizationTokenWrapper(UserAuthorization, turnContext, "SSO"));
#endif

Expand All @@ -124,9 +124,9 @@ protected async Task SendMessageToAzureAgent(ITurnContext turnContext, ITurnStat
if (agentModel == null)
{
// subtle hint to the client that the agent model is being fetched.
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Connecting to Azure AI Foundry.", cancellationToken).ConfigureAwait(false);
await turnContext.StreamingResponse.QueueInformativeUpdateAsync("Connecting to Microsoft Foundry.", cancellationToken).ConfigureAwait(false);

// If the agent model is not found in the conversation state, fetch it from the Azure AI Foundry project.
// If the agent model is not found in the conversation state, fetch it from the Microsoft Foundry project.
agentModel = await _aiProjectClient.Administration.GetAgentAsync(this._agentId).ConfigureAwait(false);
// Cache the agent model for future use.
_agentModelCache.TryAdd(this._agentId, agentModel);
Expand Down
2 changes: 1 addition & 1 deletion ProxyAgent-CSharp/AzureAgentToM365ATK/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
// Enabling anonymous access to the root and controller endpoints in development for testing purposes.
if (app.Environment.IsDevelopment() )
{
app.MapGet("/", () => "Microsoft Agents SDK From Azure AI Foundry Agent Service Sample");
app.MapGet("/", () => "Microsoft Agents SDK From Microsoft Foundry Agent Service Sample");
app.UseDeveloperExceptionPage();
app.MapControllers().AllowAnonymous();
}
Expand Down
69 changes: 35 additions & 34 deletions ProxyAgent-CSharp/M365Agent/AZURE_DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ When you run `atk provision --env dev`, the following Azure resources are create
| **User Assigned Managed Identity** | Bot identity (no passwords!) | `{resourceBaseName}-identity` |
| **App Service Plan** | Compute resources (Linux) | `{resourceBaseName}-plan` |
| **Web App** | Hosts .NET 9 bot application | `{resourceBaseName}-app` |
| **Azure Bot Service** | Bot Framework registration | `{resourceBaseName}` |
| **Azure Bot Service** | Azure Bot Service registration | `{resourceBaseName}` |
| **Entra ID App Registration** | SSO authentication | `{botDisplayName}` |
| **OAuth Connection** | SSO token exchange | `SsoConnection` |

Expand Down Expand Up @@ -151,35 +151,20 @@ Edit `M365Agent/env/.env.dev`:

```bash
# ============================================================================
# Azure Configuration (REQUIRED - Set these before provisioning)
# Microsoft Foundry Configuration (REQUIRED - Set these before provisioning)
# ============================================================================
AZURE_SUBSCRIPTION_ID=<your-subscription-id>
AZURE_RESOURCE_GROUP_NAME=rg-m365agent-prod
RESOURCE_SUFFIX=prod123
AZURE_AI_FOUNDRY_PROJECT_ENDPOINT=<URL of your MS Foundry endpoint>
AGENT_ID=<Agent ID that start by asst_>

# ============================================================================
# Environment Settings
# ============================================================================
TEAMSFX_ENV=dev
APP_NAME_SUFFIX=dev

# ============================================================================
# Output Variables (Auto-populated by atk provision)
# ============================================================================
# Do not edit these - they are populated automatically during deployment
```

**Tips:**
- Get your subscription ID: `az account show --query id -o tsv`
- `RESOURCE_SUFFIX` must be globally unique (lowercase, alphanumeric, max 10 chars)
- Use descriptive names like `prod001`, `dev001`, etc.

### Step 2: Provision Azure Infrastructure

```powershell
cd M365Agent
atk provision --env dev
```
**Using Microsoft 365 Agents Toolkit UI (Recommended):**
1. Open the **Microsoft 365 Agents Toolkit** extension panel in VS Code or Visual Studio
2. Navigate to the **Lifecycle** section
3. Select environment: **dev**
4. Click **Provision** to create Azure resources

**What happens:**
1. ✅ Creates Teams app registration in Teams Developer Portal
Expand All @@ -193,9 +178,9 @@ atk provision --env dev
```
✓ Teams app created successfully
✓ Provisioning Azure resources...
✓ Managed Identity created: botprod123-identity
✓ App Service deployed: https://botprod123-app.azurewebsites.net
✓ Bot Service registered: botprod123
✓ Managed Identity created: <AppName>-identity
✓ App Service deployed: https://<AppName>-app.azurewebsites.net
✓ Bot Service registered: <AppName>
✓ SSO App Registration created
✓ OAuth Connection configured
✓ Teams app package validated
Expand All @@ -204,12 +189,20 @@ atk provision --env dev

**Duration:** ~5-8 minutes

### Step 3: Deploy Application Code

**Alternatively, using CLI:**
```powershell
atk deploy --env dev
cd M365Agent
atk provision --env dev
```

### Step 3: Deploy Application Code

**Using Microsoft 365 Agents Toolkit UI (Recommended):**
1. In the **Microsoft 365 Agents Toolkit** extension panel
2. Navigate to the **Lifecycle** section
3. Select environment: **dev**
4. Click **Deploy** to publish application code

**What happens:**
1. ✅ Builds .NET application (`dotnet publish -c Release`)
2. ✅ Creates deployment package
Expand All @@ -226,7 +219,15 @@ atk deploy --env dev

**Duration:** ~2-3 minutes

### Step 4: Install in Teams
**Alternatively, using CLI:**
```powershell
cd M365Agent
atk deploy --env dev
```

### Step 4: Install in Microsoft 365 Copilot & Microsoft Teams

**Note:** The app is automatically registered in Teams Developer Portal during provisioning. However, you can manually install it if needed:

1. Open **Microsoft Teams**
2. Go to **Apps** → **Manage your apps**
Expand Down Expand Up @@ -261,7 +262,7 @@ Location: Same as resource group
- ✅ No secrets to manage or rotate
- ✅ Secure authentication to Azure services
- ✅ Integrated Azure RBAC support
- ✅ Used as bot's identity in Bot Framework
- ✅ Used as bot's identity in Azure Bot Service

---

Expand Down Expand Up @@ -315,7 +316,7 @@ Location: Same as resource group

**Module:** `modules/azurebot.bicep`

**Purpose:** Registers your web service as a bot with the Bot Framework and enables Teams channel.
**Purpose:** Registers your web service as a bot with Azure Bot Service and enables Teams channel.

**Resources Created:**

Expand Down Expand Up @@ -885,7 +886,7 @@ You've successfully deployed your M365 Agent to Azure! 🎉
- [Microsoft 365 Agents Toolkit Documentation](https://aka.ms/teams-toolkit-docs)
- [Azure Bot Service Documentation](https://learn.microsoft.com/azure/bot-service/)
- [Bicep Documentation](https://learn.microsoft.com/azure/azure-resource-manager/bicep/)
- [Bot Framework SDK](https://github.com/microsoft/botbuilder-dotnet)
- [M365 Agent SDK](https://github.com/microsoft/agents)

**Support:**
- GitHub Issues: [Teams Toolkit Repository](https://github.com/OfficeDev/TeamsFx/issues)
Expand Down
14 changes: 7 additions & 7 deletions ProxyAgent-CSharp/M365Agent/LOCAL_DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ That's it! Everything else happens automatically.

1. **Configure environment** in `.env.local`:
```bash
# Azure AI Foundry configuration
# Microsoft Foundry configuration
AZURE_AI_FOUNDRY_PROJECT_ENDPOINT=<your-ai-foundry-endpoint>
AGENT_ID=<your-agent-id>
```
Expand Down Expand Up @@ -419,7 +419,7 @@ The automated deployment creates **two separate app registrations** for security
**Authentication:** Client ID + Client Secret

**Used for:**
- Bot Framework authentication
- Azure Bot Service authentication
- Bot Service communication
- Local development identity (replaces Managed Identity)

Expand All @@ -444,7 +444,7 @@ Required for: Bot Service to verify bot identity
**Configuration:**
```yaml
OAuth Scope: access_as_user
Federated Credentials: Bot Framework token issuer
Federated Credentials: Azure Bot Service token issuer
Pre-authorized Clients: Teams, Outlook, M365 apps
No client secrets: More secure than password-based auth
```
Expand Down Expand Up @@ -690,7 +690,7 @@ Then press **F5** again - everything will retry automatically.
# Check if bot is accessible via tunnel
curl <BOT_ENDPOINT_from_.env.local>

# Should return bot framework response
# Should return Azure Bot Service response
```

---
Expand Down Expand Up @@ -972,9 +972,9 @@ Edit `.vscode/launch.json`:

Press F5 and select the Chrome configuration.

### Azure AI Foundry Integration
### Microsoft Foundry Integration

**Add your Azure AI Foundry project:**
**Add your Microsoft Foundry project:**

1. After first F5 run, open `M365Agent/env/.env.local`
2. Update these values:
Expand Down Expand Up @@ -1224,7 +1224,7 @@ Production uses:

**Documentation:**
- [Microsoft 365 Agents Toolkit](https://aka.ms/teams-toolkit-docs)
- [Bot Framework SDK for .NET](https://github.com/microsoft/botbuilder-dotnet)
- [M365 Agent SDK for .NET](https://github.com/microsoft/agents)
- [Dev Tunnels Documentation](https://learn.microsoft.com/azure/developer/dev-tunnels/)
- [Teams Platform](https://learn.microsoft.com/microsoftteams/platform/)

Expand Down
2 changes: 1 addition & 1 deletion ProxyAgent-CSharp/M365Agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ An intelligent agent that helps with Azure operations and tasks, built with Micr
1. Activate the #define DISABLE_SSO flag in AzureAgent.cs file
2. Install the Microsoft 365 Agents Playground: https://learn.microsoft.com/en-us/microsoft-365/agents-sdk/test-with-toolkit-project?tabs=windows
3. In VS, set the debugging target to "AzureAgentToM365ATK"
4. In a terminal, use 'az login' and use an account that can call the Azure AI Foundry agent. This will be used by the DefaultAzureCredentials call.
4. In a terminal, use 'az login' and use an account that can call the Microsoft Foundry agent. This will be used by the DefaultAzureCredentials call.
5. Press F5 to start debugging, find the http endpoint in the debug console
6. Launch in a terminal: 'agentsplayground -e "http://localhost:<your-agent-port>/api/messages"

Expand Down
2 changes: 1 addition & 1 deletion ProxyAgent-CSharp/M365Agent/infra/azure-local.json
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@
],
"issuer": "[format('{0}{1}/v2.0', environment().authentication.loginEndpoint, parameters('tenantId'))]",
"subject": "[format('/eid1/c/pub/t/{0}/a/9ExAW52n_ky4ZiS_jhpJIQ/{1}', reference('tenantIdEncoder').outputs.encodedGuid.value, guid(resourceGroup().id, parameters('aadAppName'), 'BotServiceOauthConnection'))]",
"description": "Federated credential for Bot Framework token exchange"
"description": "Federated credential for Azure Bot Service token exchange"
},
"dependsOn": [
"aadApplication",
Expand Down
4 changes: 2 additions & 2 deletions ProxyAgent-CSharp/M365Agent/infra/azure.json
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@
"type": "string",
"defaultValue": "",
"metadata": {
"description": "Azure AI Foundry Project Endpoint (optional)"
"description": "Microsoft Foundry Project Endpoint (optional)"
}
},
"azureAIAgentId": {
Expand Down Expand Up @@ -868,7 +868,7 @@
],
"issuer": "[format('{0}{1}/v2.0', environment().authentication.loginEndpoint, parameters('tenantId'))]",
"subject": "[format('/eid1/c/pub/t/{0}/a/9ExAW52n_ky4ZiS_jhpJIQ/{1}', reference('tenantIdEncoder').outputs.encodedGuid.value, guid(resourceGroup().id, parameters('aadAppName'), 'BotServiceOauthConnection'))]",
"description": "Federated credential for Bot Framework token exchange"
"description": "Federated credential for Azure Bot Service token exchange"
},
"dependsOn": [
"aadApplication",
Expand Down
Loading