use std::cell::RefCell;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use bytes::{Bytes, BytesMut};
use futures_core::Stream;
use pin_project_lite::pin_project;
use crate::error::{LiterLlmError, Result};
use crate::provider::StreamFormat;
use crate::types::ChatCompletionChunk;
#[cfg(feature = "native-http")]
pub use tokio_util::sync::CancellationToken;
#[cfg(feature = "native-http")]
type CancelField = Option<CancellationToken>;
#[cfg(not(feature = "native-http"))]
type CancelField = Option<std::convert::Infallible>;
const MAX_POOL_BUFFER_CAPACITY: usize = 64 * 1024;
thread_local! {
static EGRESS_BYTES_POOL: RefCell<Option<BytesMut>> = const { RefCell::new(None) };
}
pub(crate) fn pool_acquire() -> BytesMut {
EGRESS_BYTES_POOL.with(|cell| {
cell.borrow_mut()
.take()
.map(|mut buf| {
buf.clear();
buf
})
.unwrap_or_else(|| BytesMut::with_capacity(4096))
})
}
pub(crate) fn pool_release(buf: BytesMut) {
if buf.capacity() <= MAX_POOL_BUFFER_CAPACITY {
EGRESS_BYTES_POOL.with(|cell| {
*cell.borrow_mut() = Some(buf);
});
}
}
pub trait ChunkMiddleware: Send + Sync {
fn process(&self, chunk: ChatCompletionChunk) -> Result<Option<ChatCompletionChunk>>;
}
impl<M: ChunkMiddleware + ?Sized> ChunkMiddleware for Arc<M> {
fn process(&self, chunk: ChatCompletionChunk) -> Result<Option<ChatCompletionChunk>> {
(**self).process(chunk)
}
}
pin_project! {
pub struct IngressStream<S, P> {
#[pin]
inner: S,
buffer: String,
pending: Vec<u8>,
cursor: usize,
done: bool,
parse_event: P,
cancel: CancelField,
}
}
impl<S, P> IngressStream<S, P>
where
P: Fn(&str) -> Result<Option<ChatCompletionChunk>>,
{
pub fn new_sse(inner: S, parse_event: P, cancel: CancelField) -> Self {
Self {
inner,
buffer: String::with_capacity(4096),
pending: Vec::new(),
cursor: 0,
done: false,
parse_event,
cancel,
}
}
}
impl<S, P, E> Stream for IngressStream<S, P>
where
S: Stream<Item = std::result::Result<Bytes, E>>,
E: Into<LiterLlmError>,
P: Fn(&str) -> Result<Option<ChatCompletionChunk>>,
{
type Item = Result<ChatCompletionChunk>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.project();
#[cfg(feature = "native-http")]
if this.cancel.as_ref().is_some_and(|t| t.is_cancelled()) {
*this.done = true;
return Poll::Ready(None);
}
loop {
if let Some(offset) = memchr_newline(&this.buffer.as_bytes()[*this.cursor..]) {
let newline_pos = *this.cursor + offset;
let line = this.buffer[*this.cursor..newline_pos].trim_end_matches('\r').trim();
if line.is_empty() || line.starts_with(':') {
*this.cursor = newline_pos + 1;
compact_buffer(this.buffer, this.cursor);
continue;
}
if let Some(raw) = line.strip_prefix("data:") {
let data = raw.strip_prefix(' ').unwrap_or(raw).trim();
if data == "[DONE]" {
*this.cursor = newline_pos + 1;
compact_buffer(this.buffer, this.cursor);
return Poll::Ready(None);
}
let result = (this.parse_event)(data);
*this.cursor = newline_pos + 1;
compact_buffer(this.buffer, this.cursor);
match result {
Ok(None) => continue,
Ok(Some(chunk)) => return Poll::Ready(Some(Ok(chunk))),
Err(e) => return Poll::Ready(Some(Err(e))),
}
}
*this.cursor = newline_pos + 1;
compact_buffer(this.buffer, this.cursor);
continue;
}
if *this.done {
let remaining = this.buffer.len() - *this.cursor;
if remaining > 0 {
this.buffer.clear();
*this.cursor = 0;
}
this.pending.clear();
return Poll::Ready(None);
}
#[cfg(feature = "native-http")]
if this.cancel.as_ref().is_some_and(|t| t.is_cancelled()) {
*this.done = true;
return Poll::Ready(None);
}
match this.inner.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(bytes))) => {
const MAX_BUFFER_BYTES: usize = 1024 * 1024;
if this.buffer.len() + this.pending.len() + bytes.len() > MAX_BUFFER_BYTES {
*this.done = true;
return Poll::Ready(Some(Err(LiterLlmError::Streaming {
message: format!("SSE buffer exceeded {MAX_BUFFER_BYTES} bytes; stream aborted"),
})));
}
this.pending.extend_from_slice(&bytes);
match std::str::from_utf8(this.pending) {
Ok(s) => {
this.buffer.push_str(s);
this.pending.clear();
}
Err(e) => {
let valid = e.valid_up_to();
let complete_error = e.error_len().is_some();
this.buffer
.push_str(unsafe { std::str::from_utf8_unchecked(&this.pending[..valid]) });
if complete_error {
*this.done = true;
return Poll::Ready(Some(Err(LiterLlmError::Streaming {
message: format!("invalid UTF-8 in SSE stream: {e}"),
})));
}
this.pending.drain(..valid);
}
}
}
Poll::Ready(Some(Err(e))) => {
return Poll::Ready(Some(Err(e.into())));
}
Poll::Ready(None) => {
*this.done = true;
continue;
}
Poll::Pending => return Poll::Pending,
}
}
}
}
pin_project! {
pub struct StreamPipeline<S> {
#[pin]
inner: S,
middleware: Vec<Box<dyn ChunkMiddleware>>,
cancel: CancelField,
done: bool,
}
}
impl<S> StreamPipeline<S> {
pub fn new(inner: S, middleware: Vec<Box<dyn ChunkMiddleware>>, cancel: CancelField) -> Self {
Self {
inner,
middleware,
cancel,
done: false,
}
}
}
impl<S> Stream for StreamPipeline<S>
where
S: Stream<Item = Result<ChatCompletionChunk>>,
{
type Item = Result<ChatCompletionChunk>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.project();
if *this.done {
return Poll::Ready(None);
}
#[cfg(feature = "native-http")]
if this.cancel.as_ref().is_some_and(|t| t.is_cancelled()) {
*this.done = true;
return Poll::Ready(None);
}
loop {
#[cfg(feature = "native-http")]
if this.cancel.as_ref().is_some_and(|t| t.is_cancelled()) {
*this.done = true;
return Poll::Ready(None);
}
match this.inner.as_mut().poll_next(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => {
*this.done = true;
return Poll::Ready(None);
}
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Ready(Some(Ok(chunk))) => {
let mut accumulator: Option<ChatCompletionChunk> = Some(chunk);
let mut error: Option<LiterLlmError> = None;
for mw in this.middleware.iter() {
match accumulator.take() {
None => break,
Some(c) => match mw.process(c) {
Ok(Some(next)) => accumulator = Some(next),
Ok(None) => {
accumulator = None;
break;
}
Err(e) => {
error = Some(e);
break;
}
},
}
}
if let Some(e) = error {
return Poll::Ready(Some(Err(e)));
}
match accumulator {
None => {
continue;
}
Some(final_chunk) => return Poll::Ready(Some(Ok(final_chunk))),
}
}
}
}
}
}
enum EgressMode {
Passthrough,
ParseAndEncode(EgressEncoding),
}
enum EgressEncoding {
OpenAiSse,
}
pin_project! {
pub struct EgressStream<S> {
#[pin]
inner: S,
mode: EgressMode,
cancel: CancelField,
done: bool,
}
}
impl<S> EgressStream<S> {
pub fn new(
inner: S,
ingress_format: StreamFormat,
egress_format: StreamFormat,
middleware_count: usize,
cancel: CancelField,
) -> Self {
let mode = if ingress_format == egress_format && middleware_count == 0 {
EgressMode::Passthrough
} else {
let encoding = match egress_format {
StreamFormat::Sse | StreamFormat::AwsEventStream => EgressEncoding::OpenAiSse,
};
EgressMode::ParseAndEncode(encoding)
};
Self {
inner,
mode,
cancel,
done: false,
}
}
}
impl<S> Stream for EgressStream<S>
where
S: Stream<Item = Result<ChatCompletionChunk>>,
{
type Item = Result<Bytes>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = self.project();
if *this.done {
return Poll::Ready(None);
}
#[cfg(feature = "native-http")]
if this.cancel.as_ref().is_some_and(|t| t.is_cancelled()) {
*this.done = true;
return Poll::Ready(None);
}
match this.inner.as_mut().poll_next(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(None) => {
*this.done = true;
Poll::Ready(None)
}
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
Poll::Ready(Some(Ok(chunk))) => {
#[cfg(feature = "native-http")]
if this.cancel.as_ref().is_some_and(|t| t.is_cancelled()) {
*this.done = true;
return Poll::Ready(None);
}
match this.mode {
EgressMode::Passthrough => {
Poll::Ready(Some(encode_sse_chunk(&chunk)))
}
EgressMode::ParseAndEncode(EgressEncoding::OpenAiSse) => {
Poll::Ready(Some(encode_sse_chunk(&chunk)))
}
}
}
}
}
}
fn encode_sse_chunk(chunk: &ChatCompletionChunk) -> Result<Bytes> {
let json = serde_json::to_string(chunk).map_err(|e| LiterLlmError::Streaming {
message: format!("failed to serialise chunk: {e}"),
})?;
let mut buf = pool_acquire();
buf.extend_from_slice(b"data: ");
buf.extend_from_slice(json.as_bytes());
buf.extend_from_slice(b"\n\n");
let frozen = Bytes::copy_from_slice(&buf);
buf.clear();
pool_release(buf);
Ok(frozen)
}
#[inline]
fn memchr_newline(haystack: &[u8]) -> Option<usize> {
haystack.iter().position(|&b| b == b'\n')
}
fn compact_buffer(buffer: &mut String, cursor: &mut usize) {
if *cursor > buffer.len() / 2 {
buffer.drain(..*cursor);
*cursor = 0;
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};
use futures_util::StreamExt;
use super::*;
fn make_chunk(content: &str) -> ChatCompletionChunk {
use crate::types::chat::{StreamChoice, StreamDelta};
ChatCompletionChunk {
id: "test-id".to_string(),
object: "chat.completion.chunk".to_string(),
created: 0,
model: "test-model".to_string(),
choices: vec![StreamChoice {
index: 0,
delta: StreamDelta {
content: Some(content.to_string()),
..Default::default()
},
finish_reason: None,
}],
usage: None,
system_fingerprint: None,
service_tier: None,
}
}
fn chunk_to_sse(chunk: &ChatCompletionChunk) -> String {
format!("data: {}\n\n", serde_json::to_string(chunk).unwrap())
}
struct AppendMiddleware;
impl ChunkMiddleware for AppendMiddleware {
fn process(&self, mut chunk: ChatCompletionChunk) -> Result<Option<ChatCompletionChunk>> {
for choice in &mut chunk.choices {
if let Some(content) = &choice.delta.content {
choice.delta.content = Some(format!("{content} [mw]"));
}
}
Ok(Some(chunk))
}
}
fn sse_byte_stream(lines: Vec<String>) -> impl Stream<Item = std::result::Result<Bytes, reqwest::Error>> + Unpin {
let joined = lines.join("");
futures_util::stream::iter(vec![Ok::<_, reqwest::Error>(Bytes::from(joined))])
}
fn split_byte_stream(
parts: Vec<Vec<u8>>,
) -> impl Stream<Item = std::result::Result<Bytes, reqwest::Error>> + Unpin {
let items: Vec<_> = parts
.into_iter()
.map(|p| Ok::<_, reqwest::Error>(Bytes::from(p)))
.collect();
futures_util::stream::iter(items)
}
fn json_parse(data: &str) -> Result<Option<ChatCompletionChunk>> {
serde_json::from_str(data)
.map(Some)
.map_err(|e| LiterLlmError::Streaming { message: e.to_string() })
}
#[test]
fn egress_pool_reuses_buffer() {
let buf = pool_acquire();
let ptr_before = buf.as_ptr();
pool_release(buf);
let buf2 = pool_acquire();
let ptr_after = buf2.as_ptr();
assert_eq!(
ptr_before, ptr_after,
"egress pool should reuse the same BytesMut allocation"
);
pool_release(buf2);
}
#[test]
fn egress_pool_discards_oversized_buffers() {
let mut big = BytesMut::with_capacity(MAX_POOL_BUFFER_CAPACITY + 1);
big.resize(MAX_POOL_BUFFER_CAPACITY + 1, 0u8);
pool_release(big);
let acquired = pool_acquire();
assert!(
acquired.capacity() <= 4096,
"oversized buffer should have been discarded; got capacity {}",
acquired.capacity()
);
pool_release(acquired);
}
#[tokio::test]
async fn ingress_stream_parses_sse() {
let chunk = make_chunk("hello");
let sse_line = chunk_to_sse(&chunk);
let done = "data: [DONE]\n\n".to_string();
let byte_stream = sse_byte_stream(vec![sse_line, done]);
let parse = |data: &str| -> Result<Option<ChatCompletionChunk>> {
serde_json::from_str(data)
.map(Some)
.map_err(|e| LiterLlmError::Streaming { message: e.to_string() })
};
let mut stream = IngressStream::new_sse(byte_stream, parse, None);
let result = stream
.next()
.await
.expect("should yield one chunk")
.expect("should be Ok");
assert_eq!(result.choices[0].delta.content.as_deref(), Some("hello"));
assert!(stream.next().await.is_none());
}
#[tokio::test]
async fn should_reassemble_multibyte_char_split_across_chunks() {
let content = "你好,世界 🌍 café";
let chunk = make_chunk(content);
let sse_line = chunk_to_sse(&chunk);
let full = format!("{sse_line}data: [DONE]\n\n");
let bytes = full.into_bytes();
let split = (1..bytes.len())
.find(|&i| std::str::from_utf8(&bytes[..i]).is_err())
.expect("input contains multi-byte codepoints");
let byte_stream = split_byte_stream(vec![bytes[..split].to_vec(), bytes[split..].to_vec()]);
let mut stream = IngressStream::new_sse(byte_stream, json_parse, None);
let result = stream
.next()
.await
.expect("should yield one chunk")
.expect("split codepoint must not abort the stream");
assert_eq!(result.choices[0].delta.content.as_deref(), Some(content));
assert!(stream.next().await.is_none());
}
#[tokio::test]
async fn should_error_on_genuinely_invalid_utf8() {
let byte_stream = split_byte_stream(vec![b"data: ".to_vec(), vec![0xFF], b"\n\n".to_vec()]);
let mut stream = IngressStream::new_sse(byte_stream, json_parse, None);
match stream.next().await {
Some(Err(LiterLlmError::Streaming { message })) => {
assert!(message.contains("invalid UTF-8"), "unexpected message: {message}");
}
other => panic!("expected a Streaming UTF-8 error, got {other:?}"),
}
}
#[tokio::test]
async fn pipeline_applies_middleware_in_order() {
let chunk = make_chunk("hi");
let inner = futures_util::stream::iter(vec![Ok::<_, LiterLlmError>(chunk)]);
let mw = Box::new(AppendMiddleware) as Box<dyn ChunkMiddleware>;
let mut pipeline = StreamPipeline::new(inner, vec![mw], None);
let result = pipeline.next().await.expect("should yield").expect("should be Ok");
assert_eq!(result.choices[0].delta.content.as_deref(), Some("hi [mw]"));
}
#[tokio::test]
async fn pipeline_no_middleware_passes_through() {
let chunk = make_chunk("raw");
let inner = futures_util::stream::iter(vec![Ok::<_, LiterLlmError>(chunk.clone())]);
let mut pipeline = StreamPipeline::new(inner, vec![], None);
let result = pipeline.next().await.expect("should yield").expect("should be Ok");
assert_eq!(result, chunk);
}
#[tokio::test]
async fn egress_stream_encodes_to_sse() {
let chunk = make_chunk("world");
let inner = futures_util::stream::iter(vec![Ok::<_, LiterLlmError>(chunk.clone())]);
let mut egress = EgressStream::new(inner, StreamFormat::Sse, StreamFormat::Sse, 0, None);
let bytes = egress.next().await.expect("should yield bytes").expect("should be Ok");
let text = std::str::from_utf8(&bytes).expect("bytes should be valid UTF-8");
assert!(text.starts_with("data: "), "should start with 'data: '");
assert!(text.ends_with("\n\n"), "should end with \\n\\n");
let json_part = text.trim_start_matches("data: ").trim_end_matches("\n\n");
let decoded: ChatCompletionChunk = serde_json::from_str(json_part).expect("encoded bytes should deserialise");
assert_eq!(decoded.choices[0].delta.content.as_deref(), Some("world"));
}
#[tokio::test]
async fn ingress_egress_passthrough_avoids_reparse() {
let chunk = make_chunk("direct");
let sse_line = chunk_to_sse(&chunk);
let done = "data: [DONE]\n\n".to_string();
let parse_count = Arc::new(AtomicUsize::new(0));
let parse_count_clone = Arc::clone(&parse_count);
let byte_stream = sse_byte_stream(vec![sse_line, done]);
let ingress = IngressStream::new_sse(
byte_stream,
move |data: &str| -> Result<Option<ChatCompletionChunk>> {
parse_count_clone.fetch_add(1, Ordering::Relaxed);
serde_json::from_str(data)
.map(Some)
.map_err(|e| LiterLlmError::Streaming { message: e.to_string() })
},
None,
);
let pipeline = StreamPipeline::new(ingress, vec![], None);
let mut egress = EgressStream::new(pipeline, StreamFormat::Sse, StreamFormat::Sse, 0, None);
let mut byte_count = 0usize;
while let Some(item) = egress.next().await {
let bytes = item.expect("should be Ok");
byte_count += bytes.len();
}
assert!(byte_count > 0, "should have produced some output bytes");
assert_eq!(
parse_count.load(Ordering::Relaxed),
1,
"passthrough mode must call the parse function exactly once (ingress only)"
);
}
#[tokio::test]
async fn ingress_egress_with_middleware_reparses() {
let chunk = make_chunk("before");
let sse_line = chunk_to_sse(&chunk);
let done = "data: [DONE]\n\n".to_string();
let byte_stream = sse_byte_stream(vec![sse_line, done]);
let ingress = IngressStream::new_sse(
byte_stream,
|data: &str| -> Result<Option<ChatCompletionChunk>> {
serde_json::from_str(data)
.map(Some)
.map_err(|e| LiterLlmError::Streaming { message: e.to_string() })
},
None,
);
let mw = Box::new(AppendMiddleware) as Box<dyn ChunkMiddleware>;
let pipeline = StreamPipeline::new(ingress, vec![mw], None);
let mut egress = EgressStream::new(pipeline, StreamFormat::Sse, StreamFormat::Sse, 1, None);
let bytes = egress.next().await.expect("should yield bytes").expect("should be Ok");
let text = std::str::from_utf8(&bytes).expect("valid UTF-8");
let json_part = text.trim_start_matches("data: ").trim_end_matches("\n\n");
let decoded: ChatCompletionChunk = serde_json::from_str(json_part).expect("should deserialise");
assert_eq!(
decoded.choices[0].delta.content.as_deref(),
Some("before [mw]"),
"middleware should have mutated the chunk before re-encode"
);
}
#[tokio::test]
async fn aws_event_stream_ingress_sse_egress_round_trips() {
let chunk = make_chunk("bedrock content");
let inner = futures_util::stream::iter(vec![Ok::<ChatCompletionChunk, LiterLlmError>(chunk.clone())]);
let pipeline = StreamPipeline::new(inner, vec![], None);
let mut egress = EgressStream::new(pipeline, StreamFormat::AwsEventStream, StreamFormat::Sse, 0, None);
let bytes = egress.next().await.expect("should yield bytes").expect("should be Ok");
let text = std::str::from_utf8(&bytes).expect("valid UTF-8");
let json_part = text.trim_start_matches("data: ").trim_end_matches("\n\n");
let decoded: ChatCompletionChunk = serde_json::from_str(json_part).expect("should deserialise");
assert_eq!(
decoded.choices[0].delta.content.as_deref(),
Some("bedrock content"),
"content should be preserved through format conversion"
);
}
#[cfg(feature = "native-http")]
#[tokio::test]
async fn cancellation_propagates_through_pipeline_layers() {
use std::time::Duration;
struct NeverStream;
impl Stream for NeverStream {
type Item = std::result::Result<Bytes, reqwest::Error>;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Poll::Pending
}
}
let token = CancellationToken::new();
let cancel_clone = token.clone();
let ingress = IngressStream::new_sse(
NeverStream,
|_: &str| -> Result<Option<ChatCompletionChunk>> { Ok(None) },
Some(cancel_clone.clone()),
);
let pipeline = StreamPipeline::new(ingress, vec![], Some(cancel_clone.clone()));
let mut egress = EgressStream::new(pipeline, StreamFormat::Sse, StreamFormat::Sse, 0, Some(cancel_clone));
token.cancel();
let deadline = tokio::time::Instant::now() + Duration::from_millis(50);
let result = tokio::time::timeout_at(deadline, egress.next()).await;
match result {
Ok(None) => {}
Ok(Some(_)) => panic!("cancelled pipeline should yield None, not a chunk"),
Err(_elapsed) => panic!("cancelled pipeline did not terminate within 50ms"),
}
}
#[tokio::test]
async fn bytes_pool_reused_in_egress_under_load() {
let sentinel = pool_acquire();
let sentinel_ptr = sentinel.as_ptr();
pool_release(sentinel);
for _ in 0..100 {
let chunk = make_chunk("x");
let inner = futures_util::stream::iter(vec![Ok::<_, LiterLlmError>(chunk)]);
let pipeline = StreamPipeline::new(inner, vec![], None);
let mut egress = EgressStream::new(pipeline, StreamFormat::Sse, StreamFormat::Sse, 0, None);
while let Some(item) = egress.next().await {
item.expect("should be Ok");
}
}
let reclaimed = pool_acquire();
assert_eq!(
reclaimed.as_ptr(),
sentinel_ptr,
"pool buffer should have been reused across 100 egress streams"
);
pool_release(reclaimed);
}
}