Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@geffzhang
Copy link
Collaborator

@geffzhang geffzhang commented Jan 1, 2026

User description

1、a2a test agent:https://github.com/geffzhang/MS.AI.AgentProtocol/tree/main/src/Hosting.A2A
2、 test agent in pizzabot
This pull request introduces a new integration for Agent-to-Agent (A2A) protocol support, enabling BotSharp to communicate with external agents via the A2A protocol. The update adds a new plugin, supporting services, configuration, and routing logic to facilitate seamless remote agent delegation. Additionally, it updates solution/project files and configuration to register and enable this functionality.

A2A Protocol Integration

  • Added the BotSharp.Core.A2A plugin, which registers services, hooks, and function callbacks to support A2A protocol integration (A2APlugin, A2ADelegationFn, A2AAgentHook, A2AService, IA2AService, A2ASettings) [1] [2] [3] [4] [5] [6].
  • Introduced the new agent type constant A2ARemote in AgentType for identifying A2A remote agents.

Configuration and Solution Updates

  • Registered the new BotSharp.Core.A2A project in the solution and as a dependency in WebStarter and test projects (BotSharp.sln, WebStarter.csproj, PizzaBotPlugin.csproj) [1] [2] [3] [4] [5] [6].
  • Added the A2A NuGet package to central package management.
  • Updated appsettings.json to include A2AIntegration configuration, enabling the protocol and specifying remote agents.

Routing and Agent Discovery

  • Modified the routing logic to include agents of type A2ARemote as routable agents, allowing them to be considered for delegation.

These changes collectively enable BotSharp to delegate tasks to external agents using the A2A protocol, expanding its interoperability and integration capabilities.


PR Type

Enhancement


Description

  • Introduces A2A protocol integration enabling agent-to-agent communication

  • Adds new BotSharp.Core.A2A plugin with services, hooks, and delegation functions

  • Implements remote agent discovery and capability loading via A2A endpoints

  • Registers A2A agents in routing service and configuration files


Diagram Walkthrough

flowchart LR
  A["A2A Remote Agent"] -->|"GetCapabilities"| B["A2AService"]
  B -->|"Fetch AgentCard"| C["A2ACardResolver"]
  D["A2AAgentHook"] -->|"OnAgentLoaded"| B
  D -->|"Inject delegate_to_a2a"| E["Agent Functions"]
  F["A2ADelegationFn"] -->|"SendMessage"| B
  B -->|"HTTP Client"| A
  G["RoutingService"] -->|"Include A2ARemote"| H["Routable Agents"]
Loading

File Walkthrough

Relevant files
Enhancement
7 files
AgentType.cs
Add A2ARemote agent type constant                                               
+5/-0     
A2APlugin.cs
Create A2A plugin with DI registration                                     
+36/-0   
A2ADelegationFn.cs
Implement A2A delegation function callback                             
+65/-0   
A2AAgentHook.cs
Add agent hook for A2A remote agent loading                           
+85/-0   
A2AService.cs
Implement A2A service with message and task handling         
+158/-0 
IA2AService.cs
Define A2A service interface contract                                       
+26/-0   
RoutingService.cs
Include A2ARemote agents in routing logic                               
+1/-1     
Configuration changes
8 files
A2ASettings.cs
Define A2A configuration and remote agent settings             
+23/-0   
PizzaBotPlugin.cs
Register SportKiosk A2A remote agent                                         
+2/-1     
BotSharp.sln
Add BotSharp.Core.A2A project to solution                               
+11/-0   
BotSharp.Core.A2A.csproj
Create A2A core project file                                                         
+21/-0   
WebStarter.csproj
Add A2A project reference to WebStarter                                   
+1/-0     
appsettings.json
Configure A2A integration and SportKiosk agent                     
+13/-1   
BotSharp.Plugin.PizzaBot.csproj
Add SportKiosk agent data file reference                                 
+6/-0     
agent.json
Define SportKiosk A2A remote agent configuration                 
+14/-0   
Dependencies
1 files
Directory.Packages.props
Add A2A NuGet package dependency                                                 
+1/-0     

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
SSRF via endpoint

