Skip to content

feat(agents): support declarative prompt-voice agents - #9364

Merged
Travis Angevine (trangevi) merged 30 commits into
Azure:mainfrom
v1212:users/wujia/prompt-voice-agent-managed-model
Aug 12, 2026
Merged

feat(agents): support declarative prompt-voice agents#9364
Travis Angevine (trangevi) merged 30 commits into
Azure:mainfrom
v1212:users/wujia/prompt-voice-agent-managed-model

Conversation

@v1212

@v1212 JianW (v1212) commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #9336

Adds azd support for a new declarative prompt-voice agent kind that creates a managed speech-to-speech (voice) agent on Azure AI Foundry, end-to-end through azd initazure.yamlazd deploy.

Scope (intentionally narrow): prompt-voice + managed model only — i.e. scaffold (init) and deploy only. BYOM, hosted-voice, tools, avatar, and cascaded models are deliberately left as follow-up PRs. azd-native text invoke, list, and a Portal Playground link are out of scope for this PR (see Follow-ups).

What's included

  • yaml (agent_yaml/yaml.go, parse.go): new prompt-voice authoring kind + VoiceAgent struct/parsing/validation.
  • map (agent_yaml/map.go): translate authoring kind prompt-voice → data-plane service kind voice; default the audio pipeline (PCM16 @ 24 kHz, server_vad, whisper-1, DragonHD default voice); default model_type: managed; pass through model_type: self_deployed for BYOM.
  • agent_api (agent_api/models.go, operations.go): VoiceAgentDefinition wire structs + CreateVoiceAgent with the required preview header Foundry-Features: VoiceAgents=V1Preview.
  • project (project/agent_definition.go, service_target_agent.go): voice-aware definition read/write + an isolated deployVoiceAgent deploy path — the existing hosted/container path is byte-for-byte unchanged.
  • init (cmd/init.go, init_from_templates_helpers.go): private-preview gated prompt-voice scaffold path, voice manifest synthesis, and a new interactive prompt option when explicitly enabled.
  • schema + tests: azure.ai.agent.json gains the prompt-voice kind + voice service properties; unit tests cover the map translation, voice-type selection, and manifest parse/validation.

Compatibility

All changes are additive — new case branches with untouched defaults and omitempty fields. Existing azd init / invoke / deploy / list flows and the hosted/workflow code paths are unchanged.

Testing

Automated

  • go build ./..., go vet ./..., and cspell clean.
  • New unit tests (map_voice_test.go, parse_voice_test.go, init_test.go, nextstep/state_test.go, voice_deploy_dispatch_test.go) plus the existing suite pass.

Live validation

  • Against a real Foundry project: an agent created via the real CreateVoiceAgent code appears in the project list and its stored definition is byte-identical to the service's own managed reference agent (kind:voice / model_type:managed / audio pipeline / voice config). Connect reaches session.created.

How to test end-to-end

Prompt voice init is private-preview gated. Set AZD_AI_AGENT_ENABLE_PROMPT_VOICE=true before running either interactive or no-prompt prompt-voice init.

Build the branch binary and install the branch build of the azure.ai.agents extension first:

cd cli/azd && go build -o azd-branch.exe ./     # then: AZD=./azd-branch.exe
# install the branch-built azure.ai.agents extension from local source (overrides the released build)

1) Interactive (recommended)

export AZD_AI_AGENT_ENABLE_PROMPT_VOICE=true
mkdir voice-test && cd voice-test
"$AZD" ai agent init      # choose "Create a prompt voice agent", then follow the standard init prompts
"$AZD" provision          # standard azd provision (subscription / location / resource group / Foundry project)
"$AZD" deploy             # POST /voice_agents and writes AGENT_<KEY>_NAME / AGENT_<KEY>_ENDPOINT
"$AZD" env get-values | grep AGENT_    # expect AGENT_<KEY>_NAME and AGENT_<KEY>_ENDPOINT
  • The interactive azd ai agent init follows the same prompt flow as other agent kinds (agent name, then the standard Foundry-project selection). --model (default gpt-realtime) and --voice are flags, not prompts — pass them on the same command to override, e.g. azd ai agent init --model gpt-realtime --voice alloy.
  • azd provision and azd deploy add no voice-specific interaction — they behave exactly like the existing hosted/code experience.
  • No on-disk infra/ is required. When infra/ is absent, the Foundry provider synthesizes the embedded ARM template in-memory and resolves the required parameters via the interactive prompts. --infra is optional (only needed to eject IaC to disk).
  • Resulting azure.yaml service: host: azure.ai.agent, kind: prompt-voice, modelType: managed, model: { id: gpt-realtime }. To test BYOM, edit the service to modelType: self_deployed and set model.id to an existing Foundry realtime model deployment before azd deploy.

