From 25ad2fae889ed1422088fd8974a155acfda90d80 Mon Sep 17 00:00:00 2001 From: zlbcodeOnMac <2154167841@qq.com> Date: Mon, 29 Dec 2025 16:57:48 +0800 Subject: [PATCH 1/2] chore(build): fix uv module-name config --- packages/kaos/pyproject.toml | 2 +- packages/kosong/pyproject.toml | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kaos/pyproject.toml b/packages/kaos/pyproject.toml index 6a46f93c..e3d20bbf 100644 --- a/packages/kaos/pyproject.toml +++ b/packages/kaos/pyproject.toml @@ -24,7 +24,7 @@ requires = ["uv_build>=0.8.5,<0.9.0"] build-backend = "uv_build" [tool.uv.build-backend] -module-name = ["kaos"] +module-name = "kaos" source-exclude = ["tests/**/*"] [tool.ruff] diff --git a/packages/kosong/pyproject.toml b/packages/kosong/pyproject.toml index ae53d006..1206075d 100644 --- a/packages/kosong/pyproject.toml +++ b/packages/kosong/pyproject.toml @@ -39,7 +39,7 @@ requires = ["uv_build>=0.8.5,<0.10.0"] build-backend = "uv_build" [tool.uv.build-backend] -module-name = ["kosong"] +module-name = "kosong" [tool.ruff] line-length = 100 diff --git a/pyproject.toml b/pyproject.toml index ba6b09e4..54208901 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ requires = ["uv_build>=0.8.5,<0.10.0"] build-backend = "uv_build" [tool.uv.build-backend] -module-name = ["kimi_cli"] +module-name = "kimi_cli" source-exclude = ["examples/**/*", "tests/**/*", "src/kimi_cli/deps/**/*"] [tool.uv.workspace] From 2fbdcd17607d450e917444ff2b3e7a2747e28877 Mon Sep 17 00:00:00 2001 From: zlbcodeOnMac <2154167841@qq.com> Date: Mon, 29 Dec 2025 16:58:17 +0800 Subject: [PATCH 2/2] feat(slash): allow custom instructions for compact --- src/kimi_cli/soul/compaction.py | 18 +++++++++++++----- src/kimi_cli/soul/kimisoul.py | 8 ++++++-- src/kimi_cli/soul/slash.py | 8 +++++--- tests/test_simple_compaction.py | 23 +++++++++++++++++++++++ 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/kimi_cli/soul/compaction.py b/src/kimi_cli/soul/compaction.py index f25f292d..a65ca075 100644 --- a/src/kimi_cli/soul/compaction.py +++ b/src/kimi_cli/soul/compaction.py @@ -15,13 +15,16 @@ @runtime_checkable class Compaction(Protocol): - async def compact(self, messages: Sequence[Message], llm: LLM) -> Sequence[Message]: + async def compact( + self, messages: Sequence[Message], llm: LLM, instruction: str | None = None + ) -> Sequence[Message]: """ Compact a sequence of messages into a new sequence of messages. Args: messages (Sequence[Message]): The messages to compact. llm (LLM): The LLM to use for compaction. + instruction (str | None): Optional custom summarization instruction. Returns: Sequence[Message]: The compacted messages. @@ -42,8 +45,10 @@ class SimpleCompaction: def __init__(self, max_preserved_messages: int = 2) -> None: self.max_preserved_messages = max_preserved_messages - async def compact(self, messages: Sequence[Message], llm: LLM) -> Sequence[Message]: - compact_message, to_preserve = self.prepare(messages) + async def compact( + self, messages: Sequence[Message], llm: LLM, instruction: str | None = None + ) -> Sequence[Message]: + compact_message, to_preserve = self.prepare(messages, instruction=instruction) if compact_message is None: return to_preserve @@ -78,7 +83,7 @@ class PrepareResult(NamedTuple): compact_message: Message | None to_preserve: Sequence[Message] - def prepare(self, messages: Sequence[Message]) -> PrepareResult: + def prepare(self, messages: Sequence[Message], instruction: str | None = None) -> PrepareResult: if not messages or self.max_preserved_messages <= 0: return self.PrepareResult(compact_message=None, to_preserve=messages) @@ -111,5 +116,8 @@ def prepare(self, messages: Sequence[Message]) -> PrepareResult: compact_message.content.extend( part for part in msg.content if not isinstance(part, ThinkPart) ) - compact_message.content.append(TextPart(text="\n" + prompts.COMPACT)) + prompt_tail = prompts.COMPACT + if instruction: + prompt_tail = f"{prompts.COMPACT}\n\nUser instruction: {instruction}" + compact_message.content.append(TextPart(text="\n" + prompt_tail)) return self.PrepareResult(compact_message=compact_message, to_preserve=to_preserve) diff --git a/src/kimi_cli/soul/kimisoul.py b/src/kimi_cli/soul/kimisoul.py index 5b39a7f9..e464fbdd 100644 --- a/src/kimi_cli/soul/kimisoul.py +++ b/src/kimi_cli/soul/kimisoul.py @@ -360,7 +360,7 @@ async def _grow_context(self, result: StepResult, tool_results: list[ToolResult] await self._context.append_message(tool_messages) # token count of tool results are not available yet - async def compact_context(self) -> None: + async def compact_context(self, instruction: str | None = None) -> None: """ Compact the context. @@ -379,7 +379,11 @@ async def compact_context(self) -> None: async def _compact_with_retry() -> Sequence[Message]: if self._runtime.llm is None: raise LLMNotSet() - return await self._compaction.compact(self._context.history, self._runtime.llm) + return await self._compaction.compact( + self._context.history, + self._runtime.llm, + instruction=instruction, + ) wire_send(CompactionBegin()) compacted_messages = await _compact_with_retry() diff --git a/src/kimi_cli/soul/slash.py b/src/kimi_cli/soul/slash.py index 8454e5b6..cd345f79 100644 --- a/src/kimi_cli/soul/slash.py +++ b/src/kimi_cli/soul/slash.py @@ -51,14 +51,16 @@ async def init(soul: KimiSoul, args: list[str]): @registry.command async def compact(soul: KimiSoul, args: list[str]): - """Compact the context""" + """Compact context; optional hint via `/compact ` (e.g. summarize code only)""" if soul.context.n_checkpoints == 0: wire_send(TextPart(text="The context is empty.")) return + instruction = " ".join(args).strip() if args else "" logger.info("Running `/compact`") - await soul.compact_context() - wire_send(TextPart(text="The context has been compacted.")) + await soul.compact_context(instruction if instruction else None) + suffix = f' with instruction: "{instruction}"' if instruction else "" + wire_send(TextPart(text=f"The context has been compacted{suffix}.")) @registry.command diff --git a/tests/test_simple_compaction.py b/tests/test_simple_compaction.py index 0f8bfa15..5397aa78 100644 --- a/tests/test_simple_compaction.py +++ b/tests/test_simple_compaction.py @@ -73,3 +73,26 @@ def test_prepare_builds_compact_message_and_preserves_tail(): Message(role="assistant", content=[TextPart(text="Latest answer")]), ] ) + + +def test_prepare_injects_custom_instruction(): + messages = [ + Message(role="user", content=[TextPart(text="Old question")]), + Message(role="assistant", content=[TextPart(text="Old answer")]), + Message(role="user", content=[TextPart(text="Latest question")]), + Message(role="assistant", content=[TextPart(text="Latest answer")]), + ] + + instruction = "Summarize code only" + result = SimpleCompaction(max_preserved_messages=1).prepare(messages, instruction=instruction) + + assert result.compact_message is not None + compact_tail = result.compact_message.content[-1] + assert isinstance(compact_tail, TextPart) + assert compact_tail.text == snapshot( + "\n" + + prompts.COMPACT + + """ + +User instruction: Summarize code only""" + )