Skip to content

feat(twilio): add sms, mms, and voice helpers#558

Merged
dancer merged 5 commits into
mainfrom
josh/twilio
May 27, 2026
Merged

feat(twilio): add sms, mms, and voice helpers#558
dancer merged 5 commits into
mainfrom
josh/twilio

Conversation

@dancer

@dancer dancer commented May 27, 2026

Copy link
Copy Markdown
Collaborator

summary

adds a first-class Twilio adapter for SMS and MMS bots, plus low-level voice helpers for custom Twilio voice routes

this includes webhook parsing and signature verification, outbound Messages API helpers, phone-number and Messaging Service sending, inbound MMS attachments with authenticated fetchData, plain text card fallback rendering, markdown conversion, and runtime-light api, webhook, voice, and format subpaths

the adapter intentionally avoids the twilio npm runtime dependency so apps can use the low-level helpers without pulling in the full SDK

@vercel

vercel Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview, Comment, Open in v0 May 27, 2026 7:24pm
chat-sdk-nextjs-chat Ready Ready Preview, Comment, Open in v0 May 27, 2026 7:24pm

@dancer dancer merged commit 25ebc3b into main May 27, 2026
17 checks passed
@dancer dancer deleted the josh/twilio branch May 27, 2026 22:39
patrick-chinchill pushed a commit to Chinchill-AI/chat-sdk-python that referenced this pull request Jun 13, 2026
Completes the Twilio Programmable Messaging adapter (10th platform),
porting packages/adapter-twilio from upstream chat@4.30.0. Builds on the
PR-1 scaffolding (types, format converter, cards).

Source:
- adapter.py  -- TwilioAdapter + create_twilio_adapter: webhook routing,
  SMS/MMS send, REST/webhook message parsing, thread-ID encode/decode
  (twilio:{sender}:{recipient}), attachment rehydration, not-implemented
  surfaces (edit/reactions), accumulate-and-post streaming.
- api.py      -- hand-rolled Messages/Calls/Media REST helpers over an
  injectable transport (no official twilio SDK, mirroring upstream);
  non-2xx responses mapped to the typed shared.errors hierarchy.
- webhook.py  -- X-Twilio-Signature HMAC-SHA1 verification (sorted/
  deduplicated base string, GET vs POST signing) compared with
  hmac.compare_digest; custom webhook_verifier precedence; body parsing
  (text / status / unsupported).
- voice.py    -- standalone call + transcription webhook parsing and TwiML
  response builders (Say / Gather speech), XML-escaped.
- utils.py / thread.py -- request/param plumbing, encodeURIComponent
  parity, sender-field split, thread-ID helpers.
- __init__.py / pyproject.toml -- public exports + the [twilio] extra
  (aiohttp); imports stay lazy so the package loads without aiohttp.

Tests (122 total across the package): test_twilio_api.py (28),
test_twilio_webhook.py (23), test_twilio_voice.py (14), and a new
test_twilio_adapter.py (33) porting upstream src/index.test.ts (thread
IDs, webhook routing, media rehydration, SMS/MMS/media-only/messaging-
service sends, REST parsing, fetch_messages merge/sort/limit, streaming).
Real assertions, AsyncMock for async transports, env isolation.

Python-specific divergence (documented in docs/UPSTREAM_SYNC.md and
CHANGELOG.md): the media-download path validates the rehydrated URL
(https + Twilio-owned host) before forwarding HTTP Basic credentials,
where upstream fetchTwilioMedia GETs blindly -- folded into the existing
rehydrate_attachment URL allowlist non-parity row, with a regression test.

Validation: ruff check + format clean; pyrefly 0 errors; audit_test_quality
0 hard failures; full suite 4572 passed / 6 skipped.

https://claude.ai/code/session_013zwTcMek5rNqBTQvs2oF64
patrick-chinchill added a commit to Chinchill-AI/chat-sdk-python that referenced this pull request Jun 18, 2026
)

