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.
Files changed (261) hide show
  1. fast_agent/__init__.py +183 -0
  2. fast_agent/acp/__init__.py +19 -0
  3. fast_agent/acp/acp_aware_mixin.py +304 -0
  4. fast_agent/acp/acp_context.py +437 -0
  5. fast_agent/acp/content_conversion.py +136 -0
  6. fast_agent/acp/filesystem_runtime.py +427 -0
  7. fast_agent/acp/permission_store.py +269 -0
  8. fast_agent/acp/server/__init__.py +5 -0
  9. fast_agent/acp/server/agent_acp_server.py +1472 -0
  10. fast_agent/acp/slash_commands.py +1050 -0
  11. fast_agent/acp/terminal_runtime.py +408 -0
  12. fast_agent/acp/tool_permission_adapter.py +125 -0
  13. fast_agent/acp/tool_permissions.py +474 -0
  14. fast_agent/acp/tool_progress.py +814 -0
  15. fast_agent/agents/__init__.py +85 -0
  16. fast_agent/agents/agent_types.py +64 -0
  17. fast_agent/agents/llm_agent.py +350 -0
  18. fast_agent/agents/llm_decorator.py +1139 -0
  19. fast_agent/agents/mcp_agent.py +1337 -0
  20. fast_agent/agents/tool_agent.py +271 -0
  21. fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
  22. fast_agent/agents/workflow/chain_agent.py +212 -0
  23. fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
  24. fast_agent/agents/workflow/iterative_planner.py +652 -0
  25. fast_agent/agents/workflow/maker_agent.py +379 -0
  26. fast_agent/agents/workflow/orchestrator_models.py +218 -0
  27. fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
  28. fast_agent/agents/workflow/parallel_agent.py +250 -0
  29. fast_agent/agents/workflow/router_agent.py +353 -0
  30. fast_agent/cli/__init__.py +0 -0
  31. fast_agent/cli/__main__.py +73 -0
  32. fast_agent/cli/commands/acp.py +159 -0
  33. fast_agent/cli/commands/auth.py +404 -0
  34. fast_agent/cli/commands/check_config.py +783 -0
  35. fast_agent/cli/commands/go.py +514 -0
  36. fast_agent/cli/commands/quickstart.py +557 -0
  37. fast_agent/cli/commands/serve.py +143 -0
  38. fast_agent/cli/commands/server_helpers.py +114 -0
  39. fast_agent/cli/commands/setup.py +174 -0
  40. fast_agent/cli/commands/url_parser.py +190 -0
  41. fast_agent/cli/constants.py +40 -0
  42. fast_agent/cli/main.py +115 -0
  43. fast_agent/cli/terminal.py +24 -0
  44. fast_agent/config.py +798 -0
  45. fast_agent/constants.py +41 -0
  46. fast_agent/context.py +279 -0
  47. fast_agent/context_dependent.py +50 -0
  48. fast_agent/core/__init__.py +92 -0
  49. fast_agent/core/agent_app.py +448 -0
  50. fast_agent/core/core_app.py +137 -0
  51. fast_agent/core/direct_decorators.py +784 -0
  52. fast_agent/core/direct_factory.py +620 -0
  53. fast_agent/core/error_handling.py +27 -0
  54. fast_agent/core/exceptions.py +90 -0
  55. fast_agent/core/executor/__init__.py +0 -0
  56. fast_agent/core/executor/executor.py +280 -0
  57. fast_agent/core/executor/task_registry.py +32 -0
  58. fast_agent/core/executor/workflow_signal.py +324 -0
  59. fast_agent/core/fastagent.py +1186 -0
  60. fast_agent/core/logging/__init__.py +5 -0
  61. fast_agent/core/logging/events.py +138 -0
  62. fast_agent/core/logging/json_serializer.py +164 -0
  63. fast_agent/core/logging/listeners.py +309 -0
  64. fast_agent/core/logging/logger.py +278 -0
  65. fast_agent/core/logging/transport.py +481 -0
  66. fast_agent/core/prompt.py +9 -0
  67. fast_agent/core/prompt_templates.py +183 -0
  68. fast_agent/core/validation.py +326 -0
  69. fast_agent/event_progress.py +62 -0
  70. fast_agent/history/history_exporter.py +49 -0
  71. fast_agent/human_input/__init__.py +47 -0
  72. fast_agent/human_input/elicitation_handler.py +123 -0
  73. fast_agent/human_input/elicitation_state.py +33 -0
  74. fast_agent/human_input/form_elements.py +59 -0
  75. fast_agent/human_input/form_fields.py +256 -0
  76. fast_agent/human_input/simple_form.py +113 -0
  77. fast_agent/human_input/types.py +40 -0
  78. fast_agent/interfaces.py +310 -0
  79. fast_agent/llm/__init__.py +9 -0
  80. fast_agent/llm/cancellation.py +22 -0
  81. fast_agent/llm/fastagent_llm.py +931 -0
  82. fast_agent/llm/internal/passthrough.py +161 -0
  83. fast_agent/llm/internal/playback.py +129 -0
  84. fast_agent/llm/internal/silent.py +41 -0
  85. fast_agent/llm/internal/slow.py +38 -0
  86. fast_agent/llm/memory.py +275 -0
  87. fast_agent/llm/model_database.py +490 -0
  88. fast_agent/llm/model_factory.py +388 -0
  89. fast_agent/llm/model_info.py +102 -0
  90. fast_agent/llm/prompt_utils.py +155 -0
  91. fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
  92. fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
  93. fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
  94. fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
  95. fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
  96. fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
  97. fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
  98. fast_agent/llm/provider/google/google_converter.py +466 -0
  99. fast_agent/llm/provider/google/llm_google_native.py +681 -0
  100. fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
  101. fast_agent/llm/provider/openai/llm_azure.py +143 -0
  102. fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
  103. fast_agent/llm/provider/openai/llm_generic.py +35 -0
  104. fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
  105. fast_agent/llm/provider/openai/llm_groq.py +42 -0
  106. fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
  107. fast_agent/llm/provider/openai/llm_openai.py +1195 -0
  108. fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
  109. fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
  110. fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
  111. fast_agent/llm/provider/openai/llm_xai.py +38 -0
  112. fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
  113. fast_agent/llm/provider/openai/openai_multipart.py +169 -0
  114. fast_agent/llm/provider/openai/openai_utils.py +67 -0
  115. fast_agent/llm/provider/openai/responses.py +133 -0
  116. fast_agent/llm/provider_key_manager.py +139 -0
  117. fast_agent/llm/provider_types.py +34 -0
  118. fast_agent/llm/request_params.py +61 -0
  119. fast_agent/llm/sampling_converter.py +98 -0
  120. fast_agent/llm/stream_types.py +9 -0
  121. fast_agent/llm/usage_tracking.py +445 -0
  122. fast_agent/mcp/__init__.py +56 -0
  123. fast_agent/mcp/common.py +26 -0
  124. fast_agent/mcp/elicitation_factory.py +84 -0
  125. fast_agent/mcp/elicitation_handlers.py +164 -0
  126. fast_agent/mcp/gen_client.py +83 -0
  127. fast_agent/mcp/helpers/__init__.py +36 -0
  128. fast_agent/mcp/helpers/content_helpers.py +352 -0
  129. fast_agent/mcp/helpers/server_config_helpers.py +25 -0
  130. fast_agent/mcp/hf_auth.py +147 -0
  131. fast_agent/mcp/interfaces.py +92 -0
  132. fast_agent/mcp/logger_textio.py +108 -0
  133. fast_agent/mcp/mcp_agent_client_session.py +411 -0
  134. fast_agent/mcp/mcp_aggregator.py +2175 -0
  135. fast_agent/mcp/mcp_connection_manager.py +723 -0
  136. fast_agent/mcp/mcp_content.py +262 -0
  137. fast_agent/mcp/mime_utils.py +108 -0
  138. fast_agent/mcp/oauth_client.py +509 -0
  139. fast_agent/mcp/prompt.py +159 -0
  140. fast_agent/mcp/prompt_message_extended.py +155 -0
  141. fast_agent/mcp/prompt_render.py +84 -0
  142. fast_agent/mcp/prompt_serialization.py +580 -0
  143. fast_agent/mcp/prompts/__init__.py +0 -0
  144. fast_agent/mcp/prompts/__main__.py +7 -0
  145. fast_agent/mcp/prompts/prompt_constants.py +18 -0
  146. fast_agent/mcp/prompts/prompt_helpers.py +238 -0
  147. fast_agent/mcp/prompts/prompt_load.py +186 -0
  148. fast_agent/mcp/prompts/prompt_server.py +552 -0
  149. fast_agent/mcp/prompts/prompt_template.py +438 -0
  150. fast_agent/mcp/resource_utils.py +215 -0
  151. fast_agent/mcp/sampling.py +200 -0
  152. fast_agent/mcp/server/__init__.py +4 -0
  153. fast_agent/mcp/server/agent_server.py +613 -0
  154. fast_agent/mcp/skybridge.py +44 -0
  155. fast_agent/mcp/sse_tracking.py +287 -0
  156. fast_agent/mcp/stdio_tracking_simple.py +59 -0
  157. fast_agent/mcp/streamable_http_tracking.py +309 -0
  158. fast_agent/mcp/tool_execution_handler.py +137 -0
  159. fast_agent/mcp/tool_permission_handler.py +88 -0
  160. fast_agent/mcp/transport_tracking.py +634 -0
  161. fast_agent/mcp/types.py +24 -0
  162. fast_agent/mcp/ui_agent.py +48 -0
  163. fast_agent/mcp/ui_mixin.py +209 -0
  164. fast_agent/mcp_server_registry.py +89 -0
  165. fast_agent/py.typed +0 -0
  166. fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
  167. fast_agent/resources/examples/data-analysis/analysis.py +68 -0
  168. fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
  169. fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
  170. fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
  171. fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
  172. fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
  173. fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
  174. fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
  175. fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
  176. fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
  177. fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
  178. fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
  179. fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
  180. fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
  181. fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
  182. fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
  183. fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
  184. fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
  185. fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
  186. fast_agent/resources/examples/researcher/researcher.py +36 -0
  187. fast_agent/resources/examples/tensorzero/.env.sample +2 -0
  188. fast_agent/resources/examples/tensorzero/Makefile +31 -0
  189. fast_agent/resources/examples/tensorzero/README.md +56 -0
  190. fast_agent/resources/examples/tensorzero/agent.py +35 -0
  191. fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  192. fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
  193. fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  194. fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
  195. fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
  196. fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
  197. fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
  198. fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
  199. fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
  200. fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
  201. fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
  202. fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
  203. fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
  204. fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
  205. fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
  206. fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
  207. fast_agent/resources/examples/workflows/chaining.py +37 -0
  208. fast_agent/resources/examples/workflows/evaluator.py +77 -0
  209. fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
  210. fast_agent/resources/examples/workflows/graded_report.md +89 -0
  211. fast_agent/resources/examples/workflows/human_input.py +28 -0
  212. fast_agent/resources/examples/workflows/maker.py +156 -0
  213. fast_agent/resources/examples/workflows/orchestrator.py +70 -0
  214. fast_agent/resources/examples/workflows/parallel.py +56 -0
  215. fast_agent/resources/examples/workflows/router.py +69 -0
  216. fast_agent/resources/examples/workflows/short_story.md +13 -0
  217. fast_agent/resources/examples/workflows/short_story.txt +19 -0
  218. fast_agent/resources/setup/.gitignore +30 -0
  219. fast_agent/resources/setup/agent.py +28 -0
  220. fast_agent/resources/setup/fastagent.config.yaml +65 -0
  221. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  222. fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
  223. fast_agent/skills/__init__.py +9 -0
  224. fast_agent/skills/registry.py +235 -0
  225. fast_agent/tools/elicitation.py +369 -0
  226. fast_agent/tools/shell_runtime.py +402 -0
  227. fast_agent/types/__init__.py +59 -0
  228. fast_agent/types/conversation_summary.py +294 -0
  229. fast_agent/types/llm_stop_reason.py +78 -0
  230. fast_agent/types/message_search.py +249 -0
  231. fast_agent/ui/__init__.py +38 -0
  232. fast_agent/ui/console.py +59 -0
  233. fast_agent/ui/console_display.py +1080 -0
  234. fast_agent/ui/elicitation_form.py +946 -0
  235. fast_agent/ui/elicitation_style.py +59 -0
  236. fast_agent/ui/enhanced_prompt.py +1400 -0
  237. fast_agent/ui/history_display.py +734 -0
  238. fast_agent/ui/interactive_prompt.py +1199 -0
  239. fast_agent/ui/markdown_helpers.py +104 -0
  240. fast_agent/ui/markdown_truncator.py +1004 -0
  241. fast_agent/ui/mcp_display.py +857 -0
  242. fast_agent/ui/mcp_ui_utils.py +235 -0
  243. fast_agent/ui/mermaid_utils.py +169 -0
  244. fast_agent/ui/message_primitives.py +50 -0
  245. fast_agent/ui/notification_tracker.py +205 -0
  246. fast_agent/ui/plain_text_truncator.py +68 -0
  247. fast_agent/ui/progress_display.py +10 -0
  248. fast_agent/ui/rich_progress.py +195 -0
  249. fast_agent/ui/streaming.py +774 -0
  250. fast_agent/ui/streaming_buffer.py +449 -0
  251. fast_agent/ui/tool_display.py +422 -0
  252. fast_agent/ui/usage_display.py +204 -0
  253. fast_agent/utils/__init__.py +5 -0
  254. fast_agent/utils/reasoning_stream_parser.py +77 -0
  255. fast_agent/utils/time.py +22 -0
  256. fast_agent/workflow_telemetry.py +261 -0
  257. fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
  258. fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
  259. fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
  260. fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
  261. 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()