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.
- code_puppy/__init__.py +10 -0
- code_puppy/__main__.py +10 -0
- code_puppy/agents/__init__.py +31 -0
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +117 -0
- code_puppy/agents/agent_code_reviewer.py +90 -0
- code_puppy/agents/agent_cpp_reviewer.py +132 -0
- code_puppy/agents/agent_creator_agent.py +638 -0
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_helios.py +124 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +742 -0
- code_puppy/agents/agent_pack_leader.py +385 -0
- code_puppy/agents/agent_planning.py +165 -0
- code_puppy/agents/agent_python_programmer.py +169 -0
- code_puppy/agents/agent_python_reviewer.py +90 -0
- code_puppy/agents/agent_qa_expert.py +163 -0
- code_puppy/agents/agent_qa_kitten.py +208 -0
- code_puppy/agents/agent_scheduler.py +121 -0
- code_puppy/agents/agent_security_auditor.py +181 -0
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +166 -0
- code_puppy/agents/base_agent.py +2156 -0
- code_puppy/agents/event_stream_handler.py +348 -0
- code_puppy/agents/json_agent.py +202 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +327 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +453 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +75 -0
- code_puppy/api/routers/sessions.py +234 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +692 -0
- code_puppy/chatgpt_codex_client.py +338 -0
- code_puppy/claude_cache_client.py +672 -0
- code_puppy/cli_runner.py +1073 -0
- code_puppy/command_line/__init__.py +1 -0
- code_puppy/command_line/add_model_menu.py +1092 -0
- code_puppy/command_line/agent_menu.py +662 -0
- code_puppy/command_line/attachments.py +395 -0
- code_puppy/command_line/autosave_menu.py +704 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +532 -0
- code_puppy/command_line/command_handler.py +293 -0
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +719 -0
- code_puppy/command_line/core_commands.py +867 -0
- code_puppy/command_line/diff_menu.py +865 -0
- code_puppy/command_line/file_path_completion.py +73 -0
- code_puppy/command_line/load_context_completion.py +52 -0
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/base.py +32 -0
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +138 -0
- code_puppy/command_line/mcp/help_command.py +147 -0
- code_puppy/command_line/mcp/install_command.py +214 -0
- code_puppy/command_line/mcp/install_menu.py +705 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +235 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +100 -0
- code_puppy/command_line/mcp/search_command.py +123 -0
- code_puppy/command_line/mcp/start_all_command.py +135 -0
- code_puppy/command_line/mcp/start_command.py +117 -0
- code_puppy/command_line/mcp/status_command.py +184 -0
- code_puppy/command_line/mcp/stop_all_command.py +112 -0
- code_puppy/command_line/mcp/stop_command.py +80 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +334 -0
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +197 -0
- code_puppy/command_line/model_settings_menu.py +932 -0
- code_puppy/command_line/motd.py +96 -0
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +342 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +846 -0
- code_puppy/command_line/session_commands.py +302 -0
- code_puppy/command_line/shell_passthrough.py +145 -0
- code_puppy/command_line/skills_completion.py +160 -0
- code_puppy/command_line/uc_menu.py +893 -0
- code_puppy/command_line/utils.py +93 -0
- code_puppy/command_line/wiggum_state.py +78 -0
- code_puppy/config.py +1770 -0
- code_puppy/error_logging.py +134 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +754 -0
- code_puppy/hook_engine/README.md +105 -0
- code_puppy/hook_engine/__init__.py +21 -0
- code_puppy/hook_engine/aliases.py +155 -0
- code_puppy/hook_engine/engine.py +221 -0
- code_puppy/hook_engine/executor.py +296 -0
- code_puppy/hook_engine/matcher.py +156 -0
- code_puppy/hook_engine/models.py +240 -0
- code_puppy/hook_engine/registry.py +106 -0
- code_puppy/hook_engine/validator.py +144 -0
- code_puppy/http_utils.py +361 -0
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +10 -0
- code_puppy/mcp_/__init__.py +66 -0
- code_puppy/mcp_/async_lifecycle.py +286 -0
- code_puppy/mcp_/blocking_startup.py +469 -0
- code_puppy/mcp_/captured_stdio_server.py +275 -0
- code_puppy/mcp_/circuit_breaker.py +290 -0
- code_puppy/mcp_/config_wizard.py +507 -0
- code_puppy/mcp_/dashboard.py +308 -0
- code_puppy/mcp_/error_isolation.py +407 -0
- code_puppy/mcp_/examples/retry_example.py +226 -0
- code_puppy/mcp_/health_monitor.py +589 -0
- code_puppy/mcp_/managed_server.py +428 -0
- code_puppy/mcp_/manager.py +807 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +451 -0
- code_puppy/mcp_/retry_manager.py +337 -0
- code_puppy/mcp_/server_registry_catalog.py +1126 -0
- code_puppy/mcp_/status_tracker.py +355 -0
- code_puppy/mcp_/system_tools.py +209 -0
- code_puppy/mcp_prompts/__init__.py +1 -0
- code_puppy/mcp_prompts/hook_creator.py +103 -0
- code_puppy/messaging/__init__.py +255 -0
- code_puppy/messaging/bus.py +613 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +361 -0
- code_puppy/messaging/messages.py +569 -0
- code_puppy/messaging/queue_console.py +271 -0
- code_puppy/messaging/renderers.py +311 -0
- code_puppy/messaging/rich_renderer.py +1158 -0
- code_puppy/messaging/spinner/__init__.py +83 -0
- code_puppy/messaging/spinner/console_spinner.py +240 -0
- code_puppy/messaging/spinner/spinner_base.py +95 -0
- code_puppy/messaging/subagent_console.py +460 -0
- code_puppy/model_factory.py +848 -0
- code_puppy/model_switching.py +63 -0
- code_puppy/model_utils.py +168 -0
- code_puppy/models.json +174 -0
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +186 -0
- code_puppy/plugins/agent_skills/__init__.py +22 -0
- code_puppy/plugins/agent_skills/config.py +175 -0
- code_puppy/plugins/agent_skills/discovery.py +136 -0
- code_puppy/plugins/agent_skills/downloader.py +392 -0
- code_puppy/plugins/agent_skills/installer.py +22 -0
- code_puppy/plugins/agent_skills/metadata.py +219 -0
- code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
- code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
- code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
- code_puppy/plugins/agent_skills/skills_menu.py +781 -0
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +133 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
- code_puppy/plugins/antigravity_oauth/storage.py +288 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +863 -0
- code_puppy/plugins/antigravity_oauth/utils.py +168 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
- code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy/plugins/claude_code_hooks/config.py +137 -0
- code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
- code_puppy/plugins/claude_code_oauth/config.py +52 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
- code_puppy/plugins/claude_code_oauth/utils.py +640 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/hook_creator/__init__.py +1 -0
- code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy/plugins/hook_manager/__init__.py +1 -0
- code_puppy/plugins/hook_manager/config.py +290 -0
- code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
- code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
- code_puppy/plugins/oauth_puppy_html.py +228 -0
- code_puppy/plugins/scheduler/__init__.py +1 -0
- code_puppy/plugins/scheduler/register_callbacks.py +88 -0
- code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
- code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/plugins/synthetic_status/__init__.py +1 -0
- code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
- code_puppy/plugins/synthetic_status/status_api.py +147 -0
- code_puppy/plugins/universal_constructor/__init__.py +13 -0
- code_puppy/plugins/universal_constructor/models.py +138 -0
- code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
- code_puppy/plugins/universal_constructor/registry.py +302 -0
- code_puppy/plugins/universal_constructor/sandbox.py +584 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/pydantic_patches.py +356 -0
- code_puppy/reopenable_async_client.py +232 -0
- code_puppy/round_robin_model.py +150 -0
- code_puppy/scheduler/__init__.py +41 -0
- code_puppy/scheduler/__main__.py +9 -0
- code_puppy/scheduler/cli.py +118 -0
- code_puppy/scheduler/config.py +126 -0
- code_puppy/scheduler/daemon.py +280 -0
- code_puppy/scheduler/executor.py +155 -0
- code_puppy/scheduler/platform.py +19 -0
- code_puppy/scheduler/platform_unix.py +22 -0
- code_puppy/scheduler/platform_win.py +32 -0
- code_puppy/session_storage.py +338 -0
- code_puppy/status_display.py +257 -0
- code_puppy/summarization_agent.py +176 -0
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +501 -0
- code_puppy/tools/agent_tools.py +603 -0
- code_puppy/tools/ask_user_question/__init__.py +26 -0
- code_puppy/tools/ask_user_question/constants.py +73 -0
- code_puppy/tools/ask_user_question/demo_tui.py +55 -0
- code_puppy/tools/ask_user_question/handler.py +232 -0
- code_puppy/tools/ask_user_question/models.py +304 -0
- code_puppy/tools/ask_user_question/registration.py +26 -0
- code_puppy/tools/ask_user_question/renderers.py +309 -0
- code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
- code_puppy/tools/ask_user_question/theme.py +155 -0
- code_puppy/tools/ask_user_question/tui_loop.py +423 -0
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +289 -0
- code_puppy/tools/browser/browser_interactions.py +545 -0
- code_puppy/tools/browser/browser_locators.py +640 -0
- code_puppy/tools/browser/browser_manager.py +378 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +179 -0
- code_puppy/tools/browser/browser_scripts.py +462 -0
- code_puppy/tools/browser/browser_workflows.py +221 -0
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +534 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +1346 -0
- code_puppy/tools/common.py +1409 -0
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +886 -0
- code_puppy/tools/file_operations.py +802 -0
- code_puppy/tools/scheduler_tools.py +412 -0
- code_puppy/tools/skills_tools.py +244 -0
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/tools/tools_content.py +51 -0
- code_puppy/tools/universal_constructor.py +889 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +82 -0
- codepp-0.0.437.dist-info/METADATA +766 -0
- codepp-0.0.437.dist-info/RECORD +288 -0
- codepp-0.0.437.dist-info/WHEEL +4 -0
- codepp-0.0.437.dist-info/entry_points.txt +3 -0
- 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
|
+
)
|