unique-search-proxy 2026.44.0.dev9__tar.gz → 2026.44.0.dev11__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 (119) hide show
  1. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/PKG-INFO +2 -2
  2. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/pyproject.toml +2 -2
  3. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/v1/crawl.py +20 -0
  4. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/service.py +15 -1
  5. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/registry.py +4 -0
  6. unique_search_proxy-2026.44.0.dev11/unique_search_proxy_client/web/settings/request_limits.py +79 -0
  7. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/README.md +0 -0
  8. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/__init__.py +0 -0
  9. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/__init__.py +0 -0
  10. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/__init__.py +0 -0
  11. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/health.py +0 -0
  12. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/v1/__init__.py +0 -0
  13. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/v1/agent_search.py +0 -0
  14. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/v1/configuration.py +0 -0
  15. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/v1/context_headers.py +0 -0
  16. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/v1/openapi_examples.py +0 -0
  17. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/api/v1/search.py +0 -0
  18. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/app.py +0 -0
  19. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/context.py +0 -0
  20. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/__init__.py +0 -0
  21. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/__init__.py +0 -0
  22. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/bing/cleanup.py +0 -0
  23. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/bing/client.py +0 -0
  24. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/bing/runner.py +0 -0
  25. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/bing/service.py +0 -0
  26. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/factory.py +0 -0
  27. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/service_base.py +0 -0
  28. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/structured_output.py +0 -0
  29. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/vertexai/client.py +0 -0
  30. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/vertexai/gemini.py +0 -0
  31. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/vertexai/redirects.py +0 -0
  32. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/agent_engines/vertexai/service.py +0 -0
  33. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/client/__init__.py +0 -0
  34. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/client/azure_transport.py +0 -0
  35. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/client/credentials.py +0 -0
  36. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/client/service.py +0 -0
  37. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/__init__.py +0 -0
  38. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/__init__.py +0 -0
  39. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/processing/__init__.py +0 -0
  40. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/processing/errors.py +0 -0
  41. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/processing/html_markdown.py +0 -0
  42. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/__init__.py +0 -0
  43. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/html.py +0 -0
  44. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/pdf.py +0 -0
  45. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/plain_text.py +0 -0
  46. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/processing/registry.py +0 -0
  47. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/settings.py +0 -0
  48. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/basic/user_agent.py +0 -0
  49. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/factory.py +0 -0
  50. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/firecrawl/polling.py +0 -0
  51. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/firecrawl/request_body.py +0 -0
  52. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/firecrawl/service.py +0 -0
  53. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/jina/request_body.py +0 -0
  54. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/jina/service.py +0 -0
  55. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/pinned_egress.py +0 -0
  56. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/tavily/request_body.py +0 -0
  57. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/crawlers/tavily/service.py +0 -0
  58. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/provider_response.py +0 -0
  59. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/providers.py +0 -0
  60. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/registry.py +0 -0
  61. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/__init__.py +0 -0
  62. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/brave/__init__.py +0 -0
  63. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/brave/pagination.py +0 -0
  64. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/brave/query_params.py +0 -0
  65. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/brave/service.py +0 -0
  66. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/custom_api/__init__.py +0 -0
  67. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/custom_api/service.py +0 -0
  68. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/descriptor.py +0 -0
  69. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/factory.py +0 -0
  70. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/google/__init__.py +0 -0
  71. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/google/pagination.py +0 -0
  72. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/google/query_params.py +0 -0
  73. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/google/service.py +0 -0
  74. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/pagination.py +0 -0
  75. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/perplexity/__init__.py +0 -0
  76. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/perplexity/request_body.py +0 -0
  77. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/perplexity/service.py +0 -0
  78. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/search_engines/service_base.py +0 -0
  79. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/url_safety/__init__.py +0 -0
  80. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/core/url_safety/gate.py +0 -0
  81. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/error_handlers.py +0 -0
  82. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/__init__.py +0 -0
  83. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/generator/__init__.py +0 -0
  84. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/generator/introspect.py +0 -0
  85. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/generator/schema.py +0 -0
  86. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/generator/template.py +0 -0
  87. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/generator/templates/provider_values_yaml.j2 +0 -0
  88. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/generator/values_yaml.py +0 -0
  89. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/metadata.py +0 -0
  90. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/helm/url_safety_registration.py +0 -0
  91. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/logging_config.py +0 -0
  92. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/middleware/context.py +0 -0
  93. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/monitoring/__init__.py +0 -0
  94. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/monitoring/metrics.py +0 -0
  95. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/monitoring/setup.py +0 -0
  96. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/presets/__init__.py +0 -0
  97. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/presets/common.py +0 -0
  98. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/presets/crawl.py +0 -0
  99. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/presets/search.py +0 -0
  100. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/presets/types.py +0 -0
  101. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/__init__.py +0 -0
  102. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/app.py +0 -0
  103. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/base.py +0 -0
  104. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/client.py +0 -0
  105. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/monitoring.py +0 -0
  106. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/__init__.py +0 -0
  107. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/base.py +0 -0
  108. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/bing_agent.py +0 -0
  109. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/brave.py +0 -0
  110. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/firecrawl.py +0 -0
  111. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/google.py +0 -0
  112. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/jina.py +0 -0
  113. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/perplexity.py +0 -0
  114. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/tavily.py +0 -0
  115. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/providers/vertexai_agent.py +0 -0
  116. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/settings/secret_str.py +0 -0
  117. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/startup_report.py +0 -0
  118. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/utils/__init__.py +0 -0
  119. {unique_search_proxy-2026.44.0.dev9 → unique_search_proxy-2026.44.0.dev11}/unique_search_proxy_client/web/utils/url.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-search-proxy
