Files
lserp_cs_6.0/插件库/Lskj.AgentPet.Host.Core/AstrBot/SseParser.cs
T
2026-08-14 14:28:28 +08:00

74 lines
2.5 KiB
C#

using System.Runtime.CompilerServices;
using System.Text;
namespace Lskj.AgentPet.Host.Core.AstrBot;
public sealed record SseFrame(string Event, string Data, string? Id);
public static class SseParser
{
public const int MaxEventCharacters = 1024 * 1024;
public static async IAsyncEnumerable<SseFrame> ReadAsync(
Stream source,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(source);
using StreamReader reader = new(
source,
new UTF8Encoding(false, true),
detectEncodingFromByteOrderMarks: true,
bufferSize: 4096,
leaveOpen: true);
string eventName = "message";
string? id = null;
StringBuilder data = new();
while (true)
{
string? line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
if (line is null)
{
if (data.Length > 0)
yield return new SseFrame(eventName, TrimFinalNewline(data), id);
yield break;
}
if (line.Length == 0)
{
if (data.Length > 0)
yield return new SseFrame(eventName, TrimFinalNewline(data), id);
eventName = "message";
data.Clear();
continue;
}
if (line[0] == ':') continue;
int separator = line.IndexOf(':');
string field = separator < 0 ? line : line[..separator];
string value = separator < 0 ? string.Empty : line[(separator + 1)..];
if (value.StartsWith(' ')) value = value[1..];
switch (field)
{
case "event":
eventName = string.IsNullOrWhiteSpace(value) ? "message" : value;
break;
case "data":
if (data.Length + value.Length + 1 > MaxEventCharacters)
throw new HostError("astrbot_sse_event_too_large", "AstrBot SSE 单个事件超过 1 MB。 ");
data.Append(value).Append('\n');
break;
case "id":
if (!value.Contains('\0')) id = value;
break;
}
}
}
private static string TrimFinalNewline(StringBuilder value)
{
return value.Length > 0 && value[value.Length - 1] == '\n'
? value.ToString(0, value.Length - 1)
: value.ToString();
}
}