feat(openai): support codex models via the Responses API (#87)#318
feat(openai): support codex models via the Responses API (#87)#318proyectoauraorg wants to merge 4 commits into
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughDetects 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. ChangesCodex Model Support
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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
src/api/providers/openai.tsESLint 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/api/providers/__tests__/openai-codex-responses.spec.ts (3)
223-225: 💤 Low valueAdd 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 winAdd 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:
- Passes tools in the
createMessageoptions- Verifies the request body's
toolsparameter uses the Responses API formatAs 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 winAdd 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
📒 Files selected for processing (2)
src/api/providers/__tests__/openai-codex-responses.spec.tssrc/api/providers/openai.ts
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
src/api/providers/__tests__/openai-codex-responses.spec.ts
| 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) | ||
| }) |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
@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:
- Passes an MCP-style tool (with
inputSchemainstead ofparameters) - Asserts the converted output has
strict: false - Verifies the
nameanddescriptionare 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.
There was a problem hiding this comment.
@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)ˆ)
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/api/providers/__tests__/openai-codex-responses.spec.ts (1)
519-539: ⚡ Quick winAdd the no-details
response.errorcase too.Line 519 covers the
response.failedfallback, but the siblingresponse.error/errorbranch insrc/api/providers/openai.tsis 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
📒 Files selected for processing (2)
src/api/providers/__tests__/openai-codex-responses.spec.tssrc/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.
b2a9390 to
b2ea01f
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
src/api/providers/__tests__/openai-codex-responses.spec.tssrc/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
There was a problem hiding this comment.
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 winURL-based images are not handled correctly.
Anthropic's
ImageBlockParam.sourcecan 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 likedata: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
📒 Files selected for processing (1)
src/api/providers/openai.ts
📊 Codecov Coverage NoteThe Plan to reach 80%:
I will push these test additions shortly to bring diff coverage above 80%. |
Closes #87
Problem
The OpenAI-compatible provider (
OpenAiHandler) routes every request throughchat.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/completePromptnow 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 usingchat.completionsunchanged.Note on tool-call finalization
The codex stream emits
tool_call_partialduring argument deltas; streamed calls are finalized by the consumer's end-of-streamNativeToolCallParser.finalizeRawChunks()(Task.ts), and the completetool_callevent is guarded bystreamedToolCallIdsto 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). Existingopenai.spec.tsunchanged and green.tsc/eslint/prettierand the full openai suite (65 tests) pass locally.Summary by CodeRabbit
New Features
Tests