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,580 @@
1
+ """
2
+ Utilities for converting between different prompt message formats.
3
+
4
+ This module provides utilities for converting between different serialization formats
5
+ and PromptMessageExtended objects. It includes functionality for:
6
+
7
+ 1. JSON Serialization:
8
+ - Converting PromptMessageExtended objects to MCP-compatible GetPromptResult JSON format
9
+ - Parsing GetPromptResult JSON into PromptMessageExtended objects
10
+ - This is ideal for programmatic use and ensures full MCP compatibility
11
+
12
+ 2. Delimited Text Format:
13
+ - Converting PromptMessageExtended objects to delimited text (---USER, ---ASSISTANT)
14
+ - Converting resources to JSON after resource delimiter (---RESOURCE)
15
+ - Parsing delimited text back into PromptMessageExtended objects
16
+ - This maintains human readability for text content while preserving structure for resources
17
+ """
18
+
19
+ import json
20
+
21
+ from mcp.types import (
22
+ EmbeddedResource,
23
+ GetPromptResult,
24
+ ImageContent,
25
+ PromptMessage,
26
+ TextContent,
27
+ TextResourceContents,
28
+ )
29
+
30
+ from fast_agent.mcp.prompts.prompt_constants import (
31
+ ASSISTANT_DELIMITER,
32
+ RESOURCE_DELIMITER,
33
+ USER_DELIMITER,
34
+ )
35
+ from fast_agent.types import PromptMessageExtended
36
+
37
+ # -------------------------------------------------------------------------
38
+ # Serialization Helpers
39
+ # -------------------------------------------------------------------------
40
+
41
+
42
+ def serialize_to_dict(obj, exclude_none: bool = True):
43
+ """Standardized Pydantic serialization to dictionary.
44
+
45
+ Args:
46
+ obj: Pydantic model object to serialize
47
+ exclude_none: Whether to exclude None values (default: True)
48
+
49
+ Returns:
50
+ Dictionary representation suitable for JSON serialization
51
+ """
52
+ return obj.model_dump(by_alias=True, mode="json", exclude_none=exclude_none)
53
+
54
+
55
+ # -------------------------------------------------------------------------
56
+ # JSON Serialization Functions
57
+ # -------------------------------------------------------------------------
58
+
59
+
60
+ def to_get_prompt_result(
61
+ messages: list[PromptMessageExtended],
62
+ ) -> GetPromptResult:
63
+ """
64
+ Convert PromptMessageExtended objects to a GetPromptResult container.
65
+
66
+ Args:
67
+ messages: List of PromptMessageExtended objects
68
+
69
+ Returns:
70
+ GetPromptResult object containing flattened messages
71
+ """
72
+ # Convert multipart messages to regular PromptMessage objects
73
+ flat_messages = []
74
+ for message in messages:
75
+ flat_messages.extend(message.from_multipart())
76
+
77
+ # Create a GetPromptResult with the flattened messages
78
+ return GetPromptResult(messages=flat_messages)
79
+
80
+
81
+
82
+ def to_get_prompt_result_json(messages: list[PromptMessageExtended]) -> str:
83
+ """
84
+ Convert PromptMessageExtended objects to MCP-compatible GetPromptResult JSON.
85
+
86
+ This is a lossy conversion that flattens multipart messages and loses extended fields
87
+ like tool_calls, channels, and stop_reason. Use for MCP server compatibility.
88
+
89
+ Args:
90
+ messages: List of PromptMessageExtended objects
91
+
92
+ Returns:
93
+ JSON string in GetPromptResult format
94
+ """
95
+ result = to_get_prompt_result(messages)
96
+ result_dict = serialize_to_dict(result)
97
+ return json.dumps(result_dict, indent=2)
98
+
99
+
100
+ def to_json(messages: list[PromptMessageExtended]) -> str:
101
+ """
102
+ Convert PromptMessageExtended objects directly to JSON, preserving all extended fields.
103
+
104
+ This preserves tool_calls, tool_results, channels, and stop_reason that would be lost
105
+ in the standard GetPromptResult conversion.
106
+
107
+ Args:
108
+ messages: List of PromptMessageExtended objects
109
+
110
+ Returns:
111
+ JSON string representation preserving all PromptMessageExtended data
112
+ """
113
+ # Convert each message to dict using standardized serialization
114
+ messages_dicts = [serialize_to_dict(msg) for msg in messages]
115
+
116
+ # Wrap in a container similar to GetPromptResult for consistency
117
+ result_dict = {"messages": messages_dicts}
118
+
119
+ # Convert to JSON string
120
+ return json.dumps(result_dict, indent=2)
121
+
122
+
123
+ def from_json(json_str: str) -> list[PromptMessageExtended]:
124
+ """
125
+ Parse a JSON string into PromptMessageExtended objects.
126
+
127
+ Handles both:
128
+ - Enhanced format with full PromptMessageExtended data
129
+ - Legacy GetPromptResult format (missing extended fields default to None)
130
+
131
+ Args:
132
+ json_str: JSON string representation
133
+
134
+ Returns:
135
+ List of PromptMessageExtended objects
136
+ """
137
+ # Parse JSON to dictionary
138
+ result_dict = json.loads(json_str)
139
+
140
+ # Extract messages array
141
+ messages_data = result_dict.get("messages", [])
142
+
143
+ extended_messages: list[PromptMessageExtended] = []
144
+ basic_buffer: list[PromptMessage] = []
145
+
146
+ def flush_basic_buffer() -> None:
147
+ nonlocal basic_buffer
148
+ if not basic_buffer:
149
+ return
150
+ extended_messages.extend(PromptMessageExtended.to_extended(basic_buffer))
151
+ basic_buffer = []
152
+
153
+ for msg_data in messages_data:
154
+ content = msg_data.get("content")
155
+ is_enhanced = isinstance(content, list)
156
+ if is_enhanced:
157
+ try:
158
+ msg = PromptMessageExtended.model_validate(msg_data)
159
+ except Exception:
160
+ is_enhanced = False
161
+ else:
162
+ flush_basic_buffer()
163
+ extended_messages.append(msg)
164
+ continue
165
+
166
+ try:
167
+ basic_msg = PromptMessage.model_validate(msg_data)
168
+ except Exception:
169
+ continue
170
+ basic_buffer.append(basic_msg)
171
+
172
+ flush_basic_buffer()
173
+
174
+ return extended_messages
175
+
176
+
177
+ def save_json(messages: list[PromptMessageExtended], file_path: str) -> None:
178
+ """
179
+ Save PromptMessageExtended objects to a JSON file using enhanced format.
180
+
181
+ Uses the enhanced format that preserves tool_calls, tool_results, channels,
182
+ and stop_reason data.
183
+
184
+ Args:
185
+ messages: List of PromptMessageExtended objects
186
+ file_path: Path to save the JSON file
187
+ """
188
+ json_str = to_json(messages)
189
+
190
+ with open(file_path, "w", encoding="utf-8") as f:
191
+ f.write(json_str)
192
+
193
+
194
+ def load_json(file_path: str) -> list[PromptMessageExtended]:
195
+ """
196
+ Load PromptMessageExtended objects from a JSON file.
197
+
198
+ Handles both enhanced format and legacy GetPromptResult format.
199
+
200
+ Args:
201
+ file_path: Path to the JSON file
202
+
203
+ Returns:
204
+ List of PromptMessageExtended objects
205
+ """
206
+ with open(file_path, "r", encoding="utf-8") as f:
207
+ json_str = f.read()
208
+
209
+ return from_json(json_str)
210
+
211
+
212
+ def save_messages(messages: list[PromptMessageExtended], file_path: str) -> None:
213
+ """
214
+ Save PromptMessageExtended objects to a file, with format determined by file extension.
215
+
216
+ Uses enhanced JSON format for .json files (preserves all fields) and
217
+ delimited text format for other extensions.
218
+
219
+ Args:
220
+ messages: List of PromptMessageExtended objects
221
+ file_path: Path to save the file
222
+ """
223
+ path_str = str(file_path).lower()
224
+
225
+ if path_str.endswith(".json"):
226
+ save_json(messages, file_path)
227
+ else:
228
+ save_delimited(messages, file_path)
229
+
230
+
231
+ def load_messages(file_path: str) -> list[PromptMessageExtended]:
232
+ """
233
+ Load PromptMessageExtended objects from a file, with format determined by file extension.
234
+
235
+ Uses JSON format for .json files and delimited text format for other extensions.
236
+
237
+ Args:
238
+ file_path: Path to the file
239
+
240
+ Returns:
241
+ List of PromptMessageExtended objects
242
+ """
243
+ path_str = str(file_path).lower()
244
+
245
+ if path_str.endswith(".json"):
246
+ return load_json(file_path)
247
+ else:
248
+ return load_delimited(file_path)
249
+
250
+
251
+ # -------------------------------------------------------------------------
252
+ # Delimited Text Format Functions
253
+ # -------------------------------------------------------------------------
254
+
255
+
256
+ def multipart_messages_to_delimited_format(
257
+ messages: list[PromptMessageExtended],
258
+ user_delimiter: str = USER_DELIMITER,
259
+ assistant_delimiter: str = ASSISTANT_DELIMITER,
260
+ resource_delimiter: str = RESOURCE_DELIMITER,
261
+ combine_text: bool = True, # Set to False to maintain backward compatibility
262
+ ) -> list[str]:
263
+ """
264
+ Convert PromptMessageExtended objects to a hybrid delimited format:
265
+ - Plain text for user/assistant text content with delimiters
266
+ - JSON for resources after resource delimiter
267
+
268
+ This approach maintains human readability for text content while
269
+ preserving structure for resources.
270
+
271
+ Args:
272
+ messages: List of PromptMessageExtended objects
273
+ user_delimiter: Delimiter for user messages
274
+ assistant_delimiter: Delimiter for assistant messages
275
+ resource_delimiter: Delimiter for resources
276
+ combine_text: Whether to combine multiple text contents into one (default: True)
277
+
278
+ Returns:
279
+ List of strings representing the delimited content
280
+ """
281
+ delimited_content = []
282
+
283
+ for message in messages:
284
+ # Add role delimiter
285
+ if message.role == "user":
286
+ delimited_content.append(user_delimiter)
287
+ else:
288
+ delimited_content.append(assistant_delimiter)
289
+
290
+ # Process content parts based on combine_text preference
291
+ if combine_text:
292
+ # Collect text content parts
293
+ text_contents = []
294
+
295
+ # First, add all text content
296
+ for content in message.content:
297
+ if content.type == "text":
298
+ # Collect text content to combine
299
+ text_contents.append(content.text)
300
+
301
+ # Add combined text content if any exists
302
+ if text_contents:
303
+ delimited_content.append("\n\n".join(text_contents))
304
+
305
+ # Then add resources and images
306
+ for content in message.content:
307
+ if content.type != "text":
308
+ # Resource or image - add delimiter and JSON
309
+ delimited_content.append(resource_delimiter)
310
+
311
+ # Convert to dictionary using proper JSON mode
312
+ content_dict = serialize_to_dict(content)
313
+
314
+ # Add to delimited content as JSON
315
+ delimited_content.append(json.dumps(content_dict, indent=2))
316
+ else:
317
+ # Don't combine text contents - preserve each content part in sequence
318
+ for content in message.content:
319
+ if content.type == "text":
320
+ # Add each text content separately
321
+ delimited_content.append(content.text)
322
+ else:
323
+ # Resource or image - add delimiter and JSON
324
+ delimited_content.append(resource_delimiter)
325
+
326
+ # Convert to dictionary using proper JSON mode
327
+ content_dict = serialize_to_dict(content)
328
+
329
+ # Add to delimited content as JSON
330
+ delimited_content.append(json.dumps(content_dict, indent=2))
331
+
332
+ return delimited_content
333
+
334
+
335
+ def delimited_format_to_extended_messages(
336
+ content: str,
337
+ user_delimiter: str = USER_DELIMITER,
338
+ assistant_delimiter: str = ASSISTANT_DELIMITER,
339
+ resource_delimiter: str = RESOURCE_DELIMITER,
340
+ ) -> list[PromptMessageExtended]:
341
+ """
342
+ Parse hybrid delimited format into PromptMessageExtended objects:
343
+ - Plain text for user/assistant text content with delimiters
344
+ - JSON for resources after resource delimiter
345
+
346
+ Args:
347
+ content: String containing the delimited content
348
+ user_delimiter: Delimiter for user messages
349
+ assistant_delimiter: Delimiter for assistant messages
350
+ resource_delimiter: Delimiter for resources
351
+
352
+ Returns:
353
+ List of PromptMessageExtended objects
354
+ """
355
+ if user_delimiter not in content and assistant_delimiter not in content:
356
+ stripped = content.strip()
357
+ if not stripped:
358
+ return []
359
+ return [
360
+ PromptMessageExtended(
361
+ role="user",
362
+ content=[TextContent(type="text", text=stripped)],
363
+ )
364
+ ]
365
+
366
+ lines = content.split("\n")
367
+ messages = []
368
+
369
+ current_role = None
370
+ text_contents = [] # List of TextContent
371
+ resource_contents = [] # List of EmbeddedResource or ImageContent
372
+ collecting_json = False
373
+ json_lines = []
374
+ collecting_text = False
375
+ text_lines = []
376
+
377
+ # Check if this is a legacy format (pre-JSON serialization)
378
+ legacy_format = resource_delimiter in content and '"type":' not in content
379
+
380
+ # Add a condition to ensure we process the first user message properly
381
+ # This is the key fix: We need to process the first line correctly
382
+ if lines and lines[0].strip() == user_delimiter:
383
+ current_role = "user"
384
+ collecting_text = True
385
+
386
+ # Process each line
387
+ for line in lines[1:] if lines else []: # Skip the first line if already processed above
388
+ line_stripped = line.strip()
389
+
390
+ # Handle role delimiters
391
+ if line_stripped == user_delimiter or line_stripped == assistant_delimiter:
392
+ # Save previous message if it exists
393
+ if current_role is not None and (text_contents or resource_contents or text_lines):
394
+ # If we were collecting text, add it to the text contents
395
+ if collecting_text and text_lines:
396
+ text_contents.append(TextContent(type="text", text="\n".join(text_lines)))
397
+ text_lines = []
398
+
399
+ # Create content list with text parts first, then resource parts
400
+ combined_content = []
401
+
402
+ # Filter out any empty text content items
403
+ filtered_text_contents = [tc for tc in text_contents if tc.text.strip() != ""]
404
+
405
+ combined_content.extend(filtered_text_contents)
406
+ combined_content.extend(resource_contents)
407
+
408
+ messages.append(
409
+ PromptMessageExtended(
410
+ role=current_role,
411
+ content=combined_content,
412
+ )
413
+ )
414
+
415
+ # Start a new message
416
+ current_role = "user" if line_stripped == user_delimiter else "assistant"
417
+ text_contents = []
418
+ resource_contents = []
419
+ collecting_json = False
420
+ json_lines = []
421
+ collecting_text = False
422
+ text_lines = []
423
+
424
+ # Handle resource delimiter
425
+ elif line_stripped == resource_delimiter:
426
+ # If we were collecting text, add it to text contents
427
+ if collecting_text and text_lines:
428
+ text_contents.append(TextContent(type="text", text="\n".join(text_lines)))
429
+ text_lines = []
430
+
431
+ # Switch to collecting JSON or legacy format
432
+ collecting_text = False
433
+ collecting_json = True
434
+ json_lines = []
435
+
436
+ # Process content based on context
437
+ elif current_role is not None:
438
+ if collecting_json:
439
+ # Collect JSON data
440
+ json_lines.append(line)
441
+
442
+ # For legacy format or files where resources are just plain text
443
+ if legacy_format and line_stripped and not line_stripped.startswith("{"):
444
+ # This is probably a legacy resource reference like a filename
445
+ resource_uri = line_stripped
446
+ if not resource_uri.startswith("resource://"):
447
+ resource_uri = f"resource://fast-agent/{resource_uri}"
448
+
449
+ # Create a simple resource with just the URI
450
+ # For legacy format, we don't have the actual content, just the reference
451
+ resource = EmbeddedResource(
452
+ type="resource",
453
+ resource=TextResourceContents(
454
+ uri=resource_uri,
455
+ mimeType="text/plain",
456
+ text="", # Legacy format doesn't include content
457
+ ),
458
+ )
459
+ resource_contents.append(resource)
460
+ collecting_json = False
461
+ json_lines = []
462
+ continue
463
+
464
+ # Try to parse the JSON to see if we have a complete object
465
+ try:
466
+ json_text = "\n".join(json_lines)
467
+ json_data = json.loads(json_text)
468
+
469
+ # Successfully parsed JSON
470
+ content_type = json_data.get("type")
471
+
472
+ if content_type == "resource":
473
+ # Create resource object using model_validate
474
+ resource = EmbeddedResource.model_validate(json_data)
475
+ resource_contents.append(resource) # Add to resource contents
476
+ elif content_type == "image":
477
+ # Create image object using model_validate
478
+ image = ImageContent.model_validate(json_data)
479
+ resource_contents.append(image) # Add to resource contents
480
+
481
+ # Reset JSON collection
482
+ collecting_json = False
483
+ json_lines = []
484
+
485
+ except json.JSONDecodeError:
486
+ # Not a complete JSON object yet, keep collecting
487
+ pass
488
+ else:
489
+ # Regular text content
490
+ if not collecting_text:
491
+ collecting_text = True
492
+ text_lines = []
493
+
494
+ text_lines.append(line)
495
+
496
+ # Handle any remaining content
497
+ if current_role is not None:
498
+ # Add any remaining text
499
+ if collecting_text and text_lines:
500
+ text_contents.append(TextContent(type="text", text="\n".join(text_lines)))
501
+
502
+ # Add the final message if it has content
503
+ if text_contents or resource_contents:
504
+ # Create content list with text parts first, then resource parts
505
+ combined_content = []
506
+
507
+ # Filter out any empty text content items
508
+ filtered_text_contents = [tc for tc in text_contents if tc.text.strip() != ""]
509
+
510
+ combined_content.extend(filtered_text_contents)
511
+ combined_content.extend(resource_contents)
512
+
513
+ messages.append(
514
+ PromptMessageExtended(
515
+ role=current_role,
516
+ content=combined_content,
517
+ )
518
+ )
519
+
520
+ return messages
521
+
522
+
523
+ def save_delimited(
524
+ messages: list[PromptMessageExtended],
525
+ file_path: str,
526
+ user_delimiter: str = USER_DELIMITER,
527
+ assistant_delimiter: str = ASSISTANT_DELIMITER,
528
+ resource_delimiter: str = RESOURCE_DELIMITER,
529
+ combine_text: bool = True,
530
+ ) -> None:
531
+ """
532
+ Save PromptMessageExtended objects to a file in hybrid delimited format.
533
+
534
+ Args:
535
+ messages: List of PromptMessageExtended objects
536
+ file_path: Path to save the file
537
+ user_delimiter: Delimiter for user messages
538
+ assistant_delimiter: Delimiter for assistant messages
539
+ resource_delimiter: Delimiter for resources
540
+ combine_text: Whether to combine multiple text contents into one (default: True)
541
+ """
542
+ delimited_content = multipart_messages_to_delimited_format(
543
+ messages,
544
+ user_delimiter,
545
+ assistant_delimiter,
546
+ resource_delimiter,
547
+ combine_text=combine_text,
548
+ )
549
+
550
+ with open(file_path, "w", encoding="utf-8") as f:
551
+ f.write("\n".join(delimited_content))
552
+
553
+
554
+ def load_delimited(
555
+ file_path: str,
556
+ user_delimiter: str = USER_DELIMITER,
557
+ assistant_delimiter: str = ASSISTANT_DELIMITER,
558
+ resource_delimiter: str = RESOURCE_DELIMITER,
559
+ ) -> list[PromptMessageExtended]:
560
+ """
561
+ Load PromptMessageExtended objects from a file in hybrid delimited format.
562
+
563
+ Args:
564
+ file_path: Path to the file
565
+ user_delimiter: Delimiter for user messages
566
+ assistant_delimiter: Delimiter for assistant messages
567
+ resource_delimiter: Delimiter for resources
568
+
569
+ Returns:
570
+ List of PromptMessageExtended objects
571
+ """
572
+ with open(file_path, "r", encoding="utf-8") as f:
573
+ content = f.read()
574
+
575
+ return delimited_format_to_extended_messages(
576
+ content,
577
+ user_delimiter,
578
+ assistant_delimiter,
579
+ resource_delimiter,
580
+ )
File without changes
@@ -0,0 +1,7 @@
1
+ from fast_agent.mcp.prompts.prompt_server import main
2
+
3
+ # For the entry point in pyproject.toml
4
+ app = main
5
+
6
+ if __name__ == "__main__":
7
+ main()
@@ -0,0 +1,18 @@
1
+ """
2
+ Constants for the prompt system.
3
+
4
+ This module defines constants used throughout the prompt system, including
5
+ delimiters for parsing prompt files and serializing prompt messages.
6
+ """
7
+
8
+ # Standard delimiters used for prompt template parsing and serialization
9
+ USER_DELIMITER = "---USER"
10
+ ASSISTANT_DELIMITER = "---ASSISTANT"
11
+ RESOURCE_DELIMITER = "---RESOURCE"
12
+
13
+ # Default delimiter mapping used by PromptTemplate and PromptTemplateLoader
14
+ DEFAULT_DELIMITER_MAP = {
15
+ USER_DELIMITER: "user",
16
+ ASSISTANT_DELIMITER: "assistant",
17
+ RESOURCE_DELIMITER: "resource",
18
+ }