@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
@@ -0,0 +1,112 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * What arrived before your agent was ready to hear it.
5
+ *
6
+ * A call starts the moment StandIn dials. Your agent starts a little later: a
7
+ * socket has to open, a room has to be joined, a session has to be configured.
8
+ * Everything the caller does in that gap still arrives, and if nothing holds it,
9
+ * it is gone.
10
+ *
11
+ * Two things arrive in that gap and both matter:
12
+ *
13
+ * **The caller's first words.** People start talking the instant the call
14
+ * connects, and often the first thing they say is the reason they called. Drop
15
+ * it and the agent opens by asking a question that was already answered.
16
+ *
17
+ * **The first context.** The "there are three people here, stay quiet unless
18
+ * addressed" sentence and the recording-status change both land within the first
19
+ * moment of a meeting. Drop those and a group-call gate never engages and a
20
+ * recording gate never opens.
21
+ *
22
+ * This exists because every plugin was solving half of it. Of the nine plugins
23
+ * in this SDK, two held both, and five held one and silently lost the other.
24
+ *
25
+ * Bounded on purpose. A socket that never opens must not grow a buffer for the
26
+ * length of the call, so the oldest entries are dropped rather than the newest.
27
+ *
28
+ * Identical in shape to the Python SDK's `standin.startup`.
29
+ */
30
+ /**
31
+ * About four seconds of speech at the wire's frame size. Enough to hold an
32
+ * opening sentence, far short of enough to hide a socket that never opened.
33
+ */
34
+ export const MAX_PENDING_AUDIO = 200;
35
+ /** Context sentences are rare and each one is small. This is generous. */
36
+ export const MAX_PENDING_CONTEXT = 20;
37
+ /**
38
+ * Holds caller audio and call context until the agent can take them.
39
+ *
40
+ * Order is preserved within each lane, and audio is released before context,
41
+ * because the provider needs the caller's words in the order they were said and
42
+ * the context is a note about the call rather than part of the conversation.
43
+ */
44
+ export class StartupBuffer {
45
+ #maxAudio;
46
+ #maxContext;
47
+ #audio = [];
48
+ #context = [];
49
+ #holding = true;
50
+ #droppedAudio = 0;
51
+ #droppedContext = 0;
52
+ constructor(maxAudio = MAX_PENDING_AUDIO, maxContext = MAX_PENDING_CONTEXT) {
53
+ this.#maxAudio = Math.max(1, maxAudio);
54
+ this.#maxContext = Math.max(1, maxContext);
55
+ }
56
+ /** Whether the agent is still being set up. */
57
+ get holding() {
58
+ return this.#holding;
59
+ }
60
+ /**
61
+ * How much was lost to the bounds.
62
+ *
63
+ * Worth logging when it is not zero: it means the agent took long enough to
64
+ * start that the caller outran it.
65
+ */
66
+ get dropped() {
67
+ return { audio: this.#droppedAudio, context: this.#droppedContext };
68
+ }
69
+ /** Hold one frame of the caller's voice. */
70
+ audio(pcm) {
71
+ if (pcm.length === 0)
72
+ return;
73
+ this.#audio.push(pcm);
74
+ if (this.#audio.length > this.#maxAudio) {
75
+ this.#audio.shift();
76
+ this.#droppedAudio += 1;
77
+ }
78
+ }
79
+ /** Hold one line of call context. */
80
+ context(text) {
81
+ if (!text)
82
+ return;
83
+ this.#context.push(text);
84
+ if (this.#context.length > this.#maxContext) {
85
+ this.#context.shift();
86
+ this.#droppedContext += 1;
87
+ }
88
+ }
89
+ /**
90
+ * Hand everything over, in order, and stop holding.
91
+ *
92
+ * Both callbacks may be sync or async, because a provider's send is often
93
+ * fire-and-forget. Safe to call twice: the second call releases nothing.
94
+ */
95
+ async release(sendAudio, sendContext) {
96
+ this.#holding = false;
97
+ const audio = this.#audio.splice(0);
98
+ const context = this.#context.splice(0);
99
+ for (const frame of audio)
100
+ await sendAudio?.(frame);
101
+ for (const line of context)
102
+ await sendContext?.(line);
103
+ return { audio: audio.length, context: context.length };
104
+ }
105
+ /** Throw it away. For a call that ended before the agent was ready. */
106
+ discard() {
107
+ this.#holding = false;
108
+ this.#audio = [];
109
+ this.#context = [];
110
+ }
111
+ }
112
+ //# sourceMappingURL=startup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup.js","sourceRoot":"","sources":["../src/startup.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAErC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IACf,SAAS,CAAS;IAClB,WAAW,CAAS;IAC7B,MAAM,GAAa,EAAE,CAAC;IACtB,QAAQ,GAAa,EAAE,CAAC;IACxB,QAAQ,GAAG,IAAI,CAAC;IAChB,aAAa,GAAG,CAAC,CAAC;IAClB,eAAe,GAAG,CAAC,CAAC;IAEpB,YAAY,QAAQ,GAAG,iBAAiB,EAAE,UAAU,GAAG,mBAAmB;QACxE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IAC7C,CAAC;IAED,+CAA+C;IAC/C,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;;;;OAKG;IACH,IAAI,OAAO;QACT,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC;IACtE,CAAC;IAED,4CAA4C;IAC5C,KAAK,CAAC,GAAW;QACf,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACpB,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,qCAAqC;IACrC,OAAO,CAAC,IAAY;QAClB,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC5C,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CACX,SAAiD,EACjD,WAAoD;QAEpD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACxC,KAAK,MAAM,KAAK,IAAI,KAAK;YAAE,MAAM,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC;QACpD,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,MAAM,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC;QACtD,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;IAC1D,CAAC;IAED,uEAAuE;IACvE,OAAO;QACL,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACrB,CAAC;CACF"}
package/dist/tile.d.ts ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Putting your agent's own face on the bot's video tile.
3
+ *
4
+ * StandIn renders an avatar by default. When your agent already produces video
5
+ * of its own - an avatar worker, a rendered face, a camera - this streams that
6
+ * onto the tile instead, as a continuous run of `display.frame` messages.
7
+ *
8
+ * The awkward part is not sending frames. It is sending them at a rate that does
9
+ * not hurt the call, and that is what {@link TileStream} owns:
10
+ *
11
+ * **Latest wins, and each frame goes at most once.** Frames are offered into a
12
+ * single slot, never a queue. A ticker takes whatever is newest and sends it.
13
+ * That means a source producing faster than the wire drops the middle frames
14
+ * rather than falling behind, and a source that STOPS producing goes quiet
15
+ * rather than repeating one stale frame forever. Silence is how a stream ends.
16
+ *
17
+ * **The timestamp is the audio clock.** `ts` comes from
18
+ * {@link CallSession.mediaTimeMs}, the same timeline the outbound audio is
19
+ * stamped on. A wall clock keeps ticking through listening silence while the
20
+ * audio clock does not, so a wall-clock stamp makes the two streams look like
21
+ * they are drifting apart when they are in step.
22
+ *
23
+ * **Video yields to audio.** The budget here is tighter than the audio one on
24
+ * purpose. Both streams share a socket, and a caller forgives a dropped frame
25
+ * far more readily than a break in the voice.
26
+ *
27
+ * Encoding is yours to supply or ours to find. Pass an `encoder` and the stream
28
+ * uses it; pass nothing and it looks for sharp. If neither is there, the tile
29
+ * relay stays off with one line in the log and **the call is unaffected**.
30
+ *
31
+ * Identical in shape to the Python SDK's `standin.tile`.
32
+ */
33
+ import type { CallSession } from "./handler.js";
34
+ /**
35
+ * The tile size frames are encoded to. Shipping an avatar's native resolution
36
+ * only spends bandwidth on pixels the tile will not show.
37
+ */
38
+ export declare const TILE_WIDTH = 640;
39
+ export declare const TILE_HEIGHT = 360;
40
+ /**
41
+ * A sender-side sanity clamp, not a protocol limit. A talking-head tile gains
42
+ * nothing above this, and a higher rate only spends local CPU on encoding and
43
+ * base64.
44
+ */
45
+ export declare const MAX_TILE_FPS = 20;
46
+ /** What an encoder is: packed RGB in, JPEG bytes out. */
47
+ export type Encoder = (rgb: Buffer, width: number, height: number) => Promise<Buffer>;
48
+ /**
49
+ * Find an encoder, or return undefined having said why.
50
+ *
51
+ * sharp is an optional peer precisely because most deployments never put their
52
+ * own video on the tile. A missing encoder is a tile relay that does not run,
53
+ * which is a smaller problem than a dependency every install pays for.
54
+ */
55
+ export declare function jpegEncoder(): Promise<Encoder | undefined>;
56
+ /** Options for {@link TileStream}. */
57
+ export interface TileStreamOptions {
58
+ /** Frames per second, clamped to {@link MAX_TILE_FPS}. */
59
+ fps?: number;
60
+ /** Supply your own, or leave it and sharp is looked for. */
61
+ encoder?: Encoder;
62
+ /** Outbound bytes past which a frame is dropped rather than sent. */
63
+ maxBufferedBytes?: number;
64
+ }
65
+ /**
66
+ * A paced run of `display.frame` messages onto the bot's video tile.
67
+ *
68
+ * Built by a plugin that has video, driven by whatever produces it:
69
+ *
70
+ * ```ts
71
+ * const tile = new TileStream(session, { fps: 12 });
72
+ * await tile.start();
73
+ * tile.offerRgb(rgb, width, height); // as often as you like
74
+ * await tile.aclose();
75
+ * ```
76
+ *
77
+ * {@link offerRgb} and {@link offerJpeg} never block and never throw. They are
78
+ * meant to be called from a drain loop that must not be slowed down by the wire.
79
+ */
80
+ export declare class TileStream {
81
+ #private;
82
+ constructor(session: CallSession, options?: TileStreamOptions);
83
+ /** How many frames have reached the tile. */
84
+ get framesSent(): number;
85
+ /** How many were dropped for backpressure. A healthy call has some. */
86
+ get framesDropped(): number;
87
+ /** Offer packed RGB. Replaces whatever was waiting. */
88
+ offerRgb(rgb: Buffer, width: number, height: number): void;
89
+ /**
90
+ * Offer an already-encoded frame, skipping the encoder entirely.
91
+ *
92
+ * For a source that hands you JPEG already. Nothing is re-encoded, and no
93
+ * encoder needs to be installed.
94
+ */
95
+ offerJpeg(jpeg: Buffer, width?: number, height?: number): void;
96
+ /** Begin sending. Returns once armed; the pacing runs in the background. */
97
+ start(): Promise<void>;
98
+ /** Stop sending. Safe to call twice, and on every teardown path. */
99
+ aclose(): Promise<void>;
100
+ }
101
+ //# sourceMappingURL=tile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tile.d.ts","sourceRoot":"","sources":["../src/tile.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD;;;GAGG;AACH,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,WAAW,MAAM,CAAC;AAQ/B;;;;GAIG;AACH,eAAO,MAAM,YAAY,KAAK,CAAC;AAK/B,yDAAyD;AACzD,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB;;;;;;GAMG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CA6BhE;AAED,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,UAAU;;gBAiBT,OAAO,EAAE,WAAW,EAAE,OAAO,GAAE,iBAAsB;IAWjE,6CAA6C;IAC7C,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,uEAAuE;IACvE,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,uDAAuD;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAM1D;;;;;OAKG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAa,EAAE,MAAM,SAAc,GAAG,IAAI;IAMvE,4EAA4E;IACtE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,oEAAoE;IAC9D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAoE9B"}
package/dist/tile.js ADDED
@@ -0,0 +1,195 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ import { logger } from "./log.js";
4
+ /**
5
+ * The tile size frames are encoded to. Shipping an avatar's native resolution
6
+ * only spends bandwidth on pixels the tile will not show.
7
+ */
8
+ export const TILE_WIDTH = 640;
9
+ export const TILE_HEIGHT = 360;
10
+ /**
11
+ * Encoder quality. Chosen where a talking head still looks right and the frame
12
+ * still fits comfortably inside the wire envelope.
13
+ */
14
+ const JPEG_QUALITY = 58;
15
+ /**
16
+ * A sender-side sanity clamp, not a protocol limit. A talking-head tile gains
17
+ * nothing above this, and a higher rate only spends local CPU on encoding and
18
+ * base64.
19
+ */
20
+ export const MAX_TILE_FPS = 20;
21
+ /** Tighter than the audio buffer cap, deliberately. See the module comment. */
22
+ const VIDEO_BACKPRESSURE_BYTES = 320 * 1024;
23
+ /**
24
+ * Find an encoder, or return undefined having said why.
25
+ *
26
+ * sharp is an optional peer precisely because most deployments never put their
27
+ * own video on the tile. A missing encoder is a tile relay that does not run,
28
+ * which is a smaller problem than a dependency every install pays for.
29
+ */
30
+ export async function jpegEncoder() {
31
+ try {
32
+ // A variable specifier, so the compiler does not require the optional
33
+ // module to be present; it is resolved only at runtime when enabled.
34
+ const specifier = "sharp";
35
+ const mod = (await import(specifier)).default;
36
+ return async (rgb, width, height) => mod(rgb, { raw: { width, height, channels: 3 } })
37
+ .resize(TILE_WIDTH, TILE_HEIGHT, { fit: "fill" })
38
+ .jpeg({ quality: JPEG_QUALITY })
39
+ .toBuffer();
40
+ }
41
+ catch {
42
+ logger.warn("standin: the avatar tile relay needs sharp to encode frames (npm install sharp); " +
43
+ "the relay is off and audio is unaffected");
44
+ return undefined;
45
+ }
46
+ }
47
+ /**
48
+ * A paced run of `display.frame` messages onto the bot's video tile.
49
+ *
50
+ * Built by a plugin that has video, driven by whatever produces it:
51
+ *
52
+ * ```ts
53
+ * const tile = new TileStream(session, { fps: 12 });
54
+ * await tile.start();
55
+ * tile.offerRgb(rgb, width, height); // as often as you like
56
+ * await tile.aclose();
57
+ * ```
58
+ *
59
+ * {@link offerRgb} and {@link offerJpeg} never block and never throw. They are
60
+ * meant to be called from a drain loop that must not be slowed down by the wire.
61
+ */
62
+ export class TileStream {
63
+ #session;
64
+ #fps;
65
+ #periodMs;
66
+ #maxBuffered;
67
+ #encoder;
68
+ #resolvedEncoder = false;
69
+ /** The single newest frame awaiting a send. Never a queue. */
70
+ #latest;
71
+ #ticker;
72
+ #encoding = false;
73
+ #closed = false;
74
+ #sent = 0;
75
+ #dropped = 0;
76
+ #lastDropLog = 0;
77
+ constructor(session, options = {}) {
78
+ this.#session = session;
79
+ this.#fps = Math.max(1, Math.min(Math.trunc(options.fps ?? 12), MAX_TILE_FPS));
80
+ this.#periodMs = Math.max(1, Math.round(1000 / this.#fps));
81
+ this.#encoder = options.encoder;
82
+ this.#maxBuffered = options.maxBufferedBytes ?? VIDEO_BACKPRESSURE_BYTES;
83
+ }
84
+ /** How many frames have reached the tile. */
85
+ get framesSent() {
86
+ return this.#sent;
87
+ }
88
+ /** How many were dropped for backpressure. A healthy call has some. */
89
+ get framesDropped() {
90
+ return this.#dropped;
91
+ }
92
+ /** Offer packed RGB. Replaces whatever was waiting. */
93
+ offerRgb(rgb, width, height) {
94
+ if (!this.#closed && rgb.length > 0) {
95
+ this.#latest = { data: rgb, width, height, jpeg: false };
96
+ }
97
+ }
98
+ /**
99
+ * Offer an already-encoded frame, skipping the encoder entirely.
100
+ *
101
+ * For a source that hands you JPEG already. Nothing is re-encoded, and no
102
+ * encoder needs to be installed.
103
+ */
104
+ offerJpeg(jpeg, width = TILE_WIDTH, height = TILE_HEIGHT) {
105
+ if (!this.#closed && jpeg.length > 0) {
106
+ this.#latest = { data: jpeg, width, height, jpeg: true };
107
+ }
108
+ }
109
+ /** Begin sending. Returns once armed; the pacing runs in the background. */
110
+ async start() {
111
+ if (this.#ticker !== undefined || this.#closed)
112
+ return;
113
+ if (this.#encoder === undefined && !this.#resolvedEncoder) {
114
+ this.#resolvedEncoder = true;
115
+ this.#encoder = await jpegEncoder();
116
+ }
117
+ this.#ticker = setInterval(() => void this.#tick(), this.#periodMs);
118
+ this.#ticker.unref?.();
119
+ logger.info(`standin: avatar tile relay armed at ${this.#fps} fps, ${TILE_WIDTH}x${TILE_HEIGHT}`);
120
+ }
121
+ /** Stop sending. Safe to call twice, and on every teardown path. */
122
+ async aclose() {
123
+ this.#closed = true;
124
+ this.#latest = undefined;
125
+ if (this.#ticker !== undefined) {
126
+ clearInterval(this.#ticker);
127
+ this.#ticker = undefined;
128
+ }
129
+ }
130
+ async #tick() {
131
+ if (this.#closed || this.#latest === undefined || this.#encoding)
132
+ return;
133
+ const frame = this.#latest;
134
+ // Consume the slot. Each offered frame is sent at most once, so a source
135
+ // that goes quiet leaves a silent wire rather than a frozen repeat.
136
+ this.#latest = undefined;
137
+ if (this.#overBudget())
138
+ return;
139
+ let data = frame.data;
140
+ let { width, height } = frame;
141
+ if (!frame.jpeg) {
142
+ const encoder = this.#encoder;
143
+ if (encoder === undefined)
144
+ return;
145
+ this.#encoding = true;
146
+ try {
147
+ data = await encoder(frame.data, frame.width, frame.height);
148
+ width = TILE_WIDTH;
149
+ height = TILE_HEIGHT;
150
+ }
151
+ catch (err) {
152
+ logger.warn(`standin: encoding an avatar frame failed: ${String(err)}`);
153
+ return;
154
+ }
155
+ finally {
156
+ this.#encoding = false;
157
+ }
158
+ if (this.#closed)
159
+ return;
160
+ // Re-check after the encode yielded: audio may have filled the socket
161
+ // while we were off the loop, and a video frame must not be what starves
162
+ // the voice.
163
+ if (this.#overBudget())
164
+ return;
165
+ }
166
+ this.#sent += 1;
167
+ try {
168
+ // The session owns the sequence and the timestamp, the same way it owns
169
+ // them for audio, so the two streams share one clock.
170
+ await this.#session.sendTileFrame(data, width, height);
171
+ }
172
+ catch {
173
+ // A dying socket is the close handler's business, not the ticker's.
174
+ }
175
+ }
176
+ #overBudget() {
177
+ let buffered = 0;
178
+ try {
179
+ buffered = this.#session.bufferedBytes;
180
+ }
181
+ catch {
182
+ buffered = 0;
183
+ }
184
+ if (buffered <= this.#maxBuffered)
185
+ return false;
186
+ this.#dropped += 1;
187
+ const now = Date.now();
188
+ if (now - this.#lastDropLog >= 5000) {
189
+ logger.info(`standin: avatar tile is dropping frames to protect the audio (${this.#dropped} so far)`);
190
+ this.#lastDropLog = now;
191
+ }
192
+ return true;
193
+ }
194
+ }
195
+ //# sourceMappingURL=tile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tile.js","sourceRoot":"","sources":["../src/tile.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAoC/B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,CAAC;AAC9B,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAE/B;;;GAGG;AACH,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAE/B,+EAA+E;AAC/E,MAAM,wBAAwB,GAAG,GAAG,GAAG,IAAI,CAAC;AAS5C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,CAAC;QACH,sEAAsE;QACtE,qEAAqE;QACrE,MAAM,SAAS,GAAG,OAAO,CAAC;QAC1B,MAAM,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,OAWrC,CAAC;QACF,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAClC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;aAC9C,MAAM,CAAC,UAAU,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;aAChD,IAAI,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;aAC/B,QAAQ,EAAE,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CACT,mFAAmF;YACjF,0CAA0C,CAC7C,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAYD;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,UAAU;IACZ,QAAQ,CAAc;IACtB,IAAI,CAAS;IACb,SAAS,CAAS;IAClB,YAAY,CAAS;IAC9B,QAAQ,CAAsB;IAC9B,gBAAgB,GAAG,KAAK,CAAC;IACzB,8DAA8D;IAC9D,OAAO,CACsE;IAC7E,OAAO,CAA6B;IACpC,SAAS,GAAG,KAAK,CAAC;IAClB,OAAO,GAAG,KAAK,CAAC;IAChB,KAAK,GAAG,CAAC,CAAC;IACV,QAAQ,GAAG,CAAC,CAAC;IACb,YAAY,GAAG,CAAC,CAAC;IAEjB,YAAY,OAAoB,EAAE,UAA6B,EAAE;QAC/D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAClB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,CACtD,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,gBAAgB,IAAI,wBAAwB,CAAC;IAC3E,CAAC;IAED,6CAA6C;IAC7C,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,uEAAuE;IACvE,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,uDAAuD;IACvD,QAAQ,CAAC,GAAW,EAAE,KAAa,EAAE,MAAc;QACjD,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAC3D,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,SAAS,CAAC,IAAY,EAAE,KAAK,GAAG,UAAU,EAAE,MAAM,GAAG,WAAW;QAC9D,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACvD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1D,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;YAC7B,IAAI,CAAC,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACpE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CACT,uCAAuC,IAAI,CAAC,IAAI,SAAS,UAAU,IAAI,WAAW,EAAE,CACrF,CAAC;IACJ,CAAC;IAED,oEAAoE;IACpE,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAC/B,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC5B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QACzE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC;QAC3B,yEAAyE;QACzE,oEAAoE;QACpE,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO;QAE/B,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACtB,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC9B,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO;YAClC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC5D,KAAK,GAAG,UAAU,CAAC;gBACnB,MAAM,GAAG,WAAW,CAAC;YACvB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,6CAA6C,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACxE,OAAO;YACT,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACzB,CAAC;YACD,IAAI,IAAI,CAAC,OAAO;gBAAE,OAAO;YACzB,sEAAsE;YACtE,yEAAyE;YACzE,aAAa;YACb,IAAI,IAAI,CAAC,WAAW,EAAE;gBAAE,OAAO;QACjC,CAAC;QAED,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC;YACH,wEAAwE;YACxE,sDAAsD;YACtD,MAAM,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;QACtE,CAAC;IACH,CAAC;IAED,WAAW;QACT,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,CAAC,CAAC;QACf,CAAC;QACD,IAAI,QAAQ,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,KAAK,CAAC;QAChD,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CACT,iEAAiE,IAAI,CAAC,QAAQ,UAAU,CACzF,CAAC;YACF,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC;QAC1B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export declare const VERSION = "0.1.0";
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,UAAU,CAAC"}
@@ -0,0 +1,4 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ export const VERSION = "0.1.0";
4
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
@@ -0,0 +1,160 @@
1
+ /** The two things a caller can show: their camera, or their screen share. */
2
+ export declare const VIDEO_SOURCES: readonly ["camera", "screenshare"];
3
+ /** Which lane a frame came from. */
4
+ export type VideoSource = (typeof VIDEO_SOURCES)[number];
5
+ /** What StandIn will draw on the bot tile. JPEG or PNG, nothing else. */
6
+ export declare const DISPLAY_IMAGE_MIME_TYPES: readonly ["image/jpeg", "image/png"];
7
+ /** A MIME type StandIn will draw. */
8
+ export type DisplayImageMime = (typeof DISPLAY_IMAGE_MIME_TYPES)[number];
9
+ /** `"fullscreen"` replaces the tile; `"overlay"` draws a picture-in-picture inset. */
10
+ export type DisplayImageMode = "fullscreen" | "overlay";
11
+ /**
12
+ * One wire message is bounded at 2 MB by both SDKs, and base64 costs a third on
13
+ * top of the raw bytes. Refusing an oversized image here names the real problem,
14
+ * rather than letting the service close the socket mid-call.
15
+ */
16
+ export declare const MAX_IMAGE_BYTES = 1400000;
17
+ /**
18
+ * One sampled frame of what the caller is showing.
19
+ *
20
+ * `participantId` and `participantName` are best-effort and absent for guest and
21
+ * anonymous participants, so a group-call prompt that says who is sharing must
22
+ * tolerate not knowing.
23
+ */
24
+ export interface VideoFrame {
25
+ /** Which lane this came from. */
26
+ readonly source: VideoSource;
27
+ /** Capture time in milliseconds. */
28
+ readonly ts: number;
29
+ /** Pixel width, already downscaled by StandIn before sending. */
30
+ readonly width: number;
31
+ /** Pixel height. */
32
+ readonly height: number;
33
+ /** Image MIME type. StandIn sends `image/jpeg`. */
34
+ readonly mime: string;
35
+ /**
36
+ * The image, base64-encoded, exactly as it arrived.
37
+ *
38
+ * Kept in this form because it is the form most providers want back: a `data:`
39
+ * URL for a vision model costs one template string from here, while
40
+ * {@link VideoFrame.data} costs a decode.
41
+ */
42
+ readonly dataBase64: string;
43
+ /** Whose frame this is, when StandIn could tell. */
44
+ readonly participantId?: string;
45
+ /** Display name matching {@link VideoFrame.participantId}. */
46
+ readonly participantName?: string;
47
+ /** The decoded image bytes, for an API that uploads a file. */
48
+ readonly data: Buffer;
49
+ /** The frame as a `data:` URL, which is what most vision models take. */
50
+ readonly dataUrl: string;
51
+ }
52
+ /**
53
+ * Read a `video.frame`, or return `undefined` if it is unusable.
54
+ *
55
+ * Never throws. A frame that fails any check is a frame to drop: the call is
56
+ * healthy, the caller is still talking, and one malformed image is not worth
57
+ * ending a conversation over. That is the same leniency the rest of the wire
58
+ * contract is built on, where a receiver ignores what it cannot use.
59
+ */
60
+ export declare function parseVideoFrame(msg: Record<string, unknown>): VideoFrame | undefined;
61
+ /** Options for {@link displayImage}. */
62
+ export interface DisplayImageOptions {
63
+ /** `image/jpeg` (the default) or `image/png`. */
64
+ mime?: string;
65
+ /** How long to show it. StandIn applies its own default when omitted. */
66
+ durationMs?: number;
67
+ /** `fullscreen` (StandIn's default) or `overlay`. */
68
+ mode?: DisplayImageMode;
69
+ /** Short label drawn along the bottom of the image. */
70
+ caption?: string;
71
+ }
72
+ /**
73
+ * Build a `display.image`: show the caller a still, then return to the avatar.
74
+ *
75
+ * `image` is a Buffer or an already-base64 string.
76
+ */
77
+ export declare function displayImage(image: Buffer | string, options?: DisplayImageOptions): string;
78
+ /** Options for {@link displayFrame}. */
79
+ export interface DisplayFrameOptions {
80
+ /** Frame encoding; senders send `image/jpeg`. */
81
+ mime?: string;
82
+ /** Source pixel width (informational). */
83
+ width?: number;
84
+ /** Source pixel height (informational). */
85
+ height?: number;
86
+ }
87
+ /**
88
+ * Build a `display.frame`: one frame of continuous avatar video.
89
+ *
90
+ * Latest wins. There is no handshake, the first frames start the stream and
91
+ * silence ends it, and a sender under backpressure MUST drop frames rather than
92
+ * buffer them, exactly as it does for hot-path audio.
93
+ *
94
+ * `ts` belongs to the sender's own media timeline, the same one its outbound
95
+ * audio is stamped on, so the two streams share a clock.
96
+ */
97
+ export declare function displayFrame(seq: number, ts: number, image: Buffer | string, options?: DisplayFrameOptions): string;
98
+ /** How to reach the vision model. */
99
+ export interface FrameDescriberOptions {
100
+ /** Chat-completions endpoint, for example `https://api.openai.com/v1/chat/completions`. */
101
+ url: string;
102
+ /** The vision model to ask. */
103
+ model: string;
104
+ /** Sent as a bearer token when set. A local endpoint usually needs none. */
105
+ apiKey?: string;
106
+ }
107
+ /**
108
+ * Turn a frame into a sentence, using a vision model you choose.
109
+ *
110
+ * Most speech-to-speech providers hear but cannot see. This is what lets one
111
+ * answer "what is on my screen?": the frame goes to any OpenAI-compatible
112
+ * chat-completions endpoint that accepts image input (OpenAI, Azure OpenAI,
113
+ * Ollama, vLLM, whatever you run), and what comes back is text the agent can say
114
+ * out loud.
115
+ *
116
+ * The frame is sent for inference and not stored, which is the difference
117
+ * between this and uploading it into a provider's own conversation history.
118
+ *
119
+ * Deliberately NOT put through the guard in `fetch.ts`: this URL is yours, set
120
+ * by you in the environment, and a vision model on localhost is a normal way to
121
+ * run one. That is the opposite of an image URL a model chose.
122
+ */
123
+ /**
124
+ * A short, stable fingerprint of one frame.
125
+ *
126
+ * For asking "is this the same screen as last time?" without keeping the
127
+ * picture. A hash of the encoded form is enough: two encodes of an unchanged
128
+ * screen are byte-identical.
129
+ */
130
+ export declare function frameDigest(dataBase64: string): string;
131
+ /** Who is showing this, when the wire said. */
132
+ export declare function frameOwner(frame: VideoFrame): string | undefined;
133
+ /**
134
+ * Who to say it is when nobody was named.
135
+ *
136
+ * Attribution that degrades rather than vanishing: "a participant's screen" is
137
+ * worth more to a model than an unlabelled picture.
138
+ */
139
+ export declare function fallbackOwner(source: string): string;
140
+ /** The sentence that goes beside a frame, so a model knows whose it is. */
141
+ export declare function frameCaption(owner: string): string;
142
+ export declare class FrameDescriber {
143
+ readonly url: string;
144
+ readonly model: string;
145
+ readonly apiKey: string | undefined;
146
+ constructor(options: FrameDescriberOptions);
147
+ /**
148
+ * Build one from `STANDIN_VISION_API_URL` and `STANDIN_VISION_MODEL`.
149
+ *
150
+ * Returns `undefined` when they are not set, which is the signal a
151
+ * plugin uses to tell an agent that looking is not available here.
152
+ */
153
+ static fromEnv(): FrameDescriber | undefined;
154
+ /**
155
+ * Ask the model about one frame. Throws on anything that goes wrong, so a
156
+ * caller can hand the reason back to the agent that asked.
157
+ */
158
+ describe(frame: VideoFrame, question: string): Promise<string>;
159
+ }
160
+ //# sourceMappingURL=vision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision.d.ts","sourceRoot":"","sources":["../src/vision.ts"],"names":[],"mappings":"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"}