@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,238 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * One Microsoft Teams call, bridged to OpenClaw's realtime speech-to-speech session.
5
+ *
6
+ * This file is the reason the plugin cannot be a standalone worker. It does not
7
+ * drive a model: it CONSUMES the host's realtime session
8
+ * (`createRealtimeVoiceBridgeSession`), the host's provider registry and the
9
+ * host's config, all of which are in-process objects inside the OpenClaw gateway.
10
+ * Nothing here opens a provider socket, and nothing here knows which vendor is on
11
+ * the other end.
12
+ *
13
+ * What it does own is the audio boundary between two rates and two framings:
14
+ *
15
+ * caller 16 kHz --> 24 kHz --> host realtime session
16
+ * model 24 kHz --> 16 kHz --> session.sendAudio
17
+ *
18
+ * and the three things that make a spoken call feel right rather than merely
19
+ * work: the echo guard, the playout clock the guard keys on, and barge-in.
20
+ *
21
+ * Everything the SDK owns is deliberately absent here: the socket, the sequence
22
+ * number, the outbound timeline, the frame loop and the watchdogs.
23
+ */
24
+ import { FrameAligner, REALTIME_SAMPLE_RATE_HZ, SAMPLE_RATE_HZ, frameDurationMs, resamplePcm16, } from "../../index.js";
25
+ import { REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, createRealtimeVoiceBridgeSession, } from "openclaw/plugin-sdk/realtime-voice";
26
+ import { shouldSuppressEcho } from "../../echoGuard.js";
27
+ import { isVerbalInterrupt } from "../../gate.js";
28
+ /**
29
+ * Build the bridge for one call.
30
+ *
31
+ * `session` is the SDK's {@link CallSession}: the only route to the caller, and
32
+ * the owner of the sequence number and the outbound timeline, so nothing here
33
+ * tracks either.
34
+ */
35
+ export function createRealtimeCall(params) {
36
+ const { session, deps } = params;
37
+ const { logger } = deps;
38
+ const callId = session.callId;
39
+ let closed = false;
40
+ /**
41
+ * Estimated epoch ms at which the audio already sent finishes PLAYING.
42
+ *
43
+ * The model generates faster than realtime and the service queues what we send,
44
+ * so send-time is not play-time. Summing each chunk's own duration tracks the
45
+ * playout clock instead, which is what the echo guard has to be keyed on: a
46
+ * guard armed off last-send disarms in the middle of the sentence it exists to
47
+ * cover.
48
+ */
49
+ let playbackEndAt = 0;
50
+ /**
51
+ * False until the caller has genuinely spoken once. See
52
+ * {@link EchoGuardOptions.allowBargeIn}: the opening greeting echoing off a
53
+ * speakerphone will clear the barge-in RMS, so the loudness exception has to
54
+ * stay off until there is a real caller turn to measure against.
55
+ */
56
+ let callerTurnStarted = false;
57
+ /**
58
+ * Microsoft Teams recording status, seeded from `session.start` and updated by context.
59
+ * Gates every media-derived path (audio in, DTMF, transcripts) when
60
+ * `requireRecordingStatus` is on.
61
+ */
62
+ let recordingActive = session.start.recordingStatus === "active";
63
+ const recordingGateBlocks = () => deps.requireRecordingStatus && !recordingActive;
64
+ /**
65
+ * Whole-frame alignment for the downlink.
66
+ *
67
+ * A 24 kHz delta resampled to 16 kHz does not divide evenly into the wire's
68
+ * 640-byte frame, and dropping the remainder clips a few milliseconds off every
69
+ * chunk seam - audible over a call as clipped word endings. The SDK owns this;
70
+ * do not hand-roll it.
71
+ */
72
+ const aligner = new FrameAligner();
73
+ /**
74
+ * The host's audio sink is SYNCHRONOUS and `session.sendAudio` is not, so the
75
+ * sends are chained rather than awaited. A chain, not fire-and-forget: two
76
+ * unordered sends would put the model's voice out of order on the wire, which
77
+ * is worse than late audio. The catch is inside the chain so one failed send
78
+ * cannot poison every send after it.
79
+ */
80
+ let sendChain = Promise.resolve();
81
+ const enqueue = (op) => {
82
+ sendChain = sendChain.then(op).catch((err) => {
83
+ logger?.debug?.(`standin-msteams: send failed on ${callId} - ${err instanceof Error ? err.message : String(err)}`);
84
+ });
85
+ };
86
+ /** Flush the held-back tail of a finished turn, zero-padded to a whole frame. */
87
+ const flushTail = () => {
88
+ const tail = aligner.flush();
89
+ if (tail)
90
+ enqueue(() => session.sendAudio(tail));
91
+ };
92
+ /**
93
+ * Barge-in. THE ORDER HERE IS THE WHOLE POINT.
94
+ *
95
+ * `cancelPlayback` is the only lever that un-sends audio the service has already
96
+ * buffered, so it goes FIRST: cancel the model first and the caller still hears
97
+ * the rest of the interrupted turn play out, which is exactly the "it kept
98
+ * talking over me" complaint barge-in exists to fix. The aligner's residual
99
+ * belongs to that same dead turn, so it is dropped rather than flushed.
100
+ */
101
+ const flushPlayback = () => {
102
+ aligner.reset();
103
+ enqueue(() => session.cancelPlayback());
104
+ // The flush stops playout immediately, so the playout estimate collapses to now.
105
+ playbackEndAt = Date.now();
106
+ };
107
+ const realtime = createRealtimeVoiceBridgeSession({
108
+ provider: deps.provider,
109
+ providerConfig: deps.providerConfig,
110
+ cfg: deps.cfg,
111
+ audioFormat: REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ,
112
+ instructions: deps.instructions,
113
+ initialGreetingInstructions: deps.greetingInstructions,
114
+ triggerGreetingOnReady: Boolean(deps.greetingInstructions),
115
+ autoRespondToAudio: true,
116
+ interruptResponseOnInputAudio: true,
117
+ audioSink: {
118
+ isOpen: () => !closed,
119
+ sendAudio: (pcm24k) => {
120
+ if (closed || pcm24k.length === 0) {
121
+ return;
122
+ }
123
+ const pcm16k = resamplePcm16(pcm24k, REALTIME_SAMPLE_RATE_HZ, SAMPLE_RATE_HZ);
124
+ playbackEndAt =
125
+ Math.max(playbackEndAt, Date.now()) + frameDurationMs(pcm16k);
126
+ for (const frame of aligner.push(pcm16k)) {
127
+ enqueue(() => session.sendAudio(frame));
128
+ }
129
+ },
130
+ // CANCEL SITE 1: the model truncated its own turn because it heard the
131
+ // caller start speaking. Upstream is already cancelled - the model is the
132
+ // one telling us - so the only thing left to undo is the audio the service
133
+ // still has queued.
134
+ clearAudio: () => {
135
+ flushPlayback();
136
+ },
137
+ },
138
+ onTranscript: (role, text, isFinal) => {
139
+ // The caller's first real speech ends the opening echo-only window and
140
+ // restores normal RMS barge-in for the rest of the call.
141
+ if (role === "user" && text.trim().length > 0) {
142
+ callerTurnStarted = true;
143
+ }
144
+ if (role === "assistant" && isFinal) {
145
+ // End of turn: the residual is real audio, not a fragment to discard.
146
+ flushTail();
147
+ }
148
+ if (role !== "user" || !isFinal) {
149
+ return;
150
+ }
151
+ // CANCEL SITE 2: a deterministic verbal interrupt ("stop", "hold on").
152
+ // Handled here rather than left to the model because the model is
153
+ // mid-generation when it arrives; matching the phrase ourselves is what
154
+ // makes the cut feel instant. Only while we are actually still speaking -
155
+ // otherwise "stop" is just a word in a sentence.
156
+ if (Date.now() < playbackEndAt && isVerbalInterrupt(text)) {
157
+ logger?.debug?.(`standin-msteams: verbal interrupt on ${callId} - flushing playback`);
158
+ flushPlayback();
159
+ }
160
+ },
161
+ onError: (error) => {
162
+ logger?.warn?.(`standin-msteams: realtime session error on ${callId} - ${error.message}`);
163
+ },
164
+ });
165
+ return {
166
+ connect: () => realtime.connect(),
167
+ pushAudio: (pcm16k) => {
168
+ if (closed || pcm16k.length === 0) {
169
+ return;
170
+ }
171
+ // Recording gate: caller audio is call media, and the Microsoft Media Access
172
+ // API says a bot does not process it before recording status is active. A
173
+ // no-op when requireRecordingStatus is off.
174
+ if (recordingGateBlocks()) {
175
+ return;
176
+ }
177
+ if (shouldSuppressEcho(pcm16k, playbackEndAt, {
178
+ ...deps.echo,
179
+ allowBargeIn: callerTurnStarted,
180
+ })) {
181
+ return;
182
+ }
183
+ realtime.sendAudio(resamplePcm16(pcm16k, SAMPLE_RATE_HZ, REALTIME_SAMPLE_RATE_HZ));
184
+ },
185
+ pushContext: (text) => {
186
+ if (closed || !text.trim()) {
187
+ return;
188
+ }
189
+ // DTMF and participant counts are media-derived too, so they sit behind the
190
+ // same gate as audio. Recording-status context itself is handled by the
191
+ // handler before it reaches here - that one must never be gated on itself.
192
+ if (recordingGateBlocks()) {
193
+ return;
194
+ }
195
+ realtime.sendUserMessage(text);
196
+ },
197
+ setRecordingActive: (active) => {
198
+ recordingActive = active;
199
+ },
200
+ interrupt: () => {
201
+ if (closed)
202
+ return;
203
+ try {
204
+ realtime.handleBargeIn();
205
+ }
206
+ catch (err) {
207
+ // Non-fatal: handleBargeIn throws when nothing is speaking, which is a
208
+ // perfectly ordinary state to interrupt from.
209
+ logger?.debug?.(`standin-msteams: barge-in on ${callId} - ${err instanceof Error ? err.message : String(err)}`);
210
+ }
211
+ },
212
+ say: (text) => {
213
+ if (closed || !text.trim())
214
+ return;
215
+ try {
216
+ // Same path as the greeting: inject the line as an instruction and trigger
217
+ // a spoken response. There is no separate "speak this" surface.
218
+ realtime.triggerGreeting(text);
219
+ }
220
+ catch (err) {
221
+ logger?.warn?.(`standin-msteams: say failed on ${callId} - ${err instanceof Error ? err.message : String(err)}`);
222
+ }
223
+ },
224
+ close: () => {
225
+ if (closed)
226
+ return;
227
+ closed = true;
228
+ aligner.reset();
229
+ try {
230
+ realtime.close();
231
+ }
232
+ catch (err) {
233
+ logger?.debug?.(`standin-msteams: realtime close on ${callId} - ${err instanceof Error ? err.message : String(err)}`);
234
+ }
235
+ },
236
+ };
237
+ }
238
+ //# sourceMappingURL=realtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/plugins/openclaw/realtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ import type { ResolvedPluginConfig } from "./config.js";
3
+ /** The service OpenClaw starts and stops. One per gateway process. */
4
+ export declare class StandInCallRuntime {
5
+ #private;
6
+ constructor(api: OpenClawPluginApi, config: ResolvedPluginConfig);
7
+ /** Live calls right now. Useful to a health check, and to a test. */
8
+ get activeCalls(): number;
9
+ start(): Promise<void>;
10
+ /**
11
+ * Close live calls first, then the listener.
12
+ *
13
+ * That order is not cosmetic: the SDK's teardown frees a call's slot only once
14
+ * its close settles, so tearing the listener down over live calls is how a
15
+ * shutdown ends up waiting on sockets nobody is reading any more.
16
+ */
17
+ stop(): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/plugins/openclaw/runtime.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAkDxD,sEAAsE;AACtE,qBAAa,kBAAkB;;gBAOjB,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,oBAAoB;IAMhE,qEAAqE;IACrE,IAAI,WAAW,IAAI,MAAM,CAExB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA6C5B;;;;;;OAMG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAmC5B"}
@@ -0,0 +1,160 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * The plugin's background service: one call listener, shared by every call.
5
+ *
6
+ * OpenClaw starts this on boot and stops it on shutdown or reload, so it is the
7
+ * teardown hook. It is short because the WebSocket server, the handshake, the
8
+ * replay guard, the frame loop and the watchdogs all belong to the SDK's
9
+ * `CallServer`. What is left is the two things the SDK cannot know: which
10
+ * realtime provider the host resolved, and how many calls this operator agreed
11
+ * to take at once.
12
+ *
13
+ * Two frictions with the SDK's defaults are handled here on purpose, both by
14
+ * passing the value explicitly rather than letting the default win:
15
+ *
16
+ * - `CallServer` reads `STANDIN_SECRET` from the environment. The secret arrives
17
+ * through `api.pluginConfig`, having been resolved by the host from a secret
18
+ * reference, so it is passed in. An env var would silently override a portal
19
+ * value the operator can actually see.
20
+ * - `CallServer` binds 0.0.0.0, which suits a worker behind an ingress but not a
21
+ * gateway reached through a local tunnel. Neither is wrong, so `bindAddress` is
22
+ * passed when the operator set it and the SDK's default stands when they did not.
23
+ */
24
+ import { CallServer } from "../../index.js";
25
+ import { resolveConfiguredRealtimeVoiceProvider } from "openclaw/plugin-sdk/realtime-voice";
26
+ import { TeamsCallHandler, } from "./handler.js";
27
+ /**
28
+ * Live calls, and the operator's concurrency cap.
29
+ *
30
+ * A slot is reserved BEFORE the model session is built, so two calls arriving
31
+ * together cannot both pass the check and then both open a provider socket. A
32
+ * reserved-but-unbound entry is a call still in `onStart`.
33
+ */
34
+ class LiveCalls {
35
+ #max;
36
+ #calls = new Map();
37
+ constructor(max) {
38
+ this.#max = max;
39
+ }
40
+ get size() {
41
+ return this.#calls.size;
42
+ }
43
+ acquire(callId) {
44
+ if (this.#calls.size >= this.#max)
45
+ return false;
46
+ this.#calls.set(callId, undefined);
47
+ return true;
48
+ }
49
+ bind(callId, call) {
50
+ // Only if the slot is still ours: a teardown that raced onStart has already
51
+ // released it, and re-adding here would leak the slot for the worker's life.
52
+ if (this.#calls.has(callId))
53
+ this.#calls.set(callId, call);
54
+ }
55
+ release(callId) {
56
+ this.#calls.delete(callId);
57
+ }
58
+ /** Close every live call. Used by shutdown, before the listener goes away. */
59
+ closeAll() {
60
+ for (const call of this.#calls.values())
61
+ call?.close();
62
+ this.#calls.clear();
63
+ }
64
+ }
65
+ /** The service OpenClaw starts and stops. One per gateway process. */
66
+ export class StandInCallRuntime {
67
+ #api;
68
+ #config;
69
+ #calls;
70
+ #server;
71
+ #realtime;
72
+ constructor(api, config) {
73
+ this.#api = api;
74
+ this.#config = config;
75
+ this.#calls = new LiveCalls(config.media.maxConcurrentCalls);
76
+ }
77
+ /** Live calls right now. Useful to a health check, and to a test. */
78
+ get activeCalls() {
79
+ return this.#calls.size;
80
+ }
81
+ async start() {
82
+ const log = this.#api.logger;
83
+ this.#realtime = this.#resolveRealtime();
84
+ // Fail LOUD, not closed. A gateway that boots healthy and then refuses every
85
+ // call with "realtime-unavailable" is the single most expensive way to
86
+ // discover a missing API key, so name the configured provider here where
87
+ // someone is reading the log, rather than only on the first call that fails.
88
+ if (!this.#realtime) {
89
+ const providerId = this.#config.voice.realtime.provider;
90
+ log.warn("standin-msteams: no realtime voice provider resolved" +
91
+ (providerId
92
+ ? ` (configured provider "${providerId}" has no usable credentials)`
93
+ : " (no realtime provider configured)") +
94
+ '. Every call will be refused with "realtime-unavailable". Set the provider API key.');
95
+ }
96
+ const server = new CallServer({
97
+ handlerFactory: () => new TeamsCallHandler({
98
+ config: this.#config,
99
+ realtime: this.#realtime,
100
+ cfg: this.#api.config,
101
+ registry: this.#calls,
102
+ logger: log,
103
+ }),
104
+ secret: this.#config.media.secret,
105
+ port: this.#config.media.port,
106
+ wsPath: this.#config.media.path,
107
+ maxConnections: this.#config.media.maxConcurrentCalls,
108
+ ...(this.#config.media.bindAddress
109
+ ? { host: this.#config.media.bindAddress }
110
+ : {}),
111
+ });
112
+ await server.start();
113
+ this.#server = server;
114
+ log.info(`standin-msteams: listening on ${this.#config.media.bindAddress ?? "0.0.0.0"}:${server.port}${server.wsPath}` +
115
+ ` (max ${this.#config.media.maxConcurrentCalls} concurrent)`);
116
+ }
117
+ /**
118
+ * Close live calls first, then the listener.
119
+ *
120
+ * That order is not cosmetic: the SDK's teardown frees a call's slot only once
121
+ * its close settles, so tearing the listener down over live calls is how a
122
+ * shutdown ends up waiting on sockets nobody is reading any more.
123
+ */
124
+ async stop() {
125
+ this.#calls.closeAll();
126
+ const server = this.#server;
127
+ this.#server = undefined;
128
+ if (server)
129
+ await server.aclose();
130
+ this.#api.logger.info("standin-msteams: stopped");
131
+ }
132
+ /**
133
+ * Ask the host which realtime provider is configured.
134
+ *
135
+ * Returns undefined rather than throwing on ANY failure, including a throw from
136
+ * the host resolver: a provider that cannot be resolved is a call that gets
137
+ * refused, not a gateway that will not boot. The plugin is one service among
138
+ * many in that process.
139
+ */
140
+ #resolveRealtime() {
141
+ try {
142
+ const resolved = resolveConfiguredRealtimeVoiceProvider({
143
+ configuredProviderId: this.#config.voice.realtime.provider,
144
+ providerConfigs: this.#config.voice.realtime.providers,
145
+ cfg: this.#api.config,
146
+ });
147
+ if (!resolved?.provider || !resolved.providerConfig)
148
+ return undefined;
149
+ return {
150
+ provider: resolved.provider,
151
+ providerConfig: resolved.providerConfig,
152
+ };
153
+ }
154
+ catch (err) {
155
+ this.#api.logger.warn(`standin-msteams: realtime provider resolution failed - ${err instanceof Error ? err.message : String(err)}`);
156
+ return undefined;
157
+ }
158
+ }
159
+ }
160
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/plugins/openclaw/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Who is allowed to call the agent.
3
+ *
4
+ * StandIn authenticates the SOCKET, not the human: the HMAC handshake proves the
5
+ * call came from StandIn, and `session.start` then names a caller. Deciding
6
+ * whether that caller may be answered is the deployment's business, so it lives
7
+ * here rather than in the SDK.
8
+ *
9
+ * Framework-neutral on purpose: nothing here imports OpenClaw, so the policy can
10
+ * be read, tested and reused without a gateway in the process.
11
+ */
12
+ /** Normalize a phone number to digits only. */
13
+ export declare function normalizePhoneNumber(input?: string): string;
14
+ /**
15
+ * True when the caller matches an allowlist entry - by phone number (digits
16
+ * only) OR by exact caller id, case-insensitive.
17
+ *
18
+ * The id match is what lets a Microsoft Teams caller be allowlisted at all: their id is an
19
+ * AAD object id, not a phone number, so phone normalization would reduce it to
20
+ * the empty string and match nothing.
21
+ */
22
+ export declare function isAllowlistedCaller(from: string | undefined, allowFrom: string[] | undefined): boolean;
23
+ /**
24
+ * Inbound-policy decision. `from` is the caller's AAD object id for a Microsoft Teams call.
25
+ *
26
+ * An unset or unknown policy REFUSES. Defaulting the other way would mean a
27
+ * config typo silently opens the agent to anyone who can reach the number.
28
+ */
29
+ export declare function isInboundCallAllowed(inboundPolicy: "disabled" | "allowlist" | "pairing" | "open" | undefined, allowFrom: string[] | undefined, from: string | undefined): boolean;
30
+ /**
31
+ * An actionable log line for a caller the policy just refused.
32
+ *
33
+ * "pairing" is enforced as a plain allowlist here: no pairing codes, expirations
34
+ * or approval prompts are issued for calls, so the operator's fix is the same as
35
+ * under "allowlist".
36
+ */
37
+ export declare function describeInboundRejection(inboundPolicy: "disabled" | "allowlist" | "pairing" | "open" | undefined, from: string | undefined): string;
38
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,+CAA+C;AAC/C,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAK3D;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,GAC9B,OAAO,CAwBT;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,EACxE,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAUT;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,EACxE,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,CAqBR"}
package/dist/policy.js ADDED
@@ -0,0 +1,95 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Who is allowed to call the agent.
5
+ *
6
+ * StandIn authenticates the SOCKET, not the human: the HMAC handshake proves the
7
+ * call came from StandIn, and `session.start` then names a caller. Deciding
8
+ * whether that caller may be answered is the deployment's business, so it lives
9
+ * here rather than in the SDK.
10
+ *
11
+ * Framework-neutral on purpose: nothing here imports OpenClaw, so the policy can
12
+ * be read, tested and reused without a gateway in the process.
13
+ */
14
+ /** Normalize a phone number to digits only. */
15
+ export function normalizePhoneNumber(input) {
16
+ if (!input) {
17
+ return "";
18
+ }
19
+ return input.replace(/\D/g, "");
20
+ }
21
+ /**
22
+ * True when the caller matches an allowlist entry - by phone number (digits
23
+ * only) OR by exact caller id, case-insensitive.
24
+ *
25
+ * The id match is what lets a Microsoft Teams caller be allowlisted at all: their id is an
26
+ * AAD object id, not a phone number, so phone normalization would reduce it to
27
+ * the empty string and match nothing.
28
+ */
29
+ export function isAllowlistedCaller(from, allowFrom) {
30
+ const raw = from?.trim();
31
+ if (!raw) {
32
+ return false;
33
+ }
34
+ const idFrom = raw.toLowerCase();
35
+ const normalizedFrom = normalizePhoneNumber(raw);
36
+ return (allowFrom ?? []).some((entry) => {
37
+ const trimmed = entry.trim();
38
+ if (!trimmed) {
39
+ return false;
40
+ }
41
+ // Exact caller-id match (e.g. a Microsoft Teams AAD object id), case-insensitive.
42
+ if (trimmed.toLowerCase() === idFrom) {
43
+ return true;
44
+ }
45
+ // Phone-number match (digits only).
46
+ const normalizedAllow = normalizePhoneNumber(trimmed);
47
+ return (normalizedAllow !== "" &&
48
+ normalizedFrom !== "" &&
49
+ normalizedAllow === normalizedFrom);
50
+ });
51
+ }
52
+ /**
53
+ * Inbound-policy decision. `from` is the caller's AAD object id for a Microsoft Teams call.
54
+ *
55
+ * An unset or unknown policy REFUSES. Defaulting the other way would mean a
56
+ * config typo silently opens the agent to anyone who can reach the number.
57
+ */
58
+ export function isInboundCallAllowed(inboundPolicy, allowFrom, from) {
59
+ switch (inboundPolicy) {
60
+ case "open":
61
+ return true;
62
+ case "allowlist":
63
+ case "pairing":
64
+ return isAllowlistedCaller(from, allowFrom);
65
+ default:
66
+ return false;
67
+ }
68
+ }
69
+ /**
70
+ * An actionable log line for a caller the policy just refused.
71
+ *
72
+ * "pairing" is enforced as a plain allowlist here: no pairing codes, expirations
73
+ * or approval prompts are issued for calls, so the operator's fix is the same as
74
+ * under "allowlist".
75
+ */
76
+ export function describeInboundRejection(inboundPolicy, from) {
77
+ const policy = inboundPolicy ?? "disabled";
78
+ const caller = from?.trim()
79
+ ? `caller "${from.trim()}"`
80
+ : "caller with no caller id";
81
+ if (policy === "disabled" || policy === "open") {
82
+ // "open" never rejects; kept for exhaustiveness if callers reuse this.
83
+ return (`inbound call rejected by policy "${policy}": inbound calling is ` +
84
+ (policy === "disabled"
85
+ ? 'disabled - set inboundPolicy to "allowlist" and add callers to allowFrom to accept calls'
86
+ : "open") +
87
+ ` (${caller})`);
88
+ }
89
+ const pairingHint = policy === "pairing"
90
+ ? ' Note: "pairing" currently enforces a plain allowlist (this plugin issues no pairing codes' +
91
+ " or approvals for calls) - add the caller's AAD object id to allowFrom."
92
+ : " Add the caller's AAD object id (or phone number) to allowFrom to accept them.";
93
+ return `inbound call rejected by policy "${policy}": ${caller} is not in allowFrom.${pairingHint}`;
94
+ }
95
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;GAUG;AAEH,+CAA+C;AAC/C,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAwB,EACxB,SAA+B;IAE/B,MAAM,GAAG,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,cAAc,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IACjD,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QACtC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,KAAK,CAAC;QACf,CAAC;QACD,kFAAkF;QAClF,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,oCAAoC;QACpC,MAAM,eAAe,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACtD,OAAO,CACL,eAAe,KAAK,EAAE;YACtB,cAAc,KAAK,EAAE;YACrB,eAAe,KAAK,cAAc,CACnC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,aAAwE,EACxE,SAA+B,EAC/B,IAAwB;IAExB,QAAQ,aAAa,EAAE,CAAC;QACtB,KAAK,MAAM;YACT,OAAO,IAAI,CAAC;QACd,KAAK,WAAW,CAAC;QACjB,KAAK,SAAS;YACZ,OAAO,mBAAmB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC9C;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,aAAwE,EACxE,IAAwB;IAExB,MAAM,MAAM,GAAG,aAAa,IAAI,UAAU,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE;QACzB,CAAC,CAAC,WAAW,IAAI,CAAC,IAAI,EAAE,GAAG;QAC3B,CAAC,CAAC,0BAA0B,CAAC;IAC/B,IAAI,MAAM,KAAK,UAAU,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QAC/C,uEAAuE;QACvE,OAAO,CACL,oCAAoC,MAAM,wBAAwB;YAClE,CAAC,MAAM,KAAK,UAAU;gBACpB,CAAC,CAAC,0FAA0F;gBAC5F,CAAC,CAAC,MAAM,CAAC;YACX,KAAK,MAAM,GAAG,CACf,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GACf,MAAM,KAAK,SAAS;QAClB,CAAC,CAAC,4FAA4F;YAC5F,yEAAyE;QAC3E,CAAC,CAAC,gFAAgF,CAAC;IACvF,OAAO,oCAAoC,MAAM,MAAM,MAAM,wBAAwB,WAAW,EAAE,CAAC;AACrG,CAAC"}
@@ -0,0 +1,44 @@
1
+ export { contextSentences, decodePcm, parseMessage } from "./protocolRuntime.js";
2
+ export declare const SAMPLE_RATE_HZ = 16000;
3
+ export declare const NUM_CHANNELS = 1;
4
+ export declare const TYPE_SESSION_START = "session.start";
5
+ export declare const TYPE_SESSION_END = "session.end";
6
+ export declare const TYPE_RECORDING_STATUS = "recording.status";
7
+ export declare const TYPE_AUDIO_FRAME = "audio.frame";
8
+ export declare const TYPE_VIDEO_FRAME = "video.frame";
9
+ export declare const TYPE_PARTICIPANTS = "participants";
10
+ export declare const TYPE_DTMF = "dtmf";
11
+ export declare const TYPE_PING = "ping";
12
+ export declare const TYPE_ASSISTANT_SAY = "assistant.say";
13
+ export declare const TYPE_ASSISTANT_CANCEL = "assistant.cancel";
14
+ export declare const TYPE_EXPRESSION = "expression";
15
+ export declare const TYPE_SPEECH_MARKS = "speech.marks";
16
+ export declare const TYPE_DISPLAY_IMAGE = "display.image";
17
+ export declare const TYPE_DISPLAY_FRAME = "display.frame";
18
+ export declare const TYPE_PONG = "pong";
19
+ /** Caller identity; blank or absent values normalize to undefined. */
20
+ export interface Caller {
21
+ readonly aadId?: string;
22
+ readonly displayName?: string;
23
+ readonly tenantId?: string;
24
+ }
25
+ /** Call context with the SDK's compatible defaults. */
26
+ export interface SessionStart {
27
+ readonly callId: string;
28
+ readonly threadId: string;
29
+ readonly caller: Caller;
30
+ readonly direction: "inbound" | "outbound";
31
+ readonly recordingStatus?: string;
32
+ readonly tenantId?: string;
33
+ }
34
+ /** Read call context; only callId lacks a safe default. */
35
+ export declare function parseSessionStart(msg: Record<string, unknown>): SessionStart;
36
+ /** Build an outbound `audio.frame` JSON frame. */
37
+ export declare function audioFrame(seq: number, timestampMs: number, pcm: Buffer): string;
38
+ /** Build an outbound `pong` JSON frame. */
39
+ export declare function pong(ts: unknown): string;
40
+ /** Build an outbound `assistant.cancel` JSON frame. */
41
+ export declare function assistantCancel(turnId: number): string;
42
+ /** Build an outbound `session.end` JSON frame. */
43
+ export declare function sessionEnd(reason: string): string;
44
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEjF,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAC9C,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAC9C,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAC9C,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,SAAS,SAAS,CAAC;AAChC,eAAO,MAAM,SAAS,SAAS,CAAC;AAChC,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,SAAS,SAAS,CAAC;AAEhC,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,2DAA2D;AAC3D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAoB5E;AAED,kDAAkD;AAClD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED,2CAA2C;AAC3C,wBAAgB,IAAI,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAKxC;AAED,uDAAuD;AACvD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,kDAAkD;AAClD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKjD"}