Created
August 4, 2026 19:00
-
-
Save da5is/858fae0cff726404e48c17a3f8f0e876 to your computer and use it in GitHub Desktop.
FoundryPicker.ps1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env pwsh | |
| # Interactive BYOM launcher for GitHub Copilot CLI (PowerShell edition). | |
| # | |
| # ./run_copilot_foundry.ps1 | |
| # | |
| # Walks every Azure AI Foundry (Cognitive Services / AIServices) resource you | |
| # can see, lists their model deployments, and lets you pick one. It then mints a | |
| # short-lived Entra (Azure AD) bearer token scoped to Cognitive Services, sets | |
| # the COPILOT_* provider variables, and launches Copilot in that context. When | |
| # Copilot exits, the COPILOT_* variables are cleared again. | |
| # | |
| # Run it directly: the env vars only live for this process and its child Copilot | |
| # process, and are cleared on exit. | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(ValueFromRemainingArguments = $true)] | |
| [string[]]$CopilotArgs | |
| ) | |
| $ErrorActionPreference = 'Stop' | |
| $BYOM_TOKEN_RESOURCE = 'https://cognitiveservices.azure.com' | |
| # Azure OpenAI data-plane requires an explicit api-version query parameter. | |
| $BYOM_AZURE_API_VERSION = if ($env:BYOM_AZURE_API_VERSION) { $env:BYOM_AZURE_API_VERSION } else { '2024-10-21' } | |
| # Honor the work-scoped Azure CLI config if present, else use default creds. | |
| $workConfig = Join-Path $HOME '.azure-work' | |
| if (Test-Path -LiteralPath $workConfig -PathType Container) { | |
| $env:AZURE_CONFIG_DIR = $workConfig | |
| } | |
| foreach ($bin in @('az', 'copilot')) { | |
| if (-not (Get-Command $bin -ErrorAction SilentlyContinue)) { | |
| Write-Error "Required command '$bin' not found on PATH." | |
| exit 1 | |
| } | |
| } | |
| Write-Host 'Discovering Azure AI Foundry resources...' -ForegroundColor Cyan | |
| # Grab every AIServices / OpenAI account visible to the current credential. | |
| $accountsRaw = az cognitiveservices account list ` | |
| --query "[?kind=='AIServices' || kind=='OpenAI'].{name:name, rg:resourceGroup, endpoint:properties.endpoint, location:location}" ` | |
| -o json 2>$null | |
| $accounts = @() | |
| if ($accountsRaw) { $accounts = $accountsRaw | ConvertFrom-Json } | |
| if (-not $accounts -or $accounts.Count -eq 0) { | |
| Write-Error "No Foundry (AIServices/OpenAI) resources found for the current context.`nEnsure you are logged in ('az login') and pointed at the right subscription." | |
| exit 1 | |
| } | |
| # Build a flat list of model deployments across all discovered resources. | |
| $options = New-Object System.Collections.Generic.List[object] | |
| foreach ($account in $accounts) { | |
| $deploymentsRaw = az cognitiveservices account deployment list ` | |
| -n $account.name -g $account.rg ` | |
| --query "[?properties.provisioningState=='Succeeded'].{name:name, model:properties.model.name}" ` | |
| -o json 2>$null | |
| $deployments = @() | |
| if ($deploymentsRaw) { $deployments = $deploymentsRaw | ConvertFrom-Json } | |
| foreach ($dep in $deployments) { | |
| $endpoint = $account.endpoint.TrimEnd('/') | |
| $options.Add([pscustomobject]@{ | |
| Endpoint = $endpoint | |
| Model = $dep.model | |
| Deployment = $dep.name | |
| Label = "$($account.name) ($($account.location)) -> $($dep.name) [model: $($dep.model)]" | |
| }) | |
| } | |
| } | |
| if ($options.Count -eq 0) { | |
| Write-Error 'Found Foundry resources but no succeeded model deployments to offer.' | |
| exit 1 | |
| } | |
| Write-Host '' | |
| Write-Host 'Available Foundry model deployments:' | |
| for ($i = 0; $i -lt $options.Count; $i++) { | |
| '{0,4}) {1}' -f ($i + 1), $options[$i].Label | Write-Host | |
| } | |
| Write-Host '' | |
| $choice = $null | |
| while ($true) { | |
| $answer = Read-Host "Select a deployment [1-$($options.Count)] (q to quit)" | |
| if ($answer -match '^[qQ]$') { | |
| Write-Host 'Aborted.' | |
| exit 0 | |
| } | |
| if ($answer -notmatch '^\d+$') { | |
| Write-Host 'Please enter a number.' | |
| continue | |
| } | |
| $num = [int]$answer | |
| if ($num -ge 1 -and $num -le $options.Count) { | |
| $choice = $num | |
| break | |
| } | |
| Write-Host 'Out of range.' | |
| } | |
| $sel = $options[$choice - 1] | |
| Write-Host "Acquiring Entra bearer token for $BYOM_TOKEN_RESOURCE..." -ForegroundColor Cyan | |
| $token = az account get-access-token ` | |
| --resource $BYOM_TOKEN_RESOURCE ` | |
| --query accessToken -o tsv 2>$null | |
| if (-not $token) { | |
| Write-Error "Failed to acquire Entra bearer token via 'az account get-access-token'.`nEnsure you are logged in ('az login') and have access to the Foundry resource." | |
| exit 1 | |
| } | |
| $env:COPILOT_PROVIDER_TYPE = 'azure' | |
| $env:COPILOT_PROVIDER_BASE_URL = "$($sel.Endpoint)/openai/deployments/$($sel.Deployment)" | |
| $env:COPILOT_PROVIDER_AZURE_API_VERSION = $BYOM_AZURE_API_VERSION | |
| $env:COPILOT_PROVIDER_MODEL_ID = $sel.Model | |
| $env:COPILOT_PROVIDER_BEARER_TOKEN = $token | |
| Write-Host "BYOM enabled: $($env:COPILOT_PROVIDER_MODEL_ID) via $($env:COPILOT_PROVIDER_BASE_URL) (api-version $($env:COPILOT_PROVIDER_AZURE_API_VERSION), Entra bearer token)." -ForegroundColor Green | |
| try { | |
| Write-Host 'Launching Copilot...' -ForegroundColor Cyan | |
| if ($CopilotArgs) { | |
| copilot @CopilotArgs | |
| } else { | |
| copilot | |
| } | |
| } | |
| finally { | |
| # Always clear the provider variables when Copilot exits, however it exits. | |
| Remove-Item Env:COPILOT_PROVIDER_TYPE -ErrorAction SilentlyContinue | |
| Remove-Item Env:COPILOT_PROVIDER_BASE_URL -ErrorAction SilentlyContinue | |
| Remove-Item Env:COPILOT_PROVIDER_AZURE_API_VERSION -ErrorAction SilentlyContinue | |
| Remove-Item Env:COPILOT_PROVIDER_MODEL_ID -ErrorAction SilentlyContinue | |
| Remove-Item Env:COPILOT_PROVIDER_BEARER_TOKEN -ErrorAction SilentlyContinue | |
| Write-Host 'BYOM disabled: COPILOT_* provider variables cleared.' | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment