@webex/internal-plugin-llm 3.12.0-next.8 → 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/test/unit/spec/llm.js
CHANGED
|
@@ -19,182 +19,47 @@ 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
|
-
|
|
27
22
|
llmService = webex.internal.llm;
|
|
28
|
-
llmService.
|
|
23
|
+
llmService.connect = sinon.stub().callsFake(() => {
|
|
24
|
+
llmService.connected = true;
|
|
25
|
+
});
|
|
29
26
|
llmService.disconnect = sinon.stub().resolves(true);
|
|
30
27
|
llmService.request = sinon.stub().resolves({
|
|
31
28
|
headers: {},
|
|
32
29
|
body: {
|
|
33
30
|
binding: 'binding',
|
|
34
|
-
webSocketUrl: '
|
|
31
|
+
webSocketUrl: 'url',
|
|
35
32
|
},
|
|
36
33
|
});
|
|
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
|
-
})
|
|
46
34
|
});
|
|
47
35
|
|
|
48
|
-
afterEach(() => sinon.restore());
|
|
49
|
-
|
|
50
36
|
describe('#registerAndConnect', () => {
|
|
51
37
|
it('registers connection', async () => {
|
|
52
|
-
llmService.register = sinon.stub().
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
binding: 'binding',
|
|
58
|
-
webSocketUrl: 'wss://example.com/socket',
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
});
|
|
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);
|
|
66
|
-
});
|
|
67
|
-
|
|
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
|
-
};
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
await llmService.registerAndConnect();
|
|
81
|
-
assert.equal(llmService.isConnected(), false);
|
|
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
|
-
};
|
|
38
|
+
llmService.register = sinon.stub().resolves({
|
|
39
|
+
body: {
|
|
40
|
+
binding: 'binding',
|
|
41
|
+
webSocketUrl: 'url',
|
|
42
|
+
},
|
|
94
43
|
});
|
|
95
|
-
|
|
96
44
|
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
|
-
|
|
45
|
+
await llmService.registerAndConnect(locusUrl, datachannelUrl);
|
|
107
46
|
assert.equal(llmService.isConnected(), true);
|
|
108
47
|
});
|
|
109
48
|
|
|
110
|
-
it('
|
|
111
|
-
llmService.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
};
|
|
49
|
+
it("doesn't registers connection for invalid input", async () => {
|
|
50
|
+
llmService.register = sinon.stub().resolves({
|
|
51
|
+
body: {
|
|
52
|
+
binding: 'binding',
|
|
53
|
+
webSocketUrl: 'url',
|
|
54
|
+
},
|
|
147
55
|
});
|
|
148
|
-
|
|
149
|
-
|
|
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();
|
|
56
|
+
await llmService.registerAndConnect();
|
|
57
|
+
assert.equal(llmService.isConnected(), false);
|
|
174
58
|
});
|
|
175
59
|
});
|
|
176
60
|
|
|
177
61
|
describe('#register', () => {
|
|
178
|
-
|
|
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 () => {
|
|
62
|
+
it('registers connection', async () => {
|
|
198
63
|
await llmService.register(datachannelUrl);
|
|
199
64
|
|
|
200
65
|
sinon.assert.calledOnceWithExactly(
|
|
@@ -203,40 +68,21 @@ describe('plugin-llm', () => {
|
|
|
203
68
|
method: 'POST',
|
|
204
69
|
url: `${datachannelUrl}`,
|
|
205
70
|
body: {deviceUrl: webex.internal.device.url},
|
|
206
|
-
headers: {},
|
|
207
71
|
})
|
|
208
72
|
);
|
|
209
|
-
});
|
|
210
73
|
|
|
211
|
-
|
|
212
|
-
llmService.isDataChannelTokenEnabled.resolves(false);
|
|
213
|
-
|
|
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
|
-
);
|
|
74
|
+
assert.equal(llmService.getBinding(), 'binding');
|
|
224
75
|
});
|
|
225
76
|
});
|
|
226
77
|
|
|
227
78
|
describe('#getLocusUrl', () => {
|
|
228
79
|
it('gets LocusUrl', async () => {
|
|
229
|
-
llmService.register = sinon.stub().
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
binding: 'binding',
|
|
235
|
-
webSocketUrl: 'wss://example.com/socket',
|
|
236
|
-
},
|
|
237
|
-
};
|
|
80
|
+
llmService.register = sinon.stub().resolves({
|
|
81
|
+
body: {
|
|
82
|
+
binding: 'binding',
|
|
83
|
+
webSocketUrl: 'url',
|
|
84
|
+
},
|
|
238
85
|
});
|
|
239
|
-
|
|
240
86
|
await llmService.registerAndConnect(locusUrl, datachannelUrl);
|
|
241
87
|
assert.equal(llmService.getLocusUrl(), locusUrl);
|
|
242
88
|
});
|
|
@@ -244,15 +90,11 @@ describe('plugin-llm', () => {
|
|
|
244
90
|
|
|
245
91
|
describe('#getDatachannelUrl', () => {
|
|
246
92
|
it('gets dataChannel Url', async () => {
|
|
247
|
-
llmService.register = sinon.stub().
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
binding: 'binding',
|
|
253
|
-
webSocketUrl: 'wss://example.com/socket',
|
|
254
|
-
},
|
|
255
|
-
};
|
|
93
|
+
llmService.register = sinon.stub().resolves({
|
|
94
|
+
body: {
|
|
95
|
+
binding: 'binding',
|
|
96
|
+
webSocketUrl: 'url',
|
|
97
|
+
},
|
|
256
98
|
});
|
|
257
99
|
await llmService.registerAndConnect(locusUrl, datachannelUrl);
|
|
258
100
|
assert.equal(llmService.getDatachannelUrl(), datachannelUrl);
|
|
@@ -270,181 +112,42 @@ describe('plugin-llm', () => {
|
|
|
270
112
|
});
|
|
271
113
|
});
|
|
272
114
|
|
|
273
|
-
describe('
|
|
115
|
+
describe('disconnectLLM', () => {
|
|
274
116
|
let instance;
|
|
275
117
|
|
|
276
118
|
beforeEach(() => {
|
|
277
119
|
instance = {
|
|
278
120
|
disconnect: jest.fn(() => Promise.resolve()),
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.
|
|
289
|
-
this.datachannelTokens[sessionId] = undefined;
|
|
121
|
+
locusUrl: 'someUrl',
|
|
122
|
+
datachannelUrl: 'someUrl',
|
|
123
|
+
binding: {},
|
|
124
|
+
webSocketUrl: 'someUrl',
|
|
125
|
+
disconnectLLM: function (options) {
|
|
126
|
+
return this.disconnect(options).then(() => {
|
|
127
|
+
this.locusUrl = undefined;
|
|
128
|
+
this.datachannelUrl = undefined;
|
|
129
|
+
this.binding = undefined;
|
|
130
|
+
this.webSocketUrl = undefined;
|
|
290
131
|
});
|
|
291
|
-
}
|
|
132
|
+
}
|
|
292
133
|
};
|
|
293
134
|
});
|
|
294
135
|
|
|
295
|
-
it('
|
|
296
|
-
await instance.disconnectLLM({
|
|
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'));
|
|
136
|
+
it('should call disconnect and clear relevant properties', async () => {
|
|
137
|
+
await instance.disconnectLLM({});
|
|
310
138
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
).
|
|
139
|
+
expect(instance.disconnect).toHaveBeenCalledWith({});
|
|
140
|
+
expect(instance.locusUrl).toBeUndefined();
|
|
141
|
+
expect(instance.datachannelUrl).toBeUndefined();
|
|
142
|
+
expect(instance.binding).toBeUndefined();
|
|
143
|
+
expect(instance.webSocketUrl).toBeUndefined();
|
|
314
144
|
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
describe('#setRefreshHandler', () => {
|
|
318
|
-
it('stores the provided handler', () => {
|
|
319
|
-
const handler = sinon.stub().resolves({ body: { datachannelToken: 'newToken' } });
|
|
320
|
-
llmService.setRefreshHandler(handler);
|
|
321
|
-
|
|
322
|
-
// @ts-ignore
|
|
323
|
-
assert.equal(llmService.refreshHandler, handler);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
145
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
webex.internal.feature.getFeature.returns(true);
|
|
330
|
-
|
|
331
|
-
const result = await llmService.isDataChannelTokenEnabled();
|
|
332
|
-
|
|
333
|
-
sinon.assert.calledOnceWithExactly(
|
|
334
|
-
webex.internal.feature.getFeature,
|
|
335
|
-
'developer',
|
|
336
|
-
'data-channel-with-jwt-token'
|
|
337
|
-
);
|
|
146
|
+
it('should handle errors from disconnect gracefully', async () => {
|
|
147
|
+
instance.disconnect.mockRejectedValue(new Error('Disconnect failed'));
|
|
338
148
|
|
|
339
|
-
|
|
149
|
+
await expect(instance.disconnectLLM({})).rejects.toThrow('Disconnect failed');
|
|
340
150
|
});
|
|
341
151
|
});
|
|
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
|
-
});
|
|
448
|
-
|
|
449
152
|
});
|
|
450
153
|
});
|