fast-agent-mcp 0.2.43__py3-none-any.whl → 0.2.45__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_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/METADATA +6 -5
- {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/RECORD +37 -36
- mcp_agent/agents/base_agent.py +60 -22
- mcp_agent/agents/workflow/evaluator_optimizer.py +39 -63
- mcp_agent/agents/workflow/router_agent.py +46 -21
- mcp_agent/config.py +2 -0
- mcp_agent/context.py +4 -0
- mcp_agent/core/agent_app.py +15 -5
- mcp_agent/core/direct_decorators.py +4 -5
- mcp_agent/core/enhanced_prompt.py +80 -11
- mcp_agent/core/fastagent.py +9 -1
- mcp_agent/core/interactive_prompt.py +60 -1
- mcp_agent/core/usage_display.py +10 -3
- mcp_agent/human_input/elicitation_form.py +16 -13
- mcp_agent/llm/augmented_llm.py +5 -7
- mcp_agent/llm/augmented_llm_passthrough.py +4 -0
- mcp_agent/llm/providers/augmented_llm_anthropic.py +258 -98
- mcp_agent/llm/providers/augmented_llm_bedrock.py +3 -3
- mcp_agent/llm/providers/augmented_llm_google_native.py +4 -7
- mcp_agent/llm/providers/augmented_llm_openai.py +5 -8
- mcp_agent/llm/providers/augmented_llm_tensorzero.py +6 -7
- mcp_agent/llm/providers/google_converter.py +6 -9
- mcp_agent/llm/providers/multipart_converter_anthropic.py +5 -4
- mcp_agent/llm/providers/multipart_converter_openai.py +33 -0
- mcp_agent/llm/providers/multipart_converter_tensorzero.py +3 -2
- mcp_agent/logging/rich_progress.py +6 -2
- mcp_agent/logging/transport.py +30 -36
- mcp_agent/mcp/helpers/content_helpers.py +26 -11
- mcp_agent/mcp/interfaces.py +22 -2
- mcp_agent/mcp/prompt_message_multipart.py +2 -3
- mcp_agent/resources/examples/workflows/evaluator.py +2 -2
- mcp_agent/resources/examples/workflows/router.py +1 -1
- mcp_agent/ui/console_display.py +363 -142
- mcp_agent/ui/console_display_legacy.py +401 -0
- {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/entry_points.txt +0 -0
- {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/licenses/LICENSE +0 -0
mcp_agent/ui/console_display.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
from json import JSONDecodeError
|
|
1
2
|
from typing import Optional, Union
|
|
2
3
|
|
|
3
4
|
from mcp.types import CallToolResult
|
|
5
|
+
from rich.json import JSON
|
|
4
6
|
from rich.panel import Panel
|
|
5
7
|
from rich.text import Text
|
|
6
8
|
|
|
@@ -10,6 +12,7 @@ from mcp_agent.mcp.mcp_aggregator import MCPAggregator
|
|
|
10
12
|
|
|
11
13
|
# Constants
|
|
12
14
|
HUMAN_INPUT_TOOL_NAME = "__human_input__"
|
|
15
|
+
CODE_STYLE = "native"
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class ConsoleDisplay:
|
|
@@ -28,102 +31,195 @@ class ConsoleDisplay:
|
|
|
28
31
|
self.config = config
|
|
29
32
|
self._markup = config.logger.enable_markup if config else True
|
|
30
33
|
|
|
31
|
-
def show_tool_result(self, result: CallToolResult, name:
|
|
32
|
-
"""Display a tool result in
|
|
34
|
+
def show_tool_result(self, result: CallToolResult, name: str | None = None) -> None:
|
|
35
|
+
"""Display a tool result in the new visual style."""
|
|
33
36
|
if not self.config or not self.config.logger.show_tools:
|
|
34
37
|
return
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
# Import content helpers
|
|
40
|
+
from mcp_agent.mcp.helpers.content_helpers import get_text, is_text_content
|
|
41
|
+
|
|
42
|
+
# Use red for errors, magenta for success (keep block bright)
|
|
43
|
+
block_color = "red" if result.isError else "magenta"
|
|
44
|
+
text_color = "dim red" if result.isError else "dim magenta"
|
|
45
|
+
|
|
46
|
+
# Analyze content to determine display format and status
|
|
47
|
+
content = result.content
|
|
48
|
+
if result.isError:
|
|
49
|
+
status = "ERROR"
|
|
50
|
+
else:
|
|
51
|
+
# Check if it's a list with content blocks
|
|
52
|
+
if len(content) == 0:
|
|
53
|
+
status = "No Content"
|
|
54
|
+
elif len(content) == 1 and is_text_content(content[0]):
|
|
55
|
+
text_content = get_text(content[0])
|
|
56
|
+
char_count = len(text_content) if text_content else 0
|
|
57
|
+
status = f"Text Only {char_count} chars"
|
|
58
|
+
else:
|
|
59
|
+
text_count = sum(1 for item in content if is_text_content(item))
|
|
60
|
+
if text_count == len(content):
|
|
61
|
+
status = f"{len(content)} Text Blocks" if len(content) > 1 else "1 Text Block"
|
|
62
|
+
else:
|
|
63
|
+
status = (
|
|
64
|
+
f"{len(content)} Content Blocks" if len(content) > 1 else "1 Content Block"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Combined separator and status line
|
|
68
|
+
left = f"[{block_color}]▎[/{block_color}][{text_color}]▶[/{text_color}]{f' [{block_color}]{name}[/{block_color}]' if name else ''}"
|
|
69
|
+
right = f"[dim]tool result - {status}[/dim]"
|
|
70
|
+
self._create_combined_separator_status(left, right)
|
|
71
|
+
|
|
72
|
+
# Display tool result content
|
|
73
|
+
content = result.content
|
|
74
|
+
|
|
75
|
+
# Handle special case: single text content block
|
|
76
|
+
if isinstance(content, list) and len(content) == 1 and is_text_content(content[0]):
|
|
77
|
+
# Display just the text content directly
|
|
78
|
+
text_content = get_text(content[0])
|
|
79
|
+
if text_content:
|
|
80
|
+
if self.config and self.config.logger.truncate_tools and len(text_content) > 360:
|
|
81
|
+
text_content = text_content[:360] + "..."
|
|
82
|
+
console.console.print(text_content, style="dim", markup=self._markup)
|
|
83
|
+
else:
|
|
84
|
+
console.console.print("(empty text)", style="dim", markup=self._markup)
|
|
85
|
+
else:
|
|
86
|
+
# Use Rich pretty printing for everything else
|
|
87
|
+
try:
|
|
88
|
+
import json
|
|
37
89
|
|
|
38
|
-
|
|
39
|
-
Text(str(result.content), overflow="..."),
|
|
40
|
-
title=f"[TOOL RESULT]{f' ({name})' if name else ''}",
|
|
41
|
-
title_align="right",
|
|
42
|
-
style=style,
|
|
43
|
-
border_style="white",
|
|
44
|
-
padding=(1, 2),
|
|
45
|
-
)
|
|
90
|
+
from rich.pretty import Pretty
|
|
46
91
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
92
|
+
# Try to parse as JSON for pretty printing
|
|
93
|
+
if isinstance(content, str):
|
|
94
|
+
json_obj = json.loads(content)
|
|
95
|
+
else:
|
|
96
|
+
json_obj = content
|
|
50
97
|
|
|
51
|
-
|
|
52
|
-
|
|
98
|
+
# Use Rich's built-in truncation with dimmed styling
|
|
99
|
+
if self.config and self.config.logger.truncate_tools:
|
|
100
|
+
pretty_obj = Pretty(json_obj, max_length=10, max_string=50)
|
|
101
|
+
else:
|
|
102
|
+
pretty_obj = Pretty(json_obj)
|
|
53
103
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if not self.config or not self.config.logger.show_tools:
|
|
57
|
-
return
|
|
104
|
+
# Print with dim styling
|
|
105
|
+
console.console.print(pretty_obj, style="dim", markup=self._markup)
|
|
58
106
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
padding=(1, 2),
|
|
66
|
-
)
|
|
107
|
+
except (json.JSONDecodeError, TypeError, ValueError, AttributeError):
|
|
108
|
+
# Fall back to string representation if not valid JSON
|
|
109
|
+
content_str = str(content)
|
|
110
|
+
if self.config and self.config.logger.truncate_tools and len(content_str) > 360:
|
|
111
|
+
content_str = content_str[:360] + "..."
|
|
112
|
+
console.console.print(content_str, style="dim", markup=self._markup)
|
|
67
113
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
console.console.print(panel, markup=self._markup)
|
|
73
|
-
console.console.print("\n")
|
|
114
|
+
# Bottom separator (no additional info for tool results)
|
|
115
|
+
console.console.print()
|
|
116
|
+
console.console.print("─" * console.console.size.width, style="dim")
|
|
117
|
+
console.console.print()
|
|
74
118
|
|
|
75
119
|
def show_tool_call(
|
|
76
|
-
self, available_tools, tool_name, tool_args, name:
|
|
120
|
+
self, available_tools, tool_name, tool_args, name: str | None = None
|
|
77
121
|
) -> None:
|
|
78
|
-
"""Display a tool call in
|
|
122
|
+
"""Display a tool call in the new visual style."""
|
|
79
123
|
if not self.config or not self.config.logger.show_tools:
|
|
80
124
|
return
|
|
81
125
|
|
|
82
126
|
display_tool_list = self._format_tool_list(available_tools, tool_name)
|
|
83
127
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
style="magenta",
|
|
89
|
-
border_style="white",
|
|
90
|
-
subtitle=display_tool_list,
|
|
91
|
-
subtitle_align="left",
|
|
92
|
-
padding=(1, 2),
|
|
93
|
-
)
|
|
128
|
+
# Combined separator and status line
|
|
129
|
+
left = f"[magenta]▎[/magenta][dim magenta]◀[/dim magenta]{f' [magenta]{name}[/magenta]' if name else ''}"
|
|
130
|
+
right = f"[dim]tool request - {tool_name}[/dim]"
|
|
131
|
+
self._create_combined_separator_status(left, right)
|
|
94
132
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
133
|
+
# Display tool arguments using Rich JSON pretty printing (dimmed)
|
|
134
|
+
try:
|
|
135
|
+
import json
|
|
98
136
|
|
|
99
|
-
|
|
100
|
-
|
|
137
|
+
from rich.pretty import Pretty
|
|
138
|
+
|
|
139
|
+
# Try to parse as JSON for pretty printing
|
|
140
|
+
if isinstance(tool_args, str):
|
|
141
|
+
json_obj = json.loads(tool_args)
|
|
142
|
+
else:
|
|
143
|
+
json_obj = tool_args
|
|
144
|
+
|
|
145
|
+
# Use Rich's built-in truncation with dimmed styling
|
|
146
|
+
if self.config and self.config.logger.truncate_tools:
|
|
147
|
+
pretty_obj = Pretty(json_obj, max_length=10, max_string=50)
|
|
148
|
+
else:
|
|
149
|
+
pretty_obj = Pretty(json_obj)
|
|
150
|
+
|
|
151
|
+
# Print with dim styling
|
|
152
|
+
console.console.print(pretty_obj, style="dim", markup=self._markup)
|
|
153
|
+
|
|
154
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
155
|
+
# Fall back to string representation if not valid JSON
|
|
156
|
+
content = str(tool_args)
|
|
157
|
+
if self.config and self.config.logger.truncate_tools and len(content) > 360:
|
|
158
|
+
content = content[:360] + "..."
|
|
159
|
+
console.console.print(content, style="dim", markup=self._markup)
|
|
160
|
+
|
|
161
|
+
# Bottom separator with tool list: ─ [tool1] [tool2] ────────
|
|
162
|
+
console.console.print()
|
|
163
|
+
if display_tool_list and len(display_tool_list) > 0:
|
|
164
|
+
# Truncate tool list if needed (leave space for "─ " prefix and some separator)
|
|
165
|
+
max_tool_width = console.console.size.width - 10 # Reserve space for separators
|
|
166
|
+
truncated_tool_list = self._truncate_list_if_needed(display_tool_list, max_tool_width)
|
|
167
|
+
tool_width = truncated_tool_list.cell_len
|
|
168
|
+
|
|
169
|
+
# Calculate how much space is left for separator line on the right
|
|
170
|
+
total_width = console.console.size.width
|
|
171
|
+
remaining_width = max(0, total_width - tool_width - 2) # -2 for "─ " prefix
|
|
172
|
+
right_sep = "─" * remaining_width if remaining_width > 0 else ""
|
|
173
|
+
|
|
174
|
+
# Create the separator line: ─ [tools] ────────
|
|
175
|
+
combined = Text()
|
|
176
|
+
combined.append("─ ", style="dim")
|
|
177
|
+
combined.append_text(truncated_tool_list)
|
|
178
|
+
combined.append(right_sep, style="dim")
|
|
179
|
+
|
|
180
|
+
console.console.print(combined, markup=self._markup)
|
|
181
|
+
else:
|
|
182
|
+
# Full separator if no tools
|
|
183
|
+
console.console.print("─" * console.console.size.width, style="dim")
|
|
184
|
+
console.console.print()
|
|
101
185
|
|
|
102
186
|
async def show_tool_update(self, aggregator: MCPAggregator | None, updated_server: str) -> None:
|
|
103
|
-
"""Show a tool update for a server"""
|
|
187
|
+
"""Show a tool update for a server in the new visual style."""
|
|
104
188
|
if not self.config or not self.config.logger.show_tools:
|
|
105
189
|
return
|
|
106
190
|
|
|
107
|
-
|
|
191
|
+
# Check if aggregator is actually an agent (has name attribute)
|
|
192
|
+
agent_name = None
|
|
193
|
+
if aggregator and hasattr(aggregator, "name") and aggregator.name:
|
|
194
|
+
agent_name = aggregator.name
|
|
108
195
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
196
|
+
# Combined separator and status line
|
|
197
|
+
if agent_name:
|
|
198
|
+
left = (
|
|
199
|
+
f"[magenta]▎[/magenta][dim magenta]▶[/dim magenta] [magenta]{agent_name}[/magenta]"
|
|
200
|
+
)
|
|
201
|
+
else:
|
|
202
|
+
left = "[magenta]▎[/magenta][dim magenta]▶[/dim magenta]"
|
|
113
203
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
console.console.print(
|
|
125
|
-
|
|
126
|
-
|
|
204
|
+
right = f"[dim]{updated_server}[/dim]"
|
|
205
|
+
self._create_combined_separator_status(left, right)
|
|
206
|
+
|
|
207
|
+
# Display update message
|
|
208
|
+
message = f"Updating tools for server {updated_server}"
|
|
209
|
+
console.console.print(message, style="dim", markup=self._markup)
|
|
210
|
+
|
|
211
|
+
# Bottom separator
|
|
212
|
+
console.console.print()
|
|
213
|
+
console.console.print("─" * console.console.size.width, style="dim")
|
|
214
|
+
console.console.print()
|
|
215
|
+
|
|
216
|
+
# Force prompt_toolkit redraw if active
|
|
217
|
+
try:
|
|
218
|
+
from prompt_toolkit.application.current import get_app
|
|
219
|
+
|
|
220
|
+
get_app().invalidate() # Forces prompt_toolkit to redraw
|
|
221
|
+
except: # noqa: E722
|
|
222
|
+
pass # No active prompt_toolkit session
|
|
127
223
|
|
|
128
224
|
def _format_tool_list(self, available_tools, selected_tool_name):
|
|
129
225
|
"""Format the list of available tools, highlighting the selected one."""
|
|
@@ -158,18 +254,86 @@ class ConsoleDisplay:
|
|
|
158
254
|
|
|
159
255
|
return display_tool_list
|
|
160
256
|
|
|
257
|
+
def _truncate_list_if_needed(self, text_list: Text, max_width: int) -> Text:
|
|
258
|
+
"""Truncate a Text list if it exceeds the maximum width."""
|
|
259
|
+
if text_list.cell_len <= max_width:
|
|
260
|
+
return text_list
|
|
261
|
+
|
|
262
|
+
# Create a new truncated version
|
|
263
|
+
truncated = Text()
|
|
264
|
+
current_width = 0
|
|
265
|
+
|
|
266
|
+
for span in text_list._spans:
|
|
267
|
+
text_part = text_list.plain[span.start : span.end]
|
|
268
|
+
if current_width + len(text_part) <= max_width - 1: # -1 for ellipsis
|
|
269
|
+
truncated.append(text_part, style=span.style)
|
|
270
|
+
current_width += len(text_part)
|
|
271
|
+
else:
|
|
272
|
+
# Add what we can fit and ellipsis
|
|
273
|
+
remaining = max_width - current_width - 1
|
|
274
|
+
if remaining > 0:
|
|
275
|
+
truncated.append(text_part[:remaining], style=span.style)
|
|
276
|
+
truncated.append("…", style="dim")
|
|
277
|
+
break
|
|
278
|
+
|
|
279
|
+
return truncated
|
|
280
|
+
|
|
281
|
+
def _create_combined_separator_status(self, left_content: str, right_info: str = "") -> None:
|
|
282
|
+
"""
|
|
283
|
+
Create a combined separator and status line.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
left_content: The main content (block, arrow, name) - left justified with color
|
|
287
|
+
right_info: Supplementary information to show in brackets - right aligned
|
|
288
|
+
"""
|
|
289
|
+
width = console.console.size.width
|
|
290
|
+
|
|
291
|
+
# Create left text
|
|
292
|
+
left_text = Text.from_markup(left_content)
|
|
293
|
+
|
|
294
|
+
# Create right text if we have info
|
|
295
|
+
if right_info and right_info.strip():
|
|
296
|
+
# Add dim brackets around the right info
|
|
297
|
+
right_text = Text()
|
|
298
|
+
right_text.append("[", style="dim")
|
|
299
|
+
right_text.append_text(Text.from_markup(right_info))
|
|
300
|
+
right_text.append("]", style="dim")
|
|
301
|
+
# Calculate separator count
|
|
302
|
+
separator_count = width - left_text.cell_len - right_text.cell_len
|
|
303
|
+
if separator_count < 1:
|
|
304
|
+
separator_count = 1 # Always at least 1 separator
|
|
305
|
+
else:
|
|
306
|
+
right_text = Text("")
|
|
307
|
+
separator_count = width - left_text.cell_len
|
|
308
|
+
|
|
309
|
+
# Build the combined line
|
|
310
|
+
combined = Text()
|
|
311
|
+
combined.append_text(left_text)
|
|
312
|
+
combined.append(" ", style="default")
|
|
313
|
+
combined.append("─" * (separator_count - 1), style="dim")
|
|
314
|
+
combined.append_text(right_text)
|
|
315
|
+
|
|
316
|
+
# Print with empty line before
|
|
317
|
+
console.console.print()
|
|
318
|
+
console.console.print(combined, markup=self._markup)
|
|
319
|
+
console.console.print()
|
|
320
|
+
|
|
161
321
|
async def show_assistant_message(
|
|
162
322
|
self,
|
|
163
323
|
message_text: Union[str, Text],
|
|
164
324
|
aggregator=None,
|
|
165
325
|
highlight_namespaced_tool: str = "",
|
|
166
326
|
title: str = "ASSISTANT",
|
|
167
|
-
name:
|
|
327
|
+
name: str | None = None,
|
|
328
|
+
model: str | None = None,
|
|
168
329
|
) -> None:
|
|
169
330
|
"""Display an assistant message in a formatted panel."""
|
|
331
|
+
from rich.markdown import Markdown
|
|
332
|
+
|
|
170
333
|
if not self.config or not self.config.logger.show_chat:
|
|
171
334
|
return
|
|
172
335
|
|
|
336
|
+
# Build server list for bottom separator (using same logic as legacy)
|
|
173
337
|
display_server_list = Text()
|
|
174
338
|
|
|
175
339
|
if aggregator:
|
|
@@ -192,42 +356,91 @@ class ConsoleDisplay:
|
|
|
192
356
|
style = "green" if server_name == mcp_server_name else "dim white"
|
|
193
357
|
display_server_list.append(f"[{server_name}] ", style)
|
|
194
358
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
359
|
+
# Combined separator and status line
|
|
360
|
+
left = f"[green]▎[/green][dim green]◀[/dim green]{f' [bold green]{name}[/bold green]' if name else ''}"
|
|
361
|
+
right = f"[dim]{model}[/dim]" if model else ""
|
|
362
|
+
self._create_combined_separator_status(left, right)
|
|
363
|
+
|
|
364
|
+
if isinstance(message_text, str):
|
|
365
|
+
content = message_text
|
|
366
|
+
|
|
367
|
+
# Try to detect and pretty print JSON
|
|
368
|
+
try:
|
|
369
|
+
import json
|
|
370
|
+
|
|
371
|
+
json.loads(content)
|
|
372
|
+
json = JSON(message_text)
|
|
373
|
+
console.console.print(json, markup=self._markup)
|
|
374
|
+
except (JSONDecodeError, TypeError, ValueError):
|
|
375
|
+
# Not JSON, treat as markdown
|
|
376
|
+
md = Markdown(content, code_theme=CODE_STYLE)
|
|
377
|
+
console.console.print(md, markup=self._markup)
|
|
378
|
+
else:
|
|
379
|
+
# Handle Rich Text objects directly
|
|
380
|
+
console.console.print(message_text, markup=self._markup)
|
|
381
|
+
|
|
382
|
+
# Bottom separator with server list: ─ [server1] [server2] ────────
|
|
383
|
+
console.console.print()
|
|
384
|
+
if display_server_list and len(display_server_list) > 0:
|
|
385
|
+
# Truncate server list if needed (leave space for "─ " prefix and some separator)
|
|
386
|
+
max_server_width = console.console.size.width - 10 # Reserve space for separators
|
|
387
|
+
truncated_server_list = self._truncate_list_if_needed(
|
|
388
|
+
display_server_list, max_server_width
|
|
389
|
+
)
|
|
390
|
+
server_width = truncated_server_list.cell_len
|
|
391
|
+
|
|
392
|
+
# Calculate how much space is left for separator line on the right
|
|
393
|
+
total_width = console.console.size.width
|
|
394
|
+
remaining_width = max(0, total_width - server_width - 2) # -2 for "─ " prefix
|
|
395
|
+
right_sep = "─" * remaining_width if remaining_width > 0 else ""
|
|
396
|
+
|
|
397
|
+
# Create the separator line: ─ [servers] ────────
|
|
398
|
+
combined = Text()
|
|
399
|
+
combined.append("─ ", style="dim")
|
|
400
|
+
combined.append_text(truncated_server_list)
|
|
401
|
+
combined.append(right_sep, style="dim")
|
|
402
|
+
|
|
403
|
+
console.console.print(combined, markup=self._markup)
|
|
404
|
+
else:
|
|
405
|
+
# Full separator if no servers
|
|
406
|
+
console.console.print("─" * console.console.size.width, style="dim")
|
|
407
|
+
console.console.print()
|
|
207
408
|
|
|
208
409
|
def show_user_message(
|
|
209
|
-
self, message, model:
|
|
410
|
+
self, message, model: str | None = None, chat_turn: int = 0, name: str | None = None
|
|
210
411
|
) -> None:
|
|
211
|
-
"""Display a user message in
|
|
412
|
+
"""Display a user message in the new visual style."""
|
|
413
|
+
from rich.markdown import Markdown
|
|
414
|
+
|
|
212
415
|
if not self.config or not self.config.logger.show_chat:
|
|
213
416
|
return
|
|
214
417
|
|
|
215
|
-
|
|
418
|
+
# Combined separator and status line
|
|
419
|
+
left = f"[blue]▎[/blue][dim blue]▶[/dim blue]{f' [bold blue]{name}[/bold blue]' if name else ''}"
|
|
420
|
+
|
|
421
|
+
# Build right side with model and turn
|
|
422
|
+
right_parts = []
|
|
423
|
+
if model:
|
|
424
|
+
right_parts.append(model)
|
|
216
425
|
if chat_turn > 0:
|
|
217
|
-
|
|
426
|
+
right_parts.append(f"turn {chat_turn}")
|
|
218
427
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
428
|
+
right = f"[dim]{' '.join(right_parts)}[/dim]" if right_parts else ""
|
|
429
|
+
self._create_combined_separator_status(left, right)
|
|
430
|
+
|
|
431
|
+
# Display content as markdown if it looks like markdown, otherwise as text
|
|
432
|
+
if isinstance(message, str):
|
|
433
|
+
content = message
|
|
434
|
+
md = Markdown(content, code_theme=CODE_STYLE)
|
|
435
|
+
console.console.print(md, markup=self._markup)
|
|
436
|
+
else:
|
|
437
|
+
# Handle Text objects directly
|
|
438
|
+
console.console.print(message, markup=self._markup)
|
|
439
|
+
|
|
440
|
+
# Bottom separator (no server list for user messages)
|
|
441
|
+
console.console.print()
|
|
442
|
+
console.console.print("─" * console.console.size.width, style="dim")
|
|
443
|
+
console.console.print()
|
|
231
444
|
|
|
232
445
|
async def show_prompt_loaded(
|
|
233
446
|
self,
|
|
@@ -305,60 +518,68 @@ class ConsoleDisplay:
|
|
|
305
518
|
|
|
306
519
|
def show_parallel_results(self, parallel_agent) -> None:
|
|
307
520
|
"""Display parallel agent results in a clean, organized format.
|
|
308
|
-
|
|
521
|
+
|
|
309
522
|
Args:
|
|
310
523
|
parallel_agent: The parallel agent containing fan_out_agents with results
|
|
311
524
|
"""
|
|
312
525
|
from rich.markdown import Markdown
|
|
313
526
|
from rich.text import Text
|
|
314
|
-
|
|
527
|
+
|
|
315
528
|
if self.config and not self.config.logger.show_chat:
|
|
316
529
|
return
|
|
317
|
-
|
|
318
|
-
if not parallel_agent or not hasattr(parallel_agent,
|
|
530
|
+
|
|
531
|
+
if not parallel_agent or not hasattr(parallel_agent, "fan_out_agents"):
|
|
319
532
|
return
|
|
320
|
-
|
|
533
|
+
|
|
321
534
|
# Collect results and agent information
|
|
322
535
|
agent_results = []
|
|
323
|
-
|
|
536
|
+
|
|
324
537
|
for agent in parallel_agent.fan_out_agents:
|
|
325
538
|
# Get the last response text from this agent
|
|
326
539
|
message_history = agent.message_history
|
|
327
540
|
if not message_history:
|
|
328
541
|
continue
|
|
329
|
-
|
|
542
|
+
|
|
330
543
|
last_message = message_history[-1]
|
|
331
544
|
content = last_message.last_text()
|
|
332
|
-
|
|
545
|
+
|
|
333
546
|
# Get model name
|
|
334
|
-
model =
|
|
335
|
-
if
|
|
336
|
-
|
|
337
|
-
|
|
547
|
+
model = "unknown"
|
|
548
|
+
if (
|
|
549
|
+
hasattr(agent, "_llm")
|
|
550
|
+
and agent._llm
|
|
551
|
+
and hasattr(agent._llm, "default_request_params")
|
|
552
|
+
):
|
|
553
|
+
model = getattr(agent._llm.default_request_params, "model", "unknown")
|
|
554
|
+
|
|
338
555
|
# Get usage information
|
|
339
556
|
tokens = 0
|
|
340
557
|
tool_calls = 0
|
|
341
|
-
if hasattr(agent,
|
|
558
|
+
if hasattr(agent, "usage_accumulator") and agent.usage_accumulator:
|
|
342
559
|
summary = agent.usage_accumulator.get_summary()
|
|
343
|
-
tokens = summary.get(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
560
|
+
tokens = summary.get("cumulative_input_tokens", 0) + summary.get(
|
|
561
|
+
"cumulative_output_tokens", 0
|
|
562
|
+
)
|
|
563
|
+
tool_calls = summary.get("cumulative_tool_calls", 0)
|
|
564
|
+
|
|
565
|
+
agent_results.append(
|
|
566
|
+
{
|
|
567
|
+
"name": agent.name,
|
|
568
|
+
"model": model,
|
|
569
|
+
"content": content,
|
|
570
|
+
"tokens": tokens,
|
|
571
|
+
"tool_calls": tool_calls,
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
|
|
354
575
|
if not agent_results:
|
|
355
576
|
return
|
|
356
|
-
|
|
577
|
+
|
|
357
578
|
# Display header
|
|
358
579
|
console.console.print()
|
|
359
580
|
console.console.print("[dim]Parallel execution complete[/dim]")
|
|
360
581
|
console.console.print()
|
|
361
|
-
|
|
582
|
+
|
|
362
583
|
# Display results for each agent
|
|
363
584
|
for i, result in enumerate(agent_results):
|
|
364
585
|
if i > 0:
|
|
@@ -366,49 +587,49 @@ class ConsoleDisplay:
|
|
|
366
587
|
console.console.print()
|
|
367
588
|
console.console.print("─" * console.console.size.width, style="dim")
|
|
368
589
|
console.console.print()
|
|
369
|
-
|
|
590
|
+
|
|
370
591
|
# Two column header: model name (green) + usage info (dim)
|
|
371
592
|
left = f"[green]▎[/green] [bold green]{result['model']}[/bold green]"
|
|
372
|
-
|
|
593
|
+
|
|
373
594
|
# Build right side with tokens and tool calls if available
|
|
374
595
|
right_parts = []
|
|
375
|
-
if result[
|
|
596
|
+
if result["tokens"] > 0:
|
|
376
597
|
right_parts.append(f"{result['tokens']:,} tokens")
|
|
377
|
-
if result[
|
|
598
|
+
if result["tool_calls"] > 0:
|
|
378
599
|
right_parts.append(f"{result['tool_calls']} tools")
|
|
379
|
-
|
|
600
|
+
|
|
380
601
|
right = f"[dim]{' • '.join(right_parts) if right_parts else 'no usage data'}[/dim]"
|
|
381
|
-
|
|
602
|
+
|
|
382
603
|
# Calculate padding to right-align usage info
|
|
383
604
|
width = console.console.size.width
|
|
384
605
|
left_text = Text.from_markup(left)
|
|
385
606
|
right_text = Text.from_markup(right)
|
|
386
607
|
padding = max(1, width - left_text.cell_len - right_text.cell_len)
|
|
387
|
-
|
|
608
|
+
|
|
388
609
|
console.console.print(left + " " * padding + right, markup=self._markup)
|
|
389
610
|
console.console.print()
|
|
390
|
-
|
|
611
|
+
|
|
391
612
|
# Display content as markdown if it looks like markdown, otherwise as text
|
|
392
|
-
content = result[
|
|
393
|
-
if any(marker in content for marker in [
|
|
394
|
-
md = Markdown(content)
|
|
613
|
+
content = result["content"]
|
|
614
|
+
if any(marker in content for marker in ["##", "**", "*", "`", "---", "###"]):
|
|
615
|
+
md = Markdown(content, code_theme=CODE_STYLE)
|
|
395
616
|
console.console.print(md, markup=self._markup)
|
|
396
617
|
else:
|
|
397
618
|
console.console.print(content, markup=self._markup)
|
|
398
|
-
|
|
619
|
+
|
|
399
620
|
# Summary
|
|
400
621
|
console.console.print()
|
|
401
622
|
console.console.print("─" * console.console.size.width, style="dim")
|
|
402
|
-
|
|
403
|
-
total_tokens = sum(result[
|
|
404
|
-
total_tools = sum(result[
|
|
405
|
-
|
|
623
|
+
|
|
624
|
+
total_tokens = sum(result["tokens"] for result in agent_results)
|
|
625
|
+
total_tools = sum(result["tool_calls"] for result in agent_results)
|
|
626
|
+
|
|
406
627
|
summary_parts = [f"{len(agent_results)} models"]
|
|
407
628
|
if total_tokens > 0:
|
|
408
629
|
summary_parts.append(f"{total_tokens:,} tokens")
|
|
409
630
|
if total_tools > 0:
|
|
410
631
|
summary_parts.append(f"{total_tools} tools")
|
|
411
|
-
|
|
632
|
+
|
|
412
633
|
summary_text = " • ".join(summary_parts)
|
|
413
634
|
console.console.print(f"[dim]{summary_text}[/dim]")
|
|
414
635
|
console.console.print()
|