mod request;
mod response;
mod stream_delta;
mod streaming;
pub use request::build_request_body;
pub use response::{buffered_defect, parse_response};
pub use streaming::sse_to_canonical_events;
use crate::services::ai::ModelLimits;
use crate::wire::canonical::CanonicalRequest;
pub(crate) fn is_reasoning_model(model: &str, limits: Option<ModelLimits>) -> bool {
const REASONING_PREFIXES: [&str; 4] = ["gpt-5", "o1", "o3", "o4"];
if limits
.and_then(|l| l.max_thinking_budget)
.is_some_and(|b| b > 0)
{
return true;
}
REASONING_PREFIXES
.iter()
.any(|prefix| model.starts_with(prefix))
}
pub(crate) fn output_token_ceiling(
request: &CanonicalRequest,
upstream_model: &str,
limits: Option<ModelLimits>,
) -> u32 {
passthrough_output_tokens(request.max_tokens, upstream_model, limits)
}
#[must_use]
pub fn passthrough_output_tokens(
requested: u32,
upstream_model: &str,
limits: Option<ModelLimits>,
) -> u32 {
let max_output_tokens = limits.map(|l| l.max_output_tokens);
match max_output_tokens {
Some(cap) if cap > 0 && is_reasoning_model(upstream_model, limits) => cap,
_ => super::clamp_output_tokens(requested, max_output_tokens),
}
}