openai-api-dispatch 0.1.1

OpenAI-compatible chat requests through memory or NATS queues.
Documentation
use alloc::vec::Vec;

use super::{MemoryQueue, nostd::MemoryQueueNoStd};
use crate::{
    queue::{QueueProducer as _, QueueWorker as _, WrappedTask},
    task::{Interaction, Response, Task, TaskBuilder},
};

fn task(id: u128) -> Task {
    TaskBuilder::new()
        .with_system("system prompt")
        .with_prompt("user prompt")
        .with_model("test-model")
        .with_max_tokens(128)
        .with_schema(id)
        .with_payload(id)
        .build_chat()
        .unwrap()
        .with_id(id)
}

fn response(id: u128) -> Response {
    Response::success(task(id), id as u64, "test-model", "response contents")
        .with_history(Vec::from([Interaction::assistant("assistant response")]))
}

fn wrapped(task: Task) -> WrappedTask<()> {
    WrappedTask { message: (), task }
}

#[tokio::test]
async fn tasks_are_received_in_fifo_order() {
    let queue = MemoryQueue::default();
    let tasks = [task(1), task(2), task(3)];

    for task in &tasks {
        queue.send_task(task.clone()).await.unwrap();
    }

    for task in tasks {
        assert_eq!(queue.receive_task().await.unwrap(), Some(wrapped(task)));
    }
}

#[tokio::test]
async fn responses_are_received_by_message() {
    let queue = MemoryQueue::default();
    let responses = [response(1), response(2), response(3)];
    let mut messages = Vec::new();

    for response in &responses {
        messages.push(queue.send_task(response.task.clone()).await.unwrap());
        queue.send_response((), response.clone()).await.unwrap();
    }

    for index in [2, 0, 1] {
        assert_eq!(
            queue.receive_response(messages[index]).await.unwrap(),
            Some(responses[index].clone())
        );
    }
}

#[tokio::test]
async fn clones_share_tasks_and_responses() {
    let queue = MemoryQueue::default();
    let clone = queue.clone();
    let expected_task = task(1);
    let expected_response = response(1);

    let producer_message = queue.send_task(expected_task.clone()).await.unwrap();
    let wrapped_task = clone.receive_task().await.unwrap().unwrap();
    assert_eq!(wrapped_task.task, expected_task);

    clone
        .send_response((), expected_response.clone())
        .await
        .unwrap();

    assert_eq!(
        queue.receive_response(producer_message).await.unwrap(),
        Some(expected_response)
    );
}

#[tokio::test]
async fn task_and_response_channels_are_independent() {
    let queue = MemoryQueue::default();
    let expected_task = task(1);
    let expected_response = response(1);

    queue
        .send_response((), expected_response.clone())
        .await
        .unwrap();
    let producer_message = queue.send_task(expected_task.clone()).await.unwrap();

    assert_eq!(
        queue.receive_task().await.unwrap(),
        Some(wrapped(expected_task))
    );
    assert_eq!(
        queue.receive_response(producer_message).await.unwrap(),
        Some(expected_response)
    );
}

#[tokio::test]
async fn no_std_queue_returns_none_when_empty() {
    let queue = MemoryQueueNoStd::default();

    assert_eq!(queue.receive_task().await.unwrap(), None);
    assert_eq!(queue.receive_response(1).await.unwrap(), None);
}

#[tokio::test]
async fn no_std_capacity_evicts_the_oldest_items() {
    let queue = MemoryQueueNoStd::with_capacity(2);
    let second_task = task(2);
    let third_task = task(3);
    let second_response = response(2);
    let third_response = response(3);
    let mut messages = Vec::new();

    for id in 1..=3 {
        messages.push(queue.send_task(task(id)).await.unwrap());
        queue.send_response((), response(id)).await.unwrap();
    }

    assert_eq!(
        queue.receive_task().await.unwrap(),
        Some(wrapped(second_task))
    );
    assert_eq!(
        queue.receive_task().await.unwrap(),
        Some(wrapped(third_task))
    );
    assert_eq!(queue.receive_task().await.unwrap(), None);

    assert_eq!(
        queue.receive_response(messages[1]).await.unwrap(),
        Some(second_response)
    );
    assert_eq!(
        queue.receive_response(messages[2]).await.unwrap(),
        Some(third_response)
    );
    assert_eq!(queue.receive_response(messages[0]).await.unwrap(), None);
}

#[cfg(feature = "std")]
#[tokio::test]
async fn std_capacity_applies_backpressure_without_losing_items() {
    use core::time::Duration;

    use tokio::time::timeout;

    let queue = MemoryQueue::with_capacity(1);
    let first_task = task(1);
    let second_task = task(2);

    let first_message = queue.send_task(first_task.clone()).await.unwrap();

    let blocked_send = queue.send_task(second_task.clone());
    tokio::pin!(blocked_send);
    assert!(
        timeout(Duration::from_millis(20), &mut blocked_send)
            .await
            .is_err()
    );

    assert_eq!(
        queue.receive_task().await.unwrap(),
        Some(wrapped(first_task))
    );
    let second_message = timeout(Duration::from_secs(1), blocked_send)
        .await
        .expect("send should complete after capacity becomes available")
        .unwrap();
    assert_eq!(
        queue.receive_task().await.unwrap(),
        Some(wrapped(second_task))
    );

    let first_response = response(1);
    let second_response = response(2);

    queue
        .send_response((), first_response.clone())
        .await
        .unwrap();

    let blocked_send = queue.send_response((), second_response.clone());
    tokio::pin!(blocked_send);
    assert!(
        timeout(Duration::from_millis(20), &mut blocked_send)
            .await
            .is_err()
    );

    assert_eq!(
        queue.receive_response(first_message).await.unwrap(),
        Some(first_response)
    );
    timeout(Duration::from_secs(1), blocked_send)
        .await
        .expect("send should complete after capacity becomes available")
        .unwrap();
    assert_eq!(
        queue.receive_response(second_message).await.unwrap(),
        Some(second_response)
    );
}