fred-runtime 3.4.0__tar.gz → 3.4.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.4.0 → fred_runtime-3.4.1}/PKG-INFO +1 -1
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/agent_app.py +203 -16
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/context.py +54 -16
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/openai_compat_router.py +5 -2
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/assembly.py +3 -1
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/document_access/capability.py +22 -158
- fred_runtime-3.4.1/fred_runtime/capabilities/document_summarize/__init__.py +38 -0
- fred_runtime-3.4.1/fred_runtime/capabilities/document_summarize/capability.py +325 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/deep/deep_runtime.py +1 -2
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/model_routing/contracts.py +67 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/model_routing/provider.py +35 -4
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/checkpoint_hygiene.py +15 -11
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/tool_observability.py +5 -4
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_runtime.py +1 -8
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_tool_binding.py +30 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/support/thinking.py +72 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime.egg-info/PKG-INFO +1 -1
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime.egg-info/SOURCES.txt +5 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime.egg-info/entry_points.txt +1 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/pyproject.toml +4 -1
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_agent_app.py +267 -13
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_document_access_1906.py +9 -175
- fred_runtime-3.4.1/tests/test_capability_document_summarize.py +324 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_endpoints_1974.py +14 -4
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_context.py +162 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_deep_agent_middleware.py +0 -8
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_history.py +219 -0
- fred_runtime-3.4.1/tests/test_model_reasoning_enablement.py +328 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_models_catalog_projection.py +60 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_react_middleware_frame.py +42 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_react_prompting.py +45 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_react_thinking.py +124 -0
- fred_runtime-3.4.1/tests/test_tool_carrier_middleware_naming.py +139 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/README.md +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/__main__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/_catalogs.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/config.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/config_loader.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/container.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/dependencies.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/mcp_config.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/app/observability_factory.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/assets.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/demo.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/demo_migrations/env.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/demo_migrations/versions/d1e2m0a0b0c0_create_demo_echo_notes.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/document_access/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/errors.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/mcp.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/openapi_dump.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/capabilities/registry.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/completion.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/entrypoint.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/history_display.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/kpi_display.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/pod_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/repl.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/repl_helpers.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/cli/url_helpers.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/context_aware_tool.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/kf_base_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/kf_document_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/kf_fast_text_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/kf_http_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/kf_markdown_media_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/kf_tag_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/kf_workspace_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/mcp_interceptors.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/mcp_runtime.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/mcp_toolkit.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/mcp_utils.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/structures.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/token_expiry.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/common/tool_node_utils.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/deep/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/eval/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/eval/collector.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/graph/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/graph/graph_runtime.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/integrations/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/integrations/inprocess_toolkit_registry.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/integrations/kf_vector_search/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/integrations/kf_vector_search/toolkit.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/integrations/v2_runtime/adapters.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/migrations.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/model_routing/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/model_routing/authz.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/model_routing/catalog.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/model_routing/resolver.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/dynamic_prompt.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/frame.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/hitl.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/model_routing.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/shared.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/middleware/tracing_kpi.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_langchain_adapter.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_message_codec.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_model_adapter.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_prompting.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_stream_adapter.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_tool_loop.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_tool_rendering.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_tool_resolution.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_tool_utils.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/react/react_tracing.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/runtime_context.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/runtime_support/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/runtime_support/checkpoints.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/runtime_support/model_metadata.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/runtime_support/sql_checkpointer.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/support/__init__.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/support/filesystem_context.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime/support/tool_loop.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime.egg-info/dependency_links.txt +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime.egg-info/requires.txt +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/fred_runtime.egg-info/top_level.txt +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/setup.cfg +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_agent_1973.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_chat_controls_1976.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_chat_parts_1977.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_registry_1973.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_routes_tables_1979.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_selection_1974.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_capability_tool_return_convention.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_config_loader.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_context_aware_tool.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_conversational_memory.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_eval_collector.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_eval_trace.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_fred_workspace_fs.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_graph_capability_bridge.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_graph_runtime_invoke_agent.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_graph_runtime_observability.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_kf_vector_search_toolkit.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_kf_workspace_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_knowledge_search_tool_invoker_sources.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_kpi_display.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_mcp_config.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_model_enforcement.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_model_routing.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_openai_compat_router.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_pod_client.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_react_loop_regressions_1972.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_repl_helpers.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_runtime_context_prompt_injection.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_smoke.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_spike_capability_state_1971.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_sql_checkpointer_owner.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_token_expiry.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_tool_loop_trim.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_tool_observability_middleware.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_tracing_kpi_content_confinement.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_url_helpers.py +0 -0
- {fred_runtime-3.4.0 → fred_runtime-3.4.1}/tests/test_user_token_refresher.py +0 -0
|
@@ -56,6 +56,7 @@ from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status
|
|
|
56
56
|
from fastapi.middleware.cors import CORSMiddleware
|
|
57
57
|
from fastapi.responses import StreamingResponse
|
|
58
58
|
from fred_core.common.config_loader import get_config
|
|
59
|
+
from fred_core.diagnostics import install_gc_diagnostics
|
|
59
60
|
from fred_core.history.history_schema import ChatMessage
|
|
60
61
|
from fred_core.kpi import KPIMiddleware
|
|
61
62
|
from fred_core.kpi.kpi_runtime_stage_metric import runtime_stage_timer
|
|
@@ -793,6 +794,7 @@ def _build_runtime_services(
|
|
|
793
794
|
return RuntimeServices(
|
|
794
795
|
tracer=build_default_tracer(),
|
|
795
796
|
metrics=KPIWriterMetricsAdapter(runtime_config.kpi_writer),
|
|
797
|
+
kpi_writer=runtime_config.kpi_writer,
|
|
796
798
|
chat_model_factory=runtime_config.chat_model_factory,
|
|
797
799
|
tool_invoker=tool_invoker,
|
|
798
800
|
tool_provider=tool_provider,
|
|
@@ -983,6 +985,19 @@ class _ModelCatalogEntry(BaseModel):
|
|
|
983
985
|
team-settings picker needs the reverse (which profile_ids this enabled
|
|
984
986
|
capability actually offers). Declaration order in the source YAML,
|
|
985
987
|
first-seen first — same order `default_profile_by_capability` favors."""
|
|
988
|
+
thinking_profile_ids: list[str] = Field(default_factory=list)
|
|
989
|
+
"""The subset of `profile_ids` above declaring `supports_thinking`
|
|
990
|
+
(`MODEL-REASONING-ENABLEMENT-RFC.md` §5.3, REASON-01). DERIVED here,
|
|
991
|
+
never authored twice: aptitude is declared per profile (§4, where the
|
|
992
|
+
behaviour actually differs), while the admin toggle is keyed per model
|
|
993
|
+
because that is what the capability id space and the admin UI both are
|
|
994
|
+
(§5.2 — the id space is not negotiable). This field is the projection
|
|
995
|
+
between the two.
|
|
996
|
+
|
|
997
|
+
Empty means this model has NO reasoning-capable profile, and the admin row
|
|
998
|
+
shows no reasoning control at all — aptitude is not a choice, an
|
|
999
|
+
administrator cannot make a model reason. Non-empty means the row carries
|
|
1000
|
+
the toggle. Same established join as `profile_ids` above."""
|
|
986
1001
|
|
|
987
1002
|
|
|
988
1003
|
class _ModelCatalogResponse(BaseModel):
|
|
@@ -1017,15 +1032,20 @@ def _project_model_catalog_entries(catalog: Any) -> list[_ModelCatalogEntry]:
|
|
|
1017
1032
|
key = (provider, name)
|
|
1018
1033
|
existing = seen.get(key)
|
|
1019
1034
|
if existing is None:
|
|
1020
|
-
|
|
1035
|
+
existing = _ModelCatalogEntry(
|
|
1021
1036
|
id=model_capability_id(provider, name),
|
|
1022
1037
|
provider=provider,
|
|
1023
1038
|
name=name,
|
|
1024
1039
|
description=profile.description,
|
|
1025
1040
|
profile_ids=[profile.profile_id],
|
|
1026
1041
|
)
|
|
1042
|
+
seen[key] = existing
|
|
1027
1043
|
else:
|
|
1028
1044
|
existing.profile_ids.append(profile.profile_id)
|
|
1045
|
+
# REASON-01 §5.3: aptitude is per profile, the toggle is per model —
|
|
1046
|
+
# this one condition is the whole projection between them.
|
|
1047
|
+
if profile.supports_thinking:
|
|
1048
|
+
existing.thinking_profile_ids.append(profile.profile_id)
|
|
1029
1049
|
return list(seen.values())
|
|
1030
1050
|
|
|
1031
1051
|
|
|
@@ -1358,12 +1378,21 @@ async def _authorize_execution_or_raise(
|
|
|
1358
1378
|
the owner's `team_editor` tuple self-heals on first touch inside
|
|
1359
1379
|
`RebacEngine.check_user_team_permission_or_raise`, and `add_relation`'s
|
|
1360
1380
|
write-guard refuses any tuple naming a personal team except that one, so
|
|
1361
|
-
the
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
team
|
|
1381
|
+
the `CAN_USE_TEAM_AGENTS` check below already authorizes the owner
|
|
1382
|
+
(`team_editor` implies `team_member`) and denies everyone else (another
|
|
1383
|
+
user's space, or the bare `"personal"` alias, for which no tuple is
|
|
1384
|
+
ever provisioned) — no special-casing needed here.
|
|
1385
|
+
- otherwise require the caller to hold `CAN_USE_TEAM_AGENTS` on the
|
|
1386
|
+
requested team — `team_member`-only, unlike `CAN_READ` (which also
|
|
1387
|
+
admits the `public` marketplace-discovery relation, TEAM-09/TEAM-10).
|
|
1388
|
+
Execution is real content access, not discovery, so a public-team
|
|
1389
|
+
visitor must not pass here even though they can see the team profile.
|
|
1390
|
+
This used to check `CAN_READ` (the same relation the control-plane
|
|
1391
|
+
required before it would mint a grant) — that predates TEAM-09's
|
|
1392
|
+
`public` relation and was never revisited; `product/api.py`'s
|
|
1393
|
+
`post_prepare_execution` made the identical correction for the same
|
|
1394
|
+
reason (returns prompt content, not just config). The team is
|
|
1395
|
+
caller-supplied but safe: OpenFGA only authorizes teams the user
|
|
1367
1396
|
actually holds a relation to. Authorization and denial are both audited;
|
|
1368
1397
|
any OpenFGA denial fails closed (403).
|
|
1369
1398
|
|
|
@@ -1446,7 +1475,7 @@ async def _authorize_execution_or_raise(
|
|
|
1446
1475
|
# since no tuple is ever provisioned for that literal string).
|
|
1447
1476
|
try:
|
|
1448
1477
|
await rebac.check_user_team_permission_or_raise(
|
|
1449
|
-
authenticated_user, TeamPermission.
|
|
1478
|
+
authenticated_user, TeamPermission.CAN_USE_TEAM_AGENTS, team_id
|
|
1450
1479
|
)
|
|
1451
1480
|
except AuthorizationError as exc:
|
|
1452
1481
|
_emit_audit_event(
|
|
@@ -1730,6 +1759,27 @@ async def _validate_session_checkpoint_access(
|
|
|
1730
1759
|
)
|
|
1731
1760
|
|
|
1732
1761
|
|
|
1762
|
+
@dataclass
|
|
1763
|
+
class _OpenThought:
|
|
1764
|
+
"""
|
|
1765
|
+
One reasoning block being accumulated while mapping a turn to history rows.
|
|
1766
|
+
|
|
1767
|
+
Why the rank is reserved at THOUGHT_START rather than at THOUGHT_END:
|
|
1768
|
+
- a model-native block opens on the first reasoning token and closes only at
|
|
1769
|
+
the first answer delta (RFC AGENT-THINKING-API §7.3), so it brackets every
|
|
1770
|
+
tool call of the turn
|
|
1771
|
+
- the live UI assigns the row's rank at `thought_start` (`useChatSse.ts`);
|
|
1772
|
+
ranking it at close instead would file the reasoning *after* the tools it
|
|
1773
|
+
actually preceded, so a reloaded trace would not match what the user saw
|
|
1774
|
+
"""
|
|
1775
|
+
|
|
1776
|
+
rank: int
|
|
1777
|
+
phase: str
|
|
1778
|
+
title: str | None
|
|
1779
|
+
source: str | None
|
|
1780
|
+
text: list[str] = field(default_factory=list)
|
|
1781
|
+
|
|
1782
|
+
|
|
1733
1783
|
async def _write_turn_history(
|
|
1734
1784
|
*,
|
|
1735
1785
|
session_id: str,
|
|
@@ -1765,6 +1815,7 @@ async def _write_turn_history(
|
|
|
1765
1815
|
Event-to-message mapping:
|
|
1766
1816
|
- ``tool_call`` → ``Role.assistant / Channel.tool_call``
|
|
1767
1817
|
- ``tool_result`` → ``Role.tool / Channel.tool_result``
|
|
1818
|
+
- ``thought_*`` → ``Role.assistant / Channel.thought`` (one row per block)
|
|
1768
1819
|
- ``awaiting_human`` → ``Role.system / Channel.hitl_request`` (full choices)
|
|
1769
1820
|
- ``node_error`` → ``Role.system / Channel.error``
|
|
1770
1821
|
- ``final`` → ``Role.assistant / Channel.final`` (answer + sources)
|
|
@@ -1774,6 +1825,7 @@ async def _write_turn_history(
|
|
|
1774
1825
|
from fred_core.history.history_schema import (
|
|
1775
1826
|
Channel,
|
|
1776
1827
|
ChatMessage,
|
|
1828
|
+
ChatMetadata,
|
|
1777
1829
|
ChatTokenUsage,
|
|
1778
1830
|
Role,
|
|
1779
1831
|
TextPart,
|
|
@@ -1817,6 +1869,55 @@ async def _write_turn_history(
|
|
|
1817
1869
|
messages.append(make_user_text(session_id, exchange_id, rank, request_message))
|
|
1818
1870
|
rank += 1
|
|
1819
1871
|
|
|
1872
|
+
# Reasoning blocks still accumulating, keyed by thought_id. A block reserves
|
|
1873
|
+
# its rank when it opens but only becomes a row once its text is complete.
|
|
1874
|
+
open_thoughts: dict[str, _OpenThought] = {}
|
|
1875
|
+
|
|
1876
|
+
def _thought_row(
|
|
1877
|
+
block: _OpenThought,
|
|
1878
|
+
*,
|
|
1879
|
+
thought_id: str,
|
|
1880
|
+
conclusion: str | None = None,
|
|
1881
|
+
duration_ms: int | None = None,
|
|
1882
|
+
) -> ChatMessage:
|
|
1883
|
+
"""
|
|
1884
|
+
Build one ``Channel.thought`` row from an accumulated reasoning block.
|
|
1885
|
+
|
|
1886
|
+
The envelope must match what the live stream emits client-side
|
|
1887
|
+
(`useChatSse.ts`) — same role, same channel, same ``metadata.extras``
|
|
1888
|
+
keys — because the chat UI feeds streamed and reloaded messages through
|
|
1889
|
+
a single rendering path (`traceUtils.thoughtExtras`).
|
|
1890
|
+
|
|
1891
|
+
``streaming_delta`` is deliberately absent: a persisted block is
|
|
1892
|
+
complete, and carrying that flag would render it as still running.
|
|
1893
|
+
"""
|
|
1894
|
+
return ChatMessage(
|
|
1895
|
+
session_id=session_id,
|
|
1896
|
+
exchange_id=exchange_id,
|
|
1897
|
+
rank=block.rank,
|
|
1898
|
+
timestamp=datetime.now(timezone.utc),
|
|
1899
|
+
role=Role.assistant,
|
|
1900
|
+
channel=Channel.thought,
|
|
1901
|
+
parts=[TextPart(text="".join(block.text))],
|
|
1902
|
+
# `extras` is not a declared field: `ChatMetadata` sets
|
|
1903
|
+
# `extra="allow"` precisely so callers can attach structured payloads
|
|
1904
|
+
# without every consumer of the model growing a dependency on them.
|
|
1905
|
+
# `model_validate` is how that is reached type-safely — a keyword
|
|
1906
|
+
# argument would not type-check against the declared fields.
|
|
1907
|
+
metadata=ChatMetadata.model_validate(
|
|
1908
|
+
{
|
|
1909
|
+
"extras": {
|
|
1910
|
+
"thought_id": thought_id,
|
|
1911
|
+
"phase": block.phase,
|
|
1912
|
+
"title": block.title,
|
|
1913
|
+
"source": block.source,
|
|
1914
|
+
"conclusion": conclusion,
|
|
1915
|
+
"duration_ms": duration_ms,
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
),
|
|
1919
|
+
)
|
|
1920
|
+
|
|
1820
1921
|
# 2. Map runtime events to messages
|
|
1821
1922
|
final_content = ""
|
|
1822
1923
|
final_sources: list[VectorSearchHit] = []
|
|
@@ -1854,6 +1955,48 @@ async def _write_turn_history(
|
|
|
1854
1955
|
)
|
|
1855
1956
|
rank += 1
|
|
1856
1957
|
|
|
1958
|
+
elif kind == "thought_start":
|
|
1959
|
+
# `tool_use` blocks are synthetic bookkeeping the runtime opens around
|
|
1960
|
+
# every tool call (RFC Amendment A, Layer 1). The chat UI has hidden
|
|
1961
|
+
# them since Amendment B — the tool call/result combo row already
|
|
1962
|
+
# carries what ran, how it went and how long it took. Persisting them
|
|
1963
|
+
# would file up to `max_tool_calls_per_turn` content-free rows a turn.
|
|
1964
|
+
if payload.get("phase") == "tool_use":
|
|
1965
|
+
continue
|
|
1966
|
+
thought_id = str(payload.get("thought_id") or "")
|
|
1967
|
+
if not thought_id:
|
|
1968
|
+
continue
|
|
1969
|
+
open_thoughts[thought_id] = _OpenThought(
|
|
1970
|
+
rank=rank,
|
|
1971
|
+
phase=str(payload.get("phase") or "planning"),
|
|
1972
|
+
title=payload.get("title"),
|
|
1973
|
+
source=payload.get("source"),
|
|
1974
|
+
)
|
|
1975
|
+
rank += 1
|
|
1976
|
+
|
|
1977
|
+
elif kind == "thought_delta":
|
|
1978
|
+
block = open_thoughts.get(str(payload.get("thought_id") or ""))
|
|
1979
|
+
if block is not None:
|
|
1980
|
+
block.text.append(str(payload.get("delta") or ""))
|
|
1981
|
+
|
|
1982
|
+
elif kind == "thought_end":
|
|
1983
|
+
thought_id = str(payload.get("thought_id") or "")
|
|
1984
|
+
block = open_thoughts.pop(thought_id, None)
|
|
1985
|
+
# A block with neither text nor conclusion carries nothing to show;
|
|
1986
|
+
# persisting it would render an empty reasoning card on reload. Its
|
|
1987
|
+
# reserved rank is simply left unused — ranks must increase, not be
|
|
1988
|
+
# contiguous.
|
|
1989
|
+
conclusion = payload.get("conclusion")
|
|
1990
|
+
if block is not None and ("".join(block.text) or conclusion):
|
|
1991
|
+
messages.append(
|
|
1992
|
+
_thought_row(
|
|
1993
|
+
block,
|
|
1994
|
+
thought_id=thought_id,
|
|
1995
|
+
conclusion=conclusion,
|
|
1996
|
+
duration_ms=payload.get("duration_ms"),
|
|
1997
|
+
)
|
|
1998
|
+
)
|
|
1999
|
+
|
|
1857
2000
|
elif kind == "awaiting_human":
|
|
1858
2001
|
# Store the full HITL gate definition — question and all choices —
|
|
1859
2002
|
# so audit logs and UI replay have the complete structured record.
|
|
@@ -1912,6 +2055,14 @@ async def _write_turn_history(
|
|
|
1912
2055
|
final_model = payload.get("model_name")
|
|
1913
2056
|
final_finish_reason = payload.get("finish_reason")
|
|
1914
2057
|
|
|
2058
|
+
# 2b. Blocks the stream never closed (truncated turn, crashed node). The live
|
|
2059
|
+
# UI closes them itself on `final` so the accordion does not pulse forever
|
|
2060
|
+
# (`useChatSse.ts`); history keeps the same parity — reasoning the user was
|
|
2061
|
+
# shown must not disappear on reload just because the block was left open.
|
|
2062
|
+
for thought_id, block in open_thoughts.items():
|
|
2063
|
+
if "".join(block.text):
|
|
2064
|
+
messages.append(_thought_row(block, thought_id=thought_id))
|
|
2065
|
+
|
|
1915
2066
|
# 3. Terminal assistant message (from FinalRuntimeEvent)
|
|
1916
2067
|
if final_content or final_model:
|
|
1917
2068
|
messages.append(
|
|
@@ -2646,6 +2797,35 @@ async def _iterate_runtime_event_payloads(
|
|
|
2646
2797
|
for rule in (ctx.get("operation_route_rules") or [])
|
|
2647
2798
|
]
|
|
2648
2799
|
or None,
|
|
2800
|
+
# Platform reasoning activation snapshot (REASON-01,
|
|
2801
|
+
# `MODEL-REASONING-ENABLEMENT-RFC.md` §5.5) — same channel and same
|
|
2802
|
+
# session-prep lifecycle as the two fields above, and forwarded here
|
|
2803
|
+
# for the same hard-won reason: this mapping is field-by-field, so a
|
|
2804
|
+
# field that is not named is silently dropped. Both comments above
|
|
2805
|
+
# record a production bug caused by exactly that. `RoutedChatModelFactory`
|
|
2806
|
+
# strips the reasoning settings for every model absent from this list
|
|
2807
|
+
# (§5.6.2), so dropping it here would pin reasoning permanently OFF and
|
|
2808
|
+
# make the admin toggle decorative — the failure §5.6.2 exists to
|
|
2809
|
+
# prevent, one layer up from where it is enforced.
|
|
2810
|
+
#
|
|
2811
|
+
# Intersected with level 3 (the agent's own switch) rather than taken
|
|
2812
|
+
# as-is, and that is the whole point of doing it HERE: this list rides
|
|
2813
|
+
# the request, `tuning` is resolved server-side from the managed
|
|
2814
|
+
# instance. An agent whose author did not enable reasoning must not
|
|
2815
|
+
# reason whatever the request claims the platform allows — all four
|
|
2816
|
+
# levels must hold (§3), so the ceiling is `level 2 AND level 3`.
|
|
2817
|
+
# Absent tuning (agent-to-agent invocation, no managed instance) means
|
|
2818
|
+
# no author ever enabled it: off, the default everywhere in REASON-01.
|
|
2819
|
+
reasoning_enabled_model_ids=(
|
|
2820
|
+
ctx.get("reasoning_enabled_model_ids")
|
|
2821
|
+
if tuning is not None and tuning.reasoning_enabled
|
|
2822
|
+
else []
|
|
2823
|
+
),
|
|
2824
|
+
# The user's per-question reasoning choice (REASON-01 level 4). Same
|
|
2825
|
+
# trap as every field above: unnamed here means silently dropped. Kept
|
|
2826
|
+
# tri-state on purpose — `ctx.get` yielding None means "the agent never
|
|
2827
|
+
# offered the choice", which is NOT the same as the user answering no.
|
|
2828
|
+
reasoning=ctx.get("reasoning"),
|
|
2649
2829
|
)
|
|
2650
2830
|
|
|
2651
2831
|
binding = BoundRuntimeContext(
|
|
@@ -4027,14 +4207,19 @@ def create_agent_app(
|
|
|
4027
4207
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
4028
4208
|
# Boot order (must be preserved — each step depends on the previous):
|
|
4029
4209
|
# 1. log_setup — formatter/handlers ready for all subsequent logs
|
|
4030
|
-
# 2.
|
|
4031
|
-
#
|
|
4032
|
-
#
|
|
4033
|
-
#
|
|
4034
|
-
#
|
|
4035
|
-
#
|
|
4036
|
-
#
|
|
4037
|
-
#
|
|
4210
|
+
# 2. install_gc_diagnostics — independent of everything below; installed
|
|
4211
|
+
# early so SIGUSR1/SIGUSR2 (fred_core.diagnostics) are live for the
|
|
4212
|
+
# pod's whole lifetime, protecting every agent pod (ours and third
|
|
4213
|
+
# parties' alike) against the reference-cycle class of leak found in
|
|
4214
|
+
# ISSUE-010 without any per-pod code
|
|
4215
|
+
# 3. initialize_kpi_writer — needed by bootstrap_observability
|
|
4216
|
+
# 4. initialize_control_plane_client — sync, no network until first call
|
|
4217
|
+
# 5. bootstrap_observability — global tracer + metrics provider
|
|
4218
|
+
# 6. attach_pod_container — container in app.state before any request
|
|
4219
|
+
# 7. initialize_sql — async, may take time
|
|
4220
|
+
# 8. start_metrics_exporter — prometheus thread, after KPI writer exists
|
|
4221
|
+
# 9. start_kpi_tasks — asyncio tasks, after SQL engine is known
|
|
4222
|
+
# 10. set_runtime_context — wires all built parts into the global config
|
|
4038
4223
|
log_setup(
|
|
4039
4224
|
service_name=config.app.name,
|
|
4040
4225
|
log_level=config.app.log_level,
|
|
@@ -4043,6 +4228,7 @@ def create_agent_app(
|
|
|
4043
4228
|
opensearch_config=config.storage.opensearch,
|
|
4044
4229
|
),
|
|
4045
4230
|
)
|
|
4231
|
+
gc_diagnostics = install_gc_diagnostics()
|
|
4046
4232
|
container = build_pod_container(config)
|
|
4047
4233
|
container.initialize_kpi_writer()
|
|
4048
4234
|
container.initialize_control_plane_client()
|
|
@@ -4111,6 +4297,7 @@ def create_agent_app(
|
|
|
4111
4297
|
list(registry.keys()),
|
|
4112
4298
|
)
|
|
4113
4299
|
yield
|
|
4300
|
+
await gc_diagnostics.stop()
|
|
4114
4301
|
await container.shutdown()
|
|
4115
4302
|
|
|
4116
4303
|
app = FastAPI(
|
|
@@ -47,6 +47,11 @@ _CONTROL_PLANE_CLIENT_LIMITS = httpx.Limits(
|
|
|
47
47
|
max_connections=50, max_keepalive_connections=20, keepalive_expiry=10.0
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
# Bounds the one-time connectivity check in initialize_sql() — asyncpg's own
|
|
51
|
+
# connect timeout defaults to 60s, which would otherwise let a single boot
|
|
52
|
+
# attempt hang well past what a readiness-driven operator expects.
|
|
53
|
+
_SQL_BOOT_PING_TIMEOUT_S = 5.0
|
|
54
|
+
|
|
50
55
|
|
|
51
56
|
# ---------------------------------------------------------------------------
|
|
52
57
|
# TypedDicts for ring buffer entries
|
|
@@ -141,31 +146,64 @@ class PodApplicationContext:
|
|
|
141
146
|
)
|
|
142
147
|
|
|
143
148
|
async def initialize_sql(self) -> None:
|
|
144
|
-
"""Build SQL engine, checkpointer, and history store
|
|
149
|
+
"""Build SQL engine, checkpointer, and history store.
|
|
150
|
+
|
|
151
|
+
Durable storage is mandatory for every fred-runtime pod — dev uses a
|
|
152
|
+
local SQLite file (`sqlite_path`), production uses Postgres, but both
|
|
153
|
+
are "durable", never "no storage". Any failure here (bad config,
|
|
154
|
+
unreachable/misconfigured Postgres) must abort pod startup so the
|
|
155
|
+
FastAPI lifespan never completes and Kubernetes never routes traffic
|
|
156
|
+
to this replica — see RUNTIME-EXECUTION-CONTRACT.md §8 (dated entry)
|
|
157
|
+
for the incident this closes.
|
|
158
|
+
"""
|
|
145
159
|
from fred_core.history.postgres_history_store import PostgresHistoryStore
|
|
146
160
|
from fred_core.sql.base_sql import create_async_engine_from_config
|
|
147
161
|
from fred_core.users.store.postgres_user_store import init_user_store
|
|
162
|
+
from sqlalchemy import text
|
|
148
163
|
|
|
149
164
|
from fred_runtime.runtime_support.sql_checkpointer import FredSqlCheckpointer
|
|
150
165
|
|
|
166
|
+
postgres_config = self.configuration.storage.postgres
|
|
167
|
+
engine = create_async_engine_from_config(postgres_config)
|
|
168
|
+
|
|
169
|
+
async def _ping() -> None:
|
|
170
|
+
# SQLAlchemy async engines are lazy — nothing above opened a real
|
|
171
|
+
# connection. Without this, an unreachable/misconfigured Postgres
|
|
172
|
+
# would pass initialize_sql() silently and only fail mid-turn.
|
|
173
|
+
async with engine.connect() as conn:
|
|
174
|
+
await conn.execute(text("SELECT 1"))
|
|
175
|
+
|
|
151
176
|
try:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
history_store = PostgresHistoryStore(engine, kpi=self.get_kpi_writer())
|
|
158
|
-
self._sql_engine = engine
|
|
159
|
-
self._checkpointer = checkpointer
|
|
160
|
-
self._history_store = history_store
|
|
161
|
-
logger.info(
|
|
162
|
-
"[fred-runtime] SQL checkpointer and history store ready (dialect=%s)",
|
|
177
|
+
await asyncio.wait_for(_ping(), timeout=_SQL_BOOT_PING_TIMEOUT_S)
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
logger.error(
|
|
180
|
+
"[fred-runtime] SQL storage unreachable at startup — "
|
|
181
|
+
"dialect=%s host=%s port=%s database=%s error_type=%s",
|
|
163
182
|
engine.dialect.name,
|
|
183
|
+
postgres_config.host,
|
|
184
|
+
postgres_config.port,
|
|
185
|
+
postgres_config.database,
|
|
186
|
+
type(exc).__name__,
|
|
164
187
|
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
188
|
+
# This engine never reaches self._sql_engine on this path, so
|
|
189
|
+
# shutdown()'s own dispose() (below) can never reach it either —
|
|
190
|
+
# dispose it here or it leaks. Harmless today (the sole caller,
|
|
191
|
+
# agent_app.py's lifespan, has no retry and the process exits on
|
|
192
|
+
# this raise) but becomes a real leak the moment any retry/backoff
|
|
193
|
+
# wrapper is added around initialize_sql() or the lifespan.
|
|
194
|
+
await engine.dispose()
|
|
195
|
+
raise
|
|
196
|
+
|
|
197
|
+
init_user_store(engine)
|
|
198
|
+
checkpointer = FredSqlCheckpointer(engine, kpi=self.get_kpi_writer())
|
|
199
|
+
history_store = PostgresHistoryStore(engine, kpi=self.get_kpi_writer())
|
|
200
|
+
self._sql_engine = engine
|
|
201
|
+
self._checkpointer = checkpointer
|
|
202
|
+
self._history_store = history_store
|
|
203
|
+
logger.info(
|
|
204
|
+
"[fred-runtime] SQL checkpointer and history store ready (dialect=%s)",
|
|
205
|
+
engine.dialect.name,
|
|
206
|
+
)
|
|
169
207
|
|
|
170
208
|
def start_metrics_exporter(self) -> None:
|
|
171
209
|
"""Start the Prometheus scrape endpoint when configured."""
|
|
@@ -188,7 +188,10 @@ def create_openai_compat_router(
|
|
|
188
188
|
|
|
189
189
|
# F-A: gate this surface with the same pod-side OpenFGA check as
|
|
190
190
|
# /agents/execute. When ReBAC is active, the caller must present a team
|
|
191
|
-
# (X-Fred-Team-Id) and hold
|
|
191
|
+
# (X-Fred-Team-Id) and hold CAN_USE_TEAM_AGENTS on it (team_member-only,
|
|
192
|
+
# unlike CAN_READ which also admits the public marketplace-discovery
|
|
193
|
+
# relation — this is real execution, not discovery); identity is the
|
|
194
|
+
# validated JWT.
|
|
192
195
|
rebac = get_runtime_context().config.rebac_engine
|
|
193
196
|
if authenticated_user is not None and rebac is not None and rebac.enabled:
|
|
194
197
|
if not team_id:
|
|
@@ -198,7 +201,7 @@ def create_openai_compat_router(
|
|
|
198
201
|
)
|
|
199
202
|
try:
|
|
200
203
|
await rebac.check_user_team_permission_or_raise(
|
|
201
|
-
authenticated_user, TeamPermission.
|
|
204
|
+
authenticated_user, TeamPermission.CAN_USE_TEAM_AGENTS, team_id
|
|
202
205
|
)
|
|
203
206
|
except AuthorizationError as exc:
|
|
204
207
|
raise HTTPException(
|
|
@@ -373,7 +373,9 @@ def build_capability_agent_block(
|
|
|
373
373
|
# combining capability's tools() output from the ReAct binding
|
|
374
374
|
# (block.tools/HITL still saw it, but create_agent() never did).
|
|
375
375
|
if capability_tools:
|
|
376
|
-
middleware.append(
|
|
376
|
+
middleware.append(
|
|
377
|
+
ToolCarrierMiddleware(capability_tools, capability_id=cap_id)
|
|
378
|
+
)
|
|
377
379
|
if type(capability).middleware is not AgentCapability.middleware:
|
|
378
380
|
# Overridden: call it too, for the hook tools() cannot express.
|
|
379
381
|
# No double-computation risk — the default middleware() (which
|