feat(cli): display model reasoning content during streaming
Add show_reasoning config (default: False) to display model thinking/reasoning content in the TUI during streaming. Reasoning is emitted via a new emit_reasoning hook on AgentHook, gated by the channels config. Display uses ✻ prefix with dim italic styling. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d630ac90d1
commit
3a27af0018
@@ -13,6 +13,17 @@ def _ctx() -> AgentHookContext:
|
||||
return AgentHookContext(iteration=0, messages=[])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Base AgentHook emit_reasoning: no-op
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_base_hook_emit_reasoning_is_noop():
|
||||
hook = AgentHook()
|
||||
await hook.emit_reasoning("should not raise")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fan-out: every hook is called in order
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -45,6 +56,9 @@ async def test_composite_fans_out_all_async_methods():
|
||||
async def before_iteration(self, context: AgentHookContext) -> None:
|
||||
events.append("before_iteration")
|
||||
|
||||
async def emit_reasoning(self, reasoning_content: str | None) -> None:
|
||||
events.append(f"emit_reasoning:{reasoning_content}")
|
||||
|
||||
async def on_stream(self, context: AgentHookContext, delta: str) -> None:
|
||||
events.append(f"on_stream:{delta}")
|
||||
|
||||
@@ -61,6 +75,7 @@ async def test_composite_fans_out_all_async_methods():
|
||||
ctx = _ctx()
|
||||
|
||||
await hook.before_iteration(ctx)
|
||||
await hook.emit_reasoning("thinking...")
|
||||
await hook.on_stream(ctx, "hi")
|
||||
await hook.on_stream_end(ctx, resuming=True)
|
||||
await hook.before_execute_tools(ctx)
|
||||
@@ -68,6 +83,7 @@ async def test_composite_fans_out_all_async_methods():
|
||||
|
||||
assert events == [
|
||||
"before_iteration", "before_iteration",
|
||||
"emit_reasoning:thinking...", "emit_reasoning:thinking...",
|
||||
"on_stream:hi", "on_stream:hi",
|
||||
"on_stream_end:True", "on_stream_end:True",
|
||||
"before_execute_tools", "before_execute_tools",
|
||||
@@ -120,6 +136,8 @@ async def test_composite_error_isolation_all_async():
|
||||
calls: list[str] = []
|
||||
|
||||
class Bad(AgentHook):
|
||||
async def emit_reasoning(self, reasoning_content):
|
||||
raise RuntimeError("err")
|
||||
async def on_stream_end(self, context, *, resuming):
|
||||
raise RuntimeError("err")
|
||||
async def before_execute_tools(self, context):
|
||||
@@ -128,6 +146,8 @@ async def test_composite_error_isolation_all_async():
|
||||
raise RuntimeError("err")
|
||||
|
||||
class Good(AgentHook):
|
||||
async def emit_reasoning(self, reasoning_content):
|
||||
calls.append("emit_reasoning")
|
||||
async def on_stream_end(self, context, *, resuming):
|
||||
calls.append("on_stream_end")
|
||||
async def before_execute_tools(self, context):
|
||||
@@ -137,10 +157,11 @@ async def test_composite_error_isolation_all_async():
|
||||
|
||||
hook = CompositeHook([Bad(), Good()])
|
||||
ctx = _ctx()
|
||||
await hook.emit_reasoning("test")
|
||||
await hook.on_stream_end(ctx, resuming=False)
|
||||
await hook.before_execute_tools(ctx)
|
||||
await hook.after_iteration(ctx)
|
||||
assert calls == ["on_stream_end", "before_execute_tools", "after_iteration"]
|
||||
assert calls == ["emit_reasoning", "on_stream_end", "before_execute_tools", "after_iteration"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user