Skip to content

Commit aa7599b

Browse files
authored
Merge pull request #8 from neuroglia-io/feat-push-notifications
Implements push notifications
2 parents 2f7db71 + bc3d27c commit aa7599b

File tree

38 files changed

+593
-51
lines changed

38 files changed

+593
-51
lines changed

a2a-net.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "a2a-net.Samples.SemanticKer
7171
EndProject
7272
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "a2a-net.Samples.SemanticKernel.Client", "samples\semantic-kernel\a2a-net.Samples.SemanticKernel.Client\a2a-net.Samples.SemanticKernel.Client.csproj", "{2C78A491-5476-3A37-4591-BB876B59E903}"
7373
EndProject
74+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "a2a-net.Samples.SemanticKernel.PushNotificationClient", "samples\semantic-kernel\a2a-net.Samples.SemanticKernel\a2a-net.Samples.SemanticKernel.PushNotificationClient\a2a-net.Samples.SemanticKernel.PushNotificationClient.csproj", "{5F053B7C-80AE-4E76-A5F6-8EF9672600A7}"
75+
EndProject
7476
Global
7577
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7678
Debug|Any CPU = Debug|Any CPU
@@ -129,6 +131,10 @@ Global
129131
{2C78A491-5476-3A37-4591-BB876B59E903}.Debug|Any CPU.Build.0 = Debug|Any CPU
130132
{2C78A491-5476-3A37-4591-BB876B59E903}.Release|Any CPU.ActiveCfg = Release|Any CPU
131133
{2C78A491-5476-3A37-4591-BB876B59E903}.Release|Any CPU.Build.0 = Release|Any CPU
134+
{5F053B7C-80AE-4E76-A5F6-8EF9672600A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
135+
{5F053B7C-80AE-4E76-A5F6-8EF9672600A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
136+
{5F053B7C-80AE-4E76-A5F6-8EF9672600A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
137+
{5F053B7C-80AE-4E76-A5F6-8EF9672600A7}.Release|Any CPU.Build.0 = Release|Any CPU
132138
EndGlobalSection
133139
GlobalSection(SolutionProperties) = preSolution
134140
HideSolutionNode = FALSE
@@ -150,6 +156,7 @@ Global
150156
{614D5E64-63BF-4DAE-8A80-E07FB1C2462F} = {889361E4-EDC0-46F1-8A0F-E390418939BB}
151157
{564F5A5C-D32B-CDB7-788E-9AE19EF75EC2} = {614D5E64-63BF-4DAE-8A80-E07FB1C2462F}
152158
{2C78A491-5476-3A37-4591-BB876B59E903} = {614D5E64-63BF-4DAE-8A80-E07FB1C2462F}
159+
{5F053B7C-80AE-4E76-A5F6-8EF9672600A7} = {614D5E64-63BF-4DAE-8A80-E07FB1C2462F}
153160
EndGlobalSection
154161
GlobalSection(ExtensibilityGlobals) = postSolution
155162
SolutionGuid = {698C2979-1A16-437B-8C04-1357EB80CC7F}

samples/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ This folder contains example projects that demonstrate how to use [a2a-net](#) t
99
Demonstrates how to build and host an A2A-compatible agent using [Microsoft's Semantic Kernel](https://aka.ms/semantic-kernel) and OpenAI.
1010
Includes:
1111
- a server that hosts the agent and exposes it via A2A and HTTP endpoints
12-
- a client that connects to the agent using the JSON-RPC protocol over HTTP
12+
- a client that connects to the agent using the JSON-RPC protocol over HTTP
13+
- a clientt used to consume push notifications

samples/semantic-kernel/a2a-net.Samples.SemanticKernel.Client/Configuration/ApplicationOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,9 @@ public class ApplicationOptions
2525
[Required]
2626
public Uri Server { get; set; } = null!;
2727

28+
/// <summary>
29+
/// Gets/sets the URI, if any, of the endpoint to send push notifications to
30+
/// </summary>
31+
public Uri? PushNotificationClient { get; set; }
32+
2833
}

samples/semantic-kernel/a2a-net.Samples.SemanticKernel.Client/Program.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
.Build();
2020
var applicationOptions = new ApplicationOptions();
2121
configuration.Bind(applicationOptions);
22+
using var httpClient = new HttpClient();
23+
var discoveryDocument = await httpClient.GetA2ADiscoveryDocumentAsync(applicationOptions.Server);
24+
var agent = discoveryDocument.Agents[0];
2225
var services = new ServiceCollection();
2326
services.AddA2AProtocolHttpClient(options =>
2427
{
25-
options.Endpoint = applicationOptions.Server;
28+
options.Endpoint = agent.Url.IsAbsoluteUri ? agent.Url : new(applicationOptions.Server, agent.Url);
2629
});
2730
var provider = services.BuildServiceProvider();
2831
var client = provider.GetRequiredService<IA2AProtocolClient>();
@@ -45,13 +48,21 @@
4548
{
4649
Role = MessageRole.User,
4750
Parts = [new TextPart(prompt)]
51+
},
52+
PushNotification = applicationOptions.PushNotificationClient == null ? null : new()
53+
{
54+
Url = applicationOptions.PushNotificationClient
4855
}
4956
}
5057
};
5158
try
5259
{
5360
var response = await client.SendTaskAsync(request, cancellationSource.Token);
54-
if (response.Result?.Artifacts is { Count: > 0 })
61+
if (response.Error != null)
62+
{
63+
AnsiConsole.MarkupLine($"[italic red]Agent>[/] [bold red]Error {response.Error.Code}:[/] {response.Error.Message}");
64+
}
65+
else if (response.Result?.Artifacts is { Count: > 0 })
5566
{
5667
foreach (var artifact in response.Result.Artifacts)
5768
{

samples/semantic-kernel/a2a-net.Samples.SemanticKernel.Client/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"a2a-net.Samples.SemanticKernel.Client": {
44
"commandName": "Project",
5-
"commandLineArgs": "--server http://localhost:5079/a2a"
5+
"commandLineArgs": "--Server http://localhost:5079 --PushNotificationClient http://localhost:5198"
66
}
77
}
88
}

samples/semantic-kernel/a2a-net.Samples.SemanticKernel.Server/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@
5656
var app = builder.Build();
5757

5858
app.MapA2AWellKnownAgentEndpoint();
59-
app.MapA2AAgentHttpEndpoint("/a2a");
59+
app.MapA2AHttpEndpoint("/a2a");
6060

6161
await app.RunAsync();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright � 2025-Present the a2a-net Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"),
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
namespace a2a_net.Samples.SemanticKernel.PushNotificationClient.Configuration;
15+
16+
/// <summary>
17+
/// Represents the options used to configure the application
18+
/// </summary>
19+
public class ApplicationOptions
20+
{
21+
22+
/// <summary>
23+
/// Gets/sets the remote server's URI
24+
/// </summary>
25+
public required Uri Server { get; set; }
26+
27+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright � 2025-Present the a2a-net Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"),
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
var builder = WebApplication.CreateBuilder(args);
15+
builder.Services.AddOptions<ApplicationOptions>().Bind(builder.Configuration).ValidateDataAnnotations().ValidateOnStart();
16+
builder.Services.AddHttpClient();
17+
var app = builder.Build();
18+
19+
app.MapGet("/", (HttpRequest request) =>
20+
{
21+
if (request.Query.TryGetValue("validationToken", out var token)) return Results.Text(token);
22+
else return Results.BadRequest("Missing validationToken query param");
23+
});
24+
app.MapPost("/", async (HttpRequest request, HttpClient httpClient, IOptions<ApplicationOptions> options) =>
25+
{
26+
var json = await httpClient.GetStringAsync(new Uri(options.Value.Server, "/.well-known/jwks.json"), request.HttpContext.RequestAborted);
27+
var jwks = new JsonWebKeySet(json);
28+
using var reader = new StreamReader(request.Body);
29+
var payload = await reader.ReadToEndAsync(request.HttpContext.RequestAborted);
30+
var token = request.Headers.Authorization.ToString()["Bearer ".Length..].Trim();
31+
var tokenHandler = new JsonWebTokenHandler();
32+
var result = await tokenHandler.ValidateTokenAsync(token, new TokenValidationParameters
33+
{
34+
ValidateIssuer = false,
35+
ValidateAudience = false,
36+
RequireSignedTokens = true,
37+
ValidateLifetime = true,
38+
IssuerSigningKeys = jwks.GetSigningKeys(),
39+
ValidAlgorithms = [SecurityAlgorithms.RsaSha256],
40+
RequireExpirationTime = false
41+
});
42+
if (result.IsValid) return Results.Ok();
43+
else return Results.Forbid();
44+
});
45+
46+
await app.RunAsync();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "https://json.schemastore.org/launchsettings.json",
3+
"profiles": {
4+
"http": {
5+
"commandName": "Project",
6+
"dotnetRunMessages": true,
7+
"launchBrowser": true,
8+
"applicationUrl": "http://localhost:5198",
9+
"environmentVariables": {
10+
"ASPNETCORE_ENVIRONMENT": "Development",
11+
"SERVER": "http://localhost:5079"
12+
}
13+
}
14+
}
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright � 2025-Present the a2a-net Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"),
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
global using a2a_net.Samples.SemanticKernel.PushNotificationClient.Configuration;
15+
global using Microsoft.Extensions.Options;
16+
global using Microsoft.IdentityModel.JsonWebTokens;
17+
global using Microsoft.IdentityModel.Tokens;

0 commit comments

Comments
 (0)