From 06be158da8cb122b24bbbf5ea5b93a292eb23e9b Mon Sep 17 00:00:00 2001 From: nightcityblade Date: Mon, 1 Jun 2026 23:27:56 +0800 Subject: [PATCH] feat: send request timeout header --- src/openai/_base_client.py | 14 +++++++++----- tests/test_client.py | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/openai/_base_client.py b/src/openai/_base_client.py index 17863bc067..a3b0e3f747 100644 --- a/src/openai/_base_client.py +++ b/src/openai/_base_client.py @@ -451,12 +451,16 @@ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0 lower_custom_headers = [header.lower() for header in custom_headers] if "x-stainless-retry-count" not in lower_custom_headers: headers["x-stainless-retry-count"] = str(retries_taken) + timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout if "x-stainless-read-timeout" not in lower_custom_headers: - timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout - if isinstance(timeout, Timeout): - timeout = timeout.read - if timeout is not None: - headers["x-stainless-read-timeout"] = str(timeout) + read_timeout = timeout + if isinstance(read_timeout, Timeout): + read_timeout = read_timeout.read + if read_timeout is not None: + headers["x-stainless-read-timeout"] = str(read_timeout) + if "request-timeout-ms" not in lower_custom_headers: + if not isinstance(timeout, Timeout) and timeout is not None: + headers["request-timeout-ms"] = str(int(timeout * 1000)) return headers diff --git a/tests/test_client.py b/tests/test_client.py index 396f6dea99..07a717d9c5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1002,6 +1002,30 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + @pytest.mark.respx(base_url=base_url) + @pytest.mark.parametrize( + ("timeout", "extra_headers", "expected"), + [ + (12.5, {}, "12500"), + (httpx.Timeout(12.5), {}, None), + (12.5, {"request-timeout-ms": Omit()}, None), + (12.5, {"request-timeout-ms": "42"}, "42"), + ], + ) + def test_request_timeout_ms_header( + self, client: OpenAI, respx_mock: MockRouter, timeout: float | httpx.Timeout, extra_headers: dict[str, Any], expected: str | None + ) -> None: + respx_mock.post("/chat/completions").mock(return_value=httpx.Response(200)) + + response = client.chat.completions.with_raw_response.create( + messages=[{"content": "string", "role": "developer"}], + model="gpt-4o", + timeout=timeout, + extra_headers=extra_headers, + ) + + assert response.http_request.headers.get("request-timeout-ms") == expected + @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @mock.patch("openai._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url)