Skip to content

Feature/code deploy zip upload#8146

Merged
trangevi merged 19 commits into
Azure:mainfrom
v1212:feature/code-deploy-zip-upload
May 13, 2026
Merged

Feature/code deploy zip upload#8146
trangevi merged 19 commits into
Azure:mainfrom
v1212:feature/code-deploy-zip-upload

Conversation

@v1212
Copy link
Copy Markdown
Collaborator

@v1212 v1212 commented May 12, 2026

Summary

Adds code deploy (ZIP upload) support for hosted agents as an alternative to container-based deployment. This enables deploying Python agent source code directly without requiring Docker/ACR.

Init Flow: 5 Supported Paths

The deploy mode prompt appears in all init flows when a Python project is detected. For non-Python projects, the code deploy option is hidden and the container path is used automatically. Users can re-run azd ai agent init at any time to switch between code and container modes. Template files (Dockerfile, etc.) are never deleted.

1. New Project (Template) -> Container Deploy

azd ai agent init           # empty dir -> template selection
-> Language -> Python
-> Template -> select template
-> Deploy mode -> Container Image (Docker)
-> Model -> Use existing model deployment(s)
-> Subscription -> select
-> Foundry Project -> select
-> App Insights -> optional
-> Model deployment -> select
-> ACR -> select
-> Resources (CPU/Memory) -> select
-> Writes agent.yaml (no code_configuration), azure.yaml (with docker block)

2. New Project (Template) -> Code Deploy

azd ai agent init           # empty dir -> template selection
-> Language -> Python
-> Template -> select template
-> Deploy mode -> Source Code (ZIP upload)
-> Runtime -> Python 3.12
-> Entry point -> main.py
-> Dependency resolution -> Remote build / Bundled
-> Model -> Use existing model deployment(s)
-> Subscription -> select
-> Foundry Project -> select
-> App Insights -> optional
-> Model deployment -> select
-> Resources (CPU/Memory) -> select (for local run compatibility)
-> Writes agent.yaml (with code_configuration), azure.yaml (no docker block)
-> Template Dockerfile preserved (unused)

3. Existing Code -> Container Deploy

azd ai agent init           # non-empty dir with Python code
-> "Use the code in the current directory"
-> Agent name
-> Deploy mode -> Container Image (Docker)
-> Protocols
-> Model -> Use existing model deployment(s)
-> Subscription -> select
-> Foundry Project -> select
-> ACR -> select
-> Writes agent.yaml (no code_configuration), azure.yaml (with docker block)

4. Existing Code -> Code Deploy

azd ai agent init           # non-empty dir with Python code
-> "Use the code in the current directory"
-> Agent name
-> Deploy mode -> Source Code (ZIP upload)
-> Runtime -> Python 3.12
-> Entry point -> main.py
-> Dependency resolution -> Remote build / Bundled
-> Protocols
-> Model -> Use existing model deployment(s)
-> Subscription -> select
-> Foundry Project -> select
-> Writes agent.yaml (with code_configuration), azure.yaml (no docker block)

5. Re-init with Existing Manifest (mode switch)

azd ai agent init           # dir with agent.manifest.yaml
-> "An existing agent manifest was found at agent.manifest.yaml. Use it?" -> Yes
-> Deploy mode -> Source Code (ZIP upload) / Container Image (Docker)
-> Runtime -> Python 3.12 (code deploy only)
-> Entry point -> main.py (code deploy only)
-> Dependency resolution -> Remote build / Bundled (code deploy only)
-> Model -> reuses environment values
-> App Insights -> optional
-> Model deployment -> select
-> Resources (CPU/Memory) -> select

Deploy Path Changes

  • Deploy path (azd deploy): New packageCodeDeploy() creates a ZIP archive of agent source, deployHostedCodeAgent() uploads via multipart form-data POST with SHA-256 verification. Auto-detected from code_configuration in agent.yaml.
  • API integration: Uses Foundry-Features: CodeAgents=V1Preview,HostedAgents=V1Preview header, x-ms-code-zip-sha256 for integrity, dependency_resolution field (string enum).
  • azure.yaml: Code deploy uses language: python (no docker block), but still includes container.resources and startupCommand for azd ai agent run compatibility.
  • ZIP exclusions: Excludes .azure/, .env, .env.*, __pycache__/, .venv/, venv/, .git/, node_modules/, .mypy_cache/, .pytest_cache/, .pyc/.pyo files, symlinks, and agent.yaml itself. Temp files are cleaned up on error via defer os.Remove.

