Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fddc3c4
feat(agents): add code deploy (ZIP upload) support for hosted agents
May 11, 2026
346b972
feat(agents): add code deploy mode to 'azd ai agent init --from-code'
May 11, 2026
5b61016
fix(agents): address CI lint errors and align with spec #164 defaults
May 12, 2026
1e5650b
fix(agents): address Copilot review feedback on code deploy
May 12, 2026
026b598
fix(agents): apply agent_endpoint/agent_card via PatchAgent for code …
May 12, 2026
26c8087
fix(agents): resolve CI lint and cspell errors
May 12, 2026
510ed08
style: use map[string]any instead of map[string]interface{}
May 12, 2026
905d3ac
feat(agents): add code deploy support to template init flow
May 12, 2026
a5463b4
fix(agents): improve deploy mode prompt and clean agent.yaml on mode …
May 12, 2026
550b844
fix(agents): guard configureAcrConnection with skipACR check
May 12, 2026
cb8833c
fix(agents): resolve project path when init runs from subdirectory
May 12, 2026
09a94dc
fix(agents): address PR #8146 review feedback (13 items)
May 13, 2026
10d08a8
fix(agents): add TODO for streaming ZIP in zipDeployRequest (C2)
May 13, 2026
d67863b
merge: resolve conflicts with main (prebuilt image support #8104)
May 13, 2026
7ee295e
fix: remove unused isContainerAgent method and accidental files
May 13, 2026
bae1bd6
chore: remove accidentally committed files
May 13, 2026
50577dc
fix(agents): restrict code deploy to supported regions and fix depRes…
May 13, 2026
437bcb1
fix(agents): deduplicate codeDeployRegions and handle empty AZURE_LOC…
May 13, 2026
04b59be
fix(agents): improve deploy failure diagnostics and rename resource p…
May 13, 2026
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
1 change: 1 addition & 0 deletions cli/azd/extensions/azure.ai.agents/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ words:
- mcpservertoolalwaysrequireapprovalmode
- mcpservertoolneverrequireapprovalmode
- mcpservertoolspecifyapprovalmode
- mypy
- myregistry
- normalises
- openapitool
Expand Down
75 changes: 68 additions & 7 deletions cli/azd/extensions/azure.ai.agents/internal/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type InitAction struct {

deploymentDetails []project.Deployment
containerSettings *project.ContainerSettings
isCodeDeploy bool // true when user selects code deploy mode; skips ACR config
httpClient *http.Client
serviceNameOverride string // when set, addToProject uses this instead of the manifest name
}
Expand Down Expand Up @@ -570,6 +571,35 @@ func (a *InitAction) Run(ctx context.Context) error {
return fmt.Errorf("downloading agent.yaml: %w", err)
}

// Prompt for deploy mode (code vs container) for hosted agents
if _, ok := agentManifest.Template.(agent_yaml.ContainerAgent); ok {
deployMode, err := promptDeployMode(ctx, a.azdClient, a.flags.noPrompt)
if err != nil {
return fmt.Errorf("prompting for deploy mode: %w", err)
}
a.isCodeDeploy = (deployMode == "code")

if a.isCodeDeploy {
// Prompt for code configuration and update the manifest
codeConfig, err := promptCodeConfigurationShared(ctx, a.azdClient, targetDir)
if err != nil {
return fmt.Errorf("prompting for code configuration: %w", err)
}

hostedAgent := agentManifest.Template.(agent_yaml.ContainerAgent)
hostedAgent.CodeConfiguration = codeConfig
agentManifest.Template = hostedAgent
} else {
// Container mode: ensure any pre-existing code_configuration is removed
// (e.g. when switching from code deploy back to container)
hostedAgent := agentManifest.Template.(agent_yaml.ContainerAgent)
if hostedAgent.CodeConfiguration != nil {
hostedAgent.CodeConfiguration = nil
agentManifest.Template = hostedAgent
}
}
}

// Model configuration: prompt user for "use existing" vs "deploy new"
agentManifest, err = a.configureModelChoice(ctx, agentManifest)
if err != nil {
Expand Down Expand Up @@ -792,6 +822,7 @@ func (a *InitAction) configureModelChoice(
selectedProject, err := selectFoundryProject(
ctx, a.azdClient, a.credential, a.azureContext, a.environment.Name,
a.azureContext.Scope.SubscriptionId, a.flags.projectResourceId,
a.isCodeDeploy,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -840,6 +871,7 @@ func (a *InitAction) configureModelChoice(
selectedProject, err := selectFoundryProject(
ctx, a.azdClient, a.credential, a.azureContext, a.environment.Name,
a.azureContext.Scope.SubscriptionId, "",
a.isCodeDeploy,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -932,6 +964,7 @@ func (a *InitAction) configureModelChoice(
selectedProject, err := selectFoundryProject(
ctx, a.azdClient, a.credential, a.azureContext, a.environment.Name,
a.azureContext.Scope.SubscriptionId, a.flags.projectResourceId,
a.isCodeDeploy,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1453,6 +1486,16 @@ func writeAgentDefinitionFile(targetDir string, agentManifest *agent_yaml.AgentM
}

func (a *InitAction) addToProject(ctx context.Context, targetDir string, agentManifest *agent_yaml.AgentManifest) error {
// If targetDir is ".", resolve the actual relative path from the project root to cwd.
// This ensures azure.yaml gets the correct "project:" value when init is run from a subdirectory.
if targetDir == "." {
if cwd, err := os.Getwd(); err == nil && a.projectConfig != nil && a.projectConfig.Path != "" {
if relPath, err := filepath.Rel(a.projectConfig.Path, cwd); err == nil && relPath != "." {
targetDir = filepath.ToSlash(relPath)
}
}
}

// Convert the template to bytes
templateBytes, err := json.Marshal(agentManifest.Template)
if err != nil {
Expand Down Expand Up @@ -1558,11 +1601,25 @@ func (a *InitAction) addToProject(ctx context.Context, targetDir string, agentMa
}

// Detect startup command from the project source directory
startupCmd, err := resolveStartupCommandForInit(ctx, a.azdClient, a.projectConfig.Path, targetDir, a.flags.noPrompt)
if err != nil {
return err
if a.isCodeDeploy {
Comment thread
v1212 marked this conversation as resolved.
// For code deploy, auto-derive startupCommand from entry point in agent.yaml
agentYamlPath := filepath.Join(a.projectConfig.Path, targetDir, "agent.yaml")
if data, readErr := os.ReadFile(agentYamlPath); readErr == nil { //nolint:gosec // path is constructed from project config
var containerAgent agent_yaml.ContainerAgent
if yamlErr := yaml.Unmarshal(data, &containerAgent); yamlErr == nil && containerAgent.CodeConfiguration != nil {
agentConfig.StartupCommand = "python " + containerAgent.CodeConfiguration.EntryPoint
}
}
if agentConfig.StartupCommand == "" {
agentConfig.StartupCommand = "python main.py"
}
} else {
startupCmd, err := resolveStartupCommandForInit(ctx, a.azdClient, a.projectConfig.Path, targetDir, a.flags.noPrompt)
if err != nil {
return err
}
agentConfig.StartupCommand = startupCmd
}
agentConfig.StartupCommand = startupCmd

var agentConfigStruct *structpb.Struct
if agentConfigStruct, err = project.MarshalStruct(&agentConfig); err != nil {
Expand All @@ -1577,10 +1634,14 @@ func (a *InitAction) addToProject(ctx context.Context, targetDir string, agentMa
Config: agentConfigStruct,
}

// For hosted (container-based) agents, set remoteBuild to true by default
// For hosted agents, configure Docker or code deploy settings
if agentDef.Kind == agent_yaml.AgentKindHosted {
serviceConfig.Docker = &azdext.DockerProjectOptions{
RemoteBuild: true,
if a.isCodeDeploy {
serviceConfig.Language = "python"
} else {
serviceConfig.Docker = &azdext.DockerProjectOptions{
RemoteBuild: true,
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,15 @@ func lookupAcrResourceId(

// configureFoundryProjectEnv sets all Foundry project environment variables and discovers
// ACR and AppInsights connections. This is the shared implementation used by both init flows.
// When skipACR is true, ACR connection discovery and configuration is skipped (used for code deploy).
func configureFoundryProjectEnv(
ctx context.Context,
azdClient *azdext.AzdClient,
credential azcore.TokenCredential,
envName string,
project FoundryProjectInfo,
subscriptionId string,
skipACR bool,
) error {
resourceId := project.ResourceId
if resourceId == "" {
Expand Down Expand Up @@ -365,7 +367,9 @@ func configureFoundryProjectEnv(
for _, conn := range connections {
switch conn.Type {
case azure.ConnectionTypeContainerRegistry:
acrConnections = append(acrConnections, conn)
if !skipACR {
acrConnections = append(acrConnections, conn)
}
case azure.ConnectionTypeAppInsights:
connWithCreds, err := foundryClient.GetConnectionWithCredentials(ctx, conn.Name)
if err != nil {
Expand All @@ -379,8 +383,10 @@ func configureFoundryProjectEnv(
}
}

if err := configureAcrConnection(ctx, azdClient, credential, envName, subscriptionId, acrConnections); err != nil {
return err
if !skipACR {
if err := configureAcrConnection(ctx, azdClient, credential, envName, subscriptionId, acrConnections); err != nil {
return err
}
}

if err := configureAppInsightsConnection(ctx, azdClient, envName, appInsightsConnections); err != nil {
Expand Down Expand Up @@ -999,6 +1005,7 @@ func selectFoundryProject(
envName string,
subscriptionId string,
projectResourceId string,
skipACR bool,
) (*FoundryProjectInfo, error) {
spinnerText := "Searching for Foundry projects in your subscription..."
if projectResourceId != "" {
Expand Down Expand Up @@ -1095,7 +1102,7 @@ func selectFoundryProject(
}

// Configure all Foundry project environment variables
if err := configureFoundryProjectEnv(ctx, azdClient, credential, envName, selectedProject, subscriptionId); err != nil {
if err := configureFoundryProjectEnv(ctx, azdClient, credential, envName, selectedProject, subscriptionId, skipACR); err != nil {
return nil, fmt.Errorf("failed to configure Foundry project environment: %w", err)
}

Expand Down
Loading
Loading