@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,264 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * One OpenAI Realtime session, as a socket.
5
+ *
6
+ * The one place in the SDK where the wire rate and the model rate disagree.
7
+ * StandIn speaks PCM16 at 16 kHz; the Realtime API speaks PCM at 24 kHz and
8
+ * nothing else. This file owns that conversion in both directions, so the
9
+ * handler above it and the call below it both stay at their own rate and
10
+ * neither has to think about it.
11
+ *
12
+ * The Python SDK's `standin.audio.resample_pcm16` is the same resampler, driven
13
+ * by the same conformance vectors, so a call sounds identical whichever
14
+ * language the plugin is written in.
15
+ */
16
+ import { WebSocket } from "ws";
17
+ import { REALTIME_SAMPLE_RATE_HZ, resamplePcm16 } from "../../audio.js";
18
+ import { toolSchemas } from "../../callTools.js";
19
+ import { logger } from "../../log.js";
20
+ import { SAMPLE_RATE_HZ } from "../../protocol.js";
21
+ const CONNECT_TIMEOUT_MS = 10_000;
22
+ /** A stalled socket must not accumulate an unbounded queue of caller audio. */
23
+ const MAX_SEND_BUFFER_BYTES = 1024 * 1024;
24
+ /**
25
+ * The call capabilities every session gets, declared as tools.
26
+ *
27
+ * The list is the SDK's, in the Realtime API's shape, so every provider offers
28
+ * the same agent and a new capability needs no edit here.
29
+ */
30
+ export const BUILT_IN_TOOLS = toolSchemas("openai");
31
+ /** The names above, for the shadowing check. */
32
+ export const BUILT_IN_TOOL_NAMES = new Set(BUILT_IN_TOOLS.map((tool) => tool.name));
33
+ /**
34
+ * Normalise a remote MCP tool entry for the session's tools array.
35
+ *
36
+ * The Realtime API dials the server itself and runs those tools server-side, so
37
+ * nothing here executes at call time. Approval defaults to never because a live
38
+ * voice call has no approval interface, and a pending approval is a caller
39
+ * listening to silence.
40
+ */
41
+ export function mcpTool(entry) {
42
+ if (typeof entry.server_label !== "string" ||
43
+ typeof entry.server_url !== "string") {
44
+ throw new Error("an MCP tool entry needs server_label and server_url");
45
+ }
46
+ return { require_approval: "never", ...entry, type: "mcp" };
47
+ }
48
+ /** Compose the effective instructions: your prompt, the caller, and tool guidance. */
49
+ export function buildInstructions(base, caller) {
50
+ return [
51
+ base.trim(),
52
+ `The caller's name is ${caller.callerName}. Their organization id is ` +
53
+ `${caller.tenantId}. This is an ${caller.direction} call.`,
54
+ "Use the end_call tool when the conversation is over. Use the look tool when the " +
55
+ "caller refers to something on their camera or shared screen. Use show_image to put " +
56
+ "a picture on your video tile, and express to show an emotion on your avatar.",
57
+ ].join("\n\n");
58
+ }
59
+ /** Per-call session configuration, the Realtime equivalent of a conversation init. */
60
+ export function buildSessionUpdate(config, instructions, extraTools = []) {
61
+ const input = {
62
+ format: { type: "audio/pcm", rate: REALTIME_SAMPLE_RATE_HZ },
63
+ // interrupt_response cancels the in-flight response server-side the moment
64
+ // the caller speaks. The handler mirrors that to the call, which is what
65
+ // actually stops the bot talking.
66
+ turn_detection: {
67
+ type: config.vadType,
68
+ create_response: true,
69
+ interrupt_response: true,
70
+ },
71
+ };
72
+ if (config.transcriptionModel)
73
+ input.transcription = { model: config.transcriptionModel };
74
+ const output = {
75
+ format: { type: "audio/pcm", rate: REALTIME_SAMPLE_RATE_HZ },
76
+ };
77
+ if (config.voice)
78
+ output.voice = config.voice;
79
+ return {
80
+ type: "session.update",
81
+ session: {
82
+ type: "realtime",
83
+ output_modalities: ["audio"],
84
+ instructions,
85
+ audio: { input, output },
86
+ tools: [...BUILT_IN_TOOLS, ...extraTools],
87
+ tool_choice: "auto",
88
+ },
89
+ };
90
+ }
91
+ /** The socket to one OpenAI Realtime session. */
92
+ export class AgentSocket {
93
+ #ws;
94
+ #pendingBytes = 0;
95
+ #dropped = 0;
96
+ #lastDropWarning = 0;
97
+ #closedByUs = false;
98
+ constructor() { }
99
+ /** Open the session, retrying once on a transient failure. */
100
+ static async connect(config, onMessage,
101
+ /** Agent audio, already resampled DOWN to the wire rate. */
102
+ onAudio, onClose) {
103
+ const socket = new AgentSocket();
104
+ const open = async () => {
105
+ const url = `wss://${config.host}/v1/realtime?model=${encodeURIComponent(config.model)}`;
106
+ const ws = new WebSocket(url, {
107
+ headers: { authorization: `Bearer ${config.apiKey}` },
108
+ maxPayload: 16 * 1024 * 1024,
109
+ });
110
+ await new Promise((resolve, reject) => {
111
+ const fail = setTimeout(() => {
112
+ ws.terminate();
113
+ reject(new Error("the OpenAI Realtime socket did not open in time"));
114
+ }, CONNECT_TIMEOUT_MS);
115
+ fail.unref?.();
116
+ ws.once("open", () => {
117
+ clearTimeout(fail);
118
+ resolve();
119
+ });
120
+ ws.once("error", (err) => {
121
+ clearTimeout(fail);
122
+ reject(err);
123
+ });
124
+ });
125
+ return ws;
126
+ };
127
+ try {
128
+ socket.#ws = await open();
129
+ }
130
+ catch (err) {
131
+ logger.warn(`standin: OpenAI connect failed (${String(err)}); retrying once`);
132
+ await new Promise((resolve) => setTimeout(resolve, 250));
133
+ socket.#ws = await open();
134
+ }
135
+ socket.#listen(onMessage, onAudio, onClose);
136
+ return socket;
137
+ }
138
+ #listen(onMessage, onAudio, onClose) {
139
+ const ws = this.#ws;
140
+ if (ws === undefined)
141
+ return;
142
+ ws.on("message", (raw) => {
143
+ let message;
144
+ try {
145
+ message = JSON.parse(raw.toString());
146
+ }
147
+ catch {
148
+ logger.warn("standin: OpenAI sent an unparseable frame; dropping");
149
+ return;
150
+ }
151
+ if (typeof message !== "object" || message === null)
152
+ return;
153
+ const typed = message;
154
+ if (typeof typed.type !== "string")
155
+ return;
156
+ // The hot path first: audio deltas are resampled here so nothing above
157
+ // this line ever sees the model's rate.
158
+ if (typed.type === "response.output_audio.delta" &&
159
+ typeof typed.delta === "string") {
160
+ const atModelRate = Buffer.from(typed.delta, "base64");
161
+ if (atModelRate.length > 0) {
162
+ const atWireRate = resamplePcm16(atModelRate, REALTIME_SAMPLE_RATE_HZ, SAMPLE_RATE_HZ);
163
+ void Promise.resolve(onAudio(atWireRate)).catch((err) => {
164
+ logger.error(`standin: handling OpenAI audio failed: ${String(err)}`);
165
+ });
166
+ }
167
+ return;
168
+ }
169
+ void Promise.resolve(onMessage(typed)).catch((err) => {
170
+ logger.error(`standin: handling OpenAI ${typed.type} failed: ${String(err)}`);
171
+ });
172
+ });
173
+ ws.on("close", (code, reason) => {
174
+ void onClose(code, reason.toString());
175
+ });
176
+ ws.on("error", (err) => {
177
+ logger.warn(`standin: the OpenAI Realtime socket failed: ${String(err)}`);
178
+ });
179
+ }
180
+ get isOpen() {
181
+ return this.#ws !== undefined && this.#ws.readyState === WebSocket.OPEN;
182
+ }
183
+ #send(message, droppable = false) {
184
+ const ws = this.#ws;
185
+ if (ws === undefined || ws.readyState !== WebSocket.OPEN)
186
+ return;
187
+ const payload = JSON.stringify(message);
188
+ if (droppable && this.#pendingBytes > MAX_SEND_BUFFER_BYTES) {
189
+ this.#dropped += 1;
190
+ const now = Date.now();
191
+ if (now - this.#lastDropWarning >= 1000) {
192
+ logger.warn(`standin: OpenAI send backpressure, dropped ${this.#dropped} chunk(s)`);
193
+ this.#lastDropWarning = now;
194
+ this.#dropped = 0;
195
+ }
196
+ return;
197
+ }
198
+ this.#pendingBytes += payload.length;
199
+ ws.send(payload, () => {
200
+ this.#pendingBytes -= payload.length;
201
+ });
202
+ }
203
+ sendSessionUpdate(update) {
204
+ this.#send(update);
205
+ }
206
+ /** Caller audio at the wire rate, resampled up to the model's rate. */
207
+ sendAudio(pcm) {
208
+ const atModelRate = resamplePcm16(pcm, SAMPLE_RATE_HZ, REALTIME_SAMPLE_RATE_HZ);
209
+ this.#send({
210
+ type: "input_audio_buffer.append",
211
+ audio: atModelRate.toString("base64"),
212
+ }, true);
213
+ }
214
+ /**
215
+ * Background context, with no response requested, so the model sees it on its
216
+ * next turn rather than interrupting to acknowledge it.
217
+ */
218
+ sendContext(text) {
219
+ this.#send({
220
+ type: "conversation.item.create",
221
+ item: {
222
+ type: "message",
223
+ role: "user",
224
+ content: [{ type: "input_text", text }],
225
+ },
226
+ });
227
+ }
228
+ /** An interrupting user turn, which is how a goodbye gets spoken. */
229
+ sendUserTurn(text) {
230
+ this.#send({
231
+ type: "conversation.item.create",
232
+ item: {
233
+ type: "message",
234
+ role: "user",
235
+ content: [{ type: "input_text", text }],
236
+ },
237
+ });
238
+ this.#send({ type: "response.create" });
239
+ }
240
+ cancelResponse() {
241
+ this.#send({ type: "response.cancel" });
242
+ }
243
+ /**
244
+ * Answer a tool call, then ask for the turn that speaks the answer. Without
245
+ * the second message the model has the result and says nothing about it.
246
+ */
247
+ sendToolResult(callId, output) {
248
+ this.#send({
249
+ type: "conversation.item.create",
250
+ item: { type: "function_call_output", call_id: callId, output },
251
+ });
252
+ this.#send({ type: "response.create" });
253
+ }
254
+ /** Close the session. Safe to call twice. */
255
+ async aclose() {
256
+ if (this.#closedByUs)
257
+ return;
258
+ this.#closedByUs = true;
259
+ const ws = this.#ws;
260
+ if (ws !== undefined && ws.readyState === WebSocket.OPEN)
261
+ ws.close(1000, "session-end");
262
+ }
263
+ }
264
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/plugins/openai/agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/plugins/openai/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Komaa DigiTech
3
+ // SPDX-License-Identifier: MIT
4
+ /** `npx standin-openai` */
5
+ import { serve } from "./index.js";
6
+ serve().catch((err) => {
7
+ console.error(String(err));
8
+ process.exitCode = 1;
9
+ });
10
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/plugins/openai/cli.ts"],"names":[],"mappings":";AACA,oCAAoC;AACpC,+BAA+B;AAE/B,2BAA2B;AAE3B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC7B,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ export declare const DEFAULT_INSTRUCTIONS: string;
2
+ /** Everything the OpenAI plugin needs, resolved once per worker. */
3
+ export interface OpenAIConfig {
4
+ /** `OPENAI_API_KEY`. Never logged, never sent to StandIn. */
5
+ apiKey: string;
6
+ /** `OPENAI_REALTIME_MODEL`. */
7
+ model: string;
8
+ /** `OPENAI_REALTIME_HOST`. */
9
+ host: string;
10
+ /** `OPENAI_VOICE`, or the model's default when unset. */
11
+ voice?: string;
12
+ /** `OPENAI_INSTRUCTIONS`: the agent's base prompt. */
13
+ instructions: string;
14
+ /**
15
+ * `OPENAI_VAD_TYPE`: `server_vad` hears silence, `semantic_vad` hears
16
+ * finished thoughts. Semantic interrupts less often mid-sentence.
17
+ */
18
+ vadType: "server_vad" | "semantic_vad";
19
+ /** `OPENAI_TRANSCRIPTION_MODEL`, to get transcripts alongside the audio. */
20
+ transcriptionModel?: string;
21
+ /**
22
+ * `OPENAI_LOG_TRANSCRIPTS`. Off by default, and gated a second time on the
23
+ * call actually being recorded.
24
+ */
25
+ logTranscripts: boolean;
26
+ }
27
+ /** Read the configuration, or throw naming the variable that is missing. */
28
+ export declare function openAIConfigFromEnv(): OpenAIConfig;
29
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/plugins/openai/config.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,oBAAoB,QAGd,CAAC;AAEpB,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,EAAE,YAAY,GAAG,cAAc,CAAC;IACvC,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AASD,4EAA4E;AAC5E,wBAAgB,mBAAmB,IAAI,YAAY,CA0BlD"}
@@ -0,0 +1,37 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ /** What the OpenAI Realtime plugin reads from the environment. */
4
+ import { StandInError } from "../../errors.js";
5
+ /** Refuse a host that is not OpenAI or Azure: the API key travels to it. */
6
+ const ALLOWED_HOST_SUFFIXES = [".openai.com", ".azure.com"];
7
+ export const DEFAULT_INSTRUCTIONS = "You are a helpful voice assistant on a live Microsoft Teams call. You are speaking " +
8
+ "aloud: keep replies short, natural and conversational, and never use markdown, " +
9
+ "lists or emoji.";
10
+ function required(name) {
11
+ const value = (process.env[name] ?? "").trim();
12
+ if (!value)
13
+ throw new StandInError(`${name} is required to answer calls with OpenAI`);
14
+ return value;
15
+ }
16
+ /** Read the configuration, or throw naming the variable that is missing. */
17
+ export function openAIConfigFromEnv() {
18
+ const host = (process.env.OPENAI_REALTIME_HOST ?? "").trim() || "api.openai.com";
19
+ if (!ALLOWED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix))) {
20
+ throw new StandInError(`OPENAI_REALTIME_HOST must be an openai.com or azure.com host, got ${host}`);
21
+ }
22
+ const vad = (process.env.OPENAI_VAD_TYPE ?? "").trim() || "semantic_vad";
23
+ if (vad !== "server_vad" && vad !== "semantic_vad") {
24
+ throw new StandInError(`OPENAI_VAD_TYPE must be server_vad or semantic_vad, got ${vad}`);
25
+ }
26
+ return {
27
+ apiKey: required("OPENAI_API_KEY"),
28
+ model: (process.env.OPENAI_REALTIME_MODEL ?? "").trim() || "gpt-realtime",
29
+ host,
30
+ voice: (process.env.OPENAI_VOICE ?? "").trim() || undefined,
31
+ instructions: (process.env.OPENAI_INSTRUCTIONS ?? "").trim() || DEFAULT_INSTRUCTIONS,
32
+ vadType: vad,
33
+ transcriptionModel: (process.env.OPENAI_TRANSCRIPTION_MODEL ?? "").trim() || undefined,
34
+ logTranscripts: process.env.OPENAI_LOG_TRANSCRIPTS === "true",
35
+ };
36
+ }
37
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/plugins/openai/config.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B,kEAAkE;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,4EAA4E;AAC5E,MAAM,qBAAqB,GAAG,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;AAE5D,MAAM,CAAC,MAAM,oBAAoB,GAC/B,qFAAqF;IACrF,iFAAiF;IACjF,iBAAiB,CAAC;AA4BpB,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,IAAI,CAAC,KAAK;QACR,MAAM,IAAI,YAAY,CAAC,GAAG,IAAI,0CAA0C,CAAC,CAAC;IAC5E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GACR,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,gBAAgB,CAAC;IACtE,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,YAAY,CACpB,qEAAqE,IAAI,EAAE,CAC5E,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,cAAc,CAAC;IACzE,IAAI,GAAG,KAAK,YAAY,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;QACnD,MAAM,IAAI,YAAY,CACpB,2DAA2D,GAAG,EAAE,CACjE,CAAC;IACJ,CAAC;IACD,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC;QAClC,KAAK,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,cAAc;QACzE,IAAI;QACJ,KAAK,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS;QAC3D,YAAY,EACV,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,oBAAoB;QACxE,OAAO,EAAE,GAAG;QACZ,kBAAkB,EAChB,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS;QACpE,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,KAAK,MAAM;KAC9D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The relay: one Microsoft Teams call on one side, one OpenAI Realtime session
3
+ * on the other.
4
+ *
5
+ * Speech to speech, so there is no transcription step in the middle and the
6
+ * model hears the caller's voice rather than a transcript of it. The one thing
7
+ * this plugin must get right that the others do not is the rate: the
8
+ * Realtime API speaks 24 kHz and the call speaks 16 kHz, and `agent.ts` owns
9
+ * that conversion so nothing here has to think about it.
10
+ *
11
+ * The call capabilities come from `standin/callTools`, so a capability added to
12
+ * the SDK reaches this plugin without an edit. Add your own with
13
+ * {@link CustomTool} and they are declared the same way.
14
+ */
15
+ import type { CallHandler, CallSession } from "../../handler.js";
16
+ import { FrameDescriber, type VideoFrame } from "../../vision.js";
17
+ import { type AgentMessage, type AgentPort } from "./agent.js";
18
+ import { type OpenAIConfig } from "./config.js";
19
+ /** What a custom tool is told about the call it is running inside. */
20
+ export interface ToolContext {
21
+ call: CallSession;
22
+ /** Whether the call is being recorded. Gate anything that stores what the caller showed. */
23
+ recording: boolean;
24
+ }
25
+ /** A function of your own, which the model calls and your code answers. */
26
+ export interface CustomTool {
27
+ name: string;
28
+ description: string;
29
+ parameters?: Record<string, unknown>;
30
+ handler: (params: Record<string, unknown>, ctx: ToolContext) => string | Promise<string>;
31
+ }
32
+ /** How the handler reaches OpenAI. Replaceable so tests need no network. */
33
+ export type Connect = (config: OpenAIConfig, onMessage: (message: AgentMessage) => void | Promise<void>, onAudio: (pcm: Buffer) => void | Promise<void>, onClose: (code: number, reason: string) => void | Promise<void>) => Promise<AgentPort>;
34
+ /** Options for {@link OpenAIHandler}. */
35
+ export interface OpenAIHandlerOptions {
36
+ config?: OpenAIConfig;
37
+ tools?: CustomTool[];
38
+ /** Remote MCP entries, normalised with `mcpTool`. Run server-side by OpenAI. */
39
+ mcpTools?: Array<Record<string, unknown>>;
40
+ describer?: FrameDescriber;
41
+ connect?: Connect;
42
+ }
43
+ /** One Microsoft Teams call answered by one OpenAI Realtime session. */
44
+ export declare class OpenAIHandler implements CallHandler {
45
+ #private;
46
+ constructor(options?: OpenAIHandlerOptions);
47
+ onStart(session: CallSession): Promise<void>;
48
+ onCallerAudio(pcm: Buffer): Promise<void>;
49
+ /**
50
+ * Context arrives as a user item with no response requested, so the model
51
+ * reads it on its next turn instead of interrupting to acknowledge it.
52
+ */
53
+ /**
54
+ * Keep a short history, so a question about a slide already gone can still be
55
+ * answered. Only kept while the call is recorded.
56
+ */
57
+ onVideoFrame(frame: VideoFrame): Promise<void>;
58
+ onContext(text: string): Promise<void>;
59
+ /** Cancel whatever is being said, then ask for the goodbye. */
60
+ onGoodbye(text: string): Promise<void>;
61
+ aclose(): Promise<void>;
62
+ /** Run one tool and return what the model is told. Exposed for tests. */
63
+ dispatch(name: string, params: Record<string, unknown>): Promise<string>;
64
+ }
65
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/plugins/openai/handler.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAOjE,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,SAAS,EAEf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAQrE,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,4FAA4F;IAC5F,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,GAAG,EAAE,WAAW,KACb,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED,4EAA4E;AAC5E,MAAM,MAAM,OAAO,GAAG,CACpB,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC1D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC9C,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAC5D,OAAO,CAAC,SAAS,CAAC,CAAC;AAExB,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wEAAwE;AACxE,qBAAa,aAAc,YAAW,WAAW;;gBAmBnC,OAAO,GAAE,oBAAyB;IA+BxC,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAiE5C,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/C;;;OAGG;IACH;;;OAGG;IACG,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS5C,+DAA+D;IACzD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAStC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAsJ7B,yEAAyE;IACnE,QAAQ,CACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC;CAkBnB"}