flowcat_core/realtime/
service_adapter.rs1use std::sync::Arc;
23
24use async_trait::async_trait;
25
26use crate::error::FlowcatError;
27use crate::processor::frame::AudioFrame;
28use crate::realtime::{RealtimeKickoff, RealtimeLlm};
29use crate::service::RealtimeLlmService;
30use crate::types::{AudioChunk, RealtimeEvent, RealtimeSetup, ToolDecl};
31
32pub struct ServiceRealtimeAdapter<S: RealtimeLlmService> {
35 inner: S,
36}
37
38impl<S: RealtimeLlmService> ServiceRealtimeAdapter<S> {
39 pub fn new(inner: S) -> Self {
41 Self { inner }
42 }
43
44 pub fn into_inner(self) -> S {
46 self.inner
47 }
48}
49
50#[async_trait]
51impl<S: RealtimeLlmService> RealtimeLlm for ServiceRealtimeAdapter<S> {
52 async fn connect(&mut self, setup: RealtimeSetup) -> Result<(), FlowcatError> {
53 self.inner.connect(setup).await
54 }
55
56 async fn send_audio(&mut self, chunk: AudioChunk) -> Result<(), FlowcatError> {
57 self.inner
60 .send_audio(Arc::new(AudioFrame::from(chunk)))
61 .await
62 }
63
64 async fn update_system(
65 &mut self,
66 prompt: String,
67 tools: Vec<ToolDecl>,
68 ) -> Result<(), FlowcatError> {
69 self.inner.update_system(prompt, tools).await
71 }
72
73 async fn send_tool_result(
74 &mut self,
75 id: String,
76 result: serde_json::Value,
77 ) -> Result<(), FlowcatError> {
78 self.inner.send_tool_result(id, result).await
79 }
80
81 async fn next_event(&mut self) -> Option<RealtimeEvent> {
82 self.inner.next_event().await
83 }
84
85 fn input_sample_rate(&self) -> u32 {
86 self.inner.input_sample_rate()
87 }
88
89 fn event_notify(&self) -> Option<std::sync::Arc<tokio::sync::Notify>> {
95 self.inner.event_notify()
96 }
97
98 async fn poll_event(&mut self) -> crate::realtime::PollEvent {
99 self.inner.poll_event().await
100 }
101}
102
103#[async_trait]
104impl<S: RealtimeLlmService> RealtimeKickoff for ServiceRealtimeAdapter<S> {
105 async fn kickoff(&mut self) -> Result<(), FlowcatError> {
106 self.inner.kickoff().await
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[derive(Default)]
117 struct SpyService {
118 connected: bool,
119 last_audio_rate: Option<u32>,
120 kicked: bool,
121 }
122
123 #[async_trait]
124 impl RealtimeLlmService for SpyService {
125 async fn connect(&mut self, _setup: RealtimeSetup) -> Result<(), FlowcatError> {
126 self.connected = true;
127 Ok(())
128 }
129 async fn send_audio(&mut self, chunk: Arc<AudioFrame>) -> Result<(), FlowcatError> {
130 self.last_audio_rate = Some(chunk.sample_rate);
131 Ok(())
132 }
133 async fn update_system(
134 &mut self,
135 _p: String,
136 _t: Vec<ToolDecl>,
137 ) -> Result<(), FlowcatError> {
138 Ok(())
139 }
140 async fn send_tool_result(
141 &mut self,
142 _id: String,
143 _r: serde_json::Value,
144 ) -> Result<(), FlowcatError> {
145 Ok(())
146 }
147 async fn next_event(&mut self) -> Option<RealtimeEvent> {
148 None
149 }
150 fn input_sample_rate(&self) -> u32 {
151 24_000
152 }
153 async fn kickoff(&mut self) -> Result<(), FlowcatError> {
154 self.kicked = true;
155 Ok(())
156 }
157 }
158
159 #[tokio::test]
160 async fn adapter_delegates_and_converts_audio() {
161 let mut a = ServiceRealtimeAdapter::new(SpyService::default());
162 assert_eq!(a.input_sample_rate(), 24_000);
164 a.send_audio(AudioChunk::new(vec![0i16; 10], 24_000))
166 .await
167 .unwrap();
168 assert_eq!(a.inner.last_audio_rate, Some(24_000));
169 RealtimeKickoff::kickoff(&mut a).await.unwrap();
171 assert!(a.inner.kicked);
172 }
173}