Skip to content

VAPI-3989: map Twilio Stream <Parameter> children to StreamParam - #28

Merged
mramasubramanian-bw merged 3 commits into
mainfrom
VAPI-3989/stream-parameter-to-streamparam
Sep 22, 2026
Merged

mramasubramanian-bw merged 3 commits into
mainfrom
VAPI-3989/stream-parameter-to-streamparam

Conversation

@mramasubramanian-bw

@mramasubramanian-bw mramasubramanian-bw commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Twilio bots attach key/value context to a media stream with <Parameter> children on <Stream>. That is how most bots receive callSid, tenant, and similar values when the WebSocket opens. The translator read only the <Stream> attributes and ignored its children, so a Stream with two Parameters produced byte-identical BXML to one with none, no finding was raised, and the bot connected with an empty customParameters map.

Each <Parameter name value/> now becomes a nested <StreamParam name value/> under the emitted <StartStream>, in order. Bandwidth echoes those in its WebSocket start event as streamParams, and a new bridge helper maps that to the Twilio customParameters the bot expects.

Changes

Translator (src/translator/translate.ts)

  • <Parameter> children map to nested <StreamParam/> for both Connect (bidirectional) and Start (fork) streams. Values are XML-escaped by the builder.
  • Bandwidth allows at most 12 StreamParam per StartStream, with name up to 256 characters and value up to 2048. Exceeding any of these makes Bandwidth reject the whole BXML document, so an over-limit <Parameter> is dropped with a Stream warning that names the count or length. Twilio's only limit is 500 characters for name plus value combined, which bounds value but leaves name free to exceed 256 in valid TwiML.
  • A <Parameter> missing name or value, or any non-Parameter child, is dropped with a warning instead of emitting BXML Bandwidth would reject.
  • Duplicate <Parameter> names: streamParams is a flat map, so only one value can survive and which one is undocumented. The first is kept, later ones are dropped with a warning.
  • Findings never echo more than 32 characters of a parameter name.

Bridge (src/streams/bridge.ts)

  • New customParametersFromBwStart() maps Bandwidth's start event (streamParams, a flat name to value map) to the Twilio customParameters map the bridge already forwards in its own start message. String, number, and boolean values are forwarded as strings. Nested objects and arrays are outside the documented flat shape and are skipped. Malformed input yields an empty object. The result is a null-prototype object so a parameter literally named __proto__ is kept rather than swallowed by the prototype setter.
  • Wiring a live Bandwidth source that calls it is VAPI-3991.

Docs: Stream matrix note and AGENTS.md.

Before / after

Input:

<Response><Connect><Stream url="wss://bot.test/ws">
  <Parameter name="callSid" value="CA123"/>
  <Parameter name="tenant" value="acme"/>
</Stream></Connect></Response>

Before:

<Response><StartStream name="connect-stream-1" destination="wss://bot.test/ws" mode="bidirectional" tracks="inbound"/><StopStream name="connect-stream-1" wait="true"/></Response>

After:

<Response><StartStream name="connect-stream-1" destination="wss://bot.test/ws" mode="bidirectional" tracks="inbound"><StreamParam name="callSid" value="CA123"/><StreamParam name="tenant" value="acme"/></StartStream><StopStream name="connect-stream-1" wait="true"/></Response>

Verification

  • npm run typecheck && npx vitest run: 44 files, 321 passed, 6 skipped.
  • New tests in test/translate-stream-conference.test.ts cover ordering, nesting inside StartStream, the fork case, escaping, the 12 cap, the 256 / 2048 length limits, duplicate names, an exact cap tally in mixed cases, invalid Parameters, and unknown children.
  • New tests in test/streams-wire.test.ts push a Bandwidth-shaped start event through the mapper and assert the bot receives it as customParameters, including a key named __proto__.

Open question for VAPI-3991: the length checks count UTF-16 code units because the docs say "characters". If Bandwidth counts bytes, a multibyte value could pass here and still be rejected. Confirm against a live response during fixture capture.

