@fonoster/common 0.27.0 → 0.31.0

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 (209) hide show
  1. package/dist/{voice/PlayDtmf.d.ts → agents/index.d.ts} +3 -5
  2. package/dist/{assistants → agents}/index.js +2 -6
  3. package/dist/agents/sessionAgentSchema.d.ts +755 -0
  4. package/dist/agents/sessionAgentSchema.js +117 -0
  5. package/dist/{voice/Dial.d.ts → calls/index.d.ts} +16 -13
  6. package/dist/calls/index.js +44 -0
  7. package/dist/cellsecrets/index.d.ts +91 -0
  8. package/dist/cellsecrets/index.js +299 -0
  9. package/dist/config/cli/check.d.ts +6 -0
  10. package/dist/config/cli/check.js +202 -0
  11. package/dist/config/cli/envFile.d.ts +23 -0
  12. package/dist/config/cli/envFile.js +81 -0
  13. package/dist/config/cli/exposure.d.ts +10 -0
  14. package/dist/config/cli/exposure.js +93 -0
  15. package/dist/config/cli/index.d.ts +2 -0
  16. package/dist/{identity/tokenHasAccessKeyId.js → config/cli/index.js} +7 -8
  17. package/dist/config/cli/info.d.ts +6 -0
  18. package/dist/config/cli/info.js +62 -0
  19. package/dist/config/cli/init.d.ts +9 -0
  20. package/dist/config/cli/init.js +152 -0
  21. package/dist/config/cli/main.d.ts +11 -0
  22. package/dist/config/cli/main.js +155 -0
  23. package/dist/config/cli/render.d.ts +44 -0
  24. package/dist/config/cli/render.js +129 -0
  25. package/dist/config/cli/secrets.d.ts +21 -0
  26. package/dist/config/cli/secrets.js +117 -0
  27. package/dist/config/cli/types.d.ts +24 -0
  28. package/dist/config/cli/types.js +7 -0
  29. package/dist/{assistants/index.d.ts → config/configHints.d.ts} +8 -7
  30. package/dist/config/configHints.js +47 -0
  31. package/dist/{voice → config}/index.d.ts +11 -13
  32. package/dist/{voice → config}/index.js +11 -13
  33. package/dist/config/interpolateEnv.d.ts +34 -0
  34. package/dist/config/interpolateEnv.js +70 -0
  35. package/dist/config/loadServiceConfig.d.ts +28 -0
  36. package/dist/config/loadServiceConfig.js +112 -0
  37. package/dist/config/logsSchema.d.ts +76 -0
  38. package/dist/config/logsSchema.js +49 -0
  39. package/dist/config/natsConnection.d.ts +53 -0
  40. package/dist/config/natsConnection.js +58 -0
  41. package/dist/config/parseConfigFile.d.ts +7 -0
  42. package/dist/config/parseConfigFile.js +60 -0
  43. package/dist/config/resolveConfigFile.d.ts +17 -0
  44. package/dist/config/resolveConfigFile.js +54 -0
  45. package/dist/config/resolveSecretRefs.d.ts +35 -0
  46. package/dist/config/resolveSecretRefs.js +129 -0
  47. package/dist/{assistants/audioFiltersSchema.d.ts → config/strictDeep.d.ts} +9 -12
  48. package/dist/config/strictDeep.js +58 -0
  49. package/dist/config/telemetrySchema.d.ts +39 -0
  50. package/dist/config/telemetrySchema.js +44 -0
  51. package/dist/config/toJsonSchema.d.ts +30 -0
  52. package/dist/config/toJsonSchema.js +161 -0
  53. package/dist/constants.d.ts +1 -6
  54. package/dist/constants.js +4 -7
  55. package/dist/envs.d.ts +2 -1
  56. package/dist/envs.js +13 -11
  57. package/dist/errors/handleZodError.js +3 -1
  58. package/dist/filters/index.d.ts +70 -0
  59. package/dist/filters/index.js +101 -0
  60. package/dist/identity/createAuthInterceptor.d.ts +9 -2
  61. package/dist/identity/createAuthInterceptor.js +36 -30
  62. package/dist/identity/hasAccess.d.ts +9 -3
  63. package/dist/identity/hasAccess.js +32 -8
  64. package/dist/identity/index.d.ts +1 -0
  65. package/dist/identity/index.js +1 -0
  66. package/dist/identity/isValidToken.d.ts +12 -2
  67. package/dist/identity/isValidToken.js +25 -9
  68. package/dist/identity/roles.js +33 -7
  69. package/dist/identity/types.d.ts +6 -1
  70. package/dist/identity/types.js +1 -0
  71. package/dist/{assistants/LanguageModelProvider.js → identity/verifyAccessToken.d.ts} +10 -11
  72. package/dist/identity/verifyAccessToken.js +44 -0
  73. package/dist/index.d.ts +15 -2
  74. package/dist/index.js +18 -3
  75. package/dist/net/assertPublicDestination.d.ts +52 -0
  76. package/dist/net/assertPublicDestination.js +342 -0
  77. package/dist/{voice/StopSay.d.ts → net/index.d.ts} +1 -6
  78. package/dist/net/index.js +35 -0
  79. package/dist/notifications/createSendEmail.js +3 -1
  80. package/dist/protos/applications.proto +138 -3
  81. package/dist/protos/authz.proto +26 -4
  82. package/dist/protos/billing.proto +92 -0
  83. package/dist/protos/calls.proto +4 -0
  84. package/dist/protos/capabilities.proto +102 -0
  85. package/dist/protos/identity.proto +26 -1
  86. package/dist/protos/providers.proto +139 -0
  87. package/dist/protos/roomr.proto +146 -0
  88. package/dist/protos/session.proto +194 -0
  89. package/dist/protos/usage.proto +59 -0
  90. package/dist/protos/webhooks.proto +140 -0
  91. package/dist/{utils/types.d.ts → regions/hub/index.d.ts} +1 -5
  92. package/dist/regions/hub/index.js +35 -0
  93. package/dist/regions/hub/natsJwt.d.ts +63 -0
  94. package/dist/regions/hub/natsJwt.js +195 -0
  95. package/dist/registry/Registry.d.ts +87 -0
  96. package/dist/registry/Registry.js +432 -0
  97. package/dist/registry/cli.d.ts +20 -0
  98. package/dist/registry/cli.js +436 -0
  99. package/dist/registry/features.d.ts +31 -0
  100. package/dist/registry/features.js +38 -0
  101. package/dist/registry/index.d.ts +24 -0
  102. package/dist/registry/index.js +40 -0
  103. package/dist/registry/prisma.d.ts +67 -0
  104. package/dist/registry/prisma.js +399 -0
  105. package/dist/registry/stores.d.ts +21 -0
  106. package/dist/{assistants/languageModelConfigSchema.js → registry/stores.js} +55 -38
  107. package/dist/registry/types.d.ts +110 -0
  108. package/dist/{assistants/LanguageModelProvider.d.ts → registry/types.js} +6 -8
  109. package/dist/registry/workspaceKeys.d.ts +41 -0
  110. package/dist/registry/workspaceKeys.js +64 -0
  111. package/dist/roomr/health.d.ts +9 -0
  112. package/dist/{assistants/eventsHookSchema.js → roomr/health.js} +22 -18
  113. package/dist/roomr/index.d.ts +75 -0
  114. package/dist/roomr/index.js +146 -0
  115. package/dist/roomr/session.d.ts +133 -0
  116. package/dist/roomr/session.js +132 -0
  117. package/dist/session/index.d.ts +19 -0
  118. package/dist/session/index.js +35 -0
  119. package/dist/session/schema.d.ts +2140 -0
  120. package/dist/session/schema.js +448 -0
  121. package/dist/sessions/index.d.ts +96 -0
  122. package/dist/sessions/index.js +197 -0
  123. package/dist/telemetry.d.ts +76 -0
  124. package/dist/telemetry.js +360 -0
  125. package/dist/types.d.ts +3 -5
  126. package/dist/{voice/Gather.d.ts → usage/CallUsage.d.ts} +19 -18
  127. package/dist/usage/CallUsage.js +76 -0
  128. package/dist/usage/index.d.ts +22 -0
  129. package/dist/usage/index.js +38 -0
  130. package/dist/usage/meters.d.ts +113 -0
  131. package/dist/usage/meters.js +90 -0
  132. package/dist/usage/types.d.ts +159 -0
  133. package/dist/usage/types.js +84 -0
  134. package/dist/usage/usageId.d.ts +6 -0
  135. package/dist/usage/usageId.js +45 -0
  136. package/dist/utils/{findIntegrationsCredentials.js → WriteBehind.d.ts} +13 -10
  137. package/dist/utils/WriteBehind.js +64 -0
  138. package/dist/utils/index.d.ts +1 -3
  139. package/dist/utils/index.js +1 -3
  140. package/dist/utils/{createFetchSingleCallByCallId.d.ts → logUnhandledRejections.d.ts} +8 -4
  141. package/dist/{assistants/audioFiltersSchema.js → utils/logUnhandledRejections.js} +17 -12
  142. package/dist/utils/withErrorHandling.js +1 -1
  143. package/dist/validators/calls.d.ts +3 -0
  144. package/dist/validators/calls.js +12 -2
  145. package/dist/validators/identity.d.ts +37 -8
  146. package/dist/validators/identity.js +22 -3
  147. package/dist/validators/index.d.ts +1 -0
  148. package/dist/validators/index.js +1 -0
  149. package/dist/validators/sip.d.ts +40 -0
  150. package/dist/validators/sip.js +99 -0
  151. package/dist/validators/sipnet/credentials.d.ts +2 -2
  152. package/dist/validators/sipnet/domains.js +7 -7
  153. package/dist/validators/sipnet/trunks.d.ts +14 -14
  154. package/dist/validators/sipnet/trunks.js +4 -2
  155. package/dist/webhooks/Dispatcher.d.ts +87 -0
  156. package/dist/webhooks/Dispatcher.js +225 -0
  157. package/dist/webhooks/bus.d.ts +68 -0
  158. package/dist/webhooks/bus.js +178 -0
  159. package/dist/webhooks/events.d.ts +66 -0
  160. package/dist/webhooks/events.js +62 -0
  161. package/dist/webhooks/index.d.ts +23 -0
  162. package/dist/webhooks/index.js +39 -0
  163. package/dist/webhooks/prisma.d.ts +55 -0
  164. package/dist/webhooks/prisma.js +242 -0
  165. package/dist/webhooks/signing.d.ts +8 -0
  166. package/dist/webhooks/signing.js +53 -0
  167. package/package.json +25 -11
  168. package/schema/session.v1.json +871 -0
  169. package/dist/assistants/assistantSchema.d.ts +0 -739
  170. package/dist/assistants/assistantSchema.js +0 -37
  171. package/dist/assistants/conversationSettingsSchema.d.ts +0 -122
  172. package/dist/assistants/conversationSettingsSchema.js +0 -133
  173. package/dist/assistants/eventsHookSchema.d.ts +0 -38
  174. package/dist/assistants/languageModelConfigSchema.d.ts +0 -206
  175. package/dist/identity/tokenHasAccessKeyId.d.ts +0 -2
  176. package/dist/protos/voice.proto +0 -557
  177. package/dist/utils/createFetchSingleCallByCallId.js +0 -33
  178. package/dist/utils/findIntegrationsCredentials.d.ts +0 -23
  179. package/dist/utils/types.js +0 -2
  180. package/dist/voice/Dial.js +0 -20
  181. package/dist/voice/Gather.js +0 -9
  182. package/dist/voice/Mute.d.ts +0 -31
  183. package/dist/voice/Mute.js +0 -9
  184. package/dist/voice/Play.d.ts +0 -30
  185. package/dist/voice/Play.js +0 -2
  186. package/dist/voice/PlayDtmf.js +0 -2
  187. package/dist/voice/PlaybackControl.d.ts +0 -31
  188. package/dist/voice/PlaybackControl.js +0 -11
  189. package/dist/voice/Record.d.ts +0 -36
  190. package/dist/voice/Record.js +0 -7
  191. package/dist/voice/Say.d.ts +0 -28
  192. package/dist/voice/Say.js +0 -2
  193. package/dist/voice/StopSay.js +0 -2
  194. package/dist/voice/Stream.d.ts +0 -53
  195. package/dist/voice/Stream.js +0 -19
  196. package/dist/voice/StreamGather.d.ts +0 -34
  197. package/dist/voice/StreamGather.js +0 -9
  198. package/dist/voice/Verb.d.ts +0 -27
  199. package/dist/voice/Verb.js +0 -2
  200. package/dist/voice/voice.d.ts +0 -171
  201. package/dist/voice/voice.js +0 -65
  202. package/dist/{assistants → agents}/testCasesSchema.d.ts +4 -4
  203. /package/dist/{assistants → agents}/testCasesSchema.js +0 -0
  204. /package/dist/{assistants → agents}/tools/index.d.ts +0 -0
  205. /package/dist/{assistants → agents}/tools/index.js +0 -0
  206. /package/dist/{assistants → agents}/tools/propertySchema.d.ts +0 -0
  207. /package/dist/{assistants → agents}/tools/propertySchema.js +0 -0
  208. /package/dist/{assistants → agents}/tools/toolSchema.d.ts +0 -0
  209. /package/dist/{assistants → agents}/tools/toolSchema.js +0 -0
