Skip to content

Commit b2df454

Browse files
committed
feat: add synchronous discovery extensions
Signed-off-by: Charles d'Avernas <[email protected]>
1 parent aa7599b commit b2df454

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

src/a2a-net.Client/Extensions/HttpClientExtensions.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,40 @@ namespace A2A.Client;
1919
public static class HttpClientExtensions
2020
{
2121

22+
/// <summary>
23+
/// Sends a GET request to the specified URL and returns the HTTP response message
24+
/// </summary>
25+
/// <param name="client">The HttpClient instance</param>
26+
/// <param name="url">The request URL</param>
27+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
28+
/// <returns>The HTTP response message</returns>
29+
public static HttpResponseMessage Get(this HttpClient client, Uri url, CancellationToken cancellationToken = default)
30+
{
31+
var request = new HttpRequestMessage(HttpMethod.Get, url);
32+
return client.Send(request, cancellationToken);
33+
}
34+
35+
/// <summary>
36+
/// Sends a GET request to the specified URL, expecting a JSON response, and deserializes it into the specified type
37+
/// </summary>
38+
/// <typeparam name="T">The type to deserialize the JSON response to</typeparam>
39+
/// <param name="client">The HttpClient instance</param>
40+
/// <param name="url">The request URL</param>
41+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
42+
/// <returns>The deserialized response object</returns>
43+
public static T? GetFromJson<T>(this HttpClient client, Uri url, CancellationToken cancellationToken = default)
44+
{
45+
var request = new HttpRequestMessage(HttpMethod.Get, url);
46+
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
47+
var response = client.Send(request, cancellationToken);
48+
if (response.IsSuccessStatusCode)
49+
{
50+
using var reader = new StreamReader(response.Content.ReadAsStream(cancellationToken), Encoding.UTF8, leaveOpen: true);
51+
return JsonSerializer.Deserialize<T>(reader.ReadToEnd());
52+
}
53+
else throw new HttpRequestException($"Request failed with status code {response.StatusCode}");
54+
}
55+
2256
/// <summary>
2357
/// Retrieves the A2A discovery document from a remote agent
2458
/// </summary>
@@ -85,4 +119,70 @@ public static Task<A2ADiscoveryDocument> GetA2ADiscoveryDocumentAsync(this HttpC
85119
return httpClient.GetA2ADiscoveryDocumentAsync(request, cancellationToken);
86120
}
87121

122+
/// <summary>
123+
/// Retrieves the A2A discovery document from a remote agent
124+
/// </summary>
125+
/// <param name="httpClient">The <see cref="HttpClient"/> to use to perform the request</param>
126+
/// <param name="request">The discovery request containing the base URI and optional configuration</param>
127+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
128+
/// <returns>The retrieved <see cref="A2ADiscoveryDocument"/></returns>
129+
public static A2ADiscoveryDocument GetA2ADiscoveryDocument(this HttpClient httpClient, A2ADiscoveryDocumentRequest request, CancellationToken cancellationToken = default)
130+
{
131+
ArgumentNullException.ThrowIfNull(httpClient);
132+
ArgumentNullException.ThrowIfNull(request);
133+
try
134+
{
135+
var path = "/.well-known/agent.json";
136+
var endpoint = request.Address == null ? new Uri(path, UriKind.Relative) : new Uri(request.Address, path);
137+
var agentCard = httpClient.GetFromJson<AgentCard>(endpoint, cancellationToken);
138+
return new()
139+
{
140+
Endpoint = endpoint.IsAbsoluteUri ? endpoint : new(httpClient.BaseAddress!, path),
141+
Agents = agentCard == null ? [] : [agentCard]
142+
};
143+
}
144+
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
145+
{
146+
var path = "/.well-known/agents.json";
147+
var endpoint = request.Address == null ? new Uri(path, UriKind.Relative) : new Uri(request.Address, path);
148+
var agentCards = httpClient.GetFromJson<List<AgentCard>>(endpoint, cancellationToken);
149+
return new()
150+
{
151+
Endpoint = endpoint.IsAbsoluteUri ? endpoint : new(httpClient.BaseAddress!, path),
152+
Agents = agentCards ?? []
153+
};
154+
}
155+
}
156+
157+
/// <summary>
158+
/// Retrieves the A2A discovery document from a remote agent
159+
/// </summary>
160+
/// <param name="httpClient">The <see cref="HttpClient"/> to use to perform the request</param>
161+
/// <param name="address">The base URI of the remote server to query for discovery metadata</param>
162+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
163+
/// <returns>The retrieved <see cref="A2ADiscoveryDocument"/></returns>
164+
public static A2ADiscoveryDocument GetA2ADiscoveryDocument(this HttpClient httpClient, Uri address, CancellationToken cancellationToken = default)
165+
{
166+
ArgumentNullException.ThrowIfNull(httpClient);
167+
ArgumentNullException.ThrowIfNull(address);
168+
var request = new A2ADiscoveryDocumentRequest()
169+
{
170+
Address = address
171+
};
172+
return httpClient.GetA2ADiscoveryDocument(request, cancellationToken);
173+
}
174+
175+
/// <summary>
176+
/// Retrieves the A2A discovery document from a remote agent
177+
/// </summary>
178+
/// <param name="httpClient">The <see cref="HttpClient"/> to use to perform the request</param>
179+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
180+
/// <returns>The retrieved <see cref="A2ADiscoveryDocument"/></returns>
181+
public static A2ADiscoveryDocument GetA2ADiscoveryDocument(this HttpClient httpClient, CancellationToken cancellationToken = default)
182+
{
183+
ArgumentNullException.ThrowIfNull(httpClient);
184+
var request = new A2ADiscoveryDocumentRequest();
185+
return httpClient.GetA2ADiscoveryDocument(request, cancellationToken);
186+
}
187+
88188
}

src/a2a-net.Client/Usings.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@
1313

1414
global using A2A.Models;
1515
global using System.Net;
16-
global using System.Net.Http.Json;
16+
global using System.Net.Http.Headers;
17+
global using System.Net.Http.Json;
18+
global using System.Net.Mime;
19+
global using System.Text;
20+
global using System.Text.Json;

0 commit comments

Comments
 (0)