Skip to content

SWML messaging — integration#317

Open
Devon-White wants to merge 32 commits into
mainfrom
Devon/swml-messaging
Open

SWML messaging — integration#317
Devon-White wants to merge 32 commits into
mainfrom
Devon/swml-messaging

Conversation

@Devon-White
Copy link
Copy Markdown
Collaborator

Integration PR. Aggregates two child PRs into a single atomic ship to main.

Child PRs (both target this integration branch)

When both children merge in, this PR's diff becomes the union (schema + docs + this README seed). Approve and squash-merge to ship the schema + docs to prod simultaneously.

Why an integration branch?

Schema and docs are reviewable independently (zero file overlap) but must ship together — publishing the messaging schema without docs (or vice versa) leaves the feature half-shipped. The integration branch lets each child PR get its own focused review while preserving atomic delivery to main.

Current diff vs main

Only the README update that seeded this branch — a clearer breakdown of the three TypeSpec projects under specs/ and what each one emits. Real scope lands when the child PRs merge.

After both children merge

  1. Rebase this PR on main if main has moved.
  2. Re-run cd specs && yarn build:all to refresh generated outputs.
  3. Spot-check Fern docs preview.
  4. Squash-merge to main.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

The README's prior single-line description of the spec workspace was
accurate but thin — readers couldn't tell that `specs/` actually houses
three sibling TypeSpec projects with different downstream artifacts.

Adds a table covering each project (signalwire-rest, compatibility-api,
SWML), what it compiles to (OpenAPI 3.1 for the REST projects, JSON
Schema for SWML), and which surface that artifact powers. Calls out
`specs/_shared/` as the home of repo-wide scalars and the custom
`@webhook(...)` decorator, and pins down the don't-hand-edit rule for
generated artifacts in `fern/apis/` and `specs/swml/**/tsp-output/`.

This commit seeds the SWML messaging integration branch so the
integration → main PR can be opened. The two child PRs
(Devon/swml-schema, Devon/swml-docs) target this integration branch and
merge into it independently.
@Devon-White Devon-White force-pushed the Devon/swml-messaging branch from 1724fd5 to 7bdbf82 Compare May 12, 2026 12:22
Devon-White and others added 24 commits May 12, 2026 08:22
…saging

Splits the SWML TypeSpec schema into two sibling namespaces:

* `SWML.Calling` — the existing schema, relocated under `specs/swml/calling/`.
  Every method TSP file moves from `specs/swml/Methods/<method>/` to
  `specs/swml/calling/Methods/<method>/` (139 renames). Behavior unchanged.
* `SWML.Messaging` — new namespace under `specs/swml/messaging/` covering
  inbound SMS/MMS handlers. Methods: execute, goto, label, receive, reply,
  request, return, switch, transfer. `SWMLObject` is a `@oneOf` union of a
  full document (top-level `sections`) and a light document (single `reply`
  or `receive`, including the empty `{}` shorthand).

`specs/package.json` replaces the single `build:swml` script with
`build:swml-calling` and `build:swml-messaging` (both wired into
`build:schema`). The legacy `specs/swml/tsp-output/.../SWMLObject.json` file
is kept in place — consumers may have hardcoded that URL path; the new
calling-only schema lives at `specs/swml/calling/tsp-output/...`.

REST consumers that embed SWML documents are updated to use the new
namespaced types:
* `signalwire-rest/calling-api/calls/models/{requests,examples}.tsp` —
  `SWML.Calling.SWMLObject` for call-control SWML payloads.
* `signalwire-rest/fabric-api/swml-scripts/models/{core,requests}.tsp` —
  splits the `contents` field across `SWML.Calling.SWMLObject` and
  `SWML.Messaging.SWMLObject` based on script type.
* `signalwire-rest/fabric-api/ai-agent/models/{core,ai/main}.tsp` — pulls
  AI method types from `SWML.Calling`.

Adds the second SWML inbound webhook to the SWML Webhooks namespace:
`@webhook("inboundMessageWebhook", InboundMessageWebhookPayload, ...)` with
companion `InboundMessageMediaItem`, `InboundMessageContext`, and
`InboundMessageWebhookPayload` models in
`signalwire-rest/fabric-api/swml-webhook/models/webhooks.tsp`. The Calling
sibling `@webhook("inboundCallWebhook", ...)` already shipped with the
decorator PR; this PR completes the pair. `fern/products/apis/apis.yml`
gains the matching nav entry.

