@vellumai/credential-executor 0.7.1 → 0.7.3

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.
@@ -7,6 +7,8 @@
7
7
  "exports": {
8
8
  ".": "./src/index.ts",
9
9
  "./credential-rpc": "./src/credential-rpc.ts",
10
+ "./ingress": "./src/ingress.ts",
11
+ "./twilio-ingress": "./src/twilio-ingress.ts",
10
12
  "./trust-rules": "./src/trust-rules.ts",
11
13
  "./handles": "./src/handles.ts",
12
14
  "./grants": "./src/grants.ts",
@@ -33,6 +33,8 @@ describe("package independence", () => {
33
33
  "../transport.ts",
34
34
  "../credential-rpc.ts",
35
35
  "../trust-rules.ts",
36
+ "../ingress.ts",
37
+ "../twilio-ingress.ts",
36
38
  "../error.ts",
37
39
  ];
38
40
 
@@ -219,6 +221,7 @@ describe("ToolResponseBaseSchema", () => {
219
221
  result: { html: "<html></html>" },
220
222
  });
221
223
  expect(result.success).toBe(true);
224
+ if (!result.success) throw new Error("Expected successful response");
222
225
  expect(result.result).toEqual({ html: "<html></html>" });
223
226
  });
224
227
 
@@ -238,6 +241,7 @@ describe("ToolResponseBaseSchema", () => {
238
241
  },
239
242
  });
240
243
  expect(result.success).toBe(false);
244
+ if (result.success) throw new Error("Expected failed response");
241
245
  expect(result.error.code).toBe("TOOL_FAILED");
242
246
  });
243
247
 
@@ -0,0 +1,107 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ normalizeHttpPublicBaseUrl,
5
+ normalizePublicBaseUrl,
6
+ } from "../ingress.js";
7
+ import {
8
+ buildTwilioConnectActionUrl,
9
+ buildTwilioMediaStreamUrl,
10
+ buildTwilioPhoneNumberWebhookUrls,
11
+ buildTwilioRelayUrl,
12
+ buildTwilioVoiceWebhookUrl,
13
+ resolveTwilioPublicBaseUrl,
14
+ } from "../twilio-ingress.js";
15
+
16
+ describe("normalizePublicBaseUrl", () => {
17
+ test("trims whitespace and trailing slashes", () => {
18
+ expect(normalizePublicBaseUrl(" https://example.test/path/// ")).toBe(
19
+ "https://example.test/path",
20
+ );
21
+ });
22
+
23
+ test("rejects non-string and empty values", () => {
24
+ expect(normalizePublicBaseUrl(undefined)).toBeUndefined();
25
+ expect(normalizePublicBaseUrl(" ")).toBeUndefined();
26
+ });
27
+ });
28
+
29
+ describe("normalizeHttpPublicBaseUrl", () => {
30
+ test("normalizes valid HTTP and HTTPS URLs", () => {
31
+ expect(normalizeHttpPublicBaseUrl(" HTTPS://EXAMPLE.TEST/twilio ")).toBe(
32
+ "https://example.test/twilio",
33
+ );
34
+ expect(normalizeHttpPublicBaseUrl("https://example.test/twilio///")).toBe(
35
+ "https://example.test/twilio",
36
+ );
37
+ expect(normalizeHttpPublicBaseUrl("https://example.test")).toBe(
38
+ "https://example.test/",
39
+ );
40
+ });
41
+
42
+ test("rejects non-HTTP URLs and malformed values", () => {
43
+ expect(normalizeHttpPublicBaseUrl("ftp://example.test")).toBeUndefined();
44
+ expect(normalizeHttpPublicBaseUrl("notaurl")).toBeUndefined();
45
+ expect(normalizeHttpPublicBaseUrl("")).toBeUndefined();
46
+ });
47
+
48
+ test("rejects query strings and fragments instead of mutating them", () => {
49
+ expect(
50
+ normalizeHttpPublicBaseUrl("https://example.test/twilio?token=abc/"),
51
+ ).toBeUndefined();
52
+ expect(
53
+ normalizeHttpPublicBaseUrl("https://example.test/twilio#section/"),
54
+ ).toBeUndefined();
55
+ expect(
56
+ normalizeHttpPublicBaseUrl("https://example.test/twilio?"),
57
+ ).toBeUndefined();
58
+ expect(
59
+ normalizeHttpPublicBaseUrl("https://example.test/twilio#"),
60
+ ).toBeUndefined();
61
+ });
62
+ });
63
+
64
+ describe("Twilio ingress helpers", () => {
65
+ test("resolves public base URL with fallback", () => {
66
+ expect(
67
+ resolveTwilioPublicBaseUrl({
68
+ publicBaseUrl: " https://twilio.example.test/twilio/ ",
69
+ }),
70
+ ).toBe("https://twilio.example.test/twilio");
71
+ expect(
72
+ resolveTwilioPublicBaseUrl({
73
+ publicBaseUrl: " ",
74
+ }),
75
+ ).toBeUndefined();
76
+ expect(
77
+ resolveTwilioPublicBaseUrl({
78
+ publicBaseUrl: " ",
79
+ }, "https://fallback.example.test/"),
80
+ ).toBe("https://fallback.example.test");
81
+ expect(
82
+ resolveTwilioPublicBaseUrl({}, "https://fallback.example.test/"),
83
+ ).toBe("https://fallback.example.test");
84
+ });
85
+
86
+ test("builds Twilio webhook and WebSocket URLs from one base URL", () => {
87
+ expect(buildTwilioVoiceWebhookUrl("https://example.test")).toBe(
88
+ "https://example.test/webhooks/twilio/voice",
89
+ );
90
+ expect(buildTwilioVoiceWebhookUrl("https://example.test", "call-123")).toBe(
91
+ "https://example.test/webhooks/twilio/voice?callSessionId=call-123",
92
+ );
93
+ expect(buildTwilioConnectActionUrl("https://example.test")).toBe(
94
+ "https://example.test/webhooks/twilio/connect-action",
95
+ );
96
+ expect(buildTwilioRelayUrl("https://example.test")).toBe(
97
+ "wss://example.test/webhooks/twilio/relay",
98
+ );
99
+ expect(buildTwilioMediaStreamUrl("http://example.test")).toBe(
100
+ "ws://example.test/webhooks/twilio/media-stream",
101
+ );
102
+ expect(buildTwilioPhoneNumberWebhookUrls("https://example.test")).toEqual({
103
+ statusCallbackUrl: "https://example.test/webhooks/twilio/status",
104
+ voiceUrl: "https://example.test/webhooks/twilio/voice",
105
+ });
106
+ });
107
+ });
@@ -6,9 +6,11 @@
6
6
  *
