@vellumai/vellum-gateway 0.4.45 → 0.4.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +37 -78
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Vellum Gateway
2
2
 
3
- Standalone service that serves as the public ingress boundary for all external webhooks and callbacks. It owns Telegram integration end-to-end, routes Twilio voice and SMS webhooks, handles OAuth callbacks, and optionally acts as an authenticated reverse proxy for the assistant runtime.
3
+ Standalone service that serves as the public ingress boundary for all external webhooks and callbacks. It owns Telegram integration end-to-end, routes Twilio voice webhooks, handles OAuth callbacks, and optionally acts as an authenticated reverse proxy for the assistant runtime.
4
4
 
5
5
  ## Architecture
6
6
 
@@ -12,7 +12,7 @@ Client → gateway/ (Bearer auth) → Assistant Runtime (any path)
12
12
 
13
13
  The web app is **not** in the Telegram request path. When proxy mode is enabled, non-Telegram requests are forwarded to the assistant runtime with optional bearer token authentication.
14
14
 
15
- For ingress and channel architecture details, see [`ARCHITECTURE.md`](ARCHITECTURE.md) and [`docs/sms-twilio-parity-checklist.md`](docs/sms-twilio-parity-checklist.md).
15
+ For ingress and channel architecture details, see [`ARCHITECTURE.md`](ARCHITECTURE.md).
16
16
 
17
17
  ## Setup
18
18
 
@@ -47,9 +47,8 @@ bun run dev
47
47
  | `GATEWAY_MAX_WEBHOOK_PAYLOAD_BYTES` | No | `1048576` | Max inbound webhook payload size (rejects with 413) |
48
48
  | `GATEWAY_MAX_ATTACHMENT_BYTES` | No | `20971520` | Max single attachment size (oversized are skipped) |
49
49
  | `GATEWAY_MAX_ATTACHMENT_CONCURRENCY` | No | `3` | Max concurrent attachment download/upload operations |
50
- | `TWILIO_ACCOUNT_SID` | No | — | Twilio Account SID for sending outbound SMS via the Messages API |
51
- | `TWILIO_AUTH_TOKEN` | No | — | Twilio Auth Token for HMAC-SHA1 webhook signature validation and outbound SMS |
52
- | `TWILIO_PHONE_NUMBER` | No | — | Twilio phone number (E.164) used as the `From` for outbound SMS |
50
+ | `TWILIO_ACCOUNT_SID` | No | — | Twilio Account SID for voice integration |
51
+ | `TWILIO_AUTH_TOKEN` | No | — | Twilio Auth Token for HMAC-SHA1 webhook signature validation |
53
52
 
54
53
  Channel operational settings (Telegram API base URL, timeouts, deliver auth bypass flags) are managed via `workspace/config.json` through `ConfigFileCache`. See the channel-specific sections in `ARCHITECTURE.md` for details.
55
54
 
