refactor: group rpc transport client and methods

This commit is contained in:
lda
2026-06-08 21:44:41 +07:00 Verified
parent 7582c1d6fb
commit c39d361873
25 changed files with 104 additions and 114 deletions
@@ -0,0 +1,37 @@
from __future__ import annotations
from dataclasses import dataclass
from .admin import RpcAdminClientMixin
from .artifacts import RpcArtifactClientMixin
from .base import RpcClientTransport
from .capabilities import RpcCapabilityClientMixin
from .deployments import RpcDeploymentClientMixin
from .drafts import RpcDraftClientMixin
from .runs import RpcRunClientMixin
from .source_registry import RpcSourceRegistryClientMixin
from .sources import RpcSourceAdminClientMixin
@dataclass(slots=True)
class RpcWorkflowApiClient(
RpcClientTransport,
RpcCapabilityClientMixin,
RpcDraftClientMixin,
RpcArtifactClientMixin,
RpcDeploymentClientMixin,
RpcRunClientMixin,
RpcSourceAdminClientMixin,
RpcSourceRegistryClientMixin,
RpcAdminClientMixin,
):
"""WorkflowApiSurface implementation backed by JSON-RPC HTTP calls.
The inheritance order is intentional: `RpcClientTransport` owns dataclass
fields and `_call`; domain mixins are stateless method groups that may only
depend on `_call`. If a domain needs state or lifecycle later, prefer a
composed domain client instead of adding fields to a mixin.
"""
__all__ = ["RpcWorkflowApiClient"]
+52
View File
@@ -0,0 +1,52 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
class RpcAdminClientMixin:
"""JSON-RPC implementation of read-only admin/config surface methods."""
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
async def list_connections(self) -> dict[str, Any]:
return await self._call("workflow.admin.connections.list", {})
async def get_connection_statuses(self) -> dict[str, Any]:
return await self._call("workflow.admin.connection_statuses.list", {})
async def list_events(self) -> dict[str, Any]:
return await self._call("workflow.admin.events.list", {})
async def list_auth_records(self) -> dict[str, Any]:
return await self._call("workflow.admin.auth.list", {})
async def inspect_auth_record(self, auth_ref: str) -> dict[str, Any]:
return await self._call(
"workflow.admin.auth.inspect",
{"auth_ref": auth_ref},
)
async def save_auth_record(
self,
*,
auth_ref: str,
scheme: str,
payload: Mapping[str, object],
metadata: Mapping[str, object] | None = None,
) -> dict[str, Any]:
return await self._call(
"workflow.admin.auth.save",
{
"auth_ref": auth_ref,
"scheme": scheme,
"payload": dict(payload),
"metadata": dict(metadata or {}),
},
)
async def delete_auth_record(self, auth_ref: str) -> dict[str, Any]:
return await self._call(
"workflow.admin.auth.delete",
{"auth_ref": auth_ref},
)
@@ -0,0 +1,38 @@
from __future__ import annotations
from typing import Any, Literal
class RpcArtifactClientMixin:
"""JSON-RPC implementation of workflow artifact surface methods."""
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
async def list_artifacts(
self,
*,
query: str | None = None,
kind: Literal["workflow", "wrapper"] | None = None,
cursor: str | None = None,
limit: int = 50,
) -> dict[str, Any]:
return await self._call(
"workflow.artifacts.list",
{
"query": query,
"kind": kind,
"cursor": cursor,
"limit": limit,
},
)
async def inspect_artifact(
self, *, artifact_id: str, version: int
) -> dict[str, Any]:
return await self._call(
"workflow.artifacts.inspect",
{"artifact_id": artifact_id, "version": version},
)
async def save_artifact(self, artifact: dict[str, Any]) -> dict[str, Any]:
return await self._call("workflow.artifacts.save", {"artifact": artifact})
+46
View File
@@ -0,0 +1,46 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from uuid import uuid4
import httpx
@dataclass(slots=True)
class RpcClientTransport:
"""Shared JSON-RPC request plumbing for workflow RPC client mixins.
If `http_client` is provided, that client's own timeout configuration wins;
`timeout_seconds` is only used when this transport creates an `AsyncClient`.
"""
url: str
timeout_seconds: float = 30.0
http_client: httpx.AsyncClient | None = None
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
request = {
"jsonrpc": "2.0",
"id": uuid4().hex,
"method": method,
"params": params,
}
if self.http_client is None:
async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
response = await client.post(self.url, json=request)
else:
response = await self.http_client.post(self.url, json=request)
response.raise_for_status()
payload = response.json()
if "error" in payload:
error = payload["error"]
message = error.get("message", "JSON-RPC error")
data = error.get("data")
if isinstance(data, dict) and data.get("message"):
message = f"{message}: {data['message']}"
raise RuntimeError(message)
result = payload.get("result")
if not isinstance(result, dict):
raise RuntimeError("JSON-RPC response result must be an object")
return result
@@ -0,0 +1,33 @@
from __future__ import annotations
from typing import Any
class RpcCapabilityClientMixin:
"""JSON-RPC implementation of workflow capability surface methods."""
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
async def list_capabilities(
self,
*,
query: str | None = None,
source_id: str | None = None,
cursor: str | None = None,
limit: int = 50,
) -> dict[str, Any]:
return await self._call(
"workflow.capabilities.list",
{
"query": query,
"source_id": source_id,
"cursor": cursor,
"limit": limit,
},
)
async def inspect_capability(self, *, qualified_name: str) -> dict[str, Any]:
return await self._call(
"workflow.capabilities.inspect",
{"qualified_name": qualified_name},
)
@@ -0,0 +1,35 @@
from __future__ import annotations
from typing import Any
class RpcDeploymentClientMixin:
"""JSON-RPC implementation of workflow deployment surface methods."""
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
async def list_deployments(self) -> dict[str, Any]:
return await self._call("workflow.deployments.list", {})
async def inspect_deployment(self, *, deployment_id: str) -> dict[str, Any]:
return await self._call(
"workflow.deployments.inspect",
{"deployment_id": deployment_id},
)
async def validate_deployment(
self, *, deployment_id: str, live_check: bool = False
) -> dict[str, Any]:
return await self._call(
"workflow.deployments.validate",
{"deployment_id": deployment_id, "live_check": live_check},
)
async def save_deployment(self, deployment: dict[str, Any]) -> dict[str, Any]:
return await self._call("workflow.deployments.save", {"deployment": deployment})
async def delete_deployment(self, *, deployment_id: str) -> dict[str, Any]:
return await self._call(
"workflow.deployments.delete",
{"deployment_id": deployment_id},
)
+138
View File
@@ -0,0 +1,138 @@
from __future__ import annotations
from collections.abc import Sequence
from typing import Any, Literal
class RpcDraftClientMixin:
"""JSON-RPC implementation of workflow draft workspace surface methods."""
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
async def list_draft_workspaces(self) -> dict[str, Any]:
return await self._call("workflow.draft_workspaces.list", {})
async def get_draft_workspace(
self,
*,
workspace_id: str,
include_draft: bool = False,
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.get",
{"workspace_id": workspace_id, "include_draft": include_draft},
)
async def create_draft_workspace_from_capability(
self,
*,
workspace_id: str,
capability_name: str,
name: str | None = None,
title: str | None = None,
input_schema: dict[str, Any] | None = None,
state_schema: dict[str, Any] | None = None,
output_schema: dict[str, Any] | None = None,
input: Sequence[Any] | None = None,
output: Sequence[Any] | None = None,
input_map: dict[str, str] | None = None,
output_map: dict[str, str] | None = None,
error_message_source: Any | None = None,
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.create_from_capability",
{
"workspace_id": workspace_id,
"capability_name": capability_name,
"name": name,
"title": title,
"input_schema": input_schema,
"state_schema": state_schema,
"output_schema": output_schema,
"input": input,
"output": output,
"input_map": input_map,
"output_map": output_map,
"error_message_source": error_message_source,
},
)
async def patch_draft_workspace(
self,
*,
workspace_id: str,
revision: int,
patch: list[dict[str, Any]],
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.patch",
{"workspace_id": workspace_id, "revision": revision, "patch": patch},
)
async def validate_draft_workspace(
self,
*,
workspace_id: str,
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.validate",
{"workspace_id": workspace_id},
)
async def create_artifact_from_workspace(
self,
*,
workspace_id: str,
artifact_id: str,
version: int,
title: str,
outcomes: Sequence[str],
kind: Literal["workflow", "wrapper"] = "workflow",
description: str | None = None,
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.create_artifact",
{
"workspace_id": workspace_id,
"artifact_id": artifact_id,
"version": version,
"title": title,
"outcomes": list(outcomes),
"kind": kind,
"description": description,
"required_capabilities": required_capabilities,
"source_bindings": source_bindings,
"created_from_catalog_version": created_from_catalog_version,
},
)
async def create_wrapper_from_workspace(
self,
*,
workspace_id: str,
artifact_id: str,
version: int,
title: str,
outcomes: Sequence[str],
description: str | None = None,
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
return await self._call(
"workflow.draft_workspaces.create_wrapper",
{
"workspace_id": workspace_id,
"artifact_id": artifact_id,
"version": version,
"title": title,
"outcomes": list(outcomes),
"description": description,
"required_capabilities": required_capabilities,
"source_bindings": source_bindings,
"created_from_catalog_version": created_from_catalog_version,
},
)
+70
View File
@@ -0,0 +1,70 @@
from __future__ import annotations
from typing import Any
from wf_api.runs import TraceRangeLike
class RpcRunClientMixin:
"""JSON-RPC implementation of workflow run lifecycle surface methods."""
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
async def run_deployment(
self,
*,
deployment_id: str,
workflow_input: dict[str, Any],
trace_range: TraceRangeLike | None = None,
) -> dict[str, Any]:
return await self._call(
"workflow.runs.start",
{
"deployment_id": deployment_id,
"workflow_input": workflow_input,
"trace_range": _trace_range_payload(trace_range),
},
)
async def resume_run(
self,
*,
run_id: str,
resume_payload: dict[str, Any],
resume_outcome: str = "submitted",
trace_range: TraceRangeLike | None = None,
) -> dict[str, Any]:
return await self._call(
"workflow.runs.resume",
{
"run_id": run_id,
"resume_payload": resume_payload,
"resume_outcome": resume_outcome,
"trace_range": _trace_range_payload(trace_range),
},
)
async def inspect_run(self, *, run_id: str) -> dict[str, Any]:
return await self._call("workflow.runs.inspect", {"run_id": run_id})
async def read_run_trace(
self,
*,
run_id: str,
trace_range: TraceRangeLike,
) -> dict[str, Any]:
return await self._call(
"workflow.runs.trace",
{
"run_id": run_id,
"trace_range": _trace_range_payload(trace_range),
},
)
def _trace_range_payload(
trace_range: TraceRangeLike | None,
) -> dict[str, int] | None:
if trace_range is None:
return None
return {"start": trace_range.start, "limit": trace_range.limit}
@@ -0,0 +1,87 @@
from __future__ import annotations
from typing import Any
class RpcSourceRegistryClientMixin:
"""JSON-RPC implementation of source registry surface methods."""
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
async def list_registry_entries(
self,
*,
cursor: str | None = None,
limit: int = 50,
) -> dict[str, Any]:
return await self._call(
"workflow.admin.source_registry.list",
{"cursor": cursor, "limit": limit},
)
async def inspect_registry_entry(
self,
*,
source_id: str,
) -> dict[str, Any]:
return await self._call(
"workflow.admin.source_registry.inspect",
{"source_id": source_id},
)
async def add_registry_entry(
self,
*,
entry: dict[str, Any],
) -> dict[str, Any]:
return await self._call(
"workflow.admin.source_registry.add",
{"entry": entry},
)
async def update_registry_entry(
self,
*,
source_id: str,
patch: dict[str, Any],
) -> dict[str, Any]:
return await self._call(
"workflow.admin.source_registry.update",
{"source_id": source_id, "patch": patch},
)
async def enable_registry_entry(
self,
*,
source_id: str,
) -> dict[str, Any]:
return await self._call(
"workflow.admin.source_registry.enable",
{"source_id": source_id},
)
async def disable_registry_entry(
self,
*,
source_id: str,
) -> dict[str, Any]:
return await self._call(
"workflow.admin.source_registry.disable",
{"source_id": source_id},
)
async def remove_registry_entry(
self,
*,
source_id: str,
) -> dict[str, Any]:
return await self._call(
"workflow.admin.source_registry.remove",
{"source_id": source_id},
)
async def apply_registry_changes(self) -> dict[str, Any]:
return await self._call(
"workflow.admin.source_registry.apply",
{},
)
@@ -0,0 +1,29 @@
from __future__ import annotations
from typing import Any
class RpcSourceAdminClientMixin:
"""JSON-RPC implementation of read-only source admin surface methods."""
async def _call(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ...
async def list_sources(
self,
*,
cursor: str | None = None,
limit: int = 50,
) -> dict[str, Any]:
return await self._call(
"workflow.sources.list",
{
"cursor": cursor,
"limit": limit,
},
)
async def inspect_source(self, *, source_id: str) -> dict[str, Any]:
return await self._call(
"workflow.sources.inspect",
{"source_id": source_id},
)