diff --git a/src/BingChat/BingChat.csproj b/src/BingChat/BingChat.csproj
index 14d0a87..d9850f1 100644
--- a/src/BingChat/BingChat.csproj
+++ b/src/BingChat/BingChat.csproj
@@ -15,7 +15,8 @@
-
+
+
diff --git a/src/BingChat/BingChatConversation.cs b/src/BingChat/BingChatConversation.cs
index 5969f9e..4518038 100644
--- a/src/BingChat/BingChatConversation.cs
+++ b/src/BingChat/BingChatConversation.cs
@@ -1,7 +1,12 @@
-using System.Net.WebSockets;
-using System.Reactive.Linq;
-using System.Text.Json;
-using Websocket.Client;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Http.Connections;
+using Microsoft.AspNetCore.Http.Connections.Client;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.AspNetCore.SignalR.Client;
+using Microsoft.AspNetCore.SignalR.Protocol;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
namespace BingChat;
@@ -10,7 +15,29 @@ namespace BingChat;
///
internal sealed class BingChatConversation : IBingChattable
{
- private const char TerminalChar = '\u001e';
+ private static readonly HttpConnectionFactory ConnectionFactory = new(Options.Create(
+ new HttpConnectionOptions
+ {
+ DefaultTransferFormat = TransferFormat.Text,
+ SkipNegotiation = true,
+ Transports = HttpTransportType.WebSockets,
+ Headers =
+ {
+ ["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
+ "AppleWebKit/537.36 (KHTML, like Gecko) " +
+ "Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57"
+ }
+ }),
+ NullLoggerFactory.Instance);
+
+ private static readonly JsonHubProtocol HubProtocol = new(
+ Options.Create(new JsonHubProtocolOptions()
+ {
+ PayloadSerializerOptions = SerializerContext.Default.Options
+ }));
+
+ private static readonly UriEndPoint HubEndpoint = new(new Uri("wss://sydney.bing.com/sydney/ChatHub"));
+
private readonly BingChatRequest _request;
internal BingChatConversation(
@@ -20,122 +47,81 @@ internal BingChatConversation(
}
///
- public Task AskAsync(string message)
+ public async Task AskAsync(string message)
{
- var wsClient = new WebsocketClient(new Uri("wss://sydney.bing.com/sydney/ChatHub"));
- var tcs = new TaskCompletionSource();
-
- void Cleanup()
- {
- wsClient.Stop(WebSocketCloseStatus.Empty, string.Empty).ContinueWith(t =>
- {
- if (t.IsFaulted) tcs.SetException(t.Exception!);
- wsClient.Dispose();
- });
- }
+ var request = _request.ConstructInitialPayload(message);
- string? GetAnswer(BingChatConversationResponse response)
- {
- //Check status
- if (!response.Item.Result.Value.Equals("Success", StringComparison.OrdinalIgnoreCase))
- {
- throw new BingChatException($"{response.Item.Result.Value}: {response.Item.Result.Message}");
- }
+ await using var conn = new HubConnection(
+ ConnectionFactory,
+ HubProtocol,
+ HubEndpoint,
+ new ServiceCollection().BuildServiceProvider(),
+ NullLoggerFactory.Instance);
- //Collect messages, including of types: Chat, SearchQuery, LoaderMessage, Disengaged
- var messages = new List();
- foreach (var itemMessage in response.Item.Messages)
- {
- //Not needed
- if (itemMessage.Author != "bot") continue;
- if (itemMessage.MessageType == "InternalSearchResult" ||
- itemMessage.MessageType == "RenderCardRequest")
- continue;
-
- //Not supported
- if (itemMessage.MessageType == "GenerateContentQuery")
- continue;
-
- //From Text
- var text = itemMessage.Text;
-
- //From AdaptiveCards
- var adaptiveCards = itemMessage.AdaptiveCards;
- if (text is null && adaptiveCards is not null && adaptiveCards.Length > 0)
- {
- var bodies = new List();
- foreach (var body in adaptiveCards[0].Body)
- {
- if (body.Type != "TextBlock" || body.Text is null) continue;
- bodies.Add(body.Text);
- }
- text = bodies.Count > 0 ? string.Join("\n", bodies) : null;
- }
+ await conn.StartAsync();
- //From MessageType
- text ??= $"<{itemMessage.MessageType}>";
+ var response = await conn
+ .StreamAsync("chat", request)
+ .FirstAsync();
- //From SourceAttributions
- var sourceAttributions = itemMessage.SourceAttributions;
- if (sourceAttributions is not null && sourceAttributions.Length > 0)
- {
- text += "\n";
- for (var nIndex = 0; nIndex < sourceAttributions.Length; nIndex++)
- {
- var sourceAttribution = sourceAttributions[nIndex];
- text += $"\n[{nIndex + 1}]: {sourceAttribution.SeeMoreUrl} \"{sourceAttribution.ProviderDisplayName}\"";
- }
- }
-
- messages.Add(text);
- }
+ return BuildAnswer(response) ?? "";
+ }
- return messages.Count > 0 ? string.Join("\n\n", messages) : null;
+ private static string? BuildAnswer(BingChatConversationResponse response)
+ {
+ //Check status
+ if (!response.Result.Value.Equals("Success", StringComparison.OrdinalIgnoreCase))
+ {
+ throw new BingChatException($"{response.Result.Value}: {response.Result.Message}");
}
- void OnMessageReceived(string text)
+ //Collect messages, including of types: Chat, SearchQuery, LoaderMessage, Disengaged
+ var messages = new List();
+ foreach (var itemMessage in response.Messages)
{
- try
- {
- foreach (var part in text.Split(TerminalChar, StringSplitOptions.RemoveEmptyEntries))
- {
- var json = JsonSerializer.Deserialize(part, SerializerContext.Default.BingChatConversationResponse);
+ //Not needed
+ if (itemMessage.Author != "bot") continue;
+ if (itemMessage.MessageType is "InternalSearchResult" or "RenderCardRequest")
+ continue;
- if (json is not { Type: 2 }) continue;
+ //Not supported
+ if (itemMessage.MessageType is "GenerateContentQuery")
+ continue;
- Cleanup();
+ //From Text
+ var text = itemMessage.Text;
- tcs.SetResult(GetAnswer(json) ?? "");
- return;
- }
- }
- catch (Exception e)
+ //From AdaptiveCards
+ var adaptiveCards = itemMessage.AdaptiveCards;
+ if (text is null && adaptiveCards?.Length > 0)
{
- Cleanup();
- tcs.SetException(e);
+ var bodies = new List();
+ foreach (var body in adaptiveCards[0].Body)
+ {
+ if (body.Type != "TextBlock" || body.Text is null) continue;
+ bodies.Add(body.Text);
+ }
+ text = bodies.Count > 0 ? string.Join("\n", bodies) : null;
}
- }
- wsClient.MessageReceived
- .Where(msg => msg.MessageType == WebSocketMessageType.Text)
- .Select(msg => msg.Text)
- .Subscribe(OnMessageReceived);
+ //From MessageType
+ text ??= $"<{itemMessage.MessageType}>";
- // Start the WebSocket client
- wsClient.Start().ContinueWith(t =>
- {
- if (t.IsFaulted)
+ //From SourceAttributions
+ var sourceAttributions = itemMessage.SourceAttributions;
+ if (sourceAttributions?.Length > 0)
{
- Cleanup();
- tcs.SetException(t.Exception!);
- return;
+ text += "\n";
+ for (var nIndex = 0; nIndex < sourceAttributions.Length; nIndex++)
+ {
+ var sourceAttribution = sourceAttributions[nIndex];
+ text += $"\n[{nIndex + 1}]: {sourceAttribution.SeeMoreUrl} \"{sourceAttribution.ProviderDisplayName}\"";
+ }
}
- // Send initial messages
- wsClient.Send("{\"protocol\":\"json\",\"version\":1}" + TerminalChar);
- wsClient.Send(_request.ConstructInitialPayload(message) + TerminalChar);
- });
+ messages.Add(text);
+ }
- return tcs.Task;
+ return messages.Count > 0 ? string.Join("\n\n", messages) : null;
}
-}
+}
\ No newline at end of file
diff --git a/src/BingChat/BingChatConversationRequest.cs b/src/BingChat/BingChatConversationRequest.cs
index 09e81b3..8203cf3 100644
--- a/src/BingChat/BingChatConversationRequest.cs
+++ b/src/BingChat/BingChatConversationRequest.cs
@@ -1,5 +1,3 @@
-using System.Text.Json.Serialization;
-
// ReSharper disable MemberCanBeInternal
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
@@ -10,52 +8,16 @@ namespace BingChat;
internal sealed class BingChatConversationRequest
{
- [JsonPropertyName("type")]
- public int Type { get; set; }
-
- [JsonPropertyName("invocationId")]
- public string InvocationId { get; set; }
-
- [JsonPropertyName("target")]
- public string Target { get; set; }
-
- [JsonPropertyName("arguments")]
- public RequestArgument[] Arguments { get; set; }
-}
-
-internal sealed class RequestArgument
-{
- [JsonPropertyName("source")]
public string Source { get; set; }
-
- [JsonPropertyName("optionsSets")]
public string[] OptionsSets { get; set; }
-
- [JsonPropertyName("allowedMessageTypes")]
public string[] AllowedMessageTypes { get; set; }
-
- [JsonPropertyName("sliceIds")]
public string[] SliceIds { get; set; }
-
- [JsonPropertyName("traceId")]
public string TraceId { get; set; }
-
- [JsonPropertyName("isStartOfSession")]
public bool IsStartOfSession { get; set; }
-
- [JsonPropertyName("message")]
public RequestMessage Message { get; set; }
-
- [JsonPropertyName("tone")]
public string Tone { get; set; }
-
- [JsonPropertyName("conversationSignature")]
public string ConversationSignature { get; set; }
-
- [JsonPropertyName("participant")]
public Participant Participant { get; set; }
-
- [JsonPropertyName("conversationId")]
public string ConversationId { get; set; }
}
@@ -67,25 +29,14 @@ internal sealed class RequestMessage
// region = ,
// location = ,
// locationHints: [],
-
- [JsonPropertyName("timestamp")]
public DateTime Timestamp { get; set; }
-
- [JsonPropertyName("author")]
public string Author { get; set; }
-
- [JsonPropertyName("inputMethod")]
public string InputMethod { get; set; }
-
- [JsonPropertyName("messageType")]
public string MessageType { get; set; }
-
- [JsonPropertyName("text")]
public string Text { get; set; }
}
internal struct Participant
{
- [JsonPropertyName("id")]
public string Id { get; set; }
-}
+}
\ No newline at end of file
diff --git a/src/BingChat/BingChatConversationResponse.cs b/src/BingChat/BingChatConversationResponse.cs
index 73fc0dd..e46bf2c 100644
--- a/src/BingChat/BingChatConversationResponse.cs
+++ b/src/BingChat/BingChatConversationResponse.cs
@@ -1,6 +1,4 @@
-using System.Text.Json.Serialization;
-
-// ReSharper disable MemberCanBeInternal
+// ReSharper disable MemberCanBeInternal
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
@@ -10,69 +8,38 @@ namespace BingChat;
internal sealed class BingChatConversationResponse
{
- [JsonPropertyName("type")]
- public int Type { get; set; }
-
- [JsonPropertyName("item")]
- public ResponseItem Item { get; set; }
-}
-
-internal sealed class ResponseItem
-{
- [JsonPropertyName("messages")]
public ResponseMessage[] Messages { get; set; }
-
- [JsonPropertyName("result")]
public ResponseResult Result { get; set; }
}
internal sealed class ResponseMessage
{
- [JsonPropertyName("text")]
public string? Text { get; set; }
-
- [JsonPropertyName("author")]
public string Author { get; set; }
-
- [JsonPropertyName("messageType")]
public string? MessageType { get; set; }
-
- [JsonPropertyName("adaptiveCards")]
public AdaptiveCard[]? AdaptiveCards { get; set; }
-
- [JsonPropertyName("sourceAttributions")]
public SourceAttribution[]? SourceAttributions { get; set; }
}
internal sealed class AdaptiveCard
{
- [JsonPropertyName("body")]
public ResponseBody[] Body { get; set; }
}
internal sealed class ResponseBody
{
- [JsonPropertyName("type")]
public string Type { get; set; }
-
- [JsonPropertyName("text")]
public string? Text { get; set; }
}
internal sealed class SourceAttribution
{
- [JsonPropertyName("providerDisplayName")]
public string ProviderDisplayName { get; set; }
-
- [JsonPropertyName("seeMoreUrl")]
public string SeeMoreUrl { get; set; }
}
internal sealed class ResponseResult
{
- [JsonPropertyName("value")]
public string Value { get; set; }
-
- [JsonPropertyName("message")]
public string? Message { get; set; }
-}
+}
\ No newline at end of file
diff --git a/src/BingChat/BingChatRequest.cs b/src/BingChat/BingChatRequest.cs
index 145f110..0a42248 100644
--- a/src/BingChat/BingChatRequest.cs
+++ b/src/BingChat/BingChatRequest.cs
@@ -24,7 +24,7 @@ internal BingChatRequest(
/// Construct the initial payload for each message
///
/// User message to Bing Chat
- internal string ConstructInitialPayload(string message)
+ internal BingChatConversationRequest ConstructInitialPayload(string message)
{
var bytes = (stackalloc byte[16]);
Random.Shared.NextBytes(bytes);
@@ -32,41 +32,32 @@ internal string ConstructInitialPayload(string message)
var payload = new BingChatConversationRequest
{
- Type = 4,
- InvocationId = _invocationId.ToString(CultureInfo.InvariantCulture),
- Target = "chat",
- Arguments = new[]
+ Source = "cib",
+ OptionsSets = _tone switch
{
- new RequestArgument
- {
- Source = "cib",
- OptionsSets = _tone switch
- {
- BingChatTone.Creative => BingChatConstants.CreativeOptionSets,
- BingChatTone.Precise => BingChatConstants.PreciseOptionSets,
- BingChatTone.Balanced or _ => BingChatConstants.BalancedOptionSets
- },
- AllowedMessageTypes = BingChatConstants.AllowedMessageTypes,
- SliceIds = Array.Empty(),
- TraceId = traceId,
- IsStartOfSession = _invocationId == 0,
- Message = new RequestMessage
- {
- Timestamp = DateTime.Now,
- Author = "user",
- InputMethod = "Keyboard",
- MessageType = "Chat",
- Text = message
- },
- Tone = _tone.ToString(),
- ConversationSignature = _conversationSignature,
- Participant = new() { Id = _clientId },
- ConversationId = _conversationId
- }
- }
+ BingChatTone.Creative => BingChatConstants.CreativeOptionSets,
+ BingChatTone.Precise => BingChatConstants.PreciseOptionSets,
+ BingChatTone.Balanced or _ => BingChatConstants.BalancedOptionSets
+ },
+ AllowedMessageTypes = BingChatConstants.AllowedMessageTypes,
+ SliceIds = Array.Empty(),
+ TraceId = traceId,
+ IsStartOfSession = _invocationId == 0,
+ Message = new RequestMessage
+ {
+ Timestamp = DateTime.Now,
+ Author = "user",
+ InputMethod = "Keyboard",
+ MessageType = "Chat",
+ Text = message
+ },
+ Tone = _tone.ToString(),
+ ConversationSignature = _conversationSignature,
+ Participant = new() { Id = _clientId },
+ ConversationId = _conversationId
};
_invocationId++;
- return JsonSerializer.Serialize(payload, SerializerContext.Default.BingChatConversationRequest);
+ return payload;
}
}
\ No newline at end of file
diff --git a/src/BingChat/IBingChattable.cs b/src/BingChat/IBingChattable.cs
index a39a7ee..addc6fc 100644
--- a/src/BingChat/IBingChattable.cs
+++ b/src/BingChat/IBingChattable.cs
@@ -3,7 +3,7 @@
public interface IBingChattable
{
///
- /// Ask for a answer.
+ /// Ask for an answer.
///
Task AskAsync(string message);
}
\ No newline at end of file