use std::sync::Arc;
use super::session::SessionBuilder;
use crate::{
ZaiResult,
client::{ApiFamily, endpoint::EndpointConfig, secret::ApiSecret},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthMode {
Bearer,
Jwt {
ttl_seconds: i64,
},
}
#[derive(Clone)]
pub struct RealtimeClient {
api_key: Arc<ApiSecret>,
auth: AuthMode,
endpoint_config: EndpointConfig,
}
impl RealtimeClient {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: Arc::new(ApiSecret::new(api_key)),
auth: AuthMode::Bearer,
endpoint_config: EndpointConfig::defaults()
.expect("built-in realtime endpoint must be valid"),
}
}
pub fn with_jwt(mut self, ttl_seconds: i64) -> Self {
self.auth = AuthMode::Jwt { ttl_seconds };
self
}
pub fn with_bearer(mut self) -> Self {
self.auth = AuthMode::Bearer;
self
}
pub fn with_endpoint_config(mut self, config: EndpointConfig) -> Self {
self.endpoint_config = config;
self
}
pub fn try_with_realtime_base(mut self, base: impl AsRef<str>) -> ZaiResult<Self> {
self.endpoint_config =
self.endpoint_config
.with_base(ApiFamily::Realtime, base.as_ref(), false)?;
Ok(self)
}
pub fn session<M: super::RealtimeModel>(&self, model: M) -> SessionBuilder {
let realtime_url = self.realtime_url();
let model_name: String = model.into();
SessionBuilder::new(
Arc::clone(&self.api_key),
self.auth,
realtime_url,
model_name,
)
}
pub fn realtime_url(&self) -> String {
self.endpoint_config.base(ApiFamily::Realtime).to_string()
}
pub fn auth(&self) -> &AuthMode {
&self.auth
}
}
impl std::fmt::Debug for RealtimeClient {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("RealtimeClient")
.field("api_key", &self.api_key)
.field("auth", &self.auth)
.field("endpoint_config", &self.endpoint_config)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_redacts_api_key() {
let client = RealtimeClient::new("abcdefghij.0123456789abcdef");
let debug = format!("{client:?}");
assert!(debug.contains("[REDACTED]"));
assert!(!debug.contains("abcdefghij"));
}
#[test]
fn realtime_override_accepts_an_owned_non_static_base() {
let base = format!("wss://{}/realtime", "example.com");
let client = RealtimeClient::new("abcdefghij.0123456789abcdef")
.try_with_realtime_base(&base)
.unwrap();
assert_eq!(client.realtime_url(), "wss://example.com/realtime");
assert!(
client
.try_with_realtime_base("ws://public.example/realtime")
.is_err()
);
}
}