2) Non-interactive (CI / scripted)

--no-prompt cannot prompt for the three required provision parameters, so eject IaC once and set them explicitly:

export AZD_AI_AGENT_ENABLE_PROMPT_VOICE=true
export AZURE_SUBSCRIPTION_ID=<sub> AZURE_LOCATION=eastus2 AZURE_TENANT_ID=<tenant>

"$AZD" init --minimal --environment voice-test --no-prompt
"$AZD" ai agent init --kind prompt-voice --agent-name my-voice-agent \
       --model gpt-realtime --voice alloy --no-prompt
"$AZD" ai agent init --infra --no-prompt                              # eject embedded Bicep to ./infra
"$AZD" env config set infra.parameters.location eastus2
"$AZD" env config set infra.parameters.resourceGroupName rg-voice-test
"$AZD" env config set infra.parameters.foundryProjectName fp-voice-test
"$AZD" provision --no-prompt
"$AZD" deploy --no-prompt
"$AZD" env get-values | grep AGENT_

3) Runtime check (client)

azd-native text invoke is out of scope for this PR (managed voice agents are Voice Live realtime over WebSocket; the /voice_agents data plane has no HTTP text-invoke endpoint). Validate runtime instead with a voice client connecting to the deployed agent — a session reaches session.created and supports voice/text turns. A read-only control-plane GET /voice_agents/<name> (with header Foundry-Features: VoiceAgents=V1Preview) is a quick sanity check: expect state=enabled, definition.model_type=managed.

Known gaps

  • End-to-end voice session runtime still surfaces errors that reproduce equally on the service's own reference managed agent (e.g. session.audio.output.voice string coercion at the Voice Live layer, and a demo-client session.update framing issue) — being investigated; not specific to this change.

Follow-ups

  • hosted-voice, tools, avatar, cascaded models
  • azd-native text invoke, list, and Portal Playground link for voice agents
  • azd ai agent delete for voice services — the current delete path targets /agents/<name> and returns CodeAgentNotFound (404) for a voice agent, which lives under /voice_agents/<name>. Voice-aware teardown (and an idempotent redeploy/update path over the create-only /voice_agents route) is a follow-up.
  • Remove AZURE_VOICE_OVERRIDDEN_HOST / x-ms-overridden-host once the public Foundry APIM voice route is generally rolled out; will be filed as a tracking issue.

Follow-ups / known gaps

Add azd support for a new 'prompt-voice' agent kind that creates a
managed speech-to-speech (voice) agent on Azure AI Foundry.

- yaml: new prompt-voice kind + VoiceAgent authoring struct/parsing
- map: translate authoring kind prompt-voice -> data-plane kind voice,
  defaulting the audio pipeline (PCM16@24k, server_vad, whisper-1,
  DragonHD default voice) and v1 implicit managed model_type
- agent_api: VoiceAgentDefinition wire structs + CreateVoiceAgent with
  Foundry-Features: VoiceAgents=V1Preview preview header
- project: voice-aware agent_definition read/write + isolated
  deployVoiceAgent deploy path (container path unchanged)
- init: --kind/--voice flags, voice manifest synthesis, prompt option

Scope: prompt-voice + managed model only. BYOM, hosted-voice, tools,
avatar, and cascaded models are follow-ups. Draft: needs further
end-to-end session testing and optimization.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
21 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions github-actions Bot added the ext-agents azure.ai.agents extension label Jul 30, 2026
- map_voice_test.go: cover CreateVoiceAgentAPIRequest defaults/overrides,
  managed enforcement, BYOM rejection, missing-model error, and the
  isOpenAIVoice/buildVoiceConfig voice-type selection
- parse_voice_test.go: cover prompt-voice manifest parsing and
  ValidateAgentDefinition (ok / missing model.id / self_deployed rejected)
- azure.ai.agent.json: add prompt-voice to the kind enum and document the
  voice service properties (modelType/model/instructions/voice/store)
