Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions A2A.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<Project Path="src/A2A.Client/A2A.Client.csproj" Id="1314fd10-cebf-4b91-b9cf-56cae0205d50" />
<Project Path="src/A2A.Core.JsonRpc/A2A.Core.JsonRpc.csproj" />
<Project Path="src/A2A.Core/A2A.Core.csproj" />
<Project Path="src/A2A.Extensions.A2UI/A2A.Extensions.A2UI.csproj" Id="02619520-5a85-47a8-ac72-d6c2e68f2504" />
<Project Path="src/A2A.Server.Abstractions/A2A.Server.Abstractions.csproj" Id="9d4c091b-82aa-4d82-b77b-80f4b03a842a" />
<Project Path="src/A2A.Server.AspNetCore/A2A.Server.csproj" />
<Project Path="src/A2A.Server.Persistence.Memory/A2A.Server.Persistence.Memory.csproj" Id="42f60b02-fa31-4431-8735-f1e56f738f27" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>A2A.Client</RootNamespace>
<VersionPrefix>0.15.0</VersionPrefix>
<VersionPrefix>0.16.0</VersionPrefix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
12 changes: 12 additions & 0 deletions src/A2A.Client.Abstractions/IA2AClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ public interface IA2AClient
: IA2AProtocolApi
{

/// <summary>
/// Activates the A2A extension identified by the given URI.
/// </summary>
/// <param name="uri">The URI of the A2A extension to activate.</param>
/// <returns>The configured <see cref="IA2AClient"/>.</returns>
IA2AClient ActivateExtension(Uri uri);

/// <summary>
/// Deactivates the A2A extension identified by the given URI.
/// </summary>
/// <param name="uri">The URI of the A2A extension to deactivate.</param>
/// <returns>The configured <see cref="IA2AClient"/>.</returns>
IA2AClient DeactivateExtension(Uri uri);

}
3 changes: 2 additions & 1 deletion src/A2A.Client.Abstractions/IA2AClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ IA2AClientBuilder UseTransport<TTransport>()
/// <summary>
/// Builds the configured <see cref="IA2AClient"/>.
/// </summary>
void Build();
/// <returns>A new <see cref="IA2AClient"/>.</returns>
IA2AClient Build();

}
10 changes: 10 additions & 0 deletions src/A2A.Client.Abstractions/IA2AClientTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ public interface IA2AClientTransport
: IA2AProtocolApi
{

/// <summary>
/// Activates the A2A extension identified by the given URI.
/// </summary>
/// <param name="uri">The URI of the A2A extension to activate.</param>
void ActivateExtension(Uri uri);

/// <summary>
/// Deactivates the A2A extension identified by the given URI.
/// </summary>
/// <param name="uri">The URI of the A2A extension to deactivate.</param>
void DeactivateExtension(Uri uri);

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>A2A.Client.Transports</RootNamespace>
<VersionPrefix>0.15.0</VersionPrefix>
<VersionPrefix>0.16.0</VersionPrefix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
47 changes: 45 additions & 2 deletions src/A2A.Client.Transports.Grpc/A2AGrpcClientTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// limitations under the License.

using Grpc.Core;
using System.Collections.Generic;
using Task = System.Threading.Tasks.Task;

namespace A2A.Client.Transports;
Expand All @@ -24,18 +25,27 @@ public sealed class A2AGrpcClientTransport(A2a.V1.A2AService.A2AServiceClient gr
: IA2AClientTransport
{

const string VersionMetadataKey = "A2A-Version";
const string ExtensionMetadataKey = "A2A-Extensions";

ListValue? extensions;

/// <inheritdoc/>
public async Task<Models.Response> SendMessageAsync(Models.SendMessageRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
return A2AGrpcMapper.MapFromGrpc(await grpcClient.SendMessageAsync(A2AGrpcMapper.MapToGrpc(request), cancellationToken: cancellationToken).ConfigureAwait(false));
var grpcRequest = A2AGrpcMapper.MapToGrpc(request);
SetRequestMetadata(grpcRequest.Metadata);
return A2AGrpcMapper.MapFromGrpc(await grpcClient.SendMessageAsync(grpcRequest, cancellationToken: cancellationToken).ConfigureAwait(false));
}

/// <inheritdoc/>
public async IAsyncEnumerable<Models.StreamResponse> SendStreamingMessageAsync(Models.SendMessageRequest request, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
var result = grpcClient.SendStreamingMessage(A2AGrpcMapper.MapToGrpc(request), cancellationToken: cancellationToken);
var grpcRequest = A2AGrpcMapper.MapToGrpc(request);
SetRequestMetadata(grpcRequest.Metadata);
var result = grpcClient.SendStreamingMessage(grpcRequest, cancellationToken: cancellationToken);
await foreach (var streamResponse in result.ResponseStream.ReadAllAsync(cancellationToken).ConfigureAwait(false))
{
if (streamResponse is null) continue;
Expand Down Expand Up @@ -142,4 +152,37 @@ await grpcClient.DeleteTaskPushNotificationConfigAsync(new()
}, cancellationToken: cancellationToken).ConfigureAwait(false);
}

void SetRequestMetadata(Struct metadata)
{
metadata.Fields[VersionMetadataKey] = Value.ForString(A2AProtocolVersion.Latest);
if (extensions is not null) metadata.Fields[ExtensionMetadataKey] = new()
{
ListValue = extensions
};
}

/// <inheritdoc/>
public void ActivateExtension(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
var value = uri.OriginalString;
extensions ??= new();
if (extensions.Values.Any(v => v.StringValue.Equals(value, StringComparison.OrdinalIgnoreCase))) return;
extensions.Values.Add(new Value()
{
StringValue = value
});
}

/// <inheritdoc/>
public void DeactivateExtension(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
if (extensions is null) return;
var value = uri.OriginalString;
var toRemove = extensions.Values.FirstOrDefault(v => v.StringValue.Equals(value, StringComparison.OrdinalIgnoreCase));
if (toRemove is null) return;
extensions.Values.Remove(toRemove);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>A2A.Client.Transports</RootNamespace>
<VersionPrefix>0.15.0</VersionPrefix>
<VersionPrefix>0.16.0</VersionPrefix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
23 changes: 23 additions & 0 deletions src/A2A.Client.Transports.Http/A2AHttpClientTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public sealed class A2AHttpClientTransport(HttpClient httpClient)
: IA2AClientTransport
{

const string ExtensionHeaderName = "A2A-Extensions";

/// <inheritdoc/>
public async Task<Response> SendMessageAsync(SendMessageRequest request, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -169,4 +171,25 @@ public async Task DeletePushNotificationConfigAsync(string taskId, string config

}

/// <inheritdoc/>
public void ActivateExtension(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
var value = uri.OriginalString;
if (httpClient.DefaultRequestHeaders.TryGetValues(ExtensionHeaderName, out var existing) && existing.Contains(value, StringComparer.OrdinalIgnoreCase)) return;
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(ExtensionHeaderName, value);
}

/// <inheritdoc/>
public void DeactivateExtension(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
var value = uri.OriginalString;
if (!httpClient.DefaultRequestHeaders.TryGetValues(ExtensionHeaderName, out var existing)) return;
var extensions = existing.Where(v => !string.Equals(v, value, StringComparison.OrdinalIgnoreCase)).ToArray();
httpClient.DefaultRequestHeaders.Remove(ExtensionHeaderName);
if (extensions.Length == 0) return;
foreach (var extension in extensions) httpClient.DefaultRequestHeaders.TryAddWithoutValidation(ExtensionHeaderName, extension);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static IA2AClientBuilder UseHttpTransport(this IA2AClientBuilder builder,
builder.Services.AddHttpClient<A2AHttpClientTransport>(httpClient =>
{
httpClient.BaseAddress = baseAddress;
httpClient.DefaultRequestHeaders.Add("A2A-Version", A2AProtocolVersion.Latest);
});
builder.Services.AddSingleton<IA2AClientTransport>(provider => provider.GetRequiredService<A2AHttpClientTransport>());
return builder.UseTransport<A2AHttpClientTransport>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>A2A.Client.Transports</RootNamespace>
<VersionPrefix>0.15.0</VersionPrefix>
<VersionPrefix>0.16.0</VersionPrefix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
23 changes: 23 additions & 0 deletions src/A2A.Client.Transports.JsonRpc/A2AJsonRpcClientTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public sealed class A2AJsonRpcClientTransport(HttpClient httpClient)
: IA2AClientTransport
{

const string ExtensionHeaderName = "A2A-Extensions";

/// <inheritdoc/>
public async Task<Response> SendMessageAsync(SendMessageRequest request, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -257,4 +259,25 @@ public async Task DeletePushNotificationConfigAsync(string taskId, string config
if (rpcResponse.Error is not null) throw new Exception($"An error occurred while processing the JSON-RPC request (error code: {rpcResponse.Error.Code}): {rpcResponse.Error.Message}).");
}

/// <inheritdoc/>
public void ActivateExtension(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
var value = uri.OriginalString;
if (httpClient.DefaultRequestHeaders.TryGetValues(ExtensionHeaderName, out var existing) && existing.Contains(value, StringComparer.OrdinalIgnoreCase)) return;
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(ExtensionHeaderName, value);
}

/// <inheritdoc/>
public void DeactivateExtension(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
var value = uri.OriginalString;
if (!httpClient.DefaultRequestHeaders.TryGetValues(ExtensionHeaderName, out var existing)) return;
var extensions = existing.Where(v => !string.Equals(v, value, StringComparison.OrdinalIgnoreCase)).ToArray();
httpClient.DefaultRequestHeaders.Remove(ExtensionHeaderName);
if (extensions.Length == 0) return;
foreach (var extension in extensions) httpClient.DefaultRequestHeaders.TryAddWithoutValidation(ExtensionHeaderName, extension);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static IA2AClientBuilder UseJsonRpcTransport(this IA2AClientBuilder build
builder.Services.AddHttpClient<A2AJsonRpcClientTransport>(httpClient =>
{
httpClient.BaseAddress = baseAddress;
httpClient.DefaultRequestHeaders.Add("A2A-Version", A2AProtocolVersion.Latest);
});
builder.Services.AddSingleton<IA2AClientTransport>(provider => provider.GetRequiredService<A2AJsonRpcClientTransport>());
return builder.UseTransport<A2AJsonRpcClientTransport>();
Expand Down
2 changes: 1 addition & 1 deletion src/A2A.Client/A2A.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>A2A.Client</RootNamespace>
<VersionPrefix>0.15.0</VersionPrefix>
<VersionPrefix>0.16.0</VersionPrefix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
17 changes: 16 additions & 1 deletion src/A2A.Client/A2AClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using A2A.Models;
using Task = System.Threading.Tasks.Task;

namespace A2A.Client;
Expand Down Expand Up @@ -54,4 +53,20 @@ public sealed class A2AClient(IA2AClientTransport transport)
/// <inheritdoc/>
public Task DeletePushNotificationConfigAsync(string taskId, string configId, string? tenant = null, CancellationToken cancellationToken = default) => transport.DeletePushNotificationConfigAsync(taskId, configId, tenant, cancellationToken);

/// <inheritdoc/>
public IA2AClient ActivateExtension(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
transport.ActivateExtension(uri);
return this;
}

/// <inheritdoc/>
public IA2AClient DeactivateExtension(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
transport.DeactivateExtension(uri);
return this;
}

}
21 changes: 16 additions & 5 deletions src/A2A.Client/A2AClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@ namespace A2A.Client;
/// <summary>
/// Represents the default implementation of the <see cref="IA2AClientBuilder"/> interface.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to configure.</param>
public sealed class A2AClientBuilder(IServiceCollection services)
public sealed class A2AClientBuilder
: IA2AClientBuilder
{

Type? transportType;

internal A2AClientBuilder(IServiceCollection services)
{
Services = services;
Services.AddSingleton<IA2AClient, A2AClient>();
}

/// <inheritdoc/>
public IServiceCollection Services { get; } = services;
public IServiceCollection Services { get; }

/// <inheritdoc/>
public IA2AClientBuilder UseTransport<TTransport>()
Expand All @@ -35,10 +40,16 @@ public IA2AClientBuilder UseTransport<TTransport>()
}

/// <inheritdoc/>
public void Build()
public IA2AClient Build()
{
if (transportType is null) throw new InvalidOperationException("The transport type must be specified before building the client.");
Services.AddSingleton<IA2AClient, A2AClient>();
return Services.BuildServiceProvider().GetRequiredService<IA2AClient>();
}

/// <summary>
/// Creates a new <see cref="IA2AClientBuilder"/>.
/// </summary>
/// <returns>A new <see cref="IA2AClientBuilder"/>.</returns>
public static IA2AClientBuilder Create() => new A2AClientBuilder(new ServiceCollection());

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ public static IServiceCollection AddA2AClient(this IServiceCollection services,
{
var builder = new A2AClientBuilder(services);
setup.Invoke(builder);
builder.Build();
services.AddSingleton<IA2AClient, A2AClient>();
return services;
}

Expand Down
2 changes: 1 addition & 1 deletion src/A2A.Core.JsonRpc/A2A.Core.JsonRpc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>A2A</RootNamespace>
<VersionPrefix>0.15.0</VersionPrefix>
<VersionPrefix>0.16.0</VersionPrefix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
2 changes: 1 addition & 1 deletion src/A2A.Core/A2A.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>A2A</RootNamespace>
<VersionPrefix>0.15.0</VersionPrefix>
<VersionPrefix>0.16.0</VersionPrefix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
Expand Down
27 changes: 27 additions & 0 deletions src/A2A.Core/A2AProtocolVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright © 2025-Present the a2a-net Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace A2A;

/// <summary>
/// Enumerates the supported A2A protocol versions.
/// </summary>
public static class A2AProtocolVersion
{

/// <summary>
/// Gets the latest supported A2A protocol version.
/// </summary>
public const string Latest = "0.3";

}
Loading