fix(acp): only deliver final_response after streaming when transformed
PR #29119 dropped the 'not streamed_message' guard unconditionally so that plugin-transformed responses (transform_llm_output hook) would reach ACP clients. That regressed test_prompt_does_not_duplicate_streamed_final_message: when no transform happened, the streamed text was re-sent as a duplicate final delivery. Tighten the condition to mirror the gateway side: deliver after streaming only when response_transformed=True. Otherwise keep the old guard. Adds test_prompt_delivers_transformed_response_after_streaming so the transformed path stays covered.
This commit is contained in:
@ -1534,9 +1534,11 @@ class HermesACPAgent(acp.Agent):
|
|||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.debug("Failed to auto-title ACP session %s", session_id, exc_info=True)
|
logger.debug("Failed to auto-title ACP session %s", session_id, exc_info=True)
|
||||||
if final_response and conn:
|
if final_response and conn and (not streamed_message or result.get("response_transformed")):
|
||||||
# Always deliver the final response — plugins may have transformed
|
# Deliver the final response when streaming did not already send it,
|
||||||
# it after streaming finished (e.g. transform_llm_output hook).
|
# or when a plugin hook transformed the response after streaming
|
||||||
|
# finished (e.g. transform_llm_output) — otherwise the appended /
|
||||||
|
# rewritten text never reaches the client.
|
||||||
update = acp.update_agent_message_text(final_response)
|
update = acp.update_agent_message_text(final_response)
|
||||||
await conn.session_update(session_id, update)
|
await conn.session_update(session_id, update)
|
||||||
|
|
||||||
|
|||||||
@ -1203,6 +1203,48 @@ class TestPrompt:
|
|||||||
assert len(agent_chunks) == 1
|
assert len(agent_chunks) == 1
|
||||||
assert agent_chunks[0].content.text == "streamed answer"
|
assert agent_chunks[0].content.text == "streamed answer"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_prompt_delivers_transformed_response_after_streaming(self, agent):
|
||||||
|
"""If a transform_llm_output plugin hook modifies the response after
|
||||||
|
streaming, ACP must deliver the transformed final_response so the
|
||||||
|
appended/rewritten text reaches the client.
|
||||||
|
"""
|
||||||
|
new_resp = await agent.new_session(cwd=".")
|
||||||
|
state = agent.session_manager.get_session(new_resp.session_id)
|
||||||
|
|
||||||
|
def mock_run(*args, **kwargs):
|
||||||
|
state.agent.stream_delta_callback("original answer")
|
||||||
|
return {
|
||||||
|
"final_response": "original answer\n\n[plugin appended this]",
|
||||||
|
"response_transformed": True,
|
||||||
|
"messages": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
state.agent.run_conversation = mock_run
|
||||||
|
|
||||||
|
mock_conn = MagicMock(spec=acp.Client)
|
||||||
|
mock_conn.session_update = AsyncMock()
|
||||||
|
agent._conn = mock_conn
|
||||||
|
|
||||||
|
prompt = [TextContentBlock(type="text", text="hello")]
|
||||||
|
await agent.prompt(prompt=prompt, session_id=new_resp.session_id)
|
||||||
|
|
||||||
|
updates = [
|
||||||
|
call.kwargs.get("update") or call.args[1]
|
||||||
|
for call in mock_conn.session_update.call_args_list
|
||||||
|
]
|
||||||
|
# The streamed chunk and the post-stream transformed message should
|
||||||
|
# both be present (final delivery is a separate update_agent_message_text
|
||||||
|
# call carrying the full transformed text).
|
||||||
|
all_texts = [
|
||||||
|
getattr(getattr(u, "content", None), "text", None)
|
||||||
|
for u in updates
|
||||||
|
]
|
||||||
|
assert any(
|
||||||
|
text and "[plugin appended this]" in text for text in all_texts
|
||||||
|
), f"expected transformed final to be delivered, got: {all_texts!r}"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_prompt_auto_titles_session(self, agent):
|
async def test_prompt_auto_titles_session(self, agent):
|
||||||
new_resp = await agent.new_session(cwd=".")
|
new_resp = await agent.new_session(cwd=".")
|
||||||
|
|||||||
Reference in New Issue
Block a user