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")