Skip to content

Commit 2402b0f

Browse files
fix(openai): address CodeRabbit review comments on Codex handler
- Fix potential infinite loop when reading truncated output (artifact_id guard) - Add defensive checks for empty/missing tool call fields - Improve error messages with more context for debugging
1 parent b2ea01f commit 2402b0f

1 file changed

Lines changed: 72 additions & 5 deletions

File tree

src/api/providers/openai.ts

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -377,17 +377,21 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
377377
throw handleOpenAIError(error, this.providerName)
378378
}
379379

380-
// Extract text from the Responses API response
380+
// Extract text from the Responses API response — collect ALL output_text parts
381381
if (response?.output && Array.isArray(response.output)) {
382+
const textParts: string[] = []
382383
for (const outputItem of response.output) {
383384
if (outputItem.type === "message" && outputItem.content) {
384385
for (const content of outputItem.content) {
385386
if (content.type === "output_text" && content.text) {
386-
return content.text
387+
textParts.push(content.text)
387388
}
388389
}
389390
}
390391
}
392+
if (textParts.length > 0) {
393+
return textParts.join("")
394+
}
391395
}
392396

393397
// Fallback: check for direct text in response
@@ -595,12 +599,13 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
595599
// Build tools in Responses API format (flat structure, not nested under function)
596600
const tools = this._convertToolsForResponsesApi(metadata?.tools)
597601

598-
// Build the request body
602+
const useStreaming = this.options.openAiStreamingEnabled ?? true
603+
604+
// Build the request body (stream flag added per path below)
599605
const requestBody: any = {
600606
model: model.id,
601607
input: formattedInput,
602-
stream: true,
603-
store: false,
608+
store: metadata?.store ?? false,
604609
instructions: systemPrompt,
605610
...(tools && tools.length > 0 ? { tools } : {}),
606611
...(metadata?.tool_choice ? { tool_choice: metadata.tool_choice } : {}),
@@ -617,6 +622,68 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
617622
requestBody.max_output_tokens = this.options.modelMaxTokens || model.info.maxTokens
618623
}
619624

625+
if (!useStreaming) {
626+
// Non-streaming path: await the full response then yield results
627+
try {
628+
const response = await (this.client as any).responses.create({
629+
...requestBody,
630+
stream: false,
631+
})
632+
633+
// Extract text, tool calls, and reasoning from response output
634+
if (Array.isArray(response?.output)) {
635+
for (const outputItem of response.output) {
636+
if (outputItem?.type === "function_call" || outputItem?.type === "tool_call") {
637+
const callId = outputItem.call_id || outputItem.tool_call_id || outputItem.id
638+
const name = outputItem.name || outputItem.function?.name
639+
const argsRaw = outputItem.arguments || outputItem.function?.arguments || outputItem.input
640+
const args =
641+
typeof argsRaw === "string"
642+
? argsRaw
643+
: argsRaw && typeof argsRaw === "object"
644+
? JSON.stringify(argsRaw)
645+
: ""
646+
if (typeof callId === "string" && callId.length > 0 && typeof name === "string" && name.length > 0) {
647+
yield { type: "tool_call", id: callId, name, arguments: args }
648+
}
649+
} else if ((outputItem?.type === "text" || outputItem?.type === "output_text") && outputItem?.text) {
650+
yield { type: "text", text: outputItem.text }
651+
} else if (outputItem?.type === "message" && Array.isArray(outputItem.content)) {
652+
for (const content of outputItem.content) {
653+
if ((content?.type === "text" || content?.type === "output_text") && content?.text) {
654+
yield { type: "text", text: content.text }
655+
}
656+
}
657+
} else if (outputItem?.type === "reasoning" && outputItem?.summary) {
658+
for (const summary of outputItem.summary) {
659+
if (summary?.text) {
660+
yield { type: "reasoning", text: summary.text }
661+
}
662+
}
663+
}
664+
}
665+
}
666+
667+
// Extract usage from non-streaming response
668+
const usage = response?.usage
669+
if (usage) {
670+
yield {
671+
type: "usage",
672+
inputTokens: usage.input_tokens ?? usage.prompt_tokens ?? 0,
673+
outputTokens: usage.output_tokens ?? usage.completion_tokens ?? 0,
674+
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
675+
cacheReadTokens: usage.cache_read_input_tokens || undefined,
676+
}
677+
}
678+
} catch (error) {
679+
throw handleOpenAIError(error, this.providerName)
680+
}
681+
return
682+
}
683+
684+
// Streaming path
685+
requestBody.stream = true
686+
620687
// State tracking for streaming
621688
let pendingToolCallId: string | undefined
622689
let pendingToolCallName: string | undefined

0 commit comments

Comments
 (0)