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,1199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive prompt functionality for agents.
|
|
3
|
+
|
|
4
|
+
This module provides interactive command-line functionality for agents,
|
|
5
|
+
extracted from the original AgentApp implementation to support the new DirectAgentApp.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
prompt = InteractivePrompt()
|
|
9
|
+
await prompt.prompt_loop(
|
|
10
|
+
send_func=agent_app.send,
|
|
11
|
+
default_agent="default_agent",
|
|
12
|
+
available_agents=["agent1", "agent2"],
|
|
13
|
+
prompt_provider=agent_app
|
|
14
|
+
)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Union, cast
|
|
19
|
+
|
|
20
|
+
from fast_agent.constants import CONTROL_MESSAGE_SAVE_HISTORY
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from fast_agent.core.agent_app import AgentApp
|
|
24
|
+
|
|
25
|
+
from mcp.types import Prompt, PromptMessage
|
|
26
|
+
from rich import print as rich_print
|
|
27
|
+
|
|
28
|
+
from fast_agent.agents.agent_types import AgentType
|
|
29
|
+
from fast_agent.history.history_exporter import HistoryExporter
|
|
30
|
+
from fast_agent.mcp.mcp_aggregator import SEP
|
|
31
|
+
from fast_agent.mcp.types import McpAgentProtocol
|
|
32
|
+
from fast_agent.types import PromptMessageExtended
|
|
33
|
+
from fast_agent.ui.enhanced_prompt import (
|
|
34
|
+
_display_agent_info_helper,
|
|
35
|
+
get_argument_input,
|
|
36
|
+
get_enhanced_input,
|
|
37
|
+
get_selection_input,
|
|
38
|
+
handle_special_commands,
|
|
39
|
+
show_mcp_status,
|
|
40
|
+
)
|
|
41
|
+
from fast_agent.ui.history_display import display_history_overview
|
|
42
|
+
from fast_agent.ui.progress_display import progress_display
|
|
43
|
+
from fast_agent.ui.usage_display import collect_agents_from_provider, display_usage_report
|
|
44
|
+
|
|
45
|
+
# Type alias for the send function
|
|
46
|
+
SendFunc = Callable[[Union[str, PromptMessage, PromptMessageExtended], str], Awaitable[str]]
|
|
47
|
+
|
|
48
|
+
# Type alias for the agent getter function
|
|
49
|
+
AgentGetter = Callable[[str], object | None]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class InteractivePrompt:
|
|
53
|
+
"""
|
|
54
|
+
Provides interactive prompt functionality that works with any agent implementation.
|
|
55
|
+
This is extracted from the original AgentApp implementation to support DirectAgentApp.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, agent_types: dict[str, AgentType] | None = None) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Initialize the interactive prompt.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
agent_types: Dictionary mapping agent names to their types for display
|
|
64
|
+
"""
|
|
65
|
+
self.agent_types: dict[str, AgentType] = agent_types or {}
|
|
66
|
+
|
|
67
|
+
async def prompt_loop(
|
|
68
|
+
self,
|
|
69
|
+
send_func: SendFunc,
|
|
70
|
+
default_agent: str,
|
|
71
|
+
available_agents: list[str],
|
|
72
|
+
prompt_provider: "AgentApp",
|
|
73
|
+
default: str = "",
|
|
74
|
+
) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Start an interactive prompt session.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
send_func: Function to send messages to agents
|
|
80
|
+
default_agent: Name of the default agent to use
|
|
81
|
+
available_agents: List of available agent names
|
|
82
|
+
prompt_provider: AgentApp instance for accessing agents and prompts
|
|
83
|
+
default: Default message to use when user presses enter
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The result of the interactive session
|
|
87
|
+
"""
|
|
88
|
+
agent = default_agent
|
|
89
|
+
if not agent:
|
|
90
|
+
if available_agents:
|
|
91
|
+
agent = available_agents[0]
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError("No default agent available")
|
|
94
|
+
|
|
95
|
+
if agent not in available_agents:
|
|
96
|
+
raise ValueError(f"No agent named '{agent}'")
|
|
97
|
+
|
|
98
|
+
# Ensure we track available agents in a set for fast lookup
|
|
99
|
+
available_agents_set = set(available_agents)
|
|
100
|
+
|
|
101
|
+
result = ""
|
|
102
|
+
while True:
|
|
103
|
+
with progress_display.paused():
|
|
104
|
+
# Use the enhanced input method with advanced features
|
|
105
|
+
user_input = await get_enhanced_input(
|
|
106
|
+
agent_name=agent,
|
|
107
|
+
default=default,
|
|
108
|
+
show_default=(default != ""),
|
|
109
|
+
show_stop_hint=True,
|
|
110
|
+
multiline=False, # Default to single-line mode
|
|
111
|
+
available_agent_names=available_agents,
|
|
112
|
+
agent_types=self.agent_types, # Pass agent types for display
|
|
113
|
+
agent_provider=prompt_provider, # Pass agent provider for info display
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Handle special commands - pass "True" to enable agent switching
|
|
117
|
+
command_result = await handle_special_commands(user_input, True)
|
|
118
|
+
|
|
119
|
+
# Check if we should switch agents
|
|
120
|
+
if isinstance(command_result, dict):
|
|
121
|
+
command_dict: dict[str, Any] = command_result
|
|
122
|
+
if "switch_agent" in command_dict:
|
|
123
|
+
new_agent = command_dict["switch_agent"]
|
|
124
|
+
if new_agent in available_agents_set:
|
|
125
|
+
agent = new_agent
|
|
126
|
+
# Display new agent info immediately when switching
|
|
127
|
+
rich_print() # Add spacing
|
|
128
|
+
await _display_agent_info_helper(agent, prompt_provider)
|
|
129
|
+
continue
|
|
130
|
+
else:
|
|
131
|
+
rich_print(f"[red]Agent '{new_agent}' not found[/red]")
|
|
132
|
+
continue
|
|
133
|
+
# Keep the existing list_prompts handler for backward compatibility
|
|
134
|
+
elif "list_prompts" in command_dict:
|
|
135
|
+
# Use the prompt_provider directly
|
|
136
|
+
await self._list_prompts(prompt_provider, agent)
|
|
137
|
+
continue
|
|
138
|
+
elif "select_prompt" in command_dict:
|
|
139
|
+
# Handle prompt selection, using both list_prompts and apply_prompt
|
|
140
|
+
prompt_name = command_dict.get("prompt_name")
|
|
141
|
+
prompt_index = command_dict.get("prompt_index")
|
|
142
|
+
|
|
143
|
+
# If a specific index was provided (from /prompt <number>)
|
|
144
|
+
if prompt_index is not None:
|
|
145
|
+
# First get a list of all prompts to look up the index
|
|
146
|
+
all_prompts = await self._get_all_prompts(prompt_provider, agent)
|
|
147
|
+
if not all_prompts:
|
|
148
|
+
rich_print("[yellow]No prompts available[/yellow]")
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
# Check if the index is valid
|
|
152
|
+
if 1 <= prompt_index <= len(all_prompts):
|
|
153
|
+
# Get the prompt at the specified index (1-based to 0-based)
|
|
154
|
+
selected_prompt = all_prompts[prompt_index - 1]
|
|
155
|
+
# Use the already created namespaced_name to ensure consistency
|
|
156
|
+
await self._select_prompt(
|
|
157
|
+
prompt_provider,
|
|
158
|
+
agent,
|
|
159
|
+
selected_prompt["namespaced_name"],
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
rich_print(
|
|
163
|
+
f"[red]Invalid prompt number: {prompt_index}. Valid range is 1-{len(all_prompts)}[/red]"
|
|
164
|
+
)
|
|
165
|
+
# Show the prompt list for convenience
|
|
166
|
+
await self._list_prompts(prompt_provider, agent)
|
|
167
|
+
else:
|
|
168
|
+
# Use the name-based selection
|
|
169
|
+
await self._select_prompt(prompt_provider, agent, prompt_name)
|
|
170
|
+
continue
|
|
171
|
+
elif "list_tools" in command_dict:
|
|
172
|
+
# Handle tools list display
|
|
173
|
+
await self._list_tools(prompt_provider, agent)
|
|
174
|
+
continue
|
|
175
|
+
elif "list_skills" in command_dict:
|
|
176
|
+
await self._list_skills(prompt_provider, agent)
|
|
177
|
+
continue
|
|
178
|
+
elif "show_usage" in command_dict:
|
|
179
|
+
# Handle usage display
|
|
180
|
+
await self._show_usage(prompt_provider, agent)
|
|
181
|
+
continue
|
|
182
|
+
elif "show_history" in command_dict:
|
|
183
|
+
history_info = command_dict.get("show_history")
|
|
184
|
+
history_agent = (
|
|
185
|
+
history_info.get("agent") if isinstance(history_info, dict) else None
|
|
186
|
+
)
|
|
187
|
+
target_agent = history_agent or agent
|
|
188
|
+
try:
|
|
189
|
+
agent_obj = prompt_provider._agent(target_agent)
|
|
190
|
+
except Exception:
|
|
191
|
+
rich_print(f"[red]Unable to load agent '{target_agent}'[/red]")
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
history = getattr(agent_obj, "message_history", [])
|
|
195
|
+
usage = getattr(agent_obj, "usage_accumulator", None)
|
|
196
|
+
display_history_overview(target_agent, history, usage)
|
|
197
|
+
continue
|
|
198
|
+
elif "clear_last" in command_dict:
|
|
199
|
+
clear_info = command_dict.get("clear_last")
|
|
200
|
+
clear_agent = (
|
|
201
|
+
clear_info.get("agent") if isinstance(clear_info, dict) else None
|
|
202
|
+
)
|
|
203
|
+
target_agent = clear_agent or agent
|
|
204
|
+
try:
|
|
205
|
+
agent_obj = prompt_provider._agent(target_agent)
|
|
206
|
+
except Exception:
|
|
207
|
+
rich_print(f"[red]Unable to load agent '{target_agent}'[/red]")
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
removed_message = None
|
|
211
|
+
pop_callable = getattr(agent_obj, "pop_last_message", None)
|
|
212
|
+
if callable(pop_callable):
|
|
213
|
+
removed_message = pop_callable()
|
|
214
|
+
else:
|
|
215
|
+
history = getattr(agent_obj, "message_history", [])
|
|
216
|
+
if history:
|
|
217
|
+
try:
|
|
218
|
+
removed_message = history.pop()
|
|
219
|
+
except Exception:
|
|
220
|
+
removed_message = None
|
|
221
|
+
|
|
222
|
+
if removed_message:
|
|
223
|
+
role = getattr(removed_message, "role", "message")
|
|
224
|
+
role_display = role.capitalize() if isinstance(role, str) else "Message"
|
|
225
|
+
rich_print(
|
|
226
|
+
f"[green]Removed last {role_display} for agent '{target_agent}'.[/green]"
|
|
227
|
+
)
|
|
228
|
+
else:
|
|
229
|
+
rich_print(
|
|
230
|
+
f"[yellow]No messages to remove for agent '{target_agent}'.[/yellow]"
|
|
231
|
+
)
|
|
232
|
+
continue
|
|
233
|
+
elif "clear_history" in command_dict:
|
|
234
|
+
clear_info = command_dict.get("clear_history")
|
|
235
|
+
clear_agent = (
|
|
236
|
+
clear_info.get("agent") if isinstance(clear_info, dict) else None
|
|
237
|
+
)
|
|
238
|
+
target_agent = clear_agent or agent
|
|
239
|
+
try:
|
|
240
|
+
agent_obj = prompt_provider._agent(target_agent)
|
|
241
|
+
except Exception:
|
|
242
|
+
rich_print(f"[red]Unable to load agent '{target_agent}'[/red]")
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
if hasattr(agent_obj, "clear"):
|
|
246
|
+
try:
|
|
247
|
+
agent_obj.clear()
|
|
248
|
+
rich_print(
|
|
249
|
+
f"[green]History cleared for agent '{target_agent}'.[/green]"
|
|
250
|
+
)
|
|
251
|
+
except Exception as exc:
|
|
252
|
+
rich_print(
|
|
253
|
+
f"[red]Failed to clear history for '{target_agent}': {exc}[/red]"
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
rich_print(
|
|
257
|
+
f"[yellow]Agent '{target_agent}' does not support clearing history.[/yellow]"
|
|
258
|
+
)
|
|
259
|
+
continue
|
|
260
|
+
elif "show_system" in command_dict:
|
|
261
|
+
# Handle system prompt display
|
|
262
|
+
await self._show_system(prompt_provider, agent)
|
|
263
|
+
continue
|
|
264
|
+
elif "show_markdown" in command_dict:
|
|
265
|
+
# Handle markdown display
|
|
266
|
+
await self._show_markdown(prompt_provider, agent)
|
|
267
|
+
continue
|
|
268
|
+
elif "show_mcp_status" in command_dict:
|
|
269
|
+
rich_print()
|
|
270
|
+
await show_mcp_status(agent, prompt_provider)
|
|
271
|
+
continue
|
|
272
|
+
elif "save_history" in command_dict:
|
|
273
|
+
# Save history for the current agent
|
|
274
|
+
filename = command_dict.get("filename")
|
|
275
|
+
try:
|
|
276
|
+
agent_obj = prompt_provider._agent(agent)
|
|
277
|
+
|
|
278
|
+
# Prefer type-safe exporter over magic string
|
|
279
|
+
saved_path = await HistoryExporter.save(agent_obj, filename)
|
|
280
|
+
rich_print(f"[green]History saved to {saved_path}[/green]")
|
|
281
|
+
except Exception:
|
|
282
|
+
# Fallback to magic string path for maximum compatibility
|
|
283
|
+
control = CONTROL_MESSAGE_SAVE_HISTORY + (
|
|
284
|
+
f" {filename}" if filename else ""
|
|
285
|
+
)
|
|
286
|
+
result = await send_func(control, agent)
|
|
287
|
+
if result:
|
|
288
|
+
rich_print(f"[green]{result}[/green]")
|
|
289
|
+
continue
|
|
290
|
+
elif "load_history" in command_dict:
|
|
291
|
+
# Load history for the current agent
|
|
292
|
+
if command_dict.get("error"):
|
|
293
|
+
rich_print(f"[red]{command_dict['error']}[/red]")
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
filename = command_dict.get("filename")
|
|
297
|
+
try:
|
|
298
|
+
from fast_agent.mcp.prompts.prompt_load import load_history_into_agent
|
|
299
|
+
|
|
300
|
+
# Get the agent object and its underlying LLM
|
|
301
|
+
agent_obj = prompt_provider._agent(agent)
|
|
302
|
+
|
|
303
|
+
# Load history directly without triggering LLM call
|
|
304
|
+
load_history_into_agent(agent_obj, Path(filename))
|
|
305
|
+
|
|
306
|
+
msg_count = len(agent_obj.message_history)
|
|
307
|
+
rich_print(
|
|
308
|
+
f"[green]Loaded {msg_count} messages from {filename}[/green]"
|
|
309
|
+
)
|
|
310
|
+
except FileNotFoundError:
|
|
311
|
+
rich_print(f"[red]File not found: {filename}[/red]")
|
|
312
|
+
except Exception as e:
|
|
313
|
+
rich_print(f"[red]Error loading history: {e}[/red]")
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
# Skip further processing if:
|
|
317
|
+
# 1. The command was handled (command_result is truthy)
|
|
318
|
+
# 2. The original input was a dictionary (special command like /prompt)
|
|
319
|
+
# 3. The command result itself is a dictionary (special command handling result)
|
|
320
|
+
# This fixes the issue where /prompt without arguments gets sent to the LLM
|
|
321
|
+
if (
|
|
322
|
+
command_result
|
|
323
|
+
or isinstance(user_input, dict)
|
|
324
|
+
or isinstance(command_result, dict)
|
|
325
|
+
):
|
|
326
|
+
continue
|
|
327
|
+
|
|
328
|
+
if user_input.upper() == "STOP":
|
|
329
|
+
return result
|
|
330
|
+
if user_input == "":
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
# Send the message to the agent
|
|
334
|
+
result = await send_func(user_input, agent)
|
|
335
|
+
|
|
336
|
+
return result
|
|
337
|
+
|
|
338
|
+
def _create_combined_separator_status(
|
|
339
|
+
self, left_content: str, right_info: str, console
|
|
340
|
+
) -> None:
|
|
341
|
+
"""
|
|
342
|
+
Create a combined separator and status line using the new visual style.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
left_content: The main content (block, arrow, name) - left justified with color
|
|
346
|
+
right_info: Supplementary information to show in brackets - right aligned
|
|
347
|
+
console: Rich console instance to use
|
|
348
|
+
"""
|
|
349
|
+
from rich.text import Text
|
|
350
|
+
|
|
351
|
+
width = console.size.width
|
|
352
|
+
|
|
353
|
+
# Create left text
|
|
354
|
+
left_text = Text.from_markup(left_content)
|
|
355
|
+
|
|
356
|
+
# Create right text if we have info
|
|
357
|
+
if right_info and right_info.strip():
|
|
358
|
+
# Add dim brackets around the right info
|
|
359
|
+
right_text = Text()
|
|
360
|
+
right_text.append("[", style="dim")
|
|
361
|
+
right_text.append_text(Text.from_markup(right_info))
|
|
362
|
+
right_text.append("]", style="dim")
|
|
363
|
+
# Calculate separator count
|
|
364
|
+
separator_count = width - left_text.cell_len - right_text.cell_len
|
|
365
|
+
if separator_count < 1:
|
|
366
|
+
separator_count = 1 # Always at least 1 separator
|
|
367
|
+
else:
|
|
368
|
+
right_text = Text("")
|
|
369
|
+
separator_count = width - left_text.cell_len
|
|
370
|
+
|
|
371
|
+
# Build the combined line
|
|
372
|
+
combined = Text()
|
|
373
|
+
combined.append_text(left_text)
|
|
374
|
+
combined.append(" ", style="default")
|
|
375
|
+
combined.append("─" * (separator_count - 1), style="dim")
|
|
376
|
+
combined.append_text(right_text)
|
|
377
|
+
|
|
378
|
+
# Print with empty line before
|
|
379
|
+
rich_print()
|
|
380
|
+
console.print(combined)
|
|
381
|
+
rich_print()
|
|
382
|
+
|
|
383
|
+
async def _get_all_prompts(self, prompt_provider: "AgentApp", agent_name: str | None = None):
|
|
384
|
+
"""
|
|
385
|
+
Get a list of all available prompts.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
prompt_provider: Provider that implements list_prompts
|
|
389
|
+
agent_name: Optional agent name (for multi-agent apps)
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
List of prompt info dictionaries, sorted by server and name
|
|
393
|
+
"""
|
|
394
|
+
try:
|
|
395
|
+
# Call list_prompts on the provider
|
|
396
|
+
prompt_servers = await prompt_provider.list_prompts(
|
|
397
|
+
namespace=None, agent_name=agent_name
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
all_prompts = []
|
|
401
|
+
|
|
402
|
+
# Process the returned prompt servers
|
|
403
|
+
if prompt_servers:
|
|
404
|
+
# First collect all prompts
|
|
405
|
+
for server_name, prompts_info in prompt_servers.items():
|
|
406
|
+
if prompts_info and hasattr(prompts_info, "prompts") and prompts_info.prompts:
|
|
407
|
+
for prompt in prompts_info.prompts:
|
|
408
|
+
# Use the SEP constant for proper namespacing
|
|
409
|
+
all_prompts.append(
|
|
410
|
+
{
|
|
411
|
+
"server": server_name,
|
|
412
|
+
"name": prompt.name,
|
|
413
|
+
"namespaced_name": f"{server_name}{SEP}{prompt.name}",
|
|
414
|
+
"title": prompt.title or None,
|
|
415
|
+
"description": prompt.description or "No description",
|
|
416
|
+
"arg_count": len(prompt.arguments or []),
|
|
417
|
+
"arguments": prompt.arguments or [],
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
elif isinstance(prompts_info, list) and prompts_info:
|
|
421
|
+
for prompt in prompts_info:
|
|
422
|
+
if isinstance(prompt, dict) and "name" in prompt:
|
|
423
|
+
all_prompts.append(
|
|
424
|
+
{
|
|
425
|
+
"server": server_name,
|
|
426
|
+
"name": prompt["name"],
|
|
427
|
+
"namespaced_name": f"{server_name}{SEP}{prompt['name']}",
|
|
428
|
+
"title": prompt.get("title", None),
|
|
429
|
+
"description": prompt.get("description", "No description"),
|
|
430
|
+
"arg_count": len(prompt.get("arguments", [])),
|
|
431
|
+
"arguments": prompt.get("arguments", []),
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
else:
|
|
435
|
+
# Handle Prompt objects from mcp.types
|
|
436
|
+
prompt_obj = cast("Prompt", prompt)
|
|
437
|
+
all_prompts.append(
|
|
438
|
+
{
|
|
439
|
+
"server": server_name,
|
|
440
|
+
"name": prompt_obj.name,
|
|
441
|
+
"namespaced_name": f"{server_name}{SEP}{prompt_obj.name}",
|
|
442
|
+
"title": prompt_obj.title or None,
|
|
443
|
+
"description": prompt_obj.description or "No description",
|
|
444
|
+
"arg_count": len(prompt_obj.arguments or []),
|
|
445
|
+
"arguments": prompt_obj.arguments or [],
|
|
446
|
+
}
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Sort prompts by server and name for consistent ordering
|
|
450
|
+
all_prompts.sort(key=lambda p: (p["server"], p["name"]))
|
|
451
|
+
|
|
452
|
+
return all_prompts
|
|
453
|
+
|
|
454
|
+
except Exception as e:
|
|
455
|
+
import traceback
|
|
456
|
+
|
|
457
|
+
from rich import print as rich_print
|
|
458
|
+
|
|
459
|
+
rich_print(f"[red]Error getting prompts: {e}[/red]")
|
|
460
|
+
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
461
|
+
return []
|
|
462
|
+
|
|
463
|
+
async def _list_prompts(self, prompt_provider: "AgentApp", agent_name: str) -> None:
|
|
464
|
+
"""
|
|
465
|
+
List available prompts for an agent.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
prompt_provider: Provider that implements list_prompts
|
|
469
|
+
agent_name: Name of the agent
|
|
470
|
+
"""
|
|
471
|
+
try:
|
|
472
|
+
# Get all prompts using the helper function
|
|
473
|
+
all_prompts = await self._get_all_prompts(prompt_provider, agent_name)
|
|
474
|
+
|
|
475
|
+
rich_print(f"\n[bold]Prompts for agent [cyan]{agent_name}[/cyan]:[/bold]")
|
|
476
|
+
|
|
477
|
+
if not all_prompts:
|
|
478
|
+
rich_print("[yellow]No prompts available for this agent[/yellow]")
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
rich_print()
|
|
482
|
+
|
|
483
|
+
# Display prompts using clean compact format
|
|
484
|
+
for i, prompt in enumerate(all_prompts, 1):
|
|
485
|
+
# Main line: [ 1] server•prompt_name Title
|
|
486
|
+
from rich.text import Text
|
|
487
|
+
|
|
488
|
+
prompt_line = Text()
|
|
489
|
+
prompt_line.append(f"[{i:2}] ", style="dim cyan")
|
|
490
|
+
prompt_line.append(f"{prompt['server']}•", style="dim green")
|
|
491
|
+
prompt_line.append(prompt["name"], style="bright_blue bold")
|
|
492
|
+
|
|
493
|
+
# Add title if available
|
|
494
|
+
if prompt["title"] and prompt["title"].strip():
|
|
495
|
+
prompt_line.append(f" {prompt['title']}", style="default")
|
|
496
|
+
|
|
497
|
+
rich_print(prompt_line)
|
|
498
|
+
|
|
499
|
+
# Description lines - show 2-3 rows if needed
|
|
500
|
+
if prompt["description"] and prompt["description"].strip():
|
|
501
|
+
description = prompt["description"].strip()
|
|
502
|
+
# Calculate rough character limit for 2-3 lines (assuming ~80 chars per line with indent)
|
|
503
|
+
char_limit = 240 # About 3 lines worth
|
|
504
|
+
|
|
505
|
+
if len(description) > char_limit:
|
|
506
|
+
# Find a good break point near the limit (prefer sentence/word boundaries)
|
|
507
|
+
truncate_pos = char_limit
|
|
508
|
+
# Look back for sentence end
|
|
509
|
+
sentence_break = description.rfind(". ", 0, char_limit + 20)
|
|
510
|
+
if sentence_break > char_limit - 50: # If we found a nearby sentence break
|
|
511
|
+
truncate_pos = sentence_break + 1
|
|
512
|
+
else:
|
|
513
|
+
# Look for word boundary
|
|
514
|
+
word_break = description.rfind(" ", 0, char_limit + 10)
|
|
515
|
+
if word_break > char_limit - 30: # If we found a nearby word break
|
|
516
|
+
truncate_pos = word_break
|
|
517
|
+
|
|
518
|
+
description = description[:truncate_pos].rstrip() + "..."
|
|
519
|
+
|
|
520
|
+
# Split into lines and wrap
|
|
521
|
+
import textwrap
|
|
522
|
+
|
|
523
|
+
wrapped_lines = textwrap.wrap(description, width=72, subsequent_indent=" ")
|
|
524
|
+
for line in wrapped_lines:
|
|
525
|
+
if line.startswith(" "): # Already indented continuation line
|
|
526
|
+
rich_print(f" [white]{line[5:]}[/white]")
|
|
527
|
+
else: # First line needs indent
|
|
528
|
+
rich_print(f" [white]{line}[/white]")
|
|
529
|
+
|
|
530
|
+
# Arguments line - show argument names if available
|
|
531
|
+
if prompt["arg_count"] > 0:
|
|
532
|
+
arg_names = prompt.get("arg_names", [])
|
|
533
|
+
required_args = prompt.get("required_args", [])
|
|
534
|
+
|
|
535
|
+
if arg_names:
|
|
536
|
+
arg_list = []
|
|
537
|
+
for arg_name in arg_names:
|
|
538
|
+
if arg_name in required_args:
|
|
539
|
+
arg_list.append(f"{arg_name}*")
|
|
540
|
+
else:
|
|
541
|
+
arg_list.append(arg_name)
|
|
542
|
+
|
|
543
|
+
args_text = ", ".join(arg_list)
|
|
544
|
+
if len(args_text) > 80:
|
|
545
|
+
args_text = args_text[:77] + "..."
|
|
546
|
+
rich_print(f" [dim magenta]args: {args_text}[/dim magenta]")
|
|
547
|
+
else:
|
|
548
|
+
rich_print(
|
|
549
|
+
f" [dim magenta]args: {prompt['arg_count']} parameter{'s' if prompt['arg_count'] != 1 else ''}[/dim magenta]"
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
rich_print() # Space between prompts
|
|
553
|
+
|
|
554
|
+
# Add usage instructions
|
|
555
|
+
rich_print(
|
|
556
|
+
"[dim]Usage: /prompt <number> to select by number, or /prompts for interactive selection[/dim]"
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
except Exception as e:
|
|
560
|
+
import traceback
|
|
561
|
+
|
|
562
|
+
rich_print(f"[red]Error listing prompts: {e}[/red]")
|
|
563
|
+
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
564
|
+
|
|
565
|
+
async def _select_prompt(
|
|
566
|
+
self,
|
|
567
|
+
prompt_provider: "AgentApp",
|
|
568
|
+
agent_name: str,
|
|
569
|
+
requested_name: str | None = None,
|
|
570
|
+
send_func: SendFunc | None = None,
|
|
571
|
+
) -> None:
|
|
572
|
+
"""
|
|
573
|
+
Select and apply a prompt.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
prompt_provider: Provider that implements list_prompts and get_prompt
|
|
577
|
+
agent_name: Name of the agent
|
|
578
|
+
requested_name: Optional name of the prompt to apply
|
|
579
|
+
"""
|
|
580
|
+
try:
|
|
581
|
+
# Get all available prompts directly from the prompt provider
|
|
582
|
+
rich_print(f"\n[bold]Fetching prompts for agent [cyan]{agent_name}[/cyan]...[/bold]")
|
|
583
|
+
|
|
584
|
+
# Call list_prompts on the provider
|
|
585
|
+
prompt_servers = await prompt_provider.list_prompts(
|
|
586
|
+
namespace=None, agent_name=agent_name
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
if not prompt_servers:
|
|
590
|
+
rich_print("[yellow]No prompts available for this agent[/yellow]")
|
|
591
|
+
return
|
|
592
|
+
|
|
593
|
+
# Process fetched prompts
|
|
594
|
+
all_prompts = []
|
|
595
|
+
for server_name, prompts_info in prompt_servers.items():
|
|
596
|
+
if not prompts_info:
|
|
597
|
+
continue
|
|
598
|
+
|
|
599
|
+
# Extract prompts
|
|
600
|
+
prompts: list[Prompt] = []
|
|
601
|
+
if hasattr(prompts_info, "prompts"):
|
|
602
|
+
prompts = prompts_info.prompts
|
|
603
|
+
elif isinstance(prompts_info, list):
|
|
604
|
+
prompts = prompts_info
|
|
605
|
+
|
|
606
|
+
# Process each prompt
|
|
607
|
+
for prompt in prompts:
|
|
608
|
+
# Get basic prompt info
|
|
609
|
+
prompt_name = prompt.name
|
|
610
|
+
prompt_title = prompt.title or None
|
|
611
|
+
prompt_description = prompt.description or "No description"
|
|
612
|
+
|
|
613
|
+
# Extract argument information
|
|
614
|
+
arg_names = []
|
|
615
|
+
required_args = []
|
|
616
|
+
optional_args = []
|
|
617
|
+
arg_descriptions = {}
|
|
618
|
+
|
|
619
|
+
# Get arguments list
|
|
620
|
+
if prompt.arguments:
|
|
621
|
+
for arg in prompt.arguments:
|
|
622
|
+
arg_names.append(arg.name)
|
|
623
|
+
|
|
624
|
+
# Store description if available
|
|
625
|
+
if arg.description:
|
|
626
|
+
arg_descriptions[arg.name] = arg.description
|
|
627
|
+
|
|
628
|
+
# Check if required
|
|
629
|
+
if arg.required:
|
|
630
|
+
required_args.append(arg.name)
|
|
631
|
+
else:
|
|
632
|
+
optional_args.append(arg.name)
|
|
633
|
+
|
|
634
|
+
# Create namespaced version using the consistent separator
|
|
635
|
+
namespaced_name = f"{server_name}{SEP}{prompt_name}"
|
|
636
|
+
|
|
637
|
+
# Add to collection
|
|
638
|
+
all_prompts.append(
|
|
639
|
+
{
|
|
640
|
+
"server": server_name,
|
|
641
|
+
"name": prompt_name,
|
|
642
|
+
"namespaced_name": namespaced_name,
|
|
643
|
+
"title": prompt_title,
|
|
644
|
+
"description": prompt_description,
|
|
645
|
+
"arg_count": len(arg_names),
|
|
646
|
+
"arg_names": arg_names,
|
|
647
|
+
"required_args": required_args,
|
|
648
|
+
"optional_args": optional_args,
|
|
649
|
+
"arg_descriptions": arg_descriptions,
|
|
650
|
+
}
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
if not all_prompts:
|
|
654
|
+
rich_print("[yellow]No prompts available for this agent[/yellow]")
|
|
655
|
+
return
|
|
656
|
+
|
|
657
|
+
# Sort prompts by server then name
|
|
658
|
+
all_prompts.sort(key=lambda p: (p["server"], p["name"]))
|
|
659
|
+
|
|
660
|
+
# Handle specifically requested prompt
|
|
661
|
+
if requested_name:
|
|
662
|
+
matching_prompts = [
|
|
663
|
+
p
|
|
664
|
+
for p in all_prompts
|
|
665
|
+
if p["name"] == requested_name or p["namespaced_name"] == requested_name
|
|
666
|
+
]
|
|
667
|
+
|
|
668
|
+
if not matching_prompts:
|
|
669
|
+
rich_print(f"[red]Prompt '{requested_name}' not found[/red]")
|
|
670
|
+
rich_print("[yellow]Available prompts:[/yellow]")
|
|
671
|
+
for p in all_prompts:
|
|
672
|
+
rich_print(f" {p['namespaced_name']}")
|
|
673
|
+
return
|
|
674
|
+
|
|
675
|
+
# If exactly one match, use it
|
|
676
|
+
if len(matching_prompts) == 1:
|
|
677
|
+
selected_prompt = matching_prompts[0]
|
|
678
|
+
else:
|
|
679
|
+
# Handle multiple matches
|
|
680
|
+
rich_print(f"[yellow]Multiple prompts match '{requested_name}':[/yellow]")
|
|
681
|
+
for i, p in enumerate(matching_prompts):
|
|
682
|
+
rich_print(f" {i + 1}. {p['namespaced_name']} - {p['description']}")
|
|
683
|
+
|
|
684
|
+
# Get user selection
|
|
685
|
+
selection = (
|
|
686
|
+
await get_selection_input("Enter prompt number to select: ", default="1")
|
|
687
|
+
or ""
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
try:
|
|
691
|
+
idx = int(selection) - 1
|
|
692
|
+
if 0 <= idx < len(matching_prompts):
|
|
693
|
+
selected_prompt = matching_prompts[idx]
|
|
694
|
+
else:
|
|
695
|
+
rich_print("[red]Invalid selection[/red]")
|
|
696
|
+
return
|
|
697
|
+
except ValueError:
|
|
698
|
+
rich_print("[red]Invalid input, please enter a number[/red]")
|
|
699
|
+
return
|
|
700
|
+
else:
|
|
701
|
+
# Show prompt selection UI using clean compact format
|
|
702
|
+
rich_print(f"\n[bold]Select a prompt for agent [cyan]{agent_name}[/cyan]:[/bold]")
|
|
703
|
+
rich_print()
|
|
704
|
+
|
|
705
|
+
# Display prompts using the same format as _list_prompts
|
|
706
|
+
for i, prompt in enumerate(all_prompts, 1):
|
|
707
|
+
# Main line: [ 1] server•prompt_name Title
|
|
708
|
+
from rich.text import Text
|
|
709
|
+
|
|
710
|
+
prompt_line = Text()
|
|
711
|
+
prompt_line.append(f"[{i:2}] ", style="dim cyan")
|
|
712
|
+
prompt_line.append(f"{prompt['server']}•", style="dim green")
|
|
713
|
+
prompt_line.append(prompt["name"], style="bright_blue bold")
|
|
714
|
+
|
|
715
|
+
# Add title if available
|
|
716
|
+
if prompt["title"] and prompt["title"].strip():
|
|
717
|
+
prompt_line.append(f" {prompt['title']}", style="default")
|
|
718
|
+
|
|
719
|
+
rich_print(prompt_line)
|
|
720
|
+
|
|
721
|
+
# Description lines - show 2-3 rows if needed
|
|
722
|
+
if prompt["description"] and prompt["description"].strip():
|
|
723
|
+
description = prompt["description"].strip()
|
|
724
|
+
# Calculate rough character limit for 2-3 lines (assuming ~80 chars per line with indent)
|
|
725
|
+
char_limit = 240 # About 3 lines worth
|
|
726
|
+
|
|
727
|
+
if len(description) > char_limit:
|
|
728
|
+
# Find a good break point near the limit (prefer sentence/word boundaries)
|
|
729
|
+
truncate_pos = char_limit
|
|
730
|
+
# Look back for sentence end
|
|
731
|
+
sentence_break = description.rfind(". ", 0, char_limit + 20)
|
|
732
|
+
if (
|
|
733
|
+
sentence_break > char_limit - 50
|
|
734
|
+
): # If we found a nearby sentence break
|
|
735
|
+
truncate_pos = sentence_break + 1
|
|
736
|
+
else:
|
|
737
|
+
# Look for word boundary
|
|
738
|
+
word_break = description.rfind(" ", 0, char_limit + 10)
|
|
739
|
+
if word_break > char_limit - 30: # If we found a nearby word break
|
|
740
|
+
truncate_pos = word_break
|
|
741
|
+
|
|
742
|
+
description = description[:truncate_pos].rstrip() + "..."
|
|
743
|
+
|
|
744
|
+
# Split into lines and wrap
|
|
745
|
+
import textwrap
|
|
746
|
+
|
|
747
|
+
wrapped_lines = textwrap.wrap(
|
|
748
|
+
description, width=72, subsequent_indent=" "
|
|
749
|
+
)
|
|
750
|
+
for line in wrapped_lines:
|
|
751
|
+
if line.startswith(" "): # Already indented continuation line
|
|
752
|
+
rich_print(f" [white]{line[5:]}[/white]")
|
|
753
|
+
else: # First line needs indent
|
|
754
|
+
rich_print(f" [white]{line}[/white]")
|
|
755
|
+
|
|
756
|
+
# Arguments line - show argument names if available
|
|
757
|
+
if prompt["arg_count"] > 0:
|
|
758
|
+
arg_names = prompt.get("arg_names", [])
|
|
759
|
+
required_args = prompt.get("required_args", [])
|
|
760
|
+
|
|
761
|
+
if arg_names:
|
|
762
|
+
arg_list = []
|
|
763
|
+
for arg_name in arg_names:
|
|
764
|
+
if arg_name in required_args:
|
|
765
|
+
arg_list.append(f"{arg_name}*")
|
|
766
|
+
else:
|
|
767
|
+
arg_list.append(arg_name)
|
|
768
|
+
|
|
769
|
+
args_text = ", ".join(arg_list)
|
|
770
|
+
if len(args_text) > 80:
|
|
771
|
+
args_text = args_text[:77] + "..."
|
|
772
|
+
rich_print(f" [dim magenta]args: {args_text}[/dim magenta]")
|
|
773
|
+
else:
|
|
774
|
+
rich_print(
|
|
775
|
+
f" [dim magenta]args: {prompt['arg_count']} parameter{'s' if prompt['arg_count'] != 1 else ''}[/dim magenta]"
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
rich_print() # Space between prompts
|
|
779
|
+
|
|
780
|
+
prompt_names = [str(i) for i, _ in enumerate(all_prompts, 1)]
|
|
781
|
+
|
|
782
|
+
# Get user selection
|
|
783
|
+
selection = await get_selection_input(
|
|
784
|
+
"Enter prompt number to select (or press Enter to cancel): ",
|
|
785
|
+
options=prompt_names,
|
|
786
|
+
allow_cancel=True,
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
# Handle cancellation
|
|
790
|
+
if not selection or selection.strip() == "":
|
|
791
|
+
rich_print("[yellow]Prompt selection cancelled[/yellow]")
|
|
792
|
+
return
|
|
793
|
+
|
|
794
|
+
try:
|
|
795
|
+
idx = int(selection) - 1
|
|
796
|
+
if 0 <= idx < len(all_prompts):
|
|
797
|
+
selected_prompt = all_prompts[idx]
|
|
798
|
+
else:
|
|
799
|
+
rich_print("[red]Invalid selection[/red]")
|
|
800
|
+
return
|
|
801
|
+
except ValueError:
|
|
802
|
+
rich_print("[red]Invalid input, please enter a number[/red]")
|
|
803
|
+
return
|
|
804
|
+
|
|
805
|
+
# Get prompt arguments
|
|
806
|
+
required_args = selected_prompt["required_args"]
|
|
807
|
+
optional_args = selected_prompt["optional_args"]
|
|
808
|
+
arg_descriptions = selected_prompt.get("arg_descriptions", {})
|
|
809
|
+
arg_values = {}
|
|
810
|
+
|
|
811
|
+
# Show argument info if any
|
|
812
|
+
if required_args or optional_args:
|
|
813
|
+
if required_args and optional_args:
|
|
814
|
+
rich_print(
|
|
815
|
+
f"\n[bold]Prompt [cyan]{selected_prompt['name']}[/cyan] requires {len(required_args)} arguments and has {len(optional_args)} optional arguments:[/bold]"
|
|
816
|
+
)
|
|
817
|
+
elif required_args:
|
|
818
|
+
rich_print(
|
|
819
|
+
f"\n[bold]Prompt [cyan]{selected_prompt['name']}[/cyan] requires {len(required_args)} arguments:[/bold]"
|
|
820
|
+
)
|
|
821
|
+
elif optional_args:
|
|
822
|
+
rich_print(
|
|
823
|
+
f"\n[bold]Prompt [cyan]{selected_prompt['name']}[/cyan] has {len(optional_args)} optional arguments:[/bold]"
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
# Collect required arguments
|
|
827
|
+
for arg_name in required_args:
|
|
828
|
+
description = arg_descriptions.get(arg_name, "")
|
|
829
|
+
arg_value = await get_argument_input(
|
|
830
|
+
arg_name=arg_name,
|
|
831
|
+
description=description,
|
|
832
|
+
required=True,
|
|
833
|
+
)
|
|
834
|
+
if arg_value is not None:
|
|
835
|
+
arg_values[arg_name] = arg_value
|
|
836
|
+
|
|
837
|
+
# Collect optional arguments
|
|
838
|
+
if optional_args:
|
|
839
|
+
for arg_name in optional_args:
|
|
840
|
+
description = arg_descriptions.get(arg_name, "")
|
|
841
|
+
arg_value = await get_argument_input(
|
|
842
|
+
arg_name=arg_name,
|
|
843
|
+
description=description,
|
|
844
|
+
required=False,
|
|
845
|
+
)
|
|
846
|
+
if arg_value:
|
|
847
|
+
arg_values[arg_name] = arg_value
|
|
848
|
+
|
|
849
|
+
# Apply the prompt using generate() for proper progress display
|
|
850
|
+
namespaced_name = selected_prompt["namespaced_name"]
|
|
851
|
+
rich_print(f"\n[bold]Applying prompt [cyan]{namespaced_name}[/cyan]...[/bold]")
|
|
852
|
+
|
|
853
|
+
# Get the agent directly for generate() call
|
|
854
|
+
assert hasattr(prompt_provider, "_agent"), (
|
|
855
|
+
"Interactive prompt expects an AgentApp with _agent()"
|
|
856
|
+
)
|
|
857
|
+
agent = prompt_provider._agent(agent_name)
|
|
858
|
+
|
|
859
|
+
try:
|
|
860
|
+
# Use agent.apply_prompt() which handles everything properly:
|
|
861
|
+
# - get_prompt() to fetch template
|
|
862
|
+
# - convert to multipart
|
|
863
|
+
# - call generate() for progress display
|
|
864
|
+
# - return response text
|
|
865
|
+
# Response display is handled by the agent's show_ methods, don't print it here
|
|
866
|
+
|
|
867
|
+
# Fetch the prompt first (without progress display)
|
|
868
|
+
prompt_result = await agent.get_prompt(namespaced_name, arg_values)
|
|
869
|
+
|
|
870
|
+
if not prompt_result or not prompt_result.messages:
|
|
871
|
+
rich_print(
|
|
872
|
+
f"[red]Prompt '{namespaced_name}' could not be found or contains no messages[/red]"
|
|
873
|
+
)
|
|
874
|
+
return
|
|
875
|
+
|
|
876
|
+
# Convert to multipart format
|
|
877
|
+
from fast_agent.types import PromptMessageExtended
|
|
878
|
+
|
|
879
|
+
multipart_messages = PromptMessageExtended.from_get_prompt_result(prompt_result)
|
|
880
|
+
|
|
881
|
+
# Now start progress display for the actual generation
|
|
882
|
+
progress_display.resume()
|
|
883
|
+
try:
|
|
884
|
+
await agent.generate(multipart_messages, None)
|
|
885
|
+
finally:
|
|
886
|
+
# Pause again for the next UI interaction
|
|
887
|
+
progress_display.pause()
|
|
888
|
+
|
|
889
|
+
# Show usage info after the turn (same as send_wrapper does)
|
|
890
|
+
prompt_provider._show_turn_usage(agent_name)
|
|
891
|
+
|
|
892
|
+
except Exception as e:
|
|
893
|
+
rich_print(f"[red]Error applying prompt: {e}[/red]")
|
|
894
|
+
|
|
895
|
+
except Exception as e:
|
|
896
|
+
import traceback
|
|
897
|
+
|
|
898
|
+
rich_print(f"[red]Error selecting or applying prompt: {e}[/red]")
|
|
899
|
+
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
900
|
+
|
|
901
|
+
async def _list_tools(self, prompt_provider: "AgentApp", agent_name: str) -> None:
|
|
902
|
+
"""
|
|
903
|
+
List available tools for an agent.
|
|
904
|
+
|
|
905
|
+
Args:
|
|
906
|
+
prompt_provider: Provider that implements list_tools
|
|
907
|
+
agent_name: Name of the agent
|
|
908
|
+
"""
|
|
909
|
+
try:
|
|
910
|
+
# Get agent to list tools from
|
|
911
|
+
assert hasattr(prompt_provider, "_agent"), (
|
|
912
|
+
"Interactive prompt expects an AgentApp with _agent()"
|
|
913
|
+
)
|
|
914
|
+
agent = prompt_provider._agent(agent_name)
|
|
915
|
+
|
|
916
|
+
rich_print(f"\n[bold]Tools for agent [cyan]{agent_name}[/cyan]:[/bold]")
|
|
917
|
+
|
|
918
|
+
# Get tools using list_tools
|
|
919
|
+
tools_result = await agent.list_tools()
|
|
920
|
+
|
|
921
|
+
if not tools_result or not hasattr(tools_result, "tools") or not tools_result.tools:
|
|
922
|
+
rich_print("[yellow]No tools available for this agent[/yellow]")
|
|
923
|
+
return
|
|
924
|
+
|
|
925
|
+
rich_print()
|
|
926
|
+
|
|
927
|
+
# Display tools using clean compact format
|
|
928
|
+
index = 1
|
|
929
|
+
for tool in tools_result.tools:
|
|
930
|
+
# Main line: [ 1] tool_name Title
|
|
931
|
+
from rich.text import Text
|
|
932
|
+
|
|
933
|
+
meta = getattr(tool, "meta", {}) or {}
|
|
934
|
+
|
|
935
|
+
tool_line = Text()
|
|
936
|
+
tool_line.append(f"[{index:2}] ", style="dim cyan")
|
|
937
|
+
tool_line.append(tool.name, style="bright_blue bold")
|
|
938
|
+
|
|
939
|
+
# Add title if available
|
|
940
|
+
if tool.title and tool.title.strip():
|
|
941
|
+
tool_line.append(f" {tool.title}", style="default")
|
|
942
|
+
|
|
943
|
+
if meta.get("openai/skybridgeEnabled"):
|
|
944
|
+
tool_line.append(" (skybridge)", style="cyan")
|
|
945
|
+
|
|
946
|
+
rich_print(tool_line)
|
|
947
|
+
|
|
948
|
+
# Description lines - show 2-3 rows if needed
|
|
949
|
+
if tool.description and tool.description.strip():
|
|
950
|
+
description = tool.description.strip()
|
|
951
|
+
# Calculate rough character limit for 2-3 lines (assuming ~80 chars per line with indent)
|
|
952
|
+
char_limit = 240 # About 3 lines worth
|
|
953
|
+
|
|
954
|
+
if len(description) > char_limit:
|
|
955
|
+
# Find a good break point near the limit (prefer sentence/word boundaries)
|
|
956
|
+
truncate_pos = char_limit
|
|
957
|
+
# Look back for sentence end
|
|
958
|
+
sentence_break = description.rfind(". ", 0, char_limit + 20)
|
|
959
|
+
if sentence_break > char_limit - 50: # If we found a nearby sentence break
|
|
960
|
+
truncate_pos = sentence_break + 1
|
|
961
|
+
else:
|
|
962
|
+
# Look for word boundary
|
|
963
|
+
word_break = description.rfind(" ", 0, char_limit + 10)
|
|
964
|
+
if word_break > char_limit - 30: # If we found a nearby word break
|
|
965
|
+
truncate_pos = word_break
|
|
966
|
+
|
|
967
|
+
description = description[:truncate_pos].rstrip() + "..."
|
|
968
|
+
|
|
969
|
+
# Split into lines and wrap
|
|
970
|
+
import textwrap
|
|
971
|
+
|
|
972
|
+
wrapped_lines = textwrap.wrap(description, width=72, subsequent_indent=" ")
|
|
973
|
+
for line in wrapped_lines:
|
|
974
|
+
if line.startswith(" "): # Already indented continuation line
|
|
975
|
+
rich_print(f" [white]{line[5:]}[/white]")
|
|
976
|
+
else: # First line needs indent
|
|
977
|
+
rich_print(f" [white]{line}[/white]")
|
|
978
|
+
|
|
979
|
+
# Arguments line - show schema info if available
|
|
980
|
+
if hasattr(tool, "inputSchema") and tool.inputSchema:
|
|
981
|
+
schema = tool.inputSchema
|
|
982
|
+
if "properties" in schema:
|
|
983
|
+
properties = schema["properties"]
|
|
984
|
+
required = schema.get("required", [])
|
|
985
|
+
|
|
986
|
+
arg_list = []
|
|
987
|
+
for prop_name, prop_info in properties.items():
|
|
988
|
+
if prop_name in required:
|
|
989
|
+
arg_list.append(f"{prop_name}*")
|
|
990
|
+
else:
|
|
991
|
+
arg_list.append(prop_name)
|
|
992
|
+
|
|
993
|
+
if arg_list:
|
|
994
|
+
args_text = ", ".join(arg_list)
|
|
995
|
+
if len(args_text) > 80:
|
|
996
|
+
args_text = args_text[:77] + "..."
|
|
997
|
+
rich_print(f" [dim magenta]args: {args_text}[/dim magenta]")
|
|
998
|
+
|
|
999
|
+
if meta.get("openai/skybridgeEnabled"):
|
|
1000
|
+
template = meta.get("openai/skybridgeTemplate")
|
|
1001
|
+
if template:
|
|
1002
|
+
rich_print(f" [dim magenta]template:[/dim magenta] {template}")
|
|
1003
|
+
|
|
1004
|
+
rich_print() # Space between tools
|
|
1005
|
+
index += 1
|
|
1006
|
+
|
|
1007
|
+
if index == 1:
|
|
1008
|
+
rich_print("[yellow]No MCP tools available for this agent[/yellow]")
|
|
1009
|
+
except Exception as e:
|
|
1010
|
+
import traceback
|
|
1011
|
+
|
|
1012
|
+
rich_print(f"[red]Error listing tools: {e}[/red]")
|
|
1013
|
+
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
1014
|
+
|
|
1015
|
+
async def _list_skills(self, prompt_provider: "AgentApp", agent_name: str) -> None:
|
|
1016
|
+
"""List available local skills for an agent."""
|
|
1017
|
+
|
|
1018
|
+
try:
|
|
1019
|
+
assert hasattr(prompt_provider, "_agent"), (
|
|
1020
|
+
"Interactive prompt expects an AgentApp with _agent()"
|
|
1021
|
+
)
|
|
1022
|
+
agent = prompt_provider._agent(agent_name)
|
|
1023
|
+
|
|
1024
|
+
rich_print(f"\n[bold]Skills for agent [cyan]{agent_name}[/cyan]:[/bold]")
|
|
1025
|
+
|
|
1026
|
+
skill_manifests = getattr(agent, "_skill_manifests", None)
|
|
1027
|
+
manifests = list(skill_manifests) if skill_manifests else []
|
|
1028
|
+
|
|
1029
|
+
if not manifests:
|
|
1030
|
+
rich_print("[yellow]No skills available for this agent[/yellow]")
|
|
1031
|
+
return
|
|
1032
|
+
|
|
1033
|
+
rich_print()
|
|
1034
|
+
|
|
1035
|
+
for index, manifest in enumerate(manifests, 1):
|
|
1036
|
+
from rich.text import Text
|
|
1037
|
+
|
|
1038
|
+
name = getattr(manifest, "name", "")
|
|
1039
|
+
description = getattr(manifest, "description", "")
|
|
1040
|
+
path = Path(getattr(manifest, "path", Path()))
|
|
1041
|
+
|
|
1042
|
+
tool_line = Text()
|
|
1043
|
+
tool_line.append(f"[{index:2}] ", style="dim cyan")
|
|
1044
|
+
tool_line.append(name, style="bright_blue bold")
|
|
1045
|
+
rich_print(tool_line)
|
|
1046
|
+
|
|
1047
|
+
if description:
|
|
1048
|
+
import textwrap
|
|
1049
|
+
|
|
1050
|
+
wrapped_lines = textwrap.wrap(
|
|
1051
|
+
description.strip(), width=72, subsequent_indent=" "
|
|
1052
|
+
)
|
|
1053
|
+
for line in wrapped_lines:
|
|
1054
|
+
if line.startswith(" "):
|
|
1055
|
+
rich_print(f" [white]{line[5:]}[/white]")
|
|
1056
|
+
else:
|
|
1057
|
+
rich_print(f" [white]{line}[/white]")
|
|
1058
|
+
|
|
1059
|
+
source_path = path if path else Path(".")
|
|
1060
|
+
if source_path.is_file():
|
|
1061
|
+
source_path = source_path.parent
|
|
1062
|
+
try:
|
|
1063
|
+
display_path = source_path.relative_to(Path.cwd())
|
|
1064
|
+
except ValueError:
|
|
1065
|
+
display_path = source_path
|
|
1066
|
+
|
|
1067
|
+
rich_print(f" [dim green]source:[/dim green] {display_path}")
|
|
1068
|
+
rich_print()
|
|
1069
|
+
|
|
1070
|
+
except Exception as exc: # noqa: BLE001
|
|
1071
|
+
import traceback
|
|
1072
|
+
|
|
1073
|
+
rich_print(f"[red]Error listing skills: {exc}[/red]")
|
|
1074
|
+
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
1075
|
+
|
|
1076
|
+
async def _show_usage(self, prompt_provider: "AgentApp", agent_name: str) -> None:
|
|
1077
|
+
"""
|
|
1078
|
+
Show usage statistics for the current agent(s) in a colorful table format.
|
|
1079
|
+
|
|
1080
|
+
Args:
|
|
1081
|
+
prompt_provider: Provider that has access to agents
|
|
1082
|
+
agent_name: Name of the current agent
|
|
1083
|
+
"""
|
|
1084
|
+
try:
|
|
1085
|
+
# Collect all agents from the prompt provider
|
|
1086
|
+
agents_to_show = collect_agents_from_provider(prompt_provider, agent_name)
|
|
1087
|
+
|
|
1088
|
+
if not agents_to_show:
|
|
1089
|
+
rich_print("[yellow]No usage data available[/yellow]")
|
|
1090
|
+
return
|
|
1091
|
+
|
|
1092
|
+
# Use the shared display utility
|
|
1093
|
+
display_usage_report(agents_to_show, show_if_progress_disabled=True)
|
|
1094
|
+
|
|
1095
|
+
except Exception as e:
|
|
1096
|
+
rich_print(f"[red]Error showing usage: {e}[/red]")
|
|
1097
|
+
|
|
1098
|
+
async def _show_system(self, prompt_provider: "AgentApp", agent_name: str) -> None:
|
|
1099
|
+
"""
|
|
1100
|
+
Show the current system prompt for the agent.
|
|
1101
|
+
|
|
1102
|
+
Args:
|
|
1103
|
+
prompt_provider: Provider that has access to agents
|
|
1104
|
+
agent_name: Name of the current agent
|
|
1105
|
+
"""
|
|
1106
|
+
try:
|
|
1107
|
+
# Get agent to display from
|
|
1108
|
+
assert hasattr(prompt_provider, "_agent"), (
|
|
1109
|
+
"Interactive prompt expects an AgentApp with _agent()"
|
|
1110
|
+
)
|
|
1111
|
+
agent = prompt_provider._agent(agent_name)
|
|
1112
|
+
|
|
1113
|
+
# Get the system prompt
|
|
1114
|
+
system_prompt = getattr(agent, "instruction", None)
|
|
1115
|
+
if not system_prompt:
|
|
1116
|
+
rich_print("[yellow]No system prompt available[/yellow]")
|
|
1117
|
+
return
|
|
1118
|
+
|
|
1119
|
+
# Get server count for display
|
|
1120
|
+
server_count = 0
|
|
1121
|
+
if isinstance(agent, McpAgentProtocol):
|
|
1122
|
+
server_names = agent.aggregator.server_names
|
|
1123
|
+
server_count = len(server_names) if server_names else 0
|
|
1124
|
+
|
|
1125
|
+
# Use the display utility to show the system prompt
|
|
1126
|
+
agent_display = getattr(agent, "display", None)
|
|
1127
|
+
if agent_display:
|
|
1128
|
+
agent_display.show_system_message(
|
|
1129
|
+
system_prompt=system_prompt, agent_name=agent_name, server_count=server_count
|
|
1130
|
+
)
|
|
1131
|
+
else:
|
|
1132
|
+
# Fallback to basic display
|
|
1133
|
+
from fast_agent.ui.console_display import ConsoleDisplay
|
|
1134
|
+
|
|
1135
|
+
agent_context = getattr(agent, "context", None)
|
|
1136
|
+
display = ConsoleDisplay(
|
|
1137
|
+
config=agent_context.config if hasattr(agent_context, "config") else None
|
|
1138
|
+
)
|
|
1139
|
+
display.show_system_message(
|
|
1140
|
+
system_prompt=system_prompt, agent_name=agent_name, server_count=server_count
|
|
1141
|
+
)
|
|
1142
|
+
|
|
1143
|
+
except Exception as e:
|
|
1144
|
+
import traceback
|
|
1145
|
+
|
|
1146
|
+
rich_print(f"[red]Error showing system prompt: {e}[/red]")
|
|
1147
|
+
rich_print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
1148
|
+
|
|
1149
|
+
async def _show_markdown(self, prompt_provider: "AgentApp", agent_name: str) -> None:
|
|
1150
|
+
"""
|
|
1151
|
+
Show the last assistant message without markdown formatting.
|
|
1152
|
+
|
|
1153
|
+
Args:
|
|
1154
|
+
prompt_provider: Provider that has access to agents
|
|
1155
|
+
agent_name: Name of the current agent
|
|
1156
|
+
"""
|
|
1157
|
+
try:
|
|
1158
|
+
# Get agent to display from
|
|
1159
|
+
assert hasattr(prompt_provider, "_agent"), (
|
|
1160
|
+
"Interactive prompt expects an AgentApp with _agent()"
|
|
1161
|
+
)
|
|
1162
|
+
agent = prompt_provider._agent(agent_name)
|
|
1163
|
+
|
|
1164
|
+
# Check if agent has message history
|
|
1165
|
+
if not agent.llm:
|
|
1166
|
+
rich_print("[yellow]No message history available[/yellow]")
|
|
1167
|
+
return
|
|
1168
|
+
|
|
1169
|
+
message_history = agent.llm.message_history
|
|
1170
|
+
if not message_history:
|
|
1171
|
+
rich_print("[yellow]No messages in history[/yellow]")
|
|
1172
|
+
return
|
|
1173
|
+
|
|
1174
|
+
# Find the last assistant message
|
|
1175
|
+
last_assistant_msg = None
|
|
1176
|
+
for msg in reversed(message_history):
|
|
1177
|
+
if msg.role == "assistant":
|
|
1178
|
+
last_assistant_msg = msg
|
|
1179
|
+
break
|
|
1180
|
+
|
|
1181
|
+
if not last_assistant_msg:
|
|
1182
|
+
rich_print("[yellow]No assistant messages found[/yellow]")
|
|
1183
|
+
return
|
|
1184
|
+
|
|
1185
|
+
# Get the text content and display without markdown
|
|
1186
|
+
content = last_assistant_msg.last_text()
|
|
1187
|
+
|
|
1188
|
+
# Display with a simple header
|
|
1189
|
+
rich_print("\n[bold blue]Last Assistant Response (Plain Text):[/bold blue]")
|
|
1190
|
+
rich_print("─" * 60)
|
|
1191
|
+
# Use console.print with markup=False to display raw text
|
|
1192
|
+
from fast_agent.ui import console
|
|
1193
|
+
|
|
1194
|
+
console.console.print(content, markup=False)
|
|
1195
|
+
rich_print("─" * 60)
|
|
1196
|
+
rich_print()
|
|
1197
|
+
|
|
1198
|
+
except Exception as e:
|
|
1199
|
+
rich_print(f"[red]Error showing markdown: {e}[/red]")
|