attenlabs-saa 0.3.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.
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .env
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .DS_Store
13
+ .vscode/
14
+ .idea/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Attention Labs
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,260 @@
1
+ Metadata-Version: 2.4
2
+ Name: attenlabs-saa
3
+ Version: 0.3.0
4
+ Summary: Python SDK for AttentionLabs real-time attention detection.
5
+ Project-URL: Homepage, https://attentionlabs.ai
6
+ Author: Attention Labs
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: attention,audio,realtime,saa,sd-attention,vad,video,websocket
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: numpy>=1.24
18
+ Requires-Dist: opencv-python>=4.8
19
+ Requires-Dist: sounddevice>=0.4.6
20
+ Requires-Dist: websocket-client>=1.6
21
+ Description-Content-Type: text/markdown
22
+
23
+ # attenlabs-saa
24
+
25
+ Python SDK for [Attention Labs](https://attentionlabs.ai) real-time selective auditory attention.
26
+
27
+ Every voice pipeline has the same problem: the microphone hears everything, but your ASR should only process speech directed at the device. Wake words solve this with a rigid trigger phrase. SAA solves it without one — classifying every audio frame as **silent**, **human-directed**, or **device-directed** and routing only what matters.
28
+
29
+ `attenlabs-saa` streams mic and webcam data to the SAA inference server over WebSocket and emits typed events: attention predictions, voice activity, conversation state, and ready-to-forward speech audio. LLM routing is left to you.
30
+
31
+ ## Sign up
32
+
33
+ Get your API token at [attentionlabs.ai/dashboard](https://attentionlabs.ai/dashboard).
34
+
35
+ **You need your API Key for this project to work**
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install attenlabs-saa
41
+ ```
42
+
43
+ Requires Python 3.10+. `sounddevice` and `opencv-python` are pulled in automatically for mic and camera access.
44
+
45
+ ## Quickstart
46
+
47
+ ```python
48
+ import time
49
+ from saa import AttentionClient
50
+
51
+ client = AttentionClient(token="your-token")
52
+
53
+ @client.on_prediction
54
+ def _(event):
55
+ label = {0: "silent", 1: "human", 2: "device"}.get(event.cls, "?")
56
+ print(f"{label} {event.confidence:.0%} faces={event.num_faces} src={event.source}")
57
+
58
+ @client.on_speech_ready
59
+ def _(event):
60
+ # event.audio_base64 — base64 PCM16 @ 16 kHz mono, ready for OpenAI Realtime / any LLM
61
+ # event.audio_pcm16 — same audio as np.int16 array
62
+ print(f"speech ready ({event.duration_sec:.2f}s)")
63
+
64
+ @client.on_error
65
+ def _(event):
66
+ print(f"ERROR: {event.title}: {event.message}")
67
+
68
+ client.start()
69
+ try:
70
+ while True:
71
+ time.sleep(0.1)
72
+ except KeyboardInterrupt:
73
+ client.stop()
74
+ ```
75
+
76
+ A full CLI demo wiring SAA + OpenAI Realtime lives at [**saa-py-demo**](https://github.com/attenlabs/saa-py-demo).
77
+
78
+ ---
79
+
80
+ ## API
81
+
82
+ ### `AttentionClient`
83
+
84
+ ```python
85
+ from saa import AttentionClient, CameraConfig, MicConfig
86
+
87
+ client = AttentionClient(
88
+ token="...", # Auth token — sent as WS subprotocol
89
+ url=None, # Server URL (default: wss://server.attentionlabs.ai/ws)
90
+ video=CameraConfig(), # Webcam config
91
+ audio=MicConfig(), # Mic config
92
+ initial_threshold=0.7, # Device-class confidence threshold (0..1)
93
+ enable_audio=True, # Set False to skip mic capture
94
+ enable_video=True, # Set False to skip webcam capture
95
+ )
96
+ ```
97
+
98
+ ### Configuration
99
+
100
+ #### `MicConfig`
101
+
102
+ | field | type | default | notes |
103
+ | ---------- | ----------------------- | ------- | ------------------------------------------ |
104
+ | `device` | `int \| str \| None` | `None` | Device index, name, or `None` for system default |
105
+ | `channels` | `int` | `1` | Number of input channels |
106
+
107
+ #### `CameraConfig`
108
+
109
+ | field | type | default | notes |
110
+ | -------------- | ----- | ------- | ----------------------------- |
111
+ | `device_index` | `int` | `0` | Webcam device index |
112
+ | `width` | `int` | `1920` | Capture width |
113
+ | `height` | `int` | `1080` | Capture height |
114
+ | `jpeg_quality` | `int` | `60` | JPEG compression quality 0–100 |
115
+
116
+ ### Methods
117
+
118
+ | method | description |
119
+ | ---------------------------- | ----------- |
120
+ | `start()` | Opens WebSocket, acquires mic + camera, starts capture threads. Non-blocking. Raises on handshake failure. |
121
+ | `stop()` | Tears down capture, joins threads, closes WebSocket. |
122
+ | `mute()` | Pauses upstream audio and signals server to stop VAD. |
123
+ | `unmute()` | Resumes upstream audio. |
124
+ | `mark_responding(bool)` | Tell the server an LLM response is in flight. Server stops emitting predictions while `True`. |
125
+ | `set_threshold(value: float)` | Update device-class confidence threshold (0..1). Server acks via `config` event. |
126
+
127
+ ### Events
128
+
129
+ Register handlers with decorators. All callbacks fire on internal threads — keep them fast or hand work off to your own thread.
130
+
131
+ ```python
132
+ @client.on_prediction
133
+ def handle(event):
134
+ ...
135
+ ```
136
+
137
+ | decorator | payload | fires when |
138
+ | --------------------- | ------------------------------------------------------------------------ | --------------------------------------- |
139
+ | `@on_connected` | — | WebSocket opens |
140
+ | `@on_started` | — | Server-side warmup complete |
141
+ | `@on_warmup_complete` | — | First non-zero-confidence prediction |
142
+ | `@on_prediction` | `PredictionEvent` | Each attention prediction |
143
+ | `@on_vad` | `VadEvent` | Voice activity update |
144
+ | `@on_state` | `StateEvent` | Conversation state transition |
145
+ | `@on_speech_ready` | `SpeechReadyEvent` | Complete speech segment ready to forward |
146
+ | `@on_config` | `ConfigEvent` | Server acks a threshold change |
147
+ | `@on_stats` | `StatsEvent` | Every ~10s with connection health |
148
+ | `@on_error` | `AttentionErrorEvent` | Connection, auth, or server error |
149
+ | `@on_disconnected` | `DisconnectedEvent` | WebSocket closes |
150
+
151
+ ### Event types
152
+
153
+ #### `PredictionEvent`
154
+
155
+ ```python
156
+ cls: int # 0 = silent, 1 = human-directed, 2 = device-directed
157
+ confidence: float # 0..1
158
+ source: str # "video" or "audio"
159
+ num_faces: int # faces detected in frame
160
+ ```
161
+
162
+ #### `VadEvent`
163
+
164
+ ```python
165
+ probability: float # VAD probability 0..1
166
+ is_speech: bool # whether speech was detected
167
+ ```
168
+
169
+ #### `StateEvent`
170
+
171
+ ```python
172
+ state: ConversationState # "listening" | "sending" | "cancelled" | "idle"
173
+ ```
174
+
175
+ #### `SpeechReadyEvent`
176
+
177
+ ```python
178
+ audio_pcm16: np.ndarray # int16 array @ 16 kHz mono
179
+ audio_base64: str # same audio as base64 — ready for OpenAI Realtime, etc.
180
+ duration_sec: float # duration in seconds
181
+ ```
182
+
183
+ #### `ConfigEvent`
184
+
185
+ ```python
186
+ model_class2_threshold: float # server-confirmed threshold
187
+ ```
188
+
189
+ #### `StatsEvent`
190
+
191
+ ```python
192
+ rtt_ms: float | None # round-trip latency in ms
193
+ sent_video: int # total video frames sent
194
+ skipped_video: int # total video frames skipped
195
+ sent_audio: int # total audio chunks sent
196
+ uptime_s: float # connection uptime in seconds
197
+ ```
198
+
199
+ #### `AttentionErrorEvent`
200
+
201
+ ```python
202
+ title: str # error category ("Auth Failed", "Connection Stalled", etc.)
203
+ message: str # human-readable message
204
+ detail: str | None = None # technical detail
205
+ code: int | None = None # WebSocket close code, if applicable
206
+ ```
207
+
208
+ #### `DisconnectedEvent`
209
+
210
+ ```python
211
+ code: int # WebSocket close code
212
+ reason: str # close reason
213
+ was_clean: bool # True if code == 1000
214
+ ```
215
+
216
+ ---
217
+
218
+ ## LLM integration
219
+
220
+ LLM routing is intentionally **not** part of the SDK. The `speech_ready` event hands you PCM16 audio — both as a NumPy array and as base64 — forward it wherever you like.
221
+
222
+ When your LLM starts generating, call `mute()` + `mark_responding(True)` to suppress predictions during playback. When it finishes, `unmute()` + `mark_responding(False)`.
223
+
224
+ ```python
225
+ from saa import AttentionClient
226
+
227
+ client = AttentionClient(token="...")
228
+
229
+ @client.on_speech_ready
230
+ def _(event):
231
+ # Forward to your LLM of choice
232
+ your_llm.send(event.audio_base64)
233
+
234
+ def on_llm_speaking():
235
+ client.mute()
236
+ client.mark_responding(True)
237
+
238
+ def on_llm_done():
239
+ client.unmute()
240
+ client.mark_responding(False)
241
+ ```
242
+
243
+ See [**saa-py-demo**](https://github.com/attenlabs/saa-py-demo) for a full working example with OpenAI Realtime.
244
+
245
+ ## Threading model
246
+
247
+ The SDK manages four threads internally:
248
+
249
+ | thread | purpose |
250
+ | ---------------- | ---------------------------------- |
251
+ | `saa-ws` | WebSocket send/receive |
252
+ | `saa-heartbeat` | JSON pings every 5s, stats every 10s |
253
+ | `saa-camera` | JPEG capture at 4 fps (250 ms) |
254
+ | *(sounddevice)* | Audio callback at native sample rate, resampled to 16 kHz |
255
+
256
+ All event callbacks fire on `saa-ws` or `saa-heartbeat`. Don't block them — offload heavy work to your own thread.
257
+
258
+ ## License
259
+
260
+ MIT
@@ -0,0 +1,238 @@
1
+ # attenlabs-saa
2
+
3
+ Python SDK for [Attention Labs](https://attentionlabs.ai) real-time selective auditory attention.
4
+
5
+ Every voice pipeline has the same problem: the microphone hears everything, but your ASR should only process speech directed at the device. Wake words solve this with a rigid trigger phrase. SAA solves it without one — classifying every audio frame as **silent**, **human-directed**, or **device-directed** and routing only what matters.
6
+
7
+ `attenlabs-saa` streams mic and webcam data to the SAA inference server over WebSocket and emits typed events: attention predictions, voice activity, conversation state, and ready-to-forward speech audio. LLM routing is left to you.
8
+
9
+ ## Sign up
10
+
11
+ Get your API token at [attentionlabs.ai/dashboard](https://attentionlabs.ai/dashboard).
12
+
13
+ **You need your API Key for this project to work**
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install attenlabs-saa
19
+ ```
20
+
21
+ Requires Python 3.10+. `sounddevice` and `opencv-python` are pulled in automatically for mic and camera access.
22
+
23
+ ## Quickstart
24
+
25
+ ```python
26
+ import time
27
+ from saa import AttentionClient
28
+
29
+ client = AttentionClient(token="your-token")
30
+
31
+ @client.on_prediction
32
+ def _(event):
33
+ label = {0: "silent", 1: "human", 2: "device"}.get(event.cls, "?")
34
+ print(f"{label} {event.confidence:.0%} faces={event.num_faces} src={event.source}")
35
+
36
+ @client.on_speech_ready
37
+ def _(event):
38
+ # event.audio_base64 — base64 PCM16 @ 16 kHz mono, ready for OpenAI Realtime / any LLM
39
+ # event.audio_pcm16 — same audio as np.int16 array
40
+ print(f"speech ready ({event.duration_sec:.2f}s)")
41
+
42
+ @client.on_error
43
+ def _(event):
44
+ print(f"ERROR: {event.title}: {event.message}")
45
+
46
+ client.start()
47
+ try:
48
+ while True:
49
+ time.sleep(0.1)
50
+ except KeyboardInterrupt:
51
+ client.stop()
52
+ ```
53
+
54
+ A full CLI demo wiring SAA + OpenAI Realtime lives at [**saa-py-demo**](https://github.com/attenlabs/saa-py-demo).
55
+
56
+ ---
57
+
58
+ ## API
59
+
60
+ ### `AttentionClient`
61
+
62
+ ```python
63
+ from saa import AttentionClient, CameraConfig, MicConfig
64
+
65
+ client = AttentionClient(
66
+ token="...", # Auth token — sent as WS subprotocol
67
+ url=None, # Server URL (default: wss://server.attentionlabs.ai/ws)
68
+ video=CameraConfig(), # Webcam config
69
+ audio=MicConfig(), # Mic config
70
+ initial_threshold=0.7, # Device-class confidence threshold (0..1)
71
+ enable_audio=True, # Set False to skip mic capture
72
+ enable_video=True, # Set False to skip webcam capture
73
+ )
74
+ ```
75
+
76
+ ### Configuration
77
+
78
+ #### `MicConfig`
79
+
80
+ | field | type | default | notes |
81
+ | ---------- | ----------------------- | ------- | ------------------------------------------ |
82
+ | `device` | `int \| str \| None` | `None` | Device index, name, or `None` for system default |
83
+ | `channels` | `int` | `1` | Number of input channels |
84
+
85
+ #### `CameraConfig`
86
+
87
+ | field | type | default | notes |
88
+ | -------------- | ----- | ------- | ----------------------------- |
89
+ | `device_index` | `int` | `0` | Webcam device index |
90
+ | `width` | `int` | `1920` | Capture width |
91
+ | `height` | `int` | `1080` | Capture height |
92
+ | `jpeg_quality` | `int` | `60` | JPEG compression quality 0–100 |
93
+
94
+ ### Methods
95
+
96
+ | method | description |
97
+ | ---------------------------- | ----------- |
98
+ | `start()` | Opens WebSocket, acquires mic + camera, starts capture threads. Non-blocking. Raises on handshake failure. |
99
+ | `stop()` | Tears down capture, joins threads, closes WebSocket. |
100
+ | `mute()` | Pauses upstream audio and signals server to stop VAD. |
101
+ | `unmute()` | Resumes upstream audio. |
102
+ | `mark_responding(bool)` | Tell the server an LLM response is in flight. Server stops emitting predictions while `True`. |
103
+ | `set_threshold(value: float)` | Update device-class confidence threshold (0..1). Server acks via `config` event. |
104
+
105
+ ### Events
106
+
107
+ Register handlers with decorators. All callbacks fire on internal threads — keep them fast or hand work off to your own thread.
108
+
109
+ ```python
110
+ @client.on_prediction
111
+ def handle(event):
112
+ ...
113
+ ```
114
+
115
+ | decorator | payload | fires when |
116
+ | --------------------- | ------------------------------------------------------------------------ | --------------------------------------- |
117
+ | `@on_connected` | — | WebSocket opens |
118
+ | `@on_started` | — | Server-side warmup complete |
119
+ | `@on_warmup_complete` | — | First non-zero-confidence prediction |
120
+ | `@on_prediction` | `PredictionEvent` | Each attention prediction |
121
+ | `@on_vad` | `VadEvent` | Voice activity update |
122
+ | `@on_state` | `StateEvent` | Conversation state transition |
123
+ | `@on_speech_ready` | `SpeechReadyEvent` | Complete speech segment ready to forward |
124
+ | `@on_config` | `ConfigEvent` | Server acks a threshold change |
125
+ | `@on_stats` | `StatsEvent` | Every ~10s with connection health |
126
+ | `@on_error` | `AttentionErrorEvent` | Connection, auth, or server error |
127
+ | `@on_disconnected` | `DisconnectedEvent` | WebSocket closes |
128
+
129
+ ### Event types
130
+
131
+ #### `PredictionEvent`
132
+
133
+ ```python
134
+ cls: int # 0 = silent, 1 = human-directed, 2 = device-directed
135
+ confidence: float # 0..1
136
+ source: str # "video" or "audio"
137
+ num_faces: int # faces detected in frame
138
+ ```
139
+
140
+ #### `VadEvent`
141
+
142
+ ```python
143
+ probability: float # VAD probability 0..1
144
+ is_speech: bool # whether speech was detected
145
+ ```
146
+
147
+ #### `StateEvent`
148
+
149
+ ```python
150
+ state: ConversationState # "listening" | "sending" | "cancelled" | "idle"
151
+ ```
152
+
153
+ #### `SpeechReadyEvent`
154
+
155
+ ```python
156
+ audio_pcm16: np.ndarray # int16 array @ 16 kHz mono
157
+ audio_base64: str # same audio as base64 — ready for OpenAI Realtime, etc.
158
+ duration_sec: float # duration in seconds
159
+ ```
160
+
161
+ #### `ConfigEvent`
162
+
163
+ ```python
164
+ model_class2_threshold: float # server-confirmed threshold
165
+ ```
166
+
167
+ #### `StatsEvent`
168
+
169
+ ```python
170
+ rtt_ms: float | None # round-trip latency in ms
171
+ sent_video: int # total video frames sent
172
+ skipped_video: int # total video frames skipped
173
+ sent_audio: int # total audio chunks sent
174
+ uptime_s: float # connection uptime in seconds
175
+ ```
176
+
177
+ #### `AttentionErrorEvent`
178
+
179
+ ```python
180
+ title: str # error category ("Auth Failed", "Connection Stalled", etc.)
181
+ message: str # human-readable message
182
+ detail: str | None = None # technical detail
183
+ code: int | None = None # WebSocket close code, if applicable
184
+ ```
185
+
186
+ #### `DisconnectedEvent`
187
+
188
+ ```python
189
+ code: int # WebSocket close code
190
+ reason: str # close reason
191
+ was_clean: bool # True if code == 1000
192
+ ```
193
+
194
+ ---
195
+
196
+ ## LLM integration
197
+
198
+ LLM routing is intentionally **not** part of the SDK. The `speech_ready` event hands you PCM16 audio — both as a NumPy array and as base64 — forward it wherever you like.
199
+
200
+ When your LLM starts generating, call `mute()` + `mark_responding(True)` to suppress predictions during playback. When it finishes, `unmute()` + `mark_responding(False)`.
201
+
202
+ ```python
203
+ from saa import AttentionClient
204
+
205
+ client = AttentionClient(token="...")
206
+
207
+ @client.on_speech_ready
208
+ def _(event):
209
+ # Forward to your LLM of choice
210
+ your_llm.send(event.audio_base64)
211
+
212
+ def on_llm_speaking():
213
+ client.mute()
214
+ client.mark_responding(True)
215
+
216
+ def on_llm_done():
217
+ client.unmute()
218
+ client.mark_responding(False)
219
+ ```
220
+
221
+ See [**saa-py-demo**](https://github.com/attenlabs/saa-py-demo) for a full working example with OpenAI Realtime.
222
+
223
+ ## Threading model
224
+
225
+ The SDK manages four threads internally:
226
+
227
+ | thread | purpose |
228
+ | ---------------- | ---------------------------------- |
229
+ | `saa-ws` | WebSocket send/receive |
230
+ | `saa-heartbeat` | JSON pings every 5s, stats every 10s |
231
+ | `saa-camera` | JPEG capture at 4 fps (250 ms) |
232
+ | *(sounddevice)* | Audio callback at native sample rate, resampled to 16 kHz |
233
+
234
+ All event callbacks fire on `saa-ws` or `saa-heartbeat`. Don't block them — offload heavy work to your own thread.
235
+
236
+ ## License
237
+
238
+ MIT
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "attenlabs-saa"
7
+ version = "0.3.0"
8
+ description = "Python SDK for AttentionLabs real-time attention detection."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ { name = "Attention Labs" },
14
+ ]
15
+ keywords = ["sd-attention", "attention", "saa", "audio", "video", "vad", "realtime", "websocket"]
16
+ classifiers = [
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ ]
24
+ dependencies = [
25
+ "numpy>=1.24",
26
+ "websocket-client>=1.6",
27
+ "sounddevice>=0.4.6",
28
+ "opencv-python>=4.8",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://attentionlabs.ai"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/saa"]
36
+
37
+ [tool.hatch.build.targets.sdist]
38
+ include = [
39
+ "src/saa",
40
+ "README.md",
41
+ "LICENSE",
42
+ "pyproject.toml",
43
+ ]
@@ -0,0 +1,36 @@
1
+ """saa-py — Python SDK for the SD Attention Server (SAA)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .capture import CameraConfig, MicConfig
6
+ from .client import AttentionClient
7
+ from .events import (
8
+ AttentionErrorEvent,
9
+ ConfigEvent,
10
+ ConversationState,
11
+ DisconnectedEvent,
12
+ PredictionEvent,
13
+ StateEvent,
14
+ StatsEvent,
15
+ TurnFrame,
16
+ TurnReadyEvent,
17
+ VadEvent,
18
+ )
19
+
20
+ __all__ = [
21
+ "AttentionClient",
22
+ "CameraConfig",
23
+ "MicConfig",
24
+ "PredictionEvent",
25
+ "VadEvent",
26
+ "StateEvent",
27
+ "TurnFrame",
28
+ "TurnReadyEvent",
29
+ "ConfigEvent",
30
+ "StatsEvent",
31
+ "AttentionErrorEvent",
32
+ "DisconnectedEvent",
33
+ "ConversationState",
34
+ ]
35
+
36
+ __version__ = "0.3.0"