massgen 0.1.3__py3-none-any.whl → 0.1.5__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 (90) hide show
  1. massgen/__init__.py +1 -1
  2. massgen/api_params_handler/_chat_completions_api_params_handler.py +4 -0
  3. massgen/api_params_handler/_claude_api_params_handler.py +4 -0
  4. massgen/api_params_handler/_gemini_api_params_handler.py +4 -0
  5. massgen/api_params_handler/_response_api_params_handler.py +4 -0
  6. massgen/backend/base_with_custom_tool_and_mcp.py +25 -5
  7. massgen/backend/docs/permissions_and_context_files.md +2 -2
  8. massgen/backend/response.py +2 -0
  9. massgen/chat_agent.py +340 -20
  10. massgen/cli.py +326 -19
  11. massgen/configs/README.md +92 -41
  12. massgen/configs/memory/gpt5mini_gemini_baseline_research_to_implementation.yaml +94 -0
  13. massgen/configs/memory/gpt5mini_gemini_context_window_management.yaml +187 -0
  14. massgen/configs/memory/gpt5mini_gemini_research_to_implementation.yaml +127 -0
  15. massgen/configs/memory/gpt5mini_high_reasoning_gemini.yaml +107 -0
  16. massgen/configs/memory/single_agent_compression_test.yaml +64 -0
  17. massgen/configs/tools/custom_tools/crawl4ai_example.yaml +55 -0
  18. massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_multi.yaml +61 -0
  19. massgen/configs/tools/custom_tools/multimodal_tools/text_to_file_generation_single.yaml +29 -0
  20. massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_multi.yaml +51 -0
  21. massgen/configs/tools/custom_tools/multimodal_tools/text_to_image_generation_single.yaml +33 -0
  22. massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_multi.yaml +55 -0
  23. massgen/configs/tools/custom_tools/multimodal_tools/text_to_speech_generation_single.yaml +33 -0
  24. massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_multi.yaml +47 -0
  25. massgen/configs/tools/custom_tools/multimodal_tools/text_to_video_generation_single.yaml +29 -0
  26. massgen/configs/tools/custom_tools/multimodal_tools/understand_audio.yaml +1 -1
  27. massgen/configs/tools/custom_tools/multimodal_tools/understand_file.yaml +1 -1
  28. massgen/configs/tools/custom_tools/multimodal_tools/understand_image.yaml +1 -1
  29. massgen/configs/tools/custom_tools/multimodal_tools/understand_video.yaml +1 -1
  30. massgen/configs/tools/custom_tools/multimodal_tools/youtube_video_analysis.yaml +1 -1
  31. massgen/filesystem_manager/_filesystem_manager.py +1 -0
  32. massgen/filesystem_manager/_path_permission_manager.py +148 -0
  33. massgen/memory/README.md +277 -0
  34. massgen/memory/__init__.py +26 -0
  35. massgen/memory/_base.py +193 -0
  36. massgen/memory/_compression.py +237 -0
  37. massgen/memory/_context_monitor.py +211 -0
  38. massgen/memory/_conversation.py +255 -0
  39. massgen/memory/_fact_extraction_prompts.py +333 -0
  40. massgen/memory/_mem0_adapters.py +257 -0
  41. massgen/memory/_persistent.py +687 -0
  42. massgen/memory/docker-compose.qdrant.yml +36 -0
  43. massgen/memory/docs/DESIGN.md +388 -0
  44. massgen/memory/docs/QUICKSTART.md +409 -0
  45. massgen/memory/docs/SUMMARY.md +319 -0
  46. massgen/memory/docs/agent_use_memory.md +408 -0
  47. massgen/memory/docs/orchestrator_use_memory.md +586 -0
  48. massgen/memory/examples.py +237 -0
  49. massgen/message_templates.py +160 -12
  50. massgen/orchestrator.py +223 -7
  51. massgen/tests/memory/test_agent_compression.py +174 -0
  52. massgen/{configs/tools → tests}/memory/test_context_window_management.py +30 -30
  53. massgen/tests/memory/test_force_compression.py +154 -0
  54. massgen/tests/memory/test_simple_compression.py +147 -0
  55. massgen/tests/test_agent_memory.py +534 -0
  56. massgen/tests/test_binary_file_blocking.py +274 -0
  57. massgen/tests/test_case_studies.md +12 -12
  58. massgen/tests/test_conversation_memory.py +382 -0
  59. massgen/tests/test_multimodal_size_limits.py +407 -0
  60. massgen/tests/test_orchestrator_memory.py +620 -0
  61. massgen/tests/test_persistent_memory.py +435 -0
  62. massgen/token_manager/token_manager.py +6 -0
  63. massgen/tool/_manager.py +7 -2
  64. massgen/tool/_multimodal_tools/image_to_image_generation.py +293 -0
  65. massgen/tool/_multimodal_tools/text_to_file_generation.py +455 -0
  66. massgen/tool/_multimodal_tools/text_to_image_generation.py +222 -0
  67. massgen/tool/_multimodal_tools/text_to_speech_continue_generation.py +226 -0
  68. massgen/tool/_multimodal_tools/text_to_speech_transcription_generation.py +217 -0
  69. massgen/tool/_multimodal_tools/text_to_video_generation.py +223 -0
  70. massgen/tool/_multimodal_tools/understand_audio.py +19 -1
  71. massgen/tool/_multimodal_tools/understand_file.py +6 -1
  72. massgen/tool/_multimodal_tools/understand_image.py +112 -8
  73. massgen/tool/_multimodal_tools/understand_video.py +32 -5
  74. massgen/tool/_web_tools/crawl4ai_tool.py +718 -0
  75. massgen/tool/docs/multimodal_tools.md +589 -0
  76. massgen/tools/__init__.py +8 -0
  77. massgen/tools/_planning_mcp_server.py +520 -0
  78. massgen/tools/planning_dataclasses.py +434 -0
  79. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/METADATA +142 -82
  80. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/RECORD +84 -41
  81. massgen/configs/tools/custom_tools/crawl4ai_mcp_example.yaml +0 -67
  82. massgen/configs/tools/custom_tools/crawl4ai_multi_agent_example.yaml +0 -68
  83. massgen/configs/tools/memory/README.md +0 -199
  84. massgen/configs/tools/memory/gpt5mini_gemini_context_window_management.yaml +0 -131
  85. massgen/configs/tools/memory/gpt5mini_gemini_no_persistent_memory.yaml +0 -133
  86. massgen/configs/tools/multimodal/gpt5mini_gpt5nano_documentation_evolution.yaml +0 -97
  87. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/WHEEL +0 -0
  88. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/entry_points.txt +0 -0
  89. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/licenses/LICENSE +0 -0
  90. {massgen-0.1.3.dist-info → massgen-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,293 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Create variations based on multiple input images using OpenAI's gpt-4.1 API.
4
+ """
5
+
6
+ import base64
7
+ import json
8
+ import os
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ from typing import List, Optional
12
+
13
+ from dotenv import load_dotenv
14
+ from openai import OpenAI
15
+
16
+ from massgen.tool._result import ExecutionResult, TextContent
17
+
18
+
19
+ def _validate_path_access(path: Path, allowed_paths: Optional[List[Path]] = None) -> None:
20
+ """
21
+ Validate that a path is within allowed directories.
22
+
23
+ Args:
24
+ path: Path to validate
25
+ allowed_paths: List of allowed base paths (optional)
26
+ agent_cwd: Agent\'s current working directory (automatically injected)
27
+
28
+ Raises:
29
+ ValueError: If path is not within allowed directories
30
+ """
31
+ if not allowed_paths:
32
+ return # No restrictions
33
+
34
+ for allowed_path in allowed_paths:
35
+ try:
36
+ path.relative_to(allowed_path)
37
+ return # Path is within this allowed directory
38
+ except ValueError:
39
+ continue
40
+
41
+ raise ValueError(f"Path not in allowed directories: {path}")
42
+
43
+
44
+ async def image_to_image_generation(
45
+ base_image_paths: List[str],
46
+ prompt: str = "Create a variation of the provided images",
47
+ model: str = "gpt-4.1",
48
+ storage_path: Optional[str] = None,
49
+ allowed_paths: Optional[List[str]] = None,
50
+ agent_cwd: Optional[str] = None,
51
+ ) -> ExecutionResult:
52
+ """
53
+ Create variations based on multiple input images using OpenAI's gpt-4.1 API.
54
+
55
+ This tool generates image variations based on multiple base images using OpenAI's gpt-4.1 API
56
+ and saves them to the workspace with automatic organization.
57
+
58
+ Args:
59
+ base_image_paths: List of paths to base images (PNG/JPEG files, less than 4MB)
60
+ - Relative path: Resolved relative to agent's workspace
61
+ - Absolute path: Must be within allowed directories
62
+ prompt: Text description for the variation (default: "Create a variation of the provided images")
63
+ model: Model to use (default: "gpt-4.1")
64
+ storage_path: Directory path where to save variations (optional)
65
+ - Relative path: Resolved relative to agent's workspace
66
+ - Absolute path: Must be within allowed directories
67
+ - None/empty: Saves to agent's workspace root
68
+ allowed_paths: List of allowed base paths for validation (optional)
69
+ agent_cwd: Agent\'s current working directory (automatically injected)
70
+
71
+ Returns:
72
+ ExecutionResult containing:
73
+ - success: Whether operation succeeded
74
+ - operation: "generate_and_store_image_with_input_images"
75
+ - note: Note about usage
76
+ - images: List of generated images with file paths and metadata
77
+ - model: Model used for generation
78
+ - prompt: The prompt used
79
+ - total_images: Total number of images generated
80
+
81
+ Examples:
82
+ generate_and_store_image_with_input_images(["cat.png", "dog.png"], "Combine these animals")
83
+ → Generates a variation combining both images
84
+
85
+ generate_and_store_image_with_input_images(["art/logo.png", "art/icon.png"], "Create a unified design")
86
+ → Generates variations based on both images
87
+
88
+ Security:
89
+ - Requires valid OpenAI API key
90
+ - Input images must be valid image files less than 4MB
91
+ - Files are saved to specified path within workspace
92
+ """
93
+ try:
94
+ # Convert allowed_paths from strings to Path objects
95
+ allowed_paths_list = [Path(p) for p in allowed_paths] if allowed_paths else None
96
+
97
+ # Use agent_cwd if available, otherwise fall back to Path.cwd()
98
+ base_dir = Path(agent_cwd) if agent_cwd else Path.cwd()
99
+
100
+ # Load environment variables
101
+ script_dir = Path(__file__).parent.parent.parent.parent
102
+ env_path = script_dir / ".env"
103
+ if env_path.exists():
104
+ load_dotenv(env_path)
105
+ else:
106
+ load_dotenv()
107
+
108
+ openai_api_key = os.getenv("OPENAI_API_KEY")
109
+
110
+ if not openai_api_key:
111
+ result = {
112
+ "success": False,
113
+ "operation": "generate_and_store_image_with_input_images",
114
+ "error": "OpenAI API key not found. Please set OPENAI_API_KEY in .env file or environment variable.",
115
+ }
116
+ return ExecutionResult(
117
+ output_blocks=[TextContent(data=json.dumps(result, indent=2))],
118
+ )
119
+
120
+ # Initialize OpenAI client
121
+ client = OpenAI(api_key=openai_api_key)
122
+
123
+ # Prepare content list with prompt and images
124
+ content = [{"type": "input_text", "text": prompt}]
125
+
126
+ # Process and validate all input images
127
+ validated_paths = []
128
+ for image_path_str in base_image_paths:
129
+ # Resolve image path
130
+ if Path(image_path_str).is_absolute():
131
+ image_path = Path(image_path_str).resolve()
132
+ else:
133
+ image_path = (base_dir / image_path_str).resolve()
134
+
135
+ # Validate image path
136
+ _validate_path_access(image_path, allowed_paths_list)
137
+
138
+ if not image_path.exists():
139
+ result = {
140
+ "success": False,
141
+ "operation": "generate_and_store_image_with_input_images",
142
+ "error": f"Image file does not exist: {image_path}",
143
+ }
144
+ return ExecutionResult(
145
+ output_blocks=[TextContent(data=json.dumps(result, indent=2))],
146
+ )
147
+
148
+ # Allow both PNG and JPEG formats
149
+ if image_path.suffix.lower() not in [".png", ".jpg", ".jpeg"]:
150
+ result = {
151
+ "success": False,
152
+ "operation": "generate_and_store_image_with_input_images",
153
+ "error": f"Image must be PNG or JPEG format: {image_path}",
154
+ }
155
+ return ExecutionResult(
156
+ output_blocks=[TextContent(data=json.dumps(result, indent=2))],
157
+ )
158
+
159
+ # Check file size (must be less than 4MB)
160
+ file_size = image_path.stat().st_size
161
+ if file_size > 4 * 1024 * 1024:
162
+ result = {
163
+ "success": False,
164
+ "operation": "generate_and_store_image_with_input_images",
165
+ "error": f"Image file too large (must be < 4MB): {image_path} is {file_size / (1024*1024):.2f}MB",
166
+ }
167
+ return ExecutionResult(
168
+ output_blocks=[TextContent(data=json.dumps(result, indent=2))],
169
+ )
170
+
171
+ validated_paths.append(image_path)
172
+
173
+ # Read and encode image to base64
174
+ with open(image_path, "rb") as f:
175
+ image_data = f.read()
176
+ image_base64 = base64.b64encode(image_data).decode("utf-8")
177
+
178
+ # Determine MIME type
179
+ mime_type = "image/jpeg" if image_path.suffix.lower() in [".jpg", ".jpeg"] else "image/png"
180
+
181
+ # Add image to content
182
+ content.append(
183
+ {
184
+ "type": "input_image",
185
+ "image_url": f"data:{mime_type};base64,{image_base64}",
186
+ },
187
+ )
188
+
189
+ # Determine storage directory
190
+ if storage_path:
191
+ if Path(storage_path).is_absolute():
192
+ storage_dir = Path(storage_path).resolve()
193
+ else:
194
+ storage_dir = (base_dir / storage_path).resolve()
195
+ else:
196
+ storage_dir = base_dir
197
+
198
+ # Validate storage directory
199
+ _validate_path_access(storage_dir, allowed_paths_list)
200
+ storage_dir.mkdir(parents=True, exist_ok=True)
201
+
202
+ try:
203
+ # Generate variations using gpt-4.1 API with all images at once
204
+ response = client.responses.create(
205
+ model=model,
206
+ input=[
207
+ {
208
+ "role": "user",
209
+ "content": content,
210
+ },
211
+ ],
212
+ tools=[{"type": "image_generation"}],
213
+ )
214
+
215
+ # Extract image generation calls from response
216
+ image_generation_calls = [output for output in response.output if output.type == "image_generation_call"]
217
+
218
+ all_variations = []
219
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
220
+
221
+ # Process generated images
222
+ for idx, output in enumerate(image_generation_calls):
223
+ if hasattr(output, "result"):
224
+ image_base64 = output.result
225
+ image_bytes = base64.b64decode(image_base64)
226
+
227
+ # Generate filename
228
+ if len(image_generation_calls) > 1:
229
+ filename = f"variation_{idx+1}_{timestamp}.png"
230
+ else:
231
+ filename = f"variation_{timestamp}.png"
232
+
233
+ # Full file path
234
+ file_path = storage_dir / filename
235
+
236
+ # Save image
237
+ file_path.write_bytes(image_bytes)
238
+
239
+ all_variations.append(
240
+ {
241
+ "source_images": [str(p) for p in validated_paths],
242
+ "file_path": str(file_path),
243
+ "filename": filename,
244
+ "size": len(image_bytes),
245
+ "index": idx,
246
+ },
247
+ )
248
+
249
+ # If no images were generated, check for text response
250
+ if not all_variations:
251
+ text_outputs = [output.content for output in response.output if hasattr(output, "content")]
252
+ if text_outputs:
253
+ result = {
254
+ "success": False,
255
+ "operation": "generate_and_store_image_with_input_images",
256
+ "error": f"No images generated. Response: {' '.join(text_outputs)}",
257
+ }
258
+ return ExecutionResult(
259
+ output_blocks=[TextContent(data=json.dumps(result, indent=2))],
260
+ )
261
+
262
+ except Exception as api_error:
263
+ result = {
264
+ "success": False,
265
+ "operation": "generate_and_store_image_with_input_images",
266
+ "error": f"OpenAI API error: {str(api_error)}",
267
+ }
268
+ return ExecutionResult(
269
+ output_blocks=[TextContent(data=json.dumps(result, indent=2))],
270
+ )
271
+
272
+ result = {
273
+ "success": True,
274
+ "operation": "generate_and_store_image_with_input_images",
275
+ "note": "If no input images were provided, you must use generate_and_store_image_no_input_images tool.",
276
+ "images": all_variations,
277
+ "model": model,
278
+ "prompt": prompt,
279
+ "total_images": len(all_variations),
280
+ }
281
+ return ExecutionResult(
282
+ output_blocks=[TextContent(data=json.dumps(result, indent=2))],
283
+ )
284
+
285
+ except Exception as e:
286
+ result = {
287
+ "success": False,
288
+ "operation": "generate_and_store_image_with_input_images",
289
+ "error": f"Failed to generate variations: {str(e)}",
290
+ }
291
+ return ExecutionResult(
292
+ output_blocks=[TextContent(data=json.dumps(result, indent=2))],
293
+ )