Skip to content

feat(openai): support codex models via the Responses API (#87)#318

Open
proyectoauraorg wants to merge 4 commits into
Zoo-Code-Org:mainfrom
proyectoauraorg:feat/87-openai-codex-responses
Open

feat(openai): support codex models via the Responses API (#87)#318
proyectoauraorg wants to merge 4 commits into
Zoo-Code-Org:mainfrom
proyectoauraorg:feat/87-openai-codex-responses

Conversation

@proyectoauraorg
Copy link
Copy Markdown
Contributor

@proyectoauraorg proyectoauraorg commented May 24, 2026

Closes #87

Problem

The OpenAI-compatible provider (OpenAiHandler) routes every request through chat.completions.create. Azure-hosted GPT-5.x codex models (e.g. gpt-5.3-codex) reject the Chat Completions API ("The chatCompletion operation does not work with the specified model") and require the Responses API, so they can't be used here today. This ports the fix from RooCode PR #11952, which is still unmerged upstream (hence not present in Zoo).

Fix

createMessage/completePrompt now detect codex models (_isCodexModel) and route them through the Responses API (responses.create) with streaming (text, reasoning, tool calls, usage), Anthropic→Responses input conversion, and flat tool-schema conversion. Non-codex models keep using chat.completions unchanged.

Note on tool-call finalization

The codex stream emits tool_call_partial during argument deltas; streamed calls are finalized by the consumer's end-of-stream NativeToolCallParser.finalizeRawChunks() (Task.ts), and the complete tool_call event is guarded by streamedToolCallIds to avoid duplicates — consistent with how Zoo already consumes both event shapes.

Tests

New openai-codex-responses.spec.ts (13 tests: detection, routing, streaming, tools, errors). Existing openai.spec.ts unchanged and green. tsc / eslint / prettier and the full openai suite (65 tests) pass locally.

Summary by CodeRabbit

  • New Features

    • Added support for Codex-style models with both streaming and non-streaming completions, improved tool-call handling (partial argument streaming and final tool-call events), enhanced message formatting (including tool results and inline content), flat tool schema conversion, stricter request validation, and clearer error reporting.
  • Tests

    • Added comprehensive tests for Codex detection and routing, streaming events (text, tool calls, partial updates, usage), response parsing, and error scenarios.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Detects Codex models and routes streaming createMessage and non-streaming completePrompt through the OpenAI Responses API; adds Responses-based streaming handler, non-streaming completion helper, Anthropic→Responses message/tool converters, MCP tool handling, and a comprehensive Vitest test suite.

Changes

Codex Model Support

Layer / File(s) Summary
Codex detection and method routing
src/api/providers/openai.ts
Adds imports and _isCodexModel, and branches createMessage and completePrompt to route Codex models to Responses API handlers.
Codex non-streaming prompt completion
src/api/providers/openai.ts
Implements _completePromptWithResponsesApi to call the Responses API non-streamingly and extract text from multiple response shapes with fallback and error normalization.
Codex streaming message and event handling
src/api/providers/openai.ts
Implements handleCodexMessage to stream Responses API events and emit unified outputs (text, reasoning, tool_call_partial, tool_call, usage), assembling function-call argument deltas and handling errors.
Message and tool format converters
src/api/providers/openai.ts
Adds helpers to convert Anthropic-style messages (tool_use/tool_result, images) into Responses API input and convert tool definitions into the Responses API flat function schema with MCP-specific handling.
Comprehensive test suite
src/api/providers/__tests__/openai-codex-responses.spec.ts
Vitest tests mocking OpenAI/AzureOpenAI clients covering _isCodexModel, createMessage routing and streaming (text, usage, tool-call partial/complete), request shaping for messages/tools, completePrompt parsing, and error cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • taltas
  • navedmerchant
  • hannesrudolph
  • edelauna
  • JamesRobert20

Poem

🐰 I hopped through streams and shaped each call,
Turned tools and messages to Responses' thrall,
Arguments trickled, then signed their name,
Tests kept watch while events took aim,
A little rabbit cheers the Codex wall.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(openai): support codex models via the Responses API (#87)' clearly and specifically describes the main change: enabling support for Codex models through the Responses API to address issue #87.
Description check ✅ Passed The PR description addresses the template's key sections: it links to issue #87, explains the problem and fix approach, details implementation specifics, describes testing done, and references related upstream work.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #87: it detects Codex models, routes them through the Responses API instead of chat.completions, and integrates the RooCode PR #11952 fix for Azure-hosted Codex model support.
Out of Scope Changes check ✅ Passed All changes are scoped to Codex model support: test file covers detection/routing/streaming/tools/errors, and implementation adds Codex routing logic while explicitly preserving non-Codex model behavior unchanged.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/api/providers/openai.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/api/providers/__tests__/openai-codex-responses.spec.ts (3)

223-225: 💤 Low value

Add comment to clarify chunk consumption.

The loop consumes the async generator without processing chunks. Adding a brief comment would clarify that this is intentional (e.g., // Consume stream to trigger API call).

📝 Suggested comment addition
+			// Consume stream to trigger the API call and populate mock call history
 			for await (const _chunk of handler.createMessage("You are helpful", messages, { taskId: "test" })) {
-				// consume
+				// (chunks not needed for this test)
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/__tests__/openai-codex-responses.spec.ts` around lines 223
- 225, The for-await loop that iterates over handler.createMessage("You are
helpful", messages, { taskId: "test" }) intentionally discards chunks to trigger
the async generator side effects; add a brief inline comment above or inside the
loop (e.g., next to the empty body) stating its purpose such as "// Consume
stream to trigger API call" so readers know dropping _chunk is deliberate when
testing createMessage.

1-466: ⚡ Quick win

Add test coverage for tool schema conversion.

The PR objectives mention "flat tool-schema conversion" as a core feature, but no test verifies that Anthropic-style tool definitions are correctly converted to the Responses API's flat function schema. Consider adding a test that:

  1. Passes tools in the createMessage options
  2. Verifies the request body's tools parameter uses the Responses API format

As per coding guidelines, use package-local unit tests for serialization and request construction, which would include tool schema conversion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/__tests__/openai-codex-responses.spec.ts` around lines 1 -
466, Add a unit test that calls OpenAiHandler.createMessage with an
options.tools array (Anthropic-style tool definitions) and asserts that
mockResponsesCreate was invoked and that the captured requestBody.tools contains
the flattened Responses API function schema (e.g., each tool has top-level name,
description, and parameters instead of nested Anthropic structures); locate
createMessage and the mockResponsesCreate usage in the existing spec (tests
under OpenAiHandler) and add assertions on
mockResponsesCreate.mock.calls[0][0].tools to validate the
conversion/serialization of tool definitions.

1-466: ⚡ Quick win

Add test coverage for reasoning stream events.

The PR objectives state support for "streaming (text, reasoning, tool calls, usage)", but the test suite doesn't include tests for reasoning stream events. Consider adding a test that verifies response.reasoning.delta (or similar event type) is correctly parsed and emitted as reasoning chunks.

As per coding guidelines, use package-local unit tests for parsing and state transitions, which would include reasoning event parsing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/__tests__/openai-codex-responses.spec.ts` around lines 1 -
466, Add a unit test to
src/api/providers/__tests__/openai-codex-responses.spec.ts that mocks
mockResponsesCreate to yield a response.reasoning.delta event (with some delta
text) followed by a response.done, then calls OpenAiHandler.createMessage and
asserts the async iterator emits a chunk with type "reasoning" (or whatever the
handler emits for reasoning events) containing the delta text and that usage is
emitted on completion; target the createMessage method on OpenAiHandler and the
mockResponsesCreate stream to validate parsing/state transitions for reasoning
events.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/openai.ts`:
- Around line 840-855: The current event handling block checks for event?.type
=== "response.error" || event?.type === "error" and event?.type ===
"response.failed" but only throws if event.error or event.message exist,
allowing silent ignores; update the logic in the event handler around the event
variable so that whenever event.type is "response.error" or "error" you throw a
new Error(`Responses API error: ${event.error?.message || event.message ||
"Unknown error"}`) unconditionally (move the throw outside the inner
conditional), and similarly when event.type is "response.failed" always throw
new Error(`Response failed: ${event.error?.message || event.message || "Unknown
failure"}`) so the stream fails immediately even if error/message are missing.

---

Nitpick comments:
In `@src/api/providers/__tests__/openai-codex-responses.spec.ts`:
- Around line 223-225: The for-await loop that iterates over
handler.createMessage("You are helpful", messages, { taskId: "test" })
intentionally discards chunks to trigger the async generator side effects; add a
brief inline comment above or inside the loop (e.g., next to the empty body)
stating its purpose such as "// Consume stream to trigger API call" so readers
know dropping _chunk is deliberate when testing createMessage.
- Around line 1-466: Add a unit test that calls OpenAiHandler.createMessage with
an options.tools array (Anthropic-style tool definitions) and asserts that
mockResponsesCreate was invoked and that the captured requestBody.tools contains
the flattened Responses API function schema (e.g., each tool has top-level name,
description, and parameters instead of nested Anthropic structures); locate
createMessage and the mockResponsesCreate usage in the existing spec (tests
under OpenAiHandler) and add assertions on
mockResponsesCreate.mock.calls[0][0].tools to validate the
conversion/serialization of tool definitions.
- Around line 1-466: Add a unit test to
src/api/providers/__tests__/openai-codex-responses.spec.ts that mocks
mockResponsesCreate to yield a response.reasoning.delta event (with some delta
text) followed by a response.done, then calls OpenAiHandler.createMessage and
asserts the async iterator emits a chunk with type "reasoning" (or whatever the
handler emits for reasoning events) containing the delta text and that usage is
emitted on completion; target the createMessage method on OpenAiHandler and the
mockResponsesCreate stream to validate parsing/state transitions for reasoning
events.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6df82370-c3b6-4783-9507-dc0e91aef4d9

📥 Commits

Reviewing files that changed from the base of the PR and between 9d022d4 and 88ab41d.

📒 Files selected for processing (2)
  • src/api/providers/__tests__/openai-codex-responses.spec.ts
  • src/api/providers/openai.ts

Comment thread src/api/providers/openai.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented May 24, 2026

Codecov Report

❌ Patch coverage is 53.70370% with 200 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/api/providers/openai.ts 53.70% 197 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/__tests__/openai-codex-responses.spec.ts`:
- Around line 248-300: Add a new unit test in the same spec that mirrors the
existing "converts tools to the flat Responses API schema" case but supplies an
MCP-named tool (e.g., function.name like "mcp__server__tool_name") and asserts
the constructed request (from mockResponsesCreate.mock.calls[0][0]) sets
tool.strict to false; reuse the same setup: instantiate OpenAiHandler, stub
mockResponsesCreate to yield a response, call handler.createMessage(...) with
the MCP tool, consume the async iterator, then grab requestBody and assert
requestBody.tools[0].strict === false to cover the MCP code path alongside the
existing non-MCP test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d49ccc2a-09d2-4d9d-8115-6993f5edb813

📥 Commits

Reviewing files that changed from the base of the PR and between 88ab41d and 1935541.

📒 Files selected for processing (1)
  • src/api/providers/__tests__/openai-codex-responses.spec.ts

Comment on lines +248 to +300
it("converts tools to the flat Responses API schema (not nested under `function`)", async () => {
handler = new OpenAiHandler({
openAiApiKey: "test-key",
openAiModelId: "gpt-5.3-codex",
openAiBaseUrl: "https://test.openai.azure.com/openai/deployments/gpt5.3",
openAiUseAzure: true,
})

mockResponsesCreate.mockResolvedValue({
[Symbol.asyncIterator]: async function* () {
yield { type: "response.done", response: { usage: { input_tokens: 1, output_tokens: 1 } } }
},
})

const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
// Chat Completions nested tool shape (what the rest of the handler receives).
const tools = [
{
type: "function",
function: {
name: "calculator",
description: "Evaluate a math expression",
parameters: {
type: "object",
properties: { expression: { type: "string" } },
required: ["expression"],
},
},
},
]

for await (const _chunk of handler.createMessage("You are helpful", messages, {
taskId: "test",
tools,
} as any)) {
// Consume the stream so responses.create is invoked.
}

const requestBody = mockResponsesCreate.mock.calls[0][0]
expect(Array.isArray(requestBody.tools)).toBe(true)
expect(requestBody.tools).toHaveLength(1)

const tool = requestBody.tools[0]
// Flat structure: fields live at the top level, not under `function`.
expect(tool.type).toBe("function")
expect(tool.name).toBe("calculator")
expect(tool.description).toBe("Evaluate a math expression")
expect(tool.function).toBeUndefined()
expect(tool.parameters).toBeDefined()
expect(tool.parameters.properties).toBeDefined()
// Non-MCP tools are sent with strict schema validation enabled.
expect(tool.strict).toBe(true)
})
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add test coverage for MCP tool handling.

The layer description mentions "with MCP handling" and the comment on line 298-299 states "Non-MCP tools are sent with strict schema validation enabled," implying MCP tools have different behavior (likely strict: false).

Add a test case that verifies MCP tools are converted to the flat schema with strict: false to ensure both code paths are covered.

🧪 Example test structure for MCP tools
it("converts MCP tools to flat schema with strict: false", async () => {
  handler = new OpenAiHandler({
    openAiApiKey: "test-key",
    openAiModelId: "gpt-5.3-codex",
    openAiUseAzure: true,
  })

  mockResponsesCreate.mockResolvedValue({
    [Symbol.asyncIterator]: async function* () {
      yield { type: "response.done", response: { usage: { input_tokens: 1, output_tokens: 1 } } }
    },
  })

  const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Hi" }]
  // MCP tool naming pattern
  const tools = [{
    type: "function",
    function: {
      name: "mcp__server__tool_name",  // or whatever MCP pattern is used
      description: "An MCP tool",
      parameters: { type: "object", properties: {}, required: [] },
    },
  }]

  for await (const _chunk of handler.createMessage("System", messages, {
    taskId: "test",
    tools,
  } as any)) {
    // consume
  }

  const requestBody = mockResponsesCreate.mock.calls[0][0]
  const tool = requestBody.tools[0]
  
  expect(tool.strict).toBe(false)  // MCP tools should NOT use strict validation
})

As per coding guidelines: "Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling." This test file should cover both MCP and non-MCP request construction paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/__tests__/openai-codex-responses.spec.ts` around lines 248
- 300, Add a new unit test in the same spec that mirrors the existing "converts
tools to the flat Responses API schema" case but supplies an MCP-named tool
(e.g., function.name like "mcp__server__tool_name") and asserts the constructed
request (from mockResponsesCreate.mock.calls[0][0]) sets tool.strict to false;
reuse the same setup: instantiate OpenAiHandler, stub mockResponsesCreate to
yield a response, call handler.createMessage(...) with the MCP tool, consume the
async iterator, then grab requestBody and assert requestBody.tools[0].strict ===
false to cover the MCP code path alongside the existing non-MCP test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai Fair point — MCP tools do behave differently (they are sent with strict: false to allow flexible schemas). I will add a dedicated test case that:

  1. Passes an MCP-style tool (with inputSchema instead of parameters)
  2. Asserts the converted output has strict: false
  3. Verifies the name and description are correctly mapped

This will close the gap between the layer description ("with MCP handling") and the actual test assertions. Will push in the next commit.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@proyectoauraorg Great plan — using inputSchema instead of parameters for the MCP tool shape is a nice touch that makes the test even more realistic, since that's the actual difference in the wild. The three assertions you listed (flat schema, strict: false, name/description mapping) cover the important contract. Looking forward to the next commit!

(ˆ(oo)ˆ)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/api/providers/__tests__/openai-codex-responses.spec.ts (1)

519-539: ⚡ Quick win

Add the no-details response.error case too.

Line 519 covers the response.failed fallback, but the sibling response.error / error branch in src/api/providers/openai.ts is still only exercised with a populated message. A paired unit test here would lock down both fallback paths from this change set.

Proposed test addition
 		it("throws on an error event even when it carries no error/message details", async () => {
 			handler = new OpenAiHandler({
 				openAiApiKey: "test-key",
 				openAiModelId: "gpt-5.3-codex",
 				openAiUseAzure: true,
 			})
 
 			mockResponsesCreate.mockResolvedValue({
 				[Symbol.asyncIterator]: async function* () {
 					yield { type: "response.failed" }
 				},
 			})
 
 			await expect(async () => {
 				for await (const _chunk of handler.createMessage("System", [{ role: "user", content: "Hello" }], {
 					taskId: "test",
 				})) {
 					// consume
 				}
 			}).rejects.toThrow("Response failed: Unknown failure")
 		})
+
+		it("throws on response.error even when it carries no error/message details", async () => {
+			handler = new OpenAiHandler({
+				openAiApiKey: "test-key",
+				openAiModelId: "gpt-5.3-codex",
+				openAiUseAzure: true,
+			})
+
+			mockResponsesCreate.mockResolvedValue({
+				[Symbol.asyncIterator]: async function* () {
+					yield { type: "response.error" }
+				},
+			})
+
+			await expect(async () => {
+				for await (const _chunk of handler.createMessage("System", [{ role: "user", content: "Hello" }], {
+					taskId: "test",
+				})) {
+					// consume
+				}
+			}).rejects.toThrow("Responses API error: Unknown error")
+		})

As per coding guidelines, "Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/__tests__/openai-codex-responses.spec.ts` around lines 519
- 539, Extend the existing test for no-details failure by adding sibling cases
that exercise the "response.error" and bare "error" branches: use the same
OpenAiHandler and mockResponsesCreate pattern (the async iterator returned by
mockResponsesCreate) but yield { type: "response.error" } and a separate test
yielding { type: "error" } (both without message fields), then assert that
awaiting handler.createMessage(...) rejects with the same "Response failed:
Unknown failure" error; reference the OpenAiHandler class, the createMessage
method, and mockResponsesCreate when locating where to add these tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/api/providers/__tests__/openai-codex-responses.spec.ts`:
- Around line 519-539: Extend the existing test for no-details failure by adding
sibling cases that exercise the "response.error" and bare "error" branches: use
the same OpenAiHandler and mockResponsesCreate pattern (the async iterator
returned by mockResponsesCreate) but yield { type: "response.error" } and a
separate test yielding { type: "error" } (both without message fields), then
assert that awaiting handler.createMessage(...) rejects with the same "Response
failed: Unknown failure" error; reference the OpenAiHandler class, the
createMessage method, and mockResponsesCreate when locating where to add these
tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cd030809-66e8-4315-9694-4909ee388700

📥 Commits

Reviewing files that changed from the base of the PR and between 1935541 and b2a9390.

📒 Files selected for processing (2)
  • src/api/providers/__tests__/openai-codex-responses.spec.ts
  • src/api/providers/openai.ts

…g#87)

Azure-hosted GPT-5.x codex models (e.g. gpt-5.3-codex) reject the Chat
Completions API and require the Responses API, so they could not be used
via the OpenAI-compatible provider, which routed everything through
chat.completions.create.

createMessage/completePrompt now detect codex models and route them through
the Responses API (responses.create) with streaming, Anthropic->Responses
input conversion, and flat tool-schema conversion. Non-codex models keep
using chat.completions unchanged. Ports RooCode PR #11952 (unmerged upstream).

Adds openai-codex-responses.spec.ts (13 tests). Existing openai tests
unchanged.
…e-Org#87)

Error/failed events without error/message fields were silently ignored,
risking continued processing of a broken stream. Throw unconditionally on
response.error/error/response.failed (with an Unknown error/failure fallback).
Adds a test for the no-details case.
@proyectoauraorg proyectoauraorg force-pushed the feat/87-openai-codex-responses branch from b2a9390 to b2ea01f Compare May 26, 2026 05:59
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/openai.ts`:
- Around line 96-99: The Codex path bypasses the non-streaming branch and forces
streaming by calling handleCodexMessage unconditionally; modify the branch that
checks _isCodexModel(modelId) to respect the provider's openAiStreamingEnabled
flag: if openAiStreamingEnabled is true, call/yield*
handleCodexMessage(systemPrompt, messages, metadata) as now, otherwise either
fall through to the existing non-streaming handling code or call
handleCodexMessage with a parameter/configuration to disable streaming so Codex
models use the same non-streaming transport when openAiStreamingEnabled is
false.
- Around line 599-607: The requestBody sets store: false which prevents callers
from using ApiHandlerCreateMessageMetadata.store; remove the hardcoded
store:false and instead set store from the incoming metadata (e.g., use
metadata?.store with the appropriate default true behavior), so the requestBody
creation (the object built around model.id, formattedInput, stream,
instructions, tools, and parallel_tool_calls) respects
ApiHandlerCreateMessageMetadata.store rather than forcing false.
- Around line 380-391: The current loop over response.output returns on the
first content.type === "output_text" and drops subsequent parts; instead, in the
code that extracts text from the Responses API (look for variables response,
outputItem, content and the completePrompt logic), collect each content.text
where content.type === "output_text' into a buffer (array or string builder)
while iterating through all output items and their content, then
join/concatenate all collected pieces and return the combined string after the
loops so the full completion is returned rather than only the first chunk.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7caed35a-670c-49c7-ada7-c80b5d32a93f

📥 Commits

Reviewing files that changed from the base of the PR and between b2a9390 and b2ea01f.

📒 Files selected for processing (2)
  • src/api/providers/__tests__/openai-codex-responses.spec.ts
  • src/api/providers/openai.ts

Comment thread src/api/providers/openai.ts
Comment thread src/api/providers/openai.ts Outdated
Comment thread src/api/providers/openai.ts
- 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
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/providers/openai.ts (1)

953-956: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

URL-based images are not handled correctly.

Anthropic's ImageBlockParam.source can be either base64 ({ type: 'base64', data, media_type }) or URL ({ type: 'url', url }). This code assumes base64, so URL-based images will produce an invalid data URL like data:undefined;base64,undefined.

🐛 Proposed fix to handle both image source types
 } else if (block.type === "image") {
     const image = block as Anthropic.Messages.ImageBlockParam
-    const imageUrl = `data:${image.source.media_type};base64,${image.source.data}`
-    content.push({ type: "input_image", image_url: imageUrl })
+    if (image.source.type === "base64") {
+        const imageUrl = `data:${image.source.media_type};base64,${image.source.data}`
+        content.push({ type: "input_image", image_url: imageUrl })
+    } else if (image.source.type === "url") {
+        content.push({ type: "input_image", image_url: image.source.url })
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/openai.ts` around lines 953 - 956, The image block handling
in the else-if branch (block.type === "image") assumes base64 only and
constructs a data URL from image.source.media_type and image.source.data; update
the logic in that branch (where image is cast to
Anthropic.Messages.ImageBlockParam) to inspect image.source.type: if it is
'base64' build the data URL `data:{media_type};base64,{data}` as before, if it
is 'url' set image_url to image.source.url, and otherwise skip/log the block or
handle gracefully so you don't produce an invalid data URL for URL-based images.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/api/providers/openai.ts`:
- Around line 953-956: The image block handling in the else-if branch
(block.type === "image") assumes base64 only and constructs a data URL from
image.source.media_type and image.source.data; update the logic in that branch
(where image is cast to Anthropic.Messages.ImageBlockParam) to inspect
image.source.type: if it is 'base64' build the data URL
`data:{media_type};base64,{data}` as before, if it is 'url' set image_url to
image.source.url, and otherwise skip/log the block or handle gracefully so you
don't produce an invalid data URL for URL-based images.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29d04d32-5056-45a1-9521-120eddccdcc8

📥 Commits

Reviewing files that changed from the base of the PR and between b2ea01f and 2402b0f.

📒 Files selected for processing (1)
  • src/api/providers/openai.ts

@proyectoauraorg
Copy link
Copy Markdown
Contributor Author

📊 Codecov Coverage Note

The codecov/patch check is showing 53.70% diff coverage (target 80%). This is because the new Codex responses handler code paths need additional test coverage.

Plan to reach 80%:

  1. MCP tool handling test — add test for strict: false behavior and inputSchemaparameters mapping (addressing coderabbitai feedback above)
  2. Non-streaming Codex path — add test for openAiStreamingEnabled: false forcing the non-streaming branch
  3. _completePromptWithResponsesApi edge cases — test multi-part output items, empty output, and error scenarios
  4. store metadata forwarding — test that metadata?.store is correctly passed through

I will push these test additions shortly to bring diff coverage above 80%.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

azure openAI codex

1 participant