fast-agent-mcp 0.3.13__py3-none-any.whl → 0.3.15__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 (44) hide show
  1. fast_agent/agents/llm_agent.py +59 -37
  2. fast_agent/agents/llm_decorator.py +13 -2
  3. fast_agent/agents/mcp_agent.py +21 -5
  4. fast_agent/agents/tool_agent.py +41 -29
  5. fast_agent/agents/workflow/router_agent.py +2 -1
  6. fast_agent/cli/commands/check_config.py +48 -1
  7. fast_agent/config.py +65 -2
  8. fast_agent/constants.py +3 -0
  9. fast_agent/context.py +42 -9
  10. fast_agent/core/fastagent.py +14 -1
  11. fast_agent/core/logging/listeners.py +1 -1
  12. fast_agent/core/validation.py +31 -33
  13. fast_agent/event_progress.py +2 -3
  14. fast_agent/human_input/form_fields.py +4 -1
  15. fast_agent/interfaces.py +12 -2
  16. fast_agent/llm/fastagent_llm.py +31 -0
  17. fast_agent/llm/model_database.py +2 -2
  18. fast_agent/llm/model_factory.py +8 -1
  19. fast_agent/llm/provider_key_manager.py +1 -0
  20. fast_agent/llm/provider_types.py +1 -0
  21. fast_agent/llm/request_params.py +3 -1
  22. fast_agent/mcp/mcp_aggregator.py +313 -40
  23. fast_agent/mcp/mcp_connection_manager.py +39 -9
  24. fast_agent/mcp/prompt_message_extended.py +2 -2
  25. fast_agent/mcp/skybridge.py +45 -0
  26. fast_agent/mcp/sse_tracking.py +287 -0
  27. fast_agent/mcp/transport_tracking.py +37 -3
  28. fast_agent/mcp/types.py +24 -0
  29. fast_agent/resources/examples/workflows/router.py +1 -0
  30. fast_agent/resources/setup/fastagent.config.yaml +7 -1
  31. fast_agent/ui/console_display.py +946 -84
  32. fast_agent/ui/elicitation_form.py +23 -1
  33. fast_agent/ui/enhanced_prompt.py +153 -58
  34. fast_agent/ui/interactive_prompt.py +57 -34
  35. fast_agent/ui/markdown_truncator.py +942 -0
  36. fast_agent/ui/mcp_display.py +110 -29
  37. fast_agent/ui/plain_text_truncator.py +68 -0
  38. fast_agent/ui/rich_progress.py +4 -1
  39. fast_agent/ui/streaming_buffer.py +449 -0
  40. {fast_agent_mcp-0.3.13.dist-info → fast_agent_mcp-0.3.15.dist-info}/METADATA +4 -3
  41. {fast_agent_mcp-0.3.13.dist-info → fast_agent_mcp-0.3.15.dist-info}/RECORD +44 -38
  42. {fast_agent_mcp-0.3.13.dist-info → fast_agent_mcp-0.3.15.dist-info}/WHEEL +0 -0
  43. {fast_agent_mcp-0.3.13.dist-info → fast_agent_mcp-0.3.15.dist-info}/entry_points.txt +0 -0
  44. {fast_agent_mcp-0.3.13.dist-info → fast_agent_mcp-0.3.15.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,6 @@
1
1
  """Simplified, robust elicitation form dialog."""
2
2
 
3
+ import re
3
4
  from datetime import date, datetime
4
5
  from typing import Any, Dict, Optional
5
6
 
@@ -63,9 +64,15 @@ class SimpleNumberValidator(Validator):
63
64
  class SimpleStringValidator(Validator):
64
65
  """Simple string validator with real-time feedback."""
65
66
 
66
- def __init__(self, min_length: Optional[int] = None, max_length: Optional[int] = None):
67
+ def __init__(
68
+ self,
69
+ min_length: Optional[int] = None,
70
+ max_length: Optional[int] = None,
71
+ pattern: Optional[str] = None
72
+ ):
67
73
  self.min_length = min_length
68
74
  self.max_length = max_length
75
+ self.pattern = re.compile(pattern, re.DOTALL) if pattern else None
69
76
 
70
77
  def validate(self, document):
71
78
  text = document.text
@@ -83,6 +90,12 @@ class SimpleStringValidator(Validator):
83
90
  cursor_position=self.max_length,
84
91
  )
85
92
 
93
+ if self.pattern is not None and self.pattern.fullmatch(text) is None:
94
+ # TODO: Wrap or truncate line if too long
95
+ raise ValidationError(
96
+ message=f"Must match pattern '{self.pattern.pattern}'", cursor_position=len(text)
97
+ )
98
+
86
99
 
87
100
  class FormatValidator(Validator):
88
101
  """Format-specific validator using Pydantic validators."""
@@ -429,6 +442,8 @@ class ElicitationForm:
429
442
  constraints["minLength"] = field_def["minLength"]
430
443
  if field_def.get("maxLength") is not None:
431
444
  constraints["maxLength"] = field_def["maxLength"]
445
+ if field_def.get("pattern") is not None:
446
+ constraints["pattern"] = field_def["pattern"]
432
447
 
433
448
  # Check anyOf constraints (for Optional fields)
434
449
  if "anyOf" in field_def:
@@ -438,6 +453,8 @@ class ElicitationForm:
438
453
  constraints["minLength"] = variant["minLength"]
439
454
  if variant.get("maxLength") is not None:
440
455
  constraints["maxLength"] = variant["maxLength"]
456
+ if variant.get("pattern") is not None:
457
+ constraints["pattern"] = variant["pattern"]
441
458
  break
442
459
 
443
460
  return constraints
@@ -468,6 +485,10 @@ class ElicitationForm:
468
485
  if constraints.get("maxLength"):
469
486
  hints.append(f"max {constraints['maxLength']} chars")
470
487
 
488
+ if constraints.get("pattern"):
489
+ # TODO: Wrap or truncate line if too long
490
+ format_hint = f"Pattern: {constraints['pattern']}"
491
+
471
492
  # Handle format hints separately (these go on next line)
472
493
  format_type = field_def.get("format")
473
494
  if format_type:
@@ -545,6 +566,7 @@ class ElicitationForm:
545
566
  validator = SimpleStringValidator(
546
567
  min_length=constraints.get("minLength"),
547
568
  max_length=constraints.get("maxLength"),
569
+ pattern=constraints.get("pattern"),
548
570
  )
549
571
  else:
550
572
  constraints = {}
@@ -9,7 +9,7 @@ import shlex
9
9
  import subprocess
10
10
  import tempfile
11
11
  from importlib.metadata import version
12
- from typing import TYPE_CHECKING, List, Optional
12
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
13
13
 
14
14
  from prompt_toolkit import PromptSession
15
15
  from prompt_toolkit.completion import Completer, Completion, WordCompleter
@@ -24,6 +24,7 @@ from fast_agent.agents.agent_types import AgentType
24
24
  from fast_agent.constants import FAST_AGENT_ERROR_CHANNEL, FAST_AGENT_REMOVED_METADATA_CHANNEL
25
25
  from fast_agent.core.exceptions import PromptExitError
26
26
  from fast_agent.llm.model_info import get_model_info
27
+ from fast_agent.mcp.types import McpAgentProtocol
27
28
  from fast_agent.ui.mcp_display import render_mcp_status
28
29
 
29
30
  if TYPE_CHECKING:
