Support prompt voice telephony bindings - #9804
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 22 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Azure Pipelines: Successfully started running 2 pipeline(s). 20 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 Changes recommended
Binding normalization, regional routing, legacy validation, and lint failures need correction.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds declarative Foundry telephony bindings for prompt voice agents.
Changes:
- Adds telephony YAML models, schema, validation, and round-trip support.
- Adds binding create/get API operations and drift detection.
- Creates bindings after successful voice-agent deployment.
File summaries
| File | Description |
|---|---|
cspell.yaml |
Adds telephony terminology. |
internal/exterrors/codes.go |
Adds binding operation codes. |
internal/pkg/agents/agent_api/models.go |
Defines binding API models. |
internal/pkg/agents/agent_api/operations.go |
Implements binding API calls. |
internal/pkg/agents/agent_api/operations_test.go |
Tests binding requests. |
internal/pkg/agents/agent_yaml/parse.go |
Validates telephony configuration. |
internal/pkg/agents/agent_yaml/parse_voice_test.go |
Tests validation behavior. |
internal/pkg/agents/agent_yaml/yaml.go |
Defines authoring models. |
internal/project/agent_definition.go |
Supports inline round trips. |
internal/project/service_target_agent.go |
Integrates binding deployment. |
internal/project/service_target_agent_test.go |
Tests detection and matching. |
schemas/azure.ai.agent.json |
Publishes the configuration schema. |
Review details
Suppressed comments (2)
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:2507
- These binding requests drop
AZURE_VOICE_OVERRIDDEN_HOST. The surrounding voice-agent GET/create/update calls propagate that header because some deployments must bypass public APIM; in those deployments the agent operation can succeed and the subsequent telephony GET/create can route incorrectly. Pass the resolved override through both telephony client operations.
remoteBinding, getErr := agentClient.GetTelephonyBinding(
ctx,
agentObject.Name,
bindingID,
agent_api.TelephonyBindingAPIVersion,
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:2557
- Normalize the response ID before comparing it. The new GET test models the service response as
twilio:%2B14255550123, whiledesiredIDistwilio:+14255550123; this comparison therefore reports drift for an unchanged Twilio binding on every redeploy.
desiredID := fmt.Sprintf("%s:%s", strings.TrimSpace(desired.Provider), strings.TrimSpace(desired.Identifier))
if strings.TrimSpace(remote.ID) != "" && strings.TrimSpace(remote.ID) != desiredID {
return false
- Files reviewed: 12/12 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
Referenced non-voice definitions can bypass telephony validation, and an added Go line violates the enforced line-length limit.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:1409
- This checks only the raw property bags, so a hosted service whose
$reffile containstelephonybypasses the prompt-voice restriction: kind resolution expands the reference, butserviceHasTelephonysees only$ref, and the hosted conversion later drops the field. Validate the resolved effective properties (and add a$refregression case) before entering the non-voice deploy path.
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go:20
- This 133-character Go line exceeds the repository's enforced 125-character limit (
cli/azd/AGENTS.md:115-126), so thelllcheck will fail. Split theMustCompilecall across lines.
var acsTpeRawIDPattern = regexp.MustCompile(`^28:orgid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
- Files reviewed: 12/12 changed files
- Comments generated: 2
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Non-voice validation and drift detection have correctness gaps, and several added Go lines fail the enforced line-length lint rule.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:1409
agentDefinitionPathis also populated for an ordinary service-directoryagent.yaml(lines 470–479), so this condition bypasses the non-voice rejection for every legacy file-based service, not only for an explicitAGENT_DEFINITION_PATHoverride. Check the environment override itself so a hosted service cannot silently ignoretelephony.
if !isVoice && p.agentDefinitionPath == "" && serviceHasTelephony(serviceConfig) {
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go:20
- This regex declaration is 133 columns, exceeding the enforced 125-column Go limit (
cli/azd/AGENTS.md:115-126) and causinggolangci-lintto fail.
var acsTpeRawIDPattern = regexp.MustCompile(`^28:orgid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse_voice_test.go:365
- These three added rows are 157–179 columns, exceeding the enforced 125-column Go limit (
cli/azd/AGENTS.md:115-126) and causinggolangci-lintto fail. Expand each table entry across fields.
{name: "bad provider", yaml: "telephony:\n bindings:\n - provider: sip\n identifier: +14255550123\n connection: c", want: "provider must be acs or twilio"},
{name: "bad twilio id", yaml: "telephony:\n bindings:\n - provider: twilio\n identifier: not-a-number\n connection: c", want: "identifier must be +<E.164>"},
{name: "missing connection", yaml: "telephony:\n bindings:\n - provider: acs\n identifier: 4:+14255550123", want: "connection is required"},
- Files reviewed: 13/13 changed files
- Comments generated: 5
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Failed remote bindings can be accepted as successful, and one added line violates the enforced Go line-length limit.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:2524
- A matching binding is accepted without checking
remoteBinding.Status, so a binding in a terminalfailedstate makesazd deployreport success even though calls cannot route. Reject failed bindings with actionable cleanup guidance, and handle any documented transitional statuses before continuing; add a regression test for the failed-status case.
- Files reviewed: 13/13 changed files
- Comments generated: 1
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Schema enforcement and transfer-target drift comparison have correctness gaps.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:2591
reflect.DeepEqualcompares the concrete types inside these dynamic maps rather than their JSON values. An on-diskagent.yamldecodes a numeric transfer-target property as an integer, while the GET response decodes the same JSON number asfloat64, so an unchanged binding allowed by the schema is reported as drift. Compare canonical JSON representations or introduce a typed transfer-target model, and cover the on-disk numeric round trip.
cli/azd/extensions/azure.ai.agents/schemas/azure.ai.agent.json:148telephonyis declared unconditionally, and none of the rootallOfrules requireskind: prompt-voice, so schema validation still accepts a hosted service with this property even though deploy rejects it. Add a conditional rule for the presence oftelephonythat requireskindto beprompt-voice, together with a schema regression test, as is already done for other kind-specific properties.
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Conflicting duplicate bindings can pass validation and make every deployment fail with unavoidable drift.
Review details
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go:582
- Reject duplicate provider/identifier pairs during validation. Two entries for the same binding with different connections or transfer targets pass validation, then deployment creates the first and permanently reports drift on the second; the remote binding can never satisfy both entries.
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:2507 - The tests exercise the matcher and HTTP methods separately, but none execute this orchestration. Add a fakeable client boundary and cover existing-match, drift, GET 404 → create, and non-404/create failures so the core create-only lifecycle and error classification cannot regress unnoticed.
cli/azd/extensions/azure.ai.agents/README.md:267
- This required cleanup command cannot be run as written because
$TOKEN,$PROJECT_ENDPOINT,$AGENT_NAME, and$BINDING_IDare not defined anywhere in the README. Since the preceding text warns that agent deletion can orphan bindings, document exact commands for obtaining each value (including the token audience) or link to complete cleanup instructions.
```bash
curl -X DELETE \
-H "Authorization: Bearer $TOKEN" \
-H "Foundry-Features: VoiceAgents=V1Preview" \
"$PROJECT_ENDPOINT/agents/$AGENT_NAME/telephony/$BINDING_ID?api-version=2025-11-15-preview"
- Files reviewed: 14/14 changed files
- Comments generated: 0 new
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Duplicate binding identities can cause partial remote state, and the new deployment orchestration lacks direct coverage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 14/14 changed files
- Comments generated: 3
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Schema validation is incomplete, and transfer-target comparison can falsely report configuration drift.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:2608
reflect.DeepEqualcan report false drift for an unchanged file-based binding whentransfer_targetscontains numbers: YAML decoding produces integer values, while the GET response's JSON decoding producesfloat64, even though both serialize to the same JSON number. Normalize both target lists through JSON-compatible values before comparing them.
- Files reviewed: 14/14 changed files
- Comments generated: 1
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Transfer-target drift comparison can reject semantically identical numeric values.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:2608
reflect.DeepEqualcompares concrete Go numeric types. File-basedagent.yamltargets are decoded by YAML (for example, numbers becomeint), while GET responses are decoded byencoding/json(float64), so an otherwise identical binding with a numeric transfer-target field is permanently reported as drift. The schema permits arbitrary object values here; normalize both lists through JSON before comparison or use a typed transfer-target model.
- Files reviewed: 14/14 changed files
- Comments generated: 0 new
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Telephony request logging can persist customer phone-number data without redaction.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/operations.go:358
- These requests expose phone-number identifiers (and transfer targets) to the Azure SDK debug logger:
AgentClientenablesIncludeBody, whileinternal/cmd/debug.go:19,51-53writes SDK messages to disk and only redacts connection-string fields. The GET URL also contains the identifier. Running with debug logging can therefore persist customer phone numbers; redact telephony identifiers/targets from request URLs and bodies before logging, or disable body logging for these operations.
- Files reviewed: 14/14 changed files
- Comments generated: 0 new
- Review effort level: Balanced
azure.ai.agents PR buildNote This is an unsigned development build. Install it only if you trust this PR. Install the extension: azd ext install "https://azuresdkartifacts.z5.web.core.windows.net/azd/extensions/pr/9804/azure-ai-agents.zip"
|
Summary
azure.yamlsupport for Foundry-side telephony bindings on prompt voice agents.azd ai agent init,azd provision,azd deploy, andazd down; no new command is introduced.kind: prompt-voice; other agent types rejecttelephonyconfiguration.Spec
What changes for users
Users can optionally add phone-number bindings to a prompt voice agent service:
Then they continue to use the normal lifecycle:
Scope
agent_versionand arbitraryprovider_configare intentionally not exposed because the current service contract does not support version-pinned bindings and unknown provider config fields are ignored.Validation
providermust beacsortwilio.identifierandconnectionare required.28:orgid:<guid>for TPE or4:+<E.164>for ACS-purchased numbers.+<E.164>.provider: acsis mapped to the service canonical providerazure-communication-service.telephonyon non-prompt-voiceservices is rejected unless an explicitAGENT_DEFINITION_PATHoverride is routing the deploy to a different agent definition.Implementation
azure.yamlround-trip support.Test plan and private package
D:\jwshare\adc-hosted-agent\ws-endpoint\voice-telephony-binding-private-test-share-pr9804-win-amd64.ziptelephony-twilioconnection.Validation run
go test ./... -count=1go build ./...cspell lint 'extensions/**/*.go' 'extensions/**/*.md' --config ./.vscode/cspell.yaml --no-progressprovider=twilio, dedicated/test numbers bound throughtelephony-twilio, binding statusactive, and real inbound calls reached prompt voice agents.wujia-voice-agent-telephony-demo0904, phone+1 978-627-7446, bindingtwilio:+19786277446.telephony-acsconnection, project MI RBAC, and ACS binding statusactive.Fixes #9803