diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go index 3b8b3132ba5..b48a4b16cc4 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go @@ -89,6 +89,10 @@ func runRun(ctx context.Context, flags *runFlags) error { } projectDir := runCtx.ProjectDir + // Detect project type early — used for both start-command resolution and + // environment setup (e.g., setting ASPNETCORE_URLS for .NET). + pt := detectProjectType(projectDir) + // Resolve start command: --start-command flag > azure.yaml startupCommand > detect startCmd := flags.startCommand if startCmd == "" { @@ -96,7 +100,6 @@ func runRun(ctx context.Context, flags *runFlags) error { } if startCmd == "" { - pt := detectProjectType(projectDir) if pt.StartCmd != "" { startCmd = pt.StartCmd fmt.Printf("Detected %s project. Start command: %s\n", pt.Language, startCmd) @@ -135,7 +138,7 @@ func runRun(ctx context.Context, flags *runFlags) error { cmdParts = resolveVenvCommand(projectDir, cmdParts) env := os.Environ() - env = append(env, fmt.Sprintf("PORT=%d", flags.port)) + env = appendPortEnvVars(env, pt, flags.port) // Load azd environment variables (e.g., AZURE_AI_PROJECT_ENDPOINT) // so the agent can reach Azure services during local development. @@ -200,6 +203,17 @@ func runRun(ctx context.Context, flags *runFlags) error { return nil } +// appendPortEnvVars appends PORT and, for .NET projects, ASPNETCORE_URLS to the +// environment slice so the agent listens on the correct port. +// ASP.NET Core ignores PORT — it uses ASPNETCORE_URLS to configure Kestrel. +func appendPortEnvVars(env []string, pt ProjectType, port int) []string { + env = append(env, fmt.Sprintf("PORT=%d", port)) + if pt.Language == "dotnet" { + env = append(env, fmt.Sprintf("ASPNETCORE_URLS=http://localhost:%d", port)) + } + return env +} + // --- Dependency installation --- func installDependencies(projectDir string) error { diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/run_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/run_test.go index afc7d0dbc96..bca96f30fb3 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/run_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/run_test.go @@ -336,3 +336,78 @@ func TestAppendFoundryEnvVars(t *testing.T) { } }) } + +func TestAppendPortEnvVars(t *testing.T) { + t.Parallel() + + t.Run("dotnet project includes ASPNETCORE_URLS", func(t *testing.T) { + t.Parallel() + pt := ProjectType{Language: "dotnet", StartCmd: "dotnet run"} + env := appendPortEnvVars(nil, pt, 8088) + + if !slices.Contains(env, "PORT=8088") { + t.Errorf("expected PORT=8088 in env, got %v", env) + } + if !slices.Contains(env, "ASPNETCORE_URLS=http://localhost:8088") { + t.Errorf("expected ASPNETCORE_URLS=http://localhost:8088 in env, got %v", env) + } + }) + + t.Run("python project does not include ASPNETCORE_URLS", func(t *testing.T) { + t.Parallel() + pt := ProjectType{Language: "python", StartCmd: "python main.py"} + env := appendPortEnvVars(nil, pt, 8088) + + if !slices.Contains(env, "PORT=8088") { + t.Errorf("expected PORT=8088 in env, got %v", env) + } + for _, entry := range env { + if strings.HasPrefix(entry, "ASPNETCORE_URLS=") { + t.Errorf("ASPNETCORE_URLS should not be set for python, got %v", env) + } + } + }) + + t.Run("node project does not include ASPNETCORE_URLS", func(t *testing.T) { + t.Parallel() + pt := ProjectType{Language: "node", StartCmd: "npm start"} + env := appendPortEnvVars(nil, pt, 9090) + + if !slices.Contains(env, "PORT=9090") { + t.Errorf("expected PORT=9090 in env, got %v", env) + } + for _, entry := range env { + if strings.HasPrefix(entry, "ASPNETCORE_URLS=") { + t.Errorf("ASPNETCORE_URLS should not be set for node, got %v", env) + } + } + }) + + t.Run("dotnet project respects custom port", func(t *testing.T) { + t.Parallel() + pt := ProjectType{Language: "dotnet", StartCmd: "dotnet run"} + env := appendPortEnvVars(nil, pt, 3000) + + if !slices.Contains(env, "PORT=3000") { + t.Errorf("expected PORT=3000 in env, got %v", env) + } + expected := "ASPNETCORE_URLS=http://localhost:3000" + if !slices.Contains(env, expected) { + t.Errorf("expected %q in env, got %v", expected, env) + } + }) + + t.Run("preserves existing env entries", func(t *testing.T) { + t.Parallel() + pt := ProjectType{Language: "dotnet", StartCmd: "dotnet run"} + existing := []string{"HOME=/home/user", "PATH=/usr/bin"} + env := appendPortEnvVars(existing, pt, 8088) + + if len(env) != 4 { + t.Errorf("expected 4 entries (2 existing + PORT + ASPNETCORE_URLS), got %d: %v", len(env), env) + } + if env[0] != "HOME=/home/user" || env[1] != "PATH=/usr/bin" { + t.Errorf("existing entries not preserved, got %v", env) + } + }) +}