diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go index 489cfe84af0..03a521f0986 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go @@ -3298,6 +3298,7 @@ func extractToolboxAndConnectionConfigs( // External tools with target/authType need a connection toolName, _ := toolMap["name"].(string) authType, _ := toolMap["authType"].(string) + authType = string(agent_yaml.NormalizeConnectionAuthType(agent_yaml.AuthType(authType))) credentials, _ := toolMap["credentials"].(map[string]any) connName := toolName @@ -3455,13 +3456,14 @@ func extractConnectionConfigs( } creds := maps.Clone(connResource.Credentials) - authType := string(connResource.AuthType) + authType := string(agent_yaml.NormalizeConnectionAuthType(connResource.AuthType)) // Surface credentials.type to top-level authType when not explicitly set. - // This must happen before externalization so we capture the raw value. + // Do this before externalization so "type" isn't converted into an env var entry, + // and normalize legacy auth types for provisioning compatibility. if authType == "" && len(creds) > 0 { if credType, ok := creds["type"].(string); ok && credType != "" { - authType = credType + authType = string(agent_yaml.NormalizeConnectionAuthType(agent_yaml.AuthType(credType))) delete(creds, "type") } } diff --git a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_test.go b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_test.go index 8ce93ddb7a4..e60ac017b9c 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init_test.go @@ -911,6 +911,46 @@ func TestExtractToolboxAndConnectionConfigs_RawToolsFallback(t *testing.T) { } } +func TestExtractToolboxAndConnectionConfigs_NormalizesAgenticIdentityAuthType(t *testing.T) { + t.Parallel() + + manifest := &agent_yaml.AgentManifest{ + Resources: []any{ + agent_yaml.ToolboxResource{ + Resource: agent_yaml.Resource{ + Name: "platform-tools", + Kind: agent_yaml.ResourceKindToolbox, + }, + Tools: []any{ + map[string]any{ + "type": "mcp", + "name": "agentic-tool", + "target": "https://example.com/mcp", + "authType": "AgenticIdentity", + }, + }, + }, + }, + } + + _, connections, _, err := extractToolboxAndConnectionConfigs(manifest) + if err != nil { + t.Fatalf("extractToolboxAndConnectionConfigs failed: %v", err) + } + + if len(connections) != 1 { + t.Fatalf("Expected 1 connection, got %d", len(connections)) + } + + if connections[0].AuthType != string(agent_yaml.AuthTypeAgenticIdentityToken) { + t.Errorf( + "Expected authType %q, got %q", + agent_yaml.AuthTypeAgenticIdentityToken, + connections[0].AuthType, + ) + } +} + func TestExtractToolboxAndConnectionConfigs_NilManifest(t *testing.T) { t.Parallel() @@ -1203,6 +1243,42 @@ func TestExtractConnectionConfigs_SurfacesCredentialsType(t *testing.T) { wantCredKeyCount: 2, wantEnvVarCount: 2, // both "type" and "key" externalized }, + { + name: "normalizes explicit AgenticIdentity authType", + connResource: agent_yaml.ConnectionResource{ + Resource: agent_yaml.Resource{ + Name: "my-conn", + Kind: agent_yaml.ResourceKindConnection, + }, + Target: "https://example.com", + AuthType: agent_yaml.AuthTypeAgenticIdentity, + Credentials: map[string]any{ + "key": "val", + }, + }, + wantAuthType: string(agent_yaml.AuthTypeAgenticIdentityToken), + wantCredHasType: false, + wantCredKeyCount: 1, + wantEnvVarCount: 1, + }, + { + name: "normalizes credentials.type AgenticIdentity when authType is empty", + connResource: agent_yaml.ConnectionResource{ + Resource: agent_yaml.Resource{ + Name: "my-conn", + Kind: agent_yaml.ResourceKindConnection, + }, + Target: "https://example.com", + Credentials: map[string]any{ + "type": "AgenticIdentity", + "key": "secret-value", + }, + }, + wantAuthType: string(agent_yaml.AuthTypeAgenticIdentityToken), + wantCredHasType: false, + wantCredKeyCount: 1, + wantEnvVarCount: 1, + }, { name: "no credentials.type and no authType stays empty", connResource: agent_yaml.ConnectionResource{ diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse_test.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse_test.go index 34bbcfa3311..46a5d454666 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse_test.go @@ -602,6 +602,8 @@ func TestExtractResourceDefinitions_ConnectionAllAuthTypes(t *testing.T) { AuthTypeNone, AuthTypeOAuth2, AuthTypePAT, + AuthTypeAgenticIdentity, + AuthTypeAgenticIdentityToken, } for _, authType := range authTypes { diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml.go index 2d6b4d6281a..988e3294707 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml.go @@ -80,22 +80,35 @@ func NormalizeToolKind(kind ToolKind) ToolKind { type AuthType string const ( - AuthTypeAAD AuthType = "AAD" - AuthTypeApiKey AuthType = "ApiKey" - AuthTypeCustomKeys AuthType = "CustomKeys" - AuthTypeNone AuthType = "None" - AuthTypeOAuth2 AuthType = "OAuth2" - AuthTypePAT AuthType = "PAT" - AuthTypeUserEntraToken AuthType = "UserEntraToken" - AuthTypeAgenticIdentity AuthType = "AgenticIdentity" - AuthTypeManagedIdentity AuthType = "ProjectManagedIdentity" - AuthTypeServicePrincipal AuthType = "ServicePrincipal" - AuthTypeUsernamePassword AuthType = "UsernamePassword" - AuthTypeAccessKey AuthType = "AccessKey" - AuthTypeAccountKey AuthType = "AccountKey" - AuthTypeSAS AuthType = "SAS" + AuthTypeAAD AuthType = "AAD" + AuthTypeApiKey AuthType = "ApiKey" + AuthTypeCustomKeys AuthType = "CustomKeys" + AuthTypeNone AuthType = "None" + AuthTypeOAuth2 AuthType = "OAuth2" + AuthTypePAT AuthType = "PAT" + AuthTypeUserEntraToken AuthType = "UserEntraToken" + AuthTypeAgenticIdentity AuthType = "AgenticIdentity" + AuthTypeAgenticIdentityToken AuthType = "AgenticIdentityToken" + AuthTypeManagedIdentity AuthType = "ProjectManagedIdentity" + AuthTypeServicePrincipal AuthType = "ServicePrincipal" + AuthTypeUsernamePassword AuthType = "UsernamePassword" + AuthTypeAccessKey AuthType = "AccessKey" + AuthTypeAccountKey AuthType = "AccountKey" + AuthTypeSAS AuthType = "SAS" ) +// NormalizeConnectionAuthType maps auth types accepted in agent.yaml to +// the management-plane value required for project connection provisioning. +// Legacy AgenticIdentity values are normalized to AgenticIdentityToken +// for API compatibility. +func NormalizeConnectionAuthType(authType AuthType) AuthType { + if authType == AuthTypeAgenticIdentity { + return AuthTypeAgenticIdentityToken + } + + return authType +} + // CategoryKind represents the category of a connection resource. type CategoryKind string diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml_test.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml_test.go index d2593d00ede..56d74f6558d 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml_test.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/yaml_test.go @@ -217,3 +217,19 @@ func TestConnectionResourceNewFieldsYAMLRoundTrip(t *testing.T) { t.Errorf("scopes dropped: got %v", got.Scopes) } } + +func TestNormalizeConnectionAuthType(t *testing.T) { + t.Parallel() + + if got := NormalizeConnectionAuthType(AuthTypeAgenticIdentity); got != AuthTypeAgenticIdentityToken { + t.Fatalf("NormalizeConnectionAuthType(AgenticIdentity) = %q, want %q", got, AuthTypeAgenticIdentityToken) + } + + if got := NormalizeConnectionAuthType(AuthTypeAgenticIdentityToken); got != AuthTypeAgenticIdentityToken { + t.Fatalf("NormalizeConnectionAuthType(AgenticIdentityToken) = %q, want %q", got, AuthTypeAgenticIdentityToken) + } + + if got := NormalizeConnectionAuthType(AuthTypeOAuth2); got != AuthTypeOAuth2 { + t.Fatalf("NormalizeConnectionAuthType(OAuth2) = %q, want %q", got, AuthTypeOAuth2) + } +} diff --git a/cli/azd/extensions/azure.ai.agents/schemas/azure.ai.agent.json b/cli/azd/extensions/azure.ai.agents/schemas/azure.ai.agent.json index b217d58eeb6..312b415bc6e 100644 --- a/cli/azd/extensions/azure.ai.agents/schemas/azure.ai.agent.json +++ b/cli/azd/extensions/azure.ai.agents/schemas/azure.ai.agent.json @@ -125,7 +125,7 @@ "authType": { "type": "string", "description": "Authentication type for the connection.", - "enum": ["AAD", "AccessKey", "AccountKey", "ApiKey", "CustomKeys", "ManagedIdentity", "None", "OAuth2", "PAT", "ServicePrincipal", "UsernamePassword", "ProjectManagedIdentity", "UserEntraToken"] + "enum": ["AAD", "AccessKey", "AccountKey", "ApiKey", "CustomKeys", "ManagedIdentity", "None", "OAuth2", "PAT", "ServicePrincipal", "UsernamePassword", "ProjectManagedIdentity", "UserEntraToken", "AgenticIdentityToken"] }, "credentials": { "type": "object", @@ -165,7 +165,7 @@ "authType": { "type": "string", "description": "Authentication type.", - "enum": ["AAD", "AccessKey", "AccountKey", "AgenticIdentity", "ApiKey", "CustomKeys", "ManagedIdentity", "None", "OAuth2", "PAT", "SAS", "ServicePrincipal", "UsernamePassword", "UserEntraToken", "ProjectManagedIdentity"] + "enum": ["AAD", "AccessKey", "AccountKey", "AgenticIdentity", "AgenticIdentityToken", "ApiKey", "CustomKeys", "ManagedIdentity", "None", "OAuth2", "PAT", "SAS", "ServicePrincipal", "UsernamePassword", "UserEntraToken", "ProjectManagedIdentity"] }, "credentials": { "type": "object", @@ -184,7 +184,7 @@ "description": "OAuth2 scopes to request (optional for OAuth2 authType).", "items": { "type": "string" } }, - "audience": { "type": "string", "description": "Token audience for AAD/ProjectManagedIdentity/AgenticIdentity/UserEntraToken auth types." }, + "audience": { "type": "string", "description": "Token audience for AAD/ProjectManagedIdentity/AgenticIdentity/AgenticIdentityToken/UserEntraToken auth types." }, "connectorName": { "type": "string", "description": "Connector name for Oauth2 auth type." }, "expiryTime": { "type": "string", "description": "Connection expiry time." }, "isSharedToAll": { "type": "boolean", "description": "Whether the connection is shared to all users." },