massgen 0.0.3__py3-none-any.whl → 0.1.0__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.

Potentially problematic release.


This version of massgen might be problematic. Click here for more details.

Files changed (268) hide show
  1. massgen/__init__.py +142 -8
  2. massgen/adapters/__init__.py +29 -0
  3. massgen/adapters/ag2_adapter.py +483 -0
  4. massgen/adapters/base.py +183 -0
  5. massgen/adapters/tests/__init__.py +0 -0
  6. massgen/adapters/tests/test_ag2_adapter.py +439 -0
  7. massgen/adapters/tests/test_agent_adapter.py +128 -0
  8. massgen/adapters/utils/__init__.py +2 -0
  9. massgen/adapters/utils/ag2_utils.py +236 -0
  10. massgen/adapters/utils/tests/__init__.py +0 -0
  11. massgen/adapters/utils/tests/test_ag2_utils.py +138 -0
  12. massgen/agent_config.py +329 -55
  13. massgen/api_params_handler/__init__.py +10 -0
  14. massgen/api_params_handler/_api_params_handler_base.py +99 -0
  15. massgen/api_params_handler/_chat_completions_api_params_handler.py +176 -0
  16. massgen/api_params_handler/_claude_api_params_handler.py +113 -0
  17. massgen/api_params_handler/_response_api_params_handler.py +130 -0
  18. massgen/backend/__init__.py +39 -4
  19. massgen/backend/azure_openai.py +385 -0
  20. massgen/backend/base.py +341 -69
  21. massgen/backend/base_with_mcp.py +1102 -0
  22. massgen/backend/capabilities.py +386 -0
  23. massgen/backend/chat_completions.py +577 -130
  24. massgen/backend/claude.py +1033 -537
  25. massgen/backend/claude_code.py +1203 -0
  26. massgen/backend/cli_base.py +209 -0
  27. massgen/backend/docs/BACKEND_ARCHITECTURE.md +126 -0
  28. massgen/backend/{CLAUDE_API_RESEARCH.md → docs/CLAUDE_API_RESEARCH.md} +18 -18
  29. massgen/backend/{GEMINI_API_DOCUMENTATION.md → docs/GEMINI_API_DOCUMENTATION.md} +9 -9
  30. massgen/backend/docs/Gemini MCP Integration Analysis.md +1050 -0
  31. massgen/backend/docs/MCP_IMPLEMENTATION_CLAUDE_BACKEND.md +177 -0
  32. massgen/backend/docs/MCP_INTEGRATION_RESPONSE_BACKEND.md +352 -0
  33. massgen/backend/docs/OPENAI_GPT5_MODELS.md +211 -0
  34. massgen/backend/{OPENAI_RESPONSES_API_FORMAT.md → docs/OPENAI_RESPONSE_API_TOOL_CALLS.md} +3 -3
  35. massgen/backend/docs/OPENAI_response_streaming.md +20654 -0
  36. massgen/backend/docs/inference_backend.md +257 -0
  37. massgen/backend/docs/permissions_and_context_files.md +1085 -0
  38. massgen/backend/external.py +126 -0
  39. massgen/backend/gemini.py +1850 -241
  40. massgen/backend/grok.py +40 -156
  41. massgen/backend/inference.py +156 -0
  42. massgen/backend/lmstudio.py +171 -0
  43. massgen/backend/response.py +1095 -322
  44. massgen/chat_agent.py +131 -113
  45. massgen/cli.py +1560 -275
  46. massgen/config_builder.py +2396 -0
  47. massgen/configs/BACKEND_CONFIGURATION.md +458 -0
  48. massgen/configs/README.md +559 -216
  49. massgen/configs/ag2/ag2_case_study.yaml +27 -0
  50. massgen/configs/ag2/ag2_coder.yaml +34 -0
  51. massgen/configs/ag2/ag2_coder_case_study.yaml +36 -0
  52. massgen/configs/ag2/ag2_gemini.yaml +27 -0
  53. massgen/configs/ag2/ag2_groupchat.yaml +108 -0
  54. massgen/configs/ag2/ag2_groupchat_gpt.yaml +118 -0
  55. massgen/configs/ag2/ag2_single_agent.yaml +21 -0
  56. massgen/configs/basic/multi/fast_timeout_example.yaml +37 -0
  57. massgen/configs/basic/multi/gemini_4o_claude.yaml +31 -0
  58. massgen/configs/basic/multi/gemini_gpt5nano_claude.yaml +36 -0
  59. massgen/configs/{gemini_4o_claude.yaml → basic/multi/geminicode_4o_claude.yaml} +3 -3
  60. massgen/configs/basic/multi/geminicode_gpt5nano_claude.yaml +36 -0
  61. massgen/configs/basic/multi/glm_gemini_claude.yaml +25 -0
  62. massgen/configs/basic/multi/gpt4o_audio_generation.yaml +30 -0
  63. massgen/configs/basic/multi/gpt4o_image_generation.yaml +31 -0
  64. massgen/configs/basic/multi/gpt5nano_glm_qwen.yaml +26 -0
  65. massgen/configs/basic/multi/gpt5nano_image_understanding.yaml +26 -0
  66. massgen/configs/{three_agents_default.yaml → basic/multi/three_agents_default.yaml} +8 -4
  67. massgen/configs/basic/multi/three_agents_opensource.yaml +27 -0
  68. massgen/configs/basic/multi/three_agents_vllm.yaml +20 -0
  69. massgen/configs/basic/multi/two_agents_gemini.yaml +19 -0
  70. massgen/configs/{two_agents.yaml → basic/multi/two_agents_gpt5.yaml} +14 -6
  71. massgen/configs/basic/multi/two_agents_opensource_lmstudio.yaml +31 -0
  72. massgen/configs/basic/multi/two_qwen_vllm_sglang.yaml +28 -0
  73. massgen/configs/{single_agent.yaml → basic/single/single_agent.yaml} +1 -1
  74. massgen/configs/{single_flash2.5.yaml → basic/single/single_flash2.5.yaml} +1 -2
  75. massgen/configs/basic/single/single_gemini2.5pro.yaml +16 -0
  76. massgen/configs/basic/single/single_gpt4o_audio_generation.yaml +22 -0
  77. massgen/configs/basic/single/single_gpt4o_image_generation.yaml +22 -0
  78. massgen/configs/basic/single/single_gpt4o_video_generation.yaml +24 -0
  79. massgen/configs/basic/single/single_gpt5nano.yaml +20 -0
  80. massgen/configs/basic/single/single_gpt5nano_file_search.yaml +18 -0
  81. massgen/configs/basic/single/single_gpt5nano_image_understanding.yaml +17 -0
  82. massgen/configs/basic/single/single_gptoss120b.yaml +15 -0
  83. massgen/configs/basic/single/single_openrouter_audio_understanding.yaml +15 -0
  84. massgen/configs/basic/single/single_qwen_video_understanding.yaml +15 -0
  85. massgen/configs/debug/code_execution/command_filtering_blacklist.yaml +29 -0
  86. massgen/configs/debug/code_execution/command_filtering_whitelist.yaml +28 -0
  87. massgen/configs/debug/code_execution/docker_verification.yaml +29 -0
  88. massgen/configs/debug/skip_coordination_test.yaml +27 -0
  89. massgen/configs/debug/test_sdk_migration.yaml +17 -0
  90. massgen/configs/docs/DISCORD_MCP_SETUP.md +208 -0
  91. massgen/configs/docs/TWITTER_MCP_ENESCINAR_SETUP.md +82 -0
  92. massgen/configs/providers/azure/azure_openai_multi.yaml +21 -0
  93. massgen/configs/providers/azure/azure_openai_single.yaml +19 -0
  94. massgen/configs/providers/claude/claude.yaml +14 -0
  95. massgen/configs/providers/gemini/gemini_gpt5nano.yaml +28 -0
  96. massgen/configs/providers/local/lmstudio.yaml +11 -0
  97. massgen/configs/providers/openai/gpt5.yaml +46 -0
  98. massgen/configs/providers/openai/gpt5_nano.yaml +46 -0
  99. massgen/configs/providers/others/grok_single_agent.yaml +19 -0
  100. massgen/configs/providers/others/zai_coding_team.yaml +108 -0
  101. massgen/configs/providers/others/zai_glm45.yaml +12 -0
  102. massgen/configs/{creative_team.yaml → teams/creative/creative_team.yaml} +16 -6
  103. massgen/configs/{travel_planning.yaml → teams/creative/travel_planning.yaml} +16 -6
  104. massgen/configs/{news_analysis.yaml → teams/research/news_analysis.yaml} +16 -6
  105. massgen/configs/{research_team.yaml → teams/research/research_team.yaml} +15 -7
  106. massgen/configs/{technical_analysis.yaml → teams/research/technical_analysis.yaml} +16 -6
  107. massgen/configs/tools/code-execution/basic_command_execution.yaml +25 -0
  108. massgen/configs/tools/code-execution/code_execution_use_case_simple.yaml +41 -0
  109. massgen/configs/tools/code-execution/docker_claude_code.yaml +32 -0
  110. massgen/configs/tools/code-execution/docker_multi_agent.yaml +32 -0
  111. massgen/configs/tools/code-execution/docker_simple.yaml +29 -0
  112. massgen/configs/tools/code-execution/docker_with_resource_limits.yaml +32 -0
  113. massgen/configs/tools/code-execution/multi_agent_playwright_automation.yaml +57 -0
  114. massgen/configs/tools/filesystem/cc_gpt5_gemini_filesystem.yaml +34 -0
  115. massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +68 -0
  116. massgen/configs/tools/filesystem/claude_code_flash2.5.yaml +43 -0
  117. massgen/configs/tools/filesystem/claude_code_flash2.5_gptoss.yaml +49 -0
  118. massgen/configs/tools/filesystem/claude_code_gpt5nano.yaml +31 -0
  119. massgen/configs/tools/filesystem/claude_code_single.yaml +40 -0
  120. massgen/configs/tools/filesystem/fs_permissions_test.yaml +87 -0
  121. massgen/configs/tools/filesystem/gemini_gemini_workspace_cleanup.yaml +54 -0
  122. massgen/configs/tools/filesystem/gemini_gpt5_filesystem_casestudy.yaml +30 -0
  123. massgen/configs/tools/filesystem/gemini_gpt5nano_file_context_path.yaml +43 -0
  124. massgen/configs/tools/filesystem/gemini_gpt5nano_protected_paths.yaml +45 -0
  125. massgen/configs/tools/filesystem/gpt5mini_cc_fs_context_path.yaml +31 -0
  126. massgen/configs/tools/filesystem/grok4_gpt5_gemini_filesystem.yaml +32 -0
  127. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_claude_code_filesystem_multiturn.yaml +58 -0
  128. massgen/configs/tools/filesystem/multiturn/grok4_gpt5_gemini_filesystem_multiturn.yaml +58 -0
  129. massgen/configs/tools/filesystem/multiturn/two_claude_code_filesystem_multiturn.yaml +47 -0
  130. massgen/configs/tools/filesystem/multiturn/two_gemini_flash_filesystem_multiturn.yaml +48 -0
  131. massgen/configs/tools/mcp/claude_code_discord_mcp_example.yaml +27 -0
  132. massgen/configs/tools/mcp/claude_code_simple_mcp.yaml +35 -0
  133. massgen/configs/tools/mcp/claude_code_twitter_mcp_example.yaml +32 -0
  134. massgen/configs/tools/mcp/claude_mcp_example.yaml +24 -0
  135. massgen/configs/tools/mcp/claude_mcp_test.yaml +27 -0
  136. massgen/configs/tools/mcp/five_agents_travel_mcp_test.yaml +157 -0
  137. massgen/configs/tools/mcp/five_agents_weather_mcp_test.yaml +103 -0
  138. massgen/configs/tools/mcp/gemini_mcp_example.yaml +24 -0
  139. massgen/configs/tools/mcp/gemini_mcp_filesystem_test.yaml +23 -0
  140. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_sharing.yaml +23 -0
  141. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_single_agent.yaml +17 -0
  142. massgen/configs/tools/mcp/gemini_mcp_filesystem_test_with_claude_code.yaml +24 -0
  143. massgen/configs/tools/mcp/gemini_mcp_test.yaml +27 -0
  144. massgen/configs/tools/mcp/gemini_notion_mcp.yaml +52 -0
  145. massgen/configs/tools/mcp/gpt5_nano_mcp_example.yaml +24 -0
  146. massgen/configs/tools/mcp/gpt5_nano_mcp_test.yaml +27 -0
  147. massgen/configs/tools/mcp/gpt5mini_claude_code_discord_mcp_example.yaml +38 -0
  148. massgen/configs/tools/mcp/gpt_oss_mcp_example.yaml +25 -0
  149. massgen/configs/tools/mcp/gpt_oss_mcp_test.yaml +28 -0
  150. massgen/configs/tools/mcp/grok3_mini_mcp_example.yaml +24 -0
  151. massgen/configs/tools/mcp/grok3_mini_mcp_test.yaml +27 -0
  152. massgen/configs/tools/mcp/multimcp_gemini.yaml +111 -0
  153. massgen/configs/tools/mcp/qwen_api_mcp_example.yaml +25 -0
  154. massgen/configs/tools/mcp/qwen_api_mcp_test.yaml +28 -0
  155. massgen/configs/tools/mcp/qwen_local_mcp_example.yaml +24 -0
  156. massgen/configs/tools/mcp/qwen_local_mcp_test.yaml +27 -0
  157. massgen/configs/tools/planning/five_agents_discord_mcp_planning_mode.yaml +140 -0
  158. massgen/configs/tools/planning/five_agents_filesystem_mcp_planning_mode.yaml +151 -0
  159. massgen/configs/tools/planning/five_agents_notion_mcp_planning_mode.yaml +151 -0
  160. massgen/configs/tools/planning/five_agents_twitter_mcp_planning_mode.yaml +155 -0
  161. massgen/configs/tools/planning/gpt5_mini_case_study_mcp_planning_mode.yaml +73 -0
  162. massgen/configs/tools/web-search/claude_streamable_http_test.yaml +43 -0
  163. massgen/configs/tools/web-search/gemini_streamable_http_test.yaml +43 -0
  164. massgen/configs/tools/web-search/gpt5_mini_streamable_http_test.yaml +43 -0
  165. massgen/configs/tools/web-search/gpt_oss_streamable_http_test.yaml +44 -0
  166. massgen/configs/tools/web-search/grok3_mini_streamable_http_test.yaml +43 -0
  167. massgen/configs/tools/web-search/qwen_api_streamable_http_test.yaml +44 -0
  168. massgen/configs/tools/web-search/qwen_local_streamable_http_test.yaml +43 -0
  169. massgen/coordination_tracker.py +708 -0
  170. massgen/docker/README.md +462 -0
  171. massgen/filesystem_manager/__init__.py +21 -0
  172. massgen/filesystem_manager/_base.py +9 -0
  173. massgen/filesystem_manager/_code_execution_server.py +545 -0
  174. massgen/filesystem_manager/_docker_manager.py +477 -0
  175. massgen/filesystem_manager/_file_operation_tracker.py +248 -0
  176. massgen/filesystem_manager/_filesystem_manager.py +813 -0
  177. massgen/filesystem_manager/_path_permission_manager.py +1261 -0
  178. massgen/filesystem_manager/_workspace_tools_server.py +1815 -0
  179. massgen/formatter/__init__.py +10 -0
  180. massgen/formatter/_chat_completions_formatter.py +284 -0
  181. massgen/formatter/_claude_formatter.py +235 -0
  182. massgen/formatter/_formatter_base.py +156 -0
  183. massgen/formatter/_response_formatter.py +263 -0
  184. massgen/frontend/__init__.py +1 -2
  185. massgen/frontend/coordination_ui.py +471 -286
  186. massgen/frontend/displays/base_display.py +56 -11
  187. massgen/frontend/displays/create_coordination_table.py +1956 -0
  188. massgen/frontend/displays/rich_terminal_display.py +1259 -619
  189. massgen/frontend/displays/simple_display.py +9 -4
  190. massgen/frontend/displays/terminal_display.py +27 -68
  191. massgen/logger_config.py +681 -0
  192. massgen/mcp_tools/README.md +232 -0
  193. massgen/mcp_tools/__init__.py +105 -0
  194. massgen/mcp_tools/backend_utils.py +1035 -0
  195. massgen/mcp_tools/circuit_breaker.py +195 -0
  196. massgen/mcp_tools/client.py +894 -0
  197. massgen/mcp_tools/config_validator.py +138 -0
  198. massgen/mcp_tools/docs/circuit_breaker.md +646 -0
  199. massgen/mcp_tools/docs/client.md +950 -0
  200. massgen/mcp_tools/docs/config_validator.md +478 -0
  201. massgen/mcp_tools/docs/exceptions.md +1165 -0
  202. massgen/mcp_tools/docs/security.md +854 -0
  203. massgen/mcp_tools/exceptions.py +338 -0
  204. massgen/mcp_tools/hooks.py +212 -0
  205. massgen/mcp_tools/security.py +780 -0
  206. massgen/message_templates.py +342 -64
  207. massgen/orchestrator.py +1515 -241
  208. massgen/stream_chunk/__init__.py +35 -0
  209. massgen/stream_chunk/base.py +92 -0
  210. massgen/stream_chunk/multimodal.py +237 -0
  211. massgen/stream_chunk/text.py +162 -0
  212. massgen/tests/mcp_test_server.py +150 -0
  213. massgen/tests/multi_turn_conversation_design.md +0 -8
  214. massgen/tests/test_azure_openai_backend.py +156 -0
  215. massgen/tests/test_backend_capabilities.py +262 -0
  216. massgen/tests/test_backend_event_loop_all.py +179 -0
  217. massgen/tests/test_chat_completions_refactor.py +142 -0
  218. massgen/tests/test_claude_backend.py +15 -28
  219. massgen/tests/test_claude_code.py +268 -0
  220. massgen/tests/test_claude_code_context_sharing.py +233 -0
  221. massgen/tests/test_claude_code_orchestrator.py +175 -0
  222. massgen/tests/test_cli_backends.py +180 -0
  223. massgen/tests/test_code_execution.py +679 -0
  224. massgen/tests/test_external_agent_backend.py +134 -0
  225. massgen/tests/test_final_presentation_fallback.py +237 -0
  226. massgen/tests/test_gemini_planning_mode.py +351 -0
  227. massgen/tests/test_grok_backend.py +7 -10
  228. massgen/tests/test_http_mcp_server.py +42 -0
  229. massgen/tests/test_integration_simple.py +198 -0
  230. massgen/tests/test_mcp_blocking.py +125 -0
  231. massgen/tests/test_message_context_building.py +29 -47
  232. massgen/tests/test_orchestrator_final_presentation.py +48 -0
  233. massgen/tests/test_path_permission_manager.py +2087 -0
  234. massgen/tests/test_rich_terminal_display.py +14 -13
  235. massgen/tests/test_timeout.py +133 -0
  236. massgen/tests/test_v3_3agents.py +11 -12
  237. massgen/tests/test_v3_simple.py +8 -13
  238. massgen/tests/test_v3_three_agents.py +11 -18
  239. massgen/tests/test_v3_two_agents.py +8 -13
  240. massgen/token_manager/__init__.py +7 -0
  241. massgen/token_manager/token_manager.py +400 -0
  242. massgen/utils.py +52 -16
  243. massgen/v1/agent.py +45 -91
  244. massgen/v1/agents.py +18 -53
  245. massgen/v1/backends/gemini.py +50 -153
  246. massgen/v1/backends/grok.py +21 -54
  247. massgen/v1/backends/oai.py +39 -111
  248. massgen/v1/cli.py +36 -93
  249. massgen/v1/config.py +8 -12
  250. massgen/v1/logging.py +43 -127
  251. massgen/v1/main.py +18 -32
  252. massgen/v1/orchestrator.py +68 -209
  253. massgen/v1/streaming_display.py +62 -163
  254. massgen/v1/tools.py +8 -12
  255. massgen/v1/types.py +9 -23
  256. massgen/v1/utils.py +5 -23
  257. massgen-0.1.0.dist-info/METADATA +1245 -0
  258. massgen-0.1.0.dist-info/RECORD +273 -0
  259. massgen-0.1.0.dist-info/entry_points.txt +2 -0
  260. massgen/frontend/logging/__init__.py +0 -9
  261. massgen/frontend/logging/realtime_logger.py +0 -197
  262. massgen-0.0.3.dist-info/METADATA +0 -568
  263. massgen-0.0.3.dist-info/RECORD +0 -76
  264. massgen-0.0.3.dist-info/entry_points.txt +0 -2
  265. /massgen/backend/{Function calling openai responses.md → docs/Function calling openai responses.md} +0 -0
  266. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/WHEEL +0 -0
  267. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/licenses/LICENSE +0 -0
  268. {massgen-0.0.3.dist-info → massgen-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,681 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Centralized logging configuration for MassGen using loguru.
4
+
5
+ This module provides a unified logging system for all MassGen components,
6
+ with special focus on debugging orchestrator and agent backend activities.
7
+
8
+ Color Scheme for Debug Logging:
9
+ - Magenta: Orchestrator activities (🎯)
10
+ - Blue: Messages sent from orchestrator to agents (📤)
11
+ - Green: Messages received from agents (📥)
12
+ - Yellow: Backend activities (⚙️)
13
+ - Cyan: General agent activities (📨)
14
+ - Light-black: Tool calls (🔧)
15
+ - Red: Coordination steps (🔄)
16
+ """
17
+
18
+ import inspect
19
+ import sys
20
+ from datetime import datetime
21
+ from pathlib import Path
22
+ from typing import Any, Optional
23
+
24
+ from loguru import logger
25
+
26
+ # Remove default logger to have full control
27
+ logger.remove()
28
+
29
+ # Global debug flag
30
+ _DEBUG_MODE = False
31
+
32
+ # Global log session directory and turn tracking
33
+ _LOG_SESSION_DIR = None
34
+ _LOG_BASE_SESSION_DIR = None # Base session dir (without turn subdirectory)
35
+ _CURRENT_TURN = None
36
+
37
+ # Console logging suppression (for Rich Live display compatibility)
38
+ _CONSOLE_HANDLER_ID = None
39
+ _CONSOLE_SUPPRESSED = False
40
+
41
+
42
+ def get_log_session_dir(turn: Optional[int] = None) -> Path:
43
+ """Get the current log session directory.
44
+
45
+ Args:
46
+ turn: Optional turn number for multi-turn conversations
47
+
48
+ Returns:
49
+ Path to the log directory
50
+ """
51
+ global _LOG_SESSION_DIR, _LOG_BASE_SESSION_DIR, _CURRENT_TURN
52
+
53
+ # Initialize base session dir once per session
54
+ if _LOG_BASE_SESSION_DIR is None:
55
+ # Check if we're running from within the MassGen development directory
56
+ # by looking for pyproject.toml with massgen package
57
+ cwd = Path.cwd()
58
+
59
+ # Check if pyproject.toml exists and contains massgen package definition
60
+ pyproject_file = cwd / "pyproject.toml"
61
+ if pyproject_file.exists():
62
+ try:
63
+ content = pyproject_file.read_text()
64
+ if 'name = "massgen"' in content:
65
+ pass
66
+ except Exception:
67
+ pass
68
+
69
+ log_base_dir = Path(".massgen") / "massgen_logs"
70
+ log_base_dir.mkdir(parents=True, exist_ok=True)
71
+
72
+ # Create timestamped session directory
73
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
74
+ _LOG_BASE_SESSION_DIR = log_base_dir / f"log_{timestamp}"
75
+ _LOG_BASE_SESSION_DIR.mkdir(parents=True, exist_ok=True)
76
+
77
+ # If turn changed, update the directory
78
+ if turn is not None and turn != _CURRENT_TURN:
79
+ _CURRENT_TURN = turn
80
+ _LOG_SESSION_DIR = None # Force recreation
81
+
82
+ if _LOG_SESSION_DIR is None:
83
+ # Create directory structure based on turn
84
+ if _CURRENT_TURN and _CURRENT_TURN > 0:
85
+ # Multi-turn conversation: organize by turn within session
86
+ _LOG_SESSION_DIR = _LOG_BASE_SESSION_DIR / f"turn_{_CURRENT_TURN}"
87
+ else:
88
+ # First execution or single execution: use base session dir
89
+ _LOG_SESSION_DIR = _LOG_BASE_SESSION_DIR
90
+
91
+ _LOG_SESSION_DIR.mkdir(parents=True, exist_ok=True)
92
+
93
+ return _LOG_SESSION_DIR
94
+
95
+
96
+ def save_execution_metadata(query: str, config_path: Optional[str] = None, config_content: Optional[dict] = None):
97
+ """Save the query and config metadata to the log directory.
98
+
99
+ This allows reconstructing what was executed in this session.
100
+
101
+ Args:
102
+ query: The user's query/prompt
103
+ config_path: Path to the config file that was used (optional)
104
+ config_content: The actual config dictionary (optional)
105
+ """
106
+ import yaml
107
+
108
+ log_dir = get_log_session_dir()
109
+
110
+ # Create a single metadata file with all execution info
111
+ metadata = {
112
+ "query": query,
113
+ "timestamp": datetime.now().isoformat(),
114
+ }
115
+
116
+ if config_path:
117
+ metadata["config_path"] = str(config_path)
118
+
119
+ if config_content:
120
+ metadata["config"] = config_content
121
+
122
+ metadata_file = log_dir / "execution_metadata.yaml"
123
+ try:
124
+ with open(metadata_file, "w", encoding="utf-8") as f:
125
+ yaml.dump(metadata, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
126
+ logger.info(f"Saved execution metadata to: {metadata_file}")
127
+ except Exception as e:
128
+ logger.warning(f"Failed to save execution metadata: {e}")
129
+
130
+
131
+ def setup_logging(debug: bool = False, log_file: Optional[str] = None, turn: Optional[int] = None):
132
+ """
133
+ Configure MassGen logging system using loguru.
134
+
135
+ Args:
136
+ debug: Enable debug mode with verbose logging
137
+ log_file: Optional path to log file for persistent logging
138
+ turn: Optional turn number for multi-turn conversations
139
+ """
140
+ global _DEBUG_MODE, _CONSOLE_HANDLER_ID, _CONSOLE_SUPPRESSED
141
+ _DEBUG_MODE = debug
142
+ _CONSOLE_SUPPRESSED = False
143
+
144
+ # Remove all existing handlers
145
+ logger.remove()
146
+
147
+ if debug:
148
+ # Debug mode: verbose console output with full details
149
+ def custom_format(record):
150
+ # Color code the module name based on category
151
+ name = record["extra"].get("name", "")
152
+ if "orchestrator" in name:
153
+ name_color = "magenta"
154
+ elif "backend" in name:
155
+ name_color = "yellow"
156
+ elif "agent" in name:
157
+ name_color = "cyan"
158
+ elif "coordination" in name:
159
+ name_color = "red"
160
+ else:
161
+ name_color = "white"
162
+
163
+ # Format the name to be more readable
164
+ formatted_name = name if name else "{name}"
165
+
166
+ return (
167
+ f"<green>{{time:HH:mm:ss.SSS}}</green> | <level>{{level: <8}}</level> | "
168
+ f"<{name_color}>{formatted_name}</{name_color}>:<{name_color}>{{function}}</{name_color}>:"
169
+ f"<{name_color}>{{line}}</{name_color}> - {{message}}\n{{exception}}"
170
+ )
171
+
172
+ _CONSOLE_HANDLER_ID = logger.add(
173
+ sys.stderr,
174
+ format=custom_format,
175
+ level="DEBUG",
176
+ colorize=True,
177
+ backtrace=True,
178
+ diagnose=True,
179
+ )
180
+
181
+ # Also log to file in debug mode
182
+ if not log_file:
183
+ log_session_dir = get_log_session_dir(turn=turn)
184
+ log_file = log_session_dir / "massgen_debug.log"
185
+
186
+ logger.add(
187
+ str(log_file),
188
+ format=custom_format,
189
+ level="DEBUG",
190
+ rotation="100 MB",
191
+ retention="1 week",
192
+ compression="zip",
193
+ backtrace=True,
194
+ diagnose=True,
195
+ enqueue=True, # Thread-safe logging
196
+ colorize=False, # Keep color codes in file
197
+ )
198
+
199
+ logger.info("Debug logging enabled - logging to console and file: {}", log_file)
200
+ else:
201
+ # Normal mode: only important messages to console, but all INFO+ to file
202
+ console_format = "<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <level>{message}</level>"
203
+
204
+ _CONSOLE_HANDLER_ID = logger.add(
205
+ sys.stderr,
206
+ format=console_format,
207
+ level="WARNING", # Only show WARNING and above on console in non-debug mode
208
+ colorize=True,
209
+ )
210
+
211
+ # Always create log file in non-debug mode to capture INFO messages
212
+ if not log_file:
213
+ log_session_dir = get_log_session_dir(turn=turn)
214
+ log_file = log_session_dir / "massgen.log"
215
+
216
+ # Use the same format as console with color codes
217
+ logger.add(
218
+ str(log_file),
219
+ format=console_format,
220
+ level="INFO", # Capture INFO and above in file
221
+ rotation="10 MB",
222
+ retention="3 days",
223
+ compression="zip",
224
+ enqueue=True,
225
+ colorize=False, # Keep color codes in file
226
+ )
227
+
228
+ logger.info("Logging enabled - logging INFO+ to file: {}", log_file)
229
+
230
+
231
+ def suppress_console_logging():
232
+ """
233
+ Temporarily suppress console logging to prevent interference with Rich Live display.
234
+
235
+ This removes the console handler while keeping file logging active.
236
+ Call restore_console_logging() to re-enable console output.
237
+ """
238
+ global _CONSOLE_HANDLER_ID, _CONSOLE_SUPPRESSED
239
+
240
+ if _CONSOLE_HANDLER_ID is not None and not _CONSOLE_SUPPRESSED:
241
+ try:
242
+ logger.remove(_CONSOLE_HANDLER_ID)
243
+ _CONSOLE_SUPPRESSED = True
244
+ except ValueError:
245
+ # Handler already removed
246
+ pass
247
+
248
+
249
+ def restore_console_logging():
250
+ """
251
+ Restore console logging after it was suppressed.
252
+
253
+ Re-adds the console handler with the same settings that were used during setup.
254
+ """
255
+ global _CONSOLE_HANDLER_ID, _CONSOLE_SUPPRESSED, _DEBUG_MODE
256
+
257
+ if not _CONSOLE_SUPPRESSED:
258
+ return
259
+
260
+ # Re-add console handler with same settings as setup_logging
261
+ if _DEBUG_MODE:
262
+
263
+ def custom_format(record):
264
+ name = record["extra"].get("name", "")
265
+ if "orchestrator" in name:
266
+ name_color = "magenta"
267
+ elif "backend" in name:
268
+ name_color = "yellow"
269
+ elif "agent" in name:
270
+ name_color = "cyan"
271
+ elif "coordination" in name:
272
+ name_color = "red"
273
+ else:
274
+ name_color = "white"
275
+ formatted_name = name if name else "{name}"
276
+ return (
277
+ f"<green>{{time:HH:mm:ss.SSS}}</green> | <level>{{level: <8}}</level> | "
278
+ f"<{name_color}>{formatted_name}</{name_color}>:<{name_color}>{{function}}</{name_color}>:"
279
+ f"<{name_color}>{{line}}</{name_color}> - {{message}}\n{{exception}}"
280
+ )
281
+
282
+ _CONSOLE_HANDLER_ID = logger.add(
283
+ sys.stderr,
284
+ format=custom_format,
285
+ level="DEBUG",
286
+ colorize=True,
287
+ backtrace=True,
288
+ diagnose=True,
289
+ )
290
+ else:
291
+ console_format = "<green>{time:HH:mm:ss}</green> | <level>{level: <8}</level> | <level>{message}</level>"
292
+ _CONSOLE_HANDLER_ID = logger.add(
293
+ sys.stderr,
294
+ format=console_format,
295
+ level="WARNING",
296
+ colorize=True,
297
+ )
298
+
299
+ _CONSOLE_SUPPRESSED = False
300
+
301
+
302
+ def get_logger(name: str):
303
+ """
304
+ Get a logger instance with the given name.
305
+
306
+ Args:
307
+ name: Logger name (typically __name__ of the module)
308
+
309
+ Returns:
310
+ Configured logger instance
311
+ """
312
+ return logger.bind(name=name)
313
+
314
+
315
+ def _get_caller_info():
316
+ """
317
+ Get the caller's line number and function name from the stack frame.
318
+
319
+ Returns:
320
+ Tuple of (function_name, line_number where the logging function was called)
321
+ """
322
+ frame = inspect.currentframe()
323
+ # Stack frames:
324
+ # - frame: _get_caller_info (this function)
325
+ # - frame.f_back: log_orchestrator_agent_message or log_backend_agent_message
326
+ # - frame.f_back.f_back: the actual caller (e.g., _stream_agent_execution)
327
+
328
+ if frame and frame.f_back and frame.f_back.f_back:
329
+ caller_frame = frame.f_back.f_back
330
+ function_name = caller_frame.f_code.co_name
331
+ # Get the line number where the logging function was called from within the caller
332
+ line_number = caller_frame.f_lineno
333
+ return function_name, line_number
334
+ return "unknown", 0
335
+
336
+
337
+ def log_orchestrator_activity(orchestrator_id: str, activity: str, details: dict = None):
338
+ """
339
+ Log orchestrator activities for debugging.
340
+
341
+ Args:
342
+ orchestrator_id: ID of the orchestrator
343
+ activity: Description of the activity
344
+ details: Additional details as dictionary
345
+ """
346
+ # Get caller information
347
+ func_name, line_num = _get_caller_info()
348
+ log = logger.bind(name=f"orchestrator.{orchestrator_id}:{func_name}:{line_num}")
349
+ if _DEBUG_MODE:
350
+ # Use magenta color for orchestrator activities
351
+ log.opt(colors=True).debug("<magenta>🎯 {}: {}</magenta>", activity, details or {})
352
+
353
+
354
+ def log_agent_message(agent_id: str, direction: str, message: dict, backend_name: str = None):
355
+ """
356
+ Log agent messages (sent/received) for debugging.
357
+
358
+ Args:
359
+ agent_id: ID of the agent
360
+ direction: "SEND" or "RECV"
361
+ message: Message content as dictionary
362
+ backend_name: Optional name of the backend provider
363
+ """
364
+ # Build a descriptive name with both agent ID and backend
365
+ if backend_name:
366
+ log_name = f"{agent_id}.{backend_name}"
367
+ log = logger.bind(name=log_name)
368
+ else:
369
+ log_name = agent_id
370
+ log = logger.bind(name=log_name)
371
+
372
+ if _DEBUG_MODE:
373
+ if direction == "SEND":
374
+ # Use blue color for sent messages
375
+ log.opt(colors=True).debug(
376
+ "<blue>📤 [{}] Sending message: {}</blue>",
377
+ log_name,
378
+ _format_message(message),
379
+ )
380
+ elif direction == "RECV":
381
+ # Use green color for received messages
382
+ log.opt(colors=True).debug(
383
+ "<green>📥 [{}] Received message: {}</green>",
384
+ log_name,
385
+ _format_message(message),
386
+ )
387
+ else:
388
+ log.opt(colors=True).debug(
389
+ "<cyan>📨 [{}] {}: {}</cyan>",
390
+ log_name,
391
+ direction,
392
+ _format_message(message),
393
+ )
394
+
395
+
396
+ def log_orchestrator_agent_message(agent_id: str, direction: str, message: dict, backend_name: str = None):
397
+ """
398
+ Log orchestrator-to-agent messages for debugging.
399
+
400
+ Args:
401
+ agent_id: ID of the agent
402
+ direction: "SEND" or "RECV"
403
+ message: Message content as dictionary
404
+ backend_name: Optional name of the backend provider
405
+ """
406
+ # Get caller information
407
+ func_name, line_num = _get_caller_info()
408
+
409
+ # Build a descriptive name with orchestrator prefix
410
+ if backend_name:
411
+ log_name = f"orchestrator→{agent_id}.{backend_name}:{func_name}:{line_num}"
412
+ log = logger.bind(name=log_name)
413
+ else:
414
+ log_name = f"orchestrator→{agent_id}:{func_name}:{line_num}"
415
+ log = logger.bind(name=log_name)
416
+
417
+ if _DEBUG_MODE:
418
+ if direction == "SEND":
419
+ # Use magenta color for orchestrator sent messages
420
+ log.opt(colors=True).debug(
421
+ "<magenta>🎯📤 [{}] Orchestrator sending to agent: {}</magenta>",
422
+ log_name,
423
+ _format_message(message),
424
+ )
425
+ elif direction == "RECV":
426
+ # Use magenta color for orchestrator received messages
427
+ log.opt(colors=True).debug(
428
+ "<magenta>🎯📥 [{}] Orchestrator received from agent: {}</magenta>",
429
+ log_name,
430
+ _format_message(message),
431
+ )
432
+ else:
433
+ log.opt(colors=True).debug(
434
+ "<magenta>🎯📨 [{}] {}: {}</magenta>",
435
+ log_name,
436
+ direction,
437
+ _format_message(message),
438
+ )
439
+
440
+
441
+ def log_backend_agent_message(agent_id: str, direction: str, message: dict, backend_name: str = None):
442
+ """
443
+ Log backend-to-LLM messages for debugging.
444
+
445
+ Args:
446
+ agent_id: ID of the agent
447
+ direction: "SEND" or "RECV"
448
+ message: Message content as dictionary
449
+ backend_name: Optional name of the backend provider
450
+ """
451
+ # Get caller information
452
+ func_name, line_num = _get_caller_info()
453
+
454
+ # Build a descriptive name with backend prefix
455
+ if backend_name:
456
+ log_name = f"backend.{backend_name}→{agent_id}:{func_name}:{line_num}"
457
+ log = logger.bind(name=log_name)
458
+ else:
459
+ log_name = f"backend→{agent_id}:{func_name}:{line_num}"
460
+ log = logger.bind(name=log_name)
461
+
462
+ if _DEBUG_MODE:
463
+ if direction == "SEND":
464
+ # Use yellow color for backend sent messages
465
+ log.opt(colors=True).debug(
466
+ "<yellow>⚙️📤 [{}] Backend sending to LLM: {}</yellow>",
467
+ log_name,
468
+ _format_message(message),
469
+ )
470
+ elif direction == "RECV":
471
+ # Use yellow color for backend received messages
472
+ log.opt(colors=True).debug(
473
+ "<yellow>⚙️📥 [{}] Backend received from LLM: {}</yellow>",
474
+ log_name,
475
+ _format_message(message),
476
+ )
477
+ else:
478
+ log.opt(colors=True).debug(
479
+ "<yellow>⚙️📨 [{}] {}: {}</yellow>",
480
+ log_name,
481
+ direction,
482
+ _format_message(message),
483
+ )
484
+
485
+
486
+ def log_backend_activity(backend_name: str, activity: str, details: dict = None, agent_id: str = None):
487
+ """
488
+ Log backend activities for debugging.
489
+
490
+ Args:
491
+ backend_name: Name of the backend (e.g., "openai", "claude")
492
+ activity: Description of the activity
493
+ details: Additional details as dictionary
494
+ agent_id: Optional ID of the agent using this backend
495
+ """
496
+ # Get caller information
497
+ func_name, line_num = _get_caller_info()
498
+
499
+ # Build a descriptive name with both agent ID and backend
500
+ if agent_id:
501
+ log_name = f"{agent_id}.{backend_name}"
502
+ log = logger.bind(name=f"{log_name}:{func_name}:{line_num}")
503
+ else:
504
+ log_name = backend_name
505
+ log = logger.bind(name=f"backend.{backend_name}:{func_name}:{line_num}")
506
+
507
+ if _DEBUG_MODE:
508
+ # Use yellow color for backend activities
509
+ log.opt(colors=True).debug("<yellow>⚙️ [{}] {}: {}</yellow>", log_name, activity, details or {})
510
+
511
+
512
+ def log_mcp_activity(backend_name: str, message: str, details: dict = None, agent_id: str = None):
513
+ """
514
+ Log MCP (Model Context Protocol) activities at INFO level.
515
+
516
+ Args:
517
+ backend_name: Name of the backend (e.g., "claude", "openai")
518
+ message: Description of the MCP activity
519
+ details: Additional details as dictionary
520
+ agent_id: Optional ID of the agent using this backend
521
+ """
522
+ func_name, line_num = _get_caller_info()
523
+
524
+ if agent_id:
525
+ log_name = f"{agent_id}.{backend_name}"
526
+ log = logger.bind(name=f"{log_name}:{func_name}:{line_num}")
527
+ else:
528
+ log_name = backend_name
529
+ log = logger.bind(name=f"backend.{backend_name}:{func_name}:{line_num}")
530
+
531
+ log.info("MCP: {} - {}", message, details or {})
532
+
533
+
534
+ def log_tool_call(
535
+ agent_id: str,
536
+ tool_name: str,
537
+ arguments: dict,
538
+ result: Any = None,
539
+ backend_name: str = None,
540
+ ):
541
+ """
542
+ Log tool calls made by agents.
543
+
544
+ Args:
545
+ agent_id: ID of the agent making the tool call
546
+ tool_name: Name of the tool being called
547
+ arguments: Arguments passed to the tool
548
+ result: Result returned by the tool (optional)
549
+ backend_name: Optional name of the backend provider
550
+ """
551
+ # Build a descriptive name with both agent ID and backend
552
+ if backend_name:
553
+ log_name = f"{agent_id}.{backend_name}"
554
+ log = logger.bind(name=f"{log_name}.tools")
555
+ else:
556
+ log_name = agent_id
557
+ log = logger.bind(name=f"{agent_id}.tools")
558
+
559
+ if _DEBUG_MODE:
560
+ if result is not None:
561
+ # Use light gray color for tool calls
562
+ log.opt(colors=True).debug(
563
+ "<light-black>🔧 [{}] Tool '{}' called with args: {} -> Result: {}</light-black>",
564
+ log_name,
565
+ tool_name,
566
+ arguments,
567
+ result,
568
+ )
569
+ else:
570
+ log.opt(colors=True).debug(
571
+ "<light-black>🔧 [{}] Calling tool '{}' with args: {}</light-black>",
572
+ log_name,
573
+ tool_name,
574
+ arguments,
575
+ )
576
+
577
+
578
+ def log_coordination_step(step: str, details: dict = None):
579
+ """
580
+ Log coordination workflow steps.
581
+
582
+ Args:
583
+ step: Description of the coordination step
584
+ details: Additional details as dictionary
585
+ """
586
+ log = logger.bind(name="coordination")
587
+ if _DEBUG_MODE:
588
+ # Use red color for coordination steps (distinctive from orchestrator)
589
+ log.opt(colors=True).debug("<red>🔄 {}: {}</red>", step, details or {})
590
+
591
+
592
+ def log_stream_chunk(source: str, chunk_type: str, content: Any = None, agent_id: str = None):
593
+ """
594
+ Log stream chunks at INFO level (always logged to file).
595
+
596
+ Args:
597
+ source: Source of the stream chunk (e.g., "orchestrator", "backend.claude_code")
598
+ chunk_type: Type of the chunk (e.g., "content", "tool_call", "error")
599
+ content: Content of the chunk
600
+ agent_id: Optional agent ID for context
601
+ """
602
+ # Get caller information from the actual caller (not this function)
603
+ frame = inspect.currentframe()
604
+ # Stack frames:
605
+ # - frame: log_stream_chunk (this function)
606
+ # - frame.f_back: the actual caller (e.g., _present_final_answer)
607
+
608
+ if frame and frame.f_back:
609
+ caller_frame = frame.f_back
610
+ function_name = caller_frame.f_code.co_name
611
+ line_number = caller_frame.f_lineno
612
+ else:
613
+ function_name = "unknown"
614
+ line_number = 0
615
+
616
+ if agent_id:
617
+ log_name = f"{source}.{agent_id}"
618
+ else:
619
+ log_name = source
620
+
621
+ # Create a custom logger that will show the source name instead of module path
622
+ log = logger.bind(name=f"{log_name}:{function_name}:{line_number}")
623
+
624
+ # Always log stream chunks at INFO level (will go to file)
625
+ # Format content based on type
626
+ if content:
627
+ if isinstance(content, dict):
628
+ log.info("Stream chunk [{}]: {}", chunk_type, content)
629
+ else:
630
+ # No truncation - show full content
631
+ log.info("Stream chunk [{}]: {}", chunk_type, content)
632
+ else:
633
+ log.info("Stream chunk [{}]", chunk_type)
634
+
635
+
636
+ def _format_message(message: dict) -> str:
637
+ """
638
+ Format message for logging without truncation.
639
+
640
+ Args:
641
+ message: Message dictionary
642
+
643
+ Returns:
644
+ Formatted message string
645
+ """
646
+ if not message:
647
+ return "<empty>"
648
+
649
+ # Format based on message type
650
+ if "role" in message and "content" in message:
651
+ content = message.get("content", "")
652
+ if isinstance(content, str):
653
+ # No truncation - show full content
654
+ return f"[{message['role']}] {content}"
655
+ else:
656
+ return f"[{message['role']}] {str(content)}"
657
+
658
+ # For other message types, just stringify without truncation
659
+ msg_str = str(message)
660
+ return msg_str
661
+
662
+
663
+ # Export main components
664
+ __all__ = [
665
+ "logger",
666
+ "setup_logging",
667
+ "suppress_console_logging",
668
+ "restore_console_logging",
669
+ "get_logger",
670
+ "get_log_session_dir",
671
+ "save_execution_metadata",
672
+ "log_orchestrator_activity",
673
+ "log_agent_message",
674
+ "log_orchestrator_agent_message",
675
+ "log_backend_agent_message",
676
+ "log_backend_activity",
677
+ "log_mcp_activity",
678
+ "log_tool_call",
679
+ "log_coordination_step",
680
+ "log_stream_chunk",
681
+ ]