Files
agent-framework/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentSession.cs
SergeyMenshykh dfc3079d68 .NET: Add A2A input-request content for human-in-the-loop scenarios (#5743)
* .NET: Add A2A input-request content for human-in-the-loop scenarios

Adds first-class support for handling user input requests from A2A agents
when they return an `input-required` task state.

- Add `A2AInputRequestContent` (wraps the requested `AIContent`) and
  `A2AInputResponseContent` (wraps the user's `AIContent` reply), with
  `CreateResponse` helper overloads on the request type.
- Surface input requests on `AgentResponse` / `AgentResponseUpdate` via
  `AgentTask` and `TaskStatusUpdateEvent` mappings.
- Link follow-up messages containing `A2AInputResponseContent` to the
  existing task via `TaskId` instead of `ReferenceTaskIds`.
- Add `A2AAgent_HumanInTheLoop` sample and register it in the solution
  and parent README.
- Add unit tests for the new types, extensions, and `A2AAgent` paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove unnecessary using directive flagged by CI format check

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* address feedback

* Guard against null TaskId when sending A2AInputResponseContent

Throw InvalidOperationException if TaskId is missing when the message
contains A2AInputResponseContent, preventing silent no-op responses.
Also adds tests for both RunAsync and RunStreamingAsync paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Leave Contents null for non-InputRequired status updates

Remove unnecessary '?? []' fallback so Contents stays null when there
are no input requests, matching the other update mapping patterns.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Use consistent GUID format for request IDs

Use ToString("N") to match message ID format used elsewhere in
the A2A component.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Remove Debug build exclusion for the HumanInTheLoop sample so it                                                                                                                                                                                                               participates in normal solution validation.

* Add missing using Microsoft.Extensions.AI to A2AAgent_HumanInTheLoop

The sample uses ChatMessage, TextContent, and ChatRole types from
Microsoft.Extensions.AI but was missing the using directive, causing
CS0246 build errors on all CI jobs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* change the way user input requests are handled based on pr review comments

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-12 13:10:18 +00:00

71 lines
2.3 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using TaskState = A2A.TaskState;
namespace Microsoft.Agents.AI.A2A;
/// <summary>
/// Session for A2A based agents.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public sealed class A2AAgentSession : AgentSession
{
internal A2AAgentSession()
{
}
[JsonConstructor]
internal A2AAgentSession(string? contextId, string? taskId, TaskState? taskState, AgentSessionStateBag? stateBag) : base(stateBag ?? new())
{
this.ContextId = contextId;
this.TaskId = taskId;
this.TaskState = taskState;
}
/// <summary>
/// Gets the ID for the current conversation with the A2A agent.
/// </summary>
[JsonPropertyName("contextId")]
public string? ContextId { get; internal set; }
/// <summary>
/// Gets the ID for the task the agent is currently working on.
/// </summary>
[JsonPropertyName("taskId")]
public string? TaskId { get; internal set; }
/// <summary>
/// Gets the state of the task the agent is currently working on.
/// </summary>
[JsonPropertyName("taskState")]
public TaskState? TaskState { get; internal set; }
/// <inheritdoc/>
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
var jso = jsonSerializerOptions ?? A2AJsonUtilities.DefaultOptions;
return JsonSerializer.SerializeToElement(this, jso.GetTypeInfo(typeof(A2AAgentSession)));
}
internal static A2AAgentSession Deserialize(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null)
{
if (serializedState.ValueKind != JsonValueKind.Object)
{
throw new ArgumentException("The serialized session state must be a JSON object.", nameof(serializedState));
}
var jso = jsonSerializerOptions ?? A2AJsonUtilities.DefaultOptions;
return serializedState.Deserialize(jso.GetTypeInfo(typeof(A2AAgentSession))) as A2AAgentSession
?? new A2AAgentSession();
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay =>
$"ContextId = {this.ContextId}, TaskId = {this.TaskId}, TaskState = {this.TaskState}, StateBag Count = {this.StateBag.Count}";
}