@webex/internal-plugin-llm 3.12.0-next.9 → 3.12.0-task-refactor.1
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 +12 -83
- package/dist/constants.js +1 -8
- package/dist/constants.js.map +1 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +1 -1
- package/dist/llm.js +51 -247
- package/dist/llm.js.map +1 -1
- package/dist/llm.types.js +0 -6
- package/dist/llm.types.js.map +1 -1
- package/package.json +6 -6
- package/src/constants.ts +0 -12
- package/src/index.ts +0 -2
- package/src/llm.ts +33 -236
- package/src/llm.types.ts +5 -28
- package/test/unit/spec/llm.js +52 -349
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"node": ">=18"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@webex/internal-plugin-mercury": "3.12.0-
|
|
16
|
+
"@webex/internal-plugin-mercury": "3.12.0-task-refactor.1"
|
|
17
17
|
},
|
|
18
18
|
"browserify": {
|
|
19
19
|
"transform": [
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"@webex/eslint-config-legacy": "0.0.0",
|
|
28
28
|
"@webex/jest-config-legacy": "0.0.0",
|
|
29
29
|
"@webex/legacy-tools": "0.0.0",
|
|
30
|
-
"@webex/test-helper-chai": "3.
|
|
31
|
-
"@webex/test-helper-mocha": "3.
|
|
32
|
-
"@webex/test-helper-mock-webex": "3.
|
|
33
|
-
"@webex/test-helper-test-users": "3.
|
|
30
|
+
"@webex/test-helper-chai": "3.12.0-task-refactor.1",
|
|
31
|
+
"@webex/test-helper-mocha": "3.12.0-task-refactor.1",
|
|
32
|
+
"@webex/test-helper-mock-webex": "3.12.0-task-refactor.1",
|
|
33
|
+
"@webex/test-helper-test-users": "3.12.0-task-refactor.1",
|
|
34
34
|
"eslint": "^8.24.0",
|
|
35
35
|
"prettier": "^2.7.1",
|
|
36
36
|
"sinon": "^9.2.4"
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"test:style": "eslint ./src/**/*.*",
|
|
45
45
|
"test:unit": "webex-legacy-tools test --unit --runner jest"
|
|
46
46
|
},
|
|
47
|
-
"version": "3.12.0-
|
|
47
|
+
"version": "3.12.0-task-refactor.1"
|
|
48
48
|
}
|
package/src/constants.ts
CHANGED
|
@@ -1,14 +1,2 @@
|
|
|
1
1
|
// eslint-disable-next-line import/prefer-default-export
|
|
2
2
|
export const LLM = 'llm';
|
|
3
|
-
|
|
4
|
-
export const LLM_DEFAULT_SESSION = 'llm-default-session';
|
|
5
|
-
|
|
6
|
-
export const DATA_CHANNEL_WITH_JWT_TOKEN = 'data-channel-with-jwt-token';
|
|
7
|
-
|
|
8
|
-
export const SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM = 'subscriptionAwareSubchannels';
|
|
9
|
-
|
|
10
|
-
export const DATA_CHNANEL_TYPE = {
|
|
11
|
-
TRANSCRIPTION: 'transcription',
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const AWARE_DATA_CHANNEL = [DATA_CHNANEL_TYPE.TRANSCRIPTION];
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import * as WebexCore from '@webex/webex-core';
|
|
2
2
|
import LLMChannel, {config} from './llm';
|
|
3
|
-
import {DataChannelTokenType} from './llm.types';
|
|
4
3
|
|
|
5
4
|
WebexCore.registerInternalPlugin('llm', LLMChannel, {
|
|
6
5
|
config,
|
|
7
6
|
});
|
|
8
7
|
|
|
9
|
-
export {DataChannelTokenType};
|
|
10
8
|
export {default} from './llm';
|
package/src/llm.ts
CHANGED
|
@@ -2,15 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import Mercury from '@webex/internal-plugin-mercury';
|
|
4
4
|
|
|
5
|
+
import {LLM} from './constants';
|
|
5
6
|
// eslint-disable-next-line no-unused-vars
|
|
6
|
-
import {
|
|
7
|
-
LLM,
|
|
8
|
-
DATA_CHANNEL_WITH_JWT_TOKEN,
|
|
9
|
-
AWARE_DATA_CHANNEL,
|
|
10
|
-
SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM,
|
|
11
|
-
LLM_DEFAULT_SESSION,
|
|
12
|
-
} from './constants';
|
|
13
|
-
import {ILLMChannel, DataChannelTokenType} from './llm.types';
|
|
7
|
+
import {ILLMChannel} from './llm.types';
|
|
14
8
|
|
|
15
9
|
export const config = {
|
|
16
10
|
llm: {
|
|
@@ -48,287 +42,90 @@ export const config = {
|
|
|
48
42
|
*/
|
|
49
43
|
export default class LLMChannel extends (Mercury as any) implements ILLMChannel {
|
|
50
44
|
namespace = LLM;
|
|
51
|
-
|
|
45
|
+
|
|
52
46
|
/**
|
|
53
|
-
*
|
|
54
|
-
* @
|
|
55
|
-
* @type {
|
|
47
|
+
* If the LLM plugin has been registered and listening
|
|
48
|
+
* @instance
|
|
49
|
+
* @type {Boolean}
|
|
50
|
+
* @public
|
|
56
51
|
*/
|
|
57
|
-
private connections: Map<
|
|
58
|
-
string,
|
|
59
|
-
{
|
|
60
|
-
webSocketUrl?: string;
|
|
61
|
-
binding?: string;
|
|
62
|
-
locusUrl?: string;
|
|
63
|
-
datachannelUrl?: string;
|
|
64
|
-
datachannelToken?: string;
|
|
65
|
-
}
|
|
66
|
-
> = new Map();
|
|
67
52
|
|
|
68
|
-
private
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
53
|
+
private webSocketUrl?: string;
|
|
54
|
+
|
|
55
|
+
private binding?: string;
|
|
56
|
+
|
|
57
|
+
private locusUrl?: string;
|
|
72
58
|
|
|
73
|
-
private
|
|
74
|
-
body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
|
|
75
|
-
}>;
|
|
59
|
+
private datachannelUrl?: string;
|
|
76
60
|
|
|
77
61
|
/**
|
|
78
62
|
* Register to the websocket
|
|
79
63
|
* @param {string} llmSocketUrl
|
|
80
|
-
* @param {string} datachannelToken
|
|
81
|
-
* @param {string} sessionId - Connection identifier
|
|
82
64
|
* @returns {Promise<void>}
|
|
83
65
|
*/
|
|
84
|
-
private register =
|
|
85
|
-
|
|
86
|
-
datachannelToken?: string,
|
|
87
|
-
sessionId: string = LLM_DEFAULT_SESSION
|
|
88
|
-
): Promise<void> => {
|
|
89
|
-
const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
|
|
90
|
-
|
|
91
|
-
return this.request({
|
|
66
|
+
private register = (llmSocketUrl: string): Promise<void> =>
|
|
67
|
+
this.request({
|
|
92
68
|
method: 'POST',
|
|
93
69
|
url: llmSocketUrl,
|
|
94
70
|
body: {deviceUrl: this.webex.internal.device.url},
|
|
95
|
-
headers:
|
|
96
|
-
isDataChannelTokenEnabled && datachannelToken
|
|
97
|
-
? {'Data-Channel-Auth-Token': datachannelToken}
|
|
98
|
-
: {},
|
|
99
71
|
})
|
|
100
72
|
.then((res: {body: {webSocketUrl: string; binding: string}}) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
sessionData.webSocketUrl = res.body.webSocketUrl;
|
|
104
|
-
sessionData.binding = res.body.binding;
|
|
105
|
-
this.connections.set(sessionId, sessionData);
|
|
73
|
+
this.webSocketUrl = res.body.webSocketUrl;
|
|
74
|
+
this.binding = res.body.binding;
|
|
106
75
|
})
|
|
107
76
|
.catch((error: any) => {
|
|
108
|
-
this.logger.error(`Error connecting to websocket
|
|
77
|
+
this.logger.error(`Error connecting to websocket: ${error}`);
|
|
109
78
|
throw error;
|
|
110
79
|
});
|
|
111
|
-
};
|
|
112
80
|
|
|
113
81
|
/**
|
|
114
82
|
* Register and connect to the websocket
|
|
115
83
|
* @param {string} locusUrl
|
|
116
84
|
* @param {string} datachannelUrl
|
|
117
|
-
* @param {string} datachannelToken
|
|
118
|
-
* @param {string} sessionId - Connection identifier
|
|
119
85
|
* @returns {Promise<void>}
|
|
120
86
|
*/
|
|
121
|
-
public registerAndConnect = (
|
|
122
|
-
|
|
123
|
-
datachannelUrl: string,
|
|
124
|
-
datachannelToken?: string,
|
|
125
|
-
sessionId: string = LLM_DEFAULT_SESSION
|
|
126
|
-
): Promise<void> =>
|
|
127
|
-
this.register(datachannelUrl, datachannelToken, sessionId).then(async () => {
|
|
87
|
+
public registerAndConnect = (locusUrl: string, datachannelUrl: string): Promise<void> =>
|
|
88
|
+
this.register(datachannelUrl).then(() => {
|
|
128
89
|
if (!locusUrl || !datachannelUrl) return undefined;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
sessionData.locusUrl = locusUrl;
|
|
133
|
-
sessionData.datachannelUrl = datachannelUrl;
|
|
134
|
-
sessionData.datachannelToken = datachannelToken;
|
|
135
|
-
this.connections.set(sessionId, sessionData);
|
|
136
|
-
|
|
137
|
-
const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
|
|
138
|
-
|
|
139
|
-
const connectUrl = isDataChannelTokenEnabled
|
|
140
|
-
? LLMChannel.buildUrlWithAwareSubchannels(sessionData.webSocketUrl, AWARE_DATA_CHANNEL)
|
|
141
|
-
: sessionData.webSocketUrl;
|
|
142
|
-
|
|
143
|
-
return this.connect(connectUrl, sessionId);
|
|
90
|
+
this.locusUrl = locusUrl;
|
|
91
|
+
this.datachannelUrl = datachannelUrl;
|
|
92
|
+
this.connect(this.webSocketUrl);
|
|
144
93
|
});
|
|
145
94
|
|
|
146
95
|
/**
|
|
147
96
|
* Tells if LLM socket is connected
|
|
148
|
-
* @param {string} sessionId - Connection identifier
|
|
149
97
|
* @returns {boolean} connected
|
|
150
98
|
*/
|
|
151
|
-
public isConnected = (
|
|
152
|
-
const socket = this.getSocket(sessionId);
|
|
153
|
-
|
|
154
|
-
return socket ? socket.connected : false;
|
|
155
|
-
};
|
|
99
|
+
public isConnected = (): boolean => this.connected;
|
|
156
100
|
|
|
157
101
|
/**
|
|
158
102
|
* Tells if LLM socket is binding
|
|
159
|
-
* @param {string} sessionId - Connection identifier
|
|
160
103
|
* @returns {string} binding
|
|
161
104
|
*/
|
|
162
|
-
public getBinding = (
|
|
163
|
-
const sessionData = this.connections.get(sessionId);
|
|
164
|
-
|
|
165
|
-
return sessionData?.binding;
|
|
166
|
-
};
|
|
105
|
+
public getBinding = (): string => this.binding;
|
|
167
106
|
|
|
168
107
|
/**
|
|
169
108
|
* Get Locus URL for the connection
|
|
170
|
-
* @param {string} sessionId - Connection identifier
|
|
171
109
|
* @returns {string} locus Url
|
|
172
110
|
*/
|
|
173
|
-
public getLocusUrl = (
|
|
174
|
-
const sessionData = this.connections.get(sessionId);
|
|
175
|
-
|
|
176
|
-
return sessionData?.locusUrl;
|
|
177
|
-
};
|
|
111
|
+
public getLocusUrl = (): string => this.locusUrl;
|
|
178
112
|
|
|
179
113
|
/**
|
|
180
114
|
* Get data channel URL for the connection
|
|
181
|
-
* @param {string} sessionId - Connection identifier
|
|
182
115
|
* @returns {string} data channel Url
|
|
183
116
|
*/
|
|
184
|
-
public getDatachannelUrl = (
|
|
185
|
-
const sessionData = this.connections.get(sessionId);
|
|
186
|
-
|
|
187
|
-
return sessionData?.datachannelUrl;
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Get data channel token for the connection
|
|
192
|
-
* @param {DataChannelTokenType} dataChannelTokenType
|
|
193
|
-
* @returns {string} data channel token
|
|
194
|
-
*/
|
|
195
|
-
public getDatachannelToken = (
|
|
196
|
-
dataChannelTokenType: DataChannelTokenType = DataChannelTokenType.Default
|
|
197
|
-
): string => {
|
|
198
|
-
return this.datachannelTokens[dataChannelTokenType];
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Set data channel token for the connection
|
|
203
|
-
* @param {string} datachannelToken - data channel token
|
|
204
|
-
* @param {DataChannelTokenType} dataChannelTokenType
|
|
205
|
-
* @returns {void}
|
|
206
|
-
*/
|
|
207
|
-
public setDatachannelToken = (
|
|
208
|
-
datachannelToken: string,
|
|
209
|
-
dataChannelTokenType: DataChannelTokenType = DataChannelTokenType.Default
|
|
210
|
-
): void => {
|
|
211
|
-
this.datachannelTokens[dataChannelTokenType] = datachannelToken;
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Resets all data‑channel tokens to their initial undefined values.
|
|
216
|
-
* Used when leaving or disconnecting from a meeting.
|
|
217
|
-
* @returns {void}
|
|
218
|
-
*/
|
|
219
|
-
public resetDatachannelTokens() {
|
|
220
|
-
this.datachannelTokens = {
|
|
221
|
-
[DataChannelTokenType.Default]: undefined,
|
|
222
|
-
[DataChannelTokenType.PracticeSession]: undefined,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Set the handler used to refresh the DataChannel token
|
|
228
|
-
*
|
|
229
|
-
* @param {function} handler - Function that returns a refreshed token
|
|
230
|
-
* @returns {void}
|
|
231
|
-
*/
|
|
232
|
-
public setRefreshHandler(
|
|
233
|
-
handler: () => Promise<{
|
|
234
|
-
body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
|
|
235
|
-
}>
|
|
236
|
-
) {
|
|
237
|
-
this.refreshHandler = handler;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Refresh the data channel token using the injected handler.
|
|
242
|
-
* Logs a descriptive error if the handler is missing or fails.
|
|
243
|
-
*
|
|
244
|
-
* @returns {Promise<string>} The refreshed token.
|
|
245
|
-
*/
|
|
246
|
-
public async refreshDataChannelToken() {
|
|
247
|
-
if (!this.refreshHandler) {
|
|
248
|
-
this.logger.warn(
|
|
249
|
-
'llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh'
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
const res = await this.refreshHandler();
|
|
257
|
-
|
|
258
|
-
return res;
|
|
259
|
-
} catch (error: any) {
|
|
260
|
-
this.logger.warn(
|
|
261
|
-
`llm#refreshDataChannelToken --> DataChannel token refresh failed (likely locus changed or participant left): ${
|
|
262
|
-
error?.message || error
|
|
263
|
-
}`
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
117
|
+
public getDatachannelUrl = (): string => this.datachannelUrl;
|
|
269
118
|
|
|
270
119
|
/**
|
|
271
120
|
* Disconnects websocket connection
|
|
272
121
|
* @param {{code: number, reason: string}} options - The disconnect option object with code and reason
|
|
273
|
-
* @param {string} sessionId - Connection identifier
|
|
274
|
-
* @returns {Promise<void>}
|
|
275
|
-
*/
|
|
276
|
-
public disconnectLLM = (
|
|
277
|
-
options: {code: number; reason: string},
|
|
278
|
-
sessionId: string = LLM_DEFAULT_SESSION
|
|
279
|
-
): Promise<void> =>
|
|
280
|
-
this.disconnect(options, sessionId).then(() => {
|
|
281
|
-
// Clean up sessions data
|
|
282
|
-
this.connections.delete(sessionId);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Disconnects all LLM websocket connections
|
|
287
|
-
* @param {{code: number, reason: string}} options - The disconnect option object with code and reason
|
|
288
122
|
* @returns {Promise<void>}
|
|
289
123
|
*/
|
|
290
|
-
public
|
|
291
|
-
this.
|
|
292
|
-
|
|
293
|
-
this.
|
|
124
|
+
public disconnectLLM = (options: object): Promise<void> =>
|
|
125
|
+
this.disconnect(options).then(() => {
|
|
126
|
+
this.locusUrl = undefined;
|
|
127
|
+
this.datachannelUrl = undefined;
|
|
128
|
+
this.binding = undefined;
|
|
129
|
+
this.webSocketUrl = undefined;
|
|
294
130
|
});
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Get all active LLM connections
|
|
298
|
-
* @returns {Map} Map of sessionId to session data
|
|
299
|
-
*/
|
|
300
|
-
public getAllConnections = (): Map<
|
|
301
|
-
string,
|
|
302
|
-
{
|
|
303
|
-
webSocketUrl?: string;
|
|
304
|
-
binding?: string;
|
|
305
|
-
locusUrl?: string;
|
|
306
|
-
datachannelUrl?: string;
|
|
307
|
-
datachannelToken?: string;
|
|
308
|
-
}
|
|
309
|
-
> => new Map(this.connections);
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Returns true if data channel token is enabled, false otherwise
|
|
313
|
-
* @returns {Promise<boolean>} resolves with true if data channel token is enabled
|
|
314
|
-
*/
|
|
315
|
-
public isDataChannelTokenEnabled(): Promise<boolean> {
|
|
316
|
-
// @ts-ignore
|
|
317
|
-
return this.webex.internal.feature.getFeature('developer', DATA_CHANNEL_WITH_JWT_TOKEN);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Builds a WebSocket URL with the `subscriptionAwareSubchannels` query parameter.
|
|
322
|
-
*
|
|
323
|
-
* @param {string} baseUrl - The original WebSocket URL.
|
|
324
|
-
* @param {string[]} subchannels - List of subchannels to declare as subscription-aware.
|
|
325
|
-
* @returns {string} The final URL with updated query parameters.
|
|
326
|
-
*/
|
|
327
|
-
|
|
328
|
-
public static buildUrlWithAwareSubchannels = (baseUrl: string, subchannels: string[]) => {
|
|
329
|
-
const urlObj = new URL(baseUrl);
|
|
330
|
-
urlObj.searchParams.set(SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM, subchannels.join(','));
|
|
331
|
-
|
|
332
|
-
return urlObj.toString();
|
|
333
|
-
};
|
|
334
131
|
}
|
package/src/llm.types.ts
CHANGED
|
@@ -1,32 +1,9 @@
|
|
|
1
1
|
interface ILLMChannel {
|
|
2
|
-
registerAndConnect: (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
) => Promise<void>;
|
|
8
|
-
isConnected: (sessionId?: string) => boolean;
|
|
9
|
-
getBinding: (sessionId?: string) => string;
|
|
10
|
-
getLocusUrl: (sessionId?: string) => string;
|
|
11
|
-
getDatachannelUrl: (sessionId?: string) => string;
|
|
12
|
-
disconnectLLM: (options: {code: number; reason: string}, sessionId?: string) => Promise<void>;
|
|
13
|
-
disconnectAllLLM: (options?: {code: number; reason: string}) => Promise<void>;
|
|
14
|
-
getAllConnections: () => Map<
|
|
15
|
-
string,
|
|
16
|
-
{
|
|
17
|
-
webSocketUrl?: string;
|
|
18
|
-
binding?: string;
|
|
19
|
-
locusUrl?: string;
|
|
20
|
-
datachannelUrl?: string;
|
|
21
|
-
datachannelToken?: string;
|
|
22
|
-
}
|
|
23
|
-
>;
|
|
2
|
+
registerAndConnect: (locusUrl: string, datachannelUrl: string) => Promise<void>;
|
|
3
|
+
isConnected: () => boolean;
|
|
4
|
+
getBinding: () => string;
|
|
5
|
+
getLocusUrl: () => string;
|
|
6
|
+
disconnectLLM: (options: {code: number; reason: string}) => Promise<void>;
|
|
24
7
|
}
|
|
25
|
-
|
|
26
|
-
export enum DataChannelTokenType {
|
|
27
|
-
Default = 'llm-default-session',
|
|
28
|
-
PracticeSession = 'llm-practice-session',
|
|
29
|
-
}
|
|
30
|
-
|
|
31
8
|
// eslint-disable-next-line import/prefer-default-export
|
|
32
9
|
export type {ILLMChannel};
|