open-swarm 0.1.1743070217__py3-none-any.whl → 0.1.1743364176__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.1743364176.dist-info/METADATA +286 -0
  2. open_swarm-0.1.1743364176.dist-info/RECORD +260 -0
  3. {open_swarm-0.1.1743070217.dist-info → open_swarm-0.1.1743364176.dist-info}/WHEEL +1 -2
  4. open_swarm-0.1.1743364176.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.1743364176.dist-info}/licenses/LICENSE +0 -0
@@ -1,208 +1,91 @@
1
- """
2
- Configuration Loader for Open Swarm MCP Framework.
3
- """
4
-
5
- import os
6
1
  import json
7
- import re
8
- import logging
9
- from typing import Any, Dict, List, Tuple, Optional
2
+ import os
10
3
  from pathlib import Path
11
- from dotenv import load_dotenv
12
- try: from .server_config import save_server_config
13
- except ImportError: save_server_config = None
14
-
15
- SWARM_DEBUG = os.getenv("SWARM_DEBUG", "False").lower() in ("true", "1", "yes")
16
- try: from swarm.settings import BASE_DIR
17
- except ImportError: BASE_DIR = Path(__file__).resolve().parent.parent.parent
18
-
19
- from swarm.utils.redact import redact_sensitive_data
4
+ import logging
5
+ from typing import Dict, Any, Optional
20
6
 
21
7
  logger = logging.getLogger(__name__)
22
- logger.setLevel(logging.DEBUG if SWARM_DEBUG else logging.INFO)
23
8
 
24
- # Add handler only if needed, DO NOT set handler level conditionally here
25
- if not logger.handlers and not logging.getLogger().hasHandlers():
26
- stream_handler = logging.StreamHandler()
27
- formatter = logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s:%(lineno)d - %(message)s")
28
- stream_handler.setFormatter(formatter)
29
- logger.addHandler(stream_handler)
30
- # --- REMOVED CONDITIONAL HANDLER LEVEL SETTING ---
31
- # if not SWARM_DEBUG:
32
- # stream_handler.setLevel(logging.WARNING)
33
-
34
- config: Dict[str, Any] = {}
35
- load_dotenv()
36
- logger.debug("Environment variables potentially loaded from .env file.")
37
-
38
- def process_config(config_dict: dict) -> dict:
39
- """Processes config: resolves placeholders, merges external MCP."""
9
+ DEFAULT_CONFIG_FILENAME = "swarm_config.json"
10
+
11
+ # --- find_config_file, load_config, save_config, validate_config, get_profile_from_config, _substitute_env_vars_recursive ---
12
+ # (Keep these functions as they were)
13
+ def find_config_file( specific_path: Optional[str]=None, start_dir: Optional[Path]=None, default_dir: Optional[Path]=None,) -> Optional[Path]:
14
+ if specific_path: p=Path(specific_path); return p.resolve() if p.is_file() else logger.warning(f"Specified config path DNE: {specific_path}") or None # Fall through
15
+ if start_dir:
16
+ current=start_dir.resolve()
17
+ while current != current.parent:
18
+ if (cp := current / DEFAULT_CONFIG_FILENAME).is_file(): logger.debug(f"Found config upwards: {cp}"); return cp.resolve()
19
+ current = current.parent
20
+ if (cp := current / DEFAULT_CONFIG_FILENAME).is_file(): logger.debug(f"Found config at root: {cp}"); return cp.resolve()
21
+ if default_dir and (cp := default_dir.resolve() / DEFAULT_CONFIG_FILENAME).is_file(): logger.debug(f"Found config default: {cp}"); return cp.resolve()
22
+ cwd=Path.cwd();
23
+ if start_dir is None or cwd != start_dir.resolve():
24
+ if (cp := cwd / DEFAULT_CONFIG_FILENAME).is_file(): logger.debug(f"Found config cwd: {cp}"); return cp.resolve()
25
+ logger.debug(f"Config '{DEFAULT_CONFIG_FILENAME}' not found."); return None
26
+
27
+ def load_config(config_path: Path) -> Dict[str, Any]:
28
+ logger.debug(f"Loading config from {config_path}")
40
29
  try:
