codepp 0.0.437__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.
Files changed (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,338 @@
1
+ """HTTP client interceptor for ChatGPT Codex API.
2
+
3
+ ChatGPTCodexAsyncClient: httpx client that injects required fields into
4
+ request bodies for the ChatGPT Codex API and handles stream-to-non-stream
5
+ conversion.
6
+
7
+ The Codex API requires:
8
+ - "store": false - Disables conversation storage
9
+ - "stream": true - Streaming is mandatory
10
+
11
+ Removes unsupported parameters:
12
+ - "max_output_tokens" - Not supported by Codex API
13
+ - "max_tokens" - Not supported by Codex API
14
+ - "verbosity" - Not supported by Codex API
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import logging
21
+ from typing import Any
22
+
23
+ import httpx
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ def _is_reasoning_model(model_name: str) -> bool:
29
+ """Check if a model supports reasoning parameters."""
30
+ reasoning_models = [
31
+ "gpt-5", # All GPT-5 variants
32
+ "o1", # o1 series
33
+ "o3", # o3 series
34
+ "o4", # o4 series
35
+ ]
36
+ model_lower = model_name.lower()
37
+ return any(model_lower.startswith(prefix) for prefix in reasoning_models)
38
+
39
+
40
+ class ChatGPTCodexAsyncClient(httpx.AsyncClient):
41
+ """Async HTTP client that handles ChatGPT Codex API requirements.
42
+
43
+ This client:
44
+ 1. Injects required fields (store=false, stream=true)
45
+ 2. Strips unsupported parameters
46
+ 3. Converts streaming responses to non-streaming format
47
+ """
48
+
49
+ async def send(
50
+ self, request: httpx.Request, *args: Any, **kwargs: Any
51
+ ) -> httpx.Response:
52
+ """Intercept requests and inject required Codex fields."""
53
+ force_stream_conversion = False
54
+
55
+ try:
56
+ # Only modify POST requests to the Codex API
57
+ if request.method == "POST":
58
+ body_bytes = self._extract_body_bytes(request)
59
+ if body_bytes:
60
+ updated, force_stream_conversion = self._inject_codex_fields(
61
+ body_bytes
62
+ )
63
+ if updated is not None:
64
+ try:
65
+ rebuilt = self.build_request(
66
+ method=request.method,
67
+ url=request.url,
68
+ headers=request.headers,
69
+ content=updated,
70
+ )
71
+
72
+ # Copy core internals so httpx uses the modified body/stream
73
+ if hasattr(rebuilt, "_content"):
74
+ request._content = rebuilt._content # type: ignore[attr-defined]
75
+ if hasattr(rebuilt, "stream"):
76
+ request.stream = rebuilt.stream
77
+ if hasattr(rebuilt, "extensions"):
78
+ request.extensions = rebuilt.extensions
79
+
80
+ # Ensure Content-Length matches the new body
81
+ request.headers["Content-Length"] = str(len(updated))
82
+
83
+ except Exception as e:
84
+ logger.debug(
85
+ "Failed to rebuild request with Codex fields: %s", e
86
+ )
87
+ except Exception as e:
88
+ logger.debug("Failed to inject Codex fields into request: %s", e)
89
+
90
+ # Make the actual request
91
+ response = await super().send(request, *args, **kwargs)
92
+
93
+ # If we forced streaming, convert the SSE stream to a regular response
94
+ if force_stream_conversion and response.status_code == 200:
95
+ try:
96
+ response = await self._convert_stream_to_response(response)
97
+ except Exception as e:
98
+ logger.warning(f"Failed to convert stream response: {e}")
99
+
100
+ return response
101
+
102
+ @staticmethod
103
+ def _extract_body_bytes(request: httpx.Request) -> bytes | None:
104
+ """Extract the request body as bytes."""
105
+ try:
106
+ content = request.content
107
+ if content:
108
+ return content
109
+ except Exception:
110
+ pass
111
+
112
+ try:
113
+ content = getattr(request, "_content", None)
114
+ if content:
115
+ return content
116
+ except Exception:
117
+ pass
118
+
119
+ return None
120
+
121
+ @staticmethod
122
+ def _inject_codex_fields(body: bytes) -> tuple[bytes | None, bool]:
123
+ """Inject required Codex fields and remove unsupported ones.
124
+
125
+ Returns:
126
+ Tuple of (modified body bytes or None, whether stream was forced)
127
+ """
128
+ try:
129
+ data = json.loads(body.decode("utf-8"))
130
+ except Exception:
131
+ return None, False
132
+
133
+ if not isinstance(data, dict):
134
+ return None, False
135
+
136
+ modified = False
137
+ forced_stream = False
138
+
139
+ # CRITICAL: ChatGPT Codex backend requires store=false
140
+ if "store" not in data or data.get("store") is not False:
141
+ data["store"] = False
142
+ modified = True
143
+
144
+ # CRITICAL: ChatGPT Codex backend requires stream=true
145
+ # If stream is already true (e.g., pydantic-ai with event_stream_handler),
146
+ # don't force conversion - let streaming events flow through naturally
147
+ if data.get("stream") is not True:
148
+ data["stream"] = True
149
+ forced_stream = True # Only convert if WE forced streaming
150
+ modified = True
151
+
152
+ # Add reasoning settings for reasoning models (gpt-5.2, o-series, etc.)
153
+ model = data.get("model", "")
154
+ if "reasoning" not in data and _is_reasoning_model(model):
155
+ data["reasoning"] = {
156
+ "effort": "medium",
157
+ "summary": "auto",
158
+ }
159
+ modified = True
160
+
161
+ # When `store=false` (Codex requirement), the backend does NOT persist input items.
162
+ # That means any later request that tries to reference a previous item by id will 404.
163
+ # We defensively strip reference-style items (especially reasoning_content) to avoid:
164
+ # "Item with id 'rs_...' not found. Items are not persisted when store is false."
165
+ input_items = data.get("input")
166
+ if data.get("store") is False and isinstance(input_items, list):
167
+ original_len = len(input_items)
168
+
169
+ def _looks_like_unpersisted_reference(it: dict) -> bool:
170
+ it_id = it.get("id")
171
+ if it_id in {"reasoning_content", "rs_reasoning_content"}:
172
+ return True
173
+
174
+ # Common reference-ish shapes: {"type": "input_item_reference", "id": "..."}
175
+ it_type = it.get("type")
176
+ if it_type in {"input_item_reference", "item_reference", "reference"}:
177
+ return True
178
+
179
+ # Ultra-conservative: if it's basically just an id (no actual content), drop it.
180
+ # A legit content item will typically have fields like `content`, `text`, `role`, etc.
181
+ non_id_keys = {k for k in it.keys() if k not in {"id", "type"}}
182
+ if not non_id_keys and isinstance(it_id, str) and it_id:
183
+ return True
184
+
185
+ return False
186
+
187
+ filtered: list[object] = []
188
+ for item in input_items:
189
+ if isinstance(item, dict) and _looks_like_unpersisted_reference(item):
190
+ modified = True
191
+ continue
192
+ filtered.append(item)
193
+
194
+ if len(filtered) != original_len:
195
+ data["input"] = filtered
196
+
197
+ # Normalize invalid input IDs (Codex expects reasoning ids to start with "rs_")
198
+ # Note: this is only safe for actual content items, NOT references.
199
+ input_items = data.get("input")
200
+ if isinstance(input_items, list):
201
+ for item in input_items:
202
+ if not isinstance(item, dict):
203
+ continue
204
+ item_id = item.get("id")
205
+ if (
206
+ isinstance(item_id, str)
207
+ and item_id
208
+ and "reasoning" in item_id
209
+ and not item_id.startswith("rs_")
210
+ ):
211
+ item["id"] = f"rs_{item_id}"
212
+ modified = True
213
+
214
+ # Remove unsupported parameters
215
+ # Note: verbosity should be under "text" object, not top-level
216
+ unsupported_params = ["max_output_tokens", "max_tokens", "verbosity"]
217
+ for param in unsupported_params:
218
+ if param in data:
219
+ del data[param]
220
+ modified = True
221
+
222
+ if not modified:
223
+ return None, False
224
+
225
+ return json.dumps(data).encode("utf-8"), forced_stream
226
+
227
+ async def _convert_stream_to_response(
228
+ self, response: httpx.Response
229
+ ) -> httpx.Response:
230
+ """Convert an SSE streaming response to a complete response.
231
+
232
+ Consumes the SSE stream and reconstructs the final response object.
233
+ """
234
+ logger.debug("Converting SSE stream to non-streaming response")
235
+ final_response_data = None
236
+ collected_text = []
237
+ collected_tool_calls = []
238
+
239
+ # Read the entire stream
240
+ async for line in response.aiter_lines():
241
+ if not line or not line.startswith("data:"):
242
+ continue
243
+
244
+ data_str = line[5:].strip() # Remove "data:" prefix
245
+ if data_str == "[DONE]":
246
+ break
247
+
248
+ try:
249
+ event = json.loads(data_str)
250
+ event_type = event.get("type", "")
251
+
252
+ if event_type == "response.output_text.delta":
253
+ # Collect text deltas
254
+ delta = event.get("delta", "")
255
+ if delta:
256
+ collected_text.append(delta)
257
+
258
+ elif event_type == "response.completed":
259
+ # This contains the final response object
260
+ final_response_data = event.get("response", {})
261
+
262
+ elif event_type == "response.function_call_arguments.done":
263
+ # Collect tool calls
264
+ tool_call = {
265
+ "name": event.get("name", ""),
266
+ "arguments": event.get("arguments", ""),
267
+ "call_id": event.get("call_id", ""),
268
+ }
269
+ collected_tool_calls.append(tool_call)
270
+
271
+ except json.JSONDecodeError:
272
+ continue
273
+
274
+ logger.debug(
275
+ f"Collected {len(collected_text)} text chunks, {len(collected_tool_calls)} tool calls"
276
+ )
277
+ if final_response_data:
278
+ logger.debug(
279
+ f"Got final response data with keys: {list(final_response_data.keys())}"
280
+ )
281
+
282
+ # Build the final response body
283
+ if final_response_data:
284
+ response_body = final_response_data
285
+ else:
286
+ # Fallback: construct a minimal response from collected data
287
+ response_body = {
288
+ "id": "reconstructed",
289
+ "object": "response",
290
+ "output": [],
291
+ }
292
+
293
+ if collected_text:
294
+ response_body["output"].append(
295
+ {
296
+ "type": "message",
297
+ "role": "assistant",
298
+ "content": [
299
+ {"type": "output_text", "text": "".join(collected_text)}
300
+ ],
301
+ }
302
+ )
303
+
304
+ for tool_call in collected_tool_calls:
305
+ response_body["output"].append(
306
+ {
307
+ "type": "function_call",
308
+ "name": tool_call["name"],
309
+ "arguments": tool_call["arguments"],
310
+ "call_id": tool_call["call_id"],
311
+ }
312
+ )
313
+
314
+ # Create a new response with the complete body
315
+ body_bytes = json.dumps(response_body).encode("utf-8")
316
+ logger.debug(f"Reconstructed response body: {len(body_bytes)} bytes")
317
+
318
+ new_response = httpx.Response(
319
+ status_code=response.status_code,
320
+ headers=response.headers,
321
+ content=body_bytes,
322
+ request=response.request,
323
+ )
324
+ return new_response
325
+
326
+
327
+ def create_codex_async_client(
328
+ headers: dict[str, str] | None = None,
329
+ verify: str | bool = True,
330
+ **kwargs: Any,
331
+ ) -> ChatGPTCodexAsyncClient:
332
+ """Create a ChatGPT Codex async client with proper configuration."""
333
+ return ChatGPTCodexAsyncClient(
334
+ headers=headers,
335
+ verify=verify,
336
+ timeout=httpx.Timeout(300.0, connect=30.0),
337
+ **kwargs,
338
+ )