use std::io::{Read, Write};
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::bindings::hook::exports::murmur::hook::lifecycle::{HookOutput, StageEvent};
use crate::bindings::hook::murmur::conversation::read::MessagePage;
use crate::bindings::hook::murmur::runtime::inference::{InferenceRequest, InferenceResponse};
use crate::bindings::hook::murmur::task_io::read::{IoError, TaskInputForm};
use crate::bindings::host::murmur::tool::run::{ToolInput, ToolResult};
use crate::hooks::{HookArg, HookSpec};
use crate::inference_import::HookInferencePurpose;
use crate::limits::CallDeadline;
use crate::process_driver::{DriverAnswer, DriverCall, DriverError};
use crate::runtime::{CapsuleCall, CapsuleReply, CapsuleRunError, CapsuleSpec, ToolCallSpec};
use crate::session_process::{ProcessCall, ProcessReply};
use super::WorkerKind;
pub(crate) const MAX_FRAME_BYTES: u32 = 256 * 1024 * 1024;
#[derive(Debug, Serialize, Deserialize)]
pub(crate) enum ToWorker {
Init { kind: WorkerKind },
Load {
req: u64,
guest: u64,
name: String,
#[serde(with = "raw_bytes")]
form: Vec<u8>,
},
RunTool {
req: u64,
guest: u64,
spec: Box<ToolCallSpec>,
input: ToolInput,
},
HookOpen {
req: u64,
guest: u64,
instance: u64,
spec: Box<HookSpec>,
},
HookCall {
req: u64,
instance: u64,
fn_name: String,
arg: Box<HookArg>,
purpose: HookInferencePurpose,
},
HookStage {
req: u64,
guest: u64,
spec: Box<HookSpec>,
event: StageEvent,
},
HookClose { instance: u64 },
DriverOpen {
req: u64,
guest: u64,
instance: u64,
name: String,
version: String,
},
DriverCall {
req: u64,
instance: u64,
call: Box<DriverCall>,
},
DriverClose { instance: u64 },
RunCapsule {
req: u64,
guest: u64,
spec: Box<CapsuleSpec>,
},
Cancel { req: u64 },
HostReply { host: u64, reply: HostReply },
BodyChunk {
stream: u64,
#[serde(with = "raw_bytes")]
bytes: Vec<u8>,
},
BodyEnd {
stream: u64,
error: Option<HttpError>,
},
Shutdown,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) enum FromWorker {
Hello {
protocol: u32,
version: String,
pid: u32,
},
Reply { req: u64, reply: Reply },
HostCall {
host: u64,
call: u64,
request: HostRequest,
},
Event { call: u64, event: StreamEvent },
RunStarted { call: u64, deadline: Duration },
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) enum Reply {
Loaded(Result<(), String>),
Tool {
result: Result<ToolResult, String>,
deadlines: Vec<CallDeadline>,
},
HookOpened {
result: Result<Vec<String>, String>,
deadlines: Vec<CallDeadline>,
},
Hook {
result: Result<Option<HookOutput>, String>,
deadlines: Vec<CallDeadline>,
},
Driver {
result: Result<DriverAnswer, DriverError>,
deadlines: Vec<CallDeadline>,
},
Capsule {
result: Result<(), CapsuleRunError>,
deadlines: Vec<CallDeadline>,
},
}
impl Reply {
pub(crate) fn take_deadlines(&mut self) -> Vec<CallDeadline> {
match self {
Self::Loaded(_) => Vec::new(),
Self::Tool { deadlines, .. }
| Self::HookOpened { deadlines, .. }
| Self::Hook { deadlines, .. }
| Self::Driver { deadlines, .. }
| Self::Capsule { deadlines, .. } => std::mem::take(deadlines),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) enum HostRequest {
Http(HttpRequestHead),
RequestInput { prompt: String },
Process(ProcessCall),
Inference {
purpose: HookInferencePurpose,
request: InferenceRequest,
},
TaskIo(TaskIoCall),
Conversation { cursor: Option<String>, limit: u32 },
Capsule(Box<CapsuleCall>),
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) enum TaskIoCall {
InputLen(TaskInputForm),
ReadInput(TaskInputForm, u64, u64),
OutputLen,
ReadOutput(u64, u64),
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) enum TaskIoReply {
Len(Result<u64, IoError>),
Text(Result<String, IoError>),
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) enum HostReply {
Http(Result<HttpResponseHead, HttpError>),
Input(Result<String, String>),
Process(ProcessReply),
Inference(Result<InferenceResponse, String>),
TaskIo(TaskIoReply),
Conversation(Result<MessagePage, String>),
Capsule(Box<CapsuleReply>),
Refused(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) enum StreamEvent {
Chunk(String),
ThinkingChunk(String),
ToolCallStarted { id: String, name: String },
ToolCallInputBytes { id: String, bytes: u64 },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct HttpRequestHead {
pub(crate) method: String,
pub(crate) uri: String,
pub(crate) headers: Vec<(String, Vec<u8>)>,
pub(crate) connect_timeout: Option<Duration>,
pub(crate) first_byte_timeout: Option<Duration>,
pub(crate) between_bytes_timeout: Option<Duration>,
pub(crate) has_options: bool,
#[serde(with = "raw_bytes")]
pub(crate) body: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct HttpResponseHead {
pub(crate) status: u16,
pub(crate) headers: Vec<(String, Vec<u8>)>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) enum HttpError {
HttpRequestBodySize(Option<u64>),
DnsTimeout,
DnsError {
rcode: Option<String>,
info_code: Option<u16>,
},
DestinationNotFound,
DestinationUnavailable,
DestinationIpProhibited,
DestinationIpUnroutable,
ConnectionRefused,
ConnectionTerminated,
ConnectionTimeout,
ConnectionReadTimeout,
ConnectionWriteTimeout,
ConnectionLimitReached,
TlsProtocolError,
TlsCertificateError,
TlsAlertReceived {
alert_id: Option<u8>,
alert_message: Option<String>,
},
HttpRequestDenied,
HttpRequestLengthRequired,
HttpRequestMethodInvalid,
HttpRequestUriInvalid,
HttpRequestUriTooLong,
HttpRequestHeaderSectionSize(Option<u32>),
HttpRequestHeaderSize {
field_name: Option<String>,
field_size: Option<u32>,
},
HttpRequestTrailerSectionSize(Option<u32>),
HttpRequestTrailerSize {
field_name: Option<String>,
field_size: Option<u32>,
},
HttpResponseIncomplete,
HttpResponseHeaderSectionSize(Option<u32>),
HttpResponseHeaderSize {
field_name: Option<String>,
field_size: Option<u32>,
},
HttpResponseBodySize(Option<u64>),
HttpResponseTrailerSectionSize(Option<u32>),
HttpResponseTrailerSize {
field_name: Option<String>,
field_size: Option<u32>,
},
HttpResponseTransferCoding(Option<String>),
HttpResponseContentCoding(Option<String>),
HttpResponseTimeout,
HttpUpgradeFailed,
HttpProtocolError,
LoopDetected,
ConfigurationError,
InternalError(Option<String>),
}
mod raw_bytes {
use serde::de::{Deserializer, Error, Visitor};
use serde::Serializer;
pub(crate) fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_bytes(bytes)
}
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<u8>, D::Error> {
struct Bytes;
impl<'de> Visitor<'de> for Bytes {
type Value = Vec<u8>;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a byte buffer")
}
fn visit_bytes<E: Error>(self, bytes: &[u8]) -> Result<Vec<u8>, E> {
Ok(bytes.to_vec())
}
fn visit_byte_buf<E: Error>(self, bytes: Vec<u8>) -> Result<Vec<u8>, E> {
Ok(bytes)
}
}
deserializer.deserialize_byte_buf(Bytes)
}
}
#[derive(Debug)]
pub(crate) enum FrameError {
Closed,
Io(std::io::Error),
Oversize(u32),
Decode(String),
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Closed => write!(f, "the connection closed"),
Self::Io(error) => write!(f, "{error}"),
Self::Oversize(len) => write!(
f,
"a frame of {len} bytes exceeds the {MAX_FRAME_BYTES}-byte limit"
),
Self::Decode(error) => write!(f, "a frame did not decode: {error}"),
}
}
}
pub(crate) fn encode<T: Serialize>(message: &T) -> Result<Vec<u8>, String> {
let body = postcard::to_stdvec(message).map_err(|error| error.to_string())?;
let len = u32::try_from(body.len())
.ok()
.filter(|len| *len <= MAX_FRAME_BYTES)
.ok_or_else(|| format!("a {}-byte frame exceeds the limit", body.len()))?;
let mut frame = Vec::with_capacity(4 + body.len());
frame.extend_from_slice(&len.to_le_bytes());
frame.extend_from_slice(&body);
Ok(frame)
}
pub(crate) fn write_frame<T: Serialize>(
writer: &mut impl Write,
message: &T,
) -> Result<(), String> {
let frame = encode(message)?;
writer
.write_all(&frame)
.map_err(|error| error.to_string())?;
writer.flush().map_err(|error| error.to_string())
}
pub(crate) fn read_frame<T: for<'de> Deserialize<'de>>(
reader: &mut impl Read,
) -> Result<T, FrameError> {
let mut len = [0u8; 4];
let mut got = 0;
while got < len.len() {
match reader.read(&mut len[got..]) {
Ok(0) if got == 0 => return Err(FrameError::Closed),
Ok(0) => {
return Err(FrameError::Io(std::io::Error::from(
std::io::ErrorKind::UnexpectedEof,
)))
}
Ok(read) => got += read,
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
Err(error) => return Err(FrameError::Io(error)),
}
}
let len = u32::from_le_bytes(len);
if len > MAX_FRAME_BYTES {
return Err(FrameError::Oversize(len));
}
let mut body = vec![0u8; len as usize];
reader.read_exact(&mut body).map_err(FrameError::Io)?;
postcard::from_bytes(&body).map_err(|error| FrameError::Decode(error.to_string()))
}