use std::sync::Arc;
use std::time::Duration;
use agent_client_protocol as acp;
use agent_client_protocol_schema as schema;
use tokio::sync::{mpsc, oneshot};
use crate::error::AcpError;
pub(crate) struct ElicitationRequest {
pub create_req: schema::CreateElicitationRequest,
pub response_tx: oneshot::Sender<Result<schema::CreateElicitationResponse, AcpError>>,
}
pub(crate) type ElicitationSender = mpsc::Sender<ElicitationRequest>;
pub(crate) type ElicitationReceiver = mpsc::Receiver<ElicitationRequest>;
pub(crate) fn elicitation_channel() -> (ElicitationSender, ElicitationReceiver) {
mpsc::channel(4)
}
pub(crate) fn spawn_elicitation_bridge(
cx: acp::ConnectionTo<acp::Client>,
mut elicitation_rx: ElicitationReceiver,
cancel_signal: Arc<tokio::sync::Notify>,
timeout_secs: u64,
) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
loop {
tokio::select! {
biased;
() = cancel_signal.notified() => {
tracing::debug!("elicitation bridge: session cancelled, exiting");
break;
}
req = elicitation_rx.recv() => {
let Some(ElicitationRequest { create_req, response_tx }) = req else {
tracing::debug!("elicitation bridge: channel closed, exiting");
break;
};
let result = send_elicitation(&cx, create_req, timeout_secs).await;
let _ = response_tx.send(result);
}
}
}
})
}
async fn send_elicitation(
cx: &acp::ConnectionTo<acp::Client>,
req: schema::CreateElicitationRequest,
timeout_secs: u64,
) -> Result<schema::CreateElicitationResponse, AcpError> {
let params = serde_json::to_value(&req).map_err(|e| AcpError::ClientError(e.to_string()))?;
let msg = acp::UntypedMessage::new("elicitation/create", params)
.map_err(|e| AcpError::ClientError(e.to_string()))?;
let timeout = Duration::from_secs(timeout_secs);
let raw: serde_json::Value = tokio::time::timeout(timeout, cx.send_request(msg).block_task())
.await
.map_err(|_| AcpError::ChannelClosed)?
.map_err(|e| AcpError::ClientError(e.to_string()))?;
serde_json::from_value(raw).map_err(|e| AcpError::ClientError(e.to_string()))
}
#[allow(dead_code)]
pub(crate) struct ElicitationBridge {
pub tx: ElicitationSender,
pub timeout_secs: u64,
}
impl ElicitationBridge {
#[allow(dead_code)]
pub(crate) async fn elicit(
&self,
session_id: Arc<schema::SessionId>,
message: impl Into<String>,
requested_schema: schema::ElicitationSchema,
) -> Result<schema::ElicitationAction, AcpError> {
let scope = schema::ElicitationScope::Session(schema::ElicitationSessionScope::new(
session_id.to_string(),
));
let mode = schema::ElicitationFormMode::new(scope, requested_schema);
let create_req = schema::CreateElicitationRequest::new(mode, message);
let (response_tx, response_rx) = oneshot::channel();
self.tx
.send(ElicitationRequest {
create_req,
response_tx,
})
.await
.map_err(|_| AcpError::ChannelClosed)?;
let resp = response_rx.await.map_err(|_| AcpError::ChannelClosed)??;
Ok(resp.action)
}
}