voiceblender 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. voiceblender-1.0.0/.claude/settings.json +8 -0
  2. voiceblender-1.0.0/.claude/settings.local.json +14 -0
  3. voiceblender-1.0.0/.gitignore +14 -0
  4. voiceblender-1.0.0/LICENSE +21 -0
  5. voiceblender-1.0.0/Makefile +40 -0
  6. voiceblender-1.0.0/PKG-INFO +121 -0
  7. voiceblender-1.0.0/README.md +70 -0
  8. voiceblender-1.0.0/examples/ivr/.env.example +21 -0
  9. voiceblender-1.0.0/examples/ivr/README.md +118 -0
  10. voiceblender-1.0.0/examples/ivr/main.py +676 -0
  11. voiceblender-1.0.0/pyproject.toml +98 -0
  12. voiceblender-1.0.0/src/voiceblender/__init__.py +189 -0
  13. voiceblender-1.0.0/src/voiceblender/_client.py +212 -0
  14. voiceblender-1.0.0/src/voiceblender/_errors.py +91 -0
  15. voiceblender-1.0.0/src/voiceblender/_events.py +845 -0
  16. voiceblender-1.0.0/src/voiceblender/_http.py +97 -0
  17. voiceblender-1.0.0/src/voiceblender/_hub.py +270 -0
  18. voiceblender-1.0.0/src/voiceblender/_legs.py +642 -0
  19. voiceblender-1.0.0/src/voiceblender/_models.py +159 -0
  20. voiceblender-1.0.0/src/voiceblender/_playback.py +65 -0
  21. voiceblender-1.0.0/src/voiceblender/_requests.py +418 -0
  22. voiceblender-1.0.0/src/voiceblender/_responses.py +18 -0
  23. voiceblender-1.0.0/src/voiceblender/_responses_extra.py +86 -0
  24. voiceblender-1.0.0/src/voiceblender/_rooms.py +390 -0
  25. voiceblender-1.0.0/src/voiceblender/_stream.py +292 -0
  26. voiceblender-1.0.0/src/voiceblender/_sync_helpers.py +354 -0
  27. voiceblender-1.0.0/src/voiceblender/_vsi.py +1567 -0
  28. voiceblender-1.0.0/src/voiceblender/_webrtc.py +55 -0
  29. voiceblender-1.0.0/src/voiceblender/py.typed +0 -0
  30. voiceblender-1.0.0/src/voiceblender/sync/__init__.py +22 -0
  31. voiceblender-1.0.0/src/voiceblender/sync/_facade.py +284 -0
  32. voiceblender-1.0.0/tests/__init__.py +0 -0
  33. voiceblender-1.0.0/tests/_vsi_mock.py +104 -0
  34. voiceblender-1.0.0/tests/conftest.py +25 -0
  35. voiceblender-1.0.0/tests/test_errors.py +54 -0
  36. voiceblender-1.0.0/tests/test_generator_determinism.py +107 -0
  37. voiceblender-1.0.0/tests/test_http_flow.py +177 -0
  38. voiceblender-1.0.0/tests/test_hub.py +124 -0
  39. voiceblender-1.0.0/tests/test_import.py +26 -0
  40. voiceblender-1.0.0/tests/test_ivr_example.py +348 -0
  41. voiceblender-1.0.0/tests/test_naming.py +72 -0
  42. voiceblender-1.0.0/tests/test_parity.py +139 -0
  43. voiceblender-1.0.0/tests/test_playback.py +45 -0
  44. voiceblender-1.0.0/tests/test_roundtrip.py +182 -0
  45. voiceblender-1.0.0/tests/test_sync_facade.py +138 -0
  46. voiceblender-1.0.0/tests/test_vsi_stream.py +126 -0
  47. voiceblender-1.0.0/tools/generate.py +1867 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read(//home/csiwek/go/src/voiceblender-workspace/**)",
5
+ "Bash(ls -la voiceblender-go/)"
6
+ ]
7
+ }
8
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(make generate *)",
5
+ "Bash(python3 -c \"import sys; print\\(sys.prefix\\)\")",
6
+ "Bash(python3 -m pip list)",
7
+ "Bash(python3 -m pip show ruamel.yaml)",
8
+ "Bash(.venv/bin/python -c \"import ruamel.yaml, sys; print\\('ruamel OK in .venv:', ruamel.yaml.__version__ if hasattr\\(ruamel.yaml,'__version__'\\) else 'installed'\\); print\\(sys.executable\\)\")",
9
+ "Bash(.venv/bin/python -m pip list)",
10
+ "Bash(git checkout *)",
11
+ "Bash(PATH=__TRACKED_VAR__/.venv/bin:__TRACKED_VAR__ PY=.venv/bin/python make generate)"
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ .venv/
5
+ venv/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .mypy_cache/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .coverage
13
+ htmlcov/
14
+ .tox/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Messageroute Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # VOICEBLENDER points to the VoiceBlender repository root.
2
+ # Override on the command line: make generate VOICEBLENDER=/other/path
3
+ VOICEBLENDER ?= ../VoiceBlender
4
+ OPENAPI := $(VOICEBLENDER)/openapi.yaml
5
+ ASYNCAPI := $(VOICEBLENDER)/asyncapi.yaml
6
+
7
+ PY ?= python3
8
+
9
+ .PHONY: generate lint format typecheck test build install-dev clean
10
+
11
+ # generate reads openapi.yaml + asyncapi.yaml and rewrites the generated files
12
+ # (_models.py, _requests.py, _responses.py, _events.py, _legs.py, _rooms.py,
13
+ # _webrtc.py, _vsi.py). Run this whenever either spec changes.
14
+ generate:
15
+ $(PY) tools/generate.py --openapi $(OPENAPI) --asyncapi $(ASYNCAPI) --out src/voiceblender
16
+ ruff format src/voiceblender
17
+ ruff check --fix src/voiceblender
18
+ $(MAKE) typecheck
19
+
20
+ lint:
21
+ ruff check src/voiceblender tools tests
22
+
23
+ format:
24
+ ruff format src/voiceblender tools tests
25
+
26
+ typecheck:
27
+ mypy src/voiceblender
28
+
29
+ test:
30
+ pytest -q
31
+
32
+ build:
33
+ $(PY) -m build
34
+
35
+ install-dev:
36
+ $(PY) -m pip install -e ".[dev]"
37
+
38
+ clean:
39
+ rm -rf build/ dist/ *.egg-info src/voiceblender/__pycache__ tests/__pycache__
40
+ rm -rf .mypy_cache .pytest_cache .ruff_cache
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: voiceblender
3
+ Version: 1.0.0
4
+ Summary: Python client for the VoiceBlender API
5
+ Project-URL: Homepage, https://voiceblender.com
6
+ Project-URL: Repository, https://github.com/VoiceBlender/voiceblender-python
7
+ Author: Messageroute Ltd
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Messageroute Ltd
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: sip,telephony,voice,voiceblender,webrtc
31
+ Classifier: Development Status :: 4 - Beta
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Topic :: Communications :: Telephony
39
+ Classifier: Typing :: Typed
40
+ Requires-Python: >=3.10
41
+ Requires-Dist: httpx>=0.27
42
+ Requires-Dist: pydantic>=2.6
43
+ Requires-Dist: websockets>=13
44
+ Provides-Extra: dev
45
+ Requires-Dist: mypy>=1.11; extra == 'dev'
46
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
47
+ Requires-Dist: pytest>=8; extra == 'dev'
48
+ Requires-Dist: ruamel-yaml>=0.18; extra == 'dev'
49
+ Requires-Dist: ruff>=0.6; extra == 'dev'
50
+ Description-Content-Type: text/markdown
51
+
52
+ # voiceblender-python
53
+
54
+ > [!IMPORTANT]
55
+ > **This library is auto-generated.** Models, request/response/event types, and
56
+ > method bindings are produced by `tools/generate.py` from the OpenAPI/AsyncAPI
57
+ > specs (see [Code generation](#code-generation)). Do **not** edit the generated
58
+ > sources directly — your changes will be overwritten on the next run. Open pull
59
+ > requests against the generator (`tools/generate.py`) and/or the upstream specs
60
+ > only.
61
+
62
+ Python client for the [VoiceBlender](https://voiceblender.com) API.
63
+
64
+ VoiceBlender bridges SIP and WebRTC voice calls with multi-party audio mixing,
65
+ real-time speech-to-text, text-to-speech, AI agent integration, recording, and
66
+ webhook-based event delivery.
67
+
68
+ This is a Python port of [voiceblender-go](https://github.com/VoiceBlender/voiceblender-go).
69
+ Models, request types, response types, event types, and method bindings are
70
+ **code-generated** from `../VoiceBlender/openapi.yaml` + `asyncapi.yaml` by
71
+ `tools/generate.py` — the same spec-driven pattern as the Go SDK.
72
+
73
+ ## Installation
74
+
75
+ ```bash
76
+ pip install voiceblender
77
+ ```
78
+
79
+ ## Quick start (async)
80
+
81
+ ```python
82
+ import asyncio
83
+ import voiceblender
84
+
85
+ async def main():
86
+ async with voiceblender.Client(base_url="http://localhost:8080/v1") as c:
87
+ leg = await c.create_leg(voiceblender.CreateLegRequest(
88
+ type=voiceblender.LegType.SIP_OUTBOUND,
89
+ to="sip:alice@example.com",
90
+ ))
91
+ print("Created leg:", leg.id)
92
+
93
+ asyncio.run(main())
94
+ ```
95
+
96
+ ## Quick start (sync)
97
+
98
+ ```python
99
+ from voiceblender.sync import SyncClient
100
+ import voiceblender
101
+
102
+ with SyncClient(base_url="http://localhost:8080/v1") as c:
103
+ leg = c.create_leg(voiceblender.CreateLegRequest(
104
+ type=voiceblender.LegType.SIP_OUTBOUND,
105
+ to="sip:alice@example.com",
106
+ ))
107
+ print("Created leg:", leg.id)
108
+ ```
109
+
110
+ ## Code generation
111
+
112
+ ```bash
113
+ make generate # regenerate from openapi.yaml + asyncapi.yaml
114
+ make test # run pytest
115
+ make lint # ruff check
116
+ make typecheck # mypy --strict
117
+ ```
118
+
119
+ ## License
120
+
121
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,70 @@
1
+ # voiceblender-python
2
+
3
+ > [!IMPORTANT]
4
+ > **This library is auto-generated.** Models, request/response/event types, and
5
+ > method bindings are produced by `tools/generate.py` from the OpenAPI/AsyncAPI
6
+ > specs (see [Code generation](#code-generation)). Do **not** edit the generated
7
+ > sources directly — your changes will be overwritten on the next run. Open pull
8
+ > requests against the generator (`tools/generate.py`) and/or the upstream specs
9
+ > only.
10
+
11
+ Python client for the [VoiceBlender](https://voiceblender.com) API.
12
+
13
+ VoiceBlender bridges SIP and WebRTC voice calls with multi-party audio mixing,
14
+ real-time speech-to-text, text-to-speech, AI agent integration, recording, and
15
+ webhook-based event delivery.
16
+
17
+ This is a Python port of [voiceblender-go](https://github.com/VoiceBlender/voiceblender-go).
18
+ Models, request types, response types, event types, and method bindings are
19
+ **code-generated** from `../VoiceBlender/openapi.yaml` + `asyncapi.yaml` by
20
+ `tools/generate.py` — the same spec-driven pattern as the Go SDK.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install voiceblender
26
+ ```
27
+
28
+ ## Quick start (async)
29
+
30
+ ```python
31
+ import asyncio
32
+ import voiceblender
33
+
34
+ async def main():
35
+ async with voiceblender.Client(base_url="http://localhost:8080/v1") as c:
36
+ leg = await c.create_leg(voiceblender.CreateLegRequest(
37
+ type=voiceblender.LegType.SIP_OUTBOUND,
38
+ to="sip:alice@example.com",
39
+ ))
40
+ print("Created leg:", leg.id)
41
+
42
+ asyncio.run(main())
43
+ ```
44
+
45
+ ## Quick start (sync)
46
+
47
+ ```python
48
+ from voiceblender.sync import SyncClient
49
+ import voiceblender
50
+
51
+ with SyncClient(base_url="http://localhost:8080/v1") as c:
52
+ leg = c.create_leg(voiceblender.CreateLegRequest(
53
+ type=voiceblender.LegType.SIP_OUTBOUND,
54
+ to="sip:alice@example.com",
55
+ ))
56
+ print("Created leg:", leg.id)
57
+ ```
58
+
59
+ ## Code generation
60
+
61
+ ```bash
62
+ make generate # regenerate from openapi.yaml + asyncapi.yaml
63
+ make test # run pytest
64
+ make lint # ruff check
65
+ make typecheck # mypy --strict
66
+ ```
67
+
68
+ ## License
69
+
70
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,21 @@
1
+ # VoiceBlender IVR — example environment variables
2
+ # Copy to .env and fill in the required values.
3
+
4
+ # VoiceBlender API base URL. The IVR connects to <VOICEBLENDER_URL>/vsi
5
+ # (with http:// → ws:// and https:// → wss:// substitution) over a single
6
+ # outbound WebSocket — no inbound port to open.
7
+ VOICEBLENDER_URL=http://localhost:8080/v1
8
+
9
+ # TTS API key (e.g. ElevenLabs).
10
+ # Optional if already configured in the VoiceBlender service.
11
+ # TTS_API_KEY=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
12
+
13
+ # Deepgram API key for the AI agent (operator queue, digit 0).
14
+ # DEEPGRAM_API_KEY=
15
+
16
+ # TTS voice / provider used for all IVR prompts.
17
+ TTS_VOICE=Rachel
18
+ TTS_PROVIDER=elevenlabs
19
+
20
+ # Company name spoken in the greeting.
21
+ COMPANY_NAME=Acme Corp
@@ -0,0 +1,118 @@
1
+ # Example: Company IVR (VSI / Python)
2
+
3
+ A multi-department IVR (Interactive Voice Response) that answers inbound calls,
4
+ greets the caller with a TTS prompt, presents a DTMF menu, and routes the
5
+ caller to a department room — wired up over a **single outbound WebSocket** to
6
+ VoiceBlender's `/v1/vsi` endpoint.
7
+
8
+ This is a VSI redesign of [`../../../voiceblender-go/examples/ivr`](../../../voiceblender-go/examples/ivr),
9
+ which uses REST + HTTP webhooks. The behaviour is identical; the I/O substrate
10
+ is different.
11
+
12
+ ## Call flow
13
+
14
+ ```
15
+ Inbound call
16
+ └─ leg.ringing → early media: UK ringback for 3 s → answer
17
+ └─ leg.connected → "Thank you for calling Acme Corp. Please hold…"
18
+ └─ tts.finished → main menu prompt
19
+ └─ dtmf.received
20
+ 1 → Sales queue
21
+ 2 → Support queue
22
+ 3 → Billing queue
23
+ 0 → Operator queue (Deepgram AI agent)
24
+ 9 → Repeat menu
25
+ * → Goodbye → hang up
26
+ ? → "Invalid option, please try again" (max 3 attempts, then goodbye)
27
+ └─ leg.disconnected → cleanup
28
+ ```
29
+
30
+ Once a caller is routed, they are added to the department's persistent room
31
+ where agents can join to handle the call. Hold music is played in the room
32
+ while they wait.
33
+
34
+ ## Architecture
35
+
36
+ ```
37
+ SIP carrier
38
+ │ inbound INVITE
39
+ ▼
40
+ VoiceBlender ◄──── outbound WS ◄──── IVR (this program)
41
+ │ events (leg.ringing, dtmf.received, tts.finished, …)
42
+ │
43
+ ▼ ──── VSI command frames ──►
44
+ (same WebSocket, bidirectional)
45
+
46
+ ◄──── <cmd>.result ────
47
+ ```
48
+
49
+ No inbound HTTP server. No public DNS. No ngrok. The IVR is a plain WebSocket
50
+ client — deployable behind any firewall that allows outbound connections to
51
+ VoiceBlender.
52
+
53
+ ## Prerequisites
54
+
55
+ - A running [VoiceBlender](https://github.com/VoiceBlender/voiceblender) instance
56
+ reachable on the network from this host
57
+ - An [ElevenLabs](https://elevenlabs.io) API key for TTS prompts, unless already
58
+ configured in VoiceBlender
59
+ - Python 3.10+
60
+
61
+ ## Configuration
62
+
63
+ | Environment variable | Required | Default | Description |
64
+ |----------------------|----------|---------|-------------|
65
+ | `VOICEBLENDER_URL` | no | `http://localhost:8080/v1` | VoiceBlender API base URL |
66
+ | `TTS_API_KEY` | no | — | TTS API key (omit if pre-configured in VoiceBlender) |
67
+ | `TTS_VOICE` | no | `Rachel` | TTS voice name |
68
+ | `TTS_PROVIDER` | no | `elevenlabs` | TTS provider name |
69
+ | `DEEPGRAM_API_KEY` | no | — | Deepgram API key for the operator AI agent |
70
+ | `COMPANY_NAME` | no | `Acme Corp` | Company name spoken in greeting |
71
+
72
+ See [`.env.example`](.env.example) for a copy-paste-ready template.
73
+
74
+ ## Running
75
+
76
+ From the `voiceblender-python` repo root:
77
+
78
+ ```bash
79
+ pip install -e ".[dev]" # one-time
80
+ python examples/ivr/main.py
81
+ ```
82
+
83
+ That's it. No port to forward, no tunnel to set up.
84
+
85
+ VoiceBlender must be configured to send inbound SIP calls to the same
86
+ instance. The IVR creates the four department rooms (`sales`, `support`,
87
+ `billing`, `operator`) on startup if they don't already exist — using the VSI
88
+ `create_room` command, no webhook registration required.
89
+
90
+ ## Code structure
91
+
92
+ Each active call is a `Call` dataclass holding the current IVR state
93
+ (`GREETING → MENU → ROUTED/GOODBYE`). Events arrive over the WebSocket and
94
+ are dispatched to `asyncio.create_task` handlers so the read loop never
95
+ blocks; all state transitions are protected by a per-call `asyncio.Lock`.
96
+
97
+ | Old (REST + webhook) | New (VSI) |
98
+ |----------------------|-----------|
99
+ | `aiohttp.web.Application` + `/webhook` route | `client.events_stream()` + `client.subscribe()` |
100
+ | `voiceblender.Client._do(...)` (HTTP) | `stream.<vsi_method>(...)` (WS) |
101
+ | `leg.early_media(...)` / `leg.play(...)` / … | `stream.leg_early_media(payload)` / `stream.leg_play_start(payload)` / … |
102
+ | `room.add_leg(...)` / `room.play(...)` | `stream.add_leg_to_room(payload)` / `stream.room_play_start(payload)` |
103
+ | `client.create_room(...)` | `stream.create_room(CreateRoomRequest(...))` |
104
+
105
+ The TTS sequencing trick is identical: each call tracks its `active_tts_id`
106
+ and `tts.finished` events for replaced prompts are silently discarded.
107
+
108
+ ## Reconnect
109
+
110
+ `main.py` wraps the stream loop in a simple `while True: try / except +
111
+ asyncio.sleep(5)` block. On any disconnect (network blip, server restart) the
112
+ IVR drops its in-memory call state and reconnects after a fixed 5-second
113
+ delay. In-flight calls that survive the disconnect would have to re-ring; that
114
+ matches what the server already does on its side.
115
+
116
+ A production deployment should swap the fixed delay for exponential backoff
117
+ and may want to reconcile per-call state across reconnects via `list_legs` /
118
+ `list_rooms` VSI commands.