feat: expose draft document replacement over rpc

This commit is contained in:
lda
2026-07-29 09:31:22 +07:00 Verified
parent 725b313ab9
commit 2e98ece805
6 changed files with 133 additions and 0 deletions
+2
View File
@@ -34,6 +34,7 @@ from .models import (
PatchDraftParams,
PatchDraftWorkspaceParams,
ReadRunTraceParams,
ReplaceDraftWorkspaceDocumentParams,
RemoveDraftBindingParams,
RemoveDraftRouteParams,
RemoveDraftStepParams,
@@ -90,6 +91,7 @@ __all__ = [
"PatchDraftParams",
"PatchDraftWorkspaceParams",
"ReadRunTraceParams",
"ReplaceDraftWorkspaceDocumentParams",
"RemoveDraftBindingParams",
"RemoveDraftRouteParams",
"RemoveDraftStepParams",
@@ -99,6 +99,22 @@ class RpcDraftClientMixin:
{"workspace_id": workspace_id, "revision": revision, "patch": patch},
)
async def replace_draft_workspace_document(
self: RpcCaller,
*,
workspace_id: str,
revision: int,
draft: dict[str, Any],
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.replace_document",
{
"workspace_id": workspace_id,
"revision": revision,
"draft": draft,
},
)
async def set_draft_name(
self: RpcCaller,
*,
@@ -24,6 +24,7 @@ from ..models import (
ListDraftWorkspacesParams,
PatchDraftParams,
PatchDraftWorkspaceParams,
ReplaceDraftWorkspaceDocumentParams,
RemoveDraftBindingParams,
RemoveDraftRouteParams,
RemoveDraftStepParams,
@@ -147,6 +148,22 @@ def register_methods(
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
raise_workflow_rpc_error(exc)
@entrypoint.method(
name="workflow.draft_workspaces.replace_document",
errors=[WorkflowRpcError],
)
async def workflow_draft_workspaces_replace_document(
params: ReplaceDraftWorkspaceDocumentParams = RpcParams(),
) -> dict[str, Any]:
try:
return await server.api.replace_draft_workspace_document(
workspace_id=params.workspace_id,
revision=params.revision,
draft=params.draft,
)
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
raise_workflow_rpc_error(exc)
@entrypoint.method(
name="workflow.draft_workspaces.set_name", errors=[WorkflowRpcError]
)
+6
View File
@@ -153,6 +153,12 @@ class PatchDraftWorkspaceParams(RpcParamsModel):
patch: list[dict[str, Any]]
class ReplaceDraftWorkspaceDocumentParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
revision: int = Field(ge=1)
draft: dict[str, Any]
class SetDraftNameParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
revision: int = Field(ge=1)
+54
View File
@@ -413,6 +413,60 @@ async def test_rpc_draft_workspace_methods(tmp_path) -> None:
assert artifact["result"]["artifact_id"] == "remote_artifact"
async def test_rpc_replace_document_replaces_complete_draft_workspace(
tmp_path,
) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
await _rpc(
client,
"workflow.draft_workspaces.create_empty",
{"workspace_id": "report", "name": "initial"},
)
initial = await _rpc(
client,
"workflow.draft_workspaces.get",
{"workspace_id": "report", "include_draft": True},
)
replacement = {**initial["result"]["draft"], "name": "replacement"}
replaced = await _rpc(
client,
"workflow.draft_workspaces.replace_document",
{
"workspace_id": "report",
"revision": 1,
"draft": replacement,
},
)
inspected = await _rpc(
client,
"workflow.draft_workspaces.get",
{"workspace_id": "report", "include_draft": True},
)
malformed = await _rpc(
client,
"workflow.draft_workspaces.replace_document",
{
"workspace_id": "report",
"revision": 2,
"draft": [],
},
)
after_malformed = await _rpc(
client,
"workflow.draft_workspaces.get",
{"workspace_id": "report", "include_draft": True},
)
assert replaced["result"]["revision"] == 2
assert inspected["result"]["draft"] == replacement
assert malformed["error"]["code"] == -32602
assert after_malformed["result"] == inspected["result"]
async def test_rpc_draft_workspace_lifecycle_methods(tmp_path) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)
@@ -399,6 +399,44 @@ async def test_rpc_client_sends_exact_draft_lifecycle_payloads() -> None:
]
async def test_rpc_client_sends_exact_replace_document_payload() -> None:
calls: list[dict[str, Any]] = []
class Client(RpcDraftClientMixin):
async def _call(self, method: str, params: dict[str, object]):
calls.append({"method": method, "params": params})
return {"revision": 5}
client = Client()
draft = {
"name": "report",
"input_schema": {"type": "object", "properties": {}},
"state_schema": {"type": "object", "properties": {}},
"output_schema": {"type": "object", "properties": {}},
"start": "finish",
"steps": {"finish": {"end": {}}},
"routes": {},
}
result = await client.replace_draft_workspace_document(
workspace_id="report",
revision=4,
draft=draft,
)
assert result["revision"] == 5
assert calls == [
{
"method": "workflow.draft_workspaces.replace_document",
"params": {
"workspace_id": "report",
"revision": 4,
"draft": draft,
},
}
]
async def test_rpc_client_builds_capability_free_draft_lifecycle(tmp_path) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)