@komaa/standin-sdk 0.1.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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/dist/ambient.d.ts +80 -0
  4. package/dist/ambient.d.ts.map +1 -0
  5. package/dist/ambient.js +281 -0
  6. package/dist/ambient.js.map +1 -0
  7. package/dist/attachments.d.ts +149 -0
  8. package/dist/attachments.d.ts.map +1 -0
  9. package/dist/attachments.js +451 -0
  10. package/dist/attachments.js.map +1 -0
  11. package/dist/audio.d.ts +80 -0
  12. package/dist/audio.d.ts.map +1 -0
  13. package/dist/audio.js +170 -0
  14. package/dist/audio.js.map +1 -0
  15. package/dist/avatar.d.ts +111 -0
  16. package/dist/avatar.d.ts.map +1 -0
  17. package/dist/avatar.js +201 -0
  18. package/dist/avatar.js.map +1 -0
  19. package/dist/callServer.d.ts +151 -0
  20. package/dist/callServer.d.ts.map +1 -0
  21. package/dist/callServer.js +1097 -0
  22. package/dist/callServer.js.map +1 -0
  23. package/dist/callTools.d.ts +154 -0
  24. package/dist/callTools.d.ts.map +1 -0
  25. package/dist/callTools.js +255 -0
  26. package/dist/callTools.js.map +1 -0
  27. package/dist/chat.d.ts +226 -0
  28. package/dist/chat.d.ts.map +1 -0
  29. package/dist/chat.js +574 -0
  30. package/dist/chat.js.map +1 -0
  31. package/dist/config.d.ts +32 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +86 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/consult.d.ts +147 -0
  36. package/dist/consult.d.ts.map +1 -0
  37. package/dist/consult.js +388 -0
  38. package/dist/consult.js.map +1 -0
  39. package/dist/delivery.d.ts +81 -0
  40. package/dist/delivery.d.ts.map +1 -0
  41. package/dist/delivery.js +187 -0
  42. package/dist/delivery.js.map +1 -0
  43. package/dist/echoGuard.d.ts +56 -0
  44. package/dist/echoGuard.d.ts.map +1 -0
  45. package/dist/echoGuard.js +57 -0
  46. package/dist/echoGuard.js.map +1 -0
  47. package/dist/errors.d.ts +5 -0
  48. package/dist/errors.d.ts.map +1 -0
  49. package/dist/errors.js +10 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/fetch.d.ts +40 -0
  52. package/dist/fetch.d.ts.map +1 -0
  53. package/dist/fetch.js +240 -0
  54. package/dist/fetch.js.map +1 -0
  55. package/dist/gate.d.ts +80 -0
  56. package/dist/gate.d.ts.map +1 -0
  57. package/dist/gate.js +249 -0
  58. package/dist/gate.js.map +1 -0
  59. package/dist/handler.d.ts +283 -0
  60. package/dist/handler.d.ts.map +1 -0
  61. package/dist/handler.js +4 -0
  62. package/dist/handler.js.map +1 -0
  63. package/dist/hmac.d.ts +32 -0
  64. package/dist/hmac.d.ts.map +1 -0
  65. package/dist/hmac.js +108 -0
  66. package/dist/hmac.js.map +1 -0
  67. package/dist/index.d.ts +59 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +60 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/lane.d.ts +92 -0
  72. package/dist/lane.d.ts.map +1 -0
  73. package/dist/lane.js +269 -0
  74. package/dist/lane.js.map +1 -0
  75. package/dist/lipsync.d.ts +136 -0
  76. package/dist/lipsync.d.ts.map +1 -0
  77. package/dist/lipsync.js +310 -0
  78. package/dist/lipsync.js.map +1 -0
  79. package/dist/log.d.ts +18 -0
  80. package/dist/log.d.ts.map +1 -0
  81. package/dist/log.js +20 -0
  82. package/dist/log.js.map +1 -0
  83. package/dist/media.d.ts +43 -0
  84. package/dist/media.d.ts.map +1 -0
  85. package/dist/media.js +190 -0
  86. package/dist/media.js.map +1 -0
  87. package/dist/minutes.d.ts +398 -0
  88. package/dist/minutes.d.ts.map +1 -0
  89. package/dist/minutes.js +805 -0
  90. package/dist/minutes.js.map +1 -0
  91. package/dist/outbound.d.ts +388 -0
  92. package/dist/outbound.d.ts.map +1 -0
  93. package/dist/outbound.js +933 -0
  94. package/dist/outbound.js.map +1 -0
  95. package/dist/plugins/cartesia/agent.d.ts +58 -0
  96. package/dist/plugins/cartesia/agent.d.ts.map +1 -0
  97. package/dist/plugins/cartesia/agent.js +254 -0
  98. package/dist/plugins/cartesia/agent.js.map +1 -0
  99. package/dist/plugins/cartesia/cli.d.ts +3 -0
  100. package/dist/plugins/cartesia/cli.d.ts.map +1 -0
  101. package/dist/plugins/cartesia/cli.js +10 -0
  102. package/dist/plugins/cartesia/cli.js.map +1 -0
  103. package/dist/plugins/cartesia/config.d.ts +30 -0
  104. package/dist/plugins/cartesia/config.d.ts.map +1 -0
  105. package/dist/plugins/cartesia/config.js +29 -0
  106. package/dist/plugins/cartesia/config.js.map +1 -0
  107. package/dist/plugins/cartesia/handler.d.ts +40 -0
  108. package/dist/plugins/cartesia/handler.d.ts.map +1 -0
  109. package/dist/plugins/cartesia/handler.js +142 -0
  110. package/dist/plugins/cartesia/handler.js.map +1 -0
  111. package/dist/plugins/cartesia/index.d.ts +6 -0
  112. package/dist/plugins/cartesia/index.d.ts.map +1 -0
  113. package/dist/plugins/cartesia/index.js +54 -0
  114. package/dist/plugins/cartesia/index.js.map +1 -0
  115. package/dist/plugins/deepgram/agent.d.ts +60 -0
  116. package/dist/plugins/deepgram/agent.d.ts.map +1 -0
  117. package/dist/plugins/deepgram/agent.js +302 -0
  118. package/dist/plugins/deepgram/agent.js.map +1 -0
  119. package/dist/plugins/deepgram/cli.d.ts +3 -0
  120. package/dist/plugins/deepgram/cli.d.ts.map +1 -0
  121. package/dist/plugins/deepgram/cli.js +10 -0
  122. package/dist/plugins/deepgram/cli.js.map +1 -0
  123. package/dist/plugins/deepgram/config.d.ts +39 -0
  124. package/dist/plugins/deepgram/config.d.ts.map +1 -0
  125. package/dist/plugins/deepgram/config.js +66 -0
  126. package/dist/plugins/deepgram/config.js.map +1 -0
  127. package/dist/plugins/deepgram/handler.d.ts +87 -0
  128. package/dist/plugins/deepgram/handler.d.ts.map +1 -0
  129. package/dist/plugins/deepgram/handler.js +268 -0
  130. package/dist/plugins/deepgram/handler.js.map +1 -0
  131. package/dist/plugins/deepgram/index.d.ts +11 -0
  132. package/dist/plugins/deepgram/index.d.ts.map +1 -0
  133. package/dist/plugins/deepgram/index.js +76 -0
  134. package/dist/plugins/deepgram/index.js.map +1 -0
  135. package/dist/plugins/echo/cli.d.ts +3 -0
  136. package/dist/plugins/echo/cli.d.ts.map +1 -0
  137. package/dist/plugins/echo/cli.js +10 -0
  138. package/dist/plugins/echo/cli.js.map +1 -0
  139. package/dist/plugins/echo/index.d.ts +37 -0
  140. package/dist/plugins/echo/index.d.ts.map +1 -0
  141. package/dist/plugins/echo/index.js +61 -0
  142. package/dist/plugins/echo/index.js.map +1 -0
  143. package/dist/plugins/elevenlabs/agent.d.ts +72 -0
  144. package/dist/plugins/elevenlabs/agent.d.ts.map +1 -0
  145. package/dist/plugins/elevenlabs/agent.js +305 -0
  146. package/dist/plugins/elevenlabs/agent.js.map +1 -0
  147. package/dist/plugins/elevenlabs/cli.d.ts +3 -0
  148. package/dist/plugins/elevenlabs/cli.d.ts.map +1 -0
  149. package/dist/plugins/elevenlabs/cli.js +10 -0
  150. package/dist/plugins/elevenlabs/cli.js.map +1 -0
  151. package/dist/plugins/elevenlabs/config.d.ts +27 -0
  152. package/dist/plugins/elevenlabs/config.d.ts.map +1 -0
  153. package/dist/plugins/elevenlabs/config.js +42 -0
  154. package/dist/plugins/elevenlabs/config.js.map +1 -0
  155. package/dist/plugins/elevenlabs/handler.d.ts +67 -0
  156. package/dist/plugins/elevenlabs/handler.d.ts.map +1 -0
  157. package/dist/plugins/elevenlabs/handler.js +419 -0
  158. package/dist/plugins/elevenlabs/handler.js.map +1 -0
  159. package/dist/plugins/elevenlabs/index.d.ts +11 -0
  160. package/dist/plugins/elevenlabs/index.d.ts.map +1 -0
  161. package/dist/plugins/elevenlabs/index.js +72 -0
  162. package/dist/plugins/elevenlabs/index.js.map +1 -0
  163. package/dist/plugins/livekit/cli.d.ts +3 -0
  164. package/dist/plugins/livekit/cli.d.ts.map +1 -0
  165. package/dist/plugins/livekit/cli.js +10 -0
  166. package/dist/plugins/livekit/cli.js.map +1 -0
  167. package/dist/plugins/livekit/config.d.ts +47 -0
  168. package/dist/plugins/livekit/config.d.ts.map +1 -0
  169. package/dist/plugins/livekit/config.js +46 -0
  170. package/dist/plugins/livekit/config.js.map +1 -0
  171. package/dist/plugins/livekit/handler.d.ts +59 -0
  172. package/dist/plugins/livekit/handler.d.ts.map +1 -0
  173. package/dist/plugins/livekit/handler.js +211 -0
  174. package/dist/plugins/livekit/handler.js.map +1 -0
  175. package/dist/plugins/livekit/index.d.ts +6 -0
  176. package/dist/plugins/livekit/index.d.ts.map +1 -0
  177. package/dist/plugins/livekit/index.js +68 -0
  178. package/dist/plugins/livekit/index.js.map +1 -0
  179. package/dist/plugins/livekit/room.d.ts +117 -0
  180. package/dist/plugins/livekit/room.d.ts.map +1 -0
  181. package/dist/plugins/livekit/room.js +449 -0
  182. package/dist/plugins/livekit/room.js.map +1 -0
  183. package/dist/plugins/openai/agent.d.ts +74 -0
  184. package/dist/plugins/openai/agent.d.ts.map +1 -0
  185. package/dist/plugins/openai/agent.js +264 -0
  186. package/dist/plugins/openai/agent.js.map +1 -0
  187. package/dist/plugins/openai/cli.d.ts +3 -0
  188. package/dist/plugins/openai/cli.d.ts.map +1 -0
  189. package/dist/plugins/openai/cli.js +10 -0
  190. package/dist/plugins/openai/cli.js.map +1 -0
  191. package/dist/plugins/openai/config.d.ts +29 -0
  192. package/dist/plugins/openai/config.d.ts.map +1 -0
  193. package/dist/plugins/openai/config.js +37 -0
  194. package/dist/plugins/openai/config.js.map +1 -0
  195. package/dist/plugins/openai/handler.d.ts +65 -0
  196. package/dist/plugins/openai/handler.d.ts.map +1 -0
  197. package/dist/plugins/openai/handler.js +297 -0
  198. package/dist/plugins/openai/handler.js.map +1 -0
  199. package/dist/plugins/openai/index.d.ts +6 -0
  200. package/dist/plugins/openai/index.d.ts.map +1 -0
  201. package/dist/plugins/openai/index.js +72 -0
  202. package/dist/plugins/openai/index.js.map +1 -0
  203. package/dist/plugins/openclaw/allowlist.d.ts +9 -0
  204. package/dist/plugins/openclaw/allowlist.d.ts.map +1 -0
  205. package/dist/plugins/openclaw/allowlist.js +11 -0
  206. package/dist/plugins/openclaw/allowlist.js.map +1 -0
  207. package/dist/plugins/openclaw/config.d.ts +50 -0
  208. package/dist/plugins/openclaw/config.d.ts.map +1 -0
  209. package/dist/plugins/openclaw/config.js +53 -0
  210. package/dist/plugins/openclaw/config.js.map +1 -0
  211. package/dist/plugins/openclaw/handler.d.ts +90 -0
  212. package/dist/plugins/openclaw/handler.d.ts.map +1 -0
  213. package/dist/plugins/openclaw/handler.js +146 -0
  214. package/dist/plugins/openclaw/handler.js.map +1 -0
  215. package/dist/plugins/openclaw/index.d.ts +55 -0
  216. package/dist/plugins/openclaw/index.d.ts.map +1 -0
  217. package/dist/plugins/openclaw/index.js +89 -0
  218. package/dist/plugins/openclaw/index.js.map +1 -0
  219. package/dist/plugins/openclaw/openclaw.plugin.json +74 -0
  220. package/dist/plugins/openclaw/package.json +14 -0
  221. package/dist/plugins/openclaw/realtime.d.ts +82 -0
  222. package/dist/plugins/openclaw/realtime.d.ts.map +1 -0
  223. package/dist/plugins/openclaw/realtime.js +238 -0
  224. package/dist/plugins/openclaw/realtime.js.map +1 -0
  225. package/dist/plugins/openclaw/runtime.d.ts +19 -0
  226. package/dist/plugins/openclaw/runtime.d.ts.map +1 -0
  227. package/dist/plugins/openclaw/runtime.js +160 -0
  228. package/dist/plugins/openclaw/runtime.js.map +1 -0
  229. package/dist/policy.d.ts +38 -0
  230. package/dist/policy.d.ts.map +1 -0
  231. package/dist/policy.js +95 -0
  232. package/dist/policy.js.map +1 -0
  233. package/dist/protocol.d.ts +44 -0
  234. package/dist/protocol.d.ts.map +1 -0
  235. package/dist/protocol.js +77 -0
  236. package/dist/protocol.js.map +1 -0
  237. package/dist/protocolRuntime.d.ts +19 -0
  238. package/dist/protocolRuntime.d.ts.map +1 -0
  239. package/dist/protocolRuntime.js +78 -0
  240. package/dist/protocolRuntime.js.map +1 -0
  241. package/dist/smoke.d.ts +65 -0
  242. package/dist/smoke.d.ts.map +1 -0
  243. package/dist/smoke.js +293 -0
  244. package/dist/smoke.js.map +1 -0
  245. package/dist/startup.d.ts +74 -0
  246. package/dist/startup.d.ts.map +1 -0
  247. package/dist/startup.js +112 -0
  248. package/dist/startup.js.map +1 -0
  249. package/dist/tile.d.ts +101 -0
  250. package/dist/tile.d.ts.map +1 -0
  251. package/dist/tile.js +195 -0
  252. package/dist/tile.js.map +1 -0
  253. package/dist/version.d.ts +2 -0
  254. package/dist/version.d.ts.map +1 -0
  255. package/dist/version.js +4 -0
  256. package/dist/version.js.map +1 -0
  257. package/dist/vision.d.ts +160 -0
  258. package/dist/vision.d.ts.map +1 -0
  259. package/dist/vision.js +301 -0
  260. package/dist/vision.js.map +1 -0
  261. package/dist/visionTools.d.ts +237 -0
  262. package/dist/visionTools.d.ts.map +1 -0
  263. package/dist/visionTools.js +578 -0
  264. package/dist/visionTools.js.map +1 -0
  265. package/dist/voice.d.ts +143 -0
  266. package/dist/voice.d.ts.map +1 -0
  267. package/dist/voice.js +403 -0
  268. package/dist/voice.js.map +1 -0
  269. package/package.json +129 -0