7
7
  * - `@vellumai/service-contracts/credential-rpc` — transport, RPC, handles, grants, rendering, error
8
8
  * - `@vellumai/service-contracts/trust-rules` — trust-rule types and parsing helpers
9
+ * - `@vellumai/service-contracts/twilio-ingress` — shared Twilio ingress config constants
10
+ * - `@vellumai/service-contracts/ingress` — shared public ingress URL helpers
9
11
  *
10
12
  * Fine-grained subpaths are also available for low-friction migration:
11
- * `./rpc`, `./handles`, `./grants`, `./rendering`, `./error`, `./trust-rules`
13
+ * `./rpc`, `./handles`, `./grants`, `./rendering`, `./error`, `./trust-rules`, `./ingress`, `./twilio-ingress`
12
14
  *
13
15
  * Neutral wire-protocol contracts for communication between the assistant
14
16
  * daemon and the Credential Execution Service (CES). This package is
@@ -23,3 +25,5 @@ export * from "./grants.js";
23
25
  export * from "./rpc.js";
24
26
  export * from "./rendering.js";
25
27
  export * from "./trust-rules.js";
28
+ export * from "./ingress.js";
29
+ export * from "./twilio-ingress.js";
@@ -0,0 +1,24 @@
1
+ export function normalizePublicBaseUrl(value: unknown): string | undefined {
2
+ if (typeof value !== "string") return undefined;
3
+ const normalized = value.trim().replace(/\/+$/, "");
4
+ return normalized.length > 0 ? normalized : undefined;
5
+ }
6
+
7
+ export function normalizeHttpPublicBaseUrl(value: unknown): string | undefined {
8
+ if (typeof value !== "string") return undefined;
9
+ const trimmed = value.trim();
10
+ if (trimmed.length === 0) return undefined;
11
+ if (/[?#]/.test(trimmed)) return undefined;
12
+
13
+ try {
14
+ const url = new URL(trimmed);
15
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
16
+ return undefined;
17
+ }
18
+ if (!url.hostname) return undefined;
19
+ url.pathname = url.pathname.replace(/\/+$/, "") || "/";
20
+ return url.toString();
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ }
@@ -0,0 +1,84 @@
1
+ import { normalizePublicBaseUrl } from "./ingress.js";
2
+
3
+ export const TWILIO_VOICE_WEBHOOK_PATH = "/webhooks/twilio/voice";
4
+ export const TWILIO_STATUS_WEBHOOK_PATH = "/webhooks/twilio/status";
5
+ export const TWILIO_CONNECT_ACTION_WEBHOOK_PATH =
6
+ "/webhooks/twilio/connect-action";
7
+ export const TWILIO_RELAY_WEBHOOK_PATH = "/webhooks/twilio/relay";
8
+ export const TWILIO_MEDIA_STREAM_WEBHOOK_PATH = "/webhooks/twilio/media-stream";
9
+
10
+ /**
11
+ * Sentinel placeholder embedded in TwiML by the assistant where the real
12
+ * public base URL should go. The gateway replaces `wss://__VELLUM_PUBLIC_BASE_URL__/…`
13
+ * with the actual public URL (from Velay registration, config, or the
14
+ * `X-Vellum-Ingress-URL` header) before returning TwiML to Twilio.
15
+ *
16
+ * The placeholder uses `https://` so that `buildTwilioRelayUrl` /
17
+ * `buildTwilioMediaStreamUrl` can apply the standard `http→ws` scheme
18
+ * conversion, producing `wss://__VELLUM_PUBLIC_BASE_URL__/…` in the output.
19
+ */
20
+ export const TWILIO_PUBLIC_BASE_URL_PLACEHOLDER =
21
+ "https://__VELLUM_PUBLIC_BASE_URL__";
22
+
23
+ /**
24
+ * The WebSocket-scheme form of the placeholder that appears in TwiML after
25
+ * the `http→ws` scheme conversion applied by the URL builders.
26
+ */
27
+ export const TWILIO_PUBLIC_BASE_WSS_PLACEHOLDER =
28
+ "wss://__VELLUM_PUBLIC_BASE_URL__";
29
+
30
+ export { normalizePublicBaseUrl } from "./ingress.js";
31
+
32
+ export type TwilioPhoneNumberWebhookUrls = {
33
+ statusCallbackUrl: string;
34
+ voiceUrl: string;
35
+ };
36
+
37
+ export function resolveTwilioPublicBaseUrl(
38
+ ingress: { publicBaseUrl?: unknown } | undefined,
39
+ fallbackPublicBaseUrl?: unknown,
40
+ ): string | undefined {
41
+ const publicBaseUrl = normalizePublicBaseUrl(ingress?.publicBaseUrl);
42
+ if (publicBaseUrl) return publicBaseUrl;
43
+
44
+ return normalizePublicBaseUrl(fallbackPublicBaseUrl);
45
+ }
46
+
47
+ export function buildTwilioVoiceWebhookUrl(
48
+ baseUrl: string,
49
+ callSessionId?: string,
50
+ ): string {
51
+ if (callSessionId) {
52
+ return `${baseUrl}${TWILIO_VOICE_WEBHOOK_PATH}?callSessionId=${callSessionId}`;
53
+ }
54
+ return `${baseUrl}${TWILIO_VOICE_WEBHOOK_PATH}`;
55
+ }
56
+
57
+ export function buildTwilioStatusWebhookUrl(baseUrl: string): string {
58
+ return `${baseUrl}${TWILIO_STATUS_WEBHOOK_PATH}`;
59
+ }
60
+
61
+ export function buildTwilioConnectActionUrl(baseUrl: string): string {
62
+ return `${baseUrl}${TWILIO_CONNECT_ACTION_WEBHOOK_PATH}`;
63
+ }
64
+
65
+ export function buildTwilioRelayUrl(baseUrl: string): string {
66
+ return `${toTwilioWebSocketBaseUrl(baseUrl)}${TWILIO_RELAY_WEBHOOK_PATH}`;
67
+ }
68
+
69
+ export function buildTwilioMediaStreamUrl(baseUrl: string): string {
70
+ return `${toTwilioWebSocketBaseUrl(baseUrl)}${TWILIO_MEDIA_STREAM_WEBHOOK_PATH}`;
71
+ }
72
+
73
+ export function buildTwilioPhoneNumberWebhookUrls(
74
+ baseUrl: string,
75
+ ): TwilioPhoneNumberWebhookUrls {
76
+ return {
77
+ statusCallbackUrl: buildTwilioStatusWebhookUrl(baseUrl),
78
+ voiceUrl: buildTwilioVoiceWebhookUrl(baseUrl),
79
+ };
80
+ }
81
+
82
+ function toTwilioWebSocketBaseUrl(baseUrl: string): string {
83
+ return baseUrl.replace(/^http(s?)/, "ws$1");
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/credential-executor",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {