pub mod gemini_live;
pub mod service_adapter;
use std::sync::Arc;
use async_trait::async_trait;
use tokio::sync::Notify;
use crate::error::FlowcatError;
use crate::types::{AudioChunk, RealtimeEvent, RealtimeSetup, ToolDecl};
pub use gemini_live::{gemini_schema_subset, GeminiLive};
pub use service_adapter::ServiceRealtimeAdapter;
pub enum PollEvent {
Ready(Option<RealtimeEvent>),
Pending,
}
#[async_trait]
pub trait RealtimeLlm: Send {
async fn connect(&mut self, setup: RealtimeSetup) -> Result<(), FlowcatError>;
async fn send_audio(&mut self, chunk: AudioChunk) -> Result<(), FlowcatError>;
async fn update_system(
&mut self,
prompt: String,
tools: Vec<ToolDecl>,
) -> Result<(), FlowcatError>;
async fn send_tool_result(
&mut self,
id: String,
result: serde_json::Value,
) -> Result<(), FlowcatError>;
async fn next_event(&mut self) -> Option<RealtimeEvent>;
fn event_notify(&self) -> Option<Arc<Notify>> {
None
}
async fn poll_event(&mut self) -> PollEvent {
PollEvent::Ready(self.next_event().await)
}
fn input_sample_rate(&self) -> u32 {
16_000
}
}
#[async_trait]
pub trait RealtimeKickoff {
async fn kickoff(&mut self) -> Result<(), FlowcatError>;
}
pub trait RealtimeBackend: RealtimeLlm + RealtimeKickoff + Send {}
impl<T: RealtimeLlm + RealtimeKickoff + Send> RealtimeBackend for T {}
#[async_trait]
impl RealtimeLlm for Box<dyn RealtimeBackend> {
async fn connect(&mut self, setup: RealtimeSetup) -> Result<(), FlowcatError> {
(**self).connect(setup).await
}
async fn send_audio(&mut self, chunk: AudioChunk) -> Result<(), FlowcatError> {
(**self).send_audio(chunk).await
}
async fn update_system(
&mut self,
prompt: String,
tools: Vec<ToolDecl>,
) -> Result<(), FlowcatError> {
(**self).update_system(prompt, tools).await
}
async fn send_tool_result(
&mut self,
id: String,
result: serde_json::Value,
) -> Result<(), FlowcatError> {
(**self).send_tool_result(id, result).await
}
async fn next_event(&mut self) -> Option<RealtimeEvent> {
(**self).next_event().await
}
fn event_notify(&self) -> Option<Arc<Notify>> {
(**self).event_notify()
}
async fn poll_event(&mut self) -> PollEvent {
(**self).poll_event().await
}
fn input_sample_rate(&self) -> u32 {
(**self).input_sample_rate()
}
}
#[async_trait]
impl RealtimeKickoff for Box<dyn RealtimeBackend> {
async fn kickoff(&mut self) -> Result<(), FlowcatError> {
(**self).kickoff().await
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::VecDeque;
use std::sync::Mutex as StdMutex;
use std::time::Duration;
use tokio::sync::Mutex as AsyncMutex;
struct BufferedRealtime {
notify: Arc<Notify>,
events: StdMutex<VecDeque<Option<RealtimeEvent>>>,
}
impl BufferedRealtime {
fn new() -> Self {
Self {
notify: Arc::new(Notify::new()),
events: StdMutex::new(VecDeque::new()),
}
}
fn deliver(&self, ev: Option<RealtimeEvent>) {
self.events.lock().unwrap().push_back(ev);
self.notify.notify_one();
}
}
#[async_trait]
impl RealtimeLlm for BufferedRealtime {
async fn connect(&mut self, _: RealtimeSetup) -> Result<(), FlowcatError> {
Ok(())
}
async fn send_audio(&mut self, _: AudioChunk) -> Result<(), FlowcatError> {
Ok(())
}
async fn update_system(&mut self, _: String, _: Vec<ToolDecl>) -> Result<(), FlowcatError> {
Ok(())
}
async fn send_tool_result(
&mut self,
_: String,
_: serde_json::Value,
) -> Result<(), FlowcatError> {
Ok(())
}
async fn next_event(&mut self) -> Option<RealtimeEvent> {
self.events.lock().unwrap().pop_front().flatten()
}
fn event_notify(&self) -> Option<Arc<Notify>> {
Some(self.notify.clone())
}
async fn poll_event(&mut self) -> PollEvent {
match self.events.lock().unwrap().pop_front() {
Some(ev) => PollEvent::Ready(ev),
None => PollEvent::Pending,
}
}
}
#[tokio::test]
async fn reader_releases_lock_while_waiting_then_wakes_on_notify() {
let rt = Arc::new(AsyncMutex::new(BufferedRealtime::new()));
let notify = rt.lock().await.event_notify().expect("provides a notify");
assert!(matches!(
rt.lock().await.poll_event().await,
PollEvent::Pending
));
let rt_reader = rt.clone();
let reader = tokio::spawn(async move {
loop {
let polled = { rt_reader.lock().await.poll_event().await };
match polled {
PollEvent::Ready(ev) => break ev,
PollEvent::Pending => notify.notified().await,
}
}
});
tokio::time::sleep(Duration::from_millis(20)).await;
{
let mut guard = rt.lock().await;
guard
.send_audio(AudioChunk::new(vec![0i16; 8], 16_000))
.await
.unwrap();
guard.deliver(Some(RealtimeEvent::BotText("hi".into())));
}
let ev = tokio::time::timeout(Duration::from_secs(2), reader)
.await
.expect("no deadlock: the reader woke on the notify")
.expect("reader task joined");
assert!(matches!(ev, Some(RealtimeEvent::BotText(t)) if t == "hi"));
}
}