Skip to content

Commit 2bd62c3

Browse files
committed
refactor: improve error handling and streaming response management in Codex endpoints
Enhanced error handling for non-streaming Codex responses by properly detecting and returning error responses with correct HTTP status codes instead of treating them as streaming responses. Added comprehensive error response parsing to handle various error formats (detail, error, message fields) and improved streaming response collection to differentiate between actual streaming content and error responses. Key improvements: - Added error response detection in both streaming and non-streaming paths - Proper status code forwarding for error responses - Enhanced JSON error parsing with fallback mechanisms - Improved streaming response buffering and replay functionality - Better content-type checking to distinguish between streaming and error responses - Added comprehensive error logging for debugging purposes feat: improve Codex streaming response handling with proper header management - Capture and forward upstream response headers from Codex API - Add comprehensive error handling for HTTP 4xx/5xx responses in streaming mode - Convert API errors to streaming-compatible JSON format with proper error structure - Filter out conflicting headers (content-length, content-encoding, date) for streaming responses - Implement header capture pattern in both codex.py and proxy_service.py - Ensure streaming responses maintain proper SSE format even for error conditions - Add debug logging for response headers and error conditions refactor: simplify Codex adapter imports and consolidate response handling - Removed unused CodexAdapter and CodexResponseChoice from adapters/codex/__init__.py - Consolidated Codex models to use centralized request/response models from ccproxy.models - Simplified response data extraction in response_adapter.py by removing redundant type checks - Enhanced codex chat completions route with improved error handling and streaming logic - Added proper type hints and error response handling for better debugging - Streamlined SSE event processing with clearer reasoning block detection - Fixed response format conversion to maintain consistency with OpenAI Chat Completions API The changes consolidate Codex-related code organization, improve error handling robustness, and enhance the streaming response conversion between Response API format and Chat Completions format. This reduces code duplication and improves maintainability while ensuring proper OpenAI API compatibility.
1 parent c6fb72b commit 2bd62c3

File tree

4 files changed

+854
-537
lines changed

4 files changed

+854
-537
lines changed

ccproxy/adapters/codex/__init__.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
"""Codex adapter for format conversion."""
22

3-
from ccproxy.adapters.codex.adapter import CodexAdapter
4-
from ccproxy.adapters.codex.models import (
5-
CodexMessage,
6-
CodexRequest,
7-
CodexResponse,
8-
CodexResponseChoice,
9-
)
3+
from ccproxy.models.requests import CodexMessage, CodexRequest
4+
from ccproxy.models.responses import CodexResponse
105

116

127
__all__ = [
13-
"CodexAdapter",
148
"CodexMessage",
159
"CodexRequest",
1610
"CodexResponse",
17-
"CodexResponseChoice",
1811
]

ccproxy/adapters/openai/response_adapter.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -123,37 +123,37 @@ def response_to_chat_completion(
123123
Returns:
124124
Chat Completions formatted response
125125
"""
126+
# Extract the actual response data
127+
response_dict: dict[str, Any]
126128
if isinstance(response_data, ResponseCompleted):
127-
response = response_data.response
128-
elif isinstance(response_data, dict):
129+
# Convert Pydantic model to dict
130+
response_dict = response_data.response.model_dump()
131+
else: # isinstance(response_data, dict)
129132
if "response" in response_data:
130-
response = response_data["response"]
133+
response_dict = response_data["response"]
131134
else:
132-
response = response_data
133-
else:
134-
response = response_data
135+
response_dict = response_data
135136

136137
# Extract content from Response API output
137138
content = ""
138-
if isinstance(response, dict):
139-
output = response.get("output", [])
140-
# Look for message type output (skip reasoning)
141-
for output_item in output:
142-
if output_item.get("type") == "message":
143-
output_content = output_item.get("content", [])
144-
for content_block in output_content:
145-
if content_block.get("type") in ["output_text", "text"]:
146-
content += content_block.get("text", "")
139+
output = response_dict.get("output", [])
140+
# Look for message type output (skip reasoning)
141+
for output_item in output:
142+
if output_item.get("type") == "message":
143+
output_content = output_item.get("content", [])
144+
for content_block in output_content:
145+
if content_block.get("type") in ["output_text", "text"]:
146+
content += content_block.get("text", "")
147147

148148
# Build Chat Completions response
149-
usage_data = response.get("usage") if isinstance(response, dict) else None
149+
usage_data = response_dict.get("usage")
150150
converted_usage = self._convert_usage(usage_data) if usage_data else None
151-
151+
152152
return OpenAIChatCompletionResponse(
153-
id=response.get("id", f"resp_{uuid.uuid4().hex}") if isinstance(response, dict) else f"resp_{uuid.uuid4().hex}",
153+
id=response_dict.get("id", f"resp_{uuid.uuid4().hex}"),
154154
object="chat.completion",
155-
created=response.get("created_at", int(time.time())) if isinstance(response, dict) else int(time.time()),
156-
model=response.get("model", "gpt-5") if isinstance(response, dict) else "gpt-5",
155+
created=response_dict.get("created_at", int(time.time())),
156+
model=response_dict.get("model", "gpt-5"),
157157
choices=[
158158
OpenAIChoice(
159159
index=0,
@@ -164,7 +164,7 @@ def response_to_chat_completion(
164164
)
165165
],
166166
usage=converted_usage,
167-
system_fingerprint=response.get("safety_identifier") if isinstance(response, dict) else None,
167+
system_fingerprint=response_dict.get("safety_identifier"),
168168
)
169169

170170
async def stream_response_to_chat(
@@ -310,12 +310,12 @@ async def stream_response_to_chat(
310310
{"index": 0, "delta": {}, "finish_reason": "stop"}
311311
],
312312
}
313-
313+
314314
# Add usage if available
315315
converted_usage = self._convert_usage(usage) if usage else None
316316
if converted_usage:
317317
chunk_data["usage"] = converted_usage.model_dump()
318-
318+
319319
yield chunk_data
320320

321321
logger.debug(

0 commit comments

Comments
 (0)