- cspell.yaml: allow BYOM
The azure.ai.projects synthesis copy must stay byte-identical to the
azure.ai.agents copy (TestAgentsSynthesisCopyMatches). Mirror the
prompt-voice comment update made in the agents synthesizer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds managed declarative prompt-voice agents across initialization, configuration, API mapping, and deployment.

Changes:

  • Adds voice-agent YAML/schema models and validation.
  • Maps voice manifests to Foundry’s preview API contract.
  • Adds voice-specific initialization and deployment paths with tests.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
schemas/azure.ai.agent.json Adds voice-agent schema fields.
internal/synthesis/synthesizer.go Marks voice agents as non-container.
internal/project/service_target_agent.go Adds voice deployment handling.
internal/project/agent_definition.go Adds inline voice configuration conversion.
internal/pkg/agents/agent_yaml/yaml.go Defines voice authoring models.
internal/pkg/agents/agent_yaml/parse.go Parses and validates voice manifests.
internal/pkg/agents/agent_yaml/parse_voice_test.go Tests voice parsing and validation.
internal/pkg/agents/agent_yaml/map.go Maps voice manifests to API requests.
internal/pkg/agents/agent_yaml/map_voice_test.go Tests voice request mapping.
internal/pkg/agents/agent_api/operations.go Adds the preview create operation.
internal/pkg/agents/agent_api/models.go Defines voice API wire models.
internal/cmd/init.go Adds voice initialization flags and flow.
internal/cmd/init_from_templates_helpers.go Adds the interactive voice option.
cspell.yaml Adds voice-related terminology.

Comment thread cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go Outdated
@github-actions github-actions Bot added the ext-projects azure.ai.projects extension label Jul 30, 2026
Resolve four correctness gaps in the declarative prompt-voice flow surfaced
in review, keeping every existing hosted/container/workflow path unchanged:

- init: validate --kind (and its --image incompatibility) before either the
  image or prompt-voice synthesis fast path, so `--kind prompt-voice --image`
  is rejected instead of silently creating a hosted image agent.
- init: skipACR now also covers prompt-voice (managed, no container), while a
  new isHostedAgent decision drives hosted-region filtering. selectFoundryProject
  gains a distinct filterHostedRegions parameter so a voice agent skips ACR
  without being constrained to hosted-agent regions.
- deploy: resolve an explicit AGENT_DEFINITION_PATH override before the
  voice/container dispatch (resolveVoiceAgentForDeploy), so an override wins for
  voice just as it does for the container path.
- deploy contract: make Endpoints() and next-step isDeployed voice-aware. Voice
  agents record only NAME + base ENDPOINT (no agent-version / per-protocol
  endpoints), so both consumers now treat the base endpoint as the deployment
  marker instead of reporting a created voice agent as undeployed.

Adds unit tests for the skipACR/isHostedAgent split, the override-precedence
dispatch, and the voice deployed-marker fallback.
@azure-pipelines

Copy link
Copy Markdown
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

@jongio Jon Gallant (jongio) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ran through the voice path end to end against the current head. Build is clean and the new tests pass locally. Three things I'd like to sort out before this ships, plus two nits.

Medium

  1. The new deployed-agent detection keys off "VERSION empty and ENDPOINT set" rather than the service kind, in both Endpoints() and nextstep.isDeployed(). Inline comments on both.
  2. Voice create is an unconditional POST to /voice_agents with no version model. What's the intended behavior for a second azd deploy? Inline comment on operations.go.
  3. Delete has no voice path. AgentClient.DeleteAgent builds %s/agents/%s, and DeleteAction.cleanupEnvVars clears the three AGENT_<KEY>_* vars regardless. So azd ai agent delete against a prompt-voice service hits /agents/<name>, gets a 404, and classifyDeleteError turns that into CodeAgentNotFound. The user is told the agent doesn't exist while it's still live in the project, and there's no way to tear it down from azd. If delete is intentionally out of scope for this PR, could you add it to the follow-ups list in the description so it doesn't get lost?

Low

  1. Orphaned comment fragment in init.go. Inline comment.
  2. isOpenAIVoice classifies by name shape. Inline comment.

One question on AZURE_VOICE_OVERRIDDEN_HOST: the comment says it exists to bypass the public Foundry APIM while the voice route rolls out. Worth filing an issue to remove it once the route is live, otherwise it tends to stick around forever.

