@webex/internal-plugin-llm 3.11.0-webex-services-ready.1 → 3.12.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 +83 -12
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/llm.js +249 -51
- package/dist/llm.js.map +1 -1
- package/dist/llm.types.js +6 -0
- package/dist/llm.types.js.map +1 -1
- package/package.json +6 -6
- package/src/constants.ts +12 -0
- package/src/index.ts +2 -0
- package/src/llm.ts +238 -33
- package/src/llm.types.ts +28 -5
- package/test/unit/spec/llm.js +351 -52
package/test/unit/spec/llm.js
CHANGED
|
@@ -19,47 +19,182 @@ describe('plugin-llm', () => {
|
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
webex.internal.feature = {
|
|
23
|
+
setFeature: sinon.stub().resolves({value: true}),
|
|
24
|
+
getFeature: sinon.stub().resolves(true),
|
|
25
|
+
};
|
|
26
|
+
|
|
22
27
|
llmService = webex.internal.llm;
|
|
23
|
-
llmService.
|
|
24
|
-
llmService.connected = true;
|
|
25
|
-
});
|
|
28
|
+
llmService.webSocketUrl = 'wss://example.com/socket';
|
|
26
29
|
llmService.disconnect = sinon.stub().resolves(true);
|
|
27
30
|
llmService.request = sinon.stub().resolves({
|
|
28
31
|
headers: {},
|
|
29
32
|
body: {
|
|
30
33
|
binding: 'binding',
|
|
31
|
-
webSocketUrl: '
|
|
34
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
32
35
|
},
|
|
33
36
|
});
|
|
37
|
+
const sockets = new Map();
|
|
38
|
+
|
|
39
|
+
llmService.connect = sinon.stub().callsFake((url, sessionId) => {
|
|
40
|
+
sockets.set(sessionId, {connected: true});
|
|
41
|
+
llmService.getSocket = sinon.stub().callsFake((sid) => sockets.get(sid));
|
|
42
|
+
});
|
|
43
|
+
llmService.connections.set('llm-default-session',{
|
|
44
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
45
|
+
})
|
|
34
46
|
});
|
|
35
47
|
|
|
48
|
+
afterEach(() => sinon.restore());
|
|
49
|
+
|
|
36
50
|
describe('#registerAndConnect', () => {
|
|
37
51
|
it('registers connection', async () => {
|
|
38
|
-
llmService.register = sinon.stub().
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
llmService.register = sinon.stub().callsFake(async () => {
|
|
53
|
+
llmService.binding = 'binding';
|
|
54
|
+
llmService.webSocketUrl = 'wss://example.com/socket';
|
|
55
|
+
return {
|
|
56
|
+
body: {
|
|
57
|
+
binding: 'binding',
|
|
58
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
43
61
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
|
|
63
|
+
assert.equal(llmService.isConnected('llm-default-session'), false);
|
|
64
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl,undefined);
|
|
65
|
+
assert.equal(llmService.isConnected('llm-default-session'), true);
|
|
47
66
|
});
|
|
48
67
|
|
|
49
|
-
it("doesn't
|
|
50
|
-
llmService.register = sinon.stub().
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
it("doesn't register connection for invalid input", async () => {
|
|
69
|
+
llmService.register = sinon.stub().callsFake(async () => {
|
|
70
|
+
llmService.binding = 'binding';
|
|
71
|
+
llmService.webSocketUrl = 'wss://example.com/socket';
|
|
72
|
+
return {
|
|
73
|
+
body: {
|
|
74
|
+
binding: 'binding',
|
|
75
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
55
78
|
});
|
|
79
|
+
|
|
56
80
|
await llmService.registerAndConnect();
|
|
57
81
|
assert.equal(llmService.isConnected(), false);
|
|
58
82
|
});
|
|
83
|
+
|
|
84
|
+
it('registers connection with token', async () => {
|
|
85
|
+
llmService.register = sinon.stub().callsFake(async () => {
|
|
86
|
+
llmService.binding = 'binding';
|
|
87
|
+
llmService.webSocketUrl = 'wss://example.com/socket';
|
|
88
|
+
return {
|
|
89
|
+
body: {
|
|
90
|
+
binding: 'binding',
|
|
91
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
assert.equal(llmService.isConnected(), false);
|
|
97
|
+
|
|
98
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl,'abc123');
|
|
99
|
+
|
|
100
|
+
sinon.assert.calledOnceWithExactly(
|
|
101
|
+
llmService.register,
|
|
102
|
+
datachannelUrl,
|
|
103
|
+
'abc123',
|
|
104
|
+
'llm-default-session'
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
assert.equal(llmService.isConnected(), true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('connects with subscriptionAwareSubchannels when token enabled', async () => {
|
|
111
|
+
llmService.isDataChannelTokenEnabled = sinon.stub().returns(true);
|
|
112
|
+
|
|
113
|
+
llmService.register = sinon.stub().callsFake(async () => {
|
|
114
|
+
llmService.binding = 'binding';
|
|
115
|
+
llmService.webSocketUrl = 'wss://example.com/socket';
|
|
116
|
+
return {
|
|
117
|
+
body: {
|
|
118
|
+
binding: 'binding',
|
|
119
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const buildSpy = sinon.spy(LLMService, 'buildUrlWithAwareSubchannels');
|
|
125
|
+
|
|
126
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl,'abc123');
|
|
127
|
+
|
|
128
|
+
sinon.assert.calledOnce(buildSpy);
|
|
129
|
+
sinon.assert.calledOnce(llmService.connect);
|
|
130
|
+
|
|
131
|
+
const calledUrl = llmService.connect.getCall(0).args[0];
|
|
132
|
+
assert.include(calledUrl, 'subscriptionAwareSubchannels=');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('connects without subscriptionAwareSubchannels when token disabled', async () => {
|
|
136
|
+
llmService.isDataChannelTokenEnabled = sinon.stub().returns(false);
|
|
137
|
+
|
|
138
|
+
llmService.register = sinon.stub().callsFake(async () => {
|
|
139
|
+
llmService.binding = 'binding';
|
|
140
|
+
llmService.webSocketUrl = 'wss://example.com/socket';
|
|
141
|
+
return {
|
|
142
|
+
body: {
|
|
143
|
+
binding: 'binding',
|
|
144
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const buildSpy = sinon.spy(LLMService, 'buildUrlWithAwareSubchannels');
|
|
150
|
+
|
|
151
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl);
|
|
152
|
+
|
|
153
|
+
sinon.assert.notCalled(buildSpy);
|
|
154
|
+
sinon.assert.calledOnce(llmService.connect);
|
|
155
|
+
|
|
156
|
+
const calledUrl = llmService.connect.getCall(0).args[0];
|
|
157
|
+
assert.equal(calledUrl, llmService.webSocketUrl);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('connects without subscriptionAwareSubchannels when token enabled BUT token missing', async () => {
|
|
161
|
+
llmService.isDataChannelTokenEnabled = sinon.stub().resolves(true);
|
|
162
|
+
|
|
163
|
+
const buildSpy = sinon.spy(LLMService, 'buildUrlWithAwareSubchannels');
|
|
164
|
+
|
|
165
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined);
|
|
166
|
+
|
|
167
|
+
sinon.assert.calledOnce(buildSpy);
|
|
168
|
+
sinon.assert.calledOnce(llmService.connect);
|
|
169
|
+
|
|
170
|
+
const calledUrl = llmService.connect.getCall(0).args[0];
|
|
171
|
+
assert.include(calledUrl, 'subscriptionAwareSubchannels=');
|
|
172
|
+
|
|
173
|
+
buildSpy.restore();
|
|
174
|
+
});
|
|
59
175
|
});
|
|
60
176
|
|
|
61
177
|
describe('#register', () => {
|
|
62
|
-
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
llmService.isDataChannelTokenEnabled = sinon.stub();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('registers connection with token header', async () => {
|
|
183
|
+
llmService.isDataChannelTokenEnabled.resolves(true);
|
|
184
|
+
await llmService.register(datachannelUrl, 'abc123');
|
|
185
|
+
|
|
186
|
+
sinon.assert.calledOnceWithExactly(
|
|
187
|
+
llmService.request,
|
|
188
|
+
sinon.match({
|
|
189
|
+
method: 'POST',
|
|
190
|
+
url: `${datachannelUrl}`,
|
|
191
|
+
body: {deviceUrl: webex.internal.device.url},
|
|
192
|
+
headers: {'Data-Channel-Auth-Token': 'abc123'},
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('registers connection without token header when none provided', async () => {
|
|
63
198
|
await llmService.register(datachannelUrl);
|
|
64
199
|
|
|
65
200
|
sinon.assert.calledOnceWithExactly(
|
|
@@ -68,21 +203,40 @@ describe('plugin-llm', () => {
|
|
|
68
203
|
method: 'POST',
|
|
69
204
|
url: `${datachannelUrl}`,
|
|
70
205
|
body: {deviceUrl: webex.internal.device.url},
|
|
206
|
+
headers: {},
|
|
71
207
|
})
|
|
72
208
|
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('registers connection without token header when toggle disabled', async () => {
|
|
212
|
+
llmService.isDataChannelTokenEnabled.resolves(false);
|
|
73
213
|
|
|
74
|
-
|
|
214
|
+
await llmService.register(datachannelUrl,'abc123');
|
|
215
|
+
sinon.assert.calledOnceWithExactly(
|
|
216
|
+
llmService.request,
|
|
217
|
+
sinon.match({
|
|
218
|
+
method: 'POST',
|
|
219
|
+
url: `${datachannelUrl}`,
|
|
220
|
+
body: {deviceUrl: webex.internal.device.url},
|
|
221
|
+
headers: {},
|
|
222
|
+
})
|
|
223
|
+
);
|
|
75
224
|
});
|
|
76
225
|
});
|
|
77
226
|
|
|
78
227
|
describe('#getLocusUrl', () => {
|
|
79
228
|
it('gets LocusUrl', async () => {
|
|
80
|
-
llmService.register = sinon.stub().
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
229
|
+
llmService.register = sinon.stub().callsFake(async () => {
|
|
230
|
+
llmService.binding = 'binding';
|
|
231
|
+
llmService.webSocketUrl = 'wss://example.com/socket';
|
|
232
|
+
return {
|
|
233
|
+
body: {
|
|
234
|
+
binding: 'binding',
|
|
235
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
236
|
+
},
|
|
237
|
+
};
|
|
85
238
|
});
|
|
239
|
+
|
|
86
240
|
await llmService.registerAndConnect(locusUrl, datachannelUrl);
|
|
87
241
|
assert.equal(llmService.getLocusUrl(), locusUrl);
|
|
88
242
|
});
|
|
@@ -90,11 +244,15 @@ describe('plugin-llm', () => {
|
|
|
90
244
|
|
|
91
245
|
describe('#getDatachannelUrl', () => {
|
|
92
246
|
it('gets dataChannel Url', async () => {
|
|
93
|
-
llmService.register = sinon.stub().
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
247
|
+
llmService.register = sinon.stub().callsFake(async () => {
|
|
248
|
+
llmService.binding = 'binding';
|
|
249
|
+
llmService.webSocketUrl = 'wss://example.com/socket';
|
|
250
|
+
return {
|
|
251
|
+
body: {
|
|
252
|
+
binding: 'binding',
|
|
253
|
+
webSocketUrl: 'wss://example.com/socket',
|
|
254
|
+
},
|
|
255
|
+
};
|
|
98
256
|
});
|
|
99
257
|
await llmService.registerAndConnect(locusUrl, datachannelUrl);
|
|
100
258
|
assert.equal(llmService.getDatachannelUrl(), datachannelUrl);
|
|
@@ -112,42 +270,183 @@ describe('plugin-llm', () => {
|
|
|
112
270
|
});
|
|
113
271
|
});
|
|
114
272
|
|
|
115
|
-
describe('disconnectLLM', () => {
|
|
273
|
+
describe('#disconnectLLM', () => {
|
|
116
274
|
let instance;
|
|
117
275
|
|
|
118
276
|
beforeEach(() => {
|
|
119
277
|
instance = {
|
|
120
278
|
disconnect: jest.fn(() => Promise.resolve()),
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.
|
|
279
|
+
connections: new Map([
|
|
280
|
+
['llm-default-session', { foo: 'bar' }],
|
|
281
|
+
]),
|
|
282
|
+
datachannelTokens: {
|
|
283
|
+
'llm-default-session': 'session-token',
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
disconnectLLM: function (options, sessionId = 'llm-default-session') {
|
|
287
|
+
return this.disconnect(options, sessionId).then(() => {
|
|
288
|
+
this.connections.delete(sessionId);
|
|
289
|
+
this.datachannelTokens[sessionId] = undefined;
|
|
131
290
|
});
|
|
132
|
-
}
|
|
291
|
+
},
|
|
133
292
|
};
|
|
134
293
|
});
|
|
135
294
|
|
|
136
|
-
it('
|
|
137
|
-
await instance.disconnectLLM({});
|
|
295
|
+
it('calls disconnect and clears session connection + token', async () => {
|
|
296
|
+
await instance.disconnectLLM({ code: 3000, reason: 'bye' });
|
|
297
|
+
|
|
298
|
+
expect(instance.disconnect).toHaveBeenCalledWith(
|
|
299
|
+
{ code: 3000, reason: 'bye' },
|
|
300
|
+
'llm-default-session'
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
expect(instance.connections.has('llm-default-session')).toBe(false);
|
|
304
|
+
|
|
305
|
+
expect(instance.datachannelTokens['llm-default-session']).toBeUndefined();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('propagates disconnect errors', async () => {
|
|
309
|
+
instance.disconnect.mockRejectedValue(new Error('disconnect failed'));
|
|
310
|
+
|
|
311
|
+
await expect(
|
|
312
|
+
instance.disconnectLLM({ code: 3000, reason: 'bye' })
|
|
313
|
+
).rejects.toThrow('disconnect failed');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('#setRefreshHandler', () => {
|
|
318
|
+
it('stores the provided handler', () => {
|
|
319
|
+
const handler = sinon.stub().resolves({ body: { datachannelToken: 'newToken' } });
|
|
320
|
+
llmService.setRefreshHandler(handler);
|
|
138
321
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
expect(instance.datachannelUrl).toBeUndefined();
|
|
142
|
-
expect(instance.binding).toBeUndefined();
|
|
143
|
-
expect(instance.webSocketUrl).toBeUndefined();
|
|
322
|
+
// @ts-ignore
|
|
323
|
+
assert.equal(llmService.refreshHandler, handler);
|
|
144
324
|
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('#isDataChannelTokenEnabled', () => {
|
|
328
|
+
it('works correctly', async () => {
|
|
329
|
+
webex.internal.feature.getFeature.returns(true);
|
|
145
330
|
|
|
146
|
-
|
|
147
|
-
instance.disconnect.mockRejectedValue(new Error('Disconnect failed'));
|
|
331
|
+
const result = await llmService.isDataChannelTokenEnabled();
|
|
148
332
|
|
|
149
|
-
|
|
333
|
+
sinon.assert.calledOnceWithExactly(
|
|
334
|
+
webex.internal.feature.getFeature,
|
|
335
|
+
'developer',
|
|
336
|
+
'data-channel-with-jwt-token'
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
assert.equal(result, true);
|
|
150
340
|
});
|
|
151
341
|
});
|
|
342
|
+
|
|
343
|
+
describe('#refreshDataChannelToken', () => {
|
|
344
|
+
it('returns null and logs warn if no handler is set', async () => {
|
|
345
|
+
const warnSpy = llmService.logger.warn
|
|
346
|
+
|
|
347
|
+
const result = await llmService.refreshDataChannelToken();
|
|
348
|
+
|
|
349
|
+
assert.equal(result, null);
|
|
350
|
+
|
|
351
|
+
sinon.assert.calledOnce(warnSpy);
|
|
352
|
+
sinon.assert.calledWithMatch(
|
|
353
|
+
warnSpy,
|
|
354
|
+
sinon.match('LLM refreshHandler is not set')
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('returns token when handler resolves', async () => {
|
|
359
|
+
const mockToken = { body: { datachannelToken: 'newToken', isPracticeSession: false } };
|
|
360
|
+
const handler = sinon.stub().resolves(mockToken);
|
|
361
|
+
|
|
362
|
+
llmService.setRefreshHandler(handler);
|
|
363
|
+
|
|
364
|
+
const token = await llmService.refreshDataChannelToken();
|
|
365
|
+
|
|
366
|
+
assert.equal(token, mockToken);
|
|
367
|
+
sinon.assert.calledOnce(handler);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('logs warn and returns null when handler rejects', async () => {
|
|
371
|
+
const handler = sinon.stub().rejects(new Error('throw error'));
|
|
372
|
+
llmService.setRefreshHandler(handler);
|
|
373
|
+
|
|
374
|
+
const warnSpy = llmService.logger.warn
|
|
375
|
+
|
|
376
|
+
const result = await llmService.refreshDataChannelToken();
|
|
377
|
+
|
|
378
|
+
assert.equal(result, null);
|
|
379
|
+
|
|
380
|
+
sinon.assert.calledOnce(warnSpy);
|
|
381
|
+
sinon.assert.calledWithMatch(
|
|
382
|
+
warnSpy,
|
|
383
|
+
sinon.match('DataChannel token refresh failed'),
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe('#getDatachannelToken / #setDatachannelToken', () => {
|
|
389
|
+
it('sets and gets datachannel token', () => {
|
|
390
|
+
llmService.setDatachannelToken('abc123','llm-default-session');
|
|
391
|
+
assert.equal(llmService.getDatachannelToken('llm-default-session'), 'abc123');
|
|
392
|
+
llmService.setDatachannelToken('123abc','llm-practice-session');
|
|
393
|
+
assert.equal(llmService.getDatachannelToken('llm-practice-session'), '123abc');
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe('multi-connection logic', () => {
|
|
398
|
+
const locusUrl2 = 'locusUrl2';
|
|
399
|
+
const datachannelUrl2 = 'datachannelUrl2';
|
|
400
|
+
|
|
401
|
+
it('tracks multiple sessions independently', async () => {
|
|
402
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined, 's1');
|
|
403
|
+
await llmService.registerAndConnect(locusUrl2, datachannelUrl2, undefined, 's2');
|
|
404
|
+
|
|
405
|
+
assert.equal(llmService.isConnected('s1'), true);
|
|
406
|
+
assert.equal(llmService.isConnected('s2'), true);
|
|
407
|
+
assert.equal(llmService.getLocusUrl('s1'), locusUrl);
|
|
408
|
+
assert.equal(llmService.getLocusUrl('s2'), locusUrl2);
|
|
409
|
+
assert.equal(llmService.getDatachannelUrl('s1'), datachannelUrl);
|
|
410
|
+
assert.equal(llmService.getDatachannelUrl('s2'), datachannelUrl2);
|
|
411
|
+
|
|
412
|
+
const all = llmService.getAllConnections();
|
|
413
|
+
assert.equal(all.has('s1'), true);
|
|
414
|
+
assert.equal(all.has('s2'), true);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('disconnectLLM clears only the targeted session', async () => {
|
|
418
|
+
llmService.disconnect = sinon.stub().resolves(true);
|
|
419
|
+
|
|
420
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined, 's1');
|
|
421
|
+
await llmService.registerAndConnect(locusUrl2, datachannelUrl2, undefined, 's2');
|
|
422
|
+
|
|
423
|
+
const options = {code: 1000, reason: 'test'};
|
|
424
|
+
await llmService.disconnectLLM(options, 's1');
|
|
425
|
+
|
|
426
|
+
sinon.assert.calledOnceWithExactly(llmService.disconnect, options, 's1');
|
|
427
|
+
|
|
428
|
+
const all = llmService.getAllConnections();
|
|
429
|
+
assert.equal(all.has('s1'), false);
|
|
430
|
+
assert.equal(all.has('s2'), true);
|
|
431
|
+
|
|
432
|
+
assert.equal(llmService.datachannelTokens['s1'], undefined);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('disconnectAllLLM clears all sessions', async () => {
|
|
436
|
+
llmService.disconnectAll = sinon.stub().resolves(true);
|
|
437
|
+
sinon.spy(llmService, 'resetDatachannelTokens');
|
|
438
|
+
|
|
439
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl, undefined, 's1');
|
|
440
|
+
await llmService.registerAndConnect(locusUrl2, datachannelUrl2, undefined, 's2');
|
|
441
|
+
|
|
442
|
+
await llmService.disconnectAllLLM({code: 1000, reason: 'all'});
|
|
443
|
+
|
|
444
|
+
sinon.assert.calledOnce(llmService.disconnectAll);
|
|
445
|
+
assert.equal(llmService.getAllConnections().size, 0);
|
|
446
|
+
|
|
447
|
+
sinon.assert.calledOnce(llmService.resetDatachannelTokens);
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
152
451
|
});
|
|
153
452
|
});
|