fix(rust): enforce OCR response limits and lint contracts

This commit is contained in:
Yujong Lee 2026-09-12 11:24:33 -07:00
parent 1b88828229
commit 02e150c4a4
5 changed files with 20 additions and 22 deletions

View file

@ -2,10 +2,10 @@
//! API has to resolve its own crypto provider, in a test binary where nothing
//! has installed a process-wide one, and has to leave it uninstalled.
use std::collections::HashMap;
use std::time::Duration;
use litellm_ai_gateway::io::responses_ws::ResponsesWebSocketConnection;
use futures_util::{sink, stream};
use litellm_ai_gateway::io::responses_ws::async_responses_websocket;
use tokio::net::TcpListener;
async fn dead_tls_server() -> u16 {
@ -30,10 +30,15 @@ async fn dead_tls_server() -> u16 {
async fn dialing_wss_returns_an_error_instead_of_panicking() {
let port = dead_tls_server().await;
let result = ResponsesWebSocketConnection::connect_url(
&format!("wss://127.0.0.1:{port}/"),
&HashMap::new(),
let result = async_responses_websocket(
"gpt-5",
Some("test-key"),
Some(&format!("wss://127.0.0.1:{port}/")),
None,
Some(Duration::from_secs(10)),
|_| {},
stream::empty(),
sink::drain(),
)
.await;

View file

@ -174,7 +174,7 @@ pub fn decode_request(wire: OcrWireRequest) -> Result<LiteLLMOcrRequest, Error>
value
.as_u64()
.and_then(|value| usize::try_from(value).ok())
.filter(|value| *value > 0)
.filter(|value| *value > 0 && *value <= defaults.max_response_bytes)
.ok_or_else(|| OcrRequestError::RequestField {
path: "max_response_bytes".into(),
})

View file

@ -727,6 +727,7 @@ fn response_limit_is_validated_and_not_forwarded_to_the_provider() {
json!(true),
json!("123"),
json!(1.5),
json!(crate::constants::OCR_RESPONSE_MAX_BYTES + 1),
Value::Null,
] {
let wire = serde_json::from_value(json!({

View file

@ -45,17 +45,13 @@ pub(crate) trait PythonRoute: Send + Sync {
fn traverse(&self, visit: &PyVisit<'_>) -> Result<(), PyTraverseError>;
}
type HostResumeStep<R> = HostStep<
NativeCallStep<
<<R as PythonRoute>::Call as NativeCall>::Operation,
<<R as PythonRoute>::Call as NativeCall>::Complete,
>,
Py<PyAny>,
>;
type NativeStep<C> = NativeCallStep<<C as NativeCall>::Operation, <C as NativeCall>::Complete>;
type NativeResult<C> = Result<NativeStep<C>, litellm_core::Error>;
type HostResumeStep<R> = HostStep<NativeStep<<R as PythonRoute>::Call>, Py<PyAny>>;
struct NativeCallState<C: NativeCall> {
call: C,
result: Option<Result<NativeCallStep<C::Operation, C::Complete>, litellm_core::Error>>,
result: Option<NativeResult<C>>,
}
enum PendingOperation {
@ -141,11 +137,7 @@ impl<R: PythonRoute> PythonLifecycle<R> {
}
}
fn take_native_result(
&self,
) -> PyResult<
NativeCallStep<<R::Call as NativeCall>::Operation, <R::Call as NativeCall>::Complete>,
> {
fn take_native_result(&self) -> PyResult<NativeStep<R::Call>> {
self.call
.as_ref()
.ok_or_else(missing_state)?

View file

@ -22,7 +22,7 @@ struct PythonOcrHost {
enum OcrHostData {
Unprojected { request: Py<PyAny> },
Projected(ProjectedOcrHost),
Projected(Box<ProjectedOcrHost>),
Released,
}
@ -192,13 +192,13 @@ impl PythonRoute for PythonOcrHost {
let projected = project_request(py, request.bind(py), self.state.kwargs.bind(py))?;
let has_token_provider = projected.fields.azure_ad_token_provider.is_some();
let request = projected.request;
self.data = OcrHostData::Projected(ProjectedOcrHost {
self.data = OcrHostData::Projected(Box::new(ProjectedOcrHost {
fields: projected.fields,
pre_call: None,
retained_fields: None,
body: None,
headers: None,
});
}));
OcrHostResult::Request(Ok((Box::new(request), has_token_provider)))
}
OcrHostOperation::AcquireAzureAdToken => {