Also, the description still says Draft but the PR is open and review is requested. Probably just needs updating.

Comment thread cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state.go
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init.go Outdated
- Endpoints()/isDeployed(): gate voice base-endpoint fallback on the
  service's actual prompt-voice kind instead of the env-var shape, so a
  partially-failed hosted deploy still surfaces CodeMissingAgentEnvVars
- add nextstep isVoiceService helper (mirrors project kind gate; the two
  stay in separate packages to avoid a project->nextstep import cycle)
- CreateVoiceAgent: document create-only redeploy semantics
- isOpenAIVoice: classify via explicit OpenAI voice set + Azure Neural
  locale-prefix pattern instead of a bare '-' check
- init.go: drop orphaned comment fragment
- tests: cover hosted lingering-endpoint gate and voice name classification
Copilot AI review requested due to automatic review settings August 3, 2026 12:43
@v1212

Copy link
Copy Markdown
Contributor Author

Jon Gallant (@jongio) thanks for the review — pushed 438466c addressing the inline threads (replied on each; left them unresolved for you to confirm). For the remaining points from the review body:

  • azd ai agent delete on a voice service (404 CodeAgentNotFound because it targets /agents/<name> not /voice_agents/<name>): out of scope for this init+deploy PR — added a Follow-ups entry for voice-aware teardown (plus the idempotent redeploy/update path over the create-only /voice_agents route).
  • Nit: > Draft text — removed from the description (PR is out of draft).
  • Nit: AZURE_VOICE_OVERRIDDEN_HOST / x-ms-overridden-host cleanup — added a Follow-ups entry; will file a tracking issue to remove it once the public Foundry APIM voice route is rolled out.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Suppressed comments (8)

cli/azd/extensions/azure.ai.agents/internal/cmd/init_foundry_project_setup.go:148

  • [azd-code-reviewer] The interactive existing-project branch has the same coupling: prompt-voice sets skipACR=true, which now turns on hosted-region filtering and hides otherwise valid voice projects. Use the separate hosted-agent decision here as well.
				skipACR, // filterHostedRegions: this path is code/container only (non-voice)

cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:440

  • [azd-code-reviewer] This voice check ignores AGENT_DEFINITION_PATH, although deployment now lets that override determine voice/container dispatch. A voice override on a hosted service deploys successfully but endpoint discovery still treats it as hosted and fails on the missing VERSION; next-step state has the same mismatch. Persist or resolve a common effective-kind marker in all three consumers.
	if _, isVoice, err := VoiceAgentFromResolvedService(serviceConfig, p.projectPath); err != nil {

cli/azd/extensions/azure.ai.agents/internal/cmd/init.go:1646

  • [azd-code-reviewer] This resolved name is not pinned to flags.agentName. runInitFromManifest later reaches downloadAgentYaml, which calls resolveInitAgentName again, so the interactive voice flow asks for the agent name twice. Pin the result before entering the manifest flow and add a regression test.
					resolvedName, err := resolveInitAgentName(ctx, azdClient, flags, "voice-agent")

cli/azd/extensions/azure.ai.agents/internal/cmd/init_foundry_project_setup.go:74

  • [azd-code-reviewer] This still equates skipping ACR with requiring hosted-agent regions. Prompt-voice reaches configureFoundryProject with skipACR=true, so a voice init using --project-id incorrectly filters out projects in regions that do not support hosted agents. Pass a separate filterHostedRegions decision through this helper.

This issue also appears on line 148 of the same file.

			skipACR, // filterHostedRegions: this path is code/container only (non-voice)

cli/azd/extensions/azure.ai.agents/internal/cmd/init.go:1288

  • [azd-code-reviewer] The prompt-voice fast path silently ignores --model-deployment, even though the shared flag help says it takes precedence over --model. Because this PR supports managed models only, reject this combination instead of creating the default gpt-realtime agent.
				if flags.image != "" {

cli/azd/extensions/azure.ai.agents/internal/cmd/init.go:2216

  • [azd-code-reviewer] Recording isVoiceAgent does not preserve the documented two-question scaffold flow. A synthesized voice manifest has no model resources, so this function continues into configureFoundryProject, whose interactive branch prompts for project choice, subscription, and location during init rather than deferring them to azd provision as the PR description states.
	if kind, err := agentManifestKind(agentManifest); err == nil {
		a.isVoiceAgent = kind == agent_yaml.AgentKindPromptVoice

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/operations.go:188

  • [azd-code-reviewer] The linked REST contract does not have the create-only behavior documented here: POST /voice_agents creates an agent or a new version of an existing one, and POST /voice_agents/{agent_name} provides update semantics. Treating every second deploy as an unavoidable failure makes azd deploy non-repeatable despite API support; implement the existing-agent path rather than deferring it.
// Redeploy semantics: the voice data-plane exposes create-only POST /voice_agents
// with no version/upsert model (unlike hosted agents, which mint a new
// agent-version per deploy). A second `azd deploy` of the same voice service
// therefore re-POSTs with the same name and the service rejects it with a
// non-success status, which this method surfaces as a deploy error rather than
// silently overwriting the existing agent. Idempotent redeploy/update is tracked
// as a follow-up (see the PR "Follow-ups" section); until the service adds an
// update route, redeploy requires deleting the existing voice agent first.

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/operations.go:212

  • [azd-code-reviewer] CreateVoiceAgent has no operation-level test, leaving the required preview header, optional overridden-host header, route, and accepted response statuses unverified. Add a fake-transport test alongside operations_test.go that asserts these request details and response parsing.
	// Voice agents are a preview feature; the service rejects the request with
	// 403 preview_feature_required unless this opt-in header is present.
	req.Raw().Header.Set("Foundry-Features", voiceAgentsPreviewFeature)

	if overriddenHost != "" {
		req.Raw().Header.Set("x-ms-overridden-host", overriddenHost)

Comment thread cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go Outdated
Copilot AI review requested due to automatic review settings August 10, 2026 05:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go:338

  • [azd-code-reviewer] This makes --project-id container initialization apply hosted-region filtering even though the equivalent interactive branch below uses deployMode == "code", and the previous behavior tied both decisions to that same condition. A container project can therefore be rejected when supplied by ID but accepted through interactive selection. Keep the two branches consistent.
		filterHostedRegions := true

cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go:140

  • [azd-code-reviewer] With the preview disabled (the default), an empty interactive directory no longer follows the previous automatic template path; it now opens a one-item selection prompt. This contradicts the PR's claim that existing init flows remain unchanged. Preserve the early empty -> initModeTemplate return when voice preview is off, while showing the prompt when the voice choice is enabled, and update the default-disabled regression test accordingly.
	if noPrompt {
		if empty {
			return initModeTemplate, nil
		}
		return initModeFromCode, nil
	}

cli/azd/extensions/azure.ai.agents/internal/cmd/doctor/checks_agent_status.go:733

  • [azd-code-reviewer] This classification omits AGENT_DEFINITION_PATH, although deploy, Endpoints, and next-step all honor that override. If a hosted or kind-less service is deployed through a prompt-voice override, it has no VERSION, but doctor classifies it as hosted and runs the hosted NAME/VERSION probe, producing a false failure. Pass the process override here as well and add an override regression test.
		isVoice, err := agentkind.IsPromptVoice(svc, resp.Project.GetPath(), "")

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Copilot AI review requested due to automatic review settings August 10, 2026 08:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated no new comments.

Suppressed comments (5)

cli/azd/extensions/azure.ai.agents/schemas/azure.ai.agent.json:58

  • [azd-code-reviewer] This schema still accepts model: { id: "" }, although both manifest validation and request mapping reject an empty model.id. Add a non-empty constraint so editor/schema validation matches deployment behavior as the new $comment promises.
    cli/azd/extensions/azure.ai.agents/internal/cmd/init.go:1767
  • [azd-code-reviewer] The shared --model help now applies to prompt-voice init too, but it only advertises the hosted-agent default. Prompt-voice synthesis defaults to gpt-realtime, so azd ai agent init --help gives the wrong expectation for this new path. Qualify both defaults here.
				"--model-deployment takes precedence.",

cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go:29

  • [azd-code-reviewer] This new process environment variable is required to discover or select the private-preview flow, but it is only described in the PR text. Add it to the extension's durable documentation with its default-off behavior and accepted values (1, true, or yes, case-insensitive), so users of the shipped extension can enable the feature correctly.
const promptVoicePreviewEnvVar = "AZD_AI_AGENT_ENABLE_PROMPT_VOICE"

cli/azd/extensions/azure.ai.agents/internal/project/agent_definition.go:1036

  • [azd-code-reviewer] Add a round-trip test for the normal inline azure.yaml path: create properties with VoiceAgentDefinitionToServiceProperties, then read them through VoiceAgentFromResolvedService or resolveVoiceAgentForDeploy and assert modelType, model, instructions, voice, and store. The current dispatch tests only exercise AGENT_DEFINITION_PATH, so the init-produced inline representation that deploy normally consumes is unverified.
func VoiceAgentFromResolvedService(

cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:1875

  • [azd-code-reviewer] This routing override is not documented outside the PR description. Because deployment reads it from azd environment values rather than directly from the process environment, document the expected host format, the default public-route behavior, and that it must be configured with azd env set AZURE_VOICE_OVERRIDDEN_HOST <host>; otherwise private-preview users are likely to export it and see no effect.
const voiceOverriddenHostEnvKey = "AZURE_VOICE_OVERRIDDEN_HOST"

Copilot AI review requested due to automatic review settings August 11, 2026 05:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated no new comments.

Suppressed comments (3)

cli/azd/extensions/azure.ai.agents/internal/cmd/init.go:3352

  • [azd-code-reviewer] This calls the hosted-agent next-step resolver for a voice-only service. When the project endpoint already exists, ResolveAfterInit takes its default branch and prints azd ai agent run plus a local invoke command, but prompt-voice has no local runtime and this PR explicitly leaves native run/invoke out of scope. Make the next-step state/resolver voice-aware so voice init emits only supported provision/deploy guidance.
	_ = printAllNextIfTerminal(os.Stdout, nextstep.ResolveAfterInit(state, readmeExistsForProject(ctx, a.azdClient)))

cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go:33

  • [azd-code-reviewer] This required private-preview switch is not documented in the extension README or the environment-variable reference; the PR description is the only place that explains it. Preview users will not have durable guidance on how to enable the otherwise hidden prompt and flags. Document the accepted values and default-off behavior with the extension's environment variables.
const promptVoicePreviewEnvVar = "AZD_AI_AGENT_ENABLE_PROMPT_VOICE"

func promptVoicePreviewEnabled() bool {
	value := strings.TrimSpace(os.Getenv(promptVoicePreviewEnvVar))
	return strings.EqualFold(value, "1") || strings.EqualFold(value, "true") || strings.EqualFold(value, "yes")

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go:530

  • [azd-code-reviewer] A whitespace-only model.id passes this required-field check (and the new schema/parser checks) and is then posted as the model name, guaranteeing a service-side failure instead of a local manifest error. Trim the ID before validating and serializing it, and align the schema/parser validation so editor and runtime behavior agree.
func CreateVoiceAgentAPIRequest(voiceAgent VoiceAgent) (*agent_api.CreateAgentRequest, error) {
	if voiceAgent.Model == nil || voiceAgent.Model.Id == "" {
		return nil, fmt.Errorf("model.id is required for a prompt-voice agent")

Copilot AI review requested due to automatic review settings August 11, 2026 05:50
@v1212 JianW (v1212) changed the title feat(agents): support declarative prompt-voice agents (managed model) feat(agents): support declarative prompt-voice agents Aug 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated no new comments.

Suppressed comments (3)

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go:555

  • [azd-code-reviewer] Whitespace-only voice values pass validation, enter this branch, and are then trimmed to an empty OpenAI voice name by buildVoiceConfig, so a manifest that is effectively blank sends an invalid request instead of using the documented default. Trim before the emptiness check (or reject blank values during validation).
	voiceName := defaultVoiceName
	if voiceAgent.Voice != nil && *voiceAgent.Voice != "" {
		voiceName = *voiceAgent.Voice
	}

cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go:33

  • [azd-code-reviewer] This new environment variable is missing from the azure.ai.agents section of cli/azd/docs/environment-variables.md, which is the repository's required source of truth for variables read by azd. Document that it gates private-preview prompt-voice initialization, defaults to disabled, and accepts 1, true, or yes case-insensitively.
const promptVoicePreviewEnvVar = "AZD_AI_AGENT_ENABLE_PROMPT_VOICE"

func promptVoicePreviewEnabled() bool {
	value := strings.TrimSpace(os.Getenv(promptVoicePreviewEnvVar))
	return strings.EqualFold(value, "1") || strings.EqualFold(value, "true") || strings.EqualFold(value, "yes")

cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:1875

  • [azd-code-reviewer] AZURE_VOICE_OVERRIDDEN_HOST is a new extension-specific routing control but is absent from cli/azd/docs/environment-variables.md. Add it to the azure.ai.agents variables (or the internal/debug section if unsupported), including that deploy reads it from the active azd environment rather than directly from the process environment.
// voiceOverriddenHostEnvKey optionally routes the /voice_agents call directly to
// a regional data-plane host (bypassing the public Foundry APIM, whose voice
// route may not yet be rolled out). When unset, default endpoint routing is used.
//
//nolint:gosec // env var key name, not a credential
const voiceOverriddenHostEnvKey = "AZURE_VOICE_OVERRIDDEN_HOST"

Copilot AI review requested due to automatic review settings August 11, 2026 07:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated no new comments.

Suppressed comments (4)

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go:470

  • [azd-code-reviewer] The PR description says the default input transcription model is whisper-1, while this implementation and its tests send azure-speech. Align the implementation with the stated contract, or update the PR description if azure-speech is the intended service value.
	// defaultVoiceInputTranscriptionModel enables user-speech transcription events.
	// azure-speech is accepted by both realtime and cascaded voice pipelines.
	defaultVoiceInputTranscriptionModel = "azure-speech"

cli/azd/extensions/azure.ai.agents/internal/cmd/init_adopt.go:975

  • [azd-code-reviewer] This hardcoded value changes the existing adopt flow for container deployments. When usesContainer is true, skipACR is false; before this refactor that also meant no hosted-region filtering, but this now filters those projects and can prevent an otherwise unchanged container sample from being adopted. Preserve the previous non-voice behavior by deriving this value from skipACR.
	filterHostedRegions := true

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go:556

  • [azd-code-reviewer] A whitespace-only voice value bypasses the default because it is not equal to ""; buildVoiceConfig then trims it and sends an OpenAI voice with an empty name. Treat a trimmed-empty value like an omitted value so hand-edited manifests do not fail at the service.
	voiceName := defaultVoiceName
	if voiceAgent.Voice != nil && *voiceAgent.Voice != "" {
		voiceName = *voiceAgent.Voice
	}

cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go:33

  • [azd-code-reviewer] This environment variable is the only way to expose prompt-voice initialization, but it is documented only in the PR description. Add a durable private-preview section to the extension README covering the default-off behavior, accepted truthy values, and interactive/non-interactive enablement so the workflow remains discoverable after merge.
const promptVoicePreviewEnvVar = "AZD_AI_AGENT_ENABLE_PROMPT_VOICE"

func promptVoicePreviewEnabled() bool {
	value := strings.TrimSpace(os.Getenv(promptVoicePreviewEnvVar))
	return strings.EqualFold(value, "1") || strings.EqualFold(value, "true") || strings.EqualFold(value, "yes")

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Outdated
Copilot AI review requested due to automatic review settings August 12, 2026 09:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated 2 comments.

Suppressed comments (6)

cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:565

  • [azd-code-reviewer] Once this service resolves as voice, an empty voice endpoint must not fall through to the hosted path. If the service was changed from hosted to prompt-voice, stale NAME/VERSION and protocol endpoint variables can make Endpoints return the old hosted endpoint. Handle the voice kind as an exclusive branch and require its NAME+ENDPOINT contract.
	if isVoice, err := agentkind.IsPromptVoice(
		serviceConfig, projectRoot, agentDefinitionPath,
	); err == nil && isVoice && azdEnv[agentEndpointKey] != "" {
		return []string{azdEnv[agentEndpointKey]}, nil
	}

cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:1916

  • [azd-code-reviewer] This reads only the persisted azd environment map, so exporting AZURE_VOICE_OVERRIDDEN_HOST in the process has no effect. The provider already exposes dependencyEnvValue, which checks the azd environment and then os.Getenv; use it so this environment variable behaves as advertised.
	agentObject, err := agentClient.CreateVoiceAgent(
		ctx, request, agent_api.AgentEndpointAPIVersion, azdEnv[voiceOverriddenHostEnvKey],
	)

cli/azd/extensions/azure.ai.agents/internal/cmd/nextstep/state.go:790

  • [azd-code-reviewer] A voice service can retain AGENT_<KEY>_VERSION when an existing hosted service is changed to prompt-voice, and the voice deploy path never clears it. Returning here then reports the voice service as deployed even when its required ENDPOINT is absent. For voice services, ignore VERSION and require the endpoint marker.
	if value != "" {
		return true

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go:470

  • [azd-code-reviewer] The PR summary says the default input transcription model is whisper-1, but this request emits azure-speech. Confirm the intended service contract and either use whisper-1 (with updated tests) or correct the PR description so the documented and deployed pipelines agree.
	// defaultVoiceInputTranscriptionModel enables user-speech transcription events.
	// azure-speech is accepted by both realtime and cascaded voice pipelines.
	defaultVoiceInputTranscriptionModel = "azure-speech"

cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go:33

  • [azd-code-reviewer] This new preview gate is user-configured but is absent from the environment-variable reference. Document that it defaults to disabled and list the accepted truthy values so users can reliably enable the private-preview flow.
const promptVoicePreviewEnvVar = "AZD_AI_AGENT_ENABLE_PROMPT_VOICE"

func promptVoicePreviewEnabled() bool {
	value := strings.TrimSpace(os.Getenv(promptVoicePreviewEnvVar))
	return strings.EqualFold(value, "1") || strings.EqualFold(value, "true") || strings.EqualFold(value, "yes")

cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go:1875

  • [azd-code-reviewer] This new routing override is not documented in the environment-variable reference. Add it to the extension/debug variables documentation with the expected hostname format, where it is read from, and an unsupported/private-preview warning; otherwise users cannot safely configure or troubleshoot the temporary route.
// voiceOverriddenHostEnvKey optionally routes the /voice_agents call directly to
// a regional data-plane host (bypassing the public Foundry APIM, whose voice
// route may not yet be rolled out). When unset, default endpoint routing is used.
//
//nolint:gosec // env var key name, not a credential
const voiceOverriddenHostEnvKey = "AZURE_VOICE_OVERRIDDEN_HOST"

Comment thread cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go Outdated
Copilot AI review requested due to automatic review settings August 12, 2026 09:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated no new comments.

Suppressed comments (4)

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go:555

  • [azd-code-reviewer] A whitespace-only voice bypasses the default here, then buildVoiceConfig trims it and emits an OpenAI voice with an empty name. This produces an invalid request instead of the documented default. Trim before deciding whether the override is present.
	if voiceAgent.Voice != nil && *voiceAgent.Voice != "" {
		voiceName = *voiceAgent.Voice

cli/azd/extensions/azure.ai.agents/internal/project/agent_definition.go:963

  • [azd-code-reviewer] The shared kind resolver can classify a legacy on-disk agent.yaml as prompt-voice, but this line reparses only inline/config properties. When the kind exists only in the service directory, this returns found=false, so Deploy falls into the container path and rejects the valid voice definition. After inline resolution misses, load the same agent.yaml/agent.yml with voiceAgentFromDefinitionFile, and add a dispatch regression for this path.
	return VoiceAgentFromResolvedService(svc, projectRoot)

cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_templates_helpers.go:33

  • [azd-code-reviewer] This new os.Getenv feature gate is not added to the repository's environment-variable reference. Because both related flags are hidden, users invited to the private preview otherwise have no checked-in documentation for enabling it or for the accepted values (1, true, and yes). Add the variable and its default/accepted format to the appropriate azd environment-variable documentation.
const promptVoicePreviewEnvVar = "AZD_AI_AGENT_ENABLE_PROMPT_VOICE"

func promptVoicePreviewEnabled() bool {
	value := strings.TrimSpace(os.Getenv(promptVoicePreviewEnvVar))
	return strings.EqualFold(value, "1") || strings.EqualFold(value, "true") || strings.EqualFold(value, "yes")

cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go:470

  • [azd-code-reviewer] The PR summary says the default input transcription model is whisper-1, but the generated payload uses azure-speech. Since this changes the deployed agent's runtime configuration, align the default and tests with the stated behavior or update the PR description if azure-speech is intentional.

This issue also appears on line 554 of the same file.

	defaultVoiceInputTranscriptionModel = "azure-speech"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/extensions Extensions (general) ext-agents azure.ai.agents extension ext-projects azure.ai.projects extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Voice-based prompt agents

5 participants