@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.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/ambient.d.ts +80 -0
- package/dist/ambient.d.ts.map +1 -0
- package/dist/ambient.js +281 -0
- package/dist/ambient.js.map +1 -0
- package/dist/attachments.d.ts +149 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +451 -0
- package/dist/attachments.js.map +1 -0
- package/dist/audio.d.ts +80 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +170 -0
- package/dist/audio.js.map +1 -0
- package/dist/avatar.d.ts +111 -0
- package/dist/avatar.d.ts.map +1 -0
- package/dist/avatar.js +201 -0
- package/dist/avatar.js.map +1 -0
- package/dist/callServer.d.ts +151 -0
- package/dist/callServer.d.ts.map +1 -0
- package/dist/callServer.js +1097 -0
- package/dist/callServer.js.map +1 -0
- package/dist/callTools.d.ts +154 -0
- package/dist/callTools.d.ts.map +1 -0
- package/dist/callTools.js +255 -0
- package/dist/callTools.js.map +1 -0
- package/dist/chat.d.ts +226 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +574 -0
- package/dist/chat.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +86 -0
- package/dist/config.js.map +1 -0
- package/dist/consult.d.ts +147 -0
- package/dist/consult.d.ts.map +1 -0
- package/dist/consult.js +388 -0
- package/dist/consult.js.map +1 -0
- package/dist/delivery.d.ts +81 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +187 -0
- package/dist/delivery.js.map +1 -0
- package/dist/echoGuard.d.ts +56 -0
- package/dist/echoGuard.d.ts.map +1 -0
- package/dist/echoGuard.js +57 -0
- package/dist/echoGuard.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/fetch.d.ts +40 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +240 -0
- package/dist/fetch.js.map +1 -0
- package/dist/gate.d.ts +80 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +249 -0
- package/dist/gate.js.map +1 -0
- package/dist/handler.d.ts +283 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +4 -0
- package/dist/handler.js.map +1 -0
- package/dist/hmac.d.ts +32 -0
- package/dist/hmac.d.ts.map +1 -0
- package/dist/hmac.js +108 -0
- package/dist/hmac.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/lane.d.ts +92 -0
- package/dist/lane.d.ts.map +1 -0
- package/dist/lane.js +269 -0
- package/dist/lane.js.map +1 -0
- package/dist/lipsync.d.ts +136 -0
- package/dist/lipsync.d.ts.map +1 -0
- package/dist/lipsync.js +310 -0
- package/dist/lipsync.js.map +1 -0
- package/dist/log.d.ts +18 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +20 -0
- package/dist/log.js.map +1 -0
- package/dist/media.d.ts +43 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +190 -0
- package/dist/media.js.map +1 -0
- package/dist/minutes.d.ts +398 -0
- package/dist/minutes.d.ts.map +1 -0
- package/dist/minutes.js +805 -0
- package/dist/minutes.js.map +1 -0
- package/dist/outbound.d.ts +388 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/outbound.js +933 -0
- package/dist/outbound.js.map +1 -0
- package/dist/plugins/cartesia/agent.d.ts +58 -0
- package/dist/plugins/cartesia/agent.d.ts.map +1 -0
- package/dist/plugins/cartesia/agent.js +254 -0
- package/dist/plugins/cartesia/agent.js.map +1 -0
- package/dist/plugins/cartesia/cli.d.ts +3 -0
- package/dist/plugins/cartesia/cli.d.ts.map +1 -0
- package/dist/plugins/cartesia/cli.js +10 -0
- package/dist/plugins/cartesia/cli.js.map +1 -0
- package/dist/plugins/cartesia/config.d.ts +30 -0
- package/dist/plugins/cartesia/config.d.ts.map +1 -0
- package/dist/plugins/cartesia/config.js +29 -0
- package/dist/plugins/cartesia/config.js.map +1 -0
- package/dist/plugins/cartesia/handler.d.ts +40 -0
- package/dist/plugins/cartesia/handler.d.ts.map +1 -0
- package/dist/plugins/cartesia/handler.js +142 -0
- package/dist/plugins/cartesia/handler.js.map +1 -0
- package/dist/plugins/cartesia/index.d.ts +6 -0
- package/dist/plugins/cartesia/index.d.ts.map +1 -0
- package/dist/plugins/cartesia/index.js +54 -0
- package/dist/plugins/cartesia/index.js.map +1 -0
- package/dist/plugins/deepgram/agent.d.ts +60 -0
- package/dist/plugins/deepgram/agent.d.ts.map +1 -0
- package/dist/plugins/deepgram/agent.js +302 -0
- package/dist/plugins/deepgram/agent.js.map +1 -0
- package/dist/plugins/deepgram/cli.d.ts +3 -0
- package/dist/plugins/deepgram/cli.d.ts.map +1 -0
- package/dist/plugins/deepgram/cli.js +10 -0
- package/dist/plugins/deepgram/cli.js.map +1 -0
- package/dist/plugins/deepgram/config.d.ts +39 -0
- package/dist/plugins/deepgram/config.d.ts.map +1 -0
- package/dist/plugins/deepgram/config.js +66 -0
- package/dist/plugins/deepgram/config.js.map +1 -0
- package/dist/plugins/deepgram/handler.d.ts +87 -0
- package/dist/plugins/deepgram/handler.d.ts.map +1 -0
- package/dist/plugins/deepgram/handler.js +268 -0
- package/dist/plugins/deepgram/handler.js.map +1 -0
- package/dist/plugins/deepgram/index.d.ts +11 -0
- package/dist/plugins/deepgram/index.d.ts.map +1 -0
- package/dist/plugins/deepgram/index.js +76 -0
- package/dist/plugins/deepgram/index.js.map +1 -0
- package/dist/plugins/echo/cli.d.ts +3 -0
- package/dist/plugins/echo/cli.d.ts.map +1 -0
- package/dist/plugins/echo/cli.js +10 -0
- package/dist/plugins/echo/cli.js.map +1 -0
- package/dist/plugins/echo/index.d.ts +37 -0
- package/dist/plugins/echo/index.d.ts.map +1 -0
- package/dist/plugins/echo/index.js +61 -0
- package/dist/plugins/echo/index.js.map +1 -0
- package/dist/plugins/elevenlabs/agent.d.ts +72 -0
- package/dist/plugins/elevenlabs/agent.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/agent.js +305 -0
- package/dist/plugins/elevenlabs/agent.js.map +1 -0
- package/dist/plugins/elevenlabs/cli.d.ts +3 -0
- package/dist/plugins/elevenlabs/cli.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/cli.js +10 -0
- package/dist/plugins/elevenlabs/cli.js.map +1 -0
- package/dist/plugins/elevenlabs/config.d.ts +27 -0
- package/dist/plugins/elevenlabs/config.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/config.js +42 -0
- package/dist/plugins/elevenlabs/config.js.map +1 -0
- package/dist/plugins/elevenlabs/handler.d.ts +67 -0
- package/dist/plugins/elevenlabs/handler.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/handler.js +419 -0
- package/dist/plugins/elevenlabs/handler.js.map +1 -0
- package/dist/plugins/elevenlabs/index.d.ts +11 -0
- package/dist/plugins/elevenlabs/index.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/index.js +72 -0
- package/dist/plugins/elevenlabs/index.js.map +1 -0
- package/dist/plugins/livekit/cli.d.ts +3 -0
- package/dist/plugins/livekit/cli.d.ts.map +1 -0
- package/dist/plugins/livekit/cli.js +10 -0
- package/dist/plugins/livekit/cli.js.map +1 -0
- package/dist/plugins/livekit/config.d.ts +47 -0
- package/dist/plugins/livekit/config.d.ts.map +1 -0
- package/dist/plugins/livekit/config.js +46 -0
- package/dist/plugins/livekit/config.js.map +1 -0
- package/dist/plugins/livekit/handler.d.ts +59 -0
- package/dist/plugins/livekit/handler.d.ts.map +1 -0
- package/dist/plugins/livekit/handler.js +211 -0
- package/dist/plugins/livekit/handler.js.map +1 -0
- package/dist/plugins/livekit/index.d.ts +6 -0
- package/dist/plugins/livekit/index.d.ts.map +1 -0
- package/dist/plugins/livekit/index.js +68 -0
- package/dist/plugins/livekit/index.js.map +1 -0
- package/dist/plugins/livekit/room.d.ts +117 -0
- package/dist/plugins/livekit/room.d.ts.map +1 -0
- package/dist/plugins/livekit/room.js +449 -0
- package/dist/plugins/livekit/room.js.map +1 -0
- package/dist/plugins/openai/agent.d.ts +74 -0
- package/dist/plugins/openai/agent.d.ts.map +1 -0
- package/dist/plugins/openai/agent.js +264 -0
- package/dist/plugins/openai/agent.js.map +1 -0
- package/dist/plugins/openai/cli.d.ts +3 -0
- package/dist/plugins/openai/cli.d.ts.map +1 -0
- package/dist/plugins/openai/cli.js +10 -0
- package/dist/plugins/openai/cli.js.map +1 -0
- package/dist/plugins/openai/config.d.ts +29 -0
- package/dist/plugins/openai/config.d.ts.map +1 -0
- package/dist/plugins/openai/config.js +37 -0
- package/dist/plugins/openai/config.js.map +1 -0
- package/dist/plugins/openai/handler.d.ts +65 -0
- package/dist/plugins/openai/handler.d.ts.map +1 -0
- package/dist/plugins/openai/handler.js +297 -0
- package/dist/plugins/openai/handler.js.map +1 -0
- package/dist/plugins/openai/index.d.ts +6 -0
- package/dist/plugins/openai/index.d.ts.map +1 -0
- package/dist/plugins/openai/index.js +72 -0
- package/dist/plugins/openai/index.js.map +1 -0
- package/dist/plugins/openclaw/allowlist.d.ts +9 -0
- package/dist/plugins/openclaw/allowlist.d.ts.map +1 -0
- package/dist/plugins/openclaw/allowlist.js +11 -0
- package/dist/plugins/openclaw/allowlist.js.map +1 -0
- package/dist/plugins/openclaw/config.d.ts +50 -0
- package/dist/plugins/openclaw/config.d.ts.map +1 -0
- package/dist/plugins/openclaw/config.js +53 -0
- package/dist/plugins/openclaw/config.js.map +1 -0
- package/dist/plugins/openclaw/handler.d.ts +90 -0
- package/dist/plugins/openclaw/handler.d.ts.map +1 -0
- package/dist/plugins/openclaw/handler.js +146 -0
- package/dist/plugins/openclaw/handler.js.map +1 -0
- package/dist/plugins/openclaw/index.d.ts +55 -0
- package/dist/plugins/openclaw/index.d.ts.map +1 -0
- package/dist/plugins/openclaw/index.js +89 -0
- package/dist/plugins/openclaw/index.js.map +1 -0
- package/dist/plugins/openclaw/openclaw.plugin.json +74 -0
- package/dist/plugins/openclaw/package.json +14 -0
- package/dist/plugins/openclaw/realtime.d.ts +82 -0
- package/dist/plugins/openclaw/realtime.d.ts.map +1 -0
- package/dist/plugins/openclaw/realtime.js +238 -0
- package/dist/plugins/openclaw/realtime.js.map +1 -0
- package/dist/plugins/openclaw/runtime.d.ts +19 -0
- package/dist/plugins/openclaw/runtime.d.ts.map +1 -0
- package/dist/plugins/openclaw/runtime.js +160 -0
- package/dist/plugins/openclaw/runtime.js.map +1 -0
- package/dist/policy.d.ts +38 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +95 -0
- package/dist/policy.js.map +1 -0
- package/dist/protocol.d.ts +44 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +77 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocolRuntime.d.ts +19 -0
- package/dist/protocolRuntime.d.ts.map +1 -0
- package/dist/protocolRuntime.js +78 -0
- package/dist/protocolRuntime.js.map +1 -0
- package/dist/smoke.d.ts +65 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +293 -0
- package/dist/smoke.js.map +1 -0
- package/dist/startup.d.ts +74 -0
- package/dist/startup.d.ts.map +1 -0
- package/dist/startup.js +112 -0
- package/dist/startup.js.map +1 -0
- package/dist/tile.d.ts +101 -0
- package/dist/tile.d.ts.map +1 -0
- package/dist/tile.js +195 -0
- package/dist/tile.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/dist/vision.d.ts +160 -0
- package/dist/vision.d.ts.map +1 -0
- package/dist/vision.js +301 -0
- package/dist/vision.js.map +1 -0
- package/dist/visionTools.d.ts +237 -0
- package/dist/visionTools.d.ts.map +1 -0
- package/dist/visionTools.js +578 -0
- package/dist/visionTools.js.map +1 -0
- package/dist/voice.d.ts +143 -0
- package/dist/voice.d.ts.map +1 -0
- package/dist/voice.js +403 -0
- package/dist/voice.js.map +1 -0
- 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
|
package/dist/lane.js.map
ADDED
|
@@ -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"}
|