fast-agent-mcp 0.4.7__py3-none-any.whl
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.
- fast_agent/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Direct AgentApp implementation for interacting with agents without proxies.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Mapping, Union
|
|
6
|
+
|
|
7
|
+
from deprecated import deprecated
|
|
8
|
+
from mcp.types import GetPromptResult, PromptMessage
|
|
9
|
+
from rich import print as rich_print
|
|
10
|
+
|
|
11
|
+
from fast_agent.agents.agent_types import AgentType
|
|
12
|
+
from fast_agent.core.exceptions import AgentConfigError, ServerConfigError
|
|
13
|
+
from fast_agent.interfaces import AgentProtocol
|
|
14
|
+
from fast_agent.types import PromptMessageExtended, RequestParams
|
|
15
|
+
from fast_agent.ui.interactive_prompt import InteractivePrompt
|
|
16
|
+
from fast_agent.ui.progress_display import progress_display
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AgentApp:
|
|
20
|
+
"""
|
|
21
|
+
Container for active agents that provides a simple API for interacting with them.
|
|
22
|
+
This implementation works directly with Agent instances without proxies.
|
|
23
|
+
|
|
24
|
+
The DirectAgentApp provides both attribute-style access (app.agent_name)
|
|
25
|
+
and dictionary-style access (app["agent_name"]) to agents.
|
|
26
|
+
|
|
27
|
+
It also implements the AgentProtocol interface, automatically forwarding
|
|
28
|
+
calls to the default agent (the first agent in the container).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, agents: dict[str, AgentProtocol]) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Initialize the DirectAgentApp.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
agents: Dictionary of agent instances keyed by name
|
|
37
|
+
"""
|
|
38
|
+
if len(agents) == 0:
|
|
39
|
+
raise ValueError("No agents provided!")
|
|
40
|
+
self._agents = agents
|
|
41
|
+
|
|
42
|
+
def __getitem__(self, key: str) -> AgentProtocol:
|
|
43
|
+
"""Allow access to agents using dictionary syntax."""
|
|
44
|
+
if key not in self._agents:
|
|
45
|
+
raise KeyError(f"Agent '{key}' not found")
|
|
46
|
+
return self._agents[key]
|
|
47
|
+
|
|
48
|
+
def __getattr__(self, name: str) -> AgentProtocol:
|
|
49
|
+
"""Allow access to agents using attribute syntax."""
|
|
50
|
+
if name in self._agents:
|
|
51
|
+
return self._agents[name]
|
|
52
|
+
raise AttributeError(f"Agent '{name}' not found")
|
|
53
|
+
|
|
54
|
+
async def __call__(
|
|
55
|
+
self,
|
|
56
|
+
message: Union[str, PromptMessage, PromptMessageExtended] | None = None,
|
|
57
|
+
agent_name: str | None = None,
|
|
58
|
+
default_prompt: str = "",
|
|
59
|
+
request_params: RequestParams | None = None,
|
|
60
|
+
) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Make the object callable to send messages or start interactive prompt.
|
|
63
|
+
This mirrors the FastAgent implementation that allowed agent("message").
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
message: Message content in various formats:
|
|
67
|
+
- String: Converted to a user PromptMessageExtended
|
|
68
|
+
- PromptMessage: Converted to PromptMessageExtended
|
|
69
|
+
- PromptMessageExtended: Used directly
|
|
70
|
+
agent_name: Optional name of the agent to send to (defaults to first agent)
|
|
71
|
+
default_prompt: Default message to use in interactive prompt mode
|
|
72
|
+
request_params: Optional request parameters including MCP metadata
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The agent's response as a string or the result of the interactive session
|
|
76
|
+
"""
|
|
77
|
+
if message:
|
|
78
|
+
return await self._agent(agent_name).send(message, request_params)
|
|
79
|
+
|
|
80
|
+
return await self.interactive(
|
|
81
|
+
agent_name=agent_name, default_prompt=default_prompt, request_params=request_params
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
async def send(
|
|
85
|
+
self,
|
|
86
|
+
message: Union[str, PromptMessage, PromptMessageExtended],
|
|
87
|
+
agent_name: str | None = None,
|
|
88
|
+
request_params: RequestParams | None = None,
|
|
89
|
+
) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Send a message to the specified agent (or to all agents).
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
message: Message content in various formats:
|
|
95
|
+
- String: Converted to a user PromptMessageExtended
|
|
96
|
+
- PromptMessage: Converted to PromptMessageExtended
|
|
97
|
+
- PromptMessageExtended: Used directly
|
|
98
|
+
agent_name: Optional name of the agent to send to
|
|
99
|
+
request_params: Optional request parameters including MCP metadata
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
The agent's response as a string
|
|
103
|
+
"""
|
|
104
|
+
return await self._agent(agent_name).send(message, request_params)
|
|
105
|
+
|
|
106
|
+
def _agent(self, agent_name: str | None) -> AgentProtocol:
|
|
107
|
+
if agent_name:
|
|
108
|
+
if agent_name not in self._agents:
|
|
109
|
+
raise ValueError(f"Agent '{agent_name}' not found")
|
|
110
|
+
return self._agents[agent_name]
|
|
111
|
+
|
|
112
|
+
for agent in self._agents.values():
|
|
113
|
+
if agent.config.default:
|
|
114
|
+
return agent
|
|
115
|
+
|
|
116
|
+
return next(iter(self._agents.values()))
|
|
117
|
+
|
|
118
|
+
async def apply_prompt(
|
|
119
|
+
self,
|
|
120
|
+
prompt: Union[str, GetPromptResult],
|
|
121
|
+
arguments: dict[str, str] | None = None,
|
|
122
|
+
agent_name: str | None = None,
|
|
123
|
+
as_template: bool = False,
|
|
124
|
+
) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Apply a prompt template to an agent (default agent if not specified).
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
prompt: Name of the prompt template to apply OR a GetPromptResult object
|
|
130
|
+
arguments: Optional arguments for the prompt template
|
|
131
|
+
agent_name: Name of the agent to send to
|
|
132
|
+
as_template: If True, store as persistent template (always included in context)
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The agent's response as a string
|
|
136
|
+
"""
|
|
137
|
+
return await self._agent(agent_name).apply_prompt(
|
|
138
|
+
prompt, arguments, as_template=as_template
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
async def list_prompts(self, namespace: str | None = None, agent_name: str | None = None):
|
|
142
|
+
"""
|
|
143
|
+
List available prompts for an agent.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
server_name: Optional name of the server to list prompts from
|
|
147
|
+
agent_name: Name of the agent to list prompts for
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Dictionary mapping server names to lists of available prompts
|
|
151
|
+
"""
|
|
152
|
+
if not agent_name:
|
|
153
|
+
results = {}
|
|
154
|
+
for agent in self._agents.values():
|
|
155
|
+
curr_prompts = await agent.list_prompts(namespace=namespace)
|
|
156
|
+
results.update(curr_prompts)
|
|
157
|
+
return results
|
|
158
|
+
return await self._agent(agent_name).list_prompts(namespace=namespace)
|
|
159
|
+
|
|
160
|
+
async def get_prompt(
|
|
161
|
+
self,
|
|
162
|
+
prompt_name: str,
|
|
163
|
+
arguments: dict[str, str] | None = None,
|
|
164
|
+
server_name: str | None = None,
|
|
165
|
+
agent_name: str | None = None,
|
|
166
|
+
):
|
|
167
|
+
"""
|
|
168
|
+
Get a prompt from a server.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
prompt_name: Name of the prompt, optionally namespaced
|
|
172
|
+
arguments: Optional dictionary of arguments to pass to the prompt template
|
|
173
|
+
server_name: Optional name of the server to get the prompt from
|
|
174
|
+
agent_name: Name of the agent to use
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
GetPromptResult containing the prompt information
|
|
178
|
+
"""
|
|
179
|
+
return await self._agent(agent_name).get_prompt(
|
|
180
|
+
prompt_name=prompt_name, arguments=arguments, namespace=server_name
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
async def with_resource(
|
|
184
|
+
self,
|
|
185
|
+
prompt_content: Union[str, PromptMessage, PromptMessageExtended],
|
|
186
|
+
resource_uri: str,
|
|
187
|
+
server_name: str | None = None,
|
|
188
|
+
agent_name: str | None = None,
|
|
189
|
+
) -> str:
|
|
190
|
+
"""
|
|
191
|
+
Send a message with an attached MCP resource.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
prompt_content: Content in various formats (String, PromptMessage, or PromptMessageExtended)
|
|
195
|
+
resource_uri: URI of the resource to retrieve
|
|
196
|
+
server_name: Optional name of the MCP server to retrieve the resource from
|
|
197
|
+
agent_name: Name of the agent to use
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
The agent's response as a string
|
|
201
|
+
"""
|
|
202
|
+
return await self._agent(agent_name).with_resource(
|
|
203
|
+
prompt_content=prompt_content, resource_uri=resource_uri, namespace=server_name
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
async def list_resources(
|
|
207
|
+
self,
|
|
208
|
+
server_name: str | None = None,
|
|
209
|
+
agent_name: str | None = None,
|
|
210
|
+
) -> Mapping[str, list[str]]:
|
|
211
|
+
"""
|
|
212
|
+
List available resources from one or all servers.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
server_name: Optional server name to list resources from
|
|
216
|
+
agent_name: Name of the agent to use
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Dictionary mapping server names to lists of resource URIs
|
|
220
|
+
"""
|
|
221
|
+
return await self._agent(agent_name).list_resources(namespace=server_name)
|
|
222
|
+
|
|
223
|
+
async def get_resource(
|
|
224
|
+
self,
|
|
225
|
+
resource_uri: str,
|
|
226
|
+
server_name: str | None = None,
|
|
227
|
+
agent_name: str | None = None,
|
|
228
|
+
):
|
|
229
|
+
"""
|
|
230
|
+
Get a resource from an MCP server.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
resource_uri: URI of the resource to retrieve
|
|
234
|
+
server_name: Optional name of the MCP server to retrieve the resource from
|
|
235
|
+
agent_name: Name of the agent to use
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
ReadResourceResult object containing the resource content
|
|
239
|
+
"""
|
|
240
|
+
return await self._agent(agent_name).get_resource(
|
|
241
|
+
resource_uri=resource_uri, namespace=server_name
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
@deprecated
|
|
245
|
+
async def prompt(
|
|
246
|
+
self,
|
|
247
|
+
agent_name: str | None = None,
|
|
248
|
+
default_prompt: str = "",
|
|
249
|
+
request_params: RequestParams | None = None,
|
|
250
|
+
) -> str:
|
|
251
|
+
"""
|
|
252
|
+
Deprecated - use interactive() instead.
|
|
253
|
+
"""
|
|
254
|
+
return await self.interactive(
|
|
255
|
+
agent_name=agent_name, default_prompt=default_prompt, request_params=request_params
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
async def interactive(
|
|
259
|
+
self,
|
|
260
|
+
agent_name: str | None = None,
|
|
261
|
+
default_prompt: str = "",
|
|
262
|
+
pretty_print_parallel: bool = False,
|
|
263
|
+
request_params: RequestParams | None = None,
|
|
264
|
+
) -> str:
|
|
265
|
+
"""
|
|
266
|
+
Interactive prompt for sending messages with advanced features.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
agent_name: Optional target agent name (uses default if not specified)
|
|
270
|
+
default: Default message to use when user presses enter
|
|
271
|
+
pretty_print_parallel: Enable clean parallel results display for parallel agents
|
|
272
|
+
request_params: Optional request parameters including MCP metadata
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
The result of the interactive session
|
|
276
|
+
"""
|
|
277
|
+
# Get the default agent name if none specified
|
|
278
|
+
if agent_name:
|
|
279
|
+
# Validate that this agent exists
|
|
280
|
+
if agent_name not in self._agents:
|
|
281
|
+
raise ValueError(f"Agent '{agent_name}' not found")
|
|
282
|
+
target_name = agent_name
|
|
283
|
+
else:
|
|
284
|
+
target_name = None
|
|
285
|
+
for agent in self._agents.values():
|
|
286
|
+
if agent.config.default:
|
|
287
|
+
target_name = agent.name
|
|
288
|
+
break
|
|
289
|
+
|
|
290
|
+
if not target_name:
|
|
291
|
+
# Use the first agent's name as default
|
|
292
|
+
target_name = next(iter(self._agents.keys()))
|
|
293
|
+
|
|
294
|
+
# Don't delegate to the agent's own prompt method - use our implementation
|
|
295
|
+
# The agent's prompt method doesn't fully support switching between agents
|
|
296
|
+
|
|
297
|
+
# Create agent_types dictionary mapping agent names to their types
|
|
298
|
+
agent_types = {name: agent.agent_type for name, agent in self._agents.items()}
|
|
299
|
+
|
|
300
|
+
# Create the interactive prompt
|
|
301
|
+
prompt = InteractivePrompt(agent_types=agent_types)
|
|
302
|
+
|
|
303
|
+
# Helper for pretty formatting the FINAL error
|
|
304
|
+
def _format_final_error(error: Exception) -> str:
|
|
305
|
+
detail = getattr(error, "message", None) or str(error)
|
|
306
|
+
detail = detail.strip() if isinstance(detail, str) else ""
|
|
307
|
+
clean_detail = detail.replace("\n", " ")
|
|
308
|
+
if len(clean_detail) > 300:
|
|
309
|
+
clean_detail = clean_detail[:297] + "..."
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
f"⚠️ **System Error:** The agent failed after repeated attempts.\n"
|
|
313
|
+
f"Error details: {clean_detail}\n"
|
|
314
|
+
f"\n*Your context is preserved. You can try sending the message again.*"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
async def send_wrapper(message, agent_name):
|
|
318
|
+
try:
|
|
319
|
+
# The LLM layer will handle the 10s/20s/30s retries internally.
|
|
320
|
+
return await self.send(message, agent_name, request_params)
|
|
321
|
+
|
|
322
|
+
except Exception as e:
|
|
323
|
+
# If we catch an exception here, it means all retries FAILED.
|
|
324
|
+
if isinstance(e, (KeyboardInterrupt, AgentConfigError, ServerConfigError)):
|
|
325
|
+
raise e
|
|
326
|
+
|
|
327
|
+
# Return pretty text for API failures (keeps session alive)
|
|
328
|
+
return _format_final_error(e)
|
|
329
|
+
|
|
330
|
+
return await prompt.prompt_loop(
|
|
331
|
+
send_func=send_wrapper,
|
|
332
|
+
default_agent=target_name, # Pass the agent name, not the agent object
|
|
333
|
+
available_agents=list(self._agents.keys()),
|
|
334
|
+
prompt_provider=self, # Pass self as the prompt provider
|
|
335
|
+
default=default_prompt,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def _show_turn_usage(self, agent_name: str) -> None:
|
|
339
|
+
"""Show subtle usage information after each turn."""
|
|
340
|
+
agent = self._agents.get(agent_name)
|
|
341
|
+
if not agent:
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
# Check if this is a parallel agent
|
|
345
|
+
if agent.agent_type == AgentType.PARALLEL:
|
|
346
|
+
self._show_parallel_agent_usage(agent)
|
|
347
|
+
else:
|
|
348
|
+
self._show_regular_agent_usage(agent)
|
|
349
|
+
|
|
350
|
+
def _show_regular_agent_usage(self, agent) -> None:
|
|
351
|
+
"""Show usage for a regular (non-parallel) agent."""
|
|
352
|
+
usage_info = self._format_agent_usage(agent)
|
|
353
|
+
if usage_info:
|
|
354
|
+
with progress_display.paused():
|
|
355
|
+
rich_print(
|
|
356
|
+
f"[dim]Last turn: {usage_info['display_text']}[/dim]{usage_info['cache_suffix']}"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def _show_parallel_agent_usage(self, parallel_agent) -> None:
|
|
360
|
+
"""Show usage for a parallel agent and its children."""
|
|
361
|
+
# Collect usage from all child agents
|
|
362
|
+
child_usage_data = []
|
|
363
|
+
total_input = 0
|
|
364
|
+
total_output = 0
|
|
365
|
+
total_tool_calls = 0
|
|
366
|
+
|
|
367
|
+
# Get usage from fan-out agents
|
|
368
|
+
if hasattr(parallel_agent, "fan_out_agents") and parallel_agent.fan_out_agents:
|
|
369
|
+
for child_agent in parallel_agent.fan_out_agents:
|
|
370
|
+
usage_info = self._format_agent_usage(child_agent)
|
|
371
|
+
if usage_info:
|
|
372
|
+
child_usage_data.append({**usage_info, "name": child_agent.name})
|
|
373
|
+
total_input += usage_info["input_tokens"]
|
|
374
|
+
total_output += usage_info["output_tokens"]
|
|
375
|
+
total_tool_calls += usage_info["tool_calls"]
|
|
376
|
+
|
|
377
|
+
# Get usage from fan-in agent
|
|
378
|
+
if hasattr(parallel_agent, "fan_in_agent") and parallel_agent.fan_in_agent:
|
|
379
|
+
usage_info = self._format_agent_usage(parallel_agent.fan_in_agent)
|
|
380
|
+
if usage_info:
|
|
381
|
+
child_usage_data.append({**usage_info, "name": parallel_agent.fan_in_agent.name})
|
|
382
|
+
total_input += usage_info["input_tokens"]
|
|
383
|
+
total_output += usage_info["output_tokens"]
|
|
384
|
+
total_tool_calls += usage_info["tool_calls"]
|
|
385
|
+
|
|
386
|
+
if not child_usage_data:
|
|
387
|
+
return
|
|
388
|
+
|
|
389
|
+
# Show aggregated usage for parallel agent (no context percentage)
|
|
390
|
+
with progress_display.paused():
|
|
391
|
+
tool_info = f", {total_tool_calls} tool calls" if total_tool_calls > 0 else ""
|
|
392
|
+
rich_print(
|
|
393
|
+
f"[dim]Last turn (parallel): {total_input:,} Input, {total_output:,} Output{tool_info}[/dim]"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# Show individual child agent usage
|
|
397
|
+
for i, usage_data in enumerate(child_usage_data):
|
|
398
|
+
is_last = i == len(child_usage_data) - 1
|
|
399
|
+
prefix = "└─" if is_last else "├─"
|
|
400
|
+
rich_print(
|
|
401
|
+
f"[dim] {prefix} {usage_data['name']}: {usage_data['display_text']}[/dim]{usage_data['cache_suffix']}"
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
def _format_agent_usage(self, agent) -> dict | None:
|
|
405
|
+
"""Format usage information for a single agent."""
|
|
406
|
+
if not agent or not agent.usage_accumulator:
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
# Get the last turn's usage (if any)
|
|
410
|
+
turns = agent.usage_accumulator.turns
|
|
411
|
+
if not turns:
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
last_turn = turns[-1]
|
|
415
|
+
input_tokens = last_turn.display_input_tokens
|
|
416
|
+
output_tokens = last_turn.output_tokens
|
|
417
|
+
|
|
418
|
+
# Build cache indicators with bright colors
|
|
419
|
+
cache_indicators = ""
|
|
420
|
+
if last_turn.cache_usage.cache_write_tokens > 0:
|
|
421
|
+
cache_indicators += "[bright_yellow]^[/bright_yellow]"
|
|
422
|
+
if (
|
|
423
|
+
last_turn.cache_usage.cache_read_tokens > 0
|
|
424
|
+
or last_turn.cache_usage.cache_hit_tokens > 0
|
|
425
|
+
):
|
|
426
|
+
cache_indicators += "[bright_green]*[/bright_green]"
|
|
427
|
+
|
|
428
|
+
# Build context percentage - get from accumulator, not individual turn
|
|
429
|
+
context_info = ""
|
|
430
|
+
context_percentage = agent.usage_accumulator.context_usage_percentage
|
|
431
|
+
if context_percentage is not None:
|
|
432
|
+
context_info = f" ({context_percentage:.1f}%)"
|
|
433
|
+
|
|
434
|
+
# Build tool call info
|
|
435
|
+
tool_info = f", {last_turn.tool_calls} tool calls" if last_turn.tool_calls > 0 else ""
|
|
436
|
+
|
|
437
|
+
# Build display text
|
|
438
|
+
display_text = f"{input_tokens:,} Input, {output_tokens:,} Output{tool_info}{context_info}"
|
|
439
|
+
cache_suffix = f" {cache_indicators}" if cache_indicators else ""
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
"input_tokens": input_tokens,
|
|
443
|
+
"output_tokens": output_tokens,
|
|
444
|
+
"tool_calls": last_turn.tool_calls,
|
|
445
|
+
"context_percentage": context_percentage,
|
|
446
|
+
"display_text": display_text,
|
|
447
|
+
"cache_suffix": cache_suffix,
|
|
448
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
6
|
+
|
|
7
|
+
from fast_agent.core.logging.logger import get_logger
|
|
8
|
+
from fast_agent.event_progress import ProgressAction
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
# Only imported for type checking to avoid circular imports at runtime
|
|
12
|
+
from os import PathLike
|
|
13
|
+
|
|
14
|
+
from fast_agent.config import Settings
|
|
15
|
+
from fast_agent.context import Context
|
|
16
|
+
from fast_agent.core.executor.workflow_signal import SignalWaitCallback
|
|
17
|
+
|
|
18
|
+
R = TypeVar("R")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Core:
|
|
22
|
+
"""
|
|
23
|
+
fast-agent core. handles application settings, config and context management.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
name: str = "fast-agent",
|
|
29
|
+
settings: Settings | None | str | PathLike[str] = None,
|
|
30
|
+
signal_notification: SignalWaitCallback | None = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Initialize the core.
|
|
34
|
+
Args:
|
|
35
|
+
name:
|
|
36
|
+
settings: If unspecified, the settings are loaded from fastagent.config.yaml.
|
|
37
|
+
If this is a string or path-like object, it is treated as the path to the config file to load.
|
|
38
|
+
signal_notification: Callback for getting notified on workflow signals/events.
|
|
39
|
+
"""
|
|
40
|
+
self.name = name
|
|
41
|
+
|
|
42
|
+
# We use these to initialize the context in initialize()
|
|
43
|
+
self._config_or_path = settings
|
|
44
|
+
self._signal_notification = signal_notification
|
|
45
|
+
|
|
46
|
+
self._logger = None
|
|
47
|
+
# Use forward reference for type to avoid runtime import
|
|
48
|
+
self._context: "Context" | None = None
|
|
49
|
+
self._initialized = False
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def context(self) -> "Context":
|
|
53
|
+
if self._context is None:
|
|
54
|
+
raise RuntimeError(
|
|
55
|
+
"Core not initialized, please call initialize() first, or use async with app.run()."
|
|
56
|
+
)
|
|
57
|
+
return self._context
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def config(self):
|
|
61
|
+
return self.context.config
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def server_registry(self):
|
|
65
|
+
return self.context.server_registry
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def logger(self):
|
|
69
|
+
if self._logger is None:
|
|
70
|
+
self._logger = get_logger(f"fast_agent.{self.name}")
|
|
71
|
+
return self._logger
|
|
72
|
+
|
|
73
|
+
async def initialize(self) -> None:
|
|
74
|
+
"""Initialize the fast-agent core. Sets up context (and therefore logging and loading settings)."""
|
|
75
|
+
if self._initialized:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Import here to avoid circular imports during module initialization
|
|
79
|
+
from fast_agent import context as _context_mod
|
|
80
|
+
|
|
81
|
+
self._context = await _context_mod.initialize_context(
|
|
82
|
+
self._config_or_path, store_globally=True
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Set the properties that were passed in the constructor
|
|
86
|
+
self._context.signal_notification = self._signal_notification
|
|
87
|
+
# Note: upstream_session support removed for now
|
|
88
|
+
|
|
89
|
+
self._initialized = True
|
|
90
|
+
self.logger.info(
|
|
91
|
+
"fast-agent initialized",
|
|
92
|
+
data={
|
|
93
|
+
"progress_action": "Running",
|
|
94
|
+
"target": self.name or "mcp_application",
|
|
95
|
+
"agent_name": self.name or "fast-agent core",
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async def cleanup(self) -> None:
|
|
100
|
+
"""Cleanup application resources."""
|
|
101
|
+
if not self._initialized:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
self.logger.info(
|
|
105
|
+
"fast-agent cleanup",
|
|
106
|
+
data={
|
|
107
|
+
"progress_action": ProgressAction.FINISHED,
|
|
108
|
+
"target": self.name or "fast-agent",
|
|
109
|
+
"agent_name": self.name or "fast-agent core",
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
try:
|
|
113
|
+
# Import here to avoid circular imports during module initialization
|
|
114
|
+
from fast_agent import context as _context_mod
|
|
115
|
+
|
|
116
|
+
await _context_mod.cleanup_context()
|
|
117
|
+
except asyncio.CancelledError:
|
|
118
|
+
self.logger.debug("Cleanup cancelled error during shutdown")
|
|
119
|
+
|
|
120
|
+
self._context = None
|
|
121
|
+
self._initialized = False
|
|
122
|
+
|
|
123
|
+
@asynccontextmanager
|
|
124
|
+
async def run(self):
|
|
125
|
+
"""
|
|
126
|
+
Use core for context management
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
async with core.run() as running_app:
|
|
130
|
+
# App is initialized here
|
|
131
|
+
pass
|
|
132
|
+
"""
|
|
133
|
+
await self.initialize()
|
|
134
|
+
try:
|
|
135
|
+
yield self
|
|
136
|
+
finally:
|
|
137
|
+
await self.cleanup()
|