41
- resolved_config = resolve_placeholders(config_dict)
42
- if logger.isEnabledFor(logging.DEBUG): logger.debug("Config after resolving placeholders: " + json.dumps(redact_sensitive_data(resolved_config), indent=2))
43
- disable_merge = os.getenv("DISABLE_MCP_MERGE", "false").lower() in ("true", "1", "yes")
44
- if not disable_merge:
45
- if os.name == "nt": external_mcp_path = Path(os.getenv("APPDATA", Path.home())) / "Claude" / "claude_desktop_config.json"
46
- else: external_mcp_path = Path.home() / ".vscode-server" / "data" / "User" / "globalStorage" / "rooveterinaryinc.roo-cline" / "settings" / "cline_mcp_settings.json"
47
- if external_mcp_path.exists():
48
- logger.info(f"Found external MCP settings file at: {external_mcp_path}")
49
- try:
50
- with open(external_mcp_path, "r", encoding='utf-8') as mcp_file: external_mcp_config = json.load(mcp_file)
51
- if logger.isEnabledFor(logging.DEBUG): logger.debug("Loaded external MCP settings: " + json.dumps(redact_sensitive_data(external_mcp_config), indent=2))
52
- main_mcp_servers = resolved_config.get("mcpServers", {}); external_mcp_servers = external_mcp_config.get("mcpServers", {})
53
- merged_mcp_servers = main_mcp_servers.copy(); servers_added_count = 0
54
- for name, server_cfg in external_mcp_servers.items():
55
- if name not in merged_mcp_servers and not server_cfg.get("disabled", False): merged_mcp_servers[name] = server_cfg; servers_added_count += 1
56
- if servers_added_count > 0: resolved_config["mcpServers"] = merged_mcp_servers; logger.info(f"Merged {servers_added_count} MCP servers.");
57
- else: logger.debug("No new MCP servers added from external settings.")
58
- except Exception as merge_err: logger.error(f"Failed to load/merge MCP settings from '{external_mcp_path}': {merge_err}", exc_info=logger.isEnabledFor(logging.DEBUG))
59
- else: logger.debug(f"External MCP settings file not found at {external_mcp_path}. Skipping merge.")
60
- else: logger.debug("MCP settings merge disabled.")
61
- except Exception as e: logger.error(f"Failed during config processing: {e}", exc_info=logger.isEnabledFor(logging.DEBUG)); raise
62
- globals()["config"] = resolved_config
63
- return resolved_config
64
-
65
- def resolve_placeholders(obj: Any) -> Any:
66
- """Recursively resolve ${VAR_NAME} placeholders."""
67
- if isinstance(obj, dict): return {k: resolve_placeholders(v) for k, v in obj.items()}
68
- elif isinstance(obj, list): return [resolve_placeholders(item) for item in obj]
69
- elif isinstance(obj, str):
70
- pattern = re.compile(r'\$\{(\w+(?:[_-]\w+)*)\}')
71
- resolved_string = obj; any_unresolved = False
72
- for var_name in pattern.findall(obj):
73
- env_value = os.getenv(var_name); placeholder = f'${{{var_name}}}'
74
- if env_value is None:
75
- log_level = logging.DEBUG
76
- if resolved_string == placeholder:
77
- log_level = logging.WARNING
78
- resolved_string = None
79
- any_unresolved = True
80
- logger.log(log_level, f"Env var '{var_name}' not set for placeholder '{placeholder}'. Resolving to None.")
81
- return None
82
- else:
83
- resolved_string = resolved_string.replace(placeholder, "")
84
- any_unresolved = True
85
- logger.log(log_level, f"Env var '{var_name}' not set for placeholder '{placeholder}'. Removing from string.")
86
- else:
87
- resolved_string = resolved_string.replace(placeholder, env_value)
88
- if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Resolved placeholder '{placeholder}' using env var '{var_name}'.")
89
- if any_unresolved and resolved_string is not None:
90
- logger.debug(f"String '{obj}' contained unresolved placeholders. Result: '{resolved_string}'")
91
- return resolved_string
92
- else: return obj
93
-
94
- def load_server_config(file_path: Optional[str] = None) -> dict:
95
- """Loads, resolves, and merges server config from JSON file."""
96
- config_path: Optional[Path] = None
97
- if file_path:
98
- path_obj = Path(file_path)
99
- if path_obj.is_file(): config_path = path_obj; logger.info(f"Using provided config file path: {config_path}")
100
- else: logger.warning(f"Provided path '{file_path}' not found. Searching standard locations.")
101
- if not config_path:
102
- standard_paths = [ Path.cwd() / "swarm_config.json", Path(BASE_DIR) / "swarm_config.json", Path.home() / ".swarm" / "swarm_config.json" ]
103
- config_path = next((p for p in standard_paths if p.is_file()), None)
104
- if not config_path: raise FileNotFoundError(f"Config file 'swarm_config.json' not found. Checked: {[str(p) for p in standard_paths]}")
105
- logger.info(f"Using config file found at: {config_path}")
30
+ with open(config_path, 'r') as f: config = json.load(f)
31
+ logger.info(f"Loaded config from {config_path}"); validate_config(config); return config
32
+ except FileNotFoundError: logger.error(f"Config DNE: {config_path}"); raise
33
+ except json.JSONDecodeError as e: logger.error(f"JSON error {config_path}: {e}"); raise ValueError(f"Invalid JSON: {config_path}") from e
34
+ except Exception as e: logger.error(f"Load error {config_path}: {e}"); raise
35
+
36
+ def save_config(config: Dict[str, Any], config_path: Path):
37
+ logger.info(f"Saving config to {config_path}")
38
+ try: config_path.parent.mkdir(parents=True,exist_ok=True); f = config_path.open('w'); json.dump(config, f, indent=4); f.close(); logger.debug("Save OK.")
39
+ except Exception as e: logger.error(f"Save failed {config_path}: {e}", exc_info=True); raise
40
+
41
+ def validate_config(config: Dict[str, Any]):
42
+ logger.debug("Validating config structure...")
43
+ if "llm" not in config or not isinstance(config["llm"],dict): raise ValueError("Config 'llm' section missing/malformed.")
44
+ for name, prof in config.get("llm",{}).items():
45
+ if not isinstance(prof,dict): raise ValueError(f"LLM profile '{name}' not dict.")
46
+ logger.debug("Config basic structure OK.")
47
+
48
+ def get_profile_from_config(config: Dict[str, Any], profile_name: str) -> Dict[str, Any]:
49
+ profile_data = config.get("llm", {}).get(profile_name)
50
+ if profile_data is None: raise ValueError(f"LLM profile '{profile_name}' not found.")
51
+ if not isinstance(profile_data, dict): raise ValueError(f"LLM profile '{profile_name}' not dict.")
52
+ return _substitute_env_vars_recursive(profile_data)
53
+
54
+ def _substitute_env_vars_recursive(data: Any) -> Any:
55
+ if isinstance(data,dict): return {k:_substitute_env_vars_recursive(v) for k,v in data.items()}
56
+ if isinstance(data,list): return [_substitute_env_vars_recursive(i) for i in data]
57
+ if isinstance(data,str): return os.path.expandvars(data)
58
+ return data
59
+
60
+ def create_default_config(config_path: Path):
61
+ """Creates a default configuration file with valid JSON."""
62
+ default_config = {
63
+ "llm": {
64
+ "default": {
65
+ "provider": "openai",
66
+ "model": "gpt-4o",
67
+ "api_key": "${OPENAI_API_KEY}",
68
+ "base_url": None,
69
+ "description": "Default OpenAI profile. Requires OPENAI_API_KEY env var."
70
+ },
71
+ "ollama_example": {
72
+ "provider": "ollama",
73
+ "model": "llama3",
74
+ "api_key": "ollama", # Usually not needed
75
+ "base_url": "http://localhost:11434",
76
+ "description": "Example for local Ollama Llama 3 model."
77
+ }
78
+ },
79
+ "agents": {},
80
+ "settings": {
81
+ "default_markdown_output": True
82
+ }
83
+ }
84
+ logger.info(f"Creating default configuration file at {config_path}")
106
85
  try:
