207 lines
7.6 KiB
Python
207 lines
7.6 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
from wf_artifacts import FileWorkflowArtifactStore
|
|
from wf_mcp.broker import WfMcpService
|
|
from wf_mcp.models import ConnectionConfig
|
|
from wf_mcp.storage import FileStore
|
|
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
|
|
|
|
from ..test_support import echo_tool, local_temp_root
|
|
from .conftest import (
|
|
ContentOnlyOutputAdapter,
|
|
echo_artifact,
|
|
failing_tool,
|
|
handlers,
|
|
)
|
|
|
|
|
|
def test_workflow_surface_lists_planner_visible_capabilities() -> None:
|
|
h = handlers(FileWorkflowArtifactStore(local_temp_root() / "surface_caps"))
|
|
|
|
payload = asyncio.run(h.list_capabilities(limit=2))
|
|
names = [capability["name"] for capability in payload["capabilities"]]
|
|
first = payload["capabilities"][0]
|
|
|
|
assert len(names) == 2
|
|
assert payload["total"] >= 2
|
|
assert payload["next_cursor"] == "2"
|
|
assert "description" in first
|
|
assert "source_id" in first
|
|
assert first["kind"] == "node_spec"
|
|
assert "input_fields" in first
|
|
assert "output_fields" in first
|
|
assert "input_schema" not in first
|
|
assert "wf.admin.list_sources" not in names
|
|
|
|
|
|
def test_workflow_surface_filters_stdlib_capabilities_by_source() -> None:
|
|
h = handlers(FileWorkflowArtifactStore(local_temp_root() / "surface_filtered_caps"))
|
|
|
|
payload = asyncio.run(h.list_capabilities(source_id="wf.std", query="truthy"))
|
|
|
|
assert [capability["name"] for capability in payload["capabilities"]] == [
|
|
"wf.std.truthy"
|
|
]
|
|
assert payload["capabilities"][0]["source_id"] == "wf.std"
|
|
|
|
|
|
def test_workflow_surface_call_capability_returns_structured_error() -> None:
|
|
service = WfMcpService(
|
|
store=FileStore(local_temp_root() / "surface_call_capability_error_mcp"),
|
|
artifact_store=FileWorkflowArtifactStore(
|
|
local_temp_root() / "surface_call_capability_error"
|
|
),
|
|
)
|
|
service.register_connection(
|
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
|
)
|
|
service.register_specs("demo.personal", failing_tool)
|
|
h = WorkflowSurfaceHandlers(service)
|
|
|
|
payload = asyncio.run(
|
|
h.call_capability(
|
|
qualified_name="demo.personal.failing_tool",
|
|
payload={"message": "hello"},
|
|
)
|
|
)
|
|
|
|
assert payload["qualified_name"] == "demo.personal.failing_tool"
|
|
assert payload["source_id"] == "demo.personal"
|
|
assert payload["kind"] == "node_spec"
|
|
assert payload["outcome"] == "runtime_error"
|
|
assert payload["output"] is None
|
|
assert payload["diagnostics"][0]["code"] == "capability_call_failed"
|
|
assert payload["diagnostics"][0]["severity"] == "error"
|
|
assert "demo.personal.failing_tool" in payload["diagnostics"][0]["message"]
|
|
assert "upstream exploded" in payload["diagnostics"][0]["message"]
|
|
|
|
|
|
def test_workflow_surface_lists_saved_wrapper_capabilities() -> None:
|
|
artifact_store = FileWorkflowArtifactStore(
|
|
local_temp_root() / "surface_wrapper_caps"
|
|
)
|
|
artifact_store.save_artifact(
|
|
echo_artifact().model_copy(
|
|
update={
|
|
"id": "echo_wrapper",
|
|
"kind": "wrapper",
|
|
"description": "Reusable echo wrapper.",
|
|
}
|
|
)
|
|
)
|
|
artifact_store.save_artifact(echo_artifact())
|
|
h = handlers(artifact_store)
|
|
|
|
payload = asyncio.run(h.list_capabilities(source_id="workflow", query="echo"))
|
|
|
|
names = [capability["name"] for capability in payload["capabilities"]]
|
|
assert names == ["workflow.echo_wrapper.v1"]
|
|
assert payload["capabilities"][0]["source_id"] == "workflow"
|
|
assert payload["capabilities"][0]["kind"] == "wrapper_artifact"
|
|
assert payload["capabilities"][0]["artifact_id"] == "echo_wrapper"
|
|
assert payload["capabilities"][0]["version"] == 1
|
|
assert payload["capabilities"][0]["title"] == "Echo"
|
|
assert payload["capabilities"][0]["outcomes"] == ["completed"]
|
|
assert payload["capabilities"][0]["input_fields"] == ["text"]
|
|
assert payload["capabilities"][0]["output_fields"] == ["echoed"]
|
|
|
|
|
|
def test_workflow_surface_inspects_one_capability() -> None:
|
|
h = handlers(FileWorkflowArtifactStore(local_temp_root() / "surface_inspect_cap"))
|
|
|
|
payload = asyncio.run(h.inspect_capability(qualified_name="wf.std.runtime_error"))
|
|
|
|
assert payload["name"] == "wf.std.runtime_error"
|
|
assert payload["outcomes"] == ["ok"]
|
|
assert "input_schema" in payload
|
|
|
|
|
|
def test_workflow_surface_inspect_capability_includes_wrapper_hints() -> None:
|
|
service = WfMcpService(
|
|
store=FileStore(local_temp_root() / "surface_wrapper_hints_mcp"),
|
|
artifact_store=FileWorkflowArtifactStore(
|
|
local_temp_root() / "surface_wrapper_hints_artifacts"
|
|
),
|
|
)
|
|
service.register_connection(
|
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
|
)
|
|
service.register_specs("demo.personal", echo_tool)
|
|
h = WorkflowSurfaceHandlers(service)
|
|
|
|
payload = asyncio.run(
|
|
h.inspect_capability(qualified_name="demo.personal.echo_tool")
|
|
)
|
|
|
|
hints = payload["wrapper_hints"]
|
|
assert hints["capability_name"] == "demo.personal.echo_tool"
|
|
assert hints["declared_outcomes"] == ["ok"]
|
|
assert hints["input_map"] == {"input.text": "text"}
|
|
assert hints["output_map"] == {"echoed": "state.echoed"}
|
|
assert hints["outcome_policy"] == "preserve_declared"
|
|
|
|
|
|
def test_workflow_surface_inspects_saved_wrapper_capability() -> None:
|
|
artifact_store = FileWorkflowArtifactStore(
|
|
local_temp_root() / "surface_inspect_wrapper_cap"
|
|
)
|
|
artifact_store.save_artifact(
|
|
echo_artifact().model_copy(update={"id": "echo_wrapper", "kind": "wrapper"})
|
|
)
|
|
h = handlers(artifact_store)
|
|
|
|
payload = asyncio.run(
|
|
h.inspect_capability(qualified_name="workflow.echo_wrapper.v1")
|
|
)
|
|
|
|
assert payload["name"] == "workflow.echo_wrapper.v1"
|
|
assert payload["source_id"] == "workflow"
|
|
assert payload["kind"] == "wrapper_artifact"
|
|
assert payload["artifact_id"] == "echo_wrapper"
|
|
assert payload["outcomes"] == ["completed"]
|
|
assert "input_schema" in payload
|
|
hints = payload["wrapper_hints"]
|
|
assert hints["capability_name"] == "workflow.echo_wrapper.v1"
|
|
assert hints["declared_outcomes"] == ["completed"]
|
|
assert hints["suggested_wrapper_outcomes"] == ["completed"]
|
|
assert hints["input_map"] == {"input.text": "text"}
|
|
assert hints["output_map"] == {"echoed": "state.echoed"}
|
|
|
|
|
|
def test_workflow_surface_does_not_auto_map_raw_mcp_content_blocks() -> None:
|
|
artifact_store = FileWorkflowArtifactStore(
|
|
local_temp_root() / "surface_content_only_content_hint"
|
|
)
|
|
service = WfMcpService(
|
|
store=FileStore(local_temp_root() / "surface_content_only_content_hint_mcp"),
|
|
artifact_store=artifact_store,
|
|
)
|
|
service.register_connection(
|
|
ConnectionConfig(
|
|
id="everything.default", server="everything", account="default"
|
|
)
|
|
)
|
|
service.register_adapter("everything", ContentOnlyOutputAdapter())
|
|
asyncio.run(service.refresh_connection_catalog("everything.default"))
|
|
h = WorkflowSurfaceHandlers(service)
|
|
|
|
inspected = asyncio.run(
|
|
h.inspect_capability(qualified_name="everything.default.echo")
|
|
)
|
|
created = asyncio.run(
|
|
h.create_draft_workspace_from_capability(
|
|
workspace_id="content_blocks",
|
|
capability_name="everything.default.echo",
|
|
name="content_blocks",
|
|
)
|
|
)
|
|
|
|
assert inspected["wrapper_hints"]["output_map"] == {}
|
|
assert created["wrapper_hints"]["output_map"] == {}
|
|
assert inspected["wrapper_hints"]["missing_decisions"][0]["kind"] == (
|
|
"review_nested_output"
|
|
)
|
|
assert "Raw MCP content blocks" in inspected["wrapper_hints"]["notes"][2]
|