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
44 changes: 32 additions & 12 deletions azure.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
name: ai-foundry-starter-basic

infra:
provider: bicep
path: ./infra

requiredVersions:
extensions:
# the azd ai agent extension is required for this template
"azure.ai.agents": ">=0.1.0-preview"

# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json

# Azure Developer CLI (azd) configuration for the Forge project
# This file defines how azd provisions and deploys Azure infrastructure and services.
# Learn more: https://learn.microsoft.com/azure/developer/azure-developer-cli/

# Project identifier used across Azure resources
name: azd-ai-starter-basic

# Infrastructure provisioning configuration
infra:
provider: bicep
path: ./infra
module: main

# Hooks to run custom scripts after provisioning infrastructure
hooks:
postprovision:
# NOTE: because caphost creation is not idempotent, we cannot run this in bicep directly
windows:
shell: pwsh
run: ./scripts/create_capability_host.ps1
interactive: true
posix:
shell: sh
run: sh ./scripts/create_capability_host.sh
interactive: true

# Recommended extensions for this project
requiredVersions:
extensions:
azure.ai.agents: '>=0.1.3-preview'
10 changes: 0 additions & 10 deletions infra/core/ai/ai-project.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,6 @@ resource aiAccount 'Microsoft.CognitiveServices/accounts@2025-06-01' = {
seqDeployments
]
}

resource aiFoundryAccountCapabilityHost 'capabilityHosts@2025-10-01-preview' = if (enableHostedAgents) {
name: 'agents'
properties: {
capabilityHostKind: 'Agents'
// IMPORTANT: this is required to enable hosted agents deployment
// if no BYO Net is provided
enablePublicHostingEnvironment: true
}
}
}


Expand Down
2 changes: 1 addition & 1 deletion infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ var aiProjectConnections = json(aiProjectConnectionsJson)
var aiProjectDependentResources = json(aiProjectDependentResourcesJson)

@description('Enable hosted agent deployment')
param enableHostedAgents bool
param enableHostedAgents bool = false

@description('Enable monitoring for the AI project')
param enableMonitoring bool = true
Expand Down
140 changes: 140 additions & 0 deletions scripts/create_capability_host.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/usr/bin/env pwsh

# Script to create an Azure AI Foundry capability host using the REST API
# This script checks if the capability host exists before attempting to create it

$ErrorActionPreference = "Stop"

# Load environment variables from azd
if (-not $env:AZURE_SUBSCRIPTION_ID) {
Write-Error "AZURE_SUBSCRIPTION_ID not set. Please run 'azd env refresh' first."
exit 1
}

if (-not $env:AZURE_RESOURCE_GROUP) {
Write-Error "AZURE_RESOURCE_GROUP not set. Please run 'azd env refresh' first."
exit 1
}

if (-not $env:AZURE_AI_ACCOUNT_NAME) {
Write-Error "AZURE_AI_ACCOUNT_NAME not set. Please run 'azd env refresh' first."
exit 1
}

$subscriptionId = $env:AZURE_SUBSCRIPTION_ID
$resourceGroup = $env:AZURE_RESOURCE_GROUP
$accountName = $env:AZURE_AI_ACCOUNT_NAME
$capabilityHostName = "agents"
$apiVersion = "2025-10-01-preview"

# Get Azure access token
Write-Host "Getting Azure access token..." -ForegroundColor Cyan
try {
$tokenResponse = azd auth token --output json --scope https://management.azure.com/.default | ConvertFrom-Json
$accessToken = $tokenResponse.token
if (-not $accessToken) {
throw "Failed to get access token"
}
}
catch {
Write-Error "Failed to get access token. Please run 'azd auth login' first."
exit 1
}

# Check if capability host already exists
Write-Host "Checking if capability host '$capabilityHostName' already exists..." -ForegroundColor Cyan
$checkUrl = "https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.CognitiveServices/accounts/${accountName}/capabilityHosts/${capabilityHostName}?api-version=${apiVersion}"

Write-Host "Debug - Check URL: $checkUrl" -ForegroundColor Gray

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