107
- raw_config = json.loads(config_path.read_text(encoding='utf-8'))
108
- if logger.isEnabledFor(logging.DEBUG): logger.debug(f"Raw config loaded: {redact_sensitive_data(raw_config)}")
109
- processed_config = process_config(raw_config)
110
- globals()["config"] = processed_config
111
- logger.info(f"Config loaded and processed from {config_path}")
112
- return processed_config
113
- except json.JSONDecodeError as e: logger.critical(f"Invalid JSON in {config_path}: {e}"); raise ValueError(f"Invalid JSON") from e
114
- except Exception as e: logger.critical(f"Failed to read/process config {config_path}: {e}"); raise ValueError("Failed to load/process config") from e
115
-
116
- def load_llm_config(config_dict: Optional[Dict[str, Any]] = None, llm_name: Optional[str] = None) -> Dict[str, Any]:
117
- """Loads, validates, and resolves API keys for a specific LLM profile."""
118
- if config_dict is None:
119
- global_config = globals().get("config")
120
- if not global_config:
121
- try: config_dict = load_server_config(); globals()["config"] = config_dict
122
- except Exception as e: raise ValueError("Global config not loaded and no config_dict provided.") from e
123
- else: config_dict = global_config
124
-
125
- target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
126
- logger.debug(f"LOAD_LLM: Loading profile: '{target_llm_name}'.")
127
-
128
- resolved_config = resolve_placeholders(config_dict)
129
- llm_profiles = resolved_config.get("llm", {})
130
- if not isinstance(llm_profiles, dict): raise ValueError("'llm' section must be a dictionary.")
131
-
132
- llm_config = llm_profiles.get(target_llm_name)
133
- config_source = f"config file ('{target_llm_name}')"
134
- logger.debug(f"LOAD_LLM: Initial lookup for '{target_llm_name}': {'Found' if llm_config else 'Missing'}")
135
-
136
- if not llm_config:
137
- logger.warning(f"LOAD_LLM: Config for '{target_llm_name}' not found. Generating fallback.")
138
- config_source = "fallback generation"
139
- fb_provider = os.getenv("DEFAULT_LLM_PROVIDER", "openai"); fb_model = os.getenv("DEFAULT_LLM_MODEL", "gpt-4o")
140
- llm_config = { "provider": fb_provider, "model": fb_model, "api_key": None, "base_url": None }
141
- logger.debug(f"LOAD_LLM: Generated fallback core config: {llm_config}")
142
-
143
- if not isinstance(llm_config, dict): raise ValueError(f"LLM profile '{target_llm_name}' must be a dictionary.")
144
-
145
- final_api_key = llm_config.get("api_key"); key_log_source = f"{config_source} (resolved)" if final_api_key else config_source
146
- provider = llm_config.get("provider"); api_key_required = llm_config.get("api_key_required", True)
147
- logger.debug(f"LOAD_LLM: Initial key from {key_log_source}: {'****' if final_api_key else 'None'}. Required={api_key_required}")
148
-
149
- logger.debug(f"LOAD_LLM: Checking ENV vars for potential override.")
150
- specific_env_var_name = f"{provider.upper()}_API_KEY" if provider else "PROVIDER_API_KEY"
151
- common_fallback_var = "OPENAI_API_KEY"
152
- specific_key_from_env = os.getenv(specific_env_var_name); fallback_key_from_env = os.getenv(common_fallback_var)
153
- logger.debug(f"LOAD_LLM: Env Check: Specific ('{specific_env_var_name}')={'****' if specific_key_from_env else 'None'}, Fallback ('{common_fallback_var}')={'****' if fallback_key_from_env else 'None'}")
154
-
155
- if specific_key_from_env:
156
- if final_api_key != specific_key_from_env: logger.info(f"LOAD_LLM: Overriding key with env var '{specific_env_var_name}'.")
157
- final_api_key = specific_key_from_env; key_log_source = f"env var '{specific_env_var_name}'"
158
- elif fallback_key_from_env:
159
- if not specific_key_from_env or specific_env_var_name == common_fallback_var:
160
- if final_api_key != fallback_key_from_env: logger.info(f"LOAD_LLM: Overriding key with fallback env var '{common_fallback_var}'.")
161
- final_api_key = fallback_key_from_env; key_log_source = f"env var '{common_fallback_var}'"
162
- else: logger.debug(f"LOAD_LLM: Specific env key '{specific_env_var_name}' unset, NOT using fallback.")
163
- else: logger.debug(f"LOAD_LLM: No relevant API key found in environment variables.")
164
-
165
- key_is_still_missing_or_empty = final_api_key is None or (isinstance(final_api_key, str) and not final_api_key.strip())
166
- logger.debug(f"LOAD_LLM: Key after env check: {'****' if final_api_key else 'None'}. Source: {key_log_source}. Still MissingOrEmpty={key_is_still_missing_or_empty}")
167
-
168
- if key_is_still_missing_or_empty:
169
- if api_key_required and not os.getenv("SUPPRESS_DUMMY_KEY"):
170
- final_api_key = "sk-DUMMYKEY"; key_log_source = "dummy key"; logger.warning(f"LOAD_LLM: Applying dummy key for '{target_llm_name}'.")
171
- elif api_key_required:
172
- key_log_source = "MISSING - ERROR"; raise ValueError(f"Required API key for LLM profile '{target_llm_name}' is missing.")
173
- else: key_log_source = "Not Required/Not Found"
174
-
175
- final_llm_config = llm_config.copy(); final_llm_config["api_key"] = final_api_key; final_llm_config["_log_key_source"] = key_log_source
176
- logger.debug(f"LOAD_LLM: Returning final config for '{target_llm_name}': {redact_sensitive_data(final_llm_config)}")
177
- return final_llm_config
178
-
179
- def get_llm_model(config_dict: Dict[str, Any], llm_name: Optional[str] = None) -> str:
180
- target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
181
- try: llm_config = load_llm_config(config_dict, target_llm_name)
182
- except ValueError as e: raise ValueError(f"Could not load config for LLM '{target_llm_name}': {e}") from e
183
- model_name = llm_config.get("model")
184
- if not model_name or not isinstance(model_name, str): raise ValueError(f"'model' name missing/invalid for LLM '{target_llm_name}'.")
185
- logger.debug(f"Retrieved model name '{model_name}' for LLM '{target_llm_name}'")
186
- return model_name
187
-
188
- def load_and_validate_llm(config_dict: Dict[str, Any], llm_name: Optional[str] = None) -> Dict[str, Any]:
189
- target_llm_name = llm_name or os.getenv("DEFAULT_LLM", "default")
190
- logger.debug(f"Loading and validating LLM (via load_llm_config) for profile: {target_llm_name}")
191
- return load_llm_config(config_dict, target_llm_name)
192
-
193
- def get_server_params(server_config: Dict[str, Any], server_name: str) -> Optional[Dict[str, Any]]:
194
- """Extracts and validates parameters needed to start an MCP server."""
195
- command = server_config.get("command"); args = server_config.get("args", []); config_env = server_config.get("env", {})
196
- if not command: logger.error(f"MCP server '{server_name}' missing 'command'."); return None
197
- if not isinstance(args, list): logger.error(f"MCP server '{server_name}' 'args' must be list."); return None
198
- if not isinstance(config_env, dict): logger.error(f"MCP server '{server_name}' 'env' must be dict."); return None
199
- env = {**os.environ.copy(), **config_env}; valid_env = {}
200
- for k, v in env.items():
201
- if v is None: logger.warning(f"Env var '{k}' for MCP server '{server_name}' resolved to None. Omitting.")
202
- else: valid_env[k] = str(v)
203
- return {"command": command, "args": args, "env": valid_env}
204
-
205
- def list_mcp_servers(config_dict: Dict[str, Any]) -> List[str]:
206
- """Returns a list of configured MCP server names."""
207
- return list(config_dict.get("mcpServers", {}).keys())
86
+ save_config(default_config, config_path) # Use save_config to write valid JSON
87
+ logger.debug("Default configuration file created successfully.")
88
+ except Exception as e:
89
+ logger.error(f"Failed to create default config file at {config_path}: {e}", exc_info=True)
90
+ raise
208
91
 
File without changes