pocketstation 0.1.2__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 (108) hide show
  1. pocketstation-0.1.2/PKG-INFO +312 -0
  2. pocketstation-0.1.2/README.md +281 -0
  3. pocketstation-0.1.2/RELEASE_NOTES.md +126 -0
  4. pocketstation-0.1.2/docs/README.md +47 -0
  5. pocketstation-0.1.2/docs/concepts/session-and-bounds.md +50 -0
  6. pocketstation-0.1.2/docs/getting-started/capture.md +80 -0
  7. pocketstation-0.1.2/docs/guides/application-audio.md +66 -0
  8. pocketstation-0.1.2/docs/guides/integrations.md +171 -0
  9. pocketstation-0.1.2/docs/guides/record-and-observe.md +69 -0
  10. pocketstation-0.1.2/docs/guides/relay.md +58 -0
  11. pocketstation-0.1.2/docs/guides/voice.md +90 -0
  12. pocketstation-0.1.2/docs/operations/platform-support.md +125 -0
  13. pocketstation-0.1.2/docs/reference/api-map.md +68 -0
  14. pocketstation-0.1.2/docs/troubleshooting.md +76 -0
  15. pocketstation-0.1.2/examples/README.md +123 -0
  16. pocketstation-0.1.2/examples/debug_voice_ai.py +50 -0
  17. pocketstation-0.1.2/examples/send_audio_to_websocket.py +33 -0
  18. pocketstation-0.1.2/examples/stream_any_app_audio.py +29 -0
  19. pocketstation-0.1.2/examples/transcribe_voice_app.py +23 -0
  20. pocketstation-0.1.2/native/Cargo.lock +2816 -0
  21. pocketstation-0.1.2/native/Cargo.toml +25 -0
  22. pocketstation-0.1.2/native/build.rs +73 -0
  23. pocketstation-0.1.2/native/src/audio_input.rs +289 -0
  24. pocketstation-0.1.2/native/src/connector/driver.rs +636 -0
  25. pocketstation-0.1.2/native/src/connector/mod.rs +17 -0
  26. pocketstation-0.1.2/native/src/connector/observations.rs +181 -0
  27. pocketstation-0.1.2/native/src/connector/values.rs +409 -0
  28. pocketstation-0.1.2/native/src/connector/worker.rs +580 -0
  29. pocketstation-0.1.2/native/src/endpoint_authoring.rs +767 -0
  30. pocketstation-0.1.2/native/src/errors.rs +131 -0
  31. pocketstation-0.1.2/native/src/extensions.rs +416 -0
  32. pocketstation-0.1.2/native/src/graph.rs +1363 -0
  33. pocketstation-0.1.2/native/src/lib.rs +38 -0
  34. pocketstation-0.1.2/native/src/observations.rs +2385 -0
  35. pocketstation-0.1.2/native/src/operator_authoring/driver.rs +533 -0
  36. pocketstation-0.1.2/native/src/operator_authoring/mod.rs +15 -0
  37. pocketstation-0.1.2/native/src/operator_authoring/values.rs +229 -0
  38. pocketstation-0.1.2/native/src/relay.rs +141 -0
  39. pocketstation-0.1.2/native/src/session.rs +1436 -0
  40. pocketstation-0.1.2/native/src/sidecar.rs +485 -0
  41. pocketstation-0.1.2/native/src/signals.rs +1004 -0
  42. pocketstation-0.1.2/native/src/source_authoring/driver.rs +299 -0
  43. pocketstation-0.1.2/native/src/source_authoring/mod.rs +17 -0
  44. pocketstation-0.1.2/native/src/source_authoring/values.rs +221 -0
  45. pocketstation-0.1.2/native/src/sources.rs +692 -0
  46. pocketstation-0.1.2/native/src/streams.rs +399 -0
  47. pocketstation-0.1.2/pyproject.toml +76 -0
  48. pocketstation-0.1.2/python/pocketstation/__init__.py +41 -0
  49. pocketstation-0.1.2/python/pocketstation/_api.py +617 -0
  50. pocketstation-0.1.2/python/pocketstation/_native.pyi +1354 -0
  51. pocketstation-0.1.2/python/pocketstation/aio/__init__.py +23 -0
  52. pocketstation-0.1.2/python/pocketstation/aio/_api.py +226 -0
  53. pocketstation-0.1.2/python/pocketstation/aio/audio_input.py +108 -0
  54. pocketstation-0.1.2/python/pocketstation/aio/capture.py +205 -0
  55. pocketstation-0.1.2/python/pocketstation/aio/connector.py +811 -0
  56. pocketstation-0.1.2/python/pocketstation/aio/control.py +264 -0
  57. pocketstation-0.1.2/python/pocketstation/aio/conversation.py +15 -0
  58. pocketstation-0.1.2/python/pocketstation/aio/endpoint_authoring.py +290 -0
  59. pocketstation-0.1.2/python/pocketstation/aio/event_input.py +161 -0
  60. pocketstation-0.1.2/python/pocketstation/aio/extensions.py +21 -0
  61. pocketstation-0.1.2/python/pocketstation/aio/observations.py +77 -0
  62. pocketstation-0.1.2/python/pocketstation/aio/operator_authoring.py +302 -0
  63. pocketstation-0.1.2/python/pocketstation/aio/relay.py +278 -0
  64. pocketstation-0.1.2/python/pocketstation/aio/session.py +697 -0
  65. pocketstation-0.1.2/python/pocketstation/aio/sidecar.py +146 -0
  66. pocketstation-0.1.2/python/pocketstation/aio/source_authoring.py +300 -0
  67. pocketstation-0.1.2/python/pocketstation/aio/sources.py +42 -0
  68. pocketstation-0.1.2/python/pocketstation/aio/streams.py +268 -0
  69. pocketstation-0.1.2/python/pocketstation/audio_input.py +213 -0
  70. pocketstation-0.1.2/python/pocketstation/capture.py +201 -0
  71. pocketstation-0.1.2/python/pocketstation/compatibility.py +30 -0
  72. pocketstation-0.1.2/python/pocketstation/connector.py +1357 -0
  73. pocketstation-0.1.2/python/pocketstation/control.py +614 -0
  74. pocketstation-0.1.2/python/pocketstation/conversation.py +35 -0
  75. pocketstation-0.1.2/python/pocketstation/endpoint_authoring.py +476 -0
  76. pocketstation-0.1.2/python/pocketstation/errors.py +333 -0
  77. pocketstation-0.1.2/python/pocketstation/extensions.py +167 -0
  78. pocketstation-0.1.2/python/pocketstation/graph.py +1146 -0
  79. pocketstation-0.1.2/python/pocketstation/identity.py +40 -0
  80. pocketstation-0.1.2/python/pocketstation/observations.py +1274 -0
  81. pocketstation-0.1.2/python/pocketstation/operator_authoring.py +327 -0
  82. pocketstation-0.1.2/python/pocketstation/py.typed +0 -0
  83. pocketstation-0.1.2/python/pocketstation/relay.py +419 -0
  84. pocketstation-0.1.2/python/pocketstation/session.py +496 -0
  85. pocketstation-0.1.2/python/pocketstation/sidecar.py +398 -0
  86. pocketstation-0.1.2/python/pocketstation/signal.py +277 -0
  87. pocketstation-0.1.2/python/pocketstation/source_authoring.py +349 -0
  88. pocketstation-0.1.2/python/pocketstation/sources.py +637 -0
  89. pocketstation-0.1.2/python/pocketstation/streams.py +355 -0
  90. pocketstation-0.1.2/python/pocketstation/voice/__init__.py +106 -0
  91. pocketstation-0.1.2/python/pocketstation/voice/capabilities.py +158 -0
  92. pocketstation-0.1.2/python/pocketstation/voice/configuration.py +265 -0
  93. pocketstation-0.1.2/python/pocketstation/voice/conversation.py +1396 -0
  94. pocketstation-0.1.2/python/pocketstation/voice/duplex.py +58 -0
  95. pocketstation-0.1.2/python/pocketstation/voice/errors.py +59 -0
  96. pocketstation-0.1.2/python/pocketstation/voice/events.py +40 -0
  97. pocketstation-0.1.2/python/pocketstation/voice/response.py +105 -0
  98. pocketstation-0.1.2/python/pocketstation/voice/speech_detection.py +63 -0
  99. pocketstation-0.1.2/python/pocketstation/voice/synthesis.py +74 -0
  100. pocketstation-0.1.2/python/pocketstation/voice/transcription.py +120 -0
  101. pocketstation-0.1.2/python/pocketstation/voice/turns.py +81 -0
  102. pocketstation-0.1.2/python/pocketstation_demo/__init__.py +15 -0
  103. pocketstation-0.1.2/python/pocketstation_demo/audio_windows.py +192 -0
  104. pocketstation-0.1.2/python/pocketstation_demo/demo.py +41 -0
  105. pocketstation-0.1.2/python/pocketstation_demo/faster_whisper.py +456 -0
  106. pocketstation-0.1.2/python/pocketstation_demo/openai_realtime.py +1244 -0
  107. pocketstation-0.1.2/python/pocketstation_demo/relay.py +27 -0
  108. pocketstation-0.1.2/python/pocketstation_demo/transcript.py +42 -0
