fix: keep Kimi anthropic tool payloads native (#60391) (thanks @Eric-Guo)

This commit is contained in:
Ayaan Zaidi 2026-04-04 08:33:38 +05:30
parent 41e16a883b
commit d7b8faa7bf
4 changed files with 28 additions and 30 deletions

View File

@ -118,6 +118,7 @@ Docs: https://docs.openclaw.ai
- Plugins/media understanding: enable bundled Groq and Deepgram providers by default so configured audio transcription models load without extra plugin activation config. (#59982) Thanks @yxjsxy.
- Providers/OpenAI Codex: add forward-compat `openai-codex/gpt-5.4-mini` synthesis across provider runtime, model catalog, and model listing so Codex mini works before bundled Pi catalog updates land.
- Plugins/marketplace: block remote marketplace symlink escapes without rewriting ordinary local marketplace install paths. (#60556) Thanks @eleqtrizit.
- Plugins/Kimi Coding: keep native Anthropic tool payloads on the Kimi coding endpoint while still parsing tagged tool-call text on the response path, so tool calls execute again instead of echoing raw markup. (#60391) Thanks @Eric-Guo.
## 2026.4.2

View File

@ -2,7 +2,7 @@ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { applyKimiCodeConfig, KIMI_CODING_MODEL_REF } from "./onboard.js";
import { buildKimiCodingProvider } from "./provider-catalog.js";
import { wrapKimiProviderStream } from "./stream.js";
import { createKimiToolCallMarkupWrapper } from "./stream.js";
const PLUGIN_ID = "kimi";
const PROVIDER_ID = "kimi";
@ -87,7 +87,7 @@ export default definePluginEntry({
},
},
buildReplayPolicy: () => buildKimiReplayPolicy(),
wrapStreamFn: (ctx) => wrapKimiProviderStream(ctx.streamFn),
wrapStreamFn: (ctx) => createKimiToolCallMarkupWrapper(ctx.streamFn),
});
},
});

View File

@ -1,6 +1,5 @@
import type { StreamFn } from "@mariozechner/pi-agent-core";
import { streamSimple } from "@mariozechner/pi-ai";
import { createOpenAIAnthropicToolPayloadCompatibilityWrapper } from "openclaw/plugin-sdk/provider-stream";
const TOOL_CALLS_SECTION_BEGIN = "<|tool_calls_section_begin|>";
const TOOL_CALLS_SECTION_END = "<|tool_calls_section_end|>";
@ -180,9 +179,3 @@ export function createKimiToolCallMarkupWrapper(baseStreamFn: StreamFn | undefin
return wrapKimiTaggedToolCalls(maybeStream);
};
}
export function wrapKimiProviderStream(baseStreamFn: StreamFn | undefined): StreamFn {
return createKimiToolCallMarkupWrapper(
createOpenAIAnthropicToolPayloadCompatibilityWrapper(baseStreamFn),
);
}

View File

@ -54,10 +54,7 @@ import {
resolveExtraParams,
resolvePreparedExtraParams,
} from "./pi-embedded-runner.js";
import {
createAnthropicToolPayloadCompatibilityWrapper,
createOpenAIAnthropicToolPayloadCompatibilityWrapper,
} from "./pi-embedded-runner/anthropic-family-tool-payload-compat.js";
import { createAnthropicToolPayloadCompatibilityWrapper } from "./pi-embedded-runner/anthropic-family-tool-payload-compat.js";
import {
createBedrockNoCacheWrapper,
isAnthropicBedrockModel,
@ -164,8 +161,14 @@ beforeEach(() => {
params.context.thinkingLevel,
);
}
if (params.provider === "test-anthropic-tool-compat") {
return createAnthropicToolPayloadCompatibilityWrapper(params.context.streamFn, {
toolSchemaMode: "openai-functions",
toolChoiceMode: "openai-string-modes",
});
}
if (params.provider === "kimi") {
return createOpenAIAnthropicToolPayloadCompatibilityWrapper(params.context.streamFn);
return params.context.streamFn;
}
if (params.provider === "minimax" || params.provider === "minimax-portal") {
return createMinimaxFastModeWrapper(
@ -1222,7 +1225,7 @@ describe("applyExtraParamsToAgent", () => {
});
});
it("rewrites anthropic tool payloads for Kimi", () => {
it("keeps anthropic tool payloads native for Kimi", () => {
const payloads: Record<string, unknown>[] = [];
const baseStreamFn: StreamFn = (_model, _context, options) => {
const payload: Record<string, unknown> = {
@ -1259,22 +1262,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"],
},
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" },
});
expect(payloads[0]?.tool_choice).toEqual({ type: "tool", name: "read" });
});
it("does not rewrite anthropic tool schema for non-kimi endpoints", () => {
@ -1377,11 +1374,18 @@ describe("applyExtraParamsToAgent", () => {
};
const agent = { streamFn: baseStreamFn };
applyExtraParamsToAgent(agent, undefined, "kimi", "proxy-model", undefined, "low");
applyExtraParamsToAgent(
agent,
undefined,
"test-anthropic-tool-compat",
"proxy-model",
undefined,
"low",
);
const model = {
api: "anthropic-messages",
provider: "kimi",
provider: "test-anthropic-tool-compat",
id: "proxy-model",
} as Model<"anthropic-messages">;
const context: Context = { messages: [] };