fast-agent-mcp 0.4.7__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.
- fast_agent/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Mapping
|
|
5
|
+
|
|
6
|
+
from rich.syntax import Syntax
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
from fast_agent.ui import console
|
|
10
|
+
from fast_agent.ui.message_primitives import MESSAGE_CONFIGS, MessageType
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from mcp.types import CallToolResult
|
|
14
|
+
|
|
15
|
+
from fast_agent.mcp.skybridge import SkybridgeServerConfig
|
|
16
|
+
from fast_agent.ui.console_display import ConsoleDisplay
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ToolDisplay:
|
|
20
|
+
"""Encapsulates rendering logic for tool calls and results."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, display: "ConsoleDisplay") -> None:
|
|
23
|
+
self._display = display
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def _markup(self) -> bool:
|
|
27
|
+
return self._display._markup
|
|
28
|
+
|
|
29
|
+
def show_tool_result(
|
|
30
|
+
self,
|
|
31
|
+
result: CallToolResult,
|
|
32
|
+
*,
|
|
33
|
+
name: str | None = None,
|
|
34
|
+
tool_name: str | None = None,
|
|
35
|
+
skybridge_config: "SkybridgeServerConfig | None" = None,
|
|
36
|
+
timing_ms: float | None = None,
|
|
37
|
+
type_label: str = "tool result",
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Display a tool result in the console."""
|
|
40
|
+
config = self._display.config
|
|
41
|
+
if config and not config.logger.show_tools:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
from fast_agent.mcp.helpers.content_helpers import get_text, is_text_content
|
|
45
|
+
|
|
46
|
+
content = result.content
|
|
47
|
+
structured_content = getattr(result, "structuredContent", None)
|
|
48
|
+
has_structured = structured_content is not None
|
|
49
|
+
|
|
50
|
+
is_skybridge_tool = False
|
|
51
|
+
skybridge_resource_uri: str | None = None
|
|
52
|
+
if has_structured and tool_name and skybridge_config:
|
|
53
|
+
for tool_cfg in skybridge_config.tools:
|
|
54
|
+
if tool_cfg.tool_name == tool_name and tool_cfg.is_valid:
|
|
55
|
+
is_skybridge_tool = True
|
|
56
|
+
skybridge_resource_uri = tool_cfg.resource_uri
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
if result.isError:
|
|
60
|
+
status = "ERROR"
|
|
61
|
+
else:
|
|
62
|
+
if not content:
|
|
63
|
+
status = "No Content"
|
|
64
|
+
elif len(content) == 1 and is_text_content(content[0]):
|
|
65
|
+
text_content = get_text(content[0])
|
|
66
|
+
char_count = len(text_content) if text_content else 0
|
|
67
|
+
status = f"Text Only {char_count} chars"
|
|
68
|
+
else:
|
|
69
|
+
text_count = sum(1 for item in content if is_text_content(item))
|
|
70
|
+
if text_count == len(content):
|
|
71
|
+
status = f"{len(content)} Text Blocks" if len(content) > 1 else "1 Text Block"
|
|
72
|
+
else:
|
|
73
|
+
status = (
|
|
74
|
+
f"{len(content)} Content Blocks" if len(content) > 1 else "1 Content Block"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
channel = getattr(result, "transport_channel", None)
|
|
78
|
+
bottom_metadata_items: list[str] = []
|
|
79
|
+
if channel:
|
|
80
|
+
if channel == "post-json":
|
|
81
|
+
transport_info = "HTTP (JSON-RPC)"
|
|
82
|
+
elif channel == "post-sse":
|
|
83
|
+
transport_info = "HTTP (SSE)"
|
|
84
|
+
elif channel == "get":
|
|
85
|
+
transport_info = "Legacy SSE"
|
|
86
|
+
elif channel == "resumption":
|
|
87
|
+
transport_info = "Resumption"
|
|
88
|
+
elif channel == "stdio":
|
|
89
|
+
transport_info = "STDIO"
|
|
90
|
+
else:
|
|
91
|
+
transport_info = channel.upper()
|
|
92
|
+
|
|
93
|
+
bottom_metadata_items.append(transport_info)
|
|
94
|
+
|
|
95
|
+
# Use timing from FAST_AGENT_TOOL_TIMING (passed as parameter)
|
|
96
|
+
if timing_ms is not None:
|
|
97
|
+
# Convert ms to seconds for display
|
|
98
|
+
timing_seconds = timing_ms / 1000.0
|
|
99
|
+
bottom_metadata_items.append(self._display._format_elapsed(timing_seconds))
|
|
100
|
+
|
|
101
|
+
if has_structured:
|
|
102
|
+
bottom_metadata_items.append("Structured ■")
|
|
103
|
+
|
|
104
|
+
bottom_metadata = bottom_metadata_items or None
|
|
105
|
+
right_info = f"[dim]{type_label} - {status}[/dim]"
|
|
106
|
+
|
|
107
|
+
if has_structured:
|
|
108
|
+
config_map = MESSAGE_CONFIGS[MessageType.TOOL_RESULT]
|
|
109
|
+
block_color = "red" if result.isError else config_map["block_color"]
|
|
110
|
+
arrow = config_map["arrow"]
|
|
111
|
+
arrow_style = config_map["arrow_style"]
|
|
112
|
+
left = f"[{block_color}]▎[/{block_color}][{arrow_style}]{arrow}[/{arrow_style}]"
|
|
113
|
+
if name:
|
|
114
|
+
name_color = block_color if not result.isError else "red"
|
|
115
|
+
left += f" [{name_color}]{name}[/{name_color}]"
|
|
116
|
+
|
|
117
|
+
self._display._create_combined_separator_status(left, right_info)
|
|
118
|
+
|
|
119
|
+
self._display._display_content(
|
|
120
|
+
content,
|
|
121
|
+
True,
|
|
122
|
+
result.isError,
|
|
123
|
+
MessageType.TOOL_RESULT,
|
|
124
|
+
check_markdown_markers=False,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
console.console.print()
|
|
128
|
+
total_width = console.console.size.width
|
|
129
|
+
|
|
130
|
+
if is_skybridge_tool:
|
|
131
|
+
resource_label = (
|
|
132
|
+
f"skybridge resource: {skybridge_resource_uri}"
|
|
133
|
+
if skybridge_resource_uri
|
|
134
|
+
else "skybridge resource"
|
|
135
|
+
)
|
|
136
|
+
prefix = Text("─| ")
|
|
137
|
+
prefix.stylize("dim")
|
|
138
|
+
resource_text = Text(resource_label, style="magenta")
|
|
139
|
+
suffix = Text(" |")
|
|
140
|
+
suffix.stylize("dim")
|
|
141
|
+
|
|
142
|
+
separator_line = Text()
|
|
143
|
+
separator_line.append_text(prefix)
|
|
144
|
+
separator_line.append_text(resource_text)
|
|
145
|
+
separator_line.append_text(suffix)
|
|
146
|
+
remaining = total_width - separator_line.cell_len
|
|
147
|
+
if remaining > 0:
|
|
148
|
+
separator_line.append("─" * remaining, style="dim")
|
|
149
|
+
console.console.print(separator_line, markup=self._markup)
|
|
150
|
+
console.console.print()
|
|
151
|
+
|
|
152
|
+
json_str = json.dumps(structured_content, indent=2)
|
|
153
|
+
syntax_obj = Syntax(
|
|
154
|
+
json_str,
|
|
155
|
+
"json",
|
|
156
|
+
theme=self._display.code_style,
|
|
157
|
+
background_color="default",
|
|
158
|
+
)
|
|
159
|
+
console.console.print(syntax_obj, markup=self._markup)
|
|
160
|
+
else:
|
|
161
|
+
prefix = Text("─| ")
|
|
162
|
+
prefix.stylize("dim")
|
|
163
|
+
suffix = Text(" |")
|
|
164
|
+
suffix.stylize("dim")
|
|
165
|
+
available = max(0, total_width - prefix.cell_len - suffix.cell_len)
|
|
166
|
+
|
|
167
|
+
metadata_text = self._display._format_bottom_metadata(
|
|
168
|
+
bottom_metadata_items,
|
|
169
|
+
None,
|
|
170
|
+
config_map["highlight_color"],
|
|
171
|
+
max_width=available,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
line = Text()
|
|
175
|
+
line.append_text(prefix)
|
|
176
|
+
line.append_text(metadata_text)
|
|
177
|
+
line.append_text(suffix)
|
|
178
|
+
remaining = total_width - line.cell_len
|
|
179
|
+
if remaining > 0:
|
|
180
|
+
line.append("─" * remaining, style="dim")
|
|
181
|
+
console.console.print(line, markup=self._markup)
|
|
182
|
+
console.console.print()
|
|
183
|
+
else:
|
|
184
|
+
self._display.display_message(
|
|
185
|
+
content=content,
|
|
186
|
+
message_type=MessageType.TOOL_RESULT,
|
|
187
|
+
name=name,
|
|
188
|
+
right_info=right_info,
|
|
189
|
+
bottom_metadata=bottom_metadata,
|
|
190
|
+
is_error=result.isError,
|
|
191
|
+
truncate_content=True,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def show_tool_call(
|
|
195
|
+
self,
|
|
196
|
+
tool_name: str,
|
|
197
|
+
tool_args: dict[str, Any] | None,
|
|
198
|
+
*,
|
|
199
|
+
bottom_items: list[str] | None = None,
|
|
200
|
+
highlight_index: int | None = None,
|
|
201
|
+
max_item_length: int | None = None,
|
|
202
|
+
name: str | None = None,
|
|
203
|
+
metadata: dict[str, Any] | None = None,
|
|
204
|
+
type_label: str = "tool call",
|
|
205
|
+
) -> None:
|
|
206
|
+
"""Display a tool call header and body."""
|
|
207
|
+
config = self._display.config
|
|
208
|
+
if config and not config.logger.show_tools:
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
tool_args = tool_args or {}
|
|
212
|
+
metadata = metadata or {}
|
|
213
|
+
|
|
214
|
+
right_info = f"[dim]{type_label} - {tool_name}[/dim]"
|
|
215
|
+
content: Any = tool_args
|
|
216
|
+
pre_content: Text | None = None
|
|
217
|
+
truncate_content = True
|
|
218
|
+
|
|
219
|
+
if metadata.get("variant") == "shell":
|
|
220
|
+
bottom_items = list()
|
|
221
|
+
max_item_length = 50
|
|
222
|
+
command = metadata.get("command") or tool_args.get("command")
|
|
223
|
+
|
|
224
|
+
command_text = Text()
|
|
225
|
+
if command and isinstance(command, str):
|
|
226
|
+
command_text.append("$ ", style="magenta")
|
|
227
|
+
command_text.append(command, style="white")
|
|
228
|
+
else:
|
|
229
|
+
command_text.append("$ ", style="magenta")
|
|
230
|
+
command_text.append("(no shell command provided)", style="dim")
|
|
231
|
+
|
|
232
|
+
content = command_text
|
|
233
|
+
|
|
234
|
+
shell_name = metadata.get("shell_name") or "shell"
|
|
235
|
+
shell_path = metadata.get("shell_path")
|
|
236
|
+
if shell_path:
|
|
237
|
+
bottom_items.append(str(shell_path))
|
|
238
|
+
|
|
239
|
+
right_parts: list[str] = []
|
|
240
|
+
if shell_path and shell_path != shell_name:
|
|
241
|
+
right_parts.append(f"{shell_name} ({shell_path})")
|
|
242
|
+
elif shell_name:
|
|
243
|
+
right_parts.append(shell_name)
|
|
244
|
+
|
|
245
|
+
right_info = f"[dim]{' | '.join(right_parts)}[/dim]" if right_parts else ""
|
|
246
|
+
truncate_content = False
|
|
247
|
+
|
|
248
|
+
working_dir_display = metadata.get("working_dir_display") or metadata.get("working_dir")
|
|
249
|
+
if working_dir_display:
|
|
250
|
+
bottom_items.append(f"cwd: {working_dir_display}")
|
|
251
|
+
|
|
252
|
+
timeout_seconds = metadata.get("timeout_seconds")
|
|
253
|
+
warning_interval = metadata.get("warning_interval_seconds")
|
|
254
|
+
|
|
255
|
+
if timeout_seconds and warning_interval:
|
|
256
|
+
bottom_items.append(
|
|
257
|
+
f"timeout: {timeout_seconds}s, warning every {warning_interval}s"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
self._display.display_message(
|
|
261
|
+
content=content,
|
|
262
|
+
message_type=MessageType.TOOL_CALL,
|
|
263
|
+
name=name,
|
|
264
|
+
pre_content=pre_content,
|
|
265
|
+
right_info=right_info,
|
|
266
|
+
bottom_metadata=bottom_items,
|
|
267
|
+
highlight_index=highlight_index,
|
|
268
|
+
max_item_length=max_item_length,
|
|
269
|
+
truncate_content=truncate_content,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
async def show_tool_update(self, updated_server: str, *, agent_name: str | None = None) -> None:
|
|
273
|
+
"""Show a background tool update notification."""
|
|
274
|
+
config = self._display.config
|
|
275
|
+
if config and not config.logger.show_tools:
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
from prompt_toolkit.application.current import get_app
|
|
280
|
+
|
|
281
|
+
app = get_app()
|
|
282
|
+
from fast_agent.ui import notification_tracker
|
|
283
|
+
|
|
284
|
+
notification_tracker.add_tool_update(updated_server)
|
|
285
|
+
app.invalidate()
|
|
286
|
+
except Exception: # noqa: BLE001
|
|
287
|
+
if agent_name:
|
|
288
|
+
left = f"[magenta]▎[/magenta][dim magenta]▶[/dim magenta] [magenta]{agent_name}[/magenta]"
|
|
289
|
+
else:
|
|
290
|
+
left = "[magenta]▎[/magenta][dim magenta]▶[/dim magenta]"
|
|
291
|
+
|
|
292
|
+
right = f"[dim]{updated_server}[/dim]"
|
|
293
|
+
self._display._create_combined_separator_status(left, right)
|
|
294
|
+
|
|
295
|
+
message = f"Updating tools for server {updated_server}"
|
|
296
|
+
console.console.print(message, style="dim", markup=self._markup)
|
|
297
|
+
|
|
298
|
+
console.console.print()
|
|
299
|
+
console.console.print("─" * console.console.size.width, style="dim")
|
|
300
|
+
console.console.print()
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def summarize_skybridge_configs(
|
|
304
|
+
configs: Mapping[str, "SkybridgeServerConfig"] | None,
|
|
305
|
+
) -> tuple[list[dict[str, Any]], list[str]]:
|
|
306
|
+
"""Convert Skybridge configs into display-ready structures."""
|
|
307
|
+
server_rows: list[dict[str, Any]] = []
|
|
308
|
+
warnings: list[str] = []
|
|
309
|
+
warning_seen: set[str] = set()
|
|
310
|
+
|
|
311
|
+
if not configs:
|
|
312
|
+
return server_rows, warnings
|
|
313
|
+
|
|
314
|
+
def add_warning(message: str) -> None:
|
|
315
|
+
formatted = message.strip()
|
|
316
|
+
if not formatted:
|
|
317
|
+
return
|
|
318
|
+
if formatted not in warning_seen:
|
|
319
|
+
warnings.append(formatted)
|
|
320
|
+
warning_seen.add(formatted)
|
|
321
|
+
|
|
322
|
+
for server_name in sorted(configs.keys()):
|
|
323
|
+
config = configs.get(server_name)
|
|
324
|
+
if not config:
|
|
325
|
+
continue
|
|
326
|
+
resources = list(config.ui_resources or [])
|
|
327
|
+
has_skybridge_signal = bool(
|
|
328
|
+
config.enabled or resources or config.tools or config.warnings
|
|
329
|
+
)
|
|
330
|
+
if not has_skybridge_signal:
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
valid_resource_count = sum(1 for resource in resources if resource.is_skybridge)
|
|
334
|
+
|
|
335
|
+
server_rows.append(
|
|
336
|
+
{
|
|
337
|
+
"server_name": server_name,
|
|
338
|
+
"config": config,
|
|
339
|
+
"resources": resources,
|
|
340
|
+
"valid_resource_count": valid_resource_count,
|
|
341
|
+
"total_resource_count": len(resources),
|
|
342
|
+
"active_tools": [
|
|
343
|
+
{
|
|
344
|
+
"name": tool.display_name,
|
|
345
|
+
"template": str(tool.template_uri) if tool.template_uri else None,
|
|
346
|
+
}
|
|
347
|
+
for tool in config.tools
|
|
348
|
+
if tool.is_valid
|
|
349
|
+
],
|
|
350
|
+
"enabled": config.enabled,
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
for warning in config.warnings:
|
|
355
|
+
message = warning.strip()
|
|
356
|
+
if not message:
|
|
357
|
+
continue
|
|
358
|
+
if not message.startswith(server_name):
|
|
359
|
+
message = f"{server_name} {message}"
|
|
360
|
+
add_warning(message)
|
|
361
|
+
|
|
362
|
+
return server_rows, warnings
|
|
363
|
+
|
|
364
|
+
def show_skybridge_summary(
|
|
365
|
+
self,
|
|
366
|
+
agent_name: str,
|
|
367
|
+
configs: Mapping[str, "SkybridgeServerConfig"] | None,
|
|
368
|
+
) -> None:
|
|
369
|
+
"""Display aggregated Skybridge status."""
|
|
370
|
+
server_rows, warnings = self.summarize_skybridge_configs(configs)
|
|
371
|
+
|
|
372
|
+
if not server_rows and not warnings:
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
heading = "[dim]OpenAI Apps SDK ([/dim][cyan]skybridge[/cyan][dim]) detected:[/dim]"
|
|
376
|
+
console.console.print()
|
|
377
|
+
console.console.print(heading, markup=self._markup)
|
|
378
|
+
|
|
379
|
+
if not server_rows:
|
|
380
|
+
console.console.print("[dim] ● none detected[/dim]", markup=self._markup)
|
|
381
|
+
else:
|
|
382
|
+
for row in server_rows:
|
|
383
|
+
server_name = row["server_name"]
|
|
384
|
+
resource_count = row["valid_resource_count"]
|
|
385
|
+
tool_infos = row["active_tools"]
|
|
386
|
+
enabled = row["enabled"]
|
|
387
|
+
|
|
388
|
+
tool_count = len(tool_infos)
|
|
389
|
+
tool_word = "tool" if tool_count == 1 else "tools"
|
|
390
|
+
resource_word = (
|
|
391
|
+
"skybridge resource" if resource_count == 1 else "skybridge resources"
|
|
392
|
+
)
|
|
393
|
+
tool_segment = f"[cyan]{tool_count}[/cyan][dim] {tool_word}[/dim]"
|
|
394
|
+
resource_segment = f"[cyan]{resource_count}[/cyan][dim] {resource_word}[/dim]"
|
|
395
|
+
name_style = "cyan" if enabled else "yellow"
|
|
396
|
+
status_suffix = "" if enabled else "[dim] (issues detected)[/dim]"
|
|
397
|
+
|
|
398
|
+
console.console.print(
|
|
399
|
+
f"[dim] ● [/dim][{name_style}]{server_name}[/{name_style}]{status_suffix}"
|
|
400
|
+
f"[dim] — [/dim]{tool_segment}[dim], [/dim]{resource_segment}",
|
|
401
|
+
markup=self._markup,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
if tool_infos:
|
|
405
|
+
for tool in tool_infos:
|
|
406
|
+
template_info = (
|
|
407
|
+
f" [dim]({tool['template']})[/dim]" if tool["template"] else ""
|
|
408
|
+
)
|
|
409
|
+
console.console.print(
|
|
410
|
+
f"[dim] · [/dim]{tool['name']}{template_info}", markup=self._markup
|
|
411
|
+
)
|
|
412
|
+
else:
|
|
413
|
+
console.console.print("[dim] · no active tools[/dim]", markup=self._markup)
|
|
414
|
+
|
|
415
|
+
if warnings:
|
|
416
|
+
console.console.print()
|
|
417
|
+
console.console.print(
|
|
418
|
+
"[yellow]Warnings[/yellow]",
|
|
419
|
+
markup=self._markup,
|
|
420
|
+
)
|
|
421
|
+
for warning in warnings:
|
|
422
|
+
console.console.print(f"[yellow]- {warning}[/yellow]", markup=self._markup)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility module for displaying usage statistics in a consistent format.
|
|
3
|
+
Consolidates the usage display logic that was duplicated between fastagent.py and interactive_prompt.py.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def display_usage_report(
|
|
12
|
+
agents: dict[str, Any], show_if_progress_disabled: bool = False, subdued_colors: bool = False
|
|
13
|
+
) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Display a formatted table of token usage for all agents.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
agents: Dictionary of agent name -> agent object
|
|
19
|
+
show_if_progress_disabled: If True, show even when progress display is disabled
|
|
20
|
+
subdued_colors: If True, use dim styling for a more subdued appearance
|
|
21
|
+
"""
|
|
22
|
+
# Check if progress display is enabled (only relevant for fastagent context)
|
|
23
|
+
if not show_if_progress_disabled:
|
|
24
|
+
try:
|
|
25
|
+
from fast_agent import config
|
|
26
|
+
|
|
27
|
+
settings = config.get_settings()
|
|
28
|
+
if not settings.logger.progress_display:
|
|
29
|
+
return
|
|
30
|
+
except (ImportError, AttributeError):
|
|
31
|
+
# If we can't check settings, assume we should display
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
# Collect usage data from all agents
|
|
35
|
+
usage_data = []
|
|
36
|
+
total_input = 0
|
|
37
|
+
total_output = 0
|
|
38
|
+
total_tokens = 0
|
|
39
|
+
total_tool_calls = 0
|
|
40
|
+
|
|
41
|
+
for agent_name, agent in agents.items():
|
|
42
|
+
if agent.usage_accumulator:
|
|
43
|
+
summary = agent.usage_accumulator.get_summary()
|
|
44
|
+
if summary["turn_count"] > 0:
|
|
45
|
+
input_tokens = summary["cumulative_input_tokens"]
|
|
46
|
+
output_tokens = summary["cumulative_output_tokens"]
|
|
47
|
+
billing_tokens = summary["cumulative_billing_tokens"]
|
|
48
|
+
turns = summary["turn_count"]
|
|
49
|
+
tool_calls = summary["cumulative_tool_calls"]
|
|
50
|
+
|
|
51
|
+
# Get context percentage for this agent
|
|
52
|
+
context_percentage = agent.usage_accumulator.context_usage_percentage
|
|
53
|
+
|
|
54
|
+
# Get model name via typed property when available
|
|
55
|
+
model = "unknown"
|
|
56
|
+
if agent.llm:
|
|
57
|
+
model = agent.llm.model_name or "unknown"
|
|
58
|
+
|
|
59
|
+
# Standardize model name truncation - use consistent 25 char width with 22+... truncation
|
|
60
|
+
if len(model) > 25:
|
|
61
|
+
model = model[:22] + "..."
|
|
62
|
+
|
|
63
|
+
usage_data.append(
|
|
64
|
+
{
|
|
65
|
+
"name": agent_name,
|
|
66
|
+
"model": model,
|
|
67
|
+
"input": input_tokens,
|
|
68
|
+
"output": output_tokens,
|
|
69
|
+
"total": billing_tokens,
|
|
70
|
+
"turns": turns,
|
|
71
|
+
"tool_calls": tool_calls,
|
|
72
|
+
"context": context_percentage,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
total_input += input_tokens
|
|
77
|
+
total_output += output_tokens
|
|
78
|
+
total_tokens += billing_tokens
|
|
79
|
+
total_tool_calls += tool_calls
|
|
80
|
+
|
|
81
|
+
if not usage_data:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Calculate dynamic agent column width (max 15)
|
|
85
|
+
max_agent_width = min(15, max(len(data["name"]) for data in usage_data) if usage_data else 8)
|
|
86
|
+
agent_width = max(max_agent_width, 5) # Minimum of 5 for "Agent" header
|
|
87
|
+
|
|
88
|
+
# Display the table with new visual style
|
|
89
|
+
console = Console()
|
|
90
|
+
|
|
91
|
+
# Top separator
|
|
92
|
+
console.print()
|
|
93
|
+
console.print("─" * console.size.width, style="dim")
|
|
94
|
+
console.print()
|
|
95
|
+
|
|
96
|
+
# Header with block character
|
|
97
|
+
console.print("[dim]▎[/dim] [bold dim]Usage Summary[/bold dim]")
|
|
98
|
+
console.print()
|
|
99
|
+
|
|
100
|
+
# Table header with proper spacing
|
|
101
|
+
console.print(
|
|
102
|
+
f"[dim]{'Agent':<{agent_width}} {'Input':>9} {'Output':>9} {'Total':>9} {'Turns':>6} {'Tools':>6} {'Context%':>9} {'Model':<25}[/dim]"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Print agent rows - use styling based on subdued_colors flag
|
|
106
|
+
for data in usage_data:
|
|
107
|
+
input_str = f"{data['input']:,}"
|
|
108
|
+
output_str = f"{data['output']:,}"
|
|
109
|
+
total_str = f"{data['total']:,}"
|
|
110
|
+
turns_str = str(data["turns"])
|
|
111
|
+
tools_str = str(data["tool_calls"])
|
|
112
|
+
context_str = f"{data['context']:.1f}%" if data["context"] is not None else "-"
|
|
113
|
+
|
|
114
|
+
# Truncate agent name if needed
|
|
115
|
+
agent_name = data["name"]
|
|
116
|
+
if len(agent_name) > agent_width:
|
|
117
|
+
agent_name = agent_name[: agent_width - 3] + "..."
|
|
118
|
+
|
|
119
|
+
if subdued_colors:
|
|
120
|
+
# Original fastagent.py style with dim wrapper
|
|
121
|
+
console.print(
|
|
122
|
+
f"[dim]{agent_name:<{agent_width}} "
|
|
123
|
+
f"{input_str:>9} "
|
|
124
|
+
f"{output_str:>9} "
|
|
125
|
+
f"[bold]{total_str:>9}[/bold] "
|
|
126
|
+
f"{turns_str:>6} "
|
|
127
|
+
f"{tools_str:>6} "
|
|
128
|
+
f"{context_str:>9} "
|
|
129
|
+
f"{data['model']:<25}[/dim]"
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
# Original interactive_prompt.py style
|
|
133
|
+
console.print(
|
|
134
|
+
f"{agent_name:<{agent_width}} "
|
|
135
|
+
f"{input_str:>9} "
|
|
136
|
+
f"{output_str:>9} "
|
|
137
|
+
f"[bold]{total_str:>9}[/bold] "
|
|
138
|
+
f"{turns_str:>6} "
|
|
139
|
+
f"{tools_str:>6} "
|
|
140
|
+
f"{context_str:>9} "
|
|
141
|
+
f"[dim]{data['model']:<25}[/dim]"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Add total row if multiple agents
|
|
145
|
+
if len(usage_data) > 1:
|
|
146
|
+
console.print()
|
|
147
|
+
total_input_str = f"{total_input:,}"
|
|
148
|
+
total_output_str = f"{total_output:,}"
|
|
149
|
+
total_tokens_str = f"{total_tokens:,}"
|
|
150
|
+
total_tools_str = str(total_tool_calls)
|
|
151
|
+
|
|
152
|
+
if subdued_colors:
|
|
153
|
+
# Original fastagent.py style with dim wrapper on bold
|
|
154
|
+
console.print(
|
|
155
|
+
f"[bold dim]{'TOTAL':<{agent_width}} "
|
|
156
|
+
f"{total_input_str:>9} "
|
|
157
|
+
f"{total_output_str:>9} "
|
|
158
|
+
f"[bold]{total_tokens_str:>9}[/bold] "
|
|
159
|
+
f"{'':<6} "
|
|
160
|
+
f"{total_tools_str:>6} "
|
|
161
|
+
f"{'':<9} "
|
|
162
|
+
f"{'':<25}[/bold dim]"
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
# Original interactive_prompt.py style
|
|
166
|
+
console.print(
|
|
167
|
+
f"[bold]{'TOTAL':<{agent_width}}[/bold] "
|
|
168
|
+
f"[bold]{total_input_str:>9}[/bold] "
|
|
169
|
+
f"[bold]{total_output_str:>9}[/bold] "
|
|
170
|
+
f"[bold]{total_tokens_str:>9}[/bold] "
|
|
171
|
+
f"{'':<6} "
|
|
172
|
+
f"[bold]{total_tools_str:>6}[/bold] "
|
|
173
|
+
f"{'':<9} "
|
|
174
|
+
f"{'':<25}"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
console.print()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def collect_agents_from_provider(
|
|
181
|
+
prompt_provider: Any, agent_name: str | None = None
|
|
182
|
+
) -> dict[str, Any]:
|
|
183
|
+
"""
|
|
184
|
+
Collect agents from a prompt provider for usage display.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
prompt_provider: Provider that has access to agents
|
|
188
|
+
agent_name: Name of the current agent (for context)
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dictionary of agent name -> agent object
|
|
192
|
+
"""
|
|
193
|
+
agents_to_show = {}
|
|
194
|
+
|
|
195
|
+
if hasattr(prompt_provider, "_agents"):
|
|
196
|
+
# Multi-agent app - show all agents
|
|
197
|
+
agents_to_show = prompt_provider._agents
|
|
198
|
+
elif hasattr(prompt_provider, "agent"):
|
|
199
|
+
# Single agent
|
|
200
|
+
agent = prompt_provider.agent
|
|
201
|
+
if hasattr(agent, "name"):
|
|
202
|
+
agents_to_show = {agent.name: agent}
|
|
203
|
+
|
|
204
|
+
return agents_to_show
|