open-swarm 0.1.1744936173__py3-none-any.whl → 0.1.1744936297__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 (27) hide show
  1. {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/METADATA +1 -1
  2. {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/RECORD +27 -27
  3. {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/entry_points.txt +1 -0
  4. swarm/blueprints/digitalbutlers/blueprint_digitalbutlers.py +28 -0
  5. swarm/blueprints/divine_code/blueprint_divine_code.py +26 -0
  6. swarm/blueprints/django_chat/blueprint_django_chat.py +15 -4
  7. swarm/blueprints/echocraft/blueprint_echocraft.py +9 -2
  8. swarm/blueprints/family_ties/blueprint_family_ties.py +28 -0
  9. swarm/blueprints/gaggle/blueprint_gaggle.py +117 -15
  10. swarm/blueprints/monkai_magic/blueprint_monkai_magic.py +10 -0
  11. swarm/blueprints/nebula_shellz/blueprint_nebula_shellz.py +47 -29
  12. swarm/blueprints/omniplex/blueprint_omniplex.py +21 -0
  13. swarm/blueprints/rue_code/blueprint_rue_code.py +24 -25
  14. swarm/blueprints/suggestion/blueprint_suggestion.py +35 -12
  15. swarm/consumers.py +19 -0
  16. swarm/extensions/blueprint/agent_utils.py +1 -1
  17. swarm/extensions/blueprint/blueprint_base.py +265 -43
  18. swarm/extensions/blueprint/blueprint_discovery.py +13 -11
  19. swarm/extensions/blueprint/cli_handler.py +33 -55
  20. swarm/extensions/blueprint/output_utils.py +78 -0
  21. swarm/extensions/blueprint/spinner.py +30 -21
  22. swarm/extensions/cli/cli_args.py +6 -0
  23. swarm/extensions/config/config_loader.py +4 -1
  24. swarm/llm/chat_completion.py +31 -1
  25. swarm/settings.py +6 -7
  26. {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/WHEEL +0 -0
  27. {open_swarm-0.1.1744936173.dist-info → open_swarm-0.1.1744936297.dist-info}/licenses/LICENSE +0 -0
@@ -6,29 +6,36 @@ import os
6
6
  import sys
7
7
  import threading
8
8
  import time
9
+ from typing import Optional
9
10
 
10
11
  class Spinner:
11
12
  """Simple terminal spinner for interactive feedback."""
12
13
  # Define spinner characters (can be customized)
13
14
  SPINNER_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
14
- # SPINNER_CHARS = ['|', '/', '-', '\\'] # Simpler alternative
15
+ # Custom status sequences for special cases
16
+ STATUS_SEQUENCES = {
17
+ 'generating': ['Generating.', 'Generating..', 'Generating...'],
18
+ 'running': ['Running...']
19
+ }
15
20
 
16
- def __init__(self, interactive: bool):
21
+ def __init__(self, interactive: bool, custom_sequence: str = None):
17
22
  """
18
23
  Initialize the spinner.
19
24
 
20
25
  Args:
21
26
  interactive (bool): Hint whether the environment is interactive.
22
27
  Spinner is disabled if False or if output is not a TTY.
28
+ custom_sequence (str): Optional name for a custom status sequence (e.g., 'generating', 'running').
23
29
  """
24
30
  self.interactive = interactive
25
- # Check if output is a TTY (terminal) and interactive flag is True
26
31
  self.is_tty = sys.stdout.isatty()
27
32
  self.enabled = self.interactive and self.is_tty
28
33
  self.running = False
29
34
  self.thread: Optional[threading.Thread] = None
30
35
  self.status = ""
31
36
  self.index = 0
37
+ self.custom_sequence = custom_sequence
38
+ self.sequence_idx = 0
32
39
 
33
40
  def start(self, status: str = "Processing..."):
34
41
  """Start the spinner with an optional status message."""
@@ -36,7 +43,7 @@ class Spinner:
36
43
  return # Do nothing if disabled or already running
37
44
  self.status = status
38
45
  self.running = True
39
- # Run the spinner animation in a separate daemon thread
46
+ self.sequence_idx = 0
40
47
  self.thread = threading.Thread(target=self._spin, daemon=True)
41
48
  self.thread.start()
42
49
 
@@ -47,30 +54,33 @@ class Spinner:
47
54
  self.running = False
48
55
  if self.thread is not None:
49
56
  self.thread.join() # Wait for the thread to finish
50
- # Clear the spinner line using ANSI escape codes
51
- # \r: Carriage return (move cursor to beginning of line)
52
- # \033[K: Clear line from cursor to end
53
57
  sys.stdout.write("\r\033[K")
54
58
  sys.stdout.flush()
55
- self.thread = None # Reset thread
59
+ self.thread = None
56
60
 
57
61
  def _spin(self):
58
62
  """Internal method running in the spinner thread to animate."""
63
+ start_time = time.time()
64
+ warned = False
59
65
  while self.running:
60
- # Get the next spinner character
61
- char = self.SPINNER_CHARS[self.index % len(self.SPINNER_CHARS)]
62
- # Write spinner char and status, overwrite previous line content
63
- try:
64
- # \r moves cursor to beginning, \033[K clears the rest of the line
66
+ elapsed = time.time() - start_time
67
+ if self.custom_sequence and self.custom_sequence in self.STATUS_SEQUENCES:
68
+ seq = self.STATUS_SEQUENCES[self.custom_sequence]
69
+ # If taking longer than 10s, show special message
70
+ if elapsed > 10 and not warned:
71
+ msg = f"{seq[-1]} Taking longer than expected"
72
+ warned = True
73
+ else:
74
+ msg = seq[self.sequence_idx % len(seq)]
75
+ sys.stdout.write(f"\r{msg}\033[K")
76
+ sys.stdout.flush()
77
+ self.sequence_idx += 1
78
+ else:
79
+ char = self.SPINNER_CHARS[self.index % len(self.SPINNER_CHARS)]
65
80
  sys.stdout.write(f"\r{char} {self.status}\033[K")
66
81
  sys.stdout.flush()
67
- except BlockingIOError:
68
- # Handle potential issues if stdout is blocked (less likely for TTY)
69
- time.sleep(0.1)
70
- continue
71
- self.index += 1
72
- # Pause for animation effect
73
- time.sleep(0.1)
82
+ self.index += 1
83
+ time.sleep(0.4 if self.custom_sequence else 0.1)
74
84
 
75
85
  # Example usage (if run directly)
76
86
  if __name__ == "__main__":
@@ -88,4 +98,3 @@ if __name__ == "__main__":
88
98
  finally:
89
99
  s.stop() # Ensure spinner stops on exit/error
90
100
  print("Test finished.")
91
-
@@ -1,8 +1,14 @@
1
1
  # src/swarm/extensions/blueprint/modes/cli_mode/cli_args.py
2
2
 
3
3
  import argparse
4
+ import os
5
+ import sys
4
6
  from typing import Namespace
5
7
 
8
+ # --- DEBUG PRINTS REMOVED BY CASCADE ---
9
+ # print(f"[DEBUG] cli_args.py startup: sys.argv={sys.argv}")
10
+ # print(f"[DEBUG] cli_args.py startup: LITELLM_MODEL={os.environ.get('LITELLM_MODEL')}, DEFAULT_LLM={os.environ.get('DEFAULT_LLM')}")
11
+
6
12
  def parse_arguments() -> Namespace:
7
13
  """
8
14
  Parse command-line arguments for dynamic LLM configuration, MCP server management, and other overrides.
@@ -57,6 +57,10 @@ def _substitute_env_vars_recursive(data: Any) -> Any:
57
57
  if isinstance(data,str): return os.path.expandvars(data)
58
58
  return data
59
59
 
60
+ def _substitute_env_vars(data: Any) -> Any:
61
+ """Public API: Recursively substitute environment variables in dict, list, str."""
62
+ return _substitute_env_vars_recursive(data)
63
+
60
64
  def create_default_config(config_path: Path):
61
65
  """Creates a default configuration file with valid JSON."""
62
66
  default_config = {
@@ -88,4 +92,3 @@ def create_default_config(config_path: Path):
88
92
  except Exception as e:
89
93
  logger.error(f"Failed to create default config file at {config_path}: {e}", exc_info=True)
90
94
  raise
91
-
@@ -18,7 +18,6 @@ from ..utils.redact import redact_sensitive_data
18
18
  from ..utils.general_utils import serialize_datetime
19
19
  from ..utils.message_utils import filter_duplicate_system_messages, update_null_content
20
20
  from ..utils.context_utils import get_token_count, truncate_message_history
21
- # --- REMOVED import: from ..utils.message_sequence import repair_message_payload ---
22
21
 
23
22
  # Configure module-level logging
24
23
  logger = logging.getLogger(__name__)
@@ -29,6 +28,29 @@ if not logger.handlers:
29
28
  stream_handler.setFormatter(formatter)
30
29
  logger.addHandler(stream_handler)
31
30
 
31
+ # --- PATCH: Suppress OpenAI tracing/telemetry errors if using LiteLLM/custom endpoint ---
32
+ import logging
33
+ import os
34
+ if os.environ.get("LITELLM_BASE_URL") or os.environ.get("OPENAI_BASE_URL"):
35
+ # Silence openai.agents tracing/telemetry errors
36
+ logging.getLogger("openai.agents").setLevel(logging.CRITICAL)
37
+ try:
38
+ import openai.agents.tracing
39
+ openai.agents.tracing.TracingClient = lambda *a, **kw: None
40
+ except Exception:
41
+ pass
42
+
43
+ # --- PATCH: Enforce custom endpoint, never fallback to OpenAI if custom base_url is set ---
44
+ def _enforce_litellm_only(client):
45
+ # If client has a base_url attribute, check it
46
+ base_url = getattr(client, 'base_url', None)
47
+ if base_url and 'openai.com' in base_url:
48
+ return # Using OpenAI, allowed
49
+ if base_url and 'openai.com' not in base_url:
50
+ # If any fallback to OpenAI API is attempted, raise error
51
+ import traceback
52
+ raise RuntimeError(f"Attempted fallback to OpenAI API when custom base_url is set! base_url={base_url}\n{traceback.format_stack()}")
53
+
32
54
 
33
55
  async def get_chat_completion(
34
56
  client: AsyncOpenAI,
@@ -44,6 +66,7 @@ async def get_chat_completion(
44
66
  stream: bool = False,
45
67
  debug: bool = False
46
68
  ) -> Union[Dict[str, Any], AsyncGenerator[Any, None]]:
69
+ _enforce_litellm_only(client)
47
70
  """
48
71
  Retrieve a chat completion from the LLM for the given agent and history.
49
72
  Relies on openai-agents Runner for actual execution, this might become deprecated.
@@ -62,6 +85,12 @@ async def get_chat_completion(
62
85
  redacted_kwargs = redact_sensitive_data(client_kwargs, sensitive_keys=["api_key"])
63
86
  logger.debug(f"Using client with model='{active_model}', base_url='{client_kwargs.get('base_url', 'default')}', api_key={redacted_kwargs['api_key']}")
64
87
 
88
+ # --- ENFORCE: Disallow fallback to OpenAI if custom base_url is set ---
89
+ if client_kwargs.get("base_url") and "openai.com" not in client_kwargs["base_url"]:
90
+ # If the base_url is set and is not OpenAI, ensure no fallback to OpenAI API
91
+ if "openai.com" in os.environ.get("OPENAI_API_BASE", ""):
92
+ raise RuntimeError(f"[SECURITY] Fallback to OpenAI API attempted with base_url={client_kwargs['base_url']}. Refusing for safety.")
93
+
65
94
  context_variables = defaultdict(str, context_variables)
66
95
  instructions = agent.instructions(context_variables) if callable(agent.instructions) else agent.instructions
67
96
  if not isinstance(instructions, str):
@@ -152,6 +181,7 @@ async def get_chat_completion_message(
152
181
  stream: bool = False,
153
182
  debug: bool = False
154
183
  ) -> Union[Dict[str, Any], AsyncGenerator[Any, None]]:
184
+ _enforce_litellm_only(client)
155
185
  """
156
186
  Wrapper to retrieve and validate a chat completion message (returns dict or stream).
157
187
  Relies on openai-agents Runner for actual execution, this might become deprecated.
swarm/settings.py CHANGED
@@ -12,7 +12,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # Points to src/
12
12
  # --- Load .env file ---
13
13
  dotenv_path = BASE_DIR.parent / '.env'
14
14
  load_dotenv(dotenv_path=dotenv_path)
15
- print(f"[Settings] Attempted to load .env from: {dotenv_path}")
15
+ # print(f"[Settings] Attempted to load .env from: {dotenv_path}")
16
16
  # ---
17
17
 
18
18
  SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'django-insecure-fallback-key-for-dev')
@@ -30,12 +30,12 @@ SWARM_API_KEY = _raw_api_token # Assign the loaded token (or None)
30
30
  if ENABLE_API_AUTH:
31
31
  # Add assertion to satisfy type checkers within this block
32
32
  assert SWARM_API_KEY is not None, "SWARM_API_KEY cannot be None when ENABLE_API_AUTH is True"
33
- print(f"[Settings] SWARM_API_KEY loaded: {SWARM_API_KEY[:4]}...{SWARM_API_KEY[-4:]}")
34
- print("[Settings] ENABLE_API_AUTH is True.")
33
+ # print(f"[Settings] SWARM_API_KEY loaded: {SWARM_API_KEY[:4]}...{SWARM_API_KEY[-4:]}")
34
+ # print("[Settings] ENABLE_API_AUTH is True.")
35
35
  else:
36
- print("[Settings] API_AUTH_TOKEN env var not set. SWARM_API_KEY is None.")
37
- print("[Settings] ENABLE_API_AUTH is False.")
38
-
36
+ # print("[Settings] API_AUTH_TOKEN env var not set. SWARM_API_KEY is None.")
37
+ # print("[Settings] ENABLE_API_AUTH is False.")
38
+ pass
39
39
 
40
40
  SWARM_CONFIG_PATH = os.getenv('SWARM_CONFIG_PATH', str(BASE_DIR.parent / 'swarm_config.json'))
41
41
  BLUEPRINT_DIRECTORY = os.getenv('BLUEPRINT_DIRECTORY', str(BASE_DIR / 'swarm' / 'blueprints'))
@@ -175,4 +175,3 @@ LOGIN_URL = '/login/'
175
175
  LOGIN_REDIRECT_URL = '/'
176
176
  LOGOUT_REDIRECT_URL = '/'
177
177
  CSRF_TRUSTED_ORIGINS = os.getenv('DJANGO_CSRF_TRUSTED_ORIGINS', 'http://localhost:8000,http://127.0.0.1:8000').split(',')
178
-