fast-agent-mcp 0.2.58__py3-none-any.whl → 0.3.0__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.
Potentially problematic release.
This version of fast-agent-mcp might be problematic. Click here for more details.
- fast_agent/__init__.py +127 -0
- fast_agent/agents/__init__.py +36 -0
- {mcp_agent/core → fast_agent/agents}/agent_types.py +2 -1
- fast_agent/agents/llm_agent.py +217 -0
- fast_agent/agents/llm_decorator.py +486 -0
- mcp_agent/agents/base_agent.py → fast_agent/agents/mcp_agent.py +377 -385
- fast_agent/agents/tool_agent.py +168 -0
- {mcp_agent → fast_agent}/agents/workflow/chain_agent.py +43 -33
- {mcp_agent → fast_agent}/agents/workflow/evaluator_optimizer.py +31 -35
- {mcp_agent → fast_agent}/agents/workflow/iterative_planner.py +56 -47
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_models.py +4 -4
- {mcp_agent → fast_agent}/agents/workflow/parallel_agent.py +34 -41
- {mcp_agent → fast_agent}/agents/workflow/router_agent.py +54 -39
- {mcp_agent → fast_agent}/cli/__main__.py +5 -3
- {mcp_agent → fast_agent}/cli/commands/check_config.py +95 -66
- {mcp_agent → fast_agent}/cli/commands/go.py +20 -11
- {mcp_agent → fast_agent}/cli/commands/quickstart.py +4 -4
- {mcp_agent → fast_agent}/cli/commands/server_helpers.py +1 -1
- {mcp_agent → fast_agent}/cli/commands/setup.py +64 -134
- {mcp_agent → fast_agent}/cli/commands/url_parser.py +9 -8
- {mcp_agent → fast_agent}/cli/main.py +36 -16
- {mcp_agent → fast_agent}/cli/terminal.py +2 -2
- {mcp_agent → fast_agent}/config.py +10 -2
- fast_agent/constants.py +8 -0
- {mcp_agent → fast_agent}/context.py +24 -19
- {mcp_agent → fast_agent}/context_dependent.py +9 -5
- fast_agent/core/__init__.py +17 -0
- {mcp_agent → fast_agent}/core/agent_app.py +39 -36
- fast_agent/core/core_app.py +135 -0
- {mcp_agent → fast_agent}/core/direct_decorators.py +12 -26
- {mcp_agent → fast_agent}/core/direct_factory.py +95 -73
- {mcp_agent → fast_agent/core}/executor/executor.py +4 -5
- {mcp_agent → fast_agent}/core/fastagent.py +32 -32
- fast_agent/core/logging/__init__.py +5 -0
- {mcp_agent → fast_agent/core}/logging/events.py +3 -3
- {mcp_agent → fast_agent/core}/logging/json_serializer.py +1 -1
- {mcp_agent → fast_agent/core}/logging/listeners.py +85 -7
- {mcp_agent → fast_agent/core}/logging/logger.py +7 -7
- {mcp_agent → fast_agent/core}/logging/transport.py +10 -11
- fast_agent/core/prompt.py +9 -0
- {mcp_agent → fast_agent}/core/validation.py +4 -4
- fast_agent/event_progress.py +61 -0
- fast_agent/history/history_exporter.py +44 -0
- {mcp_agent → fast_agent}/human_input/__init__.py +9 -12
- {mcp_agent → fast_agent}/human_input/elicitation_handler.py +26 -8
- {mcp_agent → fast_agent}/human_input/elicitation_state.py +7 -7
- {mcp_agent → fast_agent}/human_input/simple_form.py +6 -4
- {mcp_agent → fast_agent}/human_input/types.py +1 -18
- fast_agent/interfaces.py +228 -0
- fast_agent/llm/__init__.py +9 -0
- mcp_agent/llm/augmented_llm.py → fast_agent/llm/fastagent_llm.py +127 -218
- fast_agent/llm/internal/passthrough.py +137 -0
- mcp_agent/llm/augmented_llm_playback.py → fast_agent/llm/internal/playback.py +29 -25
- mcp_agent/llm/augmented_llm_silent.py → fast_agent/llm/internal/silent.py +10 -17
- fast_agent/llm/internal/slow.py +38 -0
- {mcp_agent → fast_agent}/llm/memory.py +40 -30
- {mcp_agent → fast_agent}/llm/model_database.py +35 -2
- {mcp_agent → fast_agent}/llm/model_factory.py +103 -77
- fast_agent/llm/model_info.py +126 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/anthropic_utils.py +7 -7
- fast_agent/llm/provider/anthropic/llm_anthropic.py +603 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/multipart_converter_anthropic.py +79 -86
- {mcp_agent/llm/providers → fast_agent/llm/provider/bedrock}/bedrock_utils.py +3 -1
- mcp_agent/llm/providers/augmented_llm_bedrock.py → fast_agent/llm/provider/bedrock/llm_bedrock.py +833 -717
- {mcp_agent/llm/providers → fast_agent/llm/provider/google}/google_converter.py +66 -14
- fast_agent/llm/provider/google/llm_google_native.py +431 -0
- mcp_agent/llm/providers/augmented_llm_aliyun.py → fast_agent/llm/provider/openai/llm_aliyun.py +6 -7
- mcp_agent/llm/providers/augmented_llm_azure.py → fast_agent/llm/provider/openai/llm_azure.py +4 -4
- mcp_agent/llm/providers/augmented_llm_deepseek.py → fast_agent/llm/provider/openai/llm_deepseek.py +10 -11
- mcp_agent/llm/providers/augmented_llm_generic.py → fast_agent/llm/provider/openai/llm_generic.py +4 -4
- mcp_agent/llm/providers/augmented_llm_google_oai.py → fast_agent/llm/provider/openai/llm_google_oai.py +4 -4
- mcp_agent/llm/providers/augmented_llm_groq.py → fast_agent/llm/provider/openai/llm_groq.py +14 -16
- mcp_agent/llm/providers/augmented_llm_openai.py → fast_agent/llm/provider/openai/llm_openai.py +133 -207
- mcp_agent/llm/providers/augmented_llm_openrouter.py → fast_agent/llm/provider/openai/llm_openrouter.py +6 -6
- mcp_agent/llm/providers/augmented_llm_tensorzero_openai.py → fast_agent/llm/provider/openai/llm_tensorzero_openai.py +17 -16
- mcp_agent/llm/providers/augmented_llm_xai.py → fast_agent/llm/provider/openai/llm_xai.py +6 -6
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/multipart_converter_openai.py +125 -63
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_multipart.py +12 -12
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_utils.py +18 -16
- {mcp_agent → fast_agent}/llm/provider_key_manager.py +2 -2
- {mcp_agent → fast_agent}/llm/provider_types.py +2 -0
- {mcp_agent → fast_agent}/llm/sampling_converter.py +15 -12
- {mcp_agent → fast_agent}/llm/usage_tracking.py +23 -5
- fast_agent/mcp/__init__.py +43 -0
- {mcp_agent → fast_agent}/mcp/elicitation_factory.py +3 -3
- {mcp_agent → fast_agent}/mcp/elicitation_handlers.py +19 -10
- {mcp_agent → fast_agent}/mcp/gen_client.py +3 -3
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +183 -0
- {mcp_agent → fast_agent}/mcp/helpers/server_config_helpers.py +8 -8
- {mcp_agent → fast_agent}/mcp/hf_auth.py +25 -23
- fast_agent/mcp/interfaces.py +93 -0
- {mcp_agent → fast_agent}/mcp/logger_textio.py +4 -4
- {mcp_agent → fast_agent}/mcp/mcp_agent_client_session.py +49 -44
- {mcp_agent → fast_agent}/mcp/mcp_aggregator.py +66 -115
- {mcp_agent → fast_agent}/mcp/mcp_connection_manager.py +16 -23
- {mcp_agent/core → fast_agent/mcp}/mcp_content.py +23 -15
- {mcp_agent → fast_agent}/mcp/mime_utils.py +39 -0
- fast_agent/mcp/prompt.py +159 -0
- mcp_agent/mcp/prompt_message_multipart.py → fast_agent/mcp/prompt_message_extended.py +27 -20
- {mcp_agent → fast_agent}/mcp/prompt_render.py +21 -19
- {mcp_agent → fast_agent}/mcp/prompt_serialization.py +46 -46
- fast_agent/mcp/prompts/__main__.py +7 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_helpers.py +31 -30
- {mcp_agent → fast_agent}/mcp/prompts/prompt_load.py +8 -8
- {mcp_agent → fast_agent}/mcp/prompts/prompt_server.py +11 -19
- {mcp_agent → fast_agent}/mcp/prompts/prompt_template.py +18 -18
- {mcp_agent → fast_agent}/mcp/resource_utils.py +1 -1
- {mcp_agent → fast_agent}/mcp/sampling.py +31 -26
- {mcp_agent/mcp_server → fast_agent/mcp/server}/__init__.py +1 -1
- {mcp_agent/mcp_server → fast_agent/mcp/server}/agent_server.py +5 -6
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +90 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis-campaign.py +5 -4
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/forms_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character_handler.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/tool_call.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_one.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_two.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-eval.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-imp.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/tensorzero/agent.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/tensorzero/image_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/tensorzero/simple_agent.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/chaining.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/evaluator.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/human_input.py +5 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/orchestrator.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/parallel.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/workflows/router.py +5 -2
- fast_agent/resources/setup/.gitignore +24 -0
- fast_agent/resources/setup/agent.py +18 -0
- fast_agent/resources/setup/fastagent.config.yaml +44 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/types/__init__.py +32 -0
- fast_agent/types/llm_stop_reason.py +77 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console_display.py +1005 -0
- {mcp_agent/human_input → fast_agent/ui}/elicitation_form.py +17 -12
- mcp_agent/human_input/elicitation_forms.py → fast_agent/ui/elicitation_style.py +1 -1
- {mcp_agent/core → fast_agent/ui}/enhanced_prompt.py +96 -25
- {mcp_agent/core → fast_agent/ui}/interactive_prompt.py +330 -125
- fast_agent/ui/mcp_ui_utils.py +224 -0
- {mcp_agent → fast_agent/ui}/progress_display.py +2 -2
- {mcp_agent/logging → fast_agent/ui}/rich_progress.py +4 -4
- {mcp_agent/core → fast_agent/ui}/usage_display.py +3 -8
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.0.dist-info}/METADATA +7 -7
- fast_agent_mcp-0.3.0.dist-info/RECORD +202 -0
- fast_agent_mcp-0.3.0.dist-info/entry_points.txt +5 -0
- fast_agent_mcp-0.2.58.dist-info/RECORD +0 -193
- fast_agent_mcp-0.2.58.dist-info/entry_points.txt +0 -6
- mcp_agent/__init__.py +0 -114
- mcp_agent/agents/agent.py +0 -92
- mcp_agent/agents/workflow/__init__.py +0 -1
- mcp_agent/agents/workflow/orchestrator_agent.py +0 -597
- mcp_agent/app.py +0 -175
- mcp_agent/core/__init__.py +0 -26
- mcp_agent/core/prompt.py +0 -191
- mcp_agent/event_progress.py +0 -134
- mcp_agent/human_input/handler.py +0 -81
- mcp_agent/llm/__init__.py +0 -2
- mcp_agent/llm/augmented_llm_passthrough.py +0 -232
- mcp_agent/llm/augmented_llm_slow.py +0 -53
- mcp_agent/llm/providers/__init__.py +0 -8
- mcp_agent/llm/providers/augmented_llm_anthropic.py +0 -718
- mcp_agent/llm/providers/augmented_llm_google_native.py +0 -496
- mcp_agent/llm/providers/sampling_converter_anthropic.py +0 -57
- mcp_agent/llm/providers/sampling_converter_openai.py +0 -26
- mcp_agent/llm/sampling_format_converter.py +0 -37
- mcp_agent/logging/__init__.py +0 -0
- mcp_agent/mcp/__init__.py +0 -50
- mcp_agent/mcp/helpers/__init__.py +0 -25
- mcp_agent/mcp/helpers/content_helpers.py +0 -187
- mcp_agent/mcp/interfaces.py +0 -266
- mcp_agent/mcp/prompts/__init__.py +0 -0
- mcp_agent/mcp/prompts/__main__.py +0 -10
- mcp_agent/mcp_server_registry.py +0 -343
- mcp_agent/tools/tool_definition.py +0 -14
- mcp_agent/ui/console_display.py +0 -790
- mcp_agent/ui/console_display_legacy.py +0 -401
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_prompts.py +0 -0
- {mcp_agent/agents → fast_agent/cli}/__init__.py +0 -0
- {mcp_agent → fast_agent}/cli/constants.py +0 -0
- {mcp_agent → fast_agent}/core/error_handling.py +0 -0
- {mcp_agent → fast_agent}/core/exceptions.py +0 -0
- {mcp_agent/cli → fast_agent/core/executor}/__init__.py +0 -0
- {mcp_agent → fast_agent/core}/executor/task_registry.py +0 -0
- {mcp_agent → fast_agent/core}/executor/workflow_signal.py +0 -0
- {mcp_agent → fast_agent}/human_input/form_fields.py +0 -0
- {mcp_agent → fast_agent}/llm/prompt_utils.py +0 -0
- {mcp_agent/core → fast_agent/llm}/request_params.py +0 -0
- {mcp_agent → fast_agent}/mcp/common.py +0 -0
- {mcp_agent/executor → fast_agent/mcp/prompts}/__init__.py +0 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_constants.py +0 -0
- {mcp_agent → fast_agent}/py.typed +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_account_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_forms_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_game_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/researcher/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/.env.sample +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/Makefile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/README.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/crab.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/docker-compose.yml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/Dockerfile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/entrypoint.sh +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/mcp_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/pyproject.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_schema.json +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/graded_report.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.txt +0 -0
- {mcp_agent → fast_agent/ui}/console.py +0 -0
- {mcp_agent/core → fast_agent/ui}/mermaid_utils.py +0 -0
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.0.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.0.dist-info}/licenses/LICENSE +0 -0
mcp_agent/ui/console_display.py
DELETED
|
@@ -1,790 +0,0 @@
|
|
|
1
|
-
from json import JSONDecodeError
|
|
2
|
-
from typing import Optional, Union
|
|
3
|
-
|
|
4
|
-
from mcp.types import CallToolResult
|
|
5
|
-
from rich.json import JSON
|
|
6
|
-
from rich.panel import Panel
|
|
7
|
-
from rich.text import Text
|
|
8
|
-
|
|
9
|
-
from mcp_agent import console
|
|
10
|
-
from mcp_agent.core.mermaid_utils import (
|
|
11
|
-
create_mermaid_live_link,
|
|
12
|
-
detect_diagram_type,
|
|
13
|
-
extract_mermaid_diagrams,
|
|
14
|
-
)
|
|
15
|
-
from mcp_agent.mcp.common import SEP
|
|
16
|
-
from mcp_agent.mcp.mcp_aggregator import MCPAggregator
|
|
17
|
-
|
|
18
|
-
# Constants
|
|
19
|
-
HUMAN_INPUT_TOOL_NAME = "__human_input__"
|
|
20
|
-
CODE_STYLE = "native"
|
|
21
|
-
|
|
22
|
-
HTML_ESCAPE_CHARS = {"&": "&", "<": "<", ">": ">", '"': """, "'": "'"}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _prepare_markdown_content(content: str, escape_xml: bool = True) -> str:
|
|
26
|
-
"""Prepare content for markdown rendering by escaping HTML/XML tags
|
|
27
|
-
while preserving code blocks and inline code.
|
|
28
|
-
|
|
29
|
-
This ensures XML/HTML tags are displayed as visible text rather than
|
|
30
|
-
being interpreted as markup by the markdown renderer.
|
|
31
|
-
|
|
32
|
-
Note: This method does not handle overlapping code blocks (e.g., if inline
|
|
33
|
-
code appears within a fenced code block range). In practice, this is not
|
|
34
|
-
an issue since markdown syntax doesn't support such overlapping.
|
|
35
|
-
"""
|
|
36
|
-
if not escape_xml or not isinstance(content, str):
|
|
37
|
-
return content
|
|
38
|
-
|
|
39
|
-
protected_ranges = []
|
|
40
|
-
import re
|
|
41
|
-
|
|
42
|
-
# Protect fenced code blocks (don't escape anything inside these)
|
|
43
|
-
code_block_pattern = r"```[\s\S]*?```"
|
|
44
|
-
for match in re.finditer(code_block_pattern, content):
|
|
45
|
-
protected_ranges.append((match.start(), match.end()))
|
|
46
|
-
|
|
47
|
-
# Protect inline code (don't escape anything inside these)
|
|
48
|
-
inline_code_pattern = r"(?<!`)`(?!``)[^`\n]+`(?!`)"
|
|
49
|
-
for match in re.finditer(inline_code_pattern, content):
|
|
50
|
-
protected_ranges.append((match.start(), match.end()))
|
|
51
|
-
|
|
52
|
-
protected_ranges.sort(key=lambda x: x[0])
|
|
53
|
-
|
|
54
|
-
# Build the escaped content
|
|
55
|
-
result = []
|
|
56
|
-
last_end = 0
|
|
57
|
-
|
|
58
|
-
for start, end in protected_ranges:
|
|
59
|
-
# Escape everything outside protected ranges
|
|
60
|
-
unprotected_text = content[last_end:start]
|
|
61
|
-
for char, replacement in HTML_ESCAPE_CHARS.items():
|
|
62
|
-
unprotected_text = unprotected_text.replace(char, replacement)
|
|
63
|
-
result.append(unprotected_text)
|
|
64
|
-
|
|
65
|
-
# Keep protected ranges (code blocks) as-is
|
|
66
|
-
result.append(content[start:end])
|
|
67
|
-
last_end = end
|
|
68
|
-
|
|
69
|
-
# Escape any remaining content after the last protected range
|
|
70
|
-
remainder_text = content[last_end:]
|
|
71
|
-
for char, replacement in HTML_ESCAPE_CHARS.items():
|
|
72
|
-
remainder_text = remainder_text.replace(char, replacement)
|
|
73
|
-
result.append(remainder_text)
|
|
74
|
-
|
|
75
|
-
return "".join(result)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class ConsoleDisplay:
|
|
79
|
-
"""
|
|
80
|
-
Handles displaying formatted messages, tool calls, and results to the console.
|
|
81
|
-
This centralizes the UI display logic used by LLM implementations.
|
|
82
|
-
"""
|
|
83
|
-
|
|
84
|
-
def __init__(self, config=None) -> None:
|
|
85
|
-
"""
|
|
86
|
-
Initialize the console display handler.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
config: Configuration object containing display preferences
|
|
90
|
-
"""
|
|
91
|
-
self.config = config
|
|
92
|
-
self._markup = config.logger.enable_markup if config else True
|
|
93
|
-
self._escape_xml = True
|
|
94
|
-
|
|
95
|
-
def _render_content_smartly(self, content: str, check_markdown_markers: bool = False) -> None:
|
|
96
|
-
"""
|
|
97
|
-
Helper method to intelligently render content based on its type.
|
|
98
|
-
|
|
99
|
-
- Pure XML: Use syntax highlighting for readability
|
|
100
|
-
- Markdown (with markers): Use markdown rendering with proper escaping
|
|
101
|
-
- Plain text: Display as-is (when check_markdown_markers=True and no markers found)
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
content: The text content to render
|
|
105
|
-
check_markdown_markers: If True, only use markdown rendering when markers are present
|
|
106
|
-
"""
|
|
107
|
-
import re
|
|
108
|
-
|
|
109
|
-
from rich.markdown import Markdown
|
|
110
|
-
|
|
111
|
-
# Check if content appears to be primarily XML
|
|
112
|
-
xml_pattern = r"^<[a-zA-Z_][a-zA-Z0-9_-]*[^>]*>"
|
|
113
|
-
is_xml_content = bool(re.match(xml_pattern, content.strip())) and content.count("<") > 5
|
|
114
|
-
|
|
115
|
-
if is_xml_content:
|
|
116
|
-
# Display XML content with syntax highlighting for better readability
|
|
117
|
-
from rich.syntax import Syntax
|
|
118
|
-
|
|
119
|
-
syntax = Syntax(content, "xml", theme=CODE_STYLE, line_numbers=False)
|
|
120
|
-
console.console.print(syntax, markup=self._markup)
|
|
121
|
-
elif check_markdown_markers:
|
|
122
|
-
# Check for markdown markers before deciding to use markdown rendering
|
|
123
|
-
if any(marker in content for marker in ["##", "**", "*", "`", "---", "###"]):
|
|
124
|
-
# Has markdown markers - render as markdown with escaping
|
|
125
|
-
prepared_content = _prepare_markdown_content(content, self._escape_xml)
|
|
126
|
-
md = Markdown(prepared_content, code_theme=CODE_STYLE)
|
|
127
|
-
console.console.print(md, markup=self._markup)
|
|
128
|
-
else:
|
|
129
|
-
# Plain text - display as-is
|
|
130
|
-
console.console.print(content, markup=self._markup)
|
|
131
|
-
else:
|
|
132
|
-
# Always treat as markdown with proper escaping
|
|
133
|
-
prepared_content = _prepare_markdown_content(content, self._escape_xml)
|
|
134
|
-
md = Markdown(prepared_content, code_theme=CODE_STYLE)
|
|
135
|
-
console.console.print(md, markup=self._markup)
|
|
136
|
-
|
|
137
|
-
def show_tool_result(self, result: CallToolResult, name: str | None = None) -> None:
|
|
138
|
-
"""Display a tool result in the new visual style."""
|
|
139
|
-
if not self.config or not self.config.logger.show_tools:
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
# Import content helpers
|
|
143
|
-
from mcp_agent.mcp.helpers.content_helpers import get_text, is_text_content
|
|
144
|
-
|
|
145
|
-
# Use red for errors, magenta for success (keep block bright)
|
|
146
|
-
block_color = "red" if result.isError else "magenta"
|
|
147
|
-
text_color = "dim red" if result.isError else "dim magenta"
|
|
148
|
-
|
|
149
|
-
# Analyze content to determine display format and status
|
|
150
|
-
content = result.content
|
|
151
|
-
if result.isError:
|
|
152
|
-
status = "ERROR"
|
|
153
|
-
else:
|
|
154
|
-
# Check if it's a list with content blocks
|
|
155
|
-
if len(content) == 0:
|
|
156
|
-
status = "No Content"
|
|
157
|
-
elif len(content) == 1 and is_text_content(content[0]):
|
|
158
|
-
text_content = get_text(content[0])
|
|
159
|
-
char_count = len(text_content) if text_content else 0
|
|
160
|
-
status = f"Text Only {char_count} chars"
|
|
161
|
-
else:
|
|
162
|
-
text_count = sum(1 for item in content if is_text_content(item))
|
|
163
|
-
if text_count == len(content):
|
|
164
|
-
status = f"{len(content)} Text Blocks" if len(content) > 1 else "1 Text Block"
|
|
165
|
-
else:
|
|
166
|
-
status = (
|
|
167
|
-
f"{len(content)} Content Blocks" if len(content) > 1 else "1 Content Block"
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
# Combined separator and status line
|
|
171
|
-
left = f"[{block_color}]▎[/{block_color}][{text_color}]▶[/{text_color}]{f' [{block_color}]{name}[/{block_color}]' if name else ''}"
|
|
172
|
-
right = f"[dim]tool result - {status}[/dim]"
|
|
173
|
-
self._create_combined_separator_status(left, right)
|
|
174
|
-
|
|
175
|
-
# Display tool result content
|
|
176
|
-
content = result.content
|
|
177
|
-
|
|
178
|
-
# Handle special case: single text content block
|
|
179
|
-
if isinstance(content, list) and len(content) == 1 and is_text_content(content[0]):
|
|
180
|
-
# Display just the text content directly
|
|
181
|
-
text_content = get_text(content[0])
|
|
182
|
-
if text_content:
|
|
183
|
-
if self.config and self.config.logger.truncate_tools and len(text_content) > 360:
|
|
184
|
-
text_content = text_content[:360] + "..."
|
|
185
|
-
console.console.print(text_content, style="dim", markup=self._markup)
|
|
186
|
-
else:
|
|
187
|
-
console.console.print("(empty text)", style="dim", markup=self._markup)
|
|
188
|
-
else:
|
|
189
|
-
# Use Rich pretty printing for everything else
|
|
190
|
-
try:
|
|
191
|
-
import json
|
|
192
|
-
|
|
193
|
-
from rich.pretty import Pretty
|
|
194
|
-
|
|
195
|
-
# Try to parse as JSON for pretty printing
|
|
196
|
-
if isinstance(content, str):
|
|
197
|
-
json_obj = json.loads(content)
|
|
198
|
-
else:
|
|
199
|
-
json_obj = content
|
|
200
|
-
|
|
201
|
-
# Use Rich's built-in truncation with dimmed styling
|
|
202
|
-
if self.config and self.config.logger.truncate_tools:
|
|
203
|
-
pretty_obj = Pretty(json_obj, max_length=10, max_string=50)
|
|
204
|
-
else:
|
|
205
|
-
pretty_obj = Pretty(json_obj)
|
|
206
|
-
|
|
207
|
-
# Print with dim styling
|
|
208
|
-
console.console.print(pretty_obj, style="dim", markup=self._markup)
|
|
209
|
-
|
|
210
|
-
except (json.JSONDecodeError, TypeError, ValueError, AttributeError):
|
|
211
|
-
# Fall back to string representation if not valid JSON
|
|
212
|
-
content_str = str(content)
|
|
213
|
-
if self.config and self.config.logger.truncate_tools and len(content_str) > 360:
|
|
214
|
-
content_str = content_str[:360] + "..."
|
|
215
|
-
console.console.print(content_str, style="dim", markup=self._markup)
|
|
216
|
-
|
|
217
|
-
# Bottom separator (no additional info for tool results)
|
|
218
|
-
console.console.print()
|
|
219
|
-
console.console.print("─" * console.console.size.width, style="dim")
|
|
220
|
-
console.console.print()
|
|
221
|
-
|
|
222
|
-
def show_tool_call(
|
|
223
|
-
self, available_tools, tool_name, tool_args, name: str | None = None
|
|
224
|
-
) -> None:
|
|
225
|
-
"""Display a tool call in the new visual style."""
|
|
226
|
-
if not self.config or not self.config.logger.show_tools:
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
display_tool_list = self._format_tool_list(available_tools, tool_name)
|
|
230
|
-
|
|
231
|
-
# Combined separator and status line
|
|
232
|
-
left = f"[magenta]▎[/magenta][dim magenta]◀[/dim magenta]{f' [magenta]{name}[/magenta]' if name else ''}"
|
|
233
|
-
right = f"[dim]tool request - {tool_name}[/dim]"
|
|
234
|
-
self._create_combined_separator_status(left, right)
|
|
235
|
-
|
|
236
|
-
# Display tool arguments using Rich JSON pretty printing (dimmed)
|
|
237
|
-
try:
|
|
238
|
-
import json
|
|
239
|
-
|
|
240
|
-
from rich.pretty import Pretty
|
|
241
|
-
|
|
242
|
-
# Try to parse as JSON for pretty printing
|
|
243
|
-
if isinstance(tool_args, str):
|
|
244
|
-
json_obj = json.loads(tool_args)
|
|
245
|
-
else:
|
|
246
|
-
json_obj = tool_args
|
|
247
|
-
|
|
248
|
-
# Use Rich's built-in truncation with dimmed styling
|
|
249
|
-
if self.config and self.config.logger.truncate_tools:
|
|
250
|
-
pretty_obj = Pretty(json_obj, max_length=10, max_string=50)
|
|
251
|
-
else:
|
|
252
|
-
pretty_obj = Pretty(json_obj)
|
|
253
|
-
|
|
254
|
-
# Print with dim styling
|
|
255
|
-
console.console.print(pretty_obj, style="dim", markup=self._markup)
|
|
256
|
-
|
|
257
|
-
except (json.JSONDecodeError, TypeError, ValueError):
|
|
258
|
-
# Fall back to string representation if not valid JSON
|
|
259
|
-
content = str(tool_args)
|
|
260
|
-
if self.config and self.config.logger.truncate_tools and len(content) > 360:
|
|
261
|
-
content = content[:360] + "..."
|
|
262
|
-
console.console.print(content, style="dim", markup=self._markup)
|
|
263
|
-
|
|
264
|
-
# Bottom separator with tool list using pipe separators (matching server style)
|
|
265
|
-
console.console.print()
|
|
266
|
-
|
|
267
|
-
# Use existing tool list formatting with pipe separators
|
|
268
|
-
if display_tool_list and len(display_tool_list) > 0:
|
|
269
|
-
# Truncate tool list if needed (leave space for "─| " prefix and " |" suffix)
|
|
270
|
-
max_tool_width = console.console.size.width - 10 # Reserve space for separators
|
|
271
|
-
truncated_tool_list = self._truncate_list_if_needed(display_tool_list, max_tool_width)
|
|
272
|
-
|
|
273
|
-
# Create the separator line: ─| [tools] |──────
|
|
274
|
-
line1 = Text()
|
|
275
|
-
line1.append("─| ", style="dim")
|
|
276
|
-
line1.append_text(truncated_tool_list)
|
|
277
|
-
line1.append(" |", style="dim")
|
|
278
|
-
remaining = console.console.size.width - line1.cell_len
|
|
279
|
-
if remaining > 0:
|
|
280
|
-
line1.append("─" * remaining, style="dim")
|
|
281
|
-
else:
|
|
282
|
-
# No tools - continuous bar
|
|
283
|
-
line1 = Text()
|
|
284
|
-
line1.append("─" * console.console.size.width, style="dim")
|
|
285
|
-
|
|
286
|
-
console.console.print(line1, markup=self._markup)
|
|
287
|
-
console.console.print()
|
|
288
|
-
|
|
289
|
-
async def show_tool_update(self, aggregator: MCPAggregator | None, updated_server: str) -> None:
|
|
290
|
-
"""Show a tool update for a server in the new visual style."""
|
|
291
|
-
if not self.config or not self.config.logger.show_tools:
|
|
292
|
-
return
|
|
293
|
-
|
|
294
|
-
# Check if aggregator is actually an agent (has name attribute)
|
|
295
|
-
agent_name = None
|
|
296
|
-
if aggregator and hasattr(aggregator, "name") and aggregator.name:
|
|
297
|
-
agent_name = aggregator.name
|
|
298
|
-
|
|
299
|
-
# Combined separator and status line
|
|
300
|
-
if agent_name:
|
|
301
|
-
left = (
|
|
302
|
-
f"[magenta]▎[/magenta][dim magenta]▶[/dim magenta] [magenta]{agent_name}[/magenta]"
|
|
303
|
-
)
|
|
304
|
-
else:
|
|
305
|
-
left = "[magenta]▎[/magenta][dim magenta]▶[/dim magenta]"
|
|
306
|
-
|
|
307
|
-
right = f"[dim]{updated_server}[/dim]"
|
|
308
|
-
self._create_combined_separator_status(left, right)
|
|
309
|
-
|
|
310
|
-
# Display update message
|
|
311
|
-
message = f"Updating tools for server {updated_server}"
|
|
312
|
-
console.console.print(message, style="dim", markup=self._markup)
|
|
313
|
-
|
|
314
|
-
# Bottom separator
|
|
315
|
-
console.console.print()
|
|
316
|
-
console.console.print("─" * console.console.size.width, style="dim")
|
|
317
|
-
console.console.print()
|
|
318
|
-
|
|
319
|
-
# Force prompt_toolkit redraw if active
|
|
320
|
-
try:
|
|
321
|
-
from prompt_toolkit.application.current import get_app
|
|
322
|
-
|
|
323
|
-
get_app().invalidate() # Forces prompt_toolkit to redraw
|
|
324
|
-
except: # noqa: E722
|
|
325
|
-
pass # No active prompt_toolkit session
|
|
326
|
-
|
|
327
|
-
def _format_tool_list(self, available_tools, selected_tool_name):
|
|
328
|
-
"""Format the list of available tools, highlighting the selected one."""
|
|
329
|
-
display_tool_list = Text()
|
|
330
|
-
matching_tools = []
|
|
331
|
-
|
|
332
|
-
for display_tool in available_tools:
|
|
333
|
-
# Handle both OpenAI and Anthropic tool formats
|
|
334
|
-
if isinstance(display_tool, dict):
|
|
335
|
-
if "function" in display_tool:
|
|
336
|
-
# OpenAI format
|
|
337
|
-
tool_call_name = display_tool["function"]["name"]
|
|
338
|
-
else:
|
|
339
|
-
# Anthropic format
|
|
340
|
-
tool_call_name = display_tool["name"]
|
|
341
|
-
else:
|
|
342
|
-
# Handle potential object format (e.g., Pydantic models)
|
|
343
|
-
tool_call_name = (
|
|
344
|
-
display_tool.function.name
|
|
345
|
-
if hasattr(display_tool, "function")
|
|
346
|
-
else display_tool.name
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
parts = (
|
|
350
|
-
tool_call_name.split(SEP)
|
|
351
|
-
if SEP in tool_call_name
|
|
352
|
-
else [tool_call_name, tool_call_name]
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
if selected_tool_name.split(SEP)[0] == parts[0]:
|
|
356
|
-
shortened_name = parts[1] if len(parts[1]) <= 12 else parts[1][:11] + "…"
|
|
357
|
-
matching_tools.append((shortened_name, tool_call_name))
|
|
358
|
-
|
|
359
|
-
# Format with pipe separators instead of brackets
|
|
360
|
-
for i, (shortened_name, tool_call_name) in enumerate(matching_tools):
|
|
361
|
-
if i > 0:
|
|
362
|
-
display_tool_list.append(" | ", style="dim")
|
|
363
|
-
style = "magenta" if tool_call_name == selected_tool_name else "dim"
|
|
364
|
-
display_tool_list.append(shortened_name, style)
|
|
365
|
-
|
|
366
|
-
return display_tool_list
|
|
367
|
-
|
|
368
|
-
def _truncate_list_if_needed(self, text_list: Text, max_width: int) -> Text:
|
|
369
|
-
"""Truncate a Text list if it exceeds the maximum width."""
|
|
370
|
-
if text_list.cell_len <= max_width:
|
|
371
|
-
return text_list
|
|
372
|
-
|
|
373
|
-
# Create a new truncated version
|
|
374
|
-
truncated = Text()
|
|
375
|
-
current_width = 0
|
|
376
|
-
|
|
377
|
-
for span in text_list._spans:
|
|
378
|
-
text_part = text_list.plain[span.start : span.end]
|
|
379
|
-
if current_width + len(text_part) <= max_width - 1: # -1 for ellipsis
|
|
380
|
-
truncated.append(text_part, style=span.style)
|
|
381
|
-
current_width += len(text_part)
|
|
382
|
-
else:
|
|
383
|
-
# Add what we can fit and ellipsis
|
|
384
|
-
remaining = max_width - current_width - 1
|
|
385
|
-
if remaining > 0:
|
|
386
|
-
truncated.append(text_part[:remaining], style=span.style)
|
|
387
|
-
truncated.append("…", style="dim")
|
|
388
|
-
break
|
|
389
|
-
|
|
390
|
-
return truncated
|
|
391
|
-
|
|
392
|
-
def _create_combined_separator_status(self, left_content: str, right_info: str = "") -> None:
|
|
393
|
-
"""
|
|
394
|
-
Create a combined separator and status line.
|
|
395
|
-
|
|
396
|
-
Args:
|
|
397
|
-
left_content: The main content (block, arrow, name) - left justified with color
|
|
398
|
-
right_info: Supplementary information to show in brackets - right aligned
|
|
399
|
-
"""
|
|
400
|
-
width = console.console.size.width
|
|
401
|
-
|
|
402
|
-
# Create left text
|
|
403
|
-
left_text = Text.from_markup(left_content)
|
|
404
|
-
|
|
405
|
-
# Create right text if we have info
|
|
406
|
-
if right_info and right_info.strip():
|
|
407
|
-
# Add dim brackets around the right info
|
|
408
|
-
right_text = Text()
|
|
409
|
-
right_text.append("[", style="dim")
|
|
410
|
-
right_text.append_text(Text.from_markup(right_info))
|
|
411
|
-
right_text.append("]", style="dim")
|
|
412
|
-
# Calculate separator count
|
|
413
|
-
separator_count = width - left_text.cell_len - right_text.cell_len
|
|
414
|
-
if separator_count < 1:
|
|
415
|
-
separator_count = 1 # Always at least 1 separator
|
|
416
|
-
else:
|
|
417
|
-
right_text = Text("")
|
|
418
|
-
separator_count = width - left_text.cell_len
|
|
419
|
-
|
|
420
|
-
# Build the combined line
|
|
421
|
-
combined = Text()
|
|
422
|
-
combined.append_text(left_text)
|
|
423
|
-
combined.append(" ", style="default")
|
|
424
|
-
combined.append("─" * (separator_count - 1), style="dim")
|
|
425
|
-
combined.append_text(right_text)
|
|
426
|
-
|
|
427
|
-
# Print with empty line before
|
|
428
|
-
console.console.print()
|
|
429
|
-
console.console.print(combined, markup=self._markup)
|
|
430
|
-
console.console.print()
|
|
431
|
-
|
|
432
|
-
async def show_assistant_message(
|
|
433
|
-
self,
|
|
434
|
-
message_text: Union[str, Text],
|
|
435
|
-
aggregator=None,
|
|
436
|
-
highlight_namespaced_tool: str = "",
|
|
437
|
-
title: str = "ASSISTANT",
|
|
438
|
-
name: str | None = None,
|
|
439
|
-
model: str | None = None,
|
|
440
|
-
) -> None:
|
|
441
|
-
"""Display an assistant message in a formatted panel."""
|
|
442
|
-
|
|
443
|
-
if not self.config or not self.config.logger.show_chat:
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
# Build server list for bottom separator (using same logic as legacy)
|
|
447
|
-
display_server_list = Text()
|
|
448
|
-
|
|
449
|
-
if aggregator:
|
|
450
|
-
# Add human input tool if available
|
|
451
|
-
tools = await aggregator.list_tools()
|
|
452
|
-
if any(tool.name == HUMAN_INPUT_TOOL_NAME for tool in tools.tools):
|
|
453
|
-
style = (
|
|
454
|
-
"green" if highlight_namespaced_tool == HUMAN_INPUT_TOOL_NAME else "dim white"
|
|
455
|
-
)
|
|
456
|
-
display_server_list.append("[human] ", style)
|
|
457
|
-
|
|
458
|
-
# Add all available servers
|
|
459
|
-
mcp_server_name = (
|
|
460
|
-
highlight_namespaced_tool.split(SEP)[0]
|
|
461
|
-
if SEP in highlight_namespaced_tool
|
|
462
|
-
else highlight_namespaced_tool
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
for server_name in await aggregator.list_servers():
|
|
466
|
-
style = "green" if server_name == mcp_server_name else "dim white"
|
|
467
|
-
display_server_list.append(f"[{server_name}] ", style)
|
|
468
|
-
|
|
469
|
-
# Combined separator and status line
|
|
470
|
-
left = f"[green]▎[/green][dim green]◀[/dim green]{f' [bold green]{name}[/bold green]' if name else ''}"
|
|
471
|
-
right = f"[dim]{model}[/dim]" if model else ""
|
|
472
|
-
self._create_combined_separator_status(left, right)
|
|
473
|
-
|
|
474
|
-
if isinstance(message_text, str):
|
|
475
|
-
content = message_text
|
|
476
|
-
|
|
477
|
-
# Try to detect and pretty print JSON
|
|
478
|
-
try:
|
|
479
|
-
import json
|
|
480
|
-
|
|
481
|
-
json.loads(content)
|
|
482
|
-
json = JSON(message_text)
|
|
483
|
-
console.console.print(json, markup=self._markup)
|
|
484
|
-
except (JSONDecodeError, TypeError, ValueError):
|
|
485
|
-
# Use the smart rendering helper to handle XML vs Markdown
|
|
486
|
-
self._render_content_smartly(content)
|
|
487
|
-
else:
|
|
488
|
-
# Handle Rich Text objects directly
|
|
489
|
-
console.console.print(message_text, markup=self._markup)
|
|
490
|
-
|
|
491
|
-
# Bottom separator with server list and diagrams
|
|
492
|
-
console.console.print()
|
|
493
|
-
|
|
494
|
-
# Check for mermaid diagrams in the message content
|
|
495
|
-
diagrams = []
|
|
496
|
-
if isinstance(message_text, str):
|
|
497
|
-
diagrams = extract_mermaid_diagrams(message_text)
|
|
498
|
-
|
|
499
|
-
# Create server list with pipe separators (no "mcp:" prefix)
|
|
500
|
-
server_content = Text()
|
|
501
|
-
if display_server_list and len(display_server_list) > 0:
|
|
502
|
-
# Convert the existing server list to pipe-separated format
|
|
503
|
-
servers = []
|
|
504
|
-
if aggregator:
|
|
505
|
-
for server_name in await aggregator.list_servers():
|
|
506
|
-
servers.append(server_name)
|
|
507
|
-
|
|
508
|
-
# Create pipe-separated server list
|
|
509
|
-
for i, server_name in enumerate(servers):
|
|
510
|
-
if i > 0:
|
|
511
|
-
server_content.append(" | ", style="dim")
|
|
512
|
-
# Highlight active server, dim inactive ones
|
|
513
|
-
mcp_server_name = (
|
|
514
|
-
highlight_namespaced_tool.split(SEP)[0]
|
|
515
|
-
if SEP in highlight_namespaced_tool
|
|
516
|
-
else highlight_namespaced_tool
|
|
517
|
-
)
|
|
518
|
-
style = "bright_green" if server_name == mcp_server_name else "dim"
|
|
519
|
-
server_content.append(server_name, style)
|
|
520
|
-
|
|
521
|
-
# Create main separator line
|
|
522
|
-
line1 = Text()
|
|
523
|
-
if server_content.cell_len > 0:
|
|
524
|
-
line1.append("─| ", style="dim")
|
|
525
|
-
line1.append_text(server_content)
|
|
526
|
-
line1.append(" |", style="dim")
|
|
527
|
-
remaining = console.console.size.width - line1.cell_len
|
|
528
|
-
if remaining > 0:
|
|
529
|
-
line1.append("─" * remaining, style="dim")
|
|
530
|
-
else:
|
|
531
|
-
# No servers - continuous bar (no break)
|
|
532
|
-
line1.append("─" * console.console.size.width, style="dim")
|
|
533
|
-
|
|
534
|
-
console.console.print(line1, markup=self._markup)
|
|
535
|
-
|
|
536
|
-
# Add diagram links in panel if any diagrams found
|
|
537
|
-
if diagrams:
|
|
538
|
-
diagram_content = Text()
|
|
539
|
-
# Add bullet at the beginning
|
|
540
|
-
diagram_content.append("● ", style="dim")
|
|
541
|
-
|
|
542
|
-
for i, diagram in enumerate(diagrams, 1):
|
|
543
|
-
if i > 1:
|
|
544
|
-
diagram_content.append(" • ", style="dim")
|
|
545
|
-
|
|
546
|
-
# Generate URL
|
|
547
|
-
url = create_mermaid_live_link(diagram.content)
|
|
548
|
-
|
|
549
|
-
# Format: "1 - Title" or "1 - Flowchart" or "Diagram 1"
|
|
550
|
-
if diagram.title:
|
|
551
|
-
diagram_content.append(
|
|
552
|
-
f"{i} - {diagram.title}", style=f"bright_blue link {url}"
|
|
553
|
-
)
|
|
554
|
-
else:
|
|
555
|
-
# Try to detect diagram type, fallback to "Diagram N"
|
|
556
|
-
diagram_type = detect_diagram_type(diagram.content)
|
|
557
|
-
if diagram_type != "Diagram":
|
|
558
|
-
diagram_content.append(
|
|
559
|
-
f"{i} - {diagram_type}", style=f"bright_blue link {url}"
|
|
560
|
-
)
|
|
561
|
-
else:
|
|
562
|
-
diagram_content.append(f"Diagram {i}", style=f"bright_blue link {url}")
|
|
563
|
-
|
|
564
|
-
# Display diagrams on a simple new line (more space efficient)
|
|
565
|
-
console.console.print()
|
|
566
|
-
console.console.print(diagram_content, markup=self._markup)
|
|
567
|
-
|
|
568
|
-
console.console.print()
|
|
569
|
-
|
|
570
|
-
def show_user_message(
|
|
571
|
-
self, message, model: str | None = None, chat_turn: int = 0, name: str | None = None
|
|
572
|
-
) -> None:
|
|
573
|
-
"""Display a user message in the new visual style."""
|
|
574
|
-
|
|
575
|
-
if not self.config or not self.config.logger.show_chat:
|
|
576
|
-
return
|
|
577
|
-
|
|
578
|
-
# Combined separator and status line
|
|
579
|
-
left = f"[blue]▎[/blue][dim blue]▶[/dim blue]{f' [bold blue]{name}[/bold blue]' if name else ''}"
|
|
580
|
-
|
|
581
|
-
# Build right side with model and turn
|
|
582
|
-
right_parts = []
|
|
583
|
-
if model:
|
|
584
|
-
right_parts.append(model)
|
|
585
|
-
if chat_turn > 0:
|
|
586
|
-
right_parts.append(f"turn {chat_turn}")
|
|
587
|
-
|
|
588
|
-
right = f"[dim]{' '.join(right_parts)}[/dim]" if right_parts else ""
|
|
589
|
-
self._create_combined_separator_status(left, right)
|
|
590
|
-
|
|
591
|
-
# Display content as markdown if it looks like markdown, otherwise as text
|
|
592
|
-
if isinstance(message, str):
|
|
593
|
-
# Use the smart rendering helper to handle XML vs Markdown
|
|
594
|
-
self._render_content_smartly(message)
|
|
595
|
-
else:
|
|
596
|
-
# Handle Text objects directly
|
|
597
|
-
console.console.print(message, markup=self._markup)
|
|
598
|
-
|
|
599
|
-
# Bottom separator (no server list for user messages)
|
|
600
|
-
console.console.print()
|
|
601
|
-
console.console.print("─" * console.console.size.width, style="dim")
|
|
602
|
-
console.console.print()
|
|
603
|
-
|
|
604
|
-
async def show_prompt_loaded(
|
|
605
|
-
self,
|
|
606
|
-
prompt_name: str,
|
|
607
|
-
description: Optional[str] = None,
|
|
608
|
-
message_count: int = 0,
|
|
609
|
-
agent_name: Optional[str] = None,
|
|
610
|
-
aggregator=None,
|
|
611
|
-
arguments: Optional[dict[str, str]] = None,
|
|
612
|
-
) -> None:
|
|
613
|
-
"""
|
|
614
|
-
Display information about a loaded prompt template.
|
|
615
|
-
|
|
616
|
-
Args:
|
|
617
|
-
prompt_name: The name of the prompt that was loaded (should be namespaced)
|
|
618
|
-
description: Optional description of the prompt
|
|
619
|
-
message_count: Number of messages added to the conversation history
|
|
620
|
-
agent_name: Name of the agent using the prompt
|
|
621
|
-
aggregator: Optional aggregator instance to use for server highlighting
|
|
622
|
-
arguments: Optional dictionary of arguments passed to the prompt template
|
|
623
|
-
"""
|
|
624
|
-
if not self.config or not self.config.logger.show_tools:
|
|
625
|
-
return
|
|
626
|
-
|
|
627
|
-
# Get server name from the namespaced prompt_name
|
|
628
|
-
mcp_server_name = None
|
|
629
|
-
if SEP in prompt_name:
|
|
630
|
-
# Extract the server from the namespaced prompt name
|
|
631
|
-
mcp_server_name = prompt_name.split(SEP)[0]
|
|
632
|
-
elif aggregator and aggregator.server_names:
|
|
633
|
-
# Fallback to first server if not namespaced
|
|
634
|
-
mcp_server_name = aggregator.server_names[0]
|
|
635
|
-
|
|
636
|
-
# Build the server list with highlighting
|
|
637
|
-
display_server_list = Text()
|
|
638
|
-
if aggregator:
|
|
639
|
-
for server_name in await aggregator.list_servers():
|
|
640
|
-
style = "green" if server_name == mcp_server_name else "dim white"
|
|
641
|
-
display_server_list.append(f"[{server_name}] ", style)
|
|
642
|
-
|
|
643
|
-
# Create content text
|
|
644
|
-
content = Text()
|
|
645
|
-
messages_phrase = f"Loaded {message_count} message{'s' if message_count != 1 else ''}"
|
|
646
|
-
content.append(f"{messages_phrase} from template ", style="cyan italic")
|
|
647
|
-
content.append(f"'{prompt_name}'", style="cyan bold italic")
|
|
648
|
-
|
|
649
|
-
if agent_name:
|
|
650
|
-
content.append(f" for {agent_name}", style="cyan italic")
|
|
651
|
-
|
|
652
|
-
# Add template arguments if provided
|
|
653
|
-
if arguments:
|
|
654
|
-
content.append("\n\nArguments:", style="cyan")
|
|
655
|
-
for key, value in arguments.items():
|
|
656
|
-
content.append(f"\n {key}: ", style="cyan bold")
|
|
657
|
-
content.append(value, style="white")
|
|
658
|
-
|
|
659
|
-
if description:
|
|
660
|
-
content.append("\n\n", style="default")
|
|
661
|
-
content.append(description, style="dim white")
|
|
662
|
-
|
|
663
|
-
# Create panel
|
|
664
|
-
panel = Panel(
|
|
665
|
-
content,
|
|
666
|
-
title="[PROMPT LOADED]",
|
|
667
|
-
title_align="right",
|
|
668
|
-
style="cyan",
|
|
669
|
-
border_style="white",
|
|
670
|
-
padding=(1, 2),
|
|
671
|
-
subtitle=display_server_list,
|
|
672
|
-
subtitle_align="left",
|
|
673
|
-
)
|
|
674
|
-
|
|
675
|
-
console.console.print(panel, markup=self._markup)
|
|
676
|
-
console.console.print("\n")
|
|
677
|
-
|
|
678
|
-
def show_parallel_results(self, parallel_agent) -> None:
|
|
679
|
-
"""Display parallel agent results in a clean, organized format.
|
|
680
|
-
|
|
681
|
-
Args:
|
|
682
|
-
parallel_agent: The parallel agent containing fan_out_agents with results
|
|
683
|
-
"""
|
|
684
|
-
|
|
685
|
-
from rich.text import Text
|
|
686
|
-
|
|
687
|
-
if self.config and not self.config.logger.show_chat:
|
|
688
|
-
return
|
|
689
|
-
|
|
690
|
-
if not parallel_agent or not hasattr(parallel_agent, "fan_out_agents"):
|
|
691
|
-
return
|
|
692
|
-
|
|
693
|
-
# Collect results and agent information
|
|
694
|
-
agent_results = []
|
|
695
|
-
|
|
696
|
-
for agent in parallel_agent.fan_out_agents:
|
|
697
|
-
# Get the last response text from this agent
|
|
698
|
-
message_history = agent.message_history
|
|
699
|
-
if not message_history:
|
|
700
|
-
continue
|
|
701
|
-
|
|
702
|
-
last_message = message_history[-1]
|
|
703
|
-
content = last_message.last_text()
|
|
704
|
-
|
|
705
|
-
# Get model name
|
|
706
|
-
model = "unknown"
|
|
707
|
-
if (
|
|
708
|
-
hasattr(agent, "_llm")
|
|
709
|
-
and agent._llm
|
|
710
|
-
and hasattr(agent._llm, "default_request_params")
|
|
711
|
-
):
|
|
712
|
-
model = getattr(agent._llm.default_request_params, "model", "unknown")
|
|
713
|
-
|
|
714
|
-
# Get usage information
|
|
715
|
-
tokens = 0
|
|
716
|
-
tool_calls = 0
|
|
717
|
-
if hasattr(agent, "usage_accumulator") and agent.usage_accumulator:
|
|
718
|
-
summary = agent.usage_accumulator.get_summary()
|
|
719
|
-
tokens = summary.get("cumulative_input_tokens", 0) + summary.get(
|
|
720
|
-
"cumulative_output_tokens", 0
|
|
721
|
-
)
|
|
722
|
-
tool_calls = summary.get("cumulative_tool_calls", 0)
|
|
723
|
-
|
|
724
|
-
agent_results.append(
|
|
725
|
-
{
|
|
726
|
-
"name": agent.name,
|
|
727
|
-
"model": model,
|
|
728
|
-
"content": content,
|
|
729
|
-
"tokens": tokens,
|
|
730
|
-
"tool_calls": tool_calls,
|
|
731
|
-
}
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
if not agent_results:
|
|
735
|
-
return
|
|
736
|
-
|
|
737
|
-
# Display header
|
|
738
|
-
console.console.print()
|
|
739
|
-
console.console.print("[dim]Parallel execution complete[/dim]")
|
|
740
|
-
console.console.print()
|
|
741
|
-
|
|
742
|
-
# Display results for each agent
|
|
743
|
-
for i, result in enumerate(agent_results):
|
|
744
|
-
if i > 0:
|
|
745
|
-
# Simple full-width separator
|
|
746
|
-
console.console.print()
|
|
747
|
-
console.console.print("─" * console.console.size.width, style="dim")
|
|
748
|
-
console.console.print()
|
|
749
|
-
|
|
750
|
-
# Two column header: model name (green) + usage info (dim)
|
|
751
|
-
left = f"[green]▎[/green] [bold green]{result['model']}[/bold green]"
|
|
752
|
-
|
|
753
|
-
# Build right side with tokens and tool calls if available
|
|
754
|
-
right_parts = []
|
|
755
|
-
if result["tokens"] > 0:
|
|
756
|
-
right_parts.append(f"{result['tokens']:,} tokens")
|
|
757
|
-
if result["tool_calls"] > 0:
|
|
758
|
-
right_parts.append(f"{result['tool_calls']} tools")
|
|
759
|
-
|
|
760
|
-
right = f"[dim]{' • '.join(right_parts) if right_parts else 'no usage data'}[/dim]"
|
|
761
|
-
|
|
762
|
-
# Calculate padding to right-align usage info
|
|
763
|
-
width = console.console.size.width
|
|
764
|
-
left_text = Text.from_markup(left)
|
|
765
|
-
right_text = Text.from_markup(right)
|
|
766
|
-
padding = max(1, width - left_text.cell_len - right_text.cell_len)
|
|
767
|
-
|
|
768
|
-
console.console.print(left + " " * padding + right, markup=self._markup)
|
|
769
|
-
console.console.print()
|
|
770
|
-
|
|
771
|
-
# Display content based on its type (check for markdown markers in parallel results)
|
|
772
|
-
content = result["content"]
|
|
773
|
-
self._render_content_smartly(content, check_markdown_markers=True)
|
|
774
|
-
|
|
775
|
-
# Summary
|
|
776
|
-
console.console.print()
|
|
777
|
-
console.console.print("─" * console.console.size.width, style="dim")
|
|
778
|
-
|
|
779
|
-
total_tokens = sum(result["tokens"] for result in agent_results)
|
|
780
|
-
total_tools = sum(result["tool_calls"] for result in agent_results)
|
|
781
|
-
|
|
782
|
-
summary_parts = [f"{len(agent_results)} models"]
|
|
783
|
-
if total_tokens > 0:
|
|
784
|
-
summary_parts.append(f"{total_tokens:,} tokens")
|
|
785
|
-
if total_tools > 0:
|
|
786
|
-
summary_parts.append(f"{total_tools} tools")
|
|
787
|
-
|
|
788
|
-
summary_text = " • ".join(summary_parts)
|
|
789
|
-
console.console.print(f"[dim]{summary_text}[/dim]")
|
|
790
|
-
console.console.print()
|