Reference: StartStream docs, StreamParam.

Twilio bots attach key/value context to a media stream with <Parameter>
children on <Stream>; that is how most bots receive callSid, tenant, and
similar values when the WebSocket opens. The translator read only the
<Stream> attributes and ignored its children, so a Stream with two
Parameters produced byte-identical BXML to one with none, no finding was
raised, and the bot connected with an empty customParameters map.

Translator:
- Each <Parameter name value/> becomes a nested <StreamParam name value/>
  under the emitted <StartStream>, in order, for both Connect (bidirectional)
  and Start (fork) streams. Attribute values are XML-escaped by the builder.
- Bandwidth allows at most 12 StreamParam per StartStream; extras are
  dropped with a Stream warning naming the count. Twilio caps name+value at
  500 chars combined, so Bandwidth's 256/2048 per-attribute limits cannot
  be exceeded by valid TwiML and are not re-checked.
- A <Parameter> missing name or value, or any non-Parameter child, is
  dropped with a warning instead of emitting BXML Bandwidth would reject.

Bridge:
- Add customParametersFromBwStart(), which maps Bandwidth's StartStream
  "start" event (streamParams: flat name->value map) to the Twilio
  customParameters map the bridge already forwards in its own "start"
  message. Values are coerced to strings; malformed input yields {}.
  Wiring a live Bandwidth source that calls it is VAPI-3991.

Docs: update the Stream matrix note and AGENTS.md. Tests cover ordering,
nesting inside StartStream, the fork case, escaping, the 12 cap, invalid
Parameters, unknown children, and the bridge mapper end to end.
@mramasubramanian-bw
mramasubramanian-bw requested review from a team as code owners September 22, 2026 00:01
@bwappsec

bwappsec commented Sep 22, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Review follow-ups:

- The earlier comment claimed Twilio's 500-char combined name+value cap
  made Bandwidth's per-attribute limits unreachable. That holds for value
  (2048) but not for name (256): a 300-char Parameter name is valid TwiML
  and made Bandwidth reject the whole BXML document. Drop a Parameter whose
  name exceeds 256 or value exceeds 2048 with a warning that names the
  length, same as the 12-element cap.
- customParametersFromBwStart now builds a null-prototype object so a
  parameter literally named "__proto__" is stored as an own property
  instead of hitting the Object.prototype setter and vanishing.
- The 12-cap test asserted /2 / which also matched "12"; it now checks the
  exact dropped-count phrase. Add tests for the 256/2048 limits and the
  __proto__ key.
…ings

Review polish:

- Duplicate <Parameter> names: Bandwidth delivers streamParams as a flat
  map (as does Twilio's customParameters), so a repeated name can carry one
  value and which wins is undocumented. Keep the first, drop the rest, and
  warn, matching the project's no-silent-degradation convention.
- The 12-cap check now runs before the per-parameter validity checks, so
  every <Parameter> past the 12 accepted ones is tallied under the cap
  warning and its count is exact in mixed cases.
- The missing-name/value warning truncated nothing; a huge name went whole
  into the finding. All finding paths now echo at most 32 chars of a name.
- Drop the conditional around StartStream children: an empty children
  array already serializes as a self-closing tag.
- customParametersFromBwStart forwards only string/number/boolean values.
  Nested objects and arrays are outside the documented flat shape and are
  skipped instead of becoming "[object Object]".

Not changed: length checks count UTF-16 code units, as the docs say
"characters". Whether Bandwidth counts bytes is to be confirmed against a
live response during VAPI-3991 fixture capture.
@mramasubramanian-bw
mramasubramanian-bw merged commit 48e5f7c into main Sep 22, 2026
6 checks passed
@mramasubramanian-bw
mramasubramanian-bw deleted the VAPI-3989/stream-parameter-to-streamparam branch September 22, 2026 19:16
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.

3 participants