use klieo_core::{ids::RunId, memory::EpisodicMemory};
use std::future::Future;
use std::sync::Arc;
use tokio::task::JoinHandle;
#[derive(Clone)]
#[non_exhaustive]
pub struct RunContext {
pub episodic: Arc<dyn EpisodicMemory>,
pub run_id: RunId,
}
impl RunContext {
#[must_use]
pub fn new(episodic: Arc<dyn EpisodicMemory>, run_id: RunId) -> Self {
Self { episodic, run_id }
}
}
tokio::task_local! {
static RUN_CTX: Option<RunContext>;
}
pub async fn scope_run_context<F, R>(ctx: Option<RunContext>, fut: F) -> R
where
F: Future<Output = R>,
{
RUN_CTX.scope(ctx, fut).await
}
#[must_use]
pub fn current_run_context() -> Option<RunContext> {
RUN_CTX.try_with(|v| v.clone()).unwrap_or(None)
}
pub fn spawn_with_run_context<F>(fut: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let ctx = current_run_context();
tokio::spawn(async move { RUN_CTX.scope(ctx, fut).await })
}
#[cfg(test)]
mod tests {
use super::*;
use klieo_core::ids::RunId;
use klieo_core::test_utils::InMemoryEpisodic;
fn make_episodic() -> Arc<dyn EpisodicMemory> {
Arc::new(InMemoryEpisodic::default())
}
#[tokio::test]
async fn current_run_context_returns_scoped_value() {
let ep = make_episodic();
let run_id = RunId::new();
let result = scope_run_context(
Some(RunContext {
episodic: ep.clone(),
run_id,
}),
async {
let ctx = current_run_context().expect("inside scope");
ctx.run_id
},
)
.await;
assert_eq!(result, run_id);
}
#[tokio::test]
async fn current_run_context_none_outside_scope() {
assert!(current_run_context().is_none());
}
#[tokio::test]
async fn spawn_with_run_context_inherits() {
let ep = make_episodic();
let run_id = RunId::new();
let inherited = scope_run_context(
Some(RunContext {
episodic: ep.clone(),
run_id,
}),
async move {
let handle =
spawn_with_run_context(async { current_run_context().expect("inherited") });
handle.await.unwrap()
},
)
.await;
assert_eq!(inherited.run_id, run_id);
}
}