open-swarm 0.1.1745274976__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 (295) 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.1745274976.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.1745274976.dist-info/METADATA +0 -874
  44. open_swarm-0.1.1745274976.dist-info/RECORD +0 -318
  45. open_swarm-0.1.1745274976.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 -97
  81. swarm/blueprints/geese/blueprint_geese.py +0 -803
  82. swarm/blueprints/geese/geese_cli.py +0 -102
  83. swarm/blueprints/jeeves/README.md +0 -41
  84. swarm/blueprints/jeeves/blueprint_jeeves.py +0 -722
  85. swarm/blueprints/jeeves/jeeves_cli.py +0 -55
  86. swarm/blueprints/jeeves/metadata.json +0 -24
  87. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +0 -473
  88. swarm/blueprints/messenger/templates/messenger/messenger.html +0 -46
  89. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +0 -423
  90. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +0 -340
  91. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +0 -265
  92. swarm/blueprints/omniplex/blueprint_omniplex.py +0 -298
  93. swarm/blueprints/poets/blueprint_poets.py +0 -546
  94. swarm/blueprints/poets/poets_cli.py +0 -23
  95. swarm/blueprints/rue_code/README.md +0 -8
  96. swarm/blueprints/rue_code/blueprint_rue_code.py +0 -448
  97. swarm/blueprints/rue_code/rue_code_cli.py +0 -43
  98. swarm/blueprints/stewie/apps.py +0 -12
  99. swarm/blueprints/stewie/blueprint_family_ties.py +0 -349
  100. swarm/blueprints/stewie/models.py +0 -19
  101. swarm/blueprints/stewie/serializers.py +0 -10
  102. swarm/blueprints/stewie/settings.py +0 -17
  103. swarm/blueprints/stewie/urls.py +0 -11
  104. swarm/blueprints/stewie/views.py +0 -26
  105. swarm/blueprints/suggestion/blueprint_suggestion.py +0 -222
  106. swarm/blueprints/whinge_surf/README.md +0 -22
  107. swarm/blueprints/whinge_surf/__init__.py +0 -1
  108. swarm/blueprints/whinge_surf/blueprint_whinge_surf.py +0 -565
  109. swarm/blueprints/whinge_surf/whinge_surf_cli.py +0 -99
  110. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  111. swarm/blueprints/whiskeytango_foxtrot/apps.py +0 -11
  112. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +0 -339
  113. swarm/blueprints/zeus/__init__.py +0 -2
  114. swarm/blueprints/zeus/apps.py +0 -4
  115. swarm/blueprints/zeus/blueprint_zeus.py +0 -270
  116. swarm/blueprints/zeus/zeus_cli.py +0 -13
  117. swarm/cli/async_input.py +0 -65
  118. swarm/cli/async_input_demo.py +0 -32
  119. swarm/core/blueprint_base.py +0 -769
  120. swarm/core/blueprint_discovery.py +0 -125
  121. swarm/core/blueprint_runner.py +0 -59
  122. swarm/core/blueprint_ux.py +0 -109
  123. swarm/core/build_launchers.py +0 -15
  124. swarm/core/cli/__init__.py +0 -1
  125. swarm/core/cli/commands/__init__.py +0 -1
  126. swarm/core/cli/commands/blueprint_management.py +0 -7
  127. swarm/core/cli/interactive_shell.py +0 -14
  128. swarm/core/cli/main.py +0 -50
  129. swarm/core/cli/utils/__init__.py +0 -1
  130. swarm/core/cli/utils/discover_commands.py +0 -18
  131. swarm/core/config_loader.py +0 -122
  132. swarm/core/output_utils.py +0 -193
  133. swarm/core/session_logger.py +0 -42
  134. swarm/core/slash_commands.py +0 -89
  135. swarm/core/swarm_api.py +0 -68
  136. swarm/core/swarm_cli.py +0 -216
  137. swarm/core/utils/__init__.py +0 -0
  138. swarm/extensions/blueprint/cli_handler.py +0 -197
  139. swarm/extensions/blueprint/runnable_blueprint.py +0 -42
  140. swarm/extensions/cli/utils/__init__.py +0 -1
  141. swarm/extensions/cli/utils/async_input.py +0 -46
  142. swarm/extensions/cli/utils/prompt_user.py +0 -3
  143. swarm/management/__init__.py +0 -0
  144. swarm/management/commands/__init__.py +0 -0
  145. swarm/management/commands/runserver.py +0 -58
  146. swarm/middleware.py +0 -65
  147. swarm/permissions.py +0 -38
  148. swarm/static/contrib/fonts/fontawesome-webfont.ttf +0 -7
  149. swarm/static/contrib/fonts/fontawesome-webfont.woff +0 -7
  150. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +0 -7
  151. swarm/static/contrib/markedjs/marked.min.js +0 -6
  152. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +0 -27
  153. swarm/static/contrib/tabler-icons/alert-triangle.svg +0 -21
  154. swarm/static/contrib/tabler-icons/archive.svg +0 -21
  155. swarm/static/contrib/tabler-icons/artboard.svg +0 -27
  156. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +0 -23
  157. swarm/static/contrib/tabler-icons/box-multiple.svg +0 -19
  158. swarm/static/contrib/tabler-icons/carambola.svg +0 -19
  159. swarm/static/contrib/tabler-icons/copy.svg +0 -20
  160. swarm/static/contrib/tabler-icons/download.svg +0 -21
  161. swarm/static/contrib/tabler-icons/edit.svg +0 -21
  162. swarm/static/contrib/tabler-icons/filled/carambola.svg +0 -13
  163. swarm/static/contrib/tabler-icons/filled/paint.svg +0 -13
  164. swarm/static/contrib/tabler-icons/headset.svg +0 -22
  165. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +0 -21
  166. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +0 -21
  167. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +0 -21
  168. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +0 -21
  169. swarm/static/contrib/tabler-icons/message-chatbot.svg +0 -22
  170. swarm/static/contrib/tabler-icons/message-star.svg +0 -22
  171. swarm/static/contrib/tabler-icons/message-x.svg +0 -23
  172. swarm/static/contrib/tabler-icons/message.svg +0 -21
  173. swarm/static/contrib/tabler-icons/paperclip.svg +0 -18
  174. swarm/static/contrib/tabler-icons/playlist-add.svg +0 -22
  175. swarm/static/contrib/tabler-icons/robot.svg +0 -26
  176. swarm/static/contrib/tabler-icons/search.svg +0 -19
  177. swarm/static/contrib/tabler-icons/settings.svg +0 -20
  178. swarm/static/contrib/tabler-icons/thumb-down.svg +0 -19
  179. swarm/static/contrib/tabler-icons/thumb-up.svg +0 -19
  180. swarm/static/css/dropdown.css +0 -22
  181. swarm/static/htmx/htmx.min.js +0 -0
  182. swarm/static/js/dropdown.js +0 -23
  183. swarm/static/rest_mode/css/base.css +0 -470
  184. swarm/static/rest_mode/css/chat-history.css +0 -286
  185. swarm/static/rest_mode/css/chat.css +0 -251
  186. swarm/static/rest_mode/css/chatbot.css +0 -74
  187. swarm/static/rest_mode/css/chatgpt.css +0 -62
  188. swarm/static/rest_mode/css/colors/corporate.css +0 -74
  189. swarm/static/rest_mode/css/colors/pastel.css +0 -81
  190. swarm/static/rest_mode/css/colors/tropical.css +0 -82
  191. swarm/static/rest_mode/css/general.css +0 -142
  192. swarm/static/rest_mode/css/layout.css +0 -167
  193. swarm/static/rest_mode/css/layouts/messenger-layout.css +0 -17
  194. swarm/static/rest_mode/css/layouts/minimalist-layout.css +0 -57
  195. swarm/static/rest_mode/css/layouts/mobile-layout.css +0 -8
  196. swarm/static/rest_mode/css/messages.css +0 -84
  197. swarm/static/rest_mode/css/messenger.css +0 -135
  198. swarm/static/rest_mode/css/settings.css +0 -91
  199. swarm/static/rest_mode/css/simple.css +0 -44
  200. swarm/static/rest_mode/css/slack.css +0 -58
  201. swarm/static/rest_mode/css/style.css +0 -156
  202. swarm/static/rest_mode/css/theme.css +0 -30
  203. swarm/static/rest_mode/css/toast.css +0 -40
  204. swarm/static/rest_mode/js/auth.js +0 -9
  205. swarm/static/rest_mode/js/blueprint.js +0 -41
  206. swarm/static/rest_mode/js/blueprintUtils.js +0 -12
  207. swarm/static/rest_mode/js/chatLogic.js +0 -79
  208. swarm/static/rest_mode/js/debug.js +0 -63
  209. swarm/static/rest_mode/js/events.js +0 -98
  210. swarm/static/rest_mode/js/main.js +0 -19
  211. swarm/static/rest_mode/js/messages.js +0 -264
  212. swarm/static/rest_mode/js/messengerLogic.js +0 -355
  213. swarm/static/rest_mode/js/modules/apiService.js +0 -84
  214. swarm/static/rest_mode/js/modules/blueprintManager.js +0 -162
  215. swarm/static/rest_mode/js/modules/chatHistory.js +0 -110
  216. swarm/static/rest_mode/js/modules/debugLogger.js +0 -14
  217. swarm/static/rest_mode/js/modules/eventHandlers.js +0 -107
  218. swarm/static/rest_mode/js/modules/messageProcessor.js +0 -120
  219. swarm/static/rest_mode/js/modules/state.js +0 -7
  220. swarm/static/rest_mode/js/modules/userInteractions.js +0 -29
  221. swarm/static/rest_mode/js/modules/validation.js +0 -23
  222. swarm/static/rest_mode/js/rendering.js +0 -119
  223. swarm/static/rest_mode/js/settings.js +0 -130
  224. swarm/static/rest_mode/js/sidebar.js +0 -94
  225. swarm/static/rest_mode/js/simpleLogic.js +0 -37
  226. swarm/static/rest_mode/js/slackLogic.js +0 -66
  227. swarm/static/rest_mode/js/splash.js +0 -76
  228. swarm/static/rest_mode/js/theme.js +0 -111
  229. swarm/static/rest_mode/js/toast.js +0 -36
  230. swarm/static/rest_mode/js/ui.js +0 -265
  231. swarm/static/rest_mode/js/validation.js +0 -57
  232. swarm/static/rest_mode/svg/animated_spinner.svg +0 -12
  233. swarm/static/rest_mode/svg/arrow_down.svg +0 -5
  234. swarm/static/rest_mode/svg/arrow_left.svg +0 -5
  235. swarm/static/rest_mode/svg/arrow_right.svg +0 -5
  236. swarm/static/rest_mode/svg/arrow_up.svg +0 -5
  237. swarm/static/rest_mode/svg/attach.svg +0 -8
  238. swarm/static/rest_mode/svg/avatar.svg +0 -7
  239. swarm/static/rest_mode/svg/canvas.svg +0 -6
  240. swarm/static/rest_mode/svg/chat_history.svg +0 -4
  241. swarm/static/rest_mode/svg/close.svg +0 -5
  242. swarm/static/rest_mode/svg/copy.svg +0 -4
  243. swarm/static/rest_mode/svg/dark_mode.svg +0 -3
  244. swarm/static/rest_mode/svg/edit.svg +0 -5
  245. swarm/static/rest_mode/svg/layout.svg +0 -9
  246. swarm/static/rest_mode/svg/logo.svg +0 -29
  247. swarm/static/rest_mode/svg/logout.svg +0 -5
  248. swarm/static/rest_mode/svg/mobile.svg +0 -5
  249. swarm/static/rest_mode/svg/new_chat.svg +0 -4
  250. swarm/static/rest_mode/svg/not_visible.svg +0 -5
  251. swarm/static/rest_mode/svg/plus.svg +0 -7
  252. swarm/static/rest_mode/svg/run_code.svg +0 -6
  253. swarm/static/rest_mode/svg/save.svg +0 -4
  254. swarm/static/rest_mode/svg/search.svg +0 -6
  255. swarm/static/rest_mode/svg/settings.svg +0 -4
  256. swarm/static/rest_mode/svg/speaker.svg +0 -5
  257. swarm/static/rest_mode/svg/stop.svg +0 -6
  258. swarm/static/rest_mode/svg/thumbs_down.svg +0 -3
  259. swarm/static/rest_mode/svg/thumbs_up.svg +0 -3
  260. swarm/static/rest_mode/svg/toggle_off.svg +0 -6
  261. swarm/static/rest_mode/svg/toggle_on.svg +0 -6
  262. swarm/static/rest_mode/svg/trash.svg +0 -10
  263. swarm/static/rest_mode/svg/undo.svg +0 -3
  264. swarm/static/rest_mode/svg/visible.svg +0 -8
  265. swarm/static/rest_mode/svg/voice.svg +0 -10
  266. swarm/templates/account/login.html +0 -22
  267. swarm/templates/account/signup.html +0 -32
  268. swarm/templates/base.html +0 -30
  269. swarm/templates/chat.html +0 -43
  270. swarm/templates/index.html +0 -35
  271. swarm/templates/rest_mode/components/chat_sidebar.html +0 -55
  272. swarm/templates/rest_mode/components/header.html +0 -45
  273. swarm/templates/rest_mode/components/main_chat_pane.html +0 -41
  274. swarm/templates/rest_mode/components/settings_dialog.html +0 -97
  275. swarm/templates/rest_mode/components/splash_screen.html +0 -7
  276. swarm/templates/rest_mode/components/top_bar.html +0 -28
  277. swarm/templates/rest_mode/message_ui.html +0 -50
  278. swarm/templates/rest_mode/slackbot.html +0 -30
  279. swarm/templates/simple_blueprint_page.html +0 -24
  280. swarm/templates/websocket_partials/final_system_message.html +0 -3
  281. swarm/templates/websocket_partials/system_message.html +0 -4
  282. swarm/templates/websocket_partials/user_message.html +0 -5
  283. swarm/utils/ansi_box.py +0 -34
  284. swarm/utils/disable_tracing.py +0 -38
  285. swarm/utils/log_utils.py +0 -63
  286. swarm/utils/openai_patch.py +0 -33
  287. swarm/ux/ansi_box.py +0 -43
  288. swarm/ux/spinner.py +0 -53
  289. {open_swarm-0.1.1745274976.dist-info → open_swarm-0.1.1748636259.dist-info}/licenses/LICENSE +0 -0
  290. /swarm/{core → extensions/blueprint}/blueprint_utils.py +0 -0
  291. /swarm/{core → extensions/blueprint}/common_utils.py +0 -0
  292. /swarm/{core → extensions/config}/setup_wizard.py +0 -0
  293. /swarm/{blueprints/rue_code → extensions/config/utils}/__init__.py +0 -0
  294. /swarm/{core → extensions/config}/utils/logger.py +0 -0
  295. /swarm/{core → extensions/launchers}/swarm_wrapper.py +0 -0
