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.

Files changed (37) hide show
  1. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/METADATA +6 -5
  2. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/RECORD +37 -36
  3. mcp_agent/agents/base_agent.py +60 -22
  4. mcp_agent/agents/workflow/evaluator_optimizer.py +39 -63
  5. mcp_agent/agents/workflow/router_agent.py +46 -21
  6. mcp_agent/config.py +2 -0
  7. mcp_agent/context.py +4 -0
  8. mcp_agent/core/agent_app.py +15 -5
  9. mcp_agent/core/direct_decorators.py +4 -5
  10. mcp_agent/core/enhanced_prompt.py +80 -11
  11. mcp_agent/core/fastagent.py +9 -1
  12. mcp_agent/core/interactive_prompt.py +60 -1
  13. mcp_agent/core/usage_display.py +10 -3
  14. mcp_agent/human_input/elicitation_form.py +16 -13
  15. mcp_agent/llm/augmented_llm.py +5 -7
  16. mcp_agent/llm/augmented_llm_passthrough.py +4 -0
  17. mcp_agent/llm/providers/augmented_llm_anthropic.py +258 -98
  18. mcp_agent/llm/providers/augmented_llm_bedrock.py +3 -3
  19. mcp_agent/llm/providers/augmented_llm_google_native.py +4 -7
  20. mcp_agent/llm/providers/augmented_llm_openai.py +5 -8
  21. mcp_agent/llm/providers/augmented_llm_tensorzero.py +6 -7
  22. mcp_agent/llm/providers/google_converter.py +6 -9
  23. mcp_agent/llm/providers/multipart_converter_anthropic.py +5 -4
  24. mcp_agent/llm/providers/multipart_converter_openai.py +33 -0
  25. mcp_agent/llm/providers/multipart_converter_tensorzero.py +3 -2
  26. mcp_agent/logging/rich_progress.py +6 -2
  27. mcp_agent/logging/transport.py +30 -36
  28. mcp_agent/mcp/helpers/content_helpers.py +26 -11
  29. mcp_agent/mcp/interfaces.py +22 -2
  30. mcp_agent/mcp/prompt_message_multipart.py +2 -3
  31. mcp_agent/resources/examples/workflows/evaluator.py +2 -2
  32. mcp_agent/resources/examples/workflows/router.py +1 -1
  33. mcp_agent/ui/console_display.py +363 -142
  34. mcp_agent/ui/console_display_legacy.py +401 -0
  35. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/WHEEL +0 -0
  36. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/entry_points.txt +0 -0
  37. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.45.dist-info}/licenses/LICENSE +0 -0
@@ -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: Optional[str] = None) -> None:
32
- """Display a tool result in a formatted panel."""
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
- style = "red" if result.isError else "magenta"
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
- panel = Panel(
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
- if self.config and self.config.logger.truncate_tools:
48
- if len(str(result.content)) > 360:
49
- panel.height = 8
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
- console.console.print(panel, markup=self._markup)
52
- console.console.print("\n")
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
- def show_oai_tool_result(self, result, name: Optional[str] = None) -> None:
55
- """Display an OpenAI tool result in a formatted panel."""
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
- panel = Panel(
60
- Text(str(result), overflow="..."),
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
- )
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
- if self.config and self.config.logger.truncate_tools:
69
- if len(str(result)) > 360:
70
- panel.height = 8
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: Optional[str] = None
120
+ self, available_tools, tool_name, tool_args, name: str | None = None
77
121
  ) -> None:
78
- """Display a tool call in a formatted panel."""
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
- panel = Panel(
85
- Text(str(tool_args), overflow="ellipsis"),
86
- title=f"[TOOL CALL]{f' ({name})' if name else ''}",
87
- title_align="left",
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
- if self.config and self.config.logger.truncate_tools:
96
- if len(str(tool_args)) > 360:
97
- panel.height = 8
133
+ # Display tool arguments using Rich JSON pretty printing (dimmed)
134
+ try:
135
+ import json
98
136
 
99
- console.console.print(panel, markup=self._markup)
100
- console.console.print("\n")
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
- display_server_list = Text()
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
- if aggregator:
110
- for server_name in await aggregator.list_servers():
111
- style = "green" if updated_server == server_name else "dim white"
112
- display_server_list.append(f"[{server_name}] ", style)
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
- panel = Panel(
115
- f"[dim green]Updating tools for server {updated_server}[/]",
116
- title="[TOOL UPDATE]",
117
- title_align="left",
118
- style="green",
119
- border_style="white",
120
- padding=(1, 2),
121
- subtitle=display_server_list,
122
- subtitle_align="left",
123
- )
124
- console.console.print("\n")
125
- console.console.print(panel, markup=self._markup)
126
- console.console.print("\n")
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: Optional[str] = None,
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
- panel = Panel(
196
- message_text,
197
- title=f"[{title}]{f' ({name})' if name else ''}",
198
- title_align="left",
199
- style="green",
200
- border_style="white",
201
- padding=(1, 2),
202
- subtitle=display_server_list,
203
- subtitle_align="left",
204
- )
205
- console.console.print(panel, markup=self._markup)
206
- console.console.print("\n")
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: Optional[str], chat_turn: int, name: Optional[str] = None
410
+ self, message, model: str | None = None, chat_turn: int = 0, name: str | None = None
210
411
  ) -> None:
211
- """Display a user message in a formatted panel."""
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
- subtitle_text = Text(f"{model or 'unknown'}", style="dim white")
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
- subtitle_text.append(f" turn {chat_turn}", style="dim white")
426
+ right_parts.append(f"turn {chat_turn}")
218
427
 
219
- panel = Panel(
220
- message,
221
- title=f"{f'({name}) [USER]' if name else '[USER]'}",
222
- title_align="right",
223
- style="blue",
224
- border_style="white",
225
- padding=(1, 2),
226
- subtitle=subtitle_text,
227
- subtitle_align="left",
228
- )
229
- console.console.print(panel, markup=self._markup)
230
- console.console.print("\n")
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, 'fan_out_agents'):
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 = 'unknown'
335
- if hasattr(agent, '_llm') and agent._llm and hasattr(agent._llm, 'default_request_params'):
336
- model = getattr(agent._llm.default_request_params, 'model', 'unknown')
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, 'usage_accumulator') and agent.usage_accumulator:
558
+ if hasattr(agent, "usage_accumulator") and agent.usage_accumulator:
342
559
  summary = agent.usage_accumulator.get_summary()
343
- tokens = summary.get('cumulative_input_tokens', 0) + summary.get('cumulative_output_tokens', 0)
344
- tool_calls = summary.get('cumulative_tool_calls', 0)
345
-
346
- agent_results.append({
347
- 'name': agent.name,
348
- 'model': model,
349
- 'content': content,
350
- 'tokens': tokens,
351
- 'tool_calls': tool_calls
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['tokens'] > 0:
596
+ if result["tokens"] > 0:
376
597
  right_parts.append(f"{result['tokens']:,} tokens")
377
- if result['tool_calls'] > 0:
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['content']
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['tokens'] for result in agent_results)
404
- total_tools = sum(result['tool_calls'] for result in agent_results)
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()