From fd9a2c9aab7e18d4c8edec9688b11bd0521e9def Mon Sep 17 00:00:00 2001 From: Jakub Karolczyk Date: Mon, 17 Aug 2026 15:38:33 +0100 Subject: [PATCH 1/2] Add call.accept() to the Relay Call API --- CHANGELOG.md | 5 +++++ relay/docs/call-methods.md | 10 ++++++++++ signalwire/signalwire/relay/call.py | 4 ++++ tests/unit/relay/test_call.py | 8 ++++++++ 4 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7643d06f..ce2f35c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [Unreleased] + +### New Features +- Relay: added `call.accept(early_media=False)` — signal a provisional (180 Ringing, or 183 Session Progress with `early_media=True`) on an inbound call without answering it. The call stays unanswered and unbilled. + ## [3.2.0] - 2026-07-14 ### New Features diff --git a/relay/docs/call-methods.md b/relay/docs/call-methods.md index 8bc7e5e8..4bd78334 100644 --- a/relay/docs/call-methods.md +++ b/relay/docs/call-methods.md @@ -83,6 +83,16 @@ Answer an inbound call. await call.answer() ``` +### `accept(early_media=False, **kwargs) -> dict` + +Send a provisional response (180 Ringing, or 183 Session Progress with `early_media=True`) on an inbound call without answering it. The call stays unanswered and unbilled. + + +```python +await call.accept() +await call.accept(early_media=True) +``` + ### `hangup(reason="hangup") -> dict` End the call. diff --git a/signalwire/signalwire/relay/call.py b/signalwire/signalwire/relay/call.py index 579fcd3c..e083ecc6 100644 --- a/signalwire/signalwire/relay/call.py +++ b/signalwire/signalwire/relay/call.py @@ -539,6 +539,10 @@ async def answer(self, **kwargs: Any) -> dict[str, Any]: """Answer an inbound call.""" return await self._execute("answer", kwargs or None) + async def accept(self, early_media: bool = False, **kwargs: Any) -> dict[str, Any]: + """Signal a provisional (180 Ringing / 183 Session Progress) on an inbound call without answering it.""" + return await self._execute("accept", {"early_media": early_media, **kwargs}) + async def hangup(self, reason: str = "hangup") -> dict[str, Any]: """End/hang up the call.""" return await self._execute("end", {"reason": reason}) diff --git a/tests/unit/relay/test_call.py b/tests/unit/relay/test_call.py index 4f2633ba..a8b6aa36 100644 --- a/tests/unit/relay/test_call.py +++ b/tests/unit/relay/test_call.py @@ -139,6 +139,14 @@ async def test_answer(self, call: Call, mock_client: MagicMock) -> None: "calling.answer", {"node_id": "node-1", "call_id": "call-1"} ) + @pytest.mark.asyncio + async def test_accept(self, call: Call, mock_client: MagicMock) -> None: + await call.accept(early_media=True) + mock_client.execute.assert_called_once_with( + "calling.accept", + {"node_id": "node-1", "call_id": "call-1", "early_media": True}, + ) + @pytest.mark.asyncio async def test_hangup(self, call: Call, mock_client: MagicMock) -> None: await call.hangup(reason="busy") From 86e022d4c13bf4dd76c747fd66527ecb08bb6700 Mon Sep 17 00:00:00 2001 From: Jakub Karolczyk Date: Sat, 29 Aug 2026 13:27:59 +0100 Subject: [PATCH 2/2] Rename call.accept() to call.ring() and drop early_media --- CHANGELOG.md | 2 +- relay/docs/call-methods.md | 7 +++---- signalwire/signalwire/relay/call.py | 6 +++--- tests/unit/relay/test_call.py | 7 +++---- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2f35c1..e5fd08d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## [Unreleased] ### New Features -- Relay: added `call.accept(early_media=False)` — signal a provisional (180 Ringing, or 183 Session Progress with `early_media=True`) on an inbound call without answering it. The call stays unanswered and unbilled. +- Relay: added `call.ring()` — send 180 Ringing on an inbound call without answering it, satisfying an upstream carrier's post-dial-delay timer. The call stays unanswered and unbilled. ## [3.2.0] - 2026-07-14 diff --git a/relay/docs/call-methods.md b/relay/docs/call-methods.md index 4bd78334..04ac9259 100644 --- a/relay/docs/call-methods.md +++ b/relay/docs/call-methods.md @@ -83,14 +83,13 @@ Answer an inbound call. await call.answer() ``` -### `accept(early_media=False, **kwargs) -> dict` +### `ring(**kwargs) -> dict` -Send a provisional response (180 Ringing, or 183 Session Progress with `early_media=True`) on an inbound call without answering it. The call stays unanswered and unbilled. +Send **180 Ringing** on an inbound call without answering it, so an upstream carrier's post-dial-delay timer is satisfied. Signalling only: no media, no answer, no billing. Ringing a call takes control of it, so it can no longer be passed to another consumer. ```python -await call.accept() -await call.accept(early_media=True) +await call.ring() ``` ### `hangup(reason="hangup") -> dict` diff --git a/signalwire/signalwire/relay/call.py b/signalwire/signalwire/relay/call.py index e083ecc6..00d4788d 100644 --- a/signalwire/signalwire/relay/call.py +++ b/signalwire/signalwire/relay/call.py @@ -539,9 +539,9 @@ async def answer(self, **kwargs: Any) -> dict[str, Any]: """Answer an inbound call.""" return await self._execute("answer", kwargs or None) - async def accept(self, early_media: bool = False, **kwargs: Any) -> dict[str, Any]: - """Signal a provisional (180 Ringing / 183 Session Progress) on an inbound call without answering it.""" - return await self._execute("accept", {"early_media": early_media, **kwargs}) + async def ring(self, **kwargs: Any) -> dict[str, Any]: + """Send 180 Ringing on an inbound call without answering it.""" + return await self._execute("ring", kwargs or None) async def hangup(self, reason: str = "hangup") -> dict[str, Any]: """End/hang up the call.""" diff --git a/tests/unit/relay/test_call.py b/tests/unit/relay/test_call.py index a8b6aa36..dee3898d 100644 --- a/tests/unit/relay/test_call.py +++ b/tests/unit/relay/test_call.py @@ -140,11 +140,10 @@ async def test_answer(self, call: Call, mock_client: MagicMock) -> None: ) @pytest.mark.asyncio - async def test_accept(self, call: Call, mock_client: MagicMock) -> None: - await call.accept(early_media=True) + async def test_ring(self, call: Call, mock_client: MagicMock) -> None: + await call.ring() mock_client.execute.assert_called_once_with( - "calling.accept", - {"node_id": "node-1", "call_id": "call-1", "early_media": True}, + "calling.ring", {"node_id": "node-1", "call_id": "call-1"} ) @pytest.mark.asyncio