mod anthropic;
mod gemini;
mod openai;
mod responses;
pub mod types;
use llmposter::{Fixture, MockServer, ServerBuilder, ToolCall};
use reqwest::Client;
pub async fn server_with_text(user_message: &str, content: &str) -> (MockServer, Client) {
let server = ServerBuilder::new()
.fixture(
Fixture::new()
.match_user_message(user_message)
.respond_with_content(content),
)
.build()
.await
.unwrap();
(server, Client::new())
}
pub async fn server_with_tool_call(
user_message: &str,
name: &str,
args: serde_json::Value,
) -> (MockServer, Client) {
let server = ServerBuilder::new()
.fixture(
Fixture::new()
.match_user_message(user_message)
.respond_with_tool_calls(vec![ToolCall {
name: name.to_string(),
arguments: args,
}]),
)
.build()
.await
.unwrap();
(server, Client::new())
}
pub fn parse_sse_data(body: &str) -> Vec<String> {
body.lines()
.filter(|line| line.starts_with("data: "))
.map(|line| line.trim_start_matches("data: ").to_string())
.filter(|data| data != "[DONE]")
.collect()
}
pub fn parse_typed_sse(body: &str) -> Vec<(String, String)> {
let mut events = Vec::new();
let mut current_event = String::new();
let mut current_data = String::new();
for line in body.lines() {
if line.starts_with("event: ") {
current_event = line.trim_start_matches("event: ").to_string();
current_data.clear();
} else if line.starts_with("data: ") {
let payload = line.trim_start_matches("data: ");
if !current_data.is_empty() {
current_data.push('\n');
}
current_data.push_str(payload);
} else if line.is_empty() {
if !current_event.is_empty() {
events.push((current_event.clone(), current_data.clone()));
current_event.clear();
}
current_data.clear();
}
}
if !current_event.is_empty() {
events.push((current_event, current_data));
}
events
}
pub fn has_done_sentinel(body: &str) -> bool {
body.lines()
.rfind(|line| line.starts_with("data: "))
.map(|line| line.trim_start_matches("data: ") == "[DONE]")
.unwrap_or(false)
}