Skip to content

Commit 21cfd2b

Browse files
TJUEZOpenClaw Assistant
andauthored
fix(provider): fallback to non-streaming when OpenAI Codex streaming fails (zeroclaw-labs#4538)
Cherry-picked from PR zeroclaw-labs#4411: - Add Clone derive to ResponsesRequest and related structs for retry - Retry with stream=false when streaming decode fails - Add missing field initializers for ChannelMessage (already present in master) Co-authored-by: OpenClaw Assistant <assistant@openclaw.ai>
1 parent fb0c5c5 commit 21cfd2b

1 file changed

Lines changed: 53 additions & 6 deletions

File tree

src/providers/openai_codex.rs

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub struct OpenAiCodexProvider {
2626
client: Client,
2727
}
2828

29-
#[derive(Debug, Serialize)]
29+
#[derive(Debug, Serialize, Clone)]
3030
struct ResponsesRequest {
3131
model: String,
3232
input: Vec<ResponsesInput>,
@@ -40,13 +40,13 @@ struct ResponsesRequest {
4040
parallel_tool_calls: bool,
4141
}
4242

43-
#[derive(Debug, Serialize)]
43+
#[derive(Debug, Serialize, Clone)]
4444
struct ResponsesInput {
4545
role: String,
4646
content: Vec<ResponsesInputContent>,
4747
}
4848

49-
#[derive(Debug, Serialize)]
49+
#[derive(Debug, Serialize, Clone)]
5050
struct ResponsesInputContent {
5151
#[serde(rename = "type")]
5252
kind: String,
@@ -56,12 +56,12 @@ struct ResponsesInputContent {
5656
image_url: Option<String>,
5757
}
5858

59-
#[derive(Debug, Serialize)]
59+
#[derive(Debug, Serialize, Clone)]
6060
struct ResponsesTextOptions {
6161
verbosity: String,
6262
}
6363

64-
#[derive(Debug, Serialize)]
64+
#[derive(Debug, Serialize, Clone)]
6565
struct ResponsesReasoningOptions {
6666
effort: String,
6767
summary: String,
@@ -711,7 +711,54 @@ impl OpenAiCodexProvider {
711711
return Err(super::api_error("OpenAI Codex", response).await);
712712
}
713713

714-
decode_responses_body(response).await
714+
// Try to decode streaming response first
715+
match decode_responses_body(response).await {
716+
Ok(text) => Ok(text),
717+
// If streaming fails, retry with non-streaming request
718+
Err(e) => {
719+
tracing::warn!(
720+
error = %e,
721+
"OpenAI Codex streaming failed, retrying with non-streaming request"
722+
);
723+
let mut non_streaming_request = request.clone();
724+
non_streaming_request.stream = false;
725+
726+
let mut non_streaming_builder = self
727+
.client
728+
.post(&self.responses_url)
729+
.header("Authorization", format!("Bearer {bearer_token}"))
730+
.header("OpenAI-Beta", "responses=experimental")
731+
.header("originator", "pi")
732+
.header("Content-Type", "application/json");
733+
734+
if let Some(account_id) = account_id.as_deref() {
735+
non_streaming_builder =
736+
non_streaming_builder.header("chatgpt-account-id", account_id);
737+
}
738+
739+
if use_gateway_api_key_auth {
740+
if let Some(access_token) = access_token.as_deref() {
741+
non_streaming_builder =
742+
non_streaming_builder.header("x-openai-access-token", access_token);
743+
}
744+
if let Some(account_id) = account_id.as_deref() {
745+
non_streaming_builder =
746+
non_streaming_builder.header("x-openai-account-id", account_id);
747+
}
748+
}
749+
750+
let non_streaming_response = non_streaming_builder
751+
.json(&non_streaming_request)
752+
.send()
753+
.await?;
754+
755+
if !non_streaming_response.status().is_success() {
756+
return Err(super::api_error("OpenAI Codex", non_streaming_response).await);
757+
}
758+
759+
decode_responses_body(non_streaming_response).await
760+
}
761+
}
715762
}
716763
}
717764

0 commit comments

Comments
 (0)