Skip to content

Commit 2deff70

Browse files
committed
feat: add update_stream_delivery_config to MemoryClient
Add a focused method to update stream delivery configuration on a memory without requiring callers to drop down to MemoryControlPlaneClient. - Calls gmcp_client.update_memory with only memoryId, clientToken, and streamDeliveryResources — no other memory properties are modified - Normalizes response via _normalize_memory_response - Does not shadow the existing update_memory passthrough in _ALLOWED_GMCP_METHODS - Unit tests: config passthrough, response normalization, ClientError propagation - Integration test: builds on PR aws#317 TestStreamDeliveryResources with test_stream_delivery_update (FULL_CONTENT -> METADATA_ONLY with wait/poll)
1 parent a4de01f commit 2deff70

3 files changed

Lines changed: 133 additions & 0 deletions

File tree

src/bedrock_agentcore/memory/client.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,33 @@ def delete_memory(self, memory_id: str) -> Dict[str, Any]:
12591259
logger.error("Failed to delete memory: %s", e)
12601260
raise
12611261

1262+
def update_stream_delivery_config(
1263+
self,
1264+
memory_id: str,
1265+
stream_delivery_resources: Dict[str, Any],
1266+
) -> Dict[str, Any]:
1267+
"""Update the stream delivery configuration for a memory.
1268+
1269+
Args:
1270+
memory_id: Memory resource ID
1271+
stream_delivery_resources: Delivery configuration for streaming memory records
1272+
1273+
Returns:
1274+
Updated memory object
1275+
"""
1276+
try:
1277+
response = self.gmcp_client.update_memory(
1278+
memoryId=memory_id,
1279+
clientToken=str(uuid.uuid4()),
1280+
streamDeliveryResources=stream_delivery_resources,
1281+
)
1282+
memory = self._normalize_memory_response(response["memory"])
1283+
logger.info("Updated stream delivery config for memory: %s", memory_id)
1284+
return memory
1285+
except ClientError as e:
1286+
logger.error("Failed to update stream delivery config: %s", e)
1287+
raise
1288+
12621289
def delete_memory_and_wait(self, memory_id: str, max_wait: int = 300, poll_interval: int = 10) -> Dict[str, Any]:
12631290
"""Delete a memory and wait for deletion to complete.
12641291

tests/bedrock_agentcore/memory/test_client.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from datetime import datetime
77
from unittest.mock import MagicMock, patch
88

9+
import pytest
910
from botocore.exceptions import ClientError
1011

1112
from bedrock_agentcore.memory import MemoryClient
@@ -765,6 +766,66 @@ def test_delete_memory():
765766
assert kwargs["clientToken"] == "12345678-1234-5678-1234-567812345678"
766767

767768

769+
def test_update_stream_delivery_config():
770+
"""Test update_stream_delivery_config passes config and normalizes response."""
771+
with patch("boto3.client"):
772+
client = MemoryClient()
773+
774+
mock_gmcp = MagicMock()
775+
client.gmcp_client = mock_gmcp
776+
777+
delivery_config = {
778+
"resources": [
779+
{
780+
"kinesis": {
781+
"dataStreamArn": "arn:aws:kinesis:us-west-2:123456789012:stream/test",
782+
"contentConfigurations": [{"type": "MEMORY_RECORDS", "level": "FULL_CONTENT"}],
783+
}
784+
}
785+
]
786+
}
787+
788+
mock_gmcp.update_memory.return_value = {
789+
"memory": {
790+
"memoryId": "mem-123",
791+
"status": "ACTIVE",
792+
"streamDeliveryResources": delivery_config,
793+
}
794+
}
795+
796+
with patch("uuid.uuid4", return_value=uuid.UUID("12345678-1234-5678-1234-567812345678")):
797+
result = client.update_stream_delivery_config(
798+
memory_id="mem-123", stream_delivery_resources=delivery_config
799+
)
800+
801+
assert result["memoryId"] == "mem-123"
802+
assert result["id"] == "mem-123"
803+
assert result["streamDeliveryResources"] == delivery_config
804+
805+
args, kwargs = mock_gmcp.update_memory.call_args
806+
assert kwargs["memoryId"] == "mem-123"
807+
assert kwargs["clientToken"] == "12345678-1234-5678-1234-567812345678"
808+
assert kwargs["streamDeliveryResources"] == delivery_config
809+
assert set(kwargs.keys()) == {"memoryId", "clientToken", "streamDeliveryResources"}
810+
811+
812+
def test_update_stream_delivery_config_client_error():
813+
"""Test update_stream_delivery_config raises ClientError."""
814+
with patch("boto3.client"):
815+
client = MemoryClient()
816+
817+
mock_gmcp = MagicMock()
818+
client.gmcp_client = mock_gmcp
819+
820+
mock_gmcp.update_memory.side_effect = ClientError(
821+
{"Error": {"Code": "ResourceNotFoundException", "Message": "Not found"}},
822+
"UpdateMemory",
823+
)
824+
825+
with pytest.raises(ClientError):
826+
client.update_stream_delivery_config(memory_id="mem-999", stream_delivery_resources={})
827+
828+
768829
def test_get_memory_status():
769830
"""Test get_memory_status functionality."""
770831
with patch("boto3.client"):

tests_integ/memory/test_stream_delivery.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ def _make_delivery_config(self, level="FULL_CONTENT"):
5353
]
5454
}
5555

56+
def _get_delivery_level(self, memory_id):
57+
"""Get the current stream delivery level for a memory."""
58+
detail = self.client.gmcp_client.get_memory(memoryId=memory_id)["memory"]
59+
for resource in detail.get("streamDeliveryResources", {}).get("resources", []):
60+
configs = resource.get("kinesis", {}).get("contentConfigurations", [])
61+
if configs:
62+
return configs[0].get("level")
63+
return None
64+
65+
def _wait_for_active(self, memory_id, max_wait=180, poll_interval=10):
66+
"""Poll until memory returns to ACTIVE status."""
67+
deadline = time.time() + max_wait
68+
while time.time() < deadline:
69+
status = self.client.get_memory_status(memory_id)
70+
if status == "ACTIVE":
71+
return
72+
if status == "FAILED":
73+
pytest.fail(f"Memory {memory_id} entered FAILED status")
74+
time.sleep(poll_interval)
75+
pytest.fail(f"Memory {memory_id} did not reach ACTIVE within {max_wait}s")
76+
77+
@pytest.mark.order(1)
5678
def test_stream_delivery_create(self):
5779
"""Create memory with stream delivery config and verify via get_memory."""
5880
delivery_config = self._make_delivery_config("FULL_CONTENT")
@@ -68,3 +90,26 @@ def test_stream_delivery_create(self):
6890
self.memory_ids.append(memory_id)
6991

7092
assert memory["streamDeliveryResources"] == delivery_config
93+
94+
@pytest.mark.order(2)
95+
def test_stream_delivery_update(self):
96+
"""Create with FULL_CONTENT, update to METADATA_ONLY, verify change."""
97+
memory = self.client.create_memory_and_wait(
98+
name=f"{self.test_prefix}_update",
99+
strategies=[],
100+
memory_execution_role_arn=self.execution_role_arn,
101+
stream_delivery_resources=self._make_delivery_config("FULL_CONTENT"),
102+
)
103+
104+
memory_id = memory.get("memoryId", memory.get("id"))
105+
self.memory_ids.append(memory_id)
106+
107+
assert self._get_delivery_level(memory_id) == "FULL_CONTENT"
108+
109+
self.client.update_stream_delivery_config(
110+
memory_id=memory_id,
111+
stream_delivery_resources=self._make_delivery_config("METADATA_ONLY"),
112+
)
113+
self._wait_for_active(memory_id)
114+
115+
assert self._get_delivery_level(memory_id) == "METADATA_ONLY"

0 commit comments

Comments
 (0)