From 71856da36358baf80fdaea08a49f43238a86e90e Mon Sep 17 00:00:00 2001 From: foleydang Date: Fri, 16 Oct 2026 22:35:45 +0800 Subject: [PATCH] fix(agentstudio): send the documented precondition object on memory update The public memory-store docs (and CMA) specify the optimistic lock as a nested {"type": "content_sha256", "content_sha256": ...} object; the control controller flattens it, so the flat form also worked, but the SDK now sends the documented shape: update() takes a `precondition` argument accepting either the sha string (wrapped automatically) or the full object. Verified by e2e against the pre-release environment. Co-Authored-By: Claude Code --- .../agentstudio/resources/memory_stores.py | 12 ++++----- dashscope/agentstudio/types/params.py | 24 +++++++++++++++--- samples/test_agentstudio.py | 4 +-- tests/unit/test_agentstudio_memory_stores.py | 25 ++++++++++++++++--- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/dashscope/agentstudio/resources/memory_stores.py b/dashscope/agentstudio/resources/memory_stores.py index 573084a..df302b1 100644 --- a/dashscope/agentstudio/resources/memory_stores.py +++ b/dashscope/agentstudio/resources/memory_stores.py @@ -221,14 +221,14 @@ def update( *, path: Optional[str] = None, content: Optional[str] = None, - content_sha256: Optional[str] = None, + precondition: Optional[Any] = None, ) -> Memory: """Update a memory (``POST .../memories/{id}``). Mutate ``content``, rename via ``path``, or both — at least one must be given. Pass the sha from the previously retrieved - memory as ``content_sha256`` for optimistic concurrency; a - mismatch returns 409 ``memory_precondition_failed_error``. + memory as ``precondition`` for optimistic concurrency (a + mismatch returns 409 ``memory_precondition_failed_error``). """ if path is None and content is None: raise TypeError( @@ -238,7 +238,7 @@ def update( body = MemoryUpdateParams( path=path, content=content, - content_sha256=content_sha256, + precondition=precondition, ).to_dict() resp = self._client.transport.request( "POST", @@ -576,7 +576,7 @@ async def update( *, path: Optional[str] = None, content: Optional[str] = None, - content_sha256: Optional[str] = None, + precondition: Optional[Any] = None, ) -> Memory: if path is None and content is None: raise TypeError( @@ -586,7 +586,7 @@ async def update( body = MemoryUpdateParams( path=path, content=content, - content_sha256=content_sha256, + precondition=precondition, ).to_dict() resp = await self._client.transport.request( "POST", diff --git a/dashscope/agentstudio/types/params.py b/dashscope/agentstudio/types/params.py index d85faa6..316f0f2 100644 --- a/dashscope/agentstudio/types/params.py +++ b/dashscope/agentstudio/types/params.py @@ -983,13 +983,29 @@ class MemoryCreateParams(BaseModel): class MemoryUpdateParams(BaseModel): """Request body for ``POST /memory_stores/{store_id}/memories/{memory_id}``. - At least one of ``path`` / ``content`` must be given. Passing the - current ``content_sha256`` (from the retrieved memory) enables - optimistic concurrency — a mismatch returns 409 + At least one of ``path`` / ``content`` must be given. ``precondition`` + is the optimistic lock: the sha string from the retrieved memory + (wrapped into the documented ``{"type": "content_sha256", ...}`` + object) or the object itself — a mismatch returns 409 ``memory_precondition_failed_error``. """ - _fields = ("path", "content", "content_sha256") + _fields = ("path", "content", "precondition") + + def __init__( + self, + *, + precondition: Optional[Any] = None, + **kwargs: Any, + ) -> None: + if precondition is not None: + if isinstance(precondition, str): + precondition = { + "type": "content_sha256", + "content_sha256": precondition, + } + kwargs["precondition"] = precondition + super().__init__(**kwargs) class MemoryListParams(BaseModel): diff --git a/samples/test_agentstudio.py b/samples/test_agentstudio.py index 1e3a2b4..a63efaf 100644 --- a/samples/test_agentstudio.py +++ b/samples/test_agentstudio.py @@ -91,12 +91,12 @@ def test_memory_stores(): # Directory nodes ride the page's prefixes field (per page): print(f" prefixes={page.prefixes}") - print("==> updating with optimistic lock") + print("==> updating with optimistic lock (precondition)") updated = client.memory_stores.memories.update( store.id, mem.id, content="All reports use GAAP formatting. ISO-8601 dates.", - content_sha256=mem.content_sha256, + precondition=mem.content_sha256, ) print(f" new sha={updated.content_sha256[:12]}...") diff --git a/tests/unit/test_agentstudio_memory_stores.py b/tests/unit/test_agentstudio_memory_stores.py index b830c2a..9ec2be5 100644 --- a/tests/unit/test_agentstudio_memory_stores.py +++ b/tests/unit/test_agentstudio_memory_stores.py @@ -382,26 +382,43 @@ def test_store_list_page_extra_defaults_empty(client): assert page.prefixes == [] -def test_memory_update_sends_flattened_precondition(client): +def test_memory_update_sends_precondition_object(client): mem = client.memory_stores.memories.update( "memstore_01Hx", "mem_01Hy", content="CORRECTED", - content_sha256="abc123", + precondition="abc123", ) + # A sha string wraps into the documented precondition object. assert client.transport.calls[0]["json"] == { "content": "CORRECTED", - "content_sha256": "abc123", + "precondition": { + "type": "content_sha256", + "content_sha256": "abc123", + }, } assert isinstance(mem, Memory) +def test_memory_update_accepts_precondition_object(client): + client.memory_stores.memories.update( + "memstore_01Hx", + "mem_01Hy", + content="CORRECTED", + precondition={"type": "content_sha256", "content_sha256": "abc"}, + ) + assert client.transport.calls[0]["json"]["precondition"] == { + "type": "content_sha256", + "content_sha256": "abc", + } + + def test_memory_update_requires_path_or_content(client): with pytest.raises(TypeError): client.memory_stores.memories.update( "memstore_01Hx", "mem_01Hy", - content_sha256="abc123", + precondition="abc123", )