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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Only write entries that are worth mentioning to users.
- Core: Add `skip_yolo_prompt_injection` config option to suppress the system reminder normally injected when yolo mode is active — useful when building custom applications on top of `KimiSoul` that do not need the non-interactive mode hint
- Kimi: Add `KIMI_MODEL_THINKING_KEEP` environment variable that forwards its value verbatim to the Moonshot API as `thinking.keep`, enabling Preserved Thinking (e.g. `export KIMI_MODEL_THINKING_KEEP=all` to retain historical `reasoning_content` across turns); effective only for Moonshot models supporting Preserved Thinking (e.g. `kimi-k2.6` / `kimi-k2-thinking`), unset or empty string preserves the previous behavior and omits the field, and the override only applies when the current model is actually in thinking mode so the API never receives a `thinking.keep` without the companion `thinking.type`. Note that `keep=all` increases input tokens and API cost because history reasoning is resent
- Kosong: Fix `Kimi.with_extra_body` silently dropping previously set `thinking.type` when a later call added another `thinking.*` field — the `thinking` sub-dict is now merged field-by-field instead of shallow-replaced, so composing `with_thinking(...)` with `with_extra_body({"thinking": {...}})` preserves both contributions
- Kosong: Fix Kimi provider sending empty `content` alongside `tool_calls`, which caused 400 "text content is empty" errors from the Moonshot API. When an assistant message has tool calls and its visible content is effectively empty (no text or only whitespace/think parts), the `content` field is now omitted entirely
- Shell: Fix approval request feedback text cursor rendering — the block cursor now correctly renders at the actual cursor position instead of always being pinned to the end of the line; when the cursor is in the middle of the text, the character under the cursor is drawn with reverse video (mimicking a terminal's native block cursor)
- Kosong: Fix Moonshot 400 `At path 'properties.X': type is not defined` when an MCP server exposes tools whose parameter schemas have enum-only or otherwise type-less properties (seen with the JetBrains Rider MCP's `truncateMode`) — the Kimi provider now patches each tool's schema in-flight to fill in a JSON Schema `type` (inferred from `enum`/`const` values when possible, else defaulted to `"string"`), so the whole session no longer fails every request with a schema validation error; OpenAI and Anthropic paths are unaffected

Expand Down
1 change: 1 addition & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This page documents the changes in each Kimi Code CLI release.
- Core: Add `skip_yolo_prompt_injection` config option to suppress the system reminder normally injected when yolo mode is active — useful when building custom applications on top of `KimiSoul` that do not need the non-interactive mode hint
- Kimi: Add `KIMI_MODEL_THINKING_KEEP` environment variable that forwards its value verbatim to the Moonshot API as `thinking.keep`, enabling Preserved Thinking (e.g. `export KIMI_MODEL_THINKING_KEEP=all` to retain historical `reasoning_content` across turns); effective only for Moonshot models supporting Preserved Thinking (e.g. `kimi-k2.6` / `kimi-k2-thinking`), unset or empty string preserves the previous behavior and omits the field, and the override only applies when the current model is actually in thinking mode so the API never receives a `thinking.keep` without the companion `thinking.type`. Note that `keep=all` increases input tokens and API cost because history reasoning is resent
- Kosong: Fix `Kimi.with_extra_body` silently dropping previously set `thinking.type` when a later call added another `thinking.*` field — the `thinking` sub-dict is now merged field-by-field instead of shallow-replaced, so composing `with_thinking(...)` with `with_extra_body({"thinking": {...}})` preserves both contributions
- Kosong: Fix Kimi provider sending empty `content` alongside `tool_calls`, which caused 400 "text content is empty" errors from the Moonshot API. When an assistant message has tool calls and its visible content is effectively empty (no text or only whitespace/think parts), the `content` field is now omitted entirely
- Shell: Fix approval request feedback text cursor rendering — the block cursor now correctly renders at the actual cursor position instead of always being pinned to the end of the line; when the cursor is in the middle of the text, the character under the cursor is drawn with reverse video (mimicking a terminal's native block cursor)
- Kosong: Fix Moonshot 400 `At path 'properties.X': type is not defined` when an MCP server exposes tools whose parameter schemas have enum-only or otherwise type-less properties (seen with the JetBrains Rider MCP's `truncateMode`) — the Kimi provider now patches each tool's schema in-flight to fill in a JSON Schema `type` (inferred from `enum`/`const` values when possible, else defaulted to `"string"`), so the whole session no longer fails every request with a schema validation error; OpenAI and Anthropic paths are unaffected

Expand Down
1 change: 1 addition & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Core:新增 `skip_yolo_prompt_injection` 配置项,用于抑制 yolo 模式下注入的系统提示词——基于 `KimiSoul` 构建自定义应用且不需要该提示时很有用
- Kimi:新增环境变量 `KIMI_MODEL_THINKING_KEEP`,将其值原样作为 `thinking.keep` 字段发送给 Moonshot API,用于启用 Preserved Thinking(例如 `export KIMI_MODEL_THINKING_KEEP=all` 可让模型在多轮之间保留历史 `reasoning_content`);仅对支持 Preserved Thinking 的 Moonshot 模型(如 `kimi-k2.6` / `kimi-k2-thinking`)生效,未设置或空字符串时请求体不携带该字段、等同当前默认行为,且仅在当前模型真正处于 Thinking 模式时才注入,以避免 API 收到只有 `thinking.keep` 而缺少 `thinking.type` 的无效请求体。注意 `keep=all` 会因为重新发送历史推理内容而显著增加输入 token 与 API 费用
- Kosong:修复 `Kimi.with_extra_body` 在后续调用新增其它 `thinking.*` 字段时静默丢掉已有 `thinking.type` 的问题——`thinking` 子对象现在按字段合并,而不是被整体浅覆盖,使得 `with_thinking(...)` 与 `with_extra_body({"thinking": {...}})` 组合使用时两次设置的字段都能保留
- Kosong:修复 Kimi provider 在 `tool_calls` 旁发送空 `content` 导致 Moonshot API 返回 400 "text content is empty" 错误的问题。当 Assistant 消息带有工具调用且可见内容实际为空(无文本或仅包含空白 / think 部分)时,现在会完全省略 `content` 字段
- Shell:修复审批请求反馈文本输入的光标渲染问题——光标块现在根据实际光标位置正确渲染,不再始终固定在行尾;当光标位于文本中间时,光标所在字符会以反色显示(模拟终端原生块光标效果)
- Kosong:修复接入某些 MCP 服务端(如 JetBrains Rider MCP 的 `truncateMode`)时,Moonshot API 以 `400 At path 'properties.X': type is not defined` 拒绝每次请求导致会话完全无法使用的问题——这些 MCP 工具的参数 schema 里有仅声明 `enum`/`const` 或根本没有类型提示的属性,符合 JSON Schema 规范但过不了 Moonshot 的严格校验;现在 Kimi 供应商会在发送前为每个工具 schema 补齐 JSON Schema `type`(尽量从 `enum`/`const` 值推断,否则默认 `"string"`),OpenAI 和 Anthropic 路径不受影响

Expand Down
1 change: 1 addition & 0 deletions packages/kosong/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Kimi: Add `keep` to `ThinkingConfig` (Moonshot `thinking.keep` passthrough for Preserved Thinking); value is typed as `Any` and forwarded unchanged, with case-preservation and no validation — callers choose a value the server accepts (e.g. `"all"`)
- Kimi: Fix `with_extra_body` silently dropping earlier `thinking.*` fields on subsequent calls — the `thinking` sub-dict is now merged field-by-field so composing `with_thinking(...)` with `with_extra_body({"thinking": {...}})` preserves both contributions; other top-level keys retain last-writer-wins semantics
- Kimi: Normalize MCP tool parameter schemas before sending to Moonshot — properties that declare `enum`/`const` but no `type` (or have no type hint at all) get an inferred `type` filled in, so MCP servers whose schemas are valid JSON Schema but not strict enough for Moonshot's validator (notably the JetBrains Rider MCP, whose `truncateMode` property is enum-only) no longer make every request fail with `400 At path 'properties.X': type is not defined`; the normalization is Kimi-specific and leaves OpenAI/Anthropic conversions untouched
- Kimi: Fix sending empty `content` alongside `tool_calls`, which caused 400 "text content is empty" errors from the Moonshot API. When an assistant message has tool calls and its visible content is effectively empty (no text or only whitespace/think parts), the `content` field is now omitted entirely

## 0.51.0 (2026-04-22)

Expand Down
21 changes: 21 additions & 0 deletions packages/kosong/src/kosong/chat_provider/kimi.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,32 @@ def _convert_message(message: Message) -> ChatCompletionMessageParam:
content.append(part)
message.content = content
dumped_message = message.model_dump(exclude_none=True)
if (
message.role == "assistant"
and message.tool_calls
and _is_effectively_empty_content_parts(content)
):
# OpenAI-compatible APIs allow assistant tool-call messages to omit
# `content`, but the Kimi-for-Coding compat layer rejects a content
# list that contains an empty text part (observed: `content:
# [{"type": "text", "text": ""}]` -> 400 "text content is empty").
# Dropping `content` entirely is always accepted, so do that whenever
# the visible content is effectively empty alongside a tool call.
dumped_message.pop("content", None)
if reasoning_content:
dumped_message["reasoning_content"] = reasoning_content
return cast(ChatCompletionMessageParam, dumped_message)


def _is_effectively_empty_content_parts(content: Sequence[ContentPart]) -> bool:
for part in content:
if not isinstance(part, TextPart):
return False
if part.text.strip():
return False
return True


def _convert_tool(tool: Tool) -> ChatCompletionToolParam:
if tool.name.startswith("$"):
# Kimi builtin functions start with `$`
Expand Down
69 changes: 68 additions & 1 deletion packages/kosong/tests/api_snapshot_tests/test_kimi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from inline_snapshot import snapshot

from kosong.chat_provider.kimi import Kimi
from kosong.message import Message, TextPart, ThinkPart
from kosong.message import Message, TextPart, ThinkPart, ToolCall
from kosong.tooling import Tool

BUILTIN_TOOL = Tool(
Expand Down Expand Up @@ -36,6 +36,38 @@
Message(role="user", content="Thanks!"),
],
},
"assistant_tool_call_without_text": {
"history": [
Message(role="user", content="Call the add tool"),
Message(
role="assistant",
content=[],
tool_calls=[
ToolCall(
id="call_abc123",
function=ToolCall.FunctionBody(name="add", arguments='{"a": 2, "b": 3}'),
)
],
),
Message(role="tool", content="5", tool_call_id="call_abc123"),
],
},
"assistant_tool_call_with_reasoning_only": {
"history": [
Message(role="user", content="Think and call the add tool"),
Message(
role="assistant",
content=[ThinkPart(think="I should call the tool.")],
tool_calls=[
ToolCall(
id="call_abc123",
function=ToolCall.FunctionBody(name="add", arguments='{"a": 2, "b": 3}'),
)
],
),
Message(role="tool", content="5", tool_call_id="call_abc123"),
Comment on lines +39 to +68

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

The snapshot coverage added here exercises content=[] and content=[ThinkPart(...)], but the reported failure mode is an assistant tool-call message stored as a text part with empty/whitespace text (e.g. content=[TextPart(text="")], which serializes to content: "" or [{"type":"text","text":""}]). Consider adding an explicit snapshot case for empty/whitespace-only TextPart alongside tool_calls to ensure _is_effectively_empty_content_parts is actually covered for the original trigger.

Copilot uses AI. Check for mistakes.
],
},
}


Expand Down Expand Up @@ -285,6 +317,41 @@ async def test_kimi_message_conversion():
],
"tools": [],
},
"assistant_tool_call_without_text": {
"messages": [
{"role": "user", "content": "Call the add tool"},
{
"role": "assistant",
"tool_calls": [
{
"type": "function",
"id": "call_abc123",
"function": {"name": "add", "arguments": '{"a": 2, "b": 3}'},
}
],
},
{"role": "tool", "content": "5", "tool_call_id": "call_abc123"},
],
"tools": [],
},
"assistant_tool_call_with_reasoning_only": {
"messages": [
{"role": "user", "content": "Think and call the add tool"},
{
"role": "assistant",
"reasoning_content": "I should call the tool.",
"tool_calls": [
{
"type": "function",
"id": "call_abc123",
"function": {"name": "add", "arguments": '{"a": 2, "b": 3}'},
}
],
},
{"role": "tool", "content": "5", "tool_call_id": "call_abc123"},
],
"tools": [],
},
}
)

Expand Down
Loading
Loading