| 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<string> m_Log; |
| readonly Func<bool> 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<string> log, |
| Func<bool> 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); |
| } |
| } |
| } |
|
|