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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions infra/core/agents/botservice.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Creates an Azure Bot Service (with a Microsoft Teams channel) for an
// activity-protocol digital worker. The bot's MSA App ID is the blueprint client
// ID, and the messaging endpoint points at the agent's activity protocol route so
// inbound Teams messages reach the deployed agent.

param botName string
param displayName string
param msaAppId string
param endpoint string
param botServiceSku string = 'F0'

resource botService 'Microsoft.BotService/botServices@2022-09-15' = {
name: botName
kind: 'azurebot'
location: 'global'
sku: {
name: botServiceSku
}
properties: {
displayName: displayName
endpoint: endpoint
msaAppId: msaAppId
msaAppTenantId: tenant().tenantId
msaAppType: 'SingleTenant'
}
}

resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = {
parent: botService
location: 'global'
name: 'MsTeamsChannel'
properties: {
channelName: 'MsTeamsChannel'
}
}
45 changes: 45 additions & 0 deletions infra/core/agents/deployment-script-umi.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Creates a user-assigned managed identity used to run the digital-worker
// deployment script, and grants it the roles required to create a Managed Agent
// Identity Blueprint (MAIB) in the Foundry project via a data-plane call.

targetScope = 'resourceGroup'

@description('Name of the User Assigned Managed Identity to create')
param identityName string = 'foundry-deployment-script-umi'

resource umi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: identityName
location: resourceGroup().location
}

resource contributorAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, umi.id, 'Contributor')
scope: resourceGroup()
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
principalId: umi.properties.principalId
principalType: 'ServicePrincipal'
}
}

var cognitiveServicesUserRoleDefinitionId = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')

resource cogServicesUserRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, umi.id, cognitiveServicesUserRoleDefinitionId)
scope: resourceGroup()
properties: {
roleDefinitionId: cognitiveServicesUserRoleDefinitionId
principalId: umi.properties.principalId
principalType: 'ServicePrincipal'
}
}

output uamiClientId string = umi.properties.clientId
output uamiPrincipalId string = umi.properties.principalId
output uamiResourceId string = umi.id
87 changes: 87 additions & 0 deletions infra/core/agents/maib-creation-script.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Creates a Managed Agent Identity Blueprint (MAIB) in the Foundry project. The
// blueprint backs an activity-protocol digital worker. Because blueprint creation
// is a data-plane operation, it runs via an AzurePowerShell deployment script
// executed as the supplied user-assigned managed identity.

targetScope = 'resourceGroup'

@description('User-assigned managed identity resource ID that the script will run as')
param uamiResourceId string

@description('Azure AI Project (Foundry) endpoint URL')
param azureAIProjectEndpoint string

@description('Managed agent identity blueprint name for the Azure AI Project')
param maibName string

resource psScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'create-agent-script'
location: resourceGroup().location
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${uamiResourceId}': {}
}
}
properties: {
azPowerShellVersion: '11.5'
timeout: 'PT15M'
retentionInterval: 'P1D'
arguments: '-AzureAIProjectEndpoint "${azureAIProjectEndpoint}" -MAIBName "${maibName}"'
environmentVariables: [
{
name: 'RESOURCE_GROUP_NAME'
value: resourceGroup().name
}
]
scriptContent: '''
param(
[Parameter(Mandatory = $true)]
[string] $AzureAIProjectEndpoint,
[Parameter(Mandatory = $true)]
[string] $MAIBName
)

$ErrorActionPreference = "Stop"

$maibUrl = "$($AzureAIProjectEndpoint)/managedagentidentityblueprints/$($MAIBName)?api-version=2025-11-15-preview"

Write-Host "Connecting with managed identity..."
Connect-AzAccount -Identity

Write-Host "Getting access token for https://ai.azure.com ..."
$tokenResponse = Get-AzAccessToken -ResourceUrl "https://ai.azure.com"
$aiAzureToken = $tokenResponse.Token | ConvertFrom-SecureString -AsPlainText
Write-Host "Token length: $($aiAzureToken.Length)"

$headers = @{
"Content-Type" = "application/json"
"Accept" = "application/json"
"Authorization" = "Bearer $aiAzureToken"
}

Write-Host "Creating managed agent identity blueprint at: $maibUrl"

$response = Invoke-RestMethod -Uri $maibUrl `
-Method Put `
-Headers $headers `
-ErrorAction Stop

Write-Host ""
Write-Host "Response:"
$response | ConvertTo-Json -Depth 100 | Write-Host

$blueprintClientId = $response.agentIdentityBlueprint.clientId

$DeploymentScriptOutputs = @{
blueprintClientId = $blueprintClientId
}
'''
}
}

