Skip to content

Commit 5fed7e5

Browse files
committed
Add test for improving robustness.
1 parent 2649e1e commit 5fed7e5

3 files changed

Lines changed: 53 additions & 10 deletions

File tree

python_agent_harness/tui.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -517,12 +517,17 @@ def _build_history_rows(self) -> list[Any]:
517517
elif m.role == "assistant":
518518
body = m.text()
519519
collapsed_reasoning = False
520-
if m.reasoning:
520+
if isinstance(m.reasoning, str) and m.reasoning:
521521
stripped = _strip_reasoning(body, m.reasoning)
522522
if stripped != body:
523523
body = stripped
524524
collapsed_reasoning = True
525525
body = _tail_lines(_strip_final_check(body), 12)
526+
if collapsed_reasoning:
527+
# the reasoning streamed live while it was being
528+
# produced; once it is done it collapses to a marker
529+
# so it doesn't eat the visible-row budget
530+
rows.append(Text("💭 ...", style="dim"))
526531
if m.tool_calls:
527532
for tc in m.tool_calls:
528533
args = tc.arguments
@@ -538,11 +543,6 @@ def _build_history_rows(self) -> list[Any]:
538543
params = ""
539544
label = f"🤖 {tc.name}({params})" if params else f"🤖 {tc.name}"
540545
rows.append(Text(label, style="cyan"))
541-
if collapsed_reasoning:
542-
# the reasoning streamed live while it was being
543-
# produced; once it is done it collapses to a marker
544-
# so it doesn't eat the visible-row budget
545-
rows.append(Text("💭 ...", style="dim"))
546546
if body.strip():
547547
rows.append(Markdown(f"**assistant:** {body}"))
548548
elif m.role == "tool":

tests/test_client.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
"""Client streaming tests against the in-process fake OpenAI server."""
22

3+
import os
4+
import sys
35
import unittest
46

57
from python_agent_harness.client import Client
8+
from python_agent_harness.models import Message
69

7-
from fake_openai_server import serve
10+
# `discover -s tests` puts the tests dir on sys.path, but a direct
11+
# `-m unittest tests.test_client` invocation does not — make the
12+
# sibling helper importable either way.
13+
sys.path.insert(0, os.path.dirname(__file__))
14+
15+
from fake_openai_server import serve # noqa: E402
816

917

1018
def make_client() -> Client:
@@ -22,9 +30,7 @@ def test_reasoning_content_streamed_and_captured(self):
2230
c = make_client()
2331
deltas: list[str] = []
2432
msg, usage = c.chat(
25-
[__import__("python_agent_harness.models", fromlist=["Message"]).Message(
26-
role="user", content="hi"
27-
)],
33+
[Message(role="user", content="hi")],
2834
on_delta=deltas.append,
2935
)
3036
self.assertEqual("".join(deltas), "thinking hardHello world")

tests/test_tui.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,43 @@ def test_reasoning_collapsed_marker_shows_even_without_answer(self):
241241
self.assertIn("💭 ...", out)
242242
self.assertNotIn("pensive thoughts", out)
243243

244+
def test_reasoning_marker_before_tool_call_label(self):
245+
"""For a reasoned tool call the marker comes first: reasoning
246+
happened before the tool invocation, so it renders above the
247+
tool label."""
248+
tui, buf = make_tui()
249+
tui.session.last_messages = [
250+
Message(role="user", content="read the file"),
251+
Message(
252+
role="assistant", content="let me check the path first",
253+
reasoning="let me check the path first",
254+
tool_calls=[ToolCall(id="1", name="Read", arguments="{}")],
255+
),
256+
Message(role="tool", content="file contents", tool_call_id="1", name="Read"),
257+
]
258+
tui.console.print(tui._render_conversation())
259+
out = buf.getvalue()
260+
self.assertIn("💭 ...", out)
261+
self.assertIn("🤖 Read", out)
262+
self.assertLess(out.index("💭 ..."), out.index("🤖 Read"))
263+
self.assertNotIn("let me check", out)
264+
265+
def test_non_string_reasoning_does_not_crash(self):
266+
"""A malformed (non-string) reasoning value must not raise —
267+
display falls back to the raw content."""
268+
tui, buf = make_tui()
269+
tui.session.last_messages = [
270+
Message(role="user", content="go"),
271+
Message(
272+
role="assistant", content="full text here",
273+
reasoning=["not", "a", "string"],
274+
),
275+
]
276+
tui.console.print(tui._render_conversation())
277+
out = buf.getvalue()
278+
self.assertIn("full text here", out)
279+
self.assertNotIn("💭 ...", out)
280+
244281
def test_strip_reasoning(self):
245282
"""_strip_reasoning removes the leading reasoning prefix and
246283
leaves non-matching text untouched."""

0 commit comments

Comments
 (0)