Add community node protocol spec, Agent Skills, and make the failing tests hermetic
DevPlace CI / test (push) Failing after 31m42s

Community nodes (Phase 1 of plan.md): the protocol specification in
services/nodes/spec.py as the single source of truth, GET /nodes/spec.json,
a generated protocol docs page, the Community nodes API docs group, and the
implementation plan plus the residential proxy research. The no-open-proxy rule
is enforced in the spec (open_proxy cannot be set).

Agent Skills: register the Skills API docs group, cascade skills on account
deletion, add the nested CLAUDE.md, README and routes docs, and cover the
parser, the fetcher (end to end against a mock origin), the data layer and the
routes with 73 unit and 16 api tests.

Tests made hermetic with the repo's existing patterns instead of ambient
environment: embeddings tests set OPENROUTER_API_KEY via monkeypatch, the
workspace tests install FakeBackend via runtime.set_backend, and the skills
install tests use run_async. DockerCliBackend now returns 127 (command not
found) when the CLI is missing instead of raising FileNotFoundError, so a host
without docker answers with a clean error rather than a 500. The Providers tab
empty state no longer names the Model routes tab.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 07:13:25 +02:00
co-authored by Claude Sonnet 5.5
parent cae139ead9
commit f55c7ee5f4
102 changed files with 7414 additions and 65 deletions
@@ -595,3 +595,106 @@ def test_fetch_provider_models_none_when_provider_has_no_base_url(local_db):
assert run_async(r.fetch_provider_models("probeprovnourl")) is None
finally:
r.provider_store.remove("probeprovnourl")
def test_provider_protocol_defaults_and_validates(local_db):
assert r.ProviderIn(name="p1").protocol == "chat_completions"
assert r.ProviderIn(name="p1", protocol="responses").protocol == "responses"
assert r.ProviderIn(name="p1", protocol=" Responses ").protocol == "responses"
with pytest.raises(ValidationError):
r.ProviderIn(name="p1", protocol="soap")
def test_provider_protocol_round_trips_through_store(local_db):
r.provider_store.set(
r.ProviderIn(
name="protometa",
base_url="https://api.meta.ai/v1/responses",
api_key="sk-test",
protocol="responses",
)
)
try:
saved = r.provider_store.get("protometa")
assert saved is not None
assert saved["protocol"] == "responses"
assert saved["base_url"] == "https://api.meta.ai/v1/responses"
finally:
r.provider_store.remove("protometa")
def test_chat_overlay_carries_provider_protocol(local_db):
r.provider_store.set(
r.ProviderIn(
name="protoovl",
base_url="https://api.meta.ai/v1/responses",
api_key="sk-test",
protocol="responses",
)
)
r.model_store.set(
r.ModelRouteIn(
source_model="meta-route",
provider="protoovl",
target_model="muse-spark-1.3-contributor",
kind="chat",
)
)
try:
overlay = r.chat_overlay("meta-route", {"gateway_model": "x"})
assert overlay["gateway_upstream_protocol"] == "responses"
assert (
overlay["gateway_upstream_url"]
== "https://api.meta.ai/v1/responses"
)
finally:
_cleanup(["protoovl"], ["meta-route"])
def test_responses_models_urls_probe_both_shapes():
assert r._responses_models_urls("https://api.meta.ai/v1/responses") == [
"https://api.meta.ai/v1/models",
"https://api.meta.ai/v1/muse-code/models",
]
assert r._responses_models_urls("") == []
def test_fetch_provider_models_tries_meta_listing_second(local_db, monkeypatch):
r.provider_store.set(
r.ProviderIn(
name="probeprovmeta",
base_url="https://api.meta.ai/v1/responses",
protocol="responses",
)
)
class _SeqClient:
def __init__(self):
self.requested_urls = []
async def __aenter__(self):
return self
async def __aexit__(self, *exc_info):
return False
async def get(self, url):
self.requested_urls.append(url)
if url == "https://api.meta.ai/v1/models":
return _FakeModelsResponse(404, {})
return _FakeModelsResponse(
200, {"data": [{"id": "muse-spark-1.3-contributor"}]}
)
fake_client = _SeqClient()
_patch_stealth_client(monkeypatch, fake_client)
try:
assert run_async(r.fetch_provider_models("probeprovmeta")) == [
"muse-spark-1.3-contributor"
]
assert fake_client.requested_urls == [
"https://api.meta.ai/v1/models",
"https://api.meta.ai/v1/muse-code/models",
]
finally:
r.provider_store.remove("probeprovmeta")