unique-search-proxy 2026.46.0.dev5__tar.gz → 2026.46.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 (122) hide show
  1. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/PKG-INFO +3 -3
  2. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/pyproject.toml +3 -3
  3. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/url_safety/gate.py +55 -25
  4. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/monitoring/metrics.py +14 -0
  5. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/request_limits.py +9 -0
  6. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/README.md +0 -0
  7. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/__init__.py +0 -0
  8. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/__init__.py +0 -0
  9. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/__init__.py +0 -0
  10. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/health.py +0 -0
  11. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/__init__.py +0 -0
  12. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/agent_search.py +0 -0
  13. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/configuration.py +0 -0
  14. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/context_headers.py +0 -0
  15. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/crawl.py +0 -0
  16. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/openapi_examples.py +0 -0
  17. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/api/v1/search.py +0 -0
  18. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/app.py +0 -0
  19. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/context.py +0 -0
  20. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/__init__.py +0 -0
  21. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/__init__.py +0 -0
  22. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/citation_instructions.j2 +0 -0
  23. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/cleanup.py +0 -0
  24. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/client.py +0 -0
  25. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/instructions.py +0 -0
  26. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/runner.py +0 -0
  27. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/bing/service.py +0 -0
  28. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/factory.py +0 -0
  29. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/service_base.py +0 -0
  30. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/vertexai/client.py +0 -0
  31. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/vertexai/gemini.py +0 -0
  32. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/vertexai/redirects.py +0 -0
  33. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/agent_engines/vertexai/service.py +0 -0
  34. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/client/__init__.py +0 -0
  35. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/client/azure_transport.py +0 -0
  36. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/client/credentials.py +0 -0
  37. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/client/service.py +0 -0
  38. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawl_limiter.py +0 -0
  39. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/__init__.py +0 -0
  40. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/__init__.py +0 -0
  41. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/__init__.py +0 -0
  42. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/errors.py +0 -0
  43. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/html_markdown.py +0 -0
  44. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/__init__.py +0 -0
  45. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/html.py +0 -0
  46. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/pdf.py +0 -0
  47. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/plain_text.py +0 -0
  48. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/processing/registry.py +0 -0
  49. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/service.py +0 -0
  50. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/settings.py +0 -0
  51. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/basic/user_agent.py +0 -0
  52. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/factory.py +0 -0
  53. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/firecrawl/polling.py +0 -0
  54. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/firecrawl/request_body.py +0 -0
  55. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/firecrawl/service.py +0 -0
  56. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/jina/request_body.py +0 -0
  57. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/jina/service.py +0 -0
  58. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/pinned_egress.py +0 -0
  59. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/tavily/request_body.py +0 -0
  60. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/crawlers/tavily/service.py +0 -0
  61. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/provider_response.py +0 -0
  62. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/providers.py +0 -0
  63. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/registry.py +0 -0
  64. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/__init__.py +0 -0
  65. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/brave/__init__.py +0 -0
  66. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/brave/pagination.py +0 -0
  67. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/brave/query_params.py +0 -0
  68. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/brave/service.py +0 -0
  69. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/custom_api/__init__.py +0 -0
  70. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/custom_api/service.py +0 -0
  71. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/descriptor.py +0 -0
  72. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/factory.py +0 -0
  73. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/google/__init__.py +0 -0
  74. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/google/pagination.py +0 -0
  75. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/google/query_params.py +0 -0
  76. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/google/service.py +0 -0
  77. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/pagination.py +0 -0
  78. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/perplexity/__init__.py +0 -0
  79. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/perplexity/request_body.py +0 -0
  80. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/perplexity/service.py +0 -0
  81. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/search_engines/service_base.py +0 -0
  82. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/core/url_safety/__init__.py +0 -0
  83. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/error_handlers.py +0 -0
  84. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/__init__.py +0 -0
  85. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/__init__.py +0 -0
  86. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/introspect.py +0 -0
  87. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/schema.py +0 -0
  88. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/template.py +0 -0
  89. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/templates/provider_values_yaml.j2 +0 -0
  90. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/generator/values_yaml.py +0 -0
  91. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/metadata.py +0 -0
  92. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/registry.py +0 -0
  93. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/helm/url_safety_registration.py +0 -0
  94. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/logging_config.py +0 -0
  95. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/middleware/context.py +0 -0
  96. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/monitoring/__init__.py +0 -0
  97. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/monitoring/setup.py +0 -0
  98. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/__init__.py +0 -0
  99. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/common.py +0 -0
  100. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/crawl.py +0 -0
  101. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/search.py +0 -0
  102. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/presets/types.py +0 -0
  103. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/__init__.py +0 -0
  104. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/app.py +0 -0
  105. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/base.py +0 -0
  106. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/client.py +0 -0
  107. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/monitoring.py +0 -0
  108. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/__init__.py +0 -0
  109. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/base.py +0 -0
  110. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/bing_agent.py +0 -0
  111. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/brave.py +0 -0
  112. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/firecrawl.py +0 -0
  113. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/google.py +0 -0
  114. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/jina.py +0 -0
  115. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/perplexity.py +0 -0
  116. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/tavily.py +0 -0
  117. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/providers/vertexai_agent.py +0 -0
  118. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/settings/secret_str.py +0 -0
  119. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/startup_report.py +0 -0
  120. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/usage.py +0 -0
  121. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/unique_search_proxy_client/web/utils/__init__.py +0 -0
  122. {unique_search_proxy-2026.46.0.dev5 → unique_search_proxy-2026.46.0.dev7}/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.46.0.dev5
3
+ Version: 2026.46.0.dev7
4
4
  Summary: Web Search Proxy implementation
5
5
  Author: ThePhilAz
6
6
  Author-email: ThePhilAz <rami.azouz@philico.com>
@@ -20,8 +20,8 @@ Requires-Dist: certifi>=2025.11.12,<2027
20
20
  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
- Requires-Dist: unique-toolkit[monitoring]>=2026.46.0.dev4,<2026.46.0rc0
24
- Requires-Dist: unique-search-proxy-core>=2026.46.0.dev1,<2026.46.0rc0
23
+ Requires-Dist: unique-toolkit[monitoring]>=2026.46.0.dev10,<2026.46.0rc0
24
+ Requires-Dist: unique-search-proxy-core>=2026.46.0.dev3,<2026.46.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.46.0.dev5"
3
+ version = "2026.46.0.dev7"
4
4
  description = "Web Search Proxy implementation"
5
5
  authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
6
6
  readme = "README.md"
@@ -22,8 +22,8 @@ dependencies = [
22
22
  "google-genai>=1.73.0,<2",
23
23
  "google-auth>=2.43.0,<3",
24
24
  "jinja2>=3.1.6,<4",
25
- "unique-toolkit[monitoring]>=2026.46.0.dev4,<2026.46.0rc0",
26
- "unique-search-proxy-core>=2026.46.0.dev1,<2026.46.0rc0",
25
+ "unique-toolkit[monitoring]>=2026.46.0.dev10,<2026.46.0rc0",
26
+ "unique-search-proxy-core>=2026.46.0.dev3,<2026.46.0rc0",
27
27
  "openai>=1.105.0,<3",
28
28
  ]
29
29
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import time
5
6
  from dataclasses import dataclass, field
6
7
  from typing import TYPE_CHECKING
7
8
 
@@ -16,7 +17,13 @@ from unique_search_proxy_client.web.core.provider_response import (
16
17
  crawl_deadline_missed,
17
18
  crawl_forbidden_target,
18
19
  )
19
- from unique_search_proxy_client.web.monitoring.metrics import record_crawl_blocked
20
+ from unique_search_proxy_client.web.monitoring.metrics import (
21
+ record_crawl_blocked,
22
+ record_url_safety_check,
23
+ )
24
+ from unique_search_proxy_client.web.settings.request_limits import (
25
+ request_limits_settings,
26
+ )
20
27
 
