@komaa/standin-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/dist/ambient.d.ts +80 -0
  4. package/dist/ambient.d.ts.map +1 -0
  5. package/dist/ambient.js +281 -0
  6. package/dist/ambient.js.map +1 -0
  7. package/dist/attachments.d.ts +149 -0
  8. package/dist/attachments.d.ts.map +1 -0
  9. package/dist/attachments.js +451 -0
  10. package/dist/attachments.js.map +1 -0
  11. package/dist/audio.d.ts +80 -0
  12. package/dist/audio.d.ts.map +1 -0
  13. package/dist/audio.js +170 -0
  14. package/dist/audio.js.map +1 -0
  15. package/dist/avatar.d.ts +111 -0
  16. package/dist/avatar.d.ts.map +1 -0
  17. package/dist/avatar.js +201 -0
  18. package/dist/avatar.js.map +1 -0
  19. package/dist/callServer.d.ts +151 -0
  20. package/dist/callServer.d.ts.map +1 -0
  21. package/dist/callServer.js +1097 -0
  22. package/dist/callServer.js.map +1 -0
  23. package/dist/callTools.d.ts +154 -0
  24. package/dist/callTools.d.ts.map +1 -0
  25. package/dist/callTools.js +255 -0
  26. package/dist/callTools.js.map +1 -0
  27. package/dist/chat.d.ts +226 -0
  28. package/dist/chat.d.ts.map +1 -0
  29. package/dist/chat.js +574 -0
  30. package/dist/chat.js.map +1 -0
  31. package/dist/config.d.ts +32 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +86 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/consult.d.ts +147 -0
  36. package/dist/consult.d.ts.map +1 -0
  37. package/dist/consult.js +388 -0
  38. package/dist/consult.js.map +1 -0
  39. package/dist/delivery.d.ts +81 -0
  40. package/dist/delivery.d.ts.map +1 -0
  41. package/dist/delivery.js +187 -0
  42. package/dist/delivery.js.map +1 -0
  43. package/dist/echoGuard.d.ts +56 -0
  44. package/dist/echoGuard.d.ts.map +1 -0
  45. package/dist/echoGuard.js +57 -0
  46. package/dist/echoGuard.js.map +1 -0
  47. package/dist/errors.d.ts +5 -0
  48. package/dist/errors.d.ts.map +1 -0
  49. package/dist/errors.js +10 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/fetch.d.ts +40 -0
  52. package/dist/fetch.d.ts.map +1 -0
  53. package/dist/fetch.js +240 -0
  54. package/dist/fetch.js.map +1 -0
  55. package/dist/gate.d.ts +80 -0
  56. package/dist/gate.d.ts.map +1 -0
  57. package/dist/gate.js +249 -0
  58. package/dist/gate.js.map +1 -0
  59. package/dist/handler.d.ts +283 -0
  60. package/dist/handler.d.ts.map +1 -0
  61. package/dist/handler.js +4 -0
  62. package/dist/handler.js.map +1 -0
  63. package/dist/hmac.d.ts +32 -0
  64. package/dist/hmac.d.ts.map +1 -0
  65. package/dist/hmac.js +108 -0
  66. package/dist/hmac.js.map +1 -0
  67. package/dist/index.d.ts +59 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +60 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/lane.d.ts +92 -0
  72. package/dist/lane.d.ts.map +1 -0
  73. package/dist/lane.js +269 -0
  74. package/dist/lane.js.map +1 -0
  75. package/dist/lipsync.d.ts +136 -0
  76. package/dist/lipsync.d.ts.map +1 -0
  77. package/dist/lipsync.js +310 -0
  78. package/dist/lipsync.js.map +1 -0
  79. package/dist/log.d.ts +18 -0
  80. package/dist/log.d.ts.map +1 -0
  81. package/dist/log.js +20 -0
  82. package/dist/log.js.map +1 -0
  83. package/dist/media.d.ts +43 -0
  84. package/dist/media.d.ts.map +1 -0
  85. package/dist/media.js +190 -0
  86. package/dist/media.js.map +1 -0
  87. package/dist/minutes.d.ts +398 -0
  88. package/dist/minutes.d.ts.map +1 -0
  89. package/dist/minutes.js +805 -0
  90. package/dist/minutes.js.map +1 -0
  91. package/dist/outbound.d.ts +388 -0
  92. package/dist/outbound.d.ts.map +1 -0
  93. package/dist/outbound.js +933 -0
  94. package/dist/outbound.js.map +1 -0
  95. package/dist/plugins/cartesia/agent.d.ts +58 -0
  96. package/dist/plugins/cartesia/agent.d.ts.map +1 -0
  97. package/dist/plugins/cartesia/agent.js +254 -0
  98. package/dist/plugins/cartesia/agent.js.map +1 -0
  99. package/dist/plugins/cartesia/cli.d.ts +3 -0
  100. package/dist/plugins/cartesia/cli.d.ts.map +1 -0
  101. package/dist/plugins/cartesia/cli.js +10 -0
  102. package/dist/plugins/cartesia/cli.js.map +1 -0
  103. package/dist/plugins/cartesia/config.d.ts +30 -0
  104. package/dist/plugins/cartesia/config.d.ts.map +1 -0
  105. package/dist/plugins/cartesia/config.js +29 -0
  106. package/dist/plugins/cartesia/config.js.map +1 -0
  107. package/dist/plugins/cartesia/handler.d.ts +40 -0
  108. package/dist/plugins/cartesia/handler.d.ts.map +1 -0
  109. package/dist/plugins/cartesia/handler.js +142 -0
  110. package/dist/plugins/cartesia/handler.js.map +1 -0
  111. package/dist/plugins/cartesia/index.d.ts +6 -0
  112. package/dist/plugins/cartesia/index.d.ts.map +1 -0
  113. package/dist/plugins/cartesia/index.js +54 -0
  114. package/dist/plugins/cartesia/index.js.map +1 -0
  115. package/dist/plugins/deepgram/agent.d.ts +60 -0
  116. package/dist/plugins/deepgram/agent.d.ts.map +1 -0
  117. package/dist/plugins/deepgram/agent.js +302 -0
  118. package/dist/plugins/deepgram/agent.js.map +1 -0
  119. package/dist/plugins/deepgram/cli.d.ts +3 -0
  120. package/dist/plugins/deepgram/cli.d.ts.map +1 -0
  121. package/dist/plugins/deepgram/cli.js +10 -0
  122. package/dist/plugins/deepgram/cli.js.map +1 -0
  123. package/dist/plugins/deepgram/config.d.ts +39 -0
  124. package/dist/plugins/deepgram/config.d.ts.map +1 -0
  125. package/dist/plugins/deepgram/config.js +66 -0
  126. package/dist/plugins/deepgram/config.js.map +1 -0
  127. package/dist/plugins/deepgram/handler.d.ts +87 -0
  128. package/dist/plugins/deepgram/handler.d.ts.map +1 -0
  129. package/dist/plugins/deepgram/handler.js +268 -0
  130. package/dist/plugins/deepgram/handler.js.map +1 -0
  131. package/dist/plugins/deepgram/index.d.ts +11 -0
  132. package/dist/plugins/deepgram/index.d.ts.map +1 -0
  133. package/dist/plugins/deepgram/index.js +76 -0
  134. package/dist/plugins/deepgram/index.js.map +1 -0
  135. package/dist/plugins/echo/cli.d.ts +3 -0
  136. package/dist/plugins/echo/cli.d.ts.map +1 -0
  137. package/dist/plugins/echo/cli.js +10 -0
  138. package/dist/plugins/echo/cli.js.map +1 -0
  139. package/dist/plugins/echo/index.d.ts +37 -0
  140. package/dist/plugins/echo/index.d.ts.map +1 -0
  141. package/dist/plugins/echo/index.js +61 -0
  142. package/dist/plugins/echo/index.js.map +1 -0
  143. package/dist/plugins/elevenlabs/agent.d.ts +72 -0
  144. package/dist/plugins/elevenlabs/agent.d.ts.map +1 -0
  145. package/dist/plugins/elevenlabs/agent.js +305 -0
  146. package/dist/plugins/elevenlabs/agent.js.map +1 -0
  147. package/dist/plugins/elevenlabs/cli.d.ts +3 -0
  148. package/dist/plugins/elevenlabs/cli.d.ts.map +1 -0
  149. package/dist/plugins/elevenlabs/cli.js +10 -0
  150. package/dist/plugins/elevenlabs/cli.js.map +1 -0
  151. package/dist/plugins/elevenlabs/config.d.ts +27 -0
  152. package/dist/plugins/elevenlabs/config.d.ts.map +1 -0
  153. package/dist/plugins/elevenlabs/config.js +42 -0
  154. package/dist/plugins/elevenlabs/config.js.map +1 -0
  155. package/dist/plugins/elevenlabs/handler.d.ts +67 -0
  156. package/dist/plugins/elevenlabs/handler.d.ts.map +1 -0
  157. package/dist/plugins/elevenlabs/handler.js +419 -0
  158. package/dist/plugins/elevenlabs/handler.js.map +1 -0
  159. package/dist/plugins/elevenlabs/index.d.ts +11 -0
  160. package/dist/plugins/elevenlabs/index.d.ts.map +1 -0
  161. package/dist/plugins/elevenlabs/index.js +72 -0
  162. package/dist/plugins/elevenlabs/index.js.map +1 -0
  163. package/dist/plugins/livekit/cli.d.ts +3 -0
  164. package/dist/plugins/livekit/cli.d.ts.map +1 -0
  165. package/dist/plugins/livekit/cli.js +10 -0
  166. package/dist/plugins/livekit/cli.js.map +1 -0
  167. package/dist/plugins/livekit/config.d.ts +47 -0
  168. package/dist/plugins/livekit/config.d.ts.map +1 -0
  169. package/dist/plugins/livekit/config.js +46 -0
  170. package/dist/plugins/livekit/config.js.map +1 -0
  171. package/dist/plugins/livekit/handler.d.ts +59 -0
  172. package/dist/plugins/livekit/handler.d.ts.map +1 -0
  173. package/dist/plugins/livekit/handler.js +211 -0
  174. package/dist/plugins/livekit/handler.js.map +1 -0
  175. package/dist/plugins/livekit/index.d.ts +6 -0
  176. package/dist/plugins/livekit/index.d.ts.map +1 -0
  177. package/dist/plugins/livekit/index.js +68 -0
  178. package/dist/plugins/livekit/index.js.map +1 -0
  179. package/dist/plugins/livekit/room.d.ts +117 -0
  180. package/dist/plugins/livekit/room.d.ts.map +1 -0
  181. package/dist/plugins/livekit/room.js +449 -0
  182. package/dist/plugins/livekit/room.js.map +1 -0
  183. package/dist/plugins/openai/agent.d.ts +74 -0
  184. package/dist/plugins/openai/agent.d.ts.map +1 -0
  185. package/dist/plugins/openai/agent.js +264 -0
  186. package/dist/plugins/openai/agent.js.map +1 -0
  187. package/dist/plugins/openai/cli.d.ts +3 -0
  188. package/dist/plugins/openai/cli.d.ts.map +1 -0
  189. package/dist/plugins/openai/cli.js +10 -0
  190. package/dist/plugins/openai/cli.js.map +1 -0
  191. package/dist/plugins/openai/config.d.ts +29 -0
  192. package/dist/plugins/openai/config.d.ts.map +1 -0
  193. package/dist/plugins/openai/config.js +37 -0
  194. package/dist/plugins/openai/config.js.map +1 -0
  195. package/dist/plugins/openai/handler.d.ts +65 -0
  196. package/dist/plugins/openai/handler.d.ts.map +1 -0
  197. package/dist/plugins/openai/handler.js +297 -0
  198. package/dist/plugins/openai/handler.js.map +1 -0
  199. package/dist/plugins/openai/index.d.ts +6 -0
  200. package/dist/plugins/openai/index.d.ts.map +1 -0
  201. package/dist/plugins/openai/index.js +72 -0
  202. package/dist/plugins/openai/index.js.map +1 -0
  203. package/dist/plugins/openclaw/allowlist.d.ts +9 -0
  204. package/dist/plugins/openclaw/allowlist.d.ts.map +1 -0
  205. package/dist/plugins/openclaw/allowlist.js +11 -0
  206. package/dist/plugins/openclaw/allowlist.js.map +1 -0
  207. package/dist/plugins/openclaw/config.d.ts +50 -0
  208. package/dist/plugins/openclaw/config.d.ts.map +1 -0
  209. package/dist/plugins/openclaw/config.js +53 -0
  210. package/dist/plugins/openclaw/config.js.map +1 -0
  211. package/dist/plugins/openclaw/handler.d.ts +90 -0
  212. package/dist/plugins/openclaw/handler.d.ts.map +1 -0
  213. package/dist/plugins/openclaw/handler.js +146 -0
  214. package/dist/plugins/openclaw/handler.js.map +1 -0
  215. package/dist/plugins/openclaw/index.d.ts +55 -0
  216. package/dist/plugins/openclaw/index.d.ts.map +1 -0
  217. package/dist/plugins/openclaw/index.js +89 -0
  218. package/dist/plugins/openclaw/index.js.map +1 -0
  219. package/dist/plugins/openclaw/openclaw.plugin.json +74 -0
  220. package/dist/plugins/openclaw/package.json +14 -0
  221. package/dist/plugins/openclaw/realtime.d.ts +82 -0
  222. package/dist/plugins/openclaw/realtime.d.ts.map +1 -0
  223. package/dist/plugins/openclaw/realtime.js +238 -0
  224. package/dist/plugins/openclaw/realtime.js.map +1 -0
  225. package/dist/plugins/openclaw/runtime.d.ts +19 -0
  226. package/dist/plugins/openclaw/runtime.d.ts.map +1 -0
  227. package/dist/plugins/openclaw/runtime.js +160 -0
  228. package/dist/plugins/openclaw/runtime.js.map +1 -0
  229. package/dist/policy.d.ts +38 -0
  230. package/dist/policy.d.ts.map +1 -0
  231. package/dist/policy.js +95 -0
  232. package/dist/policy.js.map +1 -0
  233. package/dist/protocol.d.ts +44 -0
  234. package/dist/protocol.d.ts.map +1 -0
  235. package/dist/protocol.js +77 -0
  236. package/dist/protocol.js.map +1 -0
  237. package/dist/protocolRuntime.d.ts +19 -0
  238. package/dist/protocolRuntime.d.ts.map +1 -0
  239. package/dist/protocolRuntime.js +78 -0
  240. package/dist/protocolRuntime.js.map +1 -0
  241. package/dist/smoke.d.ts +65 -0
  242. package/dist/smoke.d.ts.map +1 -0
  243. package/dist/smoke.js +293 -0
  244. package/dist/smoke.js.map +1 -0
  245. package/dist/startup.d.ts +74 -0
  246. package/dist/startup.d.ts.map +1 -0
  247. package/dist/startup.js +112 -0
  248. package/dist/startup.js.map +1 -0
  249. package/dist/tile.d.ts +101 -0
  250. package/dist/tile.d.ts.map +1 -0
  251. package/dist/tile.js +195 -0
  252. package/dist/tile.js.map +1 -0
  253. package/dist/version.d.ts +2 -0
  254. package/dist/version.d.ts.map +1 -0
  255. package/dist/version.js +4 -0
  256. package/dist/version.js.map +1 -0
  257. package/dist/vision.d.ts +160 -0
  258. package/dist/vision.d.ts.map +1 -0
  259. package/dist/vision.js +301 -0
  260. package/dist/vision.js.map +1 -0
  261. package/dist/visionTools.d.ts +237 -0
  262. package/dist/visionTools.d.ts.map +1 -0
  263. package/dist/visionTools.js +578 -0
  264. package/dist/visionTools.js.map +1 -0
  265. package/dist/voice.d.ts +143 -0
  266. package/dist/voice.d.ts.map +1 -0
  267. package/dist/voice.js +403 -0
  268. package/dist/voice.js.map +1 -0
  269. package/package.json +129 -0
