open-swarm 0.1.1745275181__py3-none-any.whl → 0.1.1748636259__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 (296) hide show
  1. open_swarm-0.1.1748636259.dist-info/METADATA +188 -0
  2. open_swarm-0.1.1748636259.dist-info/RECORD +82 -0
  3. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636259.dist-info}/WHEEL +2 -1
  4. open_swarm-0.1.1748636259.dist-info/entry_points.txt +3 -0
  5. open_swarm-0.1.1748636259.dist-info/top_level.txt +1 -0
  6. swarm/agent/agent.py +49 -0
  7. swarm/auth.py +48 -113
  8. swarm/consumers.py +0 -19
  9. swarm/extensions/blueprint/__init__.py +16 -30
  10. swarm/{core → extensions/blueprint}/agent_utils.py +1 -1
  11. swarm/extensions/blueprint/blueprint_base.py +458 -0
  12. swarm/extensions/blueprint/blueprint_discovery.py +112 -0
  13. swarm/extensions/blueprint/output_utils.py +95 -0
  14. swarm/{core → extensions/blueprint}/spinner.py +21 -30
  15. swarm/extensions/cli/cli_args.py +0 -6
  16. swarm/extensions/cli/commands/blueprint_management.py +9 -47
  17. swarm/extensions/cli/commands/config_management.py +6 -5
  18. swarm/extensions/cli/commands/edit_config.py +7 -16
  19. swarm/extensions/cli/commands/list_blueprints.py +1 -1
  20. swarm/extensions/cli/commands/validate_env.py +4 -11
  21. swarm/extensions/cli/commands/validate_envvars.py +6 -6
  22. swarm/extensions/cli/interactive_shell.py +2 -16
  23. swarm/extensions/config/config_loader.py +201 -107
  24. swarm/{core → extensions/config}/config_manager.py +38 -50
  25. swarm/{core → extensions/config}/server_config.py +0 -32
  26. swarm/extensions/launchers/build_launchers.py +14 -0
  27. swarm/{core → extensions/launchers}/build_swarm_wrapper.py +0 -0
  28. swarm/extensions/launchers/swarm_api.py +64 -8
  29. swarm/extensions/launchers/swarm_cli.py +300 -8
  30. swarm/llm/chat_completion.py +195 -0
  31. swarm/serializers.py +5 -96
  32. swarm/settings.py +111 -99
  33. swarm/urls.py +74 -57
  34. swarm/utils/context_utils.py +4 -10
  35. swarm/utils/general_utils.py +0 -21
  36. swarm/utils/redact.py +36 -23
  37. swarm/views/api_views.py +39 -48
  38. swarm/views/chat_views.py +70 -237
  39. swarm/views/core_views.py +87 -80
  40. swarm/views/model_views.py +121 -64
  41. swarm/views/utils.py +441 -65
  42. swarm/views/web_views.py +2 -2
  43. open_swarm-0.1.1745275181.dist-info/METADATA +0 -874
  44. open_swarm-0.1.1745275181.dist-info/RECORD +0 -319
  45. open_swarm-0.1.1745275181.dist-info/entry_points.txt +0 -4
  46. swarm/blueprints/README.md +0 -68
  47. swarm/blueprints/blueprint_audit_status.json +0 -27
  48. swarm/blueprints/chatbot/README.md +0 -40
  49. swarm/blueprints/chatbot/blueprint_chatbot.py +0 -471
  50. swarm/blueprints/chatbot/metadata.json +0 -23
  51. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +0 -33
  52. swarm/blueprints/chucks_angels/README.md +0 -11
  53. swarm/blueprints/chucks_angels/blueprint_chucks_angels.py +0 -7
  54. swarm/blueprints/chucks_angels/test_basic.py +0 -3
  55. swarm/blueprints/codey/CODEY.md +0 -15
  56. swarm/blueprints/codey/README.md +0 -115
  57. swarm/blueprints/codey/blueprint_codey.py +0 -1072
  58. swarm/blueprints/codey/codey_cli.py +0 -373
  59. swarm/blueprints/codey/instructions.md +0 -17
  60. swarm/blueprints/codey/metadata.json +0 -23
  61. swarm/blueprints/common/operation_box_utils.py +0 -83
  62. swarm/blueprints/digitalbutlers/README.md +0 -11
  63. swarm/blueprints/digitalbutlers/__init__.py +0 -1
  64. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +0 -7
  65. swarm/blueprints/digitalbutlers/test_basic.py +0 -3
  66. swarm/blueprints/divine_code/README.md +0 -3
  67. swarm/blueprints/divine_code/__init__.py +0 -10
  68. swarm/blueprints/divine_code/apps.py +0 -11
  69. swarm/blueprints/divine_code/blueprint_divine_code.py +0 -270
  70. swarm/blueprints/django_chat/apps.py +0 -6
  71. swarm/blueprints/django_chat/blueprint_django_chat.py +0 -268
  72. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +0 -37
  73. swarm/blueprints/django_chat/urls.py +0 -8
  74. swarm/blueprints/django_chat/views.py +0 -32
  75. swarm/blueprints/echocraft/blueprint_echocraft.py +0 -384
  76. swarm/blueprints/flock/README.md +0 -11
  77. swarm/blueprints/flock/__init__.py +0 -8
  78. swarm/blueprints/flock/blueprint_flock.py +0 -7
  79. swarm/blueprints/flock/test_basic.py +0 -3
  80. swarm/blueprints/geese/README.md +0 -10
  81. swarm/blueprints/geese/__init__.py +0 -8
  82. swarm/blueprints/geese/blueprint_geese.py +0 -384
  83. swarm/blueprints/geese/geese_cli.py +0 -102
  84. swarm/blueprints/jeeves/README.md +0 -41
  85. swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
  86. swarm/blueprints/jeeves/jeeves_cli.py +0 -55
  87. swarm/blueprints/jeeves/metadata.json +0 -24
  88. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
  89. swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
  90. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
  91. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
  92. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
  93. swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
  94. swarm/blueprints/poets/blueprint_poets.py +0 -546
  95. swarm/blueprints/poets/poets_cli.py +0 -23
  96. swarm/blueprints/rue_code/README.md +0 -8
  97. swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
  98. swarm/blueprints/rue_code/rue_code_cli.py +0 -43
  99. swarm/blueprints/stewie/apps.py +0 -12
  100. swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
  101. swarm/blueprints/stewie/models.py +0 -19
  102. swarm/blueprints/stewie/serializers.py +0 -10
  103. swarm/blueprints/stewie/settings.py +0 -17
  104. swarm/blueprints/stewie/urls.py +0 -11
  105. swarm/blueprints/stewie/views.py +0 -26
  106. swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
  107. swarm/blueprints/whinge_surf/README.md +0 -22
  108. swarm/blueprints/whinge_surf/__init__.py +0 -1
  109. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
  110. swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
  111. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  112. swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
  113. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
  114. swarm/blueprints/zeus/__init__.py +0 -2
  115. swarm/blueprints/zeus/apps.py +0 -4
  116. swarm/blueprints/zeus/blueprint_zeus.py +0 -270
  117. swarm/blueprints/zeus/zeus_cli.py +0 -13
  118. swarm/cli/async_input.py +0 -65
  119. swarm/cli/async_input_demo.py +0 -32
  120. swarm/core/blueprint_base.py +0 -769
  121. swarm/core/blueprint_discovery.py +0 -125
  122. swarm/core/blueprint_runner.py +0 -59
  123. swarm/core/blueprint_ux.py +0 -109
  124. swarm/core/build_launchers.py +0 -15
  125. swarm/core/cli/__init__.py +0 -1
  126. swarm/core/cli/commands/__init__.py +0 -1
  127. swarm/core/cli/commands/blueprint_management.py +0 -7
  128. swarm/core/cli/interactive_shell.py +0 -14
  129. swarm/core/cli/main.py +0 -50
  130. swarm/core/cli/utils/__init__.py +0 -1
  131. swarm/core/cli/utils/discover_commands.py +0 -18
  132. swarm/core/config_loader.py +0 -122
  133. swarm/core/output_utils.py +0 -193
  134. swarm/core/session_logger.py +0 -42
  135. swarm/core/slash_commands.py +0 -89
  136. swarm/core/swarm_api.py +0 -68
  137. swarm/core/swarm_cli.py +0 -216
  138. swarm/core/utils/__init__.py +0 -0
  139. swarm/extensions/blueprint/cli_handler.py +0 -197
  140. swarm/extensions/blueprint/runnable_blueprint.py +0 -42
  141. swarm/extensions/cli/utils/__init__.py +0 -1
  142. swarm/extensions/cli/utils/async_input.py +0 -46
  143. swarm/extensions/cli/utils/prompt_user.py +0 -3
  144. swarm/management/__init__.py +0 -0
  145. swarm/management/commands/__init__.py +0 -0
  146. swarm/management/commands/runserver.py +0 -58
  147. swarm/middleware.py +0 -65
  148. swarm/permissions.py +0 -38
  149. swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
  150. swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
  151. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
  152. swarm/static/contrib/markedjs/marked.min.js +0 -6
  153. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
  154. swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
  155. swarm/static/contrib/tabler-icons/archive.svg +0 -21
  156. swarm/static/contrib/tabler-icons/artboard.svg +0 -27
  157. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
  158. swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
  159. swarm/static/contrib/tabler-icons/carambola.svg +0 -19
  160. swarm/static/contrib/tabler-icons/copy.svg +0 -20
  161. swarm/static/contrib/tabler-icons/download.svg +0 -21
  162. swarm/static/contrib/tabler-icons/edit.svg +0 -21
  163. swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
  164. swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
  165. swarm/static/contrib/tabler-icons/headset.svg +0 -22
  166. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
  167. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
  168. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
  169. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
  170. swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
  171. swarm/static/contrib/tabler-icons/message-star.svg +0 -22
  172. swarm/static/contrib/tabler-icons/message-x.svg +0 -23
  173. swarm/static/contrib/tabler-icons/message.svg +0 -21
  174. swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
  175. swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
  176. swarm/static/contrib/tabler-icons/robot.svg +0 -26
  177. swarm/static/contrib/tabler-icons/search.svg +0 -19
  178. swarm/static/contrib/tabler-icons/settings.svg +0 -20
  179. swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
  180. swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
  181. swarm/static/css/dropdown.css +0 -22
  182. swarm/static/htmx/htmx.min.js +0 -0
  183. swarm/static/js/dropdown.js +0 -23
  184. swarm/static/rest_mode/css/base.css +0 -470
  185. swarm/static/rest_mode/css/chat-history.css +0 -286
  186. swarm/static/rest_mode/css/chat.css +0 -251
  187. swarm/static/rest_mode/css/chatbot.css +0 -74
  188. swarm/static/rest_mode/css/chatgpt.css +0 -62
  189. swarm/static/rest_mode/css/colors/corporate.css +0 -74
  190. swarm/static/rest_mode/css/colors/pastel.css +0 -81
  191. swarm/static/rest_mode/css/colors/tropical.css +0 -82
  192. swarm/static/rest_mode/css/general.css +0 -142
  193. swarm/static/rest_mode/css/layout.css +0 -167
  194. swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
  195. swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
  196. swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
  197. swarm/static/rest_mode/css/messages.css +0 -84
  198. swarm/static/rest_mode/css/messenger.css +0 -135
  199. swarm/static/rest_mode/css/settings.css +0 -91
  200. swarm/static/rest_mode/css/simple.css +0 -44
  201. swarm/static/rest_mode/css/slack.css +0 -58
  202. swarm/static/rest_mode/css/style.css +0 -156
  203. swarm/static/rest_mode/css/theme.css +0 -30
  204. swarm/static/rest_mode/css/toast.css +0 -40
  205. swarm/static/rest_mode/js/auth.js +0 -9
  206. swarm/static/rest_mode/js/blueprint.js +0 -41
  207. swarm/static/rest_mode/js/blueprintUtils.js +0 -12
  208. swarm/static/rest_mode/js/chatLogic.js +0 -79
  209. swarm/static/rest_mode/js/debug.js +0 -63
  210. swarm/static/rest_mode/js/events.js +0 -98
  211. swarm/static/rest_mode/js/main.js +0 -19
  212. swarm/static/rest_mode/js/messages.js +0 -264
  213. swarm/static/rest_mode/js/messengerLogic.js +0 -355
  214. swarm/static/rest_mode/js/modules/apiService.js +0 -84
  215. swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
  216. swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
  217. swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
  218. swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
  219. swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
  220. swarm/static/rest_mode/js/modules/state.js +0 -7
  221. swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
  222. swarm/static/rest_mode/js/modules/validation.js +0 -23
  223. swarm/static/rest_mode/js/rendering.js +0 -119
  224. swarm/static/rest_mode/js/settings.js +0 -130
  225. swarm/static/rest_mode/js/sidebar.js +0 -94
  226. swarm/static/rest_mode/js/simpleLogic.js +0 -37
  227. swarm/static/rest_mode/js/slackLogic.js +0 -66
  228. swarm/static/rest_mode/js/splash.js +0 -76
  229. swarm/static/rest_mode/js/theme.js +0 -111
  230. swarm/static/rest_mode/js/toast.js +0 -36
  231. swarm/static/rest_mode/js/ui.js +0 -265
  232. swarm/static/rest_mode/js/validation.js +0 -57
  233. swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
  234. swarm/static/rest_mode/svg/arrow_down.svg +0 -5
  235. swarm/static/rest_mode/svg/arrow_left.svg +0 -5
  236. swarm/static/rest_mode/svg/arrow_right.svg +0 -5
  237. swarm/static/rest_mode/svg/arrow_up.svg +0 -5
  238. swarm/static/rest_mode/svg/attach.svg +0 -8
  239. swarm/static/rest_mode/svg/avatar.svg +0 -7
  240. swarm/static/rest_mode/svg/canvas.svg +0 -6
  241. swarm/static/rest_mode/svg/chat_history.svg +0 -4
  242. swarm/static/rest_mode/svg/close.svg +0 -5
  243. swarm/static/rest_mode/svg/copy.svg +0 -4
  244. swarm/static/rest_mode/svg/dark_mode.svg +0 -3
  245. swarm/static/rest_mode/svg/edit.svg +0 -5
  246. swarm/static/rest_mode/svg/layout.svg +0 -9
  247. swarm/static/rest_mode/svg/logo.svg +0 -29
  248. swarm/static/rest_mode/svg/logout.svg +0 -5
  249. swarm/static/rest_mode/svg/mobile.svg +0 -5
  250. swarm/static/rest_mode/svg/new_chat.svg +0 -4
  251. swarm/static/rest_mode/svg/not_visible.svg +0 -5
  252. swarm/static/rest_mode/svg/plus.svg +0 -7
  253. swarm/static/rest_mode/svg/run_code.svg +0 -6
  254. swarm/static/rest_mode/svg/save.svg +0 -4
  255. swarm/static/rest_mode/svg/search.svg +0 -6
  256. swarm/static/rest_mode/svg/settings.svg +0 -4
  257. swarm/static/rest_mode/svg/speaker.svg +0 -5
  258. swarm/static/rest_mode/svg/stop.svg +0 -6
  259. swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
  260. swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
  261. swarm/static/rest_mode/svg/toggle_off.svg +0 -6
  262. swarm/static/rest_mode/svg/toggle_on.svg +0 -6
  263. swarm/static/rest_mode/svg/trash.svg +0 -10
  264. swarm/static/rest_mode/svg/undo.svg +0 -3
  265. swarm/static/rest_mode/svg/visible.svg +0 -8
  266. swarm/static/rest_mode/svg/voice.svg +0 -10
  267. swarm/templates/account/login.html +0 -22
  268. swarm/templates/account/signup.html +0 -32
  269. swarm/templates/base.html +0 -30
  270. swarm/templates/chat.html +0 -43
  271. swarm/templates/index.html +0 -35
  272. swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
  273. swarm/templates/rest_mode/components/header.html +0 -45
  274. swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
  275. swarm/templates/rest_mode/components/settings_dialog.html +0 -97
  276. swarm/templates/rest_mode/components/splash_screen.html +0 -7
  277. swarm/templates/rest_mode/components/top_bar.html +0 -28
  278. swarm/templates/rest_mode/message_ui.html +0 -50
  279. swarm/templates/rest_mode/slackbot.html +0 -30
  280. swarm/templates/simple_blueprint_page.html +0 -24
  281. swarm/templates/websocket_partials/final_system_message.html +0 -3
  282. swarm/templates/websocket_partials/system_message.html +0 -4
  283. swarm/templates/websocket_partials/user_message.html +0 -5
  284. swarm/utils/ansi_box.py +0 -34
  285. swarm/utils/disable_tracing.py +0 -38
  286. swarm/utils/log_utils.py +0 -63
  287. swarm/utils/openai_patch.py +0 -33
  288. swarm/ux/ansi_box.py +0 -43
  289. swarm/ux/spinner.py +0 -53
  290. {open_swarm-0.1.1745275181.dist-info → open_swarm-0.1.1748636259.dist-info}/licenses/LICENSE +0 -0
  291. /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
  292. /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
  293. /swarm/{core → extensions/config}/setup_wizard.py +0 -0
  294. /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
  295. /swarm/{core → extensions/config}/utils/logger.py +0 -0
  296. /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
