@webex/internal-plugin-llm 3.11.0 → 3.12.0-llmrefactor.2
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 +63 -12
- package/dist/constants.js +9 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +35 -4
- package/dist/index.js.map +1 -1
- package/dist/llm-plugin.js +199 -0
- package/dist/llm-plugin.js.map +1 -0
- package/dist/llm.js +280 -48
- package/dist/llm.js.map +1 -1
- package/dist/llm.types.js +18 -0
- package/dist/llm.types.js.map +1 -1
- package/package.json +6 -6
- package/src/constants.ts +13 -0
- package/src/index.ts +12 -2
- package/src/llm-plugin.ts +115 -0
- package/src/llm.ts +229 -42
- package/src/llm.types.ts +92 -5
- package/test/unit/spec/llm-plugin.js +391 -0
- package/test/unit/spec/llm.js +473 -85
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* eslint-disable require-jsdoc */
|
|
2
|
+
import {WebexPlugin} from '@webex/webex-core';
|
|
3
|
+
import LLMChannel, {config} from './llm';
|
|
4
|
+
import {DATA_CHANNEL_WITH_JWT_TOKEN} from './constants';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* LLMPlugin — registered as `webex.internal.llm`.
|
|
8
|
+
*
|
|
9
|
+
* factory for creating LLMChannel instances. Each Meeting creates and owns
|
|
10
|
+
* its own LLMChannel(s), allowing multiple independent connections without
|
|
11
|
+
* the need for session IDs or ownership tracking.
|
|
12
|
+
*
|
|
13
|
+
* This is a breaking API change from the previous design where the plugin
|
|
14
|
+
* maintained a Map of sessions and exposed session-keyed methods.
|
|
15
|
+
*
|
|
16
|
+
* Old usage (no longer supported):
|
|
17
|
+
* webex.internal.llm.isConnected()
|
|
18
|
+
* webex.internal.llm.registerAndConnect(url, dcUrl, token, sessionId)
|
|
19
|
+
*
|
|
20
|
+
* New usage:
|
|
21
|
+
* const llm = webex.internal.llm.createChannel();
|
|
22
|
+
* await llm.registerAndConnect(url, dcUrl, token);
|
|
23
|
+
* llm.isConnected();
|
|
24
|
+
* // When done:
|
|
25
|
+
* await llm.disconnect();
|
|
26
|
+
*/
|
|
27
|
+
export class LLMPlugin extends (WebexPlugin as any) {
|
|
28
|
+
namespace = 'llm';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Registry of active LLM channels for interceptor lookup.
|
|
32
|
+
* Channels are registered when created and unregistered on disconnect.
|
|
33
|
+
*/
|
|
34
|
+
private channels = new Set<LLMChannel>();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new LLMChannel instance. The caller owns the channel and is
|
|
38
|
+
* responsible for connecting, disconnecting, and cleaning it up.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const llm = webex.internal.llm.createChannel();
|
|
42
|
+
* llm.setRefreshHandler(() => meeting.refreshDataChannelToken());
|
|
43
|
+
* await llm.registerAndConnect(locusUrl, datachannelUrl, token);
|
|
44
|
+
*
|
|
45
|
+
* // Subscribe to events directly on the channel
|
|
46
|
+
* llm.on('event:relay.event', handler);
|
|
47
|
+
* llm.on('online', onlineHandler);
|
|
48
|
+
*
|
|
49
|
+
* // When done
|
|
50
|
+
* await llm.disconnect();
|
|
51
|
+
*
|
|
52
|
+
* @returns {LLMChannel} A new LLM channel instance
|
|
53
|
+
*/
|
|
54
|
+
public createChannel(): LLMChannel {
|
|
55
|
+
// @ts-ignore — WebexPlugin children require {parent: this.webex}
|
|
56
|
+
const channel = new LLMChannel({parent: this.webex});
|
|
57
|
+
|
|
58
|
+
this.channels.add(channel);
|
|
59
|
+
channel.on('disconnected', () => this.channels.delete(channel));
|
|
60
|
+
|
|
61
|
+
return channel;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns true if the data channel token feature flag is enabled.
|
|
66
|
+
* This is a global check, not per-connection.
|
|
67
|
+
* @returns {Promise<boolean>}
|
|
68
|
+
*/
|
|
69
|
+
public isDataChannelTokenEnabled(): Promise<boolean> {
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
return this.webex.internal.feature.getFeature('developer', DATA_CHANNEL_WITH_JWT_TOKEN);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Find a channel by its datachannel URL. Used by the interceptor to
|
|
76
|
+
* route token refresh requests to the correct channel.
|
|
77
|
+
* @param {string} url - The request URL to match
|
|
78
|
+
* @returns {LLMChannel | undefined}
|
|
79
|
+
*/
|
|
80
|
+
public getChannelByDatachannelUrl(url: string): LLMChannel | undefined {
|
|
81
|
+
for (const channel of this.channels) {
|
|
82
|
+
const datachannelUrl = channel.getDatachannelUrl();
|
|
83
|
+
|
|
84
|
+
if (datachannelUrl && LLMChannel.matchesDatachannelRequestUrl(url, datachannelUrl)) {
|
|
85
|
+
return channel;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get all active channels. Useful for diagnostics/debugging.
|
|
94
|
+
* @returns {Set<LLMChannel>}
|
|
95
|
+
*/
|
|
96
|
+
public getAllChannels(): Set<LLMChannel> {
|
|
97
|
+
return new Set(this.channels);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Disconnect all active channels. Useful for cleanup on logout.
|
|
102
|
+
* @param {object} [options] - Disconnect options
|
|
103
|
+
* @param {number} [options.code] - WebSocket close code
|
|
104
|
+
* @param {string} [options.reason] - WebSocket close reason
|
|
105
|
+
* @returns {Promise<void>}
|
|
106
|
+
*/
|
|
107
|
+
public async disconnectAllChannels(options?: {code: number; reason: string}): Promise<void> {
|
|
108
|
+
const promises = Array.from(this.channels).map((channel) => channel.disconnect(options));
|
|
109
|
+
|
|
110
|
+
await Promise.all(promises);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {config};
|
|
115
|
+
export default LLMPlugin;
|
package/src/llm.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/* eslint-disable consistent-return */
|
|
2
|
-
|
|
3
2
|
import Mercury from '@webex/internal-plugin-mercury';
|
|
4
3
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
LLM,
|
|
6
|
+
DATA_CHANNEL_WITH_JWT_TOKEN,
|
|
7
|
+
AWARE_DATA_CHANNEL,
|
|
8
|
+
SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM,
|
|
9
|
+
} from './constants';
|
|
10
|
+
import {ILLMChannel, DataChannelTokenType, RegisterAndConnectTiming} from './llm.types';
|
|
8
11
|
|
|
9
12
|
export const config = {
|
|
10
13
|
llm: {
|
|
@@ -38,59 +41,128 @@ export const config = {
|
|
|
38
41
|
};
|
|
39
42
|
|
|
40
43
|
/**
|
|
41
|
-
* LLMChannel to
|
|
44
|
+
* LLMChannel — a single WebSocket connection to the LLM data channel.
|
|
45
|
+
*
|
|
46
|
+
* Created via `webex.internal.llm.createChannel()`. The caller owns the
|
|
47
|
+
* channel and is responsible for its lifecycle (connect, disconnect, cleanup).
|
|
48
|
+
* Multiple LLMChannels can exist simultaneously for different meetings.
|
|
42
49
|
*/
|
|
43
50
|
export default class LLMChannel extends (Mercury as any) implements ILLMChannel {
|
|
44
51
|
namespace = LLM;
|
|
45
52
|
|
|
46
|
-
/**
|
|
47
|
-
* If the LLM plugin has been registered and listening
|
|
48
|
-
* @instance
|
|
49
|
-
* @type {Boolean}
|
|
50
|
-
* @public
|
|
51
|
-
*/
|
|
52
|
-
|
|
53
53
|
private webSocketUrl?: string;
|
|
54
|
-
|
|
55
54
|
private binding?: string;
|
|
56
|
-
|
|
57
55
|
private locusUrl?: string;
|
|
58
|
-
|
|
59
56
|
private datachannelUrl?: string;
|
|
57
|
+
private datachannelToken?: string;
|
|
58
|
+
private refreshHandler?: () => Promise<{
|
|
59
|
+
body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
|
|
60
|
+
}>;
|
|
61
|
+
|
|
62
|
+
/** In-flight connection promise for deduplication. */
|
|
63
|
+
private connectingPromise?: Promise<RegisterAndConnectTiming | void>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if a connection is currently in progress.
|
|
67
|
+
* @returns {boolean} True if connecting
|
|
68
|
+
*/
|
|
69
|
+
public isConnecting(): boolean {
|
|
70
|
+
return !!this.connectingPromise;
|
|
71
|
+
}
|
|
60
72
|
|
|
61
73
|
/**
|
|
62
74
|
* Register to the websocket
|
|
63
75
|
* @param {string} llmSocketUrl
|
|
76
|
+
* @param {string} datachannelToken
|
|
64
77
|
* @returns {Promise<void>}
|
|
65
78
|
*/
|
|
66
|
-
private register = (llmSocketUrl: string): Promise<void> =>
|
|
67
|
-
this.
|
|
79
|
+
private register = async (llmSocketUrl: string, datachannelToken?: string): Promise<void> => {
|
|
80
|
+
const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
|
|
81
|
+
|
|
82
|
+
return this.request({
|
|
68
83
|
method: 'POST',
|
|
69
84
|
url: llmSocketUrl,
|
|
70
85
|
body: {deviceUrl: this.webex.internal.device.url},
|
|
86
|
+
headers:
|
|
87
|
+
isDataChannelTokenEnabled && datachannelToken
|
|
88
|
+
? {'Data-Channel-Auth-Token': datachannelToken}
|
|
89
|
+
: {},
|
|
71
90
|
})
|
|
72
91
|
.then((res: {body: {webSocketUrl: string; binding: string}}) => {
|
|
73
92
|
this.webSocketUrl = res.body.webSocketUrl;
|
|
74
93
|
this.binding = res.body.binding;
|
|
75
94
|
})
|
|
76
95
|
.catch((error: any) => {
|
|
77
|
-
this.logger.error(`Error connecting to websocket: ${error}`);
|
|
96
|
+
this.logger.error(`Error connecting to websocket for : ${error}`);
|
|
78
97
|
throw error;
|
|
79
98
|
});
|
|
99
|
+
};
|
|
80
100
|
|
|
81
101
|
/**
|
|
82
|
-
* Register and connect to the websocket
|
|
102
|
+
* Register and connect to the websocket.
|
|
103
|
+
* Handles deduplication: returns existing promise if connection in progress,
|
|
104
|
+
* or resolves immediately if already connected to the same URLs.
|
|
83
105
|
* @param {string} locusUrl
|
|
84
106
|
* @param {string} datachannelUrl
|
|
85
|
-
* @
|
|
107
|
+
* @param {string} datachannelToken
|
|
108
|
+
* @returns {Promise<RegisterAndConnectTiming | void>}
|
|
86
109
|
*/
|
|
87
|
-
public registerAndConnect = (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
public registerAndConnect = (
|
|
111
|
+
locusUrl: string,
|
|
112
|
+
datachannelUrl: string,
|
|
113
|
+
datachannelToken?: string
|
|
114
|
+
): Promise<RegisterAndConnectTiming | void> => {
|
|
115
|
+
// Deduplicate concurrent calls while a connection is in-flight
|
|
116
|
+
if (this.connectingPromise) {
|
|
117
|
+
return this.connectingPromise;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// If already connected to the exact same datachannel URL, avoid triggering
|
|
121
|
+
// a reconnect that would cause the server to replace with 4000 Replaced
|
|
122
|
+
if (
|
|
123
|
+
this.isConnected() &&
|
|
124
|
+
this.datachannelUrl === datachannelUrl &&
|
|
125
|
+
this.locusUrl === locusUrl
|
|
126
|
+
) {
|
|
127
|
+
return Promise.resolve();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.locusUrl = locusUrl;
|
|
131
|
+
this.datachannelUrl = datachannelUrl;
|
|
132
|
+
const registerStart = performance.now();
|
|
133
|
+
|
|
134
|
+
this.connectingPromise = this.register(datachannelUrl, datachannelToken)
|
|
135
|
+
.then(async () => {
|
|
136
|
+
const clientLLMDatachannelResponseTime = Math.round(performance.now() - registerStart);
|
|
137
|
+
const isDataChannelTokenEnabled = await this.isDataChannelTokenEnabled();
|
|
138
|
+
|
|
139
|
+
const connectUrl =
|
|
140
|
+
isDataChannelTokenEnabled && this.webSocketUrl
|
|
141
|
+
? LLMChannel.buildUrlWithAwareSubchannels(this.webSocketUrl, AWARE_DATA_CHANNEL)
|
|
142
|
+
: this.webSocketUrl;
|
|
143
|
+
|
|
144
|
+
const connectStart = performance.now();
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
await this.connect(connectUrl);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
// register() succeeded; only connect() failed. Attach the measured datachannel time so
|
|
150
|
+
// callers don't misreport a websocket failure as a registration that never completed.
|
|
151
|
+
// @ts-ignore
|
|
152
|
+
error.timing = {clientLLMDatachannelResponseTime};
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const clientLLMWebSocketConnectTime = Math.round(performance.now() - connectStart);
|
|
157
|
+
|
|
158
|
+
return {clientLLMDatachannelResponseTime, clientLLMWebSocketConnectTime};
|
|
159
|
+
})
|
|
160
|
+
.finally(() => {
|
|
161
|
+
this.connectingPromise = undefined;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return this.connectingPromise;
|
|
165
|
+
};
|
|
94
166
|
|
|
95
167
|
/**
|
|
96
168
|
* Tells if LLM socket is connected
|
|
@@ -100,32 +172,147 @@ export default class LLMChannel extends (Mercury as any) implements ILLMChannel
|
|
|
100
172
|
|
|
101
173
|
/**
|
|
102
174
|
* Tells if LLM socket is binding
|
|
103
|
-
* @returns {string} binding
|
|
175
|
+
* @returns {string | undefined} binding
|
|
104
176
|
*/
|
|
105
|
-
public getBinding = (): string => this.binding;
|
|
177
|
+
public getBinding = (): string | undefined => this.binding;
|
|
106
178
|
|
|
107
179
|
/**
|
|
108
180
|
* Get Locus URL for the connection
|
|
109
|
-
* @returns {string} locus Url
|
|
181
|
+
* @returns {string | undefined} locus Url
|
|
110
182
|
*/
|
|
111
|
-
public getLocusUrl = (): string => this.locusUrl;
|
|
183
|
+
public getLocusUrl = (): string | undefined => this.locusUrl;
|
|
112
184
|
|
|
113
185
|
/**
|
|
114
186
|
* Get data channel URL for the connection
|
|
115
|
-
* @returns {string} data channel Url
|
|
187
|
+
* @returns {string | undefined} data channel Url
|
|
116
188
|
*/
|
|
117
|
-
public getDatachannelUrl = (): string => this.datachannelUrl;
|
|
189
|
+
public getDatachannelUrl = (): string | undefined => this.datachannelUrl;
|
|
118
190
|
|
|
119
191
|
/**
|
|
120
|
-
*
|
|
121
|
-
* @
|
|
192
|
+
* Get data channel token for this connection.
|
|
193
|
+
* @returns {string | undefined}
|
|
194
|
+
*/
|
|
195
|
+
public getDatachannelToken = (): string | undefined => this.datachannelToken;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Store the data channel token for this connection.
|
|
199
|
+
* @param {string} token
|
|
200
|
+
* @returns {void}
|
|
201
|
+
*/
|
|
202
|
+
public setDatachannelToken = (token: string): void => {
|
|
203
|
+
this.datachannelToken = token;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Clear the data channel token for this connection.
|
|
208
|
+
* @returns {void}
|
|
209
|
+
*/
|
|
210
|
+
public clearDatachannelToken = (): void => {
|
|
211
|
+
this.datachannelToken = undefined;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Set the handler used to refresh the DataChannel token.
|
|
216
|
+
* @param {function} handler
|
|
217
|
+
* @returns {void}
|
|
218
|
+
*/
|
|
219
|
+
public setRefreshHandler = (
|
|
220
|
+
handler: () => Promise<{
|
|
221
|
+
body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
|
|
222
|
+
}>
|
|
223
|
+
): void => {
|
|
224
|
+
this.refreshHandler = handler;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Refresh the data channel token using the injected handler.
|
|
229
|
+
* @returns {Promise<object | null>}
|
|
230
|
+
*/
|
|
231
|
+
public async refreshDataChannelToken() {
|
|
232
|
+
if (!this.refreshHandler) {
|
|
233
|
+
this.logger.warn(
|
|
234
|
+
'llm#refreshDataChannelToken --> LLM refreshHandler is not set, skipping token refresh'
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
return await this.refreshHandler();
|
|
242
|
+
} catch (error: any) {
|
|
243
|
+
this.logger.warn(
|
|
244
|
+
`llm#refreshDataChannelToken --> DataChannel token refresh failed: ${
|
|
245
|
+
error?.message || error
|
|
246
|
+
}`
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Disconnects the WebSocket and clears all connection state.
|
|
255
|
+
* Overrides Mercury's disconnect to also clear LLM-specific state.
|
|
256
|
+
* @param {object} [options]
|
|
122
257
|
* @returns {Promise<void>}
|
|
123
258
|
*/
|
|
124
|
-
public
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
259
|
+
public async disconnect(options?: {code: number; reason: string}): Promise<void> {
|
|
260
|
+
await super.disconnect(options);
|
|
261
|
+
this.webSocketUrl = undefined;
|
|
262
|
+
this.binding = undefined;
|
|
263
|
+
this.locusUrl = undefined;
|
|
264
|
+
this.datachannelUrl = undefined;
|
|
265
|
+
this.datachannelToken = undefined;
|
|
266
|
+
this.refreshHandler = undefined;
|
|
267
|
+
this.emit('disconnected');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Matches a request URL to a stored datachannel registration URL.
|
|
272
|
+
* Host can differ (e.g. rewritten by hostmap interceptor), so we first
|
|
273
|
+
* try full URL prefix and then fall back to pathname prefix.
|
|
274
|
+
* @param {string} requestUrl
|
|
275
|
+
* @param {string} registrationUrl
|
|
276
|
+
* @returns {boolean}
|
|
277
|
+
*/
|
|
278
|
+
public static matchesDatachannelRequestUrl(requestUrl: string, registrationUrl: string): boolean {
|
|
279
|
+
if (!requestUrl || !registrationUrl) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (requestUrl.startsWith(registrationUrl)) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const request = new URL(requestUrl);
|
|
289
|
+
const registration = new URL(registrationUrl);
|
|
290
|
+
|
|
291
|
+
return request.pathname.startsWith(registration.pathname);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Returns true if the data channel token feature flag is enabled.
|
|
299
|
+
* @returns {Promise<boolean>}
|
|
300
|
+
*/
|
|
301
|
+
public isDataChannelTokenEnabled(): Promise<boolean> {
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
return this.webex.internal.feature.getFeature('developer', DATA_CHANNEL_WITH_JWT_TOKEN);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Builds a WebSocket URL with the `subscriptionAwareSubchannels` query parameter.
|
|
308
|
+
* @param {string} baseUrl
|
|
309
|
+
* @param {string[]} subchannels
|
|
310
|
+
* @returns {string}
|
|
311
|
+
*/
|
|
312
|
+
public static buildUrlWithAwareSubchannels = (baseUrl: string, subchannels: string[]) => {
|
|
313
|
+
const urlObj = new URL(baseUrl);
|
|
314
|
+
urlObj.searchParams.set(SUBSCRIPTION_AWARE_SUBCHANNELS_PARAM, subchannels.join(','));
|
|
315
|
+
|
|
316
|
+
return urlObj.toString();
|
|
317
|
+
};
|
|
131
318
|
}
|
package/src/llm.types.ts
CHANGED
|
@@ -1,9 +1,96 @@
|
|
|
1
|
+
export enum DataChannelTokenType {
|
|
2
|
+
Default = 'llm-default-session',
|
|
3
|
+
PracticeSession = 'llm-practice-session',
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
type DataChannelTokenKey = DataChannelTokenType | string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Latencies (in milliseconds) captured during register + websocket connect.
|
|
10
|
+
*/
|
|
11
|
+
type RegisterAndConnectTiming = {
|
|
12
|
+
clientLLMDatachannelResponseTime?: number;
|
|
13
|
+
clientLLMWebSocketConnectTime?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ILLMChannel — interface for a single LLM WebSocket connection.
|
|
18
|
+
* Created via `webex.internal.llm.createChannel()`.
|
|
19
|
+
*/
|
|
1
20
|
interface ILLMChannel {
|
|
2
|
-
|
|
21
|
+
/** Register with the server and connect the WebSocket. */
|
|
22
|
+
registerAndConnect: (
|
|
23
|
+
locusUrl: string,
|
|
24
|
+
datachannelUrl: string,
|
|
25
|
+
datachannelToken?: string
|
|
26
|
+
) => Promise<void>;
|
|
27
|
+
|
|
28
|
+
/** Returns true if the WebSocket is connected. */
|
|
3
29
|
isConnected: () => boolean;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
30
|
+
|
|
31
|
+
/** Returns true if a connection is currently in progress. */
|
|
32
|
+
isConnecting: () => boolean;
|
|
33
|
+
|
|
34
|
+
/** Get the underlying WebSocket. */
|
|
35
|
+
getSocket: () => any;
|
|
36
|
+
|
|
37
|
+
/** Get the binding ID for this connection. */
|
|
38
|
+
getBinding: () => string | undefined;
|
|
39
|
+
|
|
40
|
+
/** Get the Locus URL associated with this connection. */
|
|
41
|
+
getLocusUrl: () => string | undefined;
|
|
42
|
+
|
|
43
|
+
/** Get the datachannel URL for this connection. */
|
|
44
|
+
getDatachannelUrl: () => string | undefined;
|
|
45
|
+
|
|
46
|
+
/** Get the stored datachannel token. */
|
|
47
|
+
getDatachannelToken: () => string | undefined;
|
|
48
|
+
|
|
49
|
+
/** Store a datachannel token for this connection. */
|
|
50
|
+
setDatachannelToken: (datachannelToken: string) => void;
|
|
51
|
+
|
|
52
|
+
/** Clear the stored datachannel token. */
|
|
53
|
+
clearDatachannelToken: () => void;
|
|
54
|
+
|
|
55
|
+
/** Set the handler used to refresh the datachannel token. */
|
|
56
|
+
setRefreshHandler: (
|
|
57
|
+
handler: () => Promise<{
|
|
58
|
+
body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
|
|
59
|
+
}>
|
|
60
|
+
) => void;
|
|
61
|
+
|
|
62
|
+
/** Refresh the datachannel token using the injected handler. */
|
|
63
|
+
refreshDataChannelToken: () => Promise<{
|
|
64
|
+
body: {datachannelToken: string; datachannelTokenType: DataChannelTokenType};
|
|
65
|
+
} | null>;
|
|
66
|
+
|
|
67
|
+
/** Disconnect the WebSocket and clean up state. */
|
|
68
|
+
disconnect: (options?: {code: number; reason: string}) => Promise<void>;
|
|
69
|
+
|
|
70
|
+
/** Check if the datachannel token feature flag is enabled. */
|
|
71
|
+
isDataChannelTokenEnabled: () => Promise<boolean>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* ILLMPlugin — interface for the LLM plugin factory.
|
|
76
|
+
* Accessed via `webex.internal.llm`.
|
|
77
|
+
*/
|
|
78
|
+
interface ILLMPlugin {
|
|
79
|
+
/** Create a new LLM channel instance. */
|
|
80
|
+
createChannel: () => ILLMChannel;
|
|
81
|
+
|
|
82
|
+
/** Check if the datachannel token feature flag is enabled globally. */
|
|
83
|
+
isDataChannelTokenEnabled: () => Promise<boolean>;
|
|
84
|
+
|
|
85
|
+
/** Find a channel by matching a request URL to its datachannel URL. */
|
|
86
|
+
getChannelByDatachannelUrl: (url: string) => ILLMChannel | undefined;
|
|
87
|
+
|
|
88
|
+
/** Get all active channels. */
|
|
89
|
+
getAllChannels: () => Set<ILLMChannel>;
|
|
90
|
+
|
|
91
|
+
/** Disconnect all active channels. */
|
|
92
|
+
disconnectAllChannels: (options?: {code: number; reason: string}) => Promise<void>;
|
|
7
93
|
}
|
|
94
|
+
|
|
8
95
|
// eslint-disable-next-line import/prefer-default-export
|
|
9
|
-
export type {ILLMChannel};
|
|
96
|
+
export type {ILLMChannel, ILLMPlugin, DataChannelTokenKey, RegisterAndConnectTiming};
|