output blueprintClientId string = psScript.properties.outputs.blueprintClientId
47 changes: 47 additions & 0 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ param existingApplicationInsightsResourceId string = ''
@description('Optional. Name of an existing Application Insights connection on the Foundry project. If provided, no new App Insights or connection will be created.')
param existingAppInsightsConnectionName string = ''

@description('Enable creation of an activity-protocol digital worker: a Managed Agent Identity Blueprint (MAIB) and an Azure Bot Service with a Teams channel. Defaults to false so non-activity agents are unaffected.')
param enableDigitalWorker bool = false

@description('Foundry agent name. Used to name the digital worker blueprint and bot, and to build the bot messaging endpoint. Required when enableDigitalWorker is true.')
param agentName string = ''

// Tags that should be applied to all resources.
//
// Note that 'azd-service-name' tags should be applied separately to service host resources.
Expand Down Expand Up @@ -205,6 +211,42 @@ module acrForExistingProject 'core/host/acr.bicep' = if (shouldCreateAcrForExist
}
}

// Digital worker (activity protocol): MAIB + Bot Service. Created only when
// enableDigitalWorker is true and a new Foundry project is being provisioned.
var createDigitalWorker = enableDigitalWorker && !useExistingAiProject && !empty(agentName)
var digitalWorkerMaibName = '${agentName}-maib'
var digitalWorkerBotName = '${agentName}-bot'
var digitalWorkerFoundryEndpoint = useExistingAiProject ? '' : (createDigitalWorker ? aiProject.outputs.FOUNDRY_PROJECT_ENDPOINT : '')

// 1. UMI used to run the MAIB creation deployment script (data-plane op).
module digitalWorkerUmi 'core/agents/deployment-script-umi.bicep' = if (createDigitalWorker) {
scope: rg
name: 'digital-worker-umi'
}

// 2. Create the Managed Agent Identity Blueprint in the Foundry project.
module digitalWorkerMaib 'core/agents/maib-creation-script.bicep' = if (createDigitalWorker) {
scope: rg
name: 'digital-worker-maib'
params: {
uamiResourceId: digitalWorkerUmi!.outputs.uamiResourceId
azureAIProjectEndpoint: digitalWorkerFoundryEndpoint
maibName: digitalWorkerMaibName
}
}

// 3. Create the Azure Bot Service (Teams channel) targeting the agent's activity endpoint.
module digitalWorkerBot 'core/agents/botservice.bicep' = if (createDigitalWorker) {
scope: rg
name: 'digital-worker-bot'
params: {
botName: digitalWorkerBotName
displayName: '${agentName} Bot'
msaAppId: digitalWorkerMaib!.outputs.blueprintClientId
endpoint: '${digitalWorkerFoundryEndpoint}/agents/${agentName}/endpoint/protocols/activityProtocol?api-version=2025-05-15-preview'
}
}

// Resources
output AZURE_RESOURCE_GROUP string = resourceGroupName
output AZURE_AI_ACCOUNT_ID string = useExistingAiProject ? existingAiProject.outputs.accountId : aiProject.outputs.accountId
Expand Down Expand Up @@ -246,3 +288,8 @@ output AZURE_STORAGE_ACCOUNT_NAME string = useExistingAiProject ? existingAiProj

// Connections
output AI_PROJECT_CONNECTION_IDS_JSON string = useExistingAiProject ? string(existingAiProject.outputs.connectionIds) : string(aiProject.outputs.connectionIds)

// Digital worker (activity protocol)
output MAIB_NAME string = createDigitalWorker ? digitalWorkerMaibName : ''
output AGENT_IDENTITY_BLUEPRINT_ID string = createDigitalWorker ? digitalWorkerMaib!.outputs.blueprintClientId : ''
output AGENT_NAME string = agentName
6 changes: 6 additions & 0 deletions infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
},
"existingAppInsightsConnectionName": {
"value": "${APPLICATIONINSIGHTS_CONNECTION_NAME=}"
},
"enableDigitalWorker": {
"value": "${ENABLE_DIGITAL_WORKER=false}"
},
"agentName": {
"value": "${AGENT_NAME=}"
}
}
}