core/cli: update display and state for streaming, error, cancel events

This commit is contained in:
2026-07-14 22:11:30 +08:00
parent 42b50716f4
commit ed028dc00b
3 changed files with 15 additions and 8 deletions
+8 -2
View File
@@ -5,6 +5,8 @@ from typing import TYPE_CHECKING
import click import click
from plyngent.agent import ( from plyngent.agent import (
CancelledEvent,
ErrorEvent,
MaxRoundsEvent, MaxRoundsEvent,
TextDeltaEvent, TextDeltaEvent,
ToolCallEvent, ToolCallEvent,
@@ -20,7 +22,7 @@ if TYPE_CHECKING:
_TOOL_RESULT_PREVIEW = 200 _TOOL_RESULT_PREVIEW = 200
async def render_events(events: AsyncIterator[AgentEvent]) -> None: async def render_events(events: AsyncIterator[AgentEvent]) -> None: # noqa: C901, PLR0912
"""Print agent events to the terminal.""" """Print agent events to the terminal."""
printed_text = False printed_text = False
async for event in events: async for event in events:
@@ -47,6 +49,10 @@ async def render_events(events: AsyncIterator[AgentEvent]) -> None:
else content[:_TOOL_RESULT_PREVIEW] + "" else content[:_TOOL_RESULT_PREVIEW] + ""
) )
click.secho(f"[tool result] {preview}", fg="magenta") click.secho(f"[tool result] {preview}", fg="magenta")
elif isinstance(event, ErrorEvent):
click.secho(f"\n[error] {event.message}", fg="bright_red")
elif isinstance(event, CancelledEvent):
click.secho("\n[cancelled]", fg="yellow")
elif isinstance(event, MaxRoundsEvent): elif isinstance(event, MaxRoundsEvent):
if event.continued: if event.continued:
click.secho( click.secho(
@@ -60,5 +66,5 @@ async def render_events(events: AsyncIterator[AgentEvent]) -> None:
_ = event _ = event
if printed_text: if printed_text:
click.echo() click.echo()
# Blank line before the next readline prompt so log and input are not jammed.
click.echo() click.echo()
+3 -2
View File
@@ -34,7 +34,7 @@ class ReplState:
def __post_init__(self) -> None: def __post_init__(self) -> None:
# DeepSeek client uses a compatible but distinct param type; treat as ChatClient. # DeepSeek client uses a compatible but distinct param type; treat as ChatClient.
self.client = cast("ChatClient", create_client(self.provider)) self.client = cast("ChatClient", cast("object", create_client(self.provider)))
self.agent = self._make_agent() self.agent = self._make_agent()
def _tool_registry(self) -> ToolRegistry | None: def _tool_registry(self) -> ToolRegistry | None:
@@ -53,12 +53,13 @@ class ReplState:
session_id=self.session_id, session_id=self.session_id,
max_rounds=self.max_rounds, max_rounds=self.max_rounds,
on_limit=prompt_continue_limit, on_limit=prompt_continue_limit,
stream=True,
) )
def rebuild_client(self) -> None: def rebuild_client(self) -> None:
"""Recreate client and agent after provider/model/tools change.""" """Recreate client and agent after provider/model/tools change."""
messages = list(self.agent.messages) messages = list(self.agent.messages)
self.client = cast("ChatClient", create_client(self.provider)) self.client = cast("ChatClient", cast("object", create_client(self.provider)))
self.agent = self._make_agent() self.agent = self._make_agent()
self.agent.messages = messages self.agent.messages = messages
+4 -4
View File
@@ -96,10 +96,10 @@ async def test_run_chat_loop_text_only() -> None:
] ]
) )
messages: list[AnyChatMessage] = [UserChatMessage(content="hi")] messages: list[AnyChatMessage] = [UserChatMessage(content="hi")]
events = [e async for e in run_chat_loop(client, messages, model="m")] events = [e async for e in run_chat_loop(client, messages, model="m", stream=False)]
assert isinstance(events[0], AssistantMessageEvent) assert isinstance(events[0], TextDeltaEvent)
assert isinstance(events[1], TextDeltaEvent) assert events[0].content == "hello"
assert events[1].content == "hello" assert isinstance(events[1], AssistantMessageEvent)
assert len(messages) == 2 # noqa: PLR2004 assert len(messages) == 2 # noqa: PLR2004
assert len(client.calls) == 1 assert len(client.calls) == 1