Skip to content

fix(relay): sanitize null JSON Schema required values in tool definitions across protocols #479

Description

@LIghtJUNction

Upstream source

Problem

JSON Schema requires required to be an array of property names. Some clients emit "required": null in function/tool schemas. Strict upstreams reject the whole request; upstream #7531 reports this with xAI, Moonshot and Xiaomi MiMo.

The intended compatibility repair is to drop a null required member (not replace it with []), including nested schema nodes, while preserving valid required arrays.

api.lmm.best gap

Current main does not yet have upstream's unified relayconvert/internal/toolconv.ExtractRequest funnel, so #7531 cannot be safely cherry-picked.

Relevant current shapes are split by protocol:

  • OpenAI Chat: dto.GeneralOpenAIRequest.Tools []ToolCallRequest; FunctionRequest.Parameters is any.
  • Claude Messages: dto.ClaudeRequest.Tools is any; tool input_schema can retain required: null.
  • OpenAI Responses: tool payload is retained as raw/dynamic JSON in the request DTO/conversion path.
  • Gemini has its own function-declaration conversion path.

A one-protocol patch would leave equivalent failures in the other routes. Sanitizing arbitrary request JSON globally would also be too broad and could mutate non-tool payloads or explicit pass-through behavior.

Recommended LMM-native implementation

Add one protocol-neutral tool-schema sanitizer at the relay conversion/request-normalization boundary that is invoked for decoded OpenAI Chat, OpenAI Responses, Claude Messages and Gemini requests before an upstream-compatible request is emitted.

Rules:

  1. Only sanitize function/tool JSON Schema payloads (parameters, input_schema, Gemini function declaration parameters, Responses function parameters), not arbitrary request objects.
  2. Recursively remove a key only when the key is exactly required and its value is JSON null.
  3. Preserve valid arrays, empty arrays, strings/numbers/booleans, properties, items, $defs/definitions, combinators and unknown schema keywords unchanged.
  4. Bound recursion/depth (upstream uses 64) so malformed/hostile schemas cannot cause unbounded stack growth.
  5. Do not invent required: []; absence is the compatibility representation.
  6. Preserve explicit raw pass-through semantics: if the channel/request is configured for byte-for-byte pass-through, do not silently rewrite its body unless we make that behavior an explicit compatibility option.
  7. Keep pricing, groups, model price lock, /fast, OAuth restrictions, provider selection and admin AI assistant behavior untouched.

Acceptance criteria

  • Claude /v1/messages tool with top-level input_schema.required: null is forwarded without required and no longer fails strict OpenAI-compatible upstreams.
  • OpenAI Chat function tool with function.parameters.required: null is sanitized.
  • OpenAI Responses function tool with parameters.required: null is sanitized.
  • Gemini function declaration with parameters.required: null is sanitized when converted/forwarded.
  • Nested null required values under objects/arrays/combinators are removed.
  • A valid required: ["path"] and required: [] remain byte-equivalent semantically.
  • Non-tool JSON containing a field named required: null is not rewritten.
  • Tests cover recursion depth and malformed/non-schema values.
  • Existing relaykit conversion tests and Go CI pass.

Why issue first instead of direct port

Upstream #7531 is newly opened and its CI workflow currently requires action rather than being fully green. More importantly, LMM's current relaykit predates the upstream unified toolconv extraction layer, so a direct port would either miss routes or broaden rewriting too far. This should be implemented against LMM's current normalization boundary as one small cross-protocol compatibility patch rather than copied mechanically.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions