Skip to content

Commit d59eb6d

Browse files
oprizTakhoffman
authored andcommitted
fix(kimi-coding): fix kimi tool format: use native Anthropic tool schema instead of OpenAI … (#40008)
Verified: - pnpm install --frozen-lockfile - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: opriz <[email protected]> Co-authored-by: Tak Hoffman <[email protected]>
1 parent 6fac513 commit d59eb6d

File tree

6 files changed

+17
-78
lines changed

6 files changed

+17
-78
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
1212

1313
- macOS/LaunchAgent install: tighten LaunchAgent directory and plist permissions during install so launchd bootstrap does not fail when the target home path or generated plist inherited group/world-writable modes.
1414
- Gateway/Control UI: keep dashboard auth tokens in session-scoped browser storage so same-tab refreshes preserve remote token auth without restoring long-lived localStorage token persistence, while scoping tokens to the selected gateway URL and fragment-only bootstrap flow. (#40892) thanks @velvet-shark.
15+
- Models/Kimi Coding: send `anthropic-messages` tools in native Anthropic format again so `kimi-coding` stops degrading tool calls into XML/plain-text pseudo invocations instead of real `tool_use` blocks. (#38669, #39907, #40552) Thanks @opriz.
1516

1617
## 2026.3.8
1718

src/agents/models-config.providers.static.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,6 @@ export function buildKimiCodingProvider(): ProviderConfig {
233233
cost: KIMI_CODING_DEFAULT_COST,
234234
contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW,
235235
maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS,
236-
compat: {
237-
requiresOpenAiAnthropicToolPayload: true,
238-
},
239236
},
240237
],
241238
};

src/agents/pi-embedded-runner-extraparams.test.ts

Lines changed: 8 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ describe("applyExtraParamsToAgent", () => {
732732
expect(payloads[0]?.thinking).toEqual({ type: "disabled" });
733733
});
734734

735-
it("normalizes kimi-coding anthropic tools to OpenAI function format", () => {
735+
it("does not rewrite tool schema for kimi-coding (native Anthropic format)", () => {
736736
const payloads: Record<string, unknown>[] = [];
737737
const baseStreamFn: StreamFn = (_model, _context, options) => {
738738
const payload: Record<string, unknown> = {
@@ -746,14 +746,6 @@ describe("applyExtraParamsToAgent", () => {
746746
required: ["path"],
747747
},
748748
},
749-
{
750-
type: "function",
751-
function: {
752-
name: "exec",
753-
description: "Run command",
754-
parameters: { type: "object", properties: {} },
755-
},
756-
},
757749
],
758750
tool_choice: { type: "tool", name: "read" },
759751
};
@@ -777,68 +769,16 @@ describe("applyExtraParamsToAgent", () => {
777769
expect(payloads).toHaveLength(1);
778770
expect(payloads[0]?.tools).toEqual([
779771
{
780-
type: "function",
781-
function: {
782-
name: "read",
783-
description: "Read file",
784-
parameters: {
785-
type: "object",
786-
properties: { path: { type: "string" } },
787-
required: ["path"],
788-
},
789-
},
790-
},
791-
{
792-
type: "function",
793-
function: {
794-
name: "exec",
795-
description: "Run command",
796-
parameters: { type: "object", properties: {} },
772+
name: "read",
773+
description: "Read file",
774+
input_schema: {
775+
type: "object",
776+
properties: { path: { type: "string" } },
777+
required: ["path"],
797778
},
798779
},
799780
]);
800-
expect(payloads[0]?.tool_choice).toEqual({
801-
type: "function",
802-
function: { name: "read" },
803-
});
804-
});
805-
806-
it.each([
807-
{ input: { type: "auto" }, expected: "auto" },
808-
{ input: { type: "none" }, expected: "none" },
809-
{ input: { type: "required" }, expected: "required" },
810-
])("normalizes anthropic tool_choice %j for kimi-coding endpoints", ({ input, expected }) => {
811-
const payloads: Record<string, unknown>[] = [];
812-
const baseStreamFn: StreamFn = (_model, _context, options) => {
813-
const payload: Record<string, unknown> = {
814-
tools: [
815-
{
816-
name: "read",
817-
description: "Read file",
818-
input_schema: { type: "object", properties: {} },
819-
},
820-
],
821-
tool_choice: input,
822-
};
823-
options?.onPayload?.(payload, model);
824-
payloads.push(payload);
825-
return {} as ReturnType<StreamFn>;
826-
};
827-
const agent = { streamFn: baseStreamFn };
828-
829-
applyExtraParamsToAgent(agent, undefined, "kimi-coding", "k2p5", undefined, "low");
830-
831-
const model = {
832-
api: "anthropic-messages",
833-
provider: "kimi-coding",
834-
id: "k2p5",
835-
baseUrl: "https://api.kimi.com/coding/",
836-
} as Model<"anthropic-messages">;
837-
const context: Context = { messages: [] };
838-
void agent.streamFn?.(model, context, {});
839-
840-
expect(payloads).toHaveLength(1);
841-
expect(payloads[0]?.tool_choice).toBe(expected);
781+
expect(payloads[0]?.tool_choice).toEqual({ type: "tool", name: "read" });
842782
});
843783

844784
it("does not rewrite anthropic tool schema for non-kimi endpoints", () => {

src/agents/provider-capabilities.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ describe("resolveProviderCapabilities", () => {
3131
resolveProviderCapabilities("kimi-code"),
3232
);
3333
expect(resolveProviderCapabilities("kimi-code")).toEqual({
34-
anthropicToolSchemaMode: "openai-functions",
35-
anthropicToolChoiceMode: "openai-string-modes",
34+
anthropicToolSchemaMode: "native",
35+
anthropicToolChoiceMode: "native",
3636
providerFamily: "default",
3737
preserveAnthropicThinkingSignatures: false,
3838
openAiCompatTurnValidation: true,
@@ -66,9 +66,9 @@ describe("resolveProviderCapabilities", () => {
6666
expect(resolveTranscriptToolCallIdMode("mistral", "mistral-large-latest")).toBe("strict9");
6767
});
6868

69-
it("treats kimi aliases as anthropic tool payload compatibility providers", () => {
70-
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-coding")).toBe(true);
71-
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-code")).toBe(true);
69+
it("treats kimi aliases as native anthropic tool payload providers", () => {
70+
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-coding")).toBe(false);
71+
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-code")).toBe(false);
7272
expect(requiresOpenAiCompatibleAnthropicToolPayload("anthropic")).toBe(false);
7373
});
7474

src/agents/provider-capabilities.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ const PROVIDER_CAPABILITIES: Record<string, Partial<ProviderCapabilities>> = {
3333
"amazon-bedrock": {
3434
providerFamily: "anthropic",
3535
},
36+
// kimi-coding natively supports Anthropic tool framing (input_schema);
37+
// converting to OpenAI format causes XML text fallback instead of tool_use blocks.
3638
"kimi-coding": {
37-
anthropicToolSchemaMode: "openai-functions",
38-
anthropicToolChoiceMode: "openai-string-modes",
3939
preserveAnthropicThinkingSignatures: false,
4040
},
4141
mistral: {

src/config/zod-schema.core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ export const ModelCompatSchema = z
198198
requiresAssistantAfterToolResult: z.boolean().optional(),
199199
requiresThinkingAsText: z.boolean().optional(),
200200
requiresMistralToolIds: z.boolean().optional(),
201+
requiresOpenAiAnthropicToolPayload: z.boolean().optional(),
201202
})
202203
.strict()
203204
.optional();

0 commit comments

Comments
 (0)