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,134 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Unit tests for ExternalAgentBackend.
4
+ """
5
+ import pytest
6
+
7
+ from massgen.adapters import adapter_registry
8
+ from massgen.adapters.base import AgentAdapter
9
+ from massgen.backend.external import ExternalAgentBackend
10
+
11
+
12
+ class SimpleTestAdapter(AgentAdapter):
13
+ """Simple test adapter."""
14
+
15
+ async def execute_streaming(self, messages, tools, **kwargs):
16
+ """Return simple response."""
17
+ content = "Test response"
18
+ async for chunk in self.simulate_streaming(content):
19
+ yield chunk
20
+
21
+
22
+ @pytest.fixture(autouse=True)
23
+ def register_test_adapter():
24
+ """Register test adapter before tests."""
25
+ adapter_registry["test"] = SimpleTestAdapter
26
+ yield
27
+ # Cleanup
28
+ if "test" in adapter_registry:
29
+ del adapter_registry["test"]
30
+
31
+
32
+ def test_initialization_with_valid_adapter():
33
+ """Test backend initialization with valid adapter type."""
34
+ backend = ExternalAgentBackend(adapter_type="test")
35
+
36
+ assert backend.adapter_type == "test"
37
+ assert isinstance(backend.adapter, SimpleTestAdapter)
38
+ assert backend.get_provider_name() == "test"
39
+
40
+
41
+ def test_initialization_with_invalid_adapter():
42
+ """Test backend initialization with invalid adapter type."""
43
+ with pytest.raises(ValueError) as exc_info:
44
+ ExternalAgentBackend(adapter_type="nonexistent")
45
+
46
+ assert "Unsupported framework" in str(exc_info.value)
47
+ assert "nonexistent" in str(exc_info.value)
48
+
49
+
50
+ def test_adapter_type_case_insensitive():
51
+ """Test that adapter type is case-insensitive."""
52
+ backend1 = ExternalAgentBackend(adapter_type="TEST")
53
+ backend2 = ExternalAgentBackend(adapter_type="Test")
54
+ backend3 = ExternalAgentBackend(adapter_type="test")
55
+
56
+ assert backend1.adapter_type == "test"
57
+ assert backend2.adapter_type == "test"
58
+ assert backend3.adapter_type == "test"
59
+
60
+
61
+ @pytest.mark.asyncio
62
+ async def test_stream_with_tools():
63
+ """Test streaming with tools."""
64
+ backend = ExternalAgentBackend(adapter_type="test")
65
+
66
+ messages = [{"role": "user", "content": "Hello"}]
67
+ tools = []
68
+
69
+ chunks = []
70
+ async for chunk in backend.stream_with_tools(messages, tools):
71
+ chunks.append(chunk)
72
+
73
+ # Should receive chunks from adapter
74
+ assert len(chunks) > 0
75
+ assert any(c.type == "content" for c in chunks)
76
+ assert any(c.type == "done" for c in chunks)
77
+
78
+
79
+ def test_extract_adapter_config():
80
+ """Test extraction of adapter-specific config."""
81
+ backend = ExternalAgentBackend(
82
+ adapter_type="test",
83
+ # Base params (should be excluded)
84
+ type="test",
85
+ agent_id="test_agent",
86
+ session_id="session_1",
87
+ # Custom params (should be included)
88
+ custom_param="value",
89
+ temperature=0.7,
90
+ )
91
+
92
+ # Check that adapter received config
93
+ assert "custom_param" in backend.adapter.config
94
+ assert "temperature" in backend.adapter.config
95
+
96
+ # Base params should not be in adapter config
97
+ assert "type" not in backend.adapter.config
98
+ assert "agent_id" not in backend.adapter.config
99
+ assert "session_id" not in backend.adapter.config
100
+
101
+
102
+ def test_is_stateful_default():
103
+ """Test stateful check with default adapter."""
104
+ backend = ExternalAgentBackend(adapter_type="test")
105
+
106
+ # Default is False
107
+ assert backend.is_stateful() is False
108
+
109
+
110
+ def test_clear_history():
111
+ """Test clearing history."""
112
+ backend = ExternalAgentBackend(adapter_type="test")
113
+
114
+ # Add some history
115
+ backend.adapter._conversation_history = [{"role": "user", "content": "test"}]
116
+
117
+ # Clear
118
+ backend.clear_history()
119
+
120
+ assert len(backend.adapter._conversation_history) == 0
121
+
122
+
123
+ def test_reset_state():
124
+ """Test resetting state."""
125
+ backend = ExternalAgentBackend(adapter_type="test")
126
+
127
+ # Add some history
128
+ backend.adapter._conversation_history = [{"role": "user", "content": "test"}]
129
+
130
+ # Reset
131
+ backend.reset_state()
132
+
133
+ # Should clear history
134
+ assert len(backend.adapter._conversation_history) == 0
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Test script for testing the final presentation fallback functionality.
5
+ This tests the specific changes we made to handle empty final presentations.
6
+ """
7
+
8
+ import asyncio
9
+ import os
10
+ import sys
11
+ from unittest.mock import Mock
12
+
13
+ import pytest
14
+
15
+ # Ensure project root is on sys.path
16
+ PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
17
+ if PROJECT_ROOT not in sys.path:
18
+ sys.path.insert(0, PROJECT_ROOT)
19
+
20
+
21
+ @pytest.mark.asyncio
22
+ async def test_final_presentation_fallback():
23
+ """Test that the final presentation fallback works when content is empty."""
24
+ try:
25
+ try:
26
+ from massgen.orchestrator import Orchestrator
27
+ except ModuleNotFoundError as e:
28
+ # Skip if optional backend deps are missing during package import
29
+ if "claude_code_sdk" in str(e):
30
+ pytest.skip("Skipping: optional dependency 'claude_code_sdk' not installed")
31
+ raise
32
+
33
+ # Create a mock orchestrator with minimal setup
34
+ orchestrator = Orchestrator(agents={})
35
+
36
+ # Mock the agent states to simulate a stored answer
37
+ orchestrator.agent_states = {"test_agent": Mock(answer="This is a stored answer for testing purposes.")}
38
+
39
+ # Mock the message templates
40
+ orchestrator.message_templates = Mock()
41
+ orchestrator.message_templates.build_final_presentation_message.return_value = "Test message"
42
+ orchestrator.message_templates.final_presentation_system_message.return_value = "Test system message"
43
+
44
+ # Mock the current task
45
+ orchestrator.current_task = "Test task"
46
+
47
+ # Create a mock agent that returns no content
48
+ mock_agent = Mock()
49
+
50
+ # Simulate empty response from agent
51
+ async def empty_response(*args, **kwargs):
52
+ # Yield a done chunk with no content
53
+ yield Mock(type="done", content="")
54
+
55
+ # Set the chat method to return the async generator
56
+ mock_agent.chat = empty_response
57
+
58
+ # Add the mock agent to orchestrator
59
+ orchestrator.agents = {"test_agent": mock_agent}
60
+
61
+ # Test the get_final_presentation method
62
+ vote_results = {
63
+ "vote_counts": {"test_agent": 1},
64
+ "voter_details": {"test_agent": [{"voter": "other_agent", "reason": "Test reason"}]},
65
+ "is_tie": False,
66
+ }
67
+
68
+ # Collect all chunks from the method
69
+ chunks = []
70
+ async for chunk in orchestrator.get_final_presentation("test_agent", vote_results):
71
+ chunks.append(chunk)
72
+
73
+ # Check if we got the fallback content
74
+ fallback_found = any(getattr(c, "type", None) == "content" and (getattr(c, "content", "") or "").find("stored answer") != -1 for c in chunks)
75
+
76
+ assert fallback_found, "Fallback content not found"
77
+ except Exception as e:
78
+ assert False, f"Error during fallback test: {e}"
79
+
80
+
81
+ @pytest.mark.asyncio
82
+ async def test_final_presentation_with_content():
83
+ """Test that the final presentation works normally when content is provided."""
84
+ try:
85
+ from massgen.orchestrator import Orchestrator
86
+
87
+ # Create a mock orchestrator with minimal setup
88
+ orchestrator = Orchestrator(agents={})
89
+
90
+ # Mock the agent states
91
+ orchestrator.agent_states = {"test_agent": Mock(answer="This is a stored answer for testing purposes.")}
92
+
93
+ # Mock the message templates
94
+ orchestrator.message_templates = Mock()
95
+ orchestrator.message_templates.build_final_presentation_message.return_value = "Test message"
96
+ orchestrator.message_templates.final_presentation_system_message.return_value = "Test system message"
97
+
98
+ # Mock the current task
99
+ orchestrator.current_task = "Test task"
100
+
101
+ # Create a mock agent that returns content
102
+ mock_agent = Mock()
103
+
104
+ # Simulate normal response from agent
105
+ async def normal_response(*args, **kwargs):
106
+ # Yield content chunks
107
+ yield Mock(type="content", content="This is the final presentation content.")
108
+ yield Mock(type="done", content="")
109
+
110
+ # Set the chat method to return the async generator
111
+ mock_agent.chat = normal_response
112
+
113
+ # Add the mock agent to orchestrator
114
+ orchestrator.agents = {"test_agent": mock_agent}
115
+
116
+ # Test the get_final_presentation method
117
+ vote_results = {
118
+ "vote_counts": {"test_agent": 1},
119
+ "voter_details": {"test_agent": [{"voter": "other_agent", "reason": "Test reason"}]},
120
+ "is_tie": False,
121
+ }
122
+
123
+ # Collect all chunks from the method
124
+ chunks = []
125
+ async for chunk in orchestrator.get_final_presentation("test_agent", vote_results):
126
+ chunks.append(chunk)
127
+
128
+ # Check if we got the normal content (no fallback needed)
129
+ content_found = any(getattr(c, "type", None) == "content" and (getattr(c, "content", "") or "").find("final presentation content") != -1 for c in chunks)
130
+
131
+ assert content_found, "Normal content not found"
132
+ except Exception as e:
133
+ assert False, f"Error during normal content test: {e}"
134
+
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_no_stored_answer_fallback():
138
+ """Test that the fallback handles the case when there's no stored answer."""
139
+ try:
140
+ from massgen.orchestrator import Orchestrator
141
+
142
+ # Create a mock orchestrator with minimal setup
143
+ orchestrator = Orchestrator(agents={})
144
+
145
+ # Mock the agent states with no stored answer
146
+ orchestrator.agent_states = {"test_agent": Mock(answer="")} # No stored answer
147
+
148
+ # Mock the message templates
149
+ orchestrator.message_templates = Mock()
150
+ orchestrator.message_templates.build_final_presentation_message.return_value = "Test message"
151
+ orchestrator.message_templates.final_presentation_system_message.return_value = "Test system message"
152
+
153
+ # Mock the current task
154
+ orchestrator.current_task = "Test task"
155
+
156
+ # Create a mock agent that returns no content
157
+ mock_agent = Mock()
158
+
159
+ # Simulate empty response from agent
160
+ async def empty_response(*args, **kwargs):
161
+ # Yield a done chunk with no content
162
+ yield Mock(type="done", content="")
163
+
164
+ # Set the chat method to return the async generator
165
+ mock_agent.chat = empty_response
166
+
167
+ # Add the mock agent to orchestrator
168
+ orchestrator.agents = {"test_agent": mock_agent}
169
+
170
+ # Test the get_final_presentation method
171
+ vote_results = {
172
+ "vote_counts": {"test_agent": 1},
173
+ "voter_details": {"test_agent": [{"voter": "other_agent", "reason": "Test reason"}]},
174
+ "is_tie": False,
175
+ }
176
+
177
+ # Collect all chunks from the method
178
+ chunks = []
179
+ async for chunk in orchestrator.get_final_presentation("test_agent", vote_results):
180
+ chunks.append(chunk)
181
+
182
+ # Check if we got the no-content fallback message
183
+ fallback_found = any(getattr(c, "type", None) == "content" and (getattr(c, "content", "") or "").find("No content generated") != -1 for c in chunks)
184
+
185
+ assert fallback_found, "No-content fallback message not found"
186
+ except Exception as e:
187
+ assert False, f"Error during no stored answer test: {e}"
188
+
189
+
190
+ async def main():
191
+ """Main test runner."""
192
+ print("🚀 MassGen Final Presentation Fallback Test Suite")
193
+ print("Testing the specific changes we made to handle empty final presentations...")
194
+ print("=" * 80)
195
+
196
+ tests = [
197
+ ("Final Presentation Fallback", test_final_presentation_fallback),
198
+ ("Normal Final Presentation", test_final_presentation_with_content),
199
+ ("No Stored Answer Fallback", test_no_stored_answer_fallback),
200
+ ]
201
+
202
+ passed = 0
203
+ total = len(tests)
204
+
205
+ for test_name, test_func in tests:
206
+ print(f"\n{'='*20} {test_name} {'='*20}")
207
+ if await test_func():
208
+ passed += 1
209
+ print()
210
+
211
+ print("=" * 80)
212
+ print("🏁 Final Test Summary")
213
+ print("=" * 80)
214
+
215
+ if passed == total:
216
+ print("🎉 All tests passed! The final presentation fallback is working correctly.")
217
+ print("\n✅ What we've verified:")
218
+ print(" • Fallback to stored answer works when final presentation is empty")
219
+ print(" • Normal final presentation still works when content is provided")
220
+ print(" • Proper error message when no stored answer is available")
221
+ print("\n✅ The orchestrator changes are robust and won't break the program!")
222
+ return 0
223
+ else:
224
+ print(f"❌ {total - passed} tests failed. Please check the errors above.")
225
+ return 1
226
+
227
+
228
+ if __name__ == "__main__":
229
+ try:
230
+ exit_code = asyncio.run(main())
231
+ sys.exit(exit_code)
232
+ except KeyboardInterrupt:
233
+ print("\n\n⏹️ Tests interrupted by user")
234
+ sys.exit(1)
235
+ except Exception as e:
236
+ print(f"\n\n💥 Unexpected error during testing: {e}")
237
+ sys.exit(1)