use agent_client_protocol::{
Client, ConnectionTo,
schema::v1::{ContentBlock, Error, PromptRequest, PromptResponse, SessionId, StopReason},
};
use super::notify;
use crate::{
approver::AcpApprover, mode::ModedApprover, session::SessionRegistry, update::session_update,
};
use basis_core::{Event, run::EventSink};
pub(super) async fn prompt(
sessions: &SessionRegistry,
connection: &ConnectionTo<Client>,
request: PromptRequest,
) -> Result<PromptResponse, Error> {
let session = sessions
.get(&request.session_id)
.ok_or_else(|| Error::invalid_params().data("unknown session"))?;
let text = prompt_text(&request.prompt);
if text.trim().is_empty() {
return Err(Error::invalid_params().data("prompt has no text content"));
}
let approver = ModedApprover::new(
session.modes().clone(),
AcpApprover::new(request.session_id.clone(), connection.clone()),
);
let sink = NotificationSink::new(request.session_id.clone(), connection.clone());
let mut run = session.lock_turn().await;
let options = session.begin_turn();
let cancelled = options.cancel.clone();
let report = run.send_with_options(text, sink, approver, options).await;
session.end_turn();
drop(run);
if cancelled.is_some_and(|token| token.is_cancelled()) {
return Ok(PromptResponse::new(StopReason::Cancelled));
}
match report {
Ok(report) if report.succeeded() => Ok(PromptResponse::new(StopReason::EndTurn)),
Ok(report) => Err(Error::internal_error().data(match report.outcome {
basis_core::RunOutcome::Error { message } => message,
basis_core::RunOutcome::Ok => "the turn failed".to_string(),
})),
Err(error) => Err(Error::internal_error().data(error.to_string())),
}
}
pub(super) fn prompt_text(blocks: &[ContentBlock]) -> String {
blocks
.iter()
.filter_map(|block| {
match block {
ContentBlock::Text(text) => Some(text.text.clone()),
ContentBlock::ResourceLink(link) => Some(format!("[{}]({})", link.name, link.uri)),
ContentBlock::Resource(resource) => match &resource.resource {
agent_client_protocol::schema::v1::EmbeddedResourceResource::TextResourceContents(
contents,
) => Some(contents.text.clone()),
_ => None,
},
_ => None,
}
})
.collect::<Vec<_>>()
.join("\n")
}
struct NotificationSink {
session_id: SessionId,
connection: ConnectionTo<Client>,
}
impl NotificationSink {
fn new(session_id: SessionId, connection: ConnectionTo<Client>) -> Self {
Self {
session_id,
connection,
}
}
}
impl EventSink for NotificationSink {
fn emit(&mut self, event: Event) -> std::io::Result<()> {
let Some(update) = session_update(&event) else {
return Ok(());
};
notify(&self.connection, &self.session_id, update)
.map_err(|error| std::io::Error::other(error.to_string()))
}
}