@@ -103,10 +104,9 @@ async def _display_agent_info_helper(agent_name: str, agent_provider: "AgentApp
103
104
 
104
105
  # Get counts TODO -- add this to the type library or adjust the way aggregator/reporting works
105
106
  server_count = 0
106
- if hasattr(agent, "_aggregator") and hasattr(agent._aggregator, "server_names"):
107
- server_count = (
108
- len(agent._aggregator.server_names) if agent._aggregator.server_names else 0
109
- )
107
+ if isinstance(agent, McpAgentProtocol):
108
+ server_names = agent.aggregator.server_names
109
+ server_count = len(server_names) if server_names else 0
110
110
 
111
111
  tools_result = await agent.list_tools()
112
112
  tool_count = (
@@ -182,6 +182,17 @@ async def _display_agent_info_helper(agent_name: str, agent_provider: "AgentApp
182
182
  rich_print(f"[dim]Agent [/dim][blue]{agent_name}[/blue][dim]:[/dim] {content}")
183
183
  # await _render_mcp_status(agent)
184
184
 
185
+ # Display Skybridge status (if aggregator discovered any)
186
+ try:
187
+ aggregator = agent.aggregator if isinstance(agent, McpAgentProtocol) else None
188
+ display = getattr(agent, "display", None)
189
+ if aggregator and display and hasattr(display, "show_skybridge_summary"):
190
+ skybridge_configs = await aggregator.get_skybridge_configs()
191
+ display.show_skybridge_summary(agent_name, skybridge_configs)
192
+ except Exception:
193
+ # Ignore Skybridge rendering issues to avoid interfering with startup
194
+ pass
195
+
185
196
  # Mark as shown
186
197
  _agent_info_shown.add(agent_name)
187
198
 
@@ -648,60 +659,102 @@ async def get_enhanced_input(
648
659
  model_display = None
649
660
  tdv_segment = None
650
661
  turn_count = 0
651
- try:
652
- if agent_provider:
662
+ agent = None
663
+ if agent_provider:
664
+ try:
653
665
  agent = agent_provider._agent(agent_name)
666
+ except Exception as exc:
667
+ print(f"[toolbar debug] unable to resolve agent '{agent_name}': {exc}")
654
668
 
655
- # Get turn count from message history
656
- for message in agent.message_history:
657
- if message.role == "user":
658
- turn_count += 1
659
-
660
- # Get model name from LLM
661
- if agent.llm and agent.llm.model_name:
662
- model_name = agent.llm.model_name
663
- # Truncate model name to max 25 characters with ellipsis
664
- max_len = 25
665
- if len(model_name) > max_len:
666
- # Keep total length at max_len including ellipsis
667
- model_display = model_name[: max_len - 1] + "…"
668
- else:
669
- model_display = model_name
670
-
671
- # Build TDV capability segment based on model database
672
- info = get_model_info(agent)
673
- # Default to text-only if info resolution fails for any reason
674
- t, d, v = (True, False, False)
675
- if info:
676
- t, d, v = info.tdv_flags
677
-
678
- # Check for alert flags in user messages
679
- alert_flags: set[str] = set()
680
- error_seen = False
681
- for message in agent.message_history:
682
- if message.channels:
683
- if message.channels.get(FAST_AGENT_ERROR_CHANNEL):
684
- error_seen = True
685
- if message.role == "user" and message.channels:
686
- meta_blocks = message.channels.get(FAST_AGENT_REMOVED_METADATA_CHANNEL, [])
687
- alert_flags.update(_extract_alert_flags_from_meta(meta_blocks))
688
-
689
- if error_seen and not alert_flags:
690
- alert_flags.add("T")
691
-
692
- def _style_flag(letter: str, supported: bool) -> str:
693
- # Enabled uses the same color as NORMAL mode (ansigreen), disabled is dim
694
- if letter in alert_flags:
695
- return f"<style fg='ansired' bg='ansiblack'>{letter}</style>"
696
-
697
- enabled_color = "ansigreen"
698
- if supported:
699
- return f"<style fg='{enabled_color}' bg='ansiblack'>{letter}</style>"
700
- return f"<style fg='ansiblack' bg='ansiwhite'>{letter}</style>"
701
-
702
- tdv_segment = f"{_style_flag('T', t)}{_style_flag('D', d)}{_style_flag('V', v)}"
703
- except Exception:
704
- # If anything goes wrong determining the model, omit it gracefully
669
+ if agent:
670
+ for message in agent.message_history:
671
+ if message.role == "user":
672
+ turn_count += 1
673
+
674
+ # Resolve LLM reference safely (avoid assertion when unattached)
675
+ llm = None
676
+ try:
677
+ llm = agent.llm
678
+ except AssertionError:
679
+ llm = getattr(agent, "_llm", None)
680
+ except Exception as exc:
681
+ print(f"[toolbar debug] agent.llm access failed for '{agent_name}': {exc}")
682
+
683
+ model_name = None
684
+ if llm:
685
+ model_name = getattr(llm, "model_name", None)
686
+ if not model_name:
687
+ model_name = getattr(
688
+ getattr(llm, "default_request_params", None), "model", None
689
+ )
690
+
691
+ if not model_name:
692
+ model_name = getattr(agent.config, "model", None)
693
+ if not model_name and getattr(agent.config, "default_request_params", None):
694
+ model_name = getattr(agent.config.default_request_params, "model", None)
695
+ if not model_name:
696
+ context = getattr(agent, "context", None) or getattr(
697
+ agent_provider, "context", None
698
+ )
699
+ config_obj = getattr(context, "config", None) if context else None
700
+ model_name = getattr(config_obj, "default_model", None)
701
+
702
+ if model_name:
703
+ max_len = 25
704
+ model_display = (
705
+ model_name[: max_len - 1] + "…" if len(model_name) > max_len else model_name
706
+ )
707
+ else:
708
+ print(f"[toolbar debug] no model resolved for agent '{agent_name}'")
709
+ model_display = "unknown"
710
+
711
+ # Build TDV capability segment based on model database
712
+ info = None
713
+ if llm:
714
+ try:
715
+ info = get_model_info(llm)
716
+ except TypeError:
717
+ info = None
718
+ if not info and model_name:
719
+ try:
720
+ info = get_model_info(model_name)
721
+ except TypeError:
722
+ info = None
723
+ except Exception as exc:
724
+ print(f"[toolbar debug] get_model_info failed for '{agent_name}': {exc}")
725
+ info = None
726
+
727
+ # Default to text-only if info resolution fails for any reason
728
+ t, d, v = (True, False, False)
729
+ if info:
730
+ t, d, v = info.tdv_flags
731
+
732
+ # Check for alert flags in user messages
733
+ alert_flags: set[str] = set()
734
+ error_seen = False
735
+ for message in agent.message_history:
736
+ if message.channels:
737
+ if message.channels.get(FAST_AGENT_ERROR_CHANNEL):
738
+ error_seen = True
739
+ if message.role == "user" and message.channels:
740
+ meta_blocks = message.channels.get(FAST_AGENT_REMOVED_METADATA_CHANNEL, [])
741
+ alert_flags.update(_extract_alert_flags_from_meta(meta_blocks))
742
+
743
+ if error_seen and not alert_flags:
744
+ alert_flags.add("T")
745
+
746
+ def _style_flag(letter: str, supported: bool) -> str:
747
+ # Enabled uses the same color as NORMAL mode (ansigreen), disabled is dim
748
+ if letter in alert_flags:
749
+ return f"<style fg='ansired' bg='ansiblack'>{letter}</style>"
750
+
751
+ enabled_color = "ansigreen"
752
+ if supported:
753
+ return f"<style fg='{enabled_color}' bg='ansiblack'>{letter}</style>"
754
+ return f"<style fg='ansiblack' bg='ansiwhite'>{letter}</style>"
755
+
756
+ tdv_segment = f"{_style_flag('T', t)}{_style_flag('D', d)}{_style_flag('V', v)}"
757
+ else:
705
758
  model_display = None
706
759
  tdv_segment = None
707
760
 
@@ -829,6 +882,46 @@ async def get_enhanced_input(
829
882
  # Display info for all available agents with tree structure for workflows
830
883
  await _display_all_agents_with_hierarchy(available_agents, agent_provider)
831
884
 
885
+ # Show streaming status message
886
+ if agent_provider:
887
+ # Get logger settings from the agent's context (not agent_provider)
888
+ logger_settings = None
889
+ try:
890
+ agent = agent_provider._agent(agent_name)
891
+ agent_context = agent._context or agent.context
892
+ logger_settings = agent_context.config.logger
893
+ except Exception:
894
+ # If we can't get the agent or its context, logger_settings stays None
895
+ pass
896
+
897
+ # Only show streaming messages if chat display is enabled AND we have logger_settings
898
+ if logger_settings:
899
+ show_chat = getattr(logger_settings, "show_chat", True)
900
+
901
+ if show_chat:
902
+ # Check for parallel agents
903
+ has_parallel = any(
904
+ agent.agent_type == AgentType.PARALLEL
905
+ for agent in agent_provider._agents.values()
906
+ )
907
+
908
+ # Note: streaming may have been disabled by fastagent.py if parallel agents exist
909
+ # So we check has_parallel first to show the appropriate message
910
+ if has_parallel:
911
+ # Streaming is disabled due to parallel agents
912
+ rich_print(
913
+ "[dim]Markdown Streaming disabled (Parallel Agents configured)[/dim]"
914
+ )
915
+ else:
916
+ # Check if streaming is enabled
917
+ streaming_enabled = getattr(logger_settings, "streaming_display", True)
918
+ streaming_mode = getattr(logger_settings, "streaming", "markdown")
919
+ if streaming_enabled and streaming_mode != "none":
920
+ # Streaming is enabled - notify users since it's experimental
921
+ rich_print(
922
+ f"[dim]Experimental: Streaming Enabled - {streaming_mode} mode[/dim]"
923
+ )
924
+
832
925
  rich_print()
833
926
  help_message_shown = True
834
927
 
@@ -1022,7 +1115,9 @@ async def get_argument_input(
1022
1115
  prompt_session.app.exit()
1023
1116
 
1024
1117
 
1025
- async def handle_special_commands(command, agent_app=None):
1118
+ async def handle_special_commands(
1119
+ command: Any, agent_app: "AgentApp | None" = None
1120
+ ) -> bool | Dict[str, Any]:
1026
1121
  """
1027
1122
  Handle special input commands.
1028
1123
 
@@ -14,7 +14,7 @@ Usage:
14
14
  )
15
15
  """
16
16
 
17
- from typing import TYPE_CHECKING, Awaitable, Callable, Dict, List, Optional, Union
17
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Union, cast
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from fast_agent.core.agent_app import AgentApp
@@ -25,6 +25,7 @@ from rich import print as rich_print
25
25
  from fast_agent.agents.agent_types import AgentType
26
26
  from fast_agent.history.history_exporter import HistoryExporter
27
27
  from fast_agent.mcp.mcp_aggregator import SEP
28
+ from fast_agent.mcp.types import McpAgentProtocol
28
29
  from fast_agent.types import PromptMessageExtended
29
30
  from fast_agent.ui.enhanced_prompt import (
30
31
  _display_agent_info_helper,
@@ -114,8 +115,9 @@ class InteractivePrompt:
114
115
 
115
116
  # Check if we should switch agents
116
117
  if isinstance(command_result, dict):
117
- if "switch_agent" in command_result:
118
- new_agent = command_result["switch_agent"]
118
+ command_dict: Dict[str, Any] = command_result
119
+ if "switch_agent" in command_dict:
120
+ new_agent = command_dict["switch_agent"]
119
121
  if new_agent in available_agents_set:
120
122
  agent = new_agent
121
123
  # Display new agent info immediately when switching
@@ -126,14 +128,14 @@ class InteractivePrompt:
126
128
  rich_print(f"[red]Agent '{new_agent}' not found[/red]")
127
129
  continue
128
130
  # Keep the existing list_prompts handler for backward compatibility
129
- elif "list_prompts" in command_result:
131
+ elif "list_prompts" in command_dict:
130
132
  # Use the prompt_provider directly
131
133
  await self._list_prompts(prompt_provider, agent)
132
134
  continue
133
- elif "select_prompt" in command_result:
135
+ elif "select_prompt" in command_dict:
134
136
  # Handle prompt selection, using both list_prompts and apply_prompt
135
- prompt_name = command_result.get("prompt_name")
136
- prompt_index = command_result.get("prompt_index")
137
+ prompt_name = command_dict.get("prompt_name")
138
+ prompt_index = command_dict.get("prompt_index")
137
139
 
138
140
  # If a specific index was provided (from /prompt <number>)
139
141
  if prompt_index is not None:
@@ -163,16 +165,20 @@ class InteractivePrompt:
163
165
  # Use the name-based selection
164
166
  await self._select_prompt(prompt_provider, agent, prompt_name)
165
167
  continue
166
- elif "list_tools" in command_result:
168
+ elif "list_tools" in command_dict:
167
169
  # Handle tools list display
168
170
  await self._list_tools(prompt_provider, agent)
169
171
  continue
170
- elif "show_usage" in command_result:
172
+ elif "show_usage" in command_dict:
171
173
  # Handle usage display
172
174
  await self._show_usage(prompt_provider, agent)
173
175
  continue
174
- elif "show_history" in command_result:
175
- target_agent = command_result.get("show_history", {}).get("agent") or agent
176
+ elif "show_history" in command_dict:
177
+ history_info = command_dict.get("show_history")
178
+ history_agent = (
179
+ history_info.get("agent") if isinstance(history_info, dict) else None
180
+ )
181
+ target_agent = history_agent or agent
176
182
  try:
177
183
  agent_obj = prompt_provider._agent(target_agent)
178
184
  except Exception:
@@ -183,8 +189,12 @@ class InteractivePrompt:
183
189
  usage = getattr(agent_obj, "usage_accumulator", None)
184
190
  display_history_overview(target_agent, history, usage)
185
191
  continue
186
- elif "clear_history" in command_result:
187
- target_agent = command_result.get("clear_history", {}).get("agent") or agent
192
+ elif "clear_history" in command_dict:
193
+ clear_info = command_dict.get("clear_history")
194
+ clear_agent = (
195
+ clear_info.get("agent") if isinstance(clear_info, dict) else None
196
+ )
197
+ target_agent = clear_agent or agent
188
198
  try:
189
199
  agent_obj = prompt_provider._agent(target_agent)
190
200
  except Exception:
@@ -194,7 +204,9 @@ class InteractivePrompt:
194
204
  if hasattr(agent_obj, "clear"):
195
205
  try:
196
206
  agent_obj.clear()
197
- rich_print(f"[green]History cleared for agent '{target_agent}'.[/green]")
207
+ rich_print(
208
+ f"[green]History cleared for agent '{target_agent}'.[/green]"
209
+ )
198
210
  except Exception as exc:
199
211
  rich_print(
200
212
  f"[red]Failed to clear history for '{target_agent}': {exc}[/red]"
@@ -204,21 +216,21 @@ class InteractivePrompt:
204
216
  f"[yellow]Agent '{target_agent}' does not support clearing history.[/yellow]"
205
217
  )
206
218
  continue
207
- elif "show_system" in command_result:
219
+ elif "show_system" in command_dict:
208
220
  # Handle system prompt display
209
221
  await self._show_system(prompt_provider, agent)
210
222
  continue
211
- elif "show_markdown" in command_result:
223
+ elif "show_markdown" in command_dict:
212
224
  # Handle markdown display
213
225
  await self._show_markdown(prompt_provider, agent)
214
226
  continue
215
- elif "show_mcp_status" in command_result:
227
+ elif "show_mcp_status" in command_dict:
216
228
  rich_print()
217
229
  await show_mcp_status(agent, prompt_provider)
218
230
  continue
219
- elif "save_history" in command_result:
231
+ elif "save_history" in command_dict:
220
232
  # Save history for the current agent
221
- filename = command_result.get("filename")
233
+ filename = command_dict.get("filename")
222
234
  try:
223
235
  agent_obj = prompt_provider._agent(agent)
224
236
 
@@ -353,15 +365,16 @@ class InteractivePrompt:
353
365
  )
354
366
  else:
355
367
  # Handle Prompt objects from mcp.types
368
+ prompt_obj = cast("Prompt", prompt)
356
369
  all_prompts.append(
357
370
  {
358
371
  "server": server_name,
359
- "name": prompt.name,
360
- "namespaced_name": f"{server_name}{SEP}{prompt.name}",
361
- "title": prompt.title or None,
362
- "description": prompt.description or "No description",
363
- "arg_count": len(prompt.arguments or []),
364
- "arguments": prompt.arguments or [],
372
+ "name": prompt_obj.name,
373
+ "namespaced_name": f"{server_name}{SEP}{prompt_obj.name}",
374
+ "title": prompt_obj.title or None,
375
+ "description": prompt_obj.description or "No description",
376
+ "arg_count": len(prompt_obj.arguments or []),
377
+ "arguments": prompt_obj.arguments or [],
365
378
  }
366
379
  )
367
380
 
@@ -856,6 +869,10 @@ class InteractivePrompt:
856
869
  if tool.title and tool.title.strip():
857
870
  tool_line.append(f" {tool.title}", style="default")
858
871
 
872
+ meta = getattr(tool, "meta", {}) or {}
873
+ if meta.get("openai/skybridgeEnabled"):
874
+ tool_line.append(" (skybridge)", style="cyan")
875
+
859
876
  rich_print(tool_line)
860
877
 
861
878
  # Description lines - show 2-3 rows if needed
@@ -909,6 +926,11 @@ class InteractivePrompt:
909
926
  args_text = args_text[:77] + "..."
910
927
  rich_print(f" [dim magenta]args: {args_text}[/dim magenta]")
911
928
 
929
+ if meta.get("openai/skybridgeEnabled"):
930
+ template = meta.get("openai/skybridgeTemplate")
931
+ if template:
932
+ rich_print(f" [dim magenta]template:[/dim magenta] {template}")
933
+
912
934
  rich_print() # Space between tools
913
935
 
914
936
  except Exception as e:
@@ -962,22 +984,23 @@ class InteractivePrompt:
962
984
 
963
985
  # Get server count for display
964
986
  server_count = 0
965
- if hasattr(agent, "_aggregator") and hasattr(agent._aggregator, "server_names"):
966
- server_count = (
967
- len(agent._aggregator.server_names) if agent._aggregator.server_names else 0
968
- )
987
+ if isinstance(agent, McpAgentProtocol):
988
+ server_names = agent.aggregator.server_names
989
+ server_count = len(server_names) if server_names else 0
969
990
 
970
991
  # Use the display utility to show the system prompt
971
- if hasattr(agent, "display") and agent.display:
972
- agent.display.show_system_message(
992
+ agent_display = getattr(agent, "display", None)
993
+ if agent_display:
994
+ agent_display.show_system_message(
973
995
  system_prompt=system_prompt, agent_name=agent_name, server_count=server_count
974
996
  )
975
997
  else:
976
998
  # Fallback to basic display
977
999
  from fast_agent.ui.console_display import ConsoleDisplay
978
1000
 
1001
+ agent_context = getattr(agent, "context", None)
979
1002
  display = ConsoleDisplay(
980
- config=agent.context.config if hasattr(agent, "context") else None
1003
+ config=agent_context.config if hasattr(agent_context, "config") else None
981
1004
  )
982
1005
  display.show_system_message(
983
1006
  system_prompt=system_prompt, agent_name=agent_name, server_count=server_count
@@ -1005,11 +1028,11 @@ class InteractivePrompt:
1005
1028
  agent = prompt_provider._agent(agent_name)
1006
1029
 
1007
1030
  # Check if agent has message history
1008
- if not hasattr(agent, "_llm") or not agent._llm:
1031
+ if not agent.llm:
1009
1032
  rich_print("[yellow]No message history available[/yellow]")
1010
1033
  return
1011
1034
 
1012
- message_history = agent._llm.message_history
1035
+ message_history = agent.llm.message_history
1013
1036
  if not message_history:
1014
1037
  rich_print("[yellow]No messages in history[/yellow]")
1015
1038
  return