Skip to content

Commit 1feefb6

Browse files
fix(openai): prevent double emission of text/reasoning in native and codex handlers (#10888)
1 parent 21bd760 commit 1feefb6

2 files changed

Lines changed: 36 additions & 20 deletions

File tree

src/api/providers/openai-codex.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -908,17 +908,25 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
908908
}
909909
}
910910

911-
if (item.type === "text" && item.text) {
912-
yield { type: "text", text: item.text }
913-
} else if (item.type === "reasoning" && item.text) {
914-
yield { type: "reasoning", text: item.text }
915-
} else if (item.type === "message" && Array.isArray(item.content)) {
916-
for (const content of item.content) {
917-
if ((content?.type === "text" || content?.type === "output_text") && content?.text) {
918-
yield { type: "text", text: content.text }
911+
// For "added" events, yield text/reasoning content (streaming path)
912+
// For "done" events, do NOT yield text/reasoning - it's already been streamed via deltas
913+
// and would cause double-emission (A, B, C, ABC).
914+
if (event.type === "response.output_item.added") {
915+
if (item.type === "text" && item.text) {
916+
yield { type: "text", text: item.text }
917+
} else if (item.type === "reasoning" && item.text) {
918+
yield { type: "reasoning", text: item.text }
919+
} else if (item.type === "message" && Array.isArray(item.content)) {
920+
for (const content of item.content) {
921+
if ((content?.type === "text" || content?.type === "output_text") && content?.text) {
922+
yield { type: "text", text: content.text }
923+
}
919924
}
920925
}
921-
} else if (
926+
}
927+
928+
// Only handle tool/function calls from done events (to ensure arguments are complete)
929+
if (
922930
(item.type === "function_call" || item.type === "tool_call") &&
923931
event.type === "response.output_item.done"
924932
) {

src/api/providers/openai-native.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,20 +1223,28 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
12231223
}
12241224
}
12251225

1226-
if (item.type === "text" && item.text) {
1227-
yield { type: "text", text: item.text }
1228-
} else if (item.type === "reasoning" && item.text) {
1229-
yield { type: "reasoning", text: item.text }
1230-
} else if (item.type === "message" && Array.isArray(item.content)) {
1231-
for (const content of item.content) {
1232-
// Some implementations send 'text'; others send 'output_text'
1233-
if ((content?.type === "text" || content?.type === "output_text") && content?.text) {
1234-
yield { type: "text", text: content.text }
1226+
// For "added" events, yield text/reasoning content (streaming path)
1227+
// For "done" events, do NOT yield text/reasoning - it's already been streamed via deltas
1228+
// and would cause double-emission (A, B, C, ABC).
1229+
if (event.type === "response.output_item.added") {
1230+
if (item.type === "text" && item.text) {
1231+
yield { type: "text", text: item.text }
1232+
} else if (item.type === "reasoning" && item.text) {
1233+
yield { type: "reasoning", text: item.text }
1234+
} else if (item.type === "message" && Array.isArray(item.content)) {
1235+
for (const content of item.content) {
1236+
// Some implementations send 'text'; others send 'output_text'
1237+
if ((content?.type === "text" || content?.type === "output_text") && content?.text) {
1238+
yield { type: "text", text: content.text }
1239+
}
12351240
}
12361241
}
1237-
} else if (
1242+
}
1243+
1244+
// Only handle tool/function calls from done events (to ensure arguments are complete)
1245+
if (
12381246
(item.type === "function_call" || item.type === "tool_call") &&
1239-
event.type === "response.output_item.done" // Only handle done events for tool calls to ensure arguments are complete
1247+
event.type === "response.output_item.done"
12401248
) {
12411249
// Handle complete tool/function call item
12421250
// Emit as tool_call for backward compatibility with non-streaming tool handling

0 commit comments

Comments
 (0)