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,353 @@
1
+ """
2
+ Router agent implementation using the BaseAgent adapter pattern.
3
+
4
+ This provides a simplified implementation that routes messages to agents
5
+ by determining the best agent for a request and dispatching to it.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, List, Optional, Tuple, Type
9
+
10
+ from mcp import Tool
11
+ from opentelemetry import trace
12
+ from pydantic import BaseModel
13
+
14
+ from fast_agent.agents.agent_types import AgentConfig, AgentType
15
+ from fast_agent.agents.llm_agent import LlmAgent
16
+ from fast_agent.core.exceptions import AgentConfigError
17
+ from fast_agent.core.logging.logger import get_logger
18
+ from fast_agent.core.prompt import Prompt
19
+ from fast_agent.interfaces import FastAgentLLMProtocol, LLMFactoryProtocol, ModelT
20
+ from fast_agent.types import PromptMessageExtended, RequestParams
21
+
22
+ if TYPE_CHECKING:
23
+ from a2a.types import AgentCard
24
+
25
+ from fast_agent.context import Context
26
+
27
+ logger = get_logger(__name__)
28
+
29
+ # Simple system instruction for the router
30
+ ROUTING_SYSTEM_INSTRUCTION = """
31
+ You are a highly accurate request router that directs incoming requests to the most appropriate agent.
32
+ Analyze each request and determine which specialized agent would be best suited to handle it based on their capabilities.
33
+
34
+ Follow these guidelines:
35
+ - Carefully match the request's needs with each agent's capabilities and description
36
+ - Select the single most appropriate agent for the request
37
+ - Provide your confidence level (high, medium, low) and brief reasoning for your selection
38
+ """
39
+
40
+ # Default routing instruction with placeholders for context (AgentCard JSON)
41
+ ROUTING_AGENT_INSTRUCTION = """
42
+ Select from the following agents to handle the request:
43
+ <fastagent:agents>
44
+ [
45
+ {context}
46
+ ]
47
+ </fastagent:agents>
48
+
49
+ You must respond with the 'name' of one of the agents listed above.
50
+
51
+ """
52
+
53
+
54
+ class RoutingResponse(BaseModel):
55
+ """Model for the structured routing response from the LLM."""
56
+
57
+ agent: str
58
+ confidence: str
59
+ reasoning: str | None = None
60
+
61
+
62
+ class RouterAgent(LlmAgent):
63
+ """
64
+ A simplified router that uses an LLM to determine the best agent for a request,
65
+ then dispatches the request to that agent and returns the response.
66
+ """
67
+
68
+ @property
69
+ def agent_type(self) -> AgentType:
70
+ """Return the type of this agent."""
71
+ return AgentType.ROUTER
72
+
73
+ def __init__(
74
+ self,
75
+ config: AgentConfig,
76
+ agents: List[LlmAgent],
77
+ routing_instruction: str | None = None,
78
+ context: "Context | None" = None,
79
+ default_request_params: RequestParams | None = None,
80
+ **kwargs,
81
+ ) -> None:
82
+ """
83
+ Initialize a RouterAgent.
84
+
85
+ Args:
86
+ config: Agent configuration or name
87
+ agents: List of agents to route between
88
+ routing_instruction: Optional custom routing instruction
89
+ context: Optional application context
90
+ default_request_params: Optional default request parameters
91
+ **kwargs: Additional keyword arguments to pass to BaseAgent
92
+ """
93
+ super().__init__(config=config, context=context, **kwargs)
94
+
95
+ if not agents:
96
+ raise AgentConfigError("At least one agent must be provided")
97
+
98
+ self.agents = agents
99
+ self.routing_instruction = routing_instruction
100
+ self.agent_map = {agent.name: agent for agent in agents}
101
+
102
+ # Set up base router request parameters with just the base instruction for now
103
+ base_params = {"systemPrompt": ROUTING_SYSTEM_INSTRUCTION, "use_history": False}
104
+
105
+ if default_request_params:
106
+ merged_params = default_request_params.model_copy(update=base_params)
107
+ else:
108
+ merged_params = RequestParams(**base_params)
109
+
110
+ self._default_request_params = merged_params
111
+
112
+ async def initialize(self) -> None:
113
+ """Initialize the router and all agents."""
114
+ if not self.initialized:
115
+ await super().initialize()
116
+
117
+ # Initialize all agents if not already initialized
118
+ for agent in self.agents:
119
+ if not agent.initialized:
120
+ await agent.initialize()
121
+
122
+ complete_routing_instruction = await self._generate_routing_instruction(
123
+ self.agents, self.routing_instruction
124
+ )
125
+
126
+ # Update the system prompt to include the routing instruction with agent cards
127
+ combined_system_prompt = (
128
+ ROUTING_SYSTEM_INSTRUCTION + "\n\n" + complete_routing_instruction
129
+ )
130
+ self._default_request_params.systemPrompt = combined_system_prompt
131
+ self.instruction = combined_system_prompt
132
+
133
+ async def shutdown(self) -> None:
134
+ """Shutdown the router and all agents."""
135
+ await super().shutdown()
136
+
137
+ # Shutdown all agents
138
+ for agent in self.agents:
139
+ try:
140
+ await agent.shutdown()
141
+ except Exception as e:
142
+ logger.warning(f"Error shutting down agent: {str(e)}")
143
+
144
+ @staticmethod
145
+ async def _generate_routing_instruction(
146
+ agents: List[LlmAgent], routing_instruction: Optional[str] = None
147
+ ) -> str:
148
+ """
149
+ Generate the complete routing instruction with agent cards.
150
+
151
+ Args:
152
+ agents: List of agents to include in routing instruction
153
+ routing_instruction: Optional custom routing instruction template
154
+
155
+ Returns:
156
+ Complete routing instruction with agent cards formatted
157
+ """
158
+ # Generate agent descriptions
159
+ agent_descriptions = []
160
+ for agent in agents:
161
+ agent_card: AgentCard = await agent.agent_card()
162
+ agent_descriptions.append(
163
+ agent_card.model_dump_json(
164
+ include={"name", "description", "skills"}, exclude_none=True
165
+ )
166
+ )
167
+
168
+ context = ",\n".join(agent_descriptions)
169
+
170
+ # Format the routing instruction
171
+ instruction_template = routing_instruction or ROUTING_AGENT_INSTRUCTION
172
+ return instruction_template.format(context=context)
173
+
174
+ async def attach_llm(
175
+ self,
176
+ llm_factory: LLMFactoryProtocol,
177
+ model: str | None = None,
178
+ request_params: RequestParams | None = None,
179
+ **additional_kwargs,
180
+ ) -> FastAgentLLMProtocol:
181
+ return await super().attach_llm(
182
+ llm_factory, model, request_params, verb="Routing", **additional_kwargs
183
+ )
184
+
185
+ async def generate_impl(
186
+ self,
187
+ messages: List[PromptMessageExtended],
188
+ request_params: Optional[RequestParams] = None,
189
+ tools: List[Tool] | None = None,
190
+ ) -> PromptMessageExtended:
191
+ """
192
+ Route the request to the most appropriate agent and return its response.
193
+
194
+ Args:
195
+ normalized_messages: Already normalized list of PromptMessageExtended
196
+ request_params: Optional request parameters
197
+
198
+ Returns:
199
+ The response from the selected agent
200
+ """
201
+
202
+ # implementation note. the duplication between generated and structured
203
+ # is probably the most readable. alt could be a _get_route_agent or
204
+ # some form of dynamic dispatch.. but only if this gets more complex
205
+ tracer = trace.get_tracer(__name__)
206
+ with tracer.start_as_current_span(f"Routing: '{self.name}' generate"):
207
+ route, warn = await self._route_request(messages[-1])
208
+
209
+ if not route:
210
+ return Prompt.assistant(warn or "No routing result or warning received")
211
+
212
+ # Get the selected agent
213
+ agent: LlmAgent = self.agent_map[route.agent]
214
+
215
+ # Dispatch the request to the selected agent
216
+ # discarded request_params: use llm defaults for subagents
217
+ telemetry_arguments = {
218
+ "agent": route.agent,
219
+ "confidence": route.confidence,
220
+ "reasoning": route.reasoning,
221
+ }
222
+ async with self.workflow_telemetry.start_step(
223
+ "router.delegate",
224
+ server_name=self.name,
225
+ arguments=telemetry_arguments,
226
+ ) as step:
227
+ if route.reasoning:
228
+ await step.update(message=route.reasoning)
229
+ result = await agent.generate_impl(messages)
230
+ await step.finish(
231
+ True,
232
+ text=f"Delegated to {agent.name}",
233
+ )
234
+ return result
235
+
236
+ async def structured_impl(
237
+ self,
238
+ messages: List[PromptMessageExtended],
239
+ model: Type[ModelT],
240
+ request_params: Optional[RequestParams] = None,
241
+ ) -> Tuple[ModelT | None, PromptMessageExtended]:
242
+ """
243
+ Route the request to the most appropriate agent and parse its response.
244
+
245
+ Args:
246
+ messages: Messages to route
247
+ model: Pydantic model to parse the response into
248
+ request_params: Optional request parameters
249
+
250
+ Returns:
251
+ The parsed response from the selected agent, or None if parsing fails
252
+ """
253
+
254
+ tracer = trace.get_tracer(__name__)
255
+ with tracer.start_as_current_span(f"Routing: '{self.name}' structured"):
256
+ route, warn = await self._route_request(messages[-1])
257
+
258
+ if not route:
259
+ return None, Prompt.assistant(
260
+ warn or "No routing result or warning received (structured)"
261
+ )
262
+
263
+ # Get the selected agent
264
+ agent: LlmAgent = self.agent_map[route.agent]
265
+
266
+ # Dispatch the request to the selected agent
267
+ telemetry_arguments = {
268
+ "agent": route.agent,
269
+ "confidence": route.confidence,
270
+ "reasoning": route.reasoning,
271
+ }
272
+ async with self.workflow_telemetry.start_step(
273
+ "router.delegate_structured",
274
+ server_name=self.name,
275
+ arguments=telemetry_arguments,
276
+ ) as step:
277
+ if route.reasoning:
278
+ await step.update(message=route.reasoning)
279
+ structured_response = await agent.structured_impl(messages, model, request_params)
280
+ await step.finish(
281
+ True,
282
+ text=f"{agent.name} produced structured output",
283
+ )
284
+ return structured_response
285
+
286
+ async def _route_request(
287
+ self, message: PromptMessageExtended
288
+ ) -> Tuple[RoutingResponse | None, str | None]:
289
+ """
290
+ Determine which agent to route the request to.
291
+
292
+ Args:
293
+ request: The request to route
294
+
295
+ Returns:
296
+ RouterResult containing the selected agent, or None if no suitable agent was found
297
+ """
298
+ if not self.agents:
299
+ logger.error("No agents available for routing")
300
+ raise AgentConfigError("No agents available for routing - fatal error")
301
+
302
+ # go straight to agent if only one available
303
+ if len(self.agents) == 1:
304
+ return RoutingResponse(
305
+ agent=self.agents[0].name, confidence="high", reasoning="Only one agent available"
306
+ ), None
307
+
308
+ assert self._llm
309
+ # Display the user's routing request
310
+ self.display.show_user_message(message.first_text(), name=self.name)
311
+
312
+ # No need to add routing instruction here - it's already in the system prompt
313
+ response, _ = await self._llm.structured(
314
+ [message],
315
+ RoutingResponse,
316
+ self._default_request_params,
317
+ )
318
+
319
+ warn: str | None = None
320
+ if not response:
321
+ warn = "No routing response received from LLM"
322
+ elif response.agent not in self.agent_map:
323
+ warn = f"A response was received, but the agent {response.agent} was not known to the Router"
324
+
325
+ if warn:
326
+ logger.warning(warn)
327
+ return None, warn
328
+ else:
329
+ assert response
330
+ logger.info(
331
+ f"Routing structured request to agent: {response.agent or 'error'} (confidence: {response.confidence or ''})"
332
+ )
333
+
334
+ routing_message = f"Routing to: {response.agent}"
335
+ if response.reasoning:
336
+ routing_message += f" ({response.reasoning})"
337
+
338
+ # Convert highlight_items to highlight_index
339
+ agent_keys = list(self.agent_map.keys())
340
+ highlight_index = None
341
+ try:
342
+ highlight_index = agent_keys.index(response.agent)
343
+ except ValueError:
344
+ pass
345
+
346
+ await self.display.show_assistant_message(
347
+ routing_message,
348
+ bottom_items=agent_keys,
349
+ highlight_index=highlight_index,
350
+ name=self.name,
351
+ )
352
+
353
+ return response, None
File without changes
@@ -0,0 +1,73 @@
1
+ import asyncio
2
+ import json
3
+ import sys
4
+
5
+ from fast_agent.cli.constants import GO_SPECIFIC_OPTIONS, KNOWN_SUBCOMMANDS
6
+ from fast_agent.cli.main import app
7
+
8
+ # if the arguments would work with "go" we'll just route to it
9
+
10
+
11
+ def main():
12
+ """Main entry point that handles auto-routing to 'go' command."""
13
+ try:
14
+ loop = asyncio.get_event_loop()
15
+
16
+ def _log_asyncio_exception(loop: asyncio.AbstractEventLoop, context: dict) -> None:
17
+ import logging
18
+
19
+ logger = logging.getLogger("fast_agent.asyncio")
20
+
21
+ message = context.get("message", "(no message)")
22
+ task = context.get("task")
23
+ future = context.get("future")
24
+ handle = context.get("handle")
25
+ source_traceback = context.get("source_traceback")
26
+ exception = context.get("exception")
27
+
28
+ details = {
29
+ "message": message,
30
+ "task": repr(task) if task else None,
31
+ "future": repr(future) if future else None,
32
+ "handle": repr(handle) if handle else None,
33
+ "source_traceback": [str(frame) for frame in source_traceback] if source_traceback else None,
34
+ }
35
+
36
+ logger.error("Unhandled asyncio error: %s", message)
37
+ logger.error("Asyncio context: %s", json.dumps(details, indent=2))
38
+
39
+ if exception:
40
+ logger.exception("Asyncio exception", exc_info=exception)
41
+
42
+ loop.set_exception_handler(_log_asyncio_exception)
43
+ except RuntimeError:
44
+ # No running loop yet (rare for sync entry), safe to ignore
45
+ pass
46
+ # Check if we should auto-route to 'go'
47
+ if len(sys.argv) > 1:
48
+ # Check if first arg is not already a subcommand
49
+ first_arg = sys.argv[1]
50
+
51
+ # Only auto-route if any known go-specific options are present
52
+ has_go_options = any(
53
+ (arg in GO_SPECIFIC_OPTIONS) or any(arg.startswith(opt + "=") for opt in GO_SPECIFIC_OPTIONS)
54
+ for arg in sys.argv[1:]
55
+ )
56
+
57
+ if first_arg not in KNOWN_SUBCOMMANDS and has_go_options:
58
+ # Find where to insert 'go' - before the first go-specific option
59
+ insert_pos = 1
60
+ for i, arg in enumerate(sys.argv[1:], 1):
61
+ if (arg in GO_SPECIFIC_OPTIONS) or any(
62
+ arg.startswith(opt + "=") for opt in GO_SPECIFIC_OPTIONS
63
+ ):
64
+ insert_pos = i
65
+ break
66
+ # Auto-route to go command
67
+ sys.argv.insert(insert_pos, "go")
68
+
69
+ app()
70
+
71
+
72
+ if __name__ == "__main__":
73
+ main()
@@ -0,0 +1,159 @@
1
+ """Dedicated entry point for running FastAgent in ACP mode."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from fast_agent.cli.commands import serve
9
+ from fast_agent.cli.commands.go import (
10
+ collect_stdio_commands,
11
+ resolve_instruction_option,
12
+ run_async_agent,
13
+ )
14
+
15
+ app = typer.Typer(
16
+ help="Run FastAgent as an ACP stdio server without specifying --transport=acp explicitly.",
17
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
18
+ )
19
+
20
+ ROOT_SUBCOMMANDS = {
21
+ "go",
22
+ "serve",
23
+ "setup",
24
+ "check",
25
+ "auth",
26
+ "bootstrap",
27
+ "quickstart",
28
+ }
29
+
30
+
31
+ @app.callback(invoke_without_command=True, no_args_is_help=False)
32
+ def run_acp(
33
+ ctx: typer.Context,
34
+ name: str = typer.Option("fast-agent-acp", "--name", help="Name for the ACP server"),
35
+ instruction: str | None = typer.Option(
36
+ None, "--instruction", "-i", help="Path to file or URL containing instruction for the agent"
37
+ ),
38
+ config_path: str | None = typer.Option(None, "--config-path", "-c", help="Path to config file"),
39
+ servers: str | None = typer.Option(
40
+ None, "--servers", help="Comma-separated list of server names to enable from config"
41
+ ),
42
+ urls: str | None = typer.Option(
43
+ None, "--url", help="Comma-separated list of HTTP/SSE URLs to connect to"
44
+ ),
45
+ auth: str | None = typer.Option(
46
+ None, "--auth", help="Bearer token for authorization with URL-based servers"
47
+ ),
48
+ model: str | None = typer.Option(
49
+ None, "--model", "--models", help="Override the default model (e.g., haiku, sonnet, gpt-4)"
50
+ ),
51
+ skills_dir: Path | None = typer.Option(
52
+ None,
53
+ "--skills-dir",
54
+ "--skills",
55
+ help="Override the default skills directory",
56
+ ),
57
+ npx: str | None = typer.Option(
58
+ None, "--npx", help="NPX package and args to run as MCP server (quoted)"
59
+ ),
60
+ uvx: str | None = typer.Option(
61
+ None, "--uvx", help="UVX package and args to run as MCP server (quoted)"
62
+ ),
63
+ stdio: str | None = typer.Option(
64
+ None, "--stdio", help="Command to run as STDIO MCP server (quoted)"
65
+ ),
66
+ description: str | None = typer.Option(
67
+ None,
68
+ "--description",
69
+ "-d",
70
+ help="Description used for the exposed send tool (use {agent} to reference the agent name)",
71
+ ),
72
+ host: str = typer.Option(
73
+ "0.0.0.0",
74
+ "--host",
75
+ help="Host address to bind when using HTTP or SSE transport",
76
+ ),
77
+ port: int = typer.Option(
78
+ 8000,
79
+ "--port",
80
+ help="Port to use when running as a server with HTTP or SSE transport",
81
+ ),
82
+ shell: bool = typer.Option(
83
+ False,
84
+ "--shell",
85
+ "-x",
86
+ help="Enable a local shell runtime and expose the execute tool (bash or pwsh).",
87
+ ),
88
+ instance_scope: serve.InstanceScope = typer.Option(
89
+ serve.InstanceScope.SHARED,
90
+ "--instance-scope",
91
+ help="Control how ACP clients receive isolated agent instances (shared, connection, request)",
92
+ ),
93
+ no_permissions: bool = typer.Option(
94
+ False,
95
+ "--no-permissions",
96
+ help="Disable tool permission requests (allow all tool executions without asking)",
97
+ ),
98
+ ) -> None:
99
+ """
100
+ Run FastAgent with ACP transport defaults.
101
+
102
+ This mirrors `fast-agent serve --transport acp` but provides a shorter command and
103
+ a distinct default name so ACP-specific tooling can integrate more easily.
104
+ """
105
+ stdio_commands = collect_stdio_commands(npx, uvx, stdio)
106
+ shell_enabled = shell
107
+
108
+ resolved_instruction, agent_name = resolve_instruction_option(instruction)
109
+
110
+ run_async_agent(
111
+ name=name,
112
+ instruction=resolved_instruction,
113
+ config_path=config_path,
114
+ servers=servers,
115
+ urls=urls,
116
+ auth=auth,
117
+ model=model,
118
+ message=None,
119
+ prompt_file=None,
120
+ stdio_commands=stdio_commands,
121
+ agent_name=agent_name,
122
+ skills_directory=skills_dir,
123
+ shell_enabled=shell_enabled,
124
+ mode="serve",
125
+ transport=serve.ServeTransport.ACP.value,
126
+ host=host,
127
+ port=port,
128
+ tool_description=description,
129
+ instance_scope=instance_scope.value,
130
+ permissions_enabled=not no_permissions,
131
+ )
132
+
133
+
134
+ def main() -> None:
135
+ """Console script entrypoint for `fast-agent-acp`."""
136
+ # Override Click's UsageError exit code from 2 to 1 for consistency
137
+ import click
138
+
139
+ click.exceptions.UsageError.exit_code = 1
140
+
141
+ args = sys.argv[1:]
142
+ if args and args[0] in ROOT_SUBCOMMANDS:
143
+ from fast_agent.cli.__main__ import main as root_cli_main
144
+
145
+ root_cli_main()
146
+ return
147
+ try:
148
+ # Run the Typer app without triggering automatic sys.exit so we can
149
+ # guarantee error output goes to stderr with a non-zero exit code.
150
+ app(standalone_mode=False)
151
+ except click.ClickException as exc:
152
+ # Preserve Typer's rich formatting when available, otherwise fall back to plain text.
153
+ try:
154
+ import typer.rich_utils as rich_utils
155
+
156
+ rich_utils.rich_format_error(exc)
157
+ except Exception:
158
+ exc.show(file=sys.stderr)
159
+ sys.exit(getattr(exc, "exit_code", 1))