From 9b7fdf9328176cb475043d6e5dd8a4e12f3cf430 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:45:57 +0000 Subject: [PATCH 1/4] Initial plan From 2b8196a93253f9619a58a6dcbe0a5fd46dc194ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:55:48 +0000 Subject: [PATCH 2/4] Clean up env vars in agent.yaml init and add FOUNDRY_* mapping for local run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove AZURE_OPENAI_ENDPOINT and AZURE_AI_PROJECT_ENDPOINT from environment variables automatically added to agent.yaml during init. These are now auto-injected as FOUNDRY_* env vars by the hosted agent platform. - Add appendFoundryEnvVars() to translate azd env keys to FOUNDRY_* env vars in `azd ai agent run` so agent code works identically locally and in hosted containers. - Add ServiceName to ServiceRunContext to support service-specific env var mapping (AGENT_{SVC}_NAME → FOUNDRY_AGENT_NAME, etc.) - Add appendEnvVar() helper for nil-safe env var slice appending. - Update tests to reflect the new behavior. Agent-Logs-Url: https://github.com/Azure/azure-dev/sessions/aed5afe4-cc68-4bbe-9d1e-baffe28638a1 Co-authored-by: JeffreyCA <9157833+JeffreyCA@users.noreply.github.com> --- .../azure.ai.agents/internal/cmd/helpers.go | 2 + .../internal/cmd/init_from_code.go | 31 +++++---- .../internal/cmd/init_from_code_test.go | 9 ++- .../azure.ai.agents/internal/cmd/run.go | 55 +++++++++++++++- .../azure.ai.agents/internal/cmd/run_test.go | 66 +++++++++++++++++++ 5 files changed, 147 insertions(+), 16 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go index 0b94f160f3e..25c032f5251 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/helpers.go @@ -533,6 +533,7 @@ func resolveAgentServiceFromProject(ctx context.Context, azdClient *azdext.AzdCl // ServiceRunContext holds the resolved context needed for local development. type ServiceRunContext struct { + ServiceName string // the resolved service name (from azure.yaml) ProjectDir string // absolute path to the service source directory StartupCommand string // startupCommand from AdditionalProperties (may be empty) } @@ -556,6 +557,7 @@ func resolveServiceRunContext(ctx context.Context, azdClient *azdext.AzdClient, } return &ServiceRunContext{ + ServiceName: svc.Name, ProjectDir: projectDir, StartupCommand: startupCmd, }, nil 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 166f02b7601..5df34f85af6 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 @@ -578,22 +578,16 @@ func (a *InitFromCodeAction) createDefinitionFromLocalAgent(ctx context.Context) } // Create a minimal Agent Definition + // Note: FOUNDRY_PROJECT_ENDPOINT and other FOUNDRY_* env vars are automatically + // injected into hosted agent containers by the platform, so we don't need to + // add them to agent.yaml. For local development, `azd ai agent run` translates + // azd environment values to FOUNDRY_* env vars. definition := &agent_yaml.ContainerAgent{ AgentDefinition: agent_yaml.AgentDefinition{ Name: agentName, Kind: agentKind, }, Protocols: protocols, - EnvironmentVariables: &[]agent_yaml.EnvironmentVariable{ - { - Name: "AZURE_OPENAI_ENDPOINT", - Value: "${AZURE_OPENAI_ENDPOINT}", - }, - { - Name: "AZURE_AI_PROJECT_ENDPOINT", - Value: "${AZURE_AI_PROJECT_ENDPOINT}", - }, - }, } // Add model resource if a model was selected @@ -612,7 +606,7 @@ func (a *InitFromCodeAction) createDefinitionFromLocalAgent(ctx context.Context) }, }) - *definition.EnvironmentVariables = append(*definition.EnvironmentVariables, agent_yaml.EnvironmentVariable{ + definition.EnvironmentVariables = appendEnvVar(definition.EnvironmentVariables, agent_yaml.EnvironmentVariable{ Name: "AZURE_AI_MODEL_DEPLOYMENT_NAME", Value: "${AZURE_AI_MODEL_DEPLOYMENT_NAME}", }) @@ -639,7 +633,7 @@ func (a *InitFromCodeAction) createDefinitionFromLocalAgent(ctx context.Context) }, }) - *definition.EnvironmentVariables = append(*definition.EnvironmentVariables, agent_yaml.EnvironmentVariable{ + definition.EnvironmentVariables = appendEnvVar(definition.EnvironmentVariables, agent_yaml.EnvironmentVariable{ Name: "AZURE_AI_MODEL_DEPLOYMENT_NAME", Value: "${AZURE_AI_MODEL_DEPLOYMENT_NAME}", }) @@ -677,6 +671,19 @@ func (a *InitFromCodeAction) resolveSelectedModelDeployment( return selector.getModelDetails(ctx, model.Name) } +// appendEnvVar appends an environment variable to a possibly-nil slice pointer, +// initializing it if needed. +func appendEnvVar( + envVars *[]agent_yaml.EnvironmentVariable, + envVar agent_yaml.EnvironmentVariable, +) *[]agent_yaml.EnvironmentVariable { + if envVars == nil { + return &[]agent_yaml.EnvironmentVariable{envVar} + } + *envVars = append(*envVars, envVar) + return envVars +} + // sanitizeAgentName converts a string into a valid agent name: // lowercase, replace non-alphanumeric with hyphens, collapse consecutive hyphens, // strip leading/trailing hyphens, truncate to 63 chars. diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code_test.go index 968eabdefa7..1f5175e523a 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code_test.go @@ -415,8 +415,6 @@ func TestWriteDefinitionToSrcDir(t *testing.T) { {Protocol: "responses", Version: "v1"}, }, EnvironmentVariables: &[]agent_yaml.EnvironmentVariable{ - {Name: "AZURE_OPENAI_ENDPOINT", Value: "${AZURE_OPENAI_ENDPOINT}"}, - {Name: "AZURE_AI_PROJECT_ENDPOINT", Value: "${AZURE_AI_PROJECT_ENDPOINT}"}, {Name: "AZURE_AI_MODEL_DEPLOYMENT_NAME", Value: "${AZURE_AI_MODEL_DEPLOYMENT_NAME}"}, }, } @@ -440,9 +438,14 @@ func TestWriteDefinitionToSrcDir(t *testing.T) { contentStr := string(content) // Verify key content is present in the YAML - if !containsAll(contentStr, "name: test-agent", "kind: hosted", "responses", "AZURE_OPENAI_ENDPOINT") { + if !containsAll(contentStr, "name: test-agent", "kind: hosted", "responses", "AZURE_AI_MODEL_DEPLOYMENT_NAME") { t.Errorf("written content missing expected fields:\n%s", contentStr) } + // AZURE_OPENAI_ENDPOINT and AZURE_AI_PROJECT_ENDPOINT should NOT be in agent.yaml + // (they are auto-injected as FOUNDRY_PROJECT_ENDPOINT by the hosted agent platform) + if strings.Contains(contentStr, "AZURE_OPENAI_ENDPOINT") || strings.Contains(contentStr, "AZURE_AI_PROJECT_ENDPOINT") { + t.Errorf("agent.yaml should not contain AZURE_OPENAI_ENDPOINT or AZURE_AI_PROJECT_ENDPOINT:\n%s", contentStr) + } }) t.Run("creates nested directories", func(t *testing.T) { 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 8936ac84471..2dbf6d37247 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go @@ -137,11 +137,15 @@ func runRun(ctx context.Context, flags *runFlags) error { env = append(env, fmt.Sprintf("PORT=%d", flags.port)) // Load azd environment variables (e.g., AZURE_AI_PROJECT_ENDPOINT) - // so the agent can reach Azure services during local development + // so the agent can reach Azure services during local development. + // Also translate azd env keys to FOUNDRY_* env vars so the agent code + // works identically whether running locally or in a hosted container + // (where the platform automatically injects FOUNDRY_* env vars). if azdEnvVars, err := loadAzdEnvironment(ctx, azdClient); err == nil { for k, v := range azdEnvVars { env = append(env, fmt.Sprintf("%s=%s", k, v)) } + env = appendFoundryEnvVars(env, azdEnvVars, runCtx.ServiceName) } url := fmt.Sprintf("http://localhost:%d", flags.port) @@ -362,6 +366,55 @@ func venvBinDir(venvDir string) string { return filepath.Join(venvDir, "bin") } +// appendFoundryEnvVars translates azd environment keys to FOUNDRY_* env vars that hosted +// agent containers receive automatically from the platform. This ensures the agent code +// works identically whether running locally (via azd ai agent run) or in a hosted container. +// +// The mapping is: +// +// AZURE_AI_PROJECT_ENDPOINT → FOUNDRY_PROJECT_ENDPOINT +// AZURE_AI_PROJECT_ID → FOUNDRY_PROJECT_ARM_ID +// AGENT_{SVC}_NAME → FOUNDRY_AGENT_NAME +// AGENT_{SVC}_VERSION → FOUNDRY_AGENT_VERSION +// APPLICATIONINSIGHTS_CONNECTION_STRING (unchanged — already matches platform name) +func appendFoundryEnvVars(env []string, azdEnv map[string]string, serviceName string) []string { + // Static mappings from azd env key names to FOUNDRY_* env var names + staticMappings := []struct { + azdKey string + foundryKey string + }{ + {"AZURE_AI_PROJECT_ENDPOINT", "FOUNDRY_PROJECT_ENDPOINT"}, + {"AZURE_AI_PROJECT_ID", "FOUNDRY_PROJECT_ARM_ID"}, + } + + for _, m := range staticMappings { + if v := azdEnv[m.azdKey]; v != "" { + env = append(env, fmt.Sprintf("%s=%s", m.foundryKey, v)) + } + } + + // Service-specific mappings (AGENT_{SVC}_NAME → FOUNDRY_AGENT_NAME, etc.) + if serviceName != "" { + serviceKey := toServiceKey(serviceName) + agentMappings := []struct { + azdKeyFmt string + foundryKey string + }{ + {"AGENT_%s_NAME", "FOUNDRY_AGENT_NAME"}, + {"AGENT_%s_VERSION", "FOUNDRY_AGENT_VERSION"}, + } + + for _, m := range agentMappings { + azdKey := fmt.Sprintf(m.azdKeyFmt, serviceKey) + if v := azdEnv[azdKey]; v != "" { + env = append(env, fmt.Sprintf("%s=%s", m.foundryKey, v)) + } + } + } + + return env +} + // loadAzdEnvironment reads all key-value pairs from the current azd environment. func loadAzdEnvironment(ctx context.Context, azdClient *azdext.AzdClient) (map[string]string, error) { envResponse, err := azdClient.Environment().GetCurrent(ctx, &azdext.EmptyRequest{}) 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 7b83b38314e..cadbcc0876a 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 @@ -197,3 +197,69 @@ func createVenv(t *testing.T, projectDir string) string { return venvDir } + +func TestAppendFoundryEnvVars(t *testing.T) { + t.Parallel() + + t.Run("maps AZURE_AI_PROJECT_ENDPOINT to FOUNDRY_PROJECT_ENDPOINT", func(t *testing.T) { + t.Parallel() + azdEnv := map[string]string{ + "AZURE_AI_PROJECT_ENDPOINT": "https://myaccount.services.ai.azure.com/api/projects/myproject", + } + env := appendFoundryEnvVars(nil, azdEnv, "") + expected := "FOUNDRY_PROJECT_ENDPOINT=https://myaccount.services.ai.azure.com/api/projects/myproject" + if !slices.Contains(env, expected) { + t.Errorf("expected %q in env, got %v", expected, env) + } + }) + + t.Run("maps AZURE_AI_PROJECT_ID to FOUNDRY_PROJECT_ARM_ID", func(t *testing.T) { + t.Parallel() + azdEnv := map[string]string{ + "AZURE_AI_PROJECT_ID": "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.CognitiveServices/accounts/acct1/projects/proj1", + } + env := appendFoundryEnvVars(nil, azdEnv, "") + expected := "FOUNDRY_PROJECT_ARM_ID=/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.CognitiveServices/accounts/acct1/projects/proj1" + if !slices.Contains(env, expected) { + t.Errorf("expected %q in env, got %v", expected, env) + } + }) + + t.Run("maps service-specific agent vars to FOUNDRY_AGENT_*", func(t *testing.T) { + t.Parallel() + azdEnv := map[string]string{ + "AGENT_MY_SVC_NAME": "my-agent", + "AGENT_MY_SVC_VERSION": "v3", + } + env := appendFoundryEnvVars(nil, azdEnv, "my-svc") + if !slices.Contains(env, "FOUNDRY_AGENT_NAME=my-agent") { + t.Errorf("expected FOUNDRY_AGENT_NAME=my-agent in env, got %v", env) + } + if !slices.Contains(env, "FOUNDRY_AGENT_VERSION=v3") { + t.Errorf("expected FOUNDRY_AGENT_VERSION=v3 in env, got %v", env) + } + }) + + t.Run("skips missing values", func(t *testing.T) { + t.Parallel() + azdEnv := map[string]string{} + env := appendFoundryEnvVars(nil, azdEnv, "my-svc") + if len(env) != 0 { + t.Errorf("expected empty env, got %v", env) + } + }) + + t.Run("includes all mappings together", func(t *testing.T) { + t.Parallel() + azdEnv := map[string]string{ + "AZURE_AI_PROJECT_ENDPOINT": "https://acct.services.ai.azure.com/api/projects/proj", + "AZURE_AI_PROJECT_ID": "/subscriptions/sub/rg/rg/acct/proj", + "AGENT_AGENT1_NAME": "agent1", + "AGENT_AGENT1_VERSION": "v1", + } + env := appendFoundryEnvVars(nil, azdEnv, "agent1") + if len(env) != 4 { + t.Errorf("expected 4 env vars, got %d: %v", len(env), env) + } + }) +} From bd7c8b5e0117e750d0704134d15a3147cda0ec5e Mon Sep 17 00:00:00 2001 From: Jeffrey Chen Date: Wed, 8 Apr 2026 21:05:25 +0000 Subject: [PATCH 3/4] Skip FOUNDRY_* translation when key already exists in azd env When the azd environment already contains a FOUNDRY_* key (e.g. FOUNDRY_PROJECT_ENDPOINT), the translation from the corresponding AZURE_* key is skipped to avoid appending a duplicate that silently overrides the user's explicit value. Applied to both static and service-specific agent mappings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../azure.ai.agents/internal/cmd/run.go | 8 ++++-- .../azure.ai.agents/internal/cmd/run_test.go | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) 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 2dbf6d37247..45007516d84 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go @@ -389,7 +389,9 @@ func appendFoundryEnvVars(env []string, azdEnv map[string]string, serviceName st for _, m := range staticMappings { if v := azdEnv[m.azdKey]; v != "" { - env = append(env, fmt.Sprintf("%s=%s", m.foundryKey, v)) + if _, exists := azdEnv[m.foundryKey]; !exists { + env = append(env, fmt.Sprintf("%s=%s", m.foundryKey, v)) + } } } @@ -407,7 +409,9 @@ func appendFoundryEnvVars(env []string, azdEnv map[string]string, serviceName st for _, m := range agentMappings { azdKey := fmt.Sprintf(m.azdKeyFmt, serviceKey) if v := azdEnv[azdKey]; v != "" { - env = append(env, fmt.Sprintf("%s=%s", m.foundryKey, v)) + if _, exists := azdEnv[m.foundryKey]; !exists { + env = append(env, fmt.Sprintf("%s=%s", m.foundryKey, v)) + } } } } 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 cadbcc0876a..a1988039d37 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 @@ -8,6 +8,7 @@ import ( "path/filepath" "runtime" "slices" + "strings" "testing" ) @@ -262,4 +263,31 @@ func TestAppendFoundryEnvVars(t *testing.T) { t.Errorf("expected 4 env vars, got %d: %v", len(env), env) } }) + + t.Run("skips foundry key when already set in azd env", func(t *testing.T) { + t.Parallel() + azdEnv := map[string]string{ + "AZURE_AI_PROJECT_ENDPOINT": "https://from-azd.services.ai.azure.com", + "FOUNDRY_PROJECT_ENDPOINT": "https://explicit.services.ai.azure.com", + "AGENT_MY_SVC_NAME": "my-agent", + "FOUNDRY_AGENT_NAME": "explicit-agent", + } + env := appendFoundryEnvVars(nil, azdEnv, "my-svc") + + // Neither FOUNDRY_PROJECT_ENDPOINT nor FOUNDRY_AGENT_NAME should be + // appended because they already exist in azdEnv (and were thus already + // added to the env slice by the caller's loop over azdEnv). + for _, entry := range env { + if strings.HasPrefix(entry, "FOUNDRY_PROJECT_ENDPOINT=") || + strings.HasPrefix(entry, "FOUNDRY_AGENT_NAME=") { + t.Errorf("should not translate when foundry key already in azdEnv, got %q", entry) + } + } + + // AZURE_AI_PROJECT_ID has no explicit FOUNDRY_PROJECT_ARM_ID, so it should still be skipped + // (it's not in azdEnv either, so appendFoundryEnvVars skips it because the source key is empty) + if len(env) != 0 { + t.Errorf("expected no translated env vars, got %v", env) + } + }) } From 510764b964eebb9496cd06a54b7ddc8a76dd6054 Mon Sep 17 00:00:00 2001 From: Jeffrey Chen Date: Thu, 9 Apr 2026 19:02:57 +0000 Subject: [PATCH 4/4] Address PR review: check process env for existing FOUNDRY_* vars and fix comment - appendFoundryEnvVars now also checks the existing env slice (from os.Environ()) before appending FOUNDRY_* translations, preventing user shell-set values from being silently overridden. - Extract envSliceHasKey helper for the prefix check. - Add test covering the process env scenario. - Fix misleading test comment about AZURE_OPENAI_ENDPOINT auto-injection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../internal/cmd/init_from_code_test.go | 4 +- .../azure.ai.agents/internal/cmd/run.go | 13 +++++- .../azure.ai.agents/internal/cmd/run_test.go | 45 +++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code_test.go index 1f5175e523a..df921a4f31b 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_from_code_test.go @@ -441,8 +441,8 @@ func TestWriteDefinitionToSrcDir(t *testing.T) { if !containsAll(contentStr, "name: test-agent", "kind: hosted", "responses", "AZURE_AI_MODEL_DEPLOYMENT_NAME") { t.Errorf("written content missing expected fields:\n%s", contentStr) } - // AZURE_OPENAI_ENDPOINT and AZURE_AI_PROJECT_ENDPOINT should NOT be in agent.yaml - // (they are auto-injected as FOUNDRY_PROJECT_ENDPOINT by the hosted agent platform) + // AZURE_OPENAI_ENDPOINT and AZURE_AI_PROJECT_ENDPOINT should NOT be written to agent.yaml. + // Hosted agents receive platform-provided FOUNDRY_* variables such as FOUNDRY_PROJECT_ENDPOINT instead. if strings.Contains(contentStr, "AZURE_OPENAI_ENDPOINT") || strings.Contains(contentStr, "AZURE_AI_PROJECT_ENDPOINT") { t.Errorf("agent.yaml should not contain AZURE_OPENAI_ENDPOINT or AZURE_AI_PROJECT_ENDPOINT:\n%s", contentStr) } 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 45007516d84..3b8b3132ba5 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/run.go @@ -11,6 +11,7 @@ import ( "os/signal" "path/filepath" "runtime" + "slices" "strings" "syscall" @@ -389,7 +390,7 @@ func appendFoundryEnvVars(env []string, azdEnv map[string]string, serviceName st for _, m := range staticMappings { if v := azdEnv[m.azdKey]; v != "" { - if _, exists := azdEnv[m.foundryKey]; !exists { + if _, exists := azdEnv[m.foundryKey]; !exists && !envSliceHasKey(env, m.foundryKey) { env = append(env, fmt.Sprintf("%s=%s", m.foundryKey, v)) } } @@ -409,7 +410,7 @@ func appendFoundryEnvVars(env []string, azdEnv map[string]string, serviceName st for _, m := range agentMappings { azdKey := fmt.Sprintf(m.azdKeyFmt, serviceKey) if v := azdEnv[azdKey]; v != "" { - if _, exists := azdEnv[m.foundryKey]; !exists { + if _, exists := azdEnv[m.foundryKey]; !exists && !envSliceHasKey(env, m.foundryKey) { env = append(env, fmt.Sprintf("%s=%s", m.foundryKey, v)) } } @@ -419,6 +420,14 @@ func appendFoundryEnvVars(env []string, azdEnv map[string]string, serviceName st return env } +// envSliceHasKey reports whether the env slice already contains an entry for the given key. +func envSliceHasKey(env []string, key string) bool { + prefix := key + "=" + return slices.ContainsFunc(env, func(entry string) bool { + return strings.HasPrefix(entry, prefix) + }) +} + // loadAzdEnvironment reads all key-value pairs from the current azd environment. func loadAzdEnvironment(ctx context.Context, azdClient *azdext.AzdClient) (map[string]string, error) { envResponse, err := azdClient.Environment().GetCurrent(ctx, &azdext.EmptyRequest{}) 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 a1988039d37..afc7d0dbc96 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 @@ -290,4 +290,49 @@ func TestAppendFoundryEnvVars(t *testing.T) { t.Errorf("expected no translated env vars, got %v", env) } }) + + t.Run("skips foundry key when already set in process env slice", func(t *testing.T) { + t.Parallel() + // Simulate os.Environ() already containing FOUNDRY_* vars set by the user's shell + existingEnv := []string{ + "HOME=/home/user", + "FOUNDRY_PROJECT_ENDPOINT=https://user-shell.services.ai.azure.com", + "FOUNDRY_AGENT_NAME=shell-agent", + } + azdEnv := map[string]string{ + "AZURE_AI_PROJECT_ENDPOINT": "https://from-azd.services.ai.azure.com", + "AZURE_AI_PROJECT_ID": "/subscriptions/sub/rg/rg/acct/proj", + "AGENT_MY_SVC_NAME": "my-agent", + "AGENT_MY_SVC_VERSION": "v2", + } + env := appendFoundryEnvVars(existingEnv, azdEnv, "my-svc") + + // FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_AGENT_NAME should NOT be appended + // because they already exist in the process env slice. + foundryEndpointCount := 0 + foundryAgentNameCount := 0 + for _, entry := range env { + if strings.HasPrefix(entry, "FOUNDRY_PROJECT_ENDPOINT=") { + foundryEndpointCount++ + } + if strings.HasPrefix(entry, "FOUNDRY_AGENT_NAME=") { + foundryAgentNameCount++ + } + } + if foundryEndpointCount != 1 { + t.Errorf("expected exactly 1 FOUNDRY_PROJECT_ENDPOINT entry (from shell), got %d in %v", foundryEndpointCount, env) + } + if foundryAgentNameCount != 1 { + t.Errorf("expected exactly 1 FOUNDRY_AGENT_NAME entry (from shell), got %d in %v", foundryAgentNameCount, env) + } + + // FOUNDRY_PROJECT_ARM_ID and FOUNDRY_AGENT_VERSION should still be translated + // since they are NOT already present in the env slice. + if !slices.Contains(env, "FOUNDRY_PROJECT_ARM_ID=/subscriptions/sub/rg/rg/acct/proj") { + t.Errorf("expected FOUNDRY_PROJECT_ARM_ID to be translated, got %v", env) + } + if !slices.Contains(env, "FOUNDRY_AGENT_VERSION=v2") { + t.Errorf("expected FOUNDRY_AGENT_VERSION to be translated, got %v", env) + } + }) }