try {
$existingHost = Invoke-RestMethod -Uri $checkUrl -Method Get -Headers $headers
if ($existingHost) {
Write-Host "✓ Capability host '$capabilityHostName' already exists. Skipping creation." -ForegroundColor Green
exit 0
}
}
catch {
$statusCode = $_.Exception.Response.StatusCode.value__
if ($statusCode -eq 404) {
# 404 means it doesn't exist, continue with creation
Write-Host "Capability host does not exist. Creating..." -ForegroundColor Cyan
}
else {
Write-Error "Error checking for existing capability host (HTTP $statusCode): $($_.ErrorDetails.Message)"
exit 1
}
}

# Construct the REST API URL
$url = "https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.CognitiveServices/accounts/${accountName}/capabilityHosts/${capabilityHostName}?api-version=${apiVersion}"

# Construct the request body
# For hosted agents without BYONET, enablePublicHostingEnvironment is required
$requestBody = @{
properties = @{
capabilityHostKind = "Agents"
enablePublicHostingEnvironment = $true
}
} | ConvertTo-Json -Depth 10

Write-Host "Creating capability host '$capabilityHostName'..." -ForegroundColor Cyan
Write-Host "URL: $url" -ForegroundColor Gray

# Make the REST API call
try {
$response = Invoke-RestMethod -Uri $url -Method Put -Headers $headers -Body $requestBody

Write-Host "✓ Capability host creation request submitted" -ForegroundColor Green

# Poll for provisioning completion
$provisioningState = $response.properties.provisioningState
$maxAttempts = 60 # 5 minutes max (5 second intervals)
$attempt = 0

while ($provisioningState -in @("Creating", "Updating") -and $attempt -lt $maxAttempts) {
$attempt++
Write-Host "Waiting for provisioning to complete (state: $provisioningState, attempt $attempt/$maxAttempts)..." -ForegroundColor Yellow
Start-Sleep -Seconds 5

try {
$statusResponse = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
$provisioningState = $statusResponse.properties.provisioningState
}
catch {
Write-Warning "Failed to check status: $_"
break
}
}

# Check final state
if ($provisioningState -eq "Succeeded") {
Write-Host "✓ Successfully created capability host '$capabilityHostName'" -ForegroundColor Green
Write-Host "Provisioning state: $provisioningState" -ForegroundColor Green
}
elseif ($provisioningState -eq "Failed") {
Write-Error "Capability host provisioning failed"
exit 1
}
elseif ($provisioningState -eq "Canceled") {
Write-Error "Capability host provisioning was canceled"
exit 1
}
else {
Write-Warning "Capability host is still provisioning (state: $provisioningState)"
Write-Host "Check the Azure portal for status." -ForegroundColor Yellow
}
}
catch {
Write-Host "✗ Failed to create capability host" -ForegroundColor Red
Write-Host "Error: $_" -ForegroundColor Red
if ($_.ErrorDetails.Message) {
Write-Host "Details: $($_.ErrorDetails.Message)" -ForegroundColor Red
}
exit 1
}
144 changes: 144 additions & 0 deletions scripts/create_capability_host.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/bin/bash

# Script to create an Azure AI Foundry capability host using the REST API
# This script checks if the capability host exists before attempting to create it

set -e

# Load environment variables from azd
if [ -z "$AZURE_SUBSCRIPTION_ID" ]; then
echo "Error: AZURE_SUBSCRIPTION_ID not set. Please run 'azd env refresh' first."
exit 1
fi

if [ -z "$AZURE_RESOURCE_GROUP" ]; then
echo "Error: AZURE_RESOURCE_GROUP not set. Please run 'azd env refresh' first."
exit 1
fi

if [ -z "$AZURE_AI_ACCOUNT_NAME" ]; then
echo "Error: AZURE_AI_ACCOUNT_NAME not set. Please run 'azd env refresh' first."
exit 1
fi

SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID}"
RESOURCE_GROUP="${AZURE_RESOURCE_GROUP}"
ACCOUNT_NAME="${AZURE_AI_ACCOUNT_NAME}"
CAPABILITY_HOST_NAME="agents"
API_VERSION="2025-10-01-preview"

