From eeaa9816dcae1425feece38950071830fe97d447 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 03:30:57 +0000 Subject: [PATCH 01/12] Initial plan From af2a58393ad7875408318fe4f51d66816a059926 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 03:37:17 +0000 Subject: [PATCH 02/12] Fix agent.yaml parsing panic when template field is missing - Update ExtractAgentDefinition to handle both manifest and standalone formats - Add nil check for template field before type assertion - Support flat YAML structure with agent definition at root level - Add comprehensive tests for both YAML formats and edge cases Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- .../internal/pkg/agents/agent_yaml/parse.go | 16 +- .../pkg/agents/agent_yaml/parse_test.go | 235 ++++++++++++++++++ 2 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse_test.go diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go index c4d5f2ad4a7..f9100bc5335 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go @@ -47,8 +47,20 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { return nil, fmt.Errorf("YAML content is not valid: %w", err) } - template := genericManifest["template"].(map[string]interface{}) - templateBytes, _ := yaml.Marshal(template) + // Handle both manifest format (with "template" field) and standalone agent format (without "template" field) + var templateBytes []byte + + if templateValue, exists := genericManifest["template"]; exists && templateValue != nil { + // Manifest format with "template" field + template, ok := templateValue.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("template field must be a map, got %T", templateValue) + } + templateBytes, _ = yaml.Marshal(template) + } else { + // Standalone agent format - the entire document is the agent definition + templateBytes = manifestYamlContent + } var agentDef AgentDefinition if err := yaml.Unmarshal(templateBytes, &agentDef); err != nil { 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 new file mode 100644 index 00000000000..6026b4c2930 --- /dev/null +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse_test.go @@ -0,0 +1,235 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package agent_yaml + +import ( + "testing" +) + +// TestExtractAgentDefinition_WithTemplateField tests parsing YAML with a template field (manifest format) +func TestExtractAgentDefinition_WithTemplateField(t *testing.T) { + yamlContent := []byte(` +name: test-manifest +template: + kind: hosted + name: test-agent + description: Test agent with template field + protocols: + - protocol: responses + version: v1 +`) + + agent, err := ExtractAgentDefinition(yamlContent) + if err != nil { + t.Fatalf("ExtractAgentDefinition failed: %v", err) + } + + containerAgent, ok := agent.(ContainerAgent) + if !ok { + t.Fatalf("Expected ContainerAgent, got %T", agent) + } + + if containerAgent.Name != "test-agent" { + t.Errorf("Expected name 'test-agent', got '%s'", containerAgent.Name) + } + + if containerAgent.Kind != AgentKindHosted { + t.Errorf("Expected kind 'hosted', got '%s'", containerAgent.Kind) + } +} + +// TestExtractAgentDefinition_WithoutTemplateField tests parsing YAML without a template field (standalone format) +func TestExtractAgentDefinition_WithoutTemplateField(t *testing.T) { + yamlContent := []byte(` +kind: hosted +name: lego-social-media-agent +description: An AI-powered social media content generator for LEGO products. +metadata: + authors: + - LEGO Social Media Team + tags: + - Social Media + - Content Generation +protocols: + - protocol: responses + version: v1 +environment_variables: + - name: POSTGRES_SERVER + value: ${POSTGRES_SERVER} + - name: POSTGRES_DATABASE + value: ${POSTGRES_DATABASE} +`) + + agent, err := ExtractAgentDefinition(yamlContent) + if err != nil { + t.Fatalf("ExtractAgentDefinition failed: %v", err) + } + + containerAgent, ok := agent.(ContainerAgent) + if !ok { + t.Fatalf("Expected ContainerAgent, got %T", agent) + } + + if containerAgent.Name != "lego-social-media-agent" { + t.Errorf("Expected name 'lego-social-media-agent', got '%s'", containerAgent.Name) + } + + if containerAgent.Kind != AgentKindHosted { + t.Errorf("Expected kind 'hosted', got '%s'", containerAgent.Kind) + } + + if containerAgent.Description == nil || *containerAgent.Description == "" { + t.Error("Expected description to be set") + } + + if containerAgent.Metadata == nil { + t.Error("Expected metadata to be set") + } + + if len(containerAgent.Protocols) == 0 { + t.Error("Expected protocols to be set") + } + + if containerAgent.EnvironmentVariables == nil || len(*containerAgent.EnvironmentVariables) == 0 { + t.Error("Expected environment_variables to be set") + } +} + +// TestExtractAgentDefinition_InvalidTemplate tests that an invalid template field returns an error +func TestExtractAgentDefinition_InvalidTemplate(t *testing.T) { + yamlContent := []byte(` +name: test-manifest +template: "this is not a map" +`) + + _, err := ExtractAgentDefinition(yamlContent) + if err == nil { + t.Fatal("Expected error for invalid template field, got nil") + } + + expectedMsg := "template field must be a map" + if !contains(err.Error(), expectedMsg) { + t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) + } +} + +// TestExtractAgentDefinition_MissingKind tests that missing kind field returns an error +func TestExtractAgentDefinition_MissingKind(t *testing.T) { + yamlContent := []byte(` +name: test-agent +description: Test agent without kind field +`) + + _, err := ExtractAgentDefinition(yamlContent) + if err == nil { + t.Fatal("Expected error for missing kind field, got nil") + } + + expectedMsg := "unrecognized agent kind" + if !contains(err.Error(), expectedMsg) { + t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) + } +} + +// TestLoadAndValidateAgentManifest_StandaloneFormat tests the full validation flow with standalone format +func TestLoadAndValidateAgentManifest_StandaloneFormat(t *testing.T) { + yamlContent := []byte(` +kind: hosted +name: test-standalone-agent +description: A standalone agent definition +protocols: + - protocol: responses + version: v1 +`) + + manifest, err := LoadAndValidateAgentManifest(yamlContent) + if err != nil { + t.Fatalf("LoadAndValidateAgentManifest failed: %v", err) + } + + if manifest == nil { + t.Fatal("Expected manifest to be non-nil") + } + + containerAgent, ok := manifest.Template.(ContainerAgent) + if !ok { + t.Fatalf("Expected Template to be ContainerAgent, got %T", manifest.Template) + } + + if containerAgent.Name != "test-standalone-agent" { + t.Errorf("Expected name 'test-standalone-agent', got '%s'", containerAgent.Name) + } +} + +// TestExtractAgentDefinition_IssueExample tests the exact YAML from the GitHub issue +func TestExtractAgentDefinition_IssueExample(t *testing.T) { + // This is the exact YAML from the GitHub issue that was causing the panic + yamlContent := []byte(`# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml + +kind: hosted +name: lego-social-media-agent +description: | + An AI-powered social media content generator for LEGO products. +metadata: + authors: + - LEGO Social Media Team + example: + - content: Generate a Twitter post about Star Wars LEGO sets + role: user + tags: + - Social Media + - Content Generation +protocols: + - protocol: responses + version: v1 +environment_variables: + - name: POSTGRES_SERVER + value: ${POSTGRES_SERVER} + - name: POSTGRES_DATABASE + value: ${POSTGRES_DATABASE} +`) + + agent, err := ExtractAgentDefinition(yamlContent) + if err != nil { + t.Fatalf("ExtractAgentDefinition failed with issue example: %v", err) + } + + containerAgent, ok := agent.(ContainerAgent) + if !ok { + t.Fatalf("Expected ContainerAgent, got %T", agent) + } + + if containerAgent.Name != "lego-social-media-agent" { + t.Errorf("Expected name 'lego-social-media-agent', got '%s'", containerAgent.Name) + } + + if containerAgent.Kind != AgentKindHosted { + t.Errorf("Expected kind 'hosted', got '%s'", containerAgent.Kind) + } + + // Test full validation flow + manifest, err := LoadAndValidateAgentManifest(yamlContent) + if err != nil { + t.Fatalf("LoadAndValidateAgentManifest failed with issue example: %v", err) + } + + if manifest.Name != "lego-social-media-agent" { + t.Errorf("Expected manifest name 'lego-social-media-agent', got '%s'", manifest.Name) + } +} + +// Helper function to check if a string contains a substring +func contains(s, substr string) bool { + return len(s) >= len(substr) && (s == substr || len(substr) == 0 || + (len(s) > 0 && len(substr) > 0 && stringContains(s, substr))) +} + +func stringContains(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} From 428cbaf60d1471a6801559a7f4c41a821a72595c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 03:39:16 +0000 Subject: [PATCH 03/12] Support 'agent' field as alias for 'template' in agent.yaml - Add support for both 'template' and 'agent' fields in manifest format - Add test for 'agent' field format used in sample files - Now supports three formats: template field, agent field, and standalone Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- .../internal/pkg/agents/agent_yaml/parse.go | 10 +++++- .../pkg/agents/agent_yaml/parse_test.go | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go index f9100bc5335..45968de5f7c 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go @@ -47,9 +47,10 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { return nil, fmt.Errorf("YAML content is not valid: %w", err) } - // Handle both manifest format (with "template" field) and standalone agent format (without "template" field) + // Handle both manifest format (with "template" or "agent" field) and standalone agent format (without wrapper field) var templateBytes []byte + // Check for "template" field first, then "agent" field as an alias if templateValue, exists := genericManifest["template"]; exists && templateValue != nil { // Manifest format with "template" field template, ok := templateValue.(map[string]interface{}) @@ -57,6 +58,13 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { return nil, fmt.Errorf("template field must be a map, got %T", templateValue) } templateBytes, _ = yaml.Marshal(template) + } else if agentValue, exists := genericManifest["agent"]; exists && agentValue != nil { + // Manifest format with "agent" field (alias for "template") + template, ok := agentValue.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("agent field must be a map, got %T", agentValue) + } + templateBytes, _ = yaml.Marshal(template) } else { // Standalone agent format - the entire document is the agent definition templateBytes = manifestYamlContent 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 6026b4c2930..68335951960 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 @@ -39,6 +39,38 @@ template: } } +// TestExtractAgentDefinition_WithAgentField tests parsing YAML with an agent field (alias for template) +func TestExtractAgentDefinition_WithAgentField(t *testing.T) { + yamlContent := []byte(` +name: test-manifest +agent: + kind: hosted + name: test-agent-with-agent-field + description: Test agent with agent field + protocols: + - protocol: responses + version: v1 +`) + + agent, err := ExtractAgentDefinition(yamlContent) + if err != nil { + t.Fatalf("ExtractAgentDefinition failed: %v", err) + } + + containerAgent, ok := agent.(ContainerAgent) + if !ok { + t.Fatalf("Expected ContainerAgent, got %T", agent) + } + + if containerAgent.Name != "test-agent-with-agent-field" { + t.Errorf("Expected name 'test-agent-with-agent-field', got '%s'", containerAgent.Name) + } + + if containerAgent.Kind != AgentKindHosted { + t.Errorf("Expected kind 'hosted', got '%s'", containerAgent.Kind) + } +} + // TestExtractAgentDefinition_WithoutTemplateField tests parsing YAML without a template field (standalone format) func TestExtractAgentDefinition_WithoutTemplateField(t *testing.T) { yamlContent := []byte(` From 3a34c6a7b4ab3d8080c4fefc00a192bd05427131 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 03:40:46 +0000 Subject: [PATCH 04/12] Address code review feedback - Add error handling for yaml.Marshal calls - Replace custom contains function with strings.Contains from stdlib - Remove redundant helper functions Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- .../internal/pkg/agents/agent_yaml/parse.go | 12 +++++++++-- .../pkg/agents/agent_yaml/parse_test.go | 20 +++---------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go index 45968de5f7c..4da482440b6 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go @@ -57,14 +57,22 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { if !ok { return nil, fmt.Errorf("template field must be a map, got %T", templateValue) } - templateBytes, _ = yaml.Marshal(template) + var err error + templateBytes, err = yaml.Marshal(template) + if err != nil { + return nil, fmt.Errorf("failed to marshal template: %w", err) + } } else if agentValue, exists := genericManifest["agent"]; exists && agentValue != nil { // Manifest format with "agent" field (alias for "template") template, ok := agentValue.(map[string]interface{}) if !ok { return nil, fmt.Errorf("agent field must be a map, got %T", agentValue) } - templateBytes, _ = yaml.Marshal(template) + var err error + templateBytes, err = yaml.Marshal(template) + if err != nil { + return nil, fmt.Errorf("failed to marshal agent: %w", err) + } } else { // Standalone agent format - the entire document is the agent definition templateBytes = manifestYamlContent 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 68335951960..46eb30125aa 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 @@ -4,6 +4,7 @@ package agent_yaml import ( + "strings" "testing" ) @@ -141,7 +142,7 @@ template: "this is not a map" } expectedMsg := "template field must be a map" - if !contains(err.Error(), expectedMsg) { + if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } } @@ -159,7 +160,7 @@ description: Test agent without kind field } expectedMsg := "unrecognized agent kind" - if !contains(err.Error(), expectedMsg) { + if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } } @@ -250,18 +251,3 @@ environment_variables: t.Errorf("Expected manifest name 'lego-social-media-agent', got '%s'", manifest.Name) } } - -// Helper function to check if a string contains a substring -func contains(s, substr string) bool { - return len(s) >= len(substr) && (s == substr || len(substr) == 0 || - (len(s) > 0 && len(substr) > 0 && stringContains(s, substr))) -} - -func stringContains(s, substr string) bool { - for i := 0; i <= len(s)-len(substr); i++ { - if s[i:i+len(substr)] == substr { - return true - } - } - return false -} From 73d2ef21cdce937d174c3a2194e25c7300b08bab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 03:45:22 +0000 Subject: [PATCH 05/12] Fix protobuf security vulnerability CVE (JSON recursion depth bypass) Update protobuf from 5.29.5 to 6.33.5 in microsoft.azd.extensions - Addresses JSON recursion depth bypass vulnerability - Affected versions: <= 6.33.4 - Patched version: 6.33.5 Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- .../internal/resources/languages/python/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/extensions/microsoft.azd.extensions/internal/resources/languages/python/requirements.txt b/cli/azd/extensions/microsoft.azd.extensions/internal/resources/languages/python/requirements.txt index b8e8bb17834..d097860265c 100644 --- a/cli/azd/extensions/microsoft.azd.extensions/internal/resources/languages/python/requirements.txt +++ b/cli/azd/extensions/microsoft.azd.extensions/internal/resources/languages/python/requirements.txt @@ -1,6 +1,6 @@ grpcio==1.71.0 grpcio-tools==1.71.0 -protobuf==5.29.5 +protobuf==6.33.5 grpc-interceptor==0.15.4 azure.identity== 1.21.0 azure.mgmt.resource== 22.0.0 From 859d1e8db5790b1c4c2b3320b6daf781955b9b02 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:37:01 +0000 Subject: [PATCH 06/12] Remove standalone format support per review feedback - Return error when neither 'template' nor 'agent' field exists - Add check for empty template/agent maps - Update tests to expect errors for YAMLs without wrapper fields - Add test for precedence when both fields present - Add test for empty/null agent field handling Per feedback: the rest of the code assumes manifest format, so supporting standalone format would lead to undefined behavior. Now returns a clear error message with link to AgentSchema documentation. Co-authored-by: rajeshkamal5050 <11532743+rajeshkamal5050@users.noreply.github.com> --- .../internal/pkg/agents/agent_yaml/parse.go | 14 +- .../pkg/agents/agent_yaml/parse_test.go | 179 +++++++++++------- 2 files changed, 117 insertions(+), 76 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go index 4da482440b6..265f6d1783f 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go @@ -47,16 +47,19 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { return nil, fmt.Errorf("YAML content is not valid: %w", err) } - // Handle both manifest format (with "template" or "agent" field) and standalone agent format (without wrapper field) + // Handle both manifest format with "template" or "agent" field + // If both "template" and "agent" are present, "template" takes precedence and "agent" is ignored var templateBytes []byte - // Check for "template" field first, then "agent" field as an alias if templateValue, exists := genericManifest["template"]; exists && templateValue != nil { // Manifest format with "template" field template, ok := templateValue.(map[string]interface{}) if !ok { return nil, fmt.Errorf("template field must be a map, got %T", templateValue) } + if len(template) == 0 { + return nil, fmt.Errorf("YAML must contain either a 'template' or 'agent' field with the agent definition. See https://github.com/microsoft/AgentSchema for the expected format") + } var err error templateBytes, err = yaml.Marshal(template) if err != nil { @@ -68,14 +71,17 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { if !ok { return nil, fmt.Errorf("agent field must be a map, got %T", agentValue) } + if len(template) == 0 { + return nil, fmt.Errorf("YAML must contain either a 'template' or 'agent' field with the agent definition. See https://github.com/microsoft/AgentSchema for the expected format") + } var err error templateBytes, err = yaml.Marshal(template) if err != nil { return nil, fmt.Errorf("failed to marshal agent: %w", err) } } else { - // Standalone agent format - the entire document is the agent definition - templateBytes = manifestYamlContent + // Neither "template" nor "agent" field found - return error + return nil, fmt.Errorf("YAML must contain either a 'template' or 'agent' field with the agent definition. See https://github.com/microsoft/AgentSchema for the expected format") } var agentDef AgentDefinition 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 46eb30125aa..0ee86969ab3 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 @@ -72,7 +72,81 @@ agent: } } -// TestExtractAgentDefinition_WithoutTemplateField tests parsing YAML without a template field (standalone format) +// TestExtractAgentDefinition_BothTemplateAndAgent tests that template takes precedence when both fields are present +func TestExtractAgentDefinition_BothTemplateAndAgent(t *testing.T) { + yamlContent := []byte(` +name: test-manifest +template: + kind: hosted + name: test-agent-from-template + description: Agent from template field + protocols: + - protocol: responses + version: v1 +agent: + kind: hosted + name: test-agent-from-agent + description: Agent from agent field + protocols: + - protocol: responses + version: v1 +`) + + agent, err := ExtractAgentDefinition(yamlContent) + if err != nil { + t.Fatalf("ExtractAgentDefinition failed: %v", err) + } + + containerAgent, ok := agent.(ContainerAgent) + if !ok { + t.Fatalf("Expected ContainerAgent, got %T", agent) + } + + // Should use template field, not agent field + if containerAgent.Name != "test-agent-from-template" { + t.Errorf("Expected name 'test-agent-from-template' (from template field), got '%s'", containerAgent.Name) + } +} + +// TestExtractAgentDefinition_EmptyAgentField tests that an empty or null agent field returns an error +func TestExtractAgentDefinition_EmptyAgentField(t *testing.T) { + testCases := []struct { + name string + yaml string + }{ + { + name: "null agent field", + yaml: ` +name: test-manifest +agent: null +`, + }, + { + name: "empty agent field", + yaml: ` +name: test-manifest +agent: {} +`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := ExtractAgentDefinition([]byte(tc.yaml)) + if err == nil { + t.Fatal("Expected error for empty/null agent field, got nil") + } + + // The error should indicate missing template/agent field + expectedMsg := "YAML must contain either a 'template' or 'agent' field" + if !strings.Contains(err.Error(), expectedMsg) { + t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) + } + }) + } +} + +// TestExtractAgentDefinition_WithoutTemplateField tests that YAML without template or agent field returns an error func TestExtractAgentDefinition_WithoutTemplateField(t *testing.T) { yamlContent := []byte(` kind: hosted @@ -94,38 +168,14 @@ environment_variables: value: ${POSTGRES_DATABASE} `) - agent, err := ExtractAgentDefinition(yamlContent) - if err != nil { - t.Fatalf("ExtractAgentDefinition failed: %v", err) - } - - containerAgent, ok := agent.(ContainerAgent) - if !ok { - t.Fatalf("Expected ContainerAgent, got %T", agent) - } - - if containerAgent.Name != "lego-social-media-agent" { - t.Errorf("Expected name 'lego-social-media-agent', got '%s'", containerAgent.Name) - } - - if containerAgent.Kind != AgentKindHosted { - t.Errorf("Expected kind 'hosted', got '%s'", containerAgent.Kind) - } - - if containerAgent.Description == nil || *containerAgent.Description == "" { - t.Error("Expected description to be set") - } - - if containerAgent.Metadata == nil { - t.Error("Expected metadata to be set") - } - - if len(containerAgent.Protocols) == 0 { - t.Error("Expected protocols to be set") + _, err := ExtractAgentDefinition(yamlContent) + if err == nil { + t.Fatal("Expected error for YAML without template or agent field, got nil") } - if containerAgent.EnvironmentVariables == nil || len(*containerAgent.EnvironmentVariables) == 0 { - t.Error("Expected environment_variables to be set") + expectedMsg := "YAML must contain either a 'template' or 'agent' field" + if !strings.Contains(err.Error(), expectedMsg) { + t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } } @@ -147,26 +197,26 @@ template: "this is not a map" } } -// TestExtractAgentDefinition_MissingKind tests that missing kind field returns an error -func TestExtractAgentDefinition_MissingKind(t *testing.T) { +// TestExtractAgentDefinition_MissingTemplateOrAgentField tests that YAML without template or agent field returns an error +func TestExtractAgentDefinition_MissingTemplateOrAgentField(t *testing.T) { yamlContent := []byte(` name: test-agent -description: Test agent without kind field +description: Test agent without template or agent field `) _, err := ExtractAgentDefinition(yamlContent) if err == nil { - t.Fatal("Expected error for missing kind field, got nil") + t.Fatal("Expected error for YAML without template or agent field, got nil") } - expectedMsg := "unrecognized agent kind" + expectedMsg := "YAML must contain either a 'template' or 'agent' field" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } } -// TestLoadAndValidateAgentManifest_StandaloneFormat tests the full validation flow with standalone format -func TestLoadAndValidateAgentManifest_StandaloneFormat(t *testing.T) { +// TestLoadAndValidateAgentManifest_WithoutTemplateField tests that YAML without template or agent field returns an error +func TestLoadAndValidateAgentManifest_WithoutTemplateField(t *testing.T) { yamlContent := []byte(` kind: hosted name: test-standalone-agent @@ -176,28 +226,21 @@ protocols: version: v1 `) - manifest, err := LoadAndValidateAgentManifest(yamlContent) - if err != nil { - t.Fatalf("LoadAndValidateAgentManifest failed: %v", err) - } - - if manifest == nil { - t.Fatal("Expected manifest to be non-nil") - } - - containerAgent, ok := manifest.Template.(ContainerAgent) - if !ok { - t.Fatalf("Expected Template to be ContainerAgent, got %T", manifest.Template) + _, err := LoadAndValidateAgentManifest(yamlContent) + if err == nil { + t.Fatal("Expected error for YAML without template or agent field, got nil") } - if containerAgent.Name != "test-standalone-agent" { - t.Errorf("Expected name 'test-standalone-agent', got '%s'", containerAgent.Name) + expectedMsg := "YAML must contain either a 'template' or 'agent' field" + if !strings.Contains(err.Error(), expectedMsg) { + t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } } // TestExtractAgentDefinition_IssueExample tests the exact YAML from the GitHub issue func TestExtractAgentDefinition_IssueExample(t *testing.T) { // This is the exact YAML from the GitHub issue that was causing the panic + // It should now return a proper error message instead of panicking yamlContent := []byte(`# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml kind: hosted @@ -223,31 +266,23 @@ environment_variables: value: ${POSTGRES_DATABASE} `) - agent, err := ExtractAgentDefinition(yamlContent) - if err != nil { - t.Fatalf("ExtractAgentDefinition failed with issue example: %v", err) - } - - containerAgent, ok := agent.(ContainerAgent) - if !ok { - t.Fatalf("Expected ContainerAgent, got %T", agent) - } - - if containerAgent.Name != "lego-social-media-agent" { - t.Errorf("Expected name 'lego-social-media-agent', got '%s'", containerAgent.Name) + _, err := ExtractAgentDefinition(yamlContent) + if err == nil { + t.Fatal("Expected error for YAML without template or agent field, got nil") } - if containerAgent.Kind != AgentKindHosted { - t.Errorf("Expected kind 'hosted', got '%s'", containerAgent.Kind) + expectedMsg := "YAML must contain either a 'template' or 'agent' field" + if !strings.Contains(err.Error(), expectedMsg) { + t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } - // Test full validation flow - manifest, err := LoadAndValidateAgentManifest(yamlContent) - if err != nil { - t.Fatalf("LoadAndValidateAgentManifest failed with issue example: %v", err) + // Test full validation flow - should also return error + _, err = LoadAndValidateAgentManifest(yamlContent) + if err == nil { + t.Fatal("Expected error from LoadAndValidateAgentManifest for YAML without template or agent field, got nil") } - if manifest.Name != "lego-social-media-agent" { - t.Errorf("Expected manifest name 'lego-social-media-agent', got '%s'", manifest.Name) + if !strings.Contains(err.Error(), expectedMsg) { + t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } } From 748fbe79ba0c75fb7e6392e75be62814926f7b39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:42:05 +0000 Subject: [PATCH 07/12] Revert protobuf version change in microsoft.azd.extensions Remove changes to requirements.txt per review feedback - the protobuf update is not related to the agent.yaml parsing fix and should be handled in a separate PR. Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- .../internal/resources/languages/python/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/extensions/microsoft.azd.extensions/internal/resources/languages/python/requirements.txt b/cli/azd/extensions/microsoft.azd.extensions/internal/resources/languages/python/requirements.txt index d097860265c..b8e8bb17834 100644 --- a/cli/azd/extensions/microsoft.azd.extensions/internal/resources/languages/python/requirements.txt +++ b/cli/azd/extensions/microsoft.azd.extensions/internal/resources/languages/python/requirements.txt @@ -1,6 +1,6 @@ grpcio==1.71.0 grpcio-tools==1.71.0 -protobuf==6.33.5 +protobuf==5.29.5 grpc-interceptor==0.15.4 azure.identity== 1.21.0 azure.mgmt.resource== 22.0.0 From 707370572e4e7ded327b79c4ce618ad7227cd287 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:48:29 +0000 Subject: [PATCH 08/12] Standardize error messages to use AgentManifest format Update error messages to follow the existing pattern: "YAML content does not conform to AgentManifest format" This provides consistency with error messages in LoadAndValidateAgentManifest and makes it clearer to users what the expected format is. Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- .../internal/pkg/agents/agent_yaml/parse.go | 6 +++--- .../internal/pkg/agents/agent_yaml/parse_test.go | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go index 265f6d1783f..f10210eff26 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go @@ -58,7 +58,7 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { return nil, fmt.Errorf("template field must be a map, got %T", templateValue) } if len(template) == 0 { - return nil, fmt.Errorf("YAML must contain either a 'template' or 'agent' field with the agent definition. See https://github.com/microsoft/AgentSchema for the expected format") + return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: template field is empty. See https://github.com/microsoft/AgentSchema for the expected format") } var err error templateBytes, err = yaml.Marshal(template) @@ -72,7 +72,7 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { return nil, fmt.Errorf("agent field must be a map, got %T", agentValue) } if len(template) == 0 { - return nil, fmt.Errorf("YAML must contain either a 'template' or 'agent' field with the agent definition. See https://github.com/microsoft/AgentSchema for the expected format") + return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: agent field is empty. See https://github.com/microsoft/AgentSchema for the expected format") } var err error templateBytes, err = yaml.Marshal(template) @@ -81,7 +81,7 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { } } else { // Neither "template" nor "agent" field found - return error - return nil, fmt.Errorf("YAML must contain either a 'template' or 'agent' field with the agent definition. See https://github.com/microsoft/AgentSchema for the expected format") + return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: must contain either 'template' or 'agent' field. See https://github.com/microsoft/AgentSchema for the expected format") } var agentDef AgentDefinition 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 0ee86969ab3..46ca42f9cc5 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 @@ -138,7 +138,7 @@ agent: {} } // The error should indicate missing template/agent field - expectedMsg := "YAML must contain either a 'template' or 'agent' field" + expectedMsg := "YAML content does not conform to AgentManifest format" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } @@ -173,7 +173,7 @@ environment_variables: t.Fatal("Expected error for YAML without template or agent field, got nil") } - expectedMsg := "YAML must contain either a 'template' or 'agent' field" + expectedMsg := "YAML content does not conform to AgentManifest format" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } @@ -209,7 +209,7 @@ description: Test agent without template or agent field t.Fatal("Expected error for YAML without template or agent field, got nil") } - expectedMsg := "YAML must contain either a 'template' or 'agent' field" + expectedMsg := "YAML content does not conform to AgentManifest format" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } @@ -231,7 +231,7 @@ protocols: t.Fatal("Expected error for YAML without template or agent field, got nil") } - expectedMsg := "YAML must contain either a 'template' or 'agent' field" + expectedMsg := "YAML content does not conform to AgentManifest format" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } @@ -271,7 +271,7 @@ environment_variables: t.Fatal("Expected error for YAML without template or agent field, got nil") } - expectedMsg := "YAML must contain either a 'template' or 'agent' field" + expectedMsg := "YAML content does not conform to AgentManifest format" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } From bdb2c84133d2e9f2f1dda3b8c81954022c4e1f6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 02:13:48 +0000 Subject: [PATCH 09/12] Remove agent field support - only template field is valid Per review feedback, the 'agent' field is not valid in AgentManifest format. - Remove agent field handling from ExtractAgentDefinition - Update error messages to only mention 'template' field - Remove tests for agent field, BothTemplateAndAgent precedence - Update sample files to use 'template' instead of 'agent' - Rename EmptyAgentField test to EmptyTemplateField The AgentManifest struct defines yaml:"template" as the only valid field. Valid manifest examples: https://github.com/microsoft-foundry/foundry-samples Co-authored-by: rajeshkamal5050 <11532743+rajeshkamal5050@users.noreply.github.com> --- .../internal/pkg/agents/agent_yaml/parse.go | 21 +--- .../pkg/agents/agent_yaml/parse_test.go | 112 ++++-------------- .../samples/declarativeNoTools/agent.yaml | 2 +- .../tests/samples/githubMcpAgent/agent.yaml | 2 +- 4 files changed, 27 insertions(+), 110 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go index f10210eff26..793156e8c5a 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go @@ -47,8 +47,7 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { return nil, fmt.Errorf("YAML content is not valid: %w", err) } - // Handle both manifest format with "template" or "agent" field - // If both "template" and "agent" are present, "template" takes precedence and "agent" is ignored + // Handle manifest format with "template" field var templateBytes []byte if templateValue, exists := genericManifest["template"]; exists && templateValue != nil { @@ -65,23 +64,9 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { if err != nil { return nil, fmt.Errorf("failed to marshal template: %w", err) } - } else if agentValue, exists := genericManifest["agent"]; exists && agentValue != nil { - // Manifest format with "agent" field (alias for "template") - template, ok := agentValue.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("agent field must be a map, got %T", agentValue) - } - if len(template) == 0 { - return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: agent field is empty. See https://github.com/microsoft/AgentSchema for the expected format") - } - var err error - templateBytes, err = yaml.Marshal(template) - if err != nil { - return nil, fmt.Errorf("failed to marshal agent: %w", err) - } } else { - // Neither "template" nor "agent" field found - return error - return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: must contain either 'template' or 'agent' field. See https://github.com/microsoft/AgentSchema for the expected format") + // "template" field not found - return error + return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: must contain 'template' field. See https://github.com/microsoft/AgentSchema for the expected format") } var agentDef AgentDefinition 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 46ca42f9cc5..3679d528713 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 @@ -40,92 +40,24 @@ template: } } -// TestExtractAgentDefinition_WithAgentField tests parsing YAML with an agent field (alias for template) -func TestExtractAgentDefinition_WithAgentField(t *testing.T) { - yamlContent := []byte(` -name: test-manifest -agent: - kind: hosted - name: test-agent-with-agent-field - description: Test agent with agent field - protocols: - - protocol: responses - version: v1 -`) - - agent, err := ExtractAgentDefinition(yamlContent) - if err != nil { - t.Fatalf("ExtractAgentDefinition failed: %v", err) - } - - containerAgent, ok := agent.(ContainerAgent) - if !ok { - t.Fatalf("Expected ContainerAgent, got %T", agent) - } - - if containerAgent.Name != "test-agent-with-agent-field" { - t.Errorf("Expected name 'test-agent-with-agent-field', got '%s'", containerAgent.Name) - } - - if containerAgent.Kind != AgentKindHosted { - t.Errorf("Expected kind 'hosted', got '%s'", containerAgent.Kind) - } -} - -// TestExtractAgentDefinition_BothTemplateAndAgent tests that template takes precedence when both fields are present -func TestExtractAgentDefinition_BothTemplateAndAgent(t *testing.T) { - yamlContent := []byte(` -name: test-manifest -template: - kind: hosted - name: test-agent-from-template - description: Agent from template field - protocols: - - protocol: responses - version: v1 -agent: - kind: hosted - name: test-agent-from-agent - description: Agent from agent field - protocols: - - protocol: responses - version: v1 -`) - - agent, err := ExtractAgentDefinition(yamlContent) - if err != nil { - t.Fatalf("ExtractAgentDefinition failed: %v", err) - } - - containerAgent, ok := agent.(ContainerAgent) - if !ok { - t.Fatalf("Expected ContainerAgent, got %T", agent) - } - - // Should use template field, not agent field - if containerAgent.Name != "test-agent-from-template" { - t.Errorf("Expected name 'test-agent-from-template' (from template field), got '%s'", containerAgent.Name) - } -} - -// TestExtractAgentDefinition_EmptyAgentField tests that an empty or null agent field returns an error -func TestExtractAgentDefinition_EmptyAgentField(t *testing.T) { +// TestExtractAgentDefinition_EmptyTemplateField tests that an empty or null template field returns an error +func TestExtractAgentDefinition_EmptyTemplateField(t *testing.T) { testCases := []struct { name string yaml string }{ { - name: "null agent field", + name: "null template field", yaml: ` name: test-manifest -agent: null +template: null `, }, { - name: "empty agent field", + name: "empty template field", yaml: ` name: test-manifest -agent: {} +template: {} `, }, } @@ -134,10 +66,10 @@ agent: {} t.Run(tc.name, func(t *testing.T) { _, err := ExtractAgentDefinition([]byte(tc.yaml)) if err == nil { - t.Fatal("Expected error for empty/null agent field, got nil") + t.Fatal("Expected error for empty/null template field, got nil") } - // The error should indicate missing template/agent field + // The error should indicate the template field issue expectedMsg := "YAML content does not conform to AgentManifest format" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) @@ -146,7 +78,7 @@ agent: {} } } -// TestExtractAgentDefinition_WithoutTemplateField tests that YAML without template or agent field returns an error +// TestExtractAgentDefinition_WithoutTemplateField tests that YAML without template field returns an error func TestExtractAgentDefinition_WithoutTemplateField(t *testing.T) { yamlContent := []byte(` kind: hosted @@ -170,10 +102,10 @@ environment_variables: _, err := ExtractAgentDefinition(yamlContent) if err == nil { - t.Fatal("Expected error for YAML without template or agent field, got nil") + t.Fatal("Expected error for YAML without template field, got nil") } - expectedMsg := "YAML content does not conform to AgentManifest format" + expectedMsg := "must contain 'template' field" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } @@ -197,25 +129,25 @@ template: "this is not a map" } } -// TestExtractAgentDefinition_MissingTemplateOrAgentField tests that YAML without template or agent field returns an error -func TestExtractAgentDefinition_MissingTemplateOrAgentField(t *testing.T) { +// TestExtractAgentDefinition_MissingTemplateField tests that YAML without template field returns an error +func TestExtractAgentDefinition_MissingTemplateField(t *testing.T) { yamlContent := []byte(` name: test-agent -description: Test agent without template or agent field +description: Test agent without template field `) _, err := ExtractAgentDefinition(yamlContent) if err == nil { - t.Fatal("Expected error for YAML without template or agent field, got nil") + t.Fatal("Expected error for YAML without template field, got nil") } - expectedMsg := "YAML content does not conform to AgentManifest format" + expectedMsg := "must contain 'template' field" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } } -// TestLoadAndValidateAgentManifest_WithoutTemplateField tests that YAML without template or agent field returns an error +// TestLoadAndValidateAgentManifest_WithoutTemplateField tests that YAML without template field returns an error func TestLoadAndValidateAgentManifest_WithoutTemplateField(t *testing.T) { yamlContent := []byte(` kind: hosted @@ -228,10 +160,10 @@ protocols: _, err := LoadAndValidateAgentManifest(yamlContent) if err == nil { - t.Fatal("Expected error for YAML without template or agent field, got nil") + t.Fatal("Expected error for YAML without template field, got nil") } - expectedMsg := "YAML content does not conform to AgentManifest format" + expectedMsg := "must contain 'template' field" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } @@ -268,10 +200,10 @@ environment_variables: _, err := ExtractAgentDefinition(yamlContent) if err == nil { - t.Fatal("Expected error for YAML without template or agent field, got nil") + t.Fatal("Expected error for YAML without template field, got nil") } - expectedMsg := "YAML content does not conform to AgentManifest format" + expectedMsg := "must contain 'template' field" if !strings.Contains(err.Error(), expectedMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", expectedMsg, err.Error()) } @@ -279,7 +211,7 @@ environment_variables: // Test full validation flow - should also return error _, err = LoadAndValidateAgentManifest(yamlContent) if err == nil { - t.Fatal("Expected error from LoadAndValidateAgentManifest for YAML without template or agent field, got nil") + t.Fatal("Expected error from LoadAndValidateAgentManifest for YAML without template field, got nil") } if !strings.Contains(err.Error(), expectedMsg) { diff --git a/cli/azd/extensions/azure.ai.agents/tests/samples/declarativeNoTools/agent.yaml b/cli/azd/extensions/azure.ai.agents/tests/samples/declarativeNoTools/agent.yaml index 279897063b1..6458dc82dfb 100644 --- a/cli/azd/extensions/azure.ai.agents/tests/samples/declarativeNoTools/agent.yaml +++ b/cli/azd/extensions/azure.ai.agents/tests/samples/declarativeNoTools/agent.yaml @@ -1,4 +1,4 @@ -agent: +template: kind: prompt name: Learn French Agent description: |- diff --git a/cli/azd/extensions/azure.ai.agents/tests/samples/githubMcpAgent/agent.yaml b/cli/azd/extensions/azure.ai.agents/tests/samples/githubMcpAgent/agent.yaml index 59d98824513..1abf0813084 100644 --- a/cli/azd/extensions/azure.ai.agents/tests/samples/githubMcpAgent/agent.yaml +++ b/cli/azd/extensions/azure.ai.agents/tests/samples/githubMcpAgent/agent.yaml @@ -1,4 +1,4 @@ -agent: +template: kind: prompt name: github-agent description: An agent leveraging github-mcp to assist with GitHub-related tasks. From dc2875d10905d2c6d903211ab3fd0db0da66e821 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:29:46 +0000 Subject: [PATCH 10/12] Update error messages with better documentation links Per review feedback, update error messages to point to: - AgentManifest reference: https://microsoft.github.io/AgentSchema/reference/agentmanifest - Sample manifests: https://github.com/microsoft-foundry/foundry-samples This provides users with both the format specification and working examples when they encounter YAML format errors. Co-authored-by: JeffreyCA <9157833+JeffreyCA@users.noreply.github.com> --- .../azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go index 793156e8c5a..8ac33c20097 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go @@ -57,7 +57,7 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { return nil, fmt.Errorf("template field must be a map, got %T", templateValue) } if len(template) == 0 { - return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: template field is empty. See https://github.com/microsoft/AgentSchema for the expected format") + return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: template field is empty. See https://microsoft.github.io/AgentSchema/reference/agentmanifest for the expected format and https://github.com/microsoft-foundry/foundry-samples for examples") } var err error templateBytes, err = yaml.Marshal(template) @@ -66,7 +66,7 @@ func ExtractAgentDefinition(manifestYamlContent []byte) (any, error) { } } else { // "template" field not found - return error - return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: must contain 'template' field. See https://github.com/microsoft/AgentSchema for the expected format") + return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: must contain 'template' field. See https://microsoft.github.io/AgentSchema/reference/agentmanifest for the expected format and https://github.com/microsoft-foundry/foundry-samples for examples") } var agentDef AgentDefinition From 885e09fb4c06561fafbffb0278db85488b5f8a90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 23:37:48 +0000 Subject: [PATCH 11/12] Enable unit tests for azure.ai.agents extension in CI Add ci-test.ps1 script and remove SkipTests flag to enable unit tests in the CI pipeline for azure.ai.agents extension. Changes: - Create ci-test.ps1 script matching other extensions (uses gotestsum or go test) - Remove SkipTests: true from release-ext-azure-ai-agents.yml pipeline This aligns with PR #6589 to enable unit test validation in the CI pipeline. Co-authored-by: JeffreyCA <9157833+JeffreyCA@users.noreply.github.com> --- .../extensions/azure.ai.agents/ci-test.ps1 | 27 +++++++++++++++++++ eng/pipelines/release-ext-azure-ai-agents.yml | 1 - 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 cli/azd/extensions/azure.ai.agents/ci-test.ps1 diff --git a/cli/azd/extensions/azure.ai.agents/ci-test.ps1 b/cli/azd/extensions/azure.ai.agents/ci-test.ps1 new file mode 100644 index 00000000000..2e66dea3138 --- /dev/null +++ b/cli/azd/extensions/azure.ai.agents/ci-test.ps1 @@ -0,0 +1,27 @@ +$gopath = go env GOPATH +$gotestsumBinary = "gotestsum" +if ($IsWindows) { + $gotestsumBinary += ".exe" +} +$gotestsum = Join-Path $gopath "bin" $gotestsumBinary + +Write-Host "Running unit tests..." + +if (Test-Path $gotestsum) { + # Use gotestsum for better output formatting and summary + & $gotestsum --format testname -- ./... -count=1 +} else { + # Fallback to go test if gotestsum is not installed + Write-Host "gotestsum not found, using go test..." -ForegroundColor Yellow + go test ./... -v -count=1 +} + +if ($LASTEXITCODE -ne 0) { + Write-Host "" + Write-Host "Tests failed with exit code: $LASTEXITCODE" -ForegroundColor Red + exit $LASTEXITCODE +} + +Write-Host "" +Write-Host "All tests passed!" -ForegroundColor Green +exit 0 diff --git a/eng/pipelines/release-ext-azure-ai-agents.yml b/eng/pipelines/release-ext-azure-ai-agents.yml index e7d75fee630..264bc42797e 100644 --- a/eng/pipelines/release-ext-azure-ai-agents.yml +++ b/eng/pipelines/release-ext-azure-ai-agents.yml @@ -31,4 +31,3 @@ extends: AzdExtensionId: azure.ai.agents SanitizedExtensionId: azure-ai-agents AzdExtensionDirectory: cli/azd/extensions/azure.ai.agents - SkipTests: true From 7aea223963b8db475b037a97232fb9cad2334263 Mon Sep 17 00:00:00 2001 From: Jeffrey Chen Date: Tue, 3 Feb 2026 23:50:04 +0000 Subject: [PATCH 12/12] Remove repetition in error messages --- cli/azd/extensions/azure.ai.agents/internal/cmd/init.go | 2 +- .../azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 8a322daa452..73591f94ff9 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go +++ b/cli/azd/extensions/azure.ai.agents/internal/cmd/init.go @@ -1087,7 +1087,7 @@ func (a *InitAction) downloadAgentYaml( // Parse and validate the YAML content against AgentManifest structure agentManifest, err := agent_yaml.LoadAndValidateAgentManifest(content) if err != nil { - return nil, "", fmt.Errorf("AgentManifest %w", err) + return nil, "", err } fmt.Println("✓ YAML content successfully validated against AgentManifest format") diff --git a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go index 8ac33c20097..e81b3812e80 100644 --- a/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go +++ b/cli/azd/extensions/azure.ai.agents/internal/pkg/agents/agent_yaml/parse.go @@ -22,13 +22,13 @@ func LoadAndValidateAgentManifest(manifestYamlContent []byte) (*AgentManifest, e agentDef, err := ExtractAgentDefinition(manifestYamlContent) if err != nil { - return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: %w", err) + return nil, err } manifest.Template = agentDef resourceDefs, err := ExtractResourceDefinitions(manifestYamlContent) if err != nil { - return nil, fmt.Errorf("YAML content does not conform to AgentManifest format: %w", err) + return nil, err } manifest.Resources = resourceDefs