using System.Diagnostics;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Gott.Models;
using Microsoft.AspNetCore.Http;
namespace Gott.Services;
///
/// Shared SSE-Streaming-Utilities für AnthropicService und OpenAiService.
///
public static class SseHelper
{
///
/// Schreibt ein SSE-Event (wirft bei CancellationToken).
///
public static async Task WriteSseEvent(HttpResponse response, string eventType, object data, CancellationToken ct = default)
{
ct.ThrowIfCancellationRequested();
var json = JsonSerializer.Serialize(data);
await response.WriteAsync($"event: {eventType}\ndata: {json}\n\n", ct);
await response.Body.FlushAsync(ct);
}
///
/// Versucht ein SSE-Event zu schreiben, ignoriert Fehler (Client disconnected).
/// Optionaler : 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.
///
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 (gate != null)
{
await gate.WaitAsync(ct);
acquired = true;
}
await WriteSseEvent(response, eventType, data, ct);
}
catch
{
// Client bereits disconnected / abgebrochen – ignorieren
}
finally
{
if (acquired) gate.Release();
}
}
///
/// Finalisiert eine Chain in der Datenbank (Token-Counts, Stopgrund, Dauer).
///
public static async Task FinalizeChain(DatabaseService db, ChainRecord chain, TokenUsage usage, int iterations, string stopReason, Stopwatch sw, string streamErrorInfo = null)
{
chain.InputTokens = usage.InputTokens;
chain.OutputTokens = usage.OutputTokens;
chain.CacheCreationTokens = usage.CacheCreationTokens;
chain.CacheReadTokens = usage.CacheReadTokens;
chain.Iterations = iterations;
chain.StopReason = stopReason;
chain.StreamErrorInfo = streamErrorInfo;
chain.DurationMs = sw.ElapsedMilliseconds;
await db.UpdateChain(chain);
}
///
/// Kürzt Tool-Results auf max. Zeichenzahl.
///
public const int MaxToolResultChars = 20_000;
public static string TruncateToolResult(string result)
{
return result.Length > MaxToolResultChars
? result[..MaxToolResultChars] + $"\n\n[... gekürzt, {result.Length - MaxToolResultChars} weitere Zeichen ...]"
: result;
}
}
///
/// Token-Zähler für eine Chat-Session (Input, Output, Cache).
///
public class TokenUsage
{
public long InputTokens { get; set; }
public long OutputTokens { get; set; }
public long CacheCreationTokens { get; set; }
public long CacheReadTokens { get; set; }
}
///
/// Ergebnis einer Tool-Ausführung.
///
public record ToolResult(string Id, string Result, bool IsError);