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,238 @@
1
+ """
2
+ Helper functions for working with PromptMessage and PromptMessageExtended objects.
3
+
4
+ These utilities simplify extracting content from nested message structures
5
+ without repetitive type checking.
6
+ """
7
+
8
+ from typing import Union, cast
9
+
10
+ from mcp.types import (
11
+ EmbeddedResource,
12
+ PromptMessage,
13
+ TextContent,
14
+ )
15
+
16
+ from fast_agent.mcp.helpers.content_helpers import get_image_data, get_text
17
+
18
+ # Forward reference for PromptMessageExtended, actual import happens at runtime
19
+ PromptMessageExtendedType = Union[object] # Will be replaced with actual type
20
+ try:
21
+ from fast_agent.types import PromptMessageExtended
22
+
23
+ PromptMessageExtendedType = PromptMessageExtended
24
+ except ImportError:
25
+ # During initialization, there might be a circular import.
26
+ # We'll handle this gracefully.
27
+ pass
28
+
29
+
30
+ class MessageContent:
31
+ """
32
+ Helper class for working with message content in both PromptMessage and
33
+ PromptMessageExtended objects.
34
+ """
35
+
36
+ @staticmethod
37
+ def get_all_text(message: Union[PromptMessage, "PromptMessageExtended"]) -> list[str]:
38
+ """
39
+ Extract all text content from a message.
40
+
41
+ Args:
42
+ message: A PromptMessage or PromptMessageExtended
43
+
44
+ Returns:
45
+ List of text strings from all text content parts
46
+ """
47
+ if isinstance(message, PromptMessage):
48
+ text = get_text(message.content)
49
+ return [text] if text is not None else []
50
+
51
+ result = []
52
+ for content in message.content:
53
+ text = get_text(content)
54
+ if text is not None:
55
+ result.append(text)
56
+
57
+ return result
58
+
59
+ @staticmethod
60
+ def join_text(
61
+ message: Union[PromptMessage, "PromptMessageExtended"], separator: str = "\n\n"
62
+ ) -> str:
63
+ """
64
+ Join all text content in a message with a separator.
65
+
66
+ Args:
67
+ message: A PromptMessage or PromptMessageExtended
68
+ separator: String to use as separator (default: newlines)
69
+
70
+ Returns:
71
+ Joined text string
72
+ """
73
+ return separator.join(MessageContent.get_all_text(message))
74
+
75
+ @staticmethod
76
+ def get_first_text(message: Union[PromptMessage, "PromptMessageExtended"]) -> str | None:
77
+ """
78
+ Get the first available text content from a message.
79
+
80
+ Args:
81
+ message: A PromptMessage or PromptMessageExtended
82
+
83
+ Returns:
84
+ First text content or None if no text content exists
85
+ """
86
+ if isinstance(message, PromptMessage):
87
+ return get_text(message.content)
88
+
89
+ for content in message.content:
90
+ text = get_text(content)
91
+ if text is not None:
92
+ return text
93
+
94
+ return None
95
+
96
+ @staticmethod
97
+ def has_text_at_first_position(message: Union[PromptMessage, "PromptMessageExtended"]) -> bool:
98
+ """
99
+ Check if a message has a TextContent at the first position.
100
+ This is a common case when dealing with messages that start with text.
101
+
102
+ Args:
103
+ message: A PromptMessage or PromptMessageExtended
104
+
105
+ Returns:
106
+ True if the message has TextContent at first position, False otherwise
107
+ """
108
+ if isinstance(message, PromptMessage):
109
+ return isinstance(message.content, TextContent)
110
+
111
+ # For multipart messages, check if there's at least one item and the first is TextContent
112
+ return bool(message.content) and isinstance(message.content[0], TextContent)
113
+
114
+ @staticmethod
115
+ def get_text_at_first_position(
116
+ message: Union[PromptMessage, "PromptMessageExtended"],
117
+ ) -> str | None:
118
+ """
119
+ Get the text from the first position of a message if it's TextContent.
120
+
121
+ Args:
122
+ message: A PromptMessage or PromptMessageExtended
123
+
124
+ Returns:
125
+ The text content at the first position if it's TextContent,
126
+ None otherwise
127
+ """
128
+ if not MessageContent.has_text_at_first_position(message):
129
+ return None
130
+
131
+ if isinstance(message, PromptMessage):
132
+ return cast("TextContent", message.content).text
133
+
134
+ # Safe to cast since we've verified the first item is TextContent
135
+ return cast("TextContent", message.content[0]).text
136
+
137
+ @staticmethod
138
+ def get_all_images(message: Union[PromptMessage, "PromptMessageExtended"]) -> list[str]:
139
+ """
140
+ Extract all image data from a message.
141
+
142
+ Args:
143
+ message: A PromptMessage or PromptMessageExtended
144
+
145
+ Returns:
146
+ List of image data strings from all image content parts
147
+ """
148
+ if isinstance(message, PromptMessage):
149
+ img_data = get_image_data(message.content)
150
+ return [img_data] if img_data is not None else []
151
+
152
+ result = []
153
+ for content in message.content:
154
+ img_data = get_image_data(content)
155
+ if img_data is not None:
156
+ result.append(img_data)
157
+
158
+ return result
159
+
160
+ @staticmethod
161
+ def get_first_image(message: Union[PromptMessage, "PromptMessageExtended"]) -> str | None:
162
+ """
163
+ Get the first available image data from a message.
164
+
165
+ Args:
166
+ message: A PromptMessage or PromptMessageExtended
167
+
168
+ Returns:
169
+ First image data or None if no image content exists
170
+ """
171
+ if isinstance(message, PromptMessage):
172
+ return get_image_data(message.content)
173
+
174
+ for content in message.content:
175
+ img_data = get_image_data(content)
176
+ if img_data is not None:
177
+ return img_data
178
+
179
+ return None
180
+
181
+ @staticmethod
182
+ def get_all_resources(
183
+ message: Union[PromptMessage, "PromptMessageExtended"],
184
+ ) -> list[EmbeddedResource]:
185
+ """
186
+ Extract all embedded resources from a message.
187
+
188
+ Args:
189
+ message: A PromptMessage or PromptMessageExtended
190
+
191
+ Returns:
192
+ List of EmbeddedResource objects
193
+ """
194
+ if isinstance(message, PromptMessage):
195
+ if isinstance(message.content, EmbeddedResource):
196
+ return [message.content]
197
+ return []
198
+
199
+ return [content for content in message.content if isinstance(content, EmbeddedResource)]
200
+
201
+ @staticmethod
202
+ def has_text(message: Union[PromptMessage, "PromptMessageExtended"]) -> bool:
203
+ """
204
+ Check if the message has any text content.
205
+
206
+ Args:
207
+ message: A PromptMessage or PromptMessageExtended
208
+
209
+ Returns:
210
+ True if the message has text content, False otherwise
211
+ """
212
+ return len(MessageContent.get_all_text(message)) > 0
213
+
214
+ @staticmethod
215
+ def has_images(message: Union[PromptMessage, "PromptMessageExtended"]) -> bool:
216
+ """
217
+ Check if the message has any image content.
218
+
219
+ Args:
220
+ message: A PromptMessage or PromptMessageExtended
221
+
222
+ Returns:
223
+ True if the message has image content, False otherwise
224
+ """
225
+ return len(MessageContent.get_all_images(message)) > 0
226
+
227
+ @staticmethod
228
+ def has_resources(message: Union[PromptMessage, "PromptMessageExtended"]) -> bool:
229
+ """
230
+ Check if the message has any embedded resources.
231
+
232
+ Args:
233
+ message: A PromptMessage or PromptMessageExtended
234
+
235
+ Returns:
236
+ True if the message has embedded resources, False otherwise
237
+ """
238
+ return len(MessageContent.get_all_resources(message)) > 0
@@ -0,0 +1,186 @@
1
+ from pathlib import Path
2
+ from typing import Literal
3
+
4
+ from mcp.server.fastmcp.prompts.base import (
5
+ AssistantMessage,
6
+ Message,
7
+ UserMessage,
8
+ )
9
+ from mcp.types import PromptMessage, TextContent
10
+
11
+ from fast_agent.core.logging.logger import get_logger
12
+ from fast_agent.interfaces import AgentProtocol
13
+ from fast_agent.mcp import mime_utils, resource_utils
14
+ from fast_agent.mcp.prompts.prompt_template import (
15
+ PromptContent,
16
+ )
17
+ from fast_agent.types import PromptMessageExtended
18
+
19
+ # Define message role type
20
+ MessageRole = Literal["user", "assistant"]
21
+ logger = get_logger("prompt_load")
22
+
23
+
24
+ def cast_message_role(role: str) -> MessageRole:
25
+ """Cast a string role to a MessageRole literal type"""
26
+ if role == "user" or role == "assistant":
27
+ return role # type: ignore
28
+ # Default to user if the role is invalid
29
+ logger.warning(f"Invalid message role: {role}, defaulting to 'user'")
30
+ return "user"
31
+
32
+
33
+ def create_messages_with_resources(
34
+ content_sections: list[PromptContent], prompt_files: list[Path]
35
+ ) -> list[PromptMessage]:
36
+ """
37
+ Create a list of messages from content sections, with resources properly handled.
38
+
39
+ This implementation produces one message for each content section's text,
40
+ followed by separate messages for each resource (with the same role type
41
+ as the section they belong to).
42
+
43
+ Args:
44
+ content_sections: List of PromptContent objects
45
+ prompt_files: List of prompt files (to help locate resource files)
46
+
47
+ Returns:
48
+ List of Message objects
49
+ """
50
+
51
+ messages = []
52
+
53
+ for section in content_sections:
54
+ # Convert to our literal type for role
55
+ role = cast_message_role(section.role)
56
+
57
+ # Add the text message
58
+ messages.append(create_content_message(section.text, role))
59
+
60
+ # Add resource messages with the same role type as the section
61
+ for resource_path in section.resources:
62
+ try:
63
+ # Load resource with information about its type
64
+ resource_content, mime_type, is_binary = resource_utils.load_resource_content(
65
+ resource_path, prompt_files
66
+ )
67
+
68
+ # Create and add the resource message
69
+ resource_message = create_resource_message(
70
+ resource_path, resource_content, mime_type, is_binary, role
71
+ )
72
+ messages.append(resource_message)
73
+ except Exception as e:
74
+ logger.error(f"Error loading resource {resource_path}: {e}")
75
+
76
+ return messages
77
+
78
+
79
+ def create_content_message(text: str, role: MessageRole) -> PromptMessage:
80
+ """Create a text content message with the specified role"""
81
+ return PromptMessage(role=role, content=TextContent(type="text", text=text))
82
+
83
+
84
+ def create_resource_message(
85
+ resource_path: str, content: str, mime_type: str, is_binary: bool, role: MessageRole
86
+ ) -> Message:
87
+ """Create a resource message with the specified content and role"""
88
+ message_class = UserMessage if role == "user" else AssistantMessage
89
+
90
+ if mime_utils.is_image_mime_type(mime_type):
91
+ # For images, create an ImageContent
92
+ image_content = resource_utils.create_image_content(data=content, mime_type=mime_type)
93
+ return message_class(content=image_content)
94
+ else:
95
+ # For other resources, create an EmbeddedResource
96
+ embedded_resource = resource_utils.create_embedded_resource(
97
+ resource_path, content, mime_type, is_binary
98
+ )
99
+ return message_class(content=embedded_resource)
100
+
101
+
102
+ def load_prompt(file: Path | str) -> list[PromptMessageExtended]:
103
+ """
104
+ Load a prompt from a file and return as PromptMessageExtended objects.
105
+
106
+ The loader uses file extension to determine the format:
107
+ - .json files are loaded using enhanced format that preserves tool_calls, channels, etc.
108
+ - All other files are loaded using the template-based delimited format with resource loading
109
+
110
+ Args:
111
+ file: Path to the prompt file (Path object or string)
112
+
113
+ Returns:
114
+ List of PromptMessageExtended objects with full conversation state
115
+ """
116
+ if isinstance(file, str):
117
+ file = Path(file)
118
+ path_str = str(file).lower()
119
+
120
+ if path_str.endswith(".json"):
121
+ # JSON files use the serialization module directly
122
+ from fast_agent.mcp.prompt_serialization import load_messages
123
+
124
+ return load_messages(str(file))
125
+ else:
126
+ # Non-JSON files need template processing for resource loading
127
+ from fast_agent.mcp.prompts.prompt_template import PromptTemplateLoader
128
+
129
+ loader = PromptTemplateLoader()
130
+ template = loader.load_from_file(file)
131
+
132
+ # Render the template without arguments to get the messages
133
+ messages = create_messages_with_resources(
134
+ template.content_sections,
135
+ [file], # Pass the file path for resource resolution
136
+ )
137
+
138
+ # Convert to PromptMessageExtended
139
+ return PromptMessageExtended.to_extended(messages)
140
+
141
+
142
+ def load_prompt_as_get_prompt_result(file: Path):
143
+ """
144
+ Load a prompt from a file and convert to GetPromptResult format for MCP compatibility.
145
+
146
+ This loses extended fields (tool_calls, channels, etc.) but provides
147
+ compatibility with MCP prompt servers.
148
+
149
+ Args:
150
+ file: Path to the prompt file
151
+
152
+ Returns:
153
+ GetPromptResult object for MCP compatibility
154
+ """
155
+ from fast_agent.mcp.prompt_serialization import to_get_prompt_result
156
+
157
+ # Load with full data
158
+ messages = load_prompt(file)
159
+
160
+ # Convert to GetPromptResult (loses extended fields)
161
+ return to_get_prompt_result(messages)
162
+
163
+
164
+ def load_history_into_agent(agent: AgentProtocol, file_path: Path) -> None:
165
+ """
166
+ Load conversation history directly into agent without triggering LLM call.
167
+
168
+ This function restores saved conversation state by directly setting the
169
+ agent's _message_history. No LLM API calls are made.
170
+
171
+ Args:
172
+ agent: Agent instance to restore history into (FastAgentLLM or subclass)
173
+ file_path: Path to saved history file (JSON or template format)
174
+
175
+ Note:
176
+ - The agent's history is cleared before loading
177
+ - Provider diagnostic history will be updated on the next API call
178
+ - Templates are NOT cleared by this function
179
+ """
180
+ messages = load_prompt(file_path)
181
+
182
+ # Direct restoration - no LLM call
183
+ agent.clear(clear_prompts=True)
184
+ agent.message_history.extend(messages)
185
+
186
+ # Note: Provider diagnostic history will be updated on next API call