package/dist/lane.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Caller audio in, a spoken answer out, for agents that are not speech to speech.
3
+ *
4
+ * A speech-to-speech model hears the caller and talks back, and a plugin for one
5
+ * is mostly a socket. Most agents are not that. They read text, they write text,
6
+ * and getting them onto a phone call means four things in a row: work out where
7
+ * the caller stopped talking, turn that into words, ask the agent, and say the
8
+ * answer back at the rate a call consumes audio.
9
+ *
10
+ * `voice.ts` has each of those pieces. This is the thing that runs them in
11
+ * order, holds the turn together, and gets the awkward parts right:
12
+ *
13
+ * **One turn at a time.** An agent asked two questions at once answers neither
14
+ * well. A new utterance supersedes the one in flight rather than racing it.
15
+ *
16
+ * **Barge-in actually stops the answer.** Somebody who interrupts has stopped
17
+ * listening, and a worker that keeps streaming a paragraph at them is talking to
18
+ * nobody. The buffered audio is dropped at the same moment the new utterance
19
+ * opens, not when the old one finishes.
20
+ *
21
+ * **A silence is not a turn.** A cough, a door, a second of traffic: the
22
+ * segmenter opens on any loud frame, and waking the agent for every one of them
23
+ * is a bill and a caller being answered at random.
24
+ *
25
+ * **Nothing here throws into the call.** A provider that fails says so, in a
26
+ * sentence, out loud. Silence is the one thing a caller cannot interpret.
27
+ *
28
+ * Identical in shape to the Python SDK's `standin.lane`.
29
+ */
30
+ import type { CallSession } from "./handler.js";
31
+ import { UtteranceSegmenter } from "./voice.js";
32
+ /**
33
+ * What the caller hears when a step of the lane fails.
34
+ *
35
+ * Spoken, not logged and swallowed. Somebody on a phone call cannot tell a
36
+ * broken transcriber from an agent that is thinking, and will keep waiting.
37
+ */
38
+ export declare const TROUBLE_HEARING = "Sorry, I did not catch that.";
39
+ export declare const TROUBLE_ANSWERING = "Sorry, I am having trouble answering just now.";
40
+ export declare const TROUBLE_SPEAKING = "Sorry, I am having trouble speaking just now.";
41
+ /** Caller audio (PCM16 mono, 16 kHz) to words. Empty means nothing was said. */
42
+ export type Transcribe = (pcm: Buffer) => Promise<string>;
43
+ /** Words to an answer. Either the whole thing, or sentences as they are written. */
44
+ export type Answer = (text: string) => Promise<string> | AsyncIterable<string>;
45
+ /** An answer to speech (PCM16 mono, 16 kHz). Either one buffer, or chunks. */
46
+ export type Synthesize = (text: string) => Promise<Buffer> | AsyncIterable<Buffer>;
47
+ /** One exchange, after it is over. */
48
+ export interface VoiceTurn {
49
+ readonly heard: string;
50
+ readonly said: string;
51
+ /**
52
+ * Whether the caller cut the answer short. Not a failure: it is the most
53
+ * common way a real conversation goes.
54
+ */
55
+ readonly interrupted: boolean;
56
+ readonly error?: string;
57
+ }
58
+ /** Options for {@link VoiceLane}. */
59
+ export interface VoiceLaneOptions {
60
+ segmenter?: UtteranceSegmenter;
61
+ onTurn?: (turn: VoiceTurn) => void;
62
+ }
63
+ /**
64
+ * Runs one call's worth of listen, transcribe, answer, speak.
65
+ *
66
+ * Built by a plugin, which supplies the three steps. Everything about pacing,
67
+ * interruption and turn-taking is here, because getting those wrong is what
68
+ * makes a working provider sound broken.
69
+ */
70
+ export declare class VoiceLane {
71
+ #private;
72
+ constructor(session: CallSession, transcribe: Transcribe, answer: Answer, synthesize: Synthesize, options?: VoiceLaneOptions);
73
+ /** Whether the agent is talking right now. */
74
+ get speaking(): boolean;
75
+ /** Whether a turn is in flight, including the model's own thinking. */
76
+ get busy(): boolean;
77
+ /** The turn in flight, if any. Await it to let one finish. */
78
+ get turn(): Promise<void> | undefined;
79
+ /** Take one frame of caller audio. Never throws, never blocks. */
80
+ feed(pcm: Buffer): Promise<void>;
81
+ /** Stop talking, immediately. The caller interrupted. */
82
+ bargeIn(): Promise<void>;
83
+ /**
84
+ * Speak a line the agent did not have to be asked for.
85
+ *
86
+ * A greeting, a handover, something that arrived from outside the call.
87
+ */
88
+ say(text: string): Promise<VoiceTurn>;
89
+ /** Stop everything. Called once, on teardown. */
90
+ aclose(): Promise<void>;
91
+ }
92
+ //# sourceMappingURL=lane.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lane.d.ts","sourceRoot":"","sources":["../src/lane.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAiB,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,eAAe,iCAAiC,CAAC;AAC9D,eAAO,MAAM,iBAAiB,mDACoB,CAAC;AACnD,eAAO,MAAM,gBAAgB,kDAAkD,CAAC;AAEhF,gFAAgF;AAChF,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE1D,oFAAoF;AACpF,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;AAE/E,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,CACvB,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;AAE7C,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;CACpC;AAED;;;;;;GAMG;AACH,qBAAa,SAAS;;gBAalB,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,gBAAqB;IAWhC,8CAA8C;IAC9C,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,uEAAuE;IACvE,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,8DAA8D;IAC9D,IAAI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAEpC;IAED,kEAAkE;IAC5D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IActC,yDAAyD;IACnD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B;;;;OAIG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAa3C,iDAAiD;IAC3C,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAyK9B"}
package/dist/lane.js ADDED
@@ -0,0 +1,269 @@
1
+ // Copyright (c) 2026 Komaa DigiTech
2
+ // SPDX-License-Identifier: MIT
3
+ import { logger } from "./log.js";
4
+ import { PacedPlayback, UtteranceSegmenter } from "./voice.js";
5
+ /**
6
+ * What the caller hears when a step of the lane fails.
7
+ *
8
+ * Spoken, not logged and swallowed. Somebody on a phone call cannot tell a
9
+ * broken transcriber from an agent that is thinking, and will keep waiting.
10
+ */
11
+ export const TROUBLE_HEARING = "Sorry, I did not catch that.";
12
+ export const TROUBLE_ANSWERING = "Sorry, I am having trouble answering just now.";
13
+ export const TROUBLE_SPEAKING = "Sorry, I am having trouble speaking just now.";
14
+ /**
15
+ * Runs one call's worth of listen, transcribe, answer, speak.
16
+ *
17
+ * Built by a plugin, which supplies the three steps. Everything about pacing,
18
+ * interruption and turn-taking is here, because getting those wrong is what
19
+ * makes a working provider sound broken.
20
+ */
21
+ export class VoiceLane {
22
+ #session;
23
+ #transcribe;
24
+ #answer;
25
+ #synthesize;
26
+ #segmenter;
27
+ #onTurn;
28
+ #playback;
29
+ #turn;
30
+ #generation = 0;
31
+ #closed = false;
32
+ constructor(session, transcribe, answer, synthesize, options = {}) {
33
+ this.#session = session;
34
+ this.#transcribe = transcribe;
35
+ this.#answer = answer;
36
+ this.#synthesize = synthesize;
37
+ this.#segmenter = options.segmenter ?? new UtteranceSegmenter();
38
+ this.#onTurn = options.onTurn;
39
+ this.#playback = new PacedPlayback((pcm) => session.sendAudio(pcm));
40
+ }
41
+ /** Whether the agent is talking right now. */
42
+ get speaking() {
43
+ return this.#playback.playing;
44
+ }
45
+ /** Whether a turn is in flight, including the model's own thinking. */
46
+ get busy() {
47
+ return this.#turn !== undefined;
48
+ }
49
+ /** The turn in flight, if any. Await it to let one finish. */
50
+ get turn() {
51
+ return this.#turn;
52
+ }
53
+ /** Take one frame of caller audio. Never throws, never blocks. */
54
+ async feed(pcm) {
55
+ if (this.#closed)
56
+ return;
57
+ const wasSpeaking = this.#segmenter.speaking;
58
+ const utterance = this.#segmenter.feed(pcm);
59
+ if (!wasSpeaking && this.#segmenter.speaking && this.#playback.playing) {
60
+ // The caller started over the top of the answer. Drop what is buffered
61
+ // NOW rather than when this utterance finishes: the extra second of
62
+ // talking at somebody who has stopped listening is the whole difference
63
+ // between a call that feels alive and one that does not.
64
+ await this.bargeIn();
65
+ }
66
+ if (utterance !== undefined)
67
+ this.#begin(utterance);
68
+ }
69
+ /** Stop talking, immediately. The caller interrupted. */
70
+ async bargeIn() {
71
+ this.#playback.cancel();
72
+ try {
73
+ await this.#session.cancelPlayback();
74
+ }
75
+ catch {
76
+ // Best effort. A failed cancel is not worth ending a call over.
77
+ }
78
+ }
79
+ /**
80
+ * Speak a line the agent did not have to be asked for.
81
+ *
82
+ * A greeting, a handover, something that arrived from outside the call.
83
+ */
84
+ async say(text) {
85
+ // Same guard as feed(). A line handed in after teardown would otherwise
86
+ // synthesize and send on a call that has already gone.
87
+ if (this.#closed)
88
+ return {
89
+ heard: "",
90
+ said: "",
91
+ interrupted: false,
92
+ error: "the call has ended",
93
+ };
94
+ return await this.#speak(text, "");
95
+ }
96
+ /** Stop everything. Called once, on teardown. */
97
+ async aclose() {
98
+ this.#closed = true;
99
+ this.#generation += 1;
100
+ this.#playback.cancel();
101
+ this.#segmenter.reset();
102
+ const turn = this.#turn;
103
+ this.#turn = undefined;
104
+ if (turn !== undefined)
105
+ await turn.catch(() => undefined);
106
+ }
107
+ // ---- one turn ---------------------------------------------------------
108
+ /**
109
+ * Start a turn, superseding whatever was in flight.
110
+ *
111
+ * Detached on purpose: this is reached from the receive path of a live call,
112
+ * and awaiting a model there stops frames arriving.
113
+ */
114
+ #begin(utterance) {
115
+ // One turn at a time. An agent asked two questions at once answers neither
116
+ // well, and both answers would be spoken over each other. JavaScript cannot
117
+ // cancel a promise, so the older turn is retired by generation: it runs to
118
+ // completion but nothing it produces is ever spoken.
119
+ const generation = ++this.#generation;
120
+ this.#playback.cancel();
121
+ const running = this.#run(utterance, generation).finally(() => {
122
+ if (this.#turn === running)
123
+ this.#turn = undefined;
124
+ });
125
+ this.#turn = running;
126
+ }
127
+ async #run(utterance, generation) {
128
+ try {
129
+ const heard = await this.#hear(utterance, generation);
130
+ if (heard === undefined || generation !== this.#generation)
131
+ return;
132
+ await this.#respond(heard, generation);
133
+ }
134
+ catch (err) {
135
+ logger.warn(`standin: the voice turn failed: ${String(err)}`);
136
+ }
137
+ }
138
+ async #hear(utterance, generation) {
139
+ let heard;
140
+ try {
141
+ heard = ((await this.#transcribe(utterance)) ?? "").trim();
142
+ }
143
+ catch (err) {
144
+ logger.warn(`standin: could not transcribe the caller: ${String(err)}`);
145
+ if (generation === this.#generation) {
146
+ await this.#speak(TROUBLE_HEARING, "", String(err));
147
+ }
148
+ return undefined;
149
+ }
150
+ if (heard === "") {
151
+ // A cough, a door, a second of traffic. The segmenter opens on any loud
152
+ // frame, and waking the agent for every one of them is a bill and a
153
+ // caller being answered at random.
154
+ logger.debug("standin: an utterance transcribed to nothing; no turn");
155
+ return undefined;
156
+ }
157
+ return heard;
158
+ }
159
+ async #respond(heard, generation) {
160
+ let reply;
161
+ try {
162
+ reply = this.#answer(heard);
163
+ }
164
+ catch (err) {
165
+ logger.warn(`standin: the agent did not answer: ${String(err)}`);
166
+ // Gated, like every other thing this turn might say. A turn superseded
167
+ // while its answer was pending would otherwise apologise over the top of
168
+ // the turn that replaced it.
169
+ if (generation === this.#generation) {
170
+ await this.#speak(TROUBLE_ANSWERING, heard, String(err));
171
+ }
172
+ return;
173
+ }
174
+ if (isAsyncIterable(reply)) {
175
+ // Sentence by sentence, so the caller hears the beginning of a long
176
+ // answer while the rest is still being written.
177
+ const spoken = [];
178
+ let interrupted = false;
179
+ try {
180
+ for await (const piece of reply) {
181
+ if (generation !== this.#generation)
182
+ return;
183
+ if (piece.trim() === "")
184
+ continue;
185
+ const turn = await this.#speak(piece, heard);
186
+ spoken.push(turn.said);
187
+ if (turn.interrupted) {
188
+ interrupted = true;
189
+ break;
190
+ }
191
+ }
192
+ }
193
+ catch (err) {
194
+ logger.warn(`standin: the agent did not answer: ${String(err)}`);
195
+ if (generation === this.#generation) {
196
+ await this.#speak(TROUBLE_ANSWERING, heard, String(err));
197
+ }
198
+ return;
199
+ }
200
+ if (spoken.length > 0)
201
+ this.#finished({ heard, said: spoken.join(" "), interrupted });
202
+ return;
203
+ }
204
+ let said;
205
+ try {
206
+ said = ((await reply) ?? "").trim();
207
+ }
208
+ catch (err) {
209
+ logger.warn(`standin: the agent did not answer: ${String(err)}`);
210
+ if (generation === this.#generation) {
211
+ await this.#speak(TROUBLE_ANSWERING, heard, String(err));
212
+ }
213
+ return;
214
+ }
215
+ if (said === "") {
216
+ logger.debug("standin: the agent answered with nothing; staying quiet");
217
+ return;
218
+ }
219
+ if (generation !== this.#generation)
220
+ return;
221
+ this.#finished(await this.#speak(said, heard));
222
+ }
223
+ /** Say one piece of an answer, and report what the caller heard. */
224
+ async #speak(text, heard, error) {
225
+ const line = (text ?? "").trim();
226
+ if (line === "")
227
+ return { heard, said: "", interrupted: false, error };
228
+ try {
229
+ const audio = this.#synthesize(line);
230
+ if (isAsyncIterable(audio)) {
231
+ let interrupted = false;
232
+ for await (const chunk of audio) {
233
+ const played = await this.#playback.say(chunk);
234
+ if (played.interrupted) {
235
+ interrupted = true;
236
+ break;
237
+ }
238
+ }
239
+ return { heard, said: line, interrupted, error };
240
+ }
241
+ const played = await this.#playback.say(await audio);
242
+ return { heard, said: line, interrupted: played.interrupted, error };
243
+ }
244
+ catch (err) {
245
+ logger.warn(`standin: could not speak: ${String(err)}`);
246
+ if (line !== TROUBLE_SPEAKING) {
247
+ // One retry, with the sentence that says what happened. Without it a
248
+ // synthesis failure is indistinguishable from a dropped call, and the
249
+ // caller waits for an answer that is not coming.
250
+ return await this.#speak(TROUBLE_SPEAKING, heard, String(err));
251
+ }
252
+ return { heard, said: "", interrupted: false, error: String(err) };
253
+ }
254
+ }
255
+ #finished(turn) {
256
+ if (this.#onTurn === undefined)
257
+ return;
258
+ try {
259
+ this.#onTurn(turn);
260
+ }
261
+ catch {
262
+ // A plugin's own bookkeeping must not end a call.
263
+ }
264
+ }
265
+ }
266
+ function isAsyncIterable(value) {
267
+ return (typeof value === "object" && value !== null && Symbol.asyncIterator in value);
268
+ }
269
+ //# sourceMappingURL=lane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lane.js","sourceRoot":"","sources":["../src/lane.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ import type { SpeechMark } from "./avatar.js";
2
+ /**
3
+ * A closed mouth, and what the space between two words becomes. Every other
4
+ * unmapped character is skipped instead: punching silence into "3.5%" would
5
+ * close the mouth in the middle of a spoken number.
6
+ */
7
+ export declare const SILENCE_VISEME = 0;
8
+ /**
9
+ * Character to mouth shape, read-only. Sukun, shadda, tanween, the tatweel and
10
+ * every presentation form are deliberately absent: a stretch mark and a doubling
11
+ * mark carry no mouth shape of their own, and mapping them would insert phantom
12
+ * mouth changes into an otherwise correct timeline.
13
+ */
14
+ export declare const CHAR_VISEMES: Readonly<Record<string, number>>;
15
+ /**
16
+ * The mouth shape a character wears, or undefined when it has none.
17
+ *
18
+ * The lookup is on the raw character after lowercasing, with no Unicode
19
+ * normalization at all: adding NFKC here would change which characters map and
20
+ * the two SDKs would disagree on the same string. Digits, punctuation, the
21
+ * tatweel and the non-vowel diacritics come back undefined, and every caller
22
+ * skips them rather than holding the mouth closed over them.
23
+ */
24
+ export declare function visemeForChar(ch: string): number | undefined;
25
+ /**
26
+ * Spread `text` over `durationMs` as a viseme timeline.
27
+ *
28
+ * Pass the duration of the audio you actually sent for the turn, which
29
+ * {@link TurnLipSync} counts for you. Anything else is a guess, and a guessed
30
+ * timeline is worse than no timeline at all.
31
+ *
32
+ * `text` is lowercased, its whitespace runs collapsed, and trimmed. Anything
33
+ * that is not a positive finite duration returns no marks rather than being
34
+ * divided by, because an infinite or not-a-number timestamp desynchronises the
35
+ * mouth for the rest of the utterance and reaches the wire as a null.
36
+ *
37
+ * Returns `{tMs, visemeId}` marks, strictly increasing in time, ready for
38
+ * {@link speechMarks}. Empty when there is nothing to say: no text, no duration,
39
+ * or nothing in the text that has a mouth shape, which is the right answer for
40
+ * "3.5%" or an emoji on its own.
41
+ *
42
+ * ```ts
43
+ * const marks = estimateVisemes(finalTranscript, lipsync.durationMs);
44
+ * if (marks.length > 0) await session.sendSpeechMarks(marks);
45
+ * ```
46
+ */
47
+ export declare function estimateVisemes(text: string | null | undefined, durationMs: number): SpeechMark[];
48
+ /**
49
+ * Build the timeline from per-character timings the speech provider gave you.
50
+ *
51
+ * Real times are strictly better than an estimate and cost nothing when the
52
+ * provider already returns them, so prefer this whenever a synthesis call can
53
+ * hand back an alignment. Core takes the two plain arrays: `characters` as the
54
+ * provider spoke them, and `startTimesSeconds` counting from the start of the
55
+ * utterance. Normalising a vendor's field names is the speech plugin's job.
56
+ *
57
+ * Returns marks, or an empty array when the alignment holds no mouth shape at
58
+ * all (all spaces, all punctuation). Fall back to {@link estimateVisemes} on an
59
+ * EMPTY result rather than on a missing alignment: a provider that returns
60
+ * timings for punctuation only has an alignment and still needs the estimate.
61
+ *
62
+ * Ragged arrays are tolerated: the walk stops at the shorter of the two.
63
+ * Providers do return mismatched lengths, and throwing there would lose the turn
64
+ * over a cosmetic hint.
65
+ */
66
+ export declare function visemesFromAlignment(characters: readonly string[], startTimesSeconds: readonly number[]): SpeechMark[];
67
+ /** How {@link TurnLipSync} reads the buffers it is handed. */
68
+ export interface TurnLipSyncOptions {
69
+ /**
70
+ * The rate of the PCM16 mono buffers passed to {@link TurnLipSync.audioSent}.
71
+ * Defaults to the wire's own rate, which is what a plugin sending frames to
72
+ * the call is holding.
73
+ */
74
+ sampleRateHz?: number;
75
+ }
76
+ /**
77
+ * Counts the audio one turn actually sent, then times the mouth to it.
78
+ *
79
+ * Feed it every buffer you hand to the call, and ask it for the timeline when
80
+ * that turn's text is final. It resets itself, so the next turn starts from
81
+ * zero:
82
+ *
83
+ * ```ts
84
+ * const lipsync = new TurnLipSync();
85
+ *
86
+ * // the audio sink
87
+ * await session.sendAudio(chunk);
88
+ * lipsync.audioSent(chunk);
89
+ *
90
+ * // the final transcript only
91
+ * if (isFinal) {
92
+ * const marks = lipsync.finish(text);
93
+ * if (marks.length > 0) await session.sendSpeechMarks(marks);
94
+ * }
95
+ *
96
+ * // playback cancelled
97
+ * lipsync.cancel();
98
+ * ```
99
+ *
100
+ * Emit once per turn, on the final transcript. A partial would send an
101
+ * ever-lengthening timeline several times over and the avatar would restart the
102
+ * mouth mid-sentence.
103
+ *
104
+ * {@link cancel} is not optional. On a barge-in the service drops audio the
105
+ * caller never heard, and a counter that keeps those milliseconds spreads the
106
+ * next turn's text over its own audio plus the discarded audio: the mouth runs
107
+ * long for the whole of that turn and every turn after it.
108
+ */
109
+ export declare class TurnLipSync {
110
+ #private;
111
+ constructor(options?: TurnLipSyncOptions);
112
+ /** Milliseconds of audio sent for the turn in progress. Starts at 0. */
113
+ get durationMs(): number;
114
+ /** Add one PCM16 mono buffer that has gone out to the call. */
115
+ audioSent(pcm: Uint8Array): void;
116
+ /**
117
+ * Add a duration directly, for a sink that hands over encoded audio.
118
+ *
119
+ * Rounded per chunk rather than kept as a running float, so both SDKs
120
+ * accumulate the same integer for the same stream of chunks. A chunk that
121
+ * measures as nothing, or as no number at all, is ignored rather than taking
122
+ * the turn's count with it.
123
+ */
124
+ audioSentMs(ms: number): void;
125
+ /** Drop the count on a barge-in or a playback cancel, emitting nothing. */
126
+ cancel(): void;
127
+ /**
128
+ * Return the turn's timeline and reset the counter.
129
+ *
130
+ * Empty when no audio was sent or the text carries no mouth shape, and a
131
+ * caller sends nothing in that case. The reset happens either way: the next
132
+ * turn must not inherit these milliseconds.
133
+ */
134
+ finish(text: string | null | undefined): SpeechMark[];
135
+ }
136
+ //# sourceMappingURL=lipsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lipsync.d.ts","sourceRoot":"","sources":["../src/lipsync.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C;;;;GAIG;AACH,eAAO,MAAM,cAAc,IAAI,CAAC;AA4DhC;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAC7B,CAAC;AAE9B;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE5D;AAoCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,UAAU,EAAE,MAAM,GACjB,UAAU,EAAE,CAkBd;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,iBAAiB,EAAE,SAAS,MAAM,EAAE,GACnC,UAAU,EAAE,CAmBd;AAED,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,WAAW;;gBAIV,OAAO,GAAE,kBAAuB;IAI5C,wEAAwE;IACxE,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,+DAA+D;IAC/D,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAMhC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAK7B,2EAA2E;IAC3E,MAAM,IAAI,IAAI;IAId;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,UAAU,EAAE;CAKtD"}