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,557 @@
1
+ """Bootstrap command to create example applications."""
2
+
3
+ import shutil
4
+ from contextlib import ExitStack
5
+ from dataclasses import dataclass
6
+ from importlib.resources import as_file, files
7
+ from pathlib import Path
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.table import Table
13
+
14
+ from fast_agent.ui.console import console as shared_console
15
+
16
+ app = typer.Typer(
17
+ help="Create fast-agent quickstarts",
18
+ no_args_is_help=False, # Allow showing our custom help instead
19
+ )
20
+ console = shared_console
21
+
22
+
23
+ BASE_EXAMPLES_DIR = files("fast_agent").joinpath("resources").joinpath("examples")
24
+
25
+
26
+ @dataclass
27
+ class ExampleConfig:
28
+ description: str
29
+ files: list[str]
30
+ create_subdir: bool
31
+ path_in_examples: list[str]
32
+ mount_point_files: list[str] | None = None
33
+
34
+
35
+ _EXAMPLE_CONFIGS = {
36
+ "workflow": ExampleConfig(
37
+ description=(
38
+ "Example workflows, demonstrating each of the patterns in Anthropic's\n"
39
+ "'Building Effective Agents' paper. Some agents use the 'fetch'\n"
40
+ "and filesystem MCP Servers."
41
+ ),
42
+ files=[
43
+ "chaining.py",
44
+ "evaluator.py",
45
+ "human_input.py",
46
+ "orchestrator.py",
47
+ "parallel.py",
48
+ "router.py",
49
+ "short_story.txt",
50
+ "fastagent.config.yaml",
51
+ ],
52
+ create_subdir=True,
53
+ path_in_examples=["workflows"],
54
+ ),
55
+ "researcher": ExampleConfig(
56
+ description=(
57
+ "Research agent example with additional evaluation/optimization\n"
58
+ "example. Uses Brave Search and Docker MCP Servers.\n"
59
+ "Creates examples in a 'researcher' subdirectory."
60
+ ),
61
+ files=["researcher.py", "researcher-eval.py", "fastagent.config.yaml"],
62
+ create_subdir=True,
63
+ path_in_examples=["researcher"],
64
+ ),
65
+ "data-analysis": ExampleConfig(
66
+ description=(
67
+ "Data analysis agent examples that demonstrate working with\n"
68
+ "datasets, performing statistical analysis, and generating visualizations.\n"
69
+ "Creates examples in a 'data-analysis' subdirectory with mount-point for data.\n"
70
+ "Uses MCP 'roots' feature for mapping"
71
+ ),
72
+ files=["analysis.py", "fastagent.config.yaml"],
73
+ mount_point_files=["WA_Fn-UseC_-HR-Employee-Attrition.csv"],
74
+ create_subdir=True,
75
+ path_in_examples=["data-analysis"],
76
+ ),
77
+ "state-transfer": ExampleConfig(
78
+ description=(
79
+ "Example demonstrating state transfer between multiple agents.\n"
80
+ "Shows how state can be passed between agent runs to maintain context.\n"
81
+ "Creates examples in a 'state-transfer' subdirectory."
82
+ ),
83
+ files=[
84
+ "agent_one.py",
85
+ "agent_two.py",
86
+ "fastagent.config.yaml",
87
+ "fastagent.secrets.yaml.example",
88
+ ],
89
+ create_subdir=True,
90
+ path_in_examples=["mcp", "state-transfer"],
91
+ ),
92
+ "elicitations": ExampleConfig(
93
+ description=(
94
+ "Interactive form examples using MCP elicitations feature.\n"
95
+ "Demonstrates collecting structured data with forms, AI-guided workflows,\n"
96
+ "and custom handlers. Creates examples in an 'elicitations' subdirectory."
97
+ ),
98
+ files=[
99
+ "elicitation_account_server.py",
100
+ "elicitation_forms_server.py",
101
+ "elicitation_game_server.py",
102
+ "fastagent.config.yaml",
103
+ "fastagent.secrets.yaml.example",
104
+ "forms_demo.py",
105
+ "game_character.py",
106
+ "game_character_handler.py",
107
+ "tool_call.py",
108
+ ],
109
+ create_subdir=True,
110
+ path_in_examples=["mcp", "elicitations"],
111
+ ),
112
+ "tensorzero": ExampleConfig(
113
+ description=(
114
+ "A complete example showcasing the TensorZero integration.\n"
115
+ "Includes the T0 Gateway, an MCP server, an interactive agent, and \n"
116
+ "multi-modal functionality."
117
+ ),
118
+ files=[
119
+ ".env.sample",
120
+ "Makefile",
121
+ "README.md",
122
+ "agent.py",
123
+ "docker-compose.yml",
124
+ "fastagent.config.yaml",
125
+ "image_demo.py",
126
+ "simple_agent.py",
127
+ "mcp_server/",
128
+ "demo_images/",
129
+ "tensorzero_config/",
130
+ ],
131
+ create_subdir=True,
132
+ path_in_examples=["elicitations"],
133
+ ),
134
+ }
135
+
136
+
137
+ def _development_mode_fallback(example_info: ExampleConfig) -> Path:
138
+ """Fallback function for development mode."""
139
+ package_dir = Path(__file__).parent.parent.parent.parent.parent
140
+ for dir in example_info.path_in_examples:
141
+ package_dir = package_dir / dir
142
+ console.print(f"[blue]Using development directory: {package_dir}[/blue]")
143
+ return package_dir
144
+
145
+
146
+ def copy_example_files(example_type: str, target_dir: Path, force: bool = False) -> list[str]:
147
+ """Copy example files from resources to target directory."""
148
+ # Determine if we should create a subdirectory for this example type
149
+ example_info = _EXAMPLE_CONFIGS.get(example_type, None)
150
+ if example_info is None:
151
+ console.print(f"Example type '{example_type}' not found.")
152
+ return []
153
+
154
+ if example_info.create_subdir:
155
+ target_dir = target_dir / example_type
156
+ if not target_dir.exists():
157
+ target_dir.mkdir(parents=True)
158
+ console.print(f"Created subdirectory: {target_dir}")
159
+
160
+ # Determine source directory - try package resources first, then fallback
161
+ use_as_file = False
162
+ # Try to use examples from the installed package first, or fall back to the top-level directory
163
+ try:
164
+ # First try to find examples in the package resources
165
+ source_dir_traversable = BASE_EXAMPLES_DIR
166
+ for dir in example_info.path_in_examples:
167
+ source_dir_traversable = source_dir_traversable.joinpath(dir)
168
+
169
+ # Check if we found a valid directory
170
+ if not source_dir_traversable.is_dir():
171
+ console.print(
172
+ f"[yellow]Resource directory not found: {source_dir_traversable}. "
173
+ "Falling back to development mode.[/yellow]"
174
+ )
175
+ # Fall back to the top-level directory for development mode
176
+ source_dir: Path = _development_mode_fallback(example_info)
177
+ else:
178
+ # We have a valid Traversable, will need to use as_file
179
+ source_dir = source_dir_traversable # type: ignore
180
+ use_as_file = True
181
+ except (ImportError, ModuleNotFoundError, ValueError) as e:
182
+ console.print(
183
+ f"[yellow]Error accessing resources: {e}. Falling back to development mode.[/yellow]"
184
+ )
185
+ source_dir = _development_mode_fallback(example_info)
186
+
187
+ # Use as_file context manager if source_dir is a Traversable, otherwise use directly
188
+ with ExitStack() as stack:
189
+ if use_as_file:
190
+ source_path = stack.enter_context(as_file(source_dir)) # type: ignore
191
+ else:
192
+ source_path = source_dir
193
+
194
+ if not source_path.exists():
195
+ console.print(f"[red]Error: Source directory not found: {source_path}[/red]")
196
+ return []
197
+
198
+ return _copy_files_from_source(
199
+ example_type, example_info, source_path, target_dir, force
200
+ )
201
+
202
+
203
+ def _copy_files_from_source(
204
+ example_type: str, example_info: ExampleConfig, source_dir: Path, target_dir: Path, force: bool
205
+ ) -> list[str]:
206
+ """Helper function to copy files from a source directory."""
207
+ created = []
208
+ for filename in example_info.files:
209
+ source = source_dir / filename
210
+ target = target_dir / filename
211
+
212
+ try:
213
+ if not source.exists():
214
+ console.print(f"[red]Error: Source file not found: {source}[/red]")
215
+ continue
216
+
217
+ if target.exists() and not force:
218
+ console.print(f"[yellow]Skipping[/yellow] {filename} (already exists)")
219
+ continue
220
+
221
+ shutil.copy2(source, target)
222
+ try:
223
+ # This can fail in test environments where the target is not relative to target_dir.parent
224
+ rel_path = str(target.relative_to(target_dir.parent))
225
+ except ValueError:
226
+ # Fallback to just the filename
227
+ rel_path = f"{example_type}/{filename}"
228
+
229
+ created.append(rel_path)
230
+ console.print(f"[green]Created[/green] {rel_path}")
231
+
232
+ except Exception as e:
233
+ console.print(f"[red]Error copying {filename}: {str(e)}[/red]")
234
+
235
+ # Copy mount-point files if any
236
+ mount_point_files = example_info.mount_point_files or []
237
+ if mount_point_files:
238
+ mount_point_dir = target_dir / "mount-point"
239
+
240
+ # Create mount-point directory if needed
241
+ if not mount_point_dir.exists():
242
+ mount_point_dir.mkdir(parents=True)
243
+ console.print(f"Created mount-point directory: {mount_point_dir}")
244
+
245
+ for filename in mount_point_files:
246
+ source = source_dir / "mount-point" / filename
247
+ target = mount_point_dir / filename
248
+
249
+ try:
250
+ if not source.exists():
251
+ console.print(f"[red]Error: Source file not found: {source}[/red]")
252
+ continue
253
+
254
+ if target.exists() and not force:
255
+ console.print(
256
+ f"[yellow]Skipping[/yellow] mount-point/{filename} (already exists)"
257
+ )
258
+ continue
259
+
260
+ shutil.copy2(source, target)
261
+ created.append(f"{example_type}/mount-point/{filename}")
262
+ console.print(f"[green]Created[/green] mount-point/{filename}")
263
+
264
+ except Exception as e:
265
+ console.print(f"[red]Error copying mount-point/{filename}: {str(e)}[/red]")
266
+
267
+ return created
268
+
269
+
270
+ def copy_project_template(source_dir: Path, dest_dir: Path, console: Console, force: bool = False):
271
+ """
272
+ Recursively copies a project template directory.
273
+ This is a helper to handle project-based quickstarts like TensorZero.
274
+ """
275
+ if dest_dir.exists():
276
+ if force:
277
+ console.print(
278
+ f"[yellow]--force specified. Removing existing directory: {dest_dir}[/yellow]"
279
+ )
280
+ shutil.rmtree(dest_dir)
281
+ else:
282
+ console.print(
283
+ f"[bold yellow]Directory '{dest_dir.name}' already exists.[/bold yellow] Use --force to overwrite."
284
+ )
285
+ return False
286
+
287
+ try:
288
+ shutil.copytree(source_dir, dest_dir)
289
+ return True
290
+ except Exception as e:
291
+ console.print(f"[red]Error copying project template: {e}[/red]")
292
+ return False
293
+
294
+
295
+ def show_overview() -> None:
296
+ """Display an overview of available examples in a nicely formatted table."""
297
+ console.print("\n[bold cyan]fast-agent quickstarts[/bold cyan]")
298
+ console.print("Build agents and compose workflows through practical examples\n")
299
+
300
+ # Create a table for better organization
301
+ table = Table(show_header=True, header_style="bold magenta", box=None, padding=(0, 2))
302
+ table.add_column("Example")
303
+ table.add_column("Description")
304
+ table.add_column("Files")
305
+
306
+ for name, info in _EXAMPLE_CONFIGS.items():
307
+ # Just show file count instead of listing all files
308
+ file_count = len(info.files)
309
+ files_summary = f"{file_count} files"
310
+ mount_files = info.mount_point_files
311
+ if mount_files:
312
+ files_summary += f"\n+ {len(mount_files)} data files"
313
+ table.add_row(f"[green]{name}[/green]", info.description, files_summary)
314
+
315
+ console.print(table)
316
+
317
+ # Show usage instructions in a panel
318
+ usage_text = (
319
+ "[bold]Usage:[/bold]\n"
320
+ " [cyan]fast-agent[/cyan] [green]quickstart[/green] [yellow]<name>[/yellow] [dim]\\[directory][/dim]\n\n"
321
+ "[dim]directory optionally overrides the default subdirectory name[/dim]\n\n"
322
+ "[bold]Options:[/bold]\n"
323
+ " [cyan]--force[/cyan] Overwrite existing files"
324
+ )
325
+ console.print(Panel(usage_text, title="Usage", border_style="blue"))
326
+
327
+
328
+ @app.command()
329
+ def workflow(
330
+ directory: Path = typer.Argument(
331
+ Path("."),
332
+ help="Directory where workflow examples will be created",
333
+ ),
334
+ force: bool = typer.Option(False, "--force", "-f", help="Force overwrite existing files"),
335
+ ) -> None:
336
+ """Create workflow pattern examples."""
337
+ target_dir = directory.resolve()
338
+ if not target_dir.exists():
339
+ target_dir.mkdir(parents=True)
340
+ console.print(f"Created directory: {target_dir}")
341
+
342
+ created = copy_example_files("workflow", target_dir, force)
343
+ _show_completion_message("workflow", created)
344
+
345
+
346
+ @app.command()
347
+ def researcher(
348
+ directory: Path = typer.Argument(
349
+ Path("."),
350
+ help="Directory where researcher examples will be created (in 'researcher' subdirectory)",
351
+ ),
352
+ force: bool = typer.Option(False, "--force", "-f", help="Force overwrite existing files"),
353
+ ) -> None:
354
+ """Create researcher pattern examples."""
355
+ target_dir = directory.resolve()
356
+ if not target_dir.exists():
357
+ target_dir.mkdir(parents=True)
358
+ console.print(f"Created directory: {target_dir}")
359
+
360
+ created = copy_example_files("researcher", target_dir, force)
361
+ _show_completion_message("researcher", created)
362
+
363
+
364
+ @app.command()
365
+ def data_analysis(
366
+ directory: Path = typer.Argument(
367
+ Path("."),
368
+ help="Directory where data analysis examples will be created (creates 'data-analysis' subdirectory with mount-point)",
369
+ ),
370
+ force: bool = typer.Option(False, "--force", "-f", help="Force overwrite existing files"),
371
+ ) -> None:
372
+ """Create data analysis examples with sample dataset."""
373
+ target_dir = directory.resolve()
374
+ if not target_dir.exists():
375
+ target_dir.mkdir(parents=True)
376
+ console.print(f"Created directory: {target_dir}")
377
+
378
+ created = copy_example_files("data-analysis", target_dir, force)
379
+ _show_completion_message("data-analysis", created)
380
+
381
+
382
+ @app.command()
383
+ def state_transfer(
384
+ directory: Path = typer.Argument(
385
+ Path("."),
386
+ help="Directory where state transfer examples will be created (in 'state-transfer' subdirectory)",
387
+ ),
388
+ force: bool = typer.Option(False, "--force", "-f", help="Force overwrite existing files"),
389
+ ) -> None:
390
+ """Create state transfer example showing state passing between agents."""
391
+ target_dir = directory.resolve()
392
+ if not target_dir.exists():
393
+ target_dir.mkdir(parents=True)
394
+ console.print(f"Created directory: {target_dir}")
395
+
396
+ created = copy_example_files("state-transfer", target_dir, force)
397
+ _show_completion_message("state-transfer", created)
398
+
399
+
400
+ @app.command()
401
+ def elicitations(
402
+ directory: Path = typer.Argument(
403
+ Path("."),
404
+ help="Directory where elicitation examples will be created (in 'elicitations' subdirectory)",
405
+ ),
406
+ force: bool = typer.Option(False, "--force", "-f", help="Force overwrite existing files"),
407
+ ) -> None:
408
+ """Create interactive form examples using MCP elicitations."""
409
+ target_dir = directory.resolve()
410
+ if not target_dir.exists():
411
+ target_dir.mkdir(parents=True)
412
+ console.print(f"Created directory: {target_dir}")
413
+
414
+ created = copy_example_files("elicitations", target_dir, force)
415
+ _show_completion_message("elicitations", created)
416
+
417
+
418
+ def _show_completion_message(example_type: str, created: list[str]) -> None:
419
+ """Show completion message and next steps."""
420
+ if created:
421
+ console.print("\n[green]Setup completed successfully![/green]")
422
+ console.print("\nCreated files:")
423
+ for f in created:
424
+ console.print(f" - {f}")
425
+
426
+ console.print("\n[bold]Next Steps:[/bold]")
427
+ if example_type == "workflow":
428
+ console.print("1. Review chaining.py for the basic workflow example")
429
+ console.print("2. Check other examples:")
430
+ console.print(" - parallel.py: Run agents in parallel")
431
+ console.print(" - router.py: Route requests between agents")
432
+ console.print(" - evaluator.py: Add evaluation capabilities")
433
+ console.print(" - human_input.py: Incorporate human feedback")
434
+ console.print("3. Run an example with: uv run <example>.py")
435
+ console.print(
436
+ "4. Try a different model with --model=<model>, or update the agent config"
437
+ )
438
+
439
+ elif example_type == "researcher":
440
+ console.print(
441
+ "1. Set up the Brave MCP Server (get an API key from https://brave.com/search/api/)"
442
+ )
443
+ console.print("2. Try `uv run researcher.py` for the basic version")
444
+ console.print("3. Try `uv run researcher-eval.py` for the eval/optimize version")
445
+ elif example_type == "data-analysis":
446
+ console.print("1. Run uv `analysis.py` to perform data analysis and visualization")
447
+ console.print("2. The dataset is available in the mount-point directory:")
448
+ console.print(" - mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv")
449
+ console.print(
450
+ "On Windows platforms, please edit the fastagent.config.yaml and adjust the volume mount point."
451
+ )
452
+ elif example_type == "state-transfer":
453
+ console.print(
454
+ "Check [cyan][link=https://fast-agent.ai]fast-agent.ai[/link][/cyan] for quick start walkthroughs"
455
+ )
456
+ elif example_type == "elicitations":
457
+ console.print("1. Go to the `elicitations` subdirectory (cd elicitations)")
458
+ console.print("2. Try the forms demo: uv run forms_demo.py")
459
+ console.print("3. Run the game character creator: uv run game_character.py")
460
+ console.print(
461
+ "Check [cyan][link=https://fast-agent.ai/mcp/elicitations/]https://fast-agent.ai/mcp/elicitations/[/link][/cyan] for more details"
462
+ )
463
+ else:
464
+ console.print("\n[yellow]No files were created.[/yellow]")
465
+
466
+
467
+ @app.command(name="tensorzero", help="Create the TensorZero integration example project.")
468
+ def tensorzero(
469
+ directory: Path = typer.Argument(
470
+ Path("."),
471
+ help="Directory where the 'tensorzero' project folder will be created.",
472
+ ),
473
+ force: bool = typer.Option(
474
+ False, "--force", "-f", help="Force overwrite if project directory exists"
475
+ ),
476
+ ):
477
+ """Create the TensorZero project example."""
478
+ console.print("[bold green]Setting up the TensorZero quickstart example...[/bold green]")
479
+
480
+ dest_project_dir = directory.resolve() / "tensorzero"
481
+
482
+ # --- Find Source Directory ---
483
+ use_as_file = False
484
+ try:
485
+ # This path MUST match the "to" path from hatch_build.py
486
+ source_dir_traversable = (
487
+ files("fast_agent").joinpath("resources").joinpath("examples").joinpath("tensorzero")
488
+ )
489
+ if not source_dir_traversable.is_dir():
490
+ raise FileNotFoundError # Fallback to dev mode if resource isn't a dir
491
+ source_dir = source_dir_traversable # type: ignore
492
+ use_as_file = True
493
+ except (ImportError, ModuleNotFoundError, FileNotFoundError):
494
+ console.print(
495
+ "[yellow]Package resources not found. Falling back to development mode.[/yellow]"
496
+ )
497
+ # This path is relative to the project root in a development environment
498
+ source_dir = Path(__file__).parent.parent.parent.parent / "examples" / "tensorzero"
499
+
500
+ # Use as_file context manager if needed
501
+ with ExitStack() as stack:
502
+ if use_as_file:
503
+ source_path = stack.enter_context(as_file(source_dir)) # type: ignore
504
+ else:
505
+ source_path = source_dir # type: ignore[assignment]
506
+
507
+ if not source_path.exists() or not source_path.is_dir():
508
+ console.print(f"[red]Error: Source project directory not found at '{source_path}'[/red]")
509
+ raise typer.Exit(1)
510
+
511
+ console.print(f"Source directory: [dim]{source_path}[/dim]")
512
+ console.print(f"Destination: [dim]{dest_project_dir}[/dim]")
513
+
514
+ # --- Copy Project and Show Message ---
515
+ if copy_project_template(source_path, dest_project_dir, console, force):
516
+ console.print(
517
+ f"\n[bold green]✅ Success![/bold green] Your TensorZero project has been created in: [cyan]{dest_project_dir}[/cyan]"
518
+ )
519
+ console.print("\n[bold yellow]Next Steps:[/bold yellow]")
520
+ console.print("\n1. [bold]Navigate to your new project directory:[/bold]")
521
+ console.print(f" [cyan]cd {dest_project_dir.relative_to(Path.cwd())}[/cyan]")
522
+
523
+ console.print("\n2. [bold]Set up your API keys:[/bold]")
524
+ console.print(" [cyan]cp .env.sample .env[/cyan]")
525
+ console.print(
526
+ " [dim]Then, open the new '.env' file and add your OpenAI or Anthropic API key.[/dim]"
527
+ )
528
+
529
+ console.print(
530
+ "\n3. [bold]Start the required services (TensorZero Gateway & MCP Server):[/bold]"
531
+ )
532
+ console.print(" [cyan]docker compose up --build -d[/cyan]")
533
+ console.print(
534
+ " [dim](This builds and starts the necessary containers in the background)[/dim]"
535
+ )
536
+
537
+ console.print("\n4. [bold]Run the interactive agent:[/bold]")
538
+ console.print(" [cyan]make agent[/cyan] (or `uv run agent.py`)")
539
+ console.print("\nEnjoy exploring the TensorZero integration with fast-agent! ✨")
540
+
541
+
542
+ @app.command(name="t0", help="Alias for the TensorZero quickstart.", hidden=True)
543
+ def t0_alias(
544
+ directory: Path = typer.Argument(
545
+ Path("."), help="Directory for the 'tensorzero' project folder."
546
+ ),
547
+ force: bool = typer.Option(False, "--force", "-f", help="Force overwrite"),
548
+ ):
549
+ """Alias for the `tensorzero` command."""
550
+ tensorzero(directory, force)
551
+
552
+
553
+ @app.callback(invoke_without_command=True)
554
+ def main(ctx: typer.Context) -> None:
555
+ """Quickstart applications for fast-agent."""
556
+ if ctx.invoked_subcommand is None:
557
+ show_overview()