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.
- fast_agent/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- 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
|