package/dist/fetch.js ADDED
@@ -0,0 +1,240 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Fetching a URL an agent chose, without fetching your own infrastructure.
5
+ *
6
+ * An agent that can show the caller a picture will sooner or later be handed a
7
+ * URL by its own model, and that model is steered by whoever is on the call. So
8
+ * the URL is untrusted input wearing a trusted costume, and a naive GET of it is
9
+ * a server-side request forgery: `169.254.169.254` is cloud credentials,
10
+ * `127.0.0.1` is whatever else you run, and `10.0.0.0/8` is the rest of your
11
+ * network.
12
+ *
13
+ * {@link fetchPublicImage} is the guarded way to do it: http and https only, no
14
+ * embedded credentials, and no host that resolves into private, loopback,
15
+ * link-local or reserved space.
16
+ *
17
+ * The subtle half is the rebind. Validating a hostname resolves it once, and the
18
+ * resolution the HTTP stack does a moment later can answer differently, so the
19
+ * request is made through a guarded `lookup` that re-checks the address the
20
+ * socket will actually connect to. One redirect hop is followed, because image
21
+ * CDNs habitually redirect to the real asset, and the target goes through the
22
+ * whole guard again rather than being trusted for having come from a host that
23
+ * passed.
24
+ *
25
+ * Identical in shape to the Python SDK's `standin.fetch`.
26
+ */
27
+ import { lookup } from "node:dns/promises";
28
+ import { request as httpRequest } from "node:http";
29
+ import { request as httpsRequest } from "node:https";
30
+ import { isIP } from "node:net";
31
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
32
+ function ipv4ToInt(ip) {
33
+ return (ip.split(".").reduce((acc, octet) => (acc << 8) + Number(octet), 0) >>> 0);
34
+ }
35
+ function inCidr4(ip, base, maskBits) {
36
+ const mask = maskBits === 0 ? 0 : (~0 << (32 - maskBits)) >>> 0;
37
+ return (ip & mask) === (ipv4ToInt(base) & mask);
38
+ }
39
+ /** True for any IPv4 address that must never be fetched server-side. */
40
+ export function isForbiddenIpv4(ip) {
41
+ const n = ipv4ToInt(ip);
42
+ return (inCidr4(n, "0.0.0.0", 8) || // "this" network
43
+ inCidr4(n, "10.0.0.0", 8) || // RFC1918
44
+ inCidr4(n, "100.64.0.0", 10) || // carrier-grade NAT
45
+ inCidr4(n, "127.0.0.0", 8) || // loopback
46
+ inCidr4(n, "169.254.0.0", 16) || // link-local, including cloud metadata
47
+ inCidr4(n, "172.16.0.0", 12) || // RFC1918
48
+ inCidr4(n, "192.0.0.0", 24) || // IETF protocol assignments
49
+ inCidr4(n, "192.168.0.0", 16) || // RFC1918
50
+ inCidr4(n, "198.18.0.0", 15) || // benchmarking
51
+ inCidr4(n, "224.0.0.0", 3) // multicast, reserved and broadcast
52
+ );
53
+ }
54
+ /** True for any IPv6 address that must never be fetched server-side. */
55
+ export function isForbiddenIpv6(ip) {
56
+ const lower = ip.toLowerCase();
57
+ // A v4 address wearing a v6 spelling is still that v4 address, so judge what
58
+ // it embeds. Otherwise the whole v4 table above is one notation away from
59
+ // being bypassed.
60
+ if (lower.startsWith("::ffff:") || lower.startsWith("64:ff9b:")) {
61
+ const dotted = lower.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
62
+ if (dotted?.[1])
63
+ return isForbiddenIpv4(dotted[1]);
64
+ const groups = (lower.split("::").pop() ?? "").split(":").filter(Boolean);
65
+ if (groups.length <= 2) {
66
+ const hi = groups.length === 2 ? parseInt(groups[0] ?? "0", 16) : 0;
67
+ const lo = parseInt(groups[groups.length - 1] ?? "0", 16) || 0;
68
+ return isForbiddenIpv4(`${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`);
69
+ }
70
+ return true; // a malformed mapped form: refuse rather than guess
71
+ }
72
+ if (lower === "::" || lower === "::1")
73
+ return true;
74
+ const firstGroup = lower.split(":")[0] || "0";
75
+ const first16 = parseInt(firstGroup === "" ? "0" : firstGroup, 16);
76
+ if ((first16 & 0xfe00) === 0xfc00)
77
+ return true; // fc00::/7 unique-local
78
+ if ((first16 & 0xffc0) === 0xfe80)
79
+ return true; // fe80::/10 link-local
80
+ return false;
81
+ }
82
+ /**
83
+ * True for any address that must never be fetched server-side.
84
+ *
85
+ * Unparseable input is forbidden too: something that is not an address cannot be
86
+ * proven safe, and failing closed is the only correct direction here.
87
+ */
88
+ export function isForbiddenIp(ip) {
89
+ const family = isIP(ip);
90
+ if (family === 4)
91
+ return isForbiddenIpv4(ip);
92
+ if (family === 6)
93
+ return isForbiddenIpv6(ip);
94
+ return true;
95
+ }
96
+ /**
97
+ * Check a URL is safe to fetch, or throw saying why.
98
+ *
99
+ * http and https only, no embedded credentials, and every address the host
100
+ * resolves to must be public.
101
+ */
102
+ export async function assertPublicHttpUrl(raw, lookupFn = lookup, isForbiddenFn = isForbiddenIp) {
103
+ let url;
104
+ try {
105
+ url = new URL(raw);
106
+ }
107
+ catch {
108
+ throw new Error("not a valid URL");
109
+ }
110
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
111
+ throw new Error(`forbidden protocol ${url.protocol}`);
112
+ }
113
+ if (url.username || url.password) {
114
+ throw new Error("URLs with embedded credentials are not allowed");
115
+ }
116
+ // The WHATWG URL keeps the brackets on an IPv6 literal, which isIP rejects.
117
+ const host = url.hostname.replace(/^\[|\]$/g, "");
118
+ if (isIP(host)) {
119
+ if (isForbiddenFn(host))
120
+ throw new Error(`address ${host} is private or reserved`);
121
+ return url;
122
+ }
123
+ let addrs;
124
+ try {
125
+ addrs = await lookupFn(host, { all: true, verbatim: true });
126
+ }
127
+ catch {
128
+ throw new Error(`cannot resolve host ${host}`);
129
+ }
130
+ if (addrs.length === 0)
131
+ throw new Error(`host ${host} resolves to no addresses`);
132
+ for (const addr of addrs) {
133
+ if (isForbiddenFn(addr.address)) {
134
+ throw new Error(`host ${host} resolves to private or reserved address ${addr.address}`);
135
+ }
136
+ }
137
+ return url;
138
+ }
139
+ /**
140
+ * Fetch an image from an untrusted URL.
141
+ *
142
+ * Bounded in every direction that matters: total time, declared length, streamed
143
+ * length, and one redirect hop. Throws for anything that fails, so a caller can
144
+ * hand the reason straight back to the agent that supplied the URL.
145
+ */
146
+ export async function fetchPublicImage(rawUrl, maxBytes, timeoutMs = 10_000, lookupFn = lookup, isForbiddenFn = isForbiddenIp, redirectsLeft = 1) {
147
+ const url = await assertPublicHttpUrl(rawUrl, lookupFn, isForbiddenFn);
148
+ // The HTTP stack calls this to resolve the host, so an answer that was public
149
+ // during validation and private a moment later is refused here rather than
150
+ // quietly connecting to something internal.
151
+ const guardedLookup = (hostname, options, cb) => {
152
+ lookupFn(hostname, { all: true, verbatim: true }).then((addrs) => {
153
+ const bad = addrs.find((a) => isForbiddenFn(a.address));
154
+ if (bad || addrs.length === 0) {
155
+ cb(new Error(`DNS rebind blocked: ${hostname} resolved to ${bad?.address ?? "nothing"}`), null);
156
+ return;
157
+ }
158
+ if (options.all) {
159
+ cb(null, addrs);
160
+ return;
161
+ }
162
+ const first = addrs[0];
163
+ if (first === undefined) {
164
+ cb(new Error(`${hostname} resolved to nothing`), null);
165
+ return;
166
+ }
167
+ cb(null, first.address, first.family);
168
+ }, (err) => cb(err, null));
169
+ };
170
+ return new Promise((resolve, reject) => {
171
+ const req = (url.protocol === "https:" ? httpsRequest : httpRequest)(url,
172
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
+ { lookup: guardedLookup, headers: { accept: "image/*" } }, (res) => {
174
+ if (REDIRECT_STATUSES.has(res.statusCode ?? 0)) {
175
+ const location = res.headers.location;
176
+ res.resume();
177
+ if (!location) {
178
+ reject(new Error(`fetch ${rawUrl} returned HTTP ${res.statusCode} with no Location`));
179
+ return;
180
+ }
181
+ if (redirectsLeft <= 0) {
182
+ reject(new Error(`fetch ${rawUrl} followed too many redirects`));
183
+ return;
184
+ }
185
+ let next;
186
+ try {
187
+ next = new URL(location, url).toString();
188
+ }
189
+ catch {
190
+ reject(new Error(`fetch ${rawUrl} returned an invalid redirect location`));
191
+ return;
192
+ }
193
+ // Resolved against the CURRENT url, then put through the whole guard
194
+ // again. A target is not trustworthy for having been named by a host
195
+ // that passed.
196
+ fetchPublicImage(next, maxBytes, timeoutMs, lookupFn, isForbiddenFn, redirectsLeft - 1).then(resolve, reject);
197
+ return;
198
+ }
199
+ if (res.statusCode !== 200) {
200
+ res.resume();
201
+ reject(new Error(`fetch ${rawUrl} returned HTTP ${res.statusCode}`));
202
+ return;
203
+ }
204
+ const declared = Number(res.headers["content-length"] ?? NaN);
205
+ if (Number.isFinite(declared) && declared > maxBytes) {
206
+ res.destroy();
207
+ reject(new Error(`response too large (${declared} bytes, max ${maxBytes})`));
208
+ return;
209
+ }
210
+ const mime = (res.headers["content-type"] ?? "image/jpeg").split(";")[0]?.trim() ??
211
+ "image/jpeg";
212
+ const chunks = [];
213
+ let total = 0;
214
+ res.on("data", (chunk) => {
215
+ // The declared length is a claim, not a promise. Counting what
216
+ // actually arrives is what bounds a lying or chunked response.
217
+ total += chunk.length;
218
+ if (total > maxBytes) {
219
+ res.destroy();
220
+ reject(new Error(`response exceeded ${maxBytes} bytes; aborting read`));
221
+ return;
222
+ }
223
+ chunks.push(chunk);
224
+ });
225
+ res.on("end", () => resolve({ bytes: Buffer.concat(chunks), mime }));
226
+ res.on("error", reject);
227
+ });
228
+ // A hard total deadline: the socket's own timeout option is only an idle
229
+ // timer, so a slow drip would never trip it.
230
+ const deadline = setTimeout(() => req.destroy(new Error(`fetch ${rawUrl} timed out after ${timeoutMs}ms`)), timeoutMs);
231
+ deadline.unref?.();
232
+ req.on("error", (err) => {
233
+ clearTimeout(deadline);
234
+ reject(err);
235
+ });
236
+ req.on("close", () => clearTimeout(deadline));
237
+ req.end();
238
+ });
239
+ }
240
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAWhC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE7D,SAAS,SAAS,CAAC,EAAU;IAC3B,OAAO,CACL,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAC1E,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,EAAU,EAAE,IAAY,EAAE,QAAgB;IACzD,MAAM,IAAI,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC;IAChE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;AAClD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,MAAM,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;IACxB,OAAO,CACL,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,iBAAiB;QAC7C,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,UAAU;QACvC,OAAO,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC,IAAI,oBAAoB;QACpD,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,WAAW;QACzC,OAAO,CAAC,CAAC,EAAE,aAAa,EAAE,EAAE,CAAC,IAAI,uCAAuC;QACxE,OAAO,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC,IAAI,UAAU;QAC1C,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,CAAC,IAAI,4BAA4B;QAC3D,OAAO,CAAC,CAAC,EAAE,aAAa,EAAE,EAAE,CAAC,IAAI,UAAU;QAC3C,OAAO,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC,IAAI,eAAe;QAC/C,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,oCAAoC;KAChE,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/B,6EAA6E;IAC7E,0EAA0E;IAC1E,kBAAkB;IAClB,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAChE,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACpE,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC;YAAE,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1E,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpE,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;YAC/D,OAAO,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,OAAO,IAAI,CAAC,CAAC,oDAAoD;IACnE,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IAC9C,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACnE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACxE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,uBAAuB;IACvE,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACxB,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,eAAe,CAAC,EAAE,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,eAAe,CAAC,EAAE,CAAC,CAAC;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAW,EACX,WAAqB,MAAkB,EACvC,gBAA+B,aAAa;IAE5C,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,4EAA4E;IAC5E,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAClD,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACf,IAAI,aAAa,CAAC,IAAI,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,yBAAyB,CAAC,CAAC;QAC5D,OAAO,GAAG,CAAC;IACb,CAAC;IACD,IAAI,KAAiD,CAAC;IACtD,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,2BAA2B,CAAC,CAAC;IAC3D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,QAAQ,IAAI,4CAA4C,IAAI,CAAC,OAAO,EAAE,CACvE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAc,EACd,QAAgB,EAChB,SAAS,GAAG,MAAM,EAClB,WAAqB,MAAkB,EACvC,gBAA+B,aAAa,EAC5C,aAAa,GAAG,CAAC;IAEjB,MAAM,GAAG,GAAG,MAAM,mBAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAEvE,8EAA8E;IAC9E,2EAA2E;IAC3E,4CAA4C;IAC5C,MAAM,aAAa,GAAG,CACpB,QAAgB,EAChB,OAA0B,EAC1B,EAIS,EACH,EAAE;QACR,QAAQ,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CACpD,CAAC,KAAK,EAAE,EAAE;YACR,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YACxD,IAAI,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9B,EAAE,CACA,IAAI,KAAK,CACP,uBAAuB,QAAQ,gBAAgB,GAAG,EAAE,OAAO,IAAI,SAAS,EAAE,CAC3E,EACD,IAAa,CACd,CAAC;gBACF,OAAO;YACT,CAAC;YACD,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;gBAChB,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACvB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,EAAE,CAAC,IAAI,KAAK,CAAC,GAAG,QAAQ,sBAAsB,CAAC,EAAE,IAAa,CAAC,CAAC;gBAChE,OAAO;YACT,CAAC;YACD,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACxC,CAAC,EACD,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,GAA4B,EAAE,IAAa,CAAC,CACzD,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAClE,GAAG;QACH,8DAA8D;QAC9D,EAAE,MAAM,EAAE,aAAoB,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAChE,CAAC,GAAG,EAAE,EAAE;YACN,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC/C,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC;gBACtC,GAAG,CAAC,MAAM,EAAE,CAAC;gBACb,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,MAAM,CACJ,IAAI,KAAK,CACP,SAAS,MAAM,kBAAkB,GAAG,CAAC,UAAU,mBAAmB,CACnE,CACF,CAAC;oBACF,OAAO;gBACT,CAAC;gBACD,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;oBACvB,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,MAAM,8BAA8B,CAAC,CAAC,CAAC;oBACjE,OAAO;gBACT,CAAC;gBACD,IAAI,IAAY,CAAC;gBACjB,IAAI,CAAC;oBACH,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC3C,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM,CACJ,IAAI,KAAK,CACP,SAAS,MAAM,wCAAwC,CACxD,CACF,CAAC;oBACF,OAAO;gBACT,CAAC;gBACD,qEAAqE;gBACrE,qEAAqE;gBACrE,eAAe;gBACf,gBAAgB,CACd,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAa,EACb,aAAa,GAAG,CAAC,CAClB,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBACxB,OAAO;YACT,CAAC;YACD,IAAI,GAAG,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;gBAC3B,GAAG,CAAC,MAAM,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,MAAM,kBAAkB,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;gBACrE,OAAO;YACT,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,CAAC;YAC9D,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,QAAQ,EAAE,CAAC;gBACrD,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,MAAM,CACJ,IAAI,KAAK,CACP,uBAAuB,QAAQ,eAAe,QAAQ,GAAG,CAC1D,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GACR,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;gBACnE,YAAY,CAAC;YACf,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;gBAC/B,+DAA+D;gBAC/D,+DAA+D;gBAC/D,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;gBACtB,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;oBACrB,GAAG,CAAC,OAAO,EAAE,CAAC;oBACd,MAAM,CACJ,IAAI,KAAK,CAAC,qBAAqB,QAAQ,uBAAuB,CAAC,CAChE,CAAC;oBACF,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACrE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,CAAC,CACF,CAAC;QACF,yEAAyE;QACzE,6CAA6C;QAC7C,MAAM,QAAQ,GAAG,UAAU,CACzB,GAAG,EAAE,CACH,GAAG,CAAC,OAAO,CACT,IAAI,KAAK,CAAC,SAAS,MAAM,oBAAoB,SAAS,IAAI,CAAC,CAC5D,EACH,SAAS,CACV,CAAC;QACF,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC;QACnB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACtB,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9C,GAAG,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/gate.d.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * True when the utterance, AS A WHOLE, is a verbal interrupt.
3
+ *
4
+ * Deterministic and conservative: lowercase, punctuation stripped, surrounding
5
+ * filler AND the configured wake phrases removed - "hey <name>, stop" must cut as
6
+ * instantly as a bare "stop", because addressing the bot by name is how people
7
+ * interrupt it in a meeting - then an exact phrase-set match capped at four
8
+ * words. A longer sentence that merely contains "stop" does not match.
9
+ */
10
+ export declare const DEFAULT_FOLLOW_UP_WINDOW_MS = 12000;
11
+ /**
12
+ * Is this call attached to a Microsoft Teams meeting or channel thread?
13
+ *
14
+ * The group signal that actually arrives. The participant count that used to
15
+ * carry this does not reach a bot that joined through the meeting, so a gate
16
+ * keyed on it alone never fires.
17
+ */
18
+ export declare function isMeetingThread(threadId: string | undefined): boolean;
19
+ /** The outcome for one finished caller turn. */
20
+ export interface GateDecision {
21
+ /** Speak an answer to this turn. */
22
+ readonly respond: boolean;
23
+ /**
24
+ * The turn named the assistant. Opens the follow-up window, and is worth
25
+ * knowing separately from `respond`: a turn inside the window is answered
26
+ * without having been addressed.
27
+ */
28
+ readonly addressed: boolean;
29
+ }
30
+ /**
31
+ * Case-insensitive, word-boundary match of any wake phrase.
32
+ *
33
+ * Boundaries rather than substrings, so "assistant" matches and "assistants"
34
+ * does not. The boundary is expressed as "not a word character" so a non-Latin
35
+ * wake phrase behaves the same as a Latin one. An empty phrase list never
36
+ * matches.
37
+ */
38
+ export declare function isAddressed(transcript: string, wakePhrases: readonly string[]): boolean;
39
+ /** Options for {@link GroupGate}. */
40
+ export interface GroupGateOptions {
41
+ wakePhrases: readonly string[];
42
+ requireAddress?: boolean;
43
+ followUpWindowMs?: number;
44
+ threadId?: string;
45
+ }
46
+ /**
47
+ * The group-call gate for one call. Holds the follow-up window's state.
48
+ *
49
+ * An agent in a meeting that answers every sentence is worse than one that says
50
+ * nothing. The gate keeps it quiet until somebody names it, then leaves the
51
+ * floor open for a short while so a follow-up does not need the name again.
52
+ */
53
+ export declare class GroupGate {
54
+ #private;
55
+ readonly wakePhrases: readonly string[];
56
+ readonly requireAddress: boolean;
57
+ readonly followUpWindowMs: number;
58
+ constructor(options: GroupGateOptions);
59
+ /** More than one human on this call, by either signal. */
60
+ get isGroup(): boolean;
61
+ /**
62
+ * Is the gate actually muting anything right now?
63
+ *
64
+ * A gate with no wake phrase configured can never be opened, so it would mute
65
+ * the assistant for the whole call. Treat "no trigger configured" as gate off.
66
+ */
67
+ get active(): boolean;
68
+ /**
69
+ * Record a participant count from call context.
70
+ *
71
+ * Corroborating, never authoritative: a count that says 1 does not clear a
72
+ * meeting thread. The count is the signal that goes missing, so it may add
73
+ * certainty and must not remove it.
74
+ */
75
+ noteParticipants(count: number): void;
76
+ /** Answer this turn, or stay out of the meeting? */
77
+ decide(transcript: string, nowMs: number): GateDecision;
78
+ }
79
+ export declare function isVerbalInterrupt(text: string | undefined, wakePhrases?: string[]): boolean;
80
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAoHA;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAQlD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAErE;AAED,gDAAgD;AAChD,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAgBT;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,qBAAa,SAAS;;IACpB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;gBAKtB,OAAO,EAAE,gBAAgB;IAQrC,0DAA0D;IAC1D,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;;OAKG;IACH,IAAI,MAAM,IAAI,OAAO,CAMpB;IAED;;;;;;OAMG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIrC,oDAAoD;IACpD,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY;CAexD;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO,CAiCT"}
package/dist/gate.js ADDED
@@ -0,0 +1,249 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Deterministic verbal interrupts: "stop", "hold on", "never mind".
5
+ *
6
+ * These are handled in code rather than left to the model because the model is
7
+ * mid-generation when they arrive. Waiting for its own interruption handling is
8
+ * what makes a cut feel late; matching the phrase ourselves and flushing the
9
+ * caller's playback queue is what makes it feel instant.
10
+ *
11
+ * Matching is WHOLE-UTTERANCE only, after stripping filler and the configured
12
+ * wake phrases, so "stop by the store on your way" never triggers.
13
+ */
14
+ /** Normalized utterances that mean "stop talking / hold on". */
15
+ const INTERRUPT_PHRASES = new Set([
16
+ "stop",
17
+ "stop it",
18
+ "stop talking",
19
+ "wait",
20
+ "wait wait",
21
+ "wait a second",
22
+ "wait a minute",
23
+ "hold on",
24
+ "hang on",
25
+ "never mind",
26
+ "nevermind",
27
+ "be quiet",
28
+ "quiet",
29
+ "shut up",
30
+ "enough",
31
+ "thats enough",
32
+ "pause",
33
+ "one second",
34
+ "one sec",
35
+ "give me a second",
36
+ // Arabic: the deterministic cut has to work in both call languages, because the
37
+ // model mirrors the caller's. Whole-utterance matching plus the normalizer's
38
+ // tashkeel stripping keep these as conservative as the English set.
39
+ "توقف", // stop
40
+ "قف", // halt
41
+ "اسكت", // be quiet
42
+ "اصمت", // silence
43
+ "انتظر", // wait
44
+ "انتظر لحظة", // wait a moment
45
+ "استنى", // wait (colloquial)
46
+ "استنا",
47
+ "لحظة", // one moment
48
+ "لحظه",
49
+ "لحظة واحدة", // just a moment
50
+ "ثانية", // one second
51
+ "ثانيه",
52
+ "ثانية واحدة",
53
+ "دقيقة", // one minute
54
+ "دقيقه",
55
+ "مهلا", // hold on
56
+ "خلاص", // enough / that is it
57
+ "كفى", // enough
58
+ "كفاية",
59
+ "كفايه",
60
+ "بس", // enough/stop (colloquial; safe only because matching is whole-utterance)
61
+ ]);
62
+ /**
63
+ * Leading and trailing filler stripped before matching ("ok stop", "no wait",
64
+ * "stop please"; Arabic "طيب توقف" = "ok stop", "يا" = the vocative that precedes
65
+ * a name).
66
+ */
67
+ const FILLER_TOKENS = new Set([
68
+ "ok",
69
+ "okay",
70
+ "oh",
71
+ "no",
72
+ "hey",
73
+ "please",
74
+ "now",
75
+ "طيب",
76
+ "لا",
77
+ "يا",
78
+ ]);
79
+ function normalizeWords(text) {
80
+ return ((text ?? "")
81
+ .toLowerCase()
82
+ .replace(/['’]/g, "") // "that's" -> "thats", not "that s"
83
+ // Combining marks (Arabic tashkeel, accents) and the Arabic tatweel attach
84
+ // INSIDE a word - delete them outright so "تَوَقَّف" normalizes to "توقف"
85
+ // instead of splitting apart.
86
+ .replace(/[\p{M}ـ]/gu, "")
87
+ // Letters in ANY script survive: an Arabic "توقف" must cut as instantly as
88
+ // "stop". Everything else separates words.
89
+ .replace(/[^\p{L}\s]/gu, " ")
90
+ .split(/\s+/)
91
+ .filter(Boolean));
92
+ }
93
+ function startsWithSeq(words, seq) {
94
+ return (seq.length > 0 &&
95
+ seq.length <= words.length &&
96
+ seq.every((w, i) => words[i] === w));
97
+ }
98
+ function endsWithSeq(words, seq) {
99
+ const offset = words.length - seq.length;
100
+ return (seq.length > 0 &&
101
+ offset >= 0 &&
102
+ seq.every((w, i) => words[offset + i] === w));
103
+ }
104
+ /**
105
+ * True when the utterance, AS A WHOLE, is a verbal interrupt.
106
+ *
107
+ * Deterministic and conservative: lowercase, punctuation stripped, surrounding
108
+ * filler AND the configured wake phrases removed - "hey <name>, stop" must cut as
109
+ * instantly as a bare "stop", because addressing the bot by name is how people
110
+ * interrupt it in a meeting - then an exact phrase-set match capped at four
111
+ * words. A longer sentence that merely contains "stop" does not match.
112
+ */
113
+ export const DEFAULT_FOLLOW_UP_WINDOW_MS = 12_000;
114
+ /**
115
+ * Microsoft's thread-id prefix for a meeting or channel conversation. A 1:1 call
116
+ * has no such thread at all, so the presence of one is the signal.
117
+ */
118
+ const MEETING_THREAD_PREFIX = "19:";
119
+ /**
120
+ * Is this call attached to a Microsoft Teams meeting or channel thread?
121
+ *
122
+ * The group signal that actually arrives. The participant count that used to
123
+ * carry this does not reach a bot that joined through the meeting, so a gate
124
+ * keyed on it alone never fires.
125
+ */
126
+ export function isMeetingThread(threadId) {
127
+ return (threadId ?? "").trim().startsWith(MEETING_THREAD_PREFIX);
128
+ }
129
+ /**
130
+ * Case-insensitive, word-boundary match of any wake phrase.
131
+ *
132
+ * Boundaries rather than substrings, so "assistant" matches and "assistants"
133
+ * does not. The boundary is expressed as "not a word character" so a non-Latin
134
+ * wake phrase behaves the same as a Latin one. An empty phrase list never
135
+ * matches.
136
+ */
137
+ export function isAddressed(transcript, wakePhrases) {
138
+ if (!transcript || wakePhrases.length === 0)
139
+ return false;
140
+ const lowered = transcript.toLowerCase();
141
+ for (const raw of wakePhrases) {
142
+ const phrase = raw.trim().toLowerCase();
143
+ if (phrase === "")
144
+ continue;
145
+ const escaped = phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
146
+ if (new RegExp(`(?<![\\p{L}\\p{N}_])${escaped}(?![\\p{L}\\p{N}_])`, "u").test(lowered)) {
147
+ return true;
148
+ }
149
+ }
150
+ return false;
151
+ }
152
+ /**
153
+ * The group-call gate for one call. Holds the follow-up window's state.
154
+ *
155
+ * An agent in a meeting that answers every sentence is worse than one that says
156
+ * nothing. The gate keeps it quiet until somebody names it, then leaves the
157
+ * floor open for a short while so a follow-up does not need the name again.
158
+ */
159
+ export class GroupGate {
160
+ wakePhrases;
161
+ requireAddress;
162
+ followUpWindowMs;
163
+ #meetingThread;
164
+ #humanCount = 0;
165
+ #lastAddressedMs;
166
+ constructor(options) {
167
+ this.wakePhrases = options.wakePhrases;
168
+ this.requireAddress = options.requireAddress ?? true;
169
+ this.followUpWindowMs =
170
+ options.followUpWindowMs ?? DEFAULT_FOLLOW_UP_WINDOW_MS;
171
+ this.#meetingThread = isMeetingThread(options.threadId);
172
+ }
173
+ /** More than one human on this call, by either signal. */
174
+ get isGroup() {
175
+ return this.#meetingThread || this.#humanCount >= 2;
176
+ }
177
+ /**
178
+ * Is the gate actually muting anything right now?
179
+ *
180
+ * A gate with no wake phrase configured can never be opened, so it would mute
181
+ * the assistant for the whole call. Treat "no trigger configured" as gate off.
182
+ */
183
+ get active() {
184
+ return (this.isGroup &&
185
+ this.requireAddress &&
186
+ this.wakePhrases.some((p) => p.trim() !== ""));
187
+ }
188
+ /**
189
+ * Record a participant count from call context.
190
+ *
191
+ * Corroborating, never authoritative: a count that says 1 does not clear a
192
+ * meeting thread. The count is the signal that goes missing, so it may add
193
+ * certainty and must not remove it.
194
+ */
195
+ noteParticipants(count) {
196
+ this.#humanCount = Math.max(this.#humanCount, count);
197
+ }
198
+ /** Answer this turn, or stay out of the meeting? */
199
+ decide(transcript, nowMs) {
200
+ const addressed = isAddressed(transcript, this.wakePhrases);
201
+ if (!this.active)
202
+ return { respond: true, addressed };
203
+ if (addressed) {
204
+ this.#lastAddressedMs = nowMs;
205
+ return { respond: true, addressed: true };
206
+ }
207
+ if (this.#lastAddressedMs !== undefined &&
208
+ nowMs - this.#lastAddressedMs <= this.followUpWindowMs) {
209
+ return { respond: true, addressed: false };
210
+ }
211
+ return { respond: false, addressed: false };
212
+ }
213
+ }
214
+ export function isVerbalInterrupt(text, wakePhrases) {
215
+ let core = normalizeWords(text);
216
+ const wake = (wakePhrases ?? [])
217
+ .map(normalizeWords)
218
+ .filter((seq) => seq.length > 0);
219
+ // Strip surrounding filler and wake tokens until stable - they interleave
220
+ // ("ok <name> please stop").
221
+ let changed = true;
222
+ while (changed && core.length > 0) {
223
+ changed = false;
224
+ while (core.length > 0 && FILLER_TOKENS.has(core[0] ?? "")) {
225
+ core.shift();
226
+ changed = true;
227
+ }
228
+ while (core.length > 0 && FILLER_TOKENS.has(core[core.length - 1] ?? "")) {
229
+ core.pop();
230
+ changed = true;
231
+ }
232
+ for (const seq of wake) {
233
+ if (startsWithSeq(core, seq)) {
234
+ core = core.slice(seq.length);
235
+ changed = true;
236
+ }
237
+ else if (endsWithSeq(core, seq)) {
238
+ core = core.slice(0, core.length - seq.length);
239
+ changed = true;
240
+ }
241
+ }
242
+ }
243
+ // The wake word alone ("<name>?") is an address, not an interrupt.
244
+ if (core.length === 0 || core.length > 4) {
245
+ return false;
246
+ }
247
+ return INTERRUPT_PHRASES.has(core.join(" "));
248
+ }
249
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}