From 30d498e1b4692ebc1f7eac4fd11b356436a14339 Mon Sep 17 00:00:00 2001 From: Jian Wu Date: Thu, 14 May 2026 16:34:04 +0800 Subject: [PATCH 1/2] feat(agents): add active-state polling for container and code deploy paths Replace inline polling in code deploy with shared waitForAgentActive() function. Add polling to container deploy path. Uses 10s interval, 5min timeout, and requires 2 consecutive confirmations to avoid transient flickers. --- .../internal/project/service_target_agent.go | 149 ++++++++++++------ 1 file changed, 102 insertions(+), 47 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go index d4be6e75d8d..6621049eb34 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go +++ b/cli/azd/extensions/azure.ai.agents/internal/project/service_target_agent.go @@ -941,6 +941,21 @@ func (p *AgentServiceTargetProvider) deployHostedAgent( return nil, err } + // Poll until agent version is active + if agentVersionResponse.Status != "active" { + agentClient := agent_api.NewAgentClient( + azdEnv["AZURE_AI_PROJECT_ENDPOINT"], + p.credential, + ) + polledVersion, pollErr := p.waitForAgentActive(ctx, agentClient, prep.request.Name, agentVersionResponse.Version, progress) + if pollErr != nil { + return nil, pollErr + } + agentVersionResponse = polledVersion + } else { + fmt.Fprintf(os.Stderr, "Agent version %s is already active.\n", agentVersionResponse.Version) + } + return p.finalizeDeploy(ctx, progress, serviceConfig, azdEnv, agentVersionResponse, prep.protocols) } @@ -1351,55 +1366,16 @@ func (p *AgentServiceTargetProvider) deployHostedCodeAgent( } } - // Poll for status if remote build + // Poll for agent version to become active latestVersion := &agentResp.Versions.Latest - depRes := "remote_build" - if agentDef.CodeConfiguration != nil && agentDef.CodeConfiguration.DependencyResolution != nil { - depRes = *agentDef.CodeConfiguration.DependencyResolution - } - if depRes == "remote_build" && latestVersion.Status == "creating" { - fmt.Fprintf(os.Stderr, "Waiting for remote build to complete...\n") - pollTimeout := 5 * time.Minute - pollInterval := 5 * time.Second - deadline := time.Now().Add(pollTimeout) - - for time.Now().Before(deadline) { - select { - case <-ctx.Done(): - return nil, fmt.Errorf("deployment cancelled: %w", ctx.Err()) - case <-time.After(pollInterval): - } - versionResp, err := agentClient.GetAgentVersion(ctx, agentDef.Name, latestVersion.Version, agentAPIVersion) - if err != nil { - fmt.Fprintf(os.Stderr, "Warning: poll failed: %s\n", err) - continue - } - latestVersion = versionResp - if versionResp.Status == "active" { - fmt.Fprintf(os.Stderr, "Agent is active!\n") - break - } else if versionResp.Status == "failed" { - errMsg := "agent deployment failed during remote build; check agent logs or try local packaging (dependency_resolution: bundled)" - if versionResp.Error != nil { - errMsg = fmt.Sprintf("agent deployment failed: [%s] %s", versionResp.Error.Code, versionResp.Error.Message) - } - if versionResp.RequestID != "" { - errMsg += fmt.Sprintf(" (request-id: %s)", versionResp.RequestID) - } - return nil, exterrors.Internal( - exterrors.CodeAgentCreateFailed, - errMsg, - ) - } - fmt.Fprintf(os.Stderr, " Status: %s...\n", versionResp.Status) - } - - if latestVersion.Status != "active" { - return nil, exterrors.Internal( - exterrors.CodeAgentCreateFailed, - "agent deployment timed out waiting for remote build; check agent status manually or try local packaging", - ) + if latestVersion.Status != "active" { + polledVersion, err := p.waitForAgentActive(ctx, agentClient, agentDef.Name, latestVersion.Version, progress) + if err != nil { + return nil, err } + latestVersion = polledVersion + } else { + fmt.Fprintf(os.Stderr, "Agent version %s is already active.\n", latestVersion.Version) } // Patch agent-level fields (agent_endpoint, agent_card) if present. @@ -1558,6 +1534,85 @@ func AgentPlaygroundURL(projectResourceID, agentName, agentVersion string) (stri return url, nil } +// waitForAgentActive polls the agent version until it reaches a confirmed terminal state. +// It requires 2 consecutive polls with the same terminal status ("active" or "failed") to confirm, +// avoiding transient service-side flickers. Returns the final AgentVersionObject or an error. +func (p *AgentServiceTargetProvider) waitForAgentActive( + ctx context.Context, + agentClient *agent_api.AgentClient, + agentName string, + version string, + progress azdext.ProgressReporter, +) (*agent_api.AgentVersionObject, error) { + const pollInterval = 10 * time.Second + const pollTimeout = 5 * time.Minute + const confirmCount = 2 // consecutive times a terminal status must be seen + + deadline := time.Now().Add(pollTimeout) + progress("Waiting for agent to become active") + + var consecutiveActive int + var consecutiveFailed int + var lastVersion *agent_api.AgentVersionObject + + for time.Now().Before(deadline) { + select { + case <-ctx.Done(): + return nil, fmt.Errorf("deployment cancelled: %w", ctx.Err()) + case <-time.After(pollInterval): + } + + versionResp, err := agentClient.GetAgentVersion(ctx, agentName, version, agentAPIVersion) + if err != nil { + fmt.Fprintf(os.Stderr, " Warning: poll failed: %s\n", err) + // Reset counters on error — don't count transient failures + consecutiveActive = 0 + consecutiveFailed = 0 + continue + } + lastVersion = versionResp + + switch versionResp.Status { + case "active": + consecutiveActive++ + consecutiveFailed = 0 + if consecutiveActive >= confirmCount { + fmt.Fprintf(os.Stderr, "Agent version %s is active!\n", version) + return versionResp, nil + } + fmt.Fprintf(os.Stderr, " Status: active (confirming...)\n") + case "failed": + consecutiveFailed++ + consecutiveActive = 0 + if consecutiveFailed >= confirmCount { + errMsg := "agent deployment failed" + if versionResp.Error != nil { + errMsg = fmt.Sprintf("agent deployment failed: [%s] %s", versionResp.Error.Code, versionResp.Error.Message) + } + if versionResp.RequestID != "" { + errMsg += fmt.Sprintf(" (request-id: %s)", versionResp.RequestID) + } + return nil, exterrors.Internal(exterrors.CodeAgentCreateFailed, errMsg) + } + fmt.Fprintf(os.Stderr, " Status: failed (confirming...)\n") + default: + consecutiveActive = 0 + consecutiveFailed = 0 + fmt.Fprintf(os.Stderr, " Status: %s...\n", versionResp.Status) + } + } + + // Timeout + lastStatus := "unknown" + if lastVersion != nil { + lastStatus = lastVersion.Status + } + return nil, exterrors.Internal( + exterrors.CodeAgentCreateFailed, + fmt.Sprintf("agent deployment timed out (last status: %s); check agent status manually", lastStatus), + ) +} + // createAgent creates a new version of the agent using the API func (p *AgentServiceTargetProvider) createAgent( ctx context.Context, From e574f4b9b8a9cdcbdad1946ba312f7102a0ef0cb Mon Sep 17 00:00:00 2001 From: Jian Wu Date: Fri, 15 May 2026 14:48:26 +0800 Subject: [PATCH 2/2] feat: add Python 3.14 runtime option for code deploy --- .../extensions/azure.ai.agents/internal/cmd/init_from_code.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go index 260467dd124..b7146fcc205 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code.go @@ -1150,6 +1150,7 @@ func promptCodeConfig(ctx context.Context, azdClient *azdext.AzdClient, srcDir s {Label: "Python 3.11", Value: "python_3_11"}, {Label: "Python 3.12", Value: "python_3_12"}, {Label: "Python 3.13", Value: "python_3_13"}, + {Label: "Python 3.14", Value: "python_3_14"}, } } else { // Mixed or unknown — show all options @@ -1157,6 +1158,7 @@ func promptCodeConfig(ctx context.Context, azdClient *azdext.AzdClient, srcDir s {Label: "Python 3.11", Value: "python_3_11"}, {Label: "Python 3.12", Value: "python_3_12"}, {Label: "Python 3.13", Value: "python_3_13"}, + {Label: "Python 3.14", Value: "python_3_14"}, {Label: ".NET 9", Value: "dotnet_9"}, {Label: ".NET 8", Value: "dotnet_8"}, {Label: ".NET 10", Value: "dotnet_10"},