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,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Message formatting utilities.
4
+ Provides utility classes for message formatting and conversion.
5
+ """
6
+ from ._chat_completions_formatter import ChatCompletionsFormatter
7
+ from ._claude_formatter import ClaudeFormatter
8
+ from ._response_formatter import ResponseFormatter
9
+
10
+ __all__ = ["ChatCompletionsFormatter", "ResponseFormatter", "ClaudeFormatter"]
@@ -0,0 +1,284 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Chat Completions formatter implementation.
4
+ Handles formatting for OpenAI Chat Completions API format.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from typing import Any, Dict, List
11
+
12
+ from ._formatter_base import FormatterBase
13
+
14
+
15
+ class ChatCompletionsFormatter(FormatterBase):
16
+ """Formatter for Chat Completions API format."""
17
+
18
+ def format_messages(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
19
+ """
20
+ Convert messages for Chat Completions API compatibility.
21
+
22
+ Chat Completions API expects tool call arguments as JSON strings in conversation history,
23
+ but they may be passed as objects from other parts of the system.
24
+ """
25
+ converted_messages = []
26
+
27
+ for message in messages:
28
+ # Create a copy to avoid modifying the original
29
+ converted_msg = dict(message)
30
+
31
+ # Normalize multimodal content (text/image/audio/video)
32
+ converted_msg = self._convert_multimodal_content(converted_msg)
33
+
34
+ # Convert tool_calls arguments from objects to JSON strings
35
+ if message.get("role") == "assistant" and "tool_calls" in message:
36
+ converted_tool_calls = []
37
+ for tool_call in message["tool_calls"]:
38
+ converted_call = dict(tool_call)
39
+ if "function" in converted_call:
40
+ converted_function = dict(converted_call["function"])
41
+ arguments = converted_function.get("arguments")
42
+
43
+ # Convert arguments to JSON string if it's an object
44
+ if isinstance(arguments, dict):
45
+ converted_function["arguments"] = json.dumps(arguments)
46
+ elif arguments is None:
47
+ converted_function["arguments"] = "{}"
48
+ elif not isinstance(arguments, str):
49
+ # Handle other non-string types
50
+ converted_function["arguments"] = self._serialize_tool_arguments(arguments)
51
+ # If it's already a string, keep it as-is
52
+
53
+ converted_call["function"] = converted_function
54
+ converted_tool_calls.append(converted_call)
55
+ converted_msg["tool_calls"] = converted_tool_calls
56
+
57
+ converted_messages.append(converted_msg)
58
+
59
+ return converted_messages
60
+
61
+ def _convert_multimodal_content(self, message: Dict[str, Any]) -> Dict[str, Any]:
62
+ """
63
+ Convert multimodal content to Chat Completions API format.
64
+ """
65
+ content = message.get("content")
66
+
67
+ # If content is not a list, no conversion needed
68
+ if not isinstance(content, list):
69
+ return message
70
+
71
+ converted_content = []
72
+ for item in content:
73
+ if not isinstance(item, dict):
74
+ # If item is a string, treat as text
75
+ converted_content.append({"type": "text", "text": str(item)})
76
+ continue
77
+
78
+ item_type = item.get("type")
79
+
80
+ if item_type == "text":
81
+ # Text items pass through as-is
82
+ converted_content.append(item)
83
+
84
+ elif item_type == "image":
85
+ # Convert image item to image_url format
86
+ converted_item = self._convert_image_content(item)
87
+ if converted_item:
88
+ converted_content.append(converted_item)
89
+
90
+ elif item_type == "audio":
91
+ # Convert audio item to input_audio format (base64)
92
+ converted_item = self._convert_audio_content(item)
93
+ if converted_item:
94
+ converted_content.append(converted_item)
95
+
96
+ elif item_type == "video":
97
+ # Convert video item to video_url format (base64 data URL)
98
+ converted_item = self._convert_video_content(item)
99
+ if converted_item:
100
+ converted_content.append(converted_item)
101
+
102
+ elif item_type == "video_url":
103
+ # Convert video URL to video_url format
104
+ converted_item = self._convert_video_url_content(item)
105
+ if converted_item:
106
+ converted_content.append(converted_item)
107
+
108
+ elif item_type == "file_pending_upload":
109
+ continue
110
+ elif item_type in ["image_url", "input_audio", "video_url"]:
111
+ # Already in Chat Completions API format
112
+ converted_content.append(item)
113
+
114
+ else:
115
+ # Unknown type, pass through
116
+ converted_content.append(item)
117
+
118
+ message["content"] = converted_content
119
+ return message
120
+
121
+ def _convert_image_content(self, image_item: Dict[str, Any]) -> Dict[str, Any]:
122
+ """
123
+ Convert image content item to Chat Completions API format.
124
+
125
+ Supports:
126
+ - URL format: {"type": "image", "url": "https://..."}
127
+ - Base64 format: {"type": "image", "base64": "...", "mime_type": "image/jpeg"}
128
+
129
+ Returns Chat Completions format: {"type": "image_url", "image_url": {"url": "..."}}
130
+ """
131
+ # Handle URL format
132
+ if "url" in image_item:
133
+ return {
134
+ "type": "image_url",
135
+ "image_url": {"url": image_item["url"]},
136
+ }
137
+
138
+ # Handle base64 format
139
+ if "base64" in image_item:
140
+ mime_type = image_item.get("mime_type", "image/jpeg")
141
+ base64_data = image_item["base64"]
142
+
143
+ # Create data URL
144
+ data_url = f"data:{mime_type};base64,{base64_data}"
145
+ return {
146
+ "type": "image_url",
147
+ "image_url": {"url": data_url},
148
+ }
149
+
150
+ # No valid image data found
151
+ return None
152
+
153
+ def _convert_audio_content(self, audio_item: Dict[str, Any]) -> Dict[str, Any]:
154
+ """
155
+ Convert audio content item to Chat Completions API format.
156
+
157
+ Supports base64 format: {"type": "audio", "base64": "...", "mime_type": "audio/wav"}
158
+
159
+ Returns Chat Completions format: {"type": "input_audio", "input_audio": {"data": "...", "format": "wav"}}
160
+ """
161
+ if "base64" not in audio_item:
162
+ return None
163
+
164
+ base64_data = audio_item["base64"]
165
+ mime_type = audio_item.get("mime_type", "audio/wav")
166
+
167
+ # Extract format from MIME type (e.g., "audio/wav" → "wav")
168
+ audio_format = mime_type.split("/")[-1] if "/" in mime_type else "wav"
169
+
170
+ # Map common MIME types to OpenAI audio formats
171
+ format_mapping = {
172
+ "mpeg": "mp3",
173
+ "x-wav": "wav",
174
+ "wave": "wav",
175
+ }
176
+ audio_format = format_mapping.get(audio_format, audio_format)
177
+
178
+ return {
179
+ "type": "input_audio",
180
+ "input_audio": {
181
+ "data": base64_data,
182
+ "format": audio_format,
183
+ },
184
+ }
185
+
186
+ def _convert_video_content(self, video_item: Dict[str, Any]) -> Dict[str, Any]:
187
+ """
188
+ Convert video content item to Chat Completions API format.
189
+
190
+ Supports base64 format: {"type": "video", "base64": "...", "mime_type": "video/mp4"}
191
+
192
+ Returns format: {"type": "video_url", "video_url": {"url": "data:video/...;base64,..."}}
193
+ """
194
+ if "base64" not in video_item:
195
+ return None
196
+
197
+ base64_data = video_item["base64"]
198
+ mime_type = video_item.get("mime_type", "video/mp4")
199
+
200
+ # Create data URL
201
+ data_url = f"data:{mime_type};base64,{base64_data}"
202
+
203
+ return {
204
+ "type": "video_url",
205
+ "video_url": {"url": data_url},
206
+ }
207
+
208
+ def _convert_video_url_content(self, video_item: Dict[str, Any]) -> Dict[str, Any]:
209
+ """
210
+ Convert video URL content item to Chat Completions API format.
211
+
212
+ Supports URL format: {"type": "video_url", "url": "https://..."}
213
+
214
+ Returns format: {"type": "video_url", "video_url": {"url": "..."}}
215
+ """
216
+ if "url" not in video_item:
217
+ return None
218
+
219
+ return {
220
+ "type": "video_url",
221
+ "video_url": {"url": video_item["url"]},
222
+ }
223
+
224
+ def format_tools(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
225
+ """
226
+ Convert tools to Chat Completions format if needed.
227
+
228
+ Response API format: {"type": "function", "name": ..., "description": ..., "parameters": ...}
229
+ Chat Completions format: {"type": "function", "function": {"name": ..., "description": ..., "parameters": ...}}
230
+ """
231
+ if not tools:
232
+ return tools
233
+
234
+ converted_tools = []
235
+ for tool in tools:
236
+ if tool.get("type") == "function":
237
+ if "function" in tool:
238
+ # Already in Chat Completions format
239
+ converted_tools.append(tool)
240
+ elif "name" in tool and "description" in tool:
241
+ # Response API format - convert to Chat Completions format
242
+ converted_tools.append(
243
+ {
244
+ "type": "function",
245
+ "function": {
246
+ "name": tool["name"],
247
+ "description": tool["description"],
248
+ "parameters": tool.get("parameters", {}),
249
+ },
250
+ },
251
+ )
252
+ else:
253
+ # Unknown format - keep as-is
254
+ converted_tools.append(tool)
255
+ else:
256
+ # Non-function tool - keep as-is
257
+ converted_tools.append(tool)
258
+
259
+ return converted_tools
260
+
261
+ def format_mcp_tools(self, mcp_functions: Dict[str, Any]) -> List[Dict[str, Any]]:
262
+ """Convert MCP tools to Chat Completions format."""
263
+ if not mcp_functions:
264
+ return []
265
+
266
+ converted_tools = []
267
+ for mcp_function in mcp_functions.values():
268
+ if hasattr(mcp_function, "to_chat_completions_format"):
269
+ tool = mcp_function.to_chat_completions_format()
270
+ elif hasattr(mcp_function, "to_openai_format"):
271
+ tool = mcp_function.to_openai_format()
272
+ else:
273
+ # Fallback format
274
+ tool = {
275
+ "type": "function",
276
+ "function": {
277
+ "name": getattr(mcp_function, "name", "unknown"),
278
+ "description": getattr(mcp_function, "description", ""),
279
+ "parameters": getattr(mcp_function, "input_schema", {}),
280
+ },
281
+ }
282
+ converted_tools.append(tool)
283
+
284
+ return converted_tools
@@ -0,0 +1,235 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Claude formatter implementation.
4
+ Handles formatting for Anthropic Claude Messages API format.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Dict, List, Tuple
10
+
11
+ from ._formatter_base import FormatterBase
12
+
13
+
14
+ class ClaudeFormatter(FormatterBase):
15
+ """Formatter for Claude API format."""
16
+
17
+ def format_messages(self, messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
18
+ formatted, _ = self.format_messages_and_system(messages)
19
+ return formatted
20
+
21
+ def format_messages_and_system(
22
+ self,
23
+ messages: List[Dict[str, Any]],
24
+ ) -> Tuple[List[Dict[str, Any]], str]:
25
+ """
26
+ Convert messages to Claude's expected format.
27
+
28
+ Handle different tool message formats and extract system message:
29
+ - Chat Completions tool message: {"role": "tool", "tool_call_id": "...", "content": "..."}
30
+ - Response API tool message: {"type": "function_call_output", "call_id": "...", "output": "..."}
31
+ - System messages: Extract and return separately for top-level system parameter
32
+
33
+ Returns:
34
+ tuple: (converted_messages, system_message)
35
+ """
36
+ converted_messages = []
37
+ system_message = ""
38
+
39
+ for message in messages:
40
+ if message.get("role") == "system":
41
+ # Extract system message for top-level parameter
42
+ system_message = message.get("content", "")
43
+ elif message.get("role") == "tool":
44
+ # Chat Completions tool message -> Claude tool result
45
+ converted_messages.append(
46
+ {
47
+ "role": "user",
48
+ "content": [
49
+ {
50
+ "type": "tool_result",
51
+ "tool_use_id": message.get("tool_call_id"),
52
+ "content": message.get("content", ""),
53
+ },
54
+ ],
55
+ },
56
+ )
57
+ elif message.get("type") == "function_call_output":
58
+ # Response API tool message -> Claude tool result
59
+ converted_messages.append(
60
+ {
61
+ "role": "user",
62
+ "content": [
63
+ {
64
+ "type": "tool_result",
65
+ "tool_use_id": message.get("call_id"),
66
+ "content": message.get("output", ""),
67
+ },
68
+ ],
69
+ },
70
+ )
71
+ elif message.get("role") == "assistant" and "tool_calls" in message:
72
+ # Assistant message with tool calls - convert to Claude format
73
+ content = []
74
+
75
+ # Add text content if present
76
+ if message.get("content"):
77
+ content.append({"type": "text", "text": message["content"]})
78
+
79
+ # Convert tool calls to Claude tool use format
80
+ for tool_call in message["tool_calls"]:
81
+ tool_name = self.extract_tool_name(tool_call)
82
+ tool_args = self.extract_tool_arguments(tool_call)
83
+ tool_id = self.extract_tool_call_id(tool_call)
84
+
85
+ content.append(
86
+ {
87
+ "type": "tool_use",
88
+ "id": tool_id,
89
+ "name": tool_name,
90
+ "input": tool_args,
91
+ },
92
+ )
93
+
94
+ converted_messages.append({"role": "assistant", "content": content})
95
+ elif message.get("role") in ["user", "assistant"]:
96
+ # Keep user and assistant messages, skip system
97
+ converted_message = dict(message)
98
+ if isinstance(converted_message.get("content"), str):
99
+ # Claude expects content to be text for simple messages
100
+ pass
101
+ elif isinstance(converted_message.get("content"), list):
102
+ converted_message = self._convert_multimodal_content(converted_message)
103
+ converted_messages.append(converted_message)
104
+
105
+ return converted_messages, system_message
106
+
107
+ def _convert_multimodal_content(self, message: Dict[str, Any]) -> Dict[str, Any]:
108
+ """Normalize multimodal content blocks to Claude's nested source structure."""
109
+
110
+ content = message.get("content")
111
+ if not isinstance(content, list):
112
+ return message
113
+
114
+ # Formatter handles generic multimodal content; upload_files-sourced items already preprocessed in backend.
115
+ converted_items: List[Dict[str, Any]] = []
116
+
117
+ for item in content:
118
+ if not isinstance(item, dict):
119
+ converted_items.append(item)
120
+ continue
121
+
122
+ item_type = item.get("type")
123
+
124
+ if item_type in {"tool_result", "tool_use", "text", "file_pending_upload"}:
125
+ converted_items.append(item)
126
+ continue
127
+
128
+ if item_type not in {"image", "document"}:
129
+ converted_items.append(item)
130
+ continue
131
+
132
+ if isinstance(item.get("source"), dict):
133
+ converted_items.append(item)
134
+ continue
135
+
136
+ if "file_id" in item:
137
+ normalized = {key: value for key, value in item.items() if key != "file_id"}
138
+ normalized["source"] = {
139
+ "type": "file",
140
+ "file_id": item["file_id"],
141
+ }
142
+ converted_items.append(normalized)
143
+ continue
144
+
145
+ if "base64" in item:
146
+ media_type = item.get("mime_type") or item.get("media_type")
147
+ normalized = {key: value for key, value in item.items() if key not in {"base64", "mime_type", "media_type"}}
148
+ normalized["source"] = {
149
+ "type": "base64",
150
+ "media_type": media_type,
151
+ "data": item["base64"],
152
+ }
153
+ converted_items.append(normalized)
154
+ continue
155
+
156
+ if "url" in item:
157
+ normalized = {key: value for key, value in item.items() if key != "url"}
158
+ normalized["source"] = {
159
+ "type": "url",
160
+ "url": item["url"],
161
+ }
162
+ converted_items.append(normalized)
163
+ continue
164
+
165
+ converted_items.append(item)
166
+
167
+ message["content"] = converted_items
168
+ return message
169
+
170
+ def format_tools(self, tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
171
+ """
172
+ Convert tools to Claude's expected format.
173
+
174
+ Input formats supported:
175
+ - Response API format: {"type": "function", "name": ..., "description": ..., "parameters": ...}
176
+ - Chat Completions format: {"type": "function", "function": {"name": ..., "description": ..., "parameters": ...}}
177
+
178
+ Claude format: {"type": "custom", "name": ..., "description": ..., "input_schema": ...}
179
+ """
180
+ if not tools:
181
+ return tools
182
+
183
+ converted_tools = []
184
+ for tool in tools:
185
+ if tool.get("type") == "function":
186
+ if "function" in tool:
187
+ # Chat Completions format -> Claude custom tool
188
+ func = tool["function"]
189
+ converted_tools.append(
190
+ {
191
+ "type": "custom",
192
+ "name": func["name"],
193
+ "description": func["description"],
194
+ "input_schema": func.get("parameters", {}),
195
+ },
196
+ )
197
+ elif "name" in tool and "description" in tool:
198
+ # Response API format -> Claude custom tool
199
+ converted_tools.append(
200
+ {
201
+ "type": "custom",
202
+ "name": tool["name"],
203
+ "description": tool["description"],
204
+ "input_schema": tool.get("parameters", {}),
205
+ },
206
+ )
207
+ else:
208
+ # Unknown format - keep as-is
209
+ converted_tools.append(tool)
210
+ else:
211
+ # Non-function tool (builtin tools) - keep as-is
212
+ converted_tools.append(tool)
213
+
214
+ return converted_tools
215
+
216
+ def format_mcp_tools(self, mcp_functions: Dict[str, Any]) -> List[Dict[str, Any]]:
217
+ """Convert MCP tools to Claude's custom tool format."""
218
+ if not mcp_functions:
219
+ return []
220
+
221
+ converted_tools = []
222
+ for mcp_function in mcp_functions.values():
223
+ if hasattr(mcp_function, "to_claude_format"):
224
+ tool = mcp_function.to_claude_format()
225
+ else:
226
+ # Fallback format for Claude
227
+ tool = {
228
+ "type": "custom",
229
+ "name": getattr(mcp_function, "name", "unknown"),
230
+ "description": getattr(mcp_function, "description", ""),
231
+ "input_schema": getattr(mcp_function, "input_schema", {}),
232
+ }
233
+ converted_tools.append(tool)
234
+
235
+ return converted_tools
@@ -0,0 +1,156 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Base class for API parameters handlers.
4
+ Provides common functionality for building API parameters across different backends.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from abc import ABC, abstractmethod
10
+ from typing import Any, Dict, List
11
+
12
+
13
+ class FormatterBase(ABC):
14
+ """Abstract base class for API parameter handlers."""
15
+
16
+ def __init__(self) -> None:
17
+ """Initialize the API params handler.
18
+
19
+ Args:
20
+ backend_instance: The backend instance containing necessary formatters and config
21
+ """
22
+ return None
23
+
24
+ @abstractmethod
25
+ def format_messages(
26
+ self,
27
+ messages: List[Dict[str, Any]],
28
+ ) -> List[Dict[str, Any]]:
29
+ pass
30
+
31
+ @abstractmethod
32
+ def format_tools(
33
+ self,
34
+ tools: List[Dict[str, Any]],
35
+ ) -> List[Dict[str, Any]]:
36
+ pass
37
+
38
+ @abstractmethod
39
+ def format_mcp_tools(
40
+ self,
41
+ mcp_functions: Dict[str, Any],
42
+ ) -> List[Dict[str, Any]]:
43
+ pass
44
+
45
+ @staticmethod
46
+ def extract_tool_name(tool_call: Dict[str, Any]) -> str:
47
+ """
48
+ Extract tool name from a tool call (handles multiple formats).
49
+
50
+ Supports:
51
+ - Chat Completions format: {"function": {"name": "...", ...}}
52
+ - Response API format: {"name": "..."}
53
+ - Claude native format: {"name": "..."}
54
+
55
+ Args:
56
+ tool_call: Tool call data structure from any backend
57
+
58
+ Returns:
59
+ Tool name string
60
+ """
61
+ # Chat Completions format
62
+ if "function" in tool_call:
63
+ return tool_call.get("function", {}).get("name", "unknown")
64
+ # Response API / Claude native format
65
+ elif "name" in tool_call:
66
+ return tool_call.get("name", "unknown")
67
+ # Fallback
68
+ return "unknown"
69
+
70
+ @staticmethod
71
+ def extract_tool_arguments(tool_call: Dict[str, Any]) -> Dict[str, Any]:
72
+ """
73
+ Extract tool arguments from a tool call (handles multiple formats).
74
+
75
+ Supports:
76
+ - Chat Completions format: {"function": {"arguments": ...}}
77
+ - Response API format: {"arguments": ...}
78
+ - Claude native format: {"input": ...}
79
+
80
+ Args:
81
+ tool_call: Tool call data structure from any backend
82
+
83
+ Returns:
84
+ Tool arguments dictionary (parsed from JSON string if needed)
85
+ """
86
+ import json
87
+
88
+ # Chat Completions format
89
+ if "function" in tool_call:
90
+ args = tool_call.get("function", {}).get("arguments", {})
91
+ # Claude native format
92
+ elif "input" in tool_call:
93
+ args = tool_call.get("input", {})
94
+ # Response API format
95
+ elif "arguments" in tool_call:
96
+ args = tool_call.get("arguments", {})
97
+ else:
98
+ args = {}
99
+
100
+ # Parse JSON string if needed
101
+ if isinstance(args, str):
102
+ try:
103
+ return json.loads(args) if args.strip() else {}
104
+ except (json.JSONDecodeError, ValueError):
105
+ return {}
106
+ return args if isinstance(args, dict) else {}
107
+
108
+ @staticmethod
109
+ def extract_tool_call_id(tool_call: Dict[str, Any]) -> str:
110
+ """
111
+ Extract tool call ID from a tool call (handles multiple formats).
112
+
113
+ Supports:
114
+ - Chat Completions format: {"id": "..."}
115
+ - Response API format: {"call_id": "..."}
116
+ - Claude native format: {"id": "..."}
117
+
118
+ Args:
119
+ tool_call: Tool call data structure from any backend
120
+
121
+ Returns:
122
+ Tool call ID string
123
+ """
124
+ # Try multiple possible ID fields
125
+ return tool_call.get("id") or tool_call.get("call_id") or ""
126
+
127
+ @staticmethod
128
+ def _serialize_tool_arguments(arguments) -> str:
129
+ """Safely serialize tool call arguments to JSON string.
130
+
131
+ Args:
132
+ arguments: Tool arguments (can be string, dict, or other types)
133
+
134
+ Returns:
135
+ JSON string representation of arguments
136
+ """
137
+ import json
138
+
139
+ if isinstance(arguments, str):
140
+ # If already a string, validate it's valid JSON
141
+ try:
142
+ json.loads(arguments) # Validate JSON
143
+ return arguments
144
+ except (json.JSONDecodeError, ValueError):
145
+ # If not valid JSON, treat as plain string and wrap in quotes
146
+ return json.dumps(arguments)
147
+ elif arguments is None:
148
+ return "{}"
149
+ else:
150
+ # Convert to JSON string
151
+ try:
152
+ return json.dumps(arguments)
153
+ except (TypeError, ValueError) as e:
154
+ # Logger not imported at module level, use print for warning
155
+ print(f"Warning: Failed to serialize tool arguments: {e}, arguments: {arguments}")
156
+ return "{}"