feat: type exceptional draft results
This commit is contained in:
@@ -72,12 +72,12 @@
|
||||
complete OpenRPC document for all 70 registered methods. Request payloads
|
||||
retain useful Pydantic schemas, so OpenRPC is a viable transport input.
|
||||
- Typed-result slices now give `workflow.health`, all artifact, deployment,
|
||||
and run operations, and 26 uniform persisted draft-workspace operations
|
||||
named transport-neutral result schemas: 42 of 70 methods. The remaining 28
|
||||
success results still collapse to generic objects, including exceptional
|
||||
draft compile/save shapes and capability/source/admin operations. Continue
|
||||
introducing operation result DTOs before adopting generated TypeScript
|
||||
contracts.
|
||||
and run operations, every persisted draft-workspace operation, and the
|
||||
capability-bootstrap alias named transport-neutral result schemas: 47 of
|
||||
70 methods. The remaining 23 success results still collapse to generic
|
||||
objects across capability discovery/call, stateless draft patch/validate,
|
||||
source discovery, and source-registry/admin operations. Continue introducing
|
||||
operation result DTOs before adopting generated TypeScript contracts.
|
||||
- The stock `@open-rpc/generator` TypeScript client is not suitable here. It
|
||||
exhausted a 4 GB Node heap on the full contract and emitted invalid dotted
|
||||
class members plus `any` results for a minimal `workflow.health` contract.
|
||||
|
||||
+37
-27
@@ -25,11 +25,14 @@ from .capability_requirements import observed_node_specs
|
||||
from .drafts import WorkflowDraftApi
|
||||
from .listing import matches_query, paged_list_payload
|
||||
from .models import (
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
DeleteArtifactResult,
|
||||
JsonProjector,
|
||||
ListArtifactsResult,
|
||||
RawWorkflowPlan,
|
||||
SaveArtifactResult,
|
||||
SavedDraftArtifactResult,
|
||||
UnsavedDraftArtifactResult,
|
||||
WorkflowArtifactPayload,
|
||||
)
|
||||
from .operation_context import WorkflowOperationContext
|
||||
@@ -38,6 +41,8 @@ _PROJECT_ARTIFACT = JsonProjector(WorkflowArtifactPayload)
|
||||
_PROJECT_ARTIFACT_LIST = JsonProjector(ListArtifactsResult)
|
||||
_PROJECT_ARTIFACT_SAVE = JsonProjector(SaveArtifactResult)
|
||||
_PROJECT_ARTIFACT_DELETE = JsonProjector(DeleteArtifactResult)
|
||||
_PROJECT_UNSAVED_DRAFT_ARTIFACT = JsonProjector(UnsavedDraftArtifactResult)
|
||||
_PROJECT_SAVED_DRAFT_ARTIFACT = JsonProjector(SavedDraftArtifactResult)
|
||||
|
||||
|
||||
class WorkflowArtifactApi:
|
||||
@@ -95,9 +100,7 @@ class WorkflowArtifactApi:
|
||||
paged_list_payload("nodes", entries, cursor=cursor, limit=limit)
|
||||
)
|
||||
|
||||
async def save_artifact(
|
||||
self, artifact: dict[str, Any]
|
||||
) -> SaveArtifactResult:
|
||||
async def save_artifact(self, artifact: dict[str, Any]) -> SaveArtifactResult:
|
||||
workflow_artifact = WorkflowArtifact.model_validate(artifact)
|
||||
self._artifact_store().save_artifact(workflow_artifact)
|
||||
self.context.events.record_workflow_event(
|
||||
@@ -182,7 +185,7 @@ class WorkflowArtifactApi:
|
||||
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]:
|
||||
) -> SavedDraftArtifactResult:
|
||||
from wf_artifacts import compile_workflow_draft
|
||||
|
||||
plan = compile_workflow_draft(draft)
|
||||
@@ -202,6 +205,24 @@ class WorkflowArtifactApi:
|
||||
observed_node_specs=observed_node_specs(self.context),
|
||||
created_from_catalog_version=created_from_catalog_version,
|
||||
)
|
||||
required_sources = _binding_required_sources(
|
||||
workflow_artifact.required_capability_map(),
|
||||
self.context.specs.capability_sources,
|
||||
)
|
||||
result = _PROJECT_SAVED_DRAFT_ARTIFACT(
|
||||
{
|
||||
"artifact_id": workflow_artifact.id,
|
||||
"version": workflow_artifact.version,
|
||||
"saved": True,
|
||||
"required_logical_sources": required_sources,
|
||||
"suggested_bindings": _suggested_self_bindings(
|
||||
required_sources,
|
||||
self.context.specs.capability_sources,
|
||||
),
|
||||
}
|
||||
)
|
||||
# Validate the public result before persistence so a projection bug
|
||||
# cannot report failure after the artifact has already been saved.
|
||||
self._artifact_store().save_artifact(workflow_artifact)
|
||||
self.context.events.record_workflow_event(
|
||||
"workflow_artifact_saved",
|
||||
@@ -212,20 +233,7 @@ class WorkflowArtifactApi:
|
||||
"created_from_draft": True,
|
||||
},
|
||||
)
|
||||
required_sources = _binding_required_sources(
|
||||
workflow_artifact.required_capability_map(),
|
||||
self.context.specs.capability_sources,
|
||||
)
|
||||
return {
|
||||
"artifact_id": workflow_artifact.id,
|
||||
"version": workflow_artifact.version,
|
||||
"saved": True,
|
||||
"required_logical_sources": required_sources,
|
||||
"suggested_bindings": _suggested_self_bindings(
|
||||
required_sources,
|
||||
self.context.specs.capability_sources,
|
||||
),
|
||||
}
|
||||
return result
|
||||
|
||||
async def create_artifact_from_workspace(
|
||||
self,
|
||||
@@ -240,20 +248,22 @@ class WorkflowArtifactApi:
|
||||
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]:
|
||||
) -> CreateArtifactFromWorkspaceResult:
|
||||
store = self.context.draft_workspace_store
|
||||
if store is None:
|
||||
raise KeyError("draft workspace store is not configured")
|
||||
workspace = store.get_workspace(workspace_id)
|
||||
validation = await self.drafts.validate_draft(draft=workspace.draft)
|
||||
if validation["status"] != "valid":
|
||||
return {
|
||||
"saved": False,
|
||||
"workspace_id": workspace_id,
|
||||
"revision": workspace.revision,
|
||||
"status": validation["status"],
|
||||
"diagnostics": validation["diagnostics"],
|
||||
}
|
||||
return _PROJECT_UNSAVED_DRAFT_ARTIFACT(
|
||||
{
|
||||
"saved": False,
|
||||
"workspace_id": workspace_id,
|
||||
"revision": workspace.revision,
|
||||
"status": validation["status"],
|
||||
"diagnostics": validation["diagnostics"],
|
||||
}
|
||||
)
|
||||
return await self.create_artifact_from_draft(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
@@ -279,7 +289,7 @@ class WorkflowArtifactApi:
|
||||
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]:
|
||||
) -> CreateArtifactFromWorkspaceResult:
|
||||
"""Save the current draft workspace as a callable wrapper artifact."""
|
||||
return await self.create_artifact_from_workspace(
|
||||
workspace_id=workspace_id,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from wf_artifacts import (
|
||||
DependencyDiagnostic,
|
||||
@@ -21,6 +21,12 @@ from .capability_requirements import required_capability_payloads
|
||||
from .draft_authoring import WorkflowDraftAuthoringApi
|
||||
from .drafts import WorkflowDraftApi
|
||||
from .listing import matches_query, paged_list_payload
|
||||
from .models import (
|
||||
CreateDraftWorkspaceFromCapabilityResult,
|
||||
JsonProjector,
|
||||
NextActionsPayload,
|
||||
WrapperAuthoringHintsPayload,
|
||||
)
|
||||
from .next_actions import NextActions
|
||||
from .operation_context import WorkflowOperationContext
|
||||
from .refs import parse_workflow_surface_capability_id
|
||||
@@ -30,6 +36,8 @@ from .wrapper_hints import (
|
||||
wrapper_hints_for_capability,
|
||||
)
|
||||
|
||||
_PROJECT_WRAPPER_HINTS = JsonProjector(WrapperAuthoringHintsPayload)
|
||||
|
||||
|
||||
def _schema_field_names(schema: dict[str, Any]) -> list[str]:
|
||||
"""Return top-level JSON object property names for compact discovery rows."""
|
||||
@@ -351,10 +359,12 @@ class WorkflowCapabilityApi:
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: str | GraphSourcePath | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> CreateDraftWorkspaceFromCapabilityResult:
|
||||
"""Create a patchable draft workspace from inspect_capability hints."""
|
||||
capability = await self.inspect_capability(qualified_name=capability_name)
|
||||
hints = capability["wrapper_hints"]
|
||||
# Validate capability-derived guidance before workspace creation. The
|
||||
# workspace result is already projected by the draft-workspace API.
|
||||
hints = _PROJECT_WRAPPER_HINTS(capability["wrapper_hints"])
|
||||
result = await self.draft_authoring.create_minimal_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
name=name or _draft_name_from_capability(capability_name),
|
||||
@@ -371,12 +381,21 @@ class WorkflowCapabilityApi:
|
||||
error_message_source=error_message_source,
|
||||
title=title,
|
||||
)
|
||||
return {
|
||||
**result,
|
||||
"wrapper_hints": hints,
|
||||
"next_actions": NextActions.from_wrapper_hints(
|
||||
next_actions = cast(
|
||||
NextActionsPayload,
|
||||
NextActions.from_wrapper_hints(
|
||||
workspace_id=workspace_id,
|
||||
revision=int(result["revision"]),
|
||||
hints=hints,
|
||||
hints=dict(hints),
|
||||
).model_dump(mode="json"),
|
||||
}
|
||||
)
|
||||
# Both merged extensions are independently validated or model-dumped,
|
||||
# while the workspace payload was projected by the mutating API.
|
||||
return cast(
|
||||
CreateDraftWorkspaceFromCapabilityResult,
|
||||
{
|
||||
**result,
|
||||
"wrapper_hints": hints,
|
||||
"next_actions": next_actions,
|
||||
},
|
||||
)
|
||||
|
||||
+24
-13
@@ -53,8 +53,11 @@ from .draft_payloads import (
|
||||
output_bindings_payload as _draft_output_bindings_payload,
|
||||
)
|
||||
from .models import (
|
||||
CompileDraftWorkspaceResult,
|
||||
CompileDraftWorkspaceSuccess,
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
InvalidDraftResult,
|
||||
JsonProjector,
|
||||
ListDraftWorkspacesResult,
|
||||
)
|
||||
@@ -64,6 +67,8 @@ from .schema_projection import project_property_to_schema_path, schema_path_exis
|
||||
_PROJECT_DRAFT_WORKSPACE = JsonProjector(DraftWorkspaceResult)
|
||||
_PROJECT_DRAFT_WORKSPACE_LIST = JsonProjector(ListDraftWorkspacesResult)
|
||||
_PROJECT_DRAFT_WORKSPACE_DELETE = JsonProjector(DeleteDraftWorkspaceResult)
|
||||
_PROJECT_DRAFT_COMPILE = JsonProjector(CompileDraftWorkspaceSuccess)
|
||||
_PROJECT_INVALID_DRAFT = JsonProjector(InvalidDraftResult)
|
||||
|
||||
|
||||
def _empty_object_schema() -> dict[str, Any]:
|
||||
@@ -176,18 +181,22 @@ class WorkflowDraftApi:
|
||||
node_defs=self._node_defs_for_draft(draft),
|
||||
)
|
||||
|
||||
async def compile_draft(self, *, draft: dict[str, Any]) -> dict[str, Any]:
|
||||
async def compile_draft(
|
||||
self, *, draft: dict[str, Any]
|
||||
) -> CompileDraftWorkspaceSuccess:
|
||||
plan = compile_workflow_draft(draft)
|
||||
return {
|
||||
"compiled_plan": plan,
|
||||
"required_capabilities": required_capability_payloads(
|
||||
required_capabilities_for_plan(
|
||||
plan,
|
||||
source_bindings=None,
|
||||
context=self.context,
|
||||
)
|
||||
),
|
||||
}
|
||||
return _PROJECT_DRAFT_COMPILE(
|
||||
{
|
||||
"compiled_plan": plan,
|
||||
"required_capabilities": required_capability_payloads(
|
||||
required_capabilities_for_plan(
|
||||
plan,
|
||||
source_bindings=None,
|
||||
context=self.context,
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
async def patch_draft(
|
||||
self,
|
||||
@@ -331,12 +340,14 @@ class WorkflowDraftApi:
|
||||
get_draft_workspace_record(store, workspace_id=workspace_id)
|
||||
)
|
||||
|
||||
async def compile_draft_workspace(self, *, workspace_id: str) -> dict[str, Any]:
|
||||
async def compile_draft_workspace(
|
||||
self, *, workspace_id: str
|
||||
) -> CompileDraftWorkspaceResult:
|
||||
"""Compile a stored draft workspace without mutating it."""
|
||||
workspace = self._draft_store().get_workspace(workspace_id)
|
||||
validation = await self.validate_draft(draft=workspace.draft)
|
||||
if validation["status"] != "valid":
|
||||
return validation
|
||||
return _PROJECT_INVALID_DRAFT(validation)
|
||||
return await self.compile_draft(draft=workspace.draft)
|
||||
|
||||
async def patch_draft_workspace(
|
||||
|
||||
@@ -35,11 +35,21 @@ from .deployments import (
|
||||
WorkflowDeploymentPayload,
|
||||
)
|
||||
from .drafts import (
|
||||
CompileDraftWorkspaceResult,
|
||||
CompileDraftWorkspaceSuccess,
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
CreateDraftWorkspaceFromCapabilityResult,
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftDiagnosticPayload,
|
||||
DraftWorkspaceResult,
|
||||
DraftWorkspaceSummary,
|
||||
InvalidDraftResult,
|
||||
ListDraftWorkspacesResult,
|
||||
SavedDraftArtifactResult,
|
||||
UnsavedDraftArtifactResult,
|
||||
WrapperAuthoringHintsPayload,
|
||||
WrapperMissingDecisionPayload,
|
||||
WrapperOutcomeCandidatePayload,
|
||||
)
|
||||
from .runs import (
|
||||
InterruptPayload,
|
||||
@@ -60,6 +70,10 @@ __all__ = [
|
||||
"ArtifactKindPayload",
|
||||
"CapabilityKindPayload",
|
||||
"CapabilityRefPayload",
|
||||
"CompileDraftWorkspaceResult",
|
||||
"CompileDraftWorkspaceSuccess",
|
||||
"CreateArtifactFromWorkspaceResult",
|
||||
"CreateDraftWorkspaceFromCapabilityResult",
|
||||
"DeleteArtifactResult",
|
||||
"DeleteDeploymentResult",
|
||||
"DeleteDraftWorkspaceResult",
|
||||
@@ -72,6 +86,7 @@ __all__ = [
|
||||
"GuidedResultPayload",
|
||||
"InterruptPayload",
|
||||
"InterruptRoutePayload",
|
||||
"InvalidDraftResult",
|
||||
"JsonObject",
|
||||
"JsonProjector",
|
||||
"JsonSchema",
|
||||
@@ -90,12 +105,17 @@ __all__ = [
|
||||
"RunTraceResult",
|
||||
"RequiredCapabilityPayload",
|
||||
"SaveArtifactResult",
|
||||
"SavedDraftArtifactResult",
|
||||
"SaveDeploymentResult",
|
||||
"SourceBindingPayload",
|
||||
"TraceRange",
|
||||
"TraceEntryPayload",
|
||||
"UnsavedDraftArtifactResult",
|
||||
"ValidateDeploymentResult",
|
||||
"WorkflowDeploymentPayload",
|
||||
"WorkflowArtifactPayload",
|
||||
"WorkflowRefPayload",
|
||||
"WrapperAuthoringHintsPayload",
|
||||
"WrapperMissingDecisionPayload",
|
||||
"WrapperOutcomeCandidatePayload",
|
||||
]
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
from typing import Any, Literal, NotRequired, TypedDict
|
||||
|
||||
from .common import JsonObject
|
||||
from .artifacts import RequiredCapabilityPayload
|
||||
from .common import JsonObject, NextActionsPayload
|
||||
|
||||
|
||||
class DraftDiagnosticPayload(TypedDict):
|
||||
@@ -51,3 +52,96 @@ class DeleteDraftWorkspaceResult(TypedDict):
|
||||
workspace_id: str
|
||||
deleted: bool
|
||||
status: Literal["deleted", "not_found"]
|
||||
|
||||
|
||||
class InvalidDraftResult(TypedDict):
|
||||
"""Validation-only result returned when a draft cannot be compiled."""
|
||||
|
||||
status: Literal["invalid"]
|
||||
diagnostics: list[DraftDiagnosticPayload]
|
||||
|
||||
|
||||
class CompileDraftWorkspaceSuccess(TypedDict):
|
||||
"""Compiled raw plan and dependencies for one valid draft workspace."""
|
||||
|
||||
compiled_plan: JsonObject
|
||||
required_capabilities: dict[str, RequiredCapabilityPayload]
|
||||
|
||||
|
||||
type CompileDraftWorkspaceResult = CompileDraftWorkspaceSuccess | InvalidDraftResult
|
||||
|
||||
|
||||
class WrapperOutcomeCandidatePayload(TypedDict):
|
||||
"""Possible wrapper outcome mapping that still requires caller judgment."""
|
||||
|
||||
kind: Literal["boolean_control_field"]
|
||||
source: str
|
||||
candidate_outcomes: list[str]
|
||||
confidence: Literal["high", "medium", "low"]
|
||||
reason: str
|
||||
automatic: bool
|
||||
|
||||
|
||||
class WrapperMissingDecisionPayload(TypedDict):
|
||||
"""One unresolved wrapper-authoring decision."""
|
||||
|
||||
kind: Literal[
|
||||
"choose_output_fields",
|
||||
"review_nested_output",
|
||||
"confirm_boolean_outcomes",
|
||||
"choose_error_mapping",
|
||||
]
|
||||
message: str
|
||||
|
||||
|
||||
class WrapperAuthoringHintsPayload(TypedDict):
|
||||
"""Transport-neutral projection of conservative wrapper scaffolding hints."""
|
||||
|
||||
capability_name: str
|
||||
confidence: Literal["high", "medium", "low"]
|
||||
declared_outcomes: list[str]
|
||||
suggested_wrapper_outcomes: list[str]
|
||||
outcome_policy: Literal[
|
||||
"preserve_declared",
|
||||
"manual_mapping_required",
|
||||
]
|
||||
input_schema: JsonObject
|
||||
state_schema: JsonObject
|
||||
output_schema: JsonObject
|
||||
input_map: dict[str, str]
|
||||
output_map: dict[str, str]
|
||||
outcome_candidates: list[WrapperOutcomeCandidatePayload]
|
||||
missing_decisions: list[WrapperMissingDecisionPayload]
|
||||
notes: list[str]
|
||||
|
||||
|
||||
class CreateDraftWorkspaceFromCapabilityResult(DraftWorkspaceResult):
|
||||
"""Bootstrapped workspace plus the hints and next actions that shaped it."""
|
||||
|
||||
wrapper_hints: WrapperAuthoringHintsPayload
|
||||
next_actions: NextActionsPayload
|
||||
|
||||
|
||||
class UnsavedDraftArtifactResult(TypedDict):
|
||||
"""Validation result when an invalid workspace is not saved as an artifact."""
|
||||
|
||||
saved: Literal[False]
|
||||
workspace_id: str
|
||||
revision: int
|
||||
status: Literal["invalid"]
|
||||
diagnostics: list[DraftDiagnosticPayload]
|
||||
|
||||
|
||||
class SavedDraftArtifactResult(TypedDict):
|
||||
"""Saved artifact result with source-binding guidance derived from a draft."""
|
||||
|
||||
artifact_id: str
|
||||
version: int
|
||||
saved: Literal[True]
|
||||
required_logical_sources: list[str]
|
||||
suggested_bindings: dict[str, str]
|
||||
|
||||
|
||||
type CreateArtifactFromWorkspaceResult = (
|
||||
SavedDraftArtifactResult | UnsavedDraftArtifactResult
|
||||
)
|
||||
|
||||
+11
-6
@@ -14,6 +14,10 @@ from .draft_authoring import RouteSource, WorkflowDraftAuthoringApi
|
||||
from .draft_updates import CapabilityStepUpdate
|
||||
from .drafts import WorkflowDraftApi
|
||||
from .models import (
|
||||
CompileDraftWorkspaceResult,
|
||||
CompileDraftWorkspaceSuccess,
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
CreateDraftWorkspaceFromCapabilityResult,
|
||||
DeleteArtifactResult,
|
||||
DeleteDeploymentResult,
|
||||
DeleteDraftWorkspaceResult,
|
||||
@@ -26,6 +30,7 @@ from .models import (
|
||||
RunResult,
|
||||
RunTraceResult,
|
||||
SaveArtifactResult,
|
||||
SavedDraftArtifactResult,
|
||||
SaveDeploymentResult,
|
||||
ValidateDeploymentResult,
|
||||
WorkflowArtifactPayload,
|
||||
@@ -174,7 +179,7 @@ class WorkflowApi:
|
||||
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]:
|
||||
) -> SavedDraftArtifactResult:
|
||||
return await self.artifacts.create_artifact_from_draft(
|
||||
artifact_id=artifact_id,
|
||||
version=version,
|
||||
@@ -201,7 +206,7 @@ class WorkflowApi:
|
||||
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]:
|
||||
) -> CreateArtifactFromWorkspaceResult:
|
||||
return await self.artifacts.create_artifact_from_workspace(
|
||||
workspace_id=workspace_id,
|
||||
artifact_id=artifact_id,
|
||||
@@ -227,7 +232,7 @@ class WorkflowApi:
|
||||
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]:
|
||||
) -> CreateArtifactFromWorkspaceResult:
|
||||
return await self.artifacts.create_wrapper_from_workspace(
|
||||
workspace_id=workspace_id,
|
||||
artifact_id=artifact_id,
|
||||
@@ -253,7 +258,7 @@ class WorkflowApi:
|
||||
self,
|
||||
*,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> CompileDraftWorkspaceSuccess:
|
||||
return await self.drafts.compile_draft(draft=draft)
|
||||
|
||||
async def patch_draft(
|
||||
@@ -332,7 +337,7 @@ class WorkflowApi:
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> CompileDraftWorkspaceResult:
|
||||
return await self.drafts.compile_draft_workspace(workspace_id=workspace_id)
|
||||
|
||||
async def patch_draft_workspace(
|
||||
@@ -725,7 +730,7 @@ class WorkflowApi:
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> CreateDraftWorkspaceFromCapabilityResult:
|
||||
return await self.capabilities.create_draft_workspace_from_capability(
|
||||
workspace_id=workspace_id,
|
||||
capability_name=capability_name,
|
||||
|
||||
@@ -10,6 +10,9 @@ from wf_core.models.steps import InputBinding, OutputBinding
|
||||
from .draft_authoring import RouteSource
|
||||
from .draft_updates import CapabilityStepUpdate
|
||||
from .models import (
|
||||
CompileDraftWorkspaceResult,
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
CreateDraftWorkspaceFromCapabilityResult,
|
||||
DeleteArtifactResult,
|
||||
DeleteDeploymentResult,
|
||||
DeleteDraftWorkspaceResult,
|
||||
@@ -87,7 +90,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
input_map: dict[str, str] | None = None,
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> CreateDraftWorkspaceFromCapabilityResult: ...
|
||||
|
||||
async def create_empty_draft_workspace(
|
||||
self,
|
||||
@@ -314,7 +317,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> CompileDraftWorkspaceResult: ...
|
||||
|
||||
async def delete_draft_workspace(
|
||||
self,
|
||||
@@ -335,7 +338,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
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]: ...
|
||||
) -> CreateArtifactFromWorkspaceResult: ...
|
||||
|
||||
async def create_wrapper_from_workspace(
|
||||
self,
|
||||
@@ -349,7 +352,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
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]: ...
|
||||
) -> CreateArtifactFromWorkspaceResult: ...
|
||||
|
||||
|
||||
class WorkflowArtifactSurface(Protocol):
|
||||
|
||||
@@ -5,6 +5,9 @@ from typing import Any, Literal, cast
|
||||
|
||||
from wf_api import CapabilityStepUpdate
|
||||
from wf_api.models import (
|
||||
CompileDraftWorkspaceResult,
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
CreateDraftWorkspaceFromCapabilityResult,
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
ListDraftWorkspacesResult,
|
||||
@@ -62,23 +65,26 @@ class RpcDraftClientMixin:
|
||||
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,
|
||||
},
|
||||
) -> CreateDraftWorkspaceFromCapabilityResult:
|
||||
return cast(
|
||||
CreateDraftWorkspaceFromCapabilityResult,
|
||||
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 create_empty_draft_workspace(
|
||||
@@ -546,10 +552,13 @@ class RpcDraftClientMixin:
|
||||
self: RpcCaller,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.draft_workspaces.compile",
|
||||
{"workspace_id": workspace_id},
|
||||
) -> CompileDraftWorkspaceResult:
|
||||
return cast(
|
||||
CompileDraftWorkspaceResult,
|
||||
await self._call(
|
||||
"workflow.draft_workspaces.compile",
|
||||
{"workspace_id": workspace_id},
|
||||
),
|
||||
)
|
||||
|
||||
async def delete_draft_workspace(
|
||||
@@ -578,21 +587,24 @@ class RpcDraftClientMixin:
|
||||
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,
|
||||
},
|
||||
) -> CreateArtifactFromWorkspaceResult:
|
||||
return cast(
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
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(
|
||||
@@ -607,18 +619,21 @@ class RpcDraftClientMixin:
|
||||
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,
|
||||
},
|
||||
) -> CreateArtifactFromWorkspaceResult:
|
||||
return cast(
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
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,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -9,6 +9,9 @@ from typing import Any
|
||||
import fastapi_jsonrpc as jsonrpc
|
||||
|
||||
from wf_api.models import (
|
||||
CompileDraftWorkspaceResult,
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
CreateDraftWorkspaceFromCapabilityResult,
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
ListDraftWorkspacesResult,
|
||||
@@ -65,7 +68,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_drafts_create_from_capability(
|
||||
params: CreateDraftFromCapabilityParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> CreateDraftWorkspaceFromCapabilityResult:
|
||||
try:
|
||||
return await _create_from_capability(server, params)
|
||||
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
|
||||
@@ -116,7 +119,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_create_from_capability(
|
||||
params: CreateDraftFromCapabilityParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> CreateDraftWorkspaceFromCapabilityResult:
|
||||
try:
|
||||
return await _create_from_capability(server, params)
|
||||
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
|
||||
@@ -482,7 +485,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_compile(
|
||||
params: CompileDraftWorkspaceParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> CompileDraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.compile_draft_workspace(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -556,7 +559,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_create_artifact(
|
||||
params: CreateArtifactFromWorkspaceParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> CreateArtifactFromWorkspaceResult:
|
||||
try:
|
||||
return await server.api.create_artifact_from_workspace(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -578,7 +581,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_create_wrapper(
|
||||
params: CreateWrapperFromWorkspaceParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> CreateArtifactFromWorkspaceResult:
|
||||
try:
|
||||
return await server.api.create_wrapper_from_workspace(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -598,7 +601,7 @@ def register_methods(
|
||||
async def _create_from_capability(
|
||||
server: WorkflowServer,
|
||||
params: CreateDraftFromCapabilityParams,
|
||||
) -> dict[str, Any]:
|
||||
) -> CreateDraftWorkspaceFromCapabilityResult:
|
||||
return await server.api.create_draft_workspace_from_capability(
|
||||
workspace_id=params.workspace_id,
|
||||
capability_name=params.capability_name,
|
||||
|
||||
@@ -202,6 +202,95 @@ def test_openrpc_pins_nested_draft_workspace_contract(
|
||||
assert deleted["properties"]["status"]["enum"] == ["deleted", "not_found"]
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_compile_draft_workspace_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
method = _method_by_name(
|
||||
openrpc_document,
|
||||
"workflow.draft_workspaces.compile",
|
||||
)
|
||||
|
||||
assert method["result"]["schema"] == {
|
||||
"$ref": "#/components/schemas/CompileDraftWorkspaceResult"
|
||||
}
|
||||
assert openrpc_document["components"]["schemas"]["CompileDraftWorkspaceResult"] == {
|
||||
"anyOf": [
|
||||
{"$ref": ("#/components/schemas/CompileDraftWorkspaceSuccess")},
|
||||
{"$ref": "#/components/schemas/InvalidDraftResult"},
|
||||
]
|
||||
}
|
||||
schemas = openrpc_document["components"]["schemas"]
|
||||
assert schemas["CompileDraftWorkspaceSuccess"]["required"] == [
|
||||
"compiled_plan",
|
||||
"required_capabilities",
|
||||
]
|
||||
assert schemas["InvalidDraftResult"]["properties"]["status"]["const"] == "invalid"
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_capability_bootstrap_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
for method_name in (
|
||||
"workflow.drafts.create_from_capability",
|
||||
"workflow.draft_workspaces.create_from_capability",
|
||||
):
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name=method_name,
|
||||
component_name="CreateDraftWorkspaceFromCapabilityResult",
|
||||
properties={
|
||||
"workspace_id",
|
||||
"revision",
|
||||
"wrapper_hints",
|
||||
"next_actions",
|
||||
},
|
||||
)
|
||||
result = openrpc_document["components"]["schemas"][
|
||||
"CreateDraftWorkspaceFromCapabilityResult"
|
||||
]
|
||||
assert result["properties"]["wrapper_hints"] == {
|
||||
"$ref": "#/components/schemas/WrapperAuthoringHintsPayload"
|
||||
}
|
||||
assert result["properties"]["next_actions"] == {
|
||||
"$ref": "#/components/schemas/NextActionsPayload"
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"method_name",
|
||||
[
|
||||
"workflow.draft_workspaces.create_artifact",
|
||||
"workflow.draft_workspaces.create_wrapper",
|
||||
],
|
||||
)
|
||||
def test_openrpc_exposes_typed_workspace_artifact_save_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
method_name: str,
|
||||
) -> None:
|
||||
method = _method_by_name(openrpc_document, method_name)
|
||||
|
||||
assert method["result"]["schema"] == {
|
||||
"$ref": "#/components/schemas/CreateArtifactFromWorkspaceResult"
|
||||
}
|
||||
assert openrpc_document["components"]["schemas"][
|
||||
"CreateArtifactFromWorkspaceResult"
|
||||
] == {
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/SavedDraftArtifactResult"},
|
||||
{"$ref": "#/components/schemas/UnsavedDraftArtifactResult"},
|
||||
]
|
||||
}
|
||||
schemas = openrpc_document["components"]["schemas"]
|
||||
assert schemas["SavedDraftArtifactResult"]["properties"]["saved"]["const"] is True
|
||||
assert {
|
||||
"required_logical_sources",
|
||||
"suggested_bindings",
|
||||
} <= schemas["SavedDraftArtifactResult"]["properties"].keys()
|
||||
assert (
|
||||
schemas["UnsavedDraftArtifactResult"]["properties"]["saved"]["const"] is False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method_name", "component_name", "properties"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user