@memberjunction/livekit-room-server 0.0.1 → 5.42.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/README.md +75 -28
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/livekit-agent-room-coordinator.d.ts +176 -0
- package/dist/livekit-agent-room-coordinator.d.ts.map +1 -0
- package/dist/livekit-agent-room-coordinator.js +218 -0
- package/dist/livekit-agent-room-coordinator.js.map +1 -0
- package/dist/livekit-egress-service.d.ts +70 -0
- package/dist/livekit-egress-service.d.ts.map +1 -0
- package/dist/livekit-egress-service.js +68 -0
- package/dist/livekit-egress-service.js.map +1 -0
- package/dist/livekit-token-service.d.ts +100 -0
- package/dist/livekit-token-service.d.ts.map +1 -0
- package/dist/livekit-token-service.js +97 -0
- package/dist/livekit-token-service.js.map +1 -0
- package/package.json +34 -7
package/README.md
CHANGED
|
@@ -1,45 +1,92 @@
|
|
|
1
1
|
# @memberjunction/livekit-room-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Server-side LiveKit support for MemberJunction: scoped **access-token minting**, the **agent-room
|
|
4
|
+
session-start coordinator**, and **recording (egress)** control. This is the seam between the MJ realtime
|
|
5
|
+
bridge and the browser LiveKit UI.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
```
|
|
8
|
+
browser ── GraphQL ──► RealtimeBridgeResolver (MJServer)
|
|
9
|
+
│
|
|
10
|
+
▼
|
|
11
|
+
@memberjunction/livekit-room-server ← you are here
|
|
12
|
+
├─ LiveKitTokenService mint client + bot tokens (livekit-server-sdk)
|
|
13
|
+
├─ LiveKitAgentRoomCoordinator open realtime session → AIBridgeEngine.StartBridgeSession
|
|
14
|
+
└─ LiveKitEgressService start/stop room recording
|
|
15
|
+
```
|
|
6
16
|
|
|
7
|
-
|
|
17
|
+
## Why it exists
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
The realtime bridge already joins the **agent bot** to a LiveKit room with an MJ-minted token. Two gaps
|
|
20
|
+
remained: (1) a **human** browser participant needs its own scoped token for the same room, and (2) nothing
|
|
21
|
+
outside the bridge package opened a realtime session and called `StartBridgeSession`. This package closes
|
|
22
|
+
both.
|
|
10
23
|
|
|
11
|
-
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@memberjunction/livekit-room-server`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
24
|
+
## Configuration
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
Credentials resolve from explicit config or environment variables (never inline secrets):
|
|
17
27
|
|
|
18
|
-
|
|
28
|
+
```bash
|
|
29
|
+
LIVEKIT_URL=wss://livekit.myorg.com
|
|
30
|
+
LIVEKIT_API_KEY=...
|
|
31
|
+
LIVEKIT_API_SECRET=...
|
|
32
|
+
```
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
> **Where the LiveKit server comes from — see [`docker/livekit/README.md`](../../docker/livekit/README.md) (canonical setup guide):**
|
|
35
|
+
> - **Local dev:** spin up a throwaway LiveKit server with matching dev creds — Docker (`docker compose up -d`) for
|
|
36
|
+
> browser-only testing, or native `livekit-server --config docker/livekit/livekit.yaml` for the agent-bot path on
|
|
37
|
+
> macOS. Then `LIVEKIT_URL=ws://localhost:7880` / `LIVEKIT_API_KEY=devkey` / `LIVEKIT_API_SECRET=mj-local-dev-livekit-secret-0123456789`.
|
|
38
|
+
> - **Production / real-world:** use [**LiveKit Cloud**](https://cloud.livekit.io) — a hosted `wss://…livekit.cloud`
|
|
39
|
+
> URL + API key + secret to drop into `.env`, no server to run (or a properly-networked self-host).
|
|
21
40
|
|
|
22
|
-
|
|
41
|
+
## Token minting (fully functional)
|
|
23
42
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
43
|
+
```typescript
|
|
44
|
+
import { LiveKitTokenService } from '@memberjunction/livekit-room-server';
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
const tokens = new LiveKitTokenService(); // reads env, or pass { ServerUrl, ApiKey, ApiSecret }
|
|
47
|
+
const human = await tokens.MintClientToken('support-42', 'user-abc', 'Amith');
|
|
48
|
+
// → { ServerUrl, Token, Identity, RoomName } — hand Token to <mj-livekit-room>
|
|
49
|
+
```
|
|
30
50
|
|
|
31
|
-
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
51
|
+
## Agent-room session-start harness
|
|
36
52
|
|
|
37
|
-
|
|
53
|
+
`LiveKitAgentRoomCoordinator` is a `BaseSingleton`. It mints the bot token, opens the realtime model
|
|
54
|
+
session via an injectable factory seam, and bridges it into the room:
|
|
38
55
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
56
|
+
```typescript
|
|
57
|
+
import { LiveKitAgentRoomCoordinator } from '@memberjunction/livekit-room-server';
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
// At deployment/startup, bind the realtime-session factory (resolves a BaseRealtimeModel → IRealtimeSession):
|
|
60
|
+
LiveKitAgentRoomCoordinator.Instance.SetSessionFactory(async (ctx) => openRealtimeSession(ctx));
|
|
44
61
|
|
|
45
|
-
|
|
62
|
+
// Then, per request:
|
|
63
|
+
const session = await LiveKitAgentRoomCoordinator.Instance.StartAgentRoomSession({
|
|
64
|
+
AgentSessionID, RoomName: 'support-42', AgentName: 'Sage', TurnMode: 'Passive', ContextUser,
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The factory is a seam (like `LiveKitBridge.SetSdkFactory`) so this package carries no heavy agent-runtime
|
|
69
|
+
coupling and unit-tests with a stub session. The actual native room media client (`@livekit/rtc-node`) is
|
|
70
|
+
bound on the bridge driver at deployment, per the realtime-bridge buildout plan.
|
|
71
|
+
|
|
72
|
+
## Recording (egress)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { LiveKitEgressService } from '@memberjunction/livekit-room-server';
|
|
76
|
+
|
|
77
|
+
const egress = new LiveKitEgressService();
|
|
78
|
+
const rec = await egress.StartRoomRecording({ RoomName: 'support-42' }); // → { EgressID, Status }
|
|
79
|
+
await egress.StopRecording(rec.EgressID);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Egress output storage (S3 / GCS / Azure / LiveKit Cloud) is configured on the LiveKit project.
|
|
83
|
+
|
|
84
|
+
## GraphQL surface
|
|
85
|
+
|
|
86
|
+
These are exposed to the browser via MJServer's `RealtimeBridgeResolver`:
|
|
87
|
+
`MintLiveKitClientToken`, `StartLiveKitAgentRoomSession`, `StartLiveKitRecording`, `StopLiveKitRecording`.
|
|
88
|
+
Call them from the browser with `GraphQLLiveKitClient` in `@memberjunction/graphql-dataprovider`.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
ISC © MemberJunction.com
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public entry point for `@memberjunction/livekit-room-server`.
|
|
3
|
+
*
|
|
4
|
+
* Server-side LiveKit room support for MemberJunction: scoped token minting + the agent-room session-start
|
|
5
|
+
* coordinator that bridges a realtime model session into a LiveKit room.
|
|
6
|
+
*
|
|
7
|
+
* @module @memberjunction/livekit-room-server
|
|
8
|
+
*/
|
|
9
|
+
export { LiveKitTokenService, type LiveKitServerConfig, type LiveKitTokenRole, type MintTokenParams, type MintedToken } from './livekit-token-service.js';
|
|
10
|
+
export { LiveKitAgentRoomCoordinator, LIVEKIT_BRIDGE_DRIVER_CLASS, type BridgeOps, type RealtimeSessionFactory, type RealtimeSessionStartContext, type StartAgentRoomSessionParams, type AgentRoomSession, } from './livekit-agent-room-coordinator.js';
|
|
11
|
+
export { LiveKitEgressService, wsToHttpUrl, type EgressClientLike, type StartRecordingParams, type RecordingInfo } from './livekit-egress-service.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEvJ,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,KAAK,SAAS,EACd,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,gBAAgB,GACtB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public entry point for `@memberjunction/livekit-room-server`.
|
|
3
|
+
*
|
|
4
|
+
* Server-side LiveKit room support for MemberJunction: scoped token minting + the agent-room session-start
|
|
5
|
+
* coordinator that bridges a realtime model session into a LiveKit room.
|
|
6
|
+
*
|
|
7
|
+
* @module @memberjunction/livekit-room-server
|
|
8
|
+
*/
|
|
9
|
+
import { LoadLiveKitBridge } from '@memberjunction/ai-bridge-livekit';
|
|
10
|
+
// The coordinator's StartAgentRoomSession resolves the 'LiveKitBridge' driver via the engine's
|
|
11
|
+
// ClassFactory, so its @RegisterClass registration MUST have run. Importing the driver package here —
|
|
12
|
+
// the server-side LiveKit layer that DEPENDS on the driver — guarantees it: loading this package (which
|
|
13
|
+
// MJServer does) registers the bridge. The no-op call is a tree-shake guard. Without this the room start
|
|
14
|
+
// fails with "No bridge driver registered for DriverClass 'LiveKitBridge'".
|
|
15
|
+
LoadLiveKitBridge();
|
|
16
|
+
export { LiveKitTokenService } from './livekit-token-service.js';
|
|
17
|
+
export { LiveKitAgentRoomCoordinator, LIVEKIT_BRIDGE_DRIVER_CLASS, } from './livekit-agent-room-coordinator.js';
|
|
18
|
+
export { LiveKitEgressService, wsToHttpUrl } from './livekit-egress-service.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAEtE,+FAA+F;AAC/F,sGAAsG;AACtG,wGAAwG;AACxG,yGAAyG;AACzG,4EAA4E;AAC5E,iBAAiB,EAAE,CAAC;AAEpB,OAAO,EAAE,mBAAmB,EAA2F,MAAM,yBAAyB,CAAC;AAEvJ,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,GAM5B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAwE,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link LiveKitAgentRoomCoordinator} — the server-side **session-start harness** for the
|
|
3
|
+
* MJ-native LiveKit room (§1 of the realtime-bridge buildout plan). Given an agent + a room, it:
|
|
4
|
+
* 1. mints the agent bot's scoped LiveKit token ({@link LiveKitTokenService}),
|
|
5
|
+
* 2. opens the realtime model session via an injectable {@link RealtimeSessionFactory} seam, and
|
|
6
|
+
* 3. bridges that session into the room through {@link AIBridgeEngine.StartBridgeSession} (which wires
|
|
7
|
+
* the transport seam + turn-taking automatically).
|
|
8
|
+
*
|
|
9
|
+
* The realtime-session factory is a seam (mirroring `LiveKitBridge.SetSdkFactory`): production binds it to
|
|
10
|
+
* the real model-resolution path (`@memberjunction/ai-agents`), and tests/de-risk inject a stub session.
|
|
11
|
+
* This keeps the coordinator free of heavy agent-runtime coupling while still owning the orchestration.
|
|
12
|
+
*
|
|
13
|
+
* @module @memberjunction/livekit-room-server
|
|
14
|
+
*/
|
|
15
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
16
|
+
import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
|
|
17
|
+
import type { IRealtimeSession } from '@memberjunction/ai';
|
|
18
|
+
import { type BridgeDisconnectReason, type BridgeTurnMode } from '@memberjunction/ai-bridge-base';
|
|
19
|
+
import { AIBridgeEngine } from '@memberjunction/ai-bridge-server';
|
|
20
|
+
import { LiveKitTokenService } from './livekit-token-service.js';
|
|
21
|
+
/** The subset of {@link AIBridgeEngine} the coordinator drives — an injectable seam for unit testing. */
|
|
22
|
+
export type BridgeOps = Pick<AIBridgeEngine, 'Config' | 'ProviderByDriverClass' | 'StartBridgeSession' | 'StopBridgeSession'>;
|
|
23
|
+
/** The `DriverClass` the LiveKit bridge registers under (must match the `MJ: AI Bridge Providers` row). */
|
|
24
|
+
export declare const LIVEKIT_BRIDGE_DRIVER_CLASS = "LiveKitBridge";
|
|
25
|
+
/** Context passed to the realtime-session factory when starting an agent room session. */
|
|
26
|
+
export interface RealtimeSessionStartContext {
|
|
27
|
+
/** The agent to voice in the room, when known. */
|
|
28
|
+
AgentID?: string;
|
|
29
|
+
/** The agent's display name (used for the bot name + turn-taking matcher). */
|
|
30
|
+
AgentName?: string;
|
|
31
|
+
/** The TARGET agent the co-agent voices via `invoke-target-agent` (the one being "called"). */
|
|
32
|
+
TargetAgentID?: string;
|
|
33
|
+
/** Optional per-session Realtime MODEL override (Name or ID). */
|
|
34
|
+
RealtimeModelID?: string;
|
|
35
|
+
/** Optional per-session VOICE override (provider-native voice id). */
|
|
36
|
+
RealtimeVoice?: string;
|
|
37
|
+
/** The room being joined. */
|
|
38
|
+
RoomName: string;
|
|
39
|
+
/** The user the session runs as. */
|
|
40
|
+
ContextUser?: UserInfo;
|
|
41
|
+
/** The metadata provider for the session. */
|
|
42
|
+
MetadataProvider?: IMetadataProvider;
|
|
43
|
+
/** The MJ agent-session id — threaded into the co-agent observability run for session grouping. */
|
|
44
|
+
AgentSessionID?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Multi-agent meeting mode — set when the agent joins a room that already has agents. Flows to the
|
|
47
|
+
* realtime session so its model's auto-response is disabled (speaks only when addressed).
|
|
48
|
+
*/
|
|
49
|
+
MeetingMode?: boolean;
|
|
50
|
+
/** The names the agent answers to (display name + aliases) — phrasing for the meeting prompt. */
|
|
51
|
+
SelfNames?: string[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Opens a realtime model session for the agent. Production binds this to the real model-resolution path;
|
|
55
|
+
* tests inject a stub. Defaults to a clear "not bound" error so misconfiguration fails loudly.
|
|
56
|
+
*/
|
|
57
|
+
export type RealtimeSessionFactory = (ctx: RealtimeSessionStartContext) => Promise<IRealtimeSession>;
|
|
58
|
+
/** Parameters for {@link LiveKitAgentRoomCoordinator.StartAgentRoomSession}. */
|
|
59
|
+
export interface StartAgentRoomSessionParams {
|
|
60
|
+
/** The MJ agent-session id this bridge belongs to. */
|
|
61
|
+
AgentSessionID: string;
|
|
62
|
+
/** The LiveKit room the agent should join. */
|
|
63
|
+
RoomName: string;
|
|
64
|
+
/** The agent to voice, when known. */
|
|
65
|
+
AgentID?: string;
|
|
66
|
+
/** The agent's display name (bot name + addressing). */
|
|
67
|
+
AgentName?: string;
|
|
68
|
+
/**
|
|
69
|
+
* The TARGET agent the co-agent voices (the one being "called"). Passed through to the realtime
|
|
70
|
+
* session factory so the Realtime Co-Agent has someone to delegate to via `invoke-target-agent`.
|
|
71
|
+
*/
|
|
72
|
+
TargetAgentID?: string;
|
|
73
|
+
/** Optional per-session Realtime MODEL override (Name or ID) — wins over the co-agent config preference. */
|
|
74
|
+
RealtimeModelID?: string;
|
|
75
|
+
/** Optional per-session VOICE override (provider-native voice id) — gives this agent a distinct voice. */
|
|
76
|
+
RealtimeVoice?: string;
|
|
77
|
+
/** Extra aliases the agent answers to (for Passive turn-taking). */
|
|
78
|
+
AgentAliases?: string[];
|
|
79
|
+
/** Turn-taking mode. Default: `'Passive'` (speak only when addressed). */
|
|
80
|
+
TurnMode?: BridgeTurnMode;
|
|
81
|
+
/** The user the session runs as. */
|
|
82
|
+
ContextUser?: UserInfo;
|
|
83
|
+
/** The metadata provider for the session. */
|
|
84
|
+
MetadataProvider?: IMetadataProvider;
|
|
85
|
+
}
|
|
86
|
+
/** The result of starting an agent room session. */
|
|
87
|
+
export interface AgentRoomSession {
|
|
88
|
+
/** The durable `MJ: AI Agent Session Bridges` row id. */
|
|
89
|
+
SessionBridgeID: string;
|
|
90
|
+
/** The room the agent joined. */
|
|
91
|
+
RoomName: string;
|
|
92
|
+
/** The LiveKit server URL. */
|
|
93
|
+
ServerUrl: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Coordinates starting (and stopping) an agent's presence in a LiveKit room. A process-wide singleton
|
|
97
|
+
* (per MJ convention) so deployment code can bind the realtime-session factory once at startup and the
|
|
98
|
+
* GraphQL resolver can use the same bound instance.
|
|
99
|
+
*/
|
|
100
|
+
export declare class LiveKitAgentRoomCoordinator extends BaseSingleton<LiveKitAgentRoomCoordinator> {
|
|
101
|
+
private tokenService;
|
|
102
|
+
/**
|
|
103
|
+
* Per-room roster of the agents currently bridged in (keyed by lowercased room name). Drives
|
|
104
|
+
* **multi-agent meeting detection**: when an agent joins a room that ALREADY holds an agent, the new
|
|
105
|
+
* agent starts in meeting mode (auto-response off + addressed-only) so several agents can share a room
|
|
106
|
+
* without all answering every utterance. The first agent stays a normal 1:1 voice (it isn't
|
|
107
|
+
* retroactively re-gated — that needs a live session re-config, a follow-up). See
|
|
108
|
+
* `plans/realtime/multi-agent-meeting-turn-taking.md`.
|
|
109
|
+
*/
|
|
110
|
+
private roomRosters;
|
|
111
|
+
private bridgeOps;
|
|
112
|
+
private sessionFactory;
|
|
113
|
+
/** BaseSingleton requires a protected constructor. */
|
|
114
|
+
protected constructor();
|
|
115
|
+
/** The process-wide coordinator instance. */
|
|
116
|
+
static get Instance(): LiveKitAgentRoomCoordinator;
|
|
117
|
+
/**
|
|
118
|
+
* Binds the realtime-session factory (the model-session creation seam).
|
|
119
|
+
*
|
|
120
|
+
* @param factory The factory that opens an {@link IRealtimeSession} for an agent.
|
|
121
|
+
*/
|
|
122
|
+
SetSessionFactory(factory: RealtimeSessionFactory): void;
|
|
123
|
+
/**
|
|
124
|
+
* Overrides the token service used to mint the bot token (e.g. to inject explicit credentials).
|
|
125
|
+
*
|
|
126
|
+
* @param service The token service to use.
|
|
127
|
+
*/
|
|
128
|
+
SetTokenService(service: LiveKitTokenService): void;
|
|
129
|
+
/**
|
|
130
|
+
* Overrides the bridge operations used to resolve the provider + start/stop the bridge session (the
|
|
131
|
+
* injectable {@link AIBridgeEngine} seam — primarily for unit testing).
|
|
132
|
+
*
|
|
133
|
+
* @param ops The bridge operations to use.
|
|
134
|
+
*/
|
|
135
|
+
SetBridgeOps(ops: BridgeOps): void;
|
|
136
|
+
/**
|
|
137
|
+
* Starts the agent's presence in a LiveKit room: mints the bot token, opens the realtime session, and
|
|
138
|
+
* bridges it into the room.
|
|
139
|
+
*
|
|
140
|
+
* @param params The session parameters.
|
|
141
|
+
* @returns The active session handles.
|
|
142
|
+
* @throws {Error} when the LiveKit provider is not configured/registered or the session factory is unbound.
|
|
143
|
+
*/
|
|
144
|
+
StartAgentRoomSession(params: StartAgentRoomSessionParams): Promise<AgentRoomSession>;
|
|
145
|
+
/** Appends an agent to a room's roster (creating the room's list on first join). */
|
|
146
|
+
private addToRoster;
|
|
147
|
+
/** Removes an agent from whatever room roster holds its bridge id; prunes the room when it empties. */
|
|
148
|
+
private removeFromRoster;
|
|
149
|
+
/** The default native room-client wrapper specifier — the @livekit/rtc-node package this repo ships. */
|
|
150
|
+
private static readonly DEFAULT_NATIVE_MODULE;
|
|
151
|
+
/**
|
|
152
|
+
* Resolves the native LiveKit room-client module specifier for the bridge session. Prefers the
|
|
153
|
+
* `LIVEKIT_NATIVE_MODULE` env override (e.g. a deployment's custom-sample-rate wrapper), else the default
|
|
154
|
+
* {@link DEFAULT_NATIVE_MODULE}. Overridable in tests via {@link SetNativeModuleSpecifier}.
|
|
155
|
+
*/
|
|
156
|
+
private resolveNativeModuleSpecifier;
|
|
157
|
+
/**
|
|
158
|
+
* Overrides the native room-client module specifier (primarily for unit testing — production resolves it
|
|
159
|
+
* from env / the default).
|
|
160
|
+
*
|
|
161
|
+
* @param specifier The module specifier to use, or `undefined` to clear the override.
|
|
162
|
+
*/
|
|
163
|
+
SetNativeModuleSpecifier(specifier: string | undefined): void;
|
|
164
|
+
/** Test/deployment override for the native module specifier (see {@link resolveNativeModuleSpecifier}). */
|
|
165
|
+
private nativeModuleSpecifierOverride?;
|
|
166
|
+
/**
|
|
167
|
+
* Stops an agent room session (the bot leaves the room).
|
|
168
|
+
*
|
|
169
|
+
* @param sessionBridgeID The bridge row id returned from {@link StartAgentRoomSession}.
|
|
170
|
+
* @param reason Why the session is stopping. Default: `'Explicit'`.
|
|
171
|
+
* @param contextUser The acting user.
|
|
172
|
+
* @param provider The metadata provider.
|
|
173
|
+
*/
|
|
174
|
+
StopAgentRoomSession(sessionBridgeID: string, reason?: BridgeDisconnectReason, contextUser?: UserInfo, provider?: IMetadataProvider): Promise<boolean>;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=livekit-agent-room-coordinator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livekit-agent-room-coordinator.d.ts","sourceRoot":"","sources":["../src/livekit-agent-room-coordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link LiveKitAgentRoomCoordinator} — the server-side **session-start harness** for the
|
|
3
|
+
* MJ-native LiveKit room (§1 of the realtime-bridge buildout plan). Given an agent + a room, it:
|
|
4
|
+
* 1. mints the agent bot's scoped LiveKit token ({@link LiveKitTokenService}),
|
|
5
|
+
* 2. opens the realtime model session via an injectable {@link RealtimeSessionFactory} seam, and
|
|
6
|
+
* 3. bridges that session into the room through {@link AIBridgeEngine.StartBridgeSession} (which wires
|
|
7
|
+
* the transport seam + turn-taking automatically).
|
|
8
|
+
*
|
|
9
|
+
* The realtime-session factory is a seam (mirroring `LiveKitBridge.SetSdkFactory`): production binds it to
|
|
10
|
+
* the real model-resolution path (`@memberjunction/ai-agents`), and tests/de-risk inject a stub session.
|
|
11
|
+
* This keeps the coordinator free of heavy agent-runtime coupling while still owning the orchestration.
|
|
12
|
+
*
|
|
13
|
+
* @module @memberjunction/livekit-room-server
|
|
14
|
+
*/
|
|
15
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
16
|
+
import { LogError, LogStatus } from '@memberjunction/core';
|
|
17
|
+
import { AlwaysAddressedMatcher, RegexAddressedMatcher } from '@memberjunction/ai-bridge-base';
|
|
18
|
+
import { AIBridgeEngine } from '@memberjunction/ai-bridge-server';
|
|
19
|
+
import { LiveKitTokenService } from './livekit-token-service.js';
|
|
20
|
+
/** The `DriverClass` the LiveKit bridge registers under (must match the `MJ: AI Bridge Providers` row). */
|
|
21
|
+
export const LIVEKIT_BRIDGE_DRIVER_CLASS = 'LiveKitBridge';
|
|
22
|
+
/**
|
|
23
|
+
* Coordinates starting (and stopping) an agent's presence in a LiveKit room. A process-wide singleton
|
|
24
|
+
* (per MJ convention) so deployment code can bind the realtime-session factory once at startup and the
|
|
25
|
+
* GraphQL resolver can use the same bound instance.
|
|
26
|
+
*/
|
|
27
|
+
export class LiveKitAgentRoomCoordinator extends BaseSingleton {
|
|
28
|
+
/** BaseSingleton requires a protected constructor. */
|
|
29
|
+
constructor() {
|
|
30
|
+
super();
|
|
31
|
+
this.tokenService = new LiveKitTokenService();
|
|
32
|
+
/**
|
|
33
|
+
* Per-room roster of the agents currently bridged in (keyed by lowercased room name). Drives
|
|
34
|
+
* **multi-agent meeting detection**: when an agent joins a room that ALREADY holds an agent, the new
|
|
35
|
+
* agent starts in meeting mode (auto-response off + addressed-only) so several agents can share a room
|
|
36
|
+
* without all answering every utterance. The first agent stays a normal 1:1 voice (it isn't
|
|
37
|
+
* retroactively re-gated — that needs a live session re-config, a follow-up). See
|
|
38
|
+
* `plans/realtime/multi-agent-meeting-turn-taking.md`.
|
|
39
|
+
*/
|
|
40
|
+
this.roomRosters = new Map();
|
|
41
|
+
this.bridgeOps = AIBridgeEngine.Instance;
|
|
42
|
+
this.sessionFactory = () => {
|
|
43
|
+
throw new Error('LiveKitAgentRoomCoordinator has no realtime-session factory bound. Call SetSessionFactory(...) ' +
|
|
44
|
+
'with a factory that resolves a BaseRealtimeModel and returns an IRealtimeSession (see @memberjunction/ai-agents), ' +
|
|
45
|
+
'or inject a stub session in tests/de-risk runs.');
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** The process-wide coordinator instance. */
|
|
49
|
+
static get Instance() {
|
|
50
|
+
return super.getInstance();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Binds the realtime-session factory (the model-session creation seam).
|
|
54
|
+
*
|
|
55
|
+
* @param factory The factory that opens an {@link IRealtimeSession} for an agent.
|
|
56
|
+
*/
|
|
57
|
+
SetSessionFactory(factory) {
|
|
58
|
+
this.sessionFactory = factory;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Overrides the token service used to mint the bot token (e.g. to inject explicit credentials).
|
|
62
|
+
*
|
|
63
|
+
* @param service The token service to use.
|
|
64
|
+
*/
|
|
65
|
+
SetTokenService(service) {
|
|
66
|
+
this.tokenService = service;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Overrides the bridge operations used to resolve the provider + start/stop the bridge session (the
|
|
70
|
+
* injectable {@link AIBridgeEngine} seam — primarily for unit testing).
|
|
71
|
+
*
|
|
72
|
+
* @param ops The bridge operations to use.
|
|
73
|
+
*/
|
|
74
|
+
SetBridgeOps(ops) {
|
|
75
|
+
this.bridgeOps = ops;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Starts the agent's presence in a LiveKit room: mints the bot token, opens the realtime session, and
|
|
79
|
+
* bridges it into the room.
|
|
80
|
+
*
|
|
81
|
+
* @param params The session parameters.
|
|
82
|
+
* @returns The active session handles.
|
|
83
|
+
* @throws {Error} when the LiveKit provider is not configured/registered or the session factory is unbound.
|
|
84
|
+
*/
|
|
85
|
+
async StartAgentRoomSession(params) {
|
|
86
|
+
await this.bridgeOps.Config(false, params.ContextUser, params.MetadataProvider);
|
|
87
|
+
const provider = this.bridgeOps.ProviderByDriverClass(LIVEKIT_BRIDGE_DRIVER_CLASS);
|
|
88
|
+
if (!provider) {
|
|
89
|
+
throw new Error(`No active 'MJ: AI Bridge Providers' row with DriverClass='${LIVEKIT_BRIDGE_DRIVER_CLASS}' was found. ` +
|
|
90
|
+
'Seed/activate the LiveKit provider row before starting a room session.');
|
|
91
|
+
}
|
|
92
|
+
const botName = params.AgentName ?? 'Agent';
|
|
93
|
+
const botIdentity = `agent-${params.AgentSessionID}`;
|
|
94
|
+
const botToken = await this.tokenService.MintBotToken(params.RoomName, botIdentity, botName);
|
|
95
|
+
// Multi-agent MEETING detection: if the room already holds an agent, THIS agent joins as a meeting
|
|
96
|
+
// participant — auto-response OFF, speaks only when addressed by name. The first agent in a room stays
|
|
97
|
+
// a normal 1:1 voice (it answers freely; it isn't retroactively re-gated). The names the agent answers
|
|
98
|
+
// to drive both its addressing matcher (the GATE) and the meeting prompt phrasing.
|
|
99
|
+
const roomKey = params.RoomName.trim().toLowerCase();
|
|
100
|
+
const selfNames = [botName, ...(params.AgentAliases ?? [])].map(n => n.trim()).filter(n => n.length > 0);
|
|
101
|
+
const isMeeting = (this.roomRosters.get(roomKey)?.length ?? 0) > 0;
|
|
102
|
+
const session = await this.sessionFactory({
|
|
103
|
+
AgentID: params.AgentID,
|
|
104
|
+
AgentName: params.AgentName,
|
|
105
|
+
TargetAgentID: params.TargetAgentID,
|
|
106
|
+
RealtimeModelID: params.RealtimeModelID,
|
|
107
|
+
RealtimeVoice: params.RealtimeVoice,
|
|
108
|
+
RoomName: params.RoomName,
|
|
109
|
+
ContextUser: params.ContextUser,
|
|
110
|
+
MetadataProvider: params.MetadataProvider,
|
|
111
|
+
// So the co-agent observability run groups under THIS agent session (parity with native chat).
|
|
112
|
+
AgentSessionID: params.AgentSessionID,
|
|
113
|
+
MeetingMode: isMeeting || undefined,
|
|
114
|
+
SelfNames: isMeeting ? selfNames : undefined,
|
|
115
|
+
});
|
|
116
|
+
const active = await this.bridgeOps.StartBridgeSession({
|
|
117
|
+
AgentSessionID: params.AgentSessionID,
|
|
118
|
+
AgentID: params.AgentID,
|
|
119
|
+
Provider: provider,
|
|
120
|
+
RealtimeSession: session,
|
|
121
|
+
Address: botToken.ServerUrl,
|
|
122
|
+
JoinMethod: 'OnDemand',
|
|
123
|
+
TurnMode: params.TurnMode ?? 'Passive',
|
|
124
|
+
// Meeting: gate speech to ADDRESSED turns (RegexAddressedMatcher on the agent's names) AND tell the
|
|
125
|
+
// engine the model's auto-response is off so the bridge becomes the sole trigger. Solo 1:1: respond to
|
|
126
|
+
// ALL the user's speech (AlwaysAddressedMatcher) with the model's own auto-response — Passive's
|
|
127
|
+
// name-match would otherwise leave a single agent silent unless you said its name each turn.
|
|
128
|
+
TurnMatcher: isMeeting ? new RegexAddressedMatcher(selfNames) : new AlwaysAddressedMatcher(),
|
|
129
|
+
DisableAutoResponse: isMeeting || undefined,
|
|
130
|
+
// NativeModuleSpecifier tells LiveKitNativeMeetingSdk which native room-client wrapper to load — the
|
|
131
|
+
// @livekit/rtc-node-backed @memberjunction/ai-bridge-livekit-native by default, overridable via env
|
|
132
|
+
// (e.g. a one-line module setting Gemini's 16 kHz inbound rate). AccessToken is the pre-signed bot
|
|
133
|
+
// join token; the room ws URL arrives as `Address`.
|
|
134
|
+
Configuration: {
|
|
135
|
+
AccessToken: botToken.Token,
|
|
136
|
+
BotDisplayName: botName,
|
|
137
|
+
RoomName: params.RoomName,
|
|
138
|
+
NativeModuleSpecifier: this.resolveNativeModuleSpecifier(),
|
|
139
|
+
},
|
|
140
|
+
ContextUser: params.ContextUser,
|
|
141
|
+
MetadataProvider: params.MetadataProvider,
|
|
142
|
+
});
|
|
143
|
+
this.addToRoster(roomKey, { AgentSessionID: params.AgentSessionID, SessionBridgeID: active.SessionBridgeID, Names: selfNames });
|
|
144
|
+
LogStatus(`[LiveKitAgentRoomCoordinator] Agent ${botName} bridged into LiveKit room ${params.RoomName} ` +
|
|
145
|
+
`(bridge ${active.SessionBridgeID}, ${isMeeting ? 'MEETING — addressed-only' : 'solo 1:1'})`);
|
|
146
|
+
return { SessionBridgeID: active.SessionBridgeID, RoomName: params.RoomName, ServerUrl: botToken.ServerUrl };
|
|
147
|
+
}
|
|
148
|
+
/** Appends an agent to a room's roster (creating the room's list on first join). */
|
|
149
|
+
addToRoster(roomKey, entry) {
|
|
150
|
+
const roster = this.roomRosters.get(roomKey);
|
|
151
|
+
if (roster) {
|
|
152
|
+
roster.push(entry);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
this.roomRosters.set(roomKey, [entry]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Removes an agent from whatever room roster holds its bridge id; prunes the room when it empties. */
|
|
159
|
+
removeFromRoster(sessionBridgeID) {
|
|
160
|
+
for (const [roomKey, roster] of this.roomRosters) {
|
|
161
|
+
const next = roster.filter(e => e.SessionBridgeID !== sessionBridgeID);
|
|
162
|
+
if (next.length === roster.length) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (next.length === 0) {
|
|
166
|
+
this.roomRosters.delete(roomKey);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.roomRosters.set(roomKey, next);
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** The default native room-client wrapper specifier — the @livekit/rtc-node package this repo ships. */
|
|
175
|
+
static { this.DEFAULT_NATIVE_MODULE = '@memberjunction/ai-bridge-livekit-native'; }
|
|
176
|
+
/**
|
|
177
|
+
* Resolves the native LiveKit room-client module specifier for the bridge session. Prefers the
|
|
178
|
+
* `LIVEKIT_NATIVE_MODULE` env override (e.g. a deployment's custom-sample-rate wrapper), else the default
|
|
179
|
+
* {@link DEFAULT_NATIVE_MODULE}. Overridable in tests via {@link SetNativeModuleSpecifier}.
|
|
180
|
+
*/
|
|
181
|
+
resolveNativeModuleSpecifier() {
|
|
182
|
+
return (this.nativeModuleSpecifierOverride ??
|
|
183
|
+
process.env.LIVEKIT_NATIVE_MODULE ??
|
|
184
|
+
LiveKitAgentRoomCoordinator.DEFAULT_NATIVE_MODULE);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Overrides the native room-client module specifier (primarily for unit testing — production resolves it
|
|
188
|
+
* from env / the default).
|
|
189
|
+
*
|
|
190
|
+
* @param specifier The module specifier to use, or `undefined` to clear the override.
|
|
191
|
+
*/
|
|
192
|
+
SetNativeModuleSpecifier(specifier) {
|
|
193
|
+
this.nativeModuleSpecifierOverride = specifier;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Stops an agent room session (the bot leaves the room).
|
|
197
|
+
*
|
|
198
|
+
* @param sessionBridgeID The bridge row id returned from {@link StartAgentRoomSession}.
|
|
199
|
+
* @param reason Why the session is stopping. Default: `'Explicit'`.
|
|
200
|
+
* @param contextUser The acting user.
|
|
201
|
+
* @param provider The metadata provider.
|
|
202
|
+
*/
|
|
203
|
+
async StopAgentRoomSession(sessionBridgeID, reason = 'Explicit', contextUser, provider) {
|
|
204
|
+
try {
|
|
205
|
+
return await this.bridgeOps.StopBridgeSession(sessionBridgeID, reason, contextUser, provider);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
LogError(`[LiveKitAgentRoomCoordinator] StopAgentRoomSession failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
// Drop this agent from its room roster so the count reflects who's actually present (the NEXT agent's
|
|
213
|
+
// meeting-vs-solo decision reads it). Done in finally — even a failed stop means we asked it to leave.
|
|
214
|
+
this.removeFromRoster(sessionBridgeID);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=livekit-agent-room-coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livekit-agent-room-coordinator.js","sourceRoot":"","sources":["../src/livekit-agent-room-coordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link LiveKitEgressService} — starts/stops room recording (composite egress) via the
|
|
3
|
+
* LiveKit `EgressClient` (`livekit-server-sdk`). Recording is a server-authorized action, so it lives in
|
|
4
|
+
* the server package and is invoked through the GraphQL surface — never directly from the browser.
|
|
5
|
+
*
|
|
6
|
+
* Egress output storage (S3 / GCS / Azure / LiveKit Cloud bucket) is a deployment concern configured on
|
|
7
|
+
* the LiveKit project; this service starts a composite MP4 egress with a filepath template and returns the
|
|
8
|
+
* egress id so the caller can stop it later.
|
|
9
|
+
*
|
|
10
|
+
* @module @memberjunction/livekit-room-server
|
|
11
|
+
*/
|
|
12
|
+
import { EgressClient } from 'livekit-server-sdk';
|
|
13
|
+
import { type LiveKitServerConfig } from './livekit-token-service.js';
|
|
14
|
+
/** Parameters for starting a room recording. */
|
|
15
|
+
export interface StartRecordingParams {
|
|
16
|
+
/** The room to record. */
|
|
17
|
+
RoomName: string;
|
|
18
|
+
/** The composite layout (e.g. `'speaker'`, `'grid'`). Default: `'speaker-dark'`. */
|
|
19
|
+
Layout?: string;
|
|
20
|
+
/** The output filepath template (provider-specific). Default: `<room>/<timestamp>.mp4`. */
|
|
21
|
+
Filepath?: string;
|
|
22
|
+
}
|
|
23
|
+
/** The subset of {@link EgressClient} the service drives — an injectable seam for unit testing. */
|
|
24
|
+
export type EgressClientLike = Pick<EgressClient, 'startRoomCompositeEgress' | 'stopEgress' | 'listEgress'>;
|
|
25
|
+
/** Converts a `ws(s)://` server URL to its `http(s)://` form for the egress client. */
|
|
26
|
+
export declare function wsToHttpUrl(url: string): string;
|
|
27
|
+
/** A normalized recording (egress) status. */
|
|
28
|
+
export interface RecordingInfo {
|
|
29
|
+
/** The egress id (pass to {@link LiveKitEgressService.StopRecording}). */
|
|
30
|
+
EgressID: string;
|
|
31
|
+
/** The room being recorded. */
|
|
32
|
+
RoomName: string;
|
|
33
|
+
/** The raw egress status string. */
|
|
34
|
+
Status: string;
|
|
35
|
+
}
|
|
36
|
+
/** Starts/stops/lists LiveKit room recordings (composite egress). */
|
|
37
|
+
export declare class LiveKitEgressService {
|
|
38
|
+
private readonly client;
|
|
39
|
+
/**
|
|
40
|
+
* @param config Explicit credentials; omitted fields fall back to environment variables (via
|
|
41
|
+
* {@link LiveKitTokenService}). The egress client uses the HTTP(S) form of the server URL.
|
|
42
|
+
* @param client An injectable egress client (primarily for unit testing); defaults to a real
|
|
43
|
+
* `EgressClient` built from the resolved credentials.
|
|
44
|
+
*/
|
|
45
|
+
constructor(config?: Partial<LiveKitServerConfig>, client?: EgressClientLike);
|
|
46
|
+
/**
|
|
47
|
+
* Starts a composite recording of a room.
|
|
48
|
+
*
|
|
49
|
+
* @param params The recording parameters.
|
|
50
|
+
* @returns The started recording info.
|
|
51
|
+
*/
|
|
52
|
+
StartRoomRecording(params: StartRecordingParams): Promise<RecordingInfo>;
|
|
53
|
+
/**
|
|
54
|
+
* Stops a recording.
|
|
55
|
+
*
|
|
56
|
+
* @param egressId The egress id returned by {@link StartRoomRecording}.
|
|
57
|
+
* @returns The final recording info.
|
|
58
|
+
*/
|
|
59
|
+
StopRecording(egressId: string): Promise<RecordingInfo>;
|
|
60
|
+
/**
|
|
61
|
+
* Lists active recordings for a room.
|
|
62
|
+
*
|
|
63
|
+
* @param roomName The room to query.
|
|
64
|
+
* @returns The active recordings.
|
|
65
|
+
*/
|
|
66
|
+
ListActiveRecordings(roomName: string): Promise<RecordingInfo[]>;
|
|
67
|
+
/** Maps an SDK `EgressInfo` to the normalized {@link RecordingInfo}. */
|
|
68
|
+
private toRecordingInfo;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=livekit-egress-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livekit-egress-service.d.ts","sourceRoot":"","sources":["../src/livekit-egress-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAuD,MAAM,oBAAoB,CAAC;AACvG,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAExF,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,mGAAmG;AACnG,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,0BAA0B,GAAG,YAAY,GAAG,YAAY,CAAC,CAAC;AAE5G,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C;;;;;OAKG;gBACS,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,EAAE,gBAAgB;IAM5E;;;;;OAKG;IACU,kBAAkB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC;IAOrF;;;;;OAKG;IACU,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAKpE;;;;;OAKG;IACU,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAK7E,wEAAwE;IACxE,OAAO,CAAC,eAAe;CAGxB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link LiveKitEgressService} — starts/stops room recording (composite egress) via the
|
|
3
|
+
* LiveKit `EgressClient` (`livekit-server-sdk`). Recording is a server-authorized action, so it lives in
|
|
4
|
+
* the server package and is invoked through the GraphQL surface — never directly from the browser.
|
|
5
|
+
*
|
|
6
|
+
* Egress output storage (S3 / GCS / Azure / LiveKit Cloud bucket) is a deployment concern configured on
|
|
7
|
+
* the LiveKit project; this service starts a composite MP4 egress with a filepath template and returns the
|
|
8
|
+
* egress id so the caller can stop it later.
|
|
9
|
+
*
|
|
10
|
+
* @module @memberjunction/livekit-room-server
|
|
11
|
+
*/
|
|
12
|
+
import { EgressClient, EncodedFileOutput, EncodedFileType } from 'livekit-server-sdk';
|
|
13
|
+
import { LiveKitTokenService } from './livekit-token-service.js';
|
|
14
|
+
/** Converts a `ws(s)://` server URL to its `http(s)://` form for the egress client. */
|
|
15
|
+
export function wsToHttpUrl(url) {
|
|
16
|
+
return url.replace(/^ws/i, 'http');
|
|
17
|
+
}
|
|
18
|
+
/** Starts/stops/lists LiveKit room recordings (composite egress). */
|
|
19
|
+
export class LiveKitEgressService {
|
|
20
|
+
/**
|
|
21
|
+
* @param config Explicit credentials; omitted fields fall back to environment variables (via
|
|
22
|
+
* {@link LiveKitTokenService}). The egress client uses the HTTP(S) form of the server URL.
|
|
23
|
+
* @param client An injectable egress client (primarily for unit testing); defaults to a real
|
|
24
|
+
* `EgressClient` built from the resolved credentials.
|
|
25
|
+
*/
|
|
26
|
+
constructor(config, client) {
|
|
27
|
+
const token = new LiveKitTokenService(config);
|
|
28
|
+
const httpUrl = wsToHttpUrl(token.ServerUrl);
|
|
29
|
+
this.client = client ?? new EgressClient(httpUrl, config?.ApiKey ?? process.env.LIVEKIT_API_KEY, config?.ApiSecret ?? process.env.LIVEKIT_API_SECRET);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Starts a composite recording of a room.
|
|
33
|
+
*
|
|
34
|
+
* @param params The recording parameters.
|
|
35
|
+
* @returns The started recording info.
|
|
36
|
+
*/
|
|
37
|
+
async StartRoomRecording(params) {
|
|
38
|
+
const filepath = params.Filepath ?? `${params.RoomName}/${Date.now()}.mp4`;
|
|
39
|
+
const output = new EncodedFileOutput({ fileType: EncodedFileType.MP4, filepath });
|
|
40
|
+
const info = await this.client.startRoomCompositeEgress(params.RoomName, { file: output }, { layout: params.Layout ?? 'speaker-dark' });
|
|
41
|
+
return this.toRecordingInfo(info, params.RoomName);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Stops a recording.
|
|
45
|
+
*
|
|
46
|
+
* @param egressId The egress id returned by {@link StartRoomRecording}.
|
|
47
|
+
* @returns The final recording info.
|
|
48
|
+
*/
|
|
49
|
+
async StopRecording(egressId) {
|
|
50
|
+
const info = await this.client.stopEgress(egressId);
|
|
51
|
+
return this.toRecordingInfo(info, info.roomName);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Lists active recordings for a room.
|
|
55
|
+
*
|
|
56
|
+
* @param roomName The room to query.
|
|
57
|
+
* @returns The active recordings.
|
|
58
|
+
*/
|
|
59
|
+
async ListActiveRecordings(roomName) {
|
|
60
|
+
const list = await this.client.listEgress({ roomName, active: true });
|
|
61
|
+
return list.map((info) => this.toRecordingInfo(info, roomName));
|
|
62
|
+
}
|
|
63
|
+
/** Maps an SDK `EgressInfo` to the normalized {@link RecordingInfo}. */
|
|
64
|
+
toRecordingInfo(info, roomName) {
|
|
65
|
+
return { EgressID: info.egressId, RoomName: info.roomName || roomName, Status: String(info.status ?? 'unknown') };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=livekit-egress-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livekit-egress-service.js","sourceRoot":"","sources":["../src/livekit-egress-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAmB,MAAM,oBAAoB,CAAC;AACvG,OAAO,EAAE,mBAAmB,EAA4B,MAAM,yBAAyB,CAAC;AAexF,uFAAuF;AACvF,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC;AAYD,qEAAqE;AACrE,MAAM,OAAO,oBAAoB;IAG/B;;;;;OAKG;IACH,YAAY,MAAqC,EAAE,MAAyB;QAC1E,MAAM,KAAK,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,IAAI,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,EAAE,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACxJ,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,kBAAkB,CAAC,MAA4B;QAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC;QAC3E,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAC,EAAE,QAAQ,EAAE,eAAe,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;QAClF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,wBAAwB,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,cAAc,EAAE,CAAC,CAAC;QACxI,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,aAAa,CAAC,QAAgB;QACzC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAC,oBAAoB,CAAC,QAAgB;QAChD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,wEAAwE;IAChE,eAAe,CAAC,IAAgB,EAAE,QAAgB;QACxD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC,EAAE,CAAC;IACpH,CAAC;CACF"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link LiveKitTokenService} — mints scoped LiveKit access tokens (JWTs) server-side via
|
|
3
|
+
* `livekit-server-sdk`. This is the missing browser-facing seam: the bridge already joins the *agent bot*
|
|
4
|
+
* with an MJ-minted token, but a *human* participant needs their own scoped token for the same room. The
|
|
5
|
+
* service mints both, encoding room + grants + role metadata, and never exposes the API secret to clients.
|
|
6
|
+
*
|
|
7
|
+
* Credentials resolve from explicit config or the standard `LIVEKIT_URL` / `LIVEKIT_API_KEY` /
|
|
8
|
+
* `LIVEKIT_API_SECRET` environment variables (the MJ deployment supplies them; never inline secrets).
|
|
9
|
+
*
|
|
10
|
+
* @module @memberjunction/livekit-room-server
|
|
11
|
+
*/
|
|
12
|
+
/** Resolved LiveKit server credentials + URL. */
|
|
13
|
+
export interface LiveKitServerConfig {
|
|
14
|
+
/** The LiveKit server URL clients connect to (e.g. `wss://livekit.myorg.com`). */
|
|
15
|
+
ServerUrl: string;
|
|
16
|
+
/** The LiveKit API key. */
|
|
17
|
+
ApiKey: string;
|
|
18
|
+
/** The LiveKit API secret (server-only — never sent to clients). */
|
|
19
|
+
ApiSecret: string;
|
|
20
|
+
}
|
|
21
|
+
/** The role a minted token grants, stamped into participant metadata for UI role resolution. */
|
|
22
|
+
export type LiveKitTokenRole = 'host' | 'agent' | 'participant';
|
|
23
|
+
/** Parameters for minting a single access token. */
|
|
24
|
+
export interface MintTokenParams {
|
|
25
|
+
/** The room name the token authorizes joining. */
|
|
26
|
+
RoomName: string;
|
|
27
|
+
/** The participant identity to embed (stable, application-assigned). */
|
|
28
|
+
Identity: string;
|
|
29
|
+
/** The display name to publish. */
|
|
30
|
+
DisplayName?: string;
|
|
31
|
+
/** The role stamped into participant metadata (drives the UI's agent/host badges). */
|
|
32
|
+
Role?: LiveKitTokenRole;
|
|
33
|
+
/** Whether the participant may publish media. Default: true. */
|
|
34
|
+
CanPublish?: boolean;
|
|
35
|
+
/** Whether the participant may subscribe to others. Default: true. */
|
|
36
|
+
CanSubscribe?: boolean;
|
|
37
|
+
/** Whether the participant may publish data-channel messages. Default: true. */
|
|
38
|
+
CanPublishData?: boolean;
|
|
39
|
+
/** Token lifetime in seconds. Default: 3600 (1 hour). */
|
|
40
|
+
TtlSeconds?: number;
|
|
41
|
+
/** Extra metadata merged into the participant metadata JSON. */
|
|
42
|
+
Metadata?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
/** A minted token plus the connection coordinates a client needs to join. */
|
|
45
|
+
export interface MintedToken {
|
|
46
|
+
/** The LiveKit server URL to connect to. */
|
|
47
|
+
ServerUrl: string;
|
|
48
|
+
/** The signed JWT access token. */
|
|
49
|
+
Token: string;
|
|
50
|
+
/** The participant identity embedded in the token. */
|
|
51
|
+
Identity: string;
|
|
52
|
+
/** The room name the token authorizes. */
|
|
53
|
+
RoomName: string;
|
|
54
|
+
}
|
|
55
|
+
/** Mints scoped LiveKit access tokens. Stateless apart from its resolved credentials. */
|
|
56
|
+
export declare class LiveKitTokenService {
|
|
57
|
+
private readonly config;
|
|
58
|
+
/**
|
|
59
|
+
* @param config Explicit credentials; any omitted field falls back to the corresponding environment
|
|
60
|
+
* variable (`LIVEKIT_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET`).
|
|
61
|
+
*/
|
|
62
|
+
constructor(config?: Partial<LiveKitServerConfig>);
|
|
63
|
+
/** Whether the service has the credentials needed to mint tokens. */
|
|
64
|
+
get IsConfigured(): boolean;
|
|
65
|
+
/** The configured server URL (for callers that need to return it to clients). */
|
|
66
|
+
get ServerUrl(): string;
|
|
67
|
+
/**
|
|
68
|
+
* Mints an access token for the given parameters.
|
|
69
|
+
*
|
|
70
|
+
* @param params The token parameters (room, identity, grants, role).
|
|
71
|
+
* @returns The minted token + connection coordinates.
|
|
72
|
+
* @throws {Error} when credentials are not configured.
|
|
73
|
+
*/
|
|
74
|
+
MintToken(params: MintTokenParams): Promise<MintedToken>;
|
|
75
|
+
/**
|
|
76
|
+
* Mints a token for a HUMAN participant (publish + subscribe + data, `participant` role).
|
|
77
|
+
*
|
|
78
|
+
* @param roomName The room to join.
|
|
79
|
+
* @param identity The participant identity.
|
|
80
|
+
* @param displayName The display name.
|
|
81
|
+
* @param ttlSeconds Optional token lifetime.
|
|
82
|
+
*/
|
|
83
|
+
MintClientToken(roomName: string, identity: string, displayName?: string, ttlSeconds?: number): Promise<MintedToken>;
|
|
84
|
+
/**
|
|
85
|
+
* Mints a token for the AGENT bot (publish + subscribe + data, `agent` role metadata so the UI badges
|
|
86
|
+
* it correctly). The bridge passes this in its session `Configuration.AccessToken`.
|
|
87
|
+
*
|
|
88
|
+
* @param roomName The room to join.
|
|
89
|
+
* @param identity The bot identity.
|
|
90
|
+
* @param displayName The bot display name.
|
|
91
|
+
*/
|
|
92
|
+
MintBotToken(roomName: string, identity: string, displayName?: string): Promise<MintedToken>;
|
|
93
|
+
/** Builds the LiveKit video grant from the params (defaults: publish + subscribe + data all on). */
|
|
94
|
+
private buildGrant;
|
|
95
|
+
/** Builds the participant metadata JSON string, always stamping the role for UI resolution. */
|
|
96
|
+
private buildMetadata;
|
|
97
|
+
/** Throws a clear error when credentials are missing. */
|
|
98
|
+
private assertConfigured;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=livekit-token-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livekit-token-service.d.ts","sourceRoot":"","sources":["../src/livekit-token-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,iDAAiD;AACjD,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,gGAAgG;AAChG,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,CAAC;AAEhE,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,sEAAsE;IACtE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gFAAgF;IAChF,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,yFAAyF;AACzF,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAE7C;;;OAGG;gBACS,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC;IAQjD,qEAAqE;IACrE,IAAW,YAAY,IAAI,OAAO,CAEjC;IAED,iFAAiF;IACjF,IAAW,SAAS,IAAI,MAAM,CAE7B;IAED;;;;;;OAMG;IACU,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAarE;;;;;;;OAOG;IACI,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAI3H;;;;;;;OAOG;IACI,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAInG,oGAAoG;IACpG,OAAO,CAAC,UAAU;IAUlB,+FAA+F;IAC/F,OAAO,CAAC,aAAa;IAIrB,yDAAyD;IACzD,OAAO,CAAC,gBAAgB;CAQzB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview {@link LiveKitTokenService} — mints scoped LiveKit access tokens (JWTs) server-side via
|
|
3
|
+
* `livekit-server-sdk`. This is the missing browser-facing seam: the bridge already joins the *agent bot*
|
|
4
|
+
* with an MJ-minted token, but a *human* participant needs their own scoped token for the same room. The
|
|
5
|
+
* service mints both, encoding room + grants + role metadata, and never exposes the API secret to clients.
|
|
6
|
+
*
|
|
7
|
+
* Credentials resolve from explicit config or the standard `LIVEKIT_URL` / `LIVEKIT_API_KEY` /
|
|
8
|
+
* `LIVEKIT_API_SECRET` environment variables (the MJ deployment supplies them; never inline secrets).
|
|
9
|
+
*
|
|
10
|
+
* @module @memberjunction/livekit-room-server
|
|
11
|
+
*/
|
|
12
|
+
import { AccessToken } from 'livekit-server-sdk';
|
|
13
|
+
/** Mints scoped LiveKit access tokens. Stateless apart from its resolved credentials. */
|
|
14
|
+
export class LiveKitTokenService {
|
|
15
|
+
/**
|
|
16
|
+
* @param config Explicit credentials; any omitted field falls back to the corresponding environment
|
|
17
|
+
* variable (`LIVEKIT_URL`, `LIVEKIT_API_KEY`, `LIVEKIT_API_SECRET`).
|
|
18
|
+
*/
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = {
|
|
21
|
+
ServerUrl: config?.ServerUrl ?? process.env.LIVEKIT_URL ?? '',
|
|
22
|
+
ApiKey: config?.ApiKey ?? process.env.LIVEKIT_API_KEY ?? '',
|
|
23
|
+
ApiSecret: config?.ApiSecret ?? process.env.LIVEKIT_API_SECRET ?? '',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Whether the service has the credentials needed to mint tokens. */
|
|
27
|
+
get IsConfigured() {
|
|
28
|
+
return Boolean(this.config.ServerUrl && this.config.ApiKey && this.config.ApiSecret);
|
|
29
|
+
}
|
|
30
|
+
/** The configured server URL (for callers that need to return it to clients). */
|
|
31
|
+
get ServerUrl() {
|
|
32
|
+
return this.config.ServerUrl;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Mints an access token for the given parameters.
|
|
36
|
+
*
|
|
37
|
+
* @param params The token parameters (room, identity, grants, role).
|
|
38
|
+
* @returns The minted token + connection coordinates.
|
|
39
|
+
* @throws {Error} when credentials are not configured.
|
|
40
|
+
*/
|
|
41
|
+
async MintToken(params) {
|
|
42
|
+
this.assertConfigured();
|
|
43
|
+
const at = new AccessToken(this.config.ApiKey, this.config.ApiSecret, {
|
|
44
|
+
identity: params.Identity,
|
|
45
|
+
name: params.DisplayName,
|
|
46
|
+
ttl: params.TtlSeconds ?? 3600,
|
|
47
|
+
metadata: this.buildMetadata(params),
|
|
48
|
+
});
|
|
49
|
+
at.addGrant(this.buildGrant(params));
|
|
50
|
+
const token = await at.toJwt();
|
|
51
|
+
return { ServerUrl: this.config.ServerUrl, Token: token, Identity: params.Identity, RoomName: params.RoomName };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Mints a token for a HUMAN participant (publish + subscribe + data, `participant` role).
|
|
55
|
+
*
|
|
56
|
+
* @param roomName The room to join.
|
|
57
|
+
* @param identity The participant identity.
|
|
58
|
+
* @param displayName The display name.
|
|
59
|
+
* @param ttlSeconds Optional token lifetime.
|
|
60
|
+
*/
|
|
61
|
+
MintClientToken(roomName, identity, displayName, ttlSeconds) {
|
|
62
|
+
return this.MintToken({ RoomName: roomName, Identity: identity, DisplayName: displayName, Role: 'participant', TtlSeconds: ttlSeconds });
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Mints a token for the AGENT bot (publish + subscribe + data, `agent` role metadata so the UI badges
|
|
66
|
+
* it correctly). The bridge passes this in its session `Configuration.AccessToken`.
|
|
67
|
+
*
|
|
68
|
+
* @param roomName The room to join.
|
|
69
|
+
* @param identity The bot identity.
|
|
70
|
+
* @param displayName The bot display name.
|
|
71
|
+
*/
|
|
72
|
+
MintBotToken(roomName, identity, displayName) {
|
|
73
|
+
return this.MintToken({ RoomName: roomName, Identity: identity, DisplayName: displayName, Role: 'agent' });
|
|
74
|
+
}
|
|
75
|
+
/** Builds the LiveKit video grant from the params (defaults: publish + subscribe + data all on). */
|
|
76
|
+
buildGrant(params) {
|
|
77
|
+
return {
|
|
78
|
+
roomJoin: true,
|
|
79
|
+
room: params.RoomName,
|
|
80
|
+
canPublish: params.CanPublish ?? true,
|
|
81
|
+
canSubscribe: params.CanSubscribe ?? true,
|
|
82
|
+
canPublishData: params.CanPublishData ?? true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Builds the participant metadata JSON string, always stamping the role for UI resolution. */
|
|
86
|
+
buildMetadata(params) {
|
|
87
|
+
return JSON.stringify({ mjRole: params.Role ?? 'participant', ...(params.Metadata ?? {}) });
|
|
88
|
+
}
|
|
89
|
+
/** Throws a clear error when credentials are missing. */
|
|
90
|
+
assertConfigured() {
|
|
91
|
+
if (!this.IsConfigured) {
|
|
92
|
+
throw new Error('LiveKitTokenService is not configured. Set LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET ' +
|
|
93
|
+
'(or pass them to the constructor) before minting tokens.');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=livekit-token-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"livekit-token-service.js","sourceRoot":"","sources":["../src/livekit-token-service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/livekit-room-server",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "5.42.0",
|
|
5
|
+
"description": "MemberJunction: Server-side LiveKit room support — mints scoped client/bot access tokens (livekit-server-sdk) and provides the session-start coordinator that opens a realtime model session and bridges it into a LiveKit room via AIBridgeEngine. The MJ realtime-bridge ↔ LiveKit UI seam.",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"/dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc && tsc-alias -f",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest"
|
|
15
|
+
},
|
|
16
|
+
"author": "MemberJunction.com",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@memberjunction/core": "5.42.0",
|
|
20
|
+
"@memberjunction/global": "5.42.0",
|
|
21
|
+
"@memberjunction/core-entities": "5.42.0",
|
|
22
|
+
"@memberjunction/ai": "5.42.0",
|
|
23
|
+
"@memberjunction/ai-bridge-base": "5.42.0",
|
|
24
|
+
"@memberjunction/ai-bridge-server": "5.42.0",
|
|
25
|
+
"@memberjunction/ai-bridge-livekit": "5.42.0",
|
|
26
|
+
"livekit-server-sdk": "^2.9.7"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "24.10.11",
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vitest": "^4.0.18"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/MemberJunction/MJ"
|
|
36
|
+
}
|
|
10
37
|
}
|