3
- Version: 2026.44.0.dev9
3
+ Version: 2026.44.0.dev11
4
4
  Summary: Web Search Proxy implementation
5
5
  Author: ThePhilAz
6
6
  Author-email: ThePhilAz <rami.azouz@philico.com>
@@ -21,7 +21,7 @@ Requires-Dist: google-genai>=1.73.0,<2
21
21
  Requires-Dist: google-auth>=2.43.0,<3
22
22
  Requires-Dist: jinja2>=3.1.6,<4
23
23
  Requires-Dist: unique-toolkit[monitoring]>=2026.44.0.dev9,<2026.44.0rc0
24
- Requires-Dist: unique-search-proxy-core>=2026.44.0.dev8,<2026.44.0rc0
24
+ Requires-Dist: unique-search-proxy-core>=2026.44.0.dev9,<2026.44.0rc0
25
25
  Requires-Dist: openai>=1.105.0,<3
26
26
  Requires-Python: >=3.12
27
27
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique-search-proxy"
3
- version = "2026.44.0.dev9"
3
+ version = "2026.44.0.dev11"
4
4
  description = "Web Search Proxy implementation"
5
5
  authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
6
6
  readme = "README.md"
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "google-auth>=2.43.0,<3",
24
24
  "jinja2>=3.1.6,<4",
25
25
  "unique-toolkit[monitoring]>=2026.44.0.dev9,<2026.44.0rc0",
26
- "unique-search-proxy-core>=2026.44.0.dev8,<2026.44.0rc0",
26
+ "unique-search-proxy-core>=2026.44.0.dev9,<2026.44.0rc0",
27
27
  "openai>=1.105.0,<3",
28
28
  ]
29
29
 
@@ -9,6 +9,7 @@ from unique_search_proxy_core.crawlers.config_types import CrawlRequest
9
9
  from unique_search_proxy_core.errors import (
10
10
  ProxyError,
11
11
  UpstreamTimeoutError,
12
+ ValidationProxyError,
12
13
  attach_request_context,
13
14
  )
14
15
  from unique_search_proxy_core.schema import (
@@ -35,6 +36,9 @@ from unique_search_proxy_client.web.monitoring.metrics import (
35
36
  record_crawl_success,
36
37
  record_crawl_url_outcomes,
37
38
  )
39
+ from unique_search_proxy_client.web.settings.request_limits import (
40
+ request_limits_settings,
41
+ )
38
42
 
39
43
  router = APIRouter(tags=["crawl"])
40
44
  _LOGGER = logging.getLogger(__name__)
@@ -48,6 +52,21 @@ def _crawl_request_context(exc: ProxyError, *, crawler_id: str) -> ProxyError:
48
52
  )
