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.

Files changed (30) hide show
  1. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.44.dist-info}/METADATA +3 -2
  2. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.44.dist-info}/RECORD +30 -29
  3. mcp_agent/agents/base_agent.py +60 -22
  4. mcp_agent/config.py +2 -0
  5. mcp_agent/core/agent_app.py +15 -5
  6. mcp_agent/core/enhanced_prompt.py +81 -11
  7. mcp_agent/core/fastagent.py +9 -1
  8. mcp_agent/core/interactive_prompt.py +60 -1
  9. mcp_agent/core/usage_display.py +10 -3
  10. mcp_agent/llm/augmented_llm.py +4 -5
  11. mcp_agent/llm/augmented_llm_passthrough.py +15 -0
  12. mcp_agent/llm/providers/augmented_llm_anthropic.py +4 -3
  13. mcp_agent/llm/providers/augmented_llm_bedrock.py +3 -3
  14. mcp_agent/llm/providers/augmented_llm_google_native.py +4 -7
  15. mcp_agent/llm/providers/augmented_llm_openai.py +5 -8
  16. mcp_agent/llm/providers/augmented_llm_tensorzero.py +6 -7
  17. mcp_agent/llm/providers/google_converter.py +6 -9
  18. mcp_agent/llm/providers/multipart_converter_anthropic.py +5 -4
  19. mcp_agent/llm/providers/multipart_converter_openai.py +33 -0
  20. mcp_agent/llm/providers/multipart_converter_tensorzero.py +3 -2
  21. mcp_agent/logging/rich_progress.py +6 -2
  22. mcp_agent/logging/transport.py +30 -36
  23. mcp_agent/mcp/helpers/content_helpers.py +26 -11
  24. mcp_agent/mcp/interfaces.py +22 -2
  25. mcp_agent/mcp/prompt_message_multipart.py +2 -3
  26. mcp_agent/ui/console_display.py +353 -142
  27. mcp_agent/ui/console_display_legacy.py +401 -0
  28. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.44.dist-info}/WHEEL +0 -0
  29. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.44.dist-info}/entry_points.txt +0 -0
  30. {fast_agent_mcp-0.2.43.dist-info → fast_agent_mcp-0.2.44.dist-info}/licenses/LICENSE +0 -0
@@ -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: Optional[str] = None) -> None:
32
- """Display a tool result in a formatted panel."""
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
- style = "red" if result.isError else "magenta"
37
-
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
- )
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
- if self.config and self.config.logger.truncate_tools:
48
- if len(str(result.content)) > 360:
49
- panel.height = 8
86
+ from rich.pretty import Pretty
50
87
 
51
- console.console.print(panel, markup=self._markup)
52
- console.console.print("\n")
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
- 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
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
- 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
- )
100
+ # Print with dim styling
101
+ console.console.print(pretty_obj, style="dim", markup=self._markup)
67
102
 
68
- if self.config and self.config.logger.truncate_tools:
69
- if len(str(result)) > 360:
70
- panel.height = 8
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
- console.console.print(panel, markup=self._markup)
73
- console.console.print("\n")
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: Optional[str] = None
116
+ self, available_tools, tool_name, tool_args, name: str | None = None
77
117
  ) -> None:
78
- """Display a tool call in a formatted panel."""
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
- 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
- )
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
- if self.config and self.config.logger.truncate_tools:
96
- if len(str(tool_args)) > 360:
97
- panel.height = 8
129
+ # Display tool arguments using Rich JSON pretty printing (dimmed)
130
+ try:
131
+ import json
98
132
 
99
- console.console.print(panel, markup=self._markup)
100
- console.console.print("\n")
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
- display_server_list = Text()
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
- 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)
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
- 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")
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: Optional[str] = None,
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
- 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")
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: Optional[str], chat_turn: int, name: Optional[str] = None
400
+ self, message, model: str | None = None, chat_turn: int = 0, name: str | None = None
210
401
  ) -> None:
211
- """Display a user message in a formatted panel."""
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
- subtitle_text = Text(f"{model or 'unknown'}", style="dim white")
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
- subtitle_text.append(f" turn {chat_turn}", style="dim white")
416
+ right_parts.append(f"turn {chat_turn}")
218
417
 
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")
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, 'fan_out_agents'):
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 = '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
-
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, 'usage_accumulator') and agent.usage_accumulator:
548
+ if hasattr(agent, "usage_accumulator") and agent.usage_accumulator:
342
549
  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
-
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['tokens'] > 0:
586
+ if result["tokens"] > 0:
376
587
  right_parts.append(f"{result['tokens']:,} tokens")
377
- if result['tool_calls'] > 0:
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['content']
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['tokens'] for result in agent_results)
404
- total_tools = sum(result['tool_calls'] for result in agent_results)
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()