Description: Unvalidated, configuration-driven agentEndpoint is used to construct new
Uri(agentEndpoint) and send outbound HTTP requests (e.g., A2ACardResolver/A2AClient),
enabling SSRF if an attacker can influence A2AIntegration:Agents[].Endpoint to reach
internal services/metadata endpoints (e.g., http://169.254.169.254/) or other sensitive
network locations.
A2AService.cs [23-58]

Referred Code
public async Task<AgentCard> GetCapabilitiesAsync(string agentEndpoint, CancellationToken cancellationToken = default)
{
    var resolver = new A2ACardResolver(new Uri(agentEndpoint));
    return await resolver.GetAgentCardAsync();
}

public async Task<string> SendMessageAsync(string agentEndpoint, string text, string contextId, CancellationToken cancellationToken)
{

    if (!_clientCache.TryGetValue(agentEndpoint, out var client))
    {
        HttpClient httpclient = _httpClientFactory.CreateClient();

        client = new A2AClient(new Uri(agentEndpoint), httpclient);
        _clientCache[agentEndpoint] = client;
    }

    var messagePayload = new AgentMessage
    {
        Role = MessageRole.User, 
        ContextId = contextId,           


 ... (clipped 15 lines)
Error detail leakage

Description: User-facing error message includes raw exception text (ex.Message) which can leak internal
network details, endpoints, stack/context hints, or upstream service responses during A2A
communication failures.
A2ADelegationFn.cs [59-63]

Referred Code
catch (Exception ex)
{            
    message.Content = $"Communication failure with external agent: {ex.Message}";
    return false;
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Unclear identifier: The field name _iA2AService is misleading/non-idiomatic (prefix suggests interface type
rather than instance), reducing readability and self-documentation.

Referred Code
private readonly IA2AService _iA2AService;

public A2AAgentHook(IServiceProvider services, IA2AService a2AService, A2ASettings settings)
    : base(services, new AgentSettings())
{
    _iA2AService = a2AService;
    _settings = settings;
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Unsafe deserialization: JsonSerializer.Deserialize<JsonElement>(message.FunctionArgs) can throw or misbehave
on null/invalid FunctionArgs and the code does not validate or handle those edge cases
before proceeding.

Referred Code
public async Task<bool> Execute(RoleDialogModel message)
{
    var args = JsonSerializer.Deserialize<JsonElement>(message.FunctionArgs);
    string queryText = string.Empty;
    if (args.TryGetProperty("user_query", out var queryProp))
    {
        queryText = queryProp.GetString();
    }

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Leaks exception message: The user-facing response includes raw exception details via ${ex.Message}, potentially
exposing internal/network information to end users.

Referred Code
catch (Exception ex)
{            
    message.Content = $"Communication failure with external agent: {ex.Message}";
    return false;
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured console logs: The code uses Console.WriteLine (unstructured output) and also logs interpolated strings,
making logs harder to audit and increasing the risk of leaking sensitive data.

Referred Code
    Console.WriteLine(" Streaming completed.");
}

public async Task ListenForTaskEventAsync(string endPoint, string taskId, Func<SseItem<A2AEvent>, ValueTask>? onTaskEventReceived = null, CancellationToken cancellationToken = default)
{

    if (onTaskEventReceived == null)
    {
        return;
    }

    A2ACardResolver cardResolver = new(new Uri(endPoint));
    AgentCard agentCard = await cardResolver.GetAgentCardAsync();
    A2AClient client = new A2AClient(new Uri(agentCard.Url));

    await foreach (SseItem<A2AEvent> sseItem in client.SubscribeToTaskAsync(taskId))
    {
        await onTaskEventReceived.Invoke(sseItem);
        Console.WriteLine(" Task event received: " + JsonSerializer.Serialize(sseItem.Data));
    }

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing user context: Outbound A2A communications are logged without any explicit user identifier (only
contextId), so it is unclear whether audit requirements for user-attributed critical
actions are met.

Referred Code
_logger.LogInformation($"Sending A2A message to {agentEndpoint}. ContextId: {contextId}");          
var responseBase = await client.SendMessageAsync(sendParams, cancellationToken);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Endpoint not validated: agentEndpoint is used to construct Uri and make outbound requests without
validation/allowlisting, which may enable SSRF if configuration can be influenced
externally.

Referred Code
public async Task<AgentCard> GetCapabilitiesAsync(string agentEndpoint, CancellationToken cancellationToken = default)
{
    var resolver = new A2ACardResolver(new Uri(agentEndpoint));
    return await resolver.GetAgentCardAsync();
}

public async Task<string> SendMessageAsync(string agentEndpoint, string text, string contextId, CancellationToken cancellationToken)
{

    if (!_clientCache.TryGetValue(agentEndpoint, out var client))
    {
        HttpClient httpclient = _httpClientFactory.CreateClient();

        client = new A2AClient(new Uri(agentEndpoint), httpclient);
        _clientCache[agentEndpoint] = client;
    }

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix potential socket exhaustion issues

Refactor multiple methods in A2AService to use a shared helper method that
leverages IHttpClientFactory and a client cache. This prevents potential socket
exhaustion by reusing HttpClient and A2AClient instances.

src/Infrastructure/BotSharp.Core.A2A/Services/A2AService.cs [91-156]

     public async Task SendMessageStreamingAsync(string endPoint, List<Part> parts, Func<SseItem<A2AEvent>, Task>? onStreamingEventReceived, CancellationToken cancellationToken = default)
     {
-        A2ACardResolver cardResolver = new(new Uri(endPoint));
-        AgentCard agentCard = await cardResolver.GetAgentCardAsync();
-        A2AClient client = new A2AClient(new Uri(agentCard.Url));
+        var client = await GetOrCreateClient(endPoint);
 
         AgentMessage userMessage = new()
 ...
     public async Task ListenForTaskEventAsync(string endPoint, string taskId, Func<SseItem<A2AEvent>, ValueTask>? onTaskEventReceived = null, CancellationToken cancellationToken = default)
     {
 ...
-        A2ACardResolver cardResolver = new(new Uri(endPoint));
-        AgentCard agentCard = await cardResolver.GetAgentCardAsync();
-        A2AClient client = new A2AClient(new Uri(agentCard.Url));
+        var client = await GetOrCreateClient(endPoint);
 
         await foreach (SseItem<A2AEvent> sseItem in client.SubscribeToTaskAsync(taskId))
 ...
     public async Task SetPushNotifications(string endPoint, PushNotificationConfig config, CancellationToken cancellationToken = default)
     {
-        A2ACardResolver cardResolver = new(new Uri(endPoint));
-        AgentCard agentCard = await cardResolver.GetAgentCardAsync();
-        A2AClient client = new A2AClient(new Uri(agentCard.Url));
+        var client = await GetOrCreateClient(endPoint);
         await client.SetPushNotificationAsync(new TaskPushNotificationConfig()
 ...
     public async Task<AgentTask> CancelTaskAsync(string endPoint, string taskId, CancellationToken cancellationToken = default)
     {
-        A2ACardResolver cardResolver = new(new Uri(endPoint));
-        AgentCard agentCard = await cardResolver.GetAgentCardAsync();
-        A2AClient client = new A2AClient(new Uri(agentCard.Url));
+        var client = await GetOrCreateClient(endPoint);
         return await client.CancelTaskAsync(taskId);
     }
 
     public async Task<AgentTask> GetTaskAsync(string endPoint, string taskId, CancellationToken cancellationToken = default)
     {
-        A2ACardResolver cardResolver = new(new Uri(endPoint));
-        AgentCard agentCard = await cardResolver.GetAgentCardAsync();
-        A2AClient client = new A2AClient(new Uri(agentCard.Url));
+        var client = await GetOrCreateClient(endPoint);
         return await client.GetTaskAsync(taskId);
     }
 
+    private async Task<A2AClient> GetOrCreateClient(string agentEndpoint)
+    {
+        if (!_clientCache.TryGetValue(agentEndpoint, out var client))
+        {
+            A2ACardResolver cardResolver = new(new Uri(agentEndpoint));
+            AgentCard agentCard = await cardResolver.GetAgentCardAsync();
+            HttpClient httpclient = _httpClientFactory.CreateClient();
+            client = new A2AClient(new Uri(agentCard.Url), httpclient);
+            _clientCache[agentEndpoint] = client;
+        }
+        return client;
+    }
+

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies that multiple methods are not using the IHttpClientFactory or client caching, which can lead to critical socket exhaustion issues under load. The proposed refactoring is comprehensive and significantly improves the service's robustness and performance.

High
Use thread-safe client cache

Replace the non-thread-safe Dictionary for _clientCache with a
ConcurrentDictionary to prevent race conditions during concurrent access.

src/Infrastructure/BotSharp.Core.A2A/Services/A2AService.cs [14]

-private readonly Dictionary<string, A2AClient> _clientCache = new Dictionary<string, A2AClient>();
+private readonly ConcurrentDictionary<string, A2AClient> _clientCache = new ConcurrentDictionary<string, A2AClient>();
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential race condition by using a non-thread-safe Dictionary for caching. Using a ConcurrentDictionary is crucial for preventing data corruption in scenarios involving parallel operations within the same request scope, which is a critical correctness fix.

Medium
Prevent async deadlock

Add .ConfigureAwait(false) to the GetCapabilitiesAsync call before blocking with
.GetAwaiter().GetResult() to prevent potential deadlocks.

src/Infrastructure/BotSharp.Core.A2A/Hooks/A2AAgentHook.cs [48]

-var agentCard = _iA2AService.GetCapabilitiesAsync(remoteConfig.Endpoint).GetAwaiter().GetResult();
+var agentCard = _iA2AService
+    .GetCapabilitiesAsync(remoteConfig.Endpoint)
+    .ConfigureAwait(false)
+    .GetAwaiter()
+    .GetResult();
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential deadlock caused by blocking on an async call (.GetAwaiter().GetResult()) and provides the standard, lightweight solution (.ConfigureAwait(false)) to mitigate this critical runtime risk.

Medium
Learned
best practice
Preserve exception context and details

Don’t wrap exceptions in a generic Exception; throw a typed exception and
include ex as the inner exception (and use structured logging) to preserve stack
traces and diagnostics.

src/Infrastructure/BotSharp.Core.A2A/Services/A2AService.cs [79-88]

 catch (HttpRequestException ex)
 {
-    _logger.LogError(ex, $"Network error communicating with A2A agent at {agentEndpoint}");
-    throw new Exception($"Remote agent unavailable: {ex.Message}");
+    _logger.LogError(ex, "Network error communicating with A2A agent at {AgentEndpoint}", agentEndpoint);
+    throw new HttpRequestException($"Remote agent unavailable: {ex.Message}", ex);
 }
 catch (Exception ex)
 {
-    _logger.LogError(ex, $"A2A Protocol error: {ex.Message}");
+    _logger.LogError(ex, "A2A protocol error communicating with {AgentEndpoint}", agentEndpoint);
     throw;
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Preserve error details by not throwing generic exceptions that lose context; include original exception as inner exception and return meaningful error information.

Low
  • More

@geffzhang geffzhang mentioned this pull request Jan 1, 2026
@geffzhang geffzhang requested a review from iceljc January 1, 2026 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants