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
22 changes: 13 additions & 9 deletions internal/a2a/models/agent_card.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,14 @@ func (a *AgentCard) UnmarshalJSON(data []byte) error {

// Check if capabilities is present and try legacy formats first
var hasLegacyCap bool
var legacyCapabilities []Capability
if capRaw, exists := raw["capabilities"]; exists {
// Try legacy array format []Capability
var legacyCaps []Capability
if err := json.Unmarshal(capRaw, &legacyCaps); err == nil && len(legacyCaps) > 0 {
// Convert legacy array to new Capabilities object structure
// Store in metadata for backward compatibility
if a.Metadata == nil {
a.Metadata = make(map[string]any)
}
a.Metadata["legacyCapabilities"] = legacyCaps
// Store in a temporary variable to preserve across struct assignment
legacyCapabilities = legacyCaps
hasLegacyCap = true
} else if err == nil {
// Empty array, still legacy format
Expand Down Expand Up @@ -147,10 +145,8 @@ func (a *AgentCard) UnmarshalJSON(data []byte) error {
Description: description,
})
}
if a.Metadata == nil {
a.Metadata = make(map[string]any)
}
a.Metadata["legacyCapabilities"] = legacyCaps
// Store in a temporary variable to preserve across struct assignment
legacyCapabilities = legacyCaps
hasLegacyCap = true
}
} else {
Expand All @@ -177,6 +173,14 @@ func (a *AgentCard) UnmarshalJSON(data []byte) error {
return err
}
*a = AgentCard(*aux)

// Restore legacy capabilities after struct assignment
if len(legacyCapabilities) > 0 {
if a.Metadata == nil {
a.Metadata = make(map[string]any)
}
a.Metadata["legacyCapabilities"] = legacyCapabilities
Comment on lines +177 to +182

Copilot AI Jan 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restoring legacyCapabilities after *a = AgentCard(*aux) will overwrite any metadata.legacyCapabilities value that was already present in the incoming JSON (previously it would have been preserved because the struct assignment overwrote your pre-populated metadata). Consider only setting this key when it’s not already present, or using a different key/merge strategy to avoid clobbering user-provided metadata.

Suggested change
// Restore legacy capabilities after struct assignment
if len(legacyCapabilities) > 0 {
if a.Metadata == nil {
a.Metadata = make(map[string]any)
}
a.Metadata["legacyCapabilities"] = legacyCapabilities
// Restore legacy capabilities after struct assignment.
// Only set the metadata key if it is not already present to avoid
// overwriting any user-provided metadata.legacyCapabilities value.
if len(legacyCapabilities) > 0 {
if a.Metadata == nil {
a.Metadata = make(map[string]any)
}
if _, exists := a.Metadata["legacyCapabilities"]; !exists {
a.Metadata["legacyCapabilities"] = legacyCapabilities
}

Copilot uses AI. Check for mistakes.
}
} else {
// New schema format, unmarshal normally
aux := &Alias{}
Expand Down
121 changes: 121 additions & 0 deletions tests/a2a/agent_card_metadata_preservation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package a2a

import (
"encoding/json"
"testing"

"github.com/techbuzzz/agent-shaker/internal/a2a/models"
)

func TestAgentCardUnmarshal_MetadataPreservation(t *testing.T) {
// Test that legacy capabilities are preserved in metadata after struct assignment
jsonData := `{
"name": "Test Agent",
"version": "1.0.0",
"capabilities": [
{
"type": "task",
"description": "Task execution"
},
{
"type": "streaming",
"description": "SSE streaming"
}
],
"endpoints": [],
"metadata": {
"existingKey": "existingValue"
}
}`

var card models.AgentCard
err := json.Unmarshal([]byte(jsonData), &card)
if err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}

// Check if metadata exists
if card.Metadata == nil {
t.Fatal("Metadata is nil")
}

// Check if legacy capabilities are preserved
legacyCaps, ok := card.Metadata["legacyCapabilities"]
if !ok {
t.Fatal("legacyCapabilities not found in metadata - the fix failed!")
}

caps, ok := legacyCaps.([]models.Capability)
if !ok {
t.Fatalf("legacyCapabilities is not []Capability, got: %T", legacyCaps)
}

if len(caps) != 2 {
t.Fatalf("Expected 2 legacy capabilities, got %d", len(caps))
}

if caps[0].Type != "task" || caps[0].Description != "Task execution" {
t.Errorf("First capability incorrect: %+v", caps[0])
}

if caps[1].Type != "streaming" || caps[1].Description != "SSE streaming" {
t.Errorf("Second capability incorrect: %+v", caps[1])
}

// Check if existing metadata is also preserved
if existingVal, ok := card.Metadata["existingKey"].(string); !ok || existingVal != "existingValue" {
t.Errorf("Existing metadata not preserved correctly: %v", card.Metadata["existingKey"])
}

t.Log("✓ Legacy capabilities correctly preserved in metadata after struct assignment")
}

func TestAgentCardUnmarshal_ObjectFormat_MetadataPreservation(t *testing.T) {
// Test that legacy object format capabilities are also preserved
jsonData := `{
"name": "Test Agent",
"version": "1.0.0",
"capabilities": {
"task": "Task execution",
"streaming": "SSE streaming",
"artifacts": "Artifact sharing"
},
"endpoints": [],
"metadata": {
"customField": "customValue"
}
}`

var card models.AgentCard
err := json.Unmarshal([]byte(jsonData), &card)
if err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}

// Check if metadata exists
if card.Metadata == nil {
t.Fatal("Metadata is nil")
}

// Check if legacy capabilities are preserved
legacyCaps, ok := card.Metadata["legacyCapabilities"]
if !ok {
t.Fatal("legacyCapabilities not found in metadata - the fix failed!")
}

caps, ok := legacyCaps.([]models.Capability)
if !ok {
t.Fatalf("legacyCapabilities is not []Capability, got: %T", legacyCaps)
}

if len(caps) != 3 {
t.Fatalf("Expected 3 legacy capabilities, got %d", len(caps))
}

// Check if existing metadata is also preserved
if customVal, ok := card.Metadata["customField"].(string); !ok || customVal != "customValue" {
t.Errorf("Existing metadata not preserved correctly: %v", card.Metadata["customField"])
}

t.Log("✓ Legacy object format capabilities correctly preserved in metadata")
}
Loading