Files Modified

File Change
init_from_code.go Deploy mode prompt via shared promptDeployMode(), promptCodeConfig() (consolidated from two near-duplicate functions), deriveStartupCommand() (shared helper), isPythonProject() (gates code deploy visibility)
init.go Deploy mode prompt in template flow (calls promptDeployMode with isPythonProject check), skip ACR for code deploy, uses shared promptCodeConfig and deriveStartupCommand, fixed addToProject() path resolution for subdirectory re-init
init_foundry_resources_helpers.go configureFoundryProjectEnv / selectFoundryProject accept skipACR param
codes.go Added CodeAgentCreateFailed, CodeMissingCodeZipArtifact error codes
models.go Added CodeConfigurationAPI struct; unified HostedAgentDefinition with optional Image (container) and CodeConfiguration (code) fields + custom MarshalJSON/UnmarshalJSON (container uses container_protocol_versions, code uses protocol_versions)
models_test.go Round-trip tests for unified HostedAgentDefinition JSON marshaling
operations.go Added CreateAgentFromZip, UpdateAgentFromZip, zipDeployRequest
operations_test.go 2 tests for zip deploy request multipart format + headers
map.go CreateHostedAgentAPIRequest uses unified HostedAgentDefinition for both code and container paths
map_test.go Updated type assertions to use unified HostedAgentDefinition and ProtocolVersions field
yaml.go Added CodeConfiguration struct
service_target_agent.go isCodeDeployAgent(), packageCodeDeploy(), deployHostedCodeAgent(); extracted prepareDeploy()/finalizeDeploy() shared helpers to reduce duplication between container and code deploy; errors.AsType per Go 1.26
cspell.yaml Added mypy to words list

How to Build

cd cli/azd/extensions/azure.ai.agents
go build ./...
go vet ./...
azd x build

Manual Test Steps

Prerequisites

  • Azure subscription with a Foundry project
  • A model deployment (e.g. gpt-4o) in your Foundry project
  • azd CLI installed, logged in (azd auth login)
  • Build the extension: azd x build --cwd <repo>/cli/azd/extensions/azure.ai.agents

Test 1: Code Deploy with Remote Build

# 1. Create a fresh test directory
mkdir test-code-deploy
cd test-code-deploy

# 2. Init (interactive)
azd ai agent init
# Prompts (expected order):
#   1. Language -> Python
#   2. Template -> Hello World agent (Invocations, without a framework, Python)
#   3. Deploy mode -> Source Code (ZIP upload)
#   4. Runtime -> Python 3.12
#   5. Entry point -> main.py
#   6. Dependency resolution -> Remote build (server installs dependencies)
#   7. Model -> Use existing model deployment(s) from a Foundry project
#   8. Subscription -> <your subscription>
#   9. Foundry Project -> <your account> / <your project>
#  10. App Insights -> leave blank (press Enter)
#  11. Model deployment -> <select your model>
#  12. Resources (CPU/Memory) -> default (any option)

# 3. Verify config
Get-Content src\hello-world-python-invocations\agent.yaml
# Expect:
#   code_configuration:
#     runtime: python_3_12
#     entry_point: main.py
#     dependency_resolution: remote_build
Get-Content azure.yaml
# Expect: project: src/hello-world-python-invocations, language: python, NO docker block

# 4. Deploy (remote_build -- no pip install needed)
azd deploy hello-world-python-invocations
# Expect: "Packaging code" -> "Creating agent" -> "Agent is active!"

