use sacp::schema::{
AudioContent, ContentBlock, EmbeddedResourceResource, ImageContent, InitializeRequest,
NewSessionRequest, PromptRequest, RequestPermissionOutcome, RequestPermissionRequest,
RequestPermissionResponse, SessionNotification, TextContent, VERSION as PROTOCOL_VERSION,
};
use sacp::{Component, Handled, JrHandlerChain, MessageAndCx, UntypedMessage};
use std::path::PathBuf;
pub fn content_block_to_string(block: &ContentBlock) -> String {
match block {
ContentBlock::Text(TextContent { text, .. }) => text.clone(),
ContentBlock::Image(ImageContent { mime_type, .. }) => {
format!("[Image: {}]", mime_type)
}
ContentBlock::Audio(AudioContent { mime_type, .. }) => {
format!("[Audio: {}]", mime_type)
}
ContentBlock::ResourceLink(link) => link.uri.clone(),
ContentBlock::Resource(resource) => match &resource.resource {
EmbeddedResourceResource::TextResourceContents(text) => text.uri.clone(),
EmbeddedResourceResource::BlobResourceContents(blob) => blob.uri.clone(),
},
}
}
pub async fn prompt_with_callback(
component: impl Component,
prompt_text: impl ToString,
mut callback: impl AsyncFnMut(ContentBlock) + Send,
) -> Result<(), sacp::Error> {
let prompt_text = prompt_text.to_string();
JrHandlerChain::new()
.on_receive_message(async |message_and_cx: MessageAndCx<UntypedMessage>| {
tracing::trace!("received: {:?}", message_and_cx.message());
Ok(Handled::No(message_and_cx))
})
.on_receive_notification(async move |notification: SessionNotification, _cx| {
if let sacp::schema::SessionUpdate::AgentMessageChunk(content_chunk) =
notification.update
{
callback(content_chunk.content).await;
}
Ok(())
})
.on_receive_request(async move |request: RequestPermissionRequest, request_cx| {
let outcome = if let Some(option) = request.options.first() {
RequestPermissionOutcome::Selected {
option_id: option.id.clone(),
}
} else {
RequestPermissionOutcome::Cancelled
};
request_cx.respond(RequestPermissionResponse {
outcome,
meta: None,
})
})
.connect_to(component)?
.with_client(|cx: sacp::JrConnectionCx| async move {
let _init_response = cx
.send_request(InitializeRequest {
protocol_version: PROTOCOL_VERSION,
client_capabilities: Default::default(),
client_info: None,
meta: None,
})
.block_task()
.await?;
let new_session_response = cx
.send_request(NewSessionRequest {
cwd: PathBuf::from("."),
mcp_servers: vec![],
meta: None,
})
.block_task()
.await?;
let session_id = new_session_response.session_id;
let _prompt_response = cx
.send_request(PromptRequest {
session_id,
prompt: vec![ContentBlock::Text(TextContent {
annotations: None,
text: prompt_text,
meta: None,
})],
meta: None,
})
.block_task()
.await?;
Ok(())
})
.await?;
Ok(())
}
pub async fn prompt(
component: impl Component,
prompt_text: impl ToString,
) -> Result<String, sacp::Error> {
let mut accumulated_text = String::new();
prompt_with_callback(component, prompt_text, async |block| {
let text = content_block_to_string(&block);
accumulated_text.push_str(&text);
})
.await?;
Ok(accumulated_text)
}