From 3db49381d6b57e0ec652ba29b1aef262d4e33dcf Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 8 Jun 2026 15:08:41 +1000 Subject: [PATCH] feat(relay): derive descriptor from PlatformEntry CapabilityDescriptor.from_platform_entry() projects an existing PlatformEntry (label, max_message_length, emoji, platform_hint, pii_safe, name) into a descriptor, proving the descriptor is a projection of existing config rather than a parallel concept. Runtime-only capabilities (len_unit, draft/edit/ thread/markdown) are caller-supplied. max_message_length==0 ('no limit') maps to the stream_consumer 4096 default. Phase 0 complete. Task 0.3 of the gateway-relay plan. --- gateway/relay/descriptor.py | 41 ++++++++++++ .../relay/test_descriptor_from_entry.py | 64 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/gateway/relay/test_descriptor_from_entry.py diff --git a/gateway/relay/descriptor.py b/gateway/relay/descriptor.py index 2a6263e28a3..2a8d0fe78f3 100644 --- a/gateway/relay/descriptor.py +++ b/gateway/relay/descriptor.py @@ -75,3 +75,44 @@ class CapabilityDescriptor: known = {f for f in cls.__dataclass_fields__} # type: ignore[attr-defined] filtered = {k: v for k, v in raw.items() if k in known} return cls(**filtered) + + @classmethod + def from_platform_entry( + cls, + entry, + *, + len_unit: str = "chars", + supports_draft_streaming: bool = False, + supports_edit: bool = True, + supports_threads: bool = False, + markdown_dialect: str = "plain", + ) -> "CapabilityDescriptor": + """Project a ``gateway.platform_registry.PlatformEntry`` into a descriptor. + + Demonstrates the descriptor is a *subset/projection* of what + ``PlatformEntry`` already encodes, not a parallel concept: ``label``, + ``max_message_length``, ``emoji``, ``platform_hint``, ``pii_safe`` and + the platform name come straight off the entry. The runtime capability + bits that ``PlatformEntry`` does NOT encode (length unit, draft/edit/ + thread/markdown behavior) are supplied by the caller — in production + the connector fills these from the live adapter's capability methods. + + ``max_message_length`` of 0 on a ``PlatformEntry`` means "no limit"; + we map that to the stream_consumer default of 4096 so the descriptor + always carries a concrete chunking bound. + """ + max_len = getattr(entry, "max_message_length", 0) or 4096 + return cls( + contract_version=CONTRACT_VERSION, + platform=entry.name, + label=entry.label, + max_message_length=max_len, + supports_draft_streaming=supports_draft_streaming, + supports_edit=supports_edit, + supports_threads=supports_threads, + markdown_dialect=markdown_dialect, + len_unit=len_unit, + emoji=getattr(entry, "emoji", "\U0001f50c"), + platform_hint=getattr(entry, "platform_hint", ""), + pii_safe=getattr(entry, "pii_safe", False), + ) diff --git a/tests/gateway/relay/test_descriptor_from_entry.py b/tests/gateway/relay/test_descriptor_from_entry.py new file mode 100644 index 00000000000..5f46beeb02a --- /dev/null +++ b/tests/gateway/relay/test_descriptor_from_entry.py @@ -0,0 +1,64 @@ +"""Descriptor <- PlatformEntry projection (relay Phase 0, Task 0.3). + +Proves the CapabilityDescriptor is a projection of the existing PlatformEntry, +not a parallel concept: the entry's label/limit/emoji/hint/pii fields carry +straight through. +""" + +from gateway.platform_registry import PlatformEntry +from gateway.relay.descriptor import CONTRACT_VERSION, CapabilityDescriptor + + +def _entry(**overrides) -> PlatformEntry: + base = dict( + name="telegram", + label="Telegram", + adapter_factory=lambda cfg: None, + check_fn=lambda: True, + max_message_length=4096, + pii_safe=False, + emoji="\u2708\ufe0f", + platform_hint="You are on Telegram.", + ) + base.update(overrides) + return PlatformEntry(**base) + + +def test_projection_carries_platform_entry_fields(): + d = CapabilityDescriptor.from_platform_entry(_entry(), len_unit="utf16") + assert d.contract_version == CONTRACT_VERSION + assert d.platform == "telegram" + assert d.label == "Telegram" + assert d.max_message_length == 4096 + assert d.emoji == "\u2708\ufe0f" + assert d.platform_hint == "You are on Telegram." + assert d.pii_safe is False + assert d.len_unit == "utf16" + + +def test_zero_max_length_maps_to_4096_default(): + """PlatformEntry.max_message_length == 0 means 'no limit'; the descriptor + carries a concrete bound matching the stream_consumer default.""" + d = CapabilityDescriptor.from_platform_entry(_entry(max_message_length=0)) + assert d.max_message_length == 4096 + + +def test_runtime_capabilities_supplied_by_caller(): + """PlatformEntry doesn't encode draft/edit/thread/markdown behavior — those + come from the caller (the connector, reading the live adapter).""" + d = CapabilityDescriptor.from_platform_entry( + _entry(), + supports_draft_streaming=True, + supports_edit=False, + supports_threads=True, + markdown_dialect="discord", + ) + assert d.supports_draft_streaming is True + assert d.supports_edit is False + assert d.supports_threads is True + assert d.markdown_dialect == "discord" + + +def test_projection_roundtrips_through_json(): + d = CapabilityDescriptor.from_platform_entry(_entry(), len_unit="utf16") + assert CapabilityDescriptor.from_json(d.to_json()) == d