feat: list source resources and prompts

This commit is contained in:
lda
2026-06-14 00:16:56 +07:00 Verified
parent ba8c59e81a
commit 2609679b87
9 changed files with 215 additions and 16 deletions
+4 -2
View File
@@ -473,7 +473,7 @@ def test_builder_adds_explicit_end_node() -> None:
assert builder.compile().outcomes == ["ok", "error"]
class _StructuralKeyMap(Mapping[object, object]):
class _StructuralKeyMap:
def __getitem__(self, key: object) -> object:
raise KeyError(key)
@@ -484,6 +484,8 @@ class _StructuralKeyMap(Mapping[object, object]):
return 1
def items(self) -> list[tuple[dict[str, object], str]]:
# Deliberately violates Mapping's item-view contract to exercise the
# runtime guard for structural dict keys from malformed mappings.
return [
(
{"root": "input", "parts": ["email.address"]},
@@ -494,4 +496,4 @@ class _StructuralKeyMap(Mapping[object, object]):
def test_input_map_rejects_structural_dict_keys_with_clear_message() -> None:
with pytest.raises(TypeError, match="structural path dicts cannot be map keys"):
normalize_input_mapping(_StructuralKeyMap())
normalize_input_mapping(cast(Mapping[object, object], _StructuralKeyMap()))
+86
View File
@@ -34,6 +34,34 @@ class BrokenSourceAdmin:
raise RuntimeError(f"broken source admin for {source_id}")
class InventorySourceAdmin:
async def list_sources(
self,
*,
cursor: str | None = None,
limit: int = 50,
) -> dict[str, Any]:
return {"sources": [], "next_cursor": None, "total": 0}
async def inspect_source(self, *, source_id: str) -> dict[str, Any]:
return {
"id": source_id,
"capabilities": {
"resources": [
f"{source_id}.architecture.md",
f"{source_id}.startup.md",
],
"prompts": [
f"{source_id}.simple-prompt",
f"{source_id}.args-prompt",
],
},
}
async def diagnose_source(self, *, source_id: str) -> dict[str, Any]:
return {"source_id": source_id, "status": "ok"}
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(
@@ -408,6 +436,64 @@ def test_wf_source_commands_use_rpc_url_override(monkeypatch, tmp_path) -> None:
assert '"id": "wf.std"' in inspected.output
def test_wf_source_resources_and_prompts_render_names(monkeypatch, tmp_path) -> None:
fake_context = CliContext(
config_path=Path("dummy"),
service=cast(Any, object()),
handlers=build_local_static_workflow_server(tmp_path / "store").api,
source_admin=InventorySourceAdmin(),
admin=cast(Any, object()),
)
monkeypatch.setattr(
"wf_cli.commands.sources.load_cli_context_from_typer",
lambda _ctx: fake_context,
)
runner = CliRunner()
resources = runner.invoke(app, ["source", "resources", "everything.default"])
prompts = runner.invoke(app, ["source", "prompts", "everything.default"])
assert resources.exit_code == 0, resources.output
assert resources.output.splitlines() == [
"everything.default.architecture.md",
"everything.default.startup.md",
]
assert prompts.exit_code == 0, prompts.output
assert prompts.output.splitlines() == [
"everything.default.simple-prompt",
"everything.default.args-prompt",
]
def test_wf_source_resources_json_format(monkeypatch, tmp_path) -> None:
fake_context = CliContext(
config_path=Path("dummy"),
service=cast(Any, object()),
handlers=build_local_static_workflow_server(tmp_path / "store").api,
source_admin=InventorySourceAdmin(),
admin=cast(Any, object()),
)
monkeypatch.setattr(
"wf_cli.commands.sources.load_cli_context_from_typer",
lambda _ctx: fake_context,
)
result = CliRunner().invoke(
app,
["source", "resources", "everything.default", "--format", "json"],
)
assert result.exit_code == 0, result.output
payload = json.loads(result.output)
assert payload == {
"source_id": "everything.default",
"resources": [
"everything.default.architecture.md",
"everything.default.startup.md",
],
}
def test_wf_remote_source_inspect_formats_expected_rpc_error(
monkeypatch,
tmp_path,
+1
View File
@@ -98,6 +98,7 @@ def test_file_store_saves_and_loads_neutral_auth_record(tmp_path: Path) -> None:
loaded = store.load_auth_record("github.work")
assert loaded is not None
assert isinstance(loaded, NeutralAuthRecord)
assert loaded.id == record.id
assert loaded.scheme == record.scheme
assert loaded.payload == record.payload
+1
View File
@@ -102,6 +102,7 @@ def test_file_store_accepts_neutral_auth_ref_without_connection_shape(
loaded = store.load_auth_record("api_ci-1")
assert loaded is not None
assert isinstance(loaded, NeutralAuthRecord)
assert loaded.id == "api_ci-1"
assert loaded.scheme == "bearer"
assert loaded.payload == {"token": "secret"}
+18 -4
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import Any
import pytest
@@ -156,17 +158,29 @@ class _FakeAdapter(McpSdkAdapter):
def __init__(self, session: _FakeSession) -> None:
self.fake_session = session
def _client(self, connection: McpSourceConnection, auth: object | None):
@asynccontextmanager
async def _client(
self,
connection: McpSourceConnection,
auth: object | None,
) -> AsyncIterator[McpSourceClient]:
assert connection.id == "demo.personal"
assert auth is None
return _ClientContext(
async with _ClientContext(
McpSourceClient(session=self.fake_session, connection=connection)
)
) as client:
yield client
class _ExplodingClientAdapter(McpSdkAdapter):
def _client(self, connection: McpSourceConnection, auth: object | None):
@asynccontextmanager
async def _client(
self,
connection: McpSourceConnection,
auth: object | None,
) -> AsyncIterator[McpSourceClient]:
raise AssertionError("metadata lookup must not open an MCP session")
yield # pragma: no cover
def test_mcp_sdk_adapter_implements_backend_protocol() -> None: