feat: select workflow cli target from config
This commit is contained in:
+18
-1
@@ -23,9 +23,26 @@ def root(
|
|||||||
help="Path to workflow/MCP config JSON.",
|
help="Path to workflow/MCP config JSON.",
|
||||||
),
|
),
|
||||||
] = "wf_mcp.config.json",
|
] = "wf_mcp.config.json",
|
||||||
|
local: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option("--local", help="Force same-process local workflow target."),
|
||||||
|
] = False,
|
||||||
|
url: Annotated[
|
||||||
|
str | None,
|
||||||
|
typer.Option("--url", help="Override workflow JSON-RPC target URL."),
|
||||||
|
] = None,
|
||||||
|
timeout: Annotated[
|
||||||
|
float | None,
|
||||||
|
typer.Option("--timeout", min=0.1, help="Override RPC timeout seconds."),
|
||||||
|
] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run workflow platform commands."""
|
"""Run workflow platform commands."""
|
||||||
ctx.obj = {"config_path": config}
|
ctx.obj = {
|
||||||
|
"config_path": config,
|
||||||
|
"force_local": local,
|
||||||
|
"rpc_url": url,
|
||||||
|
"rpc_timeout_seconds": timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
app.add_typer(caps.app, name="cap")
|
app.add_typer(caps.app, name="cap")
|
||||||
|
|||||||
+74
-10
@@ -9,21 +9,18 @@ from wf_api import WorkflowApi
|
|||||||
from wf_mcp.broker import build_service_from_config, load_broker_config
|
from wf_mcp.broker import build_service_from_config, load_broker_config
|
||||||
from wf_mcp.broker.service import WfMcpService
|
from wf_mcp.broker.service import WfMcpService
|
||||||
from wf_mcp.broker.service.workflow_operation_context import context_from_service
|
from wf_mcp.broker.service.workflow_operation_context import context_from_service
|
||||||
|
from wf_config import FilesystemStoreConfig, LocalTargetConfig, RpcHttpTargetConfig, load_workflow_config
|
||||||
|
from wf_server import build_local_static_workflow_server
|
||||||
|
from wf_transport_rpc_http import RpcWorkflowApiClient
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CliContext:
|
class CliContext:
|
||||||
"""Protocol-neutral CLI handle over the current workflow service stack.
|
"""Protocol-neutral CLI handle over local or remote workflow operations."""
|
||||||
|
|
||||||
V1 intentionally reuses wf_mcp service construction because that is where
|
|
||||||
config, store, source, artifact, draft, and run wiring currently lives. Keep
|
|
||||||
this dependency behind context.py so later extraction does not affect every
|
|
||||||
command module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
config_path: Path
|
config_path: Path
|
||||||
service: WfMcpService
|
service: WfMcpService | None
|
||||||
handlers: WorkflowApi
|
handlers: WorkflowApi | RpcWorkflowApiClient
|
||||||
|
|
||||||
|
|
||||||
def config_path_from_context(ctx: typer.Context) -> str:
|
def config_path_from_context(ctx: typer.Context) -> str:
|
||||||
@@ -33,9 +30,16 @@ def config_path_from_context(ctx: typer.Context) -> str:
|
|||||||
return value if isinstance(value, str) else "wf_mcp.config.json"
|
return value if isinstance(value, str) else "wf_mcp.config.json"
|
||||||
|
|
||||||
|
|
||||||
def load_cli_context(config_path: str | Path) -> CliContext:
|
def load_cli_context(
|
||||||
|
config_path: str | Path,
|
||||||
|
*,
|
||||||
|
force_local: bool = False,
|
||||||
|
rpc_url: str | None = None,
|
||||||
|
rpc_timeout_seconds: float | None = None,
|
||||||
|
) -> CliContext:
|
||||||
"""Load config and build workflow-surface handlers for CLI commands."""
|
"""Load config and build workflow-surface handlers for CLI commands."""
|
||||||
resolved_config_path = Path(config_path)
|
resolved_config_path = Path(config_path)
|
||||||
|
if resolved_config_path.name == "wf_mcp.config.json":
|
||||||
config = load_broker_config(resolved_config_path)
|
config = load_broker_config(resolved_config_path)
|
||||||
service = build_service_from_config(config)
|
service = build_service_from_config(config)
|
||||||
return CliContext(
|
return CliContext(
|
||||||
@@ -43,3 +47,63 @@ def load_cli_context(config_path: str | Path) -> CliContext:
|
|||||||
service=service,
|
service=service,
|
||||||
handlers=WorkflowApi(context_from_service(service)),
|
handlers=WorkflowApi(context_from_service(service)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
config = load_workflow_config(resolved_config_path)
|
||||||
|
target = config.client.target
|
||||||
|
if rpc_url is not None:
|
||||||
|
timeout = rpc_timeout_seconds if rpc_timeout_seconds is not None else 30.0
|
||||||
|
return CliContext(
|
||||||
|
config_path=resolved_config_path,
|
||||||
|
service=None,
|
||||||
|
handlers=RpcWorkflowApiClient(url=rpc_url, timeout_seconds=timeout),
|
||||||
|
)
|
||||||
|
if force_local or isinstance(target, LocalTargetConfig):
|
||||||
|
store = config.server.store
|
||||||
|
if not isinstance(store, FilesystemStoreConfig):
|
||||||
|
raise ValueError("local CLI target currently requires filesystem store")
|
||||||
|
server = build_local_static_workflow_server(store.root)
|
||||||
|
return CliContext(
|
||||||
|
config_path=resolved_config_path,
|
||||||
|
service=None,
|
||||||
|
handlers=server.api,
|
||||||
|
)
|
||||||
|
if isinstance(target, RpcHttpTargetConfig):
|
||||||
|
return CliContext(
|
||||||
|
config_path=resolved_config_path,
|
||||||
|
service=None,
|
||||||
|
handlers=RpcWorkflowApiClient(
|
||||||
|
url=target.url,
|
||||||
|
timeout_seconds=(
|
||||||
|
rpc_timeout_seconds
|
||||||
|
if rpc_timeout_seconds is not None
|
||||||
|
else target.timeout_seconds
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
raise ValueError(f"unsupported workflow target {target!r}")
|
||||||
|
|
||||||
|
|
||||||
|
def force_local_from_context(ctx: typer.Context) -> bool:
|
||||||
|
obj = ctx.obj if isinstance(ctx.obj, dict) else {}
|
||||||
|
return bool(obj.get("force_local", False))
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_url_from_context(ctx: typer.Context) -> str | None:
|
||||||
|
obj = ctx.obj if isinstance(ctx.obj, dict) else {}
|
||||||
|
value = obj.get("rpc_url")
|
||||||
|
return value if isinstance(value, str) else None
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_timeout_from_context(ctx: typer.Context) -> float | None:
|
||||||
|
obj = ctx.obj if isinstance(ctx.obj, dict) else {}
|
||||||
|
value = obj.get("rpc_timeout_seconds")
|
||||||
|
return value if isinstance(value, float | int) else None
|
||||||
|
|
||||||
|
|
||||||
|
def load_cli_context_from_typer(ctx: typer.Context) -> CliContext:
|
||||||
|
return load_cli_context(
|
||||||
|
config_path_from_context(ctx),
|
||||||
|
force_local=force_local_from_context(ctx),
|
||||||
|
rpc_url=rpc_url_from_context(ctx),
|
||||||
|
rpc_timeout_seconds=rpc_timeout_from_context(ctx),
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from wf_cli.context import load_cli_context
|
||||||
|
from wf_transport_rpc_http import RpcWorkflowApiClient
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_cli_context_uses_rpc_client_for_rpc_http_target(tmp_path) -> None:
|
||||||
|
config_path = tmp_path / "wf.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"client": {
|
||||||
|
"target": {
|
||||||
|
"kind": "rpc_http",
|
||||||
|
"url": "http://127.0.0.1:8765/rpc",
|
||||||
|
"timeout_seconds": 9,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
context = load_cli_context(config_path)
|
||||||
|
|
||||||
|
assert isinstance(context.handlers, RpcWorkflowApiClient)
|
||||||
|
assert context.handlers.url == "http://127.0.0.1:8765/rpc"
|
||||||
|
assert context.handlers.timeout_seconds == 9
|
||||||
|
assert context.service is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_cli_context_local_override_beats_rpc_config(tmp_path) -> None:
|
||||||
|
config_path = tmp_path / "wf.json"
|
||||||
|
config_path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"client": {
|
||||||
|
"target": {
|
||||||
|
"kind": "rpc_http",
|
||||||
|
"url": "http://127.0.0.1:8765/rpc",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"server": {
|
||||||
|
"store": {"kind": "filesystem", "root": ".wf_store"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
context = load_cli_context(config_path, force_local=True)
|
||||||
|
|
||||||
|
assert not isinstance(context.handlers, RpcWorkflowApiClient)
|
||||||
|
assert context.service is None
|
||||||
|
assert context.config_path == config_path
|
||||||
Reference in New Issue
Block a user