@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The relay: one Microsoft Teams call on one side, one LiveKit room on the other.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the provider plugins, there is no model here and no tools to
|
|
5
|
+
* answer. LiveKit is a room your own agent joins, so this side publishes the
|
|
6
|
+
* caller's voice, subscribes to the agent's, and relays call context as room
|
|
7
|
+
* data on two topics the agent can subscribe to.
|
|
8
|
+
*
|
|
9
|
+
* Your agent therefore needs no Microsoft Teams awareness at all. It sees a
|
|
10
|
+
* participant who talks, and optionally two topics that tell it who is calling
|
|
11
|
+
* and when to say goodbye.
|
|
12
|
+
*
|
|
13
|
+
* The Python twin is `standin.plugins.livekit`, which additionally arms
|
|
14
|
+
* itself from inside a LiveKit worker. This one is explicit: you build the
|
|
15
|
+
* handler yourself.
|
|
16
|
+
*/
|
|
17
|
+
import { type GateDecision } from "../../gate.js";
|
|
18
|
+
import type { CallHandler, CallSession } from "../../handler.js";
|
|
19
|
+
import { type LiveKitConfig } from "./config.js";
|
|
20
|
+
import { type RoomHandlers, type RoomPort } from "./room.js";
|
|
21
|
+
/** How the handler reaches LiveKit. Replaceable so tests need no framework. */
|
|
22
|
+
export type Connect = (config: LiveKitConfig, callId: string, metadata: Record<string, string>, handlers: RoomHandlers) => Promise<RoomPort>;
|
|
23
|
+
/** Options for {@link LiveKitHandler}. */
|
|
24
|
+
export interface LiveKitHandlerOptions {
|
|
25
|
+
/**
|
|
26
|
+
* What addresses the assistant in a meeting.
|
|
27
|
+
*
|
|
28
|
+
* Empty by default, which leaves the group gate inert: an agent that answers
|
|
29
|
+
* everything keeps answering everything, and nothing changes for it.
|
|
30
|
+
*/
|
|
31
|
+
wakePhrases?: readonly string[];
|
|
32
|
+
config?: LiveKitConfig;
|
|
33
|
+
connect?: Connect;
|
|
34
|
+
}
|
|
35
|
+
/** One Microsoft Teams call answered by one LiveKit agent. */
|
|
36
|
+
export declare class LiveKitHandler implements CallHandler {
|
|
37
|
+
#private;
|
|
38
|
+
constructor(options?: LiveKitHandlerOptions);
|
|
39
|
+
/**
|
|
40
|
+
* What the gate decided about the caller's last finished turn.
|
|
41
|
+
*
|
|
42
|
+
* `undefined` until a turn has been decided, and on a call where transcripts
|
|
43
|
+
* never arrive. An agent that wants to stay out of a meeting it was not
|
|
44
|
+
* addressed in reads this; one that does not can ignore it entirely.
|
|
45
|
+
*/
|
|
46
|
+
get lastDecision(): GateDecision | undefined;
|
|
47
|
+
onStart(session: CallSession): Promise<void>;
|
|
48
|
+
/** The caller's voice, published into the room as a track. */
|
|
49
|
+
onCallerAudio(pcm: Buffer): Promise<void>;
|
|
50
|
+
/** Call context, published on a topic the agent can subscribe to. */
|
|
51
|
+
onContext(text: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* The closing line, published on its own topic so an agent can tell it apart
|
|
54
|
+
* from ordinary context and interrupt itself to say it.
|
|
55
|
+
*/
|
|
56
|
+
onGoodbye(text: string): Promise<void>;
|
|
57
|
+
aclose(): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/plugins/livekit/handler.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIjE,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,WAAW,CAAC;AAKnB,+EAA+E;AAC/E,MAAM,MAAM,OAAO,GAAG,CACpB,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,QAAQ,EAAE,YAAY,KACnB,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB,0CAA0C;AAC1C,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,8DAA8D;AAC9D,qBAAa,cAAe,YAAW,WAAW;;gBAcpC,OAAO,GAAE,qBAA0B;IAQ/C;;;;;;OAMG;IACH,IAAI,YAAY,IAAI,YAAY,GAAG,SAAS,CAE3C;IAEK,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAgFlD,8DAA8D;IACxD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B/C,qEAAqE;IAC/D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5C;;;OAGG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CA0B9B"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* The relay: one Microsoft Teams call on one side, one LiveKit room on the other.
|
|
5
|
+
*
|
|
6
|
+
* Unlike the provider plugins, there is no model here and no tools to
|
|
7
|
+
* answer. LiveKit is a room your own agent joins, so this side publishes the
|
|
8
|
+
* caller's voice, subscribes to the agent's, and relays call context as room
|
|
9
|
+
* data on two topics the agent can subscribe to.
|
|
10
|
+
*
|
|
11
|
+
* Your agent therefore needs no Microsoft Teams awareness at all. It sees a
|
|
12
|
+
* participant who talks, and optionally two topics that tell it who is calling
|
|
13
|
+
* and when to say goodbye.
|
|
14
|
+
*
|
|
15
|
+
* The Python twin is `standin.plugins.livekit`, which additionally arms
|
|
16
|
+
* itself from inside a LiveKit worker. This one is explicit: you build the
|
|
17
|
+
* handler yourself.
|
|
18
|
+
*/
|
|
19
|
+
import { GroupGate, isAddressed } from "../../gate.js";
|
|
20
|
+
import { logger } from "../../log.js";
|
|
21
|
+
import { StartupBuffer } from "../../startup.js";
|
|
22
|
+
import { TileStream } from "../../tile.js";
|
|
23
|
+
import { liveKitConfigFromEnv } from "./config.js";
|
|
24
|
+
import { TOPIC_CONTEXT, TOPIC_GOODBYE, contextPayload, connectRoom, } from "./room.js";
|
|
25
|
+
/** Caller audio that arrives while the room is still being joined. */
|
|
26
|
+
const MAX_PENDING_AUDIO = 200;
|
|
27
|
+
/** One Microsoft Teams call answered by one LiveKit agent. */
|
|
28
|
+
export class LiveKitHandler {
|
|
29
|
+
#config;
|
|
30
|
+
#connect;
|
|
31
|
+
#call;
|
|
32
|
+
#room;
|
|
33
|
+
#closed = false;
|
|
34
|
+
// Holds BOTH the caller's first words and the first context, so neither
|
|
35
|
+
// is lost while the provider is still connecting.
|
|
36
|
+
#pending = new StartupBuffer();
|
|
37
|
+
#tile;
|
|
38
|
+
#stopRelay;
|
|
39
|
+
#gate;
|
|
40
|
+
#lastDecision;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.#config = options.config ?? liveKitConfigFromEnv();
|
|
43
|
+
this.#connect = options.connect ?? connectRoom;
|
|
44
|
+
this.#wakePhrases = options.wakePhrases ?? [];
|
|
45
|
+
}
|
|
46
|
+
#wakePhrases;
|
|
47
|
+
/**
|
|
48
|
+
* What the gate decided about the caller's last finished turn.
|
|
49
|
+
*
|
|
50
|
+
* `undefined` until a turn has been decided, and on a call where transcripts
|
|
51
|
+
* never arrive. An agent that wants to stay out of a meeting it was not
|
|
52
|
+
* addressed in reads this; one that does not can ignore it entirely.
|
|
53
|
+
*/
|
|
54
|
+
get lastDecision() {
|
|
55
|
+
return this.#lastDecision;
|
|
56
|
+
}
|
|
57
|
+
async onStart(session) {
|
|
58
|
+
this.#call = session;
|
|
59
|
+
// Armed from the call's own thread. On a one-to-one call it is inert, so a
|
|
60
|
+
// plugin that never configures a wake phrase behaves exactly as before.
|
|
61
|
+
this.#gate = new GroupGate({
|
|
62
|
+
wakePhrases: this.#wakePhrases,
|
|
63
|
+
threadId: session.start.threadId ?? "",
|
|
64
|
+
});
|
|
65
|
+
const caller = session.start.caller;
|
|
66
|
+
// Job metadata is how the agent learns who is calling. Absent fields stay
|
|
67
|
+
// absent rather than becoming a shared default, so two anonymous callers
|
|
68
|
+
// never look like the same person.
|
|
69
|
+
//
|
|
70
|
+
// The KEY NAMES are a cross-language contract, not a local choice. The
|
|
71
|
+
// Python SDK's CallInfo.from_job reads exactly these, and returns a blank
|
|
72
|
+
// record for metadata in any other shape, so an agent dispatched from here
|
|
73
|
+
// and written in Python would otherwise see no caller at all.
|
|
74
|
+
const metadata = {
|
|
75
|
+
source: "msteams",
|
|
76
|
+
call_id: session.callId,
|
|
77
|
+
call_direction: session.start.direction,
|
|
78
|
+
};
|
|
79
|
+
if (caller.displayName)
|
|
80
|
+
metadata.caller_name = caller.displayName;
|
|
81
|
+
if (caller.aadId)
|
|
82
|
+
metadata.user_id = caller.aadId;
|
|
83
|
+
if (caller.tenantId)
|
|
84
|
+
metadata.tenant_id = caller.tenantId;
|
|
85
|
+
if (session.start.threadId)
|
|
86
|
+
metadata.thread_id = session.start.threadId;
|
|
87
|
+
let room;
|
|
88
|
+
try {
|
|
89
|
+
room = await this.#connect(this.#config, session.callId, metadata, {
|
|
90
|
+
onAgentAudio: (pcm) => this.#onAgentAudio(pcm),
|
|
91
|
+
onClosed: (reason) => this.#onClosed(reason),
|
|
92
|
+
// The agent's own audio is what "answered" means. Without this the
|
|
93
|
+
// core reaper would end a call an agent HAS taken but that is still
|
|
94
|
+
// listening.
|
|
95
|
+
onAnswered: () => session.markAnswered(),
|
|
96
|
+
onCallerTranscript: (text, final) => this.#onCallerTranscript(text, final),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logger.error(`standin: could not join the LiveKit room: ${String(err)}`);
|
|
101
|
+
await session.end("agent-unavailable");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// The call can end during the join above.
|
|
105
|
+
if (this.#closed) {
|
|
106
|
+
await room.aclose();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.#room = room;
|
|
110
|
+
await this.#pending.release((pcm) => room.sendCallerAudio(pcm), (text) => void room.publish(TOPIC_CONTEXT, contextPayload(text)));
|
|
111
|
+
if (this.#pending.dropped.audio || this.#pending.dropped.context) {
|
|
112
|
+
logger.info("standin: the caller outran the agent starting up; some early input was dropped");
|
|
113
|
+
}
|
|
114
|
+
// The agent's own face on the tile, when it publishes one. On by default:
|
|
115
|
+
// an agent that publishes video almost always means it for the caller.
|
|
116
|
+
if (this.#config.tileVideo !== "off" && room.startAvatarRelay) {
|
|
117
|
+
const tile = new TileStream(session, { fps: this.#config.tileVideoFps });
|
|
118
|
+
await tile.start();
|
|
119
|
+
this.#tile = tile;
|
|
120
|
+
try {
|
|
121
|
+
this.#stopRelay = await room.startAvatarRelay({
|
|
122
|
+
offerRgb: (rgb, width, height) => tile.offerRgb(rgb, width, height),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
// A tile that will not start is not a call that should fail.
|
|
127
|
+
logger.warn(`standin: could not start the avatar relay: ${String(err)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** The caller's voice, published into the room as a track. */
|
|
132
|
+
async onCallerAudio(pcm) {
|
|
133
|
+
const room = this.#room;
|
|
134
|
+
if (room === undefined || !room.isOpen) {
|
|
135
|
+
this.#pending.audio(pcm);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
room.sendCallerAudio(pcm);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* What the caller said, fed to the group gate.
|
|
142
|
+
*
|
|
143
|
+
* A partial may only NOTICE the wake phrase, never decide a turn. Deciding on
|
|
144
|
+
* a partial returns "do not respond" for a turn that is about to address the
|
|
145
|
+
* assistant, and the answer to a turn that DID address it gets cut.
|
|
146
|
+
*
|
|
147
|
+
* The text is never logged or kept: it is read for this decision and dropped.
|
|
148
|
+
*/
|
|
149
|
+
#onCallerTranscript(text, final) {
|
|
150
|
+
const gate = this.#gate;
|
|
151
|
+
if (gate === undefined)
|
|
152
|
+
return;
|
|
153
|
+
if (!final) {
|
|
154
|
+
// Stamps the window when the phrase is heard mid-turn, and cannot close
|
|
155
|
+
// it: the window is a timestamp, so a missed phrase self-heals by clock
|
|
156
|
+
// rather than stranding the agent silent for the meeting.
|
|
157
|
+
if (isAddressed(text, gate.wakePhrases))
|
|
158
|
+
gate.decide(text, Date.now());
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.#lastDecision = gate.decide(text, Date.now());
|
|
162
|
+
}
|
|
163
|
+
/** Call context, published on a topic the agent can subscribe to. */
|
|
164
|
+
async onContext(text) {
|
|
165
|
+
const room = this.#room;
|
|
166
|
+
if (room === undefined) {
|
|
167
|
+
// Held rather than dropped: a data packet reaches only participants
|
|
168
|
+
// connected at that instant, and the first context lands before the
|
|
169
|
+
// dispatched agent has joined.
|
|
170
|
+
this.#pending.context(text);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
await room.publish(TOPIC_CONTEXT, contextPayload(text));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The closing line, published on its own topic so an agent can tell it apart
|
|
177
|
+
* from ordinary context and interrupt itself to say it.
|
|
178
|
+
*/
|
|
179
|
+
async onGoodbye(text) {
|
|
180
|
+
await this.#room?.publish(TOPIC_GOODBYE, contextPayload(text));
|
|
181
|
+
}
|
|
182
|
+
async aclose() {
|
|
183
|
+
this.#closed = true;
|
|
184
|
+
this.#stopRelay?.();
|
|
185
|
+
this.#stopRelay = undefined;
|
|
186
|
+
const tile = this.#tile;
|
|
187
|
+
this.#tile = undefined;
|
|
188
|
+
if (tile !== undefined)
|
|
189
|
+
await tile.aclose();
|
|
190
|
+
const room = this.#room;
|
|
191
|
+
this.#room = undefined;
|
|
192
|
+
if (room !== undefined) {
|
|
193
|
+
try {
|
|
194
|
+
await room.aclose();
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Teardown must not throw: the slot is freed either way.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async #onAgentAudio(pcm) {
|
|
202
|
+
if (pcm.length > 0)
|
|
203
|
+
await this.#call?.sendAudio(pcm);
|
|
204
|
+
}
|
|
205
|
+
async #onClosed(reason) {
|
|
206
|
+
logger.info(`standin: the LiveKit room ended (${reason})`);
|
|
207
|
+
if (!this.#closed)
|
|
208
|
+
await this.#call?.end("agent-disconnected");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../src/plugins/livekit/handler.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAqB,MAAM,eAAe,CAAC;AAE1E,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAsB,MAAM,aAAa,CAAC;AACvE,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,GAGZ,MAAM,WAAW,CAAC;AAEnB,sEAAsE;AACtE,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAuB9B,8DAA8D;AAC9D,MAAM,OAAO,cAAc;IACzB,OAAO,CAAgB;IACvB,QAAQ,CAAU;IAClB,KAAK,CAA0B;IAC/B,KAAK,CAAuB;IAC5B,OAAO,GAAG,KAAK,CAAC;IAChB,wEAAwE;IACxE,kDAAkD;IACzC,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;IACxC,KAAK,CAAyB;IAC9B,UAAU,CAA2B;IACrC,KAAK,CAAwB;IAC7B,aAAa,CAA2B;IAExC,YAAY,UAAiC,EAAE;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,oBAAoB,EAAE,CAAC;QACxD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,IAAI,WAAW,CAAC;QAC/C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;IAChD,CAAC;IAEQ,YAAY,CAAoB;IAEzC;;;;;;OAMG;IACH,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAoB;QAChC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;QACrB,2EAA2E;QAC3E,wEAAwE;QACxE,IAAI,CAAC,KAAK,GAAG,IAAI,SAAS,CAAC;YACzB,WAAW,EAAE,IAAI,CAAC,YAAY;YAC9B,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE;SACvC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;QACpC,0EAA0E;QAC1E,yEAAyE;QACzE,mCAAmC;QACnC,EAAE;QACF,uEAAuE;QACvE,0EAA0E;QAC1E,2EAA2E;QAC3E,8DAA8D;QAC9D,MAAM,QAAQ,GAA2B;YACvC,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,OAAO,CAAC,MAAM;YACvB,cAAc,EAAE,OAAO,CAAC,KAAK,CAAC,SAAS;SACxC,CAAC;QACF,IAAI,MAAM,CAAC,WAAW;YAAE,QAAQ,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QAClE,IAAI,MAAM,CAAC,KAAK;YAAE,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC;QAClD,IAAI,MAAM,CAAC,QAAQ;YAAE,QAAQ,CAAC,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC1D,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ;YAAE,QAAQ,CAAC,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;QAExE,IAAI,IAAc,CAAC;QACnB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE;gBACjE,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;gBAC9C,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;gBAC5C,mEAAmE;gBACnE,oEAAoE;gBACpE,aAAa;gBACb,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE;gBACxC,kBAAkB,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAClC,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC;aACxC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,6CAA6C,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzE,MAAM,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QAED,0CAA0C;QAC1C,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CACzB,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,EAC1C,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CACzE,CAAC;QACF,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACjE,MAAM,CAAC,IAAI,CACT,gFAAgF,CACjF,CAAC;QACJ,CAAC;QAED,0EAA0E;QAC1E,uEAAuE;QACvE,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,KAAK,KAAK,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC9D,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;YACzE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC;oBAC5C,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC;iBACpE,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,6DAA6D;gBAC7D,MAAM,CAAC,IAAI,CACT,8CAA8C,MAAM,CAAC,GAAG,CAAC,EAAE,CAC5D,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,KAAK,CAAC,aAAa,CAAC,GAAW;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACvC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACzB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED;;;;;;;;OAQG;IACH,mBAAmB,CAAC,IAAY,EAAE,KAAc;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO;QAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,wEAAwE;YACxE,wEAAwE;YACxE,0DAA0D;YAC1D,IAAI,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,qEAAqE;IACrE,KAAK,CAAC,SAAS,CAAC,IAAY;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,oEAAoE;YACpE,oEAAoE;YACpE,+BAA+B;YAC/B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,SAAS,CAAC,IAAY;QAC1B,MAAM,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACpB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC;gBACP,yDAAyD;YAC3D,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,GAAW;QAC7B,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAAc;QAC5B,MAAM,CAAC,IAAI,CAAC,oCAAoC,MAAM,GAAG,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACjE,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { liveKitConfigFromEnv, type LiveKitConfig } from "./config.js";
|
|
2
|
+
export { LiveKitHandler, type Connect, type LiveKitHandlerOptions, } from "./handler.js";
|
|
3
|
+
export { TOPIC_CONTEXT, TOPIC_GOODBYE, contextPayload, connectRoom, type RoomHandlers, type RoomPort, } from "./room.js";
|
|
4
|
+
/** Answer Microsoft Teams calls with LiveKit until the process is interrupted. */
|
|
5
|
+
export declare function serve(): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/livekit/index.ts"],"names":[],"mappings":"AAmDA,OAAO,EAAE,oBAAoB,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EACL,cAAc,EACd,KAAK,OAAO,EACZ,KAAK,qBAAqB,GAC3B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,QAAQ,GACd,MAAM,WAAW,CAAC;AAEnB,kFAAkF;AAClF,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAgB3C"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Answer Microsoft Teams calls with a LiveKit agent.
|
|
5
|
+
*
|
|
6
|
+
* StandIn answers the Microsoft Teams call and dials this worker. This
|
|
7
|
+
* plugin answers that dial, creates one LiveKit room per call, dispatches
|
|
8
|
+
* your agent into it, and relays the audio both ways.
|
|
9
|
+
*
|
|
10
|
+
* Your agent needs no Microsoft Teams awareness: it sees a participant who
|
|
11
|
+
* talks. Who is calling arrives as job metadata, and call context arrives on two
|
|
12
|
+
* room data topics, `msteams.context` and `msteams.goodbye`, which are the same
|
|
13
|
+
* strings the Python SDK uses.
|
|
14
|
+
*
|
|
15
|
+
* This is the one plugin with dependencies of its own, because joining a
|
|
16
|
+
* room means running LiveKit's client:
|
|
17
|
+
*
|
|
18
|
+
* ```bash
|
|
19
|
+
* npm install @komaa/standin-sdk @livekit/rtc-node livekit-server-sdk
|
|
20
|
+
*
|
|
21
|
+
* export STANDIN_SECRET=... # your StandIn connection secret
|
|
22
|
+
* export LIVEKIT_URL=wss://...
|
|
23
|
+
* export LIVEKIT_API_KEY=...
|
|
24
|
+
* export LIVEKIT_API_SECRET=...
|
|
25
|
+
* export LIVEKIT_AGENT_NAME=standin-msteams
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { CallServer } from "@komaa/standin-sdk";
|
|
30
|
+
* import { LiveKitHandler } from "@komaa/standin-sdk/livekit";
|
|
31
|
+
*
|
|
32
|
+
* const server = new CallServer({ handlerFactory: () => new LiveKitHandler() });
|
|
33
|
+
* await server.start();
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* This plugin relays AUDIO. It does not put the agent's own avatar video on
|
|
37
|
+
* the bot's tile: StandIn renders the avatar, and a LiveKit-side video relay is
|
|
38
|
+
* not implemented here. `CallSession.express` and `sendSpeechMarks` still drive
|
|
39
|
+
* the avatar, and the caller hears the agent either way.
|
|
40
|
+
*
|
|
41
|
+
* Those two packages are optional peers: they are imported only when this
|
|
42
|
+
* module is reached, so the core and every other plugin install without
|
|
43
|
+
* them. Writing the agent in Python instead? `standin.plugins.livekit`
|
|
44
|
+
* does the same thing and additionally arms itself from inside your worker.
|
|
45
|
+
*/
|
|
46
|
+
import { CallServer } from "../../index.js";
|
|
47
|
+
import { LiveKitHandler } from "./handler.js";
|
|
48
|
+
import { liveKitConfigFromEnv } from "./config.js";
|
|
49
|
+
export { liveKitConfigFromEnv } from "./config.js";
|
|
50
|
+
export { LiveKitHandler, } from "./handler.js";
|
|
51
|
+
export { TOPIC_CONTEXT, TOPIC_GOODBYE, contextPayload, connectRoom, } from "./room.js";
|
|
52
|
+
/** Answer Microsoft Teams calls with LiveKit until the process is interrupted. */
|
|
53
|
+
export async function serve() {
|
|
54
|
+
const config = liveKitConfigFromEnv();
|
|
55
|
+
const server = new CallServer({
|
|
56
|
+
handlerFactory: () => new LiveKitHandler({ config }),
|
|
57
|
+
});
|
|
58
|
+
await server.start();
|
|
59
|
+
await new Promise((resolve) => {
|
|
60
|
+
const stop = () => {
|
|
61
|
+
resolve();
|
|
62
|
+
};
|
|
63
|
+
process.once("SIGINT", stop);
|
|
64
|
+
process.once("SIGTERM", stop);
|
|
65
|
+
});
|
|
66
|
+
await server.aclose();
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/plugins/livekit/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,oBAAoB,EAAsB,MAAM,aAAa,CAAC;AACvE,OAAO,EACL,cAAc,GAGf,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,GAGZ,MAAM,WAAW,CAAC;AAEnB,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC;QAC5B,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;KACrD,CAAC,CAAC;IACH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IAErB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,MAAM,IAAI,GAAG,GAAS,EAAE;YACtB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { LiveKitConfig } from "./config.js";
|
|
2
|
+
/** Non-interrupting call context, published into the room as data. */
|
|
3
|
+
export declare const TOPIC_CONTEXT = "msteams.context";
|
|
4
|
+
/** The closing line StandIn wants spoken before the call ends. */
|
|
5
|
+
export declare const TOPIC_GOODBYE = "msteams.goodbye";
|
|
6
|
+
/** LiveKit's own transcription topic. Both sides of the call publish on it. */
|
|
7
|
+
export declare const TOPIC_TRANSCRIPTION = "lk.transcription";
|
|
8
|
+
/** Which track a transcript stream is OF. Absent on some publishers. */
|
|
9
|
+
export declare const ATTRIBUTE_TRANSCRIBED_TRACK_ID = "lk.transcribed_track_id";
|
|
10
|
+
/** Marks the closing header of a finished turn. */
|
|
11
|
+
export declare const ATTRIBUTE_TRANSCRIPTION_FINAL = "lk.transcription_final";
|
|
12
|
+
/** The identity this plugin joins under. */
|
|
13
|
+
export declare const LOCAL_IDENTITY = "standin-msteams";
|
|
14
|
+
/** What a LiveKit text stream gives us, narrowed to what is read here. */
|
|
15
|
+
export interface TextStreamReader {
|
|
16
|
+
readonly info?: {
|
|
17
|
+
readonly attributes?: Record<string, string>;
|
|
18
|
+
};
|
|
19
|
+
[Symbol.asyncIterator](): AsyncIterator<string>;
|
|
20
|
+
}
|
|
21
|
+
/** How a transcript stream is classified and delivered. */
|
|
22
|
+
export interface TranscriptRoute {
|
|
23
|
+
callerTrackSid: () => string | undefined;
|
|
24
|
+
localIdentity: string;
|
|
25
|
+
agentIdentity: () => string | undefined;
|
|
26
|
+
isClosed: () => boolean;
|
|
27
|
+
deliver: (text: string, final: boolean) => void | Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether this transcript is of the CALLER rather than of the agent.
|
|
31
|
+
*
|
|
32
|
+
* The track id is exact when present. Without it, the sender identity is the
|
|
33
|
+
* fallback: livekit publishes a participant's transcript under that
|
|
34
|
+
* participant's identity, and ours is the caller's audio.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isCallerTranscript(attributes: Record<string, string> | undefined, senderIdentity: string | undefined, route: {
|
|
37
|
+
callerTrackSid?: string;
|
|
38
|
+
localIdentity: string;
|
|
39
|
+
agentIdentity?: string;
|
|
40
|
+
}): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Read one transcript stream to its end and report the caller's words.
|
|
43
|
+
*
|
|
44
|
+
* Every stream is drained, including one this call ignores: an abandoned reader
|
|
45
|
+
* keeps its subscription for the life of the process, and the agent publishes
|
|
46
|
+
* one stream per turn.
|
|
47
|
+
*
|
|
48
|
+
* Each yield is the cumulative transcript so far, so every yield is delivered as
|
|
49
|
+
* a partial and the last one as final. A partial exists so a wake phrase is
|
|
50
|
+
* noticed as it is said; only a finished turn is a turn.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readTranscript(reader: TextStreamReader, senderIdentity: string | undefined, route: TranscriptRoute): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* What goes on a topic: a JSON object carrying `text`, never the bare string.
|
|
55
|
+
*
|
|
56
|
+
* This is a cross-language contract. The Python SDK's `TeamsCall._on_data`
|
|
57
|
+
* json-decodes the packet and requires a dict with a `text` string, so a bare
|
|
58
|
+
* string is dropped in silence at the far end: every context sentence and the
|
|
59
|
+
* goodbye would reach nothing.
|
|
60
|
+
*/
|
|
61
|
+
export declare function contextPayload(text: string): string;
|
|
62
|
+
/** A sink the avatar relay pushes encoded frames into. */
|
|
63
|
+
export interface TileSink {
|
|
64
|
+
offerRgb(rgb: Buffer, width: number, height: number): void;
|
|
65
|
+
}
|
|
66
|
+
/** What the handler needs from a joined room. Tests substitute their own. */
|
|
67
|
+
export interface RoomPort {
|
|
68
|
+
/**
|
|
69
|
+
* Start relaying the agent's own avatar video onto the bot's tile.
|
|
70
|
+
*
|
|
71
|
+
* Optional, and absent on a room that has no video to relay. It returns a
|
|
72
|
+
* stop function rather than taking a lifetime, because a track swap must
|
|
73
|
+
* replace the drain rather than stack a second one on top of it.
|
|
74
|
+
*/
|
|
75
|
+
startAvatarRelay?(sink: TileSink): Promise<() => void>;
|
|
76
|
+
readonly isOpen: boolean;
|
|
77
|
+
/** The identity of the participant whose audio is relayed to the caller. */
|
|
78
|
+
readonly agentIdentity: string | undefined;
|
|
79
|
+
/** Publish one frame of the caller's voice into the room. */
|
|
80
|
+
sendCallerAudio(pcm: Buffer): void;
|
|
81
|
+
/**
|
|
82
|
+
* Publish one payload for the agent to read, verbatim.
|
|
83
|
+
*
|
|
84
|
+
* The caller builds the payload. Its SHAPE is a cross-language contract, not
|
|
85
|
+
* a transport detail: see {@link contextPayload}.
|
|
86
|
+
*/
|
|
87
|
+
publish(topic: string, payload: string): Promise<void>;
|
|
88
|
+
aclose(): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
/** What a joined room tells the handler. */
|
|
91
|
+
export interface RoomHandlers {
|
|
92
|
+
/** Agent audio at the wire rate, ready for the caller. */
|
|
93
|
+
onAgentAudio: (pcm: Buffer) => void | Promise<void>;
|
|
94
|
+
/** The room or the agent is gone, with the reason. */
|
|
95
|
+
onClosed: (reason: string) => void | Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* What the caller said, as it is said.
|
|
98
|
+
*
|
|
99
|
+
* Called with the transcript so far and whether the turn has finished. A
|
|
100
|
+
* partial exists so a wake phrase is noticed as it is spoken; only a finished
|
|
101
|
+
* turn is a turn, and a partial must never be able to close the follow-up
|
|
102
|
+
* window.
|
|
103
|
+
*
|
|
104
|
+
* The text is for deciding whether to answer. It is never logged or stored.
|
|
105
|
+
*/
|
|
106
|
+
onCallerTranscript?: (text: string, final: boolean) => void | Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* An agent has actually taken the call.
|
|
109
|
+
*
|
|
110
|
+
* Fired on the agent's first AUDIO track, not on a participant connecting:
|
|
111
|
+
* monitors, recorders and avatar workers all connect, and none of them is an
|
|
112
|
+
* agent answering.
|
|
113
|
+
*/
|
|
114
|
+
onAnswered?: () => void;
|
|
115
|
+
}
|
|
116
|
+
export declare function connectRoom(config: LiveKitConfig, callId: string, metadata: Record<string, string>, handlers: RoomHandlers): Promise<RoomPort>;
|
|
117
|
+
//# sourceMappingURL=room.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"room.d.ts","sourceRoot":"","sources":["../../../src/plugins/livekit/room.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,sEAAsE;AACtE,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C,kEAAkE;AAClE,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAEtD,wEAAwE;AACxE,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AAExE,mDAAmD;AACnD,eAAO,MAAM,6BAA6B,2BAA2B,CAAC;AAEtE,4CAA4C;AAC5C,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD,0EAA0E;AAC1E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACjE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC;CACjD;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC9C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,KAAK,EAAE;IACL,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACA,OAAO,CAaT;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,gBAAgB,EACxB,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAKD,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5D;AAED,6EAA6E;AAC7E,MAAM,WAAW,QAAQ;IACvB;;;;;;OAMG;IACH,gBAAgB,CAAC,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,4EAA4E;IAC5E,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,6DAA6D;IAC7D,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,sDAAsD;IACtD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5E;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AA0CD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,QAAQ,CAAC,CA8WnB"}
|