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 @@ -11,6 +11,7 @@ Only write entries that are worth mentioning to users.

## Unreleased

- Core: Fix context compaction failing when conversation contains media parts (images, audio, video) — switch from blacklist filtering (exclude `ThinkPart`) to whitelist filtering (only keep `TextPart`) to prevent unsupported content types from being sent to the compaction API
- Web: Fix `@` file mention index not refreshing after switching sessions or when workspace files change — reset index on session switch, auto-refresh after 30s staleness, and support path-prefix search beyond the 500-file limit

## 1.19.0 (2026-03-10)
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 @@ -4,6 +4,7 @@ This page documents the changes in each Kimi Code CLI release.

## Unreleased

- Core: Fix context compaction failing when conversation contains media parts (images, audio, video) — switch from blacklist filtering (exclude `ThinkPart`) to whitelist filtering (only keep `TextPart`) to prevent unsupported content types from being sent to the compaction API
- Web: Fix `@` file mention index not refreshing after switching sessions or when workspace files change — reset index on session switch, auto-refresh after 30s staleness, and support path-prefix search beyond the 500-file limit

## 1.19.0 (2026-03-10)
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 @@ -4,6 +4,7 @@

## 未发布

- Core:修复对话包含媒体内容(图片、音频、视频)时上下文压缩失败的问题——将过滤策略从黑名单(排除 `ThinkPart`)改为白名单(仅保留 `TextPart`),防止不支持的内容类型被发送到压缩 API
- Web:修复 `@` 文件提及索引在切换会话或工作区文件变更后不刷新的问题——切换会话时重置索引,30 秒过期自动刷新,输入路径前缀可查找超出 500 文件上限的文件

## 1.19.0 (2026-03-10)
Expand Down
2 changes: 1 addition & 1 deletion src/kimi_cli/soul/compaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def prepare(
TextPart(text=f"## Message {i + 1}\nRole: {msg.role}\nContent:\n")
)
compact_message.content.extend(
part for part in msg.content if not isinstance(part, ThinkPart)
part for part in msg.content if isinstance(part, TextPart)
)
prompt_text = "\n" + prompts.COMPACT
if custom_instruction:
Expand Down
63 changes: 62 additions & 1 deletion tests/core/test_simple_compaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from inline_snapshot import snapshot
from kosong.chat_provider import TokenUsage
from kosong.message import Message
from kosong.message import AudioURLPart, ImageURLPart, Message, VideoURLPart

import kimi_cli.prompts as prompts
from kimi_cli.soul.compaction import CompactionResult, SimpleCompaction, should_auto_compact
Expand Down Expand Up @@ -213,3 +213,64 @@ def test_custom_ratio_triggers_earlier(self):
def test_zero_tokens_never_triggers(self):
"""Empty context should never trigger compaction."""
assert not should_auto_compact(0, 200_000, trigger_ratio=0.85, reserved_context_size=50_000)


def test_prepare_only_keeps_text_parts_in_compaction():
"""Compaction input should only contain TextPart (whitelist approach).

Non-text parts (media, think, etc.) are filtered out because the compaction
API endpoint only supports text content.

Fixes: https://github.com/MoonshotAI/kimi-cli/issues/1395
Fixes: https://github.com/MoonshotAI/kimi-cli/issues/1390
"""
messages = [
Message(
role="user",
content=[
TextPart(text="Analyze these files:"),
ImageURLPart(image_url=ImageURLPart.ImageURL(url="data:image/png;base64,IMG")),
AudioURLPart(audio_url=AudioURLPart.AudioURL(url="data:audio/mp3;base64,AUD")),
VideoURLPart(video_url=VideoURLPart.VideoURL(url="data:video/mp4;base64,VID")),
ThinkPart(think="internal reasoning"),
],
),
Message(role="assistant", content=[TextPart(text="I can see all the media files.")]),
Message(role="user", content=[TextPart(text="What's your conclusion?")]),
]

result = SimpleCompaction(max_preserved_messages=1).prepare(messages)

assert result.compact_message is not None
# Verify only TextPart remains in the compaction request
for part in result.compact_message.content:
assert isinstance(part, TextPart), (
f"Only TextPart should be in compaction input, got {type(part).__name__}"
)

# Text content should be preserved
texts = [p.text for p in result.compact_message.content if isinstance(p, TextPart)]
assert any("Analyze these files:" in t for t in texts)
assert any("I can see all the media files." in t for t in texts)


def test_prepare_preserves_media_parts_in_recent_messages():
"""Media parts in preserved (recent) messages should remain untouched."""
messages = [
Message(role="user", content=[TextPart(text="Old question")]),
Message(role="assistant", content=[TextPart(text="Old answer")]),
Message(
role="user",
content=[
TextPart(text="Look at this video:"),
VideoURLPart(video_url=VideoURLPart.VideoURL(url="data:video/mp4;base64,VID")),
],
),
Message(role="assistant", content=[TextPart(text="Nice video!")]),
]

result = SimpleCompaction(max_preserved_messages=2).prepare(messages)

# Preserved messages should keep their media parts intact
preserved_user_msg = result.to_preserve[0]
assert any(isinstance(p, VideoURLPart) for p in preserved_user_msg.content)
Loading