use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex as StdMutex};
use async_trait::async_trait;
use meerkat_contracts::{RealtimeAudioFormat, RealtimeCapabilities, RealtimeTurningMode};
use meerkat_core::live_adapter::{
LiveAdapter, LiveAdapterCommand, LiveAdapterError, LiveAdapterObservation, LiveAdapterStatus,
LiveChannelCapabilities,
};
use meerkat_core::{Provider, SessionLlmIdentity};
use meerkat_llm_core::LlmError;
use meerkat_llm_core::realtime_session::{
RealtimeExternalSessionTarget, RealtimeSession, RealtimeSessionFactory,
RealtimeSessionOpenConfig,
};
#[derive(Debug, Clone, PartialEq)]
pub struct ScriptedRealtimeOpen {
pub identity: SessionLlmIdentity,
pub turning_mode: RealtimeTurningMode,
}
pub struct ScriptedRealtimeSessionFactory {
supported_provider: Option<Provider>,
fail_opens: AtomicBool,
opens: StdMutex<Vec<ScriptedRealtimeOpen>>,
adapters: StdMutex<Vec<Arc<ScriptedLiveAdapter>>>,
}
impl Default for ScriptedRealtimeSessionFactory {
fn default() -> Self {
Self::new()
}
}
impl ScriptedRealtimeSessionFactory {
#[must_use]
pub fn new() -> Self {
Self {
supported_provider: Some(Provider::OpenAI),
fail_opens: AtomicBool::new(false),
opens: StdMutex::new(Vec::new()),
adapters: StdMutex::new(Vec::new()),
}
}
#[must_use]
pub fn supporting_no_provider() -> Self {
Self {
supported_provider: None,
..Self::new()
}
}
pub fn fail_opens(&self) {
self.fail_opens.store(true, Ordering::SeqCst);
}
#[must_use]
pub fn opens(&self) -> Vec<ScriptedRealtimeOpen> {
self.opens
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
#[must_use]
pub fn open_count(&self) -> usize {
self.opens
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
}
#[must_use]
pub fn adapters(&self) -> Vec<Arc<ScriptedLiveAdapter>> {
self.adapters
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
fn record_open(&self, open_config: &RealtimeSessionOpenConfig) {
self.opens
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(ScriptedRealtimeOpen {
identity: open_config.llm_identity.clone(),
turning_mode: open_config.turning_mode,
});
}
}
#[async_trait]
impl RealtimeSessionFactory for ScriptedRealtimeSessionFactory {
fn capabilities(&self) -> RealtimeCapabilities {
RealtimeCapabilities {
audio_input_format: Some(RealtimeAudioFormat::pcm(24_000, 1)),
audio_output_format: Some(RealtimeAudioFormat::pcm(24_000, 1)),
..RealtimeCapabilities::default()
}
}
fn supports_provider(&self, provider: Provider) -> bool {
self.supported_provider == Some(provider)
}
async fn open_session(
&self,
_open_config: &RealtimeSessionOpenConfig,
) -> Result<Box<dyn RealtimeSession>, LlmError> {
Err(LlmError::InvalidConfig {
message: "ScriptedRealtimeSessionFactory serves open_live_adapter only".to_string(),
})
}
async fn attach_external_session(
&self,
_target: &RealtimeExternalSessionTarget,
_open_config: &RealtimeSessionOpenConfig,
) -> Result<Box<dyn RealtimeSession>, LlmError> {
Err(LlmError::InvalidConfig {
message: "ScriptedRealtimeSessionFactory serves open_live_adapter only".to_string(),
})
}
async fn open_live_adapter(
&self,
open_config: &RealtimeSessionOpenConfig,
) -> Result<Arc<dyn LiveAdapter>, LlmError> {
self.record_open(open_config);
if self.fail_opens.load(Ordering::SeqCst) {
return Err(LlmError::ConnectionReset);
}
let adapter = Arc::new(ScriptedLiveAdapter::new());
self.adapters
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(Arc::clone(&adapter));
Ok(adapter)
}
}
pub struct ScriptedLiveAdapter {
commands: StdMutex<Vec<LiveAdapterCommand>>,
ready_emitted: AtomicBool,
closed: AtomicBool,
close_notify: tokio::sync::Notify,
}
impl ScriptedLiveAdapter {
#[must_use]
pub fn new() -> Self {
Self {
commands: StdMutex::new(Vec::new()),
ready_emitted: AtomicBool::new(false),
closed: AtomicBool::new(false),
close_notify: tokio::sync::Notify::new(),
}
}
#[must_use]
pub fn commands(&self) -> Vec<LiveAdapterCommand> {
self.commands
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
}
impl Default for ScriptedLiveAdapter {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl LiveAdapter for ScriptedLiveAdapter {
async fn send_command(&self, command: LiveAdapterCommand) -> Result<(), LiveAdapterError> {
self.commands
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(command);
Ok(())
}
async fn next_observation(&self) -> Result<Option<LiveAdapterObservation>, LiveAdapterError> {
if !self.ready_emitted.swap(true, Ordering::SeqCst) {
return Ok(Some(LiveAdapterObservation::StatusChanged {
status: LiveAdapterStatus::Ready,
}));
}
loop {
let notified = self.close_notify.notified();
if self.closed.load(Ordering::SeqCst) {
return Ok(None);
}
notified.await;
}
}
fn status(&self) -> LiveAdapterStatus {
if self.closed.load(Ordering::SeqCst) {
LiveAdapterStatus::Closed
} else {
LiveAdapterStatus::Ready
}
}
async fn close(&self) -> Result<(), LiveAdapterError> {
self.closed.store(true, Ordering::SeqCst);
self.close_notify.notify_waiters();
Ok(())
}
fn capabilities(&self) -> LiveChannelCapabilities {
LiveChannelCapabilities {
audio_in: true,
audio_out: true,
text_in: true,
text_out: true,
..LiveChannelCapabilities::default()
}
}
}