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.
Files changed (261) hide show
  1. fast_agent/__init__.py +183 -0
  2. fast_agent/acp/__init__.py +19 -0
  3. fast_agent/acp/acp_aware_mixin.py +304 -0
  4. fast_agent/acp/acp_context.py +437 -0
  5. fast_agent/acp/content_conversion.py +136 -0
  6. fast_agent/acp/filesystem_runtime.py +427 -0
  7. fast_agent/acp/permission_store.py +269 -0
  8. fast_agent/acp/server/__init__.py +5 -0
  9. fast_agent/acp/server/agent_acp_server.py +1472 -0
  10. fast_agent/acp/slash_commands.py +1050 -0
  11. fast_agent/acp/terminal_runtime.py +408 -0
  12. fast_agent/acp/tool_permission_adapter.py +125 -0
  13. fast_agent/acp/tool_permissions.py +474 -0
  14. fast_agent/acp/tool_progress.py +814 -0
  15. fast_agent/agents/__init__.py +85 -0
  16. fast_agent/agents/agent_types.py +64 -0
  17. fast_agent/agents/llm_agent.py +350 -0
  18. fast_agent/agents/llm_decorator.py +1139 -0
  19. fast_agent/agents/mcp_agent.py +1337 -0
  20. fast_agent/agents/tool_agent.py +271 -0
  21. fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
  22. fast_agent/agents/workflow/chain_agent.py +212 -0
  23. fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
  24. fast_agent/agents/workflow/iterative_planner.py +652 -0
  25. fast_agent/agents/workflow/maker_agent.py +379 -0
  26. fast_agent/agents/workflow/orchestrator_models.py +218 -0
  27. fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
  28. fast_agent/agents/workflow/parallel_agent.py +250 -0
  29. fast_agent/agents/workflow/router_agent.py +353 -0
  30. fast_agent/cli/__init__.py +0 -0
  31. fast_agent/cli/__main__.py +73 -0
  32. fast_agent/cli/commands/acp.py +159 -0
  33. fast_agent/cli/commands/auth.py +404 -0
  34. fast_agent/cli/commands/check_config.py +783 -0
  35. fast_agent/cli/commands/go.py +514 -0
  36. fast_agent/cli/commands/quickstart.py +557 -0
  37. fast_agent/cli/commands/serve.py +143 -0
  38. fast_agent/cli/commands/server_helpers.py +114 -0
  39. fast_agent/cli/commands/setup.py +174 -0
  40. fast_agent/cli/commands/url_parser.py +190 -0
  41. fast_agent/cli/constants.py +40 -0
  42. fast_agent/cli/main.py +115 -0
  43. fast_agent/cli/terminal.py +24 -0
  44. fast_agent/config.py +798 -0
  45. fast_agent/constants.py +41 -0
  46. fast_agent/context.py +279 -0
  47. fast_agent/context_dependent.py +50 -0
  48. fast_agent/core/__init__.py +92 -0
  49. fast_agent/core/agent_app.py +448 -0
  50. fast_agent/core/core_app.py +137 -0
  51. fast_agent/core/direct_decorators.py +784 -0
  52. fast_agent/core/direct_factory.py +620 -0
  53. fast_agent/core/error_handling.py +27 -0
  54. fast_agent/core/exceptions.py +90 -0
  55. fast_agent/core/executor/__init__.py +0 -0
  56. fast_agent/core/executor/executor.py +280 -0
  57. fast_agent/core/executor/task_registry.py +32 -0
  58. fast_agent/core/executor/workflow_signal.py +324 -0
  59. fast_agent/core/fastagent.py +1186 -0
  60. fast_agent/core/logging/__init__.py +5 -0
  61. fast_agent/core/logging/events.py +138 -0
  62. fast_agent/core/logging/json_serializer.py +164 -0
  63. fast_agent/core/logging/listeners.py +309 -0
  64. fast_agent/core/logging/logger.py +278 -0
  65. fast_agent/core/logging/transport.py +481 -0
  66. fast_agent/core/prompt.py +9 -0
  67. fast_agent/core/prompt_templates.py +183 -0
  68. fast_agent/core/validation.py +326 -0
  69. fast_agent/event_progress.py +62 -0
  70. fast_agent/history/history_exporter.py +49 -0
  71. fast_agent/human_input/__init__.py +47 -0
  72. fast_agent/human_input/elicitation_handler.py +123 -0
  73. fast_agent/human_input/elicitation_state.py +33 -0
  74. fast_agent/human_input/form_elements.py +59 -0
  75. fast_agent/human_input/form_fields.py +256 -0
  76. fast_agent/human_input/simple_form.py +113 -0
  77. fast_agent/human_input/types.py +40 -0
  78. fast_agent/interfaces.py +310 -0
  79. fast_agent/llm/__init__.py +9 -0
  80. fast_agent/llm/cancellation.py +22 -0
  81. fast_agent/llm/fastagent_llm.py +931 -0
  82. fast_agent/llm/internal/passthrough.py +161 -0
  83. fast_agent/llm/internal/playback.py +129 -0
  84. fast_agent/llm/internal/silent.py +41 -0
  85. fast_agent/llm/internal/slow.py +38 -0
  86. fast_agent/llm/memory.py +275 -0
  87. fast_agent/llm/model_database.py +490 -0
  88. fast_agent/llm/model_factory.py +388 -0
  89. fast_agent/llm/model_info.py +102 -0
  90. fast_agent/llm/prompt_utils.py +155 -0
  91. fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
  92. fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
  93. fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
  94. fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
  95. fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
  96. fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
  97. fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
  98. fast_agent/llm/provider/google/google_converter.py +466 -0
  99. fast_agent/llm/provider/google/llm_google_native.py +681 -0
  100. fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
  101. fast_agent/llm/provider/openai/llm_azure.py +143 -0
  102. fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
  103. fast_agent/llm/provider/openai/llm_generic.py +35 -0
  104. fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
  105. fast_agent/llm/provider/openai/llm_groq.py +42 -0
  106. fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
  107. fast_agent/llm/provider/openai/llm_openai.py +1195 -0
  108. fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
  109. fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
  110. fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
  111. fast_agent/llm/provider/openai/llm_xai.py +38 -0
  112. fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
  113. fast_agent/llm/provider/openai/openai_multipart.py +169 -0
  114. fast_agent/llm/provider/openai/openai_utils.py +67 -0
  115. fast_agent/llm/provider/openai/responses.py +133 -0
  116. fast_agent/llm/provider_key_manager.py +139 -0
  117. fast_agent/llm/provider_types.py +34 -0
  118. fast_agent/llm/request_params.py +61 -0
  119. fast_agent/llm/sampling_converter.py +98 -0
  120. fast_agent/llm/stream_types.py +9 -0
  121. fast_agent/llm/usage_tracking.py +445 -0
  122. fast_agent/mcp/__init__.py +56 -0
  123. fast_agent/mcp/common.py +26 -0
  124. fast_agent/mcp/elicitation_factory.py +84 -0
  125. fast_agent/mcp/elicitation_handlers.py +164 -0
  126. fast_agent/mcp/gen_client.py +83 -0
  127. fast_agent/mcp/helpers/__init__.py +36 -0
  128. fast_agent/mcp/helpers/content_helpers.py +352 -0
  129. fast_agent/mcp/helpers/server_config_helpers.py +25 -0
  130. fast_agent/mcp/hf_auth.py +147 -0
  131. fast_agent/mcp/interfaces.py +92 -0
  132. fast_agent/mcp/logger_textio.py +108 -0
  133. fast_agent/mcp/mcp_agent_client_session.py +411 -0
  134. fast_agent/mcp/mcp_aggregator.py +2175 -0
  135. fast_agent/mcp/mcp_connection_manager.py +723 -0
  136. fast_agent/mcp/mcp_content.py +262 -0
  137. fast_agent/mcp/mime_utils.py +108 -0
  138. fast_agent/mcp/oauth_client.py +509 -0
  139. fast_agent/mcp/prompt.py +159 -0
  140. fast_agent/mcp/prompt_message_extended.py +155 -0
  141. fast_agent/mcp/prompt_render.py +84 -0
  142. fast_agent/mcp/prompt_serialization.py +580 -0
  143. fast_agent/mcp/prompts/__init__.py +0 -0
  144. fast_agent/mcp/prompts/__main__.py +7 -0
  145. fast_agent/mcp/prompts/prompt_constants.py +18 -0
  146. fast_agent/mcp/prompts/prompt_helpers.py +238 -0
  147. fast_agent/mcp/prompts/prompt_load.py +186 -0
  148. fast_agent/mcp/prompts/prompt_server.py +552 -0
  149. fast_agent/mcp/prompts/prompt_template.py +438 -0
  150. fast_agent/mcp/resource_utils.py +215 -0
  151. fast_agent/mcp/sampling.py +200 -0
  152. fast_agent/mcp/server/__init__.py +4 -0
  153. fast_agent/mcp/server/agent_server.py +613 -0
  154. fast_agent/mcp/skybridge.py +44 -0
  155. fast_agent/mcp/sse_tracking.py +287 -0
  156. fast_agent/mcp/stdio_tracking_simple.py +59 -0
  157. fast_agent/mcp/streamable_http_tracking.py +309 -0
  158. fast_agent/mcp/tool_execution_handler.py +137 -0
  159. fast_agent/mcp/tool_permission_handler.py +88 -0
  160. fast_agent/mcp/transport_tracking.py +634 -0
  161. fast_agent/mcp/types.py +24 -0
  162. fast_agent/mcp/ui_agent.py +48 -0
  163. fast_agent/mcp/ui_mixin.py +209 -0
  164. fast_agent/mcp_server_registry.py +89 -0
  165. fast_agent/py.typed +0 -0
  166. fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
  167. fast_agent/resources/examples/data-analysis/analysis.py +68 -0
  168. fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
  169. fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
  170. fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
  171. fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
  172. fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
  173. fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
  174. fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
  175. fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
  176. fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
  177. fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
  178. fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
  179. fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
  180. fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
  181. fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
  182. fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
  183. fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
  184. fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
  185. fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
  186. fast_agent/resources/examples/researcher/researcher.py +36 -0
  187. fast_agent/resources/examples/tensorzero/.env.sample +2 -0
  188. fast_agent/resources/examples/tensorzero/Makefile +31 -0
  189. fast_agent/resources/examples/tensorzero/README.md +56 -0
  190. fast_agent/resources/examples/tensorzero/agent.py +35 -0
  191. fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
  192. fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
  193. fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
  194. fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
  195. fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
  196. fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
  197. fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
  198. fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
  199. fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
  200. fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
  201. fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
  202. fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
  203. fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
  204. fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
  205. fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
  206. fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
  207. fast_agent/resources/examples/workflows/chaining.py +37 -0
  208. fast_agent/resources/examples/workflows/evaluator.py +77 -0
  209. fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
  210. fast_agent/resources/examples/workflows/graded_report.md +89 -0
  211. fast_agent/resources/examples/workflows/human_input.py +28 -0
  212. fast_agent/resources/examples/workflows/maker.py +156 -0
  213. fast_agent/resources/examples/workflows/orchestrator.py +70 -0
  214. fast_agent/resources/examples/workflows/parallel.py +56 -0
  215. fast_agent/resources/examples/workflows/router.py +69 -0
  216. fast_agent/resources/examples/workflows/short_story.md +13 -0
  217. fast_agent/resources/examples/workflows/short_story.txt +19 -0
  218. fast_agent/resources/setup/.gitignore +30 -0
  219. fast_agent/resources/setup/agent.py +28 -0
  220. fast_agent/resources/setup/fastagent.config.yaml +65 -0
  221. fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
  222. fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
  223. fast_agent/skills/__init__.py +9 -0
  224. fast_agent/skills/registry.py +235 -0
  225. fast_agent/tools/elicitation.py +369 -0
  226. fast_agent/tools/shell_runtime.py +402 -0
  227. fast_agent/types/__init__.py +59 -0
  228. fast_agent/types/conversation_summary.py +294 -0
  229. fast_agent/types/llm_stop_reason.py +78 -0
  230. fast_agent/types/message_search.py +249 -0
  231. fast_agent/ui/__init__.py +38 -0
  232. fast_agent/ui/console.py +59 -0
  233. fast_agent/ui/console_display.py +1080 -0
  234. fast_agent/ui/elicitation_form.py +946 -0
  235. fast_agent/ui/elicitation_style.py +59 -0
  236. fast_agent/ui/enhanced_prompt.py +1400 -0
  237. fast_agent/ui/history_display.py +734 -0
  238. fast_agent/ui/interactive_prompt.py +1199 -0
  239. fast_agent/ui/markdown_helpers.py +104 -0
  240. fast_agent/ui/markdown_truncator.py +1004 -0
  241. fast_agent/ui/mcp_display.py +857 -0
  242. fast_agent/ui/mcp_ui_utils.py +235 -0
  243. fast_agent/ui/mermaid_utils.py +169 -0
  244. fast_agent/ui/message_primitives.py +50 -0
  245. fast_agent/ui/notification_tracker.py +205 -0
  246. fast_agent/ui/plain_text_truncator.py +68 -0
  247. fast_agent/ui/progress_display.py +10 -0
  248. fast_agent/ui/rich_progress.py +195 -0
  249. fast_agent/ui/streaming.py +774 -0
  250. fast_agent/ui/streaming_buffer.py +449 -0
  251. fast_agent/ui/tool_display.py +422 -0
  252. fast_agent/ui/usage_display.py +204 -0
  253. fast_agent/utils/__init__.py +5 -0
  254. fast_agent/utils/reasoning_stream_parser.py +77 -0
  255. fast_agent/utils/time.py +22 -0
  256. fast_agent/workflow_telemetry.py +261 -0
  257. fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
  258. fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
  259. fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
  260. fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
  261. fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,68 @@
1
+ """High performance truncation for plain text streaming displays."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+
7
+
8
+ class PlainTextTruncator:
9
+ """Trim plain text content to fit within a target terminal window."""
10
+
11
+ def __init__(self, target_height_ratio: float = 0.7) -> None:
12
+ if not 0 < target_height_ratio <= 1:
13
+ raise ValueError("target_height_ratio must be between 0 and 1")
14
+ self.target_height_ratio = target_height_ratio
15
+
16
+ def truncate(self, text: str, *, terminal_height: int, terminal_width: int) -> str:
17
+ """Return the most recent portion of text that fits the terminal window.
18
+
19
+ Args:
20
+ text: Full text buffer accumulated during streaming.
21
+ terminal_height: Terminal height in rows.
22
+ terminal_width: Terminal width in columns.
23
+
24
+ Returns:
25
+ Tail portion of the text that fits within the target height ratio.
26
+ """
27
+ if not text:
28
+ return text
29
+
30
+ if terminal_height <= 0 or terminal_width <= 0:
31
+ return text
32
+
33
+ target_rows = max(1, int(terminal_height * self.target_height_ratio))
34
+ width = max(1, terminal_width)
35
+
36
+ idx = len(text)
37
+ rows_used = 0
38
+ start_idx = 0
39
+
40
+ while idx > 0 and rows_used < target_rows:
41
+ prev_newline = text.rfind("\n", 0, idx)
42
+ line_start = prev_newline + 1 if prev_newline != -1 else 0
43
+ line = text[line_start:idx]
44
+ expanded = line.expandtabs()
45
+ line_len = len(expanded)
46
+ line_rows = max(1, math.ceil(line_len / width)) if line_len else 1
47
+
48
+ if rows_used + line_rows >= target_rows:
49
+ rows_remaining = target_rows - rows_used
50
+ if rows_remaining <= 0:
51
+ start_idx = idx
52
+ break
53
+
54
+ if line_rows <= rows_remaining:
55
+ start_idx = line_start
56
+ else:
57
+ approx_chars = width * rows_remaining
58
+ keep_chars = min(len(line), approx_chars)
59
+ start_idx = idx - keep_chars
60
+ break
61
+
62
+ rows_used += line_rows
63
+ start_idx = line_start
64
+ if prev_newline == -1:
65
+ break
66
+ idx = prev_newline
67
+
68
+ return text[start_idx:]
@@ -0,0 +1,10 @@
1
+ """
2
+ Centralized progress display configuration for MCP Agent.
3
+ Provides a shared progress display instance for consistent progress handling.
4
+ """
5
+
6
+ from fast_agent.ui.console import console
7
+ from fast_agent.ui.rich_progress import RichProgressDisplay
8
+
9
+ # Main progress display instance - shared across the application
10
+ progress_display = RichProgressDisplay(console)
@@ -0,0 +1,195 @@
1
+ """Rich-based progress display for MCP Agent."""
2
+
3
+ import time
4
+ from contextlib import contextmanager
5
+ from typing import Any
6
+
7
+ from rich.console import Console
8
+ from rich.progress import Progress, SpinnerColumn, TaskID, TextColumn
9
+
10
+ from fast_agent.event_progress import ProgressAction, ProgressEvent
11
+ from fast_agent.ui.console import console as default_console
12
+
13
+
14
+ class RichProgressDisplay:
15
+ """Rich-based display for progress events."""
16
+
17
+ def __init__(self, console: Console | None = None) -> None:
18
+ """Initialize the progress display."""
19
+ self.console = console or default_console
20
+ self._taskmap: dict[str, TaskID] = {}
21
+ self._progress = Progress(
22
+ SpinnerColumn(spinner_name="simpleDotsScrolling"),
23
+ TextColumn(
24
+ "[progress.description]{task.description}▎",
25
+ # table_column=Column(max_width=16),
26
+ ),
27
+ TextColumn(text_format="{task.fields[target]:<16}", style="Bold Blue"),
28
+ TextColumn(text_format="{task.fields[details]}", style="white"),
29
+ console=self.console,
30
+ transient=False,
31
+ )
32
+ self._paused = False
33
+
34
+ def start(self) -> None:
35
+ """start"""
36
+
37
+ self._progress.start()
38
+
39
+ def stop(self) -> None:
40
+ """Stop and clear the progress display."""
41
+ # Hide all tasks before stopping (like pause does)
42
+ for task in self._progress.tasks:
43
+ task.visible = False
44
+ self._progress.stop()
45
+ self._paused = True
46
+
47
+ def pause(self) -> None:
48
+ """Pause the progress display."""
49
+ if not self._paused:
50
+ self._paused = True
51
+
52
+ for task in self._progress.tasks:
53
+ task.visible = False
54
+ self._progress.stop()
55
+
56
+ def resume(self) -> None:
57
+ """Resume the progress display."""
58
+ if self._paused:
59
+ for task in self._progress.tasks:
60
+ task.visible = True
61
+ self._paused = False
62
+ self._progress.start()
63
+
64
+ def hide_task(self, task_name: str) -> None:
65
+ """Hide an existing task from the progress display by name."""
66
+ task_id = self._taskmap.get(task_name)
67
+ if task_id is None:
68
+ return
69
+ for task in self._progress.tasks:
70
+ if task.id == task_id:
71
+ task.visible = False
72
+ break
73
+
74
+ @contextmanager
75
+ def paused(self):
76
+ """Context manager for temporarily pausing the display."""
77
+ self.pause()
78
+ try:
79
+ yield
80
+ finally:
81
+ self.resume()
82
+
83
+ def _get_action_style(self, action: ProgressAction) -> str:
84
+ """Map actions to appropriate styles."""
85
+ return {
86
+ ProgressAction.STARTING: "bold yellow",
87
+ ProgressAction.LOADED: "dim green",
88
+ ProgressAction.INITIALIZED: "dim green",
89
+ ProgressAction.CHATTING: "bold blue",
90
+ ProgressAction.STREAMING: "bold green", # Assistant Colour
91
+ ProgressAction.THINKING: "bold yellow", # Assistant Colour
92
+ ProgressAction.ROUTING: "bold blue",
93
+ ProgressAction.PLANNING: "bold blue",
94
+ ProgressAction.READY: "dim green",
95
+ ProgressAction.CALLING_TOOL: "bold magenta",
96
+ ProgressAction.TOOL_PROGRESS: "bold magenta",
97
+ ProgressAction.FINISHED: "black on green",
98
+ ProgressAction.SHUTDOWN: "black on red",
99
+ ProgressAction.AGGREGATOR_INITIALIZED: "bold green",
100
+ ProgressAction.FATAL_ERROR: "black on red",
101
+ }.get(action, "white")
102
+
103
+ def update(self, event: ProgressEvent) -> None:
104
+ """Update the progress display with a new event."""
105
+ task_name = event.agent_name or "default"
106
+
107
+ # Create new task if needed
108
+ if task_name not in self._taskmap:
109
+ task_id = self._progress.add_task(
110
+ "",
111
+ total=None,
112
+ target=event.target or task_name,
113
+ details=event.details or "",
114
+ task_name=task_name,
115
+ )
116
+ self._taskmap[task_name] = task_id
117
+ else:
118
+ task_id = self._taskmap[task_name]
119
+
120
+ # Ensure no None values in the update
121
+ # For streaming, use custom description immediately to avoid flashing
122
+ if (
123
+ event.action == ProgressAction.STREAMING or event.action == ProgressAction.THINKING
124
+ ) and event.streaming_tokens:
125
+ # Account for [dim][/dim] tags (11 characters) in padding calculation
126
+ formatted_tokens = f"▎[dim]◀[/dim] {event.streaming_tokens.strip()}".ljust(17 + 11)
127
+ description = f"[{self._get_action_style(event.action)}]{formatted_tokens}"
128
+ elif event.action == ProgressAction.CHATTING:
129
+ # Add special formatting for chatting with dimmed arrow
130
+ formatted_text = f"▎[dim]▶[/dim] {event.action.value.strip()}".ljust(17 + 11)
131
+ description = f"[{self._get_action_style(event.action)}]{formatted_text}"
132
+ elif event.action == ProgressAction.CALLING_TOOL:
133
+ # Add special formatting for calling tool with dimmed arrow
134
+ formatted_text = f"▎[dim]◀[/dim] {event.action.value}".ljust(17 + 11)
135
+ description = f"[{self._get_action_style(event.action)}]{formatted_text}"
136
+ elif event.action == ProgressAction.TOOL_PROGRESS:
137
+ # Format similar to streaming - show progress numbers
138
+ if event.progress is not None:
139
+ if event.total is not None:
140
+ progress_display = f"{int(event.progress)}/{int(event.total)}"
141
+ else:
142
+ progress_display = str(int(event.progress))
143
+ else:
144
+ progress_display = "Processing"
145
+ formatted_text = f"▎[dim]▶[/dim] {progress_display}".ljust(17 + 11)
146
+ description = f"[{self._get_action_style(event.action)}]{formatted_text}"
147
+ else:
148
+ description = f"[{self._get_action_style(event.action)}]▎ {event.action.value:<15}"
149
+
150
+ # Update basic task information
151
+ update_kwargs: dict[str, Any] = {
152
+ "description": description,
153
+ "target": event.target or task_name, # Use task_name as fallback for target
154
+ "details": event.details or "",
155
+ "task_name": task_name,
156
+ }
157
+
158
+ # For TOOL_PROGRESS events, update progress if available
159
+ if event.action == ProgressAction.TOOL_PROGRESS and event.progress is not None:
160
+ if event.total is not None:
161
+ update_kwargs["completed"] = event.progress
162
+ update_kwargs["total"] = event.total
163
+ else:
164
+ # If no total, reset to indeterminate but keep other fields
165
+ self._progress.reset(task_id)
166
+ # Still need to update after reset to apply the fields
167
+
168
+ self._progress.update(task_id, **update_kwargs)
169
+
170
+ if (
171
+ event.action == ProgressAction.INITIALIZED
172
+ or event.action == ProgressAction.READY
173
+ or event.action == ProgressAction.LOADED
174
+ ):
175
+ self._progress.update(task_id, completed=100, total=100)
176
+ elif event.action == ProgressAction.FINISHED:
177
+ self._progress.update(
178
+ task_id,
179
+ completed=100,
180
+ total=100,
181
+ target=event.target or task_name,
182
+ details=f" / Elapsed Time {time.strftime('%H:%M:%S', time.gmtime(self._progress.tasks[task_id].elapsed))}",
183
+ task_name=task_name,
184
+ )
185
+ elif event.action == ProgressAction.FATAL_ERROR:
186
+ self._progress.update(
187
+ task_id,
188
+ completed=100,
189
+ total=100,
190
+ target=event.target or task_name,
191
+ details=f" / {event.details}",
192
+ task_name=task_name,
193
+ )
194
+ else:
195
+ self._progress.reset(task_id)