free-code-dotnet/src/FreeCode.Bridge/BridgeApiClient.cs
应文浩wenhao.ying@xiaobao100.com bce2612b64 feat: 完善具体实现
2026-04-06 15:25:34 +08:00

142 lines
7.5 KiB
C#

using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using FreeCode.Core.Enums;
using FreeCode.Core.Models;
namespace FreeCode.Bridge;
public sealed class BridgeApiClient
{
private readonly HttpClient _httpClient;
public BridgeApiClient(HttpClient? httpClient = null)
{
_httpClient = httpClient ?? new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(30);
}
public async Task<BridgeStatusInfo> GetStatusAsync(BridgeConfig config, CancellationToken ct = default)
{
using var response = await _httpClient.GetAsync(Root(config, "bridge/status"), ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await ReadJsonAsync<BridgeStatusInfo>(response, ct).ConfigureAwait(false) ?? new BridgeStatusInfo(BridgeStatus.Idle);
}
public async Task PostEventAsync(BridgeConfig config, object payload, CancellationToken ct = default)
{
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, "bridge/events"), payload, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
public async Task SendSessionUpdateAsync(BridgeConfig config, string sessionId, object payload, CancellationToken ct = default)
{
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/sessions/{Uri.EscapeDataString(sessionId)}/update"), payload, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
public async Task<object?> RequestPermissionAsync(BridgeConfig config, string sessionId, object payload, CancellationToken ct = default)
{
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/sessions/{Uri.EscapeDataString(sessionId)}/permissions/request"), payload, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await ReadJsonAsync<object>(response, ct).ConfigureAwait(false);
}
public async Task<BridgeStatusInfo> RegisterBridgeEnvironmentAsync(BridgeConfig config, BridgeEnvironment environment, CancellationToken ct = default)
{
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, "bridge/environments"), environment, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await ReadJsonAsync<BridgeStatusInfo>(response, ct).ConfigureAwait(false) ?? new BridgeStatusInfo(BridgeStatus.Registered);
}
public async Task<WorkItem?> PollForWorkAsync(BridgeConfig config, string environmentId, CancellationToken ct = default)
{
using var response = await _httpClient.GetAsync(Root(config, $"bridge/environments/{Uri.EscapeDataString(environmentId)}/work"), ct).ConfigureAwait(false);
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
{
return null;
}
response.EnsureSuccessStatusCode();
return await ReadJsonAsync<WorkItem>(response, ct).ConfigureAwait(false);
}
public async Task AcknowledgeWorkAsync(BridgeConfig config, string workId, string sessionToken, CancellationToken ct = default)
{
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/work/{Uri.EscapeDataString(workId)}/ack"), new Dictionary<string, object?>
{
["sessionToken"] = sessionToken
}, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
public async Task<SessionHandle> SpawnSessionAsync(BridgeConfig config, SessionSpawnOptions options, CancellationToken ct = default)
{
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, "bridge/sessions"), options, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await ReadJsonAsync<SessionHandle>(response, ct).ConfigureAwait(false) ?? new SessionHandle(options.Environment.Id, string.Empty);
}
public async Task SendPermissionResponseAsync(BridgeConfig config, string sessionId, PermissionResponse responseBody, CancellationToken ct = default)
{
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/sessions/{Uri.EscapeDataString(sessionId)}/permissions"), responseBody, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
public async Task HeartbeatAsync(BridgeConfig config, string workId, string sessionToken, CancellationToken ct = default)
{
using var response = await SendJsonAsync(HttpMethod.Post, Root(config, $"bridge/work/{Uri.EscapeDataString(workId)}/heartbeat"), new Dictionary<string, object?>
{
["sessionToken"] = sessionToken
}, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
public async Task StopWorkAsync(BridgeConfig config, string workId, CancellationToken ct = default)
{
using var response = await _httpClient.DeleteAsync(Root(config, $"bridge/work/{Uri.EscapeDataString(workId)}"), ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
public async Task DeregisterEnvironmentAsync(BridgeConfig config, string environmentId, CancellationToken ct = default)
{
using var response = await _httpClient.DeleteAsync(Root(config, $"bridge/environments/{Uri.EscapeDataString(environmentId)}"), ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
private async Task<HttpResponseMessage> SendJsonAsync(HttpMethod method, Uri uri, object payload, CancellationToken ct)
{
var json = payload switch
{
JsonElement element => JsonSerializer.Serialize(element, SourceGenerationContext.Default.JsonElement),
Dictionary<string, object?> dictionary => JsonSerializer.Serialize(dictionary),
BridgeEnvironment environment => JsonSerializer.Serialize(environment, SourceGenerationContext.Default.BridgeEnvironment),
SessionSpawnOptions options => JsonSerializer.Serialize(options, SourceGenerationContext.Default.SessionSpawnOptions),
PermissionResponse permissionResponse => JsonSerializer.Serialize(permissionResponse, SourceGenerationContext.Default.PermissionResponse),
RemoteConnectedEvent remoteConnectedEvent => JsonSerializer.Serialize(remoteConnectedEvent, SourceGenerationContext.Default.RemoteConnectedEvent),
RemoteDisconnectedEvent remoteDisconnectedEvent => JsonSerializer.Serialize(remoteDisconnectedEvent, SourceGenerationContext.Default.RemoteDisconnectedEvent),
RemoteMessageEvent remoteMessageEvent => JsonSerializer.Serialize(remoteMessageEvent, SourceGenerationContext.Default.RemoteMessageEvent),
_ => throw new NotSupportedException($"Unsupported bridge JSON payload type: {payload.GetType().FullName}")
};
var request = new HttpRequestMessage(method, uri)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
return await _httpClient.SendAsync(request, ct).ConfigureAwait(false);
}
private static Uri Root(BridgeConfig config, string relativePath)
{
var baseUri = new Uri(config.BaseUrl.TrimEnd('/') + "/", UriKind.Absolute);
return new Uri(baseUri, relativePath);
}
private static async Task<T?> ReadJsonAsync<T>(HttpResponseMessage response, CancellationToken ct)
{
await using var stream = await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false);
return (T?)await response.Content.ReadFromJsonAsync(typeof(T), SourceGenerationContext.Default, ct).ConfigureAwait(false);
}
}