From 74ce4fd76de622ec9bffb79dae5fba9bb8e42b44 Mon Sep 17 00:00:00 2001 From: ipanghu Date: Thu, 4 Jun 2026 10:50:53 +0800 Subject: [PATCH] =?UTF-8?q?Fix=EF=BC=9AAdd=20KimiExecutor=20to=20fix=20=20?= =?UTF-8?q?the=20error=20of=20reasoning=5Fcontent=20is=20missing=20in=20ki?= =?UTF-8?q?mi=20thing=20mode=20(#3132)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix the error of reasoning_content is missing in kimi thing mode * fix: kimi always use default * fix: add kimi-coding to use KimiExecutor --- open-sse/executors/index.ts | 3 ++ open-sse/executors/kimi.ts | 90 +++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 open-sse/executors/kimi.ts diff --git a/open-sse/executors/index.ts b/open-sse/executors/index.ts index d46c7f0d13..f892125e5b 100644 --- a/open-sse/executors/index.ts +++ b/open-sse/executors/index.ts @@ -46,6 +46,7 @@ import { V0VercelWebExecutor } from "./v0-vercel-web.ts"; import { KimiWebExecutor } from "./kimi-web.ts"; import { DoubaoWebExecutor } from "./doubao-web.ts"; import { QwenWebExecutor } from "./qwen-web.ts"; +import { KimiExecutor } from "./kimi.ts" const executors = { antigravity: new AntigravityExecutor(), @@ -127,6 +128,8 @@ const executors = { v0: new V0VercelWebExecutor(), // Alias "kimi-web": new KimiWebExecutor(), kimi: new KimiWebExecutor(), // Alias + "kimi-coding-apikey": new KimiExecutor(), // Alias + "kimi-coding": new KimiExecutor(), // Alias "doubao-web": new DoubaoWebExecutor(), db: new DoubaoWebExecutor(), // Alias "qwen-web": new QwenWebExecutor(), diff --git a/open-sse/executors/kimi.ts b/open-sse/executors/kimi.ts new file mode 100644 index 0000000000..55a56268e9 --- /dev/null +++ b/open-sse/executors/kimi.ts @@ -0,0 +1,90 @@ + +import { DefaultExecutor } from "./default.ts"; +type JsonRecord = Record; +import { + type ProviderCredentials, +} from "./base.ts"; + +import { applyProviderRequestDefaults } from "../services/providerRequestDefaults.ts"; + +function hasActiveKimiThinking(body: JsonRecord): boolean { + const reasoningEffort = body.reasoning_effort; + if (typeof reasoningEffort === "string") { + const normalized = reasoningEffort.trim().toLowerCase(); + if (normalized && normalized !== "off" && normalized !== "none") return true; + } + + const reasoning = body.reasoning; + if (reasoning && typeof reasoning === "object" && !Array.isArray(reasoning)) { + const reasoningRecord = reasoning as JsonRecord; + const effort = reasoningRecord.effort; + if (typeof effort === "string") { + const normalized = effort.trim().toLowerCase(); + if (normalized && normalized !== "off" && normalized !== "none") return true; + } + if (reasoningRecord.enabled === true || reasoningRecord.type === "enabled") return true; + } + + const thinking = body.thinking; + if (thinking && typeof thinking === "object" && !Array.isArray(thinking)) { + const thinkingRecord = thinking as JsonRecord; + return thinkingRecord.type === "enabled" || thinkingRecord.type === "adaptive"; + } + + return false; +} + +function ensureToolCallReasoningContent(body: JsonRecord): JsonRecord { + if (!hasActiveKimiThinking(body) || !Array.isArray(body.messages)) return body; + + let changed = false; + const messages = body.messages.map((message: unknown) => { + if (!message || typeof message !== "object" || Array.isArray(message)) return message; + + const msg = message as JsonRecord; + if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls)) return message; + if (Object.prototype.hasOwnProperty.call(msg, "reasoning_content")) return message; + + changed = true; + return { ...msg, reasoning_content: "" }; + }); + + return changed ? { ...body, messages } : body; +} + +function hasTools(body: unknown): boolean { + const record = asRecord(body); + return Array.isArray(record?.tools) && record.tools.length > 0; +} + + +function asRecord(value: unknown): JsonRecord | null { + return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : null; +} + +function applyKimiRequestDefaults(body: unknown, defaults?: JsonRecord | null): unknown { + const withDefaults = applyProviderRequestDefaults(body, defaults); + const record = asRecord(withDefaults); + if (record && hasActiveKimiThinking(record) && hasTools(record)) { + return ensureToolCallReasoningContent(record); + } + return withDefaults; +} + +export class KimiExecutor extends DefaultExecutor { + constructor(provider = "kimi-coding") { + super(provider); + } + + transformRequest( + model: string, + body: unknown, + stream: boolean, + credentials: ProviderCredentials + ) { + const cleanedBody = super.transformRequest(model, body, stream, credentials); + return applyKimiRequestDefaults(cleanedBody); + } +} + +export default KimiExecutor;