-
Notifications
You must be signed in to change notification settings - Fork 45
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):
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
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
messageIdviolate 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) includeMessageId, 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)
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:
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:
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:
- Use
requiredkeyword for compile-time safety (Option 1) - Add runtime validation as a safety net (Option 2)
- 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
/// <summary>
/// Optional but recommended: Links this message to its parent task.
/// All A2A specification examples include this field.
/// </summary>
public string? TaskId { get; init; }Additional context
Used GitHub Copilot for analysis
Code of Conduct
- I agree to follow this project's Code of Conduct