From 6e55eb43d19e0c7bb0227ffa87b565cdc2d30767 Mon Sep 17 00:00:00 2001 From: steve02081504 Date: Sun, 1 Mar 2026 20:51:47 +0800 Subject: [PATCH] add `agent_message_clear` impls https://github.com/agentclientprotocol/agent-client-protocol/pull/465 --- schema/schema.json | 32 +++++++++++++++++++ scripts/gen_schema.py | 1 + src/acp/__init__.py | 1 + src/acp/contrib/session_state.py | 5 +++ src/acp/helpers.py | 11 +++++++ src/acp/schema.py | 14 ++++++++ tests/contrib/test_contrib_session_state.py | 14 ++++++++ .../session_update_agent_message_clear.json | 1 + tests/test_golden.py | 4 +++ 9 files changed, 83 insertions(+) create mode 100644 tests/golden/session_update_agent_message_clear.json diff --git a/schema/schema.json b/schema/schema.json index ba61b43..dc493c8 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -531,6 +531,20 @@ ], "description": "The input specification for a command." }, + "AgentMessageClear": { + "description": "Clear the accumulated streamed content for the current agent message. Subsequent agent_message_chunk updates start appending from empty.", + "properties": { + "_meta": { + "additionalProperties": true, + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", + "type": [ + "object", + "null" + ] + } + }, + "type": "object" + }, "AvailableCommandsUpdate": { "description": "Available commands are ready or have changed", "properties": { @@ -3767,6 +3781,24 @@ "sessionUpdate" ], "type": "object" + }, + { + "allOf": [ + { + "$ref": "#/$defs/AgentMessageClear" + } + ], + "description": "Clear the accumulated streamed content for the current agent message. Subsequent agent_message_chunk updates start appending from empty.", + "properties": { + "sessionUpdate": { + "const": "agent_message_clear", + "type": "string" + } + }, + "required": [ + "sessionUpdate" + ], + "type": "object" } ] }, diff --git a/scripts/gen_schema.py b/scripts/gen_schema.py index 74a8790..ea6d324 100644 --- a/scripts/gen_schema.py +++ b/scripts/gen_schema.py @@ -63,6 +63,7 @@ "SessionUpdate9": "ConfigOptionUpdate", "SessionUpdate10": "SessionInfoUpdate", "SessionUpdate11": "UsageUpdate", + "SessionUpdate12": "AgentMessageClear", "ToolCallContent1": "ContentToolCallContent", "ToolCallContent2": "FileEditToolCallContent", "ToolCallContent3": "TerminalToolCallContent", diff --git a/src/acp/__init__.py b/src/acp/__init__.py index 7cbde3c..67b556a 100644 --- a/src/acp/__init__.py +++ b/src/acp/__init__.py @@ -9,6 +9,7 @@ ) from .helpers import ( audio_block, + clear_agent_message, embedded_blob_resource, embedded_text_resource, image_block, diff --git a/src/acp/contrib/session_state.py b/src/acp/contrib/session_state.py index ee56125..07a596a 100644 --- a/src/acp/contrib/session_state.py +++ b/src/acp/contrib/session_state.py @@ -8,6 +8,7 @@ from ..schema import ( AgentMessageChunk, + AgentMessageClear, AgentPlanUpdate, AgentThoughtChunk, AvailableCommand, @@ -238,6 +239,10 @@ def _apply_update(self, update: Any) -> None: self._agent_messages.append(update.model_copy(deep=True)) return + if isinstance(update, AgentMessageClear): + self._agent_messages.clear() + return + if isinstance(update, AgentThoughtChunk): self._agent_thoughts.append(update.model_copy(deep=True)) diff --git a/src/acp/helpers.py b/src/acp/helpers.py index 8830c43..ba87e94 100644 --- a/src/acp/helpers.py +++ b/src/acp/helpers.py @@ -5,6 +5,7 @@ from .schema import ( AgentMessageChunk, + AgentMessageClear, AgentPlanUpdate, AgentThoughtChunk, AudioContentBlock, @@ -39,6 +40,7 @@ SessionUpdate = ( AgentMessageChunk + | AgentMessageClear | AgentPlanUpdate | AgentThoughtChunk | AvailableCommandsUpdate @@ -53,6 +55,7 @@ __all__ = [ "audio_block", + "clear_agent_message", "embedded_blob_resource", "embedded_text_resource", "image_block", @@ -167,6 +170,14 @@ def update_agent_message_text(text: str) -> AgentMessageChunk: return update_agent_message(text_block(text)) +def clear_agent_message() -> AgentMessageClear: + """Clear the accumulated streamed content for the current agent message. + + Subsequent agent_message_chunk updates will start appending from empty. + """ + return AgentMessageClear(session_update="agent_message_clear") + + def update_agent_thought(content: ContentBlock) -> AgentThoughtChunk: return AgentThoughtChunk(session_update="agent_thought_chunk", content=content) diff --git a/src/acp/schema.py b/src/acp/schema.py index e449e4a..0bdc4a3 100644 --- a/src/acp/schema.py +++ b/src/acp/schema.py @@ -2238,6 +2238,19 @@ class AgentThoughtChunk(ContentChunk): session_update: Annotated[Literal["agent_thought_chunk"], Field(alias="sessionUpdate")] +class AgentMessageClear(BaseModel): + # Clear the accumulated streamed content for the current agent message. + # Subsequent agent_message_chunk updates start appending from empty. + field_meta: Annotated[ + Optional[Dict[str, Any]], + Field( + alias="_meta", + description="The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", + ), + ] = None + session_update: Annotated[Literal["agent_message_clear"], Field(alias="sessionUpdate")] + + class ClientRequest(BaseModel): # JSON RPC Request Id # @@ -2831,6 +2844,7 @@ class SessionNotification(BaseModel): ConfigOptionUpdate, SessionInfoUpdate, UsageUpdate, + AgentMessageClear, ], Field(description="The actual update content.", discriminator="session_update"), ] diff --git a/tests/contrib/test_contrib_session_state.py b/tests/contrib/test_contrib_session_state.py index deaa467..d6eae14 100644 --- a/tests/contrib/test_contrib_session_state.py +++ b/tests/contrib/test_contrib_session_state.py @@ -2,6 +2,7 @@ import pytest +from acp import clear_agent_message from acp.contrib.session_state import SessionAccumulator from acp.schema import ( AgentMessageChunk, @@ -107,6 +108,19 @@ def test_session_accumulator_tracks_messages_and_commands(): assert agent_content.text == "Hi!" +def test_session_accumulator_agent_message_clear_clears_accumulated_chunks(): + acc = SessionAccumulator() + acc.apply(notification("s", AgentMessageChunk(session_update="agent_message_chunk", content=TextContentBlock(type="text", text="Thinking...")))) + acc.apply(notification("s", AgentMessageChunk(session_update="agent_message_chunk", content=TextContentBlock(type="text", text=" draft")))) + assert len(acc.snapshot().agent_messages) == 2 + acc.apply(notification("s", clear_agent_message())) + assert len(acc.snapshot().agent_messages) == 0 + acc.apply(notification("s", AgentMessageChunk(session_update="agent_message_chunk", content=TextContentBlock(type="text", text="Final result.")))) + snapshot = acc.snapshot() + assert len(snapshot.agent_messages) == 1 + assert snapshot.agent_messages[0].content.text == "Final result." + + def test_session_accumulator_auto_resets_on_new_session(): acc = SessionAccumulator() acc.apply( diff --git a/tests/golden/session_update_agent_message_clear.json b/tests/golden/session_update_agent_message_clear.json new file mode 100644 index 0000000..fd179e6 --- /dev/null +++ b/tests/golden/session_update_agent_message_clear.json @@ -0,0 +1 @@ +{"sessionUpdate":"agent_message_clear"} diff --git a/tests/test_golden.py b/tests/test_golden.py index 181945f..65a058f 100644 --- a/tests/test_golden.py +++ b/tests/test_golden.py @@ -9,6 +9,7 @@ from acp import ( audio_block, + clear_agent_message, embedded_blob_resource, embedded_text_resource, image_block, @@ -30,6 +31,7 @@ ) from acp.schema import ( AgentMessageChunk, + AgentMessageClear, AgentPlanUpdate, AgentThoughtChunk, AllowedOutcome, @@ -86,6 +88,7 @@ "request_permission_request": RequestPermissionRequest, "request_permission_response_selected": RequestPermissionResponse, "session_update_agent_message_chunk": AgentMessageChunk, + "session_update_agent_message_clear": AgentMessageClear, "session_update_agent_thought_chunk": AgentThoughtChunk, "session_update_config_option_update": ConfigOptionUpdate, "session_update_plan": AgentPlanUpdate, @@ -144,6 +147,7 @@ "tool_content_terminal": lambda: tool_terminal_ref("term_001"), "session_update_user_message_chunk": lambda: update_user_message_text("What's the capital of France?"), "session_update_agent_message_chunk": lambda: update_agent_message_text("The capital of France is Paris."), + "session_update_agent_message_clear": lambda: clear_agent_message(), "session_update_agent_thought_chunk": lambda: update_agent_thought_text("Thinking about best approach..."), "session_update_plan": lambda: update_plan([ plan_entry(