# 5. Verify & Invoke
$TOKEN = (az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
$EP = "<your-endpoint>/api/projects/<your-project>"
$AGENT = "hello-world-python-invocations"

# Check agent status (expect version: 1, status: active)
curl.exe -s "$EP/agents/$AGENT`?api-version=2025-11-15-preview" -H "Authorization: Bearer $TOKEN" | python -m json.tool

# Invoke (wait ~20s after deploy for cold start)
Start-Sleep -Seconds 20
curl.exe -s -N -X POST "$EP/agents/$AGENT/endpoint/protocols/invocations`?api-version=2025-11-15-preview" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Content-Type: application/json" `
  -d '{"message":"hello remote build"}'
# Expect: streaming SSE response with model reply

Test 2: Switch to Bundled (same project, re-init from subdirectory)

# 6. Re-init from the src subdirectory (manifest detection flow)
cd src\hello-world-python-invocations
azd ai agent init
# Expected prompts:
#   1. "An existing agent manifest was found at agent.manifest.yaml. Use it?" -> Yes
#   2. Deploy mode -> Source Code (ZIP upload)
#   3. Runtime -> Python 3.12
#   4. Entry point -> main.py
#   5. Dependency resolution -> Bundled (pre-install dependencies locally)
#   6. Model -> (reuses environment values)
#   7. App Insights -> blank
#   8. Model deployment -> <select your model>
#   9. Resources (CPU/Memory) -> select

# 7. Verify agent.yaml shows bundled
Get-Content agent.yaml | Select-String "dependency_resolution"
# Expect: dependency_resolution: bundled

# 8. Verify azure.yaml project path (bug fix validation)
cd ..\..
Get-Content azure.yaml | Select-String "project:"
# Expect: project: src/hello-world-python-invocations  (NOT "project: .")

# 9. Install dependencies for Linux into source directory
cd src\hello-world-python-invocations
pip install -r requirements.txt `
  -t . `
  --platform manylinux_2_17_x86_64 `
  --platform linux_x86_64 `
  --platform any `
  --python-version 3.12 `
  --implementation cp `
  --only-binary=:all: `
  --upgrade

# 10. Deploy (bundled)
cd ..\..
azd deploy hello-world-python-invocations
# Expect: "Packaging code" -> "Creating agent" -> done (no "Waiting for remote build")

# 11. Verify version incremented & Invoke
$TOKEN = (az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)

# Check version (expect version: 2, dependency_resolution: bundled)
curl.exe -s "$EP/agents/$AGENT`?api-version=2025-11-15-preview" -H "Authorization: Bearer $TOKEN" | python -m json.tool

# Invoke (wait ~30s for new version to activate)
Start-Sleep -Seconds 30
curl.exe -s -N -X POST "$EP/agents/$AGENT/endpoint/protocols/invocations`?api-version=2025-11-15-preview" `
  -H "Authorization: Bearer $TOKEN" `
  -H "Content-Type: application/json" `
  -d '{"message":"hello bundled mode"}'
# Expect: streaming SSE response with model reply

# 12. Cleanup (optional)
curl.exe -s -X DELETE "$EP/agents/$AGENT`?api-version=2025-11-15-preview" -H "Authorization: Bearer $TOKEN"

Test Results

All configurations passed (deploy + invoke verified):

# Protocol Package Mode Deploy Invoke
1 invocations remote_build PASS PASS (SSE)
2 invocations bundled PASS PASS (SSE)
3 responses remote_build PASS PASS (JSON)
4 responses bundled PASS PASS (JSON)

Notes

  • Auto-detection: azd deploy reads agent.yaml -- if code_configuration is present, code deploy; otherwise container deploy. No flags needed.
  • No impact on container deploy: All container deploy paths are unchanged. The skipACR parameter defaults to false at all existing call sites.
  • Mode switching: Re-run azd ai agent init to switch modes. Template files (Dockerfile, .dockerignore) are never deleted.
  • Python only (for now): Code deploy option is only shown when a Python project is detected (presence of requirements.txt or .py files). Non-Python projects default to container deploy. .NET code deploy support will come in a follow-up PR.
  • --no-prompt defaults: Deploy mode -> container (backward compatible). Runtime -> python_3_12. Entry point -> main.py (or app.py if it exists in the source directory). Dependency resolution -> remote_build.
  • Known issue (pre-existing): The postdeploy hook looks up agent by azure.yaml service name instead of agent.yaml name, causing a 404 after successful deploy. Not related to this PR.

Related

Fixes #7430

Jian Wu added 2 commits May 11, 2026 16:17
Implements code-based deployment as a complementary mode to container deploy.
Agents with code_configuration in agent.yaml are deployed via multipart
ZIP upload instead of Docker/ACR, eliminating permission complexity.
Add deploy mode prompt (code vs container) to the init flow. When code deploy is selected, prompts for runtime, entry_point, and dependency_resolution, then generates agent.yaml with code_configuration and azure.yaml with language: python (no Docker).
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds code-based deployment support for the azure.ai.agents extension by packaging agent source code into a ZIP and deploying hosted agents via a multipart ZIP upload flow (as an alternative to container/image-based deployment).

Changes:

  • Added code_configuration to agent.yaml parsing and mapped it to a new code-based hosted agent API request definition.
  • Implemented ZIP packaging in the service target and a deploy path that uploads ZIP + metadata, with optional remote-build polling.
  • Updated azd ai agent init flow to let users choose code deploy vs container deploy and to generate appropriate project/service settings.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go Adds code-deploy packaging (ZIP + SHA-256) and a deploy flow that uploads ZIP artifacts and polls remote builds.
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml.go Introduces CodeConfiguration and wires it into the hosted agent definition model.
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/map.go Maps code_configuration to a code-based hosted agent API definition (and preserves existing container/image mapping).
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/operations.go Adds multipart ZIP create/update operations for agents.
cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/models.go Adds API models for code-based agent definitions and code configuration.
cli/azd/extensions/azure.ai.agents/internal/exterrors/codes.go Adds a new internal error code for agent create/update failures.
cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Adds interactive deploy-mode selection and prompts for code deploy configuration; adjusts azure.yaml service creation accordingly.

Comment thread cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go Outdated
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_from_code.go Outdated
Jian Wu added 5 commits May 12, 2026 12:05
…aults

- Fix gosec G304 warnings with nolint annotations for safe file reads
- Fix gosec G104 by properly handling tmpFile.Close() errors
- Fix gofmt formatting in excludeDirs map
- Update runtime choices to python_3_12/3_11/3_13 per spec
- Change --no-prompt deploy mode default to container (backward compat)
- Change --no-prompt runtime default to python_3_12
- Align prompt labels with spec wording
- Add container.resources + startupCommand to azure.yaml for code deploy
- Add .azure and .env/.env.* to ZIP exclusion list to prevent uploading secrets
- Skip symlinks in WalkDir to avoid including files outside agent directory
- Stream ZIP directly to temp file with io.MultiWriter for SHA-256, reducing memory usage
- Clean up temp file on all error paths using deferred cleanup
- Only fall back to create agent on 404; propagate auth/5xx/network errors
- Use context-aware select/time.After in polling loop for responsive cancellation
- Add dedicated CodeMissingCodeZipArtifact error code
- Fix entry point auto-detection to use srcDir instead of cwd
- Replace hardcoded multipart boundary with mime/multipart.Writer
- Add unit tests for zipDeployRequest multipart format and headers
…deploy and set metadata Content-Type

- Add PatchAgent call after code deploy create/update to apply agent_endpoint and agent_card fields (matching container deploy behavior)
- Use CreatePart with explicit Content-Type: application/json for the metadata multipart part instead of CreateFormField
- Update unit test to verify metadata part Content-Type header
- Add //nolint:gosec to os.ReadFile in init_from_code.go
- Handle req.Body.Close() return value in test transport
- Suppress Close/Remove errors in deferred cleanup with _ =
- Add 'mypy' to cspell.yaml words list
@v1212 v1212 marked this pull request as ready for review May 12, 2026 05:12
Jian Wu added 4 commits May 12, 2026 14:59
Add deploy mode prompt (code vs container) to the template init flow,
allowing users to choose code deploy when initializing from a template.
Skip ACR configuration when code deploy is selected. Auto-derive
startup command from entry_point for code deploy instead of prompting.
…switch

- Default to Container (Docker) for backward compatibility
- Use clearer labels: 'Container (Docker)' / 'Code deploy (ZIP upload)'
- Remove code_configuration from agent.yaml when switching to container mode
When skipACR is true (code deploy mode), skip the configureAcrConnection
call entirely to prevent prompting users for ACR configuration.
When running 'azd ai agent init' from a subdirectory with an existing
agent.manifest.yaml, the addToProject function received targetDir='.'
which wrote 'project: .' into azure.yaml. Since azure.yaml resolves
paths relative to the project root, this caused the service to point
to the wrong directory.

Fix: resolve the actual relative path from project root to cwd when
targetDir is '.', so azure.yaml gets the correct project path (e.g.
'src/hello-world-python-invocations' instead of '.').
Copy link
Copy Markdown
Contributor

@therealjohn therealjohn left a comment

Choose a reason for hiding this comment

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

@v1212 is there anything we can do to check for supported regions if the code deploy option is selected?

What happened before was folks would choose a region supported by hosted agents, but then these features would fail because they were only available in a further subset of regions.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go
Comment thread cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/models.go Outdated
@therealjohn
Copy link
Copy Markdown
Contributor

@v1212 When I selected C#, it still gives me the options for Code Deploy and then shows Python runtimes:
image

Copy link
Copy Markdown
Member

@jongio jongio left a comment

Choose a reason for hiding this comment

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

Looked through the ZIP upload code deploy path. @therealjohn's UX feedback and the Copilot comments cover the major items well. A few structural things I noticed on top of those:

  1. Duplicated prompt logic - promptCodeConfiguration (method on InitFromCodeAction, line 915) and promptCodeConfigurationShared (standalone func, line 1162) in init_from_code.go are nearly identical. Same runtime/entry-point/dependency prompts with minor differences in
    oPrompt handling. Should be one function.

  2. Repeated agent.yaml I/O - isContainerAgent() and isCodeDeployAgent() each read and unmarshal �gent.yaml from disk independently. Both get called from Package(), Publish(), and Deploy(), so the same file is parsed 3+ times per deploy cycle.

  3. Go 1.26 convention - �rrors.As at line 1031 of service_target_agent.go should use �rrors.AsType per AGENTS.md.

  4. Startup command derivation duplicated between init.go (lines 1604-1621) and init_from_code.go's �ddToProject.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go Outdated
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_from_code.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_api/models.go Outdated
Comment thread cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go Outdated
Jian Wu added 5 commits May 13, 2026 11:25
- T1: Hide code deploy for non-Python projects (isPythonProject check)
- T2: Add TODO for region validation in code deploy
- T3: Reorder runtime list to 3.11, 3.12, 3.13
- T4: Update entry point prompt wording
- T6: Add descriptions to bundled/remote_build choices
- J1: Consolidate promptCodeConfig into single shared function
- J3: Use errors.AsType[*azcore.ResponseError] per Go 1.26
- J4: Extract shared deriveStartupCommand helper
- V1: Rename to 'Container Image (Docker)' / 'Source Code (ZIP upload)'
- V2: Unify HostedAgentDefinition with custom JSON marshal/unmarshal
- V4: Fix error message to reference 'azd package'
- V5: Extract prepareDeploy/finalizeDeploy shared helpers
- C1+C3: Verify ZIP exclusions (.azure, .env) and temp file cleanup
@v1212
Copy link
Copy Markdown
Collaborator Author

v1212 commented May 13, 2026

@v1212 is there anything we can do to check for supported regions if the code deploy option is selected?

What happened before was folks would choose a region supported by hosted agents, but then these features would fail because they were only available in a further subset of regions.

so far, there is no APIs for listing available regions for code deploy feature, service team confirmed the feature will be rolled out to all regions, so in short term, we confirmed with Santhosh and ADC team to apply a limited regions in this PR - Westus2, Canada central and Northcentralus

@v1212
Copy link
Copy Markdown
Collaborator Author

v1212 commented May 13, 2026

@v1212 When I selected C#, it still gives me the options for Code Deploy and then shows Python runtimes: image

dotnet support will be implemented in a separate PR soon, here non-python will only go to container option in this PR.

Jian Wu added 2 commits May 13, 2026 14:08
… default

Add region validation for code deploy at both init-time (filter project
list) and deploy-time (fail early with clear error). Supported regions:
westus2, canadacentral, northcentralus.

Unify dependency_resolution fallback default to 'remote_build' to match
--no-prompt behavior.
…ATION

Move CodeDeployRegions to project.config.go as a shared exported var,
referenced by both init filtering and deploy validation. Add explicit
check for empty AZURE_LOCATION with actionable error message.
…rompt

- Include service error code/message and x-request-id in remote build failure errors
- Rename 'container resource allocation' prompt to 'Select resources (CPU and Memory)'
@trangevi
Copy link
Copy Markdown
Member

/check-enforcer override

@trangevi trangevi enabled auto-merge (squash) May 13, 2026 15:29
@trangevi trangevi merged commit df9cadb into Azure:main May 13, 2026
21 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow developers to deploy hosted agents from source code without a Dockerfile

5 participants