@@ -0,0 +1,312 @@
1
+ Metadata-Version: 2.4
2
+ Name: pocketstation
3
+ Version: 0.1.2
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.11
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: Typing :: Typed
12
+ Requires-Dist: httpx>=0.27
13
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
14
+ Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
15
+ Requires-Dist: mypy>=1.15 ; extra == 'dev'
16
+ Requires-Dist: ruff>=0.11 ; extra == 'dev'
17
+ Requires-Dist: faster-whisper>=1.2.1,<2.0 ; extra == 'transcription'
18
+ Requires-Dist: websockets>=17.0,<18 ; extra == 'voice-agent-debug'
19
+ Provides-Extra: dev
20
+ Provides-Extra: transcription
21
+ Provides-Extra: voice-agent-debug
22
+ Summary: Source-aware live audio capture, processing, and routing for Python
23
+ License: MIT
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
26
+ Project-URL: Documentation, https://github.com/pocketstation-io/sdk-python/tree/main/docs
27
+ Project-URL: Issues, https://github.com/pocketstation-io/sdk-python/issues
28
+ Project-URL: Release notes, https://github.com/pocketstation-io/sdk-python/blob/main/RELEASE_NOTES.md
29
+ Project-URL: Repository, https://github.com/pocketstation-io/sdk-python
30
+
31
+ # PocketStation for Python
32
+
33
+ PocketStation captures one desktop application and an optional microphone as
34
+ separate live audio stems. A single native Session can send those stems to
35
+ Python model code, a remote browser, and a multistem recording without mixing
36
+ their source identities.
37
+
38
+ The Python package uses the PocketStation Rust engine for capture, routing,
39
+ timing, recording, and Relay transport. Your Python code owns the model and
40
+ application logic.
41
+
42
+ ## Capture a desktop application
43
+
44
+ Install PocketStation:
45
+
46
+ ```bash
47
+ python -m pip install pocketstation
48
+ ```
49
+
50
+ Capture one application without opening a microphone or writing files:
51
+
52
+ ```python
53
+ import pocketstation
54
+
55
+ with pocketstation.capture(application="Spotify") as live:
56
+ for frame in live.audio:
57
+ print(frame.source_id, frame.stem_id)
58
+ ```
59
+
60
+ Add `microphone=True` when you need the default microphone as a second
61
+ independent stem. Add `record_to="recordings"` when you want each selected stem
62
+ recorded. Both behaviors are off by default.
63
+
64
+ ## Handle permissions and source changes
65
+
66
+ PocketStation does not prompt during import or discovery. Check microphone
67
+ permission without prompting, then let Source opening report the authoritative
68
+ result. Application capture and microphone capture use separate permissions.
69
+
70
+ When an application or device disappears, PocketStation reports the change and
71
+ does not switch to another Source. Stop the current Session, discover again,
72
+ confirm any changed selection, and create a new Session. Store a discovered
73
+ identity only for its reported persistence scope. Keep fallback and provider
74
+ retry policy explicit and finite.
75
+
76
+ See [Prepare and qualify each Python platform](docs/operations/platform-support.md)
77
+ for the permission states, persistence scopes, and recovery sequence.
78
+
79
+ ## Debug a voice interruption
80
+
81
+ [`examples/debug_voice_ai.py`](examples/debug_voice_ai.py) sends a physical
82
+ microphone to OpenAI Realtime without another voice framework. PocketStation
83
+ keeps microphone input, generated assistant audio, and the selected browser's
84
+ output as independent recorded stems. Provider events and media events share
85
+ one monotonic timeline, so you can see whether delay occurred before the model,
86
+ inside the provider, in local output, or after Relay delivery.
87
+
88
+ See the [voice-agent debugger instructions](examples/README.md#debug-a-voice-agent-interruption-from-the-media-boundary).
89
+ Run repository examples from a source checkout or source archive. The installed
90
+ `pocketstation-demo` command is the packaged application-and-microphone demo.
91
+
92
+ ```bash
93
+ python -m pip install 'pocketstation[transcription]'
94
+ pocketstation-demo
95
+ ```
96
+
97
+ ## Transcribe both sides of a voice application
98
+
99
+ Install the transcription extra, then run the example:
100
+
101
+ ```bash
102
+ python -m pip install 'pocketstation[transcription]'
103
+ python examples/transcribe_voice_app.py
104
+ ```
105
+
106
+ The program asks which desktop voice application to inspect. It declares one
107
+ faster-whisper Operator, connects both stems to its audio input, and prints each
108
+ transcript with its original source identity. It does not start Relay or write a
109
+ recording.
110
+
111
+ The Session runs this path concurrently:
112
+
113
+ ```text
114
+ voice application ─┐
115
+ ├─ one bounded faster-whisper Operator ─ transcripts
116
+ physical microphone┘
117
+ ```
118
+
119
+ The complete composition is visible in
120
+ [`examples/transcribe_voice_app.py`](examples/transcribe_voice_app.py). The
121
+ example adapter imports `faster_whisper.WhisperModel` when the Operator starts;
122
+ the provider is not part of the `pocketstation` namespace.
123
+
124
+ This example does not debug turn handling, interruption, agent latency, or
125
+ browser playout. PocketStation does not receive those events in this program.
126
+
127
+ ## Stream any application audio to a browser
128
+
129
+ Run the Relay example when you want another person to listen in a browser:
130
+
131
+ ```bash
132
+ python examples/stream_any_app_audio.py
133
+ ```
134
+
135
+ Choose any running application that is producing audio. The example publishes
136
+ that application as one named AudioBus, waits for Relay readiness, and prints a
137
+ single-use word code and browser URL. It does not open the microphone or record
138
+ audio.
139
+
140
+ The example uses PocketStation's small, rate-limited demo service unless you set
141
+ `POCKETSTATION_CONTROL_URL` and `POCKETSTATION_RELAY_URL` to services you
142
+ operate. The shared URLs live in `pocketstation_demo`; application code does
143
+ not contain service credentials.
144
+
145
+ ## Read application and microphone audio
146
+
147
+ Set the optional microphone and recording parameters when the workflow needs
148
+ both sides:
149
+
150
+ ```python
151
+ import pocketstation
152
+
153
+ with pocketstation.capture(
154
+ application="Zoom",
155
+ microphone=True,
156
+ record_to="recordings",
157
+ ) as live:
158
+ for frame in live.audio:
159
+ print(frame.source_id, frame.stem_id)
160
+ ```
161
+
162
+ The iterator reads a bounded native endpoint. A slow Python consumer produces
163
+ observable pressure and discontinuities; it does not create an unbounded Python
164
+ audio queue.
165
+
166
+ ## Send application-owned audio into a Session
167
+
168
+ Use `audio_input()` when your application already owns PCM, such as generated
169
+ speech or audio received from a call provider:
170
+
171
+ ```python
172
+ session = pocketstation.Session(recording_root="recordings")
173
+ agent = session.audio_input("agent-output")
174
+ agent.output.record("agent")
175
+
176
+ with session.start():
177
+ agent.write(samples)
178
+ ```
179
+
180
+ The input uses finite preallocated Core buffers. Writes report full, closed,
181
+ cancelled, and invalid-buffer outcomes explicitly.
182
+
183
+ ## Create an integration
184
+
185
+ PocketStation uses four open boundaries:
186
+
187
+ | Boundary | Use it when |
188
+ |---|---|
189
+ | `Source` | Media or signals enter the Session. |
190
+ | `Operator` | Work transforms media or emits typed signals. |
191
+ | `Connector` | Media or signals leave for an external system. |
192
+ | `Endpoint` | You need the lower-level outbound execution contract. |
193
+
194
+ Pass one function when the destination is already open:
195
+
196
+ ```python
197
+ import pocketstation as pks
198
+ import pocketstation.aio as pks_aio
199
+
200
+ async def send_audio(frame: pks.AudioFrame) -> None:
201
+ await socket.send(frame.samples)
202
+
203
+ destination = pks_aio.Connector(send=send_audio)
204
+ application.send_to(destination)
205
+ ```
206
+
207
+ Subclass the synchronous or asyncio contract when the provider opens and
208
+ closes resources. The provider class owns its connection; the Session owns
209
+ bounded delivery, lineage, observations, drain, abort, and joined shutdown:
210
+
211
+ ```python
212
+ class WebSocketConnector(pks_aio.Connector):
213
+ def __init__(self, url, token):
214
+ self.url, self.token = url, token
215
+
216
+ async def start(self):
217
+ self.socket = await connect(self.url, token=self.token)
218
+
219
+ async def send(self, frame: pks.AudioFrame):
220
+ await self.socket.send(frame.samples)
221
+
222
+ async def stop(self):
223
+ await self.socket.close()
224
+ ```
225
+
226
+ Attach one configured object to one or more stems:
227
+
228
+ ```python
229
+ destination = WebSocketConnector(url, token)
230
+ application.send_to(destination)
231
+ microphone.send_to(destination)
232
+ ```
233
+
234
+ PocketStation calls `start()` once, interleaves both source-aware stems through
235
+ `send()`, and calls `stop()` once. A second Connector object creates a separate
236
+ destination. See [Create an integration](docs/guides/integrations.md) for
237
+ deadlines, failures, and the advanced SPI.
238
+
239
+ Python provider callbacks execute on bounded off-realtime workers. They cannot
240
+ be used as native capture callbacks. Compiled native extensions remain the path
241
+ for native provider code, and process sidecars remain available when crash
242
+ isolation is required.
243
+
244
+ ## Use Relay from Python
245
+
246
+ Python creates and deletes RelaySessions through the typed HTTP control client.
247
+ The shared Rust `pocketstation-relay` connector publishes media. The Go Relay
248
+ service forwards WebRTC audio. Python does not encode Opus, write RTP, or own a
249
+ second media plane.
250
+
251
+ The control client uses finite request deadlines, bounded response bodies,
252
+ redacted secrets, and matching synchronous and asyncio APIs.
253
+
254
+ ## Sync and asyncio
255
+
256
+ `pocketstation` and `pocketstation.aio` operate the same native Session. The
257
+ asyncio namespace provides awaitable lifecycle, stream, Relay, provider, and
258
+ audio-input operations without creating another audio queue.
259
+
260
+ Python callbacks still cross the interpreter boundary. Capture, routing,
261
+ recording, and Relay transport remain native-speed; arbitrary Python model code
262
+ does not have the same execution cost as Rust.
263
+
264
+ ## Platform support
265
+
266
+ | Area | Support |
267
+ |---|---|
268
+ | Python | 3.11 and newer |
269
+ | macOS Apple silicon | Installed wheel, application capture, physical microphone, 10 ms voice path, Relay, Chromium, and multistem recording tested |
270
+ | Linux | Core application selection and 10 ms capture tested; installed Python distribution qualification in progress |
271
+ | Windows 11 ARM64 | Core application selection and 10 ms capture tested in a VM; installed Python distribution and physical-device qualification in progress |
272
+ | WAN and TURN | Not yet qualified |
273
+
274
+ The native binding uses PocketStation Core `1.1.4` and the shared Relay
275
+ Connector `0.1.2`.
276
+
277
+ The Rust-to-Python audio read currently copies native samples into Python-owned
278
+ bytes before exposing a `memoryview`. The view avoids another Python-side copy;
279
+ the complete boundary is not zero-copy.
280
+
281
+ ## Develop the SDK
282
+
283
+ ```bash
284
+ uv sync --extra transcription
285
+ uv run pytest -q
286
+ uv run ruff check python tests examples
287
+ uv run ruff format --check python tests examples
288
+ uv run mypy python tests/qualification/typing_contract.py examples
289
+ ```
290
+
291
+ ## Reference
292
+
293
+ - [`RELEASE_NOTES.md`](RELEASE_NOTES.md) — user-visible changes and upgrade
294
+ guidance.
295
+ - [`docs/README.md`](docs/README.md) — task guides, concepts, operations, and
296
+ API ownership.
297
+ - [Write application-owned audio](docs/guides/application-audio.md) — bounded
298
+ PCM input and selective output cancellation.
299
+ - [Record and observe a Session](docs/guides/record-and-observe.md) — multistem
300
+ outcomes, route metrics, and lifecycle events.
301
+ - [Prepare each platform](docs/operations/platform-support.md) — permissions,
302
+ source persistence, explicit rediscovery, and fallback policy.
303
+ - [`examples/README.md`](examples/README.md) — runnable examples and
304
+ prerequisites.
305
+ - `pocketstation.capture` — concise application and microphone capture.
306
+ - `pocketstation.session` — Session declarations and lifecycle.
307
+ - `pocketstation.graph` — stems, ports, routes, and signal contracts.
308
+ - `pocketstation.connector` — outbound provider authoring.
309
+ - `pocketstation.operator_authoring` — computation authoring.
310
+ - `pocketstation.source_authoring` — inbound provider authoring.
311
+ - `pocketstation.aio` — asyncio projection of the same engine.
312
+
@@ -0,0 +1,281 @@
1
+ # PocketStation for Python
2
+
3
+ PocketStation captures one desktop application and an optional microphone as
4
+ separate live audio stems. A single native Session can send those stems to
5
+ Python model code, a remote browser, and a multistem recording without mixing
6
+ their source identities.
7
+
8
+ The Python package uses the PocketStation Rust engine for capture, routing,
9
+ timing, recording, and Relay transport. Your Python code owns the model and
10
+ application logic.
11
+
12
+ ## Capture a desktop application
13
+
14
+ Install PocketStation:
15
+
16
+ ```bash
17
+ python -m pip install pocketstation
18
+ ```
19
+
20
+ Capture one application without opening a microphone or writing files:
21
+
22
+ ```python
23
+ import pocketstation
24
+
25
+ with pocketstation.capture(application="Spotify") as live:
26
+ for frame in live.audio:
27
+ print(frame.source_id, frame.stem_id)
28
+ ```
29
+
30
+ Add `microphone=True` when you need the default microphone as a second
31
+ independent stem. Add `record_to="recordings"` when you want each selected stem
32
+ recorded. Both behaviors are off by default.
33
+
34
+ ## Handle permissions and source changes
35
+
36
+ PocketStation does not prompt during import or discovery. Check microphone
37
+ permission without prompting, then let Source opening report the authoritative
38
+ result. Application capture and microphone capture use separate permissions.
39
+
40
+ When an application or device disappears, PocketStation reports the change and
41
+ does not switch to another Source. Stop the current Session, discover again,
42
+ confirm any changed selection, and create a new Session. Store a discovered
43
+ identity only for its reported persistence scope. Keep fallback and provider
44
+ retry policy explicit and finite.
45
+
46
+ See [Prepare and qualify each Python platform](docs/operations/platform-support.md)
47
+ for the permission states, persistence scopes, and recovery sequence.
48
+
49
+ ## Debug a voice interruption
50
+
51
+ [`examples/debug_voice_ai.py`](examples/debug_voice_ai.py) sends a physical
52
+ microphone to OpenAI Realtime without another voice framework. PocketStation
53
+ keeps microphone input, generated assistant audio, and the selected browser's
54
+ output as independent recorded stems. Provider events and media events share
55
+ one monotonic timeline, so you can see whether delay occurred before the model,
56
+ inside the provider, in local output, or after Relay delivery.
57
+
58
+ See the [voice-agent debugger instructions](examples/README.md#debug-a-voice-agent-interruption-from-the-media-boundary).
59
+ Run repository examples from a source checkout or source archive. The installed
60
+ `pocketstation-demo` command is the packaged application-and-microphone demo.
61
+
62
+ ```bash
63
+ python -m pip install 'pocketstation[transcription]'
64
+ pocketstation-demo
65
+ ```
66
+
67
+ ## Transcribe both sides of a voice application
68
+
69
+ Install the transcription extra, then run the example:
70
+
71
+ ```bash
72
+ python -m pip install 'pocketstation[transcription]'
73
+ python examples/transcribe_voice_app.py
74
+ ```
75
+
76
+ The program asks which desktop voice application to inspect. It declares one
77
+ faster-whisper Operator, connects both stems to its audio input, and prints each
78
+ transcript with its original source identity. It does not start Relay or write a
79
+ recording.
80
+
81
+ The Session runs this path concurrently:
82
+
83
+ ```text
84
+ voice application ─┐
85
+ ├─ one bounded faster-whisper Operator ─ transcripts
86
+ physical microphone┘
87
+ ```
88
+
89
+ The complete composition is visible in
90
+ [`examples/transcribe_voice_app.py`](examples/transcribe_voice_app.py). The
91
+ example adapter imports `faster_whisper.WhisperModel` when the Operator starts;
92
+ the provider is not part of the `pocketstation` namespace.
93
+
94
+ This example does not debug turn handling, interruption, agent latency, or
95
+ browser playout. PocketStation does not receive those events in this program.
96
+
97
+ ## Stream any application audio to a browser
98
+
99
+ Run the Relay example when you want another person to listen in a browser:
100
+
101
+ ```bash
102
+ python examples/stream_any_app_audio.py
103
+ ```
104
+
105
+ Choose any running application that is producing audio. The example publishes
106
+ that application as one named AudioBus, waits for Relay readiness, and prints a
107
+ single-use word code and browser URL. It does not open the microphone or record
108
+ audio.
109
+
110
+ The example uses PocketStation's small, rate-limited demo service unless you set
111
+ `POCKETSTATION_CONTROL_URL` and `POCKETSTATION_RELAY_URL` to services you
112
+ operate. The shared URLs live in `pocketstation_demo`; application code does
113
+ not contain service credentials.
114
+
115
+ ## Read application and microphone audio
116
+
117
+ Set the optional microphone and recording parameters when the workflow needs
118
+ both sides:
119
+
120
+ ```python
121
+ import pocketstation
122
+
123
+ with pocketstation.capture(
124
+ application="Zoom",
125
+ microphone=True,
126
+ record_to="recordings",
127
+ ) as live:
128
+ for frame in live.audio:
129
+ print(frame.source_id, frame.stem_id)
130
+ ```
131
+
132
+ The iterator reads a bounded native endpoint. A slow Python consumer produces
133
+ observable pressure and discontinuities; it does not create an unbounded Python
134
+ audio queue.
135
+
136
+ ## Send application-owned audio into a Session
137
+
138
+ Use `audio_input()` when your application already owns PCM, such as generated
139
+ speech or audio received from a call provider:
140
+
141
+ ```python
142
+ session = pocketstation.Session(recording_root="recordings")
143
+ agent = session.audio_input("agent-output")
144
+ agent.output.record("agent")
145
+
146
+ with session.start():
147
+ agent.write(samples)
148
+ ```
149
+
150
+ The input uses finite preallocated Core buffers. Writes report full, closed,
151
+ cancelled, and invalid-buffer outcomes explicitly.
152
+
153
+ ## Create an integration
154
+
155
+ PocketStation uses four open boundaries:
156
+
157
+ | Boundary | Use it when |
158
+ |---|---|
159
+ | `Source` | Media or signals enter the Session. |
160
+ | `Operator` | Work transforms media or emits typed signals. |
161
+ | `Connector` | Media or signals leave for an external system. |
162
+ | `Endpoint` | You need the lower-level outbound execution contract. |
163
+
164
+ Pass one function when the destination is already open:
165
+
166
+ ```python
167
+ import pocketstation as pks
168
+ import pocketstation.aio as pks_aio
169
+
170
+ async def send_audio(frame: pks.AudioFrame) -> None:
171
+ await socket.send(frame.samples)
172
+
173
+ destination = pks_aio.Connector(send=send_audio)
174
+ application.send_to(destination)
175
+ ```
176
+
177
+ Subclass the synchronous or asyncio contract when the provider opens and
178
+ closes resources. The provider class owns its connection; the Session owns
179
+ bounded delivery, lineage, observations, drain, abort, and joined shutdown:
180
+
181
+ ```python
182
+ class WebSocketConnector(pks_aio.Connector):
183
+ def __init__(self, url, token):
184
+ self.url, self.token = url, token
185
+
186
+ async def start(self):
187
+ self.socket = await connect(self.url, token=self.token)
188
+
189
+ async def send(self, frame: pks.AudioFrame):
190
+ await self.socket.send(frame.samples)
191
+
192
+ async def stop(self):
193
+ await self.socket.close()
194
+ ```
195
+
196
+ Attach one configured object to one or more stems:
197
+
198
+ ```python
199
+ destination = WebSocketConnector(url, token)
200
+ application.send_to(destination)
201
+ microphone.send_to(destination)
202
+ ```
203
+
204
+ PocketStation calls `start()` once, interleaves both source-aware stems through
205
+ `send()`, and calls `stop()` once. A second Connector object creates a separate
206
+ destination. See [Create an integration](docs/guides/integrations.md) for
207
+ deadlines, failures, and the advanced SPI.
208
+
209
+ Python provider callbacks execute on bounded off-realtime workers. They cannot
210
+ be used as native capture callbacks. Compiled native extensions remain the path
211
+ for native provider code, and process sidecars remain available when crash
212
+ isolation is required.
213
+
214
+ ## Use Relay from Python
215
+
216
+ Python creates and deletes RelaySessions through the typed HTTP control client.
217
+ The shared Rust `pocketstation-relay` connector publishes media. The Go Relay
218
+ service forwards WebRTC audio. Python does not encode Opus, write RTP, or own a
219
+ second media plane.
220
+
221
+ The control client uses finite request deadlines, bounded response bodies,
222
+ redacted secrets, and matching synchronous and asyncio APIs.
223
+
224
+ ## Sync and asyncio
225
+
226
+ `pocketstation` and `pocketstation.aio` operate the same native Session. The
227
+ asyncio namespace provides awaitable lifecycle, stream, Relay, provider, and
228
+ audio-input operations without creating another audio queue.
229
+
230
+ Python callbacks still cross the interpreter boundary. Capture, routing,
231
+ recording, and Relay transport remain native-speed; arbitrary Python model code
232
+ does not have the same execution cost as Rust.
233
+
234
+ ## Platform support
235
+
236
+ | Area | Support |
237
+ |---|---|
238
+ | Python | 3.11 and newer |
239
+ | macOS Apple silicon | Installed wheel, application capture, physical microphone, 10 ms voice path, Relay, Chromium, and multistem recording tested |
240
+ | Linux | Core application selection and 10 ms capture tested; installed Python distribution qualification in progress |
241
+ | Windows 11 ARM64 | Core application selection and 10 ms capture tested in a VM; installed Python distribution and physical-device qualification in progress |
242
+ | WAN and TURN | Not yet qualified |
243
+
244
+ The native binding uses PocketStation Core `1.1.4` and the shared Relay
245
+ Connector `0.1.2`.
246
+
247
+ The Rust-to-Python audio read currently copies native samples into Python-owned
248
+ bytes before exposing a `memoryview`. The view avoids another Python-side copy;
249
+ the complete boundary is not zero-copy.
250
+
251
+ ## Develop the SDK
252
+
253
+ ```bash
254
+ uv sync --extra transcription
255
+ uv run pytest -q
256
+ uv run ruff check python tests examples
257
+ uv run ruff format --check python tests examples
258
+ uv run mypy python tests/qualification/typing_contract.py examples
259
+ ```
260
+
261
+ ## Reference
262
+
263
+ - [`RELEASE_NOTES.md`](RELEASE_NOTES.md) — user-visible changes and upgrade
264
+ guidance.
265
+ - [`docs/README.md`](docs/README.md) — task guides, concepts, operations, and
266
+ API ownership.
267
+ - [Write application-owned audio](docs/guides/application-audio.md) — bounded
268
+ PCM input and selective output cancellation.
269
+ - [Record and observe a Session](docs/guides/record-and-observe.md) — multistem
270
+ outcomes, route metrics, and lifecycle events.
271
+ - [Prepare each platform](docs/operations/platform-support.md) — permissions,
272
+ source persistence, explicit rediscovery, and fallback policy.
273
+ - [`examples/README.md`](examples/README.md) — runnable examples and
274
+ prerequisites.
275
+ - `pocketstation.capture` — concise application and microphone capture.
276
+ - `pocketstation.session` — Session declarations and lifecycle.
277
+ - `pocketstation.graph` — stems, ports, routes, and signal contracts.
278
+ - `pocketstation.connector` — outbound provider authoring.
279
+ - `pocketstation.operator_authoring` — computation authoring.
280
+ - `pocketstation.source_authoring` — inbound provider authoring.
281
+ - `pocketstation.aio` — asyncio projection of the same engine.