Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-kimi-required-arrays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix Kimi-compatible APIs rejecting tool schemas whose object nodes omit required arrays.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
* `kosong/provider` domain (L2) — Kimi tool-schema dialect normalization.
*
* Pure functions: dereference local `$ref` pointers by inlining definitions,
* then complete missing `type` fields from enum/const values or structural
* keys — the schema dialect the Kimi tool endpoint accepts.
* complete missing `type` fields from enum/const values or structural keys,
* and add missing `required` arrays to object nodes — the schema dialect the
* Kimi tool endpoint accepts.
*
* Circular references are detected and left as `$ref` to avoid infinite
* recursion; in that case the referenced definition bucket is preserved so the
Expand Down Expand Up @@ -227,6 +228,10 @@ function recurseSchema(node: unknown): void {
return;
}

if (node['type'] === 'object' && !hasOwn(node, 'required')) {
node['required'] = [];
}

visitChildSchemas(node, normalizeProperty);
}

Expand Down
38 changes: 38 additions & 0 deletions packages/agent-core-v2/test/kosong/provider/kimi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,44 @@ describe('kimiOpenAITrait.convertTool', () => {
},
});
});

it('adds required arrays to root and nested object schemas', () => {
const tool: Tool = {
name: 'read_file',
description: 'read',
parameters: {
type: 'object',
properties: {
options: {
type: 'object',
properties: { encoding: { type: 'string' } },
},
path: { type: 'string' },
},
required: ['path'],
},
};

expect(convertKimiTool(tool)).toEqual({
type: 'function',
function: {
name: 'read_file',
description: 'read',
parameters: {
type: 'object',
properties: {
options: {
type: 'object',
properties: { encoding: { type: 'string' } },
required: [],
},
path: { type: 'string' },
},
required: ['path'],
},
},
});
});
});

describe('kimiOpenAITrait.convertMessage', () => {
Expand Down
12 changes: 8 additions & 4 deletions packages/kosong/src/providers/kimi-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,16 @@ const NUMERIC_STRUCTURE_KEYS = new Set([
]);

/**
* Return a deep-cloned JSON Schema with missing `type` fields filled in for
* Kimi tool compatibility.
* Return a deep-cloned JSON Schema with missing `type` fields and object
* `required` arrays filled in for Kimi tool compatibility.
*
* Moonshot's tool validator rejects some valid JSON Schema shapes when nested
* property schemas omit `type` (for example enum-only MCP properties). This is
* a provider-compatibility normalizer, not a complete JSON Schema compiler:
* it resolves local refs, preserves combinator nodes, infers obvious
* scalar/object/array types, and falls back to `string` only for nested
* typeless property schemas. The root schema object is treated as a container
* and is not itself normalized.
* typeless property schemas. The root schema is not type-inferred, but an
* explicitly typed root object receives the same `required` normalization.
*/
export function normalizeKimiToolSchema(schema: Record<string, unknown>): Record<string, unknown> {
return ensureKimiPropertyTypes(derefJsonSchema(schema));
Expand Down Expand Up @@ -252,6 +252,10 @@ function recurseSchema(node: unknown): void {
return;
}

if (node['type'] === 'object' && !hasOwn(node, 'required')) {

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 Treat undefined required as missing

When a tool schema is built with an own required: undefined property (for example from a computed optional schema field), this guard treats it as present and skips adding []; the OpenAI client serializes JSON with undefined fields omitted, so the Kimi request still goes out without any required array and hits the same required must be an array rejection. The mirrored agent-core-v2 normalizer has the same guard, so both provider paths need to consider undefined missing while still preserving other invalid values if desired.

Useful? React with 👍 / 👎.

node['required'] = [];
}

visitChildSchemas(node, normalizeProperty);
}

Expand Down
1 change: 1 addition & 0 deletions packages/kosong/test/kimi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ describe('KimiChatProvider', () => {
prefixItems: [{ enum: ['left', 'right'], type: 'string' }],
},
},
required: [],
},
},
},
Expand Down
Loading