claude-smart 0.2.46 → 0.2.48

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 (170) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +19 -11
  3. package/bin/claude-smart.js +290 -68
  4. package/package.json +1 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +11 -10
  8. package/plugin/dashboard/app/layout.tsx +20 -0
  9. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
  10. package/plugin/dashboard/package-lock.json +61 -390
  11. package/plugin/dashboard/package.json +2 -2
  12. package/plugin/opencode/dist/server.mjs +76 -2
  13. package/plugin/opencode/server.mts +79 -2
  14. package/plugin/pyproject.toml +6 -2
  15. package/plugin/scripts/smart-install.sh +7 -1
  16. package/plugin/src/claude_smart/cli.py +210 -22
  17. package/plugin/src/claude_smart/context_format.py +9 -9
  18. package/plugin/src/claude_smart/cs_cite.py +66 -19
  19. package/plugin/uv.lock +5 -5
  20. package/plugin/vendor/reflexio/.env.example +7 -0
  21. package/plugin/vendor/reflexio/README.md +3 -3
  22. package/plugin/vendor/reflexio/pyproject.toml +2 -1
  23. package/plugin/vendor/reflexio/reflexio/README.md +11 -6
  24. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
  25. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
  26. package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
  27. package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
  28. package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
  29. package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
  30. package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
  31. package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
  32. package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
  33. package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
  34. package/plugin/vendor/reflexio/reflexio/lib/_search.py +27 -5
  35. package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
  36. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
  37. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
  38. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
  39. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
  40. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +37 -0
  41. package/plugin/vendor/reflexio/reflexio/server/README.md +38 -9
  42. package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
  43. package/plugin/vendor/reflexio/reflexio/server/api.py +274 -3267
  44. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
  45. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
  46. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
  47. package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
  48. package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
  49. package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
  50. package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
  51. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
  52. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
  53. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
  54. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
  55. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
  56. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
  57. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
  58. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
  59. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +57 -5
  60. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
  61. package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
  62. package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
  63. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
  64. package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
  65. package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
  66. package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
  67. package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
  68. package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
  69. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
  70. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
  71. package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
  72. package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
  73. package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
  74. package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
  75. package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
  76. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
  77. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
  78. package/plugin/vendor/reflexio/reflexio/server/services/README.md +5 -2
  79. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
  80. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
  81. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
  82. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
  83. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
  84. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
  85. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
  86. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
  87. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
  88. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
  89. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
  90. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
  91. package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
  92. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
  93. package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
  94. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +385 -78
  95. package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
  96. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
  97. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +121 -525
  98. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
  99. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
  100. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
  101. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
  102. package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
  103. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
  104. package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
  105. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
  106. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
  107. package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
  108. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
  109. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +43 -6
  110. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
  111. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +58 -351
  112. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
  113. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +452 -0
  114. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -4
  115. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
  116. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1126
  117. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
  118. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
  120. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
  121. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
  122. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
  123. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
  124. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
  125. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
  126. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
  127. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
  128. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
  129. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
  130. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
  131. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
  132. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
  133. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +955 -0
  134. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
  135. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
  136. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
  137. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +844 -0
  138. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
  139. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
  140. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
  141. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
  142. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -9
  143. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
  144. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
  145. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
  146. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
  147. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
  148. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
  149. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
  150. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
  151. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
  152. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
  153. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
  154. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
  155. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
  156. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
  157. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
  158. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
  159. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
  160. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
  161. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
  162. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
  163. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
  164. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
  165. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
  166. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
  167. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +57 -86
  168. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
  169. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
  170. package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
