using System.Collections.Concurrent; using System.Collections.Generic; namespace Gott.Services; /// /// Out-of-band-Kanal für "Steering"-Einwürfe: Nachrichten, die der User schickt /// WÄHREND eine Chain läuft (vgl. /btw bzw. Queued-Steering in Claude Code). /// 1:1 gespiegelt vom – keyed by sessionId. /// /// Der Streaming-Loop (AnthropicService.StreamChat) draint die Queue an den /// Iterations-Grenzen (nach einer Tool-Runde bzw. bevor die Chain finalisiert) /// und injiziert die Texte als User-Message in die laufende Konversation. /// Ein laufender messages.create-Request ist unveränderlich – Injektion geht /// nur ZWISCHEN den API-Calls, nicht mitten im Stream. /// /// Im CLI-Pfad (AnthropicService.StreamChatViaCli) läuft der Agentic-Loop im /// claude-Child; dort draint ein Kill-and-Restart am nächsten Tool-Boundary /// (Child killen, Verlauf + Einwurf an den Prompt hängen, CLI neu starten). /// public class SteeringQueue { private readonly ConcurrentDictionary> _queues = new ConcurrentDictionary>(); /// Reiht einen Einwurf für eine Session ein (FIFO). public void Enqueue(string sessionId, string text) { if (string.IsNullOrEmpty(sessionId) || string.IsNullOrWhiteSpace(text)) return; var q = _queues.GetOrAdd(sessionId, _ => new ConcurrentQueue()); q.Enqueue(text); } /// /// Entnimmt ALLE pending Einwürfe einer Session (FIFO) und gibt sie zurück. /// Leere Liste wenn nichts ansteht. /// public List Drain(string sessionId) { var result = new List(); if (_queues.TryGetValue(sessionId, out var q)) { while (q.TryDequeue(out var text)) result.Add(text); } return result; } /// Prüft ob für eine Session Einwürfe anstehen. public bool HasPending(string sessionId) => _queues.TryGetValue(sessionId, out var q) && !q.IsEmpty; /// Entfernt die Queue einer Session (beim Chain-Ende aufräumen). public void Clear(string sessionId) => _queues.TryRemove(sessionId, out _); }