fix(kimi-coding): fix kimi tool format: use native Anthropic tool schema instead of OpenAI … (openclaw#40008)

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: opriz <51957849+opriz@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
opriz
2026-03-09 21:28:47 +08:00
committed by GitHub
parent f2f561fab1
commit 51bae75120
6 changed files with 17 additions and 78 deletions

View File

@@ -12,6 +12,7 @@ Docs: https://docs.openclaw.ai
- 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.
- 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.
- 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.
## 2026.3.8

View File

@@ -233,9 +233,6 @@ export function buildKimiCodingProvider(): ProviderConfig {
cost: KIMI_CODING_DEFAULT_COST,
contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW,
maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS,
compat: {
requiresOpenAiAnthropicToolPayload: true,
},
},
],
};

View File

@@ -732,7 +732,7 @@ describe("applyExtraParamsToAgent", () => {
expect(payloads[0]?.thinking).toEqual({ type: "disabled" });
});
it("normalizes kimi-coding anthropic tools to OpenAI function format", () => {
it("does not rewrite tool schema for kimi-coding (native Anthropic format)", () => {
const payloads: Record<string, unknown>[] = [];
const baseStreamFn: StreamFn = (_model, _context, options) => {
const payload: Record<string, unknown> = {
@@ -746,14 +746,6 @@ describe("applyExtraParamsToAgent", () => {
required: ["path"],
},
},
{
type: "function",
function: {
name: "exec",
description: "Run command",
parameters: { type: "object", properties: {} },
},
},
],
tool_choice: { type: "tool", name: "read" },
};
@@ -777,68 +769,16 @@ describe("applyExtraParamsToAgent", () => {
expect(payloads).toHaveLength(1);
expect(payloads[0]?.tools).toEqual([
{
type: "function",
function: {
name: "read",
description: "Read file",
parameters: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"],
},
},
},
{
type: "function",
function: {
name: "exec",
description: "Run command",
parameters: { type: "object", properties: {} },
name: "read",
description: "Read file",
input_schema: {
type: "object",
properties: { path: { type: "string" } },
required: ["path"],
},
},
]);
expect(payloads[0]?.tool_choice).toEqual({
type: "function",
function: { name: "read" },
});
});
it.each([
{ input: { type: "auto" }, expected: "auto" },
{ input: { type: "none" }, expected: "none" },
{ input: { type: "required" }, expected: "required" },
])("normalizes anthropic tool_choice %j for kimi-coding endpoints", ({ input, expected }) => {
const payloads: Record<string, unknown>[] = [];
const baseStreamFn: StreamFn = (_model, _context, options) => {
const payload: Record<string, unknown> = {
tools: [
{
name: "read",
description: "Read file",
input_schema: { type: "object", properties: {} },
},
],
tool_choice: input,
};
options?.onPayload?.(payload, model);
payloads.push(payload);
return {} as ReturnType<StreamFn>;
};
const agent = { streamFn: baseStreamFn };
applyExtraParamsToAgent(agent, undefined, "kimi-coding", "k2p5", undefined, "low");
const model = {
api: "anthropic-messages",
provider: "kimi-coding",
id: "k2p5",
baseUrl: "https://api.kimi.com/coding/",
} as Model<"anthropic-messages">;
const context: Context = { messages: [] };
void agent.streamFn?.(model, context, {});
expect(payloads).toHaveLength(1);
expect(payloads[0]?.tool_choice).toBe(expected);
expect(payloads[0]?.tool_choice).toEqual({ type: "tool", name: "read" });
});
it("does not rewrite anthropic tool schema for non-kimi endpoints", () => {

View File

@@ -31,8 +31,8 @@ describe("resolveProviderCapabilities", () => {
resolveProviderCapabilities("kimi-code"),
);
expect(resolveProviderCapabilities("kimi-code")).toEqual({
anthropicToolSchemaMode: "openai-functions",
anthropicToolChoiceMode: "openai-string-modes",
anthropicToolSchemaMode: "native",
anthropicToolChoiceMode: "native",
providerFamily: "default",
preserveAnthropicThinkingSignatures: false,
openAiCompatTurnValidation: true,
@@ -66,9 +66,9 @@ describe("resolveProviderCapabilities", () => {
expect(resolveTranscriptToolCallIdMode("mistral", "mistral-large-latest")).toBe("strict9");
});
it("treats kimi aliases as anthropic tool payload compatibility providers", () => {
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-coding")).toBe(true);
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-code")).toBe(true);
it("treats kimi aliases as native anthropic tool payload providers", () => {
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-coding")).toBe(false);
expect(requiresOpenAiCompatibleAnthropicToolPayload("kimi-code")).toBe(false);
expect(requiresOpenAiCompatibleAnthropicToolPayload("anthropic")).toBe(false);
});

View File

@@ -33,9 +33,9 @@ const PROVIDER_CAPABILITIES: Record<string, Partial<ProviderCapabilities>> = {
"amazon-bedrock": {
providerFamily: "anthropic",
},
// kimi-coding natively supports Anthropic tool framing (input_schema);
// converting to OpenAI format causes XML text fallback instead of tool_use blocks.
"kimi-coding": {
anthropicToolSchemaMode: "openai-functions",
anthropicToolChoiceMode: "openai-string-modes",
preserveAnthropicThinkingSignatures: false,
},
mistral: {

View File

@@ -198,6 +198,7 @@ export const ModelCompatSchema = z
requiresAssistantAfterToolResult: z.boolean().optional(),
requiresThinkingAsText: z.boolean().optional(),
requiresMistralToolIds: z.boolean().optional(),
requiresOpenAiAnthropicToolPayload: z.boolean().optional(),
})
.strict()
.optional();