use futures_util::{SinkExt, StreamExt};
use tokio_tungstenite::tungstenite::Message;
use crate::error::{Error, Result};
use crate::process::{Harness, HarnessOptions};
use crate::protocol::{
InitializeConversationEvent, InitializeConversationResponse, InputEvent, OutputEvent,
OutputEventEvent,
};
use crate::ws;
#[derive(Debug)]
pub struct RawClient {
harness: Harness,
socket: ws::Socket,
initialize: InitializeConversationResponse,
closed: bool,
}
impl RawClient {
pub async fn launch(options: HarnessOptions) -> Result<Self> {
let harness = Harness::launch(&options).await?;
let socket = ws::connect(harness.port(), harness.api_key()).await?;
let mut client = Self {
harness,
socket,
initialize: Default::default(),
closed: false,
};
let event = InitializeConversationEvent {
config: Some(options.config().clone()),
};
client.send_json(&event).await?;
match client.next_event().await? {
Some(event) => match event.into_event() {
Some(OutputEventEvent::InitializeConversationResponse(response)) => {
client.initialize = response;
}
other => {
return Err(Error::HandshakeFailed {
stderr: format!(
"expected an initialize response, got {other:?}; harness stderr: {}",
client.harness.stderr_after_exit().await
),
})
}
},
None => {
return Err(Error::HandshakeFailed {
stderr: format!(
"harness closed the socket during initialize; stderr: {}",
client.harness.stderr_after_exit().await
),
})
}
}
Ok(client)
}
pub fn initialize_response(&self) -> &InitializeConversationResponse {
&self.initialize
}
pub fn cascade_id(&self) -> Option<&str> {
self.initialize.cascade_id.as_deref()
}
pub fn harness(&self) -> &Harness {
&self.harness
}
pub async fn send(&mut self, event: &InputEvent) -> Result<()> {
self.send_json(event).await
}
async fn send_json<T: serde::Serialize>(&mut self, value: &T) -> Result<()> {
if self.closed {
return Err(Error::SessionClosed);
}
let payload = serde_json::to_string(value).map_err(|e| Error::decode(e, ""))?;
log::trace!("--> {payload}");
self.socket.send(Message::Text(payload)).await?;
Ok(())
}
pub async fn next_event(&mut self) -> Result<Option<OutputEvent>> {
loop {
let message = match self.socket.next().await {
Some(Ok(message)) => message,
Some(Err(e)) => {
self.closed = true;
let stderr = self.harness.stderr_after_exit().await;
if stderr.is_empty() {
return Err(Error::from(e));
}
return Err(Error::HandshakeFailed { stderr });
}
None => {
self.closed = true;
return Ok(None);
}
};
match message {
Message::Text(text) => {
log::trace!("<-- {text}");
let event = serde_json::from_str::<OutputEvent>(&text)
.map_err(|e| Error::decode(e, text))?;
return Ok(Some(event));
}
Message::Close(_) => {
self.closed = true;
return Ok(None);
}
Message::Binary(_) | Message::Ping(_) | Message::Pong(_) | Message::Frame(_) => {
continue
}
}
}
}
pub fn is_closed(&self) -> bool {
self.closed
}
pub async fn shutdown(mut self) -> Result<()> {
if !self.closed {
let _ = self.send(&InputEvent::session_end()).await;
loop {
match self.next_event().await {
Ok(Some(event)) => {
if event.session_end_response == Some(true) {
break;
}
}
Ok(None) => break,
Err(_) => break,
}
}
let _ = self.socket.close(None).await;
}
self.harness.shutdown().await
}
}