Why
The LangWatch platform consumes only the TypeScript package, but the SDK is a product commitment in both languages, and the voice surface has drifted in a few places. This issue records the voice parity matrix as of main @ edf70006 (2026-09-07) plus #982, so gaps are tracked explicitly instead of rediscovered per PR.
Origin: langwatch/langwatch#7978 (voice V2), owner's track 7: "check to see if it's covering both TS and python (the scenario sdk)".
Versions
|
TypeScript |
Python |
main manifest |
javascript/package.json 1.6.0 |
python/pyproject.toml 1.5.0 |
| Latest release tag |
javascript/v1.6.0 (2026-09-06) |
python/v1.5.0 |
| Vendored by the LangWatch platform |
1.3.0 (platform/app/vendor/langwatch-scenario-1.3.0.tgz) |
not consumed |
Adapter matrix
| Adapter |
TypeScript |
Python |
Parity |
| Composable (STT→LLM→TTS) |
javascript/src/voice/adapters/composable.ts |
python/scenario/voice/adapters/composable.py |
same |
| ElevenLabs Convai |
adapters/elevenlabs.ts |
adapters/elevenlabs.py |
same |
| Gemini Live |
adapters/gemini-live.ts |
adapters/gemini_live.py |
differs: TS declares interruption: true, Python matrix lists it deferred (docs/docs/pages/voice/capability-matrix.mdx:52-55) |
| OpenAI Realtime |
adapters/openai-realtime.ts |
adapters/openai_realtime.py |
same |
| Pipecat |
adapters/pipecat.ts |
adapters/pipecat.py |
same (TS webrtc transport pending) |
| Twilio b-leg |
twilio.ts, twilio-server.ts, twilio-shared.ts, twilio-tunnel.ts |
twilio.py, _twilio_server.py, _twilio_shared.py |
same |
| Twilio a-leg external dialing (#982, unmerged) |
same files |
same files |
same, built in lockstep from one spec (specs/voice-twilio-a-leg-external.feature) |
| LiveKit |
absent |
adapters/livekit.py (stub, raises PendingTransportError) |
Python only, #563 |
| Vapi |
absent |
adapters/vapi.py (stub) |
Python only, #563 |
| WebRTC (generic) |
absent |
adapters/webrtc.py (stub) |
Python only, #563 |
| WebSocket (generic, BYO protocol) |
absent |
adapters/websocket.py (real I/O) |
Python only, functional gap |
| Twilio testing harness |
test-only __tests__/a-leg-harness.ts |
public voice/testing/{twilio_harness,tunnel,wrapper_harness}.py |
Python only as public surface |
Telemetry
javascript/src/voice/telemetry.ts declares itself a mirror of python/scenario/voice/_telemetry.py. Span names voice.adapter.connect|disconnect|dial, voice.turn, voice.audio.send, voice.audio.receive and every attribute (voice.adapter.class, voice.turn.index, voice.turn.latency_ms, voice.turn.user_audio_bytes, voice.turn.agent_audio_bytes, voice.audio.bytes, voice.audio.first_chunk_latency_ms, voice.audio.terminated_reason, voice.audio.chunk_count, vendor-specific voice.elevenlabs.*, voice.gemini.*, voice.realtime.*, voice.pipecat.*, voice.twilio.*) match one to one.
Documented, intentional differences:
voice.stt.transcribe: Python per turn under voice.turn (adapter.py:409); TS once per run under a voice.stt.backfill batch span (execution/scenario-execution.ts:852,882). Disambiguated by voice.stt.scope = turn | run.
voice.stt.backfill exists only in TS.
Needs a single-file check, not confirmed as bugs:
- ElevenLabs
voice.elevenlabs.brokered / voice.elevenlabs.session_id are set in Python (elevenlabs.py:446-448); the TS connect span only showed voice.elevenlabs.agent_id (elevenlabs.ts:517).
Recording, judge STT, effects, limits
Same on both sides: PCM16/24 kHz internal format, mulaw/8000 on the Twilio wire, save formats wav/mp3/ogg/flac via ffmpeg, tail-silence 0.6 s with the shared tail_silence reason, judge/backfill STT default gpt-4o-transcribe, effects namespace and assets, Twilio a-leg maxCallDurationSeconds / allowedCallees deny-by-default with matching error text.
Python only: modality negotiation (ModalityTier, resolve_modality, ModalityNegotiationError), process-global set_stt_provider / get_stt_provider (TS removed the global on purpose, ADR-001/002 in config.ts:1-18; Python is moving the same way in #973).
TS only: lowercase factory functions (elevenLabsAgent, twilioAgent, ...), additive sugar.
Test files per adapter
| Adapter |
TS |
Python |
| ElevenLabs |
5 |
10 |
| Gemini Live |
2 |
3 |
| OpenAI Realtime |
7 |
3 |
| Pipecat |
2 |
5 |
| Twilio incl. a-leg |
13 |
16 |
| LiveKit / Vapi / WebRTC / WebSocket |
n/a |
generic stub tests only |
Gaps to close (checklist)
Acceptance criteria
Not in scope
The LangWatch platform's own version drift (vendored 1.3.0 vs 1.6.0) is tracked in langwatch/langwatch#7978 and closed by a platform PR, not here.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KVwRyDjKCmNUesqaKAc4At
Why
The LangWatch platform consumes only the TypeScript package, but the SDK is a product commitment in both languages, and the voice surface has drifted in a few places. This issue records the voice parity matrix as of
main@edf70006(2026-09-07) plus #982, so gaps are tracked explicitly instead of rediscovered per PR.Origin: langwatch/langwatch#7978 (voice V2), owner's track 7: "check to see if it's covering both TS and python (the scenario sdk)".
Versions
mainmanifestjavascript/package.json1.6.0python/pyproject.toml1.5.0javascript/v1.6.0(2026-09-06)python/v1.5.0platform/app/vendor/langwatch-scenario-1.3.0.tgz)Adapter matrix
javascript/src/voice/adapters/composable.tspython/scenario/voice/adapters/composable.pyadapters/elevenlabs.tsadapters/elevenlabs.pyadapters/gemini-live.tsadapters/gemini_live.pyinterruption: true, Python matrix lists it deferred (docs/docs/pages/voice/capability-matrix.mdx:52-55)adapters/openai-realtime.tsadapters/openai_realtime.pyadapters/pipecat.tsadapters/pipecat.pywebrtctransport pending)twilio.ts,twilio-server.ts,twilio-shared.ts,twilio-tunnel.tstwilio.py,_twilio_server.py,_twilio_shared.pyspecs/voice-twilio-a-leg-external.feature)adapters/livekit.py(stub, raisesPendingTransportError)adapters/vapi.py(stub)adapters/webrtc.py(stub)adapters/websocket.py(real I/O)__tests__/a-leg-harness.tsvoice/testing/{twilio_harness,tunnel,wrapper_harness}.pyTelemetry
javascript/src/voice/telemetry.tsdeclares itself a mirror ofpython/scenario/voice/_telemetry.py. Span namesvoice.adapter.connect|disconnect|dial,voice.turn,voice.audio.send,voice.audio.receiveand every attribute (voice.adapter.class,voice.turn.index,voice.turn.latency_ms,voice.turn.user_audio_bytes,voice.turn.agent_audio_bytes,voice.audio.bytes,voice.audio.first_chunk_latency_ms,voice.audio.terminated_reason,voice.audio.chunk_count, vendor-specificvoice.elevenlabs.*,voice.gemini.*,voice.realtime.*,voice.pipecat.*,voice.twilio.*) match one to one.Documented, intentional differences:
voice.stt.transcribe: Python per turn undervoice.turn(adapter.py:409); TS once per run under avoice.stt.backfillbatch span (execution/scenario-execution.ts:852,882). Disambiguated byvoice.stt.scope=turn|run.voice.stt.backfillexists only in TS.Needs a single-file check, not confirmed as bugs:
voice.elevenlabs.brokered/voice.elevenlabs.session_idare set in Python (elevenlabs.py:446-448); the TS connect span only showedvoice.elevenlabs.agent_id(elevenlabs.ts:517).Recording, judge STT, effects, limits
Same on both sides: PCM16/24 kHz internal format,
mulaw/8000on the Twilio wire, save formats wav/mp3/ogg/flac via ffmpeg, tail-silence 0.6 s with the sharedtail_silencereason, judge/backfill STT defaultgpt-4o-transcribe, effects namespace and assets, Twilio a-legmaxCallDurationSeconds/allowedCalleesdeny-by-default with matching error text.Python only: modality negotiation (
ModalityTier,resolve_modality,ModalityNegotiationError), process-globalset_stt_provider/get_stt_provider(TS removed the global on purpose, ADR-001/002 inconfig.ts:1-18; Python is moving the same way in #973).TS only: lowercase factory functions (
elevenLabsAgent,twilioAgent, ...), additive sugar.Test files per adapter
Gaps to close (checklist)
WebSocketAgentAdapter(the only shipping capability Python has that TS lacks). Today the TS workaround is subclassingVoiceAgentAdapter.LiveKit,Vapi,WebRTCstubs (feat(typescript-sdk/voice): remaining TS platform adapter transports — LiveKit, Vapi, generic WebRTC #563): implement in both languages, or remove from the Python public barrel so the matrix is honest.interruptioncapability flag between the TS adapter and the Python capability matrix doc.voice.elevenlabs.brokered/session_idare emitted; add them if the TS adapter brokers a session.docs/docs/pages/voice/capability-matrix.mdx) as the single source and add a CI check that both barrels export the same adapter set, allowing an explicit per-language exemption list.Acceptance criteria
javascript/src/voice/index.ts,python/scenario/voice/__init__.py), when the parity check runs in CI, then every adapter class exported by one language is exported by the other or listed in an exemption file with an issue link. Evidence: CI job green onmain, red on a branch that adds a one-sided export.WebSocketAgentAdapteragainst the same echo server the Python tests use, when the run finishes, then the transcript andvoice.turnspans match the Python run's shape. Evidence: mirrored feature scenario bound in both languages.Not in scope
The LangWatch platform's own version drift (vendored 1.3.0 vs 1.6.0) is tracked in langwatch/langwatch#7978 and closed by a platform PR, not here.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KVwRyDjKCmNUesqaKAc4At