Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/api_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2053,24 +2053,24 @@ result.pay(
Start call tapping/monitoring.

**Parameters:**
- `uri` (str): URI to send tapped audio to
- `uri` (str): URI to send tapped audio to — `rtp://IP:port`, `ws://example.com`, or `wss://example.com`
- `control_id` (Optional[str]): Unique identifier for this tap
- `direction` (str): Tap direction: "both", "inbound", "outbound" (default: "both")
- `codec` (str): Audio codec: "PCMU", "PCMA", "G722" (default: "PCMU")
- `direction` (str): Tap direction: "speak", "listen", "both" (default: "both")
- `codec` (str): Audio codec: "PCMU" or "PCMA" (default: "PCMU")
- `rtp_ptime` (int): RTP packet time in milliseconds (default: 20)
- `status_url` (Optional[str]): Status webhook URL

**Usage:**
```python
# Basic call tapping
result.tap("sip:monitor@company.com")
result.tap("wss://monitor.company.com/tap")

# Tap with specific settings
result.tap(
uri="sip:quality@company.com",
uri="rtp://192.168.1.100:5004",
control_id="quality_monitor_001",
direction="both",
codec="G722"
codec="PCMA"
)
```

Expand Down
4 changes: 2 additions & 2 deletions docs/swaig_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,9 @@ result.tap(
- `control_id`: Identifier for this tap to use with stop_tap (optional, auto-generated if not provided)

**Audio Configuration:**
- `direction`: Audio direction to tap (default: "both")
- `direction`: Audio direction to tap (default: "both"; always sent — the underlying SWML verb defaults to "speak" when omitted)
- `"speak"`: What party says
- `"hear"`: What party hears
- `"listen"`: What party hears
- `"both"`: What party hears and says
- `codec`: Codec for tap stream - "PCMU" or "PCMA" (default: "PCMU")
- `rtp_ptime`: RTP packetization time in milliseconds (default: 20)
Expand Down
11 changes: 6 additions & 5 deletions signalwire/signalwire/core/function_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,7 @@ def tap(
self,
uri: str,
control_id: str | None = None,
direction: Literal["speak", "hear", "both"] = "both",
direction: Literal["speak", "listen", "both"] = "both",
codec: Literal["PCMU", "PCMA"] = "PCMU",
rtp_ptime: int = 20,
status_url: str | None = None,
Expand All @@ -1378,7 +1378,7 @@ def tap(
Default is generated and stored in tap_control_id variable
direction: Direction of audio to tap (default: "both")
"speak" = what party says
"hear" = what party hears
"listen" = what party hears
"both" = what party hears and says
codec: Codec for tap media stream - "PCMU" or "PCMA" (default: "PCMU")
rtp_ptime: Packetization time in milliseconds for RTP (default: 20)
Expand All @@ -1391,7 +1391,7 @@ def tap(
ValueError: If direction or codec values are invalid
"""
# Validate direction parameter
valid_directions = ["speak", "hear", "both"]
valid_directions = ["speak", "listen", "both"]
if direction not in valid_directions:
raise ValueError(f"direction must be one of {valid_directions}")

Expand All @@ -1410,8 +1410,9 @@ def tap(
# Add optional parameters if they differ from defaults
if control_id:
tap_params["control_id"] = control_id
if direction != "both":
tap_params["direction"] = direction
# Always sent: the verb's own default is "speak", not this helper's
# "both", so omitting it would tap less than the caller asked for.
tap_params["direction"] = direction
if codec != "PCMU":
tap_params["codec"] = codec
if rtp_ptime != 20:
Expand Down
23 changes: 16 additions & 7 deletions tests/unit/core/test_function_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -995,8 +995,10 @@ def test_tap_default_params(self) -> None:
swml = result.action[0]["SWML"]
tap_params = swml["sections"]["main"][0]["tap"]
assert tap_params["uri"] == "rtp://192.168.1.1:5000"
# Default params should not be included
assert "direction" not in tap_params
# direction is always emitted: the SWML verb's own default is "speak",
# so leaving it out would silently tap less than the documented "both".
assert tap_params["direction"] == "both"
# Params whose helper defaults match the verb defaults stay omitted.
assert "codec" not in tap_params
assert "rtp_ptime" not in tap_params

Expand Down Expand Up @@ -1039,11 +1041,18 @@ def test_tap_negative_rtp_ptime(self) -> None:
with pytest.raises(ValueError, match="rtp_ptime must be a positive integer"):
FunctionResult().tap("rtp://1.2.3.4:5000", rtp_ptime=-10)

def test_tap_direction_hear(self) -> None:
"""Test tap with direction=hear"""
result = FunctionResult().tap("rtp://1.2.3.4:5000", direction="hear")
def test_tap_direction_listen(self) -> None:
"""Test tap with direction=listen (the verb's name for the hear side)"""
result = FunctionResult().tap("rtp://1.2.3.4:5000", direction="listen")
tap_params = result.action[0]["SWML"]["sections"]["main"][0]["tap"]
assert tap_params["direction"] == "hear"
assert tap_params["direction"] == "listen"

def test_tap_direction_hear_is_rejected(self) -> None:
""""hear" was never a SWML tap direction — the verb's enum is
speak/listen/both, so emitting it produced a tap the platform
rejects. A loud error beats a silent no-op tap."""
with pytest.raises(ValueError, match="direction must be one of"):
FunctionResult().tap("rtp://1.2.3.4:5000", direction="hear") # type: ignore[arg-type] # intentional invalid input

def test_tap_chaining(self) -> None:
"""Test tap returns self for chaining"""
Expand Down Expand Up @@ -1165,7 +1174,7 @@ def test_validated_closed_sets_declared_as_literal(self) -> None:
literal_cases = [
(FunctionResult.record_call, "format", ("wav", "mp3", "mp4")),
(FunctionResult.record_call, "direction", ("speak", "listen", "both")),
(FunctionResult.tap, "direction", ("speak", "hear", "both")),
(FunctionResult.tap, "direction", ("speak", "listen", "both")),
(FunctionResult.tap, "codec", ("PCMU", "PCMA")),
]
for fn, param, expected in literal_cases:
Expand Down
Loading