@@ -57,10 +56,9 @@ Channel operational settings (Telegram API base URL, timeouts, deliver auth bypa
57
56
 
58
57
  v1 uses deterministic settings-based routing (no database):
59
58
 
60
- 1. **phone_number match** (SMS only) reverse lookup of the inbound `To` number against `assistantPhoneNumbers` (a `Record<string, string>` mapping assistant IDs to E.164 phone numbers, propagated from the assistant config file). This allows each assistant to have its own dedicated phone number, and inbound SMS is routed to the correct assistant based on which number received the message.
61
- 2. **conversation_id match** — explicit `conversation:<conversation_id>` entry in routing JSON
62
- 3. **actor_id match** — explicit `actor:<actor_id>` entry in routing JSON
63
- 4. **Unmapped policy** — `reject` (drop with message) or `default` (forward to `GATEWAY_DEFAULT_ASSISTANT_ID`)
59
+ 1. **conversation_id match** — explicit `conversation:<conversation_id>` entry in routing JSON
60
+ 2. **actor_id match** — explicit `actor:<actor_id>` entry in routing JSON
61
+ 3. **Unmapped policy** — `reject` (drop with message) or `default` (forward to `GATEWAY_DEFAULT_ASSISTANT_ID`)
64
62
 
65
63
  ### Routing JSON format
66
64
 
@@ -102,7 +100,7 @@ The `/webhooks/twilio/voice` endpoint handles both outbound and inbound voice ca
102
100
 
103
101
  ### Inbound voice routing
104
102
 
105
- When the voice webhook is called without a `callSessionId` query parameter, the gateway treats it as an inbound call and resolves the assistant using the same routing chain as SMS:
103
+ When the voice webhook is called without a `callSessionId` query parameter, the gateway treats it as an inbound call and resolves the assistant using the standard routing chain:
106
104
 
107
105
  1. **`resolveAssistantByPhoneNumber(config, To)`** — Reverse lookup of the inbound `To` number against `assistantPhoneNumbers`. If the dialed number matches an assistant's configured phone number, that assistant handles the call.
108
106
  2. **Fallback to `resolveAssistant(From, From)`** — If no phone number match is found, the standard routing chain is used: `conversation_id` match, `actor_id` match, then the unmapped policy.
@@ -120,35 +118,6 @@ Caller → Twilio → Gateway /webhooks/twilio/voice (no callSessionId)
120
118
  → RelayConnection detects inbound (`initiatedFromConversationId == null`), optional guardian verification gate, then receptionist-style LLM greeting
121
119
  ```
122
120
 
123
- ## SMS Ingress (Twilio)
124
-
125
- The `/webhooks/twilio/sms` endpoint receives inbound SMS messages from Twilio. On each request:
126
-
127
- 1. **Signature validation** — The `X-Twilio-Signature` header is validated using HMAC-SHA1 with the `TWILIO_AUTH_TOKEN`. When behind a tunnel or reverse proxy, the gateway reconstructs the canonical request URL from the ingress public base URL (read via `ConfigFileCache` from `ingress.publicBaseUrl` in workspace config, falling back to the `INGRESS_PUBLIC_BASE_URL` env var) for validation.
128
- 2. **MessageSid dedup** — Each `MessageSid` is tracked in an in-memory dedup cache. Duplicate webhook deliveries (Twilio retries) are silently accepted without re-forwarding.
129
- 3. **MMS detection** — The gateway treats a message as MMS when any of the following conditions are met: `NumMedia > 0`, any `MediaUrl<N>` key has a non-empty value, or any `MediaContentType<N>` key has a non-empty value. This catches media attachments even when Twilio omits `NumMedia`. The gateway replies with an unsupported notice ("MMS is not supported yet") and does not forward the payload to the runtime.
130
- 4. **`/new` command** — When the message body is exactly `/new` (case-insensitive, trimmed), the gateway resolves routing first. If routing is rejected, the gateway sends a rejection notice SMS to the sender (matching Telegram rejection semantics) and does not forward the message. If routing succeeds, the gateway resets the conversation via the runtime API and sends a confirmation SMS. The message is never forwarded to the runtime.
131
- 5. **Normalization** — The form-encoded Twilio payload is normalized into a `GatewayInboundEvent` with `sourceChannel: "sms"`. The sender's phone number (`From`) is used as both `conversationExternalId` and `actorExternalId`.
132
- 6. **Routing** — Phone-number-based routing is checked first: the `To` number is looked up in `assistantPhoneNumbers` to find the target assistant. If no match, the standard routing chain (conversation_id -> actor_id -> default/reject) is used.
133
- 7. **Forwarding** — The event is forwarded to the runtime via `POST /channels/inbound` with SMS-specific transport hints (`chat-first-medium`, `sms-character-limits`, etc.) and a `replyCallbackUrl` pointing to `/deliver/sms`.
134
-
135
- SMS is text-only in v1 — MMS payloads are explicitly rejected with a user-facing notice.
136
-
137
- ## SMS Deliver Endpoint Security
138
-
139
- The `/deliver/sms` endpoint requires the same fail-closed bearer auth as `/deliver/telegram`:
140
-
141
- | Condition | Result |
142
- | ------------------------------------------------------------------------------------ | -------------------------- |
143
- | Bearer token configured + valid `Authorization` header | Request allowed |
144
- | Bearer token configured + missing/invalid `Authorization` header | 401 Unauthorized |
145
- | No bearer token configured + `sms.deliverAuthBypass=true` in `workspace/config.json` | Request allowed (dev-only) |
146
- | No bearer token configured + bypass not set | 503 Service Not Configured |
147
-
148
- The endpoint also requires `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_PHONE_NUMBER` to be configured. If any are missing, requests return `503 SMS integration not configured`.
149
-
150
- Outbound SMS is sent via the Twilio Messages API using the configured `TWILIO_PHONE_NUMBER` as the `From` number. The request body accepts either `{ to, text }` or `{ chatId, text }` — `chatId` is an alias for `to`, allowing the runtime channel callback (which sends `{ chatId, text }`) to work without translation. When both `to` and `chatId` are provided, `to` takes precedence.
151
-
152
121
  ## Callback Query Handling
153
122
 
154
123
  The gateway normalizes Telegram `callback_query` updates (inline button clicks) into the same `GatewayInboundEvent` format used for regular messages. When a `callback_query` is present in the webhook payload, the normalizer extracts:
@@ -201,36 +170,34 @@ This can be sent as an action-only payload (without `text` or `attachments`) whe
201
170
 
202
171
  The gateway serves as the single public ingress point for all external callbacks. The following routes are handled directly by the gateway before any proxy forwarding:
203
172
 
204
- | Route | Method | Description |
205
- | ------------------------------------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
206
- | `/webhooks/telegram` | POST | Telegram bot webhook (validated via `TELEGRAM_WEBHOOK_SECRET`) |
207
- | `/deliver/telegram` | POST | Internal endpoint for the assistant runtime to deliver outbound messages/attachments to Telegram chats |
208
- | `/webhooks/twilio/voice` | POST | Twilio voice webhook (validated via HMAC-SHA1 signature) |
209
- | `/webhooks/twilio/status` | POST | Twilio status callback (validated via HMAC-SHA1 signature) |
210
- | `/webhooks/twilio/connect-action` | POST | Twilio connect-action callback (validated via HMAC-SHA1 signature) |
211
- | `/webhooks/twilio/relay` | WS | Twilio ConversationRelay WebSocket (bidirectional proxy to runtime, requires `callSessionId` query param) |
212
- | `/webhooks/twilio/sms` | POST | Twilio SMS webhookvalidates X-Twilio-Signature (HMAC-SHA1), normalizes into `GatewayInboundEvent` with `sourceChannel: "sms"`, deduplicates by `MessageSid`, and forwards to runtime |
213
- | `/deliver/sms` | POST | Internal endpoint for the assistant runtime to deliver outbound SMS messages via the Twilio Messages API |
214
- | `/webhooks/oauth/callback` | GET | OAuth2 callback endpoint receives authorization codes from OAuth providers (Google, Slack, etc.) and forwards them to the assistant runtime |
215
- | `/v1/channel-verification-sessions` | POST | Authenticated control-plane proxy for creating verification sessions (inbound challenge or outbound verification) |
216
- | `/v1/channel-verification-sessions` | DELETE | Authenticated control-plane proxy for cancelling active verification sessions |
217
- | `/v1/channel-verification-sessions/resend` | POST | Authenticated control-plane proxy for resending outbound verification code |
218
- | `/v1/channel-verification-sessions/status` | GET | Authenticated control-plane proxy for verification binding status |
219
- | `/v1/channel-verification-sessions/revoke` | POST | Authenticated control-plane proxy for revoking verification binding (cancels sessions and removes binding) |
220
- | `/v1/integrations/telegram/config` | GET/POST/DELETE | Authenticated control-plane proxy for Telegram integration config |
221
- | `/v1/integrations/telegram/commands` | POST | Authenticated control-plane proxy for Telegram command registration |
222
- | `/v1/integrations/telegram/setup` | POST | Authenticated control-plane proxy for Telegram setup orchestration |
223
- | `/v1/contacts` | GET/POST | Authenticated control-plane proxy for listing/searching and creating/updating contacts |
224
- | `/v1/contacts/:id` | GET | Authenticated control-plane proxy for retrieving a contact by ID |
225
- | `/v1/contacts/merge` | POST | Authenticated control-plane proxy for merging two contacts |
226
- | `/v1/contact-channels/:contactChannelId` | PATCH | Authenticated control-plane proxy for updating a contact channel's status/policy |
227
- | `/v1/contacts/invites` | GET/POST | Authenticated control-plane proxy for listing/creating contact invites |
228
- | `/v1/contacts/invites/:id` | DELETE | Authenticated control-plane proxy for revoking a contact invite |
229
- | `/v1/contacts/invites/redeem` | POST | Authenticated control-plane proxy for redeeming a contact invite |
230
- | `/v1/health` | GET | Authenticated runtime health proxy (`/v1/health` on runtime) |
231
- | `/healthz` | GET | Liveness probe |
232
- | `/readyz` | GET | Readiness probe |
233
- | `/schema` | GET | Returns the OpenAPI 3.1 schema for this gateway |
173
+ | Route | Method | Description |
174
+ | ------------------------------------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
175
+ | `/webhooks/telegram` | POST | Telegram bot webhook (validated via `TELEGRAM_WEBHOOK_SECRET`) |
176
+ | `/deliver/telegram` | POST | Internal endpoint for the assistant runtime to deliver outbound messages/attachments to Telegram chats |
177
+ | `/webhooks/twilio/voice` | POST | Twilio voice webhook (validated via HMAC-SHA1 signature) |
178
+ | `/webhooks/twilio/status` | POST | Twilio status callback (validated via HMAC-SHA1 signature) |
179
+ | `/webhooks/twilio/connect-action` | POST | Twilio connect-action callback (validated via HMAC-SHA1 signature) |
180
+ | `/webhooks/twilio/relay` | WS | Twilio ConversationRelay WebSocket (bidirectional proxy to runtime, requires `callSessionId` query param) |
181
+ | `/webhooks/oauth/callback` | GET | OAuth2 callback endpointreceives authorization codes from OAuth providers (Google, Slack, etc.) and forwards them to the assistant runtime |
182
+ | `/v1/channel-verification-sessions` | POST | Authenticated control-plane proxy for creating verification sessions (inbound challenge or outbound verification) |
183
+ | `/v1/channel-verification-sessions` | DELETE | Authenticated control-plane proxy for cancelling active verification sessions |
184
+ | `/v1/channel-verification-sessions/resend` | POST | Authenticated control-plane proxy for resending outbound verification code |
185
+ | `/v1/channel-verification-sessions/status` | GET | Authenticated control-plane proxy for verification binding status |
186
+ | `/v1/channel-verification-sessions/revoke` | POST | Authenticated control-plane proxy for revoking verification binding (cancels sessions and removes binding) |
187
+ | `/v1/integrations/telegram/config` | GET/POST/DELETE | Authenticated control-plane proxy for Telegram integration config |
188
+ | `/v1/integrations/telegram/commands` | POST | Authenticated control-plane proxy for Telegram command registration |
189
+ | `/v1/integrations/telegram/setup` | POST | Authenticated control-plane proxy for Telegram setup orchestration |
190
+ | `/v1/contacts` | GET/POST | Authenticated control-plane proxy for listing/searching and creating/updating contacts |
191
+ | `/v1/contacts/:id` | GET | Authenticated control-plane proxy for retrieving a contact by ID |
192
+ | `/v1/contacts/merge` | POST | Authenticated control-plane proxy for merging two contacts |
193
+ | `/v1/contact-channels/:contactChannelId` | PATCH | Authenticated control-plane proxy for updating a contact channel's status/policy |
194
+ | `/v1/contacts/invites` | GET/POST | Authenticated control-plane proxy for listing/creating contact invites |
195
+ | `/v1/contacts/invites/:id` | DELETE | Authenticated control-plane proxy for revoking a contact invite |
196
+ | `/v1/contacts/invites/redeem` | POST | Authenticated control-plane proxy for redeeming a contact invite |
197
+ | `/v1/health` | GET | Authenticated runtime health proxy (`/v1/health` on runtime) |
198
+ | `/healthz` | GET | Liveness probe |
199
+ | `/readyz` | GET | Readiness probe |
200
+ | `/schema` | GET | Returns the OpenAPI 3.1 schema for this gateway |
234
201
 
235
202
  ### Tunnel Setup
236
203
 
@@ -270,15 +237,7 @@ The assistant runtime uses this URL to construct all webhook and OAuth callback
270
237
 
271
238
  ## Ingress Boundary Guarantees
272
239
 
273
- The gateway is the **sole public ingress point** for all external webhooks, including SMS. The assistant runtime never directly accepts public webhook traffic — all Twilio and Telegram webhook routes on the runtime return `410 GATEWAY_ONLY` when accessed directly.
274
-
275
- ### SMS Ingress
276
-
277
- Inbound SMS follows the same gateway-only pattern as voice and Telegram:
278
-
279
- 1. **Twilio → Gateway** (`/webhooks/twilio/sms`) — Gateway validates `X-Twilio-Signature` using HMAC-SHA1 with the configured `TWILIO_AUTH_TOKEN`.
280
- 2. **Gateway → Runtime** (`/v1/channels/inbound`) — Gateway forwards the normalized event to the runtime with JWT bearer auth.
281
- 3. **Runtime rejects direct SMS webhooks** — Any direct POST to `/webhooks/twilio/sms` or `/v1/calls/twilio/sms` on the runtime returns `410 GATEWAY_ONLY`.
240
+ The gateway is the **sole public ingress point** for all external webhooks. The assistant runtime never directly accepts public webhook traffic — all Twilio and Telegram webhook routes on the runtime return `410 GATEWAY_ONLY` when accessed directly.
282
241
 
283
242
  ### Signature URL Tightening
284
243
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/vellum-gateway",
3
- "version": "0.4.45",
3
+ "version": "0.4.46",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./twilio/verify": "./src/twilio/verify.ts",