@@ -1,803 +0,0 @@
1
- # SECURITY WARNING: All future log/print statements that output environment variables or config values MUST use redact_sensitive_data or similar redaction utility. Never print or log secrets directly.
2
-
3
- import os
4
- import time
5
- from dotenv import load_dotenv; load_dotenv(override=True)
6
-
7
- import logging
8
- logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(name)s: %(message)s')
9
- import sys
10
- from swarm.utils.redact import redact_sensitive_data
11
- import asyncio
12
-
13
- def force_info_logging():
14
- root = logging.getLogger()
15
- for handler in root.handlers[:]:
16
- root.removeHandler(handler)
17
- loglevel = os.environ.get('LOGLEVEL', None)
18
- debug_env = os.environ.get('SWARM_DEBUG', '0') == '1'
19
- debug_arg = '--debug' in sys.argv
20
- if debug_arg or debug_env or (loglevel and loglevel.upper() == 'DEBUG'):
21
- level = logging.DEBUG
22
- else:
23
- level = logging.INFO
24
- logging.basicConfig(level=level, format='[%(levelname)s] %(name)s: %(message)s')
25
- root.setLevel(level)
26
-
27
- force_info_logging()
28
-
29
- import argparse
30
- from typing import List, Dict, Any, Optional, ClassVar
31
-
32
- project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
33
- src_path = os.path.join(project_root, 'src')
34
- if src_path not in sys.path: sys.path.insert(0, src_path)
35
-
36
- from typing import Optional
37
- from pathlib import Path
38
- try:
39
- from agents import Agent, Tool, function_tool, Runner
40
- from agents.mcp import MCPServer
41
- from agents.models.interface import Model
42
- from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
43
- from openai import AsyncOpenAI
44
- from swarm.core.blueprint_base import BlueprintBase
45
- from swarm.core.blueprint_runner import BlueprintRunner
46
- except ImportError as e:
47
- print(f"ERROR: Import failed in blueprint_geese: {e}. Check 'openai-agents' install and project structure.")
48
- print(f"sys.path: {sys.path}")
49
- sys.exit(1)
50
-
51
- import argparse
52
-
53
- def setup_logging():
54
- parser = argparse.ArgumentParser(add_help=False)
55
- parser.add_argument('--debug', action='store_true', help='Enable debug logging')
56
- args, _ = parser.parse_known_args()
57
- loglevel = os.environ.get('LOGLEVEL', None)
58
- if args.debug or os.environ.get('SWARM_DEBUG', '0') == '1' or (loglevel and loglevel.upper() == 'DEBUG'):
59
- logging.basicConfig(level=logging.DEBUG)
60
- else:
61
- logging.basicConfig(level=logging.INFO)
62
- return args
63
-
64
- args = setup_logging()
65
-
66
- logger = logging.getLogger(__name__)
67
-
68
- # --- Tools ---
69
- def _create_story_outline(topic: str) -> str:
70
- logger.info(f"Tool: Generating outline for: {topic}")
71
- outline = f"Story Outline for '{topic}':\n1. Beginning: Introduce characters and setting.\n2. Middle: Develop conflict and rising action.\n3. Climax: The peak of the conflict.\n4. End: Resolution and aftermath."
72
- logger.debug(f"Generated outline: {outline}")
73
- return outline
74
-
75
- @function_tool
76
- def create_story_outline(topic: str) -> str:
77
- """Generates a basic story outline based on a topic."""
78
- return _create_story_outline(topic)
79
-
80
- def _write_story_part(part_name: str, outline: str, previous_parts: str) -> str:
81
- logger.info(f"Tool: Writing story part: {part_name}")
82
- content = f"## {part_name}\n\nThis is the draft content for the '{part_name}' section. It follows:\n'{previous_parts[:100]}...' \nIt should align with the outline:\n'{outline}'"
83
- logger.debug(f"Generated content for {part_name}: {content[:100]}...")
84
- return content
85
-
86
- @function_tool
87
- def write_story_part(part_name: str, outline: str, previous_parts: str) -> str:
88
- """Writes a specific part of the story using the outline and previous context."""
89
- return _write_story_part(part_name, outline, previous_parts)
90
-
91
- def _edit_story(full_story: str, edit_instructions: str) -> str:
92
- logger.info(f"Tool: Editing story with instructions: {edit_instructions}")
93
- edited_content = f"*** Edited Story Draft ***\n(Based on instructions: '{edit_instructions}')\n\n{full_story}\n\n[Editor's Notes: Minor tweaks applied for flow.]"
94
- logger.debug("Editing complete.")
95
- return edited_content
96
-
97
- @function_tool
98
- def edit_story(full_story: str, edit_instructions: str) -> str:
99
- """Edits the complete story based on instructions."""
100
- return _edit_story(full_story, edit_instructions)
101
-
102
- @function_tool
103
- def read_file(path: str, encoding: Optional[str] = "utf-8") -> str:
104
- """Read and return the contents of a file."""
105
- try:
106
- with open(path, "r", encoding=encoding) as f:
107
- content = f.read()
108
- logger.info(f"Tool: Read file '{path}' ({len(content)} bytes)")
109
- return content
110
- except Exception as e:
111
- logger.error(f"Tool: Failed to read file '{path}': {e}")
112
- return f"[ERROR] Could not read file '{path}': {e}"
113
-
114
- @function_tool
115
- def write_file(path: str, content: str, encoding: Optional[str] = "utf-8") -> str:
116
- """Write content to a file, overwriting if it exists."""
117
- try:
118
- with open(path, "w", encoding=encoding) as f:
119
- f.write(content)
120
- logger.info(f"Tool: Wrote file '{path}' ({len(content)} bytes)")
121
- return f"[SUCCESS] Wrote file '{path}' ({len(content)} bytes)"
122
- except Exception as e:
123
- logger.error(f"Tool: Failed to write file '{path}': {e}")
124
- return f"[ERROR] Could not write file '{path}': {e}"
125
-
126
- from rich.console import Console
127
- from rich.panel import Panel
128
- from rich.progress import Progress, SpinnerColumn, TextColumn
129
- from rich.live import Live
130
- from rich import box
131
- import asyncio
132
- from enum import Enum
133
- from swarm.ux.ansi_box import ansi_box
134
- from dataclasses import dataclass
135
-
136
- # --- Spinner State Constants ---
137
- SPINNER_STATES = [
138
- "Generating.",
139
- "Generating..",
140
- "Generating...",
141
- "Running...",
142
- ]
143
- SLOW_SPINNER = "Generating... Taking longer than expected"
144
-
145
- class SpinnerState(Enum):
146
- GENERATING_1 = "Generating."
147
- GENERATING_2 = "Generating.."
148
- GENERATING_3 = "Generating..."
149
- RUNNING = "Running..."
150
- LONG_WAIT = "Generating... Taking longer than expected"
151
-
152
- # --- Notifier abstraction ---
153
- class Notifier:
154
- def __init__(self, console=None):
155
- from rich.console import Console
156
- self.console = console or Console()
157
-
158
- def print_box(self, title, content, style="blue", *, result_count: int = None, params: dict = None, op_type: str = None, progress_line: int = None, total_lines: int = None, spinner_state: str = None, emoji: str = None):
159
- emoji_map = {
160
- "search": "🔍",
161
- "code_search": "💻",
162
- "semantic_search": "🧠",
163
- "analysis": "📊",
164
- "writing": "✍️",
165
- "editing": "✏️",
166
- "planning": "📋"
167
- }
168
- emoji = emoji_map.get(op_type or title.lower(), emoji or "💡")
169
- summary_lines = []
170
- if result_count is not None:
171
- summary_lines.append(f"Results: {result_count}")
172
- if params:
173
- for k, v in params.items():
174
- summary_lines.append(f"{k.title()}: {v}")
175
- if progress_line and total_lines:
176
- summary_lines.append(f"Progress: {progress_line}/{total_lines}")
177
- summary = "\n".join(summary_lines)
178
- box_content = content
179
- if summary:
180
- box_content = f"{summary}\n{content}"
181
- if spinner_state:
182
- box_content += f"\n{spinner_state}"
183
- if emoji:
184
- box_content = f"{emoji} {box_content}"
185
- display_operation_box(
186
- title=title,
187
- content=box_content,
188
- result_count=result_count,
189
- params=params,
190
- progress_line=progress_line,
191
- total_lines=total_lines,
192
- spinner_state=spinner_state,
193
- emoji=emoji
194
- )
195
-
196
- def print_error(self, title, content):
197
- self.print_box(title, content, style="red", emoji="❌")
198
-
199
- def print_info(self, content):
200
- self.console.print(content)
201
-
202
- @dataclass
203
- class AgentTool:
204
- name: str
205
- description: str
206
- parameters: dict
207
- handler: callable = None
208
-
209
- class ToolRegistry:
210
- """
211
- Central registry for all tools: both LLM (OpenAI function-calling) and Python-only tools.
212
- """
213
- def __init__(self):
214
- self.llm_tools = {}
215
- self.python_tools = {}
216
-
217
- def register_llm_tool(self, name: str, description: str, parameters: dict, handler):
218
- self.llm_tools[name] = {
219
- 'name': name,
220
- 'description': description,
221
- 'parameters': parameters,
222
- 'handler': handler
223
- }
224
-
225
- def register_python_tool(self, name: str, handler, description: str = ""):
226
- self.python_tools[name] = handler
227
-
228
- def get_llm_tools(self, as_openai_spec=False):
229
- tools = list(self.llm_tools.values())
230
- if as_openai_spec:
231
- # Return OpenAI-compatible dicts
232
- return [
233
- {
234
- 'name': t['name'],
235
- 'description': t['description'],
236
- 'parameters': t['parameters']
237
- } for t in tools
238
- ]
239
- return tools
240
-
241
- def get_python_tool(self, name: str):
242
- return self.python_tools.get(name)
243
-
244
- from swarm.blueprints.common.operation_box_utils import display_operation_box
245
-
246
- class GeeseBlueprint(BlueprintBase):
247
- """
248
- Geese: Swarm-powered collaborative story writing blueprint.
249
- """
250
- metadata: ClassVar[dict] = {
251
- "name": "GeeseBlueprint",
252
- "cli_name": "geese",
253
- "title": "Geese: Swarm-powered collaborative story writing agent",
254
- "description": "A collaborative story writing blueprint leveraging multiple specialized agents to create, edit, and refine stories.",
255
- }
256
-
257
- def get_llm_profile_name(self):
258
- # Returns the active LLM profile name, prioritizing CLI-set or fallback logic
259
- if hasattr(self, '_llm_profile_name') and self._llm_profile_name:
260
- return self._llm_profile_name
261
- if hasattr(self, '_resolve_llm_profile'):
262
- return self._resolve_llm_profile()
263
- return 'default'
264
-
265
- def get_llm_profile_config(self):
266
- # Returns the config dict for the active LLM profile
267
- profile = self.get_llm_profile_name()
268
- llm_section = self.config.get('llm', {}) if hasattr(self, 'config') else {}
269
- return llm_section.get(profile, llm_section.get('default', {}))
270
-
271
- def get_model_name(self):
272
- return self.get_llm_profile_config().get('model', 'gpt-4o')
273
-
274
- def get_llm_endpoint(self):
275
- return self.get_llm_profile_config().get('base_url', 'unknown')
276
-
277
- def get_llm_api_key(self):
278
- api_key = self.get_llm_profile_config().get('api_key', 'unknown')
279
- import os
280
- # Try to resolve env vars in api_key string
281
- if isinstance(api_key, str) and api_key.startswith('${') and api_key.endswith('}'):
282
- env_var = api_key[2:-1]
283
- return os.environ.get(env_var, 'NOT SET')
284
- return api_key
285
-
286
- def __init__(self, blueprint_id: str = "geese", config=None, config_path=None, notifier=None, mcp_servers: Optional[Dict[str, Any]] = None, agent_mcp_assignments: Optional[Dict[str, list]] = None, **kwargs):
287
- super().__init__(blueprint_id, config=config, config_path=config_path, **kwargs)
288
- self.blueprint_id = blueprint_id
289
- self.config_path = config_path
290
- self._config = config if config is not None else {}
291
- self._llm_profile_name = None
292
- self._llm_profile_data = None
293
- self._markdown_output = None
294
- self.notifier = notifier
295
- self.mcp_servers = mcp_servers or {}
296
- self.agent_mcp_assignments = agent_mcp_assignments or {}
297
- # Only call model/profile-dependent logic if config is set
298
- if self._config is not None:
299
- self.model_name = self.get_model_name()
300
- else:
301
- self.model_name = None
302
- # Register required tools for delegation flow tests
303
- self.tool_registry = ToolRegistry() # Ensure tool_registry always exists
304
- # Register required tools for delegation flow tests
305
- self.tool_registry.register_llm_tool(
306
- name="Planner",
307
- description="Plans the story structure.",
308
- parameters={},
309
- handler=lambda *a, **kw: None
310
- )
311
- self.tool_registry.register_llm_tool(
312
- name="Writer",
313
- description="Writes story content.",
314
- parameters={},
315
- handler=lambda *a, **kw: None
316
- )
317
- self.tool_registry.register_llm_tool(
318
- name="Editor",
319
- description="Edits the story.",
320
- parameters={},
321
- handler=lambda *a, **kw: None
322
- )
323
- self.notifier = notifier or Notifier()
324
- self.mcp_servers = mcp_servers or getattr(self, 'mcp_server_configs', {}) or {}
325
- self.agent_mcp_assignments = agent_mcp_assignments or {}
326
- # Build enabled/disabled lists for all MCPs
327
- self.enabled_mcp_servers = {k: v for k, v in self.mcp_servers.items() if not v.get('disabled', False)}
328
- from agents import Agent, Tool
329
- from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
330
- from openai import AsyncOpenAI
331
- import os
332
- if self.model_name:
333
- model_name = self.model_name
334
- api_key = os.environ.get('OPENAI_API_KEY')
335
- openai_client = AsyncOpenAI(api_key=api_key)
336
- model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client)
337
- else:
338
- model_instance = None
339
- # Attach all available tools (LLM and Python) to the agent
340
- llm_tools = getattr(self, 'tool_registry', None)
341
- if llm_tools is not None:
342
- # Use AgentTool objects for agent.tools
343
- llm_tools = [AgentTool(**t) for t in llm_tools.get_llm_tools(as_openai_spec=False)]
344
- else:
345
- llm_tools = []
346
- python_tools = getattr(self, 'tool_registry', None)
347
- if python_tools is not None:
348
- python_tools = python_tools.python_tools
349
- else:
350
- python_tools = {}
351
- if model_instance:
352
- agent = Agent(
353
- name='GooseCoordinator',
354
- model=model_instance,
355
- instructions="You are a highly skilled code generation and automation agent.",
356
- tools=llm_tools
357
- )
358
- else:
359
- agent = Agent(
360
- name='GooseCoordinator',
361
- instructions="You are a highly skilled code generation and automation agent.",
362
- tools=llm_tools
363
- )
364
- agent.python_tools = python_tools
365
- # Restore legacy agent MCP assignment logic to satisfy agent_mcp_assignment tests
366
- self.agents = {'GooseCoordinator': agent}
367
- agent_names = set(self.agent_mcp_assignments.keys()) | {'GooseCoordinator'}
368
- for agent_name in agent_names:
369
- if agent_name == 'GooseCoordinator':
370
- continue
371
- assigned_mcps = self.agent_mcp_assignments.get(agent_name, [])
372
- assigned_mcp_objs = [self.enabled_mcp_servers[m] for m in assigned_mcps if m in self.enabled_mcp_servers]
373
- extra_agent = Agent(
374
- name=agent_name,
375
- tools=[], # Minimal tools for test compatibility
376
- model=model_instance,
377
- instructions="Test agent for MCP assignment."
378
- )
379
- extra_agent.mcp_servers = assigned_mcp_objs
380
- extra_agent.description = f"Agent {agent_name} for test MCP assignment."
381
- self.agents[agent_name] = extra_agent
382
- # Ensure MCP assignment for all agents in self.agents
383
- for agent_name, mcp_names in self.agent_mcp_assignments.items():
384
- agent = self.agents.get(agent_name)
385
- if agent is not None:
386
- agent.mcp_servers = [self.enabled_mcp_servers[m] for m in mcp_names if m in self.enabled_mcp_servers]
387
- self.coordinator = agent
388
- self.logger = logging.getLogger(__name__)
389
- self.plan = []
390
-
391
- # --- Directory/Folder and Grep Tools ---
392
- import os, re
393
- def list_folder(path: str = "."):
394
- """List immediate contents of a directory (files and folders)."""
395
- try:
396
- return {"entries": os.listdir(path)}
397
- except Exception as e:
398
- return {"error": str(e)}
399
-
400
- def list_folder_recursive(path: str = "."):
401
- """List all files and folders recursively within a directory."""
402
- results = []
403
- try:
404
- for root, dirs, files in os.walk(path):
405
- for d in dirs:
406
- results.append(os.path.join(root, d))
407
- for f in files:
408
- results.append(os.path.join(root, f))
409
- return {"entries": results}
410
- except Exception as e:
411
- return {"error": str(e)}
412
-
413
- def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
414
- """Progressive regex search in files, yields dicts of matches and progress."""
415
- matches = []
416
- import re, os
417
- flags = re.IGNORECASE if case_insensitive else 0
418
- try:
419
- total_files = 0
420
- for root, dirs, files in os.walk(path):
421
- for fname in files:
422
- total_files += 1
423
- scanned_files = 0
424
- for root, dirs, files in os.walk(path):
425
- for fname in files:
426
- fpath = os.path.join(root, fname)
427
- scanned_files += 1
428
- try:
429
- with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
430
- for i, line in enumerate(f, 1):
431
- if re.search(pattern, line, flags):
432
- matches.append({
433
- "file": fpath,
434
- "line": i,
435
- "content": line.strip()
436
- })
437
- if len(matches) >= max_results:
438
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
439
- return
440
- except Exception:
441
- continue
442
- if scanned_files % progress_yield == 0:
443
- yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
444
- # Final yield
445
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
446
- except Exception as e:
447
- yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
448
-
449
- self.tool_registry.register_llm_tool(
450
- name="grep_search",
451
- description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
452
- parameters={
453
- "type": "object",
454
- "properties": {
455
- "pattern": {"type": "string", "description": "Regex pattern to search for"},
456
- "path": {"type": "string", "description": "Directory to search (default: current directory)"},
457
- "case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
458
- "max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
459
- },
460
- "required": ["pattern"]
461
- },
462
- handler=grep_search,
463
- )
464
- # Register agent/blueprint delegation tools (stubs)
465
- def planner(prompt: str) -> str:
466
- """Stub tool for planning."""
467
- return "Planned: " + prompt
468
- self.tool_registry.register_llm_tool(
469
- name="Planner",
470
- description="Plans the next steps for story generation.",
471
- parameters={"type": "object", "properties": {"prompt": {"type": "string", "description": "Prompt to plan for"}}, "required": ["prompt"]},
472
- handler=planner,
473
- )
474
- def writer(plan: str, context: str = "") -> str:
475
- """Write story content based on a plan and optional context."""
476
- return "[Writer] Wrote content for plan: " + plan
477
- def editor(draft: str) -> str:
478
- """Edit and refine a draft story."""
479
- return "[Editor] Edited draft."
480
- self.tool_registry.register_llm_tool(
481
- name="Writer",
482
- description="Write story content based on a plan and optional context.",
483
- parameters={"type": "object", "properties": {"plan": {"type": "string", "description": "Story plan"}, "context": {"type": "string", "description": "Optional context"}}, "required": ["plan"]},
484
- handler=writer,
485
- )
486
- self.tool_registry.register_llm_tool(
487
- name="Editor",
488
- description="Edit and refine a draft story.",
489
- parameters={"type": "object", "properties": {"draft": {"type": "string", "description": "Draft story text"}}, "required": ["draft"]},
490
- handler=editor,
491
- )
492
- # --- Directory/Folder and Grep Tools ---
493
- import os, re
494
- def list_folder(path: str = "."):
495
- """List immediate contents of a directory (files and folders)."""
496
- try:
497
- return {"entries": os.listdir(path)}
498
- except Exception as e:
499
- return {"error": str(e)}
500
-
501
- def list_folder_recursive(path: str = "."):
502
- """List all files and folders recursively within a directory."""
503
- results = []
504
- try:
505
- for root, dirs, files in os.walk(path):
506
- for d in dirs:
507
- results.append(os.path.join(root, d))
508
- for f in files:
509
- results.append(os.path.join(root, f))
510
- return {"entries": results}
511
- except Exception as e:
512
- return {"error": str(e)}
513
-
514
- def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
515
- """Progressive regex search in files, yields dicts of matches and progress."""
516
- matches = []
517
- flags = re.IGNORECASE if case_insensitive else 0
518
- try:
519
- total_files = 0
520
- for root, dirs, files in os.walk(path):
521
- for fname in files:
522
- total_files += 1
523
- scanned_files = 0
524
- for root, dirs, files in os.walk(path):
525
- for fname in files:
526
- fpath = os.path.join(root, fname)
527
- scanned_files += 1
528
- try:
529
- with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
530
- for i, line in enumerate(f, 1):
531
- if re.search(pattern, line, flags):
532
- matches.append({
533
- "file": fpath,
534
- "line": i,
535
- "content": line.strip()
536
- })
537
- if len(matches) >= max_results:
538
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
539
- return
540
- except Exception:
541
- continue
542
- if scanned_files % progress_yield == 0:
543
- yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
544
- # Final yield
545
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
546
- except Exception as e:
547
- yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
548
-
549
- self.tool_registry.register_llm_tool(
550
- name="grep_search",
551
- description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
552
- parameters={
553
- "type": "object",
554
- "properties": {
555
- "pattern": {"type": "string", "description": "Regex pattern to search for"},
556
- "path": {"type": "string", "description": "Directory to search (default: current directory)"},
557
- "case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
558
- "max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
559
- },
560
- "required": ["pattern"]
561
- },
562
- handler=grep_search,
563
- )
564
- # --- Directory/Folder and Grep Tools ---
565
- import os, re
566
- def list_folder(path: str = "."):
567
- """List immediate contents of a directory (files and folders)."""
568
- try:
569
- return {"entries": os.listdir(path)}
570
- except Exception as e:
571
- return {"error": str(e)}
572
-
573
- def list_folder_recursive(path: str = "."):
574
- """List all files and folders recursively within a directory."""
575
- results = []
576
- try:
577
- for root, dirs, files in os.walk(path):
578
- for d in dirs:
579
- results.append(os.path.join(root, d))
580
- for f in files:
581
- results.append(os.path.join(root, f))
582
- return {"entries": results}
583
- except Exception as e:
584
- return {"error": str(e)}
585
-
586
- def grep_search(pattern: str, path: str = ".", case_insensitive: bool = False, max_results: int = 100, progress_yield: int = 10):
587
- """Progressive regex search in files, yields dicts of matches and progress."""
588
- matches = []
589
- flags = re.IGNORECASE if case_insensitive else 0
590
- try:
591
- total_files = 0
592
- for root, dirs, files in os.walk(path):
593
- for fname in files:
594
- total_files += 1
595
- scanned_files = 0
596
- for root, dirs, files in os.walk(path):
597
- for fname in files:
598
- fpath = os.path.join(root, fname)
599
- scanned_files += 1
600
- try:
601
- with open(fpath, "r", encoding="utf-8", errors="ignore") as f:
602
- for i, line in enumerate(f, 1):
603
- if re.search(pattern, line, flags):
604
- matches.append({
605
- "file": fpath,
606
- "line": i,
607
- "content": line.strip()
608
- })
609
- if len(matches) >= max_results:
610
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": True, "done": True}
611
- return
612
- except Exception:
613
- continue
614
- if scanned_files % progress_yield == 0:
615
- yield {"matches": matches.copy(), "progress": scanned_files, "total": total_files, "truncated": False, "done": False}
616
- # Final yield
617
- yield {"matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
618
- except Exception as e:
619
- yield {"error": str(e), "matches": matches, "progress": scanned_files, "total": total_files, "truncated": False, "done": True}
620
-
621
- self.tool_registry.register_llm_tool(
622
- name="grep_search",
623
- description="Search for a regex pattern in files under a directory tree. Returns file, line number, and line content for each match.",
624
- parameters={
625
- "type": "object",
626
- "properties": {
627
- "pattern": {"type": "string", "description": "Regex pattern to search for"},
628
- "path": {"type": "string", "description": "Directory to search (default: current directory)"},
629
- "case_insensitive": {"type": "boolean", "description": "Case-insensitive search (default: false)"},
630
- "max_results": {"type": "integer", "description": "Maximum number of results (default: 100)"}
631
- },
632
- "required": ["pattern"]
633
- },
634
- handler=grep_search,
635
- )
636
-
637
- async def run(self, messages: List[dict], **kwargs):
638
- """Main execution entry point for the Geese blueprint."""
639
- logger.info("GeeseBlueprint run method called.")
640
- instruction = messages[-1].get("content", "") if messages else ""
641
- from swarm.core.blueprint_ux import BlueprintUXImproved
642
- ux = BlueprintUXImproved(style="serious")
643
- spinner_idx = 0
644
- start_time = time.time()
645
- spinner_yield_interval = 1.0 # seconds
646
- last_spinner_time = start_time
647
- yielded_spinner = False
648
- result_chunks = []
649
- try:
650
- from agents import Runner
651
- runner_gen = Runner.run(self.create_starting_agent([]), instruction)
652
- while True:
653
- now = time.time()
654
- try:
655
- chunk = next(runner_gen)
656
- result_chunks.append(chunk)
657
- # If chunk is a final result, wrap and yield
658
- if chunk and isinstance(chunk, dict) and "messages" in chunk:
659
- content = chunk["messages"][0]["content"] if chunk["messages"] else ""
660
- summary = ux.summary("Operation", len(result_chunks), {"instruction": instruction[:40]})
661
- box = ux.ansi_emoji_box(
662
- title="Geese Result",
663
- content=content,
664
- summary=summary,
665
- params={"instruction": instruction[:40]},
666
- result_count=len(result_chunks),
667
- op_type="run",
668
- status="success"
669
- )
670
- yield {"messages": [{"role": "assistant", "content": box}]}
671
- else:
672
- yield chunk
673
- yielded_spinner = False
674
- except StopIteration:
675
- break
676
- except Exception:
677
- if now - last_spinner_time >= spinner_yield_interval:
678
- taking_long = (now - start_time > 10)
679
- spinner_msg = ux.spinner(spinner_idx, taking_long=taking_long)
680
- yield {"messages": [{"role": "assistant", "content": spinner_msg}]}
681
- spinner_idx += 1
682
- last_spinner_time = now
683
- yielded_spinner = True
684
- if not result_chunks and not yielded_spinner:
685
- yield {"messages": [{"role": "assistant", "content": ux.spinner(0)}]}
686
- except Exception as e:
687
- logger.error(f"Error during Geese run: {e}", exc_info=True)
688
- yield {"messages": [{"role": "assistant", "content": f"An error occurred: {e}"}]}
689
-
690
- async def demo_run(self, messages: List[dict], **kwargs):
691
- # --- DEMO: Progressive search for live operation box UX ---
692
- import asyncio
693
- prompt = messages[0].get('content', '') if messages else ''
694
- # Simulate a progressive code search with fake results
695
- total = 5 # test expects 5 updates
696
- matches = []
697
- for i in range(1, total + 1):
698
- await asyncio.sleep(0.3)
699
- matches.append(f"def demo_func_{i}(): ...")
700
- chunk = {
701
- "matches": matches.copy(),
702
- "progress": i,
703
- "total": total,
704
- "truncated": False,
705
- "done": i == total,
706
- "query": prompt,
707
- "type": "code_search"
708
- }
709
- display_operation_box(
710
- title="Searching Filesystem" if chunk.get("progress") else "Geese Output",
711
- content=f"Matches so far: {len(chunk.get('matches', []))}" if chunk.get("matches") is not None else str(chunk),
712
- result_count=len(chunk.get('matches', [])) if chunk.get("matches") is not None else None,
713
- params={k: v for k, v in chunk.items() if k not in {'matches', 'progress', 'total', 'truncated', 'done'}},
714
- progress_line=chunk.get('progress'),
715
- total_lines=chunk.get('total'),
716
- spinner_state=None,
717
- emoji="🔍" if chunk.get("progress") else "💡",
718
- op_type="search"
719
- )
720
- yield chunk
721
-
722
- def display_plan_box(self):
723
- if self.plan:
724
- display_operation_box(
725
- title="Planning Update",
726
- content="\n".join([f"✓ {item}" for item in self.plan]),
727
- emoji="📋"
728
- )
729
-
730
- def update_spinner(self, progress_state, elapsed_time):
731
- # Use direct reference to SpinnerState to avoid import errors when running as __main__
732
- from swarm.blueprints.geese.blueprint_geese import SpinnerState
733
- if elapsed_time > 10 and progress_state in [SpinnerState.GENERATING_1, SpinnerState.GENERATING_2, SpinnerState.GENERATING_3]:
734
- return SpinnerState.LONG_WAIT
735
- return progress_state
736
-
737
- def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
738
- """Returns the coordinator agent for GeeseBlueprint, using only assigned MCP servers."""
739
- self.logger.info(f"Coordinator assigned MCP servers: {[m.get('name', 'unknown') for m in getattr(self.coordinator, 'mcp_servers', [])]}")
740
- return self.agents['GooseCoordinator']
741
-
742
- # --- CLI entry point ---
743
- def main():
744
- import argparse
745
- import sys
746
- import asyncio
747
- import os
748
- import json
749
- parser = argparse.ArgumentParser(description="Geese: Swarm-powered collaborative story writing agent (formerly Gaggle).")
750
- parser.add_argument("prompt", nargs="?", help="Prompt or story topic (quoted)")
751
- parser.add_argument("-i", "--input", help="Input file or directory", default=None)
752
- parser.add_argument("-o", "--output", help="Output file", default=None)
753
- parser.add_argument("--model", help="Model name (codex, gpt, etc.)", default=None)
754
- parser.add_argument("--temperature", type=float, help="Sampling temperature", default=0.1)
755
- parser.add_argument("--debug", action="store_true", help="Enable debug logging")
756
- parser.add_argument("--config", help="Path to swarm_config.json", default=None)
757
- args = parser.parse_args()
758
-
759
- # Load config file if present
760
- config_path = args.config or "/home/chatgpt/open-swarm/swarm_config.json"
761
- config = None
762
- if os.path.isfile(config_path):
763
- with open(config_path, "r") as f:
764
- config = json.load(f)
765
- else:
766
- print(f"WARNING: Config file not found at {config_path}. Proceeding with empty config.")
767
- config = {}
768
-
769
- blueprint = GeeseBlueprint(blueprint_id="cli-geese", config=config)
770
- messages = []
771
- if args.prompt:
772
- messages.append({"role": "user", "content": args.prompt})
773
- else:
774
- print("Type your prompt (or 'exit' to quit):\n")
775
- while True:
776
- try:
777
- user_input = input("You: ").strip()
778
- except (EOFError, KeyboardInterrupt):
779
- print("\nExiting Geese CLI.")
780
- break
781
- if user_input.lower() in {"exit", "quit", "q"}:
782
- print("Goodbye!")
783
- break
784
- messages.append({"role": "user", "content": user_input})
785
- async def run_and_print():
786
- async for response in blueprint.run(messages, model=args.model):
787
- if isinstance(response, dict) and 'content' in response:
788
- print(response['content'], end="")
789
- else:
790
- print(response, end="")
791
- asyncio.run(run_and_print())
792
- messages = []
793
- sys.exit(0)
794
- async def run_and_print():
795
- async for response in blueprint.run(messages, model=args.model):
796
- if isinstance(response, dict) and 'content' in response:
797
- print(response['content'], end="")
798
- else:
799
- print(response, end="")
800
- asyncio.run(run_and_print())
801
-
802
- if __name__ == "__main__":
803
- main()