21
28
  if TYPE_CHECKING:
22
29
  from httpx import AsyncClient
@@ -50,32 +57,30 @@ async def apply_url_safety_gate(
50
57
  deadline: float | None = None,
51
58
  ) -> UrlSafetyGateResult:
52
59
  """Partition crawl URLs into allowed, blocked and timed-out targets."""
60
+ check_slots = asyncio.Semaphore(request_limits_settings.max_concurrent_url_checks)
61
+ outcomes = await asyncio.gather(
62
+ *[
63
+ _validate_before_deadline(
64
+ url,
65
+ redirect_http_client=redirect_http_client,
66
+ check_slots=check_slots,
67
+ deadline=deadline,
68
+ )
69
+ for url in urls
70
+ ],
71
+ )
72
+
53
73
  allowed_targets: list[AllowedCrawlTarget] = []
54
74
  blocked_by_index: dict[int, CrawlUrlResult] = {}
55
75
  timed_out_by_index: dict[int, CrawlUrlResult] = {}
56
76
 
57
- for index, url in enumerate(urls):
58
- outcome = await _validate_before_deadline(
59
- url,
60
- redirect_http_client=redirect_http_client,
61
- deadline=deadline,
62
- )
77
+ for index, (url, outcome) in enumerate(zip(urls, outcomes, strict=True)):
63
78
  if outcome is None:
64
- timed_out_by_index = {
65
- unchecked_index: crawl_deadline_missed(
66
- unchecked_url.strip(),
67
- "URL safety check did not finish before the crawl deadline",
68
- )
69
- for unchecked_index, unchecked_url in enumerate(
70
- urls[index:],
71
- start=index,
72
- )
73
- }
74
- _LOGGER.warning(
75
- "URL safety checks missed the crawl deadline unchecked=%d",
76
- len(timed_out_by_index),
79
+ timed_out_by_index[index] = crawl_deadline_missed(
80
+ url.strip(),
81
+ "URL safety check did not finish before the crawl deadline",
77
82
  )
78
- break
83
+ continue
79
84
 
80
85
  if outcome.blocked is not None:
81
86
  record_crawl_blocked(outcome.blocked.category)
@@ -96,6 +101,12 @@ async def apply_url_safety_gate(
96
101
  ),
97
102
  )
98
103
 
