feat: discover existing workflow objects

This commit is contained in:
lda
2026-09-02 05:21:48 +07:00 Verified
parent ef79eae6bf
commit 143e70bcf7
13 changed files with 345 additions and 2 deletions
+4
View File
@@ -6,6 +6,7 @@ from .app import App
from .authoring import EditableWorkflow
from .capabilities import CapabilityResult, CapabilitySummary, RemoteCapability
from .deployments import Deployment, DeploymentValidation
from .discovery import ArtifactSummary, DeploymentSummary, RunSummary
from .errors import (
ArtifactNotFound,
ArtifactVersionConflict,
@@ -33,6 +34,7 @@ __all__ = [
"ArtifactVersionConflict",
"App",
"ArtifactRef",
"ArtifactSummary",
"CapabilityNotFound",
"CapabilityRef",
"CapabilityResult",
@@ -42,11 +44,13 @@ __all__ = [
"DeploymentRequired",
"Deployment",
"DeploymentValidation",
"DeploymentSummary",
"InvalidResponse",
"Page",
"ProtocolError",
"RemoteCapability",
"Run",
"RunSummary",
"RevisionConflict",
"TransportError",
"ValidationFailed",
+34
View File
@@ -12,8 +12,10 @@ import httpx
from wf_api.models import (
CapabilityCallResult,
InspectCapabilityResult,
ListArtifactsResult,
ListCapabilitiesResult,
ListDeploymentsResult,
ListRunsResult,
RunResult,
RunTraceResult,
SaveArtifactResult,
@@ -156,6 +158,23 @@ class PublicErrorWorkflowClientPort:
version=version,
)
async def list_artifacts(
self,
*,
query: str | None = None,
kind: Literal["workflow", "wrapper"] | None = None,
cursor: str | None = None,
limit: int = 50,
) -> ListArtifactsResult:
return await self._invoke(
"workflow.artifacts.list",
self._rpc.list_artifacts,
query=query,
kind=kind,
cursor=cursor,
limit=limit,
)
async def create_artifact_from_plan(
self,
*,
@@ -256,6 +275,21 @@ class PublicErrorWorkflowClientPort:
run_id=run_id,
)
async def list_runs(
self,
*,
status: str | None = None,
cursor: str | None = None,
limit: int = 50,
) -> ListRunsResult:
return await self._invoke(
"workflow.runs.list",
self._rpc.list_runs,
status=status,
cursor=cursor,
limit=limit,
)
async def resume_run(
self,
*,
+80 -1
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Literal
from wf_platform import CapabilityRef, Page, SourceRef
from wf_transport_rpc_http import RpcWorkflowApiClient
@@ -14,10 +14,14 @@ from ._identity import require_response_identity
from .authoring import EditableWorkflow
from .capabilities import CapabilitySummary, RemoteCapability
from .codec import (
decode_artifacts_page,
decode_capabilities_page,
decode_capability_inspect,
decode_deployments,
decode_runs_page,
decode_workflow_artifact,
)
from .discovery import ArtifactSummary, DeploymentSummary, RunSummary
from .errors import InvalidResponse
from .protocols import WorkflowClientPort
from .workflows import WorkflowArtifact
@@ -148,6 +152,81 @@ class App:
total=wire["total"],
)
async def artifacts(
self,
*,
query: str | None = None,
kind: Literal["workflow", "wrapper"] | None = None,
cursor: str | None = None,
limit: int = 50,
) -> Page[ArtifactSummary]:
"""List lightweight saved artifact versions without reconstructing them."""
wire = decode_artifacts_page(
await self._port.list_artifacts(
query=query, kind=kind, cursor=cursor, limit=limit
)
)
return Page(
items=tuple(
ArtifactSummary(
artifact_id=row["artifact_id"],
version=row["version"],
kind=row["kind"],
title=row["display_name"],
description=row["description"],
outcomes=tuple(row["outcomes"]),
required_sources=tuple(row["required_sources"]),
)
for row in wire["nodes"]
),
next_cursor=wire["next_cursor"],
total=wire["total"],
)
async def deployments(self) -> tuple[DeploymentSummary, ...]:
"""List lightweight saved deployment rows."""
wire = decode_deployments(await self._port.list_deployments())
return tuple(
DeploymentSummary(
deployment_id=row["id"],
artifact_id=row["artifact_id"],
artifact_version=row["artifact_version"],
binding_count=row["binding_count"],
drift_policy=row["drift_policy"],
)
for row in wire["deployments"]
)
async def runs(
self,
*,
status: str | None = None,
cursor: str | None = None,
limit: int = 50,
) -> Page[RunSummary]:
"""List lightweight durable-run rows without loading traces."""
wire = decode_runs_page(
await self._port.list_runs(status=status, cursor=cursor, limit=limit)
)
return Page(
items=tuple(
RunSummary(
run_id=row["run_id"],
deployment_id=row["deployment_id"],
artifact_id=row["artifact_id"],
artifact_version=row["artifact_version"],
status=row["status"],
resume_readiness=row["resume_readiness"],
diagnostic_count=row["diagnostic_count"],
created_at=row["created_at"],
updated_at=row["updated_at"],
)
for row in wire["runs"]
),
next_cursor=wire["next_cursor"],
total=wire["total"],
)
def new_workflow(
self,
name: str,
+12
View File
@@ -12,8 +12,10 @@ from wf_api.models import (
CapabilityCallResult,
DependencyDiagnosticPayload,
InspectCapabilityResult,
ListArtifactsResult,
ListCapabilitiesResult,
ListDeploymentsResult,
ListRunsResult,
RawWorkflowPlan,
RunResult,
RunTraceResult,
@@ -114,6 +116,16 @@ def decode_capabilities_page(payload: object) -> ListCapabilitiesResult:
)
def decode_artifacts_page(payload: object) -> ListArtifactsResult:
"""Validate one cursor-paged artifact catalog response."""
return _validate(payload, ListArtifactsResult, "workflow.artifacts.list")
def decode_runs_page(payload: object) -> ListRunsResult:
"""Validate one cursor-paged durable-run response."""
return _validate(payload, ListRunsResult, "workflow.runs.list")
def decode_validate_artifact_plan(payload: object) -> ValidateArtifactPlanResult:
"""Validate a non-persisting artifact-plan response at the client boundary."""
return _validate(
+44
View File
@@ -0,0 +1,44 @@
"""Lightweight immutable rows for discovering existing workflow objects."""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class ArtifactSummary:
"""Catalog identity and display metadata for one artifact version."""
artifact_id: str
version: int
kind: str
title: str
description: str | None
outcomes: tuple[str, ...]
required_sources: tuple[str, ...]
@dataclass(frozen=True, slots=True)
class DeploymentSummary:
"""Identity and binding metadata for one saved deployment."""
deployment_id: str
artifact_id: str
artifact_version: int
binding_count: int
drift_policy: str
@dataclass(frozen=True, slots=True)
class RunSummary:
"""Identity and lifecycle metadata for one durable run."""
run_id: str
deployment_id: str
artifact_id: str
artifact_version: int
status: str
resume_readiness: str
diagnostic_count: int
created_at: str
updated_at: str
+19
View File
@@ -8,8 +8,10 @@ from typing import Any, Literal, Protocol
from wf_api.models import (
CapabilityCallResult,
InspectCapabilityResult,
ListArtifactsResult,
ListCapabilitiesResult,
ListDeploymentsResult,
ListRunsResult,
RunResult,
RunTraceResult,
SaveArtifactResult,
@@ -60,6 +62,15 @@ class WorkflowClientPort(Protocol):
version: int,
) -> WorkflowArtifactPayload: ...
async def list_artifacts(
self,
*,
query: str | None = None,
kind: Literal["workflow", "wrapper"] | None = None,
cursor: str | None = None,
limit: int = 50,
) -> ListArtifactsResult: ...
async def create_artifact_from_plan(
self,
*,
@@ -112,6 +123,14 @@ class WorkflowClientPort(Protocol):
trace_range: TraceRangeLike | None = None,
) -> RunResult: ...
async def list_runs(
self,
*,
status: str | None = None,
cursor: str | None = None,
limit: int = 50,
) -> ListRunsResult: ...
async def inspect_run(self, *, run_id: str) -> RunResult: ...
async def resume_run(