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,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,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
|
+
}
|