a2aproject / a2aproject/a2a-dotnet

[Feat]: Enforce required Message fields at compile-time or runtime to ensure A2A spec compliance

Open
#198 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
C#
Stars
262
Forks
64
Avg merge
5d 2h
Merged PRs (30d)
31

Description

### Is your feature request related to a problem? Please describe.

The A2A specification (v0.3.0) defines `Message.messageId` as a **required field** (Section 6.4):

```typescript
export interface Message {
readonly role: "user" | "agent";
parts: Part[];
messageId: string; // Required - must be a UUID
taskId?: string; // Optional
contextId?: string; // Optional
kind: "message";
}
```

However, the current C# SDK allows developers to construct `AgentMessage` (or `Message`) objects without providing a `messageId`, which results in **spec non-compliant responses** that may be rejected by clients or cause interoperability issues.

### Example of Current Problematic Code

```csharp
await _taskManager.UpdateStatusAsync(taskId, TaskState.InputRequired, new AgentMessage()
{
Parts = [new TextPart() { Text = "When ready say go ahead" }],
},
cancellationToken: cancellationToken);
```

This compiles and runs without errors, but produces a message with a missing or empty `messageId`, violating the A2A spec.

## Impact

- **Spec non-compliance**: Messages without `messageId` violate the A2A protocol
- **Interoperability issues**: Clients expecting spec-compliant messages may reject responses
- **Developer confusion**: No indication that a required field is missing until runtime testing
- **Inconsistent examples**: Some SDK examples (like `EchoAgent.cs`) include `MessageId`, while developers writing new agents may forget to include it

### Describe the solution you'd like

Add compile-time or runtime enforcement for required Message fields. Consider one or more of these approaches:

### Option 1: Use C# 11 `required` Keyword (Preferred)

```csharp
public class AgentMessage
{
public required string MessageId { get; init; }
public required MessageRole Role { get; init; }
public required Part[] Parts { get; init; }
public string? TaskId { get; init; }
public string? ContextId { get; init; }
public string Kind { get; init; } = "message";
}
```

**Benefits:**
- ✅ Compile-time errors when required fields are missing
- ✅ Clear intent in the type system
- ✅ IDE support (IntelliSense shows required fields)
- ✅ No runtime overhead

**Requirement:** Requires C# 11+ (.NET 7+), which the SDK already targets (.NET 8/9)

### Describe alternatives you've considered

### Option 2: Runtime Validation

Add validation in the `TaskManager` or serialization layer:

```csharp
private void ValidateMessage(AgentMessage message)
{
if (string.IsNullOrWhiteSpace(message.MessageId))
{
throw new ArgumentException(
"Message.MessageId is required by A2A specification (Section 6.4). " +
"Assign a unique identifier using: MessageId = Guid.NewGuid().ToString()",
nameof(message));
}

// Validate other required fields...
}
```

### Option 3: Builder Pattern with Validation

Provide a fluent builder that ensures required fields:

```csharp
var message = AgentMessage.Create()
.WithMessageId(Guid.NewGuid().ToString()) // Required
.WithRole(MessageRole.Agent) // Required
.WithParts(new TextPart { Text = "..." }) // Required
.WithTaskId(taskId) // Optional
.WithContextId(contextId) // Optional
.Build();
```

### Option 4: Analyzer Package (Advanced)

Create a Roslyn analyzer that warns at compile-time when `MessageId` is not set during object initialization.

## Recommended Approach

**Combination of Option 1 + Option 2:**
1. Use `required` keyword for compile-time safety (Option 1)
2. Add runtime validation as a safety net (Option 2)
3. Update all SDK examples and documentation to show the correct pattern

This provides defense-in-depth and matches the strictness of the Node.js SDK while leveraging C#'s type system.

## Additional Recommendations

### Also Consider for Optional But Recommended Fields

While `taskId` and `contextId` are optional per the spec schema, **all official spec examples include them** (Section 9.4). Consider:

- Adding XML documentation comments indicating these are "optional but recommended"
- Providing helper methods that make it easy to include them
- Adding analyzer warnings (not errors) when they're omitted

```csharp
///
/// Optional but recommended: Links this message to its parent task.
/// All A2A specification examples include this field.
///
public string? TaskId { get; init; }
```

### Additional context

Used GitHub Copilot for analysis

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Contributor guide

Open the contributing guide

Research direction

The issue is about the AgentMessage class and the A2A spec. Start by examining the AgentMessage definition in the codebase, likely in a file like AgentMessage.cs. Review how messages are currently constructed and serialized. The goal is to modify the class to enforce required fields, possibly using the 'required' keyword, and add validation. Check existing tests for message creation to understand the current behavior and update them.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend-api-design
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.