* feat(twilio): scaffolding — types, format converter (vercel/chat#558, PR 1 of 2)

PR 1 of 2 for the Twilio adapter port (upstream 25ebc3b, chat@4.30.0).
Non-adapter scaffolding only; the adapter (webhook verification, Messages
API, voice helpers, send/stream) lands in PR 2. Mirrors how the Messenger
port was split (#118 / #124).

Adds under src/chat_sdk/adapters/twilio/:
- types.py: TwilioAdapterConfig (all-optional, mirroring upstream;
  TWILIO_MESSAGING_SERVICE_SID / TWILIO_PHONE_NUMBER env fallbacks with
  `??` semantics at construction time, TWILIO_ACCOUNT_SID /
  TWILIO_AUTH_TOKEN resolved lazily at API-call time), TwilioThreadId,
  TwilioCredential/TwilioCredentials, TwilioHttpRequest/TwilioHttpResponse
  (the Python analog of upstream's injectable `fetch`), form-param
  aliases, TwilioWebhookUrl + TwilioWebhookVerifier (SECURITY surface,
  Slack-verifier-style contract), webhook payload dataclasses
  (text/status/unsupported + media), webhook error classes, and the
  TwilioMessageResource / TwilioCallResource wire TypedDicts (functional
  syntax — `from` is a Python keyword; Twilio's wire keys are already
  snake_case).
- format_converter.py: TWILIO_MESSAGE_LIMIT (1600), truncate_twilio_text
  (TypeError on non-positive/non-int limits incl. bools),
  twilio_text_or_placeholder, and TwilioFormatConverter (markdown passes
  through as literal text; tables rewritten to ASCII code blocks).
  Mirrors upstream markdown.ts + format/index.ts.
- cards.py: card_to_twilio_text — shared fallback text with `*` markers
  stripped. Mirrors upstream cards.ts.
- __init__.py: exports types/format/cards only (PR 2 adds the adapter).

Tests (28): tests/test_twilio_format.py (format/index.test.ts +
markdown.test.ts ports + config env-fallback `??` regressions),
tests/test_twilio_cards.py (cards.test.ts port), and
tests/test_twilio_boundaries.py (Python analog of upstream's four
boundary.test.ts files: no top-level aiohttp, no official `twilio` SDK,
helper modules never import the adapter — auto-extends to the PR 2
modules).

Validation: ruff check + format, audit_test_quality, pyrefly (0 errors),
fidelity --strict vs chat@4.29.0, full suite 4474 passed / 6 skipped.

Refs vercel/chat#558.

https://claude.ai/code/session_013zwTcMek5rNqBTQvs2oF64

* feat(twilio): adapter, api, voice, webhook (vercel/chat#558, PR 2 of 2)

Completes the Twilio Programmable Messaging adapter (10th platform),
porting packages/adapter-twilio from upstream chat@4.30.0. Builds on the
PR-1 scaffolding (types, format converter, cards).

Source:
- adapter.py  -- TwilioAdapter + create_twilio_adapter: webhook routing,
  SMS/MMS send, REST/webhook message parsing, thread-ID encode/decode
  (twilio:{sender}:{recipient}), attachment rehydration, not-implemented
  surfaces (edit/reactions), accumulate-and-post streaming.
- api.py      -- hand-rolled Messages/Calls/Media REST helpers over an
  injectable transport (no official twilio SDK, mirroring upstream);
  non-2xx responses mapped to the typed shared.errors hierarchy.
- webhook.py  -- X-Twilio-Signature HMAC-SHA1 verification (sorted/
  deduplicated base string, GET vs POST signing) compared with
  hmac.compare_digest; custom webhook_verifier precedence; body parsing
  (text / status / unsupported).
- voice.py    -- standalone call + transcription webhook parsing and TwiML
  response builders (Say / Gather speech), XML-escaped.
- utils.py / thread.py -- request/param plumbing, encodeURIComponent
  parity, sender-field split, thread-ID helpers.
- __init__.py / pyproject.toml -- public exports + the [twilio] extra
  (aiohttp); imports stay lazy so the package loads without aiohttp.

Tests (122 total across the package): test_twilio_api.py (28),
test_twilio_webhook.py (23), test_twilio_voice.py (14), and a new
test_twilio_adapter.py (33) porting upstream src/index.test.ts (thread
IDs, webhook routing, media rehydration, SMS/MMS/media-only/messaging-
service sends, REST parsing, fetch_messages merge/sort/limit, streaming).
Real assertions, AsyncMock for async transports, env isolation.

Python-specific divergence (documented in docs/UPSTREAM_SYNC.md and
CHANGELOG.md): the media-download path validates the rehydrated URL
(https + Twilio-owned host) before forwarding HTTP Basic credentials,
where upstream fetchTwilioMedia GETs blindly -- folded into the existing
rehydrate_attachment URL allowlist non-parity row, with a regression test.

Validation: ruff check + format clean; pyrefly 0 errors; audit_test_quality
0 hard failures; full suite 4572 passed / 6 skipped.

https://claude.ai/code/session_013zwTcMek5rNqBTQvs2oF64

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

1 participant