@@ -0,0 +1,423 @@
1
+ """Profile route handlers (extracted from api.py, Tier3 A2)."""
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ pass
8
+
9
+ from fastapi import (
10
+ APIRouter,
11
+ BackgroundTasks,
12
+ Depends,
13
+ Request,
14
+ )
15
+
16
+ from reflexio.models.api_schema.retriever_schema import (
17
+ GetProfileStatisticsResponse,
18
+ GetProfilesViewResponse,
19
+ GetUserProfilesRequest,
20
+ ProfileChangeLogViewResponse,
21
+ UpdateUserProfileRequest,
22
+ UpdateUserProfileResponse,
23
+ )
24
+ from reflexio.models.api_schema.service_schemas import (
25
+ AddUserProfileRequest,
26
+ AddUserProfileResponse,
27
+ BulkDeleteResponse,
28
+ DeleteProfilesByIdsRequest,
29
+ DeleteUserProfileRequest,
30
+ DeleteUserProfileResponse,
31
+ DowngradeProfilesRequest,
32
+ DowngradeProfilesResponse,
33
+ ManualProfileGenerationRequest,
34
+ ManualProfileGenerationResponse,
35
+ RerunProfileGenerationRequest,
36
+ RerunProfileGenerationResponse,
37
+ Status,
38
+ UpgradeProfilesRequest,
39
+ UpgradeProfilesResponse,
40
+ )
41
+ from reflexio.models.api_schema.ui.converters import (
42
+ to_profile_change_log_view,
43
+ to_profile_view,
44
+ )
45
+ from reflexio.server.api_endpoints import (
46
+ publisher_api,
47
+ )
48
+ from reflexio.server.auth import (
49
+ default_get_org_id,
50
+ )
51
+ from reflexio.server.cache import reflexio_cache
52
+ from reflexio.server.rate_limit import limiter
53
+
54
+ logger = logging.getLogger(__name__)
55
+ router = APIRouter()
56
+
57
+
58
+ @router.post(
59
+ "/api/add_user_profile",
60
+ response_model=AddUserProfileResponse,
61
+ response_model_exclude_none=True,
62
+ )
63
+ @limiter.limit("60/minute") # Rate limit for write operations
64
+ def add_user_profile_endpoint(
65
+ request: Request,
66
+ payload: AddUserProfileRequest,
67
+ org_id: str = Depends(default_get_org_id),
68
+ ) -> AddUserProfileResponse:
69
+ """Add user profile directly to storage, bypassing inference.
70
+
71
+ Args:
72
+ request (Request): The HTTP request object (for rate limiting)
73
+ payload (AddUserProfileRequest): The request containing user profiles
74
+ org_id (str): Organization ID
75
+
76
+ Returns:
77
+ AddUserProfileResponse: Response containing success status, message, and added count
78
+ """
79
+ return publisher_api.add_user_profile(org_id=org_id, request=payload)
80
+
81
+
82
+ @router.get("/api/profile_change_log", response_model=ProfileChangeLogViewResponse)
83
+ def get_profile_change_log(
84
+ org_id: str = Depends(default_get_org_id),
85
+ ) -> ProfileChangeLogViewResponse:
86
+ # Serves the reconstructed profile change log (rebuilt from lineage events). The
87
+ # legacy `profile_change_logs` table is no longer written; see
88
+ # reconstruct_profile_change_log in lib/_profiles.py.
89
+ response = reflexio_cache.get_reflexio(org_id=org_id).get_profile_change_logs()
90
+ return ProfileChangeLogViewResponse(
91
+ success=response.success,
92
+ profile_change_logs=[
93
+ to_profile_change_log_view(log) for log in response.profile_change_logs
94
+ ],
95
+ )
96
+
97
+
98
+ @router.delete(
99
+ "/api/delete_profile",
100
+ response_model=DeleteUserProfileResponse,
101
+ response_model_exclude_none=True,
102
+ )
103
+ def delete_profile(
104
+ request: DeleteUserProfileRequest,
105
+ org_id: str = Depends(default_get_org_id),
106
+ ) -> DeleteUserProfileResponse:
107
+ return publisher_api.delete_user_profile(org_id=org_id, request=request)
108
+
109
+
110
+ @router.delete(
111
+ "/api/delete_profiles_by_ids",
112
+ response_model=BulkDeleteResponse,
113
+ response_model_exclude_none=True,
114
+ )
115
+ def delete_profiles_by_ids(
116
+ request: DeleteProfilesByIdsRequest,
117
+ org_id: str = Depends(default_get_org_id),
118
+ ) -> BulkDeleteResponse:
119
+ """Delete multiple profiles by their IDs.
120
+
121
+ Args:
122
+ request (DeleteProfilesByIdsRequest): Request containing list of profile IDs to delete
123
+ org_id (str): Organization ID
124
+
125
+ Returns:
126
+ BulkDeleteResponse: Response containing success status and deleted count
127
+ """
128
+ return publisher_api.delete_profiles_by_ids(org_id=org_id, request=request)
129
+
130
+
131
+ @router.delete(
132
+ "/api/delete_all_profiles",
133
+ response_model=BulkDeleteResponse,
134
+ response_model_exclude_none=True,
135
+ )
136
+ @limiter.limit("10/minute")
137
+ def delete_all_profiles(
138
+ request: Request,
139
+ org_id: str = Depends(default_get_org_id),
140
+ ) -> BulkDeleteResponse:
141
+ """Delete all profiles.
142
+
143
+ Args:
144
+ org_id (str): Organization ID
145
+
146
+ Returns:
147
+ BulkDeleteResponse: Response containing success status and deleted count
148
+ """
149
+ return publisher_api.delete_all_profiles_bulk(org_id=org_id)
150
+
151
+
152
+ @router.post(
153
+ "/api/get_profiles",
154
+ response_model=GetProfilesViewResponse,
155
+ response_model_exclude_none=True,
156
+ )
157
+ def get_profiles(
158
+ request: GetUserProfilesRequest,
159
+ org_id: str = Depends(default_get_org_id),
160
+ ) -> GetProfilesViewResponse:
161
+ response = reflexio_cache.get_reflexio(org_id=org_id).get_profiles(request)
162
+ return GetProfilesViewResponse(
163
+ success=response.success,
164
+ user_profiles=[to_profile_view(p) for p in response.user_profiles],
165
+ msg=response.msg,
166
+ )
167
+
168
+
169
+ @router.get(
170
+ "/api/get_all_profiles",
171
+ response_model=GetProfilesViewResponse,
172
+ response_model_exclude_none=True,
173
+ )
174
+ @limiter.limit("30/minute")
175
+ def get_all_profiles(
176
+ request: Request,
177
+ limit: int = 100,
178
+ status_filter: str | None = None,
179
+ profile_id: str | None = None,
180
+ user_id: str | None = None,
181
+ query: str | None = None,
182
+ source: str | None = None,
183
+ profile_time_to_live: str | None = None,
184
+ start_time: int | None = None,
185
+ end_time: int | None = None,
186
+ include_tombstones: bool = False,
187
+ org_id: str = Depends(default_get_org_id),
188
+ ) -> GetProfilesViewResponse:
189
+ """Get all user profiles across all users.
190
+
191
+ Args:
192
+ limit (int, optional): Maximum number of profiles to return. Defaults to 100.
193
+ status_filter (str, optional): Filter by profile status. Can be "current", "pending", or "archived".
194
+ profile_id (str, optional): Exact profile ID to retrieve.
195
+ user_id (str, optional): Exact user ID to filter by.
196
+ query (str, optional): Case-insensitive text filter across visible fields.
197
+ source (str, optional): Exact profile source to filter by.
198
+ profile_time_to_live (str, optional): Exact TTL value to filter by.
199
+ start_time (int, optional): Minimum last-modified epoch seconds.
200
+ end_time (int, optional): Maximum last-modified epoch seconds.
201
+ include_tombstones (bool, optional): Include merged/superseded rows when
202
+ looking up a specific profile_id.
203
+ org_id (str): Organization ID
204
+
205
+ Returns:
206
+ GetProfilesViewResponse: Response containing all user profiles
207
+ """
208
+ reflexio = reflexio_cache.get_reflexio(org_id=org_id)
209
+
210
+ if profile_id and include_tombstones:
211
+ storage = reflexio.request_context.storage
212
+ if storage is None:
213
+ return GetProfilesViewResponse(
214
+ success=True,
215
+ user_profiles=[],
216
+ msg="Storage is not configured",
217
+ )
218
+ profile = storage.get_profile_by_id(
219
+ profile_id, include_tombstones=include_tombstones
220
+ )
221
+ profiles = [profile] if profile else []
222
+ return GetProfilesViewResponse(
223
+ success=True,
224
+ user_profiles=[to_profile_view(p) for p in profiles],
225
+ msg=f"Found {len(profiles)} profile(s)",
226
+ )
227
+
228
+ # Map status_filter string to Status list
229
+ status_filter_list = None
230
+ if status_filter == "current":
231
+ status_filter_list = [None]
232
+ elif status_filter == "pending":
233
+ status_filter_list = [Status.PENDING]
234
+ elif status_filter == "archived":
235
+ status_filter_list = [Status.ARCHIVED]
236
+
237
+ response = reflexio.get_all_profiles(
238
+ limit=limit,
239
+ status_filter=status_filter_list, # type: ignore[reportArgumentType]
240
+ user_id=user_id,
241
+ profile_id=profile_id,
242
+ query=query,
243
+ source=source,
244
+ profile_time_to_live=profile_time_to_live,
245
+ start_time=start_time,
246
+ end_time=end_time,
247
+ )
248
+ return GetProfilesViewResponse(
249
+ success=response.success,
250
+ user_profiles=[to_profile_view(p) for p in response.user_profiles],
251
+ msg=response.msg,
252
+ )
253
+
254
+
255
+ @router.get(
256
+ "/api/get_profile_statistics",
257
+ response_model=GetProfileStatisticsResponse,
258
+ response_model_exclude_none=True,
259
+ )
260
+ def get_profile_statistics(
261
+ org_id: str = Depends(default_get_org_id),
262
+ ) -> GetProfileStatisticsResponse:
263
+ """Get efficient profile statistics using storage layer queries.
264
+
265
+ Args:
266
+ org_id (str): Organization ID
267
+
268
+ Returns:
269
+ GetProfileStatisticsResponse: Response containing profile counts by status
270
+ """
271
+ # Create Reflexio instance
272
+ reflexio = reflexio_cache.get_reflexio(org_id=org_id)
273
+
274
+ # Get profile statistics using Reflexio's method
275
+ return reflexio.get_profile_statistics()
276
+
277
+
278
+ @router.put(
279
+ "/api/update_user_profile",
280
+ response_model=UpdateUserProfileResponse,
281
+ response_model_exclude_none=True,
282
+ )
283
+ def update_user_profile_endpoint(
284
+ request: UpdateUserProfileRequest,
285
+ org_id: str = Depends(default_get_org_id),
286
+ ) -> UpdateUserProfileResponse:
287
+ """Apply a partial update to an existing user profile.
288
+
289
+ Args:
290
+ request (UpdateUserProfileRequest): The update request
291
+ org_id (str): Organization ID
292
+
293
+ Returns:
294
+ UpdateUserProfileResponse: Response containing success status and message
295
+ """
296
+ return publisher_api.update_user_profile(org_id=org_id, request=request)
297
+
298
+
299
+ @router.post(
300
+ "/api/rerun_profile_generation",
301
+ response_model=RerunProfileGenerationResponse,
302
+ response_model_exclude_none=True,
303
+ )
304
+ @limiter.limit("5/minute") # Strict limit for expensive operations
305
+ def rerun_profile_generation_endpoint(
306
+ request: Request,
307
+ payload: RerunProfileGenerationRequest,
308
+ background_tasks: BackgroundTasks,
309
+ org_id: str = Depends(default_get_org_id),
310
+ ) -> RerunProfileGenerationResponse:
311
+ """Rerun profile generation for a user with filtered interactions.
312
+
313
+ Args:
314
+ request (Request): The HTTP request object (for rate limiting)
315
+ payload (RerunProfileGenerationRequest): Request containing user_id, time filters, and source
316
+ background_tasks (BackgroundTasks): Background task runner
317
+ org_id (str): Organization ID
318
+
319
+ Returns:
320
+ RerunProfileGenerationResponse: Response containing success status and profiles generated count
321
+ """
322
+ # Create Reflexio instance
323
+ reflexio = reflexio_cache.get_reflexio(org_id=org_id)
324
+
325
+ # Run the long-running task in the background to avoid proxy timeout
326
+ # Client polls get_operation_status for progress
327
+ background_tasks.add_task(reflexio.rerun_profile_generation, payload)
328
+
329
+ return RerunProfileGenerationResponse(
330
+ success=True, msg="Profile generation started"
331
+ )
332
+
333
+
334
+ @router.post(
335
+ "/api/manual_profile_generation",
336
+ response_model=ManualProfileGenerationResponse,
337
+ response_model_exclude_none=True,
338
+ )
339
+ @limiter.limit("5/minute") # Strict limit for expensive operations
340
+ def manual_profile_generation_endpoint(
341
+ request: Request,
342
+ payload: ManualProfileGenerationRequest,
343
+ org_id: str = Depends(default_get_org_id),
344
+ ) -> ManualProfileGenerationResponse:
345
+ """Manually trigger profile generation with window-sized interactions and CURRENT output.
346
+
347
+ Runs with auto_run=False, which bypasses the regular stride/should_run
348
+ gates. Only profile extraction is triggered. Each extractor uses its own
349
+ window_size_override when present, falling back to the global window_size.
350
+ Output is CURRENT profiles only.
351
+
352
+ Args:
353
+ request (Request): The HTTP request object (for rate limiting)
354
+ payload (ManualProfileGenerationRequest): Request containing user_id, source, and extractor_names
355
+ org_id (str): Organization ID
356
+
357
+ Returns:
358
+ ManualProfileGenerationResponse: Response containing success status and profiles generated count
359
+ """
360
+ # Create Reflexio instance
361
+ reflexio = reflexio_cache.get_reflexio(org_id=org_id)
362
+
363
+ # Call manual_profile_generation
364
+ return reflexio.manual_profile_generation(payload)
365
+
366
+
367
+ @router.post(
368
+ "/api/upgrade_all_profiles",
369
+ response_model=UpgradeProfilesResponse,
370
+ response_model_exclude_none=True,
371
+ )
372
+ def upgrade_all_profiles_endpoint(
373
+ request: UpgradeProfilesRequest,
374
+ org_id: str = Depends(default_get_org_id),
375
+ ) -> UpgradeProfilesResponse:
376
+ """Upgrade all profiles by deleting old ARCHIVED, archiving CURRENT, and promoting PENDING.
377
+
378
+ This operation performs three atomic steps:
379
+ 1. Delete all ARCHIVED profiles (old archived profiles from previous upgrades)
380
+ 2. Archive all CURRENT profiles → ARCHIVED (save current state for potential rollback)
381
+ 3. Promote all PENDING profiles → CURRENT (activate new profiles)
382
+
383
+ Args:
384
+ request (UpgradeProfilesRequest): The upgrade request with only_affected_users parameter
385
+ org_id (str): Organization ID
386
+
387
+ Returns:
388
+ UpgradeProfilesResponse: Response containing success status and counts
389
+ """
390
+ # Create Reflexio instance
391
+ reflexio = reflexio_cache.get_reflexio(org_id=org_id)
392
+
393
+ # Call upgrade_all_profiles with request
394
+ return reflexio.upgrade_all_profiles(request=request)
395
+
396
+
397
+ @router.post(
398
+ "/api/downgrade_all_profiles",
399
+ response_model=DowngradeProfilesResponse,
400
+ response_model_exclude_none=True,
401
+ )
402
+ def downgrade_all_profiles_endpoint(
403
+ request: DowngradeProfilesRequest,
404
+ org_id: str = Depends(default_get_org_id),
405
+ ) -> DowngradeProfilesResponse:
406
+ """Downgrade all profiles by demoting CURRENT to PENDING and restoring ARCHIVED.
407
+
408
+ This operation performs two atomic steps:
409
+ 1. Demote all CURRENT profiles → PENDING
410
+ 2. Restore all ARCHIVED profiles → CURRENT
411
+
412
+ Args:
413
+ request (DowngradeProfilesRequest): The downgrade request with only_affected_users parameter
414
+ org_id (str): Organization ID
415
+
416
+ Returns:
417
+ DowngradeProfilesResponse: Response containing success status and counts
418
+ """
419
+ # Create Reflexio instance
420
+ reflexio = reflexio_cache.get_reflexio(org_id=org_id)
421
+
422
+ # Call downgrade_all_profiles with request
423
+ return reflexio.downgrade_all_profiles(request=request)