From 0eddd38d1c3cc39ae98b798a5b44a586f8f1fb16 Mon Sep 17 00:00:00 2001 From: Rishab Motgi Date: Tue, 19 May 2026 11:09:11 -0700 Subject: [PATCH] fix(streaming): guard against out-of-bounds content index in accumulate_event; fix BetaAsyncAbstractMemoryTool docstring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes: 1. _messages.py + _beta_messages.py (fixes #1192): accumulate_event indexes directly into current_snapshot.content[event.index] for both content_block_delta and content_block_stop events. If the server sends a delta/stop for an index that has not yet appeared in a content_block_start (server bug, reconnect, or race), this raises an unhandled IndexError. Add a bounds check and return the snapshot unchanged in that case — identical guard applied to both streaming files. 2. _beta_builtin_memory_tool.py (fixes #1290): BetaAsyncAbstractMemoryTool docstring was a verbatim copy of BetaAbstractMemoryTool, telling users to subclass the sync class, use sync def methods, and use the sync Anthropic() client. Updated to show the correct async subclass name, async def methods, AsyncAnthropic client, and asyncio.run() wrapper. --- src/anthropic/lib/streaming/_beta_messages.py | 4 +++ src/anthropic/lib/streaming/_messages.py | 4 +++ .../lib/tools/_beta_builtin_memory_tool.py | 28 +++++++++++-------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/anthropic/lib/streaming/_beta_messages.py b/src/anthropic/lib/streaming/_beta_messages.py index 5a5a562a0..422ef7b7d 100644 --- a/src/anthropic/lib/streaming/_beta_messages.py +++ b/src/anthropic/lib/streaming/_beta_messages.py @@ -482,6 +482,8 @@ def accumulate_event( ), ) elif event.type == "content_block_delta": + if event.index >= len(current_snapshot.content): + return current_snapshot content = current_snapshot.content[event.index] if event.delta.type == "text_delta": if content.type == "text": @@ -530,6 +532,8 @@ def accumulate_event( if TYPE_CHECKING: # type: ignore[unreachable] assert_never(event.delta) elif event.type == "content_block_stop": + if event.index >= len(current_snapshot.content): + return current_snapshot content_block = current_snapshot.content[event.index] if content_block.type == "text" and is_given(output_format): content_block.parsed_output = parse_text(content_block.text, output_format) diff --git a/src/anthropic/lib/streaming/_messages.py b/src/anthropic/lib/streaming/_messages.py index 5c0da9992..5c370a42b 100644 --- a/src/anthropic/lib/streaming/_messages.py +++ b/src/anthropic/lib/streaming/_messages.py @@ -462,6 +462,8 @@ def accumulate_event( ), ) elif event.type == "content_block_delta": + if event.index >= len(current_snapshot.content): + return current_snapshot content = current_snapshot.content[event.index] if event.delta.type == "text_delta": if content.type == "text": @@ -497,6 +499,8 @@ def accumulate_event( if TYPE_CHECKING: # type: ignore[unreachable] assert_never(event.delta) elif event.type == "content_block_stop": + if event.index >= len(current_snapshot.content): + return current_snapshot content_block = current_snapshot.content[event.index] if content_block.type == "text" and is_given(output_format): content_block.parsed_output = parse_text(content_block.text, output_format) diff --git a/src/anthropic/lib/tools/_beta_builtin_memory_tool.py b/src/anthropic/lib/tools/_beta_builtin_memory_tool.py index edb948a26..f28c661fb 100644 --- a/src/anthropic/lib/tools/_beta_builtin_memory_tool.py +++ b/src/anthropic/lib/tools/_beta_builtin_memory_tool.py @@ -166,7 +166,7 @@ def clear_all_memory(self) -> BetaFunctionToolResultType: class BetaAsyncAbstractMemoryTool(BetaAsyncBuiltinFunctionTool): - """Abstract base class for memory tool implementations. + """Abstract base class for async memory tool implementations. This class provides the interface for implementing a custom memory backend for Claude. @@ -175,25 +175,31 @@ class BetaAsyncAbstractMemoryTool(BetaAsyncBuiltinFunctionTool): Example usage: ```py - class MyMemoryTool(BetaAbstractMemoryTool): - def view(self, command: BetaMemoryTool20250818ViewCommand) -> BetaFunctionToolResultType: + import asyncio + from anthropic import AsyncAnthropic + + class MyMemoryTool(BetaAsyncAbstractMemoryTool): + async def view(self, command: BetaMemoryTool20250818ViewCommand) -> BetaFunctionToolResultType: ... return "view result" - def create(self, command: BetaMemoryTool20250818CreateCommand) -> BetaFunctionToolResultType: + async def create(self, command: BetaMemoryTool20250818CreateCommand) -> BetaFunctionToolResultType: ... return "created successfully" # ... implement other abstract methods - client = Anthropic() - memory_tool = MyMemoryTool() - message = client.beta.messages.run_tools( - model="claude-sonnet-4-5", - messages=[{"role": "user", "content": "Remember that I like coffee"}], - tools=[memory_tool], - ).until_done() + async def main() -> None: + client = AsyncAnthropic() + memory_tool = MyMemoryTool() + message = await client.beta.messages.run_tools( + model="claude-sonnet-4-5", + messages=[{"role": "user", "content": "Remember that I like coffee"}], + tools=[memory_tool], + ).until_done() + + asyncio.run(main()) ``` """