Skip to content

fix(openai-chat): enforce type:"object" on all tool parameters, not just Kimi - #933

Draft
IMHinnG wants to merge 2 commits into
lidge-jun:devfrom
IMHinnG:fix/deepseek-tool-schema-null-type
Draft

fix(openai-chat): enforce type:"object" on all tool parameters, not just Kimi#933
IMHinnG wants to merge 2 commits into
lidge-jun:devfrom
IMHinnG:fix/deepseek-tool-schema-null-type

Conversation

@IMHinnG

@IMHinnG IMHinnG commented Aug 3, 2026

Copy link
Copy Markdown

Problem

DeepSeek (and any provider that strictly validates JSON Schema) rejects function
definitions where the root parameters.type is null or missing:

Provider error 400: Invalid schema for function 'codex_app__automation_update':
schema must be a JSON Schema of 'type: "object"', got 'type: null'.

This happens when Codex Desktop sends automation tools to the LLM. Codex tools
with oneOf/anyOf composition schemas, or tools injected via the
Responses-to-Chat translation path, may reach toolsToChatFormat with a null or
absent root type.

Previously, the fix was only applied to two providers — xAI and Kimi. Every
other provider received the raw parameters unchanged, so any non-Kimi/non-xAI
provider that validates JSON Schema strictly (DeepSeek, and likely others) hits
this 400 error.

Root cause

toolsToChatFormat() in openai-chat.ts had a three-way branch:

xAI? → normalizeXaiToolParameters  (special composition handling)
Kimi? → ensureKimiRootObjectType    (force type:"object")
else  → t.parameters                (raw, unnormalized — BUG)

The else-path is incorrect: JSON Schema for function parameters at the root
must always be type: "object". Sending type: null to any provider that
validates the schema will fail.

Fix

Make ensureRootObjectType() the default for ALL non-xAI providers by removing
the Kimi gate, collapsing the three-way branch into a two-way branch:

xAI? → normalizeXaiToolParameters  (unchanged)
else  → ensureRootObjectType        (safe default for everyone)

This is exactly how normalizeToolSchemas() in openai-responses.ts already
works — the same normalization is applied unconditionally to all providers there.
Only the chat-completions path had the provider-gated logic.

Specific changes

  • Rename ensureKimiRootObjectTypeensureRootObjectType
    (no longer Kimi-specific) and update its doc comment to reflect the broader
    scope and explain why unconditional application is safe.

  • Apply unconditionally: all non-xAI providers now go through
    ensureRootObjectType(), which is a no-op when type is already "object",
    and only adds the missing key otherwise.

  • Remove isKimiSchemaTarget() — dead code after the refactor.

Why unconditional is safe

  • JSON Schema for function parameters at the root MUST be type: "object"
  • ensureRootObjectType is a no-op when type is already "object"
  • It only fixes non-conforming cases (null type, missing type)
  • This is identical behavior to normalizeToolSchemas in openai-responses.ts
    which already applies to all providers unconditionally

Tested

Manually verified on a local opencodex deployment with deepseek-v4-flash
requests containing codex_app__automation_update that previously 400'd now
succeed normally.

Related

  • Error originally reported: codex_app__automation_update with type: null
  • Same approach as normalizeFunctionToolSchema in openai-responses.ts:358

Summary by CodeRabbit

  • Bug Fixes
    • Improved tool parameter handling across supported AI providers.
    • Standardized schema normalization for non-xAI providers while preserving xAI-specific behavior.

…ust Kimi

DeepSeek (and likely other providers) reject function schemas where the root
`parameters.type` is `null` or missing — the error is:

  Invalid schema for function 'codex_app__automation_update':
  schema must be a JSON Schema of 'type: "object"', got 'type: null'

Codex tools with oneOf/anyOf composition schemas, or tools injected via the
Responses-to-Chat translation path, may reach `toolsToChatFormat` with a
null or absent root type.  Previously only Kimi and xAI had provider-specific
workarounds; every other provider received the raw parameters unchanged.

This change removes the provider allowlist and applies
`ensureRootObjectType()` unconditionally to all non-xAI providers.  This is
safe because JSON Schema for function parameters at the root MUST be an
object — the function is a no-op when `type` is already `"object"`, and only
adds the missing key otherwise.  The same unconditional strategy is already
used by `normalizeToolSchemas()` in `openai-responses.ts`.

Changes:
- Rename `ensureKimiRootObjectType` → `ensureRootObjectType` (no longer
  Kimi-specific) and update its doc comment.
- Apply it to ALL non-xAI providers in `toolsToChatFormat()`, replacing the
  three-way branch (xAI / Kimi / passthrough) with a two-way branch
  (xAI / normalize for everyone else).
- Remove `isKimiSchemaTarget()` — dead code after the refactor.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

⚠️ Wrong branch ancestry

This pull request targets dev, but its head appears to sit on the current main tip while being far behind dev.

@IMHinnG Rebase onto the current dev branch instead of opening from main. That keeps already-released commits out of the integration branch.

This pull request is being kept as a draft automatically. Once every issue above is resolved, it will be marked ready for review again.

@github-actions github-actions Bot changed the title fix(openai-chat): enforce type:"object" on all tool parameters, not just Kimi [WRONG BRANCH] fix(openai-chat): enforce type:"object" on all tool parameters, not just Kimi Aug 3, 2026
@github-actions
github-actions Bot marked this pull request as draft August 3, 2026 07:00
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The OpenAI chat adapter renames the Kimi-specific root schema normalizer and applies the generalized ensureRootObjectType function to all non-xAI tool schemas. xAI schemas continue using normalizeXaiToolParameters.

Changes

Tool schema normalization

Layer / File(s) Summary
Shared root schema normalization
src/adapters/openai-chat.ts
Lines 411–420 rename ensureKimiRootObjectType to ensureRootObjectType while preserving object-root enforcement and existing schema fields. Lines 471–474 remove the Kimi-only branch and use the shared normalizer for all non-xAI schemas.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: wibias, ingwannu, lidge-jun

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: enforcing a root type of "object" for tool parameters across non-xAI providers.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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/adapters/openai-chat.ts`:
- Around line 411-420: Add regression tests around ensureRootObjectType and the
provider request path covering missing, null, and non-object root types becoming
"object" while preserving description, properties, required, and other fields;
confirm existing object-root schemas remain unchanged and undefined, primitive,
and array parameters use the empty-object fallback. Add xAI oneOf and anyOf
cases verifying normalizeXaiToolParameters still handles them, and validate
representative Kimi, DeepSeek, and xAI payloads.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 075d23b1-0faa-40bd-a16c-b1d57c118b8b

📥 Commits

Reviewing files that changed from the base of the PR and between f9b9440 and a7cb02c.

📒 Files selected for processing (1)
  • src/adapters/openai-chat.ts

Comment thread src/adapters/openai-chat.ts Outdated
Comment on lines +411 to +420
* Several providers (Kimi, DeepSeek) require function.parameters.type to be
* exactly "object" at the root and reject `type:null` or missing type. Codex
* tools with oneOf/anyOf schemas may omit the root type, causing 400 errors.
*
* Safe to apply unconditionally: JSON Schema for function parameters at the
* root MUST be an object — this is a no-op when type is already "object",
* and fixes every non-conforming case. Mirror of normalizeToolSchemas in
* openai-responses.ts.
*/
function ensureKimiRootObjectType(parameters: unknown): Record<string, unknown> {
function ensureRootObjectType(parameters: unknown): Record<string, unknown> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the expanded provider contract.

Line 474 now applies ensureRootObjectType to every non-xAI provider. Add tests for:

  • Missing, null, and non-object root types becoming "object".
  • Preservation of description, properties, required, and other schema fields.
  • Existing { type: "object" } schemas remaining unchanged.
  • undefined, primitive, and array parameters receiving the intended empty-object fallback.
  • xAI oneOf and anyOf schemas continuing through normalizeXaiToolParameters.

Verify representative Kimi, DeepSeek, and xAI payloads before merge.

Also applies to: 474-474

🤖 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/adapters/openai-chat.ts` around lines 411 - 420, Add regression tests
around ensureRootObjectType and the provider request path covering missing,
null, and non-object root types becoming "object" while preserving description,
properties, required, and other fields; confirm existing object-root schemas
remain unchanged and undefined, primitive, and array parameters use the
empty-object fallback. Add xAI oneOf and anyOf cases verifying
normalizeXaiToolParameters still handles them, and validate representative Kimi,
DeepSeek, and xAI payloads.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a7cb02c605

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 472 to +474
const parameters = xaiTarget
? normalizeXaiToolParameters(t.parameters)
: kimiTarget
? ensureKimiRootObjectType(t.parameters)
: t.parameters;
: ensureRootObjectType(t.parameters);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the schema-normalization regression test

