Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions open-sse/executors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -127,6 +128,8 @@ const executors = {
v0: new V0VercelWebExecutor(), // Alias
"kimi-web": new KimiWebExecutor(),
kimi: new KimiWebExecutor(), // Alias
"kimi-coding-apikey": new KimiExecutor(), // Alias

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Currently, "kimi-coding-apikey" is registered using new KimiExecutor(), which defaults the internal provider name to "kimi-coding". This means any provider-specific checks or configurations matching "kimi-coding-apikey" will not be correctly resolved within the executor instance.

Additionally, the "kimi-coding" provider itself is not registered to use KimiExecutor, meaning it will not benefit from the reasoning_content fix when thinking mode is active.

To resolve this, pass the correct provider name to the constructor for both "kimi-coding" and "kimi-coding-apikey", and register both to use KimiExecutor.

Suggested change
"kimi-coding-apikey": new KimiExecutor(), // Alias
"kimi-coding": new KimiExecutor("kimi-coding"),
"kimi-coding-apikey": new KimiExecutor("kimi-coding-apikey"),

"kimi-coding": new KimiExecutor(), // Alias
"doubao-web": new DoubaoWebExecutor(),
db: new DoubaoWebExecutor(), // Alias
"qwen-web": new QwenWebExecutor(),
Expand Down
90 changes: 90 additions & 0 deletions open-sse/executors/kimi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@

import { DefaultExecutor } from "./default.ts";
type JsonRecord = Record<string, unknown>;
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;
}
Comment on lines +65 to +72

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In applyKimiRequestDefaults, if Kimi thinking is active and tools are present, the function immediately returns the result of ensureToolCallReasoningContent and completely bypasses applyProviderRequestDefaults. This means any other configured request defaults (such as temperature or max_tokens) will not be applied.

Furthermore, if thinking is enabled via the defaults rather than the initial request body, hasActiveKimiThinking will evaluate to false before the defaults are applied, missing the opportunity to inject reasoning_content.

To fix this, apply the provider request defaults first, and then perform the Kimi-specific check and transformation on the resulting object.

function applyKimiRequestDefaults(body: unknown, defaults?: JsonRecord | null): unknown {
  const withDefaults = applyProviderRequestDefaults(body, defaults);
  const record = asRecord(withDefaults);
  if (record && hasActiveKimiThinking(record) && hasTools(record)) {
    // 此处 kimi 开启了 thinking 功能且存在工具调用,确保 tool_calls 中包含 reasoning_content 字段以兼容老版本 Kimi 后端的要求
    return ensureToolCallReasoningContent(record);
  }
  return withDefaults;
}


export class KimiExecutor extends DefaultExecutor {
constructor(provider = "kimi-coding") {
super(provider);
}
Comment on lines +74 to +77

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

According to the Repository Style Guide (Rule 9), tests must always be included when changing or adding production code in open-sse/. Please add unit tests for KimiExecutor under the tests/unit/ directory (per Rule 1) to verify the request transformation and reasoning_content injection logic.

References
  1. Rule 9: Always include tests when changing production code (src/, open-sse/, electron/, bin/). Rule 1: ALL unit tests, integration tests, ecosystem tests, or Vitest files MUST strictly be placed within the tests/ directory. (link)


transformRequest(
model: string,
body: unknown,
stream: boolean,
credentials: ProviderCredentials
) {
const cleanedBody = super.transformRequest(model, body, stream, credentials);
return applyKimiRequestDefaults(cleanedBody);
}
}

export default KimiExecutor;