open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743362777__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 (217) hide show
  1. open_swarm-0.1.1743362777.dist-info/METADATA +217 -0
  2. open_swarm-0.1.1743362777.dist-info/RECORD +260 -0
  3. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743362777.dist-info}/WHEEL +1 -2
  4. open_swarm-0.1.1743362777.dist-info/entry_points.txt +2 -0
  5. swarm/__init__.py +0 -2
  6. swarm/auth.py +53 -49
  7. swarm/blueprints/README.md +67 -0
  8. swarm/blueprints/burnt_noodles/blueprint_burnt_noodles.py +412 -0
  9. swarm/blueprints/chatbot/blueprint_chatbot.py +98 -0
  10. swarm/blueprints/chatbot/templates/chatbot/chatbot.html +33 -0
  11. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +183 -0
  12. swarm/blueprints/dilbot_universe/blueprint_dilbot_universe.py +285 -0
  13. swarm/blueprints/divine_code/__init__.py +0 -0
  14. swarm/blueprints/divine_code/apps.py +11 -0
  15. swarm/blueprints/divine_code/blueprint_divine_code.py +219 -0
  16. swarm/blueprints/django_chat/apps.py +6 -0
  17. swarm/blueprints/django_chat/blueprint_django_chat.py +84 -0
  18. swarm/blueprints/django_chat/templates/django_chat/django_chat_webpage.html +37 -0
  19. swarm/blueprints/django_chat/urls.py +8 -0
  20. swarm/blueprints/django_chat/views.py +32 -0
  21. swarm/blueprints/echocraft/blueprint_echocraft.py +44 -0
  22. swarm/blueprints/family_ties/apps.py +11 -0
  23. swarm/blueprints/family_ties/blueprint_family_ties.py +152 -0
  24. swarm/blueprints/family_ties/models.py +19 -0
  25. swarm/blueprints/family_ties/serializers.py +7 -0
  26. swarm/blueprints/family_ties/settings.py +16 -0
  27. swarm/blueprints/family_ties/urls.py +10 -0
  28. swarm/blueprints/family_ties/views.py +26 -0
  29. swarm/blueprints/flock/__init__.py +0 -0
  30. swarm/blueprints/gaggle/blueprint_gaggle.py +184 -0
  31. swarm/blueprints/gotchaman/blueprint_gotchaman.py +232 -0
  32. swarm/blueprints/mcp_demo/blueprint_mcp_demo.py +133 -0
  33. swarm/blueprints/messenger/templates/messenger/messenger.html +46 -0
  34. swarm/blueprints/mission_improbable/blueprint_mission_improbable.py +234 -0
  35. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +248 -0
  36. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +156 -0
  37. swarm/blueprints/omniplex/blueprint_omniplex.py +221 -0
  38. swarm/blueprints/rue_code/__init__.py +0 -0
  39. swarm/blueprints/rue_code/blueprint_rue_code.py +291 -0
  40. swarm/blueprints/suggestion/blueprint_suggestion.py +110 -0
  41. swarm/blueprints/unapologetic_press/blueprint_unapologetic_press.py +298 -0
  42. swarm/blueprints/whiskeytango_foxtrot/__init__.py +0 -0
  43. swarm/blueprints/whiskeytango_foxtrot/apps.py +11 -0
  44. swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +256 -0
  45. swarm/extensions/blueprint/__init__.py +30 -15
  46. swarm/extensions/blueprint/agent_utils.py +16 -40
  47. swarm/extensions/blueprint/blueprint_base.py +141 -543
  48. swarm/extensions/blueprint/blueprint_discovery.py +112 -98
  49. swarm/extensions/blueprint/cli_handler.py +185 -0
  50. swarm/extensions/blueprint/config_loader.py +122 -0
  51. swarm/extensions/blueprint/django_utils.py +181 -79
  52. swarm/extensions/blueprint/interactive_mode.py +1 -1
  53. swarm/extensions/config/config_loader.py +83 -200
  54. swarm/extensions/launchers/build_swarm_wrapper.py +0 -0
  55. swarm/extensions/launchers/swarm_cli.py +199 -287
  56. swarm/llm/chat_completion.py +26 -55
  57. swarm/management/__init__.py +0 -0
  58. swarm/management/commands/__init__.py +0 -0
  59. swarm/management/commands/runserver.py +58 -0
  60. swarm/permissions.py +38 -0
  61. swarm/serializers.py +96 -5
  62. swarm/settings.py +95 -110
  63. swarm/static/contrib/fonts/fontawesome-webfont.ttf +7 -0
  64. swarm/static/contrib/fonts/fontawesome-webfont.woff +7 -0
  65. swarm/static/contrib/fonts/fontawesome-webfont.woff2 +7 -0
  66. swarm/static/contrib/markedjs/marked.min.js +6 -0
  67. swarm/static/contrib/tabler-icons/adjustments-horizontal.svg +27 -0
  68. swarm/static/contrib/tabler-icons/alert-triangle.svg +21 -0
  69. swarm/static/contrib/tabler-icons/archive.svg +21 -0
  70. swarm/static/contrib/tabler-icons/artboard.svg +27 -0
  71. swarm/static/contrib/tabler-icons/automatic-gearbox.svg +23 -0
  72. swarm/static/contrib/tabler-icons/box-multiple.svg +19 -0
  73. swarm/static/contrib/tabler-icons/carambola.svg +19 -0
  74. swarm/static/contrib/tabler-icons/copy.svg +20 -0
  75. swarm/static/contrib/tabler-icons/download.svg +21 -0
  76. swarm/static/contrib/tabler-icons/edit.svg +21 -0
  77. swarm/static/contrib/tabler-icons/filled/carambola.svg +13 -0
  78. swarm/static/contrib/tabler-icons/filled/paint.svg +13 -0
  79. swarm/static/contrib/tabler-icons/headset.svg +22 -0
  80. swarm/static/contrib/tabler-icons/layout-sidebar-left-collapse.svg +21 -0
  81. swarm/static/contrib/tabler-icons/layout-sidebar-left-expand.svg +21 -0
  82. swarm/static/contrib/tabler-icons/layout-sidebar-right-collapse.svg +21 -0
  83. swarm/static/contrib/tabler-icons/layout-sidebar-right-expand.svg +21 -0
  84. swarm/static/contrib/tabler-icons/message-chatbot.svg +22 -0
  85. swarm/static/contrib/tabler-icons/message-star.svg +22 -0
  86. swarm/static/contrib/tabler-icons/message-x.svg +23 -0
  87. swarm/static/contrib/tabler-icons/message.svg +21 -0
  88. swarm/static/contrib/tabler-icons/paperclip.svg +18 -0
  89. swarm/static/contrib/tabler-icons/playlist-add.svg +22 -0
  90. swarm/static/contrib/tabler-icons/robot.svg +26 -0
  91. swarm/static/contrib/tabler-icons/search.svg +19 -0
  92. swarm/static/contrib/tabler-icons/settings.svg +20 -0
  93. swarm/static/contrib/tabler-icons/thumb-down.svg +19 -0
  94. swarm/static/contrib/tabler-icons/thumb-up.svg +19 -0
  95. swarm/static/css/dropdown.css +22 -0
  96. swarm/static/htmx/htmx.min.js +0 -0
  97. swarm/static/js/dropdown.js +23 -0
  98. swarm/static/rest_mode/css/base.css +470 -0
  99. swarm/static/rest_mode/css/chat-history.css +286 -0
  100. swarm/static/rest_mode/css/chat.css +251 -0
  101. swarm/static/rest_mode/css/chatbot.css +74 -0
  102. swarm/static/rest_mode/css/chatgpt.css +62 -0
  103. swarm/static/rest_mode/css/colors/corporate.css +74 -0
  104. swarm/static/rest_mode/css/colors/pastel.css +81 -0
  105. swarm/static/rest_mode/css/colors/tropical.css +82 -0
  106. swarm/static/rest_mode/css/general.css +142 -0
  107. swarm/static/rest_mode/css/layout.css +167 -0
  108. swarm/static/rest_mode/css/layouts/messenger-layout.css +17 -0
  109. swarm/static/rest_mode/css/layouts/minimalist-layout.css +57 -0
  110. swarm/static/rest_mode/css/layouts/mobile-layout.css +8 -0
  111. swarm/static/rest_mode/css/messages.css +84 -0
  112. swarm/static/rest_mode/css/messenger.css +135 -0
  113. swarm/static/rest_mode/css/settings.css +91 -0
  114. swarm/static/rest_mode/css/simple.css +44 -0
  115. swarm/static/rest_mode/css/slack.css +58 -0
  116. swarm/static/rest_mode/css/style.css +156 -0
  117. swarm/static/rest_mode/css/theme.css +30 -0
  118. swarm/static/rest_mode/css/toast.css +40 -0
  119. swarm/static/rest_mode/js/auth.js +9 -0
  120. swarm/static/rest_mode/js/blueprint.js +41 -0
  121. swarm/static/rest_mode/js/blueprintUtils.js +12 -0
  122. swarm/static/rest_mode/js/chatLogic.js +79 -0
  123. swarm/static/rest_mode/js/debug.js +63 -0
  124. swarm/static/rest_mode/js/events.js +98 -0
  125. swarm/static/rest_mode/js/main.js +19 -0
  126. swarm/static/rest_mode/js/messages.js +264 -0
  127. swarm/static/rest_mode/js/messengerLogic.js +355 -0
  128. swarm/static/rest_mode/js/modules/apiService.js +84 -0
  129. swarm/static/rest_mode/js/modules/blueprintManager.js +162 -0
  130. swarm/static/rest_mode/js/modules/chatHistory.js +110 -0
  131. swarm/static/rest_mode/js/modules/debugLogger.js +14 -0
  132. swarm/static/rest_mode/js/modules/eventHandlers.js +107 -0
  133. swarm/static/rest_mode/js/modules/messageProcessor.js +120 -0
  134. swarm/static/rest_mode/js/modules/state.js +7 -0
  135. swarm/static/rest_mode/js/modules/userInteractions.js +29 -0
  136. swarm/static/rest_mode/js/modules/validation.js +23 -0
  137. swarm/static/rest_mode/js/rendering.js +119 -0
  138. swarm/static/rest_mode/js/settings.js +130 -0
  139. swarm/static/rest_mode/js/sidebar.js +94 -0
  140. swarm/static/rest_mode/js/simpleLogic.js +37 -0
  141. swarm/static/rest_mode/js/slackLogic.js +66 -0
  142. swarm/static/rest_mode/js/splash.js +76 -0
  143. swarm/static/rest_mode/js/theme.js +111 -0
  144. swarm/static/rest_mode/js/toast.js +36 -0
  145. swarm/static/rest_mode/js/ui.js +265 -0
  146. swarm/static/rest_mode/js/validation.js +57 -0
  147. swarm/static/rest_mode/svg/animated_spinner.svg +12 -0
  148. swarm/static/rest_mode/svg/arrow_down.svg +5 -0
  149. swarm/static/rest_mode/svg/arrow_left.svg +5 -0
  150. swarm/static/rest_mode/svg/arrow_right.svg +5 -0
  151. swarm/static/rest_mode/svg/arrow_up.svg +5 -0
  152. swarm/static/rest_mode/svg/attach.svg +8 -0
  153. swarm/static/rest_mode/svg/avatar.svg +7 -0
  154. swarm/static/rest_mode/svg/canvas.svg +6 -0
  155. swarm/static/rest_mode/svg/chat_history.svg +4 -0
  156. swarm/static/rest_mode/svg/close.svg +5 -0
  157. swarm/static/rest_mode/svg/copy.svg +4 -0
  158. swarm/static/rest_mode/svg/dark_mode.svg +3 -0
  159. swarm/static/rest_mode/svg/edit.svg +5 -0
  160. swarm/static/rest_mode/svg/layout.svg +9 -0
  161. swarm/static/rest_mode/svg/logo.svg +29 -0
  162. swarm/static/rest_mode/svg/logout.svg +5 -0
  163. swarm/static/rest_mode/svg/mobile.svg +5 -0
  164. swarm/static/rest_mode/svg/new_chat.svg +4 -0
  165. swarm/static/rest_mode/svg/not_visible.svg +5 -0
  166. swarm/static/rest_mode/svg/plus.svg +7 -0
  167. swarm/static/rest_mode/svg/run_code.svg +6 -0
  168. swarm/static/rest_mode/svg/save.svg +4 -0
  169. swarm/static/rest_mode/svg/search.svg +6 -0
  170. swarm/static/rest_mode/svg/settings.svg +4 -0
  171. swarm/static/rest_mode/svg/speaker.svg +5 -0
  172. swarm/static/rest_mode/svg/stop.svg +6 -0
  173. swarm/static/rest_mode/svg/thumbs_down.svg +3 -0
  174. swarm/static/rest_mode/svg/thumbs_up.svg +3 -0
  175. swarm/static/rest_mode/svg/toggle_off.svg +6 -0
  176. swarm/static/rest_mode/svg/toggle_on.svg +6 -0
  177. swarm/static/rest_mode/svg/trash.svg +10 -0
  178. swarm/static/rest_mode/svg/undo.svg +3 -0
  179. swarm/static/rest_mode/svg/visible.svg +8 -0
  180. swarm/static/rest_mode/svg/voice.svg +10 -0
  181. swarm/templates/account/login.html +22 -0
  182. swarm/templates/account/signup.html +32 -0
  183. swarm/templates/base.html +30 -0
  184. swarm/templates/chat.html +43 -0
  185. swarm/templates/index.html +35 -0
  186. swarm/templates/rest_mode/components/chat_sidebar.html +55 -0
  187. swarm/templates/rest_mode/components/header.html +45 -0
  188. swarm/templates/rest_mode/components/main_chat_pane.html +41 -0
  189. swarm/templates/rest_mode/components/settings_dialog.html +97 -0
  190. swarm/templates/rest_mode/components/splash_screen.html +7 -0
  191. swarm/templates/rest_mode/components/top_bar.html +28 -0
  192. swarm/templates/rest_mode/message_ui.html +50 -0
  193. swarm/templates/rest_mode/slackbot.html +30 -0
  194. swarm/templates/simple_blueprint_page.html +24 -0
  195. swarm/templates/websocket_partials/final_system_message.html +3 -0
  196. swarm/templates/websocket_partials/system_message.html +4 -0
  197. swarm/templates/websocket_partials/user_message.html +5 -0
  198. swarm/urls.py +57 -74
  199. swarm/utils/log_utils.py +63 -0
  200. swarm/views/api_views.py +48 -39
  201. swarm/views/chat_views.py +156 -70
  202. swarm/views/core_views.py +85 -90
  203. swarm/views/model_views.py +64 -121
  204. swarm/views/utils.py +65 -441
  205. open_swarm-0.1.1743070217.dist-info/METADATA +0 -258
  206. open_swarm-0.1.1743070217.dist-info/RECORD +0 -89
  207. open_swarm-0.1.1743070217.dist-info/entry_points.txt +0 -3
  208. open_swarm-0.1.1743070217.dist-info/top_level.txt +0 -1
  209. swarm/agent/agent.py +0 -49
  210. swarm/core.py +0 -326
  211. swarm/extensions/mcp/__init__.py +0 -1
  212. swarm/extensions/mcp/cache_utils.py +0 -36
  213. swarm/extensions/mcp/mcp_client.py +0 -341
  214. swarm/extensions/mcp/mcp_constants.py +0 -7
  215. swarm/extensions/mcp/mcp_tool_provider.py +0 -110
  216. swarm/types.py +0 -126
  217. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743362777.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,234 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ import json
5
+ import sqlite3 # Use standard sqlite3 module
6
+ from pathlib import Path
7
+ from typing import Dict, Any, List, ClassVar, Optional
8
+
9
+ # Ensure src is in path for BlueprintBase import
10
+ project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
11
+ src_path = os.path.join(project_root, 'src')
12
+ if src_path not in sys.path: sys.path.insert(0, src_path)
13
+
14
+ try:
15
+ from agents import Agent, Tool, function_tool, Runner
16
+ from agents.mcp import MCPServer
17
+ from agents.models.interface import Model
18
+ from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
19
+ from openai import AsyncOpenAI
20
+ from swarm.extensions.blueprint.blueprint_base import BlueprintBase
21
+ except ImportError as e:
22
+ print(f"ERROR: Import failed in MissionImprobableBlueprint: {e}. Check dependencies.")
23
+ print(f"sys.path: {sys.path}")
24
+ sys.exit(1)
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # --- Database Constants ---
29
+ # Using the same DB file as dilbot_universe
30
+ DB_FILE_NAME = "swarm_instructions.db"
31
+ DB_PATH = Path(project_root) / DB_FILE_NAME
32
+ TABLE_NAME = "agent_instructions" # agent_name TEXT PRIMARY KEY, instruction_text TEXT, model_profile TEXT
33
+
34
+ # --- Define the Blueprint ---
35
+ # Renamed class for consistency
36
+ class MissionImprobableBlueprint(BlueprintBase):
37
+ """A cheeky team on a mission: led by JimFlimsy with support from CinnamonToast and RollinFumble."""
38
+ metadata: ClassVar[Dict[str, Any]] = {
39
+ "name": "MissionImprobableBlueprint",
40
+ "title": "Mission: Improbable",
41
+ "description": "A cheeky team led by JimFlimsy (coordinator), CinnamonToast (strategist/filesystem), and RollinFumble (operative/shell). Uses SQLite for instructions.",
42
+ "version": "1.1.0", # Refactored version
43
+ "author": "Open Swarm Team (Refactored)",
44
+ "tags": ["comedy", "multi-agent", "filesystem", "shell", "sqlite"],
45
+ "required_mcp_servers": ["memory", "filesystem", "mcp-shell"], # Servers needed by the agents
46
+ "env_vars": ["ALLOWED_PATH"], # Informational: filesystem MCP likely needs this
47
+ }
48
+
49
+ # Caches
50
+ _openai_client_cache: Dict[str, AsyncOpenAI] = {}
51
+ _model_instance_cache: Dict[str, Model] = {}
52
+ _db_initialized = False # Flag to ensure DB init runs only once per instance
53
+
54
+ # --- Database Interaction ---
55
+ def _init_db_and_load_data(self) -> None:
56
+ """Initializes the SQLite DB, creates table, and loads sample data if needed."""
57
+ if self._db_initialized:
58
+ return
59
+
60
+ logger.info(f"Initializing SQLite database at: {DB_PATH} for Mission Improbable")
61
+ try:
62
+ DB_PATH.parent.mkdir(parents=True, exist_ok=True)
63
+ with sqlite3.connect(DB_PATH) as conn:
64
+ cursor = conn.cursor()
65
+ # Ensure table exists (same table as dilbot)
66
+ cursor.execute(f"""
67
+ CREATE TABLE IF NOT EXISTS {TABLE_NAME} (
68
+ agent_name TEXT PRIMARY KEY,
69
+ instruction_text TEXT NOT NULL,
70
+ model_profile TEXT DEFAULT 'default'
71
+ )
72
+ """)
73
+ logger.debug(f"Table '{TABLE_NAME}' ensured in {DB_PATH}")
74
+
75
+ # Check if data for JimFlimsy needs loading
76
+ cursor.execute(f"SELECT COUNT(*) FROM {TABLE_NAME} WHERE agent_name = ?", ("JimFlimsy",))
77
+ count = cursor.fetchone()[0]
78
+
79
+ if count == 0:
80
+ logger.info(f"No instructions found for JimFlimsy in {DB_PATH}. Loading sample data...")
81
+ sample_instructions = [
82
+ ("JimFlimsy",
83
+ ("You’re JimFlimsy, the fearless leader:\n"
84
+ "1. Start with 'Syncing systems...' and use the `memory` MCP to load any relevant mission state (if available).\n"
85
+ "2. Understand the user's mission request.\n"
86
+ "3. Delegate strategic file management or planning tasks to CinnamonToast using the `CinnamonToast` agent tool.\n"
87
+ "4. Delegate command execution or operative tasks to RollinFumble using the `RollinFumble` agent tool.\n"
88
+ "5. Synthesize results from your agents and report back to the user. Log mission updates implicitly through conversation flow."),
89
+ "default"),
90
+ ("CinnamonToast",
91
+ ("You’re CinnamonToast, the quick-witted strategist:\n"
92
+ "1. Receive file management or strategic tasks from JimFlimsy.\n"
93
+ "2. Use the `filesystem` MCP tool to create, read, or delete files as requested.\n"
94
+ "3. Report the outcome of your actions clearly back to JimFlimsy."),
95
+ "default"), # Explicitly using default, could be different
96
+ ("RollinFumble",
97
+ ("You’re RollinFumble, the unpredictable operative:\n"
98
+ "1. Receive command execution tasks from JimFlimsy.\n"
99
+ "2. Use the `mcp-shell` MCP tool to execute the requested shell command. Be careful!\n"
100
+ "3. Summarize the output or result of the command and report back to JimFlimsy."),
101
+ "default")
102
+ ]
103
+ cursor.executemany(f"INSERT OR IGNORE INTO {TABLE_NAME} (agent_name, instruction_text, model_profile) VALUES (?, ?, ?)", sample_instructions)
104
+ conn.commit()
105
+ logger.info(f"Sample agent instructions for Mission Improbable loaded into {DB_PATH}")
106
+ else:
107
+ logger.info(f"Mission Improbable agent instructions found in {DB_PATH}. Skipping sample data loading.")
108
+
109
+ self._db_initialized = True
110
+
111
+ except sqlite3.Error as e:
112
+ logger.error(f"SQLite error during DB initialization/loading: {e}", exc_info=True)
113
+ self._db_initialized = False
114
+ except Exception as e:
115
+ logger.error(f"Unexpected error during DB initialization/loading: {e}", exc_info=True)
116
+ self._db_initialized = False
117
+
118
+ def get_agent_config(self, agent_name: str) -> Dict[str, Any]:
119
+ """Fetches agent config from SQLite DB or returns defaults."""
120
+ if self._db_initialized:
121
+ try:
122
+ with sqlite3.connect(DB_PATH) as conn:
123
+ conn.row_factory = sqlite3.Row
124
+ cursor = conn.cursor()
125
+ cursor.execute(f"SELECT instruction_text, model_profile FROM {TABLE_NAME} WHERE agent_name = ?", (agent_name,))
126
+ row = cursor.fetchone()
127
+ if row:
128
+ logger.debug(f"Loaded config for agent '{agent_name}' from SQLite.")
129
+ return {"instructions": row["instruction_text"], "model_profile": row["model_profile"] or "default"}
130
+ else:
131
+ logger.warning(f"No config found for agent '{agent_name}' in SQLite. Using defaults.")
132
+ except sqlite3.Error as e:
133
+ logger.error(f"SQLite error fetching config for '{agent_name}': {e}. Using defaults.", exc_info=True)
134
+ except Exception as e:
135
+ logger.error(f"Unexpected error fetching config for '{agent_name}': {e}. Using defaults.", exc_info=True)
136
+
137
+ # --- Fallback Hardcoded Defaults ---
138
+ logger.warning(f"Using hardcoded default config for agent '{agent_name}'.")
139
+ default_instructions = {
140
+ "JimFlimsy": "You are JimFlimsy, the leader. Delegate tasks. [Default - DB Failed]",
141
+ "CinnamonToast": "You are CinnamonToast, strategist. Use filesystem. [Default - DB Failed]",
142
+ "RollinFumble": "You are RollinFumble, operative. Use shell. [Default - DB Failed]",
143
+ }
144
+ return {
145
+ "instructions": default_instructions.get(agent_name, f"Default instructions for {agent_name}."),
146
+ "model_profile": "default",
147
+ }
148
+
149
+ # --- Model Instantiation Helper --- (Standard helper)
150
+ def _get_model_instance(self, profile_name: str) -> Model:
151
+ """Retrieves or creates an LLM Model instance."""
152
+ # ... (Implementation is the same as previous refactors) ...
153
+ if profile_name in self._model_instance_cache:
154
+ logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
155
+ return self._model_instance_cache[profile_name]
156
+ logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
157
+ profile_data = self.get_llm_profile(profile_name)
158
+ if not profile_data:
159
+ logger.critical(f"LLM profile '{profile_name}' (or 'default') not found.")
160
+ raise ValueError(f"Missing LLM profile configuration for '{profile_name}' or 'default'.")
161
+ provider = profile_data.get("provider", "openai").lower()
162
+ model_name = profile_data.get("model")
163
+ if not model_name:
164
+ logger.critical(f"LLM profile '{profile_name}' missing 'model' key.")
165
+ raise ValueError(f"Missing 'model' key in LLM profile '{profile_name}'.")
166
+ if provider != "openai":
167
+ logger.error(f"Unsupported LLM provider '{provider}'.")
168
+ raise ValueError(f"Unsupported LLM provider: {provider}")
169
+ client_cache_key = f"{provider}_{profile_data.get('base_url')}"
170
+ if client_cache_key not in self._openai_client_cache:
171
+ client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
172
+ filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
173
+ log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
174
+ logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}': {log_kwargs}")
175
+ try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
176
+ except Exception as e: raise ValueError(f"Failed to init OpenAI client: {e}") from e
177
+ client = self._openai_client_cache[client_cache_key]
178
+ logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for '{profile_name}'.")
179
+ try:
180
+ model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
181
+ self._model_instance_cache[profile_name] = model_instance
182
+ return model_instance
183
+ except Exception as e: raise ValueError(f"Failed to init LLM provider: {e}") from e
184
+
185
+ # --- Agent Creation ---
186
+ def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
187
+ """Creates the Mission Improbable agent team and returns JimFlimsy (Coordinator)."""
188
+ # Initialize DB and load data if needed
189
+ self._init_db_and_load_data()
190
+
191
+ logger.debug("Creating Mission Improbable agent team...")
192
+ self._model_instance_cache = {}
193
+ self._openai_client_cache = {}
194
+
195
+ # Helper to filter MCP servers
196
+ def get_agent_mcps(names: List[str]) -> List[MCPServer]:
197
+ return [s for s in mcp_servers if s.name in names]
198
+
199
+ # Create agents, fetching config and assigning MCPs
200
+ agents: Dict[str, Agent] = {}
201
+ for name in ["JimFlimsy", "CinnamonToast", "RollinFumble"]:
202
+ config = self.get_agent_config(name)
203
+ model_instance = self._get_model_instance(config["model_profile"])
204
+ agent_mcps = []
205
+ if name == "JimFlimsy": agent_mcps = get_agent_mcps(["memory"])
206
+ elif name == "CinnamonToast": agent_mcps = get_agent_mcps(["filesystem"])
207
+ elif name == "RollinFumble": agent_mcps = get_agent_mcps(["mcp-shell"])
208
+
209
+ agents[name] = Agent(
210
+ name=name,
211
+ instructions=config["instructions"],
212
+ model=model_instance,
213
+ tools=[], # Agent tools added to Jim below
214
+ mcp_servers=agent_mcps
215
+ )
216
+
217
+ # Add agent tools to the coordinator (JimFlimsy)
218
+ agents["JimFlimsy"].tools.extend([
219
+ agents["CinnamonToast"].as_tool(
220
+ tool_name="CinnamonToast",
221
+ tool_description="Delegate file management or strategic planning tasks."
222
+ ),
223
+ agents["RollinFumble"].as_tool(
224
+ tool_name="RollinFumble",
225
+ tool_description="Delegate shell command execution tasks."
226
+ )
227
+ ])
228
+
229
+ logger.debug("Mission Improbable agents created. Starting with JimFlimsy.")
230
+ return agents["JimFlimsy"] # Jim is the coordinator
231
+
232
+ # Standard Python entry point
233
+ if __name__ == "__main__":
234
+ MissionImprobableBlueprint.main()
@@ -0,0 +1,248 @@
1
+ """
2
+ MonkaiMagic: Cloud Operations Journey Blueprint
3
+
4
+ A *Monkai Magic*-inspired crew managing AWS, Fly.io, and Vercel with pre-authenticated CLIs:
5
+ - Tripitaka (Wise Leader/Coordinator)
6
+ - Monkey (Cloud Trickster/AWS Master)
7
+ - Pigsy (Greedy Tinker/CLI Handler)
8
+ - Sandy (River Sage/Ops Watcher)
9
+
10
+ Uses BlueprintBase, @function_tool for direct CLI calls, and agent-as-tool delegation.
11
+ Assumes pre-authenticated aws, flyctl, and vercel commands.
12
+ """
13
+
14
+ import os
15
+ import logging
16
+ import subprocess
17
+ import sys
18
+ import shlex # Import shlex
19
+ from typing import Dict, Any, List, ClassVar, Optional
20
+
21
+ # Ensure src is in path for BlueprintBase import
22
+ project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
23
+ src_path = os.path.join(project_root, 'src')
24
+ if src_path not in sys.path: sys.path.insert(0, src_path)
25
+
26
+ try:
27
+ from agents import Agent, Tool, function_tool, Runner
28
+ from agents.mcp import MCPServer
29
+ from agents.models.interface import Model
30
+ from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
31
+ from openai import AsyncOpenAI
32
+ from swarm.extensions.blueprint.blueprint_base import BlueprintBase
33
+ except ImportError as e:
34
+ print(f"ERROR: Import failed in MonkaiMagicBlueprint: {e}. Check dependencies.")
35
+ print(f"sys.path: {sys.path}")
36
+ sys.exit(1)
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ # --- Cloud CLI Function Tools ---
41
+ @function_tool
42
+ def aws_cli(command: str) -> str:
43
+ """Executes an AWS CLI command (e.g., 's3 ls', 'ec2 describe-instances'). Assumes pre-authentication."""
44
+ if not command: return "Error: No AWS command provided."
45
+ try:
46
+ # Avoid shell=True if possible, split command carefully
47
+ cmd_parts = ["aws"] + shlex.split(command)
48
+ logger.info(f"Executing AWS CLI: {' '.join(cmd_parts)}")
49
+ result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=120)
50
+ output = result.stdout.strip()
51
+ logger.debug(f"AWS CLI success. Output:\n{output[:500]}...")
52
+ return f"OK: AWS command successful.\nOutput:\n{output}"
53
+ except FileNotFoundError:
54
+ logger.error("AWS CLI ('aws') command not found. Is it installed and in PATH?")
55
+ return "Error: AWS CLI command not found."
56
+ except subprocess.CalledProcessError as e:
57
+ error_output = e.stderr.strip() or e.stdout.strip()
58
+ logger.error(f"AWS CLI error executing '{command}': {error_output}")
59
+ return f"Error executing AWS command '{command}': {error_output}"
60
+ except subprocess.TimeoutExpired:
61
+ logger.error(f"AWS CLI command '{command}' timed out.")
62
+ return f"Error: AWS CLI command '{command}' timed out."
63
+ except Exception as e:
64
+ logger.error(f"Unexpected error during AWS CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
65
+ return f"Error: Unexpected error during AWS CLI: {e}"
66
+
67
+ @function_tool
68
+ def fly_cli(command: str) -> str:
69
+ """Executes a Fly.io CLI command ('flyctl ...'). Assumes pre-authentication ('flyctl auth login')."""
70
+ if not command: return "Error: No Fly command provided."
71
+ try:
72
+ cmd_parts = ["flyctl"] + shlex.split(command)
73
+ logger.info(f"Executing Fly CLI: {' '.join(cmd_parts)}")
74
+ result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=120)
75
+ output = result.stdout.strip()
76
+ logger.debug(f"Fly CLI success. Output:\n{output[:500]}...")
77
+ return f"OK: Fly command successful.\nOutput:\n{output}"
78
+ except FileNotFoundError:
79
+ logger.error("Fly CLI ('flyctl') command not found. Is it installed and in PATH?")
80
+ return "Error: Fly CLI command not found."
81
+ except subprocess.CalledProcessError as e:
82
+ error_output = e.stderr.strip() or e.stdout.strip()
83
+ logger.error(f"Fly CLI error executing '{command}': {error_output}")
84
+ return f"Error executing Fly command '{command}': {error_output}"
85
+ except subprocess.TimeoutExpired:
86
+ logger.error(f"Fly CLI command '{command}' timed out.")
87
+ return f"Error: Fly CLI command '{command}' timed out."
88
+ except Exception as e:
89
+ logger.error(f"Unexpected error during Fly CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
90
+ return f"Error: Unexpected error during Fly CLI: {e}"
91
+
92
+ @function_tool
93
+ def vercel_cli(command: str) -> str:
94
+ """Executes a Vercel CLI command ('vercel ...'). Assumes pre-authentication ('vercel login')."""
95
+ if not command: return "Error: No Vercel command provided."
96
+ try:
97
+ cmd_parts = ["vercel"] + shlex.split(command)
98
+ logger.info(f"Executing Vercel CLI: {' '.join(cmd_parts)}")
99
+ result = subprocess.run(cmd_parts, check=True, capture_output=True, text=True, timeout=120)
100
+ output = result.stdout.strip()
101
+ logger.debug(f"Vercel CLI success. Output:\n{output[:500]}...")
102
+ return f"OK: Vercel command successful.\nOutput:\n{output}"
103
+ except FileNotFoundError:
104
+ logger.error("Vercel CLI ('vercel') command not found. Is it installed and in PATH?")
105
+ return "Error: Vercel CLI command not found."
106
+ except subprocess.CalledProcessError as e:
107
+ error_output = e.stderr.strip() or e.stdout.strip()
108
+ logger.error(f"Vercel CLI error executing '{command}': {error_output}")
109
+ return f"Error executing Vercel command '{command}': {error_output}"
110
+ except subprocess.TimeoutExpired:
111
+ logger.error(f"Vercel CLI command '{command}' timed out.")
112
+ return f"Error: Vercel CLI command '{command}' timed out."
113
+ except Exception as e:
114
+ logger.error(f"Unexpected error during Vercel CLI execution: {e}", exc_info=logger.level <= logging.DEBUG)
115
+ return f"Error: Unexpected error during Vercel CLI: {e}"
116
+
117
+
118
+ # --- Define the Blueprint ---
119
+ class MonkaiMagicBlueprint(BlueprintBase):
120
+ """Blueprint for a cloud operations team inspired by *Monkai Magic*."""
121
+ metadata: ClassVar[Dict[str, Any]] = {
122
+ "name": "MonkaiMagicBlueprint",
123
+ "title": "MonkaiMagic: Cloud Operations Journey",
124
+ "description": "A *Monkai Magic*-inspired crew managing AWS, Fly.io, and Vercel with pre-authenticated CLI tools and agent-as-tool delegation.",
125
+ "version": "1.1.0", # Refactored version
126
+ "author": "Open Swarm Team (Refactored)",
127
+ "tags": ["cloud", "aws", "fly.io", "vercel", "cli", "multi-agent"],
128
+ "required_mcp_servers": ["mcp-shell"], # Only Sandy needs an MCP server
129
+ "env_vars": ["AWS_REGION", "FLY_REGION", "VERCEL_ORG_ID"] # Optional vars for instruction hints
130
+ }
131
+
132
+ # Caches
133
+ _openai_client_cache: Dict[str, AsyncOpenAI] = {}
134
+ _model_instance_cache: Dict[str, Model] = {}
135
+
136
+ # --- Model Instantiation Helper --- (Standard helper)
137
+ def _get_model_instance(self, profile_name: str) -> Model:
138
+ """Retrieves or creates an LLM Model instance."""
139
+ # ... (Implementation is the same as previous refactors) ...
140
+ if profile_name in self._model_instance_cache:
141
+ logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
142
+ return self._model_instance_cache[profile_name]
143
+ logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
144
+ profile_data = self.get_llm_profile(profile_name)
145
+ if not profile_data: raise ValueError(f"Missing LLM profile '{profile_name}'.")
146
+ provider = profile_data.get("provider", "openai").lower()
147
+ model_name = profile_data.get("model")
148
+ if not model_name: raise ValueError(f"Missing 'model' in profile '{profile_name}'.")
149
+ if provider != "openai": raise ValueError(f"Unsupported provider: {provider}")
150
+ client_cache_key = f"{provider}_{profile_data.get('base_url')}"
151
+ if client_cache_key not in self._openai_client_cache:
152
+ client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
153
+ filtered_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
154
+ log_kwargs = {k:v for k,v in filtered_kwargs.items() if k != 'api_key'}
155
+ logger.debug(f"Creating new AsyncOpenAI client for '{profile_name}': {log_kwargs}")
156
+ try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_kwargs)
157
+ except Exception as e: raise ValueError(f"Failed to init client: {e}") from e
158
+ client = self._openai_client_cache[client_cache_key]
159
+ logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') for '{profile_name}'.")
160
+ try:
161
+ model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=client)
162
+ self._model_instance_cache[profile_name] = model_instance
163
+ return model_instance
164
+ except Exception as e: raise ValueError(f"Failed to init LLM: {e}") from e
165
+
166
+ def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
167
+ """Creates the MonkaiMagic agent team and returns Tripitaka."""
168
+ logger.debug("Creating MonkaiMagic agent team...")
169
+ self._model_instance_cache = {}
170
+ self._openai_client_cache = {}
171
+
172
+ default_profile_name = self.config.get("llm_profile", "default")
173
+ logger.debug(f"Using LLM profile '{default_profile_name}' for MonkaiMagic agents.")
174
+ model_instance = self._get_model_instance(default_profile_name)
175
+
176
+ # Get optional env var hints
177
+ aws_region = os.getenv("AWS_REGION")
178
+ fly_region = os.getenv("FLY_REGION")
179
+ vercel_org_id = os.getenv("VERCEL_ORG_ID")
180
+
181
+ # --- Define Agent Instructions (with optional hints) ---
182
+ tripitaka_instructions = (
183
+ "You are Tripitaka, the wise leader guiding the cloud journey:\n"
184
+ "- Lead with calm wisdom, analyzing user requests for cloud operations.\n"
185
+ "- Delegate tasks to the appropriate specialist agent using their Agent Tool:\n"
186
+ " - `Monkey`: For AWS related tasks (use the `aws_cli` function tool).\n"
187
+ " - `Pigsy`: For Fly.io or Vercel tasks (use `fly_cli` or `vercel_cli` function tools).\n"
188
+ " - `Sandy`: For monitoring or diagnostic shell commands related to deployments.\n"
189
+ "- Synthesize the results from your team into a final response for the user. You do not track state yourself."
190
+ )
191
+
192
+ monkey_instructions = (
193
+ "You are Monkey, the cloud trickster and AWS master:\n"
194
+ "- Execute AWS tasks requested by Tripitaka using the `aws_cli` function tool.\n"
195
+ "- Assume the `aws` command is pre-authenticated.\n"
196
+ f"- {f'Default AWS region seems to be {aws_region}. Use this unless specified otherwise.' if aws_region else 'No default AWS region hint available.'}\n"
197
+ "- Report the results (success or error) clearly back to Tripitaka."
198
+ )
199
+
200
+ pigsy_instructions = (
201
+ "You are Pigsy, the greedy tinker handling Fly.io and Vercel CLI hosting:\n"
202
+ "- Execute Fly.io tasks using the `fly_cli` function tool.\n"
203
+ "- Execute Vercel tasks using the `vercel_cli` function tool.\n"
204
+ "- Assume `flyctl` and `vercel` commands are pre-authenticated.\n"
205
+ f"- {f'Default Fly.io region hint: {fly_region}.' if fly_region else 'No default Fly.io region hint.'}\n"
206
+ f"- {f'Default Vercel Org ID hint: {vercel_org_id}.' if vercel_org_id else 'No default Vercel Org ID hint.'}\n"
207
+ "- Report the results clearly back to Tripitaka."
208
+ )
209
+
210
+ sandy_instructions = (
211
+ "You are Sandy, the river sage and ops watcher:\n"
212
+ "- Execute general shell commands requested by Tripitaka for monitoring or diagnostics using the `mcp-shell` MCP tool.\n"
213
+ "- Report the output or status steadily back to Tripitaka.\n"
214
+ "Available MCP Tools: mcp-shell."
215
+ )
216
+
217
+ # Instantiate agents
218
+ monkey_agent = Agent(
219
+ name="Monkey", model=model_instance, instructions=monkey_instructions,
220
+ tools=[aws_cli], # Function tool for AWS
221
+ mcp_servers=[]
222
+ )
223
+ pigsy_agent = Agent(
224
+ name="Pigsy", model=model_instance, instructions=pigsy_instructions,
225
+ tools=[fly_cli, vercel_cli], # Function tools for Fly/Vercel
226
+ mcp_servers=[]
227
+ )
228
+ sandy_agent = Agent(
229
+ name="Sandy", model=model_instance, instructions=sandy_instructions,
230
+ tools=[], # Uses MCP only
231
+ mcp_servers=[s for s in mcp_servers if s.name == 'mcp-shell'] # Pass only relevant MCP
232
+ )
233
+ tripitaka_agent = Agent(
234
+ name="Tripitaka", model=model_instance, instructions=tripitaka_instructions,
235
+ tools=[ # Delegate via Agent-as-Tool
236
+ monkey_agent.as_tool(tool_name="Monkey", tool_description="Delegate AWS tasks to Monkey."),
237
+ pigsy_agent.as_tool(tool_name="Pigsy", tool_description="Delegate Fly.io or Vercel tasks to Pigsy."),
238
+ sandy_agent.as_tool(tool_name="Sandy", tool_description="Delegate monitoring or diagnostic shell commands to Sandy.")
239
+ ],
240
+ mcp_servers=[]
241
+ )
242
+
243
+ logger.debug("MonkaiMagic Team created. Starting with Tripitaka.")
244
+ return tripitaka_agent
245
+
246
+ # Standard Python entry point
247
+ if __name__ == "__main__":
248
+ MonkaiMagicBlueprint.main()
@@ -0,0 +1,156 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ import asyncio
5
+ import subprocess
6
+ import re
7
+ import inspect
8
+ from typing import Dict, Any, List, Optional, ClassVar
9
+
10
+ try:
11
+ from agents import Agent, Tool, function_tool
12
+ from agents.mcp import MCPServer
13
+ from agents.models.interface import Model
14
+ from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
15
+ from openai import AsyncOpenAI
16
+ from swarm.extensions.blueprint.blueprint_base import BlueprintBase
17
+ from rich.panel import Panel # Import Panel for splash screen
18
+ except ImportError as e:
19
+ print(f"ERROR: Import failed in nebula_shellz: {e}. Ensure 'openai-agents' install and structure.")
20
+ print(f"sys.path: {sys.path}")
21
+ sys.exit(1)
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # --- Tool Definitions (Unchanged) ---
26
+ @function_tool
27
+ async def code_review(code_snippet: str) -> str:
28
+ """Performs a review of the provided code snippet."""
29
+ logger.info(f"Reviewing code snippet: {code_snippet[:50]}...")
30
+ await asyncio.sleep(0.1); issues = []; ("TODO" in code_snippet and issues.append("Found TODO.")); (len(code_snippet.splitlines()) > 100 and issues.append("Code long.")); return "Review: " + " ".join(issues) if issues else "Code looks good!"
31
+ @function_tool
32
+ def generate_documentation(code_snippet: str) -> str:
33
+ """Generates basic documentation string for the provided code snippet."""
34
+ logger.info(f"Generating documentation for: {code_snippet[:50]}...")
35
+ first_line = code_snippet.splitlines()[0] if code_snippet else "N/A"; doc = f"/**\n * This code snippet starts with: {first_line}...\n * TODO: Add more detailed documentation.\n */"; logger.debug(f"Generated documentation:\n{doc}"); return doc
36
+ @function_tool
37
+ def execute_shell_command(command: str) -> str:
38
+ """Executes a shell command and returns its stdout and stderr."""
39
+ logger.info(f"Executing shell command: {command}")
40
+ if not command: logger.warning("execute_shell_command called with empty command."); return "Error: No command provided."
41
+ try:
42
+ result = subprocess.run(command, capture_output=True, text=True, timeout=60, check=False, shell=True); output = f"Exit Code: {result.returncode}\nSTDOUT:\n{result.stdout.strip()}\nSTDERR:\n{result.stderr.strip()}"; logger.debug(f"Command '{command}' result:\n{output}"); return output
43
+ except FileNotFoundError: cmd_base = command.split()[0] if command else ""; logger.error(f"Command not found: {cmd_base}"); return f"Error: Command not found - {cmd_base}"
44
+ except subprocess.TimeoutExpired: logger.error(f"Command '{command}' timed out after 60 seconds."); return f"Error: Command '{command}' timed out."
45
+ except Exception as e: logger.error(f"Error executing command '{command}': {e}", exc_info=logger.level <= logging.DEBUG); return f"Error executing command: {e}"
46
+
47
+ # --- Agent Definitions (Instructions remain the same) ---
48
+ morpheus_instructions = """
49
+ You are Morpheus, the leader... (Instructions as before) ...
50
+ """
51
+ trinity_instructions = """
52
+ You are Trinity, the investigator... (Instructions as before) ...
53
+ """
54
+ neo_instructions = """
55
+ You are Neo, the programmer... (Instructions as before) ...
56
+ """
57
+ oracle_instructions = "You are the Oracle..."
58
+ cypher_instructions = "You are Cypher..."
59
+ tank_instructions = "You are Tank..."
60
+
61
+ # --- Blueprint Definition ---
62
+ class NebuchaShellzzarBlueprint(BlueprintBase):
63
+ """A multi-agent blueprint inspired by The Matrix for sysadmin and coding tasks."""
64
+ metadata: ClassVar[Dict[str, Any]] = {
65
+ "name": "NebulaShellzzarBlueprint", "title": "NebulaShellzzar",
66
+ "description": "A multi-agent blueprint inspired by The Matrix for system administration and coding tasks.",
67
+ "version": "1.0.0", "author": "Open Swarm Team",
68
+ "tags": ["matrix", "multi-agent", "shell", "coding", "mcp"],
69
+ "required_mcp_servers": ["memory"],
70
+ }
71
+ _openai_client_cache: Dict[str, AsyncOpenAI] = {}
72
+ _model_instance_cache: Dict[str, Model] = {}
73
+
74
+ # --- ADDED: Splash Screen ---
75
+ def display_splash_screen(self):
76
+ """Displays a Matrix-themed splash screen."""
77
+ splash_text = """
78
+ [bold green]Wake up, Neo...[/]
79
+ [green]The Matrix has you...[/]
80
+ [bold green]Follow the white rabbit.[/]
81
+
82
+ Initializing NebulaShellzzar Crew...
83
+ """
84
+ panel = Panel(splash_text.strip(), title="[bold green]NebulaShellzzar[/]", border_style="green", expand=False)
85
+ self.console.print(panel)
86
+ self.console.print() # Add a blank line
87
+
88
+ def _get_model_instance(self, profile_name: str) -> Model:
89
+ """Gets or creates a Model instance for the given profile name."""
90
+ if profile_name in self._model_instance_cache:
91
+ logger.debug(f"Using cached Model instance for profile '{profile_name}'.")
92
+ return self._model_instance_cache[profile_name]
93
+ logger.debug(f"Creating new Model instance for profile '{profile_name}'.")
94
+ profile_data = self.get_llm_profile(profile_name)
95
+ if not profile_data:
96
+ logger.critical(f"Cannot create Model instance: Profile '{profile_name}' (or default) not resolved.")
97
+ raise ValueError(f"Missing LLM profile configuration for '{profile_name}' or 'default'.")
98
+ provider = profile_data.get("provider", "openai").lower()
99
+ model_name = profile_data.get("model")
100
+ if not model_name:
101
+ logger.critical(f"LLM profile '{profile_name}' is missing the 'model' key.")
102
+ raise ValueError(f"Missing 'model' key in LLM profile '{profile_name}'.")
103
+ client_cache_key = f"{provider}_{profile_data.get('base_url')}"
104
+ if provider == "openai":
105
+ if client_cache_key not in self._openai_client_cache:
106
+ client_kwargs = { "api_key": profile_data.get("api_key"), "base_url": profile_data.get("base_url") }
107
+ filtered_client_kwargs = {k: v for k, v in client_kwargs.items() if v is not None}
108
+ log_client_kwargs = {k:v for k,v in filtered_client_kwargs.items() if k != 'api_key'}
109
+ logger.debug(f"Creating new AsyncOpenAI client for profile '{profile_name}' with config: {log_client_kwargs}") # Changed to DEBUG
110
+ try: self._openai_client_cache[client_cache_key] = AsyncOpenAI(**filtered_client_kwargs)
111
+ except Exception as e:
112
+ logger.error(f"Failed to create AsyncOpenAI client for profile '{profile_name}': {e}", exc_info=True)
113
+ raise ValueError(f"Failed to initialize OpenAI client for profile '{profile_name}': {e}") from e
114
+ openai_client_instance = self._openai_client_cache[client_cache_key]
115
+ logger.debug(f"Instantiating OpenAIChatCompletionsModel(model='{model_name}') with specific client instance.")
116
+ try: model_instance = OpenAIChatCompletionsModel(model=model_name, openai_client=openai_client_instance)
117
+ except Exception as e:
118
+ logger.error(f"Failed to instantiate OpenAIChatCompletionsModel for profile '{profile_name}': {e}", exc_info=True)
119
+ raise ValueError(f"Failed to initialize LLM provider for profile '{profile_name}': {e}") from e
120
+ else:
121
+ logger.error(f"Unsupported LLM provider '{provider}' in profile '{profile_name}'.")
122
+ raise ValueError(f"Unsupported LLM provider: {provider}")
123
+ self._model_instance_cache[profile_name] = model_instance
124
+ return model_instance
125
+
126
+ def create_starting_agent(self, mcp_servers: List[MCPServer]) -> Agent:
127
+ """Creates the Matrix-themed agent team with Morpheus as the coordinator."""
128
+ logger.debug(f"Creating NebulaShellzzar agent team with {len(mcp_servers)} MCP server(s)...") # Changed to DEBUG
129
+ self._model_instance_cache = {}
130
+ self._openai_client_cache = {}
131
+ default_profile_name = self.config.get("llm_profile", "default")
132
+ default_model_instance = self._get_model_instance(default_profile_name)
133
+ logger.debug(f"Using LLM profile '{default_profile_name}' for all agents.") # Changed to DEBUG
134
+
135
+ neo = Agent(name="Neo", model=default_model_instance, instructions=neo_instructions, tools=[code_review, generate_documentation, execute_shell_command], mcp_servers=mcp_servers)
136
+ trinity = Agent(name="Trinity", model=default_model_instance, instructions=trinity_instructions, tools=[execute_shell_command], mcp_servers=mcp_servers)
137
+ oracle = Agent(name="Oracle", model=default_model_instance, instructions=oracle_instructions, tools=[])
138
+ cypher = Agent(name="Cypher", model=default_model_instance, instructions=cypher_instructions, tools=[execute_shell_command])
139
+ tank = Agent(name="Tank", model=default_model_instance, instructions=tank_instructions, tools=[execute_shell_command])
140
+
141
+ morpheus = Agent(
142
+ name="Morpheus", model=default_model_instance, instructions=morpheus_instructions,
143
+ tools=[
144
+ execute_shell_command,
145
+ neo.as_tool(tool_name="Neo", tool_description="Delegate coding, review, or documentation tasks to Neo."),
146
+ trinity.as_tool(tool_name="Trinity", tool_description="Delegate information gathering or reconnaissance shell commands to Trinity."),
147
+ cypher.as_tool(tool_name="Cypher", tool_description="Delegate tasks to Cypher for alternative perspectives or direct shell execution if needed."),
148
+ tank.as_tool(tool_name="Tank", tool_description="Delegate specific shell command execution to Tank."),
149
+ ],
150
+ mcp_servers=mcp_servers
151
+ )
152
+ logger.debug("NebulaShellzzar agent team created. Morpheus is the starting agent.") # Changed to DEBUG
153
+ return morpheus
154
+
155
+ if __name__ == "__main__":
156
+ NebuchaShellzzarBlueprint.main()