fred-runtime 3.3.0__tar.gz → 3.3.1__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.
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/PKG-INFO +4 -4
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/agent_app.py +70 -3
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/mcp_runtime.py +1 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/deep/deep_runtime.py +12 -6
- fred_runtime-3.3.1/fred_runtime/integrations/inprocess_toolkit_registry.py +50 -0
- fred_runtime-3.3.1/fred_runtime/integrations/kf_vector_search/__init__.py +21 -0
- fred_runtime-3.3.1/fred_runtime/integrations/kf_vector_search/toolkit.py +186 -0
- fred_runtime-3.3.1/fred_runtime/react/react_prompting.py +310 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_runtime.py +7 -5
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_context.py +3 -2
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/sql_checkpointer.py +219 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/PKG-INFO +4 -4
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/SOURCES.txt +7 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/requires.txt +3 -3
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/pyproject.toml +4 -4
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_agent_app.py +131 -18
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_client.py +0 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_config_loader.py +0 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_context.py +0 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_context_aware_tool.py +1 -2
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_conversational_memory.py +5 -10
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_eval_trace.py +1 -2
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_fred_workspace_fs.py +2 -3
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_graph_runtime_invoke_agent.py +5 -6
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_graph_runtime_observability.py +1 -2
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_history.py +5 -6
- fred_runtime-3.3.1/tests/test_kf_vector_search_toolkit.py +136 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_kf_workspace_client.py +0 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_mcp_config.py +0 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_model_routing.py +0 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_openai_compat_router.py +2 -3
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_pod_client.py +0 -1
- fred_runtime-3.3.1/tests/test_react_prompting.py +216 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_react_thinking.py +12 -13
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_repl_helpers.py +0 -1
- fred_runtime-3.3.1/tests/test_runtime_context_prompt_injection.py +164 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_smoke.py +5 -6
- fred_runtime-3.3.1/tests/test_sql_checkpointer_owner.py +271 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_token_expiry.py +0 -1
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_user_token_refresher.py +0 -1
- fred_runtime-3.3.0/fred_runtime/react/react_prompting.py +0 -164
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/README.md +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/_catalogs.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/config.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/config_loader.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/container.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/context.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/dependencies.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/mcp_config.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/observability_factory.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/app/openai_compat_router.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/completion.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/entrypoint.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/history_display.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/kpi_display.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/pod_client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/repl.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/repl_helpers.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/cli/url_helpers.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/context_aware_tool.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_base_client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_fast_text_client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_http_client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_logs_client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_markdown_media_client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/kf_workspace_client.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/mcp_interceptors.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/mcp_toolkit.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/mcp_utils.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/structures.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/token_expiry.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/common/tool_node_utils.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/deep/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/eval/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/eval/collector.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/graph/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/graph/graph_runtime.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/integrations/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/integrations/v2_runtime/adapters.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/catalog.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/contracts.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/provider.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/model_routing/resolver.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_langchain_adapter.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_message_codec.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_model_adapter.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_stream_adapter.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_binding.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_loop.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_rendering.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_resolution.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tool_utils.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/react/react_tracing.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/checkpoints.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/model_metadata.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/__init__.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/filesystem_context.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/thinking.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/tool_approval.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime/support/tool_loop.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/dependency_links.txt +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/entry_points.txt +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/fred_runtime.egg-info/top_level.txt +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/setup.cfg +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_eval_collector.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_kpi_display.py +0 -0
- {fred_runtime-3.3.0 → fred_runtime-3.3.1}/tests/test_url_helpers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fred-runtime
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.1
|
|
4
4
|
Summary: Runtime adapters and infrastructure wiring for Fred v2 agents.
|
|
5
5
|
Author-email: Thales <noreply@thalesgroup.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -12,8 +12,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Requires-Python: <3.13,>=3.12
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
Requires-Dist: fred-core>=3.4.
|
|
16
|
-
Requires-Dist: fred-sdk>=3.3.
|
|
15
|
+
Requires-Dist: fred-core>=3.4.1
|
|
16
|
+
Requires-Dist: fred-sdk>=3.3.1
|
|
17
17
|
Requires-Dist: alembic>=1.18.4
|
|
18
18
|
Requires-Dist: deepagents>=0.4.11
|
|
19
19
|
Requires-Dist: httpx>=0.28.1
|
|
@@ -25,7 +25,7 @@ Requires-Dist: langfuse>=3.0.0
|
|
|
25
25
|
Requires-Dist: requests<3,>=2.32
|
|
26
26
|
Requires-Dist: PyYAML>=6.0
|
|
27
27
|
Provides-Extra: app
|
|
28
|
-
Requires-Dist: fastapi>=0.
|
|
28
|
+
Requires-Dist: fastapi>=0.139.0; extra == "app"
|
|
29
29
|
Provides-Extra: dev
|
|
30
30
|
Requires-Dist: bandit>=1.8.6; extra == "dev"
|
|
31
31
|
Requires-Dist: basedpyright==1.31.0; extra == "dev"
|
|
@@ -63,9 +63,13 @@ from fred_core.logs.log_setup import log_setup
|
|
|
63
63
|
from fred_core.logs.memory_log_store import RamLogStore
|
|
64
64
|
from fred_core.security.models import AuthorizationError
|
|
65
65
|
from fred_core.security.oidc import get_keycloak_client_id, get_keycloak_url
|
|
66
|
-
from fred_core.security.rebac.rebac_engine import
|
|
66
|
+
from fred_core.security.rebac.rebac_engine import (
|
|
67
|
+
ORGANIZATION_ID,
|
|
68
|
+
OrganizationPermission,
|
|
69
|
+
TeamPermission,
|
|
70
|
+
)
|
|
67
71
|
from fred_core.security.rebac.rebac_factory import rebac_factory
|
|
68
|
-
from fred_core.security.structure import KeycloakUser
|
|
72
|
+
from fred_core.security.structure import KeycloakUser, is_service_agent
|
|
69
73
|
from fred_sdk.contracts.context import (
|
|
70
74
|
AgentInvocationRequest,
|
|
71
75
|
AgentInvocationResult,
|
|
@@ -110,6 +114,7 @@ from fred_runtime.react.react_runtime import ReActRuntime
|
|
|
110
114
|
from fred_runtime.runtime_support.checkpoints import load_checkpoint
|
|
111
115
|
|
|
112
116
|
from ..common.structures import AgentSettingsLike
|
|
117
|
+
from ..integrations.inprocess_toolkit_registry import build_inprocess_toolkit
|
|
113
118
|
from ..integrations.v2_runtime.adapters import (
|
|
114
119
|
CompositeToolInvoker,
|
|
115
120
|
FredKnowledgeSearchToolInvoker,
|
|
@@ -1183,6 +1188,20 @@ async def _authorize_execution_or_raise(
|
|
|
1183
1188
|
"(runtime_context.team_id)"
|
|
1184
1189
|
),
|
|
1185
1190
|
)
|
|
1191
|
+
if is_service_agent(authenticated_user):
|
|
1192
|
+
# Solution A (RFC EVAL-AUTH): a service identity (the evaluation worker) is
|
|
1193
|
+
# recognized for execution, scoped to the request team_id, without any stored
|
|
1194
|
+
# OpenFGA relation. Legitimacy is anchored upstream at campaign creation. The
|
|
1195
|
+
# team_id is guaranteed non-None above, so this stays team-scoped (never global).
|
|
1196
|
+
_emit_audit_event(
|
|
1197
|
+
container,
|
|
1198
|
+
"info",
|
|
1199
|
+
"service_agent_authorized",
|
|
1200
|
+
user_id=authenticated_user.uid,
|
|
1201
|
+
team_id=team_id,
|
|
1202
|
+
agent_instance_id=request.agent_instance_id,
|
|
1203
|
+
)
|
|
1204
|
+
return
|
|
1186
1205
|
try:
|
|
1187
1206
|
await rebac.check_user_team_permission_or_raise(
|
|
1188
1207
|
authenticated_user, TeamPermission.CAN_READ, team_id
|
|
@@ -1255,6 +1274,31 @@ async def _enforce_session_ownership(
|
|
|
1255
1274
|
)
|
|
1256
1275
|
|
|
1257
1276
|
|
|
1277
|
+
async def _caller_can_manage_platform(caller: KeycloakUser | None) -> bool:
|
|
1278
|
+
"""True when the caller holds org-level ``can_manage_platform`` (CTRLP-12 C1).
|
|
1279
|
+
|
|
1280
|
+
The delete endpoints use this as an **admin branch**: a platform service
|
|
1281
|
+
principal — e.g. the control-plane lifecycle worker erasing a conversation at
|
|
1282
|
+
window expiry — may delete a session it does not personally own. Only the
|
|
1283
|
+
per-user *ownership* check is waived; *authentication* stays enforced upstream
|
|
1284
|
+
by ``_authenticated_user``.
|
|
1285
|
+
|
|
1286
|
+
Fails closed: returns False when there is no caller or when ReBAC is disabled
|
|
1287
|
+
(dev/no-security), so ownership behaviour is unchanged there and the bypass
|
|
1288
|
+
activates only when ReBAC is actually enforcing and the org permission is
|
|
1289
|
+
granted. Reuses the AUTHZ-01 ``can_manage_platform`` permission — no second
|
|
1290
|
+
bypass is forked.
|
|
1291
|
+
"""
|
|
1292
|
+
if caller is None:
|
|
1293
|
+
return False
|
|
1294
|
+
rebac = get_runtime_context().config.rebac_engine
|
|
1295
|
+
if rebac is None or not rebac.enabled:
|
|
1296
|
+
return False
|
|
1297
|
+
return await rebac.has_user_permission(
|
|
1298
|
+
caller, OrganizationPermission.CAN_MANAGE_PLATFORM, ORGANIZATION_ID
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1301
|
+
|
|
1258
1302
|
async def _authorize_and_resolve(
|
|
1259
1303
|
request: RuntimeExecuteRequest,
|
|
1260
1304
|
*,
|
|
@@ -2036,6 +2080,10 @@ async def _iterate_runtime_event_payloads(
|
|
|
2036
2080
|
# at prepare-execution and the frontend forwards it — but it was also
|
|
2037
2081
|
# silently dropped here, so no agent ever received a selected prompt.
|
|
2038
2082
|
context_prompt_text=ctx.get("context_prompt_text"),
|
|
2083
|
+
# Rebuilt by the frontend from the attachments that currently exist.
|
|
2084
|
+
# When the final file is deleted this is absent, so the per-turn runtime
|
|
2085
|
+
# notice disappears without leaving a checkpointed system message behind.
|
|
2086
|
+
attachments_markdown=ctx.get("attachments_markdown"),
|
|
2039
2087
|
)
|
|
2040
2088
|
|
|
2041
2089
|
binding = BoundRuntimeContext(
|
|
@@ -2195,6 +2243,7 @@ def _build_agent_router(
|
|
|
2195
2243
|
async def get_kpi_turns(
|
|
2196
2244
|
limit: int = 50,
|
|
2197
2245
|
container: PodApplicationContext = Depends(get_pod_container),
|
|
2246
|
+
caller: KeycloakUser | None = Depends(_authenticated_user),
|
|
2198
2247
|
) -> list[KpiTurnRecord]:
|
|
2199
2248
|
"""
|
|
2200
2249
|
Return recent agent.turn_completed KPI events, newest first.
|
|
@@ -2210,6 +2259,11 @@ def _build_agent_router(
|
|
|
2210
2259
|
Grafana or Prometheus; this exposes the pod-local ring buffer
|
|
2211
2260
|
- max 200 entries retained in memory (oldest evicted automatically)
|
|
2212
2261
|
"""
|
|
2262
|
+
rebac = get_runtime_context().config.rebac_engine
|
|
2263
|
+
if caller is not None and rebac is not None and rebac.enabled:
|
|
2264
|
+
await rebac.check_user_permission_or_raise(
|
|
2265
|
+
caller, OrganizationPermission.CAN_READ_METRICS, ORGANIZATION_ID
|
|
2266
|
+
)
|
|
2213
2267
|
with container._kpi_turns_lock:
|
|
2214
2268
|
events = list(container.kpi_turns_buffer)
|
|
2215
2269
|
events.reverse()
|
|
@@ -2219,6 +2273,7 @@ def _build_agent_router(
|
|
|
2219
2273
|
async def get_audit_events(
|
|
2220
2274
|
limit: int = 50,
|
|
2221
2275
|
container: PodApplicationContext = Depends(get_pod_container),
|
|
2276
|
+
caller: KeycloakUser | None = Depends(_authenticated_user),
|
|
2222
2277
|
) -> list[AuditEventRecord]:
|
|
2223
2278
|
"""
|
|
2224
2279
|
Return recent security audit events, newest first.
|
|
@@ -2233,6 +2288,11 @@ def _build_agent_router(
|
|
|
2233
2288
|
this ring buffer so the CLI can query them directly
|
|
2234
2289
|
- max 200 entries retained in memory
|
|
2235
2290
|
"""
|
|
2291
|
+
rebac = get_runtime_context().config.rebac_engine
|
|
2292
|
+
if caller is not None and rebac is not None and rebac.enabled:
|
|
2293
|
+
await rebac.check_user_permission_or_raise(
|
|
2294
|
+
caller, OrganizationPermission.CAN_MANAGE_PLATFORM, ORGANIZATION_ID
|
|
2295
|
+
)
|
|
2236
2296
|
with container._audit_events_lock:
|
|
2237
2297
|
events = list(container.audit_events_buffer)
|
|
2238
2298
|
events.reverse()
|
|
@@ -2392,6 +2452,11 @@ def _build_agent_router(
|
|
|
2392
2452
|
detail="No history store configured — session history is unavailable.",
|
|
2393
2453
|
)
|
|
2394
2454
|
caller_uid = caller.uid if caller is not None else None
|
|
2455
|
+
if await _caller_can_manage_platform(caller):
|
|
2456
|
+
# Platform admin (e.g. CTRLP-12 lifecycle erase-at-expiry): delete every
|
|
2457
|
+
# row for the session, not just the caller's — ownership is waived,
|
|
2458
|
+
# authentication stays enforced upstream.
|
|
2459
|
+
caller_uid = None
|
|
2395
2460
|
count = await history_store.delete_session(
|
|
2396
2461
|
session_id=session_id, user_id=caller_uid
|
|
2397
2462
|
)
|
|
@@ -2717,7 +2782,8 @@ def _build_agent_router(
|
|
|
2717
2782
|
caller_uid = caller.uid if caller is not None else None
|
|
2718
2783
|
history_store = _get_history_store_for_owned_access(caller)
|
|
2719
2784
|
if (
|
|
2720
|
-
|
|
2785
|
+
not await _caller_can_manage_platform(caller)
|
|
2786
|
+
and caller_uid is not None
|
|
2721
2787
|
and history_store is not None
|
|
2722
2788
|
and not await history_store.session_belongs_to_user(session_id, caller_uid)
|
|
2723
2789
|
):
|
|
@@ -3073,6 +3139,7 @@ def create_agent_app(
|
|
|
3073
3139
|
checkpointer=checkpointer,
|
|
3074
3140
|
history_store=history_store,
|
|
3075
3141
|
mcp_configuration=config.get_mcp_configuration(),
|
|
3142
|
+
inprocess_toolkit_factory=build_inprocess_toolkit,
|
|
3076
3143
|
control_plane_url=config.platform.control_plane_url,
|
|
3077
3144
|
rebac_engine=rebac_engine,
|
|
3078
3145
|
security_profile=(
|
|
@@ -375,7 +375,7 @@ class MCPRuntime:
|
|
|
375
375
|
)
|
|
376
376
|
return
|
|
377
377
|
for server in self.inprocess_servers:
|
|
378
|
-
toolkit = factory(server.provider)
|
|
378
|
+
toolkit = factory(server.provider, self.agent_instance)
|
|
379
379
|
if toolkit is None:
|
|
380
380
|
logger.warning(
|
|
381
381
|
"[MCP] agent=%s no toolkit built for provider=%s (server=%s)",
|
|
@@ -38,7 +38,7 @@ from langchain_core.tools import BaseTool
|
|
|
38
38
|
from langgraph.types import Checkpointer
|
|
39
39
|
|
|
40
40
|
from fred_runtime.react.react_prompting import (
|
|
41
|
-
|
|
41
|
+
compose_system_prompt as _compose_system_prompt,
|
|
42
42
|
)
|
|
43
43
|
from fred_runtime.react.react_prompting import (
|
|
44
44
|
render_prompt_template as _render_prompt_template,
|
|
@@ -117,11 +117,17 @@ class DeepAgentRuntime(ReActRuntime):
|
|
|
117
117
|
binding=binding,
|
|
118
118
|
agent_id=self.definition.agent_id,
|
|
119
119
|
)
|
|
120
|
-
system_prompt = (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
system_prompt = _compose_system_prompt(
|
|
121
|
+
system_prompt,
|
|
122
|
+
binding=binding,
|
|
123
|
+
definition=self.definition,
|
|
124
|
+
agent_id=self.definition.agent_id,
|
|
125
|
+
tool_suffix=_build_runtime_tool_prompt_suffix(bound_tools),
|
|
126
|
+
runtime_suffixes=(
|
|
127
|
+
_filesystem_prompt_suffix(
|
|
128
|
+
filesystem_tools_enabled=filesystem_tools_enabled
|
|
129
|
+
),
|
|
130
|
+
),
|
|
125
131
|
)
|
|
126
132
|
compiled_agent = _create_compiled_deep_agent(
|
|
127
133
|
model=self._model,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright Thales 2026
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
Registry mapping catalog `provider` keys to in-process MCP toolkits (MIGR-03.03).
|
|
16
|
+
|
|
17
|
+
Why this module exists:
|
|
18
|
+
- an MCP catalog server with `transport: inprocess` names a `provider`; the MCP
|
|
19
|
+
runtime asks this factory to build the matching toolkit for the current agent turn
|
|
20
|
+
- keeping the mapping here (not in `agent_app`) lets the runtime wire a single
|
|
21
|
+
factory reference at startup without hardcoding provider knowledge in the boot path
|
|
22
|
+
|
|
23
|
+
How to use it:
|
|
24
|
+
- pass `build_inprocess_toolkit` as `RuntimeConfig.inprocess_toolkit_factory`
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from fred_runtime.integrations.kf_vector_search import (
|
|
33
|
+
KF_VECTOR_SEARCH_PROVIDER,
|
|
34
|
+
KfVectorSearchToolkit,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build_inprocess_toolkit(provider: str | None, agent: Any) -> Any | None:
|
|
41
|
+
"""
|
|
42
|
+
Build the in-process toolkit for `provider`, bound to the current agent turn.
|
|
43
|
+
|
|
44
|
+
Returns None for an unknown or missing provider so the MCP runtime skips it with
|
|
45
|
+
a warning rather than failing the whole agent.
|
|
46
|
+
"""
|
|
47
|
+
if provider == KF_VECTOR_SEARCH_PROVIDER:
|
|
48
|
+
return KfVectorSearchToolkit(agent=agent)
|
|
49
|
+
logger.warning("[MCP] unknown inprocess provider=%s; no toolkit built.", provider)
|
|
50
|
+
return None
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright Thales 2026
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""In-process Knowledge Flow vector-search MCP toolkit."""
|
|
15
|
+
|
|
16
|
+
from fred_runtime.integrations.kf_vector_search.toolkit import (
|
|
17
|
+
KF_VECTOR_SEARCH_PROVIDER,
|
|
18
|
+
KfVectorSearchToolkit,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = ["KF_VECTOR_SEARCH_PROVIDER", "KfVectorSearchToolkit"]
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Copyright Thales 2026
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
In-process Knowledge Flow vector-search toolkit for v2 ReAct agents (MIGR-03.03).
|
|
16
|
+
|
|
17
|
+
Why this module exists:
|
|
18
|
+
- restores the kea behaviour where the "search_documents" tool, selected from the
|
|
19
|
+
Tools tab as an MCP server, returns typed `VectorSearchHit` sources so the chat
|
|
20
|
+
UI renders the Sources panel (and clickable `[N]` citations)
|
|
21
|
+
- in kea that tool was an *inprocess* provider (`transport: inprocess`,
|
|
22
|
+
`provider: kf_vector_search`) — NOT a real remote MCP endpoint. A remote MCP tool
|
|
23
|
+
returns plain text with no typed artifact, so the panel cannot render. This
|
|
24
|
+
toolkit re-establishes the inprocess path on swift.
|
|
25
|
+
|
|
26
|
+
Key design:
|
|
27
|
+
- exposes one LangChain tool (`search_documents_using_vectorization`) that returns a
|
|
28
|
+
`ToolInvocationResult` directly (NOT a `content_and_artifact` tuple). The shared
|
|
29
|
+
runtime-provider resolver invokes provider tools with a plain args dict, which
|
|
30
|
+
collapses a `content_and_artifact` tuple to its content string and drops the
|
|
31
|
+
artifact — so the sources must ride on a returned `ToolInvocationResult`, which the
|
|
32
|
+
resolver extracts (see `react_tool_resolution._resolve_runtime_provider_tool`).
|
|
33
|
+
`blocks` carry the hit JSON for the LLM; `sources` carry the typed hits the chat
|
|
34
|
+
Sources panel renders. This mirrors the built-in `knowledge.search` tool ref.
|
|
35
|
+
- retrieval scoping (libraries, document uids, search policy, session/corpus scope,
|
|
36
|
+
team) is read from the bound runtime context at call time — identical to the
|
|
37
|
+
built-in `knowledge.search` tool ref used by Rico, so both paths behave the same
|
|
38
|
+
|
|
39
|
+
How to use it:
|
|
40
|
+
- built by `build_inprocess_toolkit` when a catalog server declares
|
|
41
|
+
`provider: "kf_vector_search"`; the MCP runtime calls `.tools()` to collect the
|
|
42
|
+
LangChain tools
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import logging
|
|
48
|
+
from typing import Sequence
|
|
49
|
+
|
|
50
|
+
from fred_core.common import OwnerFilter
|
|
51
|
+
from fred_core.common.team_id import is_personal_team_id
|
|
52
|
+
from fred_core.store.vector_search import VectorSearchHit
|
|
53
|
+
from fred_sdk.contracts.context import (
|
|
54
|
+
ToolContentBlock,
|
|
55
|
+
ToolContentKind,
|
|
56
|
+
ToolInvocationResult,
|
|
57
|
+
)
|
|
58
|
+
from langchain_core.tools import BaseTool, tool
|
|
59
|
+
|
|
60
|
+
from fred_runtime.common.kf_base_client import KnowledgeFlowAgentContext
|
|
61
|
+
from fred_runtime.common.kf_vectorsearch_client import VectorSearchClient
|
|
62
|
+
from fred_runtime.runtime_support.request_context_helpers import (
|
|
63
|
+
get_document_library_tags_ids,
|
|
64
|
+
get_document_uids,
|
|
65
|
+
get_rag_knowledge_scope,
|
|
66
|
+
get_search_policy,
|
|
67
|
+
get_vector_search_scopes,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
logger = logging.getLogger(__name__)
|
|
71
|
+
|
|
72
|
+
KF_VECTOR_SEARCH_PROVIDER = "kf_vector_search"
|
|
73
|
+
|
|
74
|
+
# Only the fields the LLM needs for citation and reasoning are exposed. URL and
|
|
75
|
+
# operational fields are excluded so the model cannot reproduce broken or internal
|
|
76
|
+
# paths in its reply — this mirrors `_invoke_knowledge_search` (the tool-ref path).
|
|
77
|
+
_LLM_FIELDS = {"uid", "title", "content", "file_name", "page", "section", "score"}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _llm_slice(hit: VectorSearchHit) -> dict[str, object]:
|
|
81
|
+
return {k: v for k, v in hit.model_dump(mode="json").items() if k in _LLM_FIELDS}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class KfVectorSearchToolkit:
|
|
85
|
+
"""
|
|
86
|
+
Inprocess toolkit that binds a Knowledge Flow vector search to one agent turn.
|
|
87
|
+
|
|
88
|
+
Why this class exists:
|
|
89
|
+
- the MCP runtime resolves inprocess providers to a toolkit object and calls its
|
|
90
|
+
`tools()` method to collect LangChain tools
|
|
91
|
+
- the toolkit is built per request from the agent shim (which carries the bound
|
|
92
|
+
runtime context and agent settings), so each tool call reads the current
|
|
93
|
+
session scope
|
|
94
|
+
|
|
95
|
+
How to use it:
|
|
96
|
+
- `KfVectorSearchToolkit(agent=shim).tools()`
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, *, agent: KnowledgeFlowAgentContext) -> None:
|
|
100
|
+
self._agent = agent
|
|
101
|
+
self._client = VectorSearchClient(agent=agent)
|
|
102
|
+
|
|
103
|
+
def tools(self) -> list[BaseTool]:
|
|
104
|
+
agent = self._agent
|
|
105
|
+
client = self._client
|
|
106
|
+
|
|
107
|
+
@tool("search_documents_using_vectorization")
|
|
108
|
+
async def search_documents_using_vectorization(
|
|
109
|
+
question: str,
|
|
110
|
+
top_k: int = 8,
|
|
111
|
+
) -> ToolInvocationResult:
|
|
112
|
+
"""Search the selected document libraries using semantic similarity (RAG).
|
|
113
|
+
|
|
114
|
+
Call this tool BEFORE answering any factual, technical, or domain-specific
|
|
115
|
+
question — the corpus may hold more specific or recent information than you
|
|
116
|
+
already know. Skip it only for purely conversational exchanges (greetings,
|
|
117
|
+
thanks, clarifying what was just said).
|
|
118
|
+
|
|
119
|
+
Returns ranked hits with title and content. Cite each grounded claim with
|
|
120
|
+
the bracketed rank of the hit it relies on: [1], [2]. Only use information
|
|
121
|
+
actually present in the returned hits; never invent facts beyond them.
|
|
122
|
+
"""
|
|
123
|
+
runtime_context = agent.runtime_context
|
|
124
|
+
|
|
125
|
+
if get_rag_knowledge_scope(runtime_context) == "general_only":
|
|
126
|
+
return ToolInvocationResult(
|
|
127
|
+
tool_ref=KF_VECTOR_SEARCH_PROVIDER,
|
|
128
|
+
blocks=(
|
|
129
|
+
ToolContentBlock(
|
|
130
|
+
kind=ToolContentKind.JSON,
|
|
131
|
+
data={
|
|
132
|
+
"sources": [],
|
|
133
|
+
"note": "Corpus retrieval skipped in general-only mode.",
|
|
134
|
+
},
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
sources=(),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
top_k = top_k if isinstance(top_k, int) and top_k > 0 else 8
|
|
141
|
+
team_id = agent.agent_settings.team_id
|
|
142
|
+
scoped_team = bool(team_id) and not is_personal_team_id(team_id)
|
|
143
|
+
include_session_scope, include_corpus_scope = get_vector_search_scopes(
|
|
144
|
+
runtime_context
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
hits: Sequence[VectorSearchHit] = await client.search(
|
|
148
|
+
question=question,
|
|
149
|
+
top_k=top_k,
|
|
150
|
+
document_library_tags_ids=get_document_library_tags_ids(
|
|
151
|
+
runtime_context
|
|
152
|
+
),
|
|
153
|
+
document_uids=get_document_uids(runtime_context),
|
|
154
|
+
search_policy=get_search_policy(runtime_context),
|
|
155
|
+
owner_filter=OwnerFilter.TEAM if scoped_team else OwnerFilter.PERSONAL,
|
|
156
|
+
team_id=team_id if scoped_team else None,
|
|
157
|
+
session_id=runtime_context.session_id,
|
|
158
|
+
include_session_scope=include_session_scope,
|
|
159
|
+
include_corpus_scope=include_corpus_scope,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
logger.info(
|
|
163
|
+
"[KFVS][TOOL] question=%r top_k=%d hits=%d",
|
|
164
|
+
question[:80],
|
|
165
|
+
top_k,
|
|
166
|
+
len(hits),
|
|
167
|
+
)
|
|
168
|
+
# Return a ToolInvocationResult directly: the runtime-provider resolver
|
|
169
|
+
# invokes this tool with a plain args dict, so a content_and_artifact
|
|
170
|
+
# tuple would lose the artifact (and its sources). `blocks` feed the LLM
|
|
171
|
+
# the hit JSON; `sources` carry the typed hits the Sources panel renders.
|
|
172
|
+
return ToolInvocationResult(
|
|
173
|
+
tool_ref=KF_VECTOR_SEARCH_PROVIDER,
|
|
174
|
+
blocks=(
|
|
175
|
+
ToolContentBlock(
|
|
176
|
+
kind=ToolContentKind.JSON,
|
|
177
|
+
data={
|
|
178
|
+
"query": question,
|
|
179
|
+
"hits": [_llm_slice(h) for h in hits],
|
|
180
|
+
},
|
|
181
|
+
),
|
|
182
|
+
),
|
|
183
|
+
sources=tuple(hits),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return [search_documents_using_vectorization]
|