@@ -0,0 +1,458 @@
1
+ """
2
+ Swarm Blueprint Base Module (Sync Interactive Mode) - Updated to Use openai-agents
3
+
4
+ This module provides the base class for blueprints with interactive and non-interactive modes.
5
+ It has been refactored to use the openai-agents Runner for agent execution instead of the legacy Swarm core.
6
+ Additionally, it initializes the mcp_servers attribute from the configuration and manages context variables.
7
+ Since the original swarm.types module has been removed, minimal ChatMessage and Response classes are defined here.
8
+ """
9
+
10
+ import asyncio
11
+ import json
12
+ import logging
13
+ from abc import ABC, abstractmethod
14
+ from pathlib import Path
15
+ from typing import Optional, Dict, Any, List
16
+
17
+ import os
18
+ import sys
19
+ import uuid
20
+
21
+ from swarm.extensions.config.config_loader import load_server_config
22
+ from swarm.settings import DEBUG
23
+ from swarm.utils.redact import redact_sensitive_data
24
+ from swarm.utils.message_sequence import repair_message_payload, validate_message_sequence
25
+ from swarm.utils.context_utils import truncate_message_history
26
+ from swarm.extensions.blueprint.agent_utils import get_agent_name, initialize_agents
27
+ from swarm.extensions.blueprint.django_utils import register_django_components
28
+ from swarm.extensions.blueprint.spinner import Spinner
29
+ from dotenv import load_dotenv
30
+ import argparse
31
+
32
+ class DummyMCPServer:
33
+ async def list_tools(self) -> list:
34
+ return []
35
+
36
+ # Import Runner and Agent from the openai-agents SDK.
37
+ from agents import Runner # type: ignore
38
+ from agents.agent import Agent # type: ignore
39
+
40
+ # Minimal definitions to replace swarm.types
41
+ from dataclasses import dataclass
42
+
43
+ @dataclass
44
+ class ChatMessage:
45
+ role: str
46
+ content: str
47
+
48
+ @dataclass
49
+ class Response:
50
+ messages: List[ChatMessage]
51
+ agent: Optional[Any]
52
+ context_variables: Dict[str, Any]
53
+
54
+ logger = logging.getLogger(__name__)
55
+
56
+ class BlueprintBase(ABC):
57
+ """
58
+ Base class for blueprints using the openai-agents Runner for execution.
59
+ Agents are expected to be created via create_agents() and stored in self.agents.
60
+ Runner.run() is used to execute agents with a plain text input.
61
+
62
+ This version initializes mcp_servers from the configuration and restores context_variables.
63
+ """
64
+
65
+ # Set up initial context variables as a class variable.
66
+ context_variables: Dict[str, Any] = {"user_goal": ""}
67
+
68
+ def __init__(
69
+ self,
70
+ config: dict,
71
+ auto_complete_task: bool = False,
72
+ update_user_goal: bool = False,
73
+ update_user_goal_frequency: int = 5,
74
+ skip_django_registration: bool = False,
75
+ record_chat: bool = False,
76
+ log_file_path: Optional[str] = None,
77
+ debug: bool = False,
78
+ use_markdown: bool = False,
79
+ **kwargs
80
+ ):
81
+ self.auto_complete_task = auto_complete_task
82
+ self.update_user_goal = update_user_goal
83
+ self.update_user_goal_frequency = max(1, update_user_goal_frequency)
84
+ self.last_goal_update_count = 0
85
+ self.record_chat = record_chat
86
+ self.conversation_id = str(uuid.uuid4()) if record_chat else None
87
+ self.log_file_path = log_file_path
88
+ self.debug = debug or DEBUG
89
+ self.use_markdown = use_markdown
90
+ self._urls_registered = False # For Django URL registration
91
+
92
+ if self.use_markdown:
93
+ logger.debug("Markdown rendering enabled.")
94
+ logger.debug(f"Initializing {self.__class__.__name__} with config: {redact_sensitive_data(config)}")
95
+ if not hasattr(self, "metadata") or not isinstance(self.metadata, dict):
96
+ try:
97
+ _ = self.metadata
98
+ if not isinstance(self.metadata, dict):
99
+ raise TypeError("Metadata is not a dict")
100
+ except (AttributeError, NotImplementedError, TypeError) as e:
101
+ raise AssertionError(f"{self.__class__.__name__} must define a 'metadata' property returning a dictionary. Error: {e}")
102
+
103
+ self.truncation_mode = os.getenv("SWARM_TRUNCATION_MODE", "pairs").lower()
104
+ meta = self.metadata
105
+ self.max_context_tokens = max(1, meta.get("max_context_tokens", 8000))
106
+ self.max_context_messages = max(1, meta.get("max_context_messages", 50))
107
+ logger.debug(f"Truncation settings: mode={self.truncation_mode}, max_tokens={self.max_context_tokens}, max_messages={self.max_context_messages}")
108
+
109
+ load_dotenv()
110
+ logger.debug("Loaded environment variables from .env (if present).")
111
+
112
+ self.config = config
113
+ # Initialize mcp_servers from configuration.
114
+ self.mcp_servers: Dict[str, Any] = {}
115
+ if "mcp_servers" in self.config:
116
+ self.mcp_servers = load_server_config(self.config["mcp_servers"])
117
+ logger.debug(f"Loaded mcp_servers: {list(self.mcp_servers.keys())}")
118
+ else:
119
+ logger.debug("No mcp_servers configuration found.")
120
+
121
+ # Set default mcp server configurations if keys are missing.
122
+ if "mcp_llms_txt_server" not in self.mcp_servers:
123
+ logger.warning("mcp_llms_txt_server not found in mcp_servers; using default dummy configuration.")
124
+ self.mcp_servers["mcp_llms_txt_server"] = {"command": "echo", "args": [], "env": {}}
125
+ if "everything_server" not in self.mcp_servers:
126
+ logger.warning("everything_server not found in mcp_servers; using default dummy configuration.")
127
+ self.mcp_servers["everything_server"] = {"command": "echo", "args": [], "env": {}}
128
+
129
+ self.skip_django_registration = skip_django_registration or not os.environ.get("DJANGO_SETTINGS_MODULE")
130
+ # Initialize agents.
131
+ initialized_agents = initialize_agents(self) # type: ignore
132
+ self.agents: Dict[str, Agent] = initialized_agents if initialized_agents is not None else {}
133
+ # Restore context_variables on the instance.
134
+ self.context_variables = {"user_goal": ""}
135
+ self.starting_agent: Optional[Agent] = None
136
+
137
+ self._discovered_tools: Dict[str, List[Any]] = {}
138
+ self._discovered_resources: Dict[str, List[Any]] = {}
139
+ self.spinner = Spinner(interactive=not kwargs.get("non_interactive", False))
140
+
141
+ required_env_vars = set(meta.get("env_vars", []))
142
+ missing_vars = [var for var in required_env_vars if not os.getenv(var)]
143
+ if missing_vars:
144
+ logger.warning(f"Missing environment variables for {meta.get('title', self.__class__.__name__)}: {', '.join(missing_vars)}")
145
+
146
+ self.required_mcp_servers = meta.get("required_mcp_servers", [])
147
+ logger.debug(f"Required MCP servers from metadata: {self.required_mcp_servers}")
148
+
149
+ if self._is_create_agents_overridden():
150
+ initialized_agents = initialize_agents(self) # type: ignore
151
+ self.agents = initialized_agents if initialized_agents is not None else {}
152
+ register_django_components(self)
153
+
154
+ def _is_create_agents_overridden(self) -> bool:
155
+ return getattr(self.__class__, "create_agents") is not getattr(BlueprintBase, "create_agents")
156
+
157
+ def truncate_message_history(self, messages: List[Dict[str, Any]], model: str) -> List[Dict[str, Any]]:
158
+ return truncate_message_history(messages, model, self.max_context_tokens, self.max_context_messages)
159
+
160
+ @property
161
+ @abstractmethod
162
+ def metadata(self) -> Dict[str, Any]:
163
+ raise NotImplementedError("Subclasses must implement the 'metadata' property.")
164
+
165
+ def create_agents(self) -> Dict[str, Agent]:
166
+ logger.debug(f"{self.__class__.__name__} using default create_agents (returns empty dict). Override if agents are needed.")
167
+ return {}
168
+
169
+ def set_starting_agent(self, agent: Agent) -> None:
170
+ agent_name = get_agent_name(agent) # type: ignore
171
+ logger.debug(f"Setting starting agent to: {agent_name}")
172
+ self.starting_agent = agent
173
+ # Only track the active agent if handoffs are defined (indicating async runner usage).
174
+ if hasattr(agent, "handoffs"):
175
+ self.context_variables["active_agent_name"] = agent_name
176
+
177
+ async def determine_active_agent(self) -> Optional[Agent]:
178
+ active_agent_name = self.context_variables.get("active_agent_name")
179
+ if active_agent_name and active_agent_name in self.agents:
180
+ logger.debug(f"Active agent determined: {active_agent_name}")
181
+ return self.agents[active_agent_name]
182
+ elif self.starting_agent is not None:
183
+ agent_to_use = self.starting_agent
184
+ starting_agent_name = get_agent_name(agent_to_use) # type: ignore
185
+ if active_agent_name != starting_agent_name:
186
+ logger.warning(f"Active agent name '{active_agent_name}' invalid; defaulting to starting agent: {starting_agent_name}")
187
+ self.context_variables["active_agent_name"] = starting_agent_name
188
+ else:
189
+ logger.debug(f"Using starting agent: {starting_agent_name}")
190
+ return agent_to_use
191
+ elif self.agents:
192
+ first_agent_name = next(iter(self.agents))
193
+ logger.warning(f"No active agent set. Defaulting to first registered agent: {first_agent_name}")
194
+ self.context_variables["active_agent_name"] = first_agent_name
195
+ return self.agents[first_agent_name]
196
+ else:
197
+ logger.error("No agents registered in blueprint.")
198
+ return None
199
+
200
+ def run_with_context(self, messages: List[Dict[str, Any]], context_variables: dict) -> dict:
201
+ dict_messages = []
202
+ for msg in messages:
203
+ if hasattr(msg, "model_dump"):
204
+ dict_messages.append(msg.model_dump(exclude_none=True)) # type: ignore
205
+ elif isinstance(msg, dict):
206
+ dict_messages.append(msg)
207
+ else:
208
+ logger.warning(f"Skipping non-dict message: {type(msg)}")
209
+ continue
210
+ return asyncio.run(self.run_with_context_async(dict_messages, context_variables))
211
+
212
+ async def run_with_context_async(self, messages: List[Dict[str, Any]], context_variables: dict) -> dict:
213
+ self.context_variables.update(context_variables)
214
+ logger.debug(f"Context variables updated: {list(self.context_variables.keys())}")
215
+ active_agent = await self.determine_active_agent()
216
+ if not active_agent:
217
+ logger.error("No active agent available.")
218
+ error_msg = ChatMessage(role="assistant", content="Error: No active agent available.")
219
+ return {
220
+ "response": Response(messages=[error_msg], agent=None, context_variables=self.context_variables),
221
+ "context_variables": self.context_variables
222
+ }
223
+ input_text = " ".join(msg.get("content", "") for msg in messages if "content" in msg)
224
+ if not input_text:
225
+ logger.warning("No valid input found in messages.")
226
+ input_text = ""
227
+ logger.debug(f"Running Runner with agent {get_agent_name(active_agent)} and input: {input_text[:100]}...")
228
+ self.spinner.start(f"Generating response from {get_agent_name(active_agent)}")
229
+ try:
230
+ result = await Runner.run(active_agent, input_text) # type: ignore
231
+ except Exception as e:
232
+ logger.error(f"Runner.run failed: {e}", exc_info=True)
233
+ error_msg = ChatMessage(role="assistant", content=f"Error: {str(e)}")
234
+ result = Response(messages=[error_msg], agent=active_agent, context_variables=self.context_variables)
235
+ finally:
236
+ self.spinner.stop()
237
+ updated_context = self.context_variables.copy()
238
+ return {"response": result, "context_variables": updated_context}
239
+
240
+ def set_active_agent(self, agent_name: str) -> None:
241
+ if agent_name in self.agents:
242
+ self.context_variables["active_agent_name"] = agent_name
243
+ logger.debug(f"Active agent set to: {agent_name}")
244
+ else:
245
+ logger.error(f"Agent '{agent_name}' not found. Available: {list(self.agents.keys())}")
246
+
247
+ async def _is_task_done_async(self, user_goal: str, conversation_summary: str, last_assistant_message: str) -> bool:
248
+ if not user_goal:
249
+ logger.warning("Empty user_goal; cannot check task completion.")
250
+ return False
251
+ system_prompt = os.getenv("TASK_DONE_PROMPT", "You are a completion checker. Answer ONLY YES or NO.")
252
+ user_prompt = os.getenv(
253
+ "TASK_DONE_USER_PROMPT",
254
+ "User's goal: {user_goal}\nConversation summary: {conversation_summary}\nLast assistant message: {last_assistant_message}\nIs the task complete? Answer only YES or NO."
255
+ ).format(
256
+ user_goal=user_goal,
257
+ conversation_summary=conversation_summary,
258
+ last_assistant_message=last_assistant_message
259
+ )
260
+ check_prompt = [
261
+ {"role": "system", "content": system_prompt},
262
+ {"role": "user", "content": user_prompt}
263
+ ]
264
+ client = Runner.client # type: ignore
265
+ model_to_use = Runner.current_llm_config.get("model", Runner.model) # type: ignore
266
+ try:
267
+ if not client:
268
+ raise ValueError("Runner client not available.")
269
+ response = await client.chat.completions.create(
270
+ model=model_to_use,
271
+ messages=check_prompt,
272
+ max_tokens=5,
273
+ temperature=0
274
+ )
275
+ if response.choices:
276
+ result_content = response.choices[0].message.content.strip().upper()
277
+ is_done = result_content.startswith("YES")
278
+ logger.debug(f"Task completion check: {is_done} (raw: '{result_content}')")
279
+ return is_done
280
+ else:
281
+ logger.warning("No choices in LLM response for task completion check.")
282
+ return False
283
+ except Exception as e:
284
+ logger.error(f"Task completion check failed: {e}", exc_info=True)
285
+ return False
286
+
287
+ async def _update_user_goal_async(self, messages: List[Dict[str, Any]]) -> None:
288
+ if not messages:
289
+ logger.debug("No messages provided for goal update.")
290
+ return
291
+ system_prompt = os.getenv(
292
+ "UPDATE_GOAL_PROMPT",
293
+ "Summarize the user's primary objective from the conversation in one sentence."
294
+ )
295
+ conversation_text = "\n".join(
296
+ f"{m.get('sender', m.get('role', ''))}: {m.get('content', '') or '[Tool Call]'}"
297
+ for m in messages if m.get("content") or m.get("tool_calls")
298
+ )
299
+ if not conversation_text:
300
+ logger.debug("No usable conversation content for goal update.")
301
+ return
302
+ user_prompt = os.getenv(
303
+ "UPDATE_GOAL_USER_PROMPT",
304
+ "Summarize the user's main goal based on this conversation:\n{conversation}"
305
+ ).format(conversation=conversation_text[-2000:])
306
+ prompt = [
307
+ {"role": "system", "content": system_prompt},
308
+ {"role": "user", "content": user_prompt}
309
+ ]
310
+ client = Runner.client # type: ignore
311
+ model_to_use = Runner.current_llm_config.get("model", Runner.model) # type: ignore
312
+ try:
313
+ if not client:
314
+ raise ValueError("Runner client not available for goal update.")
315
+ response = await client.chat.completions.create(
316
+ model=model_to_use,
317
+ messages=prompt,
318
+ max_tokens=60,
319
+ temperature=0.3
320
+ )
321
+ if response.choices:
322
+ new_goal = response.choices[0].message.content.strip()
323
+ if new_goal and new_goal != self.context_variables.get("user_goal"):
324
+ self.context_variables["user_goal"] = new_goal
325
+ logger.info(f"Updated user goal: {new_goal}")
326
+ elif not new_goal:
327
+ logger.warning("LLM returned an empty goal for update.")
328
+ else:
329
+ logger.debug("LLM goal update produced the same goal.")
330
+ else:
331
+ logger.warning("No choices in LLM response for goal update.")
332
+ except Exception as e:
333
+ logger.error(f"Goal update failed: {e}", exc_info=True)
334
+
335
+ def task_completed(self, outcome: str) -> None:
336
+ print(f"\n\033[93m[System Task Outcome]\033[0m: {outcome}")
337
+
338
+ @property
339
+ def prompt(self) -> str:
340
+ active_agent_name = self.context_variables.get("active_agent_name")
341
+ active_agent = self.agents.get(active_agent_name) if active_agent_name else None
342
+ if active_agent:
343
+ return f"{get_agent_name(active_agent)} > "
344
+ else:
345
+ return "User: "
346
+
347
+ def interactive_mode(self, stream: bool = False) -> None:
348
+ try:
349
+ from .interactive_mode import run_interactive_mode
350
+ run_interactive_mode(self, stream)
351
+ except ImportError:
352
+ logger.critical("Failed to import interactive_mode runner.")
353
+ print("Error: Cannot start interactive mode.", file=sys.stderr)
354
+
355
+ def non_interactive_mode(self, instruction: str, stream: bool = False) -> None:
356
+ logger.debug(f"Starting non-interactive mode with instruction: {instruction}, stream={stream}")
357
+ try:
358
+ asyncio.run(self.non_interactive_mode_async(instruction, stream=stream))
359
+ except RuntimeError as e:
360
+ if "cannot be called from a running event loop" in str(e):
361
+ logger.error("Non-interactive mode cannot run within an async context.")
362
+ print("Error: Non-interactive mode cannot run within an async context.", file=sys.stderr)
363
+ else:
364
+ raise e
365
+ except Exception as e:
366
+ logger.error(f"Error during non-interactive mode: {e}", exc_info=True)
367
+ print(f"Error: {e}", file=sys.stderr)
368
+
369
+ async def non_interactive_mode_async(self, instruction: str, stream: bool = False) -> None:
370
+ logger.debug(f"Starting async non-interactive mode with instruction: {instruction}, stream={stream}")
371
+ if not self.agents:
372
+ logger.error("No agents available in blueprint instance.")
373
+ print("Error: No agents available.", file=sys.stderr)
374
+ return
375
+
376
+ print(f"--- {self.metadata.get('title', 'Blueprint')} Non-Interactive Mode ---")
377
+ instructions = [line.strip() for line in instruction.splitlines() if line.strip()]
378
+ if not instructions:
379
+ print("No valid instruction provided.")
380
+ return
381
+
382
+ messages: List[Dict[str, Any]] = [{"role": "user", "content": line} for line in instructions]
383
+ if not self.starting_agent:
384
+ if self.agents:
385
+ first_agent_name = next(iter(self.agents.keys()))
386
+ logger.warning(f"No starting agent set. Defaulting to first agent: {first_agent_name}")
387
+ self.set_starting_agent(self.agents[first_agent_name])
388
+ else:
389
+ logger.error("No starting agent set and no agents defined.")
390
+ print("Error: No agent available.", file=sys.stderr)
391
+ return
392
+
393
+ self.context_variables["user_goal"] = instruction
394
+ if "active_agent_name" not in self.context_variables:
395
+ self.context_variables["active_agent_name"] = get_agent_name(self.starting_agent) # type: ignore
396
+
397
+ if stream:
398
+ logger.debug("Running non-interactive in streaming mode.")
399
+ active_agent = await self.determine_active_agent()
400
+ if not active_agent:
401
+ return
402
+ response_generator = Runner.run(active_agent, " ".join(m.get("content", "") for m in messages)) # type: ignore
403
+ await self._process_and_print_streaming_response_async(response_generator)
404
+ if self.auto_complete_task:
405
+ logger.warning("Auto-completion with streaming is not fully supported.")
406
+ else:
407
+ logger.debug("Running non-interactive in non-streaming mode.")
408
+ input_text = " ".join(m.get("content", "") for m in messages)
409
+ result = await Runner.run(self.starting_agent, input_text) # type: ignore
410
+ if hasattr(result, "final_output"):
411
+ print(f"\nFinal response:\n{result.final_output}")
412
+ else:
413
+ print("Received unexpected response format.")
414
+ print("--- Execution Completed ---")
415
+
416
+ async def _process_and_print_streaming_response_async(self, response_generator) -> Optional[Dict[str, Any]]:
417
+ full_content = ""
418
+ async for chunk in response_generator:
419
+ if "error" in chunk:
420
+ logger.error(f"Streaming error: {chunk['error']}")
421
+ print(f"Error: {chunk['error']}", file=sys.stderr)
422
+ break
423
+ if "choices" in chunk:
424
+ for choice in chunk["choices"]:
425
+ delta = choice.get("delta", {}).get("content", "")
426
+ full_content += delta
427
+ print(delta, end="", flush=True)
428
+ print()
429
+ return None
430
+
431
+ async def _auto_complete_task_async(self, current_history: List[Dict[str, Any]], stream: bool) -> None:
432
+ logger.debug("Auto-complete task not implemented.")
433
+ pass
434
+
435
+ @classmethod
436
+ def main(cls):
437
+ parser = argparse.ArgumentParser(description=f"Run the {cls.__name__} blueprint.")
438
+ parser.add_argument("--config", default="./swarm_config.json", help="Path to the configuration file.")
439
+ parser.add_argument("--instruction", help="Instruction for non-interactive mode.")
440
+ parser.add_argument("--stream", action="store_true", help="Enable streaming mode.")
441
+ args = parser.parse_args()
442
+ if not os.path.exists(args.config):
443
+ logger.error(f"Configuration file {args.config} not found.")
444
+ sys.exit(1)
445
+ try:
446
+ with open(args.config, "r") as f:
447
+ config = json.load(f)
448
+ except Exception as e:
449
+ logger.error(f"Failed to load configuration: {e}")
450
+ sys.exit(1)
451
+ blueprint_instance = cls(config=config)
452
+ if args.instruction:
453
+ blueprint_instance.non_interactive_mode(instruction=args.instruction, stream=args.stream)
454
+ else:
455
+ blueprint_instance.interactive_mode(stream=args.stream)
456
+
457
+ if __name__ == "__main__":
458
+ BlueprintBase.main()
@@ -0,0 +1,112 @@
1
+ """
2
+ Blueprint Discovery Module for Open Swarm MCP.
3
+
4
+ This module dynamically discovers and imports blueprints from specified directories.
5
+ It identifies classes derived from BlueprintBase as valid blueprints and extracts their metadata.
6
+ """
7
+
8
+ import importlib.util
9
+ import inspect
10
+ import logging
11
+ import os
12
+ from pathlib import Path
13
+ from typing import Dict, List, Any
14
+ from swarm.settings import DEBUG
15
+
16
+ logger = logging.getLogger(__name__)
17
+ logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
18
+
19
+ try:
20
+ from .blueprint_base import BlueprintBase
21
+ except ImportError as e:
22
+ logger.critical(f"Failed to import BlueprintBase: {e}")
23
+ raise
24
+
25
+ def discover_blueprints(directories: List[str]) -> Dict[str, Dict[str, Any]]:
26
+ """
27
+ Discover and load blueprints from specified directories.
28
+ Extract metadata including title, description, and other attributes.
29
+
30
+ Args:
31
+ directories (List[str]): List of directories to search for blueprints.
32
+
33
+ Returns:
34
+ Dict[str, Dict[str, Any]]: Dictionary containing blueprint metadata.
35
+ """
36
+ blueprints = {}
37
+ logger.info("Starting blueprint discovery.")
38
+ swarm_blueprints = os.getenv("SWARM_BLUEPRINTS", "").split(",")
39
+ if swarm_blueprints and swarm_blueprints[0]:
40
+ logger.debug(f"Filtering blueprints to: {swarm_blueprints}")
41
+
42
+ for directory in directories:
43
+ logger.debug(f"Searching for blueprints in: {directory}")
44
+ dir_path = Path(directory)
45
+
46
+ if not dir_path.exists() or not dir_path.is_dir():
47
+ logger.warning(f"Invalid directory: {directory}. Skipping...")
48
+ continue
49
+
50
+ for blueprint_file in dir_path.rglob("blueprint_*.py"):
51
+ module_name = blueprint_file.stem
52
+ blueprint_name = module_name.replace("blueprint_", "")
53
+ if swarm_blueprints and swarm_blueprints[0] and blueprint_name not in swarm_blueprints:
54
+ logger.debug(f"Skipping blueprint '{blueprint_name}' not in SWARM_BLUEPRINTS")
55
+ continue
56
+ module_path = str(blueprint_file.parent)
57
+
58
+ logger.debug(f"Found blueprint file: {blueprint_file}")
59
+ logger.debug(f"Module name: {module_name}, Blueprint name: {blueprint_name}, Module path: {module_path}")
60
+
61
+ try:
62
+ spec = importlib.util.spec_from_file_location(module_name, str(blueprint_file))
63
+ if spec is None or spec.loader is None:
64
+ logger.error(f"Cannot load module spec for blueprint file: {blueprint_file}. Skipping.")
65
+ continue
66
+ module = importlib.util.module_from_spec(spec)
67
+ spec.loader.exec_module(module)
68
+ logger.debug(f"Successfully imported module: {module_name}")
69
+
70
+ for name, obj in inspect.getmembers(module, inspect.isclass):
71
+ if not issubclass(obj, BlueprintBase) or obj is BlueprintBase:
72
+ continue
73
+
74
+ logger.debug(f"Discovered blueprint class: {name}")
75
+
76
+ try:
77
+ metadata = obj.metadata
78
+ if callable(metadata):
79
+ metadata = metadata()
80
+ elif isinstance(metadata, property):
81
+ if metadata.fget is not None:
82
+ metadata = metadata.fget(obj)
83
+ else:
84
+ logger.error(f"Blueprint '{blueprint_name}' property 'metadata' has no getter.")
85
+ raise ValueError(f"Blueprint '{blueprint_name}' metadata is inaccessible.")
86
+
87
+ if not isinstance(metadata, dict):
88
+ logger.error(f"Metadata for blueprint '{blueprint_name}' is not a dictionary.")
89
+ raise ValueError(f"Metadata for blueprint '{blueprint_name}' is invalid or inaccessible.")
90
+
91
+ if "title" not in metadata or "description" not in metadata:
92
+ logger.error(f"Required metadata fields (title, description) are missing for blueprint '{blueprint_name}'.")
93
+ raise ValueError(f"Metadata for blueprint '{blueprint_name}' is invalid or inaccessible.")
94
+
95
+ except Exception as e:
96
+ logger.error(f"Error retrieving metadata for blueprint '{blueprint_name}': {e}")
97
+ continue
98
+
99
+ blueprints[blueprint_name] = {
100
+ "blueprint_class": obj,
101
+ "title": metadata["title"],
102
+ "description": metadata["description"],
103
+ }
104
+ logger.debug(f"Added blueprint '{blueprint_name}' with metadata: {metadata}")
105
+ except ImportError as e:
106
+ logger.error(f"Failed to import module '{module_name}': {e}")
107
+ except Exception as e:
108
+ logger.error(f"Unexpected error importing '{module_name}': {e}", exc_info=True)
109
+
110
+ logger.info("Blueprint discovery complete.")
111
+ logger.debug(f"Discovered blueprints: {list(blueprints.keys())}")
112
+ return blueprints
@@ -0,0 +1,95 @@
1
+ """
2
+ Output utilities for Swarm blueprints.
3
+ """
4
+
5
+ import json
6
+ import logging
7
+ import sys
8
+ from typing import List, Dict, Any
9
+
10
+ # Optional import for markdown rendering
11
+ try:
12
+ from rich.markdown import Markdown
13
+ from rich.console import Console
14
+ RICH_AVAILABLE = True
15
+ except ImportError:
16
+ RICH_AVAILABLE = False
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ def render_markdown(content: str) -> None:
21
+ """Render markdown content using rich, if available."""
22
+ # --- DEBUG PRINT ---
23
+ print(f"\n[DEBUG render_markdown called with rich={RICH_AVAILABLE}]", flush=True)
24
+ if not RICH_AVAILABLE:
25
+ print(content, flush=True) # Fallback print with flush
26
+ return
27
+ console = Console()
28
+ md = Markdown(content)
29
+ console.print(md) # Rich handles flushing
30
+
31
+ def pretty_print_response(messages: List[Dict[str, Any]], use_markdown: bool = False, spinner=None) -> None:
32
+ """Format and print messages, optionally rendering assistant content as markdown."""
33
+ # --- DEBUG PRINT ---
34
+ print(f"\n[DEBUG pretty_print_response called with {len(messages)} messages, use_markdown={use_markdown}]", flush=True)
35
+
36
+ if spinner:
37
+ spinner.stop()
38
+ sys.stdout.write("\r\033[K") # Clear spinner line
39
+ sys.stdout.flush()
40
+
41
+ if not messages:
42
+ logger.debug("No messages to print in pretty_print_response.")
43
+ return
44
+
45
+ for i, msg in enumerate(messages):
46
+ # --- DEBUG PRINT ---
47
+ print(f"\n[DEBUG Processing message {i}: type={type(msg)}]", flush=True)
48
+ if not isinstance(msg, dict):
49
+ print(f"[DEBUG Skipping non-dict message {i}]", flush=True)
50
+ continue
51
+
52
+ role = msg.get("role")
53
+ sender = msg.get("sender", role if role else "Unknown")
54
+ msg_content = msg.get("content")
55
+ tool_calls = msg.get("tool_calls")
56
+ # --- DEBUG PRINT ---
57
+ print(f"[DEBUG Message {i}: role={role}, sender={sender}, has_content={bool(msg_content)}, has_tools={bool(tool_calls)}]", flush=True)
58
+
59
+
60
+ if role == "assistant":
61
+ print(f"\033[94m{sender}\033[0m: ", end="", flush=True)
62
+ if msg_content:
63
+ # --- DEBUG PRINT ---
64
+ print(f"\n[DEBUG Assistant content found, printing/rendering... Rich={RICH_AVAILABLE}, Markdown={use_markdown}]", flush=True)
65
+ if use_markdown and RICH_AVAILABLE:
66
+ render_markdown(msg_content)
67
+ else:
68
+ # --- DEBUG PRINT ---
69
+ print(f"\n[DEBUG Using standard print for content:]", flush=True)
70
+ print(msg_content, flush=True) # Added flush
71
+ elif not tool_calls:
72
+ print(flush=True) # Flush newline if no content/tools
73
+
74
+ if tool_calls and isinstance(tool_calls, list):
75
+ print(" \033[92mTool Calls:\033[0m", flush=True)
76
+ for tc in tool_calls:
77
+ if not isinstance(tc, dict): continue
78
+ func = tc.get("function", {})
79
+ tool_name = func.get("name", "Unnamed Tool")
80
+ args_str = func.get("arguments", "{}")
81
+ try: args_obj = json.loads(args_str); args_pretty = ", ".join(f"{k}={v!r}" for k, v in args_obj.items())
82
+ except json.JSONDecodeError: args_pretty = args_str
83
+ print(f" \033[95m{tool_name}\033[0m({args_pretty})", flush=True)
84
+
85
+ elif role == "tool":
86
+ tool_name = msg.get("tool_name", msg.get("name", "tool"))
87
+ tool_id = msg.get("tool_call_id", "N/A")
88
+ try: content_obj = json.loads(msg_content); pretty_content = json.dumps(content_obj, indent=2)
89
+ except (json.JSONDecodeError, TypeError): pretty_content = msg_content
90
+ print(f" \033[93m[{tool_name} Result ID: {tool_id}]\033[0m:\n {pretty_content.replace(chr(10), chr(10) + ' ')}", flush=True)
91
+ else:
92
+ # --- DEBUG PRINT ---
93
+ print(f"[DEBUG Skipping message {i} with role '{role}']", flush=True)
94
+
95
+