Skip to content

fix(telephony): inbound + handoff metadata parity (Plivo extra_headers, Telnyx client_state)#180

Merged
nicolotognoni merged 1 commit into
mainfrom
fix/inbound-metadata-parity
Jun 15, 2026
Merged

fix(telephony): inbound + handoff metadata parity (Plivo extra_headers, Telnyx client_state)#180
nicolotognoni merged 1 commit into
mainfrom
fix/inbound-metadata-parity

Conversation

@nicolotognoni

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes two SDK-parity bugs in how call context flows through the telephony layer: inbound metadata on Plivo, and context-on-handoff for Telnyx transfers.
  • Both land in both SDKs (Python + TypeScript) with authentic tests.

Implementation

  • Plivo extra_headers → prompt + on_call_start (Fixed). Headers on the Plivo start frame were parsed only for caller/callee recovery and otherwise dropped, so resolve_agent_prompt and on_call_start both received an empty custom_params{placeholder} variables sourced from inbound metadata never resolved on Plivo. Now parsed unconditionally and forwarded as custom_params (internal X-PH-caller/X-PH-callee markers re-exposed as caller/callee), matching Twilio's <Parameter> channel. The TypeScript Plivo bridge never read extra_headers at all; it now passes the same customParams into handleCallStart.
    • libraries/python/getpatter/telephony/plivo.py
    • libraries/typescript/src/providers/plivo-adapter.ts (new parsePlivoExtraHeaders / plivoInboundCustomParams), libraries/typescript/src/server.ts
  • Telnyx transfer client_state (Added, opt-in). The capability existed at the adapter layer in both SDKs but was unreachable (no public option fed it). Exposed as an opt-in client_state / clientState on the programmatic transfer surface (CallControl.transfer / TransferCallOptionsTelnyxBridge.transferCall), base64-encoded into the transfer body so call context can survive a handoff. Optional with no default → the cold-transfer request body stays byte-identical for callers that don't set it; Twilio/Plivo ignore it; the LLM transfer_call tool schema is unchanged (an opaque state string is a developer concern, not an LLM one).
    • libraries/python/getpatter/models.py (CallControl.transfer, _invoke_transfer_fn)
    • libraries/typescript/src/types.ts (TransferCallOptions), libraries/typescript/src/server.ts (TelnyxBridge.transferCall)
  • No dependencies added/removed.

Breaking change?

No. Both changes are opt-in and backward compatible: Plivo behaviour only gains previously-dropped data; the new clientState transfer option is optional with no default, so the cold-transfer request body is byte-identical when unset.

Test plan

  • Python: pytest tests/ — 2664 passed, 8 skipped, 2 xfailed
  • TypeScript: npm test (2112 passed, 9 skipped) + npm run lint (clean) + npm run build (success)
  • /parity-check clean
  • E2E smoke (n/a — no pipeline/handler control-flow change)

Tests are authentic: real Plivo start-frame parsing, real prompt resolution, carrier REST mocked only at the fetch/httpx boundary. Added: test_plivo_handler_unit.py, test_warm_transfer_unit.py (Python); plivo.test.ts, warm-transfer.mocked.test.ts (TypeScript).

Docs updates

N/A in this PR (the new opt-in clientState transfer option could get a one-line mention in the transfer docs as a follow-up).

…s, Telnyx client_state)

Two parity bugs in how call context flows through the telephony layer:

- Plivo extra_headers were parsed only for caller/callee recovery and
  otherwise dropped, so resolve_agent_prompt and on_call_start both got an
  empty custom_params and {placeholder} variables from inbound metadata never
  resolved on Plivo calls. Now parsed unconditionally and forwarded as
  custom_params (X-PH-caller/X-PH-callee re-exposed as caller/callee),
  mirroring Twilio's <Parameter> channel. The TS Plivo bridge never read
  extra_headers at all; it now passes the same customParams to handleCallStart
  (new parsePlivoExtraHeaders / plivoInboundCustomParams helpers).

- Telnyx transfer client_state was implemented at the adapter layer but
  unreachable (no public option fed it). Exposed as an opt-in clientState on
  the programmatic transfer surface (CallControl.transfer /
  TransferCallOptions -> TelnyxBridge.transferCall), base64-encoded into the
  transfer body so context survives a handoff. Optional with no default ->
  cold-transfer body stays byte-identical; Twilio/Plivo ignore it; the LLM
  transfer_call tool schema is unchanged.

Both SDKs, with authentic tests (real start-frame parse, real prompt
resolution, carrier REST mocked only at the fetch/httpx boundary).
@nicolotognoni nicolotognoni merged commit e855d8e into main Jun 15, 2026
10 checks passed
@github-actions github-actions Bot deleted the fix/inbound-metadata-parity branch June 15, 2026 10:25
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