pub mod external;
pub mod rhai_cell;
use std::future::Future;
use std::sync::Arc;
use std::time::{Duration, Instant};
use async_trait::async_trait;
use serde_json::Value;
use super::host::RlmHostApi;
use super::types::{InterpreterSpec, RlmCancelFlag};
use crate::error::{Result, TinyAgentsError};
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CellEval {
pub stdout: String,
pub value: Option<Value>,
pub error: Option<String>,
}
#[async_trait]
pub trait RlmInterpreter: Send {
fn language(&self) -> &str;
fn usage_guide(&self) -> String;
async fn set_variable(&mut self, name: &str, value: Value) -> Result<()>;
async fn eval_cell(&mut self, code: &str, host: Arc<dyn RlmHostApi>) -> Result<CellEval>;
async fn shutdown(&mut self) -> Result<()>;
}
pub fn build_interpreter(
spec: &InterpreterSpec,
max_operations: u64,
) -> Result<Box<dyn RlmInterpreter>> {
match spec {
InterpreterSpec::Rhai => Ok(Box::new(rhai_cell::RhaiInterpreter::new(max_operations))),
InterpreterSpec::Python { binary, args } => Ok(Box::new(
external::ExternalInterpreter::python(binary.as_deref(), args.clone()),
)),
InterpreterSpec::Javascript { binary, args } => Ok(Box::new(
external::ExternalInterpreter::javascript(binary.as_deref(), args.clone()),
)),
InterpreterSpec::Command { binary, args } => Ok(Box::new(
external::ExternalInterpreter::command(binary.clone(), args.clone()),
)),
}
}
const CANCEL_POLL_INTERVAL: Duration = Duration::from_millis(25);
enum BridgeStop {
Deadline,
Cancelled,
}
pub(super) fn bridge_block_on<T, F>(
deadline: Option<Instant>,
cancel: &RlmCancelFlag,
future: F,
) -> Result<T>
where
F: Future<Output = Result<T>>,
{
if cancel.is_cancelled() {
return Err(TinyAgentsError::Cancelled);
}
if let Some(deadline) = deadline
&& Instant::now() >= deadline
{
return Err(TinyAgentsError::Timeout(
"rlm cell deadline elapsed before a host capability call could start".to_string(),
));
}
let (tx, rx) = futures::channel::oneshot::channel::<BridgeStop>();
let watcher_cancel = cancel.clone();
std::thread::spawn(move || {
loop {
if tx.is_canceled() {
return;
}
if watcher_cancel.is_cancelled() {
let _ = tx.send(BridgeStop::Cancelled);
return;
}
match deadline {
Some(deadline) => {
let now = Instant::now();
if now >= deadline {
let _ = tx.send(BridgeStop::Deadline);
return;
}
std::thread::sleep((deadline - now).min(CANCEL_POLL_INTERVAL));
}
None => std::thread::sleep(CANCEL_POLL_INTERVAL),
}
}
});
match futures::executor::block_on(futures::future::select(Box::pin(future), rx)) {
futures::future::Either::Left((output, _watcher)) => output,
futures::future::Either::Right((stop, _fut)) => match stop {
Ok(BridgeStop::Cancelled) => Err(TinyAgentsError::Cancelled),
Ok(BridgeStop::Deadline) | Err(_) => Err(TinyAgentsError::Timeout(
"rlm cell deadline elapsed during a host capability call".to_string(),
)),
},
}
}