@@ -0,0 +1,52 @@
1
+ import net from "node:net";
2
+ import { URL } from "node:url";
3
+ /**
4
+ * Outbound requests to tenant-chosen URLs (Autopilot HTTP tools, webhooks) must not reach the
5
+ * platform's own network (SSRF, RV-B2): http/https only, and every address the host resolves to
6
+ * must be public. Operators who self-host can allow internal destinations with
7
+ * `FONOSTER_ALLOWED_DESTINATIONS` (comma-separated host names, IPs or CIDRs; default empty).
8
+ */
9
+ export declare class UnsafeDestinationError extends Error {
10
+ constructor(message: string);
11
+ }
12
+ export type DestinationPolicy = {
13
+ /** Host names, IPs or CIDRs allowed although private; defaults to FONOSTER_ALLOWED_DESTINATIONS. */
14
+ allow?: string[];
15
+ };
16
+ /** True when the address is loopback, private, link-local, ULA, CGNAT, multicast or reserved. */
17
+ export declare function isPrivateAddress(ip: string): boolean;
18
+ /**
19
+ * Checks a tenant-chosen URL: http/https, not an internal name, and every resolved address
20
+ * public (or allowed by the operator). Returns the parsed URL.
21
+ */
22
+ export declare function assertPublicDestination(input: string | URL, policy?: DestinationPolicy): Promise<URL>;
23
+ /** Synchronous shape check (scheme, literal IPs, internal names) for request validation. */
24
+ export declare function assertPublicUrlShape(input: string | URL, policy?: DestinationPolicy): URL;
25
+ /**
26
+ * For WebSocket connections to tenant-chosen URLs (session apps, listeners): checks the URL's
27
+ * shape (ws/wss, no internal names or private literal IPs) and returns a socket `lookup` that
28
+ * refuses private addresses when the name resolves. Pass it as the `lookup` option of `ws`.
29
+ */
30
+ export declare function publicWebSocketGuard(input: string | URL, policy?: DestinationPolicy): {
31
+ lookup: net.LookupFunction;
32
+ };
33
+ /** Synchronous shape check of a ws:// or wss:// URL (see assertPublicUrlShape). */
34
+ export declare function assertPublicWebSocketUrlShape(input: string | URL, policy?: DestinationPolicy): URL;
35
+ export type SafeFetchInit = {
36
+ method?: string;
37
+ headers?: Record<string, string>;
38
+ body?: string;
39
+ signal?: AbortSignal;
40
+ /** Whole-request timeout (default 10 s). */
41
+ timeoutMs?: number;
42
+ /** Redirects followed, each re-checked (default 3). */
43
+ maxRedirects?: number;
44
+ /** Largest response body read (default 5 MiB). */
45
+ maxBytes?: number;
46
+ policy?: DestinationPolicy;
47
+ };
48
+ /**
49
+ * fetch for tenant-chosen URLs: checks the destination (and each redirect) with
50
+ * assertPublicDestination, re-checks the addresses the socket connects to, and times out.
51
+ */
52
+ export declare function safeFetch(input: string | URL, init?: SafeFetchInit): Promise<Response>;
@@ -0,0 +1,342 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.UnsafeDestinationError = void 0;
16
+ exports.isPrivateAddress = isPrivateAddress;
17
+ exports.assertPublicDestination = assertPublicDestination;
18
+ exports.assertPublicUrlShape = assertPublicUrlShape;
19
+ exports.publicWebSocketGuard = publicWebSocketGuard;
20
+ exports.assertPublicWebSocketUrlShape = assertPublicWebSocketUrlShape;
21
+ exports.safeFetch = safeFetch;
22
+ /**
23
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
24
+ * http://github.com/fonoster/fonoster
25
+ *
26
+ * This file is part of Fonoster
27
+ *
28
+ * Licensed under the MIT License (the "License");
29
+ * you may not use this file except in compliance with
30
+ * the License. You may obtain a copy of the License at
31
+ *
32
+ * https://opensource.org/licenses/MIT
33
+ *
34
+ * Unless required by applicable law or agreed to in writing, software
35
+ * distributed under the License is distributed on an "AS IS" BASIS,
36
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
37
+ * See the License for the specific language governing permissions and
38
+ * limitations under the License.
39
+ */
40
+ const node_dns_1 = __importDefault(require("node:dns"));
41
+ const node_http_1 = __importDefault(require("node:http"));
42
+ const node_https_1 = __importDefault(require("node:https"));
43
+ const node_net_1 = __importDefault(require("node:net"));
44
+ const node_url_1 = require("node:url");
45
+ /* global Response, Headers */
46
+ /**
47
+ * Outbound requests to tenant-chosen URLs (Autopilot HTTP tools, webhooks) must not reach the
48
+ * platform's own network (SSRF, RV-B2): http/https only, and every address the host resolves to
49
+ * must be public. Operators who self-host can allow internal destinations with
50
+ * `FONOSTER_ALLOWED_DESTINATIONS` (comma-separated host names, IPs or CIDRs; default empty).
51
+ */
52
+ class UnsafeDestinationError extends Error {
53
+ constructor(message) {
54
+ super(message);
55
+ this.name = "UnsafeDestinationError";
56
+ }
57
+ }
58
+ exports.UnsafeDestinationError = UnsafeDestinationError;
59
+ const blocked = new node_net_1.default.BlockList();
60
+ for (const [net4, prefix] of [
61
+ ["0.0.0.0", 8], // "this network", 0.0.0.0
62
+ ["10.0.0.0", 8],
63
+ ["100.64.0.0", 10], // CGNAT
64
+ ["127.0.0.0", 8],
65
+ ["169.254.0.0", 16], // link-local, cloud metadata
66
+ ["172.16.0.0", 12],
67
+ ["192.0.0.0", 24],
68
+ ["192.0.2.0", 24],
69
+ ["192.168.0.0", 16],
70
+ ["198.18.0.0", 15],
71
+ ["198.51.100.0", 24],
72
+ ["203.0.113.0", 24],
73
+ ["224.0.0.0", 4], // multicast
74
+ ["240.0.0.0", 4] // reserved, broadcast
75
+ ])
76
+ blocked.addSubnet(net4, prefix, "ipv4");
77
+ for (const [net6, prefix] of [
78
+ ["::", 128],
79
+ ["::1", 128],
80
+ ["64:ff9b::", 96], // NAT64 can reach IPv4 private ranges
81
+ ["100::", 64],
82
+ ["2001:db8::", 32],
83
+ ["fc00::", 7], // ULA
84
+ ["fe80::", 10], // link-local
85
+ ["ff00::", 8] // multicast
86
+ ])
87
+ blocked.addSubnet(net6, prefix, "ipv6");
88
+ const INTERNAL_SUFFIXES = [
89
+ ".localhost",
90
+ ".local",
91
+ ".internal",
92
+ ".intranet",
93
+ ".lan",
94
+ ".home.arpa",
95
+ ".cluster.local",
96
+ ".svc"
97
+ ];
98
+ /** The IPv4 address inside an IPv4-mapped IPv6 address (::ffff:a.b.c.d or ::ffff:hhhh:hhhh). */
99
+ function mappedV4(ip) {
100
+ const m = /^::ffff:(.+)$/i.exec(ip);
101
+ if (!m)
102
+ return undefined;
103
+ if (node_net_1.default.isIPv4(m[1]))
104
+ return m[1];
105
+ const hex = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(m[1]);
106
+ if (!hex)
107
+ return undefined;
108
+ const hi = parseInt(hex[1], 16);
109
+ const lo = parseInt(hex[2], 16);
110
+ return [hi >> 8, hi & 255, lo >> 8, lo & 255].join(".");
111
+ }
112
+ /** True when the address is loopback, private, link-local, ULA, CGNAT, multicast or reserved. */
113
+ function isPrivateAddress(ip) {
114
+ const bare = ip.replace(/^\[|\]$/g, "").split("%")[0];
115
+ const v4 = mappedV4(bare);
116
+ if (v4)
117
+ return blocked.check(v4, "ipv4");
118
+ if (node_net_1.default.isIPv4(bare))
119
+ return blocked.check(bare, "ipv4");
120
+ if (node_net_1.default.isIPv6(bare))
121
+ return blocked.check(bare, "ipv6");
122
+ return true; // not an address: refuse
123
+ }
124
+ function allowList(policy) {
125
+ var _a, _b;
126
+ return ((_a = policy === null || policy === void 0 ? void 0 : policy.allow) !== null && _a !== void 0 ? _a : ((_b = process.env.FONOSTER_ALLOWED_DESTINATIONS) !== null && _b !== void 0 ? _b : "").split(","))
127
+ .map((s) => s.trim().toLowerCase())
128
+ .filter(Boolean);
129
+ }
130
+ function allowedByOperator(host, ip, allow) {
131
+ var _a;
132
+ for (const entry of allow) {
133
+ if (entry === host)
134
+ return true;
135
+ if (!ip)
136
+ continue;
137
+ const [base, bits] = entry.split("/");
138
+ if (!node_net_1.default.isIP(base))
139
+ continue;
140
+ const family = node_net_1.default.isIPv4(base) ? "ipv4" : "ipv6";
141
+ const list = new node_net_1.default.BlockList();
142
+ const prefix = bits ? Number(bits) : family === "ipv4" ? 32 : 128;
143
+ list.addSubnet(base, prefix, family);
144
+ const target = (_a = mappedV4(ip)) !== null && _a !== void 0 ? _a : ip;
145
+ const targetFamily = node_net_1.default.isIPv4(target) ? "ipv4" : "ipv6";
146
+ if (targetFamily === family && list.check(target, family))
147
+ return true;
148
+ }
149
+ return false;
150
+ }
151
+ function checkAddress(host, ip, allow) {
152
+ if (isPrivateAddress(ip) && !allowedByOperator(host, ip, allow))
153
+ throw new UnsafeDestinationError(`destination ${host} resolves to a non-public address`);
154
+ }
155
+ function hostOf(url) {
156
+ return url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
157
+ }
158
+ /** Throws unless the URL is http(s) and its host is public, before any DNS lookup. */
159
+ function checkUrlShape(url, allow) {
160
+ if (url.protocol !== "http:" && url.protocol !== "https:")
161
+ throw new UnsafeDestinationError(`unsupported scheme ${url.protocol}`);
162
+ const host = hostOf(url);
163
+ if (!host)
164
+ throw new UnsafeDestinationError("missing host");
165
+ if (node_net_1.default.isIP(host)) {
166
+ checkAddress(host, host, allow);
167
+ return host;
168
+ }
169
+ if (allow.includes(host))
170
+ return host;
171
+ const name = host.replace(/\.$/, "");
172
+ if (!name.includes(".") ||
173
+ name === "localhost" ||
174
+ INTERNAL_SUFFIXES.some((s) => name.endsWith(s)))
175
+ throw new UnsafeDestinationError(`destination ${host} is an internal name`);
176
+ return host;
177
+ }
178
+ /**
179
+ * Checks a tenant-chosen URL: http/https, not an internal name, and every resolved address
180
+ * public (or allowed by the operator). Returns the parsed URL.
181
+ */
182
+ function assertPublicDestination(input, policy) {
183
+ return __awaiter(this, void 0, void 0, function* () {
184
+ let url;
185
+ try {
186
+ url = new node_url_1.URL(String(input));
187
+ }
188
+ catch (_a) {
189
+ throw new UnsafeDestinationError("invalid URL");
190
+ }
191
+ const allow = allowList(policy);
192
+ const host = checkUrlShape(url, allow);
193
+ if (node_net_1.default.isIP(host) || allow.includes(host))
194
+ return url;
195
+ let addresses;
196
+ try {
197
+ addresses = yield node_dns_1.default.promises.lookup(host, { all: true });
198
+ }
199
+ catch (_b) {
200
+ throw new UnsafeDestinationError(`cannot resolve ${host}`);
201
+ }
202
+ for (const a of addresses)
203
+ checkAddress(host, a.address, allow);
204
+ return url;
205
+ });
206
+ }
207
+ /** Synchronous shape check (scheme, literal IPs, internal names) for request validation. */
208
+ function assertPublicUrlShape(input, policy) {
209
+ let url;
210
+ try {
211
+ url = new node_url_1.URL(String(input));
212
+ }
213
+ catch (_a) {
214
+ throw new UnsafeDestinationError("invalid URL");
215
+ }
216
+ checkUrlShape(url, allowList(policy));
217
+ return url;
218
+ }
219
+ // The connection is checked when the socket resolves the name, so a DNS answer that changes
220
+ // between the check and the request (rebinding) cannot reach a private address.
221
+ function guardedLookup(host, allow) {
222
+ return (hostname, options, callback) => {
223
+ node_dns_1.default.lookup(hostname, Object.assign(Object.assign({}, options), { all: true }), (err, addrs) => {
224
+ if (err)
225
+ return callback(err, "", 4);
226
+ const list = addrs;
227
+ try {
228
+ for (const a of list)
229
+ checkAddress(host, a.address, allow);
230
+ }
231
+ catch (e) {
232
+ return callback(e, "", 4);
233
+ }
234
+ if (options.all)
235
+ return callback(null, list);
236
+ callback(null, list[0].address, list[0].family);
237
+ });
238
+ };
239
+ }
240
+ /** A ws:// or wss:// URL as the http(s) URL its handshake uses. */
241
+ function asHttpUrl(input) {
242
+ return String(input).replace(/^ws(s?):/i, "http$1:");
243
+ }
244
+ /**
245
+ * For WebSocket connections to tenant-chosen URLs (session apps, listeners): checks the URL's
246
+ * shape (ws/wss, no internal names or private literal IPs) and returns a socket `lookup` that
247
+ * refuses private addresses when the name resolves. Pass it as the `lookup` option of `ws`.
248
+ */
249
+ function publicWebSocketGuard(input, policy) {
250
+ if (!/^wss?:\/\//i.test(String(input)))
251
+ throw new UnsafeDestinationError("unsupported scheme");
252
+ const allow = allowList(policy);
253
+ const url = assertPublicUrlShape(asHttpUrl(input), { allow });
254
+ return { lookup: guardedLookup(hostOf(url), allow) };
255
+ }
256
+ /** Synchronous shape check of a ws:// or wss:// URL (see assertPublicUrlShape). */
257
+ function assertPublicWebSocketUrlShape(input, policy) {
258
+ if (!/^wss?:\/\//i.test(String(input)))
259
+ throw new UnsafeDestinationError("unsupported scheme");
260
+ return assertPublicUrlShape(asHttpUrl(input), policy);
261
+ }
262
+ const NO_BODY = new Set([101, 204, 205, 304]);
263
+ function requestOnce(url, init, method, body, signal, allow) {
264
+ const lookup = guardedLookup(hostOf(url), allow);
265
+ const lib = url.protocol === "https:" ? node_https_1.default : node_http_1.default;
266
+ return new Promise((resolve, reject) => {
267
+ var _a;
268
+ const req = lib.request(url, {
269
+ method,
270
+ headers: Object.assign(Object.assign({}, ((_a = init.headers) !== null && _a !== void 0 ? _a : {})), (body !== undefined
271
+ ? { "content-length": String(Buffer.byteLength(body)) }
272
+ : {})),
273
+ lookup,
274
+ signal
275
+ }, (res) => {
276
+ var _a;
277
+ const chunks = [];
278
+ let size = 0;
279
+ const max = (_a = init.maxBytes) !== null && _a !== void 0 ? _a : 5 * 1024 * 1024;
280
+ res.on("data", (c) => {
281
+ size += c.length;
282
+ if (size > max) {
283
+ res.destroy(new UnsafeDestinationError("response too large"));
284
+ return;
285
+ }
286
+ chunks.push(c);
287
+ });
288
+ res.on("error", reject);
289
+ res.on("end", () => {
290
+ var _a;
291
+ const headers = new Headers();
292
+ for (const [k, v] of Object.entries(res.headers)) {
293
+ if (v === undefined)
294
+ continue;
295
+ for (const item of Array.isArray(v) ? v : [v])
296
+ headers.append(k, item);
297
+ }
298
+ const status = (_a = res.statusCode) !== null && _a !== void 0 ? _a : 0;
299
+ resolve(new Response(NO_BODY.has(status) ? null : Buffer.concat(chunks), {
300
+ status: status < 200 || status > 599 ? 502 : status,
301
+ statusText: res.statusMessage,
302
+ headers
303
+ }));
304
+ });
305
+ });
306
+ req.on("error", reject);
307
+ if (body !== undefined)
308
+ req.write(body);
309
+ req.end();
310
+ });
311
+ }
312
+ /**
313
+ * fetch for tenant-chosen URLs: checks the destination (and each redirect) with
314
+ * assertPublicDestination, re-checks the addresses the socket connects to, and times out.
315
+ */
316
+ function safeFetch(input_1) {
317
+ return __awaiter(this, arguments, void 0, function* (input, init = {}) {
318
+ var _a, _b, _c;
319
+ const allow = allowList(init.policy);
320
+ const timeout = AbortSignal.timeout((_a = init.timeoutMs) !== null && _a !== void 0 ? _a : 10000);
321
+ const signal = init.signal
322
+ ? AbortSignal.any([init.signal, timeout])
323
+ : timeout;
324
+ let url = yield assertPublicDestination(input, { allow });
325
+ let method = ((_b = init.method) !== null && _b !== void 0 ? _b : "GET").toUpperCase();
326
+ let body = init.body;
327
+ const maxRedirects = (_c = init.maxRedirects) !== null && _c !== void 0 ? _c : 3;
328
+ for (let hop = 0;; hop++) {
329
+ const res = yield requestOnce(url, init, method, body, signal, allow);
330
+ const location = res.headers.get("location");
331
+ if (![301, 302, 303, 307, 308].includes(res.status) || !location)
332
+ return res;
333
+ if (hop >= maxRedirects)
334
+ throw new UnsafeDestinationError("too many redirects");
335
+ url = yield assertPublicDestination(new node_url_1.URL(location, url), { allow });
336
+ if (res.status === 303 || (res.status <= 302 && method === "POST")) {
337
+ method = "GET";
338
+ body = undefined;
339
+ }
340
+ }
341
+ });
342
+ }
@@ -16,9 +16,4 @@
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */
19
- import { VerbRequest } from "./Verb";
20
- type StopSayRequest = VerbRequest;
21
- type StopSayResponse = {
22
- mediaSessionRef: string;
23
- };
24
- export { StopSayRequest, StopSayResponse };
19
+ export * from "./assertPublicDestination";
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ /**
18
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
19
+ * http://github.com/fonoster/fonoster
20
+ *
21
+ * This file is part of Fonoster
22
+ *
23
+ * Licensed under the MIT License (the "License");
24
+ * you may not use this file except in compliance with
25
+ * the License. You may obtain a copy of the License at
26
+ *
27
+ * https://opensource.org/licenses/MIT
28
+ *
29
+ * Unless required by applicable law or agreed to in writing, software
30
+ * distributed under the License is distributed on an "AS IS" BASIS,
31
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32
+ * See the License for the specific language governing permissions and
33
+ * limitations under the License.
34
+ */
35
+ __exportStar(require("./assertPublicDestination"), exports);
@@ -37,7 +37,9 @@ function createSendEmail(config) {
37
37
  host,
38
38
  port,
39
39
  secure,
40
- auth
40
+ // Relays without authentication (e.g. a local MailHog) get no auth block;
41
+ // an empty one makes nodemailer fail with "Missing credentials".
42
+ auth: (auth === null || auth === void 0 ? void 0 : auth.user) ? auth : undefined
41
43
  });
42
44
  return function sendEmail(params) {
43
45
  return __awaiter(this, void 0, void 0, function* () {
@@ -39,13 +39,69 @@ service Applications {
39
39
  rpc EvaluateIntelligence(EvaluateIntelligenceRequest) returns (stream EvaluateIntelligenceEvent);
40
40
  // Create an Ephemeral Agent to perform test calls to an application
41
41
  rpc CreateTestToken(google.protobuf.Empty) returns (TestTokenResponse) {}
42
+ // Replace an application's signing secret; the old one stops working at once (no overlap)
43
+ rpc RotateSigningSecret(RotateSigningSecretRequest) returns (RotateSigningSecretResponse) {}
44
+ }
45
+
46
+ // Speech defaults of an application; voices and models are catalog ids (provider/model-or-voice)
47
+ message SpeechDefaults {
48
+ // Default voice, e.g. elevenlabs/rachel
49
+ string voice = 1;
50
+ // Default language (BCP 47), e.g. en-US
51
+ string language = 2;
52
+ // Speech-to-text model, e.g. deepgram/nova-3 (empty: the platform default)
53
+ string stt_model = 3;
54
+ // Text-to-speech model (empty: the voice's default)
55
+ string tts_model = 4;
56
+ // Silence timeout in milliseconds (0: AppBridge default)
57
+ int32 silence_timeout_ms = 5;
58
+ // The Pacing policy's patience: low, normal or high (empty: normal)
59
+ string patience = 6;
60
+ }
61
+
62
+ // Caller and agent audio format for the fonoster protocol
63
+ message AudioFormat {
64
+ // pcm16 or pcmu
65
+ string codec = 1;
66
+ // Sample rate in Hz: 8000, 16000 or 24000 (pcmu is 8000 only)
67
+ int32 rate = 2;
68
+ }
69
+
70
+ // Answering-machine detection: one amd.result event per call
71
+ message AmdSettings {
72
+ // Classify the caller's first seconds of speech
73
+ bool enabled = 1;
74
+ // How much caller speech to classify, in milliseconds (0: 4000; 500 to 15000)
75
+ int32 window_ms = 2;
76
+ }
77
+
78
+ // A listen-only socket dialed for every session of the app
79
+ message Listener {
80
+ // ws:// or wss:// address
81
+ string url = 1;
82
+ // inbound, outbound or both
83
+ string tracks = 2;
84
+ }
85
+
86
+ message RotateSigningSecretRequest {
87
+ // The reference to the application
88
+ string ref = 1;
89
+ }
90
+
91
+ message RotateSigningSecretResponse {
92
+ // The reference to the application
93
+ string ref = 1;
94
+ // The new secret; shown only in this response
95
+ string signing_secret = 2;
96
+ // The last characters of the secret
97
+ string signing_secret_hint = 3;
42
98
  }
43
99
 
44
100
  // The type of application
45
101
  enum ApplicationType {
46
- // Programmable Voice
47
- EXTERNAL = 0;
48
- // Internal LLM based applications
102
+ // A stream: your own WebSocket server
103
+ STREAM = 0;
104
+ // An Autopilot agent (hosted by Fonoster)
49
105
  AUTOPILOT = 1;
50
106
  }
51
107
 
@@ -58,6 +114,14 @@ message ProductContainer {
58
114
  google.protobuf.Struct credentials = 3;
59
115
  }
60
116
 
117
+ // An audio filter run on the caller's audio before speech recognition
118
+ message AudioFilter {
119
+ // Filter name (aiCoustics)
120
+ string name = 1;
121
+ // The filter's public options (model, enhancement_level, bypass)
122
+ google.protobuf.Struct options = 2;
123
+ }
124
+
61
125
  // Application definition
62
126
  message Application {
63
127
  // Reference to the application
@@ -78,6 +142,28 @@ message Application {
78
142
  int32 created_at = 8;
79
143
  // Update time
80
144
  int32 updated_at = 9;
145
+ // The WebSocket URL AppBridge dials
146
+ string url = 10;
147
+ // Stream protocol: fonoster or twilio
148
+ string protocol = 11;
149
+ // The last characters of the signing secret (the secret is never returned on reads)
150
+ string signing_secret_hint = 12;
151
+ // Speech defaults
152
+ SpeechDefaults speech = 13;
153
+ // Audio format (fonoster protocol)
154
+ AudioFormat audio = 14;
155
+ // App used when this one cannot be reached
156
+ string fallback_app_ref = 15;
157
+ // Message spoken when neither the app nor its fallback can be reached
158
+ string fallback_message = 16;
159
+ // Listen-only sockets
160
+ repeated Listener listeners = 17;
161
+ // Recording policy: off, on or consent (empty: the workspace's)
162
+ string recording = 18;
163
+ // Answering-machine detection (off when unset)
164
+ AmdSettings amd = 19;
165
+ // Audio filters in order (empty: none)
166
+ repeated AudioFilter filters = 20;
81
167
  }
82
168
 
83
169
  // Request to create a new application
@@ -94,12 +180,36 @@ message CreateApplicationRequest {
94
180
  ProductContainer speech_to_text = 5;
95
181
  // Inference engine
96
182
  ProductContainer intelligence = 6;
183
+ // The WebSocket URL (Autopilot agents default to the hosted Autopilot)
184
+ string url = 7;
185
+ // fonoster or twilio (default fonoster)
186
+ string protocol = 8;
187
+ // Speech defaults
188
+ SpeechDefaults speech = 9;
189
+ // Audio format (fonoster protocol)
190
+ AudioFormat audio = 10;
191
+ // App used when this one cannot be reached
192
+ string fallback_app_ref = 11;
193
+ // Message spoken when neither the app nor its fallback can be reached
194
+ string fallback_message = 12;
195
+ // Listen-only sockets
196
+ repeated Listener listeners = 13;
197
+ // Recording policy: off, on or consent (empty: the workspace's)
198
+ string recording = 14;
199
+ // Answering-machine detection (off when unset)
200
+ AmdSettings amd = 15;
201
+ // Audio filters in order (empty: none)
202
+ repeated AudioFilter filters = 20;
97
203
  }
98
204
 
99
205
  // Response for create application
100
206
  message CreateApplicationResponse {
101
207
  // Echo the reference of the application
102
208
  string ref = 1;
209
+ // The app's signing secret; shown only in this response
210
+ string signing_secret = 2;
211
+ // The last characters of the secret
212
+ string signing_secret_hint = 3;
103
213
  }
104
214
 
105
215
  // Request to get an application
@@ -140,12 +250,37 @@ message UpdateApplicationRequest {
140
250
  ProductContainer speech_to_text = 6;
141
251
  // Intelligence product
142
252
  ProductContainer intelligence = 7;
253
+ // The WebSocket URL (Autopilot agents default to the hosted Autopilot)
254
+ string url = 8;
255
+ // fonoster or twilio (default fonoster)
256
+ string protocol = 9;
257
+ // Speech defaults
258
+ SpeechDefaults speech = 10;
259
+ // Audio format (fonoster protocol)
260
+ AudioFormat audio = 11;
261
+ // App used when this one cannot be reached
262
+ string fallback_app_ref = 12;
263
+ // Message spoken when neither the app nor its fallback can be reached
264
+ string fallback_message = 13;
265
+ // Listen-only sockets
266
+ repeated Listener listeners = 14;
267
+ // Recording policy: off, on or consent (empty: the workspace's)
268
+ string recording = 15;
269
+ // Answering-machine detection (off when unset)
270
+ AmdSettings amd = 16;
271
+ // Audio filters in order (the update replaces the app: left out clears them)
272
+ repeated AudioFilter filters = 20;
143
273
  }
144
274
 
145
275
  // Response for update application
146
276
  message UpdateApplicationResponse {
147
277
  // Echo the reference of the application
148
278
  string ref = 1;
279
+ // A new signing secret when the update generated one (the app stopped being an Autopilot
280
+ // app); shown only in this response. Empty otherwise.
281
+ string signing_secret = 2;
282
+ // The last characters of the new secret (empty when there is none)
283
+ string signing_secret_hint = 3;
149
284
  }
150
285
 
151
286
  // Request to delete an application