mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 01:35:28 -04:00
fix(cli): suppress token placeholder on first invoke (#3302)
The status bar footer was showing "... tokens" immediately on the first LLM invocation, before any real token count had been established. This created visual noise during the initial streaming period when no context tokens exist yet.
This commit is contained in:
@@ -384,6 +384,8 @@ class StatusBar(Horizontal):
|
||||
(The actual context is larger because the generation was interrupted before
|
||||
the model reported final usage.)
|
||||
"""
|
||||
_has_token_count: bool = False
|
||||
"""Whether the status bar has displayed a real token count this session."""
|
||||
|
||||
def watch_tokens(self, new_value: int) -> None:
|
||||
"""Update token display when count changes."""
|
||||
@@ -425,6 +427,7 @@ class StatusBar(Horizontal):
|
||||
approximate: Append "+" to indicate the count is stale.
|
||||
"""
|
||||
self._approximate = approximate
|
||||
self._has_token_count = count > 0
|
||||
if self.tokens == count:
|
||||
# Reactive dedup would skip the watcher — call render directly.
|
||||
self._render_tokens(count, approximate=approximate)
|
||||
@@ -435,6 +438,8 @@ class StatusBar(Horizontal):
|
||||
|
||||
def show_pending_tokens(self) -> None:
|
||||
"""Show an unknown token count placeholder during streaming."""
|
||||
if not self._has_token_count:
|
||||
return
|
||||
try:
|
||||
self.query_one("#tokens-display", Static).update("... tokens")
|
||||
except NoMatches:
|
||||
|
||||
@@ -145,7 +145,9 @@ class UpdateProgressScreen(ModalScreen[None]):
|
||||
yield Static("Updating Deep Agents CLI", classes="up-title")
|
||||
with Horizontal(classes="up-status-row"):
|
||||
self._spinner_widget = Static(
|
||||
self._spinner.current_frame(), classes="up-spinner"
|
||||
self._spinner.current_frame(),
|
||||
classes="up-spinner",
|
||||
markup=False,
|
||||
)
|
||||
yield self._spinner_widget
|
||||
self._status_widget = Static(
|
||||
|
||||
@@ -205,13 +205,13 @@ class TestTokenDisplay:
|
||||
display = pilot.app.query_one("#tokens-display")
|
||||
assert str(display.render()) == "... tokens"
|
||||
|
||||
async def test_show_pending_tokens_before_count_shows_placeholder(self) -> None:
|
||||
async def test_show_pending_tokens_before_count_leaves_display_empty(self) -> None:
|
||||
async with StatusBarApp().run_test() as pilot:
|
||||
bar = pilot.app.query_one("#status-bar", StatusBar)
|
||||
bar.show_pending_tokens()
|
||||
await pilot.pause()
|
||||
display = pilot.app.query_one("#tokens-display")
|
||||
assert str(display.render()) == "... tokens"
|
||||
assert str(display.render()) == ""
|
||||
|
||||
async def test_set_tokens_after_pending_restores_display(self) -> None:
|
||||
"""Regression: set_tokens must refresh even when value is unchanged.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for ThreadSelectorScreen."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine
|
||||
from typing import Any, ClassVar
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
@@ -2722,10 +2723,14 @@ class TestBuildThreadMessage:
|
||||
async def test_fallback_on_timeout(self) -> None:
|
||||
"""Returns plain string when URL resolution times out."""
|
||||
app = DeepAgentsApp()
|
||||
with patch(
|
||||
"deepagents_cli.app.asyncio.wait_for",
|
||||
side_effect=TimeoutError,
|
||||
):
|
||||
|
||||
async def _raise_timeout( # noqa: RUF029 # async signature required to match asyncio.wait_for
|
||||
coro: Coroutine[Any, Any, Any], *_: Any, **__: Any
|
||||
) -> None:
|
||||
coro.close()
|
||||
raise TimeoutError
|
||||
|
||||
with patch("deepagents_cli.app.asyncio.wait_for", new=_raise_timeout):
|
||||
result = await app._build_thread_message("Resumed thread", "t-1")
|
||||
|
||||
assert isinstance(result, str)
|
||||
|
||||
Reference in New Issue
Block a user