Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion cli/azd/cmd/auto_install_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ func TestAgentDetectionIntegration(t *testing.T) {
expectedNoPrompt: true,
description: "When running under GitHub Copilot CLI (COPILOT_CLI), --no-prompt should be auto-enabled",
},
{
name: "GitHub Copilot VSCode enables no-prompt automatically",
args: []string{"deploy"},
envVars: map[string]string{"AI_AGENT": "github_copilot_vscode_agent"},
expectedNoPrompt: true,
description: "When running under GitHub Copilot VSCode, --no-prompt should be auto-enabled",
},
{
name: "Gemini agent enables no-prompt automatically",
args: []string{"init"},
Expand Down Expand Up @@ -303,7 +310,7 @@ func containsNoPromptFalse(args []string) bool {
// runs inside a CI/CD provider.
func clearAgentEnvVarsForTest(t *testing.T) {
envVarsToUnset := []string{
// GitHub Copilot App
// GitHub Copilot hosts
"AI_AGENT",
// Claude Code
"CLAUDE_CODE", "CLAUDE_CODE_ENTRYPOINT",
Expand Down
9 changes: 7 additions & 2 deletions cli/azd/internal/runcontext/agentdetect/detect_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ type envVarPattern struct {
// knownEnvVarPatterns defines environment variables that indicate known AI agents.
// These are checked in order, so more specific patterns should come first.
var knownEnvVarPatterns = []envVarPattern{
// GitHub Copilot App can expose Copilot CLI markers, so check its host-specific marker first.
// GitHub Copilot hosts can expose Copilot CLI markers, so check their host-specific markers first.
{
envVar: "AI_AGENT",
expectedValue: "github_copilot_app_agent",
agentType: AgentTypeGitHubCopilotApp,
},
{
envVar: "AI_AGENT",
expectedValue: "github_copilot_vscode_agent",
agentType: AgentTypeGitHubCopilotVSCode,
Comment thread
qinezh marked this conversation as resolved.
},

// Claude Code - Anthropic's coding agent
{envVar: "CLAUDE_CODE", agentType: AgentTypeClaudeCode},
Expand Down Expand Up @@ -70,7 +75,7 @@ var userAgentPatterns = []struct {
substring string
agentType AgentType
}{
// VS Code GitHub Copilot extension
// GitHub Copilot for Azure extension in VS Code
{substring: internal.VsCodeAzureCopilotAgentPrefix, agentType: AgentTypeVSCodeCopilot},
{substring: "github-copilot", agentType: AgentTypeGitHubCopilotCLI},
{substring: "copilot-cli", agentType: AgentTypeGitHubCopilotCLI},
Expand Down
37 changes: 36 additions & 1 deletion cli/azd/internal/runcontext/agentdetect/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestAgentType_DisplayName(t *testing.T) {
{AgentTypeClaudeCode, "Claude Code"},
{AgentTypeGitHubCopilotCLI, "GitHub Copilot CLI"},
{AgentTypeGitHubCopilotApp, "GitHub Copilot App"},
{AgentTypeGitHubCopilotVSCode, "GitHub Copilot VSCode"},
{AgentTypeVSCodeCopilot, "VS Code GitHub Copilot"},
{AgentTypeGemini, "Gemini"},
{AgentTypeOpenCode, "OpenCode"},
Expand Down Expand Up @@ -63,12 +64,46 @@ func TestDetectFromEnvVars(t *testing.T) {
expectedAgent: AgentTypeGitHubCopilotApp,
detected: true,
},
{
name: "GitHub Copilot VSCode takes precedence over Copilot CLI",
envVars: map[string]string{
"AI_AGENT": "github_copilot_vscode_agent",
"GITHUB_COPILOT_CLI": "true",
"GH_COPILOT": "1",
"COPILOT_CLI": "1",
},
expectedAgent: AgentTypeGitHubCopilotVSCode,
detected: true,
},
{
name: "Unrecognized AI_AGENT value",
envVars: map[string]string{"AI_AGENT": "another_agent"},
expectedAgent: AgentTypeUnknown,
detected: false,
},
{
name: "AI_AGENT requires an exact value",
envVars: map[string]string{
"AI_AGENT": "github_copilot_vscode_agent_extra",
},
expectedAgent: AgentTypeUnknown,
detected: false,
},
{
name: "Wrong AI_AGENT value falls back to Copilot CLI",
envVars: map[string]string{
"AI_AGENT": "github_copilot_vscode",
"COPILOT_CLI": "1",
},
expectedAgent: AgentTypeGitHubCopilotCLI,
detected: true,
},
{
name: "COPILOT_AGENT alone is not attributed",
envVars: map[string]string{"COPILOT_AGENT": "1"},
expectedAgent: AgentTypeUnknown,
detected: false,
},
{
name: "Claude Code via CLAUDE_CODE",
envVars: map[string]string{"CLAUDE_CODE": "1"},
Expand Down Expand Up @@ -343,7 +378,7 @@ func TestDisableAgentDetect(t *testing.T) {
// This list must be kept in sync with knownEnvVarPatterns in detect_env.go.
func clearAgentEnvVars(t *testing.T) {
envVarsToUnset := []string{
// GitHub Copilot App
// GitHub Copilot hosts
"AI_AGENT",
// Claude Code
"CLAUDE_CODE", "CLAUDE_CODE_ENTRYPOINT",
Expand Down
6 changes: 5 additions & 1 deletion cli/azd/internal/runcontext/agentdetect/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ const (
AgentTypeGitHubCopilotCLI AgentType = "github-copilot-cli"
// AgentTypeGitHubCopilotApp is GitHub's Copilot App agent.
AgentTypeGitHubCopilotApp AgentType = "github-copilot-app"
// AgentTypeVSCodeCopilot is VS Code GitHub Copilot extension.
// AgentTypeGitHubCopilotVSCode is GitHub Copilot agent mode in VS Code.
AgentTypeGitHubCopilotVSCode AgentType = "github-copilot-vscode"
// AgentTypeVSCodeCopilot is the GitHub Copilot for Azure extension in VS Code.
AgentTypeVSCodeCopilot AgentType = "vscode-copilot"
// AgentTypeGemini is Google's Gemini CLI.
AgentTypeGemini AgentType = "gemini"
Expand All @@ -40,6 +42,8 @@ func (a AgentType) DisplayName() string {
return "GitHub Copilot CLI"
case AgentTypeGitHubCopilotApp:
return "GitHub Copilot App"
case AgentTypeGitHubCopilotVSCode:
return "GitHub Copilot VSCode"
case AgentTypeVSCodeCopilot:
return "VS Code GitHub Copilot"
case AgentTypeGemini:
Expand Down
7 changes: 6 additions & 1 deletion cli/azd/internal/terminal/terminal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ func TestIsTerminal_AgentDetection(t *testing.T) {
envVars: map[string]string{"COPILOT_CLI": "true"},
expected: false,
},
{
name: "GitHub Copilot VSCode disables TTY",
envVars: map[string]string{"AI_AGENT": "github_copilot_vscode_agent"},
expected: false,
},
{
name: "Gemini CLI disables TTY",
envVars: map[string]string{"GEMINI_CLI": "1"},
Expand Down Expand Up @@ -92,7 +97,7 @@ func clearTestEnvVars(t *testing.T) {
envVarsToUnset := []string{
"AZD_FORCE_TTY",
// Agent env vars
"AI_AGENT",
"AI_AGENT", // GitHub Copilot hosts
"CLAUDE_CODE", "CLAUDE_CODE_ENTRYPOINT",
"GITHUB_COPILOT_CLI", "GH_COPILOT", "COPILOT_CLI",
"GEMINI_CLI", "GEMINI_CLI_NO_RELAUNCH",
Expand Down
11 changes: 6 additions & 5 deletions cli/azd/internal/tracing/fields/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,12 @@ const (
EnvCloudShell = "Azure CloudShell"

// AI Coding Agent environments
EnvClaudeCode = "Claude Code"
EnvGitHubCopilotCLI = "GitHub Copilot CLI"
EnvGitHubCopilotApp = "GitHub Copilot App"
EnvGemini = "Gemini"
EnvOpenCode = "OpenCode"
EnvClaudeCode = "Claude Code"
EnvGitHubCopilotCLI = "GitHub Copilot CLI"
EnvGitHubCopilotApp = "GitHub Copilot App"
EnvGitHubCopilotVSCode = "GitHub Copilot VSCode"
Comment thread
qinezh marked this conversation as resolved.
EnvGemini = "Gemini"
EnvOpenCode = "OpenCode"

// Continuous Integration environments

Expand Down
2 changes: 2 additions & 0 deletions cli/azd/internal/tracing/resource/exec_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ func execEnvFromAgent() string {
return fields.EnvGitHubCopilotCLI
case agentdetect.AgentTypeGitHubCopilotApp:
return fields.EnvGitHubCopilotApp
case agentdetect.AgentTypeGitHubCopilotVSCode:
return fields.EnvGitHubCopilotVSCode
case agentdetect.AgentTypeVSCodeCopilot:
return fields.EnvVSCodeAzureCopilot
case agentdetect.AgentTypeGemini:
Expand Down
42 changes: 42 additions & 0 deletions cli/azd/internal/tracing/resource/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"slices"
"testing"

"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/internal/runcontext/agentdetect"
"github.com/azure/azure-dev/cli/azd/internal/tracing/fields"
)

Expand Down Expand Up @@ -257,6 +259,46 @@ func TestExecEnvForHosts_no_host(t *testing.T) {
}
}

func TestGetExecutionEnvironment_GitHubCopilotHosts(t *testing.T) {
tests := []struct {
name string
aiAgent string
userAgent string
want string
}{
{
name: "GitHub Copilot App",
aiAgent: "github_copilot_app_agent",
want: fields.EnvGitHubCopilotApp,
},
{
name: "GitHub Copilot VSCode",
aiAgent: "github_copilot_vscode_agent",
want: fields.EnvGitHubCopilotVSCode,
},
{
name: "VS Code Azure GitHub Copilot",
userAgent: internal.VsCodeAzureCopilotAgentPrefix + "/1.0.0",
want: fields.EnvVSCodeAzureCopilot,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("AI_AGENT", tt.aiAgent)
t.Setenv(internal.AzdUserAgentEnvVar, tt.userAgent)
t.Setenv(agentdetect.DisableAgentDetectEnvVar, "")
os.Unsetenv(agentdetect.DisableAgentDetectEnvVar)
agentdetect.ResetDetection()
t.Cleanup(agentdetect.ResetDetection)

if got := getExecutionEnvironment(); got != tt.want {
t.Fatalf("getExecutionEnvironment() = %q, want %q", got, tt.want)
}
})
}
}

func TestExecEnvModifiers(t *testing.T) {
tests := []struct {
name string
Expand Down
1 change: 1 addition & 0 deletions docs/reference/telemetry-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ The `execution.environment` field identifies where azd is running. Format: `<env
| `Visual Studio` | VS integration |
| `Visual Studio Code` | VS Code integration |
| `VS Code Azure GitHub Copilot` | Azure Copilot in VS Code |
| `GitHub Copilot VSCode` | GitHub Copilot in VS Code |
| `Azure CloudShell` | Azure Cloud Shell |
| `Claude Code` | Claude Code AI agent |
| `GitHub Copilot CLI` | GitHub Copilot CLI |
Expand Down
Loading