Skip to content

fix: normalize tool schemas for Kimi/Moonshot#2480

Closed
idling11 wants to merge 3 commits into
Hmbown:mainfrom
idling11:fix/kimi-tool-schema
Closed

fix: normalize tool schemas for Kimi/Moonshot#2480
idling11 wants to merge 3 commits into
Hmbown:mainfrom
idling11:fix/kimi-tool-schema

Conversation

@idling11

@idling11 idling11 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Normalize tool function schemas for Kimi Coding Plan: push type
from the parent object into each anyOf/oneOf item, matching
Kimi's stricter JSON Schema validation.

Closes: #2438

Problem

Kimi's API rejects schemas where type sits alongside anyOf at
the root level. Every request — even "hi" — fails with HTTP 400:

type should be defined in anyOf items instead of the parent schema

Fix

  • schema_sanitize.rs: new sanitize_for_kimi() walks tool schemas
    and rewrites {"type":"object", "anyOf":[...]}{"anyOf":[ {"type":"object",...}, {"type":"object",...}]}
  • chat.rs: auto-applies the normalization for ApiProvider::Moonshot
    when serializing tools before each request

Files Changed

File Change
crates/tui/src/tools/schema_sanitize.rs +86 lines: sanitize_for_kimi + 2 tests
crates/tui/src/client/chat.rs +36/-12: apply for Moonshot provider

Tests

  • kimi_sanitize_pushes_type_into_anyof_items
  • kimi_sanitize_leaves_pure_object_untouched
  • Full suite: 3793 passed, 4 pre-existing failures

Greptile Summary

This PR adds a sanitize_for_kimi() schema normalization pass that rewrites {\"type\": \"object\", \"anyOf\": [...]} into {\"anyOf\": [{\"type\": \"object\", ...}]} so Kimi/Moonshot's stricter JSON Schema validation accepts tool definitions. The fix is applied in both create_message_chat and handle_chat_completion_stream for the Moonshot provider.

  • schema_sanitize.rs gains sanitize_for_kimi(): a recursive walk that strips type from any parent carrying anyOf/oneOf and injects it into typeless child items.
  • chat.rs applies the sanitizer to every tool's parameters field before serializing the request body, in both the blocking and streaming code paths.

Confidence Score: 4/5

Safe to merge for the common case; schemas with deeply nested anyOf items may not be fully normalized due to the top-down traversal order in sanitize_for_kimi().

The sanitizer walks schemas top-down, injecting type into anyOf items and then immediately recursing into those same items. An item that received an injected type but also carries its own nested anyOf will be re-processed by the recursive pass, which removes the freshly-injected type, leaving the item typeless so Kimi would still reject it.

crates/tui/src/tools/schema_sanitize.rs — the traversal order in sanitize_for_kimi() and the completeness of its tests warrant a second look before shipping to users with complex tool schemas.

Important Files Changed

Filename Overview
crates/tui/src/tools/schema_sanitize.rs Adds sanitize_for_kimi() with a top-down walk that injects type into anyOf/oneOf items, but the post-injection recursive pass re-processes the newly mutated items, which can silently undo the injected type on items that themselves carry a nested anyOf. Accompanying tests only exercise the already-typed-item path, leaving the core injection branch uncovered.
crates/tui/src/client/chat.rs Applies Moonshot sanitization in both request methods; logic is correct for the common case, though the identical 12-line block is duplicated verbatim across both code paths.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Tool request (Moonshot provider)"] --> B["tool_to_chat_for_base_url()"]
    B --> C{"ApiProvider::Moonshot?"}
    C -- No --> E["body[tools] = chat_tools"]
    C -- Yes --> D["For each tool:\nget .function.parameters"]
    D --> F["sanitize_for_kimi(parameters)"]
    F --> G{"schema is Object\nwith type + anyOf/oneOf?"}
    G -- No --> H["Recurse into\nall child values"]
    G -- Yes --> I["Remove type from parent\nInject into typeless items"]
    I --> H
    H --> J{"Child is\nObject or Array?"}
    J -- Object --> G
    J -- Array --> K["Recurse into\neach element"]
    K --> G
    J -- Other --> L["Leaf - skip"]
    H --> E
Loading

Reviews (3): Last reviewed commit: "fix: collapse nested ifs to satisfy clip..." | Re-trigger Greptile

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Comment thread crates/tui/src/tools/schema_sanitize.rs
Comment thread crates/tui/src/tools/schema_sanitize.rs
Comment thread crates/tui/src/client/chat.rs
@Hmbown

Hmbown commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Thanks @idling11 — harvested this into main for v0.8.49 in bea701c and 34a87e3, with an additional nested anyOf regression test in dde8654. Closing this PR as integrated and credited in the changelog.

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.

Kimi Coding Plan: tool schema rejected — type should be defined in anyOf items instead of parent

2 participants