Stream panel member responses live with a parallel SSE write-lock
Panel members now stream token-by-token and show tool uses in real time (like the session agent) instead of delivering text only at completion. The key constraint is byte-level safety: N members flushing SSE frames concurrently on the shared HttpResponse would interleave and corrupt the stream, so a SemaphoreSlim gate serializes the writes (not the model work). - SseHelper.TryWriteSse takes an optional write-lock (default null, backward compatible) - NonStreamingLoopCallbacks carries SseGate + MemberIndex; _text/_tool/_error events are tagged with the member index (review/finalizer stay at -1, no gate) - CLI members forward TextDelta/ToolStart/ToolResult live instead of discarding everything but the final text - Anthropic-API members stream per tool-round via the loop callbacks; OpenAI members fall back to collected text at panel_member_done - Frontend demuxes by member index into per-bubble interleaved text/tool rendering Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
268f11fa6d
commit
f21ed9ff9f
5 files changed
+183
-34
No files matched your search
+17
-4
@@ -25,17 +25,30 @@ public static class SseHelper
|
||||
|
||||
/// <summary>
|
||||
/// Versucht ein SSE-Event zu schreiben, ignoriert Fehler (Client disconnected).
|
||||
/// Optionaler <paramref name="gate"/>: serialisiert die Byte-Writes, wenn mehrere
|
||||
/// Producer (z.B. parallele Panel-Member) auf DIESELBE HttpResponse schreiben –
|
||||
/// sonst vermischen sich die SSE-Frames auf Byte-Ebene.
|
||||
/// </summary>
|
||||
public static async Task TryWriteSse(HttpResponse response, string eventType, object data, CancellationToken ct)
|
||||
public static async Task TryWriteSse(HttpResponse response, string eventType, object data, CancellationToken ct, SemaphoreSlim gate = null)
|
||||
{
|
||||
if (ct.IsCancellationRequested) return;
|
||||
bool acquired = false;
|
||||
try
|
||||
{
|
||||
if (!ct.IsCancellationRequested)
|
||||
await WriteSseEvent(response, eventType, data, ct);
|
||||
if (gate != null)
|
||||
{
|
||||
await gate.WaitAsync(ct);
|
||||
acquired = true;
|
||||
}
|
||||
await WriteSseEvent(response, eventType, data, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Client bereits disconnected – ignorieren
|
||||
// Client bereits disconnected / abgebrochen – ignorieren
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (acquired) gate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user