104
+ if timed_out_by_index:
105
+ _LOGGER.warning(
106
+ "URL safety checks missed the crawl deadline unchecked=%d",
107
+ len(timed_out_by_index),
108
+ )
109
+
99
110
  return UrlSafetyGateResult(
100
111
  allowed_targets=allowed_targets,
101
112
  blocked_by_index=blocked_by_index,
@@ -107,18 +118,37 @@ async def _validate_before_deadline(
107
118
  url: str,
108
119
  *,
109
120
  redirect_http_client: AsyncClient,
121
+ check_slots: asyncio.Semaphore,
110
122
  deadline: float | None,
111
123
  ) -> UrlSafetyOutcome | None:
112
124
  """Return the URL's safety outcome, or ``None`` when the deadline passes first."""
113
125
  try:
114
- async with asyncio.timeout_at(deadline):
115
- [outcome] = await UrlSafetyService.validate_urls_individually(
116
- [url],
126
+ async with asyncio.timeout_at(deadline), check_slots:
127
+ return await _validate_and_record(
128
+ url,
117
129
  redirect_http_client=redirect_http_client,
118
130
  )
119
131
  except TimeoutError:
120
132
  return None
121
- return outcome
133
+
134
+
135
+ async def _validate_and_record(
136
+ url: str,
137
+ *,
138
+ redirect_http_client: AsyncClient,
139
+ ) -> UrlSafetyOutcome:
140
+ """Validate one URL and record how long the check took, by outcome."""
141
+ started = time.perf_counter()
142
+ outcome_label = "unfinished"
143
+ try:
144
+ [outcome] = await UrlSafetyService.validate_urls_individually(
145
+ [url],
146
+ redirect_http_client=redirect_http_client,
147
+ )
148
+ outcome_label = "allowed" if outcome.blocked is None else "blocked"
149
+ return outcome
150
+ finally:
151
+ record_url_safety_check(outcome_label, time.perf_counter() - started)
122
152
 
123
153
 
124
154
  def merge_crawl_results(
@@ -40,6 +40,9 @@ def _exponential_buckets(start: float, ceiling: float, count: int) -> tuple[floa
40
40
  _SEARCH_LATENCY_BUCKETS = _linear_buckets(ceiling=5.0, steps=20)
41
41
  _CRAWL_LATENCY_BUCKETS = _linear_buckets(ceiling=60.0, steps=20)
42
42
  _AGENT_SEARCH_LATENCY_BUCKETS = _linear_buckets(ceiling=120.0, steps=20)
43
+ # Half-second steps up to 10s, so checks that hit the per-hop redirect timeout
44
+ # (5s by default) show up as their own bucket.
45
+ _URL_SAFETY_CHECK_LATENCY_BUCKETS = _linear_buckets(ceiling=10.0, steps=20)
43
46
 
44
47
  # One histogram covers every endpoint, from sub-second /v1/search to /v1/agent-search
45
48
  # runs approaching 120s — exponential buckets keep resolution useful at both ends.
@@ -94,6 +97,12 @@ crawl_blocked_total = m.counter(
94
97
  "Crawl targets blocked by URL safety policy",
95
98
  ["reason_category"],
96
99
  )
100
+ url_safety_check_duration_seconds = m.histogram(
101
+ "url_safety_check_duration_seconds",
102
+ "Time to check one crawl URL (redirect probes and DNS), by outcome",
103
+ ["outcome"],
104
+ buckets=_URL_SAFETY_CHECK_LATENCY_BUCKETS,
105
+ )
97
106
  url_safety_application_checks_enabled = m.gauge(
98
107
  "url_safety_application_checks_enabled",
99
108
  "Whether application-level URL safety checks are enabled (1) or delegated "
@@ -182,6 +191,11 @@ def record_crawl_blocked(reason_category: str, count: int = 1) -> None:
182
191
  crawl_blocked_total.labels(reason_category=reason_category).inc(count)
183
192
 
184
193
 
194
+ def record_url_safety_check(outcome: str, duration_seconds: float) -> None:
195
+ """Record one URL check; ``outcome`` is allowed, blocked or unfinished."""
196
+ url_safety_check_duration_seconds.labels(outcome=outcome).observe(duration_seconds)
197
+
198
+
185
199
  def set_url_safety_application_checks_enabled(enabled: bool) -> None:
186
200
  url_safety_application_checks_enabled.set(int(enabled))
187
201
 
@@ -23,6 +23,7 @@ REQUEST_LIMITS_ENV_PREFIX = "REQUEST_LIMITS_"
23
23
  "max_url_length",
24
24
  "max_concurrent_fetches",
25
25
  "max_concurrent_crawls",
26
+ "max_concurrent_url_checks",
26
27
  ],
27
28
  "timeouts": ["max_timeout_seconds"],
28
29
  },
@@ -82,6 +83,14 @@ class RequestLimitsSettings(BaseSettings):
82
83
  "this get a 429 instead of queueing."
83
84
  ),
84
85
  )
86
+ max_concurrent_url_checks: int = Field(
87
+ default=3,
88
+ ge=1,
89
+ description=(
90
+ "URL safety checks one crawl request runs at once before fetching. "
91
+ "Each check probes the URL's redirects with HEAD requests."
92
+ ),
93
+ )
85
94
  max_timeout_seconds: int = Field(
86
95
  default=120,
87
96
  ge=1,