use std::collections::HashMap;
use std::sync::Arc;
use async_stream::try_stream;
use async_trait::async_trait;
use futures::stream::{BoxStream, StreamExt};
use crate::error::Error;
use crate::middleware::ProviderMiddleware;
use crate::provider::Provider;
use crate::registry::Registry;
use crate::stream_event::StreamEvent;
use crate::text::{Step, TextRequest};
use crate::value_objects::Usage;
#[derive(Debug, Clone)]
pub struct RequestContext {
pub tenant_id: String,
pub user_id: Option<String>,
pub claims: serde_json::Value,
}
impl RequestContext {
pub fn new(tenant_id: impl Into<String>) -> Self {
Self {
tenant_id: tenant_id.into(),
user_id: None,
claims: serde_json::Value::Null,
}
}
pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
self.user_id = Some(user_id.into());
self
}
pub fn with_claims(mut self, claims: serde_json::Value) -> Self {
self.claims = claims;
self
}
}
#[async_trait]
pub trait TenantRegistry: Send + Sync {
async fn resolve(&self, context: &RequestContext) -> Result<Arc<Registry>, Error>;
}
#[async_trait]
impl<T: TenantRegistry + ?Sized> TenantRegistry for Arc<T> {
async fn resolve(&self, context: &RequestContext) -> Result<Arc<Registry>, Error> {
T::resolve(self, context).await
}
}
#[derive(Default)]
pub struct StaticTenantRegistry {
registries: HashMap<String, Arc<Registry>>,
}
impl StaticTenantRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with_tenant(mut self, tenant_id: impl Into<String>, registry: Registry) -> Self {
self.registries.insert(tenant_id.into(), Arc::new(registry));
self
}
}
#[async_trait]
impl TenantRegistry for StaticTenantRegistry {
async fn resolve(&self, context: &RequestContext) -> Result<Arc<Registry>, Error> {
self.registries
.get(&context.tenant_id)
.cloned()
.ok_or_else(|| Error::Store {
message: format!("unknown tenant '{}'", context.tenant_id),
})
}
}
#[async_trait]
pub trait UsageSink: Send + Sync {
async fn record(&self, tenant_id: &str, usage: Usage) -> Result<(), Error>;
}
#[async_trait]
impl<T: UsageSink + ?Sized> UsageSink for Arc<T> {
async fn record(&self, tenant_id: &str, usage: Usage) -> Result<(), Error> {
T::record(self, tenant_id, usage).await
}
}
#[derive(Debug, Default)]
pub struct InMemoryUsageSink {
recorded: std::sync::Mutex<Vec<(String, Usage)>>,
}
impl InMemoryUsageSink {
pub fn new() -> Self {
Self::default()
}
pub fn usage_for(&self, tenant_id: &str) -> Vec<Usage> {
self.recorded
.lock()
.expect("usage sink mutex poisoned")
.iter()
.filter(|(id, _)| id == tenant_id)
.map(|(_, usage)| *usage)
.collect()
}
}
#[async_trait]
impl UsageSink for InMemoryUsageSink {
async fn record(&self, tenant_id: &str, usage: Usage) -> Result<(), Error> {
self.recorded
.lock()
.expect("usage sink mutex poisoned")
.push((tenant_id.to_string(), usage));
Ok(())
}
}
pub struct UsageTrackingMiddleware<S: UsageSink> {
tenant_id: String,
sink: Arc<S>,
}
impl<S: UsageSink> UsageTrackingMiddleware<S> {
pub fn new(tenant_id: impl Into<String>, sink: S) -> Self {
Self {
tenant_id: tenant_id.into(),
sink: Arc::new(sink),
}
}
}
#[async_trait]
impl<S: UsageSink + 'static> ProviderMiddleware for UsageTrackingMiddleware<S> {
async fn text_step(&self, request: TextRequest, next: &dyn Provider) -> Result<Step, Error> {
let step = next.text_step(&request).await?;
self.sink.record(&self.tenant_id, step.usage).await?;
Ok(step)
}
async fn stream_text_once(
&self,
request: TextRequest,
next: &dyn Provider,
) -> Result<BoxStream<'static, Result<StreamEvent, Error>>, Error> {
let mut inner = next.stream_text_once(&request).await?;
let tenant_id = self.tenant_id.clone();
let sink = Arc::clone(&self.sink);
let stream = try_stream! {
while let Some(event) = inner.next().await {
let event = event?;
if let StreamEvent::StepFinish { usage, .. } = &event {
sink.record(&tenant_id, *usage).await?;
}
yield event;
}
};
Ok(stream.boxed())
}
}