Drive-along refactors that landed naturally with this work:
* `uuid` and `jwt` scalars moved from
  `specs/signalwire-rest/types/scalar-types/main.tsp` up to
  `specs/_shared/types/main.tsp` so compatibility-api can reach them.
  `@format("uuid") + string` pairs across ~135 REST + compat spec files
  collapse to the bare `uuid` scalar (TypeSpec carries the format).
* `UrlMethodType` (which actually only describes request URLs) renames to
  `RequestUrlMethodType`; `cxml-webhooks/models/requests.tsp` propagates
  the new name.
* `signalwire-rest/fabric-api/_shared/const.tsp` adds tuple-literal
  `SWML_CONTENTS_EXAMPLE` and `SWML_MESSAGING_CONTENTS_EXAMPLE` constants
  for use in `@example(...)` decorators.
* `signalwire-rest/message-api/messages/models/webhooks.tsp` clarifies in
  prose that the message status callback shape is also fired by SWML
  messaging `reply.status_url`.

Targets the SWML messaging integration branch (`Devon/swml-messaging`).
The matching SWML reference MDX docs land as a sibling PR.

Verified by `yarn build:all` from `specs/`: TypeSpec 1.11.0 compiles
signalwire-rest, compatibility-api, swml-calling, and swml-messaging
without errors. Generated `fern/apis/signalwire-rest/openapi.yaml`
exposes both `inboundCallWebhook` and `inboundMessageWebhook` under
`webhooks:`.
Splits the SWML reference docs tree into two top-level sections, mirroring
the upcoming TypeSpec namespace split (SWML.Calling, SWML.Messaging):

* `pages/reference/methods/*` renamed under `methods/calling/*` (54 files;
  light prose edits where method docs already referenced "calling-only"
  behavior — those are tightened up).
* `pages/reference/methods/messaging/*.mdx` (10 new pages): overview,
  execute, goto, label, receive, reply, request, return, switch, transfer.
* `pages/reference/errors.mdx` (new — consolidated SWML error reference).
* `pages/reference/variables.mdx` (updated to clarify which variables apply
  to calling vs messaging contexts).

Updates `swml.yml` nav to expose Calling and Messaging as sibling folders
under Methods, and adds an Errors entry under the reference tab.

Adds 30+ redirects in `fern/docs.yml` so existing `/docs/swml/reference/...`
URLs continue to resolve. The folder-shape methods (ai, amazon-bedrock) get
wildcard redirects covering every nested child page; top-level method slugs
get one redirect each (a blanket `:slug` wildcard would incorrectly catch
`/messaging/*` too).

Targets the SWML messaging integration branch (`Devon/swml-messaging`).
The matching TypeSpec schema work lands as a sibling PR.
InboundMessageWebhookPayload
- Add optional `vars` field carrying propagated runtime variables on
  transfer-driven fetches; absent on the initial inbound fetch
- `body` typed as nullable (provider intakes leave it null on media-only
  MMS with no carrier-supplied text)
- Loosen from/to descriptions to forward-compatible wording
- Tighten timestamp/media descriptions

Reply method
- ReplyInlineSwitch.case and default accept `string | ReplyPlan` so each
  branch can carry its own body/media/to/from/status_url
- Require `body` be non-empty (@minlength(1)) on both ReplyWithBody and
  ReplyWithMedia, with matching doc text
- status_url cross-links to the message status callback payload

Request method
- Rewrite description with the four request_result values, the soft vs
  hard failure semantics, and the response variables (request_response,
  request_response_code, request_response_body)

Transfer method
- Description now references the inbound message webhook payload and
  explains what message/params/vars carry on the transfer fetch

SwmlScriptCreate/UpdateRequest
- script_type optional with per-branch default; structural oneOf still
  discriminates via the contents schema. Update docs to call out the
  recommended-but-optional path.
Move the two receive-shape types out of main.tsp into receive/main.tsp
so all receive-related definitions live together. SWMLLightDocument
stays in main.tsp since it's a document-level union; it still composes
LightReceive via the Methods import.

Generated JSON Schema is unchanged.
Response side — both fields are emitted as parsed JSON objects by Rails:
- CallFlowSerializer / CallFlowVersionSerializer call
  RelayBins::ParseJsonOrYaml.call(raw_contents).as_json for relayml
- flow_data is a jsonb column that the serializer passes through directly

Spec previously typed both as `string`, which would reject the real
responses. Updated CallFlow (core), CallFlowVersion, CallFlowVersionDeployResponse,
and the dead-but-present CallFlowVersionResponse:
- relayml: SWML.Calling.SWMLObject
- flow_data: opaque object (Record<unknown>)

