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