using System.Net.WebSockets; using System.Text; using System.Text.Json.Nodes; namespace backend.Realtime; public class OpenAIRealtimeUpstream : IRealtimeUpstream { private readonly ClientWebSocket _ws = new(); public async Task ConnectAsync(string apiKey, string model, CancellationToken ct) { _ws.Options.SetRequestHeader("Authorization", $"Bearer {apiKey}"); _ws.Options.SetRequestHeader("OpenAI-Beta", "realtime=v1"); var uri = new Uri($"wss://api.openai.com/v1/realtime?model={Uri.EscapeDataString(model)}"); await _ws.ConnectAsync(uri, ct); } public async Task SendJsonAsync(JsonObject envelope, CancellationToken ct) { var json = envelope.ToJsonString(); var bytes = Encoding.UTF8.GetBytes(json); await _ws.SendAsync(bytes, WebSocketMessageType.Text, endOfMessage: true, ct); } public async Task ReceiveJsonAsync(CancellationToken ct) { var buffer = new byte[8192]; using var stream = new MemoryStream(); while (true) { WebSocketReceiveResult result; try { result = await _ws.ReceiveAsync(buffer, ct); } catch (WebSocketException) { return null; } catch (OperationCanceledException) { return null; } if (result.MessageType == WebSocketMessageType.Close) return null; stream.Write(buffer, 0, result.Count); if (result.EndOfMessage) { var json = Encoding.UTF8.GetString(stream.ToArray()); return JsonNode.Parse(json)?.AsObject(); } } } public async ValueTask DisposeAsync() { if (_ws.State == WebSocketState.Open) { try { await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None); } catch { } } _ws.Dispose(); } }