mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user