49
53
 
50
54
 
55
+ def _enforce_url_limits(urls: list[str]) -> None:
56
+ limits = request_limits_settings
57
+ if len(urls) > limits.max_urls_per_crawl:
58
+ raise ValidationProxyError(
59
+ f"Crawl request lists {len(urls)} URLs; "
60
+ f"at most {limits.max_urls_per_crawl} are allowed",
61
+ )
62
+ for index, url in enumerate(urls):
63
+ if len(url) > limits.max_url_length:
64
+ raise ValidationProxyError(
65
+ f"URL at index {index} is {len(url)} characters long; "
66
+ f"at most {limits.max_url_length} are allowed",
67
+ )
68
+
69
+
51
70
  def _url_outcomes(results: list[CrawlUrlResult]) -> list[tuple[str, str, str]]:
52
71
  """Derive ``(outcome, error_code, http_status)`` triples from merged results."""
53
72
  outcomes: list[tuple[str, str, str]] = []
@@ -84,6 +103,7 @@ async def crawl(
84
103
  )
85
104
 
86
105
  try:
106
+ _enforce_url_limits(body.urls)
87
107
  async with asyncio.timeout(timeout):
88
108
  registry = get_http_client_registry(request.app)
89
109
  client = await registry.client_for(get_request_context())
@@ -11,7 +11,7 @@ from unique_search_proxy_core.crawlers.basic.processing.policy import (
11
11
  )
12
12
  from unique_search_proxy_core.crawlers.basic.schema import BasicCrawlRequest
13
13
  from unique_search_proxy_core.errors import ResponseTooLargeError
