unique-search-proxy-core 2026.44.0.dev10__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 (68) hide show
  1. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/PKG-INFO +1 -1
  2. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/pyproject.toml +1 -1
  3. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/context.py +57 -0
  4. unique_search_proxy_core-2026.44.0.dev11/unique_search_proxy_core/usage.py +45 -0
  5. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/README.md +0 -0
  6. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/__init__.py +0 -0
  7. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/__init__.py +0 -0
  8. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/base.py +0 -0
  9. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/bing/__init__.py +0 -0
  10. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/bing/enums.py +0 -0
  11. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/bing/grounding.py +0 -0
  12. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/bing/schema.py +0 -0
  13. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/bing/settings.py +0 -0
  14. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/config_types.py +0 -0
  15. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/output_schema.py +0 -0
  16. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/resolve.py +0 -0
  17. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/response_parsing.py +0 -0
  18. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/vertexai/__init__.py +0 -0
  19. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/vertexai/schema.py +0 -0
  20. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/agent_engines/vertexai/settings.py +0 -0
  21. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/__init__.py +0 -0
  22. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/base.py +0 -0
  23. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/basic/__init__.py +0 -0
  24. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/basic/content_types.py +0 -0
  25. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/basic/processing/policy.py +0 -0
  26. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/basic/schema.py +0 -0
  27. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/config_types.py +0 -0
  28. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/firecrawl/schema.py +0 -0
  29. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/jina/schema.py +0 -0
  30. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/crawlers/tavily/schema.py +0 -0
  31. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/errors.py +0 -0
  32. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/http_client/__init__.py +0 -0
  33. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/http_client/client.py +0 -0
  34. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/http_client/credentials.py +0 -0
  35. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/http_client/response_cap.py +0 -0
  36. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/http_client/secrets.py +0 -0
  37. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/http_client/settings.py +0 -0
  38. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/logging.py +0 -0
  39. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/param_policy/__init__.py +0 -0
  40. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/param_policy/annotations.py +0 -0
  41. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/param_policy/derive.py +0 -0
  42. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/param_policy/exposable_config.py +0 -0
  43. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/param_policy/exposable_param.py +0 -0
  44. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/param_policy/exposed_params.py +0 -0
  45. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/param_policy/request_base.py +0 -0
  46. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/providers/schema.py +0 -0
  47. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/schema.py +0 -0
  48. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/__init__.py +0 -0
  49. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/base.py +0 -0
  50. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/brave/__init__.py +0 -0
  51. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/brave/schema.py +0 -0
  52. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/config_types.py +0 -0
  53. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/custom_api/__init__.py +0 -0
  54. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/custom_api/schema.py +0 -0
  55. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/google/__init__.py +0 -0
  56. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/google/schema.py +0 -0
  57. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/perplexity/__init__.py +0 -0
  58. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/search_engines/perplexity/schema.py +0 -0
  59. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/__init__.py +0 -0
  60. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/dns.py +0 -0
  61. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/egress.py +0 -0
  62. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/models.py +0 -0
  63. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/netloc.py +0 -0
  64. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/policy.py +0 -0
  65. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/redirect.py +0 -0
  66. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/resolver.py +0 -0
  67. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/unique_search_proxy_core/url_safety/service.py +0 -0
  68. {unique_search_proxy_core-2026.44.0.dev10 → unique_search_proxy_core-2026.44.0.dev11}/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.dev10
3
+ Version: 2026.44.0.dev11
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.dev10"
3
+ version = "2026.44.0.dev11"
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"
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import json
6
6
  import logging
7
7
  from collections.abc import Mapping
8
+ from enum import StrEnum
8
9
  from typing import Any
9
10
 
10
11
  from pydantic import BaseModel, ConfigDict, Field
@@ -13,6 +14,9 @@ COMPANY_ID_HEADER = "x-unique-company-id"
13
14
  USER_ID_HEADER = "x-unique-user-id"
14
15
  CHAT_ID_HEADER = "x-unique-chat-id"
15
16
  USER_METADATA_HEADER = "x-unique-user-metadata"
17
+ ENTRY_POINT_HEADER = "x-unique-entry-point"
18
+ MESSAGE_ID_HEADER = "x-unique-message-id"
19
+ SERVICE_ID_HEADER = "x-service-id"
16
20
 
