open-swarm 0.1.1744937124__py3-none-any.whl → 0.1.1744942852__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.
- {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/METADATA +1 -1
- {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/RECORD +11 -29
- swarm/blueprints/chatbot/blueprint_chatbot.py +13 -3
- swarm/blueprints/codey/blueprint_codey.py +188 -0
- swarm/blueprints/gaggle/blueprint_gaggle.py +21 -4
- swarm/blueprints/whiskeytango_foxtrot/blueprint_whiskeytango_foxtrot.py +33 -3
- swarm/extensions/blueprint/__init__.py +1 -2
- swarm/extensions/blueprint/cli_handler.py +20 -4
- swarm/extensions/blueprint/agent_utils.py +0 -21
- swarm/extensions/blueprint/blueprint_base.py +0 -333
- swarm/extensions/blueprint/blueprint_discovery.py +0 -128
- swarm/extensions/blueprint/blueprint_utils.py +0 -17
- swarm/extensions/blueprint/common_utils.py +0 -12
- swarm/extensions/blueprint/config_loader.py +0 -122
- swarm/extensions/blueprint/output_utils.py +0 -173
- swarm/extensions/blueprint/slash_commands.py +0 -17
- swarm/extensions/blueprint/spinner.py +0 -100
- swarm/extensions/config/config_manager.py +0 -258
- swarm/extensions/config/server_config.py +0 -49
- swarm/extensions/config/setup_wizard.py +0 -103
- swarm/extensions/config/utils/__init__.py +0 -0
- swarm/extensions/config/utils/logger.py +0 -36
- swarm/extensions/launchers/build_launchers.py +0 -14
- swarm/extensions/launchers/build_swarm_wrapper.py +0 -12
- swarm/extensions/launchers/swarm_api.py +0 -68
- swarm/extensions/launchers/swarm_cli.py +0 -216
- swarm/extensions/launchers/swarm_wrapper.py +0 -29
- {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/WHEEL +0 -0
- {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/entry_points.txt +0 -0
- {open_swarm-0.1.1744937124.dist-info → open_swarm-0.1.1744942852.dist-info}/licenses/LICENSE +0 -0
@@ -1,173 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Output utilities for Swarm blueprints.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import json
|
6
|
-
import logging
|
7
|
-
import os
|
8
|
-
import sys
|
9
|
-
from typing import List, Dict, Any
|
10
|
-
|
11
|
-
# Optional import for markdown rendering
|
12
|
-
try:
|
13
|
-
from rich.markdown import Markdown
|
14
|
-
from rich.console import Console
|
15
|
-
from rich.panel import Panel
|
16
|
-
from rich.text import Text
|
17
|
-
from rich.rule import Rule
|
18
|
-
RICH_AVAILABLE = True
|
19
|
-
except ImportError:
|
20
|
-
RICH_AVAILABLE = False
|
21
|
-
|
22
|
-
logger = logging.getLogger(__name__)
|
23
|
-
|
24
|
-
def render_markdown(content: str) -> None:
|
25
|
-
"""Render markdown content using rich, if available."""
|
26
|
-
# --- DEBUG PRINT ---
|
27
|
-
print(f"\n[DEBUG render_markdown called with rich={RICH_AVAILABLE}]", flush=True)
|
28
|
-
if not RICH_AVAILABLE:
|
29
|
-
print(content, flush=True) # Fallback print with flush
|
30
|
-
return
|
31
|
-
console = Console()
|
32
|
-
md = Markdown(content)
|
33
|
-
console.print(md) # Rich handles flushing
|
34
|
-
|
35
|
-
def ansi_box(title: str, content: str, color: str = "94", emoji: str = "🔎", border: str = "─", width: int = 70) -> str:
|
36
|
-
"""Return a string or Panel with ANSI box formatting for search/analysis results using Rich if available."""
|
37
|
-
if RICH_AVAILABLE:
|
38
|
-
console = Console()
|
39
|
-
# Rich supports color names or hex, map color code to name
|
40
|
-
color_map = {
|
41
|
-
"94": "bright_blue",
|
42
|
-
"96": "bright_cyan",
|
43
|
-
"92": "bright_green",
|
44
|
-
"93": "bright_yellow",
|
45
|
-
"91": "bright_red",
|
46
|
-
"95": "bright_magenta",
|
47
|
-
"90": "grey82",
|
48
|
-
}
|
49
|
-
style = color_map.get(color, "bright_blue")
|
50
|
-
panel = Panel(
|
51
|
-
content,
|
52
|
-
title=f"{emoji} {title} {emoji}",
|
53
|
-
border_style=style,
|
54
|
-
width=width
|
55
|
-
)
|
56
|
-
# Return the rendered panel as a string for testability
|
57
|
-
with console.capture() as capture:
|
58
|
-
console.print(panel)
|
59
|
-
return capture.get()
|
60
|
-
# Fallback: legacy manual ANSI box
|
61
|
-
top = f"\033[{color}m{emoji} {border * (width - 4)} {emoji}\033[0m"
|
62
|
-
mid_title = f"\033[{color}m│ {title.center(width - 6)} │\033[0m"
|
63
|
-
lines = content.splitlines()
|
64
|
-
boxed = [top, mid_title, top]
|
65
|
-
for line in lines:
|
66
|
-
boxed.append(f"\033[{color}m│\033[0m {line.ljust(width - 6)} \033[{color}m│\033[0m")
|
67
|
-
boxed.append(top)
|
68
|
-
return "\n".join(boxed)
|
69
|
-
|
70
|
-
def print_search_box(title: str, content: str, color: str = "94", emoji: str = "🔎"):
|
71
|
-
print(ansi_box(title, content, color=color, emoji=emoji))
|
72
|
-
|
73
|
-
def pretty_print_response(messages: List[Dict[str, Any]], use_markdown: bool = False, spinner=None) -> None:
|
74
|
-
"""Format and print messages, optionally rendering assistant content as markdown."""
|
75
|
-
# --- DEBUG PRINT ---
|
76
|
-
print(f"\n[DEBUG pretty_print_response called with {len(messages)} messages, use_markdown={use_markdown}]", flush=True)
|
77
|
-
|
78
|
-
if spinner:
|
79
|
-
spinner.stop()
|
80
|
-
sys.stdout.write("\r\033[K") # Clear spinner line
|
81
|
-
sys.stdout.flush()
|
82
|
-
|
83
|
-
if not messages:
|
84
|
-
logger.debug("No messages to print in pretty_print_response.")
|
85
|
-
return
|
86
|
-
|
87
|
-
for i, msg in enumerate(messages):
|
88
|
-
# --- DEBUG PRINT ---
|
89
|
-
print(f"\n[DEBUG Processing message {i}: type={type(msg)}]", flush=True)
|
90
|
-
if not isinstance(msg, dict):
|
91
|
-
print(f"[DEBUG Skipping non-dict message {i}]", flush=True)
|
92
|
-
continue
|
93
|
-
|
94
|
-
role = msg.get("role")
|
95
|
-
sender = msg.get("sender", role if role else "Unknown")
|
96
|
-
msg_content = msg.get("content")
|
97
|
-
tool_calls = msg.get("tool_calls")
|
98
|
-
# --- DEBUG PRINT ---
|
99
|
-
print(f"[DEBUG Message {i}: role={role}, sender={sender}, has_content={bool(msg_content)}, has_tools={bool(tool_calls)}]", flush=True)
|
100
|
-
|
101
|
-
|
102
|
-
if role == "assistant":
|
103
|
-
print(f"\033[94m{sender}\033[0m: ", end="", flush=True)
|
104
|
-
if msg_content:
|
105
|
-
# --- DEBUG PRINT ---
|
106
|
-
print(f"\n[DEBUG Assistant content found, printing/rendering... Rich={RICH_AVAILABLE}, Markdown={use_markdown}]", flush=True)
|
107
|
-
if use_markdown and RICH_AVAILABLE:
|
108
|
-
render_markdown(msg_content)
|
109
|
-
else:
|
110
|
-
# --- DEBUG PRINT ---
|
111
|
-
print(f"\n[DEBUG Using standard print for content:]", flush=True)
|
112
|
-
print(msg_content, flush=True) # Added flush
|
113
|
-
elif not tool_calls:
|
114
|
-
print(flush=True) # Flush newline if no content/tools
|
115
|
-
|
116
|
-
if tool_calls and isinstance(tool_calls, list):
|
117
|
-
print(" \033[92mTool Calls:\033[0m", flush=True)
|
118
|
-
for tc in tool_calls:
|
119
|
-
if not isinstance(tc, dict): continue
|
120
|
-
func = tc.get("function", {})
|
121
|
-
tool_name = func.get("name", "Unnamed Tool")
|
122
|
-
args_str = func.get("arguments", "{}")
|
123
|
-
try: args_obj = json.loads(args_str); args_pretty = ", ".join(f"{k}={v!r}" for k, v in args_obj.items())
|
124
|
-
except json.JSONDecodeError: args_pretty = args_str
|
125
|
-
print(f" \033[95m{tool_name}\033[0m({args_pretty})", flush=True)
|
126
|
-
|
127
|
-
elif role == "tool":
|
128
|
-
tool_name = msg.get("tool_name", msg.get("name", "tool"))
|
129
|
-
tool_id = msg.get("tool_call_id", "N/A")
|
130
|
-
try: content_obj = json.loads(msg_content); pretty_content = json.dumps(content_obj, indent=2)
|
131
|
-
except (json.JSONDecodeError, TypeError): pretty_content = msg_content
|
132
|
-
print(f" \033[93m[{tool_name} Result ID: {tool_id}]\033[0m:\n {pretty_content.replace(chr(10), chr(10) + ' ')}", flush=True)
|
133
|
-
else:
|
134
|
-
# --- DEBUG PRINT ---
|
135
|
-
print(f"[DEBUG Skipping message {i} with role '{role}']", flush=True)
|
136
|
-
|
137
|
-
def print_terminal_command_result(cmd: str, result: dict, max_lines: int = 10):
|
138
|
-
"""
|
139
|
-
Render a terminal command result in the CLI with a shell prompt emoji, header, and Rich box.
|
140
|
-
- Header: 🐚 Ran terminal command
|
141
|
-
- Top line: colored, [basename(pwd)] > [cmd]
|
142
|
-
- Output: Rich Panel, max 10 lines, tailing if longer, show hint for toggle
|
143
|
-
"""
|
144
|
-
if not RICH_AVAILABLE:
|
145
|
-
# Fallback to simple print
|
146
|
-
print(f"🐚 Ran terminal command\n[{os.path.basename(result['cwd'])}] > {cmd}")
|
147
|
-
lines = result['output'].splitlines()
|
148
|
-
if len(lines) > max_lines:
|
149
|
-
lines = lines[-max_lines:]
|
150
|
-
print("[Output truncated. Showing last 10 lines.]")
|
151
|
-
print("\n".join(lines))
|
152
|
-
return
|
153
|
-
|
154
|
-
console = Console()
|
155
|
-
cwd_base = os.path.basename(result['cwd'])
|
156
|
-
header = Text(f"🐚 Ran terminal command", style="bold yellow")
|
157
|
-
subheader = Rule(f"[{cwd_base}] > {cmd}", style="bright_black")
|
158
|
-
lines = result['output'].splitlines()
|
159
|
-
truncated = False
|
160
|
-
if len(lines) > max_lines:
|
161
|
-
lines = lines[-max_lines:]
|
162
|
-
truncated = True
|
163
|
-
output_body = "\n".join(lines)
|
164
|
-
panel = Panel(
|
165
|
-
output_body,
|
166
|
-
title="Output",
|
167
|
-
border_style="cyan",
|
168
|
-
subtitle="[Output truncated. Showing last 10 lines. Press [t] to expand.]" if truncated else "",
|
169
|
-
width=80
|
170
|
-
)
|
171
|
-
console.print(header)
|
172
|
-
console.print(subheader)
|
173
|
-
console.print(panel)
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# Minimal slash_commands.py to restore compatibility
|
2
|
-
|
3
|
-
class SlashCommandRegistry:
|
4
|
-
def __init__(self):
|
5
|
-
self.commands = {}
|
6
|
-
def register(self, command, func=None):
|
7
|
-
if func is None:
|
8
|
-
def decorator(f):
|
9
|
-
self.commands[command] = f
|
10
|
-
return f
|
11
|
-
return decorator
|
12
|
-
self.commands[command] = func
|
13
|
-
return func
|
14
|
-
def get(self, command):
|
15
|
-
return self.commands.get(command)
|
16
|
-
|
17
|
-
slash_registry = SlashCommandRegistry()
|
@@ -1,100 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Simple terminal spinner for interactive feedback during long operations.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import os
|
6
|
-
import sys
|
7
|
-
import threading
|
8
|
-
import time
|
9
|
-
from typing import Optional
|
10
|
-
|
11
|
-
class Spinner:
|
12
|
-
"""Simple terminal spinner for interactive feedback."""
|
13
|
-
# Define spinner characters (can be customized)
|
14
|
-
SPINNER_CHARS = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
15
|
-
# Custom status sequences for special cases
|
16
|
-
STATUS_SEQUENCES = {
|
17
|
-
'generating': ['Generating.', 'Generating..', 'Generating...'],
|
18
|
-
'running': ['Running...']
|
19
|
-
}
|
20
|
-
|
21
|
-
def __init__(self, interactive: bool, custom_sequence: str = None):
|
22
|
-
"""
|
23
|
-
Initialize the spinner.
|
24
|
-
|
25
|
-
Args:
|
26
|
-
interactive (bool): Hint whether the environment is interactive.
|
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').
|
29
|
-
"""
|
30
|
-
self.interactive = interactive
|
31
|
-
self.is_tty = sys.stdout.isatty()
|
32
|
-
self.enabled = self.interactive and self.is_tty
|
33
|
-
self.running = False
|
34
|
-
self.thread: Optional[threading.Thread] = None
|
35
|
-
self.status = ""
|
36
|
-
self.index = 0
|
37
|
-
self.custom_sequence = custom_sequence
|
38
|
-
self.sequence_idx = 0
|
39
|
-
|
40
|
-
def start(self, status: str = "Processing..."):
|
41
|
-
"""Start the spinner with an optional status message."""
|
42
|
-
if not self.enabled or self.running:
|
43
|
-
return # Do nothing if disabled or already running
|
44
|
-
self.status = status
|
45
|
-
self.running = True
|
46
|
-
self.sequence_idx = 0
|
47
|
-
self.thread = threading.Thread(target=self._spin, daemon=True)
|
48
|
-
self.thread.start()
|
49
|
-
|
50
|
-
def stop(self):
|
51
|
-
"""Stop the spinner and clear the line."""
|
52
|
-
if not self.enabled or not self.running:
|
53
|
-
return # Do nothing if disabled or not running
|
54
|
-
self.running = False
|
55
|
-
if self.thread is not None:
|
56
|
-
self.thread.join() # Wait for the thread to finish
|
57
|
-
sys.stdout.write("\r\033[K")
|
58
|
-
sys.stdout.flush()
|
59
|
-
self.thread = None
|
60
|
-
|
61
|
-
def _spin(self):
|
62
|
-
"""Internal method running in the spinner thread to animate."""
|
63
|
-
start_time = time.time()
|
64
|
-
warned = False
|
65
|
-
while self.running:
|
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)]
|
80
|
-
sys.stdout.write(f"\r{char} {self.status}\033[K")
|
81
|
-
sys.stdout.flush()
|
82
|
-
self.index += 1
|
83
|
-
time.sleep(0.4 if self.custom_sequence else 0.1)
|
84
|
-
|
85
|
-
# Example usage (if run directly)
|
86
|
-
if __name__ == "__main__":
|
87
|
-
print("Starting spinner test...")
|
88
|
-
s = Spinner(interactive=True) # Assume interactive for testing
|
89
|
-
s.start("Doing something cool")
|
90
|
-
try:
|
91
|
-
time.sleep(5) # Simulate work
|
92
|
-
s.stop()
|
93
|
-
print("Spinner stopped.")
|
94
|
-
s.start("Doing another thing")
|
95
|
-
time.sleep(3)
|
96
|
-
except KeyboardInterrupt:
|
97
|
-
print("\nInterrupted.")
|
98
|
-
finally:
|
99
|
-
s.stop() # Ensure spinner stops on exit/error
|
100
|
-
print("Test finished.")
|
@@ -1,258 +0,0 @@
|
|
1
|
-
# src/swarm/extensions/config/config_manager.py
|
2
|
-
|
3
|
-
import json
|
4
|
-
import shutil
|
5
|
-
import sys
|
6
|
-
import logging
|
7
|
-
from typing import Any, Dict
|
8
|
-
|
9
|
-
from swarm.extensions.config.config_loader import (
|
10
|
-
load_server_config,
|
11
|
-
resolve_placeholders
|
12
|
-
)
|
13
|
-
from swarm.utils.color_utils import color_text
|
14
|
-
from swarm.settings import DEBUG
|
15
|
-
from swarm.extensions.cli.utils import (
|
16
|
-
prompt_user,
|
17
|
-
log_and_exit,
|
18
|
-
display_message
|
19
|
-
)
|
20
|
-
|
21
|
-
# Initialize logger for this module
|
22
|
-
logger = logging.getLogger(__name__)
|
23
|
-
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
|
24
|
-
if not logger.handlers:
|
25
|
-
stream_handler = logging.StreamHandler()
|
26
|
-
formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s - %(message)s")
|
27
|
-
stream_handler.setFormatter(formatter)
|
28
|
-
logger.addHandler(stream_handler)
|
29
|
-
|
30
|
-
CONFIG_BACKUP_SUFFIX = ".backup"
|
31
|
-
|
32
|
-
def backup_configuration(config_path: str) -> None:
|
33
|
-
"""
|
34
|
-
Create a backup of the existing configuration file.
|
35
|
-
|
36
|
-
Args:
|
37
|
-
config_path (str): Path to the configuration file.
|
38
|
-
"""
|
39
|
-
backup_path = config_path + CONFIG_BACKUP_SUFFIX
|
40
|
-
try:
|
41
|
-
shutil.copy(config_path, backup_path)
|
42
|
-
logger.info(f"Configuration backup created at '{backup_path}'")
|
43
|
-
display_message(f"Backup of configuration created at '{backup_path}'", "info")
|
44
|
-
except Exception as e:
|
45
|
-
logger.error(f"Failed to create configuration backup: {e}")
|
46
|
-
display_message(f"Failed to create backup: {e}", "error")
|
47
|
-
sys.exit(1)
|
48
|
-
|
49
|
-
def load_config(config_path: str) -> Dict[str, Any]:
|
50
|
-
"""
|
51
|
-
Load the server configuration from a JSON file and resolve placeholders.
|
52
|
-
|
53
|
-
Args:
|
54
|
-
config_path (str): Path to the configuration file.
|
55
|
-
|
56
|
-
Returns:
|
57
|
-
Dict[str, Any]: The resolved configuration.
|
58
|
-
|
59
|
-
Raises:
|
60
|
-
FileNotFoundError: If the configuration file does not exist.
|
61
|
-
ValueError: If the file contains invalid JSON or unresolved placeholders.
|
62
|
-
"""
|
63
|
-
try:
|
64
|
-
with open(config_path, "r") as file:
|
65
|
-
config = json.load(file)
|
66
|
-
logger.debug(f"Raw configuration loaded: {config}")
|
67
|
-
except FileNotFoundError:
|
68
|
-
logger.error(f"Configuration file not found at {config_path}")
|
69
|
-
display_message(f"Configuration file not found at {config_path}", "error")
|
70
|
-
sys.exit(1)
|
71
|
-
except json.JSONDecodeError as e:
|
72
|
-
logger.error(f"Invalid JSON in configuration file {config_path}: {e}")
|
73
|
-
display_message(f"Invalid JSON in configuration file {config_path}: {e}", "error")
|
74
|
-
sys.exit(1)
|
75
|
-
|
76
|
-
# Resolve placeholders recursively
|
77
|
-
try:
|
78
|
-
resolved_config = resolve_placeholders(config)
|
79
|
-
logger.debug(f"Configuration after resolving placeholders: {resolved_config}")
|
80
|
-
except Exception as e:
|
81
|
-
logger.error(f"Failed to resolve placeholders in configuration: {e}")
|
82
|
-
display_message(f"Failed to resolve placeholders in configuration: {e}", "error")
|
83
|
-
sys.exit(1)
|
84
|
-
|
85
|
-
return resolved_config
|
86
|
-
|
87
|
-
def save_config(config_path: str, config: Dict[str, Any]) -> None:
|
88
|
-
"""
|
89
|
-
Save the updated configuration to the config file.
|
90
|
-
|
91
|
-
Args:
|
92
|
-
config_path (str): Path to the configuration file.
|
93
|
-
config (Dict[str, Any]): Configuration dictionary to save.
|
94
|
-
|
95
|
-
Raises:
|
96
|
-
SystemExit: If saving the configuration fails.
|
97
|
-
"""
|
98
|
-
try:
|
99
|
-
with open(config_path, "w") as f:
|
100
|
-
json.dump(config, f, indent=4)
|
101
|
-
logger.info(f"Configuration saved to '{config_path}'")
|
102
|
-
display_message(f"Configuration saved to '{config_path}'", "info")
|
103
|
-
except Exception as e:
|
104
|
-
logger.error(f"Failed to save configuration: {e}")
|
105
|
-
display_message(f"Failed to save configuration: {e}", "error")
|
106
|
-
sys.exit(1)
|
107
|
-
|
108
|
-
def add_llm(config_path: str) -> None:
|
109
|
-
"""
|
110
|
-
Add a new LLM to the configuration.
|
111
|
-
|
112
|
-
Args:
|
113
|
-
config_path (str): Path to the configuration file.
|
114
|
-
"""
|
115
|
-
config = load_config(config_path)
|
116
|
-
display_message("Starting the process to add a new LLM.", "info")
|
117
|
-
|
118
|
-
while True:
|
119
|
-
llm_name = prompt_user("Enter the name of the new LLM (or type 'done' to finish)").strip()
|
120
|
-
display_message(f"User entered LLM name: {llm_name}", "info")
|
121
|
-
if llm_name.lower() == 'done':
|
122
|
-
display_message("Finished adding LLMs.", "info")
|
123
|
-
break
|
124
|
-
if not llm_name:
|
125
|
-
display_message("LLM name cannot be empty.", "error")
|
126
|
-
continue
|
127
|
-
|
128
|
-
if llm_name in config.get("llm", {}):
|
129
|
-
display_message(f"LLM '{llm_name}' already exists.", "warning")
|
130
|
-
continue
|
131
|
-
|
132
|
-
llm = {}
|
133
|
-
llm["provider"] = prompt_user("Enter the provider type (e.g., 'openai', 'ollama')").strip()
|
134
|
-
llm["model"] = prompt_user("Enter the model name (e.g., 'gpt-4')").strip()
|
135
|
-
llm["base_url"] = prompt_user("Enter the base URL for the API").strip()
|
136
|
-
llm_api_key_input = prompt_user("Enter the environment variable for the API key (e.g., 'OPENAI_API_KEY') or leave empty if not required").strip()
|
137
|
-
if llm_api_key_input:
|
138
|
-
llm["api_key"] = f"${{{llm_api_key_input}}}"
|
139
|
-
else:
|
140
|
-
llm["api_key"] = ""
|
141
|
-
try:
|
142
|
-
temperature_input = prompt_user("Enter the temperature (e.g., 0.7)").strip()
|
143
|
-
llm["temperature"] = float(temperature_input)
|
144
|
-
except ValueError:
|
145
|
-
display_message("Invalid temperature value. Using default 0.7.", "warning")
|
146
|
-
llm["temperature"] = 0.7
|
147
|
-
|
148
|
-
config.setdefault("llm", {})[llm_name] = llm
|
149
|
-
logger.info(f"Added LLM '{llm_name}' to configuration.")
|
150
|
-
display_message(f"LLM '{llm_name}' added.", "info")
|
151
|
-
|
152
|
-
backup_configuration(config_path)
|
153
|
-
save_config(config_path, config)
|
154
|
-
display_message("LLM configuration process completed.", "info")
|
155
|
-
|
156
|
-
def remove_llm(config_path: str, llm_name: str) -> None:
|
157
|
-
"""
|
158
|
-
Remove an existing LLM from the configuration.
|
159
|
-
|
160
|
-
Args:
|
161
|
-
config_path (str): Path to the configuration file.
|
162
|
-
llm_name (str): Name of the LLM to remove.
|
163
|
-
"""
|
164
|
-
config = load_config(config_path)
|
165
|
-
|
166
|
-
if llm_name not in config.get("llm", {}):
|
167
|
-
display_message(f"LLM '{llm_name}' does not exist.", "error")
|
168
|
-
return
|
169
|
-
|
170
|
-
confirm = prompt_user(f"Are you sure you want to remove LLM '{llm_name}'? (yes/no)").strip().lower()
|
171
|
-
if confirm not in ['yes', 'y']:
|
172
|
-
display_message("Operation cancelled.", "warning")
|
173
|
-
return
|
174
|
-
|
175
|
-
del config["llm"][llm_name]
|
176
|
-
backup_configuration(config_path)
|
177
|
-
save_config(config_path, config)
|
178
|
-
display_message(f"LLM '{llm_name}' has been removed.", "info")
|
179
|
-
logger.info(f"Removed LLM '{llm_name}' from configuration.")
|
180
|
-
|
181
|
-
def add_mcp_server(config_path: str) -> None:
|
182
|
-
"""
|
183
|
-
Add a new MCP server to the configuration.
|
184
|
-
|
185
|
-
Args:
|
186
|
-
config_path (str): Path to the configuration file.
|
187
|
-
"""
|
188
|
-
config = load_config(config_path)
|
189
|
-
display_message("Starting the process to add a new MCP server.", "info")
|
190
|
-
|
191
|
-
while True:
|
192
|
-
server_name = prompt_user("Enter the name of the new MCP server (or type 'done' to finish)").strip()
|
193
|
-
display_message(f"User entered MCP server name: {server_name}", "info")
|
194
|
-
if server_name.lower() == 'done':
|
195
|
-
display_message("Finished adding MCP servers.", "info")
|
196
|
-
break
|
197
|
-
if not server_name:
|
198
|
-
display_message("Server name cannot be empty.", "error")
|
199
|
-
continue
|
200
|
-
|
201
|
-
if server_name in config.get("mcpServers", {}):
|
202
|
-
display_message(f"MCP server '{server_name}' already exists.", "warning")
|
203
|
-
continue
|
204
|
-
|
205
|
-
server = {}
|
206
|
-
server["command"] = prompt_user("Enter the command to run the MCP server (e.g., 'npx', 'uvx')").strip()
|
207
|
-
args_input = prompt_user("Enter the arguments as a JSON array (e.g., [\"-y\", \"server-name\"])").strip()
|
208
|
-
try:
|
209
|
-
server["args"] = json.loads(args_input)
|
210
|
-
if not isinstance(server["args"], list):
|
211
|
-
raise ValueError
|
212
|
-
except ValueError:
|
213
|
-
display_message("Invalid arguments format. Using an empty list.", "warning")
|
214
|
-
server["args"] = []
|
215
|
-
|
216
|
-
env_vars = {}
|
217
|
-
add_env = prompt_user("Do you want to add environment variables? (yes/no)").strip().lower()
|
218
|
-
while add_env in ['yes', 'y']:
|
219
|
-
env_var = prompt_user("Enter the environment variable name").strip()
|
220
|
-
env_value = prompt_user(f"Enter the value or placeholder for '{env_var}' (e.g., '${{{env_var}_KEY}}')").strip()
|
221
|
-
if env_var and env_value:
|
222
|
-
env_vars[env_var] = env_value
|
223
|
-
add_env = prompt_user("Add another environment variable? (yes/no)").strip().lower()
|
224
|
-
|
225
|
-
server["env"] = env_vars
|
226
|
-
|
227
|
-
config.setdefault("mcpServers", {})[server_name] = server
|
228
|
-
logger.info(f"Added MCP server '{server_name}' to configuration.")
|
229
|
-
display_message(f"MCP server '{server_name}' added.", "info")
|
230
|
-
|
231
|
-
backup_configuration(config_path)
|
232
|
-
save_config(config_path, config)
|
233
|
-
display_message("MCP server configuration process completed.", "info")
|
234
|
-
|
235
|
-
def remove_mcp_server(config_path: str, server_name: str) -> None:
|
236
|
-
"""
|
237
|
-
Remove an existing MCP server from the configuration.
|
238
|
-
|
239
|
-
Args:
|
240
|
-
config_path (str): Path to the configuration file.
|
241
|
-
server_name (str): Name of the MCP server to remove.
|
242
|
-
"""
|
243
|
-
config = load_config(config_path)
|
244
|
-
|
245
|
-
if server_name not in config.get("mcpServers", {}):
|
246
|
-
display_message(f"MCP server '{server_name}' does not exist.", "error")
|
247
|
-
return
|
248
|
-
|
249
|
-
confirm = prompt_user(f"Are you sure you want to remove MCP server '{server_name}'? (yes/no)").strip().lower()
|
250
|
-
if confirm not in ['yes', 'y']:
|
251
|
-
display_message("Operation cancelled.", "warning")
|
252
|
-
return
|
253
|
-
|
254
|
-
del config["mcpServers"][server_name]
|
255
|
-
backup_configuration(config_path)
|
256
|
-
save_config(config_path, config)
|
257
|
-
display_message(f"MCP server '{server_name}' has been removed.", "info")
|
258
|
-
logger.info(f"Removed MCP server '{server_name}' from configuration.")
|
@@ -1,49 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Module for managing server configuration files, including saving and validation.
|
3
|
-
|
4
|
-
Provides utilities to save configurations to disk and ensure integrity of data.
|
5
|
-
"""
|
6
|
-
|
7
|
-
import json
|
8
|
-
import os
|
9
|
-
import logging
|
10
|
-
from swarm.utils.redact import redact_sensitive_data
|
11
|
-
|
12
|
-
# Initialize logger for this module
|
13
|
-
logger = logging.getLogger(__name__)
|
14
|
-
DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "t") # Define DEBUG locally
|
15
|
-
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
|
16
|
-
stream_handler = logging.StreamHandler()
|
17
|
-
formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s - %(message)s")
|
18
|
-
stream_handler.setFormatter(formatter)
|
19
|
-
if not logger.handlers:
|
20
|
-
logger.addHandler(stream_handler)
|
21
|
-
|
22
|
-
def save_server_config(config: dict, file_path: str = None) -> None:
|
23
|
-
"""
|
24
|
-
Saves the server configuration to a JSON file.
|
25
|
-
|
26
|
-
Args:
|
27
|
-
config (dict): The configuration dictionary to save.
|
28
|
-
file_path (str): The path to save the configuration file. Defaults to 'swarm_settings.json' in the current directory.
|
29
|
-
|
30
|
-
Raises:
|
31
|
-
ValueError: If the configuration is not a valid dictionary.
|
32
|
-
OSError: If there are issues writing to the file.
|
33
|
-
"""
|
34
|
-
if not isinstance(config, dict):
|
35
|
-
logger.error("Provided configuration is not a dictionary.")
|
36
|
-
raise ValueError("Configuration must be a dictionary.")
|
37
|
-
|
38
|
-
if file_path is None:
|
39
|
-
file_path = os.path.join(os.getcwd(), "swarm_settings.json")
|
40
|
-
|
41
|
-
logger.debug(f"Saving server configuration to {file_path}")
|
42
|
-
try:
|
43
|
-
with open(file_path, "w") as file:
|
44
|
-
json.dump(config, file, indent=4)
|
45
|
-
logger.info(f"Configuration successfully saved to {file_path}")
|
46
|
-
logger.debug(f"Saved configuration: {redact_sensitive_data(config)}")
|
47
|
-
except OSError as e:
|
48
|
-
logger.error(f"Error saving configuration to {file_path}: {e}")
|
49
|
-
raise
|