feat(a2a-client): read remote agents from agents.json on every call
This commit is contained in:
63
extensions/Volcanic.AgenticCore.Extensions.A2A/AgentsFile.cs
Normal file
63
extensions/Volcanic.AgenticCore.Extensions.A2A/AgentsFile.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Volcanic.AgenticCore.Extensions.A2A;
|
||||
|
||||
public sealed record RemoteAgentEntry(string Name, string Url, string? Key, string? Description);
|
||||
|
||||
/// <param name="Problems">Skipped entries, described without keys.</param>
|
||||
public sealed record AgentsFileResult(IReadOnlyList<RemoteAgentEntry> Agents, IReadOnlyList<string> Problems, string? Error);
|
||||
|
||||
/// <summary>
|
||||
/// <c><workspace>/.volcanic/a2a/agents.json</c>: remote agents the assistant may call. A plain file the agent and the
|
||||
/// user edit; read on every call (no cache).
|
||||
/// </summary>
|
||||
public static partial class AgentsFile
|
||||
{
|
||||
[GeneratedRegex("^[a-z0-9-]{1,40}$", RegexOptions.CultureInvariant)]
|
||||
private static partial Regex NamePattern();
|
||||
|
||||
public static string PathFor(string workspace) => Path.Combine(workspace, ".volcanic", "a2a", "agents.json");
|
||||
|
||||
public static RemoteAgentEntry? Find(string workspace, string name) =>
|
||||
Read(workspace).Agents.FirstOrDefault(a => a.Name == name);
|
||||
|
||||
public static AgentsFileResult Read(string workspace)
|
||||
{
|
||||
var path = PathFor(workspace);
|
||||
if (!File.Exists(path)) return new([], [], null);
|
||||
JsonElement root;
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(path));
|
||||
root = doc.RootElement.Clone();
|
||||
}
|
||||
catch (Exception ex) when (ex is JsonException or IOException or UnauthorizedAccessException)
|
||||
{
|
||||
return new([], [], $".volcanic/a2a/agents.json could not be read: {ex.GetType().Name}.");
|
||||
}
|
||||
if (root.ValueKind != JsonValueKind.Array)
|
||||
return new([], [], ".volcanic/a2a/agents.json must be a JSON array of agents.");
|
||||
|
||||
var agents = new List<RemoteAgentEntry>();
|
||||
var problems = new List<string>();
|
||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||
var index = 0;
|
||||
foreach (var item in root.EnumerateArray())
|
||||
{
|
||||
index++;
|
||||
if (item.ValueKind != JsonValueKind.Object) { problems.Add($"entry {index} is not an object"); continue; }
|
||||
var name = Str(item, "name");
|
||||
var url = Str(item, "url");
|
||||
if (name is null || !NamePattern().IsMatch(name)) { problems.Add($"entry {index}: name must match [a-z0-9-]{{1,40}}"); continue; }
|
||||
if (url is null || !Uri.TryCreate(url, UriKind.Absolute, out var uri) || (uri.Scheme != Uri.UriSchemeHttps && uri.Scheme != Uri.UriSchemeHttp))
|
||||
{ problems.Add($"{name}: url must be an absolute http(s) URL"); continue; }
|
||||
if (!seen.Add(name)) { problems.Add($"{name}: duplicate name"); continue; }
|
||||
agents.Add(new RemoteAgentEntry(name, url, Str(item, "key"), Str(item, "description")));
|
||||
}
|
||||
return new(agents, problems, null);
|
||||
}
|
||||
|
||||
private static string? Str(JsonElement e, string property) =>
|
||||
e.TryGetProperty(property, out var v) && v.ValueKind == JsonValueKind.String && v.GetString() is { Length: > 0 } s ? s : null;
|
||||
}
|
||||
Reference in New Issue
Block a user