use std::future::{Future, IntoFuture};
use std::pin::Pin;
use std::sync::Arc;
use serde_json::Value as JsonValue;
use tokio::sync::{broadcast, mpsc};
use tokio::task::JoinHandle;
use funera_core::chat::session::SessionCmd;
use funera_core::event_bus::env_state_bus::EnvStateEvent;
use funera_core::provider::ChatProvider;
use crate::error::OrchestrateError;
use crate::event::AgentEvent;
use crate::response::{ChatResponse, ToolCallInfo};
use crate::runtime::{Acquired, AgentRuntime, Idle};
pub struct SendHandle<P: ChatProvider> {
pub(crate) runtime: AgentRuntime<P, Acquired>,
pub(crate) handle: JoinHandle<anyhow::Result<()>>,
pub(crate) event_rx: broadcast::Receiver<AgentEvent>,
pub(crate) env_state_tx: broadcast::Sender<EnvStateEvent>,
}
impl<P: ChatProvider> SendHandle<P> {
pub async fn session_context(&self) -> Vec<JsonValue> {
let (respond, rx) = tokio::sync::oneshot::channel();
let _ = self
.runtime
.session_tx
.send(SessionCmd::FetchContext { respond });
rx.await.unwrap_or_default()
}
#[cfg(all(feature = "tool", feature = "security"))]
pub fn approval_handle(&self) -> ApprovalHandle {
ApprovalHandle::new(self.runtime.env_cmd_tx.clone())
}
async fn wait(self) -> Result<(AgentRuntime<P, Idle>, ChatResponse), OrchestrateError> {
self.handle
.await
.map_err(|e| OrchestrateError::Session(e.into()))??;
let _ = self.env_state_tx.send(EnvStateEvent::SessionClosed);
let resp = aggregate_from_broadcast(self.event_rx).await?;
Ok((self.runtime.into_idle(), resp))
}
}
impl<P: ChatProvider + 'static> IntoFuture for SendHandle<P> {
type Output = Result<(AgentRuntime<P, Idle>, ChatResponse), OrchestrateError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.wait().await })
}
}
pub struct SendStreamHandle<P: ChatProvider> {
pub(crate) runtime: AgentRuntime<P, Acquired>,
pub(crate) handle: JoinHandle<anyhow::Result<()>>,
pub(crate) event_rx: broadcast::Receiver<AgentEvent>,
pub(crate) stream_rx: mpsc::Receiver<AgentEvent>,
pub(crate) env_state_tx: broadcast::Sender<EnvStateEvent>,
}
impl<P: ChatProvider> SendStreamHandle<P> {
pub async fn recv(&mut self) -> Option<AgentEvent> {
self.stream_rx.recv().await
}
pub async fn session_context(&self) -> Vec<JsonValue> {
let (respond, rx) = tokio::sync::oneshot::channel();
let _ = self
.runtime
.session_tx
.send(SessionCmd::FetchContext { respond });
rx.await.unwrap_or_default()
}
#[cfg(all(feature = "tool", feature = "security"))]
pub fn approval_handle(&self) -> ApprovalHandle {
ApprovalHandle::new(self.runtime.env_cmd_tx.clone())
}
async fn wait(self) -> Result<(AgentRuntime<P, Idle>, ChatResponse), OrchestrateError> {
self.handle
.await
.map_err(|e| OrchestrateError::Session(e.into()))??;
let _ = self.env_state_tx.send(EnvStateEvent::SessionClosed);
let resp = aggregate_from_broadcast(self.event_rx).await?;
Ok((self.runtime.into_idle(), resp))
}
}
impl<P: ChatProvider + 'static> IntoFuture for SendStreamHandle<P> {
type Output = Result<(AgentRuntime<P, Idle>, ChatResponse), OrchestrateError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.wait().await })
}
}
pub struct FireStreamHandle {
pub(crate) handle: JoinHandle<anyhow::Result<()>>,
pub(crate) event_rx: broadcast::Receiver<AgentEvent>,
pub(crate) stream_rx: mpsc::Receiver<AgentEvent>,
pub(crate) env_state_tx: broadcast::Sender<EnvStateEvent>,
}
impl FireStreamHandle {
pub async fn recv(&mut self) -> Option<AgentEvent> {
self.stream_rx.recv().await
}
async fn wait(self) -> Result<ChatResponse, OrchestrateError> {
self.handle
.await
.map_err(|e| OrchestrateError::Session(e.into()))??;
let _ = self.env_state_tx.send(EnvStateEvent::SessionClosed);
aggregate_from_broadcast(self.event_rx).await
}
}
impl IntoFuture for FireStreamHandle {
type Output = Result<ChatResponse, OrchestrateError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.wait().await })
}
}
async fn aggregate_from_broadcast(
mut event_rx: broadcast::Receiver<AgentEvent>,
) -> Result<ChatResponse, OrchestrateError> {
let mut content = String::new();
let mut tool_calls = Vec::new();
let mut iterations = 0usize;
let mut finish_reason: Option<String> = None;
let mut pending: Vec<(Arc<str>, String, serde_json::Value)> = Vec::new();
loop {
match event_rx.recv().await {
Ok(AgentEvent::Text(t)) => content = t,
Ok(AgentEvent::ToolCallRequest {
call_id,
name,
args,
..
}) => {
pending.push((call_id, name, args));
}
Ok(AgentEvent::ToolCallResult {
call_id,
name: _,
result,
}) => {
if let Some(pos) = pending.iter().position(|(id, _, _)| *id == call_id) {
let (_, name, args) = pending.remove(pos);
tool_calls.push(ToolCallInfo { name, args, result });
}
}
Ok(AgentEvent::TurnStart) => iterations += 1,
Ok(AgentEvent::TurnEnd { finish_reason: fr }) => finish_reason = fr,
Ok(AgentEvent::Done) => break,
Err(broadcast::error::RecvError::Closed) => break,
Err(broadcast::error::RecvError::Lagged(_)) => continue,
_ => {}
}
}
Ok(ChatResponse {
content,
tool_calls,
iterations,
finish_reason,
})
}
#[cfg(all(feature = "tool", feature = "security"))]
#[derive(Clone)]
pub struct ApprovalHandle {
env_cmd_tx: mpsc::UnboundedSender<funera_core::env_actor::EnvCmd>,
}
#[cfg(all(feature = "tool", feature = "security"))]
impl ApprovalHandle {
pub(crate) fn new(env_cmd_tx: mpsc::UnboundedSender<funera_core::env_actor::EnvCmd>) -> Self {
Self { env_cmd_tx }
}
pub async fn approve_tool_call(&self, call_id: &str, approved: bool) -> Result<(), String> {
let (respond, rx) = tokio::sync::oneshot::channel();
let _ = self
.env_cmd_tx
.send(funera_core::env_actor::EnvCmd::ApproveToolCall {
call_id: call_id.to_string(),
approved,
respond,
});
rx.await.unwrap_or(Err("env actor died".into()))
}
}