Request side — CallFlowCreateRequest and CallFlowUpdateRequest were
missing the relayml and flow_data fields entirely. Rails contracts
require both (with create allowing both to be omitted together to
get SignalWire defaults; update requires both present along with
document_version). Added the missing fields with required/optional
flags matching the Rails contracts.

Generated REST OpenAPI regenerated.
- reply.mdx: inline switch case/default values are now string OR full reply
  object (body/media/to/from/status_url). Updated the ParamField descriptions
  and the trailing constraint paragraph, and added a per-case-routing example.
- transfer.mdx: payload section now lists the three keys (message/params/vars)
  and embeds the inboundMessageWebhook payload snippet. Removed the inaccurate
  'same shape as the initial fetch' note.
- overview.mdx: tighten the reply summary (inline switch branches the reply,
  not just the body); replace the over-simplified 'request failures are soft'
  blurb with the precise hard-vs-soft split, surfacing the four request_result
  values.
The Fern URL slug for the message-logs operation is built from its OpenAPI
tag "Message Logs" -> kebab "message-logs", not the parent "Logs" nav
section (which uses `skip-slug: true`). Updated both occurrences to point
at /docs/apis/rest/message-logs/list-message-logs.

The inbound-message-webhook links in variables.mdx are left alone; that
target page is generated by the schema PR (#315), and the links will
resolve once both PRs land on the Devon/swml-messaging integration branch.
- Add a short paragraph up top introducing the two flavors and noting they
  share document structure / variables / execution semantics.
- Reframe the "Methods" section text to call out that calling and messaging
  each have their own method set, with direct links to each reference.
- Document-fetching webhook section now opens with a calling-vs-messaging
  payload split (two bullets linking to the inbound call/message webhook
  pages), retitles the example as "Calling request body", and notes that
  the `join_conference.wait_url` / `enter_queue.wait_url` / `connect.confirm`
  re-fetches are calling-only.
- Replace the generic "Methods reference" card with two cards — Calling
  reference and Messaging reference — preserving the Quickstart and AI
  guides cards alongside.
The TypeSpec @doc strings emit to JSON Schema descriptions and ultimately
into customer-facing surfaces (REST API reference, SDK docs). They should
say the same thing as the MDX reference docs and stay focused on purpose
and behavior — type/required/default info already lives in the type
signature and ParamField attributes.

Changes per method (eight files, ~17 description updates):

* execute — clarify that completion happens via `return` or end-of-section,
  and that URLs/inline documents are not accepted in the messaging context
  (matches the doc intro that already calls this out).

* goto — promote `goto.max` description to "section ends without running
  further steps" (matches Rails behavior in section_executor.rb; the prior
  "stopping execution" was vague). Add a goto-scoped @doc on the `label`
  field instead of inheriting Label.label's @doc which describes labels
  from the label's perspective, not from goto's.

* label — note that label names must be unique within the section.

* reply — append "reply does not end execution; subsequent steps continue"
  to the method @doc, swap the Ruby-resource framing on `from` ("PhoneRoute
  or ShortCode") for user-facing language ("owned by your project and have
  messaging capability"), and document the inline-switch no-match failure
  behavior on `default`.

* request — drop redundant "Default X" prose from `method`, `timeout`, and
  `save_variables` @docs (already declared via `= default` on the type),
  swap "Hashes" → "Objects" on `request.body` for non-Ruby-flavored docs,
  add the publicly-reachable URL constraint to `request.url`, and add the
  "map of header name to value" clarification to `request.headers`.

* return — note the `return: null` shorthand for returning without a value.

* switch — extend the method @doc with use-case framing ("useful for
  keyword-driven inbound message handling"), document key/value semantics
  on `case`, and document the no-match failure behavior on `default`.

* transfer — note the URL-only constraint in the method @doc, document
  embedded basic-auth syntax on `dest`, drop redundant "Default `POST`"
  prose on `method`, and append the runtime semantic on `params`
  ("available as `params.*` in the transferred document").

Regenerated specs/swml/messaging/tsp-output/.../SWMLObject.json is the
mechanical byproduct of these source edits.

Verified by `yarn build:swml-messaging` — compiles clean under TypeSpec
1.11.0 with no warnings or errors.
SWML messaging reference docs (MDX)
@Devon-White Devon-White marked this pull request as ready for review May 13, 2026 17:53
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