feat(plivo): add Plivo as a third carrier with full Twilio/Telnyx parity#121
Merged
Merged
Conversation
Adds Plivo support to the Patter SDK in both Python and TypeScript,
mirroring Twilio/Telnyx feature-for-feature. The authoritative wire
protocol is sourced from plivo-labs/agent-transport
(crates/agent-transport/src/audio_stream/plivo.rs) — Patter consumes it
as a spec, not a runtime dependency.
What ships
----------
* New carrier + adapter: ``Plivo`` and ``PlivoAdapter`` exported from
both SDKs. Outbound calls dial through Plivo's REST API with
``answer_url``, ``hangup_url`` (status callback) and async
``machine_detection_url``.
* New telephony module: ``getpatter.telephony.plivo`` (Python) and
``src/telephony/plivo.ts`` (TS) hold the bridge / answer handler /
V3 signature / AMD classifier / DTMF allowlist in one file per
language, keeping server.ts/server.py focused on routes + WS parsing.
* Routes: ``POST /webhooks/plivo/voice|status|amd``,
``GET|POST /webhooks/plivo/transfer`` (Dial XML for blind transfer),
``WebSocket /ws/plivo/stream/{call_id}``.
* V3 webhook signature: HMAC-SHA256 of ``url + nonce``, base64;
comma-separated header for key rotation.
* Voicemail drop via Plivo Speak API + DELETE hangup
(``handle_amd_result`` / ``dropPlivoVoicemail``).
* Native DTMF send over the media WebSocket (a parity *gain* — Twilio
Media Streams cannot send DTMF).
* Pricing from plivo.com/voice/pricing: \$0.0055/min US inbound local,
\$0.0115/min US outbound local, ceil-to-minute billing reconciled
post-call from the Plivo CDR (``total_amount``).
* Docs (``README.md``, ``docs/concepts.mdx``) and ``.env.example``
files updated.
Structural cleanups landed alongside
------------------------------------
* Introduced ``CarrierKind = 'twilio' | 'telnyx' | 'plivo'`` and
replaced every hand-written union literal in TS.
* Added ``TelephonyBridge.inputWireFormat`` so the StreamHandler picks
the inbound-transcode path off the bridge — not off a string check.
* Made ``TelephonyBridge.sendDtmf(ws, callId, digits, delayMs)`` and
removed the ``PlivoBridge.attachWs`` mutable-state workaround.
* Pricing gained a ``roundUp`` flag, replacing the
``provider === 'twilio' || 'plivo'`` ceil branch.
* Unified ElevenLabs ``setTelephonyCarrier`` on the
``CARRIER_NATIVE_FORMAT`` map (was if/else in one, map in the other).
* Extracted ``dropPlivoVoicemail`` helper from the inline AMD route.
* Moved ``PlivoBridge`` + helpers out of ``server.ts`` into
``src/telephony/plivo.ts`` to keep server.ts focused on the server.
Verification
------------
* Python: 1812 passed, 27 skipped (``pytest -m "not soak"``).
* TypeScript: 1529 passed, 8 skipped (``vitest run``); ``tsc --noEmit`` clean.
* 61 new Plivo tests (45 Python + 16 TS) covering carrier, adapter,
bridge envelopes, V3 signature, AMD classification, pricing, and
the stream-bridge lifecycle.
* End-to-end FastAPI route smoke: valid V3 sig -> 200 + answer XML;
bad sig -> 403; voice/status/amd/transfer + WS route registered.
Two correctness bugs surfaced while verifying the Plivo carrier against the official docs (https://www.plivo.com/docs): 1. V3 webhook signature was wrong ---------------------------------- My initial implementation signed ``url + nonce`` only. The actual V3 scheme (per plivo-python's ``signature_v3`` module) is: * POST: ``signed = url + sorted_post_params + "." + nonce`` where POST params are sorted alphabetically by key (case-sensitive) and concatenated as ``key1value1key2value2…`` with no delimiters. * GET: ``signed = url + "." + nonce`` — query params live in the URL already. Effect of the bug: every real Plivo webhook POST (voice / status / amd) would fail signature verification, locking out the integration from production. ``_validate_plivo_signature`` / ``validatePlivoSignature`` now accept ``params`` and ``method`` and build the correct base string. The route helpers parse the form body *before* signing so the params feed into the calculation. The TS bridge interface and the Python route handler now return ``(form, error_response)`` — mirroring the ``_read_and_validate_twilio_form`` pattern so callers don't re-parse. 2. Speak API content-type was wrong ---------------------------------- The Plivo Speak API (https://www.plivo.com/docs/voice/api/call/speak-text-on-calls) explicitly requires form-encoded body. I was sending JSON, which the Speak endpoint rejects. Fixed in both ``handle_amd_result`` (Python) and ``dropPlivoVoicemail`` (TypeScript) — voicemail drop on a machine-answered call now works. Other endpoints (Make Call, Hangup, Transfer, Record) were verified correct against the docs and the official Plivo SDK: they accept ``application/json`` per Plivo's "HTTP Request" spec, and my JSON bodies match the SDK's wire format. Tests ----- * Python: 1815 passed (1812 + 3 new V3 algorithm tests). * TypeScript: 1534 passed (1529 + 5 new V3 algorithm tests). * New tests assert POST-with-params signing, GET signing, rotation, and rejection of tampered params.
Brings Plivo to parity with Twilio (06) and Telnyx (07) in the notebook
series. Both 08 notebooks mirror the same tier-gated structure:
§1 Quickstart (T1/T2 — zero API keys, ~30 s)
- version check, local mode, cloud-mode guard, three engine types
§2 Feature Tour (T3 — offline crypto demos, ~30 s)
- V3 signature: POST with sorted params + ``.`` + nonce (the real
production case)
- V3 signature: GET (e.g. the ``/webhooks/plivo/transfer`` aleg_url)
- V3 signature: tampered POST param is rejected
- Plivo answer XML: <Stream> with WSS URL as text content,
``&`` escaped to ``&``
- AMD classifier: ``human`` / ``person`` / ``machine`` / ``true`` /
``machine_end_beep`` / ``fax`` / unknown
- Native DTMF send over the media WebSocket — the parity *gain*
over Twilio Media Streams
- E.164 patterns, Plivo carrier construction
§3 Live Appendix (T4 — gated by ENABLE_LIVE_CALLS=1)
- pre-flight env check, live Plivo call with AMD + voicemail drop
To make room, renumbered the existing higher-index notebooks in both
languages: 08-12 → 09-13. No cross-notebook references existed, so the
move is a pure file rename. README, env files, and ``_setup`` env
dataclasses updated accordingly.
Plumbing changes:
- ``examples/notebooks/python/_setup.py`` and
``examples/notebooks/typescript/_setup.ts`` gain
``plivo_auth_id`` / ``plivo_auth_token`` / ``plivo_number`` on
``NotebookEnv``, in the env loader, and in ``KEY_FIELD_MAP``.
- ``examples/notebooks/.env.example`` lists the three new Plivo vars.
- ``examples/notebooks/README.md``: count 12 → 13, OPENAI/TARGET rows
re-numbered for the post-renumber layout, new Plivo creds row,
topic table updated.
Verification:
All offline (§1+§2) cells of the Python notebook were executed
end-to-end against the editable getpatter install and pass — every
V3 sig case, the Plivo XML generator, the AMD classifier, and the
DTMF-send demo print the expected output. T4 cells correctly
auto-skip when ENABLE_LIVE_CALLS is unset.
…kEnv test - test_setup.py: pass the 3 new Plivo fields (plivo_auth_id / plivo_auth_token / plivo_number) when constructing NotebookEnv, which gained them as required fields → fixes setup-tests-python. - typescript/08_telephony_plivo.ipynb: add the "AMD classifier" markdown section to mirror the Python notebook so the Py/TS section headings line up → fixes notebooks/parity. - nbstripout normalization of both 08_telephony_plivo notebooks (cell ids, stripped outputs) → fixes Pre-commit (lint + hygiene). No carrier logic changed — maintainer CI-only fix to unblock merge.
nicolotognoni
added a commit
that referenced
this pull request
May 29, 2026
Forward-fixes landing on top of the Plivo carrier (#121) so the carrier is correct and works with the completion-aware outbound primitive: - stream-handler.ts: add the `plivo → ulaw_8000` case to isTtsOutputFormatNativeForCarrier(). Plivo streams μ-law 8 kHz and the ElevenLabs adapter already emits ulaw_8000 for it, so the previous twilio/telnyx-only check made Pipeline mode re-encode already-μ-law bytes into static. Python was unaffected (Plivo bridge uses for_twilio=True). - server.ts / server.py: wire Plivo's AMD + status webhooks into the call(wait=True) completion registry — record the AMD classification (voicemail vs answered) and resolve no-media outcomes (no_answer / busy / failed) from the Plivo status callback, mirroring the Twilio / Telnyx paths. Without this, call(wait=True) on a Plivo call would hang to the backstop. - CHANGELOG: Plivo carrier (Added) + the TS audio fix (Fixed) under 0.6.3.
Merged
4 tasks
nicolotognoni
added a commit
that referenced
this pull request
May 29, 2026
…+ guaranteed teardown (#120) * feat(python): call(wait=True) -> CallResult + async context manager Adds a completion-aware outbound primitive so an agent can place a call and await its real outcome, instead of hand-wiring on_call_end to an asyncio.Event and remembering disconnect(). - New CallResult frozen dataclass (call_id, outcome, status, duration, transcript, cost, metrics). outcome ∈ answered/voicemail/no_answer/ busy/failed — every value derived from a real carrier signal. - Patter.call(..., wait=False) keyword: wait=True returns a CallResult, resolved by the embedded server's per-call_id completion registry from the first terminal signal (status callback for no-media outcomes; AMD + media-stream end for answered/voicemail). Requires an active server (raises PatterConnectionError otherwise); backstop-timeout bounded. - async with Patter(...) — __aenter__ returns self, __aexit__ runs disconnect() so a stray TTS WS can't keep billing after the block. disconnect() now also fails any in-flight wait=True awaiters. - Server completion registry: fan-out on_call_end (no longer monopolises the single callback slot), AMD classification recorded per call_id, helpers _twilio_status_to_outcome / _telnyx_hangup_outcome. - PatterTool refactored onto call(wait=True): drops the dial-lock / _next_call_id / metrics-SSE machinery and fixes a latent bug where cost_usd/duration_seconds were always dropped (the live payload delivers a CallMetrics dataclass, not the dict the old code probed). Tests: 1890 pass. New test_call_wait.py exercises the real registry + real _on_call_end wrapper with only the carrier adapter mocked. * feat(typescript): call wait -> CallResult + asyncDispose (Python parity) Mirrors the Python feature (HEAD 0c1d984) in TS idioms, same defaults. - types.ts: CallOutcome union + readonly CallResult interface (camelCase), optional wait?: boolean on LocalCallOptions (backward compatible). - client.ts: call() returns Promise<CallResult | void>; wait:true throws PatterConnectionError without an active server, registers a completion on the embedded server keyed by the carrier callId (known synchronously), awaits it via maybeAwaitCompletion() with a ((ringTimeout ?? 25) + 1800)s backstop (unref'd). disconnect() now fails pending completions. [Symbol.asyncDispose]() -> disconnect() for `await using phone = ...`. - server.ts: completion registry on EmbeddedServer (completions/amdClass maps, registerCompletion/deleteCompletion/resolveCompletion/ failPendingCompletions), wired into the 3 terminal sites (Twilio status callback, Telnyx call.hangup, onCallEnd fan-out) + AMD-class recording. Exported twilioStatusToOutcome / telnyxHangupOutcome helpers. - integrations/patter-tool.ts: refactored onto call({wait:true}); dropped the dial-lock/SSE/EventEmitter correlation; adds outcome to the envelope. - index.ts: export CallResult + CallOutcome. Tests: 1532 pass, lint + build clean. New call-wait.test.ts mirrors the Python authentic tests (only the carrier fetch boundary mocked). * chore: export CallOutcome from Python root + CHANGELOG for call(wait) - Export CallOutcome from getpatter __init__/__all__ for symmetry with the TS package root (importable as a type-annotation alias). - CHANGELOG ## Unreleased: Added (call(wait=True)/CallResult + async context manager / asyncDispose, both SDKs) + Fixed (PatterTool Python cost_usd/duration_seconds always-dropped bug). * chore(release): 0.6.3 Bump all three version files (getpatter __init__.py, pyproject.toml, package.json) to 0.6.3 and roll the CHANGELOG ## Unreleased block into ## 0.6.3 (2026-05-29). 0.6.3 ships call(wait=True)/CallResult + the async context manager / asyncDispose, the PatterTool cost/duration fix, plus the OpenClaw/Hermes docs, Telnyx recording handler, and Skills-repo migration already accumulated since 0.6.2. * fix(plivo): TS pipeline audio + wire call(wait=True) into Plivo signals Forward-fixes landing on top of the Plivo carrier (#121) so the carrier is correct and works with the completion-aware outbound primitive: - stream-handler.ts: add the `plivo → ulaw_8000` case to isTtsOutputFormatNativeForCarrier(). Plivo streams μ-law 8 kHz and the ElevenLabs adapter already emits ulaw_8000 for it, so the previous twilio/telnyx-only check made Pipeline mode re-encode already-μ-law bytes into static. Python was unaffected (Plivo bridge uses for_twilio=True). - server.ts / server.py: wire Plivo's AMD + status webhooks into the call(wait=True) completion registry — record the AMD classification (voicemail vs answered) and resolve no-media outcomes (no_answer / busy / failed) from the Plivo status callback, mirroring the Twilio / Telnyx paths. Without this, call(wait=True) on a Plivo call would hang to the backstop. - CHANGELOG: Plivo carrier (Added) + the TS audio fix (Fixed) under 0.6.3. * docs: contributor pre-PR checklist (AGENTS.md, PR template, CONTRIBUTING) Surface the repo's hard requirements at PR time so contributions don't miss them (motivated by an external PR that shipped without a CHANGELOG entry and with un-normalized, drifted notebooks): - AGENTS.md (new, committed): agent-readable contract — parity, CHANGELOG, authentic tests, no competitor headers, async, and the pr-validate.sh gate. The cross-tool standard file (CLAUDE.md / .claude are gitignored). - .github/PULL_REQUEST_TEMPLATE.md: explicit checkboxes for CHANGELOG, both-SDK parity, pr-validate.sh, notebook parity, no-provenance. - CONTRIBUTING.md: "Before you open a PR" checklist mirroring the above.
This was referenced May 29, 2026
amalshaji-plivo
added a commit
to amalshaji-plivo/Patter
that referenced
this pull request
May 29, 2026
…ging keywords A sweep for places where Twilio/Telnyx are still hardcoded as the only two carriers, after PatterAI#121 (carrier code) and PatterAI#122 (carrier docs). Dashboard UI — real user-visible bug ------------------------------------- ``dashboard-app/src/lib/mappers.ts:mapCarrier`` had: if (provider.toLowerCase().includes('telnyx')) return 'telnyx'; return 'twilio'; …so a Plivo call (whose ``telephonyProvider: 'plivo'`` field doesn't contain "telnyx") rendered as **Twilio** in every dashboard panel. The fix introduces a single per-carrier registry as the source of truth and extracts the two display patterns into purpose-built components: * ``CARRIERS: { label, dotClass }`` in ``mappers.ts`` — adding a new carrier is one struct entry. ``CallCarrier`` derives from its keys (``keyof typeof CARRIERS``) and ``mapCarrier`` validates against a ``Set`` built from ``Object.keys(CARRIERS)`` so the union, the runtime check, and the metadata never drift apart. * ``CarrierChip`` and ``CarrierBadge`` (new ``components/CarrierBadge.tsx``) are the only places that know how to *render* a carrier — the four panels that used to each carry inline ternaries / inline hex styles now just drop in the component. Future per-carrier styling lives in one file. * ``.swatch.tw / .tx / .pl`` CSS rules added in ``dashboard.css`` so the swatch shares the existing ``.car-dot.*`` palette via a shared selector — JS no longer carries hex values. * ``CallTable`` now types its local ``Call.carrier`` as the imported ``CallCarrier`` instead of an inline literal union — addresses the pre-existing TODO at ``mappers.ts:9``. As a side-effect, this fixes a pre-existing bug where the Cost and Metrics panel swatches were hardcoded Twilio red (``#cc0000``) for every carrier — Telnyx calls already displayed with a Twilio-coloured swatch. Packaging keywords ------------------ Added ``"plivo"`` to ``libraries/python/pyproject.toml`` and ``libraries/typescript/package.json`` keywords. Per-library READMEs ------------------- ``libraries/python/README.md`` and ``libraries/typescript/README.md`` were missing every Plivo reference. Updated env-var table, "Other carriers" prose, ``Patter`` constructor type signature, API table, flat re-exports list. Root README ----------- "How It Works" ASCII table, ``configure-telephony`` skill row, and ``ring_timeout`` mapping prose all now include Plivo and its ``/webhooks/plivo/status`` callback path. Verification ------------ * ``tsc --noEmit`` (dashboard) — clean. * ``vitest run`` (dashboard) — 8/8 passed.
nicolotognoni
pushed a commit
that referenced
this pull request
May 29, 2026
…x-only mentions (#122) The merged Plivo carrier PR (#121) updated ``docs/concepts.mdx`` but missed the dedicated per-SDK carrier pages and a long tail of one-line "Twilio or Telnyx" references scattered across the doc site. This PR closes both gaps. Per-SDK carrier pages (``docs/{python,typescript}-sdk/carrier.mdx``) ------------------------------------------------------------------- New ``## Plivo`` section in each, mirroring the Twilio/Telnyx sections: * construction example (env-fallback and explicit auth_id/auth_token) * params table (auth_id / auth_token), namespaced form for Python * ``serve()`` auto-config behaviour (creates a Plivo Application + links the number, with ``manage_webhook=False`` / ``manageWebhook: false`` opt-out) * TS-only "how caller / callee reach the agent" section — WSS query string + ``extraHeaders`` fallback (mirrors the existing Telnyx explainer) * wire format and parity gains — pinned mulaw 8 kHz, native DTMF send over the WS, Speak-API voicemail drop, async AMD wiring, hangup_url status callback * V3 signature spec — POST vs GET algorithm, HMAC-SHA256, rotation * four new rows in the "Webhook Endpoints" table (voice, status, amd, transfer) Also corrected the opening capability summary: recording is no longer "Twilio-only" — Telnyx parity landed in #106 and Plivo also has it via the Record API. Updated to call out **native DTMF send** as the Plivo-specific parity gain. Wider sweep ----------- Updated stale "Twilio or Telnyx" / "Twilio + Telnyx" / "Twilio / Telnyx" mentions in: * ``docs/concepts.mdx`` — the "anatomy of a call" diagram and the "Carrier setup" card label. * ``docs/home/index.mdx`` — landing-page supported-carriers line. * ``docs/python-sdk/overview.mdx`` + ``typescript-sdk/overview.mdx`` — prerequisites + "How Patter runs". * ``docs/python-sdk/local-mode.mdx`` + ``typescript-sdk/local-mode.mdx`` — opening blurb and graceful-shutdown step. * ``docs/python-sdk/reference.mdx`` — ``WEBHOOK_VERIFICATION`` error code. * ``docs/python-sdk/tracing.mdx`` — telephony-minutes span emitters. * ``docs/python-sdk/providers/elevenlabs-convai.mdx`` + ``typescript-sdk/providers/elevenlabs-convai.mdx`` — pricing carve-out. * ``docs/python-sdk/tts.mdx`` + ``typescript-sdk/tts.mdx`` — ``.for_twilio()`` factories: noted that Plivo uses the same mulaw 8 kHz wire so the factory applies unchanged, no rename needed. * ``docs/integrations/openclaw.mdx`` — description + ``configure-telephony`` skill row. ``docs/docs.json`` needs no change — it only references the single ``carrier`` page per SDK, not per-carrier subpages.
nicolotognoni
pushed a commit
to amalshaji-plivo/Patter
that referenced
this pull request
May 29, 2026
…ging keywords A sweep for places where Twilio/Telnyx are still hardcoded as the only two carriers, after PatterAI#121 (carrier code) and PatterAI#122 (carrier docs). Dashboard UI — real user-visible bug ------------------------------------- ``dashboard-app/src/lib/mappers.ts:mapCarrier`` had: if (provider.toLowerCase().includes('telnyx')) return 'telnyx'; return 'twilio'; …so a Plivo call (whose ``telephonyProvider: 'plivo'`` field doesn't contain "telnyx") rendered as **Twilio** in every dashboard panel. The fix introduces a single per-carrier registry as the source of truth and extracts the two display patterns into purpose-built components: * ``CARRIERS: { label, dotClass }`` in ``mappers.ts`` — adding a new carrier is one struct entry. ``CallCarrier`` derives from its keys (``keyof typeof CARRIERS``) and ``mapCarrier`` validates against a ``Set`` built from ``Object.keys(CARRIERS)`` so the union, the runtime check, and the metadata never drift apart. * ``CarrierChip`` and ``CarrierBadge`` (new ``components/CarrierBadge.tsx``) are the only places that know how to *render* a carrier — the four panels that used to each carry inline ternaries / inline hex styles now just drop in the component. Future per-carrier styling lives in one file. * ``.swatch.tw / .tx / .pl`` CSS rules added in ``dashboard.css`` so the swatch shares the existing ``.car-dot.*`` palette via a shared selector — JS no longer carries hex values. * ``CallTable`` now types its local ``Call.carrier`` as the imported ``CallCarrier`` instead of an inline literal union — addresses the pre-existing TODO at ``mappers.ts:9``. As a side-effect, this fixes a pre-existing bug where the Cost and Metrics panel swatches were hardcoded Twilio red (``#cc0000``) for every carrier — Telnyx calls already displayed with a Twilio-coloured swatch. Packaging keywords ------------------ Added ``"plivo"`` to ``libraries/python/pyproject.toml`` and ``libraries/typescript/package.json`` keywords. Per-library READMEs ------------------- ``libraries/python/README.md`` and ``libraries/typescript/README.md`` were missing every Plivo reference. Updated env-var table, "Other carriers" prose, ``Patter`` constructor type signature, API table, flat re-exports list. Root README ----------- "How It Works" ASCII table, ``configure-telephony`` skill row, and ``ring_timeout`` mapping prose all now include Plivo and its ``/webhooks/plivo/status`` callback path. Verification ------------ * ``tsc --noEmit`` (dashboard) — clean. * ``vitest run`` (dashboard) — 8/8 passed.
nicolotognoni
pushed a commit
that referenced
this pull request
May 29, 2026
…ging keywords (#123) A sweep for places where Twilio/Telnyx are still hardcoded as the only two carriers, after #121 (carrier code) and #122 (carrier docs). Dashboard UI — real user-visible bug ------------------------------------- ``dashboard-app/src/lib/mappers.ts:mapCarrier`` had: if (provider.toLowerCase().includes('telnyx')) return 'telnyx'; return 'twilio'; …so a Plivo call (whose ``telephonyProvider: 'plivo'`` field doesn't contain "telnyx") rendered as **Twilio** in every dashboard panel. The fix introduces a single per-carrier registry as the source of truth and extracts the two display patterns into purpose-built components: * ``CARRIERS: { label, dotClass }`` in ``mappers.ts`` — adding a new carrier is one struct entry. ``CallCarrier`` derives from its keys (``keyof typeof CARRIERS``) and ``mapCarrier`` validates against a ``Set`` built from ``Object.keys(CARRIERS)`` so the union, the runtime check, and the metadata never drift apart. * ``CarrierChip`` and ``CarrierBadge`` (new ``components/CarrierBadge.tsx``) are the only places that know how to *render* a carrier — the four panels that used to each carry inline ternaries / inline hex styles now just drop in the component. Future per-carrier styling lives in one file. * ``.swatch.tw / .tx / .pl`` CSS rules added in ``dashboard.css`` so the swatch shares the existing ``.car-dot.*`` palette via a shared selector — JS no longer carries hex values. * ``CallTable`` now types its local ``Call.carrier`` as the imported ``CallCarrier`` instead of an inline literal union — addresses the pre-existing TODO at ``mappers.ts:9``. As a side-effect, this fixes a pre-existing bug where the Cost and Metrics panel swatches were hardcoded Twilio red (``#cc0000``) for every carrier — Telnyx calls already displayed with a Twilio-coloured swatch. Packaging keywords ------------------ Added ``"plivo"`` to ``libraries/python/pyproject.toml`` and ``libraries/typescript/package.json`` keywords. Per-library READMEs ------------------- ``libraries/python/README.md`` and ``libraries/typescript/README.md`` were missing every Plivo reference. Updated env-var table, "Other carriers" prose, ``Patter`` constructor type signature, API table, flat re-exports list. Root README ----------- "How It Works" ASCII table, ``configure-telephony`` skill row, and ``ring_timeout`` mapping prose all now include Plivo and its ``/webhooks/plivo/status`` callback path. Verification ------------ * ``tsc --noEmit`` (dashboard) — clean. * ``vitest run`` (dashboard) — 8/8 passed.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Plivo support to the Patter SDK in both Python and TypeScript, mirroring Twilio/Telnyx feature-for-feature. The authoritative wire protocol is sourced from
plivo-labs/agent-transport(crates/agent-transport/src/audio_stream/plivo.rs) — Patter consumes it as a spec, not a runtime dependency.What ships
Plivo/PlivoAdapterin both SDKs. Outbound dials use Plivo's REST API withanswer_url,hangup_url(status callback), and asyncmachine_detection_url.getpatter.telephony.plivo(Python) andsrc/telephony/plivo.ts(TS) hold the bridge / answer handler / V3 signature / AMD classifier / DTMF allowlist in one file per language, keepingserver.{ts,py}focused on routes + WS parsing.POST /webhooks/plivo/{voice,status,amd},GET|POST /webhooks/plivo/transfer(Dial XML for blind transfer),WebSocket /ws/plivo/stream/{call_id}.url + nonce, base64; comma-separated header for key rotation.handle_amd_result/dropPlivoVoicemail).total_amount)..env.exampleupdated.Wire-protocol deltas (vs Twilio)
<Stream url=…><Stream bidirectional contentType=…>{event:"media", streamSid, media:{payload}}{event:"playAudio", media:{contentType, sampleRate, payload}}clearclearAudio(ack:clearedAudio)markcheckpoint(ack:playedStream){event:"sendDTMF", dtmf}<Stream>inline in call TwiMLPOST /Call/withanswer_urlStructural cleanups landed alongside
CarrierKind = 'twilio' \| 'telnyx' \| 'plivo'— replaced every hand-written union literal in TS.TelephonyBridge.inputWireFormat— StreamHandler picks the inbound-transcode path off the bridge, not a string check.TelephonyBridge.sendDtmf(ws, callId, digits, delayMs)— removed thePlivoBridge.attachWsmutable-state workaround. PlivoBridge is now stateless.roundUpflag — replaces theprovider === 'twilio' || 'plivo'ceil branch.setTelephonyCarrieron theCARRIER_NATIVE_FORMATmap (wasif/elsein one adapter,mapin the other).dropPlivoVoicemailhelper from the inline AMD route.PlivoBridge+ helpers out ofserver.tsintosrc/telephony/plivo.ts.Verification
pytest -m "not soak"vitest runtsc --noEmit