use crate::audio::AudioChunk;
use crate::error::Result;
use crate::events::{ClientEvent, ServerEvent, ToolResponse};
use async_trait::async_trait;
use futures::Stream;
use std::pin::Pin;
#[derive(Debug, Clone)]
pub enum ContextMutationOutcome {
Applied,
RequiresResumption(Box<crate::config::RealtimeConfig>),
}
#[async_trait]
pub trait RealtimeSession: Send + Sync {
fn session_id(&self) -> &str;
fn is_connected(&self) -> bool;
async fn send_audio(&self, audio: &AudioChunk) -> Result<()>;
async fn send_audio_base64(&self, audio_base64: &str) -> Result<()>;
async fn send_text(&self, text: &str) -> Result<()>;
async fn send_tool_response(&self, response: ToolResponse) -> Result<()>;
async fn commit_audio(&self) -> Result<()>;
async fn clear_audio(&self) -> Result<()>;
async fn create_response(&self) -> Result<()>;
async fn interrupt(&self) -> Result<()>;
async fn send_event(&self, event: ClientEvent) -> Result<()>;
async fn next_event(&self) -> Option<Result<ServerEvent>>;
fn events(&self) -> Pin<Box<dyn Stream<Item = Result<ServerEvent>> + Send + '_>>;
async fn close(&self) -> Result<()>;
async fn mutate_context(
&self,
config: crate::config::RealtimeConfig,
) -> Result<ContextMutationOutcome>;
}
#[async_trait]
pub trait RealtimeSessionExt: RealtimeSession {
async fn send_audio_and_wait(&self, audio: &AudioChunk) -> Result<Vec<ServerEvent>> {
self.send_audio(audio).await?;
self.commit_audio().await?;
let mut events = Vec::new();
while let Some(event) = self.next_event().await {
let event = event?;
let is_done = matches!(&event, ServerEvent::ResponseDone { .. });
events.push(event);
if is_done {
break;
}
}
Ok(events)
}
async fn send_text_and_wait(&self, text: &str) -> Result<Vec<ServerEvent>> {
self.send_text(text).await?;
self.create_response().await?;
let mut events = Vec::new();
while let Some(event) = self.next_event().await {
let event = event?;
let is_done = matches!(&event, ServerEvent::ResponseDone { .. });
events.push(event);
if is_done {
break;
}
}
Ok(events)
}
async fn collect_audio(&self) -> Result<Vec<Vec<u8>>> {
let mut audio_chunks = Vec::new();
while let Some(event) = self.next_event().await {
match event? {
ServerEvent::AudioDelta { delta, .. } => {
audio_chunks.push(delta);
}
ServerEvent::ResponseDone { .. } => break,
ServerEvent::Error { error, .. } => {
return Err(crate::error::RealtimeError::server(
error.code.unwrap_or_default(),
error.message,
));
}
_ => {}
}
}
Ok(audio_chunks)
}
}
impl<T: RealtimeSession> RealtimeSessionExt for T {}
pub type BoxedSession = Box<dyn RealtimeSession>;