Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions openhands-sdk/openhands/sdk/event/llm_convertible/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from openhands.sdk.event.base import N_CHAR_PREVIEW, EventID, LLMConvertibleEvent
from openhands.sdk.event.types import SourceType, ToolCallID
from openhands.sdk.event.utils import render_responses_reasoning_block
from openhands.sdk.llm import (
Message,
MessageToolCall,
Expand Down Expand Up @@ -87,15 +88,9 @@ def visualize(self) -> Text:
content.append("\n\n")

# Responses API reasoning (plaintext only; never render encrypted_content)
reasoning_item = self.responses_reasoning_item
if reasoning_item is not None:
content.append("Reasoning:\n", style="bold")
if reasoning_item.summary:
for s in reasoning_item.summary:
content.append(f"- {s}\n")
if reasoning_item.content:
for b in reasoning_item.content:
content.append(f"{b}\n")
reasoning_text = render_responses_reasoning_block(self.responses_reasoning_item)
if reasoning_text:
content.append(reasoning_text)

# Display action information using action's visualize method
if self.action:
Expand Down
15 changes: 6 additions & 9 deletions openhands-sdk/openhands/sdk/event/llm_convertible/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from openhands.sdk.event.base import N_CHAR_PREVIEW, EventID, LLMConvertibleEvent
from openhands.sdk.event.types import SourceType
from openhands.sdk.event.utils import render_responses_reasoning_block
from openhands.sdk.llm import (
ImageContent,
Message,
Expand Down Expand Up @@ -74,15 +75,11 @@ def visualize(self) -> Text:
content.append("[no text content]")

# Responses API reasoning (plaintext only; never render encrypted_content)
reasoning_item = self.llm_message.responses_reasoning_item
if reasoning_item is not None:
content.append("\n\nReasoning:\n", style="bold")
if reasoning_item.summary:
for s in reasoning_item.summary:
content.append(f"- {s}\n")
if reasoning_item.content:
for b in reasoning_item.content:
content.append(f"{b}\n")
reasoning_text = render_responses_reasoning_block(
self.llm_message.responses_reasoning_item, leading_newlines=True
)
if reasoning_text:
content.append(reasoning_text)

# Add skill information if present
if self.activated_skills:
Expand Down
37 changes: 37 additions & 0 deletions openhands-sdk/openhands/sdk/event/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from rich.text import Text

from openhands.sdk.llm import ReasoningItemModel


def render_responses_reasoning_block(
reasoning_item: ReasoningItemModel | None,
*,
leading_newlines: bool = False,
) -> Text | None:
"""Build a Rich Text block for Responses API reasoning.

Only renders when either summary or content is present. Returns None
when there is nothing to render.
"""
if reasoning_item is None:
return None

has_summary = bool(reasoning_item.summary)
has_content = bool(reasoning_item.content)
if not (has_summary or has_content):
return None

t = Text()
if leading_newlines:
t.append("\n\n")
t.append("Reasoning:\n", style="bold")

if has_summary:
for s in list(reasoning_item.summary or []):
t.append(f"- {s}\n")

if has_content:
for b in list(reasoning_item.content or []):
t.append(f"{b}\n")

return t
11 changes: 11 additions & 0 deletions tests/sdk/conversation/test_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ def test_action_event_visualize():

result = event.visualize
assert isinstance(result, Text)
# If no Responses reasoning is present, Reasoning: should not be printed
event_no_reason = ActionEvent(
thought=[TextContent(text="I need to list files")],
action=action,
tool_name="terminal",
tool_call_id="call_124",
tool_call=create_tool_call("call_124", "terminal", {"command": "ls -la"}),
llm_response_id="response_457",
)
text_no_reason = event_no_reason.visualize.plain
assert "Reasoning:" not in text_no_reason

text_content = result.plain
assert "Reasoning:" in text_content
Expand Down
Loading