Skip to content

feat(plivo): add Plivo as a third carrier with full Twilio/Telnyx parity#121

Merged
nicolotognoni merged 4 commits into
PatterAI:mainfrom
amalshaji-plivo:feat/plivo-carrier
May 29, 2026
Merged

feat(plivo): add Plivo as a third carrier with full Twilio/Telnyx parity#121
nicolotognoni merged 4 commits into
PatterAI:mainfrom
amalshaji-plivo:feat/plivo-carrier

Conversation

@amalshaji-plivo
Copy link
Copy Markdown
Contributor

@amalshaji-plivo amalshaji-plivo commented May 29, 2026

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.

from getpatter import Patter, Plivo, OpenAIRealtime

phone = Patter(carrier=Plivo(), phone_number="+15550001234")
agent = phone.agent(engine=OpenAIRealtime(), system_prompt="…")
await phone.serve(agent, tunnel=True)

What ships

  • Carrier + adapterPlivo / PlivoAdapter in both SDKs. Outbound dials use Plivo's REST API with answer_url, hangup_url (status callback), and async machine_detection_url.
  • Carrier modulegetpatter.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,py} focused on routes + WS parsing.
  • RoutesPOST /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's Speak API + DELETE hangup (handle_amd_result / dropPlivoVoicemail).
  • Native DTMF send over the media WebSocket — a parity gain over Twilio Media Streams.
  • 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 + .env.example updated.

Wire-protocol deltas (vs Twilio)

Concern Twilio Plivo
Answer response TwiML <Stream url=…> Plivo XML — WSS URL is the text content of <Stream bidirectional contentType=…>
Outbound audio {event:"media", streamSid, media:{payload}} {event:"playAudio", media:{contentType, sampleRate, payload}}
Barge-in flush clear clearAudio (ack: clearedAudio)
Playback marker mark checkpoint (ack: playedStream)
DTMF send not possible {event:"sendDTMF", dtmf}
Outbound dial <Stream> inline in call TwiML REST POST /Call/ with answer_url
Signature HMAC-SHA1 V3 HMAC-SHA256 of url+nonce

Structural 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 the PlivoBridge.attachWs mutable-state workaround. PlivoBridge is now stateless.
  • Pricing roundUp flag — replaces the provider === 'twilio' || 'plivo' ceil branch.
  • Unified ElevenLabs setTelephonyCarrier on the CARRIER_NATIVE_FORMAT map (was if/else in one adapter, map in the other).
  • Extracted dropPlivoVoicemail helper from the inline AMD route.
  • Moved PlivoBridge + helpers out of server.ts into src/telephony/plivo.ts.

Verification

Check Result
Python pytest -m "not soak" 1812 passed, 27 skipped, 0 failed
TypeScript vitest run 1529 passed, 8 skipped, 0 failed
TypeScript tsc --noEmit clean
New Plivo-specific tests 61 (45 Python + 16 TS)
FastAPI route smoke valid V3 sig → 200 + correct answer XML; bad sig → 403; voice/status/amd/transfer + WS route registered

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.
@amalshaji-plivo amalshaji-plivo marked this pull request as draft May 29, 2026 05:16
amalshaji-plivo and others added 3 commits May 29, 2026 11:08
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 ``&amp;``
     - 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 nicolotognoni marked this pull request as ready for review May 29, 2026 10:25
@nicolotognoni nicolotognoni merged commit bba9d62 into PatterAI:main May 29, 2026
18 checks passed
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.
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.
@amalshaji-plivo amalshaji-plivo deleted the feat/plivo-carrier branch May 29, 2026 11:13
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants