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,271 @@
1
+ from typing import Any, Callable, Dict, List, Sequence
2
+
3
+ from mcp.server.fastmcp.tools.base import Tool as FastMCPTool
4
+ from mcp.types import CallToolResult, ListToolsResult, Tool
5
+
6
+ from fast_agent.agents.agent_types import AgentConfig
7
+ from fast_agent.agents.llm_agent import LlmAgent
8
+ from fast_agent.constants import (
9
+ DEFAULT_MAX_ITERATIONS,
10
+ FAST_AGENT_ERROR_CHANNEL,
11
+ HUMAN_INPUT_TOOL_NAME,
12
+ )
13
+ from fast_agent.context import Context
14
+ from fast_agent.core.logging.logger import get_logger
15
+ from fast_agent.mcp.helpers.content_helpers import text_content
16
+ from fast_agent.tools.elicitation import get_elicitation_fastmcp_tool
17
+ from fast_agent.types import PromptMessageExtended, RequestParams
18
+ from fast_agent.types.llm_stop_reason import LlmStopReason
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ class ToolAgent(LlmAgent):
24
+ """
25
+ A Tool Calling agent that uses FastMCP Tools for execution.
26
+
27
+ Pass either:
28
+ - FastMCP Tool objects (created via Tool.from_function)
29
+ - Regular Python functions (will be wrapped as FastMCP Tools)
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ config: AgentConfig,
35
+ tools: Sequence[FastMCPTool | Callable] = [],
36
+ context: Context | None = None,
37
+ ) -> None:
38
+ super().__init__(config=config, context=context)
39
+
40
+ self._execution_tools: dict[str, FastMCPTool] = {}
41
+ self._tool_schemas: list[Tool] = []
42
+
43
+ # Build a working list of tools and auto-inject human-input tool if missing
44
+ working_tools: list[FastMCPTool | Callable] = list(tools) if tools else []
45
+ # Only auto-inject if enabled via AgentConfig
46
+ if self.config.human_input:
47
+ existing_names = {
48
+ t.name if isinstance(t, FastMCPTool) else getattr(t, "__name__", "")
49
+ for t in working_tools
50
+ }
51
+ if HUMAN_INPUT_TOOL_NAME not in existing_names:
52
+ try:
53
+ working_tools.append(get_elicitation_fastmcp_tool())
54
+ except Exception as e:
55
+ logger.warning(f"Failed to initialize human-input tool: {e}")
56
+
57
+ for tool in working_tools:
58
+ (tool)
59
+ if isinstance(tool, FastMCPTool):
60
+ fast_tool = tool
61
+ elif callable(tool):
62
+ fast_tool = FastMCPTool.from_function(tool)
63
+ else:
64
+ logger.warning(f"Skipping unknown tool type: {type(tool)}")
65
+ continue
66
+
67
+ self._execution_tools[fast_tool.name] = fast_tool
68
+ # Create MCP Tool schema for the LLM interface
69
+ self._tool_schemas.append(
70
+ Tool(
71
+ name=fast_tool.name,
72
+ description=fast_tool.description,
73
+ inputSchema=fast_tool.parameters,
74
+ )
75
+ )
76
+
77
+ async def generate_impl(
78
+ self,
79
+ messages: List[PromptMessageExtended],
80
+ request_params: RequestParams | None = None,
81
+ tools: List[Tool] | None = None,
82
+ ) -> PromptMessageExtended:
83
+ """
84
+ Generate a response using the LLM, and handle tool calls if necessary.
85
+ Messages are already normalized to List[PromptMessageExtended].
86
+ """
87
+ if tools is None:
88
+ tools = (await self.list_tools()).tools
89
+
90
+ iterations = 0
91
+ max_iterations = request_params.max_iterations if request_params else DEFAULT_MAX_ITERATIONS
92
+
93
+ while True:
94
+ result = await super().generate_impl(
95
+ messages,
96
+ request_params=request_params,
97
+ tools=tools,
98
+ )
99
+
100
+ if LlmStopReason.TOOL_USE == result.stop_reason:
101
+ tool_message = await self.run_tools(result)
102
+ error_channel_messages = (tool_message.channels or {}).get(FAST_AGENT_ERROR_CHANNEL)
103
+ if error_channel_messages:
104
+ tool_result_contents = [
105
+ content
106
+ for tool_result in (tool_message.tool_results or {}).values()
107
+ for content in tool_result.content
108
+ ]
109
+ if tool_result_contents:
110
+ if result.content is None:
111
+ result.content = []
112
+ result.content.extend(tool_result_contents)
113
+ result.stop_reason = LlmStopReason.ERROR
114
+ break
115
+ if self.config.use_history:
116
+ messages = [tool_message]
117
+ else:
118
+ messages.extend([result, tool_message])
119
+ else:
120
+ break
121
+
122
+ iterations += 1
123
+ if iterations > max_iterations:
124
+ logger.warning("Max iterations reached, stopping tool loop")
125
+ break
126
+ return result
127
+
128
+ # we take care of tool results, so skip displaying them
129
+ def show_user_message(self, message: PromptMessageExtended) -> None:
130
+ if message.tool_results:
131
+ return
132
+ super().show_user_message(message)
133
+
134
+ async def run_tools(self, request: PromptMessageExtended) -> PromptMessageExtended:
135
+ """Runs the tools in the request, and returns a new User message with the results"""
136
+ import time
137
+
138
+ if not request.tool_calls:
139
+ logger.warning("No tool calls found in request", data=request)
140
+ return PromptMessageExtended(role="user", tool_results={})
141
+
142
+ tool_results: dict[str, CallToolResult] = {}
143
+ tool_timings: dict[str, float] = {} # Track timing for each tool call
144
+ tool_loop_error: str | None = None
145
+ # TODO -- use gather() for parallel results, update display
146
+ tool_schemas = (await self.list_tools()).tools
147
+ available_tools = [t.name for t in tool_schemas]
148
+ for correlation_id, tool_request in request.tool_calls.items():
149
+ tool_name = tool_request.params.name
150
+ tool_args = tool_request.params.arguments or {}
151
+
152
+ if tool_name not in available_tools and tool_name not in self._execution_tools:
153
+ error_message = f"Tool '{tool_name}' is not available"
154
+ logger.error(error_message)
155
+ tool_loop_error = self._mark_tool_loop_error(
156
+ correlation_id=correlation_id,
157
+ error_message=error_message,
158
+ tool_results=tool_results,
159
+ )
160
+ break
161
+
162
+ # Find the index of the current tool in available_tools for highlighting
163
+ highlight_index = None
164
+ try:
165
+ highlight_index = available_tools.index(tool_name)
166
+ except ValueError:
167
+ # Tool not found in list, no highlighting
168
+ pass
169
+
170
+ self.display.show_tool_call(
171
+ name=self.name,
172
+ tool_args=tool_args,
173
+ bottom_items=available_tools,
174
+ tool_name=tool_name,
175
+ highlight_index=highlight_index,
176
+ max_item_length=12,
177
+ )
178
+
179
+ # Track timing for tool execution
180
+ start_time = time.perf_counter()
181
+ result = await self.call_tool(tool_name, tool_args)
182
+ end_time = time.perf_counter()
183
+ duration_ms = round((end_time - start_time) * 1000, 2)
184
+
185
+ tool_results[correlation_id] = result
186
+ # Store timing info (transport_channel not available for local tools)
187
+ tool_timings[correlation_id] = {
188
+ "timing_ms": duration_ms,
189
+ "transport_channel": None
190
+ }
191
+ self.display.show_tool_result(name=self.name, result=result, tool_name=tool_name, timing_ms=duration_ms)
192
+
193
+ return self._finalize_tool_results(tool_results, tool_timings=tool_timings, tool_loop_error=tool_loop_error)
194
+
195
+ def _mark_tool_loop_error(
196
+ self,
197
+ *,
198
+ correlation_id: str,
199
+ error_message: str,
200
+ tool_results: dict[str, CallToolResult],
201
+ ) -> str:
202
+ error_result = CallToolResult(
203
+ content=[text_content(error_message)],
204
+ isError=True,
205
+ )
206
+ tool_results[correlation_id] = error_result
207
+ self.display.show_tool_result(name=self.name, result=error_result)
208
+ return error_message
209
+
210
+ def _finalize_tool_results(
211
+ self,
212
+ tool_results: dict[str, CallToolResult],
213
+ *,
214
+ tool_timings: dict[str, dict[str, float | str | None]] | None = None,
215
+ tool_loop_error: str | None = None,
216
+ ) -> PromptMessageExtended:
217
+ import json
218
+
219
+ from mcp.types import TextContent
220
+
221
+ from fast_agent.constants import FAST_AGENT_TOOL_TIMING
222
+
223
+ channels = None
224
+ content = []
225
+ if tool_loop_error:
226
+ content.append(text_content(tool_loop_error))
227
+ channels = {
228
+ FAST_AGENT_ERROR_CHANNEL: [text_content(tool_loop_error)],
229
+ }
230
+
231
+ # Add tool timing data to channels
232
+ if tool_timings:
233
+ if channels is None:
234
+ channels = {}
235
+ channels[FAST_AGENT_TOOL_TIMING] = [
236
+ TextContent(type="text", text=json.dumps(tool_timings))
237
+ ]
238
+
239
+ return PromptMessageExtended(
240
+ role="user",
241
+ content=content,
242
+ tool_results=tool_results,
243
+ channels=channels,
244
+ )
245
+
246
+ async def list_tools(self) -> ListToolsResult:
247
+ """Return available tools for this agent. Overridable by subclasses."""
248
+ return ListToolsResult(tools=list(self._tool_schemas))
249
+
250
+ async def call_tool(self, name: str, arguments: Dict[str, Any] | None = None) -> CallToolResult:
251
+ """Execute a tool by name using local FastMCP tools. Overridable by subclasses."""
252
+ fast_tool = self._execution_tools.get(name)
253
+ if not fast_tool:
254
+ logger.warning(f"Unknown tool: {name}")
255
+ return CallToolResult(
256
+ content=[text_content(f"Unknown tool: {name}")],
257
+ isError=True,
258
+ )
259
+
260
+ try:
261
+ result = await fast_tool.run(arguments or {}, convert_result=False)
262
+ return CallToolResult(
263
+ content=[text_content(str(result))],
264
+ isError=False,
265
+ )
266
+ except Exception as e:
267
+ logger.error(f"Tool {name} failed: {e}")
268
+ return CallToolResult(
269
+ content=[text_content(f"Error: {str(e)}")],
270
+ isError=True,
271
+ )