This unconditional ensureRootObjectType path now changes the non-xAI example.test tool schema by adding root type: "object", but the committed focused coverage in tests/xai-transport.test.ts still asserts that the same non-xAI schema is preserved exactly. Once dependencies are installed, that existing test will fail unless the expectation is updated or replaced with coverage for the new generic non-xAI normalization behavior.

AGENTS.md reference: src/AGENTS.md:L22-L26

Useful? React with 👍 / 👎.

…oviders

Expand the root-type fix to recursively remove `type:null` (JS null,
JSON-Schema "null" string, and {type:"null"} inside anyOf/oneOf arrays)
at every nesting level.  Codex tools like `codex_app__automation_update`
ship nullable fields such as:
  localEnvironmentConfigPath: { anyOf: [{ type: "string" }, { type: "null" }] }

DeepSeek rejects all forms of `type:null` — at root and nested — while
the original Kimi fix only enforced `type:"object"` at the root.

Changes from v1:
- Add `fixNullTypes()` — recursive walk that replaces standalone
  `type:null`/`type:"null"` with `type:"string"` and filters
  `{type:"null"}` entries out of anyOf/oneOf/allOf arrays.
- Call `fixNullTypes` from `ensureRootObjectType` so every non-xAI
  provider gets both root and deep normalization.
@lidge-jun
lidge-jun changed the base branch from main to dev August 3, 2026 08:14
@lidge-jun

Copy link
Copy Markdown
Owner

Retargeted from main to dev rather than asking you to redo it — dev is the integration branch for every contribution, and the title tag can come off now. GitHub reports it MERGEABLE against dev, so nothing else was needed.

The change itself is worth having: the current normalizeToolSchemas only fixes the root type, so a nullable field expressed as anyOf: [{type:"string"},{type:"null"}] still reaches a strict validator intact. Your recursive version handles the composition arrays, and codex_app__automation_update is a real tool that ships exactly that shape.

Two things I want to look at before merging, and I will do that on a proper review pass rather than holding it in a comment:

  • Dropping isKimiSchemaTarget widens this from a Kimi-specific fix to every openai-chat provider. That is probably right — DeepSeek proves it is not one vendor's quirk — but it needs to be deliberate, and the commit should say so.
  • Rewriting type:"null" inside anyOf changes what the schema means, not just how it is spelled. A field that was genuinely nullable becomes non-nullable to the model. Worth a sentence on why that trade is correct, and a test covering a tool where the null branch matters.

CI is running now. I will review properly once it settles.

@github-actions github-actions Bot changed the title [WRONG BRANCH] fix(openai-chat): enforce type:"object" on all tool parameters, not just Kimi fix(openai-chat): enforce type:"object" on all tool parameters, not just Kimi Aug 3, 2026
@Craybreeding

Copy link
Copy Markdown

Confirmed this still reproduces on opencodex 2.10.0 with Codex Desktop 0.146.0 (macOS, api.deepseek.com via openai-chat adapter):

Provider error 400: {"error":{"message":"Invalid schema for function 'codex_app__automation_update':
schema must be a JSON Schema of 'type: \"object\"', got 'type: null'.","type":"invalid_request_error"}}

Every Codex turn re-sends the full tool catalog, so a single offending tool bricks the whole session against DeepSeek (retries return the same 400, looks like a hang).

I verified locally that normalizing the tool schemas before forwarding to DeepSeek — coercing type: null at the root and recursively — lets DeepSeek accept the full Codex tool list (both deepseek-v4-flash and -v4-pro, with and without tools in the request). The approach in this PR covers that and generalizes it to all strict providers, which is nicer than my provider-specific patch.

+1 for getting this merged — happy to re-test against the branch if useful.

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.

3 participants