Skip to content

.NET: A2A AgentRunOptions are not passed on #2974

Closed

Description

From our main application we are calling an agent using A2A, with the following example code. We are creating the AgentRunOptions to retrieve the token usage from the request to the LLM the Agent wil do.

var a2aClient = new A2AClient(new Uri(agentUrl));
var agent = a2aClient.GetAIAgent();

var messages = new List<ChatMessage>
{
    new ChatMessage(ChatRole.User, "test Tell me something interesting.")
};

var dict = new AdditionalPropertiesDictionary
{
    { "model_extras", new { stream_options = new { include_usage = true } } }
};
var options = new AgentRunOptions { AdditionalProperties = dict };

var result = await agent.RunAsync(messages, thread: null, options: options);

In our project hosting the agent, we map the agent like this:

builder.Services.AddSingleton<TestAgent>();

var app = builder.Build();

var testAgent = app.Services.GetRequiredService<TestAgent>();
app.MapA2A(testAgent, "/agent/test", agentCard: new()
{
    Name = "Test Agent",
    Description = "A test agent for usage information",
    Version = "1.0.0",
});

When the requests arrives in the TestAgent the AgentRunOptions are null:

public override async Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null,
    AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
    using var activity = ActivitySource.StartActivity("TestAgent.Run");
    activity?.SetTag("agent.name", Name);
    activity?.SetTag("agent.id", Id);

    // Get Azure OpenAI configuration
    var endpoint = new Uri(configuration["Azure:Endpoint"]!);
    var apiKey = configuration["Azure:ApiKey"]!;
    var deploymentName = configuration["Azure:ModelId"] ?? "gpt-4.1";

    var userService = UserService.Create(httpContextAccessor.HttpContext);

    // Create Azure OpenAI client
    var client = new AzureOpenAIClient(endpoint, new AzureKeyCredential(apiKey));
    var chatClient = client.GetOpenAIResponseClient(deploymentName).AsIChatClient();

    var instructions = @"
        You are a helpful assistant. 
        Provide a brief, friendly response to the user's message.
        Keep responses concise (1-2 sentences).
    ";

    // Create ChatClientAgent
    var chatAgent = new ChatClientAgent(chatClient, instructions, "TestAgent");

    // Ensure options include usage information
    var runOptions = options ?? new AgentRunOptions();
    if (runOptions.AdditionalProperties == null || !runOptions.AdditionalProperties.ContainsKey("model_extras"))
    {
        runOptions.AdditionalProperties ??= new AdditionalPropertiesDictionary();
        runOptions.AdditionalProperties["model_extras"] = new { stream_options = new { include_usage = true } };
    }

    // Delegate to ChatClientAgent
    var result = await chatAgent.RunAsync(messages, null, options: runOptions, cancellationToken);

    // Extract the usage information from the result and include it in the result AdditionalProperties
    var usageJson = ExtractUsageFromResponse(result);
    if (!string.IsNullOrEmpty(usageJson))
    {
        result.AdditionalProperties ??= new AdditionalPropertiesDictionary();
        result.AdditionalProperties["usage"] = usageJson;

        if (result.Messages.Count > 0) {
            var lastMessage = result.Messages.Last();
            var content = lastMessage.Contents.LastOrDefault();
            if (content != null) {
                content.RawRepresentation = result.AdditionalProperties;
            }
        }
    }

    return result;
}

Is this intended behaviour?

Not sure I can ask a follow up question, but when the options are null, I hard code the AgentRunOptions to ask for token usage. The agent then returns an AgentRunResponse with usage statistics in it, except those never arrive in our main programming, calling the A2A Agent.

Activity

  1. added
    .NETUsage: [Issues, PRs], Target: .Net
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Dec 19, 2025
  2. ABHISHEKABHI52 commented on Dec 19, 2025

    @ABHISHEKABHI52

    Thanks for reviewing this issue.

    When calling an agent via A2A (A2AClient.GetAIAgent().RunAsync), the AgentRunOptions passed from the client side does not arrive in the hosted agent — it is always null.

    Observed behavior:

    • AgentRunOptions is correctly created and passed in the client call.
    • On the server side (TestAgent.RunAsync), the options parameter is always null.
    • Even if we add usage info into AgentRunResponse.AdditionalProperties, those values are not serialized back to the client.

    Expected behavior:

    • AgentRunOptions should be forwarded through the A2A transport layer so that agents can read them.
    • AgentRunResponse.AdditionalProperties (e.g., usage statistics) should be preserved and returned to the caller.

    Suggested fix:

    • Extend the A2A serialization contract to include AgentRunOptions and AdditionalProperties.
    • Ensure that AgentRunResponse metadata is not stripped when returning to the client.
    • Alternatively, if this is not intended to be supported, please document the limitation and recommend embedding metadata inside ChatMessage payloads as a workaround.

    This would allow scenarios like token usage reporting to work end‑to‑end without requiring custom hacks.

  3. added
    a2aUsage: [Issues, PRs], Target: A2A
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Jan 5, 2026
  4. moved this to In Progress in Agent Frameworkon Jan 7, 2026
  5. moved this from In Progress to In Review in Agent Frameworkon Jan 8, 2026
  6. moved this from In Review to Done in Agent Frameworkon Jan 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .Neta2aUsage: [Issues, PRs], Target: A2A

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions