unique-search-proxy-core 2026.44.0.dev6__tar.gz → 2026.44.0.dev7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/PKG-INFO +1 -1
  2. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/pyproject.toml +1 -1
  3. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/errors.py +12 -0
  4. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/http_client/__init__.py +10 -0
  5. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/http_client/client.py +16 -0
  6. unique_search_proxy_core-2026.44.0.dev7/unique_search_proxy_core/http_client/response_cap.py +155 -0
  7. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/http_client/settings.py +16 -0
  8. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/schema.py +1 -0
  9. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/README.md +0 -0
  10. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/__init__.py +0 -0
  11. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/__init__.py +0 -0
  12. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/base.py +0 -0
  13. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/bing/__init__.py +0 -0
  14. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/bing/enums.py +0 -0
  15. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/bing/grounding.py +0 -0
  16. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/bing/schema.py +0 -0
  17. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/bing/settings.py +0 -0
  18. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/config_types.py +0 -0
  19. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/output_schema.py +0 -0
  20. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/resolve.py +0 -0
  21. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/response_parsing.py +0 -0
  22. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/vertexai/__init__.py +0 -0
  23. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/vertexai/schema.py +0 -0
  24. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/agent_engines/vertexai/settings.py +0 -0
  25. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/context.py +0 -0
  26. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/__init__.py +0 -0
  27. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/base.py +0 -0
  28. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/basic/__init__.py +0 -0
  29. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/basic/content_types.py +0 -0
  30. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/basic/processing/policy.py +0 -0
  31. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/basic/schema.py +0 -0
  32. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/config_types.py +0 -0
  33. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/firecrawl/schema.py +0 -0
  34. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/jina/schema.py +0 -0
  35. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/crawlers/tavily/schema.py +0 -0
  36. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/http_client/credentials.py +0 -0
  37. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/http_client/secrets.py +0 -0
  38. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/logging.py +0 -0
  39. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/param_policy/__init__.py +0 -0
  40. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/param_policy/annotations.py +0 -0
  41. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/param_policy/derive.py +0 -0
  42. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/param_policy/exposable_config.py +0 -0
  43. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/param_policy/exposable_param.py +0 -0
  44. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/param_policy/exposed_params.py +0 -0
  45. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/param_policy/request_base.py +0 -0
  46. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/providers/schema.py +0 -0
  47. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/__init__.py +0 -0
  48. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/base.py +0 -0
  49. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/brave/__init__.py +0 -0
  50. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/brave/schema.py +0 -0
  51. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/config_types.py +0 -0
  52. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/custom_api/__init__.py +0 -0
  53. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/custom_api/schema.py +0 -0
  54. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/google/__init__.py +0 -0
  55. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/google/schema.py +0 -0
  56. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/perplexity/__init__.py +0 -0
  57. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/search_engines/perplexity/schema.py +0 -0
  58. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/__init__.py +0 -0
  59. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/dns.py +0 -0
  60. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/egress.py +0 -0
  61. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/models.py +0 -0
  62. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/netloc.py +0 -0
  63. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/policy.py +0 -0
  64. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/redirect.py +0 -0
  65. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/resolver.py +0 -0
  66. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/service.py +0 -0
  67. {unique_search_proxy_core-2026.44.0.dev6 → unique_search_proxy_core-2026.44.0.dev7}/unique_search_proxy_core/url_safety/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-search-proxy-core
3
- Version: 2026.44.0.dev6
3
+ Version: 2026.44.0.dev7
4
4
  Summary: Shared Pydantic types for the Unique Search Proxy API
5
5
  Author: ThePhilAz
6
6
  Author-email: ThePhilAz <rami.azouz@philico.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique-search-proxy-core"
3
- version = "2026.44.0.dev6"
3
+ version = "2026.44.0.dev7"
4
4
  description = "Shared Pydantic types for the Unique Search Proxy API"
5
5
  authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
6
6
  readme = "README.md"
@@ -132,3 +132,15 @@ class EmptySearchResultsError(ProxyError):
132
132
  code = ProxyErrorCode.EMPTY_SEARCH_RESULTS
133
133
  status_code = 404
134
134
  retryable = False
135
+
136
+
137
+ class ResponseTooLargeError(ProxyError):
138
+ """An upstream response body exceeded the configured byte limit."""
139
+
140
+ code = ProxyErrorCode.RESPONSE_TOO_LARGE
141
+ status_code = 502
142
+ retryable = False
143
+
144
+ @classmethod
145
+ def for_limit(cls, max_bytes: int) -> ResponseTooLargeError:
146
+ return cls(f"Upstream response exceeded the {max_bytes} byte limit")
@@ -17,6 +17,12 @@ from unique_search_proxy_core.http_client.credentials import (
17
17
  UserMetadataProxyCredentials,
18
18
  resolver_from_settings,
19
19
  )
20
+ from unique_search_proxy_core.http_client.response_cap import (
21
+ MAX_RESPONSE_BYTES_EXTENSION,
22
+ install_response_size_cap,
23
+ read_capped_body,
24
+ response_size_cap_hook,
25
+ )
20
26
  from unique_search_proxy_core.http_client.secrets import (
21
27
  read_secret,
22
28
  read_secret_mapping,
@@ -29,6 +35,7 @@ from unique_search_proxy_core.http_client.settings import (
29
35
  )
30
36
 
31
37
  __all__ = [
38
+ "MAX_RESPONSE_BYTES_EXTENSION",
32
39
  "DirectRoute",
33
40
  "EgressRoute",
34
41
  "HttpClientRegistry",
@@ -45,7 +52,10 @@ __all__ = [
45
52
  "async_client_factory",
46
53
  "build_async_client",
47
54
  "build_route",
55
+ "install_response_size_cap",
56
+ "read_capped_body",
48
57
  "read_secret",
49
58
  "read_secret_mapping",
50
59
  "resolver_from_settings",
60
+ "response_size_cap_hook",
51
61
  ]
@@ -20,6 +20,10 @@ from unique_search_proxy_core.http_client.credentials import (
20
20
  SettingsProxyCredentials,
21
21
  resolver_from_settings,
22
22
  )
23
+ from unique_search_proxy_core.http_client.response_cap import (
24
+ ResponseHook,
25
+ response_size_cap_hook,
26
+ )
23
27
  from unique_search_proxy_core.http_client.secrets import read_secret_mapping
24
28
  from unique_search_proxy_core.http_client.settings import (
25
29
  ProxyAuthMode,
@@ -96,6 +100,10 @@ def _proxy_headers(settings: ProxySettings) -> dict[str, str]:
96
100
  return read_secret_mapping(settings.proxy_headers)
97
101
 
98
102
 
103
+ def _event_hooks(settings: ProxySettings) -> dict[str, list[ResponseHook]]:
104
+ return {"response": [response_size_cap_hook(settings.max_response_bytes)]}
105
+
106
+
99
107
  def _build_httpx_proxy(
100
108
  settings: ProxySettings,
101
109
  credentials: ProxyCredentials,
@@ -174,6 +182,7 @@ def build_async_client(
174
182
  trust_env=route.trust_env,
175
183
  timeout=timeout,
176
184
  limits=limits,
185
+ event_hooks=_event_hooks(settings),
177
186
  )
178
187
  case ProxiedRoute():
179
188
  return AsyncClient(
@@ -184,6 +193,7 @@ def build_async_client(
184
193
  cert=route.cert,
185
194
  timeout=timeout,
186
195
  limits=limits,
196
+ event_hooks=_event_hooks(settings),
187
197
  )
188
198
 
189
199
 
@@ -207,6 +217,7 @@ def async_client_factory(
207
217
  verify=route.verify,
208
218
  trust_env=route.trust_env,
209
219
  timeout=effective_timeout,
220
+ event_hooks=_event_hooks(settings),
210
221
  )
211
222
  case ProxiedRoute():
212
223
  return partial(
@@ -217,6 +228,7 @@ def async_client_factory(
217
228
  trust_env=route.trust_env,
218
229
  cert=route.cert,
219
230
  timeout=effective_timeout,
231
+ event_hooks=_event_hooks(settings),
220
232
  )
221
233
 
222
234
 
@@ -283,6 +295,10 @@ class HttpClientRegistry:
283
295
  ) -> None:
284
296
  if fixed_client is None:
285
297
  _validate_url_safety_proxy_configuration(settings)
298
+ _LOGGER.info(
299
+ "Outbound response size limit: %s bytes",
300
+ settings.max_response_bytes or "unlimited",
301
+ )
286
302
  self._settings = settings
287
303
  self._resolver = resolver
288
304
  self._fixed_client = fixed_client
@@ -0,0 +1,155 @@
1
+ """Bound how many bytes an outbound response may deliver.
2
+
3
+ The limit is enforced while the body streams in, so an oversized, chunked, or
4
+ endless response is aborted before it is buffered in memory. It is installed as
5
+ an httpx response hook rather than a custom transport: a custom transport makes
6
+ httpx ignore ``trust_env`` proxy discovery and every transport-level option.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import AsyncIterator, Awaitable, Callable
12
+
13
+ import httpx
14
+
15
+ from unique_search_proxy_core.errors import ResponseTooLargeError
16
+
17
+ MAX_RESPONSE_BYTES_EXTENSION = "max_response_bytes"
18
+
19
+ # Raw chunks are re-sliced before httpx decompresses them, which bounds how much
20
+ # a single gzip/deflate decode step can expand.
21
+ _RAW_SLICE_BYTES = 16 * 1024
22
+
23
+ ResponseHook = Callable[[httpx.Response], Awaitable[None]]
24
+
25
+
26
+ class _CappedRawStream(httpx.AsyncByteStream):
27
+ """Wrap a response byte stream and abort once it delivers too many bytes.
28
+
29
+ Counts bytes as received from the network (before decompression), so it
30
+ also catches responses with a missing or understated ``Content-Length``.
31
+ """
32
+
33
+ def __init__(self, stream: httpx.AsyncByteStream, *, max_bytes: int) -> None:
34
+ self._stream = stream
35
+ self._max_bytes = max_bytes
36
+
37
+ async def __aiter__(self) -> AsyncIterator[bytes]:
38
+ received = 0
39
+ async for chunk in self._stream:
40
+ received += len(chunk)
41
+ if received > self._max_bytes:
42
+ raise ResponseTooLargeError.for_limit(self._max_bytes)
43
+ for start in range(0, len(chunk), _RAW_SLICE_BYTES):
44
+ yield chunk[start : start + _RAW_SLICE_BYTES]
45
+
46
+ async def aclose(self) -> None:
47
+ await self._stream.aclose()
48
+
49
+
50
+ def _effective_max_bytes(
51
+ request: httpx.Request,
52
+ default_max_bytes: int | None,
53
+ ) -> int | None:
54
+ """Combine the client default with a per-request override; lower wins."""
55
+ override = request.extensions.get(MAX_RESPONSE_BYTES_EXTENSION)
56
+ if override is None:
57
+ return default_max_bytes
58
+ if default_max_bytes is None:
59
+ return int(override)
60
+ return min(int(override), default_max_bytes)
61
+
62
+
63
+ def _declared_content_length(response: httpx.Response) -> int | None:
64
+ header = response.headers.get("content-length")
65
+ if header is None:
66
+ return None
67
+ try:
68
+ return int(header)
69
+ except ValueError:
70
+ return None
71
+
72
+
73
+ def response_size_cap_hook(default_max_bytes: int | None) -> ResponseHook:
74
+ """Build an httpx response hook that caps the response body size.
75
+
76
+ Requests can lower the limit with
77
+ ``extensions={"max_response_bytes": n}``; they cannot raise it above
78
+ ``default_max_bytes``.
79
+
80
+ Args:
81
+ default_max_bytes (int | None): Limit applied when a request sets no
82
+ override. ``None`` leaves such requests uncapped.
83
+
84
+ Returns:
85
+ ResponseHook: Coroutine to register under ``event_hooks["response"]``.
86
+ """
87
+
88
+ async def _cap_response_size(response: httpx.Response) -> None:
89
+ max_bytes = _effective_max_bytes(response.request, default_max_bytes)
90
+ if max_bytes is None:
91
+ return
92
+ declared = _declared_content_length(response)
93
+ if declared is not None and declared > max_bytes:
94
+ raise ResponseTooLargeError.for_limit(max_bytes)
95
+ if isinstance(response.stream, httpx.AsyncByteStream):
96
+ response.stream = _CappedRawStream(response.stream, max_bytes=max_bytes)
97
+
98
+ return _cap_response_size
99
+
100
+
101
+ def install_response_size_cap(
102
+ client: httpx.AsyncClient,
103
+ max_bytes: int | None,
104
+ ) -> httpx.AsyncClient:
105
+ """Register the response size cap on an existing client.
106
+
107
+ Args:
108
+ client (httpx.AsyncClient): Client to protect, e.g. a test client.
109
+ max_bytes (int | None): Default limit for the client's requests.
110
+
111
+ Returns:
112
+ httpx.AsyncClient: The same client, for chaining.
113
+ """
114
+ client.event_hooks["response"].append(response_size_cap_hook(max_bytes))
115
+ return client
116
+
117
+
118
+ async def read_capped_body(
119
+ response: httpx.Response,
120
+ *,
121
+ max_bytes: int | None,
122
+ ) -> bytes:
123
+ """Read a streamed response body, limiting its decompressed size.
124
+
125
+ The response hook limits bytes received from the network; this limits the
126
+ bytes after gzip/deflate decoding, which stops decompression bombs.
127
+
128
+ Args:
129
+ response (httpx.Response): Response opened with ``stream=True``.
130
+ max_bytes (int | None): Largest decoded body allowed. ``None`` reads
131
+ the whole body.
132
+
133
+ Returns:
134
+ bytes: The decoded response body.
135
+
136
+ Raises:
137
+ ResponseTooLargeError: When the decoded body exceeds ``max_bytes``.
138
+ """
139
+ chunks: list[bytes] = []
140
+ received = 0
141
+ async for chunk in response.aiter_bytes():
142
+ received += len(chunk)
143
+ if max_bytes is not None and received > max_bytes:
144
+ raise ResponseTooLargeError.for_limit(max_bytes)
145
+ chunks.append(chunk)
146
+ return b"".join(chunks)
147
+
148
+
149
+ __all__ = [
150
+ "MAX_RESPONSE_BYTES_EXTENSION",
151
+ "ResponseHook",
152
+ "install_response_size_cap",
153
+ "read_capped_body",
154
+ "response_size_cap_hook",
155
+ ]
@@ -59,6 +59,22 @@ class ProxySettings(BaseSettings):
59
59
  pool_timeout_seconds: float = 30.0
60
60
  max_connections: int = 100
61
61
  max_keepalive_connections: int = 20
62
+ # KiB rather than bytes: Helm renders numbers >= 1e6 in scientific notation,
63
+ # which pydantic cannot parse back into an int.
64
+ max_response_kib: int | None = Field(
65
+ default=5 * 1024,
66
+ ge=1,
67
+ description=(
68
+ "Largest upstream response body, in KiB, any outbound request may "
69
+ "deliver before it is aborted. None disables the limit."
70
+ ),
71
+ )
72
+
73
+ @property
74
+ def max_response_bytes(self) -> int | None:
75
+ if self.max_response_kib is None:
76
+ return None
77
+ return self.max_response_kib * 1024
62
78
 
63
79
  @model_validator(mode="after")
64
80
  def _validate_proxy_credentials_config(self) -> Self:
@@ -52,6 +52,7 @@ class ProxyErrorCode(StrEnum):
52
52
  ENGINE_NOT_CONFIGURED = "ENGINE_NOT_CONFIGURED"
53
53
  UPSTREAM_TIMEOUT = "UPSTREAM_TIMEOUT"
54
54
  EMPTY_SEARCH_RESULTS = "EMPTY_SEARCH_RESULTS"
55
+ RESPONSE_TOO_LARGE = "RESPONSE_TOO_LARGE"
55
56
 
56
57
 
57
58
  class ErrorDetail(BaseModel):