cli/state: cancel a compact's summary instead of hanging on it

This commit is contained in:
2026-11-02 18:17:08 +08:00
parent 81d443cf40
commit 0a700ec8b1
4 changed files with 96 additions and 12 deletions
+70
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import asyncio
from typing import TYPE_CHECKING, Literal, overload
import pytest
@@ -57,6 +58,31 @@ class SummaryClient:
)
class HangingClient:
"""A summarizer that never answers until the request is cancelled."""
def __init__(self) -> None:
self.entered = asyncio.Event()
@overload
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: Literal[False] = False
) -> ChatCompletionResponse: ...
@overload
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: Literal[True]
) -> AsyncIterator[ChatCompletionChunk]: ...
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: bool = False
) -> ChatCompletionResponse | AsyncIterator[ChatCompletionChunk]:
del param, stream
self.entered.set()
await asyncio.Event().wait() # until the caller cancels
raise AssertionError("the summary request should have been cancelled")
async def test_compact_to_new_session(tmp_path: Path) -> None:
memory = await MemoryStore.open(DatabaseConfig())
try:
@@ -128,6 +154,50 @@ async def test_compact_to_new_session(tmp_path: Path) -> None:
await memory.close()
async def test_compact_cancel_leaves_the_session_untouched(tmp_path: Path) -> None:
"""Ctrl+C during the summary drops the request before anything is created."""
memory = await MemoryStore.open(DatabaseConfig())
try:
provider = OpenAIProvider(access_key_or_token="sk-test")
config = ConfigStore(path=tmp_path / "plyngent.toml", document=tomlkit.document())
config.providers = {"local": provider}
state = ReplState(
config=config,
memory=memory,
workspace=tmp_path,
provider_name="local",
provider=provider,
model="gpt-test",
tools_enabled=False,
)
await state.new_session("orig")
old_id = state.session_id
assert old_id is not None
client = HangingClient()
state.client = client
state.agent = ChatAgent(
state.client,
model=state.model,
memory=state.memory,
session_id=old_id,
system_prompt="Be brief.",
)
state.agent.messages = [UserChatMessage(content="do work")]
before = {row.sid for row in await memory.list_sessions(workspace=tmp_path)}
task = asyncio.ensure_future(state.compact_to_new_session())
await asyncio.wait_for(client.entered.wait(), timeout=5)
_ = task.cancel()
with pytest.raises(ValueError, match="compaction cancelled"):
await task
assert state.session_id == old_id
after = {row.sid for row in await memory.list_sessions(workspace=tmp_path)}
assert after == before
finally:
await memory.close()
async def test_rebuild_client_preserves_persist_cursor(tmp_path: Path) -> None:
memory = await MemoryStore.open(DatabaseConfig())
try: