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
12 changes: 11 additions & 1 deletion python/packages/claude/agent_framework_claude/_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,14 @@ async def _apply_runtime_options(self, client: ClaudeSDKClient, options: dict[st
def _format_prompt(self, messages: list[Message] | None) -> str:
"""Format messages into a prompt string.

The Claude Agent SDK's streaming-input protocol only accepts ``user``-role
turns (it generates its own ``assistant`` turns), so a multi-message,
multi-role ``messages`` list can't be forwarded with real per-message
roles. Instead, when more than a single plain user turn is given (e.g. an
agent receiving prior turns from other agents and the user in a
multi-agent orchestration), each message is prefixed with its role so
Claude can still tell the turns apart from the text itself.

Args:
messages: List of chat messages.

Expand All @@ -741,7 +749,9 @@ def _format_prompt(self, messages: list[Message] | None) -> str:
"""
if not messages:
return ""
return "\n".join([msg.text or "" for msg in messages])
if len(messages) == 1 and messages[0].role == "user":
return messages[0].text or ""
return "\n".join(f"[{msg.role}]: {msg.text or ''}" for msg in messages)

@property
def default_options(self) -> dict[str, Any]:
Expand Down
23 changes: 23 additions & 0 deletions python/packages/claude/tests/test_claude_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,29 @@ def test_format_multiple_messages(self) -> None:
assert "Hello!" in result
assert "How are you?" in result

def test_format_multiple_messages_preserves_roles(self) -> None:
"""Multi-message input (e.g. multi-agent orchestration history) must keep
each message's role visible in the text, since the Claude Agent SDK's
streaming-input protocol only accepts user-role turns and can't carry
per-message roles on the wire."""
agent = ClaudeAgent()
messages = [
Message(role="assistant", contents=[Content.from_text(text="Researcher: Paris is the capital.")]),
Message(role="user", contents=[Content.from_text(text="Critic: verify that.")]),
Message(role="assistant", contents=[Content.from_text(text="Researcher: confirmed.")]),
]
result = agent._format_prompt(messages) # type: ignore[reportPrivateUsage]
assert "[assistant]: Researcher: Paris is the capital." in result
assert "[user]: Critic: verify that." in result
assert "[assistant]: Researcher: confirmed." in result

def test_format_single_user_message_has_no_role_label(self) -> None:
"""The common single-turn case is left unlabeled for backward compatibility."""
agent = ClaudeAgent()
msg = Message(role="user", contents=[Content.from_text(text="Hello")])
result = agent._format_prompt([msg]) # type: ignore[reportPrivateUsage]
assert result == "Hello"


# region Test Build Options

Expand Down
Loading