@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/vision.js ADDED
@@ -0,0 +1,301 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * The vision lane: what the caller shows you, and what you show back.
5
+ *
6
+ * A Microsoft Teams call carries more than voice. StandIn samples the caller's
7
+ * camera and their screen share and forwards single JPEG frames, and it will
8
+ * draw an image you send onto the bot's own tile. This module is both halves of
9
+ * that: {@link parseVideoFrame} reads what arrives, {@link displayImage} builds
10
+ * what goes back.
11
+ *
12
+ * Frames arrive **sparsely and best-effort**. StandIn drops a frame rather than
13
+ * queueing it when the socket is busy, so this is not a video stream and must
14
+ * not be treated as one. The useful shape is the one every provider plugin
15
+ * ends up with: keep the latest frame per source and send it to a vision model
16
+ * only when something asks to look. {@link CallSession.latestVideoFrame} does
17
+ * that for you, so a plugin that only wants on-demand vision implements
18
+ * no callback at all.
19
+ *
20
+ * {@link FrameDescriber} is the other way round, and the one most voice
21
+ * providers need: a speech-to-speech model that hears but cannot see gets a
22
+ * sentence of text instead of a picture. The frame goes to a vision model of
23
+ * your choosing, transiently, and only the description comes back.
24
+ *
25
+ * Identical in shape to the Python SDK's `standin.vision`, translated to TS
26
+ * naming.
27
+ */
28
+ import { createHash } from "node:crypto";
29
+ import { encode } from "./protocolRuntime.js";
30
+ import { TYPE_DISPLAY_FRAME, TYPE_DISPLAY_IMAGE } from "./protocol.js";
31
+ /** The two things a caller can show: their camera, or their screen share. */
32
+ export const VIDEO_SOURCES = ["camera", "screenshare"];
33
+ /** What StandIn will draw on the bot tile. JPEG or PNG, nothing else. */
34
+ export const DISPLAY_IMAGE_MIME_TYPES = ["image/jpeg", "image/png"];
35
+ /**
36
+ * One wire message is bounded at 2 MB by both SDKs, and base64 costs a third on
37
+ * top of the raw bytes. Refusing an oversized image here names the real problem,
38
+ * rather than letting the service close the socket mid-call.
39
+ */
40
+ export const MAX_IMAGE_BYTES = 1_400_000;
41
+ function text(value) {
42
+ if (typeof value !== "string")
43
+ return undefined;
44
+ const trimmed = value.trim();
45
+ return trimmed === "" ? undefined : trimmed;
46
+ }
47
+ function positiveInt(value) {
48
+ return typeof value === "number" && Number.isInteger(value) && value > 0
49
+ ? value
50
+ : undefined;
51
+ }
52
+ function decodeStrict(dataBase64) {
53
+ const data = Buffer.from(dataBase64, "base64");
54
+ // Buffer.from never throws on bad input, it silently discards what it cannot
55
+ // read. Re-encoding is the only way to know the bytes about to reach a
56
+ // provider really are the payload that was sent.
57
+ return data.length > 0 && data.toString("base64") === dataBase64
58
+ ? data
59
+ : undefined;
60
+ }
61
+ /**
62
+ * Read a `video.frame`, or return `undefined` if it is unusable.
63
+ *
64
+ * Never throws. A frame that fails any check is a frame to drop: the call is
65
+ * healthy, the caller is still talking, and one malformed image is not worth
66
+ * ending a conversation over. That is the same leniency the rest of the wire
67
+ * contract is built on, where a receiver ignores what it cannot use.
68
+ */
69
+ export function parseVideoFrame(msg) {
70
+ const source = text(msg.source);
71
+ if (source !== "camera" && source !== "screenshare")
72
+ return undefined;
73
+ const dataBase64 = msg.dataBase64;
74
+ if (typeof dataBase64 !== "string" || dataBase64 === "")
75
+ return undefined;
76
+ const data = decodeStrict(dataBase64);
77
+ if (data === undefined)
78
+ return undefined;
79
+ const width = positiveInt(msg.width);
80
+ const height = positiveInt(msg.height);
81
+ if (width === undefined || height === undefined)
82
+ return undefined;
83
+ const ts = msg.ts;
84
+ const mime = text(msg.mime) ?? "image/jpeg";
85
+ return {
86
+ source,
87
+ ts: typeof ts === "number" && Number.isInteger(ts) && ts >= 0 ? ts : 0,
88
+ width,
89
+ height,
90
+ mime,
91
+ dataBase64,
92
+ participantId: text(msg.participantId),
93
+ participantName: text(msg.participantName),
94
+ data,
95
+ dataUrl: `data:${mime};base64,${dataBase64}`,
96
+ };
97
+ }
98
+ function encodeImage(image, mime, label) {
99
+ if (!DISPLAY_IMAGE_MIME_TYPES.includes(mime)) {
100
+ throw new Error(`${label} mime must be one of ${DISPLAY_IMAGE_MIME_TYPES.join(", ")}, got ${mime}`);
101
+ }
102
+ let size;
103
+ let dataBase64;
104
+ if (typeof image === "string") {
105
+ // Already base64: measure the decoded size, because that is what the 2 MB
106
+ // envelope actually bounds.
107
+ const decoded = decodeStrict(image);
108
+ if (decoded === undefined)
109
+ throw new Error(`${label} data is not valid base64`);
110
+ size = decoded.length;
111
+ dataBase64 = image;
112
+ }
113
+ else {
114
+ size = image.length;
115
+ dataBase64 = image.toString("base64");
116
+ }
117
+ if (size === 0)
118
+ throw new Error(`${label} carries no image data`);
119
+ if (size > MAX_IMAGE_BYTES) {
120
+ throw new Error(`${label} is ${size} bytes, over the ${MAX_IMAGE_BYTES} limit`);
121
+ }
122
+ return dataBase64;
123
+ }
124
+ /**
125
+ * Build a `display.image`: show the caller a still, then return to the avatar.
126
+ *
127
+ * `image` is a Buffer or an already-base64 string.
128
+ */
129
+ export function displayImage(image, options = {}) {
130
+ const mime = options.mime ?? "image/jpeg";
131
+ const message = {
132
+ type: TYPE_DISPLAY_IMAGE,
133
+ dataBase64: encodeImage(image, mime, "display.image"),
134
+ mime,
135
+ // The wire reserves a timeline anchor this lane does not use. Both SDKs
136
+ // send 0 rather than one omitting it, so a single conformance vector
137
+ // covers both and neither can drift.
138
+ ts: 0,
139
+ };
140
+ if (options.durationMs !== undefined && options.durationMs > 0) {
141
+ message.durationMs = options.durationMs;
142
+ }
143
+ if (options.mode)
144
+ message.mode = options.mode;
145
+ if (options.caption)
146
+ message.caption = options.caption;
147
+ return encode(message);
148
+ }
149
+ /**
150
+ * Build a `display.frame`: one frame of continuous avatar video.
151
+ *
152
+ * Latest wins. There is no handshake, the first frames start the stream and
153
+ * silence ends it, and a sender under backpressure MUST drop frames rather than
154
+ * buffer them, exactly as it does for hot-path audio.
155
+ *
156
+ * `ts` belongs to the sender's own media timeline, the same one its outbound
157
+ * audio is stamped on, so the two streams share a clock.
158
+ */
159
+ export function displayFrame(seq, ts, image, options = {}) {
160
+ const mime = options.mime ?? "image/jpeg";
161
+ const message = {
162
+ type: TYPE_DISPLAY_FRAME,
163
+ seq,
164
+ ts,
165
+ mime,
166
+ dataBase64: encodeImage(image, mime, "display.frame"),
167
+ };
168
+ if (options.width !== undefined)
169
+ message.width = options.width;
170
+ if (options.height !== undefined)
171
+ message.height = options.height;
172
+ return encode(message);
173
+ }
174
+ /** Hard bound on the vision round trip. The caller hears this as silence. */
175
+ const DESCRIBE_TIMEOUT_MS = 20_000;
176
+ /** Enough for a sentence or two read aloud. A voice agent cannot relay an essay. */
177
+ const DESCRIBE_MAX_TOKENS = 300;
178
+ /**
179
+ * Turn a frame into a sentence, using a vision model you choose.
180
+ *
181
+ * Most speech-to-speech providers hear but cannot see. This is what lets one
182
+ * answer "what is on my screen?": the frame goes to any OpenAI-compatible
183
+ * chat-completions endpoint that accepts image input (OpenAI, Azure OpenAI,
184
+ * Ollama, vLLM, whatever you run), and what comes back is text the agent can say
185
+ * out loud.
186
+ *
187
+ * The frame is sent for inference and not stored, which is the difference
188
+ * between this and uploading it into a provider's own conversation history.
189
+ *
190
+ * Deliberately NOT put through the guard in `fetch.ts`: this URL is yours, set
191
+ * by you in the environment, and a vision model on localhost is a normal way to
192
+ * run one. That is the opposite of an image URL a model chose.
193
+ */
194
+ /**
195
+ * A short, stable fingerprint of one frame.
196
+ *
197
+ * For asking "is this the same screen as last time?" without keeping the
198
+ * picture. A hash of the encoded form is enough: two encodes of an unchanged
199
+ * screen are byte-identical.
200
+ */
201
+ export function frameDigest(dataBase64) {
202
+ return createHash("sha256")
203
+ .update(dataBase64 ?? "", "ascii")
204
+ .digest("hex")
205
+ .slice(0, 32);
206
+ }
207
+ /** Who is showing this, when the wire said. */
208
+ export function frameOwner(frame) {
209
+ const name = (frame.participantName ?? "").trim();
210
+ return name === "" ? undefined : name;
211
+ }
212
+ /**
213
+ * Who to say it is when nobody was named.
214
+ *
215
+ * Attribution that degrades rather than vanishing: "a participant's screen" is
216
+ * worth more to a model than an unlabelled picture.
217
+ */
218
+ export function fallbackOwner(source) {
219
+ return source === "screenshare" ? "a participant" : "the caller";
220
+ }
221
+ /** The sentence that goes beside a frame, so a model knows whose it is. */
222
+ export function frameCaption(owner) {
223
+ return owner === "a participant"
224
+ ? `screen shared by ${owner}`
225
+ : `camera of ${owner}`;
226
+ }
227
+ export class FrameDescriber {
228
+ url;
229
+ model;
230
+ apiKey;
231
+ constructor(options) {
232
+ this.url = options.url;
233
+ this.model = options.model;
234
+ this.apiKey = options.apiKey;
235
+ }
236
+ /**
237
+ * Build one from `STANDIN_VISION_API_URL` and `STANDIN_VISION_MODEL`.
238
+ *
239
+ * Returns `undefined` when they are not set, which is the signal a
240
+ * plugin uses to tell an agent that looking is not available here.
241
+ */
242
+ static fromEnv() {
243
+ const url = (process.env.STANDIN_VISION_API_URL ?? "").trim();
244
+ const model = (process.env.STANDIN_VISION_MODEL ?? "").trim();
245
+ if (!url || !model)
246
+ return undefined;
247
+ const apiKey = (process.env.STANDIN_VISION_API_KEY ?? "").trim();
248
+ return new FrameDescriber({ url, model, apiKey: apiKey || undefined });
249
+ }
250
+ /**
251
+ * Ask the model about one frame. Throws on anything that goes wrong, so a
252
+ * caller can hand the reason back to the agent that asked.
253
+ */
254
+ async describe(frame, question) {
255
+ const who = frameOwner(frame) ?? fallbackOwner(frame.source);
256
+ const seeing = frameCaption(who);
257
+ const headers = {
258
+ "content-type": "application/json",
259
+ };
260
+ if (this.apiKey)
261
+ headers.authorization = `Bearer ${this.apiKey}`;
262
+ const controller = new AbortController();
263
+ const deadline = setTimeout(() => controller.abort(), DESCRIBE_TIMEOUT_MS);
264
+ try {
265
+ const response = await fetch(this.url, {
266
+ method: "POST",
267
+ headers,
268
+ signal: controller.signal,
269
+ body: JSON.stringify({
270
+ model: this.model,
271
+ max_tokens: DESCRIBE_MAX_TOKENS,
272
+ messages: [
273
+ {
274
+ role: "user",
275
+ content: [
276
+ {
277
+ type: "text",
278
+ text: `This is a live frame from a Microsoft Teams call (${seeing}). ` +
279
+ `Answer concisely, for a voice agent to say out loud. Question: ${question}`,
280
+ },
281
+ { type: "image_url", image_url: { url: frame.dataUrl } },
282
+ ],
283
+ },
284
+ ],
285
+ }),
286
+ });
287
+ if (!response.ok) {
288
+ throw new Error(`the vision model returned HTTP ${response.status}`);
289
+ }
290
+ const data = (await response.json());
291
+ const text = (data.choices?.[0]?.message?.content ?? "").trim();
292
+ if (!text)
293
+ throw new Error("the vision model returned nothing");
294
+ return text;
295
+ }
296
+ finally {
297
+ clearTimeout(deadline);
298
+ }
299
+ }
300
+ }
301
+ //# sourceMappingURL=vision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision.js","sourceRoot":"","sources":["../src/vision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,237 @@
1
+ import type { CallSession } from "./handler.js";
2
+ import { type DisplayImageMode, type FrameDescriber, type VideoFrame, type VideoSource } from "./vision.js";
3
+ /** The only two ways a picture can be put on the tile. */
4
+ export declare const DISPLAY_MODES: readonly ["fullscreen", "overlay"];
5
+ /** How long each picture of a slideshow stays up before the next one. */
6
+ export declare const SLIDESHOW_HOLD_MS = 4000;
7
+ /**
8
+ * How much longer than the gap each picture is held for.
9
+ *
10
+ * Without it the tile blanks for the moment between one picture expiring and
11
+ * the next arriving, and the caller sees a flicker rather than a slideshow.
12
+ */
13
+ export declare const SLIDESHOW_OVERLAP_MS = 500;
14
+ /**
15
+ * How many pictures one slideshow may hold.
16
+ *
17
+ * A model handed a folder will pass the whole folder. Ten at four seconds is
18
+ * already most of a minute of a live call spent looking at pictures.
19
+ */
20
+ export declare const MAX_SLIDESHOW_IMAGES = 10;
21
+ /** How long a page has to render before the caller is told it did not. */
22
+ export declare const PAGE_RENDER_TIMEOUT_MS = 45000;
23
+ /**
24
+ * How long a rendered page stays on the tile. Longer than a chart: a page is
25
+ * read rather than glanced at.
26
+ */
27
+ export declare const PAGE_DISPLAY_MS = 15000;
28
+ /**
29
+ * The display mode this value means, or the default.
30
+ *
31
+ * One rule, shared by both SDKs and every plugin, because the value comes from
32
+ * a model: `"pip"`, `"inset"`, `"full"` and nothing at all are all things a
33
+ * model will say, and none of them is a mode.
34
+ */
35
+ export declare function normalizeDisplayMode(value: unknown, fallback?: DisplayImageMode): DisplayImageMode | undefined;
36
+ /**
37
+ * A filename to show beside a picture.
38
+ *
39
+ * Taken from the source when it looks like a filename and nothing else. The
40
+ * string came from a model steered by whoever is on the call, and it is about
41
+ * to be shown to them.
42
+ */
43
+ export declare function displayImageName(pathOrUrl: string, mime: string): string;
44
+ /** The last picture the caller actually saw. */
45
+ export declare class ShownImage {
46
+ readonly image: Buffer | string;
47
+ readonly mime: string;
48
+ readonly name: string;
49
+ readonly atMs: number;
50
+ constructor(image: Buffer | string, mime: string, name: string, atMs: number);
51
+ asBase64(): string;
52
+ }
53
+ /**
54
+ * Renders one page to bytes plus a mime type. Supplied by a plugin whose host
55
+ * already runs a browser; the core never gains one.
56
+ */
57
+ export type PageRenderer = (url: string) => Promise<{
58
+ bytes: Buffer | string;
59
+ mime: string;
60
+ }>;
61
+ /** One picture in a slideshow. Bytes, base64, or an https URL. */
62
+ export interface ShowItem {
63
+ image: Buffer | string;
64
+ mime?: string;
65
+ name?: string;
66
+ }
67
+ /**
68
+ * A ceiling on how often one call may spend on vision.
69
+ *
70
+ * A model that can look can look in a loop, and each look is a paid inference
71
+ * over somebody's screen. A sliding window rather than a total, so a long call
72
+ * is not punished for having been long.
73
+ *
74
+ * Spending returns a token and refunding takes that token back. Two tool calls
75
+ * can overlap, and a refund that simply dropped "the most recent charge" would
76
+ * refund the wrong one and let the budget drift upward under exactly the load it
77
+ * exists to bound.
78
+ */
79
+ export declare class VisionBudget {
80
+ #private;
81
+ readonly maxPerMinute: number;
82
+ constructor(maxPerMinute?: number);
83
+ /** Take one look's worth of budget, or undefined when there is none left. */
84
+ tryConsume(): string | undefined;
85
+ /**
86
+ * How much of the window only an explicit look may spend.
87
+ *
88
+ * Ambient vision spends on every scene change, which is exactly the load that
89
+ * would leave a caller's own "look at this" with nothing left. The reserve is
90
+ * what the ambient lane cannot touch.
91
+ */
92
+ get reserve(): number;
93
+ /**
94
+ * Take one look's worth, from the ambient lane only.
95
+ *
96
+ * Refused once the window is down to the reserve. Refunded through the same
97
+ * {@link refund}, with the same token, so a failed ambient push and a failed
98
+ * explicit look are given back the same way.
99
+ */
100
+ tryConsumeAmbient(): string | undefined;
101
+ /** Give back a charge whose look never happened. Idempotent. */
102
+ refund(token: string): void;
103
+ /** Looks charged in the current window. */
104
+ get spent(): number;
105
+ }
106
+ /**
107
+ * A short history of what the caller showed.
108
+ *
109
+ * The call session keeps the LATEST frame per source, which answers "what am I
110
+ * looking at now". This answers "what was on that slide a moment ago".
111
+ *
112
+ * Bounded, and **gated on the call being recorded**. Keeping a history of
113
+ * somebody's screen is a materially different promise from glancing at it once,
114
+ * and the recording is the thing that told them their call is being kept.
115
+ */
116
+ export declare class KeyframeStore {
117
+ #private;
118
+ constructor(capacity?: number);
119
+ /** Keep this frame, if the call is being recorded. Returns whether it was kept. */
120
+ offer(frame: VideoFrame, recording: boolean): boolean;
121
+ /** Frames kept so far, oldest first. */
122
+ recent(source?: VideoSource): VideoFrame[];
123
+ /** Forget everything. Called on teardown. */
124
+ clear(): void;
125
+ get size(): number;
126
+ }
127
+ /** One beat of a walkthrough: something to say, optionally something to show. */
128
+ export interface WalkthroughStep {
129
+ /** The line spoken before the image appears. */
130
+ say: string;
131
+ image?: Buffer | string;
132
+ mime?: string;
133
+ caption?: string;
134
+ }
135
+ /**
136
+ * Says one line and resolves when the caller has heard it. Supplied by the
137
+ * plugin, because "finished speaking" is a thing only the provider knows.
138
+ */
139
+ export type Speaker = (text: string) => Promise<void>;
140
+ /** Options for {@link VisionTools}. */
141
+ export interface VisionToolsOptions {
142
+ describer?: FrameDescriber;
143
+ budget?: VisionBudget;
144
+ keyframes?: KeyframeStore;
145
+ /**
146
+ * What to use when the model says nothing. Left out, no mode is sent at all,
147
+ * so the service's own default applies rather than one chosen here.
148
+ */
149
+ defaultDisplayMode?: string;
150
+ }
151
+ /**
152
+ * The capabilities, bound to one call.
153
+ *
154
+ * Every method returns a sentence for a model to read out, including when it
155
+ * failed. None of them throw.
156
+ */
157
+ export declare class VisionTools {
158
+ #private;
159
+ readonly budget: VisionBudget;
160
+ readonly keyframes: KeyframeStore;
161
+ constructor(session: CallSession, options?: VisionToolsOptions);
162
+ /**
163
+ * The picture the caller can see, if any.
164
+ *
165
+ * Recorded only after a send actually returned, so "send me that" attaches
166
+ * what they saw rather than what was attempted. One slot, replaced each time:
167
+ * a list would be a growing copy of everything shown on the call.
168
+ */
169
+ get lastShown(): ShownImage | undefined;
170
+ /** The slideshow now running, if any. Await it to let one finish. */
171
+ get slideshow(): Promise<void> | undefined;
172
+ /** Forget what was shown and stop any slideshow. Call this on teardown. */
173
+ reset(): Promise<void>;
174
+ /**
175
+ * Answer a question about what the caller is showing.
176
+ *
177
+ * Uses the newest frame, preferring the screen share, because an agent asked
178
+ * to look is nearly always being asked about what is being shown rather than
179
+ * who is showing it.
180
+ */
181
+ look(question?: string, source?: string): Promise<string>;
182
+ /**
183
+ * Answer about a frame the caller has already moved past.
184
+ *
185
+ * Only possible when the call is being recorded, because that is the only time
186
+ * frames are kept at all.
187
+ */
188
+ lookBack(question?: string): Promise<string>;
189
+ /** Put an image on the bot's video tile. */
190
+ show(image: Buffer | string, mime?: string, caption?: string, durationMs?: number, display?: string, name?: string): Promise<string>;
191
+ /**
192
+ * Fetch an image the model chose, and show it.
193
+ *
194
+ * The URL comes from a model steered by whoever is on the call, so it goes
195
+ * through the SDK's guard: public hosts only, and the address re-checked at
196
+ * connect time.
197
+ */
198
+ showUrl(url: string, caption?: string, display?: string): Promise<string>;
199
+ /**
200
+ * Put a web page on the tile, as a picture of it.
201
+ *
202
+ * The core has no browser and must never gain one. `render` is supplied by a
203
+ * plugin whose host already runs one, and it returns bytes rather than a
204
+ * path: reading a file chosen downstream of whoever is on the call is not a
205
+ * primitive this belongs in.
206
+ *
207
+ * The guard runs HERE, before the renderer is reached, and it runs even when
208
+ * that renderer is a browser advertising private-network protection of its
209
+ * own. Such a browser assumes whoever wrote the URL already has a shell on
210
+ * the machine. Here the URL was written by a model being steered by a
211
+ * stranger, which is exactly the case that relaxation lets through.
212
+ */
213
+ showPage(url: string, caption?: string, render?: PageRenderer, timeoutMs?: number): Promise<string>;
214
+ /**
215
+ * Show several pictures in turn, without waiting for all of them.
216
+ *
217
+ * The FIRST one goes before this resolves, so the model can say "here it is"
218
+ * and be right. The rest are paced from a detached chain: a model that waits
219
+ * out a ten-picture slideshow before speaking leaves the caller in silence for
220
+ * most of a minute.
221
+ *
222
+ * Never throws. The sentence says what is on screen now and what follows.
223
+ */
224
+ showMany(items: readonly ShowItem[], caption?: string, display?: string, holdMs?: number): Promise<string>;
225
+ /**
226
+ * Say and show several things in order, pausing for each.
227
+ *
228
+ * The pacing is here; the SPEAKING is not. Only the provider knows when a line
229
+ * has finished being said, so `speak` is supplied by the plugin and awaited
230
+ * before the next beat begins. Without that, a walkthrough talks over itself.
231
+ *
232
+ * `interrupted` is checked between beats. A caller who cuts in should stop the
233
+ * tour, and the plugin is the only thing that knows they did.
234
+ */
235
+ walkthrough(steps: readonly WalkthroughStep[], speak: Speaker, interrupted?: () => boolean, display?: string): Promise<string>;
236
+ }
237
+ //# sourceMappingURL=visionTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visionTools.d.ts","sourceRoot":"","sources":["../src/visionTools.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,WAAW,EAEjB,MAAM,aAAa,CAAC;AAWrB,0DAA0D;AAC1D,eAAO,MAAM,aAAa,oCAAqC,CAAC;AAEhE,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,OAAQ,CAAC;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAIvC,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,eAAe,QAAS,CAAC;AAYtC;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,gBAAgB,GAAG,SAAS,CAO9B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQxE;AAED,gDAAgD;AAChD,qBAAa,UAAU;IAEnB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAHZ,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;IAGvB,QAAQ,IAAI,MAAM;CAKnB;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,GAAG,EAAE,MAAM,KACR,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAqBvD,kEAAkE;AAClE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAGlB,YAAY,SAAI;IAI5B,6EAA6E;IAC7E,UAAU,IAAI,MAAM,GAAG,SAAS;IAYhC;;;;;;OAMG;IACH,IAAI,OAAO,IAAI,MAAM,CAGpB;IAED;;;;;;OAMG;IACH,iBAAiB,IAAI,MAAM,GAAG,SAAS;IAMvC,gEAAgE;IAChE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B,2CAA2C;IAC3C,IAAI,KAAK,IAAI,MAAM,CAGlB;CACF;AAED;;;;;;;;;GASG;AACH,qBAAa,aAAa;;gBAMZ,QAAQ,SAAK;IAIzB,mFAAmF;IACnF,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO;IAerD,wCAAwC;IACxC,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,UAAU,EAAE;IAM1C,6CAA6C;IAC7C,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEtD,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,qBAAa,WAAW;;IAGtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;gBAOtB,OAAO,EAAE,WAAW,EAAE,OAAO,GAAE,kBAAuB;IAQlE;;;;;;OAMG;IACH,IAAI,SAAS,IAAI,UAAU,GAAG,SAAS,CAEtC;IAED,qEAAqE;IACrE,IAAI,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAEzC;IAED,2EAA2E;IACrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;;;;;OAMG;IACG,IAAI,CAAC,QAAQ,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmC3D;;;;;OAKG;IACG,QAAQ,CAAC,QAAQ,SAAK,GAAG,OAAO,CAAC,MAAM,CAAC;IAyB9C,4CAA4C;IACtC,IAAI,CACR,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,IAAI,SAAe,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC;IA8BlB;;;;;;OAMG;IACG,OAAO,CACX,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;IAkBlB;;;;;;;;;;;;;OAaG;IACG,QAAQ,CACZ,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,YAAY,EACrB,SAAS,SAAyB,GACjC,OAAO,CAAC,MAAM,CAAC;IAiClB;;;;;;;;;OASG;IACG,QAAQ,CACZ,KAAK,EAAE,SAAS,QAAQ,EAAE,EAC1B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,MAAM,SAAoB,GACzB,OAAO,CAAC,MAAM,CAAC;IAkIlB;;;;;;;;;OASG;IACG,WAAW,CACf,KAAK,EAAE,SAAS,eAAe,EAAE,EACjC,KAAK,EAAE,OAAO,EACd,WAAW,CAAC,EAAE,MAAM,OAAO,EAC3B,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;CA8BnB"}