14
- from unique_search_proxy_core.http_client import read_capped_body
14
+ from unique_search_proxy_core.http_client import ByteBudget, read_capped_body
15
15
  from unique_search_proxy_core.schema import (
16
16
  CrawlUrlResult,
17
17
  ProxyErrorCode,
@@ -40,6 +40,9 @@ from unique_search_proxy_client.web.core.provider_response import (
40
40
  from unique_search_proxy_client.web.core.url_safety.gate import AllowedCrawlTarget
41
41
  from unique_search_proxy_client.web.monitoring.metrics import record_crawl_blocked
42
42
  from unique_search_proxy_client.web.settings.client import http_client_settings
43
+ from unique_search_proxy_client.web.settings.request_limits import (
44
+ request_limits_settings,
45
+ )
43
46
 
44
47
  _LOGGER = logging.getLogger(__name__)
45
48
 
@@ -106,6 +109,7 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
106
109
 
107
110
  timeout = request.timeout
108
111
  semaphore = asyncio.Semaphore(request.max_concurrent_requests)
112
+ budget = ByteBudget(request_limits_settings.max_crawl_response_bytes)
109
113
  return list(
110
114
  await asyncio.gather(
111
115
  *[
@@ -115,6 +119,7 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
115
119
  resolved_target=allowed_target.resolved,
116
120
  timeout=timeout,
117
121
  semaphore=semaphore,
122
+ budget=budget,
118
123
  content_type_handlers=request.content_types.to_handlers(),
119
124
  )
120
125
  for allowed_target in allowed_targets
@@ -130,9 +135,17 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
130
135
  resolved_target: ResolvedCrawlTarget,
131
136
  timeout: int,
132
137
  semaphore: asyncio.Semaphore,
138
+ budget: ByteBudget,
133
139
  content_type_handlers: dict[str, ContentTypeHandlerPolicy],
134
140
  ) -> CrawlUrlResult:
135
141
  async with semaphore:
142
+ if budget.exhausted:
143
+ return crawl_upstream_error(
144
+ display_url,
145
+ "Crawl response budget exhausted before this URL was fetched",
146
+ content_type=None,
147
+ code=ProxyErrorCode.RESPONSE_TOO_LARGE.value,
148
+ )
136
149
  try:
137
150
  async with safe_pinned_httpx_stream(
138
151
  client,
@@ -161,6 +174,7 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
161
174
  body = await read_capped_body(
162
175
  response,
163
176
  max_bytes=http_client_settings.max_response_bytes,
177
+ budget=budget,
164
178
  )
165
179
  raw_body = body.decode(
166
180
  response.encoding or "utf-8",
@@ -31,6 +31,9 @@ from unique_search_proxy_client.web.settings.providers import (
31
31
  tavily_crawl_credentials,
32
32
  vertexai_agent_credentials,
33
33
  )
34
+ from unique_search_proxy_client.web.settings.request_limits import (
35
+ request_limits_settings,
36
+ )
34
37
 
35
38
 
36
39
  @dataclass(frozen=True)
@@ -61,6 +64,7 @@ _REGISTERED_INSTANCES: tuple[BaseSettings, ...] = (
61
64
  jina_crawl_credentials,
62
65
  firecrawl_crawl_credentials,
63
66
  http_client_settings,
67
+ request_limits_settings,
64
68
  prometheus_settings,
65
69
  )
66
70
 
@@ -0,0 +1,79 @@
1
+ """Server-side ceilings that bound what a single proxy request may consume."""
2
+
3
+ from pydantic import Field
4
+ from pydantic_settings import BaseSettings, SettingsConfigDict
5
+
6
+ from unique_search_proxy_client.web.helm.metadata import helm_settings
7
+ from unique_search_proxy_client.web.settings.base import get_settings
8
+
9
+ REQUEST_LIMITS_ENV_PREFIX = "REQUEST_LIMITS_"
10
+
11
+
12
+ @helm_settings(
13
+ title="Request Limits",
14
+ helm_key="requestLimits",
15
+ kind="internal",
16
+ gated=False,
17
+ egress=None,
18
+ env_prefix=REQUEST_LIMITS_ENV_PREFIX,
19
+ sections={
20
+ "crawl": ["max_crawl_response_kib", "max_urls_per_crawl", "max_url_length"],
21
+ },
22
+ )
23
+ class RequestLimitsSettings(BaseSettings):
24
+ """Ceilings callers cannot raise, sized against the pod memory limit.
25
+
26
+ Byte sizes are in KiB: Helm renders numbers >= 1e6 in scientific notation,
27
+ which pydantic cannot parse back into an int.
28
+ """
29
+
30
+ model_config = SettingsConfigDict(
31
+ env_prefix=REQUEST_LIMITS_ENV_PREFIX,
32
+ extra="ignore",
33
+ )
34
+
35
+ max_crawl_response_kib: int = Field(
36
+ default=10 * 1024,
37
+ ge=1,
38
+ description=(
39
+ "Combined decoded size, in KiB, of all page bodies one crawl request "
40
+ "may keep. URLs read after the budget is spent fail individually."
41
+ ),
42
+ )
43
+
44
+ # Matches the search fetch_size ceiling: the web search tool crawls a whole
45
+ # result page in one request.
46
+ max_urls_per_crawl: int = Field(
47
+ default=100,
48
+ ge=1,
49
+ description=(
50
+ "Maximum number of URLs in one crawl request. Requests with more "
51
+ "are rejected with a 422."
52
+ ),
53
+ )
54
+ max_url_length: int = Field(
55
+ default=2048,
56
+ ge=1,
57
+ description=(
58
+ "Maximum length of each URL, in characters. Requests containing a "
59
+ "longer URL are rejected with a 422."
60
+ ),
61
+ )
62
+
63
+ @property
64
+ def max_crawl_response_bytes(self) -> int:
65
+ return self.max_crawl_response_kib * 1024
66
+
67
+
68
+ def get_request_limits_settings() -> RequestLimitsSettings:
69
+ return get_settings(RequestLimitsSettings, env_prefix=REQUEST_LIMITS_ENV_PREFIX)
70
+
71
+
72
+ request_limits_settings: RequestLimitsSettings = get_request_limits_settings()
73
+
74
+ __all__ = [
75
+ "REQUEST_LIMITS_ENV_PREFIX",
76
+ "RequestLimitsSettings",
77
+ "get_request_limits_settings",
78
+ "request_limits_settings",
79
+ ]