massgen 0.1.0a3__py3-none-any.whl → 0.1.1__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.
- massgen/__init__.py +1 -1
- massgen/agent_config.py +17 -0
- massgen/api_params_handler/_api_params_handler_base.py +1 -0
- massgen/api_params_handler/_chat_completions_api_params_handler.py +8 -1
- massgen/api_params_handler/_claude_api_params_handler.py +8 -1
- massgen/api_params_handler/_gemini_api_params_handler.py +73 -0
- massgen/api_params_handler/_response_api_params_handler.py +8 -1
- massgen/backend/base.py +31 -0
- massgen/backend/{base_with_mcp.py → base_with_custom_tool_and_mcp.py} +282 -11
- massgen/backend/chat_completions.py +182 -92
- massgen/backend/claude.py +115 -18
- massgen/backend/claude_code.py +378 -14
- massgen/backend/docs/CLAUDE_API_RESEARCH.md +3 -3
- massgen/backend/gemini.py +1275 -1607
- massgen/backend/gemini_mcp_manager.py +545 -0
- massgen/backend/gemini_trackers.py +344 -0
- massgen/backend/gemini_utils.py +43 -0
- massgen/backend/response.py +129 -70
- massgen/cli.py +577 -110
- massgen/config_builder.py +376 -27
- massgen/configs/README.md +111 -80
- massgen/configs/basic/multi/three_agents_default.yaml +1 -1
- massgen/configs/basic/single/single_agent.yaml +1 -1
- massgen/configs/providers/openai/gpt5_nano.yaml +3 -3
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example.yaml +32 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_example_no_path.yaml +28 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_mcp_example.yaml +40 -0
- massgen/configs/tools/custom_tools/claude_code_custom_tool_with_wrong_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_code_wrong_custom_tool_with_mcp_example.yaml +38 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/claude_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/claude_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gemini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gemini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/github_issue_market_analysis.yaml +94 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/gpt5_nano_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt5_nano_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/gpt_oss_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/grok3_mini_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/grok3_mini_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example.yaml +25 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_example_no_path.yaml +23 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_mcp_example.yaml +36 -0
- massgen/configs/tools/custom_tools/qwen_api_custom_tool_with_wrong_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_api_wrong_custom_tool_with_mcp_example.yaml +34 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example.yaml +24 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_example_no_path.yaml +22 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_mcp_example.yaml +35 -0
- massgen/configs/tools/custom_tools/qwen_local_custom_tool_with_wrong_mcp_example.yaml +33 -0
- massgen/configs/tools/custom_tools/qwen_local_wrong_custom_tool_with_mcp_example.yaml +33 -0
- massgen/configs/tools/filesystem/claude_code_context_sharing.yaml +1 -1
- massgen/configs/voting/gemini_gpt_voting_sensitivity.yaml +67 -0
- massgen/formatter/_chat_completions_formatter.py +104 -0
- massgen/formatter/_claude_formatter.py +120 -0
- massgen/formatter/_gemini_formatter.py +448 -0
- massgen/formatter/_response_formatter.py +88 -0
- massgen/frontend/coordination_ui.py +4 -2
- massgen/logger_config.py +35 -3
- massgen/message_templates.py +56 -6
- massgen/orchestrator.py +179 -10
- massgen/stream_chunk/base.py +3 -0
- massgen/tests/custom_tools_example.py +392 -0
- massgen/tests/mcp_test_server.py +17 -7
- massgen/tests/test_config_builder.py +423 -0
- massgen/tests/test_custom_tools.py +401 -0
- massgen/tests/test_tools.py +127 -0
- massgen/tool/README.md +935 -0
- massgen/tool/__init__.py +39 -0
- massgen/tool/_async_helpers.py +70 -0
- massgen/tool/_basic/__init__.py +8 -0
- massgen/tool/_basic/_two_num_tool.py +24 -0
- massgen/tool/_code_executors/__init__.py +10 -0
- massgen/tool/_code_executors/_python_executor.py +74 -0
- massgen/tool/_code_executors/_shell_executor.py +61 -0
- massgen/tool/_exceptions.py +39 -0
- massgen/tool/_file_handlers/__init__.py +10 -0
- massgen/tool/_file_handlers/_file_operations.py +218 -0
- massgen/tool/_manager.py +634 -0
- massgen/tool/_registered_tool.py +88 -0
- massgen/tool/_result.py +66 -0
- massgen/tool/_self_evolution/_github_issue_analyzer.py +369 -0
- massgen/tool/docs/builtin_tools.md +681 -0
- massgen/tool/docs/exceptions.md +794 -0
- massgen/tool/docs/execution_results.md +691 -0
- massgen/tool/docs/manager.md +887 -0
- massgen/tool/docs/workflow_toolkits.md +529 -0
- massgen/tool/workflow_toolkits/__init__.py +57 -0
- massgen/tool/workflow_toolkits/base.py +55 -0
- massgen/tool/workflow_toolkits/new_answer.py +126 -0
- massgen/tool/workflow_toolkits/vote.py +167 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/METADATA +89 -131
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/RECORD +111 -36
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/WHEEL +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/entry_points.txt +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {massgen-0.1.0a3.dist-info → massgen-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Comprehensive tests for config_builder.py - Agent cloning and preset application.
|
|
4
|
+
|
|
5
|
+
These tests ensure:
|
|
6
|
+
1. Cross-provider cloning preserves correct settings
|
|
7
|
+
2. Incompatible settings are skipped with warnings
|
|
8
|
+
3. Workspace uniqueness across agents
|
|
9
|
+
4. Tool compatibility matrix is enforced
|
|
10
|
+
|
|
11
|
+
Run with: uv run pytest massgen/tests/test_config_builder.py -v
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from massgen.config_builder import ConfigBuilder
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestCloneAgent:
|
|
20
|
+
"""Test agent cloning across providers with compatibility checks."""
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def builder(self):
|
|
24
|
+
"""Create a ConfigBuilder instance for testing."""
|
|
25
|
+
return ConfigBuilder()
|
|
26
|
+
|
|
27
|
+
def test_clone_openai_to_gemini_preserves_provider(self, builder):
|
|
28
|
+
"""Test cloning OpenAI to Gemini preserves Gemini provider."""
|
|
29
|
+
source = {
|
|
30
|
+
"id": "agent_a",
|
|
31
|
+
"backend": {
|
|
32
|
+
"type": "openai",
|
|
33
|
+
"model": "gpt-5",
|
|
34
|
+
"enable_web_search": True,
|
|
35
|
+
"cwd": "workspace1",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="gemini")
|
|
40
|
+
|
|
41
|
+
assert cloned["id"] == "agent_b"
|
|
42
|
+
assert cloned["backend"]["type"] == "gemini"
|
|
43
|
+
assert cloned["backend"]["model"] == "gemini-2.5-flash" # Default Gemini model
|
|
44
|
+
assert cloned["backend"]["enable_web_search"] is True # Compatible, should copy
|
|
45
|
+
assert cloned["backend"]["cwd"] == "workspace2" # Filesystem copied and updated to agent_b's number
|
|
46
|
+
|
|
47
|
+
def test_clone_copies_filesystem_cwd(self, builder):
|
|
48
|
+
"""Test that cwd (filesystem) is copied and updated for target agent."""
|
|
49
|
+
source = {
|
|
50
|
+
"id": "agent_a",
|
|
51
|
+
"backend": {"type": "openai", "model": "gpt-5", "cwd": "workspace1"},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="claude")
|
|
55
|
+
|
|
56
|
+
# cwd should exist (copied from source) and updated to agent_b's workspace number
|
|
57
|
+
assert "cwd" in cloned["backend"]
|
|
58
|
+
assert cloned["backend"]["cwd"] == "workspace2" # agent_b -> workspace2
|
|
59
|
+
|
|
60
|
+
def test_clone_updates_workspace_number(self, builder):
|
|
61
|
+
"""Test that workspace number updates for target agent ID."""
|
|
62
|
+
source = {
|
|
63
|
+
"id": "agent_a",
|
|
64
|
+
"backend": {"type": "openai", "model": "gpt-5", "cwd": "workspace1"},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cloned = builder.clone_agent(source, "agent_c", target_backend_type="openai")
|
|
68
|
+
|
|
69
|
+
assert cloned["backend"]["cwd"] == "workspace3" # agent_c -> workspace3
|
|
70
|
+
|
|
71
|
+
def test_clone_copies_compatible_tools(self, builder):
|
|
72
|
+
"""Test that compatible tools are copied across providers."""
|
|
73
|
+
source = {
|
|
74
|
+
"id": "agent_a",
|
|
75
|
+
"backend": {
|
|
76
|
+
"type": "openai",
|
|
77
|
+
"model": "gpt-5",
|
|
78
|
+
"enable_web_search": True,
|
|
79
|
+
"enable_mcp_command_line": True,
|
|
80
|
+
"command_line_execution_mode": "docker",
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="gemini")
|
|
85
|
+
|
|
86
|
+
# Universal tools should be copied
|
|
87
|
+
assert cloned["backend"]["enable_mcp_command_line"] is True
|
|
88
|
+
assert cloned["backend"]["command_line_execution_mode"] == "docker"
|
|
89
|
+
# Web search compatible with both OpenAI and Gemini
|
|
90
|
+
assert cloned["backend"]["enable_web_search"] is True
|
|
91
|
+
|
|
92
|
+
def test_clone_skips_incompatible_tools(self, builder):
|
|
93
|
+
"""Test that incompatible tools are skipped with warnings."""
|
|
94
|
+
source = {
|
|
95
|
+
"id": "agent_a",
|
|
96
|
+
"backend": {
|
|
97
|
+
"type": "openai",
|
|
98
|
+
"model": "gpt-5",
|
|
99
|
+
"enable_code_interpreter": True, # OpenAI-specific
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="gemini")
|
|
104
|
+
|
|
105
|
+
# Incompatible setting should be skipped
|
|
106
|
+
assert "enable_code_interpreter" not in cloned["backend"]
|
|
107
|
+
# Should generate skipped settings list
|
|
108
|
+
assert "_skipped_settings" in cloned
|
|
109
|
+
assert any("enable_code_interpreter" in s for s in cloned["_skipped_settings"])
|
|
110
|
+
|
|
111
|
+
def test_clone_skips_openai_reasoning_to_gemini(self, builder):
|
|
112
|
+
"""Test that OpenAI reasoning/text settings are skipped for other providers."""
|
|
113
|
+
source = {
|
|
114
|
+
"id": "agent_a",
|
|
115
|
+
"backend": {
|
|
116
|
+
"type": "openai",
|
|
117
|
+
"model": "gpt-5",
|
|
118
|
+
"text": {"verbosity": "medium"},
|
|
119
|
+
"reasoning": {"effort": "high", "summary": "auto"},
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="gemini")
|
|
124
|
+
|
|
125
|
+
# OpenAI-specific settings should be skipped
|
|
126
|
+
assert "text" not in cloned["backend"]
|
|
127
|
+
assert "reasoning" not in cloned["backend"]
|
|
128
|
+
# Should be in skipped list
|
|
129
|
+
assert any("text" in s for s in cloned["_skipped_settings"])
|
|
130
|
+
assert any("reasoning" in s for s in cloned["_skipped_settings"])
|
|
131
|
+
|
|
132
|
+
def test_clone_preserves_openai_reasoning_to_openai(self, builder):
|
|
133
|
+
"""Test that OpenAI reasoning settings are preserved when cloning to OpenAI."""
|
|
134
|
+
source = {
|
|
135
|
+
"id": "agent_a",
|
|
136
|
+
"backend": {
|
|
137
|
+
"type": "openai",
|
|
138
|
+
"model": "gpt-5",
|
|
139
|
+
"text": {"verbosity": "medium"},
|
|
140
|
+
"reasoning": {"effort": "high", "summary": "auto"},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="openai")
|
|
145
|
+
|
|
146
|
+
# OpenAI-specific settings should be preserved
|
|
147
|
+
assert cloned["backend"]["text"]["verbosity"] == "medium"
|
|
148
|
+
assert cloned["backend"]["reasoning"]["effort"] == "high"
|
|
149
|
+
|
|
150
|
+
def test_clone_skips_code_interpreter_openai_to_gemini(self, builder):
|
|
151
|
+
"""Test code_interpreter is skipped when cloning OpenAI to Gemini."""
|
|
152
|
+
source = {
|
|
153
|
+
"id": "agent_a",
|
|
154
|
+
"backend": {
|
|
155
|
+
"type": "openai",
|
|
156
|
+
"model": "gpt-5",
|
|
157
|
+
"enable_code_interpreter": True,
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="gemini")
|
|
162
|
+
|
|
163
|
+
assert "enable_code_interpreter" not in cloned["backend"]
|
|
164
|
+
assert any("enable_code_interpreter" in s for s in cloned["_skipped_settings"])
|
|
165
|
+
|
|
166
|
+
def test_clone_skips_code_execution_gemini_to_openai(self, builder):
|
|
167
|
+
"""Test code_execution is skipped when cloning Gemini to OpenAI."""
|
|
168
|
+
source = {
|
|
169
|
+
"id": "agent_a",
|
|
170
|
+
"backend": {"type": "gemini", "model": "gemini-2.5-pro", "enable_code_execution": True},
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="openai")
|
|
174
|
+
|
|
175
|
+
assert "enable_code_execution" not in cloned["backend"]
|
|
176
|
+
assert any("enable_code_execution" in s for s in cloned["_skipped_settings"])
|
|
177
|
+
|
|
178
|
+
def test_clone_copies_mcp_servers_when_supported(self, builder):
|
|
179
|
+
"""Test MCP servers are copied when target supports MCP."""
|
|
180
|
+
source = {
|
|
181
|
+
"id": "agent_a",
|
|
182
|
+
"backend": {
|
|
183
|
+
"type": "openai",
|
|
184
|
+
"model": "gpt-5",
|
|
185
|
+
"mcp_servers": [
|
|
186
|
+
{"name": "weather", "command": "npx", "args": ["-y", "@fak111/weather-mcp"]},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="gemini")
|
|
192
|
+
|
|
193
|
+
# Gemini supports MCP, should copy
|
|
194
|
+
assert "mcp_servers" in cloned["backend"]
|
|
195
|
+
assert cloned["backend"]["mcp_servers"][0]["name"] == "weather"
|
|
196
|
+
|
|
197
|
+
def test_clone_same_provider_copies_everything(self, builder):
|
|
198
|
+
"""Test cloning within same provider copies all settings."""
|
|
199
|
+
source = {
|
|
200
|
+
"id": "agent_a",
|
|
201
|
+
"backend": {
|
|
202
|
+
"type": "openai",
|
|
203
|
+
"model": "gpt-5",
|
|
204
|
+
"enable_web_search": True,
|
|
205
|
+
"enable_code_interpreter": True,
|
|
206
|
+
"text": {"verbosity": "high"},
|
|
207
|
+
"reasoning": {"effort": "medium", "summary": "auto"},
|
|
208
|
+
"cwd": "workspace1",
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="openai")
|
|
213
|
+
|
|
214
|
+
# Everything should be copied for same provider
|
|
215
|
+
assert cloned["backend"]["enable_web_search"] is True
|
|
216
|
+
assert cloned["backend"]["enable_code_interpreter"] is True
|
|
217
|
+
assert cloned["backend"]["text"]["verbosity"] == "high"
|
|
218
|
+
assert cloned["backend"]["reasoning"]["effort"] == "medium"
|
|
219
|
+
|
|
220
|
+
def test_clone_generates_skipped_settings_list(self, builder):
|
|
221
|
+
"""Test that _skipped_settings list is generated for incompatible settings."""
|
|
222
|
+
source = {
|
|
223
|
+
"id": "agent_a",
|
|
224
|
+
"backend": {
|
|
225
|
+
"type": "openai",
|
|
226
|
+
"model": "gpt-5",
|
|
227
|
+
"enable_code_interpreter": True,
|
|
228
|
+
"text": {"verbosity": "high"},
|
|
229
|
+
"reasoning": {"effort": "medium", "summary": "auto"},
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type="claude")
|
|
234
|
+
|
|
235
|
+
# Should have skipped settings
|
|
236
|
+
assert "_skipped_settings" in cloned
|
|
237
|
+
skipped = cloned["_skipped_settings"]
|
|
238
|
+
assert len(skipped) > 0
|
|
239
|
+
# All three should be skipped
|
|
240
|
+
assert any("enable_code_interpreter" in s for s in skipped)
|
|
241
|
+
assert any("text" in s for s in skipped)
|
|
242
|
+
assert any("reasoning" in s for s in skipped)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class TestApplyPresetToAgent:
|
|
246
|
+
"""Test preset application generates unique workspaces."""
|
|
247
|
+
|
|
248
|
+
@pytest.fixture
|
|
249
|
+
def builder(self):
|
|
250
|
+
"""Create a ConfigBuilder instance for testing."""
|
|
251
|
+
return ConfigBuilder()
|
|
252
|
+
|
|
253
|
+
def test_apply_preset_generates_unique_workspace(self, builder):
|
|
254
|
+
"""Test that preset application generates workspace based on agent index."""
|
|
255
|
+
agent = {"id": "agent_a", "backend": {"type": "openai", "model": "gpt-5"}}
|
|
256
|
+
|
|
257
|
+
updated = builder.apply_preset_to_agent(agent, "coding", agent_index=1)
|
|
258
|
+
|
|
259
|
+
assert updated["backend"]["cwd"] == "workspace1"
|
|
260
|
+
|
|
261
|
+
def test_apply_preset_multiple_agents_unique_workspaces(self, builder):
|
|
262
|
+
"""Test that multiple agents get unique workspace numbers."""
|
|
263
|
+
agents = [
|
|
264
|
+
{"id": "agent_a", "backend": {"type": "openai", "model": "gpt-5"}},
|
|
265
|
+
{"id": "agent_b", "backend": {"type": "gemini", "model": "gemini-2.5-pro"}},
|
|
266
|
+
{"id": "agent_c", "backend": {"type": "claude", "model": "claude-3-5-sonnet-20241022"}},
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
for i, agent in enumerate(agents):
|
|
270
|
+
updated = builder.apply_preset_to_agent(agent, "coding", agent_index=i + 1)
|
|
271
|
+
agents[i] = updated
|
|
272
|
+
|
|
273
|
+
# Check all have unique workspaces
|
|
274
|
+
assert agents[0]["backend"]["cwd"] == "workspace1"
|
|
275
|
+
assert agents[1]["backend"]["cwd"] == "workspace2"
|
|
276
|
+
assert agents[2]["backend"]["cwd"] == "workspace3"
|
|
277
|
+
|
|
278
|
+
def test_apply_preset_filesystem_enabled_for_preset(self, builder):
|
|
279
|
+
"""Test that filesystem is enabled for presets that require it."""
|
|
280
|
+
agent = {"id": "agent_a", "backend": {"type": "openai", "model": "gpt-5"}}
|
|
281
|
+
|
|
282
|
+
updated = builder.apply_preset_to_agent(agent, "coding", agent_index=1)
|
|
283
|
+
|
|
284
|
+
# Coding preset requires filesystem
|
|
285
|
+
assert "cwd" in updated["backend"]
|
|
286
|
+
|
|
287
|
+
def test_apply_preset_respects_agent_index(self, builder):
|
|
288
|
+
"""Test that agent_index correctly determines workspace number."""
|
|
289
|
+
agent = {"id": "agent_c", "backend": {"type": "openai", "model": "gpt-5"}}
|
|
290
|
+
|
|
291
|
+
updated = builder.apply_preset_to_agent(agent, "coding", agent_index=3)
|
|
292
|
+
|
|
293
|
+
assert updated["backend"]["cwd"] == "workspace3"
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class TestCrossProviderCompatibility:
|
|
297
|
+
"""Test tool compatibility matrix across providers."""
|
|
298
|
+
|
|
299
|
+
@pytest.fixture
|
|
300
|
+
def builder(self):
|
|
301
|
+
"""Create a ConfigBuilder instance for testing."""
|
|
302
|
+
return ConfigBuilder()
|
|
303
|
+
|
|
304
|
+
def test_web_search_compatible_providers(self, builder):
|
|
305
|
+
"""Test web_search is compatible with OpenAI, Gemini, Claude, Grok."""
|
|
306
|
+
source = {
|
|
307
|
+
"id": "agent_a",
|
|
308
|
+
"backend": {"type": "openai", "model": "gpt-5", "enable_web_search": True},
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
for target_type in ["gemini", "claude", "grok"]:
|
|
312
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type=target_type)
|
|
313
|
+
assert cloned["backend"]["enable_web_search"] is True, f"web_search should work on {target_type}"
|
|
314
|
+
|
|
315
|
+
def test_code_interpreter_only_openai_azure(self, builder):
|
|
316
|
+
"""Test code_interpreter only works on OpenAI and Azure OpenAI."""
|
|
317
|
+
source = {
|
|
318
|
+
"id": "agent_a",
|
|
319
|
+
"backend": {"type": "openai", "model": "gpt-5", "enable_code_interpreter": True},
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
# Should be skipped for non-OpenAI providers
|
|
323
|
+
for target_type in ["gemini", "claude", "grok"]:
|
|
324
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type=target_type)
|
|
325
|
+
assert "enable_code_interpreter" not in cloned["backend"], f"code_interpreter shouldn't work on {target_type}"
|
|
326
|
+
assert any("enable_code_interpreter" in s for s in cloned["_skipped_settings"])
|
|
327
|
+
|
|
328
|
+
def test_code_execution_only_claude_gemini(self, builder):
|
|
329
|
+
"""Test code_execution only works on Claude and Gemini."""
|
|
330
|
+
source = {
|
|
331
|
+
"id": "agent_a",
|
|
332
|
+
"backend": {"type": "gemini", "model": "gemini-2.5-pro", "enable_code_execution": True},
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
# Should work on Claude
|
|
336
|
+
cloned_claude = builder.clone_agent(source, "agent_b", target_backend_type="claude")
|
|
337
|
+
assert cloned_claude["backend"]["enable_code_execution"] is True
|
|
338
|
+
|
|
339
|
+
# Should be skipped for OpenAI
|
|
340
|
+
cloned_openai = builder.clone_agent(source, "agent_c", target_backend_type="openai")
|
|
341
|
+
assert "enable_code_execution" not in cloned_openai["backend"]
|
|
342
|
+
assert any("enable_code_execution" in s for s in cloned_openai["_skipped_settings"])
|
|
343
|
+
|
|
344
|
+
def test_filesystem_copied_across_all_providers(self, builder):
|
|
345
|
+
"""Test filesystem (cwd) is copied across all providers and updated to target agent number."""
|
|
346
|
+
source = {"id": "agent_a", "backend": {"type": "openai", "model": "gpt-5", "cwd": "workspace1"}}
|
|
347
|
+
|
|
348
|
+
for target_type in ["gemini", "claude", "grok", "claude_code", "lmstudio"]:
|
|
349
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type=target_type)
|
|
350
|
+
# cwd should exist and be updated to workspace2 for agent_b
|
|
351
|
+
assert "cwd" in cloned["backend"], f"cwd should be copied to {target_type}"
|
|
352
|
+
assert cloned["backend"]["cwd"] == "workspace2", f"cwd should be workspace2 for {target_type}"
|
|
353
|
+
|
|
354
|
+
def test_command_line_execution_mode_universal(self, builder):
|
|
355
|
+
"""Test command_line_execution_mode is universal across providers."""
|
|
356
|
+
source = {
|
|
357
|
+
"id": "agent_a",
|
|
358
|
+
"backend": {
|
|
359
|
+
"type": "openai",
|
|
360
|
+
"model": "gpt-5",
|
|
361
|
+
"enable_mcp_command_line": True,
|
|
362
|
+
"command_line_execution_mode": "docker",
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for target_type in ["gemini", "claude", "grok"]:
|
|
367
|
+
cloned = builder.clone_agent(source, "agent_b", target_backend_type=target_type)
|
|
368
|
+
assert cloned["backend"]["enable_mcp_command_line"] is True
|
|
369
|
+
assert cloned["backend"]["command_line_execution_mode"] == "docker"
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class TestWorkspaceUniqueness:
|
|
373
|
+
"""Test workspace uniqueness across different scenarios."""
|
|
374
|
+
|
|
375
|
+
@pytest.fixture
|
|
376
|
+
def builder(self):
|
|
377
|
+
"""Create a ConfigBuilder instance for testing."""
|
|
378
|
+
return ConfigBuilder()
|
|
379
|
+
|
|
380
|
+
def test_three_agents_get_workspace1_2_3(self, builder):
|
|
381
|
+
"""Test that three agents get workspace1, workspace2, workspace3."""
|
|
382
|
+
agents = [
|
|
383
|
+
{"id": "agent_a", "backend": {"type": "openai", "model": "gpt-5"}},
|
|
384
|
+
{"id": "agent_b", "backend": {"type": "gemini", "model": "gemini-2.5-pro"}},
|
|
385
|
+
{"id": "agent_c", "backend": {"type": "claude", "model": "claude-3-5-sonnet-20241022"}},
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
for i, agent in enumerate(agents):
|
|
389
|
+
agents[i] = builder.apply_preset_to_agent(agent, "coding", agent_index=i + 1)
|
|
390
|
+
|
|
391
|
+
assert agents[0]["backend"]["cwd"] == "workspace1"
|
|
392
|
+
assert agents[1]["backend"]["cwd"] == "workspace2"
|
|
393
|
+
assert agents[2]["backend"]["cwd"] == "workspace3"
|
|
394
|
+
|
|
395
|
+
def test_clone_updates_workspace_for_target_agent(self, builder):
|
|
396
|
+
"""Test that cloning updates workspace to match target agent ID."""
|
|
397
|
+
source = {"id": "agent_a", "backend": {"type": "openai", "model": "gpt-5", "cwd": "workspace1"}}
|
|
398
|
+
|
|
399
|
+
# Clone to agent_d should get workspace4
|
|
400
|
+
cloned = builder.clone_agent(source, "agent_d", target_backend_type="openai")
|
|
401
|
+
|
|
402
|
+
assert cloned["backend"]["cwd"] == "workspace4"
|
|
403
|
+
|
|
404
|
+
def test_mixed_providers_all_unique_workspaces(self, builder):
|
|
405
|
+
"""Test mixed preset + clone scenario maintains unique workspaces."""
|
|
406
|
+
# Agent A from preset
|
|
407
|
+
agent_a = {"id": "agent_a", "backend": {"type": "openai", "model": "gpt-5"}}
|
|
408
|
+
agent_a = builder.apply_preset_to_agent(agent_a, "coding", agent_index=1)
|
|
409
|
+
|
|
410
|
+
# Agent B cloned from A
|
|
411
|
+
agent_b = builder.clone_agent(agent_a, "agent_b", target_backend_type="gemini")
|
|
412
|
+
|
|
413
|
+
# Agent C from preset
|
|
414
|
+
agent_c = {"id": "agent_c", "backend": {"type": "claude", "model": "claude-3-5-sonnet-20241022"}}
|
|
415
|
+
agent_c = builder.apply_preset_to_agent(agent_c, "coding", agent_index=3)
|
|
416
|
+
|
|
417
|
+
assert agent_a["backend"]["cwd"] == "workspace1"
|
|
418
|
+
assert agent_b["backend"]["cwd"] == "workspace2" # Updated from agent_b ID
|
|
419
|
+
assert agent_c["backend"]["cwd"] == "workspace3"
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
if __name__ == "__main__":
|
|
423
|
+
pytest.main([__file__, "-v"])
|