17
21
  _CONTEXT_HEADER_FIELDS: tuple[tuple[str, str], ...] = (
18
22
  ("company_id", COMPANY_ID_HEADER),
@@ -23,6 +27,23 @@ _CONTEXT_HEADER_FIELDS: tuple[tuple[str, str], ...] = (
23
27
  _LOGGER = logging.getLogger(__name__)
24
28
 
25
29
 
30
+ class EntryPoint(StrEnum):
31
+ """Service where a search request entered the platform."""
32
+
33
+ CHAT_TOOL = "chat_tool"
34
+ PUBLIC_API = "public_api"
35
+ CONDUCT = "conduct"
36
+ UNIQUE_API = "unique_api"
37
+ GRAPHQL = "graphql"
38
+ DEEP_RESEARCH = "deep_research"
39
+ WEB_SEARCH_SERVICE = "web_search_service"
40
+ UNKNOWN = "unknown"
41
+
42
+ @classmethod
43
+ def _missing_(cls, value: object) -> EntryPoint:
44
+ return cls.UNKNOWN
45
+
46
+
26
47
  class RequestContext(BaseModel):
27
48
  """Caller identity for search-proxy requests."""
28
49
 
@@ -31,8 +52,29 @@ class RequestContext(BaseModel):
31
52
  company_id: str
32
53
  user_id: str
33
54
  chat_id: str
55
+ entry_point: EntryPoint = EntryPoint.UNKNOWN
56
+ # Calling service from ``x-service-id``; logged only, never enforced.
57
+ caller: str = "unknown"
58
+ # Reserved for analytics; optional until callers send it.
59
+ message_id: str | None = None
34
60
  user_metadata: dict[str, Any] = Field(default_factory=dict)
35
61
 
62
+ @property
63
+ def invalid_identity_headers(self) -> list[str]:
64
+ """Identity headers whose value is not a numeric id (``local``, blank, junk)."""
65
+ return [
66
+ header_name
67
+ for header_name, value in (
68
+ (COMPANY_ID_HEADER, self.company_id),
69
+ (USER_ID_HEADER, self.user_id),
70
+ )
71
+ if not (value.isascii() and value.isdecimal())
72
+ ]
73
+
74
+ @property
75
+ def is_attributed(self) -> bool:
76
+ return not self.invalid_identity_headers
77
+
36
78
  def to_headers(self) -> dict[str, str]:
37
79
  """Serialize context to the canonical HTTP header names."""
38
80
  headers = {
@@ -40,6 +82,10 @@ class RequestContext(BaseModel):
40
82
  USER_ID_HEADER: self.user_id,
41
83
  CHAT_ID_HEADER: self.chat_id,
42
84
  }
85
+ if self.entry_point is not EntryPoint.UNKNOWN:
86
+ headers[ENTRY_POINT_HEADER] = self.entry_point.value
87
+ if self.message_id:
88
+ headers[MESSAGE_ID_HEADER] = self.message_id
43
89
  if self.user_metadata:
44
90
  # ``ensure_ascii`` keeps the value latin-1 encodable, which HTTP
45
91
  # headers require.
@@ -78,6 +124,13 @@ class RequestContext(BaseModel):
78
124
  values[field_name] = str(raw)
79
125
  return cls(
80
126
  **values,
127
+ entry_point=normalized.get(
128
+ ENTRY_POINT_HEADER.lower(),
129
+ fallback.entry_point,
130
+ ),
131
+ caller=normalized.get(SERVICE_ID_HEADER) or fallback.caller,
132
+ message_id=str(normalized.get(MESSAGE_ID_HEADER) or "").strip()
133
+ or fallback.message_id,
81
134
  user_metadata=_parse_user_metadata(
82
135
  normalized.get(USER_METADATA_HEADER),
83
136
  fallback=fallback.user_metadata,
@@ -119,8 +172,12 @@ LOCAL_REQUEST_CONTEXT = RequestContext(
119
172
  __all__ = [
120
173
  "CHAT_ID_HEADER",
121
174
  "COMPANY_ID_HEADER",
175
+ "ENTRY_POINT_HEADER",
176
+ "EntryPoint",
122
177
  "LOCAL_REQUEST_CONTEXT",
178
+ "MESSAGE_ID_HEADER",
123
179
  "RequestContext",
180
+ "SERVICE_ID_HEADER",
124
181
  "USER_ID_HEADER",
125
182
  "USER_METADATA_HEADER",
126
183
  ]
@@ -0,0 +1,45 @@
1
+ """Per-request usage record emitted as a structured log line."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from datetime import UTC, datetime
7
+ from typing import Literal
8
+ from uuid import uuid4
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+ from unique_search_proxy_core.context import EntryPoint
13
+
14
+ _LOGGER = logging.getLogger(__name__)
15
+
16
+
17
+ class UsageRecord(BaseModel):
18
+ """Who triggered a billable provider operation. Never holds queries or URLs."""
19
+
20
+ event: Literal["web_search_usage"] = "web_search_usage"
21
+ # Unique per usage line, not joinable to other log lines yet. Take the trace id
22
+ # from unique_toolkit.monitoring.tracing once the proxy adopts tracing.
23
+ request_id: str = Field(default_factory=lambda: str(uuid4()))
24
+ timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC))
25
+ company_id: str
26
+ user_id: str
27
+ chat_id: str
28
+ message_id: str | None = None
29
+ entry_point: EntryPoint
30
+ caller: str
31
+ endpoint: Literal["search", "agent_search", "crawl"]
32
+ provider: str
33
+ units: int
34
+ # Results requested on /v1/search; Google and Brave page, so one unit can be several calls.
35
+ fetch_size: int | None = None
36
+ status: Literal["success", "error"]
37
+ duration_ms: int
38
+
39
+
40
+ def record_usage(record: UsageRecord) -> None:
41
+ """Write ``record`` as one JSON log line."""
42
+ _LOGGER.info(record.model_dump_json())
43
+
44
+
45
+ __all__ = ["UsageRecord", "record_usage"]