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,326 @@
1
+ """
2
+ Validation utilities for FastAgent configuration and dependencies.
3
+ """
4
+
5
+ from typing import Any
6
+
7
+ from fast_agent.agents.agent_types import AgentType
8
+ from fast_agent.core.exceptions import (
9
+ AgentConfigError,
10
+ CircularDependencyError,
11
+ ServerConfigError,
12
+ )
13
+ from fast_agent.interfaces import LlmAgentProtocol
14
+ from fast_agent.llm.fastagent_llm import FastAgentLLM
15
+
16
+
17
+ def validate_server_references(context, agents: dict[str, dict[str, Any]]) -> None:
18
+ """
19
+ Validate that all server references in agent configurations exist in config.
20
+ Raises ServerConfigError if any referenced servers are not defined.
21
+
22
+ Args:
23
+ context: Application context
24
+ agents: Dictionary of agent configurations
25
+ """
26
+ if not context.config.mcp or not context.config.mcp.servers:
27
+ available_servers = set()
28
+ else:
29
+ available_servers = set(context.config.mcp.servers.keys())
30
+
31
+ # Check each agent's server references
32
+ for name, agent_data in agents.items():
33
+ config = agent_data["config"]
34
+ if config.servers:
35
+ missing = [s for s in config.servers if s not in available_servers]
36
+ if missing:
37
+ raise ServerConfigError(
38
+ f"Missing server configuration for agent '{name}'",
39
+ f"The following servers are referenced but not defined in config: {', '.join(missing)}",
40
+ )
41
+
42
+
43
+ def validate_workflow_references(agents: dict[str, dict[str, Any]]) -> None:
44
+ """
45
+ Validate that all workflow references point to valid agents/workflows.
46
+ Also validates that referenced agents have required configuration.
47
+ Raises AgentConfigError if any validation fails.
48
+
49
+ Args:
50
+ agents: Dictionary of agent configurations
51
+ """
52
+ available_components = set(agents.keys())
53
+
54
+ for name, agent_data in agents.items():
55
+ agent_type = agent_data["type"] # This is a string from config
56
+
57
+ # Note: Compare string values from config with the Enum's string value
58
+ if agent_type == AgentType.PARALLEL.value:
59
+ # Check fan_in exists
60
+ fan_in = agent_data["fan_in"]
61
+ if fan_in and fan_in not in available_components:
62
+ raise AgentConfigError(
63
+ f"Parallel workflow '{name}' references non-existent fan_in component: {fan_in}"
64
+ )
65
+
66
+ # Check fan_out agents exist
67
+ fan_out = agent_data["fan_out"]
68
+ missing = [a for a in fan_out if a not in available_components]
69
+ if missing:
70
+ raise AgentConfigError(
71
+ f"Parallel workflow '{name}' references non-existent fan_out components: {', '.join(missing)}"
72
+ )
73
+
74
+ elif agent_type == AgentType.ORCHESTRATOR.value:
75
+ # Check all child agents exist and are properly configured
76
+ child_agents = agent_data["child_agents"]
77
+ missing = [a for a in child_agents if a not in available_components]
78
+ if missing:
79
+ raise AgentConfigError(
80
+ f"Orchestrator '{name}' references non-existent agents: {', '.join(missing)}"
81
+ )
82
+
83
+ # Validate child agents have required LLM configuration
84
+ for agent_name in child_agents:
85
+ child_data = agents[agent_name]
86
+ if child_data["type"] == AgentType.BASIC.value:
87
+ # For basic agents, we'll validate LLM config during creation
88
+ continue
89
+ # Check if it's a workflow type or has LLM capability
90
+ # Workflows like EvaluatorOptimizer and Parallel are valid for orchestrator
91
+ func = child_data["func"]
92
+ workflow_types = [
93
+ AgentType.EVALUATOR_OPTIMIZER.value,
94
+ AgentType.PARALLEL.value,
95
+ AgentType.ROUTER.value,
96
+ AgentType.CHAIN.value,
97
+ ]
98
+
99
+ if not (
100
+ isinstance(func, FastAgentLLM)
101
+ or child_data["type"] in workflow_types
102
+ or (isinstance(func, LlmAgentProtocol) and func.llm is not None)
103
+ ):
104
+ raise AgentConfigError(
105
+ f"Agent '{agent_name}' used by orchestrator '{name}' lacks LLM capability",
106
+ "All agents used by orchestrators must be LLM-capable (either an AugmentedLLM or implement LlmAgentProtocol)",
107
+ )
108
+
109
+ elif agent_type == AgentType.ROUTER.value:
110
+ # Check all referenced agents exist
111
+ router_agents = agent_data["router_agents"]
112
+ missing = [a for a in router_agents if a not in available_components]
113
+ if missing:
114
+ raise AgentConfigError(
115
+ f"Router '{name}' references non-existent agents: {', '.join(missing)}"
116
+ )
117
+
118
+ elif agent_type == AgentType.EVALUATOR_OPTIMIZER.value:
119
+ # Check both evaluator and optimizer exist
120
+ evaluator = agent_data["evaluator"]
121
+ generator = agent_data["generator"]
122
+ missing = []
123
+ if evaluator not in available_components:
124
+ missing.append(f"evaluator: {evaluator}")
125
+ if generator not in available_components:
126
+ missing.append(f"generator: {generator}")
127
+ if missing:
128
+ raise AgentConfigError(
129
+ f"Evaluator-Optimizer '{name}' references non-existent components: {', '.join(missing)}"
130
+ )
131
+
132
+ elif agent_type == AgentType.CHAIN.value:
133
+ # Check that all agents in the sequence exist
134
+ sequence = agent_data.get("sequence", agent_data.get("agents", []))
135
+ missing = [a for a in sequence if a not in available_components]
136
+ if missing:
137
+ raise AgentConfigError(
138
+ f"Chain '{name}' references non-existent agents: {', '.join(missing)}"
139
+ )
140
+
141
+
142
+ def get_dependencies(
143
+ name: str,
144
+ agents: dict[str, dict[str, Any]],
145
+ visited: set,
146
+ path: set,
147
+ agent_type: AgentType = None,
148
+ ) -> list[str]:
149
+ """
150
+ Get dependencies for an agent in topological order.
151
+ Works for both Parallel and Chain workflows.
152
+
153
+ Args:
154
+ name: Name of the agent
155
+ agents: Dictionary of agent configurations
156
+ visited: Set of already visited agents
157
+ path: Current path for cycle detection
158
+ agent_type: Optional type filter (e.g., only check Parallel or Chain)
159
+
160
+ Returns:
161
+ List of agent names in dependency order
162
+
163
+ Raises:
164
+ CircularDependencyError: If circular dependency detected
165
+ """
166
+ if name in path:
167
+ path_str = " -> ".join(path)
168
+ raise CircularDependencyError(f"Path: {path_str} -> {name}")
169
+
170
+ if name in visited:
171
+ return []
172
+
173
+ if name not in agents:
174
+ return []
175
+
176
+ config = agents[name]
177
+
178
+ # Skip if not the requested type (when filtering)
179
+ if agent_type and config["type"] != agent_type.value:
180
+ return []
181
+
182
+ path.add(name)
183
+ deps = []
184
+
185
+ # Handle dependencies based on agent type
186
+ if config["type"] == AgentType.PARALLEL.value:
187
+ # Get dependencies from fan-out agents
188
+ for fan_out in config["fan_out"]:
189
+ deps.extend(get_dependencies(fan_out, agents, visited, path, agent_type))
190
+ elif config["type"] == AgentType.CHAIN.value:
191
+ # Get dependencies from sequence agents
192
+ sequence = config.get("sequence", config.get("router_agents", []))
193
+ for agent_name in sequence:
194
+ deps.extend(get_dependencies(agent_name, agents, visited, path, agent_type))
195
+
196
+ # Add this agent after its dependencies
197
+ deps.append(name)
198
+ visited.add(name)
199
+ path.remove(name)
200
+
201
+ return deps
202
+
203
+
204
+ def get_agent_dependencies(agent_data: dict[str, Any]) -> set[str]:
205
+ deps: set[str] = set()
206
+ agent_dependency_attribute_names = {
207
+ AgentType.CHAIN: ("sequence",),
208
+ AgentType.EVALUATOR_OPTIMIZER: ("evaluator", "generator", "eval_optimizer_agents"),
209
+ AgentType.ITERATIVE_PLANNER: ("child_agents",),
210
+ AgentType.ORCHESTRATOR: ("child_agents",),
211
+ AgentType.BASIC: ("child_agents",),
212
+ AgentType.PARALLEL: ("fan_out", "fan_in", "parallel_agents"),
213
+ AgentType.ROUTER: ("router_agents",),
214
+ }
215
+ agent_type = agent_data["type"]
216
+ dependency_names = agent_dependency_attribute_names.get(agent_type, None)
217
+ if dependency_names is None:
218
+ return deps
219
+
220
+ for dependency_name in dependency_names:
221
+ dependency_value = agent_data.get(dependency_name)
222
+ if dependency_value is None:
223
+ continue
224
+ if isinstance(dependency_value, str):
225
+ deps.add(dependency_value)
226
+ else:
227
+ # here, we have an implicit assumption that if it is not a None or a string, then it is a list
228
+ deps.update(dependency_value)
229
+
230
+ return deps
231
+
232
+
233
+ def get_dependencies_groups(
234
+ agents_dict: dict[str, dict[str, Any]], allow_cycles: bool = False
235
+ ) -> list[list[str]]:
236
+ """
237
+ Get dependencies between agents and group them into dependency layers.
238
+ Each layer can be initialized in parallel.
239
+
240
+ Args:
241
+ agents_dict: Dictionary of agent configurations
242
+ allow_cycles: Whether to allow cyclic dependencies
243
+
244
+ Returns:
245
+ List of lists, where each inner list is a group of agents that can be initialized together
246
+
247
+ Raises:
248
+ CircularDependencyError: If circular dependency detected and allow_cycles is False
249
+ """
250
+ # Get all agent names
251
+ agent_names = list(agents_dict.keys())
252
+
253
+ # Dictionary to store dependencies for each agent
254
+ dependencies = {
255
+ name: get_agent_dependencies(agent_data) for name, agent_data in agents_dict.items()
256
+ }
257
+
258
+ # Check for cycles if not allowed
259
+ if not allow_cycles:
260
+ visited = set()
261
+ path = set()
262
+
263
+ def visit(node) -> None:
264
+ if node in path:
265
+ path_str = " -> ".join(path) + " -> " + node
266
+ raise CircularDependencyError(f"Circular dependency detected: {path_str}")
267
+ if node in visited:
268
+ return
269
+
270
+ path.add(node)
271
+ for dep in dependencies[node]:
272
+ if dep in agent_names: # Skip dependencies to non-existent agents
273
+ visit(dep)
274
+ path.remove(node)
275
+ visited.add(node)
276
+
277
+ # Check each node
278
+ for name in agent_names:
279
+ if name not in visited:
280
+ visit(name)
281
+
282
+ # Group agents by dependency level
283
+ result = []
284
+ remaining = set(agent_names)
285
+
286
+ while remaining:
287
+ # Find all agents that have no remaining dependencies
288
+ current_level = set()
289
+ for name in remaining:
290
+ if not dependencies[name] & remaining: # If no dependencies in remaining agents
291
+ current_level.add(name)
292
+
293
+ if not current_level:
294
+ if allow_cycles:
295
+ # If cycles are allowed, just add one remaining node to break the cycle
296
+ current_level.add(next(iter(remaining)))
297
+ else:
298
+ # This should not happen if we checked for cycles
299
+ raise CircularDependencyError("Unresolvable dependency cycle detected")
300
+
301
+ # Add the current level to the result
302
+ result.append(list(current_level))
303
+
304
+ # Remove current level from remaining
305
+ remaining -= current_level
306
+
307
+ return result
308
+
309
+
310
+ def validate_provider_keys_post_creation(active_agents: dict[str, Any]) -> None:
311
+ """
312
+ Validate that API keys are available for all created agents with LLMs.
313
+
314
+ This runs after agent creation when we have actual agent instances.
315
+
316
+ Args:
317
+ active_agents: Dictionary of created agent instances
318
+
319
+ Raises:
320
+ ProviderKeyError: If any required API key is missing
321
+ """
322
+ for agent_name, agent in active_agents.items():
323
+ llm = getattr(agent, "_llm", None)
324
+ if llm:
325
+ # This throws a ProviderKeyError if the key is not present
326
+ llm._api_key()
@@ -0,0 +1,62 @@
1
+ """Module for converting log events to progress events."""
2
+
3
+ from enum import Enum
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class ProgressAction(str, Enum):
9
+ """Progress actions available in the system."""
10
+
11
+ STARTING = "Starting"
12
+ LOADED = "Loaded"
13
+ INITIALIZED = "Initialized"
14
+ CHATTING = "Chatting"
15
+ STREAMING = "Streaming" # Special action for real-time streaming updates
16
+ THINKING = "Thinking" # Special action for real-time thinking updates
17
+ ROUTING = "Routing"
18
+ PLANNING = "Planning"
19
+ READY = "Ready"
20
+ CALLING_TOOL = "Calling Tool"
21
+ TOOL_PROGRESS = "Tool Progress"
22
+ UPDATED = "Updated"
23
+ FINISHED = "Finished"
24
+ SHUTDOWN = "Shutdown"
25
+ AGGREGATOR_INITIALIZED = "Running"
26
+ SERVER_OFFLINE = "Offline"
27
+ SERVER_RECONNECTING = "Reconnecting"
28
+ SERVER_ONLINE = "Online"
29
+ FATAL_ERROR = "Error"
30
+
31
+
32
+ class ProgressEvent(BaseModel):
33
+ """Represents a progress event converted from a log event."""
34
+
35
+ action: ProgressAction
36
+ target: str
37
+ details: str | None = None
38
+ agent_name: str | None = None
39
+ correlation_id: str | None = None
40
+ instance_name: str | None = None
41
+ tool_name: str | None = None
42
+ streaming_tokens: str | None = None # Special field for streaming token count
43
+ progress: float | None = None # Current progress value
44
+ total: float | None = None # Total value for progress calculation
45
+
46
+ def __str__(self) -> str:
47
+ """Format the progress event for display."""
48
+ # Special handling for streaming - show token count in action position
49
+ if self.action == ProgressAction.STREAMING and self.streaming_tokens:
50
+ # For streaming, show just the token count instead of "Streaming"
51
+ action_display = self.streaming_tokens.ljust(11)
52
+ base = f"{action_display}. {self.target}"
53
+ if self.details:
54
+ base += f" - {self.details}"
55
+ else:
56
+ base = f"{self.action.ljust(11)}. {self.target}"
57
+ if self.details:
58
+ base += f" - {self.details}"
59
+
60
+ if self.agent_name:
61
+ base = f"[{self.agent_name}] {base}"
62
+ return base
@@ -0,0 +1,49 @@
1
+ """
2
+ History export utilities for agents.
3
+
4
+ Provides a minimal, type-friendly way to save an agent's message history
5
+ without using control strings. Uses the existing serialization helpers
6
+ to choose JSON (for .json files) or Markdown-like delimited text otherwise.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import TYPE_CHECKING
12
+
13
+ from fast_agent.mcp.prompt_serialization import save_messages
14
+
15
+ if TYPE_CHECKING:
16
+ from fast_agent.interfaces import AgentProtocol
17
+
18
+
19
+ class HistoryExporter:
20
+ """Utility for exporting agent history to a file."""
21
+
22
+ @staticmethod
23
+ async def save(agent: AgentProtocol, filename: str | None = None) -> str:
24
+ """
25
+ Save the given agent's message history to a file.
26
+
27
+ If filename ends with ".json", the history is saved in MCP JSON format.
28
+ Otherwise, it is saved in a human-readable Markdown-style format.
29
+
30
+ Args:
31
+ agent: The agent whose history will be saved.
32
+ filename: Optional filename. If None, a default timestamped name is generated.
33
+
34
+ Returns:
35
+ The path that was written to.
36
+ """
37
+ # Determine a default filename when not provided
38
+ if not filename:
39
+ from datetime import datetime
40
+ timestamp = datetime.now().strftime("%y_%m_%d_%H_%M")
41
+ target = f"{timestamp}-conversation.json"
42
+ else:
43
+ target = filename
44
+
45
+ messages = agent.message_history
46
+ save_messages(messages, target)
47
+
48
+ # Return and optionally print a small confirmation
49
+ return target
@@ -0,0 +1,47 @@
1
+ """Human input modules for forms and elicitation.
2
+
3
+ Keep __init__ lightweight to avoid circular imports during submodule import.
4
+ Exports schema builders directly and defers simple form API imports.
5
+ """
6
+
7
+ from fast_agent.human_input.form_fields import (
8
+ BooleanField,
9
+ EnumField,
10
+ FormSchema,
11
+ IntegerField,
12
+ NumberField,
13
+ StringField,
14
+ boolean,
15
+ choice,
16
+ date,
17
+ datetime,
18
+ email,
19
+ integer,
20
+ number,
21
+ string,
22
+ url,
23
+ )
24
+
25
+ __all__ = [
26
+ # Schema builder
27
+ "FormSchema",
28
+ # Field classes
29
+ "StringField",
30
+ "IntegerField",
31
+ "NumberField",
32
+ "BooleanField",
33
+ "EnumField",
34
+ # Field convenience functions
35
+ "string",
36
+ "email",
37
+ "url",
38
+ "date",
39
+ "datetime",
40
+ "integer",
41
+ "number",
42
+ "boolean",
43
+ "choice",
44
+ ]
45
+
46
+ # Note: form(), ask() helpers are available via fast_agent.human_input.simple_form;
47
+ # intentionally not imported here to avoid import-time cycles.
@@ -0,0 +1,123 @@
1
+ from typing import Any
2
+
3
+ from fast_agent.human_input.elicitation_state import elicitation_state
4
+ from fast_agent.human_input.types import (
5
+ HumanInputRequest,
6
+ HumanInputResponse,
7
+ )
8
+ from fast_agent.tools.elicitation import set_elicitation_input_callback
9
+ from fast_agent.ui.elicitation_form import (
10
+ show_simple_elicitation_form,
11
+ )
12
+ from fast_agent.ui.elicitation_style import (
13
+ ELICITATION_STYLE,
14
+ )
15
+ from fast_agent.ui.progress_display import progress_display
16
+
17
+
18
+ async def elicitation_input_callback(
19
+ request: HumanInputRequest,
20
+ agent_name: str | None = None,
21
+ server_name: str | None = None,
22
+ server_info: dict[str, Any] | None = None,
23
+ ) -> HumanInputResponse:
24
+ """Request input from a human user for MCP server elicitation requests."""
25
+
26
+ # Extract effective names
27
+ effective_agent_name = agent_name or (
28
+ request.metadata.get("agent_name", "Unknown Agent") if request.metadata else "Unknown Agent"
29
+ )
30
+ effective_server_name = server_name or "Unknown Server"
31
+
32
+ # Start tracking elicitation operation
33
+ try:
34
+ from fast_agent.ui import notification_tracker
35
+ notification_tracker.start_elicitation(effective_server_name)
36
+ except Exception:
37
+ # Don't let notification tracking break elicitation
38
+ pass
39
+
40
+ try:
41
+ # Check if elicitation is disabled for this server
42
+ if elicitation_state.is_disabled(effective_server_name):
43
+ return HumanInputResponse(
44
+ request_id=request.request_id,
45
+ response="__CANCELLED__",
46
+ metadata={"auto_cancelled": True, "reason": "Server elicitation disabled by user"},
47
+ )
48
+
49
+ # Get the elicitation schema from metadata
50
+ schema: dict[str, Any] | None = None
51
+ if request.metadata and "requested_schema" in request.metadata:
52
+ schema = request.metadata["requested_schema"]
53
+
54
+ # Use the context manager to pause the progress display while getting input
55
+ with progress_display.paused():
56
+ try:
57
+ if schema:
58
+ form_action, form_data = await show_simple_elicitation_form(
59
+ schema=schema,
60
+ message=request.prompt,
61
+ agent_name=effective_agent_name,
62
+ server_name=effective_server_name,
63
+ )
64
+
65
+ if form_action == "accept" and form_data is not None:
66
+ # Convert form data to JSON string
67
+ import json
68
+
69
+ response = json.dumps(form_data)
70
+ elif form_action == "decline":
71
+ response = "__DECLINED__"
72
+ elif form_action == "disable":
73
+ response = "__DISABLE_SERVER__"
74
+ else: # cancel
75
+ response = "__CANCELLED__"
76
+ else:
77
+ # No schema, fall back to text input using prompt_toolkit only
78
+ from prompt_toolkit.shortcuts import input_dialog
79
+
80
+ response = await input_dialog(
81
+ title="Input Requested",
82
+ text=f"Agent: {effective_agent_name}\nServer: {effective_server_name}\n\n{request.prompt}",
83
+ style=ELICITATION_STYLE,
84
+ ).run_async()
85
+
86
+ if response is None:
87
+ response = "__CANCELLED__"
88
+
89
+ except KeyboardInterrupt:
90
+ response = "__CANCELLED__"
91
+ except EOFError:
92
+ response = "__CANCELLED__"
93
+
94
+ return HumanInputResponse(
95
+ request_id=request.request_id,
96
+ response=response.strip() if isinstance(response, str) else response,
97
+ metadata={"has_schema": schema is not None},
98
+ )
99
+ finally:
100
+ # End tracking elicitation operation
101
+ try:
102
+ from fast_agent.ui import notification_tracker
103
+ notification_tracker.end_elicitation(effective_server_name)
104
+ except Exception:
105
+ # Don't let notification tracking break elicitation
106
+ pass
107
+
108
+
109
+ # Register adapter with fast_agent tools so they can invoke this UI handler without importing types
110
+ async def _elicitation_adapter(
111
+ request_payload: dict,
112
+ agent_name: str | None = None,
113
+ server_name: str | None = None,
114
+ server_info: dict[str, Any] | None = None,
115
+ ) -> str:
116
+ req = HumanInputRequest(**request_payload)
117
+ resp = await elicitation_input_callback(
118
+ request=req, agent_name=agent_name, server_name=server_name, server_info=server_info
119
+ )
120
+ return resp.response if isinstance(resp.response, str) else str(resp.response)
121
+
122
+
123
+ set_elicitation_input_callback(_elicitation_adapter)
@@ -0,0 +1,33 @@
1
+ """Simple state management for elicitation Cancel All functionality."""
2
+
3
+
4
+
5
+ class ElicitationState:
6
+ """Manages global state for elicitation requests, including disabled servers."""
7
+
8
+ def __init__(self):
9
+ self.disabled_servers: set[str] = set()
10
+
11
+ def disable_server(self, server_name: str) -> None:
12
+ """Disable elicitation requests for a specific server."""
13
+ self.disabled_servers.add(server_name)
14
+
15
+ def is_disabled(self, server_name: str) -> bool:
16
+ """Check if elicitation is disabled for a server."""
17
+ return server_name in self.disabled_servers
18
+
19
+ def enable_server(self, server_name: str) -> None:
20
+ """Re-enable elicitation requests for a specific server."""
21
+ self.disabled_servers.discard(server_name)
22
+
23
+ def clear_all(self) -> None:
24
+ """Clear all disabled servers."""
25
+ self.disabled_servers.clear()
26
+
27
+ def get_disabled_servers(self) -> set[str]:
28
+ """Get a copy of all disabled servers."""
29
+ return self.disabled_servers.copy()
30
+
31
+
32
+ # Global instance for session-scoped Cancel All functionality
33
+ elicitation_state = ElicitationState()