# Get Azure access token
echo "Getting Azure access token..."
TOKEN_JSON=$(azd auth token --output json --scope https://management.azure.com/.default)
ACCESS_TOKEN=$(echo "$TOKEN_JSON" | jq -r '.token')

if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
echo "Error: Failed to get access token. Please run 'azd auth login' first."
exit 1
fi

# Check if capability host already exists
echo "Checking if capability host '$CAPABILITY_HOST_NAME' already exists..."
CHECK_URL="https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.CognitiveServices/accounts/${ACCOUNT_NAME}/capabilityHosts/${CAPABILITY_HOST_NAME}?api-version=${API_VERSION}"

echo "Debug - Check URL: $CHECK_URL"

HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-X GET \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
"${CHECK_URL}")

if [ "$HTTP_CODE" = "200" ]; then
echo "✓ Capability host '$CAPABILITY_HOST_NAME' already exists. Skipping creation."
exit 0
fi

echo "Capability host does not exist. Creating..."

# Construct the REST API URL
URL="https://management.azure.com/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}/providers/Microsoft.CognitiveServices/accounts/${ACCOUNT_NAME}/capabilityHosts/${CAPABILITY_HOST_NAME}?api-version=${API_VERSION}"

# Construct the request body
# For hosted agents without BYONET, enablePublicHostingEnvironment is required
REQUEST_BODY=$(cat <<EOF
{
"properties": {
"capabilityHostKind": "Agents",
"enablePublicHostingEnvironment": true
}
}
EOF
)

echo "Creating capability host '$CAPABILITY_HOST_NAME'..."
echo "URL: $URL"

# Make the REST API call
RESPONSE=$(curl -s -w "\n%{http_code}" \
-X PUT \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d "${REQUEST_BODY}" \
"${URL}")

# Extract HTTP status code (last line) and response body (everything else)
HTTP_STATUS=$(echo "$RESPONSE" | tail -n1)
RESPONSE_BODY=$(echo "$RESPONSE" | sed '$d')

echo "HTTP Status: $HTTP_STATUS"

if [ "$HTTP_STATUS" = "200" ] || [ "$HTTP_STATUS" = "201" ]; then
echo "✓ Capability host creation request submitted"

# Poll for provisioning completion
PROVISIONING_STATE=$(echo "$RESPONSE_BODY" | jq -r '.properties.provisioningState' 2>/dev/null)
MAX_ATTEMPTS=60 # 5 minutes max (5 second intervals)
ATTEMPT=0

while [ "$PROVISIONING_STATE" = "Creating" ] || [ "$PROVISIONING_STATE" = "Updating" ]; do
if [ $ATTEMPT -ge $MAX_ATTEMPTS ]; then
echo "⚠ Timeout waiting for provisioning to complete (state: $PROVISIONING_STATE)"
echo "Check the Azure portal for status."
break
fi

ATTEMPT=$((ATTEMPT + 1))
echo "Waiting for provisioning to complete (state: $PROVISIONING_STATE, attempt $ATTEMPT/$MAX_ATTEMPTS)..."
sleep 5

# Check current status
STATUS_RESPONSE=$(curl -s \
-X GET \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
"${URL}")

PROVISIONING_STATE=$(echo "$STATUS_RESPONSE" | jq -r '.properties.provisioningState' 2>/dev/null)

if [ -z "$PROVISIONING_STATE" ] || [ "$PROVISIONING_STATE" = "null" ]; then
echo "⚠ Failed to check provisioning status"
break
fi
done

# Check final state
if [ "$PROVISIONING_STATE" = "Succeeded" ]; then
echo "✓ Successfully created capability host '$CAPABILITY_HOST_NAME'"
echo "Provisioning state: $PROVISIONING_STATE"
elif [ "$PROVISIONING_STATE" = "Failed" ]; then
echo "✗ Capability host provisioning failed"
exit 1
elif [ "$PROVISIONING_STATE" = "Canceled" ]; then
echo "✗ Capability host provisioning was canceled"
exit 1
else
echo "⚠ Capability host is still provisioning (state: $PROVISIONING_STATE)"
echo "Check the Azure portal for status."
fi
else
echo "✗ Failed to create capability host"
echo "Response:"
echo "$RESPONSE_BODY" | jq '.' 2>/dev/null || echo "$RESPONSE_BODY"
exit 1
fi