using System; using System.Threading; using OnDeviceAgent.AgentCore; namespace OnDeviceAgent.VoiceManager { public sealed class VoiceEventRelay { readonly IAgentRuntime m_Runtime; readonly AgentInterruptController m_Interrupt; readonly AgentEventBus m_EventBus; readonly AgentTraceStore m_TraceStore; readonly Action m_Log; readonly Func m_StreamingPreferred; public bool EnableVoiceEvents { get; set; } = true; public bool EnableInterrupts { get; set; } = true; public bool EnableBargeIn { get; set; } = true; public bool EnableEventBus { get; set; } = true; public VoiceEventRelay( IAgentRuntime runtime, AgentInterruptController interrupt, AgentEventBus eventBus, AgentTraceStore traceStore, Action log, Func streamingPreferred) { if (runtime == null) throw new ArgumentNullException(nameof(runtime)); if (interrupt == null) throw new ArgumentNullException(nameof(interrupt)); if (eventBus == null) throw new ArgumentNullException(nameof(eventBus)); if (traceStore == null) throw new ArgumentNullException(nameof(traceStore)); m_Runtime = runtime; m_Interrupt = interrupt; m_EventBus = eventBus; m_TraceStore = traceStore; m_Log = log ?? (_ => { }); m_StreamingPreferred = streamingPreferred ?? (() => true); } public void OnFinalTranscript(string text) { if (!EnableVoiceEvents || string.IsNullOrWhiteSpace(text)) return; if (m_Runtime.IsRunning && TryHandleInterruptPrompt(text, "Voice")) return; if (EnableBargeIn && m_Runtime.IsRunning) { m_Log("Voice barge-in: cancelling current run."); m_Runtime.TryInterrupt("voice barge-in"); } PublishAgentEvent("Voice", text, AgentEventPriority.High); } public void ProcessQueuedEvents() { if (m_Runtime.IsRunning) return; AgentEvent agentEvent; if (!m_EventBus.TryDequeue(out agentEvent)) return; StartRunWithPrompt(agentEvent.Prompt, agentEvent.Source); } void PublishAgentEvent(string source, string prompt, AgentEventPriority priority) { if (EnableEventBus) { m_EventBus.Publish(source, prompt, priority); m_TraceStore.RecordEvent("event", source + " queued."); return; } if (m_Runtime.IsRunning) { m_EventBus.Publish(source, prompt, priority); return; } StartRunWithPrompt(prompt, source); } bool TryHandleInterruptPrompt(string prompt, string origin) { if (!EnableInterrupts || !m_Runtime.IsRunning) return false; AgentInterruptRequest request; if (!m_Interrupt.TryCreateInterrupt(prompt, out request)) return false; var originText = string.IsNullOrWhiteSpace(origin) ? "User" : origin.Trim(); m_Log(originText + " interrupt: " + request.Reason); m_Runtime.TryInterrupt(request.Reason); if (!string.IsNullOrWhiteSpace(request.FollowUpPrompt)) PublishAgentEvent(origin, request.FollowUpPrompt, AgentEventPriority.High); return true; } void StartRunWithPrompt(string prompt, string origin) { var label = string.IsNullOrWhiteSpace(origin) ? "Voice" : origin.Trim(); m_Log(label + ": " + (prompt ?? string.Empty)); var request = new AgentRunRequest(prompt, origin, m_StreamingPreferred(), null); m_Runtime.RunAsync(request, CancellationToken.None).ContinueWith( t => m_Log("RunAsync fault: " + t.Exception), System.Threading.Tasks.TaskContinuationOptions.OnlyOnFaulted); } } }