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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +19 -11
- package/bin/claude-smart.js +290 -68
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -10
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
- package/plugin/dashboard/package-lock.json +61 -390
- package/plugin/dashboard/package.json +2 -2
- package/plugin/opencode/dist/server.mjs +76 -2
- package/plugin/opencode/server.mts +79 -2
- package/plugin/pyproject.toml +6 -2
- package/plugin/scripts/smart-install.sh +7 -1
- package/plugin/src/claude_smart/cli.py +210 -22
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +7 -0
- package/plugin/vendor/reflexio/README.md +3 -3
- package/plugin/vendor/reflexio/pyproject.toml +2 -1
- package/plugin/vendor/reflexio/reflexio/README.md +11 -6
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +27 -5
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +37 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +38 -9
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +274 -3267
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
- package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +57 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +385 -78
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +121 -525
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +43 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +58 -351
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +452 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1126
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +955 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +844 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +57 -86
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# server/api_endpoints
|
|
2
|
-
Description:
|
|
2
|
+
Description: Shared helpers between FastAPI domain routes and business logic — builds `RequestContext`, validates requests, and delegates into `Reflexio`. Public route declarations live in `../routes/` and are aggregated by `core_router` in `../api.py`; the files here are reusable handlers/helpers those routes call.
|
|
3
3
|
|
|
4
4
|
> For the complete endpoint list (publish, retrieval, search, profile/playbook lifecycle, evaluation, Braintrust, operations), see the parent [server README](../README.md#api-endpoints).
|
|
5
5
|
|
|
@@ -18,8 +18,9 @@ Description: Bridge between FastAPI routes and business logic — builds `Reques
|
|
|
18
18
|
## Architecture Pattern
|
|
19
19
|
|
|
20
20
|
```
|
|
21
|
-
api.py (
|
|
22
|
-
->
|
|
21
|
+
api.py (create_app + core_router)
|
|
22
|
+
-> routes/<domain>.py (FastAPI routers)
|
|
23
|
+
-> Depends(get_request_context) -> RequestContext(org_id, storage, configurator, prompt_manager)
|
|
23
24
|
-> get_reflexio(org_id) -> Reflexio (reflexio_lib) -> services/
|
|
24
25
|
```
|
|
25
26
|
|
|
@@ -62,12 +62,22 @@ logger = logging.getLogger(__name__)
|
|
|
62
62
|
def add_user_interaction(
|
|
63
63
|
org_id: str,
|
|
64
64
|
request: PublishUserInteractionRequest,
|
|
65
|
+
*,
|
|
66
|
+
use_publish_limiter: bool = True,
|
|
67
|
+
publish_limiter_wait_forever: bool = True,
|
|
68
|
+
defer_learning: bool = False,
|
|
65
69
|
) -> PublishUserInteractionResponse:
|
|
66
70
|
"""Add user interaction
|
|
67
71
|
|
|
68
72
|
Args:
|
|
69
73
|
org_id (str): Organization ID
|
|
70
74
|
request (PublishUserInteractionRequest): The request containing interaction data
|
|
75
|
+
use_publish_limiter (bool): Whether GenerationService should throttle the
|
|
76
|
+
post-write learning pipeline.
|
|
77
|
+
publish_limiter_wait_forever (bool): Whether GenerationService should queue
|
|
78
|
+
indefinitely for that post-write learning limiter.
|
|
79
|
+
defer_learning (bool): Whether to enqueue post-persist learning instead
|
|
80
|
+
of running it inline.
|
|
71
81
|
|
|
72
82
|
Returns:
|
|
73
83
|
PublishUserInteractionResponse: Response containing success status and message
|
|
@@ -77,7 +87,12 @@ def add_user_interaction(
|
|
|
77
87
|
return PublishUserInteractionResponse(success=False, message=message)
|
|
78
88
|
|
|
79
89
|
reflexio = get_reflexio(org_id=org_id)
|
|
80
|
-
return reflexio.publish_interaction(
|
|
90
|
+
return reflexio.publish_interaction(
|
|
91
|
+
request=request,
|
|
92
|
+
use_publish_limiter=use_publish_limiter,
|
|
93
|
+
publish_limiter_wait_forever=publish_limiter_wait_forever,
|
|
94
|
+
defer_learning=defer_learning,
|
|
95
|
+
)
|
|
81
96
|
|
|
82
97
|
|
|
83
98
|
def add_user_playbook(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from fastapi import Depends
|
|
2
2
|
|
|
3
|
-
from reflexio.server.
|
|
3
|
+
from reflexio.server.auth import default_get_org_id
|
|
4
4
|
from reflexio.server.prompt.prompt_manager import PromptManager
|
|
5
5
|
from reflexio.server.services.configurator.base_configurator import BaseConfigurator
|
|
6
6
|
from reflexio.server.services.configurator.configurator import get_configurator_class
|
|
@@ -5,6 +5,8 @@ Lives in its own module to avoid an import cycle: api endpoints reference
|
|
|
5
5
|
:func:`default_get_org_id` (e.g. via FastAPI ``Depends``) and are themselves
|
|
6
6
|
imported by :mod:`reflexio.server.api`. Putting the dependency here keeps the
|
|
7
7
|
import graph acyclic.
|
|
8
|
+
|
|
9
|
+
Public module path: :mod:`reflexio.server.auth`.
|
|
8
10
|
"""
|
|
9
11
|
|
|
10
12
|
from __future__ import annotations
|
|
@@ -65,6 +65,17 @@ _reflexio_cache: TTLCache = TTLCache(
|
|
|
65
65
|
maxsize=REFLEXIO_CACHE_MAX_SIZE, ttl=REFLEXIO_CACHE_TTL_SECONDS
|
|
66
66
|
)
|
|
67
67
|
_reflexio_cache_lock = threading.Lock()
|
|
68
|
+
_REFLEXIO_CONSTRUCTION_LOCK_STRIPES = 64
|
|
69
|
+
_reflexio_construction_locks = tuple(
|
|
70
|
+
threading.Lock() for _ in range(_REFLEXIO_CONSTRUCTION_LOCK_STRIPES)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _get_construction_lock(cache_key: CacheKey) -> threading.Lock:
|
|
75
|
+
"""Return a bounded striped lock for cold Reflexio construction."""
|
|
76
|
+
return _reflexio_construction_locks[
|
|
77
|
+
hash(cache_key) % _REFLEXIO_CONSTRUCTION_LOCK_STRIPES
|
|
78
|
+
]
|
|
68
79
|
|
|
69
80
|
|
|
70
81
|
def _probe_version_safe(reflexio: Reflexio) -> _ProbeResult:
|
|
@@ -157,45 +168,60 @@ def get_reflexio(org_id: str, storage_base_dir: str | None = None) -> Reflexio:
|
|
|
157
168
|
del _reflexio_cache[cache_key]
|
|
158
169
|
span.set_data("evicted", evicted)
|
|
159
170
|
|
|
160
|
-
# Cache miss (or just-evicted stale entry)
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
with
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
# Cache miss (or just-evicted stale entry). Only one request per cache
|
|
172
|
+
# key may construct at a time; construction can initialize storage and
|
|
173
|
+
# run migrations, so parallel cold starts for the same org are not safe.
|
|
174
|
+
construction_lock = _get_construction_lock(cache_key)
|
|
175
|
+
with construction_lock:
|
|
176
|
+
with _reflexio_cache_lock:
|
|
177
|
+
entry = _reflexio_cache.get(cache_key)
|
|
178
|
+
if entry is not None:
|
|
179
|
+
return entry.reflexio
|
|
180
|
+
|
|
181
|
+
logger.info(
|
|
182
|
+
"Constructing Reflexio instance for org %s storage_base_dir=%s",
|
|
183
|
+
org_id,
|
|
184
|
+
storage_base_dir,
|
|
170
185
|
)
|
|
186
|
+
with profile_step("reflexio.cache.construct"):
|
|
187
|
+
reflexio = Reflexio(org_id=org_id, storage_base_dir=storage_base_dir)
|
|
188
|
+
with profile_step("reflexio.cache.version_probe", cache_state="miss") as span:
|
|
189
|
+
new_version = _probe_version_safe(reflexio)
|
|
190
|
+
span.set_data("probe_failed", new_version is _PROBE_FAILED)
|
|
191
|
+
span.set_data(
|
|
192
|
+
"probe_supported",
|
|
193
|
+
new_version is not None and new_version is not _PROBE_FAILED,
|
|
194
|
+
)
|
|
171
195
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
196
|
+
# Construction-time probe failure: serve this request from the
|
|
197
|
+
# newly-built instance but DON'T cache it. Caching with
|
|
198
|
+
# ``cached_version=None`` would conflate the entry with a
|
|
199
|
+
# legitimately-unprobeable backend and permanently disable
|
|
200
|
+
# auto-eviction. Skipping the cache means the next request pays a
|
|
201
|
+
# construction cost, but version-based eviction is preserved as
|
|
202
|
+
# soon as the backend recovers.
|
|
203
|
+
if new_version is _PROBE_FAILED:
|
|
204
|
+
logger.warning(
|
|
205
|
+
"Constructed uncached Reflexio instance for org %s because config version probe failed",
|
|
206
|
+
org_id,
|
|
207
|
+
)
|
|
208
|
+
return reflexio
|
|
209
|
+
|
|
210
|
+
new_entry = _CacheEntry(
|
|
211
|
+
reflexio=reflexio,
|
|
212
|
+
cached_version=new_version, # type: ignore[arg-type]
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
with profile_step("reflexio.cache.store") as span:
|
|
216
|
+
with _reflexio_cache_lock:
|
|
193
217
|
_reflexio_cache[cache_key] = new_entry
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
218
|
+
span.set_data("stored", True)
|
|
219
|
+
logger.info(
|
|
220
|
+
"Stored Reflexio instance for org %s storage_base_dir=%s",
|
|
221
|
+
org_id,
|
|
222
|
+
storage_base_dir,
|
|
223
|
+
)
|
|
224
|
+
return reflexio
|
|
199
225
|
|
|
200
226
|
|
|
201
227
|
def invalidate_reflexio_cache(org_id: str, storage_base_dir: str | None = None) -> bool:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Declarative deployment profile (Tier-3 Phase C).
|
|
2
|
+
|
|
3
|
+
A ``DeploymentProfile`` is a minimal, frozen description of *what a running app
|
|
4
|
+
serves*: its deployment ``role``, the set of ``router_groups`` it mounts, and
|
|
5
|
+
whether auth is required. It is a cleaner internal representation of the knobs
|
|
6
|
+
``create_app`` already accepts (``mount_data_plane`` / ``require_auth``) — the
|
|
7
|
+
profile changes nothing observable; it just gives the composition root a single
|
|
8
|
+
value to thread instead of scattered booleans.
|
|
9
|
+
|
|
10
|
+
This module is an OSS public seam: it imports nothing from ``reflexio_ext`` and
|
|
11
|
+
carries no enterprise backend knowledge (string/stdlib only). The enterprise
|
|
12
|
+
composition defines its own profiles (self-host / platform) on top of this type.
|
|
13
|
+
|
|
14
|
+
``mounts_data_plane`` is DERIVED from ``router_groups`` (membership of the
|
|
15
|
+
``DATA_PLANE_GROUP``), not stored — so the two can never disagree.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
|
|
22
|
+
# The single OSS router group: the data-plane surface (core product endpoints,
|
|
23
|
+
# stall-state, pending-tool-call) that ``create_app`` mounts when data-plane is
|
|
24
|
+
# active. Enterprise adds its own group names on top of this type.
|
|
25
|
+
DATA_PLANE_GROUP = "data-plane"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class DeploymentProfile:
|
|
30
|
+
"""What a running app serves: role, mounted router groups, auth requirement.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
role (str): Deployment role label (e.g. ``"all"``, ``"data-plane"``,
|
|
34
|
+
``"control-plane"``). Informational for the OSS factory; enterprise
|
|
35
|
+
uses it to select its router set.
|
|
36
|
+
router_groups (frozenset[str]): Router-group names this app mounts.
|
|
37
|
+
auth_required (bool): Whether the app declares/enforces auth.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
role: str
|
|
41
|
+
router_groups: frozenset[str]
|
|
42
|
+
auth_required: bool
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def mounts_data_plane(self) -> bool:
|
|
46
|
+
"""Whether this profile mounts the data-plane router group."""
|
|
47
|
+
return DATA_PLANE_GROUP in self.router_groups
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def resolve_profile(*, mount_data_plane: bool, require_auth: bool) -> DeploymentProfile:
|
|
51
|
+
"""Derive the OSS profile from the legacy ``create_app`` knobs.
|
|
52
|
+
|
|
53
|
+
Behaviour-preserving: this is the identity mapping from the two booleans
|
|
54
|
+
``create_app`` has always accepted onto the profile representation.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
mount_data_plane (bool): Whether the data-plane routers/lifespan run.
|
|
58
|
+
require_auth (bool): Whether auth (Bearer security scheme) is declared.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
DeploymentProfile: ``role="all"`` + the data-plane group when
|
|
62
|
+
``mount_data_plane`` is True, otherwise ``role="control-plane"`` with no
|
|
63
|
+
router groups. ``auth_required`` mirrors ``require_auth``.
|
|
64
|
+
"""
|
|
65
|
+
router_groups = frozenset({DATA_PLANE_GROUP}) if mount_data_plane else frozenset()
|
|
66
|
+
role = "all" if mount_data_plane else "control-plane"
|
|
67
|
+
return DeploymentProfile(
|
|
68
|
+
role=role, router_groups=router_groups, auth_required=require_auth
|
|
69
|
+
)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""OSS extension primitives: the process-global capability/service registry.
|
|
2
|
+
|
|
3
|
+
OSS defines these seams; enterprise registers implementations at its composition
|
|
4
|
+
root. OSS never imports reflexio_ext. The service registry is process-global (a
|
|
5
|
+
module dict), mirroring set_configurator_class/get_configurator_class, so it is
|
|
6
|
+
reachable from every RequestContext construction site — HTTP, library
|
|
7
|
+
(lib/_base.py), and background workers — not just app-scoped ones.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import ABC
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import ClassVar
|
|
16
|
+
|
|
17
|
+
from fastapi import APIRouter
|
|
18
|
+
|
|
19
|
+
from reflexio.server.services.unified_search_service import (
|
|
20
|
+
RetrievalCaptureHook,
|
|
21
|
+
configure_retrieval_capture_hook,
|
|
22
|
+
)
|
|
23
|
+
from reflexio.server.tracing import Tracer, configure_tracer
|
|
24
|
+
from reflexio.server.usage_metrics import (
|
|
25
|
+
UsageEventRecorder,
|
|
26
|
+
configure_usage_event_recorder,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ServiceKey[T]:
|
|
31
|
+
"""Typed handle into the runtime-service registry.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
name (str): Stable identifier, used in error messages and as the dict key.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, name: str) -> None:
|
|
38
|
+
self.name = name
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return f"ServiceKey({self.name!r})"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_services: dict[str, object] = {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register_service[T](
|
|
48
|
+
key: ServiceKey[T], value: T, *, override: bool = False
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Register a runtime-service provider under ``key`` (composition root only).
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
key (ServiceKey[T]): The typed key.
|
|
54
|
+
value (T): The provider/value.
|
|
55
|
+
override (bool): Allow replacing an existing registration. Default False.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
RuntimeError: If ``key`` is already registered and ``override`` is False.
|
|
59
|
+
"""
|
|
60
|
+
if not override and key.name in _services:
|
|
61
|
+
raise RuntimeError(f"service already registered for key {key.name!r}")
|
|
62
|
+
_services[key.name] = value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_service[T](key: ServiceKey[T]) -> T | None:
|
|
66
|
+
"""Return the provider for ``key``, or None if unset (optional providers)."""
|
|
67
|
+
return _services.get(key.name) # type: ignore[return-value]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def require_service[T](key: ServiceKey[T]) -> T:
|
|
71
|
+
"""Return the provider for ``key``, raising if unset (required providers).
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
key (ServiceKey[T]): The typed key whose provider to retrieve.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
T: The registered provider value.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
LookupError: If no provider is registered for ``key``.
|
|
81
|
+
"""
|
|
82
|
+
if key.name not in _services:
|
|
83
|
+
raise LookupError(f"required service not registered for key {key.name!r}")
|
|
84
|
+
return _services[key.name] # type: ignore[return-value]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def reset_services() -> None:
|
|
88
|
+
"""Clear all registered services. Test-only."""
|
|
89
|
+
_services.clear()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Hook registry, app context, and capability lifecycle primitives
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class HookRegistry:
|
|
98
|
+
"""Facade over OSS process-global hook setters; capabilities install through it."""
|
|
99
|
+
|
|
100
|
+
def set_tracer(self, tracer: Tracer | None) -> None:
|
|
101
|
+
"""Install (or clear) the process-global request tracer.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
tracer (Tracer | None): Tracer implementation, or None to disable.
|
|
105
|
+
"""
|
|
106
|
+
configure_tracer(tracer)
|
|
107
|
+
|
|
108
|
+
def set_usage_recorder(self, recorder: UsageEventRecorder | None) -> None:
|
|
109
|
+
"""Install (or clear) the process-global usage-event recorder.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
recorder (UsageEventRecorder | None): Recorder callable, or None to disable.
|
|
113
|
+
"""
|
|
114
|
+
configure_usage_event_recorder(recorder)
|
|
115
|
+
|
|
116
|
+
def set_retrieval_capture(self, hook: RetrievalCaptureHook | None) -> None:
|
|
117
|
+
"""Install (or clear) the process-global retrieval-capture hook.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
hook (RetrievalCaptureHook | None): Hook callable, or None to disable.
|
|
121
|
+
"""
|
|
122
|
+
configure_retrieval_capture_hook(hook)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class AppContext:
|
|
127
|
+
"""Inter-step startup data passed to Capability.on_startup.
|
|
128
|
+
|
|
129
|
+
Mirrors the values that thread through reflexio_ext lifespan locals
|
|
130
|
+
(prepare_enterprise_startup output).
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
self_host_org_id (str | None): Org ID for self-hosted deployments, or None for platform.
|
|
134
|
+
activated (bool): Whether the enterprise activation check passed.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
self_host_org_id: str | None = None
|
|
138
|
+
activated: bool = False
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Capability(ABC):
|
|
142
|
+
"""An enterprise feature plugged into the app at its composition root.
|
|
143
|
+
|
|
144
|
+
All methods are no-op defaults so the registry can drive every capability
|
|
145
|
+
through the same phases without hasattr checks. The provider owns this
|
|
146
|
+
lifecycle shape (ABC); runtime-feature *contracts* are Protocols defined by OSS.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
name: ClassVar[str]
|
|
150
|
+
|
|
151
|
+
def install_services(self) -> None: # noqa: B027
|
|
152
|
+
"""Register runtime-service providers (process-global). Default: none."""
|
|
153
|
+
|
|
154
|
+
def install_hooks(self, hooks: HookRegistry) -> None: # noqa: B027
|
|
155
|
+
"""Install pipeline hooks (tracer/usage-recorder/retrieval-capture). Default: none.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
hooks (HookRegistry): Registry to install hooks through.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def routers(self, role: str) -> list[APIRouter]: # noqa: ARG002
|
|
162
|
+
"""Return routers to mount for this deployment role. Default: none.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
role (str): Deployment role (e.g. "all", "data-plane").
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
list[APIRouter]: Routers to mount; empty list by default.
|
|
169
|
+
"""
|
|
170
|
+
return []
|
|
171
|
+
|
|
172
|
+
async def on_startup(self, ctx: AppContext) -> None: # noqa: B027
|
|
173
|
+
"""Start daemons/schedulers. Raising aborts boot (fail-loud). Default: none.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
ctx (AppContext): Startup context produced by prepare_enterprise_startup.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
async def on_shutdown(self) -> None: # noqa: B027
|
|
180
|
+
"""Stop what on_startup started. Default: none."""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class CapabilityRegistry:
|
|
184
|
+
"""Holds additive capabilities plus the two singular construction-time concerns.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
capabilities (list[Capability]): Capability instances; names must be unique.
|
|
188
|
+
configurator_class (type | None): Single configurator class, or None.
|
|
189
|
+
billing_gate (Callable | None): Billing gate factory, or None.
|
|
190
|
+
role (str | None): Deployment role to thread into ``_wire_capabilities``
|
|
191
|
+
(e.g. ``"all"``, ``"data-plane"``). When ``None``, ``create_app``
|
|
192
|
+
derives the role from ``mount_data_plane``.
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
RuntimeError: If two capabilities share the same name.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
capabilities: list[Capability],
|
|
201
|
+
*,
|
|
202
|
+
configurator_class: type | None = None,
|
|
203
|
+
billing_gate: Callable[[str], Callable[..., None]] | None = None,
|
|
204
|
+
role: str | None = None,
|
|
205
|
+
) -> None:
|
|
206
|
+
names = [c.name for c in capabilities]
|
|
207
|
+
dupes = {n for n in names if names.count(n) > 1}
|
|
208
|
+
if dupes:
|
|
209
|
+
raise RuntimeError(f"duplicate capability names: {sorted(dupes)}")
|
|
210
|
+
self.capabilities = capabilities
|
|
211
|
+
self.configurator_class = configurator_class
|
|
212
|
+
self.billing_gate = billing_gate
|
|
213
|
+
self.role = role
|