fred-runtime 2.0.2__tar.gz → 2.0.4__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-2.0.2 → fred_runtime-2.0.4}/PKG-INFO +1 -1
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/agent_app.py +105 -9
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/cli/completion.py +19 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/cli/history_display.py +3 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/cli/pod_client.py +18 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/cli/repl.py +97 -1
- fred_runtime-2.0.4/fred_runtime/cli/repl_helpers.py +401 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/graph/graph_runtime.py +22 -10
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_message_codec.py +3 -4
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_model_adapter.py +2 -3
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_prompting.py +12 -4
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_runtime.py +47 -22
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_tool_binding.py +1 -2
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_tool_resolution.py +2 -3
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime.egg-info/PKG-INFO +1 -1
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime.egg-info/SOURCES.txt +6 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/pyproject.toml +13 -1
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_agent_app.py +2 -0
- fred_runtime-2.0.4/tests/test_conversational_memory.py +359 -0
- fred_runtime-2.0.4/tests/test_eval_collector.py +242 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_eval_trace.py +1 -2
- fred_runtime-2.0.4/tests/test_model_routing.py +572 -0
- fred_runtime-2.0.4/tests/test_pod_client.py +334 -0
- fred_runtime-2.0.4/tests/test_repl_helpers.py +123 -0
- fred_runtime-2.0.4/tests/test_token_expiry.py +218 -0
- fred_runtime-2.0.2/fred_runtime/cli/repl_helpers.py +0 -188
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/README.md +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/_catalogs.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/config.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/config_loader.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/container.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/context.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/dependencies.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/mcp_config.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/observability_factory.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/app/openai_compat_router.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/cli/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/cli/entrypoint.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/cli/kpi_display.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/cli/url_helpers.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/context_aware_tool.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/kf_base_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/kf_fast_text_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/kf_http_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/kf_logs_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/kf_markdown_media_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/kf_vectorsearch_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/kf_workspace_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/mcp_interceptors.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/mcp_runtime.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/mcp_toolkit.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/mcp_utils.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/structures.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/token_expiry.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/common/tool_node_utils.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/deep/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/deep/deep_runtime.py +3 -3
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/eval/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/eval/collector.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/graph/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/integrations/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/integrations/v2_runtime/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/integrations/v2_runtime/adapters.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/model_routing/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/model_routing/catalog.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/model_routing/contracts.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/model_routing/provider.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/model_routing/resolver.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_langchain_adapter.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_stream_adapter.py +2 -2
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_tool_loop.py +5 -5
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_tool_rendering.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_tool_utils.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/react/react_tracing.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/runtime_context.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/runtime_support/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/runtime_support/checkpoints.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/runtime_support/model_metadata.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/runtime_support/request_context_helpers.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/runtime_support/sql_checkpointer.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/runtime_support/user_token_refresher.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/support/__init__.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/support/filesystem_context.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/support/tool_approval.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime/support/tool_loop.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime.egg-info/dependency_links.txt +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime.egg-info/entry_points.txt +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime.egg-info/requires.txt +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/fred_runtime.egg-info/top_level.txt +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/setup.cfg +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_config_loader.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_context.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_graph_runtime_observability.py +1 -1
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_history.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_kf_workspace_client.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_kpi_display.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_mcp_config.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_openai_compat_router.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_smoke.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_url_helpers.py +0 -0
- {fred_runtime-2.0.2 → fred_runtime-2.0.4}/tests/test_user_token_refresher.py +0 -0
|
@@ -62,15 +62,16 @@ from fred_core.logs.log_setup import log_setup
|
|
|
62
62
|
from fred_core.logs.memory_log_store import RamLogStore
|
|
63
63
|
from fred_core.security.oidc import get_keycloak_client_id, get_keycloak_url
|
|
64
64
|
from fred_core.security.structure import KeycloakUser
|
|
65
|
-
from fred_sdk.contracts.eval import EvalStep, EvalTrace
|
|
66
65
|
from fred_sdk.contracts.context import (
|
|
67
66
|
AgentInvocationRequest,
|
|
68
67
|
AgentInvocationResult,
|
|
69
68
|
BoundRuntimeContext,
|
|
69
|
+
ConversationTurn,
|
|
70
70
|
PortableContext,
|
|
71
71
|
PortableEnvironment,
|
|
72
72
|
RuntimeContext,
|
|
73
73
|
)
|
|
74
|
+
from fred_sdk.contracts.eval import EvalStep, EvalTrace
|
|
74
75
|
from fred_sdk.contracts.execution import (
|
|
75
76
|
ExecutionGrantAction,
|
|
76
77
|
ExecutionGrantViolation,
|
|
@@ -83,6 +84,7 @@ from fred_sdk.contracts.models import (
|
|
|
83
84
|
GraphAgentDefinition,
|
|
84
85
|
MCPServerConfiguration,
|
|
85
86
|
ReActAgentDefinition,
|
|
87
|
+
TuningValue,
|
|
86
88
|
)
|
|
87
89
|
from fred_sdk.contracts.react_contract import ReActInput, ReActMessage, ReActMessageRole
|
|
88
90
|
from fred_sdk.contracts.runtime import (
|
|
@@ -94,9 +96,6 @@ from fred_sdk.contracts.runtime import (
|
|
|
94
96
|
RuntimeEvent,
|
|
95
97
|
RuntimeServices,
|
|
96
98
|
)
|
|
97
|
-
from fred_runtime.graph.graph_runtime import GraphRuntime
|
|
98
|
-
from fred_runtime.react.react_runtime import ReActRuntime
|
|
99
|
-
from fred_runtime.runtime_support.checkpoints import load_checkpoint
|
|
100
99
|
from fred_sdk.support.authored_toolsets import (
|
|
101
100
|
AuthoredToolRuntimePorts,
|
|
102
101
|
build_authored_tool_handlers,
|
|
@@ -104,6 +103,9 @@ from fred_sdk.support.authored_toolsets import (
|
|
|
104
103
|
from pydantic import BaseModel, Field, TypeAdapter, model_validator
|
|
105
104
|
|
|
106
105
|
from fred_runtime.common.kf_markdown_media_client import KfMarkdownMediaClient
|
|
106
|
+
from fred_runtime.graph.graph_runtime import GraphRuntime
|
|
107
|
+
from fred_runtime.react.react_runtime import ReActRuntime
|
|
108
|
+
from fred_runtime.runtime_support.checkpoints import load_checkpoint
|
|
107
109
|
|
|
108
110
|
from ..common.structures import AgentSettingsLike
|
|
109
111
|
from ..integrations.v2_runtime.adapters import (
|
|
@@ -567,6 +569,7 @@ class LocalRegistryAgentInvoker(AgentInvokerPort):
|
|
|
567
569
|
message=request.message,
|
|
568
570
|
context=context_dict,
|
|
569
571
|
resume_payload=None,
|
|
572
|
+
invocation_turns=request.prior_turns,
|
|
570
573
|
)
|
|
571
574
|
|
|
572
575
|
content_parts: list[str] = []
|
|
@@ -748,6 +751,14 @@ class _AgentExecuteRequest(BaseModel):
|
|
|
748
751
|
"LangGraph Command(resume=...) — the message field is ignored."
|
|
749
752
|
),
|
|
750
753
|
)
|
|
754
|
+
invocation_turns: tuple[ConversationTurn, ...] = Field(
|
|
755
|
+
default=(),
|
|
756
|
+
description="Prior conversation turns forwarded by the calling agent.",
|
|
757
|
+
)
|
|
758
|
+
inline_tuning: dict[str, TuningValue] | None = Field(
|
|
759
|
+
default=None,
|
|
760
|
+
description="Optional inline tuning overrides. Honored only in agent_id (direct template) mode.",
|
|
761
|
+
)
|
|
751
762
|
|
|
752
763
|
@model_validator(mode="after")
|
|
753
764
|
def _require_message_or_resume(self) -> "_AgentExecuteRequest":
|
|
@@ -794,6 +805,8 @@ def _to_internal_request(r: RuntimeExecuteRequest) -> "_AgentExecuteRequest":
|
|
|
794
805
|
context=r.to_legacy_context() or None,
|
|
795
806
|
checkpoint_id=r.checkpoint_id,
|
|
796
807
|
resume_payload=r.resume_payload,
|
|
808
|
+
invocation_turns=r.invocation_turns,
|
|
809
|
+
inline_tuning=r.inline_tuning,
|
|
797
810
|
)
|
|
798
811
|
|
|
799
812
|
|
|
@@ -806,6 +819,18 @@ class _AgentTemplateSummary(BaseModel):
|
|
|
806
819
|
available_mcp_servers: list[MCPServerConfiguration] = Field(default_factory=list)
|
|
807
820
|
|
|
808
821
|
|
|
822
|
+
class _McpCatalogEntry(BaseModel):
|
|
823
|
+
id: str
|
|
824
|
+
name: str
|
|
825
|
+
description: str | None = None
|
|
826
|
+
enabled: bool
|
|
827
|
+
transport: str | None = None
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
class _McpCatalogResponse(BaseModel):
|
|
831
|
+
servers: list[_McpCatalogEntry]
|
|
832
|
+
|
|
833
|
+
|
|
809
834
|
class _ResolvedAgentInstance(BaseModel):
|
|
810
835
|
agent_instance_id: str
|
|
811
836
|
template_agent_id: str
|
|
@@ -827,7 +852,7 @@ def _apply_runtime_tuning(
|
|
|
827
852
|
definition: ReActAgentDefinition | GraphAgentDefinition, tuning: AgentTuning
|
|
828
853
|
) -> ReActAgentDefinition | GraphAgentDefinition:
|
|
829
854
|
"""
|
|
830
|
-
Overlay persisted business tuning onto one registered
|
|
855
|
+
Overlay persisted business tuning onto one registered agent template.
|
|
831
856
|
|
|
832
857
|
Why this exists:
|
|
833
858
|
- control-plane stores the full effective tuning for a managed agent
|
|
@@ -841,18 +866,29 @@ def _apply_runtime_tuning(
|
|
|
841
866
|
- `definition = _apply_runtime_tuning(template_definition, resolution.tuning)`
|
|
842
867
|
"""
|
|
843
868
|
|
|
869
|
+
mcp_servers = tuning.mcp_servers
|
|
870
|
+
if tuning.selected_mcp_server_ids:
|
|
871
|
+
selected = frozenset(tuning.selected_mcp_server_ids)
|
|
872
|
+
mcp_servers = [s for s in mcp_servers if s.id in selected]
|
|
873
|
+
|
|
844
874
|
update: dict[str, object] = {
|
|
845
875
|
"role": tuning.role,
|
|
846
876
|
"description": tuning.description,
|
|
847
877
|
"tags": tuple(tuning.tags),
|
|
848
878
|
"fields": tuple(field.model_copy(deep=True) for field in tuning.fields),
|
|
849
879
|
"default_mcp_servers": tuple(
|
|
850
|
-
server.model_copy(deep=True) for server in
|
|
880
|
+
server.model_copy(deep=True) for server in mcp_servers
|
|
851
881
|
),
|
|
882
|
+
# Forward all values for all agent types so every execution surface can
|
|
883
|
+
# read admin-set tuning (graph steps via context.tuning_values, ReAct
|
|
884
|
+
# prompting via definition.tuning_values).
|
|
885
|
+
"tuning_values": dict(tuning.values),
|
|
852
886
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
887
|
+
if isinstance(definition, ReActAgentDefinition):
|
|
888
|
+
# Also overlay system_prompt_template directly for ReAct runtime compatibility.
|
|
889
|
+
system_prompt = tuning.values.get("prompts.system")
|
|
890
|
+
if isinstance(system_prompt, str) and system_prompt.strip():
|
|
891
|
+
update["system_prompt_template"] = system_prompt
|
|
856
892
|
return definition.model_copy(update=update)
|
|
857
893
|
|
|
858
894
|
|
|
@@ -915,6 +951,18 @@ async def _resolve_agent_instance(
|
|
|
915
951
|
detail=f"Unknown agent_id: {request.agent_id!r}. "
|
|
916
952
|
f"Known agents: {list(registry.keys())}",
|
|
917
953
|
)
|
|
954
|
+
if request.inline_tuning:
|
|
955
|
+
definition = _apply_runtime_tuning(
|
|
956
|
+
definition,
|
|
957
|
+
AgentTuning(
|
|
958
|
+
role=definition.role,
|
|
959
|
+
description=definition.description,
|
|
960
|
+
tags=list(definition.tags),
|
|
961
|
+
fields=list(definition.fields),
|
|
962
|
+
mcp_servers=list(definition.default_mcp_servers),
|
|
963
|
+
values=request.inline_tuning,
|
|
964
|
+
),
|
|
965
|
+
)
|
|
918
966
|
return _ResolvedExecutionTarget(
|
|
919
967
|
definition=definition,
|
|
920
968
|
effective_agent_id=definition.agent_id,
|
|
@@ -1745,11 +1793,24 @@ async def _iterate_runtime_event_payloads(
|
|
|
1745
1793
|
user_groups=ctx.get("user_groups"),
|
|
1746
1794
|
language=ctx.get("language"),
|
|
1747
1795
|
access_token=access_token,
|
|
1796
|
+
refresh_token=ctx.get("refresh_token"),
|
|
1797
|
+
access_token_expires_at=ctx.get("access_token_expires_at"),
|
|
1748
1798
|
trace_id=ctx.get("trace_id"),
|
|
1749
1799
|
correlation_id=correlation_id,
|
|
1750
1800
|
agent_instance_id=request.agent_instance_id,
|
|
1751
1801
|
template_agent_id=definition.agent_id,
|
|
1752
1802
|
execution_action=execution_action,
|
|
1803
|
+
# Chat options forwarded from the frontend RuntimeContext.
|
|
1804
|
+
# These were present in ctx but were silently dropped, causing
|
|
1805
|
+
# ContextAwareTool and all KF search helpers to always use defaults.
|
|
1806
|
+
selected_document_libraries_ids=ctx.get("selected_document_libraries_ids"),
|
|
1807
|
+
selected_document_uids=ctx.get("selected_document_uids"),
|
|
1808
|
+
selected_chat_context_ids=ctx.get("selected_chat_context_ids"),
|
|
1809
|
+
search_policy=ctx.get("search_policy"),
|
|
1810
|
+
search_rag_scope=ctx.get("search_rag_scope"),
|
|
1811
|
+
include_session_scope=ctx.get("include_session_scope"),
|
|
1812
|
+
include_corpus_scope=ctx.get("include_corpus_scope"),
|
|
1813
|
+
deep_search=ctx.get("deep_search"),
|
|
1753
1814
|
)
|
|
1754
1815
|
|
|
1755
1816
|
binding = BoundRuntimeContext(
|
|
@@ -1783,6 +1844,7 @@ async def _iterate_runtime_event_payloads(
|
|
|
1783
1844
|
session_id=ctx.get("session_id") or request_id,
|
|
1784
1845
|
checkpoint_id=request.checkpoint_id,
|
|
1785
1846
|
resume_payload=request.resume_payload,
|
|
1847
|
+
invocation_turns=getattr(request, "invocation_turns", ()),
|
|
1786
1848
|
)
|
|
1787
1849
|
|
|
1788
1850
|
try:
|
|
@@ -1972,6 +2034,40 @@ def _build_agent_router(
|
|
|
1972
2034
|
for definition in registry.values()
|
|
1973
2035
|
]
|
|
1974
2036
|
|
|
2037
|
+
@router.get("/mcp-catalog")
|
|
2038
|
+
async def get_mcp_catalog() -> _McpCatalogResponse:
|
|
2039
|
+
"""
|
|
2040
|
+
Return the full MCP server catalog declared in mcp_catalog.yaml.
|
|
2041
|
+
|
|
2042
|
+
Why this endpoint exists:
|
|
2043
|
+
- control-plane drift detection needs to compare stored instance
|
|
2044
|
+
selections against the live pod catalog at listing time
|
|
2045
|
+
- returns ALL servers (enabled and disabled) so the caller can
|
|
2046
|
+
distinguish "configured but disabled" from "absent from catalog"
|
|
2047
|
+
|
|
2048
|
+
How to use it:
|
|
2049
|
+
- call from control-plane agent-instance listing to populate
|
|
2050
|
+
catalog_warnings when stored mcp_server_ids are no longer present
|
|
2051
|
+
|
|
2052
|
+
Example:
|
|
2053
|
+
- `GET /fred/agents/v2/agents/mcp-catalog`
|
|
2054
|
+
"""
|
|
2055
|
+
mcp_configuration = get_runtime_context().config.mcp_configuration
|
|
2056
|
+
if mcp_configuration is None:
|
|
2057
|
+
return _McpCatalogResponse(servers=[])
|
|
2058
|
+
return _McpCatalogResponse(
|
|
2059
|
+
servers=[
|
|
2060
|
+
_McpCatalogEntry(
|
|
2061
|
+
id=srv.id,
|
|
2062
|
+
name=srv.name,
|
|
2063
|
+
description=srv.description,
|
|
2064
|
+
enabled=srv.enabled,
|
|
2065
|
+
transport=srv.transport,
|
|
2066
|
+
)
|
|
2067
|
+
for srv in mcp_configuration.servers
|
|
2068
|
+
]
|
|
2069
|
+
)
|
|
2070
|
+
|
|
1975
2071
|
@router.get("/sessions", dependencies=_auth_deps)
|
|
1976
2072
|
async def list_sessions(user_id: str) -> list[str]:
|
|
1977
2073
|
"""
|
|
@@ -14,6 +14,7 @@ _COMMANDS: tuple[str, ...] = (
|
|
|
14
14
|
"/context",
|
|
15
15
|
"/delete-session",
|
|
16
16
|
"/delete-checkpoint",
|
|
17
|
+
"/inspect",
|
|
17
18
|
"/purge-session",
|
|
18
19
|
"/execution-context",
|
|
19
20
|
"/history",
|
|
@@ -21,17 +22,32 @@ _COMMANDS: tuple[str, ...] = (
|
|
|
21
22
|
"/login",
|
|
22
23
|
"/login-password",
|
|
23
24
|
"/mode",
|
|
25
|
+
"/run",
|
|
24
26
|
"/session",
|
|
25
27
|
"/session-info",
|
|
26
28
|
"/session-new",
|
|
27
29
|
"/sessions",
|
|
28
30
|
"/stats",
|
|
29
31
|
"/team",
|
|
32
|
+
"/tune",
|
|
33
|
+
"/tuning",
|
|
30
34
|
"/logout",
|
|
31
35
|
"/quit",
|
|
32
36
|
"/whoami",
|
|
33
37
|
)
|
|
34
38
|
|
|
39
|
+
# Scenario keywords for fred.test.assistant — used for /run tab-completion.
|
|
40
|
+
_TEST_ASSISTANT_SCENARIOS: tuple[str, ...] = (
|
|
41
|
+
"echo",
|
|
42
|
+
"error",
|
|
43
|
+
"hitl choice",
|
|
44
|
+
"hitl text",
|
|
45
|
+
"long",
|
|
46
|
+
"model planning",
|
|
47
|
+
"model routing",
|
|
48
|
+
"trace",
|
|
49
|
+
)
|
|
50
|
+
|
|
35
51
|
|
|
36
52
|
def completion_candidates(
|
|
37
53
|
line_buffer: str,
|
|
@@ -50,6 +66,9 @@ def completion_candidates(
|
|
|
50
66
|
if stripped.startswith("/mode "):
|
|
51
67
|
prefix = stripped.removeprefix("/mode ").strip()
|
|
52
68
|
return [mode for mode in ("eval", "final", "stream") if mode.startswith(prefix)]
|
|
69
|
+
if stripped.startswith("/run "):
|
|
70
|
+
prefix = stripped.removeprefix("/run ").strip()
|
|
71
|
+
return [s for s in _TEST_ASSISTANT_SCENARIOS if s.startswith(prefix)]
|
|
53
72
|
if stripped.startswith("/"):
|
|
54
73
|
return complete_slash_commands(stripped, commands=_COMMANDS)
|
|
55
74
|
return []
|
|
@@ -312,6 +312,7 @@ def run_single_turn(
|
|
|
312
312
|
stream: bool,
|
|
313
313
|
color_enabled: bool,
|
|
314
314
|
resume_payload: Any = None,
|
|
315
|
+
inline_tuning: dict[str, Any] | None = None,
|
|
315
316
|
) -> tuple[int, dict[str, Any] | None]:
|
|
316
317
|
"""
|
|
317
318
|
Execute one prompt and print the most useful runtime output.
|
|
@@ -327,6 +328,7 @@ def run_single_turn(
|
|
|
327
328
|
user_id=user_id,
|
|
328
329
|
team_id=team_id,
|
|
329
330
|
resume_payload=resume_payload,
|
|
331
|
+
inline_tuning=inline_tuning,
|
|
330
332
|
)
|
|
331
333
|
if "error" in payload:
|
|
332
334
|
print(
|
|
@@ -362,6 +364,7 @@ def run_single_turn(
|
|
|
362
364
|
user_id=user_id,
|
|
363
365
|
team_id=team_id,
|
|
364
366
|
resume_payload=resume_payload,
|
|
367
|
+
inline_tuning=inline_tuning,
|
|
365
368
|
):
|
|
366
369
|
if verbose:
|
|
367
370
|
print(json.dumps(event, ensure_ascii=False))
|
|
@@ -58,6 +58,16 @@ class AgentPodClient:
|
|
|
58
58
|
raise RuntimeError("Agent list response must be a JSON array of strings.")
|
|
59
59
|
return payload
|
|
60
60
|
|
|
61
|
+
def list_templates(self) -> list[dict[str, Any]]:
|
|
62
|
+
response = self.http_client.get(
|
|
63
|
+
f"{self.base_url}/agents/templates", headers=self._auth_headers()
|
|
64
|
+
)
|
|
65
|
+
response.raise_for_status()
|
|
66
|
+
payload = response.json()
|
|
67
|
+
if not isinstance(payload, list):
|
|
68
|
+
raise RuntimeError("Templates response must be a JSON array.")
|
|
69
|
+
return payload
|
|
70
|
+
|
|
61
71
|
def execute(
|
|
62
72
|
self,
|
|
63
73
|
*,
|
|
@@ -69,6 +79,7 @@ class AgentPodClient:
|
|
|
69
79
|
agent_instance_id: str | None = None,
|
|
70
80
|
checkpoint_id: str | None = None,
|
|
71
81
|
resume_payload: Any = None,
|
|
82
|
+
inline_tuning: dict[str, Any] | None = None,
|
|
72
83
|
) -> dict[str, Any]:
|
|
73
84
|
runtime_context: dict[str, Any] = {"user_id": user_id}
|
|
74
85
|
if team_id:
|
|
@@ -85,6 +96,8 @@ class AgentPodClient:
|
|
|
85
96
|
payload["checkpoint_id"] = checkpoint_id
|
|
86
97
|
if resume_payload is not None:
|
|
87
98
|
payload["resume_payload"] = resume_payload
|
|
99
|
+
if inline_tuning:
|
|
100
|
+
payload["inline_tuning"] = inline_tuning
|
|
88
101
|
response = self.http_client.post(
|
|
89
102
|
f"{self.base_url}/agents/execute",
|
|
90
103
|
json=payload,
|
|
@@ -142,6 +155,7 @@ class AgentPodClient:
|
|
|
142
155
|
agent_instance_id: str | None = None,
|
|
143
156
|
checkpoint_id: str | None = None,
|
|
144
157
|
resume_payload: Any = None,
|
|
158
|
+
inline_tuning: dict[str, Any] | None = None,
|
|
145
159
|
) -> list[dict[str, Any]]:
|
|
146
160
|
events: list[dict[str, Any]] = []
|
|
147
161
|
for event in self.iter_stream_events(
|
|
@@ -153,6 +167,7 @@ class AgentPodClient:
|
|
|
153
167
|
agent_instance_id=agent_instance_id,
|
|
154
168
|
checkpoint_id=checkpoint_id,
|
|
155
169
|
resume_payload=resume_payload,
|
|
170
|
+
inline_tuning=inline_tuning,
|
|
156
171
|
):
|
|
157
172
|
events.append(event)
|
|
158
173
|
return events
|
|
@@ -168,6 +183,7 @@ class AgentPodClient:
|
|
|
168
183
|
agent_instance_id: str | None = None,
|
|
169
184
|
checkpoint_id: str | None = None,
|
|
170
185
|
resume_payload: Any = None,
|
|
186
|
+
inline_tuning: dict[str, Any] | None = None,
|
|
171
187
|
) -> Iterator[dict[str, Any]]:
|
|
172
188
|
runtime_context: dict[str, Any] = {"user_id": user_id}
|
|
173
189
|
if team_id:
|
|
@@ -184,6 +200,8 @@ class AgentPodClient:
|
|
|
184
200
|
payload["checkpoint_id"] = checkpoint_id
|
|
185
201
|
if resume_payload is not None:
|
|
186
202
|
payload["resume_payload"] = resume_payload
|
|
203
|
+
if inline_tuning:
|
|
204
|
+
payload["inline_tuning"] = inline_tuning
|
|
187
205
|
with self.http_client.stream(
|
|
188
206
|
"POST",
|
|
189
207
|
f"{self.base_url}/agents/execute/stream",
|
|
@@ -33,7 +33,10 @@ from .repl_helpers import (
|
|
|
33
33
|
execution_mode_label,
|
|
34
34
|
fmt_bytes,
|
|
35
35
|
parse_mode_command,
|
|
36
|
+
parse_tuning_value,
|
|
36
37
|
print_help,
|
|
38
|
+
print_inspect,
|
|
39
|
+
print_tuning_table,
|
|
37
40
|
)
|
|
38
41
|
|
|
39
42
|
|
|
@@ -109,11 +112,21 @@ def run_interactive_chat(
|
|
|
109
112
|
current_session_id = session_id
|
|
110
113
|
current_mode: ExecutionMode = mode
|
|
111
114
|
current_team_id = team_id
|
|
115
|
+
current_inline_tuning: dict[str, Any] = {}
|
|
112
116
|
while True:
|
|
113
117
|
try:
|
|
118
|
+
tuning_badge = (
|
|
119
|
+
colorize(
|
|
120
|
+
f" ~{len(current_inline_tuning)}",
|
|
121
|
+
color=ANSI_YELLOW,
|
|
122
|
+
enabled=color_enabled,
|
|
123
|
+
)
|
|
124
|
+
if current_inline_tuning
|
|
125
|
+
else ""
|
|
126
|
+
)
|
|
114
127
|
prompt = (
|
|
115
128
|
f"{colorize(current_agent, color=ANSI_CYAN, enabled=color_enabled, bold=True)}"
|
|
116
|
-
"> "
|
|
129
|
+
f"{tuning_badge}> "
|
|
117
130
|
)
|
|
118
131
|
message = input(prompt).strip()
|
|
119
132
|
except EOFError:
|
|
@@ -1161,6 +1174,87 @@ def run_interactive_chat(
|
|
|
1161
1174
|
if message in {"/quit", "/exit"}:
|
|
1162
1175
|
return 0
|
|
1163
1176
|
|
|
1177
|
+
# ── /inspect ───────────────────────────────────────────────────────
|
|
1178
|
+
if message == "/inspect":
|
|
1179
|
+
try:
|
|
1180
|
+
templates = client.list_templates()
|
|
1181
|
+
except Exception as exc:
|
|
1182
|
+
print(
|
|
1183
|
+
colorize(
|
|
1184
|
+
f" Could not load templates: {exc}",
|
|
1185
|
+
color=ANSI_RED,
|
|
1186
|
+
enabled=color_enabled,
|
|
1187
|
+
)
|
|
1188
|
+
)
|
|
1189
|
+
continue
|
|
1190
|
+
print_inspect(templates, current_agent, color_enabled=color_enabled)
|
|
1191
|
+
continue
|
|
1192
|
+
|
|
1193
|
+
# ── /run <scenario> ────────────────────────────────────────────────
|
|
1194
|
+
if message.startswith("/run"):
|
|
1195
|
+
scenario = message.removeprefix("/run").strip()
|
|
1196
|
+
if not scenario:
|
|
1197
|
+
print(
|
|
1198
|
+
colorize(
|
|
1199
|
+
" Usage: /run <scenario> (tab-complete for available scenarios)",
|
|
1200
|
+
color=ANSI_DIM,
|
|
1201
|
+
enabled=color_enabled,
|
|
1202
|
+
)
|
|
1203
|
+
)
|
|
1204
|
+
continue
|
|
1205
|
+
message = scenario
|
|
1206
|
+
|
|
1207
|
+
# ── /tuning / /tune ────────────────────────────────────────────────
|
|
1208
|
+
if message == "/tuning":
|
|
1209
|
+
print_tuning_table(current_inline_tuning, color_enabled=color_enabled)
|
|
1210
|
+
continue
|
|
1211
|
+
if message.startswith("/tune"):
|
|
1212
|
+
arg = message.removeprefix("/tune").strip()
|
|
1213
|
+
if not arg:
|
|
1214
|
+
print_tuning_table(current_inline_tuning, color_enabled=color_enabled)
|
|
1215
|
+
continue
|
|
1216
|
+
if "=" not in arg:
|
|
1217
|
+
print(
|
|
1218
|
+
colorize(
|
|
1219
|
+
" Usage: /tune key=value (clear with key=)",
|
|
1220
|
+
color=ANSI_DIM,
|
|
1221
|
+
enabled=color_enabled,
|
|
1222
|
+
)
|
|
1223
|
+
)
|
|
1224
|
+
continue
|
|
1225
|
+
key, _, raw_val = arg.partition("=")
|
|
1226
|
+
key = key.strip()
|
|
1227
|
+
if not key:
|
|
1228
|
+
print(
|
|
1229
|
+
colorize(
|
|
1230
|
+
" Key cannot be empty.",
|
|
1231
|
+
color=ANSI_YELLOW,
|
|
1232
|
+
enabled=color_enabled,
|
|
1233
|
+
)
|
|
1234
|
+
)
|
|
1235
|
+
continue
|
|
1236
|
+
if not raw_val:
|
|
1237
|
+
current_inline_tuning.pop(key, None)
|
|
1238
|
+
print(
|
|
1239
|
+
colorize(
|
|
1240
|
+
f" Cleared tuning override for {key!r}.",
|
|
1241
|
+
color=ANSI_DIM,
|
|
1242
|
+
enabled=color_enabled,
|
|
1243
|
+
)
|
|
1244
|
+
)
|
|
1245
|
+
else:
|
|
1246
|
+
value = parse_tuning_value(raw_val)
|
|
1247
|
+
current_inline_tuning[key] = value
|
|
1248
|
+
val_repr = repr(value) if not isinstance(value, str) else f'"{value}"'
|
|
1249
|
+
print(
|
|
1250
|
+
colorize(
|
|
1251
|
+
f" Set {key} = {val_repr}",
|
|
1252
|
+
color=ANSI_GREEN,
|
|
1253
|
+
enabled=color_enabled,
|
|
1254
|
+
)
|
|
1255
|
+
)
|
|
1256
|
+
continue
|
|
1257
|
+
|
|
1164
1258
|
if message.startswith("/"):
|
|
1165
1259
|
bare = message.split()[0]
|
|
1166
1260
|
_USAGE_HINTS: dict[str, str] = {
|
|
@@ -1199,6 +1293,7 @@ def run_interactive_chat(
|
|
|
1199
1293
|
verbose=verbose,
|
|
1200
1294
|
stream=(current_mode == "stream"),
|
|
1201
1295
|
color_enabled=color_enabled,
|
|
1296
|
+
inline_tuning=current_inline_tuning or None,
|
|
1202
1297
|
)
|
|
1203
1298
|
while hitl is not None:
|
|
1204
1299
|
req = hitl.get("request") or {}
|
|
@@ -1241,6 +1336,7 @@ def run_interactive_chat(
|
|
|
1241
1336
|
stream=(current_mode == "stream"),
|
|
1242
1337
|
color_enabled=color_enabled,
|
|
1243
1338
|
resume_payload=resume_value,
|
|
1339
|
+
inline_tuning=current_inline_tuning or None,
|
|
1244
1340
|
)
|
|
1245
1341
|
if exit_code != 0:
|
|
1246
1342
|
print("The request failed. Use /help for commands or try another agent.")
|